Skip to content

Lay the theme-relative foundation: tokens, control themes, icons, system font - #14

Merged
chrisuthe merged 6 commits into
masterfrom
chrisuthe/task/lay-the-theme-relative-foundation-tokens-control
Sep 1, 2026
Merged

Lay the theme-relative foundation: tokens, control themes, icons, system font#14
chrisuthe merged 6 commits into
masterfrom
chrisuthe/task/lay-the-theme-relative-foundation-tokens-control

Conversation

@chrisuthe

Copy link
Copy Markdown
Owner

Phase 1 of the UI reskin: everything visual expressed once as resources, so the later phases only lay controls out. The shell belongs to the OS — native decorations, the system light/dark variant and accent followed live, the platform's UI font — and the content area belongs to the music. The layout inside the window will come from Sendspin for Windows; the colours do not. No layout changes here.

What changed

  • Tokens (Styles/Tokens.axaml): VeilBrush, TranslucentSurfaceBrush, ArtPlaceholderBrush, GlowDefaultBrush in Light and Dark dictionaries, plus OnAccentBrush. Every colour is a DynamicResource into Fluent, so nothing is a literal. Fluent in Avalonia 12.1.1 turned out to carry only the SystemControl* resource set; the SystemFillColorCautionBrush the old warning class named did not exist, so it now uses Fluent's error-text brush.
  • Control themes and text (Styles/PlayerStyles.axaml): a PathIcon theme that strokes at 1.7 units on a 24 box; iconButton, playButton, primary; one text scale with the old class names as aliases on the same selectors.
  • Icons (Styles/Icons.axaml): shuffle, repeat and switch-group from Sendspin for Windows, the rest from Feather (both MIT, named in the header). Every emoji glyph in the views is gone; play/pause is an icon choice on IsPlaying, repeat shows repeat-one when the server says so.
  • Accent: never overridden. OnAccentBrush is black or white from the accent's WCAG luminance (Core/Theme/AccentContrast.cs), recomputed behind one deduplicated subscription to the platform's colour-changed event (SystemColorChangeFilter, for the ~20-event storm measured on Windows). The threshold is set between the saturated picks Windows and GNOME serve and the light highlights Plasma serves.
  • System font: Fluent's font resource is overridden to $Default, Inter stays as glyph fallback. On Linux the desktop font comes from the Settings portal's font-name key, which the KDE backend serves too — and it is reachable from the Flatpak, where fontconfig's own default is DejaVu Sans; measured in the runtime sandbox. Windows measured plain Segoe UI and stays unset; macOS measured Helvetica, and the SF override is a follow-up once the resolvable family name is known. The app now logs what it resolved at start-up.
  • Icons: the green disc from the reference app at 256 px for the app, tray, AppImage and Flatpak.
  • Docs: README, App.axaml, MainWindow.axaml and the styles header state the principle; the "not copied" wording is gone. docs/ARCHITECTURE.md records the font decision and the Windows and macOS measurements.

Before / after

Light Dark
Before (master) before light before dark
After after light after dark

Both taken on the Wayland head under Plasma 6.7, switching live with plasma-apply-colorscheme. Start-up log on this box:

UI font: $Default is Noto Sans, glyphs from Noto Sans; controls use $Default, glyphs from Noto Sans; fallback face Inter

Checks

  • make test green in Debug and Release (258 + 22).
  • dotnet format --verify-no-changes clean on every project touched. One whitespace-only commit re-indents the MPRIS GetAll block that dotnet format already objected to on master.
  • Every Linux-buildable project builds clean in Release with warnings as errors.
  • Tests pin: every token resolves under both variants; every icon is a geometry inside the 24 box; no axaml names a colour and no view draws a glyph with a character; the on-accent brush; the luminance helper; the font-name parser; the change filter.

Left for later phases

  • The header's Diagnostics and Settings toggles keep Fluent's white-on-accent checked look until Phase 2/3 rebuilds the header.
  • The macOS DefaultFamilyName for SF, once the resolvable name is measured.

…colour reports

Plasma serves its highlight colour as the accent, which in a light scheme is a light
derivative of the user's pick; GNOME and Windows serve the saturated pick. Fluent's
text-on-accent brush is white for both. The contrast helper decides from WCAG relative
luminance with a threshold set between the measured saturated picks (Windows blue at
0.18, GNOME blue at 0.23) and the measured light highlights (0.37 and up), so neither
kind of desktop gets an unreadable glyph.

The filter is for Windows, where one accent change raised about twenty
ColorValuesChanged events in 600 ms and each variant flip raised two; everything hung
off that event goes through one filter so the work happens once per real change.
$Default is fontconfig's answer to an empty pattern, which inside the Flatpak is DejaVu
Sans rather than the desktop's font. The portal's org.gnome.desktop.interface/font-name
is the real setting, the KDE backend serves it as well as the GNOME one, and it is
reachable from the sandbox without a permission. Read once, synchronously and bounded,
before the app builder runs, because FontManagerOptions is fixed there.

The Pango description is reduced to a family name first: fontconfig turns a family it
does not know back into its default, so a trailing size or style word would undo the
whole point.
…tem font

Phase 1 of the reskin: everything visual expressed once as resources, so later phases
only lay controls out. The shell belongs to the OS — native decorations, the system
light/dark variant and accent followed live, the platform's UI font — and the content
area belongs to the music; its composition will come from Sendspin for Windows, its
colours will not.

- Tokens.axaml: the few brushes Fluent lacks (veil, translucent surface, art
  placeholder, glow default, on-accent), each derived from a Fluent colour by
  DynamicResource so nothing here is a literal. Fluent in Avalonia 12.1.1 carries the
  SystemControl* resource set only; the caution brush the old warning class named did
  not exist, so it now uses Fluent's error-text brush.
- PlayerStyles.axaml: a PathIcon theme that strokes its geometry at 1.7 units on a 24
  box, iconButton / playButton / primary, and one text scale with the old classes as
  aliases on the same selectors.
- Icons.axaml: the stroke set, lifted from Sendspin for Windows and Feather (both MIT),
  replacing every emoji glyph in the views; play/pause is an icon choice on IsPlaying.
- OnAccentBrush is recomputed from the accent's luminance behind one deduplicated
  subscription to the platform's colour-changed event.
- The UI font is the platform's: Fluent's font resource is overridden to $Default, the
  Linux head names the desktop font from the Settings portal, Inter stays as the glyph
  fallback and the app logs what it resolved. Windows measured Segoe UI and stays unset;
  macOS measured Helvetica, and the SF override is a follow-up once the resolvable
  name is known.
- Icons: the green disc from the reference app at 256 px, for the app, the tray, the
  AppImage and the Flatpak.
- Tests pin the tokens under both variants, the icon box, the no-colour-literal and
  no-glyph-character rules, and the on-accent brush.
…' font too

A best-effort read at start-up, before a logger exists, must not be able to stop the
app starting; the earlier list of exception types was a guess at what the bus can
throw. The font log line now also resolves ContentControlThemeFontFamily, which is the
resource controls actually draw with, rather than only $Default.
@chrisuthe
chrisuthe marked this pull request as ready for review September 1, 2026 22:18
Both this branch and PR #13 rewrote the Windows and macOS paragraphs of the
UI shell section. Keep PR #13's macOS measurements and this branch's
Windows ones and the shipped font decision, and note that the macOS system
face resolves as .AppleSystemUIFont per PR #13's table.
@chrisuthe
chrisuthe merged commit 4219c46 into master Sep 1, 2026
2 checks passed
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.

1 participant