Skip to content

Commit 4f9dae0

Browse files
committed
feat: add opacity to colours
1 parent 3520cd1 commit 4f9dae0

File tree

2 files changed

+54
-9
lines changed

2 files changed

+54
-9
lines changed

catppuccin/colour.py

Lines changed: 26 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -15,29 +15,48 @@ class Colour:
1515
red: int
1616
green: int
1717
blue: int
18+
alpha: int = 255
1819

1920
@property
2021
def rgb(self) -> Tuple[int, int, int]:
2122
"""Get the colour as a 3-tuple of red, green, and blue."""
2223
return self.red, self.green, self.blue
2324

25+
@property
26+
def rgba(self) -> Tuple[int, int, int, int]:
27+
"""Get the colour as a 4-tuple of red, green, blue, and alpha."""
28+
return self.red, self.green, self.blue, self.alpha
29+
2430
@property
2531
def hex(self) -> str:
2632
"""Get the colour as a lowercase hex string."""
33+
if self.alpha < 255:
34+
return f"{self.red:02x}{self.green:02x}{self.blue:02x}{self.alpha:02x}"
2735
return f"{self.red:02x}{self.green:02x}{self.blue:02x}"
2836

2937
def __eq__(self, other: Any) -> bool:
3038
if not isinstance(other, Colour):
3139
raise ValueError("Cannot check equality with non-colour types.")
40+
3241
return self.hex == other.hex
3342

3443
@classmethod
3544
def from_hex(cls, hex_string: str) -> Colour:
36-
"""Create a color from hex string."""
37-
if len(hex_string) != 6:
38-
raise ValueError("Hex string must be 6 characters long.")
39-
match = re.match(r"([\da-fA-F]{2})" * 3, hex_string)
45+
"""Create a colour from hex string."""
46+
if len(hex_string) not in (6, 8):
47+
raise ValueError("Hex string must be 6 or 8 characters long.")
48+
49+
num_groups = 3 if len(hex_string) == 6 else 4
50+
match = re.match(r"([\da-fA-F]{2})" * num_groups, hex_string)
4051
if match is None:
41-
raise ValueError("Hex string have an invalid format.")
42-
hex_r, hex_g, hex_b = match.groups()
43-
return Colour(*(int(col, 16) for col in (hex_r, hex_g, hex_b)))
52+
raise ValueError("Hex string has an invalid format.")
53+
54+
components = (int(col, 16) for col in match.groups())
55+
return Colour(*components)
56+
57+
def opacity(self, opacity: float) -> Colour:
58+
"""Return a new colour with the given opacity."""
59+
if not 0 <= opacity <= 1:
60+
raise ValueError("Opacity must be between 0 and 1.")
61+
62+
return Colour(self.red, self.green, self.blue, int(opacity * 255))

tests/test_colour.py

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,30 @@ def test_colour_to_rgb():
77
assert Colour(12, 123, 234).rgb == (12, 123, 234)
88

99

10-
def test_colour_to_hex():
10+
def test_colour_to_rgba():
11+
assert Colour(12, 123, 234, 35).rgba == (12, 123, 234, 35)
12+
13+
14+
def test_colour_to_rgba_default():
15+
assert Colour(12, 123, 234).rgba == (12, 123, 234, 255)
16+
17+
18+
def test_rgb_colour_to_hex():
1119
assert Colour(0x12, 0xEB, 0x77).hex == "12eb77"
1220

1321

14-
def test_hex_to_color():
22+
def test_rgba_colour_to_hex():
23+
assert Colour(0x12, 0xEB, 0x77, 0x35).hex == "12eb7735"
24+
25+
26+
def test_hex_to_colour():
1527
assert Colour.from_hex("12eb77") == Colour(0x12, 0xEB, 0x77)
1628

1729

30+
def test_hex_to_colour_with_alpha():
31+
assert Colour.from_hex("12eb7735") == Colour(0x12, 0xEB, 0x77, 0x35)
32+
33+
1834
def test_invalid_hex():
1935
for invalid_value in ("1234567", "12345", "Z00000", "ABCDEG", "0F7CBJ"):
2036
with pytest.raises(ValueError):
@@ -27,3 +43,13 @@ def test_equality():
2743

2844
with pytest.raises(ValueError):
2945
assert Colour(0x12, 0xEB, 0x77) == 42
46+
47+
48+
def test_opacity():
49+
colour = Colour(0x12, 0xEB, 0x77).opacity(0.5)
50+
assert colour == Colour(0x12, 0xEB, 0x77, 0x7F)
51+
52+
53+
def test_opacity_invalid():
54+
with pytest.raises(ValueError):
55+
Colour(0x12, 0xEB, 0x77).opacity(1.5)

0 commit comments

Comments
 (0)