utils/gatelock/gatelock/_widget_fills.py
Krzysztof kuhy Rudnicki 68ec316b28
Some checks failed
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
freedays pre-commit / pre-commit (push) Has been cancelled
freedays tests / test (push) Has been cancelled
gatelock 0.8.0: LockConfig as behaviour + GrabPolicy + theme records; pylint at 10
LockConfig was one 35-field record: the lock's behaviour, the grab
policy, nine colours, a type scale and a spacing scale, flat. It is
now the seven behavioural knobs plus four nested records --
GrabPolicy(kind, disable_vt, retry_ms, log_every, preempt_weaker_holder),
LockPalette, LockTypography and LockSpacing -- each overridden as a
unit. Breaking for every constructor that passed grab=/disable_vt=/
grab_retry_ms=/preempt_weaker_holder= or a colour, and for every read
of config.<colour>; hence the minor bump. screen-locker, leetcode-guard
and diet-guard follow in their own commits.

The two fail-open guards in the recovery loop carry per-line
suppressions: catching everything is the property they exist for, and
the check stays live for the next careless except. That was the last
finding, so the gate now says 10.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HHCbkJWXHjtTUaqvnHRAqA
2026-09-12 19:36:31 +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._window 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),
}