c0fefc19f16da6e1b95af2a00f36165ae1acc209
4
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
5f4f8f2ea7 |
Add a curated theme system for "modern" style widgets, inspired by Tesserae
Frame.theme (7 presets in app/theme_tokens.py) drives font family, corner radius, drop shadow, and an accent hue for every modern-style widget's header/accent region. Rich accent colors (not just the 6 flat panel inks) are approximated via denser Bayer stippling confined to just that region (html_render.ordered_dither_regions), so icon/text content elsewhere stays exactly as crisp as it is today -- verified directly against real Chromium renders, both in unit tests and via run-server. "classic" is a byte-identical no-visual-change default: weather's header keeps its original fixed blue gradient, tasks/calendar keep their flat THEME_* ink. Themes are purely stylistic -- battery's charge-level color, calendar/ tasks' per-owner event chips, and text's own per-widget font choice are never touched. |
||
|
|
e331f5e5a1 |
Roll out "modern" HTML/CSS render style to every widget except photos
Extends weather's experimental Chromium+Jinja2 render style to battery, text, tasks, static image, whiteboard, and calendar (all four view modes -- agenda/today_tomorrow/week/month), and gives the photos widget its own genuinely independent palette + dithering strength. Photos: Frame.photo_palette_rgb/photo_dither_strength (mirroring the existing palette_rgb/dither_strength), with a second "Photos configuration" card in Advanced Configuration. widgets/photos.py's render() quantizes itself against these before returning -- no render_panel changes needed, since photos is the only widget that genuinely needs a different reference palette and can carry that itself, the same way modern-style widgets already self-dither via ordered_dither. Battery/text/tasks/static image/whiteboard: same render_style pattern weather established (render_style column, html_render.py build function, Jinja2 template, dialog toggle). Static image/whiteboard get their first-ever visual chrome (a rounded-corner shadowed card, shared framed_image.html.jinja) since classic draws them with zero frame at all. Fixed the same "preview endpoint bypasses render_style" bug weather originally shipped with, for tasks/static/whiteboard/ calendar's preview endpoints. Calendar: own module (app/calendar_html_render.py, mirroring calendar_render.py's separation from the simpler widgets) covering all four view modes, not just agenda -- reuses calendar_render's own private helpers so event colors/times/weather/month-grid math match classic exactly. Found and fixed two real cross-day layout bugs along the way: a per-day header height that varied based on whether that specific day had a weather entry (misaligning where every other day's event rows started across the week/month grid), and regular-weight small text being fragile under Bayer ordered dithering (out-of-month day numbers degraded into unrecognizable speckle) -- fixed by using bold everywhere and de-emphasizing via size instead of weight/gray, since gray text has the same dithering fragility this project's PIL renderers already avoid for exactly this reason. Migrations 32-38 (Frame's two new columns, then one render_style column per widget config table). 452 tests passing, including new dispatch/ migration coverage per widget type and a dedicated photos test proving photo_palette_rgb produces genuinely independent quantization from the frame's main palette_rgb. |
||
|
|
37bd657299 |
Widget system Phase 2: full cutover to widget-based rendering
device.py's mode-keyed dispatch is replaced by a real compositor: load a frame's widgets, compute pixel rects via app/grid.py, render each through its widget module, and composite with render_panel. Physical NEXT/BACK buttons now execute each frame's assigned FrameButtonAction rows instead of one hardcoded per-mode action. api_frames.py, manage.py, and common.py's build_manage_content are repointed to read/write the frame's widget config rows instead of the old Frame columns, and every settings page (Photos/Calendar/ Whiteboard tabs) now pre-fills its form from the same widget config the write endpoints actually save to -- previously the read and write sides would have silently diverged. The old mode selector and photo-inlay checkbox are removed along with their now-inert wiring; arbitrary widget placement subsumes what the fixed inlay split did. Ships together with Phase 1 (per-type render/action modules) since splitting the read/write cutover across deploys would have left settings changes with no visible effect. |
||
|
|
f48daa71c8 |
Widget system Phase 1: per-type render/action modules
New app/widgets/ package (photos.py, calendar.py, whiteboard.py, plus the WIDGET_TYPES registry) -- the widget-system analogue of routers/device.py's old RENDERERS/ADVANCE_RENDERERS/BACK_RENDERERS, generalized from "one mode owns the whole panel" to "each widget renders into its own region and responds to named button actions." Each module exposes render(db, frame, widget, target_w, target_h) -> Image.Image (never raises -- a widget's own fetch hiccup falls back to a small placeholder rather than taking the whole panel's render down) and an ACTIONS registry for NEXT/BACK button assignment. Supporting changes needed to give the widget modules something to call, all mechanical/behavior-preserving for every existing caller: - image_pipeline.py: render_panel(regions, ...) generalizes render_frame's tail (paste, enhance once, overlay once, quantize once, pack once) from one photo to N regions -- not a restructuring, since calendar mode's photo-inlay feature already pastes a second composed image onto the canvas before that single shared pipeline runs. - photo_queue.py: advance_forced/back_forced/remove_from_rotation/ get_current take an explicit `frame` param now that `cfg` won't always be the Frame itself once photo-queue state moves to PhotoWidgetConfig -- caught a real latent bug while doing this: get_current was reading refresh_interval_s off `cfg`, but that's a frame-level wake-cadence setting, not something that becomes per-widget, so it now reads that off `frame` explicitly instead. - routers/common.py: list_assets/fetch_source_and_faces take album_id/ display_mode directly instead of a whole Frame (both only ever read that one attribute off it); new get_or_refresh_*_for_widget siblings of the existing calendar/weather/tasks/whiteboard cache helpers, read/ writing the new per-widget config tables -- the Frame-scoped originals are untouched and still what routers/device.py's actual dispatch calls until the Phase 2 cutover. 26 new tests (95 total): render_panel size/placement/orientation coverage, and per-widget-type render/action tests (unconfigured -> placeholder, a fetch failure -> placeholder not a crash, actions mutate the right state). Full suite passes; diff-reviewed to confirm device.py's actual RENDERERS dispatch and the old Frame-scoped get_or_refresh_* bodies are unchanged, so this is safe to deploy on its own despite being step 1 of a two-step cutover (see the project plan on why the *next* step, not this one, has to ship atomically). |