feat: more template variables (#147)

* feat(templates): add rgb/hsl channel variables to colour templates

* fix(templates): coerce colour channel values to str in dynamic templates
This commit is contained in:
dim-ghub
2026-08-09 12:20:42 -05:00
committed by GitHub
parent b059ce53ff
commit 13cd311d19
3 changed files with 55 additions and 2 deletions
+4 -1
View File
@@ -171,12 +171,15 @@ Custom user templates can be defined in `~/.config/caelestia/templates/`.
`{{ <color>.<format> }}`
- `<color>` is a theme color role derived from the Material You color system (e.g. `primary`, `secondary`, `background`)
- `<format>` is the output format: `hex` or `rgb`
- `<format>` is the output format: `hex`, `rgb`, `hsl`, or a single channel (`red`, `green`, `blue`, `hue`, `saturation`, `lightness`)
#### Examples
- `{{ primary.hex }}` outputs `3f4ba2`
- `{{ primary.rgb }}` outputs `rgb(193, 132, 207)`
- `{{ primary.red }}` outputs `193`
- `{{ primary.hsl }}` outputs `hsl(268,41%,66%)`
- `{{ primary.hue }}` outputs `268`
Output files are written to `~/.local/state/caelestia/theme/`. You can symlink them to your desired locations.
+50
View File
@@ -1,11 +1,13 @@
class Colour:
_rgb_vals: tuple[int, ...]
_hex_vals: tuple[str, ...]
_hsl_vals: tuple[int, int, int]
def __init__(self, hex: str):
hex = hex.ljust(8, "f")
self._hex_vals = tuple(hex[i : i + 2] for i in range(0, 7, 2))
self._rgb_vals = tuple(int(h, 16) for h in self._hex_vals)
self._hsl_vals = self._to_hsl(self._rgb_vals)
@property
def hex(self) -> str:
@@ -23,6 +25,54 @@ class Colour:
def rgbalpha(self) -> str:
return f"rgba({','.join(map(str, self._rgb_vals))})"
@property
def red(self) -> int:
return self._rgb_vals[0]
@property
def green(self) -> int:
return self._rgb_vals[1]
@property
def blue(self) -> int:
return self._rgb_vals[2]
@property
def hsl(self) -> str:
return f"hsl({self._hsl_vals[0]},{self._hsl_vals[1]}%,{self._hsl_vals[2]}%)"
@property
def hue(self) -> int:
return self._hsl_vals[0]
@property
def saturation(self) -> int:
return self._hsl_vals[1]
@property
def lightness(self) -> int:
return self._hsl_vals[2]
@staticmethod
def _to_hsl(rgb: tuple[int, ...]) -> tuple[int, int, int]:
r, g, b = (v / 255 for v in rgb[:3])
cmax, cmin = max(r, g, b), min(r, g, b)
delta = cmax - cmin
lightness = (cmax + cmin) / 2
saturation = 0 if delta == 0 else delta / (1 - abs(2 * lightness - 1))
if delta == 0:
hue = 0
elif cmax == r:
hue = 60 * (((g - b) / delta) % 6)
elif cmax == g:
hue = 60 * (((b - r) / delta) + 2)
else:
hue = 60 * (((r - g) / delta) + 4)
return round(hue), round(saturation * 100), round(lightness * 100)
def get_dynamic_colours(colours: dict[str, str]) -> dict[str, Colour]:
return {name: Colour(code) for name, code in colours.items()}
+1 -1
View File
@@ -60,7 +60,7 @@ def gen_replace_dynamic(colours: dict[str, str], template: Path, mode: str) -> s
col, form = data
if col not in colours_dyn or not hasattr(colours_dyn[col], form):
return match.group()
return getattr(colours_dyn[col], form)
return str(getattr(colours_dyn[col], form))
# match atomic {{ . }} pairs
dotField = r"\{\{((?:(?!\{\{|\}\}).)*)\}\}"