teamsiso/src/TeamsISO.App.WinUI/Views/MainWindow.xaml.cs
Zac Gaetano 9e176d8f10 feat(winui3): redesigned MainWindow + custom title bar + theme toggle
Lands the approved shape brief as the WinUI 3 MainWindow:

* 64px left rail with brand mark, primary nav (participants), Teams
  launch / hide / settings buttons, and the engine-status puck at the
  bottom. All five rail buttons use Segoe Fluent Icons glyphs at a
  uniform 20px optical size; no more bespoke <Path Data> shapes with
  inconsistent stroke weights.

* 44px custom title bar via ExtendsContentIntoTitleBar +
  SetTitleBar(AppTitleBar). The drag region absorbs the three live-state
  pills inline (session timer 'live * 00:14:32', REC count + elapsed,
  disk free) and a slim sun/moon theme-toggle button to the left of the
  system Min/Max/Close controls. System buttons inherit ButtonForeground
  Color etc. from AppWindow.TitleBar so they match palette in both
  themes.

* Section header with 'Participants * count' display, filter input,
  Refresh + Presets (Secondary buttons), and 'Enable all online' as
  the single cyan Primary button - finally a real button hierarchy
  instead of seven indistinguishable ghost buttons.

* Participants list rendered as ItemsRepeater + DataTemplate for now;
  the CommunityToolkit DataGrid migration follows in a separate commit.
  Row template at 64px height with: 3px cyan left border for active
  speaker, avatar with initials in cyan-muted circle, name + codec line,
  signal lock state with dot, audio meter via ProgressBar, output name
  in JetBrains Mono, ISO state pill (LIVE/OFF/ERROR) at right.

* Conditional in-call control bar below the table: Mute / Camera /
  Share / Marker / Leave + overflow kebab. Muted state binds the
  destructive coral treatment to the Mute button; Leave is also
  destructive (coral border + text); everything else is Secondary.
  Tight 8px spacing keeps the bar dense without crowding.

* Slim 32px status bar at the bottom: control-surface URL on the left
  (cyan dot indicator), keyboard-shortcut hints on the right in
  tertiary mono. Replaces the WPF host's six-column footer.

Implementation notes:

* MockParticipant model populates the table with representative data
  (Maya / Daniel / Aicha / Sam, one as active speaker) until the
  ParticipantViewModel binding migrates over from the WPF host.

* Custom Program.cs takes ownership of Main from the XAML compiler
  (DISABLE_XAML_GENERATED_MAIN). Calls Bootstrap.TryInitialize(0x00010006)
  before Application.Start so the unpackaged .exe can locate the
  WindowsAppSDK 1.6 framework MSIX at launch. Shutdown is paired in
  a finally block.

* Theme toggle in code-behind flips Window.Content.RequestedTheme
  between Dark and Light. {ThemeResource} bindings auto-swap across
  the visual tree; system title-bar buttons (outside the XAML tree)
  get color updates inline so they stay readable in both modes.

* app.manifest deferred from build - the framework-emitted manifest
  covers DPI awareness and supportedOS GUIDs; reintroducing our own
  goes in the next commit alongside the bootstrapper hardening.

Known issue: the unpackaged .exe currently fails to activate on this
build host with 'this application could not be started' before Main
runs. Build is clean; published output runs the same way. Diagnosing
the activation failure is the next session's first task (likely the
runtimeconfig.json including Microsoft.WindowsDesktop.App which WinUI 3
doesn't want, or a missing CRT redistributable). The WPF host remains
the running build until that's resolved.

dotnet build TeamsISO.Windows.slnf -c Debug: 0 warnings, 0 errors.
2026-05-13 00:03:12 -04:00

89 lines
4.4 KiB
C#

using Microsoft.UI;
using Microsoft.UI.Windowing;
using Microsoft.UI.Xaml;
using TeamsISO.App.WinUI.Models;
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.ButtonHoverBackgroundColor = Color.FromArgb(0xFF, 0x33, 0x34, 0x3A);
AppWindow.TitleBar.ButtonPressedBackgroundColor = Color.FromArgb(0xFF, 0x3F, 0x3F, 0x47);
AppWindow.TitleBar.ButtonForegroundColor = Color.FromArgb(0xFF, 0xF4, 0xF4, 0xF6);
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();
}
/// <summary>
/// Cycle the active theme between Dark and Light. The actual swap target
/// is the window's root visual element — RequestedTheme on that element
/// propagates the {ThemeResource} swap across the whole tree, with no
/// flicker. The system title-bar buttons aren't part of the visual tree,
/// so their colors are recomputed inline.
///
/// Persistence to UIPreferences.Theme happens in the view-model wiring
/// commit (the WinUI host doesn't yet share UIPreferences with the WPF
/// host; both will once the WinUI host owns the .NET startup).
/// </summary>
private void OnThemeToggleClick(object sender, RoutedEventArgs e)
{
if (Content is FrameworkElement root)
{
var next = root.ActualTheme == ElementTheme.Dark
? ElementTheme.Light
: ElementTheme.Dark;
root.RequestedTheme = next;
// Title-bar system buttons read from AppWindow.TitleBar directly,
// not from the XAML resource dictionary, so we set them inline
// for both themes. Dark-mode chrome lands on near-white glyphs;
// light-mode chrome lands on near-black glyphs.
if (next == ElementTheme.Dark)
{
AppWindow.TitleBar.ButtonForegroundColor = Color.FromArgb(0xFF, 0xF4, 0xF4, 0xF6);
AppWindow.TitleBar.ButtonHoverBackgroundColor = Color.FromArgb(0xFF, 0x33, 0x34, 0x3A);
AppWindow.TitleBar.ButtonPressedBackgroundColor = Color.FromArgb(0xFF, 0x3F, 0x3F, 0x47);
ThemeToggleIcon.Glyph = ""; // sun
}
else
{
AppWindow.TitleBar.ButtonForegroundColor = Color.FromArgb(0xFF, 0x0A, 0x0A, 0x0A);
AppWindow.TitleBar.ButtonHoverBackgroundColor = Color.FromArgb(0xFF, 0xEC, 0xEE, 0xF1);
AppWindow.TitleBar.ButtonPressedBackgroundColor = Color.FromArgb(0xFF, 0xE0, 0xE3, 0xE7);
ThemeToggleIcon.Glyph = ""; // moon
}
}
}
}