Skip to content

Add pixel-art animations to weather popup - #12982

Open
dmitry-solomadin wants to merge 1 commit into
omacom:quattrofrom
dmitry-solomadin:weather-sky-effects
Open

dmitry-solomadin wants to merge 1 commit into
omacom:quattrofrom
dmitry-solomadin:weather-sky-effects

Conversation

@dmitry-solomadin

Copy link
Copy Markdown

What

Adds an animated scene to weather widget popup, like the Apple Weather backgrounds but drawn as low-res pixel art in the theme's own colours.

How it is drawn

The scene comes from the Open-Meteo weather code and is_day flag the widget already fetches, falling back to the bar glyph when only wttr.in data is present.

The popup only animates while it is open, and the per-frame work is kept to what actually moves.

Measured on a 600×190 popup at heavy intensity with wind:

Cost
Per frame, any scene under 2 ms (budget 33 ms)
Cloud or fog strip, once per scene change about 100 ms

Colours are the theme accent, background and an ink that is white on dark themes and the theme foreground on light ones, which also get stronger opacity.

Configuration

"fx" on the widget's shell.json entry, shown as "Animated weather effects" in the widget settings. On by default; false turns it off.

screenshot-2026-09-22_17-29-40 screenshot-2026-09-22_17-30-18 screenshot-2026-09-22_17-31-34 screenshot-2026-09-22_19-44-38

Test plan

  • bash test/shell.d/weather-test.sh: WMO and glyph mapping, the fx toggle, panel wiring
  • ./test/shell: only the environment-dependent files fail (no omarchy-pkgs checkout, no omasnap, two kernel migrations), same as on quattro
  • Loaded in a running Omarchy 4.0.4 shell; no QML warnings
  • Off-screen probe: every scene × day/night × intensity × wind paints valid output, strips wrap seamlessly
  • Rendered every scene off-screen on a dark theme, Flexoki Light and Catppuccin Latte
  • Screenshots of each scene on a dark and a light theme

🤖 Generated with Claude Code

The weather popup gets an animated pixel-art scene behind the forecast that
follows the current conditions: a sun with rays, glow, a light beam and
bokeh; a haloed crescent with twinkling stars and a shooting star at night;
partly cloudy, overcast and fog; rain, snow and sleet at three intensities;
thunderstorms whose lightning lights up the clouds, with hail for WMO 96/99;
and wind streaks from 30 km/h.

The scene comes from the Open-Meteo weather code and day flag the widget
already fetches, through a table of named WMO codes in Model.js, and falls
back to matching the bar glyph against iconForCode when only wttr.in data is
present.

Drawing is kept cheap: a static canvas repainted only on open or scene
change, cloud and fog strips painted once from horizontally periodic noise
and slid sideways, and a 30 Hz canvas for the parts that move, which stays
under 2 ms a frame and stops when the popup closes. Colours are the theme
accent and background plus an ink that is white on dark themes and the
theme foreground on light ones.

A boolean "fx" widget setting, shown as "Animated weather effects", turns
it off.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@dmitry-solomadin dmitry-solomadin changed the title Draw an animated sky behind the weather popup Add animations to weather popup Sep 23, 2026
@dmitry-solomadin dmitry-solomadin changed the title Add animations to weather popup Add pixel-art animations to weather popup Sep 23, 2026
@llstrk

llstrk commented Sep 24, 2026

Copy link
Copy Markdown

Automated AI review

Community review: Independent automated community review, unaffiliated with the Omarchy team, intended to help prepare PRs for their review.

Verified: the scene mapping, day/night and wind handling, the fx toggle and the open-only animation work as described, and steady-state frames are well under the stated 2 ms. Two issues were found: the cloud and fog strips are regenerated on every popup open rather than once per scene change, and with the default location setup the sky ignores the Open-Meteo code and day flag.

Cloud and fog strips are regenerated on every popup open

The description gives about 100 ms per strip, paid once per scene change. The per-strip cost matches, but it is paid on each open. On Wayland, Quickshell's PanelWindow deletes its backing window when the popup hides, and Qt's Canvas drops its painted content when it leaves a window:

popup closes, fade ends  -> KeyboardPanel.visible = false
                         -> Quickshell (layer shell): backing window deleted, content detached
                         -> Qt Canvas.releaseResources(): 2D context and painted image freed
popup opens              -> new window created, content reattached
                         -> Canvas.itemChange -> requestPaint()
                         -> ScrollLayer.onPaint recomputes the full noise field (JS, GUI thread)

Measured in an offscreen Qt 6.11.2 harness that runs the PR's sky block verbatim and repeats the Qt part of this detach/reattach sequence, heavy intensity with wind:

Scene Strip paint on reopen Worst single frame
partly, clouds, rain, snow, sleet 65 to 111 ms (one strip) up to 115 ms
fog two strips up to 183 ms
storm (band plus hidden flash band) two strips up to 213 ms
sun, moon no strips 11 ms

A reopen during the 140 ms fade-out reuses the existing window and skips the repaint.

Impact: each open of the weather popup in a cloudy, foggy, rainy, snowy or stormy scene blocks the shell's GUI thread (bar, other popups, notifications) for roughly 65 to 210 ms before the popup appears, more on slower CPUs or a larger [spacing] scale.

Suggested change: keep the computed field outside the Canvas (keyed by spec and size) so a new window only replays draw calls, or compute it in a WorkerScript. The storm flash band could reuse the storm band's field. Either way, update the "once per scene change" line in the description.

Default location setup never uses the Open-Meteo code or day flag

fxResolved passes current to resolveSkyScene (shell/plugins/panels/weather/Panel.qml:156). current is the Open-Meteo record only when coordinates are stored (Panel.qml:135). With IP auto-detect (the default) or a name-only location, it stays the wttr.in record, which has neither openMeteoWeatherCode nor isDay, even after the Open-Meteo response has arrived and set the hero glyph. The sky then always takes the glyph fallback, not only "when only wttr.in data is present":

Open-Meteo condition With stored coordinates With auto-detect / name-only
65 heavy rain rain, heavy rain, moderate
96 thunderstorm with hail, night storm, heavy, hail, night storm, moderate, no hail, day palette
66 freezing rain sleet rain
3 overcast, night clouds, night clouds, day palette

Running the PR's Model.js in Qt's JS engine, 41 of 56 WMO code and day-flag combinations resolve differently between the two paths.

Impact: on the out-of-box setup, intensity grading, hail, sleet and the night palette for overcast, rain, snow and storm scenes never appear.

Suggested change:

-  readonly property var fxResolved: Model.resolveSkyScene(current, label)
+  readonly property var fxResolved: Model.resolveSkyScene(openMeteoCurrent || current, label)

windspeedKmph is km/h in both records, so the wind threshold is unaffected. The new tests call resolveSkyScene directly and do not cover which record the panel passes.

Optional: the sky derives its light-theme check and blends from Color.background/Color.foreground, while the card is painted with Color.popups.background. The two differ only when a user overrides [popups] colours in shell.toml, in which case the ink contrast targets the wrong surface. Color.popups.background and Color.popups.text would follow the actual card.

Verified:

  • Every named WMO code maps to a drawable scene with the stated intensity grades, hail and sleet; unlisted codes fall back to clouds.
  • The glyph fallback covers all 15 distinct glyphs iconForCode can return; night is inferred only from glyphs with a distinct night variant.
  • The 30 km/h wind threshold holds for both data sources and is independent of the imperial display setting.
  • fx: false hides the layer and stops the timer; the default is on.
  • The 30 Hz timer runs only while the popup is open and the scene is not off.
  • Steady-state animated frames cost 0.06 to 0.67 ms of JS. The static canvas costs 2 to 5 ms per paint, but only during the 0.9 s open animation.
  • The scrolling strips wrap seamlessly (checked from the noise period; not measured visually).
  • The palette matches an independent blend calculation on Tokyo Night and Flexoki Light. All 22 shipped themes fall clearly on one side of the light/dark threshold.
  • No QML warnings or errors under Qt 6.11.2.

Review information

Test scope: Source review of head 7cf2f9dc together with Quickshell v0.3.1 and Qt 6.11.2 source. Later quattro commits do not touch the files involved. test/shell.d/weather-test.sh passed at the head in an isolated sandbox (85 checks, with Deno standing in for Node), and a mutation control failed as expected. The sky layer was run offscreen with the software renderer across 12 scenes and two themes; timings come from one machine. The per-open repaint was reproduced for the Qt part only, not inside a live Quickshell layer-shell session. The widget settings UI, rounded popup corners and on-screen appearance were not checked.

AI process: Opus 5.5 Medium coordination and synthesis, independent Opus 5.5 Xhigh and GPT 6 Sol Xhigh technical assessments with targeted cross-checks of each finding, Opus 5.5 Medium editorial check.

Opt out: To stop receiving these reviews, reply to this comment saying so.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants