utils/gatelock/gatelock/_widget_fills.py
Krzysztof kuhy Rudnicki 0cb575d15f
Some checks are pending
dependency freshness / dependencies on newest stable (push) Waiting to run
file length / file-length (push) Waiting to run
gatelock pre-commit / pre-commit (push) Waiting to run
gatelock tests / test (push) Waiting to run
md-naming / markdown naming convention (push) Waiting to run
palette drift / palette (push) Waiting to run
shared gate tests / gate unit tests (push) Waiting to run
gatelock 0.8.1: public recovery/detector handles; declarative types import from _config
leetcode-guard's study mode has to stop the re-assertion loop and the
output-change watcher before it releases the grab, and could only reach
them as LockWindow._recovery / _detector -- attributes 0.8.0 folded into
the arming bundle, which broke that documented private dependency.
They are public read-only properties now, next to `surfaces`.

_window no longer re-exports the LockConfig family; it was the one
thing keeping it over the 250-line cap, and every importer now takes
those names from gatelock._config (or the package) directly.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HHCbkJWXHjtTUaqvnHRAqA
2026-09-12 19:41:13 +02:00

49 lines
1.9 KiB
Python

# Copyright (c) 2026 Krzysztof Rudnicki
"""Colour derivation for the shared widgets: hover blends and variant fills.
Split from ``widgets.py`` (250-line cap). The seam is deliberate rather than
arbitrary: everything here is pure colour arithmetic over ``LockConfig``, with
no ``tkinter`` import and no widget construction, so it is testable without a
display. ``widgets.py`` keeps the Tk-facing half and re-exports these names,
because :mod:`gatelock.widgets` is the public surface the gate apps import.
"""
from __future__ import annotations
from typing import TYPE_CHECKING, Literal
if TYPE_CHECKING:
from gatelock._config import LockConfig
ButtonVariant = Literal["primary", "secondary", "danger"]
"""Emphasis role for :func:`gatelock.widgets.make_button`, not a colour."""
# Blend fraction toward white for a button's hover/active fill.
_HOVER_LIGHTEN = 0.12
_HEX_CHANNEL_OFFSETS = (1, 3, 5)
_RGB_MAX = 255
def _lighten(hex_color: str, amount: float = _HOVER_LIGHTEN) -> str:
"""Blend ``hex_color`` toward white by ``amount``, for a hover state."""
channels = (
int(hex_color[offset : offset + 2], 16) for offset in _HEX_CHANNEL_OFFSETS
)
return "#" + "".join(
f"{round(channel + (_RGB_MAX - channel) * amount):02x}" for channel in channels
)
def _button_fills(config: LockConfig) -> dict[ButtonVariant, tuple[str, str]]:
"""Return the fill/text pair for each variant, read from the palette.
All three pairs use ``on_fill`` (never ``fg``) for text drawn on a filled
surface, per tokens.md -- which is the whole point of keying off a
variant instead of letting each call site invent its own hex pair.
"""
return {
"primary": (config.palette.accent, config.palette.on_fill),
"secondary": (config.palette.field_bg, config.palette.fg),
"danger": (config.palette.danger, config.palette.on_fill),
}