[MouseFox logo]
The MouseFox Project
Join The Community Discord Server
[Discord logo]
Edit on GitHub

kvex.colors

Home of XColor, SubTheme, and Theme.

The most common use of this module is get_color. It is able to conveniently retrieve any color from any palette in PALETTES. The PALETTES dictionary can be modified to add or replace palettes.

  1"""Home of `XColor`, `SubTheme`, and `Theme`.
  2
  3The most common use of this module is `get_color`. It is able to conveniently retrieve
  4any color from any palette in `PALETTES`. The `PALETTES` dictionary can be modified to
  5add or replace palettes.
  6"""
  7
  8from typing import NamedTuple
  9import colorsys
 10import json
 11import random
 12from .assets import ASSETS_DIR
 13
 14
 15_THEME_DATA_FILE = ASSETS_DIR / "defaultthemes.json"
 16
 17
 18class XColor:
 19    """A class to represent a color."""
 20
 21    def __init__(
 22        self,
 23        r: float = 1,
 24        g: float = 1,
 25        b: float = 1,
 26        a: float = 1,
 27    ):
 28        """Initialize the class.
 29
 30        Args:
 31            r: Red component.
 32            g: Green component.
 33            b: Blue component.
 34            a: Alpha component.
 35        """
 36        self.__rgba = r, g, b, a
 37        self.__hsv = colorsys.rgb_to_hsv(r, g, b)
 38
 39    @classmethod
 40    def from_hex(cls, hex_str: str, /) -> "XColor":
 41        """`XColor` from hex format."""
 42        rgb = tuple(
 43            int(hex_str.removeprefix("#")[i:i+2], 16) / 256
 44            for i in (0, 2, 4)
 45        )
 46        return cls(*rgb)
 47
 48    @classmethod
 49    def from_hsv(cls, h: float, s: float = 1, v: float = 1, a: float = 1) -> "XColor":
 50        """`XColor` from hsv values."""
 51        return cls(*colorsys.hsv_to_rgb(h, s, v), a)
 52
 53    @classmethod
 54    def from_name(cls, name: str, /, *, a: float = 1) -> "XColor":
 55        """`XColor` from color name in `RAINBOW`."""
 56        return cls(*RAINBOW[name], a)
 57
 58    def offset_hue(self, offset: float = 0.5, /) -> "XColor":
 59        """`XColor` that is offset in hue (between 0 and 1) from self."""
 60        h = (self.h + offset) % 1
 61        return self.from_hsv(h, self.s, self.v, self.a)
 62
 63    def modified_hue(self, hue: float, /) -> "XColor":
 64        """Identical `XColor` with different hue."""
 65        return self.from_hsv(hue, self.s, self.v, self.a)
 66
 67    def modified_value(self, value: float, /) -> "XColor":
 68        """Identical `XColor` with different value."""
 69        return self.from_hsv(self.h, self.s, value, self.a)
 70
 71    def modified_saturation(self, saturation: float, /) -> "XColor":
 72        """Identical `XColor` with different saturation."""
 73        return self.from_hsv(self.h, saturation, self.v, self.a)
 74
 75    def modified_alpha(self, alpha: float, /) -> "XColor":
 76        """Identical `XColor` with different alpha."""
 77        return self.from_hsv(self.h, self.s, self.v, alpha)
 78
 79    @classmethod
 80    def random(cls) -> "XColor":
 81        """Random `XColor`."""
 82        return cls.from_hsv(random.random(), 1, 1)
 83
 84    @classmethod
 85    def grey(cls, v: float = 0.5, /) -> "XColor":
 86        """Grey `XColor`."""
 87        return cls.from_hsv(0, 0, v=v)
 88
 89    @classmethod
 90    def white(cls) -> "XColor":
 91        """White `XColor`."""
 92        return cls.from_hsv(0, 0, v=1)
 93
 94    @classmethod
 95    def black(cls) -> "XColor":
 96        """Black `XColor`."""
 97        return cls.from_hsv(0, 0, v=0)
 98
 99    @property
100    def r(self) -> float:
101        """The red component."""
102        return self.__rgba[0]
103
104    @property
105    def g(self) -> float:
106        """The green component."""
107        return self.__rgba[1]
108
109    @property
110    def b(self) -> float:
111        """The blue component."""
112        return self.__rgba[2]
113
114    @property
115    def a(self) -> float:
116        """Alpha."""
117        return self.__rgba[3]
118
119    @property
120    def h(self):
121        """Hue."""
122        return self.__hsv[0]
123
124    @property
125    def s(self):
126        """Saturation."""
127        return self.__hsv[1]
128
129    @property
130    def v(self):
131        """Value (from hsv)."""
132        return self.__hsv[2]
133
134    @property
135    def hsv(self):
136        """Hue, saturation, and value."""
137        return self.__hsv
138
139    @property
140    def rgb(self) -> tuple[float, float, float]:
141        """The red, green, and blue components."""
142        return self.__rgba[:3]
143
144    @property
145    def rgba(self) -> tuple[float, float, float, float]:
146        """The red, green, blue, and alpha components."""
147        return self.__rgba
148
149    @property
150    def hex(self) -> str:
151        """Hex representation."""
152        return "#" + "".join(hex(round(value * 256))[2:].zfill(2) for value in self.rgb)
153
154    def markup(self, s: str) -> str:
155        """Wrap a string in color markup."""
156        return f"[color={self.hex}]{s}[/color]"
157
158    def __repr__(self):
159        """Object repr."""
160        return f"<{self.__class__.__qualname__} {self.hex}>"
161
162
163RAINBOW = {
164    "black": (0.0, 0.0, 0.0),
165    "grey": (0.5, 0.5, 0.5),
166    "white": (1.0, 1.0, 1.0),
167    "red": (0.6, 0.0, 0.1),
168    "pink": (0.9, 0.3, 0.4),
169    "yellow": (0.8, 0.7, 0.1),
170    "orange": (0.7, 0.4, 0.0),
171    "lime": (0.1, 0.4, 0.0),
172    "green": (0.4, 0.7, 0.1),
173    "cyan": (0.1, 0.7, 0.7),
174    "blue": (0.1, 0.4, 0.9),
175    "navy": (0.0, 0.1, 0.5),
176    "violet": (0.7, 0.1, 0.9),
177    "purple": (0.4, 0.0, 0.7),
178    "magenta": (0.7, 0.0, 0.5),
179}
180"""Rainbow colors in RGB."""
181
182
183class SubTheme(NamedTuple):
184    """Tuple of XColor for a theme."""
185
186    bg: XColor
187    """Background color."""
188    fg: XColor
189    """Primary foreground (text) color."""
190    fg2: XColor
191    """Secondary foreground (text) color."""
192    accent1: XColor
193    """Primary accent color."""
194    accent2: XColor
195    """Secondary accent color."""
196
197    @classmethod
198    def from_hexes(cls, *hexes) -> "SubTheme":
199        """`SubTheme` from list of colors in (string) hex format."""
200        return cls(*(XColor.from_hex(h) for h in hexes))
201
202
203SUBTHEME_COLORS = SubTheme._fields
204"""Color names in a `SubTheme`."""
205
206
207class Theme(NamedTuple):
208    """Tuple of `SubTheme`s."""
209
210    primary: SubTheme
211    """Primary subtheme."""
212    secondary: SubTheme
213    """Secondary subtheme."""
214    accent: SubTheme
215    """Accented subtheme."""
216    palette: list[XColor]
217    """All colors in the palette of this theme."""
218
219
220SUBTHEME_NAMES = ("primary", "secondary", "accent")
221"""`SubTheme` names in a `Theme`."""
222
223
224def _convert_hexes(*hexes):
225    return tuple(XColor.from_hex(h) for h in hexes)
226
227
228def _import_theme_data() -> dict:
229    themes = dict()
230    with open(_THEME_DATA_FILE) as f:
231        raw_data = json.load(f)
232    for theme_name, theme in raw_data.items():
233        theme_data = dict()
234        theme_data["palette"] = tuple(XColor.from_hex(h) for h in theme["palette"])
235        for subtheme_name in SUBTHEME_NAMES:
236            theme_data[subtheme_name] = SubTheme(**{
237                color_name: XColor.from_hex(color)
238                for color_name, color in theme[subtheme_name].items()
239            })
240        themes[theme_name] = Theme(**theme_data)
241    return themes
242
243
244THEMES = _import_theme_data()
245"""Themes data."""
246
247THEME_NAMES = tuple(THEMES.keys())
248"""`Theme` names."""
249
250
251__all__ = (
252    "XColor",
253    "Theme",
254    "SubTheme",
255    "SUBTHEME_NAMES",
256    "SUBTHEME_COLORS",
257    "THEME_NAMES",
258    "RAINBOW",
259)
class XColor:
 19class XColor:
 20    """A class to represent a color."""
 21
 22    def __init__(
 23        self,
 24        r: float = 1,
 25        g: float = 1,
 26        b: float = 1,
 27        a: float = 1,
 28    ):
 29        """Initialize the class.
 30
 31        Args:
 32            r: Red component.
 33            g: Green component.
 34            b: Blue component.
 35            a: Alpha component.
 36        """
 37        self.__rgba = r, g, b, a
 38        self.__hsv = colorsys.rgb_to_hsv(r, g, b)
 39
 40    @classmethod
 41    def from_hex(cls, hex_str: str, /) -> "XColor":
 42        """`XColor` from hex format."""
 43        rgb = tuple(
 44            int(hex_str.removeprefix("#")[i:i+2], 16) / 256
 45            for i in (0, 2, 4)
 46        )
 47        return cls(*rgb)
 48
 49    @classmethod
 50    def from_hsv(cls, h: float, s: float = 1, v: float = 1, a: float = 1) -> "XColor":
 51        """`XColor` from hsv values."""
 52        return cls(*colorsys.hsv_to_rgb(h, s, v), a)
 53
 54    @classmethod
 55    def from_name(cls, name: str, /, *, a: float = 1) -> "XColor":
 56        """`XColor` from color name in `RAINBOW`."""
 57        return cls(*RAINBOW[name], a)
 58
 59    def offset_hue(self, offset: float = 0.5, /) -> "XColor":
 60        """`XColor` that is offset in hue (between 0 and 1) from self."""
 61        h = (self.h + offset) % 1
 62        return self.from_hsv(h, self.s, self.v, self.a)
 63
 64    def modified_hue(self, hue: float, /) -> "XColor":
 65        """Identical `XColor` with different hue."""
 66        return self.from_hsv(hue, self.s, self.v, self.a)
 67
 68    def modified_value(self, value: float, /) -> "XColor":
 69        """Identical `XColor` with different value."""
 70        return self.from_hsv(self.h, self.s, value, self.a)
 71
 72    def modified_saturation(self, saturation: float, /) -> "XColor":
 73        """Identical `XColor` with different saturation."""
 74        return self.from_hsv(self.h, saturation, self.v, self.a)
 75
 76    def modified_alpha(self, alpha: float, /) -> "XColor":
 77        """Identical `XColor` with different alpha."""
 78        return self.from_hsv(self.h, self.s, self.v, alpha)
 79
 80    @classmethod
 81    def random(cls) -> "XColor":
 82        """Random `XColor`."""
 83        return cls.from_hsv(random.random(), 1, 1)
 84
 85    @classmethod
 86    def grey(cls, v: float = 0.5, /) -> "XColor":
 87        """Grey `XColor`."""
 88        return cls.from_hsv(0, 0, v=v)
 89
 90    @classmethod
 91    def white(cls) -> "XColor":
 92        """White `XColor`."""
 93        return cls.from_hsv(0, 0, v=1)
 94
 95    @classmethod
 96    def black(cls) -> "XColor":
 97        """Black `XColor`."""
 98        return cls.from_hsv(0, 0, v=0)
 99
100    @property
101    def r(self) -> float:
102        """The red component."""
103        return self.__rgba[0]
104
105    @property
106    def g(self) -> float:
107        """The green component."""
108        return self.__rgba[1]
109
110    @property
111    def b(self) -> float:
112        """The blue component."""
113        return self.__rgba[2]
114
115    @property
116    def a(self) -> float:
117        """Alpha."""
118        return self.__rgba[3]
119
120    @property
121    def h(self):
122        """Hue."""
123        return self.__hsv[0]
124
125    @property
126    def s(self):
127        """Saturation."""
128        return self.__hsv[1]
129
130    @property
131    def v(self):
132        """Value (from hsv)."""
133        return self.__hsv[2]
134
135    @property
136    def hsv(self):
137        """Hue, saturation, and value."""
138        return self.__hsv
139
140    @property
141    def rgb(self) -> tuple[float, float, float]:
142        """The red, green, and blue components."""
143        return self.__rgba[:3]
144
145    @property
146    def rgba(self) -> tuple[float, float, float, float]:
147        """The red, green, blue, and alpha components."""
148        return self.__rgba
149
150    @property
151    def hex(self) -> str:
152        """Hex representation."""
153        return "#" + "".join(hex(round(value * 256))[2:].zfill(2) for value in self.rgb)
154
155    def markup(self, s: str) -> str:
156        """Wrap a string in color markup."""
157        return f"[color={self.hex}]{s}[/color]"
158
159    def __repr__(self):
160        """Object repr."""
161        return f"<{self.__class__.__qualname__} {self.hex}>"

A class to represent a color.

XColor(r: float = 1, g: float = 1, b: float = 1, a: float = 1)
22    def __init__(
23        self,
24        r: float = 1,
25        g: float = 1,
26        b: float = 1,
27        a: float = 1,
28    ):
29        """Initialize the class.
30
31        Args:
32            r: Red component.
33            g: Green component.
34            b: Blue component.
35            a: Alpha component.
36        """
37        self.__rgba = r, g, b, a
38        self.__hsv = colorsys.rgb_to_hsv(r, g, b)

Initialize the class.

Arguments:
  • r: Red component.
  • g: Green component.
  • b: Blue component.
  • a: Alpha component.
@classmethod
def from_hex(cls, hex_str: str, /) -> kvex.colors.XColor:
40    @classmethod
41    def from_hex(cls, hex_str: str, /) -> "XColor":
42        """`XColor` from hex format."""
43        rgb = tuple(
44            int(hex_str.removeprefix("#")[i:i+2], 16) / 256
45            for i in (0, 2, 4)
46        )
47        return cls(*rgb)

XColor from hex format.

@classmethod
def from_hsv( cls, h: float, s: float = 1, v: float = 1, a: float = 1) -> kvex.colors.XColor:
49    @classmethod
50    def from_hsv(cls, h: float, s: float = 1, v: float = 1, a: float = 1) -> "XColor":
51        """`XColor` from hsv values."""
52        return cls(*colorsys.hsv_to_rgb(h, s, v), a)

XColor from hsv values.

@classmethod
def from_name(cls, name: str, /, *, a: float = 1) -> kvex.colors.XColor:
54    @classmethod
55    def from_name(cls, name: str, /, *, a: float = 1) -> "XColor":
56        """`XColor` from color name in `RAINBOW`."""
57        return cls(*RAINBOW[name], a)

XColor from color name in RAINBOW.

def offset_hue(self, offset: float = 0.5, /) -> kvex.colors.XColor:
59    def offset_hue(self, offset: float = 0.5, /) -> "XColor":
60        """`XColor` that is offset in hue (between 0 and 1) from self."""
61        h = (self.h + offset) % 1
62        return self.from_hsv(h, self.s, self.v, self.a)

XColor that is offset in hue (between 0 and 1) from self.

def modified_hue(self, hue: float, /) -> kvex.colors.XColor:
64    def modified_hue(self, hue: float, /) -> "XColor":
65        """Identical `XColor` with different hue."""
66        return self.from_hsv(hue, self.s, self.v, self.a)

Identical XColor with different hue.

def modified_value(self, value: float, /) -> kvex.colors.XColor:
68    def modified_value(self, value: float, /) -> "XColor":
69        """Identical `XColor` with different value."""
70        return self.from_hsv(self.h, self.s, value, self.a)

Identical XColor with different value.

def modified_saturation(self, saturation: float, /) -> kvex.colors.XColor:
72    def modified_saturation(self, saturation: float, /) -> "XColor":
73        """Identical `XColor` with different saturation."""
74        return self.from_hsv(self.h, saturation, self.v, self.a)

Identical XColor with different saturation.

def modified_alpha(self, alpha: float, /) -> kvex.colors.XColor:
76    def modified_alpha(self, alpha: float, /) -> "XColor":
77        """Identical `XColor` with different alpha."""
78        return self.from_hsv(self.h, self.s, self.v, alpha)

Identical XColor with different alpha.

@classmethod
def random(cls) -> kvex.colors.XColor:
80    @classmethod
81    def random(cls) -> "XColor":
82        """Random `XColor`."""
83        return cls.from_hsv(random.random(), 1, 1)

Random XColor.

@classmethod
def grey(cls, v: float = 0.5, /) -> kvex.colors.XColor:
85    @classmethod
86    def grey(cls, v: float = 0.5, /) -> "XColor":
87        """Grey `XColor`."""
88        return cls.from_hsv(0, 0, v=v)

Grey XColor.

@classmethod
def white(cls) -> kvex.colors.XColor:
90    @classmethod
91    def white(cls) -> "XColor":
92        """White `XColor`."""
93        return cls.from_hsv(0, 0, v=1)

White XColor.

@classmethod
def black(cls) -> kvex.colors.XColor:
95    @classmethod
96    def black(cls) -> "XColor":
97        """Black `XColor`."""
98        return cls.from_hsv(0, 0, v=0)

Black XColor.

r: float

The red component.

g: float

The green component.

b: float

The blue component.

a: float

Alpha.

h

Hue.

s

Saturation.

v

Value (from hsv).

hsv

Hue, saturation, and value.

rgb: tuple[float, float, float]

The red, green, and blue components.

rgba: tuple[float, float, float, float]

The red, green, blue, and alpha components.

hex: str

Hex representation.

def markup(self, s: str) -> str:
155    def markup(self, s: str) -> str:
156        """Wrap a string in color markup."""
157        return f"[color={self.hex}]{s}[/color]"

Wrap a string in color markup.

class Theme(typing.NamedTuple):
208class Theme(NamedTuple):
209    """Tuple of `SubTheme`s."""
210
211    primary: SubTheme
212    """Primary subtheme."""
213    secondary: SubTheme
214    """Secondary subtheme."""
215    accent: SubTheme
216    """Accented subtheme."""
217    palette: list[XColor]
218    """All colors in the palette of this theme."""

Tuple of SubThemes.

Theme( primary: kvex.colors.SubTheme, secondary: kvex.colors.SubTheme, accent: kvex.colors.SubTheme, palette: list[kvex.colors.XColor])

Create new instance of Theme(primary, secondary, accent, palette)

Primary subtheme.

Secondary subtheme.

Accented subtheme.

palette: list[kvex.colors.XColor]

All colors in the palette of this theme.

Inherited Members
builtins.tuple
index
count
class SubTheme(typing.NamedTuple):
184class SubTheme(NamedTuple):
185    """Tuple of XColor for a theme."""
186
187    bg: XColor
188    """Background color."""
189    fg: XColor
190    """Primary foreground (text) color."""
191    fg2: XColor
192    """Secondary foreground (text) color."""
193    accent1: XColor
194    """Primary accent color."""
195    accent2: XColor
196    """Secondary accent color."""
197
198    @classmethod
199    def from_hexes(cls, *hexes) -> "SubTheme":
200        """`SubTheme` from list of colors in (string) hex format."""
201        return cls(*(XColor.from_hex(h) for h in hexes))

Tuple of XColor for a theme.

Create new instance of SubTheme(bg, fg, fg2, accent1, accent2)

Background color.

Primary foreground (text) color.

Secondary foreground (text) color.

Primary accent color.

Secondary accent color.

@classmethod
def from_hexes(cls, *hexes) -> kvex.colors.SubTheme:
198    @classmethod
199    def from_hexes(cls, *hexes) -> "SubTheme":
200        """`SubTheme` from list of colors in (string) hex format."""
201        return cls(*(XColor.from_hex(h) for h in hexes))

SubTheme from list of colors in (string) hex format.

Inherited Members
builtins.tuple
index
count
SUBTHEME_NAMES = ('primary', 'secondary', 'accent')

SubTheme names in a Theme.

SUBTHEME_COLORS = ('bg', 'fg', 'fg2', 'accent1', 'accent2')

Color names in a SubTheme.

THEME_NAMES = ('mousefox', 'midnight', 'darkpop', 'market', 'muted', 'ocean', 'subtle', 'evening', 'travel')

Theme names.

RAINBOW = {'black': (0.0, 0.0, 0.0), 'grey': (0.5, 0.5, 0.5), 'white': (1.0, 1.0, 1.0), 'red': (0.6, 0.0, 0.1), 'pink': (0.9, 0.3, 0.4), 'yellow': (0.8, 0.7, 0.1), 'orange': (0.7, 0.4, 0.0), 'lime': (0.1, 0.4, 0.0), 'green': (0.4, 0.7, 0.1), 'cyan': (0.1, 0.7, 0.7), 'blue': (0.1, 0.4, 0.9), 'navy': (0.0, 0.1, 0.5), 'violet': (0.7, 0.1, 0.9), 'purple': (0.4, 0.0, 0.7), 'magenta': (0.7, 0.0, 0.5)}

Rainbow colors in RGB.