dragon-iso/src/TeamsISO.App.WinUI/Views/MainWindow.xaml.cs
Zac Gaetano 48ca16bc5e feat(winui3): ThemeManager service + Settings drawer + Help/About/Onboarding
Builds out the secondary surfaces of the redesigned WinUI 3 host.

ThemeManager (Services/ThemeManager.cs)
  Single-source-of-truth for the active theme. Holds the user preference
  (System / Dark / Light), resolves it to ElementTheme at request, and
  raises a Themed event when it changes so the MainWindow can push the
  AppWindow title-bar button colors. Uses Windows.UI.ViewManagement
  UISettings to follow the OS app-mode when preference is System.
  Persistence to UIPreferences lands in the engine-wiring commit.

MainWindow theme wiring
  Replaces the per-handler theme toggle with a ThemeManager subscription:
  click the title-bar sun/moon -> Toggle() -> Themed event ->
  ApplyResolvedTheme on the visual tree + the title-bar buttons. Glyph
  cue: sun = "current is Light, click to Dark"; moon = "current is Dark,
  click to Light." Initial state applied at construction so the first
  frame matches the preference.

SettingsDrawer (Views/SettingsDrawer.xaml + .cs)
  UserControl that slides in from the right over the participants table.
  56px header, NavigationView with five tabs (Appearance, Routing,
  Display, Control, Advanced), footer with Reset-to-defaults +
  Apply/Close. Appearance tab has the theme tri-state picker (System /
  Dark / Light radio group) and an "Accent peek" row showing the four
  brand accents (cyan / coral / live / warn) as swatches so the
  operator can verify Wild Dragon brand is respected on a light desk.
  CloseRequested event signals the host to collapse the drawer.

HelpDialog (Views/HelpDialog.xaml + .cs)
  ContentDialog with the keyboard shortcut cheat sheet, grouped by
  category (Global / Participants / Look / Control surface). 540px max
  height with scroll, mono-spaced shortcut labels at left, body text at
  right. Replaces the WPF host's HelpWindow at parity.

AboutDialog (Views/AboutDialog.xaml + .cs)
  ContentDialog with the Wild Dragon mark, version + host + engine +
  brand info as label/value rows, and three quick action buttons
  (open logs folder, open recordings, check for updates). Mirrors the
  WPF host's AboutWindow.

OnboardingDialog (Views/OnboardingDialog.xaml + .cs)
  Three numbered steps (Install NDI Runtime / Enable Teams NDI / Pick
  transcoder topology), no carousel, operator-tone copy ("Don't show
  this again" defaults checked). PrimaryButtonText "Get started",
  SecondaryButtonText "Skip" so the dialog is skippable from the first
  frame as the PRODUCT.md anti-references demand.

Build clean: dotnet build TeamsISO.App.WinUI -c Debug -> 0 / 0.

Next: wire the drawer's CloseRequested into MainWindow (so the settings
icon actually opens / collapses the drawer), then attack the runtime
activation blocker (Phase 3 of the migration plan).
2026-05-13 00:13:58 -04:00

86 lines
4 KiB
C#

using Microsoft.UI;
using Microsoft.UI.Windowing;
using Microsoft.UI.Xaml;
using TeamsISO.App.WinUI.Models;
using TeamsISO.App.WinUI.Services;
using Windows.Graphics;
using Windows.UI;
namespace TeamsISO.App.WinUI.Views;
public sealed partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
Title = "TeamsISO";
// ── Custom title bar wiring ───────────────────────────────────────
// ExtendsContentIntoTitleBar=true tells WindowsAppSDK to draw the
// window chrome over our content instead of reserving a Windows-default
// caption strip. SetTitleBar marks AppTitleBar as the drag region —
// clicks on it route to the system drag handler, everything else stays
// hit-testable as a normal XAML element. The system min/max/close
// buttons render on top of the right edge regardless; we just provide
// their colors so they match our palette.
ExtendsContentIntoTitleBar = true;
SetTitleBar(AppTitleBar);
AppWindow.TitleBar.ButtonBackgroundColor = Colors.Transparent;
AppWindow.TitleBar.ButtonInactiveBackgroundColor = Colors.Transparent;
AppWindow.TitleBar.ButtonHoverForegroundColor = Colors.White;
// ── Initial size & position ───────────────────────────────────────
// 1280x780 matches the WPF host's default — fits comfortably on a
// 14-inch laptop while giving the participants table 600+ pixels
// of vertical breathing room.
AppWindow.Resize(new SizeInt32(1280, 780));
// ── Mock data wiring (interim) ────────────────────────────────────
// Until ParticipantViewModel binds in the engine wiring commit, the
// table is populated from a static sample list so the visual design
// can be validated end-to-end against representative data.
ParticipantsRepeater.ItemsSource = MockParticipant.Sample();
// ── Theme system ──────────────────────────────────────────────────
// Subscribe to ThemeManager so picker changes from anywhere
// (settings drawer, title-bar toggle, system color change) reach
// the title-bar buttons and the visual tree consistently. Apply
// once at construction so the initial state matches the preference
// before the first frame.
ThemeManager.Current.Themed += (_, theme) => ApplyResolvedTheme(theme);
ApplyResolvedTheme(ThemeManager.Current.ResolveTheme());
}
/// <summary>
/// Cycle the active theme between Dark and Light from the title-bar
/// toggle. The actual swap lives in <see cref="ThemeManager"/>; this
/// handler just calls Toggle() and lets the subscription propagate.
/// </summary>
private void OnThemeToggleClick(object sender, RoutedEventArgs e)
{
ThemeManager.Current.Toggle();
}
/// <summary>
/// Push a resolved theme to the visual tree and to the AppWindow
/// title-bar buttons. Called on every <see cref="ThemeManager.Themed"/>
/// event and once at construction.
/// </summary>
private void ApplyResolvedTheme(ElementTheme theme)
{
if (Content is FrameworkElement root)
{
root.RequestedTheme = theme;
}
AppWindow.TitleBar.ButtonForegroundColor = ThemeManager.TitleBarForegroundFor(theme);
AppWindow.TitleBar.ButtonHoverBackgroundColor = ThemeManager.TitleBarHoverBgFor(theme);
AppWindow.TitleBar.ButtonPressedBackgroundColor = ThemeManager.TitleBarHoverBgFor(theme);
// Glyph cue: sun () means current is Light, click moves to Dark;
// moon () means current is Dark, click moves to Light.
ThemeToggleIcon.Glyph = theme == ElementTheme.Light ? "" : "";
}
}