Skip to content

winui: model artwork, richer island/nav, custom tray menu, new icon - #7

Merged
arctumn merged 12 commits into
cross-platformfrom
winui/device-visuals-and-tray
Aug 9, 2026
Merged

winui: model artwork, richer island/nav, custom tray menu, new icon#7
arctumn merged 12 commits into
cross-platformfrom
winui/device-visuals-and-tray

Conversation

@arctumn

@arctumn arctumn commented Aug 9, 2026

Copy link
Copy Markdown
Owner

A dogfooding pass over the WinUI client's visuals and the system tray.

What changed

Device artwork (Services/DeviceArt.cs — model number → asset family)

  • Header shows the model's render instead of the generic airpods.png.
  • Battery card shows per-component bud/case artwork above each gauge.
  • Connect island uses the model render, with the last-seen model cached
    (AppSettings.LastModel) so it's correct before the 0x1D metadata arrives.

Noise control — vertical RadioButtons → segmented row of icon buttons
(Off / NC / Transparency / Adaptive) with the active mode named below.

Island — message mode shows the noise-control mode icon (not the device) for
an ANC change; the whole popup now follows the app theme.

Nav pane — device item gains a model icon + a compact battery %, shown in the
expanded pane (for when multiple devices are listed).

Tray — renamed TrayIconTrayController. The context menu is now a custom
themed window (Popup/TrayMenuWindow + TrayMenuView) — the WinUI MenuFlyout
clipped to ~1 char in H.NotifyIcon's SecondWindow host, and a native Win32 menu
can't carry per-item icons or the app theme. Icon per row, model image, battery,
ANC checkmarks, mute/open/quit; dismissed on focus loss. Icon badge is now a large
battery number; tooltip gains the ANC mode.

Fixes — the "Connect?" prompt no longer shows while already connected; the
one-column device layout drops the phantom right gap (collapsed Col1 kept its
ColumnSpacing).

New app/tray icon (transparent background).

Settings — a UI-language selector (persisted, applied at startup).

What to test

Device page

  • Header shows the correct render for the connected model (Pro 3, etc.); falls back to generic when unknown.
  • Battery card shows L/R/Case artwork + %; charging bolts appear; "—" when a component is absent.
  • Noise-control segmented buttons switch mode; active one highlighted + named below; Off disabled when the daemon disallows it.
  • One-column (narrow window): no empty gap on the right; two-column when wide.

Nav pane

  • Expanded: device item shows model icon + battery %; collapses to icon-only when narrow.

Island popup

  • On connect: shows the right model render + battery, follows the app theme (light/dark).
  • On an ANC change: shows the mode icon (not the AirPods), themed.

Tray

  • Right-click → custom menu: themed, icon per row, model image, battery, ANC check on the active mode.
  • Clicking a mode / mute / open / quit works and closes the menu; clicking away dismisses it.
  • Menu positions near the tray and stays on-screen.
  • Icon badge: large battery number; tooltip includes the ANC mode.
  • Double-click the tray icon opens the app.

Connect prompt

  • The "Connect?" prompt does NOT appear while already connected; it clears on connect.

App icon

  • New icon in taskbar/tray with a transparent background (no black box).

Settings

  • Theme switch still works.
  • Language selector persists. Known limitation: runtime language switching does not take effect on the unpackaged build (PrimaryLanguageOverride doesn't re-resolve x:Uid) — a code-based localization pass is the follow-up.

Notes

  • Localized strings added for the new UI in en-US / pt-PT / es-ES / fr-FR.
  • No daemon/driver changes in this PR (WinUI only).

arctumn added 5 commits August 9, 2026 14:45
…icon

A pass over the WinUI client's visuals + tray, driven by dogfooding.

Device artwork (Services/DeviceArt.cs — model number → asset family):
- DeviceHeader shows the model's render instead of the generic airpods.png.
- BatteryCard shows per-component bud/case artwork above each gauge.
- The connect island uses the model render, with the last-seen model cached
  (AppSettings.LastModel) so it's right before the 0x1D metadata arrives.

Noise control: the vertical RadioButtons become a segmented row of icon buttons
(Off / NC / Transparency / Adaptive) with the active mode named below.

Island: the message mode shows the mode icon for a noise-control change (not the
device), and the whole popup now follows the app theme.

Nav pane: the device item gains a model icon + a compact battery %, shown in the
expanded pane (handy once multiple devices are listed).

Tray: renamed TrayIcon -> TrayController; the WinUI MenuFlyout (which clipped to
~1 char in H.NotifyIcon's SecondWindow host) and a native Win32 menu (no per-item
icons, no theme) are both unusable here, so the context menu is now a custom
themed window (Popup/TrayMenuWindow + TrayMenuView) with an icon per row, the
model image, battery, ANC checkmarks, mute/open/quit — dismissed on focus loss.
The icon badge is a large battery number; the tooltip gains the ANC mode.

Fixes: the "Connect?" prompt no longer shows while already connected; the
one-column device layout drops the phantom right gap (collapsed Col1 kept its
ColumnSpacing).

New app/tray icon (transparent background).

Settings: a UI-language selector (persisted, applied at startup). NOTE: runtime
language switching is limited on the unpackaged build — PrimaryLanguageOverride
does not re-resolve x:Uid there; a code-based localization pass is the follow-up.
The raster ANC art (anc_*.png) is fixed-colour — white washed out on the light
acrylic, black vanished on dark. Show a Segoe Fluent FontIcon (follows the theme
foreground) instead, matching the tray menu's mode glyphs.
The anc_*.png icons had inconsistent padding/weight so they framed unevenly next
to the (glyph) Off button. Use uniform Segoe Fluent FontIcons — consistent
framing, theme-following (incl. on the accent-selected button), matching the
island + tray mode glyphs.
x:Uid + PrimaryLanguageOverride don't re-resolve on the unpackaged build, so the
language selector needed a full restart (and didn't even work). Replace it with an
Angular-style translate service:

- Services/Loc.cs — a singleton INotifyPropertyChanged that parses the embedded
  Strings/<culture>/Resources.resw into a culture -> key -> value map. Indexer
  Loc["Key"] returns the current-culture string; SetCulture() raises the indexer
  PropertyChanged so every binding re-fetches → the whole UI switches LIVE.
- All UI text now binds to {Binding [Key_Prop], Source={StaticResource Loc}}
  instead of x:Uid (keys are the resw names with '.' → '_' for the indexer path).
- Localize.Get (code-picked strings) delegates to Loc.
- The language selector calls Loc.SetCulture — no more restart hint.
- The .resw are embedded (LogicalName loc.<culture>) alongside the existing PRI
  resources, so translations aren't duplicated.

Works on the unpackaged build (no MRT / x:Uid / PrimaryLanguageOverride).
@arctumn

arctumn commented Aug 9, 2026

Copy link
Copy Markdown
Owner Author

Update: live language switching now works

The earlier "known limitation" (language override doesn't take effect on the unpackaged build) is resolved. Replaced x:Uid / PrimaryLanguageOverride with a runtime Loc service (Angular-style): all text binds to {StaticResource Loc}, and Loc.SetCulture() raises the indexer PropertyChanged so the whole UI switches language live, no restart.

Extra test:

  • Settings → change language → all UI (device page, cards, nav, tray menu, island) switches immediately, no restart.
  • The chosen language persists across a full quit + relaunch.

arctumn added 7 commits August 9, 2026 21:41
…otify

WinUI (unlike WPF) doesn't refresh {Binding [key]} on an "Item[]" notification,
so switching language updated nothing outside the active page. Raise the empty-
string "all properties changed" notification so every binding re-fetches. Also
make the embedded-resw lookup scan the manifest names (not a hard-coded
LogicalName) so a prefixed/mangled name can't silently yield an empty map.
Confirmed: the whole app switches language live now.
The built-in NavigationView Settings item is OS-localized; re-label it from Loc
(live) so it follows the in-app language. Update the language hint to say it
applies immediately (no restart).
App.xaml instantiated a SECOND Loc via <services:Loc x:Key=Loc>, distinct from the
static Loc.Instance used by code — so SetCulture changed one while the other stayed,
leaving code strings in one language and XAML bindings in another. Inject the single
Loc.Instance into Application.Resources instead.
A daemon overlay (e.g. "Connected · battery") is a one-shot string resolved when
it arrives, so one shown before a language switch lingered in the old language.
Close it on a culture change; the next overlay renders in the new language.
…sources write

Mutating Application.Resources in the App ctor threw a COMException. Revert to
declaring the one <services:Loc x:Key=Loc/> in App.xaml (which works), but drop the
static new() so it's the ONLY instance; its ctor publishes Loc.Instance for code.
No second instance → no culture split, and no runtime resource mutation.
…itch

- ToggleSwitch On/Off content was OS-localized (system language); bind OnContent/
  OffContent to Loc (Toggle_On/Toggle_Off keys, 4 languages) so it follows the app.
- Code-picked strings (header status, mic state, ANC mode name) are only set when a
  snapshot arrives, so they lingered in the old language after a switch. Re-render
  the last snapshot on a culture change.
The BLE proximity watcher prompted "Connect?" and waited for a click. The buds
being audio-connected in Windows doesn't open our AAP L2CAP session (separate
channels), so the app sat on "Disconnected" until a manual Connect. Instead
auto-connect on the proximity edge-trigger: set connect_requested (run_receiver
opens the session) and nudge the OS audio up. Still edge-triggered per visit, so
no reconnect spam.
@arctumn
arctumn merged commit d93ff9d into cross-platform Aug 9, 2026
1 check 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