screen-locker/screen_locker/_ui_widgets.py
Krzysztof kuhy Rudnicki d9119a6582
Some checks are pending
pre-commit / pre-commit (push) Waiting to run
Tests / test (3.10) (push) Waiting to run
Tests / test (3.11) (push) Waiting to run
Tests / test (3.12) (push) Waiting to run
Add manual (unverified) workout logging with evidence form
Adds a rate-limited manual-workout entry point (StatusWindow + locked
retry screen) for activities like table tennis that ADB/RunnerUp can't
verify, gated by a 2/7d + 5/30d budget and a detailed evidence form
rather than free text. Full credit toward the weekly minimum, debt
clearing, and commitment prompt, matching phone/RunnerUp verification.

Also stops tracking personal runtime state (workout_log.json,
sick_history.json, extra_benefits_state.json, scheduled_skips.json,
shutdown_base.json, early_bird_pending.json) in git — these are
regenerated locally and never belong in a public/private repo's
history.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AbPcvAvt1JhiRzEyPDE5dA
2026-07-05 20:08:52 +02:00

127 lines
3.3 KiB
Python

"""UI widget helper methods mixin for the screen locker."""
from __future__ import annotations
import contextlib
import tkinter as tk
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from collections.abc import Callable
def disable_paste(widget: tk.Widget) -> None:
"""Disable paste in a Tk Entry/Text widget.
Friction-only: a determined user can still bypass via xdotool, but the
point is removing the trivial Ctrl+V shortcut so the user must
actually type their input.
"""
for sequence in ("<<Paste>>", "<Control-v>", "<Control-V>", "<Button-2>"):
with contextlib.suppress(tk.TclError, AttributeError):
widget.bind(sequence, lambda _e: "break")
class UIWidgetsMixin:
"""Mixin providing low-level widget creation helpers."""
def clear_container(self) -> None:
"""Remove all widgets from the main container."""
for widget in self.container.winfo_children():
widget.destroy()
def _label(
self,
text: str,
*,
font_size: int = 36,
color: str = "white",
pady: int = 20,
) -> tk.Label:
"""Create and pack a bold label in the container."""
label = tk.Label(
self.container,
text=text,
font=("Arial", font_size, "bold"),
fg=color,
bg="#1a1a1a",
)
label.pack(pady=pady)
return label
def _text(
self,
text: str,
*,
font_size: int = 18,
color: str = "white",
pady: int = 10,
) -> tk.Label:
"""Create and pack a non-bold text label in the container."""
label = tk.Label(
self.container,
text=text,
font=("Arial", font_size),
fg=color,
bg="#1a1a1a",
)
label.pack(pady=pady)
return label
def _button(
self,
parent: tk.Widget,
text: str,
*,
bg: str,
command: Callable[[], None],
width: int = 10,
) -> tk.Button:
"""Create a styled button (caller must pack)."""
return tk.Button(
parent,
text=text,
font=("Arial", 24, "bold"),
bg=bg,
fg="white",
width=width,
command=command,
cursor="hand2" if self.demo_mode else "",
)
def _button_row(self) -> tk.Frame:
"""Create and pack a horizontal button container."""
frame = tk.Frame(self.container, bg="#1a1a1a")
frame.pack(pady=20)
return frame
def _add_label_entry(
self,
parent: tk.Widget,
*,
label: str,
variable: tk.StringVar,
) -> None:
"""Add a label + single-line entry pair, with paste disabled."""
row = tk.Frame(parent, bg="#1a1a1a")
row.pack(pady=5, fill="x")
tk.Label(
row,
text=label,
font=("Arial", 14),
fg="white",
bg="#1a1a1a",
anchor="w",
).pack(side="top", anchor="w")
entry = tk.Entry(
row,
textvariable=variable,
width=50,
font=("Arial", 14),
bg="#2a2a2a",
fg="white",
insertbackground="white",
)
entry.pack(side="top", anchor="w", pady=2)
disable_paste(entry)