Files
espresso_frame/server/app/widgets/__init__.py
T
tfaour fcf3aec4c0
Build and push server image / test (push) Successful in 36s
Firmware build check / build-check (push) Successful in 2m4s
Build and push server image / build-and-push (push) Successful in 3m12s
Build and push server image / deploy (push) Successful in 58s
Move button actions to per-widget config, add hold-for-global-action
Next/back button assignment moves from a frame-level "Button
assignments" card into each widget's own gear-icon dialog, prefilled
with a sane default at creation (photos/calendar -> advance/back,
whiteboard/weather -> check_now, others -> none). At most one binding
per (widget, button) now -- cross-widget execution order never
mattered since each widget's action only touches its own state.

New firmware capability: holding NEXT or BACK past a configurable
duration (min 3s, server-side default) triggers a frame-wide action
instead of the per-widget short-press one -- cycling saved layouts,
refreshing all widgets, or freezing/unfreezing every photo widget (see
app/global_actions.py). Firmware next/back checks gain the same
hold-duration polling the combo button already had; the threshold
comes from the previous wake's /frame/config fetch (persisted in NVS),
since this wake's button decision happens before that request.

Not done here: firmware/version.txt is intentionally left unbumped --
this hasn't been built or hardware-tested (no ESP-IDF toolchain in this
environment), so no firmware release build should be triggered yet.
2026-07-27 22:09:33 +00:00

80 lines
3.7 KiB
Python

"""Registry mapping a Widget's widget_type to its render/action module --
the widget-system's analogue of routers/device.py's old RENDERERS/
ADVANCE_RENDERERS/BACK_RENDERERS dicts, generalized from "one mode owns
the whole panel" to "each widget renders into its own region and
optionally responds to named button actions."
Each module in this package exposes:
render(db, frame, widget, target_w, target_h, is_normal_wake=True) -> Image.Image
An RGB image exactly target_w x target_h, unquantized -- the
widget's content composed into its own region. Never returns
packed panel bytes or raises for a foreseeable failure (a
widget's own fetch hiccup shows a small placeholder instead) --
image_pipeline.render_panel composites every widget's own
render() result onto one shared canvas and quantizes/packs the
whole thing once (see its own docstring). is_normal_wake
distinguishes an ordinary /frame/image GET from a button-
triggered re-render -- only app/widgets/calendar.py's render()
actually uses it (resetting browse_offset back to "today" on a
normal wake), but every module accepts it for one uniform call
signature regardless.
ACTIONS: dict[str, Callable[[Session, Frame, Widget], None]]
Named button actions this widget type supports (e.g. "advance",
"back", "check_now") -- see models.FrameButtonAction. Each
function mutates the widget's own state via db.widget_locked
internally; none return a value or re-render themselves --
whatever dispatches a button press (routers/device.py, once the
widget-system cutover lands) re-renders the whole panel once
after running every assigned action.
ACTION_LABELS: dict[str, str]
Human-readable labels for ACTIONS' keys, for the button-
assignment UI.
"""
from __future__ import annotations
from ..models import FrameButtonAction
from . import battery, calendar, photos, static_image, tasks, text, weather, whiteboard
WIDGET_TYPES = {
"photos": photos,
"calendar": calendar,
"whiteboard": whiteboard,
"tasks": tasks,
"static": static_image,
"text": text,
"weather": weather,
"battery": battery,
}
def default_button_actions(frame_id: int, widget_id: int, widget_type: str) -> list[FrameButtonAction]:
"""NEXT/BACK -> whatever this widget's own advance/back concept is.
Called both when backfilling pre-widget-system frames (see
app/migration.py) and when a widget is newly created (see
routers/api_widgets.py's api_widget_create) -- a widget is never left
without a sane starting binding, so the physical buttons always do
something reasonable for it until someone deliberately reassigns
them in that widget's own config dialog."""
if widget_type == "whiteboard":
# No real "next"/"back" concept for a static board -- both
# buttons mean "check now".
return [
FrameButtonAction(frame_id=frame_id, button="next", widget_id=widget_id, action="check_now"),
FrameButtonAction(frame_id=frame_id, button="back", widget_id=widget_id, action="check_now"),
]
if widget_type == "weather":
return [
FrameButtonAction(frame_id=frame_id, button="next", widget_id=widget_id, action="check_now"),
FrameButtonAction(frame_id=frame_id, button="back", widget_id=widget_id, action="check_now"),
]
if widget_type in ("photos", "calendar"):
return [
FrameButtonAction(frame_id=frame_id, button="next", widget_id=widget_id, action="advance"),
FrameButtonAction(frame_id=frame_id, button="back", widget_id=widget_id, action="back"),
]
return []