Files
enricobuehler b50248168b fix(client/windows): draw the icon font, stop rasterizing the icons
Baking PNGs was the wrong call and it showed twice over. Reactor's
`.icon()` builds a BitmapIcon with `ShowAsMonochrome(false)` and no
size, so a baked mark is stuck at ONE colour on every theme and renders
at its source pixel count read as DIPs — a colour that never matches
the theme, and a size each control template clamps or does not.

Draw Lucide's own icon font instead. A FontIcon is sized by the control
and tinted from the foreground brush, exactly as the SymbolIcon it
replaced was: white on an accent button, the theme's colour in the
sidebar, and a vector at any DPI. The ring's discs take the same glyph
through a TextBlock, where the size is the ring's geometry and the ink
is white because a disc is dark on both themes.

Three PNG bakes, the two-colour split, the LOCALAPPDATA materializer
and its cache all go. What replaces them is a codepoint per icon in the
shared table and one 664 kB font.

The font reaches the exe by two routes, because ms-appx:/// resolves to
the install folder either way: build.rs stages it for a dev build, and
it is checked in under packaging/assets for a shipped one, which
pack-msix.ps1 copies into the layout the MSIX, the installer and the
portable zip are all packed from. That script now fails loudly if it is
missing — every icon would otherwise ship as a private-use box.
2026-08-31 00:18:19 +02:00
..

Lucide icon masters

The canonical UI marks every client draws its own icons from — the quick-action ring's slots, and the ordinary shell chrome (back, refresh, save, delete…) on the desktop clients.

Lucide v0.462.0, ISC licensed (see THIRD-PARTY-NOTICES.txt), fetched unmodified from lucide-icons/lucide at that tag. One file per icon, its own name. Every master is a 24×24 viewBox, fill="none", stroke="currentColor", stroke-width="2", round caps and joins — Lucide's own drawing contract, and what every derivative below reproduces.

Which client consumes what

scripts/gen-lucide-assets.sh derives all of it. Nothing here is hand-edited.

client form where
Skia console (gamepad UI) folded path string, stroked by Skia crates/pf-client-core/src/lucide.rscrates/pf-console-ui/src/icons.rs
GTK shell the same path string, stroked by gsk::Path crates/pf-client-core/src/lucide.rs
WinUI shell PNG, baked twice (grey and white) clients/windows/assets/lucide/

The two Rust consumers share one table, so a mark cannot differ between the console and the GTK shell. The WinUI shell bakes because windows-reactor has no vector element: its Image takes a raster URI, and its BitmapIcon is created with ShowAsMonochrome(false), so a WinUI icon cannot be tinted at runtime and has to ship in the colour it will be drawn in. Hence two bakes — lucide/ in mid-grey for ordinary surfaces, lucide-on/ in white for accent buttons and the ring's dark discs.

Adding an icon

  1. Drop the master here: curl -o assets/lucide/<name>.svg https://raw.githubusercontent.com/lucide-icons/lucide/0.462.0/icons/<name>.svg
  2. bash scripts/gen-lucide-assets.sh
  3. Add the PNG to clients/windows/src/app/lucide.rs's ICONS table — the shipped-token list, the same discipline the OS marks and launcher marks keep.

The console's icons.rs and the GTK shell need no list of their own: both read pf_client_core::lucide, which the script regenerates whole.