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
|
|
|
using Microsoft.UI;
|
|
|
|
|
using Microsoft.UI.Windowing;
|
feat(winui3): scaffold TeamsISO.App.WinUI alongside the WPF host
First step of the WinUI 3 replatform per the approved redesign brief.
The new project coexists with the existing src/TeamsISO.App (WPF) so the
WPF host keeps building and shipping while the WinUI 3 redesign lands
incrementally. Once the WinUI 3 build is feature-complete and tested
against a real Teams meeting, the WPF project is retired.
Scaffold contents:
* src/TeamsISO.App.WinUI/TeamsISO.App.WinUI.csproj
Windows App SDK 1.6 LTS (250602001), unpackaged mode
(WindowsPackageType=None) so the existing MSI installer keeps working.
Target framework net8.0-windows10.0.19041.0, min platform 10.0.17763.0
to preserve Win10 1809+ compatibility for working broadcast hardware.
Pins WindowsSdkPackageVersion=10.0.19041.38 so .NET SDK 8.0.301 builds
cleanly without an SDK upgrade on the build host.
* src/TeamsISO.App.WinUI/app.manifest
PerMonitorV2 DPI awareness + gdiScaling for crisp text on high-DPI
broadcast monitors. asInvoker trust level (control surface :9755 and
OSC :9000 bind to 127.0.0.1, no admin needed).
* App.xaml + App.xaml.cs
Minimal startup: brings up MainWindow. The full pipeline (NDI runtime
preflight, IsoController wiring, single-instance mutex, REST + OSC
bridge, tray icon, crash diagnostics, auto-update banner, onboarding)
migrates in subsequent commits.
* Themes/Tokens.xaml
Wild Dragon design tokens as ThemeDictionary entries (Default = Dark,
Light). Colors as Color resources, Brushes paired per theme so
{ThemeResource} auto-swaps when RequestedTheme flips — no app restart,
no flicker. Spacing/radii/typography tokens are theme-agnostic at the
outer level. Light palette maintains brand recognition via cyan-tinted
off-whites (#FAFAFB canvas, #F0F1F3 rail) rather than pure white, and
splits cyan into accent.cyan.surface (#97EDF0, works in both modes
because text on top is near-black) and accent.cyan.text (#97EDF0 dark
/ #0E7C82 light) so captions and inline labels keep AA contrast.
* Themes/Controls.xaml
Button hierarchy with real commitments: Primary (cyan fill, one per
surface), Secondary (transparent bordered), Tertiary (text only),
Destructive (coral border + text), Caption (titlebar), RailIcon.
Typographic ramp (Display / Title / Heading / Body / Subtle / Caption
/ Mono) at the DESIGN.md 1.25 ratio.
* CommunityToolkit.WinUI.UI.Controls.DataGrid 7.1.2 referenced for the
participants table migration. (Toolkit 8.x dropped DataGrid; 7.x is
the only currently-maintained free option for WinUI 3.)
* Inter.ttf + JetBrainsMono.ttf + dragon-mark.png + teamsiso.ico copied
from the WPF project's Assets/ so the WinUI 3 host is self-contained.
* TeamsISO.sln + TeamsISO.Windows.slnf updated to include the new
project. The .slnf paths switch to backslash form so MSBuild can match
them against the .sln's canonical path representation.
Verified: dotnet build TeamsISO.Windows.slnf -c Debug succeeds with 0
warnings and 0 errors for all 8 projects (WPF host, WinUI 3 host, engine,
NDI interop, console, three test projects).
2026-05-12 23:52:35 -04:00
|
|
|
using Microsoft.UI.Xaml;
|
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
|
|
|
using TeamsISO.App.WinUI.Services;
|
feat(winui3): engine wired — discovers Teams participants live
The WinUI 3 host now stands up the full engine pipeline on launch and
discovers participants from the operator's live Teams meeting. Verified
end-to-end against a real call: window opened, NDI runtime preflight
passed, IsoController spun up, and Participants observable yielded
three live entries ((Local), Active Speaker, Brendon Power) with their
TEAMSISO_<name> output names in the redesigned shell.
What this commit lands:
ViewModels/ (slim ports of the WPF host's view-models — engine layer
shared verbatim via ProjectReference):
* ObservableObject.cs — INPC base, mirrors the WPF version
* RelayCommand.cs — sync + typed + async variants; ICommand is the
same shared type across both hosts (System.ObjectModel.dll)
* ParticipantViewModel.cs — DisplayName / Initials / SourceCodec /
IsoStateLabel / DisplayedAudioLevel / IsActiveSpeaker / IsOnline /
OutputName + ToggleIsoCommand. Drops the WPF-specific thumbnail
WriteableBitmap path, clipboard, PreviewWindow, snapshot encoder —
those come back when the WinUI imaging pipeline is wired (Phase 5
of the migration plan).
* MainViewModel.cs — subscribes to IIsoController.Participants on a
DispatcherQueueSynchronizationContext, owns the ObservableCollection,
runs a 1Hz DispatcherQueueTimer for stats + active-speaker
highlight + session-elapsed text. Commands: EnableAllOnline,
StopAllIsos, RefreshDiscovery, DropRecordingMarker, ToggleByIndex.
App.xaml.cs:
* OnLaunched brings up MainWindow first, then fires WireEngineAsync
so the user sees the shell immediately while NDI preflight + engine
setup proceed.
* Full pipeline: EngineLogging → NdiInteropPInvoke (with friendly
fallback message if the NDI runtime isn't installed) → ConfigStore
at %APPDATA%\TeamsISO\config.json → NdiRuntimeProbe + scaler →
IsoPipeline factory → IsoController → MainViewModel →
MainWindow.AttachViewModel → IsoController.StartAsync.
* Logger writes to the same %LOCALAPPDATA%\TeamsISO\Logs as the WPF
host so a mixed-host operator sees a single timeline.
MainWindow.xaml + .xaml.cs:
* x:Name on the section header buttons (RefreshButton, StopAllButton,
EnableAllButton, MarkerButton) and on the status bar text + the
ParticipantsHost grid.
* AttachViewModel wires those buttons to view-model commands; pushes
StatusText + ParticipantCountText through PropertyChanged.
* BuildSimpleRow imperatively constructs each row (Grid with name +
codec + output + ISO toggle pill) instead of going through a
DataTemplate. Rationale: declaring a DataTemplate in
Grid.Resources OR loading one via XamlReader.Load both crash WinUI
3's XAML parser at runtime on this build host (same HR=0x802b000a
we saw with the SettingsDrawer NavigationView). Imperative
construction sidesteps the parser. The rich row template (avatar
circle, audio meter, active-speaker accent) returns in Phase 5
alongside the CommunityToolkit DataGrid swap.
* Per-row PropertyChanged subscriptions refresh DisplayName, codec,
output name, and ISO pill text as the engine pushes updates.
Verified live (PID 4824, 2026-05-13 08:02): three real Teams
participants appeared in the redesigned shell within seconds of launch,
status bar populated with "3 participants · 0 routing", section
header showed "Participants 3", and the title-bar live pills rendered
in their proper places (placeholder values for now; binding to
SessionElapsed / IsRecording lands in the next commit).
2026-05-13 08:03:32 -04:00
|
|
|
using TeamsISO.App.WinUI.ViewModels;
|
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
|
|
|
using Windows.Graphics;
|
|
|
|
|
using Windows.UI;
|
feat(winui3): scaffold TeamsISO.App.WinUI alongside the WPF host
First step of the WinUI 3 replatform per the approved redesign brief.
The new project coexists with the existing src/TeamsISO.App (WPF) so the
WPF host keeps building and shipping while the WinUI 3 redesign lands
incrementally. Once the WinUI 3 build is feature-complete and tested
against a real Teams meeting, the WPF project is retired.
Scaffold contents:
* src/TeamsISO.App.WinUI/TeamsISO.App.WinUI.csproj
Windows App SDK 1.6 LTS (250602001), unpackaged mode
(WindowsPackageType=None) so the existing MSI installer keeps working.
Target framework net8.0-windows10.0.19041.0, min platform 10.0.17763.0
to preserve Win10 1809+ compatibility for working broadcast hardware.
Pins WindowsSdkPackageVersion=10.0.19041.38 so .NET SDK 8.0.301 builds
cleanly without an SDK upgrade on the build host.
* src/TeamsISO.App.WinUI/app.manifest
PerMonitorV2 DPI awareness + gdiScaling for crisp text on high-DPI
broadcast monitors. asInvoker trust level (control surface :9755 and
OSC :9000 bind to 127.0.0.1, no admin needed).
* App.xaml + App.xaml.cs
Minimal startup: brings up MainWindow. The full pipeline (NDI runtime
preflight, IsoController wiring, single-instance mutex, REST + OSC
bridge, tray icon, crash diagnostics, auto-update banner, onboarding)
migrates in subsequent commits.
* Themes/Tokens.xaml
Wild Dragon design tokens as ThemeDictionary entries (Default = Dark,
Light). Colors as Color resources, Brushes paired per theme so
{ThemeResource} auto-swaps when RequestedTheme flips — no app restart,
no flicker. Spacing/radii/typography tokens are theme-agnostic at the
outer level. Light palette maintains brand recognition via cyan-tinted
off-whites (#FAFAFB canvas, #F0F1F3 rail) rather than pure white, and
splits cyan into accent.cyan.surface (#97EDF0, works in both modes
because text on top is near-black) and accent.cyan.text (#97EDF0 dark
/ #0E7C82 light) so captions and inline labels keep AA contrast.
* Themes/Controls.xaml
Button hierarchy with real commitments: Primary (cyan fill, one per
surface), Secondary (transparent bordered), Tertiary (text only),
Destructive (coral border + text), Caption (titlebar), RailIcon.
Typographic ramp (Display / Title / Heading / Body / Subtle / Caption
/ Mono) at the DESIGN.md 1.25 ratio.
* CommunityToolkit.WinUI.UI.Controls.DataGrid 7.1.2 referenced for the
participants table migration. (Toolkit 8.x dropped DataGrid; 7.x is
the only currently-maintained free option for WinUI 3.)
* Inter.ttf + JetBrainsMono.ttf + dragon-mark.png + teamsiso.ico copied
from the WPF project's Assets/ so the WinUI 3 host is self-contained.
* TeamsISO.sln + TeamsISO.Windows.slnf updated to include the new
project. The .slnf paths switch to backslash form so MSBuild can match
them against the .sln's canonical path representation.
Verified: dotnet build TeamsISO.Windows.slnf -c Debug succeeds with 0
warnings and 0 errors for all 8 projects (WPF host, WinUI 3 host, engine,
NDI interop, console, three test projects).
2026-05-12 23:52:35 -04:00
|
|
|
|
|
|
|
|
namespace TeamsISO.App.WinUI.Views;
|
|
|
|
|
|
|
|
|
|
public sealed partial class MainWindow : Window
|
|
|
|
|
{
|
feat(winui3): engine wired — discovers Teams participants live
The WinUI 3 host now stands up the full engine pipeline on launch and
discovers participants from the operator's live Teams meeting. Verified
end-to-end against a real call: window opened, NDI runtime preflight
passed, IsoController spun up, and Participants observable yielded
three live entries ((Local), Active Speaker, Brendon Power) with their
TEAMSISO_<name> output names in the redesigned shell.
What this commit lands:
ViewModels/ (slim ports of the WPF host's view-models — engine layer
shared verbatim via ProjectReference):
* ObservableObject.cs — INPC base, mirrors the WPF version
* RelayCommand.cs — sync + typed + async variants; ICommand is the
same shared type across both hosts (System.ObjectModel.dll)
* ParticipantViewModel.cs — DisplayName / Initials / SourceCodec /
IsoStateLabel / DisplayedAudioLevel / IsActiveSpeaker / IsOnline /
OutputName + ToggleIsoCommand. Drops the WPF-specific thumbnail
WriteableBitmap path, clipboard, PreviewWindow, snapshot encoder —
those come back when the WinUI imaging pipeline is wired (Phase 5
of the migration plan).
* MainViewModel.cs — subscribes to IIsoController.Participants on a
DispatcherQueueSynchronizationContext, owns the ObservableCollection,
runs a 1Hz DispatcherQueueTimer for stats + active-speaker
highlight + session-elapsed text. Commands: EnableAllOnline,
StopAllIsos, RefreshDiscovery, DropRecordingMarker, ToggleByIndex.
App.xaml.cs:
* OnLaunched brings up MainWindow first, then fires WireEngineAsync
so the user sees the shell immediately while NDI preflight + engine
setup proceed.
* Full pipeline: EngineLogging → NdiInteropPInvoke (with friendly
fallback message if the NDI runtime isn't installed) → ConfigStore
at %APPDATA%\TeamsISO\config.json → NdiRuntimeProbe + scaler →
IsoPipeline factory → IsoController → MainViewModel →
MainWindow.AttachViewModel → IsoController.StartAsync.
* Logger writes to the same %LOCALAPPDATA%\TeamsISO\Logs as the WPF
host so a mixed-host operator sees a single timeline.
MainWindow.xaml + .xaml.cs:
* x:Name on the section header buttons (RefreshButton, StopAllButton,
EnableAllButton, MarkerButton) and on the status bar text + the
ParticipantsHost grid.
* AttachViewModel wires those buttons to view-model commands; pushes
StatusText + ParticipantCountText through PropertyChanged.
* BuildSimpleRow imperatively constructs each row (Grid with name +
codec + output + ISO toggle pill) instead of going through a
DataTemplate. Rationale: declaring a DataTemplate in
Grid.Resources OR loading one via XamlReader.Load both crash WinUI
3's XAML parser at runtime on this build host (same HR=0x802b000a
we saw with the SettingsDrawer NavigationView). Imperative
construction sidesteps the parser. The rich row template (avatar
circle, audio meter, active-speaker accent) returns in Phase 5
alongside the CommunityToolkit DataGrid swap.
* Per-row PropertyChanged subscriptions refresh DisplayName, codec,
output name, and ISO pill text as the engine pushes updates.
Verified live (PID 4824, 2026-05-13 08:02): three real Teams
participants appeared in the redesigned shell within seconds of launch,
status bar populated with "3 participants · 0 routing", section
header showed "Participants 3", and the title-bar live pills rendered
in their proper places (placeholder values for now; binding to
SessionElapsed / IsRecording lands in the next commit).
2026-05-13 08:03:32 -04:00
|
|
|
private MainViewModel? _viewModel;
|
|
|
|
|
|
feat(winui3): scaffold TeamsISO.App.WinUI alongside the WPF host
First step of the WinUI 3 replatform per the approved redesign brief.
The new project coexists with the existing src/TeamsISO.App (WPF) so the
WPF host keeps building and shipping while the WinUI 3 redesign lands
incrementally. Once the WinUI 3 build is feature-complete and tested
against a real Teams meeting, the WPF project is retired.
Scaffold contents:
* src/TeamsISO.App.WinUI/TeamsISO.App.WinUI.csproj
Windows App SDK 1.6 LTS (250602001), unpackaged mode
(WindowsPackageType=None) so the existing MSI installer keeps working.
Target framework net8.0-windows10.0.19041.0, min platform 10.0.17763.0
to preserve Win10 1809+ compatibility for working broadcast hardware.
Pins WindowsSdkPackageVersion=10.0.19041.38 so .NET SDK 8.0.301 builds
cleanly without an SDK upgrade on the build host.
* src/TeamsISO.App.WinUI/app.manifest
PerMonitorV2 DPI awareness + gdiScaling for crisp text on high-DPI
broadcast monitors. asInvoker trust level (control surface :9755 and
OSC :9000 bind to 127.0.0.1, no admin needed).
* App.xaml + App.xaml.cs
Minimal startup: brings up MainWindow. The full pipeline (NDI runtime
preflight, IsoController wiring, single-instance mutex, REST + OSC
bridge, tray icon, crash diagnostics, auto-update banner, onboarding)
migrates in subsequent commits.
* Themes/Tokens.xaml
Wild Dragon design tokens as ThemeDictionary entries (Default = Dark,
Light). Colors as Color resources, Brushes paired per theme so
{ThemeResource} auto-swaps when RequestedTheme flips — no app restart,
no flicker. Spacing/radii/typography tokens are theme-agnostic at the
outer level. Light palette maintains brand recognition via cyan-tinted
off-whites (#FAFAFB canvas, #F0F1F3 rail) rather than pure white, and
splits cyan into accent.cyan.surface (#97EDF0, works in both modes
because text on top is near-black) and accent.cyan.text (#97EDF0 dark
/ #0E7C82 light) so captions and inline labels keep AA contrast.
* Themes/Controls.xaml
Button hierarchy with real commitments: Primary (cyan fill, one per
surface), Secondary (transparent bordered), Tertiary (text only),
Destructive (coral border + text), Caption (titlebar), RailIcon.
Typographic ramp (Display / Title / Heading / Body / Subtle / Caption
/ Mono) at the DESIGN.md 1.25 ratio.
* CommunityToolkit.WinUI.UI.Controls.DataGrid 7.1.2 referenced for the
participants table migration. (Toolkit 8.x dropped DataGrid; 7.x is
the only currently-maintained free option for WinUI 3.)
* Inter.ttf + JetBrainsMono.ttf + dragon-mark.png + teamsiso.ico copied
from the WPF project's Assets/ so the WinUI 3 host is self-contained.
* TeamsISO.sln + TeamsISO.Windows.slnf updated to include the new
project. The .slnf paths switch to backslash form so MSBuild can match
them against the .sln's canonical path representation.
Verified: dotnet build TeamsISO.Windows.slnf -c Debug succeeds with 0
warnings and 0 errors for all 8 projects (WPF host, WinUI 3 host, engine,
NDI interop, console, three test projects).
2026-05-12 23:52:35 -04:00
|
|
|
public MainWindow()
|
|
|
|
|
{
|
|
|
|
|
InitializeComponent();
|
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
|
|
|
|
feat(winui3): scaffold TeamsISO.App.WinUI alongside the WPF host
First step of the WinUI 3 replatform per the approved redesign brief.
The new project coexists with the existing src/TeamsISO.App (WPF) so the
WPF host keeps building and shipping while the WinUI 3 redesign lands
incrementally. Once the WinUI 3 build is feature-complete and tested
against a real Teams meeting, the WPF project is retired.
Scaffold contents:
* src/TeamsISO.App.WinUI/TeamsISO.App.WinUI.csproj
Windows App SDK 1.6 LTS (250602001), unpackaged mode
(WindowsPackageType=None) so the existing MSI installer keeps working.
Target framework net8.0-windows10.0.19041.0, min platform 10.0.17763.0
to preserve Win10 1809+ compatibility for working broadcast hardware.
Pins WindowsSdkPackageVersion=10.0.19041.38 so .NET SDK 8.0.301 builds
cleanly without an SDK upgrade on the build host.
* src/TeamsISO.App.WinUI/app.manifest
PerMonitorV2 DPI awareness + gdiScaling for crisp text on high-DPI
broadcast monitors. asInvoker trust level (control surface :9755 and
OSC :9000 bind to 127.0.0.1, no admin needed).
* App.xaml + App.xaml.cs
Minimal startup: brings up MainWindow. The full pipeline (NDI runtime
preflight, IsoController wiring, single-instance mutex, REST + OSC
bridge, tray icon, crash diagnostics, auto-update banner, onboarding)
migrates in subsequent commits.
* Themes/Tokens.xaml
Wild Dragon design tokens as ThemeDictionary entries (Default = Dark,
Light). Colors as Color resources, Brushes paired per theme so
{ThemeResource} auto-swaps when RequestedTheme flips — no app restart,
no flicker. Spacing/radii/typography tokens are theme-agnostic at the
outer level. Light palette maintains brand recognition via cyan-tinted
off-whites (#FAFAFB canvas, #F0F1F3 rail) rather than pure white, and
splits cyan into accent.cyan.surface (#97EDF0, works in both modes
because text on top is near-black) and accent.cyan.text (#97EDF0 dark
/ #0E7C82 light) so captions and inline labels keep AA contrast.
* Themes/Controls.xaml
Button hierarchy with real commitments: Primary (cyan fill, one per
surface), Secondary (transparent bordered), Tertiary (text only),
Destructive (coral border + text), Caption (titlebar), RailIcon.
Typographic ramp (Display / Title / Heading / Body / Subtle / Caption
/ Mono) at the DESIGN.md 1.25 ratio.
* CommunityToolkit.WinUI.UI.Controls.DataGrid 7.1.2 referenced for the
participants table migration. (Toolkit 8.x dropped DataGrid; 7.x is
the only currently-maintained free option for WinUI 3.)
* Inter.ttf + JetBrainsMono.ttf + dragon-mark.png + teamsiso.ico copied
from the WPF project's Assets/ so the WinUI 3 host is self-contained.
* TeamsISO.sln + TeamsISO.Windows.slnf updated to include the new
project. The .slnf paths switch to backslash form so MSBuild can match
them against the .sln's canonical path representation.
Verified: dotnet build TeamsISO.Windows.slnf -c Debug succeeds with 0
warnings and 0 errors for all 8 projects (WPF host, WinUI 3 host, engine,
NDI interop, console, three test projects).
2026-05-12 23:52:35 -04:00
|
|
|
Title = "TeamsISO";
|
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
|
|
|
|
|
|
|
|
ExtendsContentIntoTitleBar = true;
|
|
|
|
|
SetTitleBar(AppTitleBar);
|
|
|
|
|
|
|
|
|
|
AppWindow.TitleBar.ButtonBackgroundColor = Colors.Transparent;
|
|
|
|
|
AppWindow.TitleBar.ButtonInactiveBackgroundColor = Colors.Transparent;
|
|
|
|
|
AppWindow.TitleBar.ButtonHoverForegroundColor = Colors.White;
|
|
|
|
|
|
|
|
|
|
AppWindow.Resize(new SizeInt32(1280, 780));
|
|
|
|
|
|
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
|
|
|
ThemeManager.Current.Themed += (_, theme) => ApplyResolvedTheme(theme);
|
|
|
|
|
ApplyResolvedTheme(ThemeManager.Current.ResolveTheme());
|
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
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
feat(winui3): engine wired — discovers Teams participants live
The WinUI 3 host now stands up the full engine pipeline on launch and
discovers participants from the operator's live Teams meeting. Verified
end-to-end against a real call: window opened, NDI runtime preflight
passed, IsoController spun up, and Participants observable yielded
three live entries ((Local), Active Speaker, Brendon Power) with their
TEAMSISO_<name> output names in the redesigned shell.
What this commit lands:
ViewModels/ (slim ports of the WPF host's view-models — engine layer
shared verbatim via ProjectReference):
* ObservableObject.cs — INPC base, mirrors the WPF version
* RelayCommand.cs — sync + typed + async variants; ICommand is the
same shared type across both hosts (System.ObjectModel.dll)
* ParticipantViewModel.cs — DisplayName / Initials / SourceCodec /
IsoStateLabel / DisplayedAudioLevel / IsActiveSpeaker / IsOnline /
OutputName + ToggleIsoCommand. Drops the WPF-specific thumbnail
WriteableBitmap path, clipboard, PreviewWindow, snapshot encoder —
those come back when the WinUI imaging pipeline is wired (Phase 5
of the migration plan).
* MainViewModel.cs — subscribes to IIsoController.Participants on a
DispatcherQueueSynchronizationContext, owns the ObservableCollection,
runs a 1Hz DispatcherQueueTimer for stats + active-speaker
highlight + session-elapsed text. Commands: EnableAllOnline,
StopAllIsos, RefreshDiscovery, DropRecordingMarker, ToggleByIndex.
App.xaml.cs:
* OnLaunched brings up MainWindow first, then fires WireEngineAsync
so the user sees the shell immediately while NDI preflight + engine
setup proceed.
* Full pipeline: EngineLogging → NdiInteropPInvoke (with friendly
fallback message if the NDI runtime isn't installed) → ConfigStore
at %APPDATA%\TeamsISO\config.json → NdiRuntimeProbe + scaler →
IsoPipeline factory → IsoController → MainViewModel →
MainWindow.AttachViewModel → IsoController.StartAsync.
* Logger writes to the same %LOCALAPPDATA%\TeamsISO\Logs as the WPF
host so a mixed-host operator sees a single timeline.
MainWindow.xaml + .xaml.cs:
* x:Name on the section header buttons (RefreshButton, StopAllButton,
EnableAllButton, MarkerButton) and on the status bar text + the
ParticipantsHost grid.
* AttachViewModel wires those buttons to view-model commands; pushes
StatusText + ParticipantCountText through PropertyChanged.
* BuildSimpleRow imperatively constructs each row (Grid with name +
codec + output + ISO toggle pill) instead of going through a
DataTemplate. Rationale: declaring a DataTemplate in
Grid.Resources OR loading one via XamlReader.Load both crash WinUI
3's XAML parser at runtime on this build host (same HR=0x802b000a
we saw with the SettingsDrawer NavigationView). Imperative
construction sidesteps the parser. The rich row template (avatar
circle, audio meter, active-speaker accent) returns in Phase 5
alongside the CommunityToolkit DataGrid swap.
* Per-row PropertyChanged subscriptions refresh DisplayName, codec,
output name, and ISO pill text as the engine pushes updates.
Verified live (PID 4824, 2026-05-13 08:02): three real Teams
participants appeared in the redesigned shell within seconds of launch,
status bar populated with "3 participants · 0 routing", section
header showed "Participants 3", and the title-bar live pills rendered
in their proper places (placeholder values for now; binding to
SessionElapsed / IsRecording lands in the next commit).
2026-05-13 08:03:32 -04:00
|
|
|
/// Hook the engine view-model in. Replaces the placeholder StackPanel
|
|
|
|
|
/// inside ParticipantsHost with a live ListView. Rather than fight WinUI
|
|
|
|
|
/// 3's DataTemplate compilation, we subscribe to the Participants
|
|
|
|
|
/// collection and rebuild a simple StackPanel of row controls on
|
|
|
|
|
/// every change. Less efficient than a virtualized ListView for huge
|
|
|
|
|
/// lists, fine for the operator's ~10 max participants.
|
|
|
|
|
/// </summary>
|
|
|
|
|
public void AttachViewModel(MainViewModel viewModel)
|
|
|
|
|
{
|
|
|
|
|
_viewModel = viewModel;
|
|
|
|
|
|
|
|
|
|
// Section header + in-call buttons → view-model commands.
|
|
|
|
|
// The buttons exist in MainWindow.xaml with the matching x:Names.
|
|
|
|
|
RefreshButton.Command = viewModel.RefreshDiscoveryCommand;
|
|
|
|
|
EnableAllButton.Command = viewModel.EnableAllOnlineCommand;
|
|
|
|
|
StopAllButton.Command = viewModel.StopAllIsosCommand;
|
|
|
|
|
MarkerButton.Command = viewModel.DropRecordingMarkerCommand;
|
|
|
|
|
|
|
|
|
|
// Status bar + participant count text refresh on VM property changes.
|
|
|
|
|
ParticipantCountText.Text = viewModel.ParticipantCountText;
|
|
|
|
|
StatusBarText.Text = viewModel.StatusText;
|
|
|
|
|
viewModel.PropertyChanged += (_, e) =>
|
|
|
|
|
{
|
|
|
|
|
DispatcherQueue.TryEnqueue(() =>
|
|
|
|
|
{
|
|
|
|
|
switch (e.PropertyName)
|
|
|
|
|
{
|
|
|
|
|
case nameof(MainViewModel.ParticipantCountText):
|
|
|
|
|
ParticipantCountText.Text = viewModel.ParticipantCountText;
|
|
|
|
|
break;
|
|
|
|
|
case nameof(MainViewModel.StatusText):
|
|
|
|
|
StatusBarText.Text = viewModel.StatusText;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
ParticipantsHost.Children.Clear();
|
|
|
|
|
var stack = new Microsoft.UI.Xaml.Controls.StackPanel
|
|
|
|
|
{
|
|
|
|
|
HorizontalAlignment = HorizontalAlignment.Stretch,
|
|
|
|
|
VerticalAlignment = VerticalAlignment.Top,
|
|
|
|
|
};
|
|
|
|
|
var scroll = new Microsoft.UI.Xaml.Controls.ScrollViewer
|
|
|
|
|
{
|
|
|
|
|
VerticalScrollBarVisibility = Microsoft.UI.Xaml.Controls.ScrollBarVisibility.Auto,
|
|
|
|
|
Content = stack,
|
|
|
|
|
};
|
|
|
|
|
ParticipantsHost.Children.Add(scroll);
|
|
|
|
|
|
|
|
|
|
void Rebuild()
|
|
|
|
|
{
|
|
|
|
|
stack.Children.Clear();
|
|
|
|
|
foreach (var p in viewModel.Participants)
|
|
|
|
|
{
|
|
|
|
|
stack.Children.Add(BuildSimpleRow(p));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
viewModel.Participants.CollectionChanged += (_, _) =>
|
|
|
|
|
{
|
|
|
|
|
DispatcherQueue.TryEnqueue(Rebuild);
|
|
|
|
|
};
|
|
|
|
|
Rebuild();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Minimal participant row — name + ISO state + toggle button. Drops
|
|
|
|
|
/// the brushed avatar / theme-resource lookups that may have been
|
|
|
|
|
/// triggering the crash. The full visual row template comes back
|
|
|
|
|
/// after we've verified the binding path works.
|
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
|
|
|
/// </summary>
|
feat(winui3): engine wired — discovers Teams participants live
The WinUI 3 host now stands up the full engine pipeline on launch and
discovers participants from the operator's live Teams meeting. Verified
end-to-end against a real call: window opened, NDI runtime preflight
passed, IsoController spun up, and Participants observable yielded
three live entries ((Local), Active Speaker, Brendon Power) with their
TEAMSISO_<name> output names in the redesigned shell.
What this commit lands:
ViewModels/ (slim ports of the WPF host's view-models — engine layer
shared verbatim via ProjectReference):
* ObservableObject.cs — INPC base, mirrors the WPF version
* RelayCommand.cs — sync + typed + async variants; ICommand is the
same shared type across both hosts (System.ObjectModel.dll)
* ParticipantViewModel.cs — DisplayName / Initials / SourceCodec /
IsoStateLabel / DisplayedAudioLevel / IsActiveSpeaker / IsOnline /
OutputName + ToggleIsoCommand. Drops the WPF-specific thumbnail
WriteableBitmap path, clipboard, PreviewWindow, snapshot encoder —
those come back when the WinUI imaging pipeline is wired (Phase 5
of the migration plan).
* MainViewModel.cs — subscribes to IIsoController.Participants on a
DispatcherQueueSynchronizationContext, owns the ObservableCollection,
runs a 1Hz DispatcherQueueTimer for stats + active-speaker
highlight + session-elapsed text. Commands: EnableAllOnline,
StopAllIsos, RefreshDiscovery, DropRecordingMarker, ToggleByIndex.
App.xaml.cs:
* OnLaunched brings up MainWindow first, then fires WireEngineAsync
so the user sees the shell immediately while NDI preflight + engine
setup proceed.
* Full pipeline: EngineLogging → NdiInteropPInvoke (with friendly
fallback message if the NDI runtime isn't installed) → ConfigStore
at %APPDATA%\TeamsISO\config.json → NdiRuntimeProbe + scaler →
IsoPipeline factory → IsoController → MainViewModel →
MainWindow.AttachViewModel → IsoController.StartAsync.
* Logger writes to the same %LOCALAPPDATA%\TeamsISO\Logs as the WPF
host so a mixed-host operator sees a single timeline.
MainWindow.xaml + .xaml.cs:
* x:Name on the section header buttons (RefreshButton, StopAllButton,
EnableAllButton, MarkerButton) and on the status bar text + the
ParticipantsHost grid.
* AttachViewModel wires those buttons to view-model commands; pushes
StatusText + ParticipantCountText through PropertyChanged.
* BuildSimpleRow imperatively constructs each row (Grid with name +
codec + output + ISO toggle pill) instead of going through a
DataTemplate. Rationale: declaring a DataTemplate in
Grid.Resources OR loading one via XamlReader.Load both crash WinUI
3's XAML parser at runtime on this build host (same HR=0x802b000a
we saw with the SettingsDrawer NavigationView). Imperative
construction sidesteps the parser. The rich row template (avatar
circle, audio meter, active-speaker accent) returns in Phase 5
alongside the CommunityToolkit DataGrid swap.
* Per-row PropertyChanged subscriptions refresh DisplayName, codec,
output name, and ISO pill text as the engine pushes updates.
Verified live (PID 4824, 2026-05-13 08:02): three real Teams
participants appeared in the redesigned shell within seconds of launch,
status bar populated with "3 participants · 0 routing", section
header showed "Participants 3", and the title-bar live pills rendered
in their proper places (placeholder values for now; binding to
SessionElapsed / IsRecording lands in the next commit).
2026-05-13 08:03:32 -04:00
|
|
|
private static Microsoft.UI.Xaml.Controls.Grid BuildSimpleRow(ParticipantViewModel p)
|
|
|
|
|
{
|
|
|
|
|
var grid = new Microsoft.UI.Xaml.Controls.Grid
|
|
|
|
|
{
|
|
|
|
|
Height = 56,
|
|
|
|
|
Padding = new Thickness(20, 0, 20, 0),
|
|
|
|
|
};
|
|
|
|
|
grid.ColumnDefinitions.Add(new Microsoft.UI.Xaml.Controls.ColumnDefinition { Width = new Microsoft.UI.Xaml.GridLength(1, Microsoft.UI.Xaml.GridUnitType.Star) });
|
|
|
|
|
grid.ColumnDefinitions.Add(new Microsoft.UI.Xaml.Controls.ColumnDefinition { Width = new Microsoft.UI.Xaml.GridLength(120) });
|
|
|
|
|
grid.ColumnDefinitions.Add(new Microsoft.UI.Xaml.Controls.ColumnDefinition { Width = Microsoft.UI.Xaml.GridLength.Auto });
|
|
|
|
|
|
|
|
|
|
var nameStack = new Microsoft.UI.Xaml.Controls.StackPanel
|
|
|
|
|
{
|
|
|
|
|
VerticalAlignment = VerticalAlignment.Center,
|
|
|
|
|
Spacing = 2,
|
|
|
|
|
};
|
|
|
|
|
var nameText = new Microsoft.UI.Xaml.Controls.TextBlock
|
|
|
|
|
{
|
|
|
|
|
Text = p.DisplayName,
|
|
|
|
|
FontSize = 14,
|
|
|
|
|
FontWeight = Microsoft.UI.Text.FontWeights.Medium,
|
|
|
|
|
};
|
|
|
|
|
var codecText = new Microsoft.UI.Xaml.Controls.TextBlock
|
|
|
|
|
{
|
|
|
|
|
Text = p.SourceCodec,
|
|
|
|
|
FontSize = 11,
|
|
|
|
|
Opacity = 0.6,
|
|
|
|
|
};
|
|
|
|
|
nameStack.Children.Add(nameText);
|
|
|
|
|
nameStack.Children.Add(codecText);
|
|
|
|
|
Microsoft.UI.Xaml.Controls.Grid.SetColumn(nameStack, 0);
|
|
|
|
|
grid.Children.Add(nameStack);
|
|
|
|
|
|
|
|
|
|
var outputText = new Microsoft.UI.Xaml.Controls.TextBlock
|
|
|
|
|
{
|
|
|
|
|
Text = p.OutputName,
|
|
|
|
|
FontFamily = new Microsoft.UI.Xaml.Media.FontFamily("Consolas"),
|
|
|
|
|
FontSize = 12,
|
|
|
|
|
VerticalAlignment = VerticalAlignment.Center,
|
|
|
|
|
};
|
|
|
|
|
Microsoft.UI.Xaml.Controls.Grid.SetColumn(outputText, 1);
|
|
|
|
|
grid.Children.Add(outputText);
|
|
|
|
|
|
|
|
|
|
var pillText = new Microsoft.UI.Xaml.Controls.TextBlock
|
|
|
|
|
{
|
|
|
|
|
Text = p.IsoStateLabel,
|
|
|
|
|
FontSize = 11,
|
|
|
|
|
FontWeight = Microsoft.UI.Text.FontWeights.SemiBold,
|
|
|
|
|
HorizontalAlignment = HorizontalAlignment.Center,
|
|
|
|
|
};
|
|
|
|
|
var pill = new Microsoft.UI.Xaml.Controls.Button
|
|
|
|
|
{
|
|
|
|
|
Command = p.ToggleIsoCommand,
|
|
|
|
|
MinWidth = 80,
|
|
|
|
|
Padding = new Thickness(14, 6, 14, 6),
|
|
|
|
|
CornerRadius = new CornerRadius(999),
|
|
|
|
|
VerticalAlignment = VerticalAlignment.Center,
|
|
|
|
|
Content = pillText,
|
|
|
|
|
};
|
|
|
|
|
Microsoft.UI.Xaml.Controls.Grid.SetColumn(pill, 2);
|
|
|
|
|
grid.Children.Add(pill);
|
|
|
|
|
|
|
|
|
|
p.PropertyChanged += (_, e) =>
|
|
|
|
|
{
|
|
|
|
|
grid.DispatcherQueue.TryEnqueue(() =>
|
|
|
|
|
{
|
|
|
|
|
switch (e.PropertyName)
|
|
|
|
|
{
|
|
|
|
|
case nameof(ParticipantViewModel.DisplayName):
|
|
|
|
|
nameText.Text = p.DisplayName;
|
|
|
|
|
break;
|
|
|
|
|
case nameof(ParticipantViewModel.SourceCodec):
|
|
|
|
|
codecText.Text = p.SourceCodec;
|
|
|
|
|
break;
|
|
|
|
|
case nameof(ParticipantViewModel.OutputName):
|
|
|
|
|
outputText.Text = p.OutputName;
|
|
|
|
|
break;
|
|
|
|
|
case nameof(ParticipantViewModel.IsoStateLabel):
|
|
|
|
|
case nameof(ParticipantViewModel.IsEnabled):
|
|
|
|
|
pillText.Text = p.IsoStateLabel;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
return grid;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>Full rich row template — replaces BuildSimpleRow once we've verified the simple version doesn't crash.</summary>
|
|
|
|
|
private static Microsoft.UI.Xaml.Controls.Grid BuildParticipantRow(ParticipantViewModel p)
|
|
|
|
|
{
|
|
|
|
|
var grid = new Microsoft.UI.Xaml.Controls.Grid
|
|
|
|
|
{
|
|
|
|
|
Height = 64,
|
|
|
|
|
Padding = new Thickness(14, 0, 12, 0),
|
|
|
|
|
BorderBrush = (Microsoft.UI.Xaml.Media.Brush)Application.Current.Resources["BorderSubtle"],
|
|
|
|
|
BorderThickness = new Thickness(0, 0, 0, 1),
|
|
|
|
|
};
|
|
|
|
|
grid.ColumnDefinitions.Add(new Microsoft.UI.Xaml.Controls.ColumnDefinition { Width = new Microsoft.UI.Xaml.GridLength(44) });
|
|
|
|
|
grid.ColumnDefinitions.Add(new Microsoft.UI.Xaml.Controls.ColumnDefinition { Width = new Microsoft.UI.Xaml.GridLength(2, Microsoft.UI.Xaml.GridUnitType.Star) });
|
|
|
|
|
grid.ColumnDefinitions.Add(new Microsoft.UI.Xaml.Controls.ColumnDefinition { Width = new Microsoft.UI.Xaml.GridLength(1.4, Microsoft.UI.Xaml.GridUnitType.Star) });
|
|
|
|
|
grid.ColumnDefinitions.Add(new Microsoft.UI.Xaml.Controls.ColumnDefinition { Width = new Microsoft.UI.Xaml.GridLength(1.6, Microsoft.UI.Xaml.GridUnitType.Star) });
|
|
|
|
|
grid.ColumnDefinitions.Add(new Microsoft.UI.Xaml.Controls.ColumnDefinition { Width = Microsoft.UI.Xaml.GridLength.Auto });
|
|
|
|
|
|
|
|
|
|
// Avatar
|
|
|
|
|
var avatar = new Microsoft.UI.Xaml.Controls.Border
|
|
|
|
|
{
|
|
|
|
|
Width = 36, Height = 36,
|
|
|
|
|
CornerRadius = new CornerRadius(18),
|
|
|
|
|
Background = (Microsoft.UI.Xaml.Media.Brush)Application.Current.Resources["AccentCyanMuted"],
|
|
|
|
|
VerticalAlignment = VerticalAlignment.Center,
|
|
|
|
|
};
|
|
|
|
|
var initialsText = new Microsoft.UI.Xaml.Controls.TextBlock
|
|
|
|
|
{
|
|
|
|
|
Text = p.Initials,
|
|
|
|
|
FontSize = 13,
|
|
|
|
|
FontWeight = Microsoft.UI.Text.FontWeights.SemiBold,
|
|
|
|
|
Foreground = (Microsoft.UI.Xaml.Media.Brush)Application.Current.Resources["AccentCyanText"],
|
|
|
|
|
HorizontalAlignment = HorizontalAlignment.Center,
|
|
|
|
|
VerticalAlignment = VerticalAlignment.Center,
|
|
|
|
|
};
|
|
|
|
|
avatar.Child = initialsText;
|
|
|
|
|
Microsoft.UI.Xaml.Controls.Grid.SetColumn(avatar, 0);
|
|
|
|
|
grid.Children.Add(avatar);
|
|
|
|
|
|
|
|
|
|
// Name + codec
|
|
|
|
|
var nameStack = new Microsoft.UI.Xaml.Controls.StackPanel
|
|
|
|
|
{
|
|
|
|
|
Margin = new Thickness(12, 0, 0, 0),
|
|
|
|
|
VerticalAlignment = VerticalAlignment.Center,
|
|
|
|
|
Spacing = 2,
|
|
|
|
|
};
|
|
|
|
|
var nameText = new Microsoft.UI.Xaml.Controls.TextBlock
|
|
|
|
|
{
|
|
|
|
|
Text = p.DisplayName,
|
|
|
|
|
FontSize = 13,
|
|
|
|
|
FontWeight = Microsoft.UI.Text.FontWeights.Medium,
|
|
|
|
|
Foreground = (Microsoft.UI.Xaml.Media.Brush)Application.Current.Resources["FgPrimary"],
|
|
|
|
|
};
|
|
|
|
|
var codecText = new Microsoft.UI.Xaml.Controls.TextBlock
|
|
|
|
|
{
|
|
|
|
|
Text = p.SourceCodec,
|
|
|
|
|
FontSize = 11,
|
|
|
|
|
Foreground = (Microsoft.UI.Xaml.Media.Brush)Application.Current.Resources["FgSecondary"],
|
|
|
|
|
};
|
|
|
|
|
nameStack.Children.Add(nameText);
|
|
|
|
|
nameStack.Children.Add(codecText);
|
|
|
|
|
Microsoft.UI.Xaml.Controls.Grid.SetColumn(nameStack, 1);
|
|
|
|
|
grid.Children.Add(nameStack);
|
|
|
|
|
|
|
|
|
|
// Audio meter
|
|
|
|
|
var meter = new Microsoft.UI.Xaml.Controls.ProgressBar
|
|
|
|
|
{
|
|
|
|
|
Maximum = 1.0,
|
|
|
|
|
Value = p.DisplayedAudioLevel,
|
|
|
|
|
Height = 4,
|
|
|
|
|
Margin = new Thickness(12, 0, 12, 0),
|
|
|
|
|
VerticalAlignment = VerticalAlignment.Center,
|
|
|
|
|
};
|
|
|
|
|
Microsoft.UI.Xaml.Controls.Grid.SetColumn(meter, 2);
|
|
|
|
|
grid.Children.Add(meter);
|
|
|
|
|
|
|
|
|
|
// Output name
|
|
|
|
|
var outputText = new Microsoft.UI.Xaml.Controls.TextBlock
|
|
|
|
|
{
|
|
|
|
|
Text = p.OutputName,
|
|
|
|
|
FontFamily = new Microsoft.UI.Xaml.Media.FontFamily("Consolas"),
|
|
|
|
|
FontSize = 12,
|
|
|
|
|
Foreground = (Microsoft.UI.Xaml.Media.Brush)Application.Current.Resources["FgPrimary"],
|
|
|
|
|
VerticalAlignment = VerticalAlignment.Center,
|
|
|
|
|
};
|
|
|
|
|
Microsoft.UI.Xaml.Controls.Grid.SetColumn(outputText, 3);
|
|
|
|
|
grid.Children.Add(outputText);
|
|
|
|
|
|
|
|
|
|
// ISO toggle pill
|
|
|
|
|
var pillText = new Microsoft.UI.Xaml.Controls.TextBlock
|
|
|
|
|
{
|
|
|
|
|
Text = p.IsoStateLabel,
|
|
|
|
|
FontSize = 11,
|
|
|
|
|
FontWeight = Microsoft.UI.Text.FontWeights.SemiBold,
|
|
|
|
|
Foreground = (Microsoft.UI.Xaml.Media.Brush)Application.Current.Resources["FgPrimary"],
|
|
|
|
|
HorizontalAlignment = HorizontalAlignment.Center,
|
|
|
|
|
};
|
|
|
|
|
var pill = new Microsoft.UI.Xaml.Controls.Button
|
|
|
|
|
{
|
|
|
|
|
Command = p.ToggleIsoCommand,
|
|
|
|
|
MinWidth = 80,
|
|
|
|
|
Padding = new Thickness(14, 6, 14, 6),
|
|
|
|
|
Background = (Microsoft.UI.Xaml.Media.Brush)Application.Current.Resources["BgSurface"],
|
|
|
|
|
BorderBrush = (Microsoft.UI.Xaml.Media.Brush)Application.Current.Resources["BorderStrong"],
|
|
|
|
|
BorderThickness = new Thickness(1),
|
|
|
|
|
CornerRadius = new CornerRadius(999),
|
|
|
|
|
VerticalAlignment = VerticalAlignment.Center,
|
|
|
|
|
Content = pillText,
|
|
|
|
|
};
|
|
|
|
|
Microsoft.UI.Xaml.Controls.Grid.SetColumn(pill, 4);
|
|
|
|
|
grid.Children.Add(pill);
|
|
|
|
|
|
|
|
|
|
// Per-row property-change subscription — refresh text as the
|
|
|
|
|
// engine pushes updates.
|
|
|
|
|
p.PropertyChanged += (_, e) =>
|
|
|
|
|
{
|
|
|
|
|
grid.DispatcherQueue.TryEnqueue(() =>
|
|
|
|
|
{
|
|
|
|
|
switch (e.PropertyName)
|
|
|
|
|
{
|
|
|
|
|
case nameof(ParticipantViewModel.DisplayName):
|
|
|
|
|
nameText.Text = p.DisplayName;
|
|
|
|
|
initialsText.Text = p.Initials;
|
|
|
|
|
break;
|
|
|
|
|
case nameof(ParticipantViewModel.SourceCodec):
|
|
|
|
|
codecText.Text = p.SourceCodec;
|
|
|
|
|
break;
|
|
|
|
|
case nameof(ParticipantViewModel.DisplayedAudioLevel):
|
|
|
|
|
meter.Value = p.DisplayedAudioLevel;
|
|
|
|
|
break;
|
|
|
|
|
case nameof(ParticipantViewModel.OutputName):
|
|
|
|
|
outputText.Text = p.OutputName;
|
|
|
|
|
break;
|
|
|
|
|
case nameof(ParticipantViewModel.IsoStateLabel):
|
|
|
|
|
case nameof(ParticipantViewModel.IsEnabled):
|
|
|
|
|
pillText.Text = p.IsoStateLabel;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
return grid;
|
|
|
|
|
}
|
|
|
|
|
|
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
|
|
|
private void OnThemeToggleClick(object sender, RoutedEventArgs e)
|
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
|
|
|
{
|
|
|
|
|
ThemeManager.Current.Toggle();
|
|
|
|
|
}
|
|
|
|
|
|
feat(winui3): SettingsDrawer hosts successfully — NavigationView swap
Replaces the NavigationView in SettingsDrawer with a simpler
StackPanel of tab buttons built imperatively at runtime. The
NavigationView's resource-dictionary expansion (or its default
template loading) was crashing the XAML parser at SettingsDrawer's
InitializeComponent on WinUI 3 1.8.
New shape:
- `TabStrip` StackPanel populated in BuildTabStrip() with five
Tertiary-styled Button instances. Selection updates the foreground
to AccentCyanText for the active tab and FgSecondary for the rest.
- `TabContent` ScrollViewer remains; RebuildTabContent(key) clears
and rebuilds via the same helpers as before (SettingHeader,
SettingRow, SettingNote, AccentSwatch).
- Each tab's content moved into its own helper method
(BuildAppearanceTab / Routing / Display / Control / Advanced) so
the switch in the old OnTabSelectionChanged disappears.
MainWindow re-hosts the drawer at Grid.Row=0, RowSpan=4, right-
aligned, 400px wide, Visibility=Collapsed. OnSettingsClick toggles
visibility. Verified: dotnet build + run launches cleanly, and the
window stays alive (PID confirmed via Get-Process).
This closes Phase 6 (secondary windows) for the drawer specifically.
The Help, About, and Onboarding dialogs are ContentDialogs that
don't host inline so they should be straightforward to wire to
their respective triggers (F1 / About button / first launch) in
Phase 7.
2026-05-13 00:50:54 -04:00
|
|
|
private bool _drawerOpen;
|
|
|
|
|
|
2026-05-13 00:20:23 -04:00
|
|
|
private void OnSettingsClick(object sender, RoutedEventArgs e)
|
|
|
|
|
{
|
feat(winui3): SettingsDrawer hosts successfully — NavigationView swap
Replaces the NavigationView in SettingsDrawer with a simpler
StackPanel of tab buttons built imperatively at runtime. The
NavigationView's resource-dictionary expansion (or its default
template loading) was crashing the XAML parser at SettingsDrawer's
InitializeComponent on WinUI 3 1.8.
New shape:
- `TabStrip` StackPanel populated in BuildTabStrip() with five
Tertiary-styled Button instances. Selection updates the foreground
to AccentCyanText for the active tab and FgSecondary for the rest.
- `TabContent` ScrollViewer remains; RebuildTabContent(key) clears
and rebuilds via the same helpers as before (SettingHeader,
SettingRow, SettingNote, AccentSwatch).
- Each tab's content moved into its own helper method
(BuildAppearanceTab / Routing / Display / Control / Advanced) so
the switch in the old OnTabSelectionChanged disappears.
MainWindow re-hosts the drawer at Grid.Row=0, RowSpan=4, right-
aligned, 400px wide, Visibility=Collapsed. OnSettingsClick toggles
visibility. Verified: dotnet build + run launches cleanly, and the
window stays alive (PID confirmed via Get-Process).
This closes Phase 6 (secondary windows) for the drawer specifically.
The Help, About, and Onboarding dialogs are ContentDialogs that
don't host inline so they should be straightforward to wire to
their respective triggers (F1 / About button / first launch) in
Phase 7.
2026-05-13 00:50:54 -04:00
|
|
|
_drawerOpen = !_drawerOpen;
|
|
|
|
|
SettingsDrawerHost.Visibility = _drawerOpen
|
|
|
|
|
? Visibility.Visible
|
|
|
|
|
: Visibility.Collapsed;
|
|
|
|
|
if (_drawerOpen)
|
|
|
|
|
{
|
|
|
|
|
SettingsDrawerHost.CloseRequested -= OnDrawerCloseRequested;
|
|
|
|
|
SettingsDrawerHost.CloseRequested += OnDrawerCloseRequested;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void OnDrawerCloseRequested(object? sender, System.EventArgs e)
|
|
|
|
|
{
|
|
|
|
|
_drawerOpen = false;
|
|
|
|
|
SettingsDrawerHost.Visibility = Visibility.Collapsed;
|
2026-05-13 00:20:23 -04:00
|
|
|
}
|
|
|
|
|
|
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
|
|
|
private void ApplyResolvedTheme(ElementTheme theme)
|
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
|
|
|
{
|
|
|
|
|
if (Content is FrameworkElement root)
|
|
|
|
|
{
|
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
|
|
|
root.RequestedTheme = theme;
|
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
|
|
|
}
|
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
|
|
|
|
|
|
|
|
AppWindow.TitleBar.ButtonForegroundColor = ThemeManager.TitleBarForegroundFor(theme);
|
|
|
|
|
AppWindow.TitleBar.ButtonHoverBackgroundColor = ThemeManager.TitleBarHoverBgFor(theme);
|
|
|
|
|
AppWindow.TitleBar.ButtonPressedBackgroundColor = ThemeManager.TitleBarHoverBgFor(theme);
|
|
|
|
|
|
feat(winui3): engine wired — discovers Teams participants live
The WinUI 3 host now stands up the full engine pipeline on launch and
discovers participants from the operator's live Teams meeting. Verified
end-to-end against a real call: window opened, NDI runtime preflight
passed, IsoController spun up, and Participants observable yielded
three live entries ((Local), Active Speaker, Brendon Power) with their
TEAMSISO_<name> output names in the redesigned shell.
What this commit lands:
ViewModels/ (slim ports of the WPF host's view-models — engine layer
shared verbatim via ProjectReference):
* ObservableObject.cs — INPC base, mirrors the WPF version
* RelayCommand.cs — sync + typed + async variants; ICommand is the
same shared type across both hosts (System.ObjectModel.dll)
* ParticipantViewModel.cs — DisplayName / Initials / SourceCodec /
IsoStateLabel / DisplayedAudioLevel / IsActiveSpeaker / IsOnline /
OutputName + ToggleIsoCommand. Drops the WPF-specific thumbnail
WriteableBitmap path, clipboard, PreviewWindow, snapshot encoder —
those come back when the WinUI imaging pipeline is wired (Phase 5
of the migration plan).
* MainViewModel.cs — subscribes to IIsoController.Participants on a
DispatcherQueueSynchronizationContext, owns the ObservableCollection,
runs a 1Hz DispatcherQueueTimer for stats + active-speaker
highlight + session-elapsed text. Commands: EnableAllOnline,
StopAllIsos, RefreshDiscovery, DropRecordingMarker, ToggleByIndex.
App.xaml.cs:
* OnLaunched brings up MainWindow first, then fires WireEngineAsync
so the user sees the shell immediately while NDI preflight + engine
setup proceed.
* Full pipeline: EngineLogging → NdiInteropPInvoke (with friendly
fallback message if the NDI runtime isn't installed) → ConfigStore
at %APPDATA%\TeamsISO\config.json → NdiRuntimeProbe + scaler →
IsoPipeline factory → IsoController → MainViewModel →
MainWindow.AttachViewModel → IsoController.StartAsync.
* Logger writes to the same %LOCALAPPDATA%\TeamsISO\Logs as the WPF
host so a mixed-host operator sees a single timeline.
MainWindow.xaml + .xaml.cs:
* x:Name on the section header buttons (RefreshButton, StopAllButton,
EnableAllButton, MarkerButton) and on the status bar text + the
ParticipantsHost grid.
* AttachViewModel wires those buttons to view-model commands; pushes
StatusText + ParticipantCountText through PropertyChanged.
* BuildSimpleRow imperatively constructs each row (Grid with name +
codec + output + ISO toggle pill) instead of going through a
DataTemplate. Rationale: declaring a DataTemplate in
Grid.Resources OR loading one via XamlReader.Load both crash WinUI
3's XAML parser at runtime on this build host (same HR=0x802b000a
we saw with the SettingsDrawer NavigationView). Imperative
construction sidesteps the parser. The rich row template (avatar
circle, audio meter, active-speaker accent) returns in Phase 5
alongside the CommunityToolkit DataGrid swap.
* Per-row PropertyChanged subscriptions refresh DisplayName, codec,
output name, and ISO pill text as the engine pushes updates.
Verified live (PID 4824, 2026-05-13 08:02): three real Teams
participants appeared in the redesigned shell within seconds of launch,
status bar populated with "3 participants · 0 routing", section
header showed "Participants 3", and the title-bar live pills rendered
in their proper places (placeholder values for now; binding to
SessionElapsed / IsRecording lands in the next commit).
2026-05-13 08:03:32 -04:00
|
|
|
ThemeToggleIcon.Glyph = theme == ElementTheme.Light ? "" : "";
|
feat(winui3): scaffold TeamsISO.App.WinUI alongside the WPF host
First step of the WinUI 3 replatform per the approved redesign brief.
The new project coexists with the existing src/TeamsISO.App (WPF) so the
WPF host keeps building and shipping while the WinUI 3 redesign lands
incrementally. Once the WinUI 3 build is feature-complete and tested
against a real Teams meeting, the WPF project is retired.
Scaffold contents:
* src/TeamsISO.App.WinUI/TeamsISO.App.WinUI.csproj
Windows App SDK 1.6 LTS (250602001), unpackaged mode
(WindowsPackageType=None) so the existing MSI installer keeps working.
Target framework net8.0-windows10.0.19041.0, min platform 10.0.17763.0
to preserve Win10 1809+ compatibility for working broadcast hardware.
Pins WindowsSdkPackageVersion=10.0.19041.38 so .NET SDK 8.0.301 builds
cleanly without an SDK upgrade on the build host.
* src/TeamsISO.App.WinUI/app.manifest
PerMonitorV2 DPI awareness + gdiScaling for crisp text on high-DPI
broadcast monitors. asInvoker trust level (control surface :9755 and
OSC :9000 bind to 127.0.0.1, no admin needed).
* App.xaml + App.xaml.cs
Minimal startup: brings up MainWindow. The full pipeline (NDI runtime
preflight, IsoController wiring, single-instance mutex, REST + OSC
bridge, tray icon, crash diagnostics, auto-update banner, onboarding)
migrates in subsequent commits.
* Themes/Tokens.xaml
Wild Dragon design tokens as ThemeDictionary entries (Default = Dark,
Light). Colors as Color resources, Brushes paired per theme so
{ThemeResource} auto-swaps when RequestedTheme flips — no app restart,
no flicker. Spacing/radii/typography tokens are theme-agnostic at the
outer level. Light palette maintains brand recognition via cyan-tinted
off-whites (#FAFAFB canvas, #F0F1F3 rail) rather than pure white, and
splits cyan into accent.cyan.surface (#97EDF0, works in both modes
because text on top is near-black) and accent.cyan.text (#97EDF0 dark
/ #0E7C82 light) so captions and inline labels keep AA contrast.
* Themes/Controls.xaml
Button hierarchy with real commitments: Primary (cyan fill, one per
surface), Secondary (transparent bordered), Tertiary (text only),
Destructive (coral border + text), Caption (titlebar), RailIcon.
Typographic ramp (Display / Title / Heading / Body / Subtle / Caption
/ Mono) at the DESIGN.md 1.25 ratio.
* CommunityToolkit.WinUI.UI.Controls.DataGrid 7.1.2 referenced for the
participants table migration. (Toolkit 8.x dropped DataGrid; 7.x is
the only currently-maintained free option for WinUI 3.)
* Inter.ttf + JetBrainsMono.ttf + dragon-mark.png + teamsiso.ico copied
from the WPF project's Assets/ so the WinUI 3 host is self-contained.
* TeamsISO.sln + TeamsISO.Windows.slnf updated to include the new
project. The .slnf paths switch to backslash form so MSBuild can match
them against the .sln's canonical path representation.
Verified: dotnet build TeamsISO.Windows.slnf -c Debug succeeds with 0
warnings and 0 errors for all 8 projects (WPF host, WinUI 3 host, engine,
NDI interop, console, three test projects).
2026-05-12 23:52:35 -04:00
|
|
|
}
|
feat(winui3): engine wired — discovers Teams participants live
The WinUI 3 host now stands up the full engine pipeline on launch and
discovers participants from the operator's live Teams meeting. Verified
end-to-end against a real call: window opened, NDI runtime preflight
passed, IsoController spun up, and Participants observable yielded
three live entries ((Local), Active Speaker, Brendon Power) with their
TEAMSISO_<name> output names in the redesigned shell.
What this commit lands:
ViewModels/ (slim ports of the WPF host's view-models — engine layer
shared verbatim via ProjectReference):
* ObservableObject.cs — INPC base, mirrors the WPF version
* RelayCommand.cs — sync + typed + async variants; ICommand is the
same shared type across both hosts (System.ObjectModel.dll)
* ParticipantViewModel.cs — DisplayName / Initials / SourceCodec /
IsoStateLabel / DisplayedAudioLevel / IsActiveSpeaker / IsOnline /
OutputName + ToggleIsoCommand. Drops the WPF-specific thumbnail
WriteableBitmap path, clipboard, PreviewWindow, snapshot encoder —
those come back when the WinUI imaging pipeline is wired (Phase 5
of the migration plan).
* MainViewModel.cs — subscribes to IIsoController.Participants on a
DispatcherQueueSynchronizationContext, owns the ObservableCollection,
runs a 1Hz DispatcherQueueTimer for stats + active-speaker
highlight + session-elapsed text. Commands: EnableAllOnline,
StopAllIsos, RefreshDiscovery, DropRecordingMarker, ToggleByIndex.
App.xaml.cs:
* OnLaunched brings up MainWindow first, then fires WireEngineAsync
so the user sees the shell immediately while NDI preflight + engine
setup proceed.
* Full pipeline: EngineLogging → NdiInteropPInvoke (with friendly
fallback message if the NDI runtime isn't installed) → ConfigStore
at %APPDATA%\TeamsISO\config.json → NdiRuntimeProbe + scaler →
IsoPipeline factory → IsoController → MainViewModel →
MainWindow.AttachViewModel → IsoController.StartAsync.
* Logger writes to the same %LOCALAPPDATA%\TeamsISO\Logs as the WPF
host so a mixed-host operator sees a single timeline.
MainWindow.xaml + .xaml.cs:
* x:Name on the section header buttons (RefreshButton, StopAllButton,
EnableAllButton, MarkerButton) and on the status bar text + the
ParticipantsHost grid.
* AttachViewModel wires those buttons to view-model commands; pushes
StatusText + ParticipantCountText through PropertyChanged.
* BuildSimpleRow imperatively constructs each row (Grid with name +
codec + output + ISO toggle pill) instead of going through a
DataTemplate. Rationale: declaring a DataTemplate in
Grid.Resources OR loading one via XamlReader.Load both crash WinUI
3's XAML parser at runtime on this build host (same HR=0x802b000a
we saw with the SettingsDrawer NavigationView). Imperative
construction sidesteps the parser. The rich row template (avatar
circle, audio meter, active-speaker accent) returns in Phase 5
alongside the CommunityToolkit DataGrid swap.
* Per-row PropertyChanged subscriptions refresh DisplayName, codec,
output name, and ISO pill text as the engine pushes updates.
Verified live (PID 4824, 2026-05-13 08:02): three real Teams
participants appeared in the redesigned shell within seconds of launch,
status bar populated with "3 participants · 0 routing", section
header showed "Participants 3", and the title-bar live pills rendered
in their proper places (placeholder values for now; binding to
SessionElapsed / IsRecording lands in the next commit).
2026-05-13 08:03:32 -04:00
|
|
|
|
feat(winui3): scaffold TeamsISO.App.WinUI alongside the WPF host
First step of the WinUI 3 replatform per the approved redesign brief.
The new project coexists with the existing src/TeamsISO.App (WPF) so the
WPF host keeps building and shipping while the WinUI 3 redesign lands
incrementally. Once the WinUI 3 build is feature-complete and tested
against a real Teams meeting, the WPF project is retired.
Scaffold contents:
* src/TeamsISO.App.WinUI/TeamsISO.App.WinUI.csproj
Windows App SDK 1.6 LTS (250602001), unpackaged mode
(WindowsPackageType=None) so the existing MSI installer keeps working.
Target framework net8.0-windows10.0.19041.0, min platform 10.0.17763.0
to preserve Win10 1809+ compatibility for working broadcast hardware.
Pins WindowsSdkPackageVersion=10.0.19041.38 so .NET SDK 8.0.301 builds
cleanly without an SDK upgrade on the build host.
* src/TeamsISO.App.WinUI/app.manifest
PerMonitorV2 DPI awareness + gdiScaling for crisp text on high-DPI
broadcast monitors. asInvoker trust level (control surface :9755 and
OSC :9000 bind to 127.0.0.1, no admin needed).
* App.xaml + App.xaml.cs
Minimal startup: brings up MainWindow. The full pipeline (NDI runtime
preflight, IsoController wiring, single-instance mutex, REST + OSC
bridge, tray icon, crash diagnostics, auto-update banner, onboarding)
migrates in subsequent commits.
* Themes/Tokens.xaml
Wild Dragon design tokens as ThemeDictionary entries (Default = Dark,
Light). Colors as Color resources, Brushes paired per theme so
{ThemeResource} auto-swaps when RequestedTheme flips — no app restart,
no flicker. Spacing/radii/typography tokens are theme-agnostic at the
outer level. Light palette maintains brand recognition via cyan-tinted
off-whites (#FAFAFB canvas, #F0F1F3 rail) rather than pure white, and
splits cyan into accent.cyan.surface (#97EDF0, works in both modes
because text on top is near-black) and accent.cyan.text (#97EDF0 dark
/ #0E7C82 light) so captions and inline labels keep AA contrast.
* Themes/Controls.xaml
Button hierarchy with real commitments: Primary (cyan fill, one per
surface), Secondary (transparent bordered), Tertiary (text only),
Destructive (coral border + text), Caption (titlebar), RailIcon.
Typographic ramp (Display / Title / Heading / Body / Subtle / Caption
/ Mono) at the DESIGN.md 1.25 ratio.
* CommunityToolkit.WinUI.UI.Controls.DataGrid 7.1.2 referenced for the
participants table migration. (Toolkit 8.x dropped DataGrid; 7.x is
the only currently-maintained free option for WinUI 3.)
* Inter.ttf + JetBrainsMono.ttf + dragon-mark.png + teamsiso.ico copied
from the WPF project's Assets/ so the WinUI 3 host is self-contained.
* TeamsISO.sln + TeamsISO.Windows.slnf updated to include the new
project. The .slnf paths switch to backslash form so MSBuild can match
them against the .sln's canonical path representation.
Verified: dotnet build TeamsISO.Windows.slnf -c Debug succeeds with 0
warnings and 0 errors for all 8 projects (WPF host, WinUI 3 host, engine,
NDI interop, console, three test projects).
2026-05-12 23:52:35 -04:00
|
|
|
}
|