feat(winui3): SettingsDrawer hosts successfully — NavigationView swap
Some checks failed
CI / build-and-test (push) Failing after 29s
Some checks failed
CI / build-and-test (push) Failing after 29s
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.
This commit is contained in:
parent
27f47401d9
commit
a05c0a75d2
4 changed files with 144 additions and 83 deletions
|
|
@ -377,14 +377,13 @@
|
|||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
<!-- SettingsDrawer host deferred: even with Visibility=Collapsed
|
||||
the SettingsDrawer.xaml parse fires at constructor time and
|
||||
crashes the WinUI 3 XAML parser. Suspect the NavigationView's
|
||||
IsSelected="True" attribute on its first NavigationViewItem
|
||||
firing OnTabSelectionChanged before the code-behind is ready.
|
||||
Re-host after replacing the NavigationView with a simpler
|
||||
tab strip or after fixing the selection-change handler
|
||||
signature for WinUI 3 1.8. -->
|
||||
<!-- ─── Settings drawer ─── -->
|
||||
<views:SettingsDrawer x:Name="SettingsDrawerHost"
|
||||
Grid.Row="0"
|
||||
Grid.RowSpan="4"
|
||||
HorizontalAlignment="Right"
|
||||
Width="400"
|
||||
Visibility="Collapsed"/>
|
||||
|
||||
<!-- ─── Status bar ─── -->
|
||||
<Grid Grid.Row="4"
|
||||
|
|
|
|||
|
|
@ -60,14 +60,29 @@ public sealed partial class MainWindow : Window
|
|||
ThemeManager.Current.Toggle();
|
||||
}
|
||||
|
||||
private bool _drawerOpen;
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
|
|||
|
|
@ -48,27 +48,28 @@
|
|||
</Button>
|
||||
</Grid>
|
||||
|
||||
<!-- Tabs + body -->
|
||||
<NavigationView Grid.Row="1"
|
||||
x:Name="SettingsNav"
|
||||
PaneDisplayMode="Top"
|
||||
IsBackButtonVisible="Collapsed"
|
||||
IsSettingsVisible="False"
|
||||
OpenPaneLength="0"
|
||||
SelectionChanged="OnTabSelectionChanged">
|
||||
<NavigationView.MenuItems>
|
||||
<NavigationViewItem Tag="Appearance" Content="Appearance" IsSelected="True"/>
|
||||
<NavigationViewItem Tag="Routing" Content="Routing"/>
|
||||
<NavigationViewItem Tag="Display" Content="Display"/>
|
||||
<NavigationViewItem Tag="Control" Content="Control"/>
|
||||
<NavigationViewItem Tag="Advanced" Content="Advanced"/>
|
||||
</NavigationView.MenuItems>
|
||||
|
||||
<ScrollViewer VerticalScrollBarVisibility="Auto"
|
||||
<!-- Body. NavigationView swapped for a simpler horizontal tab
|
||||
button strip; the WinUI 3 NavigationView's resource
|
||||
dictionary expansion was crashing the XAML parser at
|
||||
SettingsDrawer construction time. -->
|
||||
<Grid Grid.Row="1">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="*"/>
|
||||
</Grid.RowDefinitions>
|
||||
<StackPanel Grid.Row="0"
|
||||
x:Name="TabStrip"
|
||||
Orientation="Horizontal"
|
||||
Spacing="4"
|
||||
Padding="12,8"
|
||||
BorderBrush="{ThemeResource BorderSubtle}"
|
||||
BorderThickness="0,0,0,1"/>
|
||||
<ScrollViewer Grid.Row="1"
|
||||
VerticalScrollBarVisibility="Auto"
|
||||
Padding="20">
|
||||
<StackPanel x:Name="TabContent" Spacing="16"/>
|
||||
</ScrollViewer>
|
||||
</NavigationView>
|
||||
</Grid>
|
||||
|
||||
<!-- Footer: Apply / Reset -->
|
||||
<Grid Grid.Row="2"
|
||||
|
|
|
|||
|
|
@ -18,7 +18,105 @@ public sealed partial class SettingsDrawer : UserControl
|
|||
public SettingsDrawer()
|
||||
{
|
||||
InitializeComponent();
|
||||
BuildAppearanceTab();
|
||||
BuildTabStrip();
|
||||
SelectTab("Appearance");
|
||||
}
|
||||
|
||||
private string _currentTab = "Appearance";
|
||||
|
||||
/// <summary>Build the tab buttons imperatively so the parse doesn't
|
||||
/// trigger a SelectionChanged before the code-behind is ready.</summary>
|
||||
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.";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Appearance tab — theme picker, accent palette peek. The theme picker
|
||||
|
|
|
|||
Loading…
Reference in a new issue