Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 42 additions & 10 deletions src/smorg/integrations/gcal/chips.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,11 @@

from __future__ import annotations

import math

from rich.text import Text

from smorg.integrations.gcal.palette import BREATH_SECONDS
from smorg.integrations.gcal.source import Event, Response
from smorg.shell.format import truncating
from smorg.shell.terminal_palette import relative_luminance
Expand All @@ -19,6 +22,7 @@
Response.NONE: "",
}
_LIGHT_TEXT_BELOW = 0.4
_PULSE_LIFT = 0.35


def _rgb(color: str) -> tuple[int, int, int]:
Expand All @@ -28,20 +32,48 @@ def _rgb(color: str) -> tuple[int, int, int]:
return int(digits[0:2], 16), int(digits[2:4], 16), int(digits[4:6], 16)


def chip_style(color: str) -> str:
"""Contrast text on the calendar colour: black on light chips, white on dark ones."""
def _text_color(color: str) -> str:
rgb = _rgb(color)
if relative_luminance(rgb) < _LIGHT_TEXT_BELOW:
return f"white on {color}"
return f"black on {color}"
return "white"
return "black"


def chip_style(color: str) -> str:
"""Contrast text on the calendar colour: black on light chips, white on dark ones."""
text_color = _text_color(color)
return f"{text_color} on {color}"


def pulsed_fill(color: str, elapsed: float) -> str:
"""The calendar colour lifted toward white by up to `_PULSE_LIFT`, on the breathing wave."""
phase = (elapsed / BREATH_SECONDS) % 1.0
brightness = 0.5 + 0.5 * math.cos(2 * math.pi * phase)
lift = _PULSE_LIFT * brightness
red, green, blue = _rgb(color)
r = round(red + (255 - red) * lift)
g = round(green + (255 - green) * lift)
b = round(blue + (255 - blue) * lift)
return f"#{r:02x}{g:02x}{b:02x}"


def format_chip(text: str, width: int, color: str, selected: bool, past: bool) -> Text:
style = chip_style(color)
if selected:
style = f"bold {style}"
if past:
style = f"dim {style}"
def format_chip(
text: str,
width: int,
color: str,
selected: bool,
past: bool,
fill: str | None = None,
) -> Text:
if selected and fill is not None:
text_color = _text_color(color)
style = f"bold {text_color} on {fill}"
else:
style = chip_style(color)
if selected:
style = f"bold {style}"
if past:
style = f"dim {style}"
chip = Text(f" {text}", style=style)
chip.truncate(max(1, width), overflow="ellipsis", pad=True)
return chip
Expand Down
5 changes: 3 additions & 2 deletions src/smorg/integrations/gcal/header.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,11 @@


def format_day_header(
days: tuple[date, ...], today: date, focused: date, column_width: int
days: tuple[date, ...], today: date, focused: date | None, column_width: int
) -> tuple[Text, Text]:
"""Two centered rows, one initial and one number per column; today in Google blue with the
icon's folded corner at its shoulder, the focused day underlined when it is a different day."""
icon's folded corner at its shoulder, `focused` underlined when it is a different day (None
when the view marks the focused day another way)."""
initials = Text()
numbers = Text()
for day in days:
Expand Down
6 changes: 5 additions & 1 deletion src/smorg/integrations/gcal/palette.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
"""Google's colours, spent sparingly: blue for today, red for whatever wants attention."""
"""Google's colours, spent sparingly, and the breathing rhythm the dots and the selected chip
share."""

from __future__ import annotations

Expand All @@ -8,3 +9,6 @@
YELLOW = "#fbbc04"
GREEN = "#34a853"
BRAND_DOTS = (BRAND_BLUE, RED, YELLOW, GREEN)

BREATH_FPS = 10
BREATH_SECONDS = 1.6
11 changes: 10 additions & 1 deletion src/smorg/integrations/gcal/panel.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
from smorg.integrations.gcal.views import CalendarView
from smorg.integrations.gcal.views.day import CalendarDay
from smorg.integrations.gcal.views.menu import CalendarMenu
from smorg.integrations.gcal.views.week import CalendarWeek
from smorg.shell.animation import FrameClock
from smorg.shell.view_host import HostedView, ViewHostPanel

Expand All @@ -43,9 +44,14 @@ def __init__(self) -> None:
def compose(self) -> ComposeResult:
yield CalendarMenu(self)
yield CalendarDay(self)
yield CalendarWeek(self)

def view_classes(self) -> dict[CalendarView, type[HostedView]]:
return {CalendarView.MENU: CalendarMenu, CalendarView.DAY: CalendarDay}
return {
CalendarView.MENU: CalendarMenu,
CalendarView.DAY: CalendarDay,
CalendarView.WEEK: CalendarWeek,
}

def on_mount(self) -> None:
super().on_mount()
Expand Down Expand Up @@ -160,4 +166,7 @@ def selected_item(self) -> Item | None:
if self.active_view is CalendarView.DAY and self.is_mounted:
day_view = self.query_one(CalendarDay)
return day_view.selected_item()
if self.active_view is CalendarView.WEEK and self.is_mounted:
week_view = self.query_one(CalendarWeek)
return week_view.selected_item()
return None
Loading
Loading