diff --git a/src/TeamsISO.App.WinUI/Views/MainWindow.xaml b/src/TeamsISO.App.WinUI/Views/MainWindow.xaml
index d41407f..0dfcb1a 100644
--- a/src/TeamsISO.App.WinUI/Views/MainWindow.xaml
+++ b/src/TeamsISO.App.WinUI/Views/MainWindow.xaml
@@ -377,14 +377,13 @@
-
+
+
- /// Settings drawer toggle. Currently a no-op because the drawer host
- /// can't be inlined in MainWindow.xaml without crashing the XAML parser;
- /// see the comment in MainWindow.xaml at the drawer placeholder.
+ /// Toggle the settings drawer. Visibility-only for now; composition-
+ /// layer Translation animation lands as part of the polish pass.
///
private void OnSettingsClick(object sender, RoutedEventArgs e)
{
- // No-op until SettingsDrawer.xaml is simplified for WinUI 3 1.8.
+ _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;
}
///
diff --git a/src/TeamsISO.App.WinUI/Views/SettingsDrawer.xaml b/src/TeamsISO.App.WinUI/Views/SettingsDrawer.xaml
index 6b41439..058e780 100644
--- a/src/TeamsISO.App.WinUI/Views/SettingsDrawer.xaml
+++ b/src/TeamsISO.App.WinUI/Views/SettingsDrawer.xaml
@@ -48,27 +48,28 @@
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
-
+
Build the tab buttons imperatively so the parse doesn't
+ /// trigger a SelectionChanged before the code-behind is ready.
+ private void BuildTabStrip()
+ {
+ foreach (var (label, key) in new[]
+ {
+ ("Appearance", "Appearance"),
+ ("Routing", "Routing"),
+ ("Display", "Display"),
+ ("Control", "Control"),
+ ("Advanced", "Advanced"),
+ })
+ {
+ var btn = new Button
+ {
+ Content = label,
+ Tag = key,
+ Style = (Style)Application.Current.Resources["ButtonTertiary"],
+ };
+ btn.Click += (s, _) =>
+ {
+ if (s is Button b && b.Tag is string k) SelectTab(k);
+ };
+ TabStrip.Children.Add(btn);
+ }
+ }
+
+ private void SelectTab(string key)
+ {
+ _currentTab = key;
+ foreach (var child in TabStrip.Children)
+ {
+ if (child is Button b)
+ {
+ var isActive = (b.Tag as string) == key;
+ b.Foreground = (Microsoft.UI.Xaml.Media.SolidColorBrush)Application.Current.Resources[
+ isActive ? "AccentCyanText" : "FgSecondary"];
+ }
+ }
+ RebuildTabContent(key);
+ }
+
+ private void RebuildTabContent(string key)
+ {
+ TabContent.Children.Clear();
+ switch (key)
+ {
+ case "Appearance": BuildAppearanceTab(); break;
+ case "Routing": BuildRoutingTab(); break;
+ case "Display": BuildDisplayTab(); break;
+ case "Control": BuildControlTab(); break;
+ case "Advanced": BuildAdvancedTab(); break;
+ }
+ }
+
+ private void BuildRoutingTab()
+ {
+ TabContent.Children.Add(SettingHeader("Routing"));
+ TabContent.Children.Add(SettingRow("Framerate", "30 fps", "Target framerate the engine normalizes incoming NDI feeds to."));
+ TabContent.Children.Add(SettingRow("Resolution", "1920 x 1080", "Output resolution applied to every routed participant."));
+ TabContent.Children.Add(SettingRow("Aspect mode", "Letterbox", "How sources whose aspect ratio doesn't match the target are framed."));
+ TabContent.Children.Add(SettingRow("Audio routing", "Per-participant", "Embed each participant's audio in their own NDI source."));
+ TabContent.Children.Add(SettingNote("Settings wired to the engine in a follow-up commit."));
+ }
+
+ private void BuildDisplayTab()
+ {
+ TabContent.Children.Add(SettingHeader("Display & participants"));
+ TabContent.Children.Add(SettingRow("Hide local self", "Yes", "Filter the operator's own preview from the participants table."));
+ TabContent.Children.Add(SettingRow("Auto-disable on departure", "No", "Keep routing alive when a participant goes offline."));
+ TabContent.Children.Add(SettingRow("Participant sort", "Join order", "Default sort for the participants table."));
+ TabContent.Children.Add(SettingRow("Minimize to tray", "No", "Keep TeamsISO running in the system tray when the window is minimized."));
+ TabContent.Children.Add(SettingRow("Launch Teams on startup", "No", "Start Teams in the background when TeamsISO opens."));
+ TabContent.Children.Add(SettingRow("Auto-hide Teams windows", "No", "Hide every visible Teams window after launch."));
+ TabContent.Children.Add(SettingRow("Auto-record on call", "No", "Start recording every active ISO when Teams enters a call."));
+ }
+
+ private void BuildControlTab()
+ {
+ TabContent.Children.Add(SettingHeader("External control surface"));
+ TabContent.Children.Add(SettingRow("REST + WebSocket", "127.0.0.1:9755", "HTTP control surface for Companion / Stream Deck."));
+ TabContent.Children.Add(SettingRow("OSC bridge", "127.0.0.1:9000", "UDP OSC for TouchOSC / hardware surfaces."));
+ TabContent.Children.Add(SettingRow("LAN reachable", "No", "Bind the REST surface to all interfaces (warning: no auth)."));
+ TabContent.Children.Add(SettingNote("Control surface protocol unchanged from the WPF host. See docs/CONTROL-SURFACE.md."));
+ }
+
+ private void BuildAdvancedTab()
+ {
+ TabContent.Children.Add(SettingHeader("Advanced"));
+ TabContent.Children.Add(SettingRow("Embed Teams window", "No", "Experimental SetParent reparent of Teams' main window."));
+ TabContent.Children.Add(SettingRow("Logs", "%LOCALAPPDATA%\\TeamsISO\\Logs", "Where rolling daily Serilog files write."));
+ TabContent.Children.Add(SettingRow("Recordings", "%USERPROFILE%\\Videos\\TeamsISO", "Default per-show recording directory."));
+ TabContent.Children.Add(SettingRow("Diagnostic bundle", "Export", "Zip logs + config + presets for a bug report."));
}
private void OnCloseClick(object sender, RoutedEventArgs e) => CloseRequested?.Invoke(this, EventArgs.Empty);
@@ -37,58 +135,6 @@ public sealed partial class SettingsDrawer : UserControl
DirtyHint.Text = "Reset queued — apply or close to commit.";
}
- ///
- /// Tab-switch handler builds the body for the selected tab. We assemble
- /// content imperatively rather than via separate XAML pages because the
- /// drawer's content is data-driven (most rows are simple label + control
- /// + tooltip triples) and pulling that into five XAML files would
- /// triplicate the row layout. The body recomposes on every selection
- /// change, which is cheap given the small surface.
- ///
- private void OnTabSelectionChanged(NavigationView sender, NavigationViewSelectionChangedEventArgs args)
- {
- TabContent.Children.Clear();
- if (args.SelectedItemContainer is not NavigationViewItem item) return;
-
- switch (item.Tag as string)
- {
- case "Appearance":
- BuildAppearanceTab();
- break;
- case "Routing":
- TabContent.Children.Add(SettingHeader("Routing"));
- TabContent.Children.Add(SettingRow("Framerate", "30 fps", "Target framerate the engine normalizes incoming NDI feeds to."));
- TabContent.Children.Add(SettingRow("Resolution", "1920 x 1080", "Output resolution applied to every routed participant."));
- TabContent.Children.Add(SettingRow("Aspect mode", "Letterbox", "How sources whose aspect ratio doesn't match the target are framed."));
- TabContent.Children.Add(SettingRow("Audio routing", "Per-participant", "Embed each participant's audio in their own NDI source."));
- TabContent.Children.Add(SettingNote("Settings wired to the engine in a follow-up commit."));
- break;
- case "Display":
- TabContent.Children.Add(SettingHeader("Display & participants"));
- TabContent.Children.Add(SettingRow("Hide local self", "Yes", "Filter the operator's own preview from the participants table."));
- TabContent.Children.Add(SettingRow("Auto-disable on departure", "No", "Keep routing alive when a participant goes offline."));
- TabContent.Children.Add(SettingRow("Participant sort", "Join order", "Default sort for the participants table."));
- TabContent.Children.Add(SettingRow("Minimize to tray", "No", "Keep TeamsISO running in the system tray when the window is minimized."));
- TabContent.Children.Add(SettingRow("Launch Teams on startup", "No", "Start Teams in the background when TeamsISO opens."));
- TabContent.Children.Add(SettingRow("Auto-hide Teams windows", "No", "Hide every visible Teams window after launch."));
- TabContent.Children.Add(SettingRow("Auto-record on call", "No", "Start recording every active ISO when Teams enters a call."));
- break;
- case "Control":
- TabContent.Children.Add(SettingHeader("External control surface"));
- TabContent.Children.Add(SettingRow("REST + WebSocket", "127.0.0.1:9755", "HTTP control surface for Companion / Stream Deck."));
- TabContent.Children.Add(SettingRow("OSC bridge", "127.0.0.1:9000", "UDP OSC for TouchOSC / hardware surfaces."));
- TabContent.Children.Add(SettingRow("LAN reachable", "No", "Bind the REST surface to all interfaces (warning: no auth)."));
- TabContent.Children.Add(SettingNote("Control surface protocol unchanged from the WPF host. See docs/CONTROL-SURFACE.md."));
- break;
- case "Advanced":
- TabContent.Children.Add(SettingHeader("Advanced"));
- TabContent.Children.Add(SettingRow("Embed Teams window", "No", "Experimental SetParent reparent of Teams' main window."));
- TabContent.Children.Add(SettingRow("Logs", "%LOCALAPPDATA%\\TeamsISO\\Logs", "Where rolling daily Serilog files write."));
- TabContent.Children.Add(SettingRow("Recordings", "%USERPROFILE%\\Videos\\TeamsISO", "Default per-show recording directory."));
- TabContent.Children.Add(SettingRow("Diagnostic bundle", "Export", "Zip logs + config + presets for a bug report."));
- break;
- }
- }
///
/// Appearance tab — theme picker, accent palette peek. The theme picker