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.
This commit is contained in:
+29
-1
@@ -178,6 +178,14 @@ class Frame(Base):
|
||||
# 0.0-1.0, see image_pipeline._quantize -- 1.0 matches this project's
|
||||
# original always-on full-strength Floyd-Steinberg dithering.
|
||||
dither_strength: Mapped[float] = mapped_column(Float, default=1.0)
|
||||
# Same shape as palette_rgb/dither_strength above, but scoped to only
|
||||
# the photos widget (widgets/photos.py quantizes against these itself,
|
||||
# before returning -- see its own docstring) -- lets a frame tune the
|
||||
# rest of its widgets' palette/dithering (e.g. a "modern" HTML-
|
||||
# rendered dashboard look) independently of what actually looks best
|
||||
# for real photographs. NULL/1.0 = same defaults as the main fields.
|
||||
photo_palette_rgb: Mapped[list | None] = mapped_column(JSON, nullable=True, default=None)
|
||||
photo_dither_strength: Mapped[float] = mapped_column(Float, default=1.0)
|
||||
|
||||
# -- calendar mode (see calendar_feed.py, calendar_render.py,
|
||||
# routers/device.py's RENDERERS["calendar"]) --
|
||||
@@ -542,6 +550,10 @@ class CalendarWidgetConfig(Base):
|
||||
week_days: Mapped[int] = mapped_column(Integer, default=7)
|
||||
week_layout: Mapped[str] = mapped_column(String, default="horizontal")
|
||||
week_start_offset: Mapped[int] = mapped_column(Integer, default=0)
|
||||
# classic (calendar_render.py) vs modern (app/calendar_html_render.py,
|
||||
# agenda mode only so far -- see that module's docstring) -- see
|
||||
# widgets/calendar.py's render().
|
||||
render_style: Mapped[str] = mapped_column(String, default="classic")
|
||||
|
||||
|
||||
class TaskWidgetConfig(Base):
|
||||
@@ -580,6 +592,9 @@ class TaskWidgetConfig(Base):
|
||||
# outstanding ones -- off by default, same "opt into more" posture
|
||||
# as calendar_weather_enabled.
|
||||
show_completed: Mapped[bool] = mapped_column(Boolean, default=False)
|
||||
# classic (hand-drawn PIL, calendar_render._build_tasks) vs modern
|
||||
# (app/html_render.py) -- see widgets/tasks.py's render().
|
||||
render_style: Mapped[str] = mapped_column(String, default="classic")
|
||||
|
||||
|
||||
class WhiteboardWidgetConfig(Base):
|
||||
@@ -593,6 +608,10 @@ class WhiteboardWidgetConfig(Base):
|
||||
url: Mapped[str] = mapped_column(String, default="")
|
||||
checked_at: Mapped[float] = mapped_column(Float, default=0.0)
|
||||
cached_image: Mapped[bytes | None] = mapped_column(LargeBinary, nullable=True)
|
||||
# classic (no chrome, unchanged) vs modern (app/html_render.py's
|
||||
# rounded-corner shadowed card) -- see widgets/whiteboard.py's
|
||||
# render().
|
||||
render_style: Mapped[str] = mapped_column(String, default="classic")
|
||||
|
||||
|
||||
class WeatherWidgetConfig(Base):
|
||||
@@ -668,6 +687,9 @@ class TextWidgetConfig(Base):
|
||||
font_family: Mapped[str] = mapped_column(String, default="sans")
|
||||
align: Mapped[str] = mapped_column(String, default="left") # "left" | "center" | "right"
|
||||
background_color: Mapped[str] = mapped_column(String, default="#ffffff")
|
||||
# classic (hand-drawn PIL) vs modern (app/html_render.py) -- see
|
||||
# widgets/text.py's render()/render_preview_png().
|
||||
render_style: Mapped[str] = mapped_column(String, default="classic")
|
||||
|
||||
|
||||
class StaticWidgetConfig(Base):
|
||||
@@ -689,6 +711,10 @@ class StaticWidgetConfig(Base):
|
||||
# minus crop_faces -- no face detection for an uploaded image (see
|
||||
# image_pipeline.STATIC_DISPLAY_MODES).
|
||||
display_mode: Mapped[str] = mapped_column(String, default="crop_fill")
|
||||
# classic (no chrome, unchanged) vs modern (app/html_render.py's
|
||||
# rounded-corner shadowed card) -- see widgets/static_image.py's
|
||||
# render().
|
||||
render_style: Mapped[str] = mapped_column(String, default="classic")
|
||||
|
||||
|
||||
class BatteryWidgetConfig(Base):
|
||||
@@ -699,12 +725,14 @@ class BatteryWidgetConfig(Base):
|
||||
of anything the widget itself fetches or the user authors. `mode`
|
||||
"compact" is icon + percent only; "detailed" (default) adds the
|
||||
routers.common.battery_estimate_s time-remaining estimate and the
|
||||
last report's age."""
|
||||
last report's age. `render_style` picks classic (hand-drawn PIL) vs
|
||||
modern (app/html_render.py) -- see widgets/battery.py's render()."""
|
||||
|
||||
__tablename__ = "battery_widget_configs"
|
||||
|
||||
widget_id: Mapped[int] = mapped_column(ForeignKey("widgets.id", ondelete="CASCADE"), primary_key=True)
|
||||
mode: Mapped[str] = mapped_column(String, default="detailed") # compact | detailed
|
||||
render_style: Mapped[str] = mapped_column(String, default="classic") # classic | modern
|
||||
|
||||
|
||||
# widget_type -> its per-type extension table, keyed by widget_id. Used
|
||||
|
||||
Reference in New Issue
Block a user