mirror of
https://github.com/kuhyx/screen-locker.git
synced 2026-11-11 18:53:55 +01:00
Every locker screen, the status window and the credit toast now show glyphs (screen_locker/_glyphs.py) and numbers, coloured by state. Sentences that carried a reason (verifier message, sync diagnosis, sick refusal, form rejection) go to the log at warning instead. New pre-commit hook text-free runs scripts/verify_text_free.py. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
175 lines
6.6 KiB
Python
175 lines
6.6 KiB
Python
"""The "choose lockers…" link: sickdays' KeepPanel, inside the lock window.
|
|
|
|
The one-tap "I'm sick" button (``_ui_flows_sick``) keeps nothing. This link,
|
|
next to it, opens sickdays' embeddable :class:`~sickdays.gui.KeepPanel` so
|
|
today can be marked sick with some lockers still on. It is built in-process
|
|
on the lock's own surfaces: a separate window would get no input under the
|
|
lock's X grab.
|
|
|
|
The panel covers each surface on a full-size backdrop instead of replacing
|
|
the screen under it, so closing it reveals exactly the screen it was opened
|
|
from -- a running penalty countdown or phone check is neither restarted nor
|
|
skipped. The panel does the write and starts its own background sync; what
|
|
the lock does afterwards depends on the pool, not on the entry:
|
|
|
|
* sick and the workout lock stands down -> the one-tap path's after-effects
|
|
(sick floor, decision, close), without a second mark or push;
|
|
* sick but the workout lock kept -> sick floor (20:00 applies on any sick
|
|
day), the lock stays up with a short note;
|
|
* cleared, or an unchanged Save -> the panel closes, nothing else.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import logging
|
|
import tkinter as tk
|
|
from typing import TYPE_CHECKING
|
|
|
|
from gatelock import bind_activate
|
|
import sickdays
|
|
from sickdays.gui import KeepPanel
|
|
|
|
from screen_locker import _glyphs as g
|
|
from screen_locker._sick import stands_down_workout
|
|
from screen_locker._surface_group import FrameGroup
|
|
|
|
if TYPE_CHECKING:
|
|
from collections.abc import Callable
|
|
|
|
from gatelock import LockConfig
|
|
|
|
_logger = logging.getLogger(__name__)
|
|
|
|
# textgate: was "choose lockers…" (sick, then pick) and "Workout lock kept —
|
|
# finish the workout to unlock." (sick, but the barbell lock stays).
|
|
LINK_TEXT = f"{g.SICK}▾"
|
|
KEPT_NOTE = f"{g.SICK} {g.LIFT} {g.LOCK}"
|
|
|
|
|
|
def build_sick_panel(
|
|
parent: tk.Misc,
|
|
colors: LockConfig,
|
|
*,
|
|
on_saved: Callable[[sickdays.SickDay | None], None],
|
|
on_cancel: Callable[[], None],
|
|
paths: sickdays.Paths | None = None,
|
|
) -> tk.Frame:
|
|
"""Centre today's KeepPanel in ``parent`` and return the frame holding it.
|
|
|
|
Separate from the mixin so ``scripts/verify_screen_fits.py`` can measure
|
|
it on a throwaway pool (``paths``) instead of the live one.
|
|
"""
|
|
outer = tk.Frame(parent, bg=colors.palette.bg)
|
|
outer.place(relx=0.5, rely=0.5, anchor="center")
|
|
KeepPanel(
|
|
outer, sickdays.today(), on_saved=on_saved, on_cancel=on_cancel, paths=paths
|
|
).pack()
|
|
return outer
|
|
|
|
|
|
class SickPanelFlowMixin:
|
|
"""The keep-aware sick-day panel, layered over the current lock screen."""
|
|
|
|
_sick_overlay: FrameGroup | None = None
|
|
|
|
def _choose_lockers_link(self, frame: FrameGroup) -> None:
|
|
"""Pack the small secondary link that opens the panel into ``frame``."""
|
|
palette = self._colors.palette
|
|
links = frame.child_widgets(
|
|
tk.Button,
|
|
text=LINK_TEXT,
|
|
font=self._colors.font("label"),
|
|
bg=palette.bg,
|
|
fg=palette.muted,
|
|
activebackground=palette.field_bg,
|
|
activeforeground=palette.fg,
|
|
command=self.open_sick_panel,
|
|
cursor="hand2" if self.demo_mode else "",
|
|
relief="flat",
|
|
borderwidth=0,
|
|
padx=self._colors.space("sm"),
|
|
pady=self._colors.space("xs"),
|
|
**self._colors.focus_kwargs(),
|
|
)
|
|
for link in links:
|
|
bind_activate(link)
|
|
links.pack(side="left", padx=self._colors.space("sm"))
|
|
|
|
def open_sick_panel(self) -> None:
|
|
"""Lay today's KeepPanel over every monitor's surface."""
|
|
overlay = self._new_sick_overlay()
|
|
panels = [
|
|
build_sick_panel(
|
|
backdrop,
|
|
self._colors,
|
|
on_saved=self._on_sick_panel_saved,
|
|
on_cancel=self._close_sick_panel,
|
|
)
|
|
for backdrop in overlay
|
|
]
|
|
# Into the panel on the primary monitor: left on the link, Tab and
|
|
# Enter would reach the hidden buttons of the screen underneath. Tk
|
|
# skips unmapped widgets when traversing, so lay the panel out first.
|
|
for panel in panels[:1]:
|
|
panel.update_idletasks()
|
|
(panel.tk_focusNext() or panel).focus_set()
|
|
|
|
def _new_sick_overlay(self) -> FrameGroup:
|
|
"""Replace any open overlay with a fresh full-surface backdrop group."""
|
|
self._close_sick_panel()
|
|
backdrops: list[tk.Frame] = []
|
|
for surface in self.container.surfaces:
|
|
backdrop = tk.Frame(surface.winfo_toplevel(), bg=self._colors.palette.bg)
|
|
backdrop.place(relx=0, rely=0, relwidth=1, relheight=1)
|
|
backdrop.lift()
|
|
backdrops.append(backdrop)
|
|
self._sick_overlay = FrameGroup(backdrops)
|
|
return self._sick_overlay
|
|
|
|
def _close_sick_panel(self) -> None:
|
|
"""Remove the overlay; the lock screen under it was never touched."""
|
|
if self._sick_overlay is not None:
|
|
self._sick_overlay.destroy()
|
|
self._sick_overlay = None
|
|
|
|
def _on_sick_panel_saved(self, entry: sickdays.SickDay | None) -> None:
|
|
"""React to the panel's write; the pool is already up to date."""
|
|
self._close_sick_panel()
|
|
if entry is None or not entry.is_sick:
|
|
_logger.warning(
|
|
"Sick panel saved without a sick day (%s); the lock stays as it was",
|
|
"unchanged" if entry is None else "cleared",
|
|
)
|
|
return
|
|
self._raise_sick_floor()
|
|
kept = ", ".join(sorted(entry.keep)) or "nothing"
|
|
if stands_down_workout():
|
|
self._unlock_sick_day(
|
|
f"Marked today sick from the lock screen's keep panel (kept: {kept})."
|
|
)
|
|
return
|
|
_logger.warning("Sick day keeps the workout lock (kept: %s); staying up", kept)
|
|
self._show_kept_note()
|
|
|
|
def _show_kept_note(self) -> None:
|
|
"""Say the lock stays because the workout locker was kept."""
|
|
box = self._new_sick_overlay().child_frame(bg=self._colors.palette.bg)
|
|
for frame in box:
|
|
frame.place(relx=0.5, rely=0.5, anchor="center")
|
|
box.child_widgets(
|
|
tk.Label,
|
|
text=KEPT_NOTE,
|
|
font=self._colors.font("title", bold=True),
|
|
fg=self._colors.palette.warning,
|
|
bg=self._colors.palette.bg,
|
|
).pack(pady=self._colors.space("md"))
|
|
back = self._button(
|
|
box,
|
|
g.BACK,
|
|
bg=self._colors.palette.accent,
|
|
command=self._close_sick_panel,
|
|
width=4,
|
|
)
|
|
back.pack(pady=self._colors.space("sm"))
|
|
back.first.focus_set()
|