feat(ui): add brand accent hook to GTK colour scheme

BRAND_ACCENT single source with shade()-derived states; accent CSS
covers suggested-action buttons and the listening chip, installed at
USER priority from main().

Refs #23
This commit is contained in:
soubarnak
2026-11-06 00:05:29 +05:30
parent 0a4d637f5f
commit 203b51ab78
3 changed files with 206 additions and 1 deletions
+2 -1
View File
@@ -418,9 +418,10 @@ def main():
if not check_display_available():
sys.exit(1)
from .utils.gtk_color_scheme import apply_os_color_scheme
from .utils.gtk_color_scheme import apply_brand_accent, apply_os_color_scheme
apply_os_color_scheme()
apply_brand_accent()
if not check_appindicator_support():
logger.warning("No StatusNotifierWatcher found on D-Bus session bus.")
+74
View File
@@ -11,6 +11,12 @@ Portal ``Read`` is tried first. If it is unavailable or reports no preference,
``gsettings`` is used as a fallback. An unknown ``gsettings`` value or read
failure leaves GTK defaults unchanged. ``GTK_THEME`` already set (AppImage or
user override) skips the helper entirely.
The Carbon-mapped house-teal accent hook lives here too: ``BRAND_ACCENT`` is
the single source of truth (a later palette change is a one-line edit) and
``apply_brand_accent()`` paints exactly recording state plus the primary
action with it. Every other surface keeps OS theme variables, and overlay
signal colours plus destructive reds are untouched.
"""
import logging
@@ -32,6 +38,74 @@ _COLOR_SCHEME_KEY = "color-scheme"
_PORTAL_TIMEOUT_MS = 1000
_GSETTINGS_TIMEOUT_S = 1
#: House teal from the Carbon-mapped palette (ticket #23). The single source
#: of truth for the GTK accent hook: recording state and the primary action.
#: White text on this opaque teal passes WCAG AA on light and dark themes.
BRAND_ACCENT = "#0f6b57"
#: Text colour used on top of the accent. Stays white across themes because
#: the accent chip itself is opaque.
_BRAND_ACCENT_ON = "#ffffff"
def brand_accent_css() -> str:
"""Return the accent-hook CSS, derived from ``BRAND_ACCENT`` only.
Covers exactly ``button.suggested-action`` (the primary action) and
``.recognition-listening`` (the recording-state readout, restyled as an
opaque teal chip so contrast holds on light and dark theme backgrounds).
Hover/active states derive via ``shade()`` so no second literal creeps
in; no ``@theme_`` variable is referenced, so switching the system theme
needs no app restart.
"""
return f"""\
/* Voculinux house-teal accent hook (#23): recording state + primary action.
All other surfaces keep OS theme variables; overlay signal colours and
the reserved reds are untouched. */
button.suggested-action {{
background-color: {BRAND_ACCENT};
color: {_BRAND_ACCENT_ON};
border-color: shade({BRAND_ACCENT}, 0.75);
}}
button.suggested-action:hover {{
background-color: shade({BRAND_ACCENT}, 0.85);
color: {_BRAND_ACCENT_ON};
}}
button.suggested-action:active {{
background-color: shade({BRAND_ACCENT}, 0.75);
color: {_BRAND_ACCENT_ON};
}}
.recognition-listening {{
background-color: {BRAND_ACCENT};
color: {_BRAND_ACCENT_ON};
border-radius: 6px;
padding: 2px 8px;
}}
"""
def apply_brand_accent() -> None:
"""Install the accent hook above per-dialog CSS without touching it.
Uses ``STYLE_PROVIDER_PRIORITY_USER`` so the hook wins over the
``APPLICATION``-priority providers each dialog installs, while every
other rule in those stylesheets keeps following theme variables.
Fails soft: without a display the app keeps theme defaults.
"""
try:
from gi.repository import Gdk, Gtk
css_provider = Gtk.CssProvider()
css_provider.load_from_data(brand_accent_css().encode())
Gtk.StyleContext.add_provider_for_screen(
Gdk.Screen.get_default(),
css_provider,
Gtk.STYLE_PROVIDER_PRIORITY_USER,
)
logger.debug("Applied brand accent %s", BRAND_ACCENT)
except Exception as exc:
logger.debug("Could not apply brand accent: %s", exc)
def read_os_color_scheme() -> Optional[ColorScheme]:
"""Return ``prefer-dark``, ``prefer-light``, or ``None`` if unknown."""