view keyboard_colors/color_profile.py @ 3:eb2aa09653bd default tip

beginnings of CPU colors, daemon
author Brad Greco <brad@bgreco.net>
date Mon, 29 Mar 2021 20:27:09 -0400
parents 091a1f59a79c
children
line wrap: on
line source

import colorsys
import gi
import importlib
import json
import os
import pathlib
import re
import sys
import types
import uuid
from types import SimpleNamespace
gi.require_version("Gtk", "3.0")
from gi.repository import Gtk  # noqa: E402
from gi.repository import GLib  # noqa: E402


class ColorProfileBase:

    type_name = '(no name)'

    def __init__(self):
        self.name = ''
        self.id = str(uuid.uuid4())
        self.transition_time = 1000
        self.frequency_time = 1000

    def color_scale_value(self, value):
        if value in self.color_scale:
            return self.color_scale[value]

        thresholds = self.color_scale.keys()
        thresholds_below = (i for i in thresholds if i < value)
        thresholds_above = (i for i in thresholds if i > value)
        threshold_below = max(thresholds_below, default=min(thresholds))
        threshold_above = min(thresholds_above, default=max(thresholds))

        percent = value / (threshold_above - threshold_below)
        return Color.get_step_color(
            self.color_scale[threshold_below],
            self.color_scale[threshold_above],
            percent
        )

    def build_settings_ui(self):
        glade_file = sys.modules[self.__module__].__file__.replace('.py', '.glade')
        builder = Gtk.Builder()
        builder.add_from_file(glade_file)
        return builder.get_object('edit_profile_container')

    def serialize(self):
        properties = self.__dict__
        properties['module'] = self.__class__.__module__
        return properties

    @staticmethod
    def unserialize(data):
        profile_module = importlib.import_module(data['module'])
        profile_class = getattr(profile_module, 'ColorProfile')
        profile = profile_class()
        profile.__dict__.update(data)
        return profile


class ProfileManager:

    def __init__(self):
        profile_list = self.load_profiles()
        self.profiles = {p.id: p for p in profile_list}

    def get_types(self):
        types = []
        profile_directory = os.path.dirname(os.path.realpath(__file__))
        (_, dirnames, _) = next(os.walk(profile_directory))
        for dirname in dirnames:
            module_file = os.path.join(profile_directory, dirname, dirname + '.py')
            if os.path.isfile(module_file):
                module_name = 'keyboard_colors.' + dirname + '.' + dirname
                profile_module = importlib.import_module(module_name)
                profile_class = getattr(profile_module, 'ColorProfile')
                types.append(profile_class)
        return types

    def config_path(self):
        dir = GLib.get_user_config_dir()
        if not os.path.isdir(dir):
            os.mkdir(dir)
        return os.path.join(dir, 'keyboard-color-profiles.conf')

    def load_profiles(self):
        if os.path.isfile(self.config_path()):
            data = pathlib.Path(self.config_path()).read_text('utf-8')
            try:
                return json.loads(data, object_hook=lambda d: ColorProfileBase.unserialize(d))
            except json.decoder.JSONDecodeError:
                return []
        else:
            return []

    def save_profiles(self):
        data = json.dumps(self.profiles, default=lambda o: o.serialize())
        pathlib.Path(self.config_path()).write_text(data, 'utf-8')

    def get_profiles(self):
        return self.profiles

    def get_profile(self, profile_id):
        return self.get_profiles()[profile_id]


class Color:

    def __init__(self, r, g, b):
        self.r = r
        self.g = g
        self.b = b

    @staticmethod
    def from_hsv(h, s, v):
        return Color(*colorsys.hsv_to_rgb(h, s, v))

    def to_hsv(self):
        return colorsys.rgb_to_hsv(self.r, self.g, self.b)

    @staticmethod
    def from_hex(hex):
        hex = hex.strip().strip('#').lower()
        if not re.match('[0-9a-f]{6}', hex):
            raise ValueError('Invalid hex color string: ' + hex)

        r = int(hex[0:2], 16) / 255
        g = int(hex[2:4], 16) / 255
        b = int(hex[4:6], 16) / 255

        return Color(r, g, b)

    def to_hex(self):
        return format(int(self.r * 255), '02x') \
             + format(int(self.g * 255), '02x') \
             + format(int(self.b * 255), '02x')

    @staticmethod
    def get_steps(start_color, end_color, step_count):
        # (start_h, start_s, start_v) = start_color.to_hsv()
        # (end_h, end_s, end_v) = end_color.to_hsv()

        # # Find the shortest distance between the two hues.
        # if abs(start_h - end_h) < 0.5:
        #     h_step = (end_h - start_h) / step_count
        # else:
        #     h_step = (1 - abs(end_h - start_h)) / step_count
        #     if (start_h < end_h):
        #         h_step = h_step * -1

        # steps = []
        # for i in range(0, step_count):
        #     h = (start_h + h_step * i) % 1
        #     s = start_s + (end_s - start_s) / step_count * i
        #     v = start_v + (end_v - start_v) / step_count * i
        #     steps.append(Color.from_hsv(h, s, v))

        steps = []
        for i in range(0, step_count):
            percent = i / step_count
            steps.append(Color.get_step_color(start_color, end_color, percent))

        return steps

    @staticmethod
    def get_step_color(start_color, end_color, percent):
        (start_h, start_s, start_v) = start_color.to_hsv()
        (end_h, end_s, end_v) = end_color.to_hsv()

        # Find the shortest distance between the two hues.
        if abs(start_h - end_h) < 0.5:
            h_diff = end_h - start_h
        else:
            h_diff = 1 - abs(end_h - start_h)
            if (start_h < end_h):
                h_diff = h_diff * -1

        h = (start_h + h_diff * percent) % 1
        s = start_s + (end_s - start_s) * percent
        v = start_v + (end_v - start_v) * percent

        return Color.from_hsv(h, s, v)

    def __repr__(self):
        return 'Color #' + self.to_hex()