fix(projects/khora): use calendar colors for events

Color day and week event cards with their calendar color and choose a contrasting foreground instead of showing only a colored marker.

Assisted-by: pi (gpt-5.6-sol)
This commit is contained in:
Gabriel Fontes
2026-08-15 15:56:37 -03:00
parent 55bd52ff49
commit 366c1ab5d7
3 changed files with 48 additions and 9 deletions
+14
View File
@@ -61,6 +61,20 @@ def display_color(value: str | None) -> str:
return _DEFAULT
def contrasting_foreground(value: str | None) -> str:
"""Choose black or white for readable text over a calendar color."""
color = display_color(value).lstrip("#")
if len(color) == 3:
color = "".join(channel * 2 for channel in color)
red, green, blue = (int(color[index : index + 2], 16) / 255 for index in (0, 2, 4))
def linear(channel: float) -> float:
return channel / 12.92 if channel <= 0.04045 else ((channel + 0.055) / 1.055) ** 2.4
luminance = 0.2126 * linear(red) + 0.7152 * linear(green) + 0.0722 * linear(blue)
return "#000000" if luminance > 0.179 else "#ffffff"
def _xterm_color(index: int) -> str:
if index < 16:
return _ANSI[index]
+28 -8
View File
@@ -8,7 +8,7 @@ gi.require_version("Adw", "1")
gi.require_version("Gtk", "4.0")
from gi.repository import Adw, Gio, GLib, Gtk, Pango
from .colors import display_color
from .colors import contrasting_foreground, display_color
from .khal_adapter import KhalRepository
from .model import Calendar, Event, event_slot_range, period_label, shifted_date, week_dates
@@ -40,6 +40,7 @@ class KhoraWindow(Adw.ApplicationWindow):
assert repository is not None
self._visible_calendars = {calendar.name for calendar in repository.calendars}
self._event_color_providers: dict[str, Gtk.CssProvider] = {}
self._view_content = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
self._time_indicator: Gtk.Widget | None = None
self._rendered_today = dt.date.today()
@@ -293,7 +294,7 @@ class KhoraWindow(Adw.ApplicationWindow):
xalign=0,
ellipsize=Pango.EllipsizeMode.END,
tooltip_text=event.summary,
css_classes=["all-day-event"],
css_classes=["all-day-event", self._event_color_class(event.color)],
)
box.append(event_label)
headers.append(box)
@@ -331,14 +332,10 @@ class KhoraWindow(Adw.ApplicationWindow):
lines=2,
ellipsize=Pango.EllipsizeMode.END,
tooltip_text=f"{event.time_label} · {event.summary}",
css_classes=["timed-event"],
css_classes=["timed-event", self._event_color_class(event.color)],
)
summary = GLib.markup_escape_text(event.summary)
color = display_color(event.color)
label.set_markup(
f'<span foreground="{color}">▌</span> '
f"<b>{summary}</b>\n<small>{event.time_label}</small>"
)
label.set_markup(f"<b>{summary}</b>\n<small>{event.time_label}</small>")
column.attach(label, 0, start, 1, end - start)
overlay = Gtk.Overlay(child=column)
@@ -383,6 +380,29 @@ class KhoraWindow(Adw.ApplicationWindow):
offset = round(elapsed_minutes / (24 * 60) * GRID_HEIGHT)
self._time_indicator.set_margin_top(max(0, offset - 4))
def _event_color_class(self, value: str | None) -> str:
color = display_color(value)
class_name = f"event-color-{color.lstrip('#').lower()}"
if class_name not in self._event_color_providers:
provider = Gtk.CssProvider()
foreground = contrasting_foreground(value)
provider.load_from_string(
f"""
.timed-event.{class_name},
.all-day-event.{class_name} {{
background-color: {color};
color: {foreground};
}}
"""
)
Gtk.StyleContext.add_provider_for_display(
self.get_display(),
provider,
Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION,
)
self._event_color_providers[class_name] = provider
return class_name
@staticmethod
def _event_row(event: Event) -> Adw.ActionRow:
details = f"{event.time_label} · {event.calendar}"
+6 -1
View File
@@ -1,4 +1,4 @@
from khora.colors import display_color
from khora.colors import contrasting_foreground, display_color
def test_preserves_html_colors() -> None:
@@ -16,3 +16,8 @@ def test_converts_xterm_color_cube() -> None:
def test_unknown_colors_use_accent_fallback() -> None:
assert display_color("auto") == "#3584e4"
assert display_color("chartreuse, probably") == "#3584e4"
def test_chooses_contrasting_event_text() -> None:
assert contrasting_foreground("dark blue") == "#ffffff"
assert contrasting_foreground("yellow") == "#000000"