2026-05-07 11:09:56 -04:00
<Window x:Class="TeamsISO.App.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
2026-05-14 12:46:24 -04:00
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
2026-05-07 11:40:49 -04:00
xmlns:vm="clr-namespace:TeamsISO.App.ViewModels"
xmlns:conv="clr-namespace:TeamsISO.App.Converters"
2026-05-14 12:46:24 -04:00
mc:Ignorable="d"
feat(ui): rebuild MainWindow with Stone-theme design system
Adopts the design language from Dammyjay93/interface-design: warm Stone neutrals, accent orange (#EA580C), borders-only depth (no shadows), 8px spacing grid, all-caps section labels, mono typography for machine names and timecodes.
Themes/StoneTheme.xaml is the single source of truth for design tokens (color brushes, typography styles, spacing) plus restyled control templates (Button, TextBox, ComboBox, ComboBoxItem, CheckBox, DataGrid + DataGridColumnHeader / DataGridRow / DataGridCell, ScrollBar). MainWindow consumes the tokens and is laid out with a header (title + status pill), section-headed Settings sidebar (Output Format / NDI Network / Display), card-wrapped participant list, and a mono status footer.
Settings sidebar surfaces the new NDI group configuration (discovery + output) and a Hide-(Local) checkbox. The latter filters the user's own self-preview from the participants list at the MainViewModel layer (HideLocalSelf=true by default) so operators don't accidentally route their own preview as an ISO. Apply Changes round-trips both FrameProcessingSettings and NdiGroupSettings through the controller in one go.
2026-05-07 23:58:02 -04:00
Title="TeamsISO"
2026-05-14 12:46:24 -04:00
Width="1280" Height="780"
MinWidth="980" MinHeight="640"
WindowStartupLocation="CenterScreen"
feat(ui): rebrand to Wild Dragon + Microsoft Teams layout
Replaces the Stone theme with Wild Dragon branding (canvas #0A0A0A, accent cyan #97EDF0, secondary #9AE0FD, coral alert #FB819C — sourced from wilddragon.net) and reorganizes MainWindow into a Microsoft Teams-style three-column layout: a 72px left rail (logo + Participants/Settings nav + engine-status indicator), a center content area (header + participants card), and a right settings panel.
Adds InitialsConverter so participant avatars render real initials (Brendon Power -> 'BP', '(Local)' -> 'L') instead of a generic glyph. Drops the obsolete StoneTheme.xaml; the project now ships exactly one theme dictionary.
Typography: Inter (with Segoe UI Variable Display fallback) for the sans stack, JetBrains Mono (Cascadia Mono fallback) for machine names and timecodes — matching the wilddragon.net site.
Verified live against the running Teams meeting: app launches, participant 'Brendon Power' displays with avatar, settings panel surfaces NDI groups + Hide-Local toggle, engine status pill shows green/live.
2026-05-08 00:08:39 -04:00
Background="{DynamicResource Wd.Canvas}"
2026-05-14 12:46:24 -04:00
Foreground="{DynamicResource Wd.Text.Primary}"
feat(ui): rebuild MainWindow with Stone-theme design system
Adopts the design language from Dammyjay93/interface-design: warm Stone neutrals, accent orange (#EA580C), borders-only depth (no shadows), 8px spacing grid, all-caps section labels, mono typography for machine names and timecodes.
Themes/StoneTheme.xaml is the single source of truth for design tokens (color brushes, typography styles, spacing) plus restyled control templates (Button, TextBox, ComboBox, ComboBoxItem, CheckBox, DataGrid + DataGridColumnHeader / DataGridRow / DataGridCell, ScrollBar). MainWindow consumes the tokens and is laid out with a header (title + status pill), section-headed Settings sidebar (Output Format / NDI Network / Display), card-wrapped participant list, and a mono status footer.
Settings sidebar surfaces the new NDI group configuration (discovery + output) and a Hide-(Local) checkbox. The latter filters the user's own self-preview from the participants list at the MainViewModel layer (HideLocalSelf=true by default) so operators don't accidentally route their own preview as an ISO. Apply Changes round-trips both FrameProcessingSettings and NdiGroupSettings through the controller in one go.
2026-05-07 23:58:02 -04:00
UseLayoutRounding="True"
2026-05-14 12:46:24 -04:00
SnapsToDevicePixels="True"
TextOptions.TextRenderingMode="ClearType"
TextOptions.TextFormattingMode="Display"
d:DataContext="{d:DesignInstance Type=vm:MainViewModel}">
2026-05-08 00:55:57 -04:00
<!--
2026-05-14 12:46:24 -04:00
v2 SHELL — "Studio Terminal"
(Approved 2026-05-13. Shape brief at docs/shapes/2026-05-13-teamsiso-v2-studio-terminal.md)
Default Windows title bar (no chromeless WindowChrome). The 32px header
below it carries the brand mark, wordmark, and three icon buttons:
⌘K (command palette), theme toggle, settings drawer. Below that, a
single transport strip carries the operator's at-a-glance status.
The participants area is the canvas — no rail, no permanent side
panel, no footer. The meeting bar at the bottom renders ONLY when
Teams is in a call.
2026-05-08 00:55:57 -04:00
-->
2026-05-07 11:40:49 -04:00
<Window.Resources>
2026-05-14 12:46:24 -04:00
<conv:BoolToVisibilityConverter x:Key="BoolToVis"
TrueValue="Visible"
FalseValue="Collapsed"/>
<conv:BoolToVisibilityConverter x:Key="BoolToVisInverse"
2026-05-10 09:41:28 -04:00
TrueValue="Collapsed"
FalseValue="Visible"/>
feat(ui): rebuild MainWindow with Stone-theme design system
Adopts the design language from Dammyjay93/interface-design: warm Stone neutrals, accent orange (#EA580C), borders-only depth (no shadows), 8px spacing grid, all-caps section labels, mono typography for machine names and timecodes.
Themes/StoneTheme.xaml is the single source of truth for design tokens (color brushes, typography styles, spacing) plus restyled control templates (Button, TextBox, ComboBox, ComboBoxItem, CheckBox, DataGrid + DataGridColumnHeader / DataGridRow / DataGridCell, ScrollBar). MainWindow consumes the tokens and is laid out with a header (title + status pill), section-headed Settings sidebar (Output Format / NDI Network / Display), card-wrapped participant list, and a mono status footer.
Settings sidebar surfaces the new NDI group configuration (discovery + output) and a Hide-(Local) checkbox. The latter filters the user's own self-preview from the participants list at the MainViewModel layer (HideLocalSelf=true by default) so operators don't accidentally route their own preview as an ISO. Apply Changes round-trips both FrameProcessingSettings and NdiGroupSettings through the controller in one go.
2026-05-07 23:58:02 -04:00
<conv:EnumDescriptionConverter x:Key="EnumDesc"/>
feat(wpf): v2 task 39+40 - studio table redesign + Ctrl+K command palette
Task 39: 5-column participants table - state LED, name+codec caption, 5-bar audio meter, mono output name, ISO pill. Row height 52, full-row active-speaker tint (no left stripe). New converter LevelThresholdConverter, OutputName property on ParticipantViewModel.
Task 40: Ctrl+K / Ctrl+P command palette - chromeless centered floating window, fuzzy Contains match across Label/Category/Keywords, arrow nav, Enter invoke, Esc close. Quick/Teams/Network/App categories cover top operator verbs and theme switching.
Also: log startup exceptions to Serilog before the modal MessageBox fires - much better triage signal than user-pasted dialog text.
2026-05-15 11:15:00 -04:00
<conv:LevelThresholdConverter x:Key="LevelGate"/>
polish(mainwindow): empty state, table widths, strings, theme tooltip
Walks the v2 polish punch list against MainWindow.
- Theme button tooltip is now "Theme (System / Dark / Light)" per the
v2 shape brief, replacing the previous "Toggle theme (Ctrl+T)".
- Participants table column widths match spec: Output 130px (was 150),
ISO pill 100px (was 110). The 24px state LED, 110px audio meter, and
52px row height already matched. The 106px Preview thumbnail column
and 32px gear-button column are intentional deviations (live thumbs
were restored at 4944de5; per-ISO override gear added at the same
time) and are now called out in the column-spec comment so a future
reader doesn't try to "fix" them.
- Empty-state placeholder finally renders when ParticipantCount == 0:
mono sentence "no ndi sources yet — open teams and start a meeting"
+ a tertiary Refresh discovery button — exactly the copy specified
by the shape brief's empty-states section. CountToVisibilityConverter
is now declared in MainWindow.Resources (it shipped as a class but
was never registered).
- OnClosing wraps WindowStateStore.Save in a try/catch so a serialization
or filesystem fault on shutdown can never block the window from
closing. Save itself already swallows its own IO errors; this is
defense-in-depth for anything that escapes.
- MessageBox copy in MainWindow.xaml.cs (Hide/show Teams, Launch Teams,
Stop Teams) moves to Properties/Strings.resx + a hand-written
Properties/Strings.Designer.cs accessor. ResourceManager reads it by
basename "TeamsISO.App.Properties.Strings"; LogicalName is set on the
EmbeddedResource so the manifest name is predictable regardless of
how MSBuild would otherwise compute it. Future-localization seam.
OnLaunchTeamsRightClick's confirmation dialog is intentional — it
guards a destructive mid-show action — and the code-behind comment
now says so; the palette also offers Stop Teams as the keyboard
surface, so the right-click affordance isn't the only one.
Build clean (0 warnings, 0 errors); 160 tests still pass (56 App +
104 Engine, Category!=ndi&requires!=ndi filter).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-15 19:26:23 -04:00
<conv:CountToVisibilityConverter x:Key="CountToVis"/>
2026-05-07 11:40:49 -04:00
</Window.Resources>
2026-05-10 09:41:28 -04:00
<Window.InputBindings>
<KeyBinding Key="F1" Command="{Binding ShowHelpCommand}"/>
<KeyBinding Key="S" Modifiers="Ctrl+Shift" Command="{Binding StopAllIsosCommand}"/>
<KeyBinding Key="R" Modifiers="Ctrl" Command="{Binding RefreshDiscoveryCommand}"/>
2026-05-14 12:46:24 -04:00
<KeyBinding Key="T" Modifiers="Ctrl" Command="{Binding ToggleThemeCommand}"/>
feat(wpf): v2 task 39+40 - studio table redesign + Ctrl+K command palette
Task 39: 5-column participants table - state LED, name+codec caption, 5-bar audio meter, mono output name, ISO pill. Row height 52, full-row active-speaker tint (no left stripe). New converter LevelThresholdConverter, OutputName property on ParticipantViewModel.
Task 40: Ctrl+K / Ctrl+P command palette - chromeless centered floating window, fuzzy Contains match across Label/Category/Keywords, arrow nav, Enter invoke, Esc close. Quick/Teams/Network/App categories cover top operator verbs and theme switching.
Also: log startup exceptions to Serilog before the modal MessageBox fires - much better triage signal than user-pasted dialog text.
2026-05-15 11:15:00 -04:00
<!-- Ctrl+K (and Ctrl+P alias) — open the v2 command palette. The
KeyBinding fires on the window; the handler is wired via the
palette button's Click in the header. -->
<KeyBinding Key="K" Modifiers="Ctrl" Command="{Binding OpenCommandPaletteCommand}"/>
<KeyBinding Key="P" Modifiers="Ctrl" Command="{Binding OpenCommandPaletteCommand}"/>
2026-05-10 21:26:37 -04:00
<KeyBinding Key="NumPad1" Command="{Binding ToggleByIndexCommand}" CommandParameter="1"/>
<KeyBinding Key="NumPad2" Command="{Binding ToggleByIndexCommand}" CommandParameter="2"/>
<KeyBinding Key="NumPad3" Command="{Binding ToggleByIndexCommand}" CommandParameter="3"/>
<KeyBinding Key="NumPad4" Command="{Binding ToggleByIndexCommand}" CommandParameter="4"/>
<KeyBinding Key="NumPad5" Command="{Binding ToggleByIndexCommand}" CommandParameter="5"/>
<KeyBinding Key="NumPad6" Command="{Binding ToggleByIndexCommand}" CommandParameter="6"/>
<KeyBinding Key="NumPad7" Command="{Binding ToggleByIndexCommand}" CommandParameter="7"/>
<KeyBinding Key="NumPad8" Command="{Binding ToggleByIndexCommand}" CommandParameter="8"/>
<KeyBinding Key="NumPad9" Command="{Binding ToggleByIndexCommand}" CommandParameter="9"/>
<KeyBinding Key="D1" Command="{Binding ToggleByIndexCommand}" CommandParameter="1"/>
<KeyBinding Key="D2" Command="{Binding ToggleByIndexCommand}" CommandParameter="2"/>
<KeyBinding Key="D3" Command="{Binding ToggleByIndexCommand}" CommandParameter="3"/>
<KeyBinding Key="D4" Command="{Binding ToggleByIndexCommand}" CommandParameter="4"/>
<KeyBinding Key="D5" Command="{Binding ToggleByIndexCommand}" CommandParameter="5"/>
<KeyBinding Key="D6" Command="{Binding ToggleByIndexCommand}" CommandParameter="6"/>
<KeyBinding Key="D7" Command="{Binding ToggleByIndexCommand}" CommandParameter="7"/>
<KeyBinding Key="D8" Command="{Binding ToggleByIndexCommand}" CommandParameter="8"/>
<KeyBinding Key="D9" Command="{Binding ToggleByIndexCommand}" CommandParameter="9"/>
2026-05-10 09:41:28 -04:00
</Window.InputBindings>
feat(ui): rebrand to Wild Dragon + Microsoft Teams layout
Replaces the Stone theme with Wild Dragon branding (canvas #0A0A0A, accent cyan #97EDF0, secondary #9AE0FD, coral alert #FB819C — sourced from wilddragon.net) and reorganizes MainWindow into a Microsoft Teams-style three-column layout: a 72px left rail (logo + Participants/Settings nav + engine-status indicator), a center content area (header + participants card), and a right settings panel.
Adds InitialsConverter so participant avatars render real initials (Brendon Power -> 'BP', '(Local)' -> 'L') instead of a generic glyph. Drops the obsolete StoneTheme.xaml; the project now ships exactly one theme dictionary.
Typography: Inter (with Segoe UI Variable Display fallback) for the sans stack, JetBrains Mono (Cascadia Mono fallback) for machine names and timecodes — matching the wilddragon.net site.
Verified live against the running Teams meeting: app launches, participant 'Brendon Power' displays with avatar, settings panel surfaces NDI groups + Hide-Local toggle, engine status pill shows green/live.
2026-05-08 00:08:39 -04:00
<Grid>
2026-05-14 12:46:24 -04:00
<Grid.RowDefinitions>
<RowDefinition Height="32"/>
<RowDefinition Height="40"/>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<!-- ═════════════════════ HEADER (32px) ═════════════════════ -->
<Border Grid.Row="0"
Background="{DynamicResource Wd.Canvas}"
feat(ui): rebrand to Wild Dragon + Microsoft Teams layout
Replaces the Stone theme with Wild Dragon branding (canvas #0A0A0A, accent cyan #97EDF0, secondary #9AE0FD, coral alert #FB819C — sourced from wilddragon.net) and reorganizes MainWindow into a Microsoft Teams-style three-column layout: a 72px left rail (logo + Participants/Settings nav + engine-status indicator), a center content area (header + participants card), and a right settings panel.
Adds InitialsConverter so participant avatars render real initials (Brendon Power -> 'BP', '(Local)' -> 'L') instead of a generic glyph. Drops the obsolete StoneTheme.xaml; the project now ships exactly one theme dictionary.
Typography: Inter (with Segoe UI Variable Display fallback) for the sans stack, JetBrains Mono (Cascadia Mono fallback) for machine names and timecodes — matching the wilddragon.net site.
Verified live against the running Teams meeting: app launches, participant 'Brendon Power' displays with avatar, settings panel surfaces NDI groups + Hide-Local toggle, engine status pill shows green/live.
2026-05-08 00:08:39 -04:00
BorderBrush="{DynamicResource Wd.Border}"
2026-05-14 12:46:24 -04:00
BorderThickness="0,0,0,1">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<!-- Brand: Wild Dragon mark + wordmark. Click on the mark opens About.
Mark is small (20px) and sits as a quality cue — not as
marketing chrome. -->
<StackPanel Grid.Column="0"
Orientation="Horizontal"
VerticalAlignment="Center"
Margin="12,0,0,0">
<Button Click="OnAboutClick"
Padding="2"
Background="Transparent"
BorderThickness="0"
Cursor="Hand"
ToolTip="About TeamsISO">
feat: app icon, FPS, drops counter, --version, About dialog, Stop Teams toggle
Six related polish items, all building on tonight's groundwork.
1. App icon: teamsiso.ico generated from dragon-mark.png at 7 sizes (16-256), wired as ApplicationIcon in the WPF csproj, MainWindow.Icon, AboutWindow.Icon, and ARPPRODUCTICON in the WiX MSI. Taskbar / window / Add-Remove-Programs all show the dragon mark now.
2. Running incoming FPS: ring buffer of last 30 frame timestamps in IsoPipeline; ComputeFps() returns moving-average rate. Surfaced on IsoHealthStats.IncomingFps and shown in the Source column of the participants DataGrid as 'WxH · 59.94 fps'. Resets cleanly on every supervisor restart.
3. Drops counter: FrameProcessor.Stats already aggregated FramesDropped (closest-frame strategy when the receiver outpaces the processor) and FramesDuplicated; just plumbed _liveProcessor through IsoPipeline so GetStats() can read them. Exposed in the Live column under the in/out counters as a coral-tinted 'drop N'.
4. Console --version flag: prints engine version (with embedded git SHA), .NET version, OS, NDI runtime banner, expected prefix, exit-code legend, plus a wilddragon.net link. Useful for support tickets.
5. About dialog: chromeless modal with the dragon mark + version / .NET / OS / NDI runtime fields and a link to wilddragon.net. Triggered by clicking the rail logo.
6. Teams launcher Stop toggle: TeamsLauncher gains IsRunning() and StopAll(). The rail's Teams button now toggles — if Teams is up, ask to close all Teams windows via WM_CLOSE; otherwise launch as before. Confirms before stopping so we don't kill the user's call mid-transition.
Tests: 74/74 unit + 9/9 NDI integration green throughout. MSI builds clean and now embeds the dragon icon for ARP.
2026-05-08 13:50:19 -04:00
<Image Source="/Assets/dragon-mark.png"
2026-05-14 12:46:24 -04:00
Width="20" Height="20"
feat: app icon, FPS, drops counter, --version, About dialog, Stop Teams toggle
Six related polish items, all building on tonight's groundwork.
1. App icon: teamsiso.ico generated from dragon-mark.png at 7 sizes (16-256), wired as ApplicationIcon in the WPF csproj, MainWindow.Icon, AboutWindow.Icon, and ARPPRODUCTICON in the WiX MSI. Taskbar / window / Add-Remove-Programs all show the dragon mark now.
2. Running incoming FPS: ring buffer of last 30 frame timestamps in IsoPipeline; ComputeFps() returns moving-average rate. Surfaced on IsoHealthStats.IncomingFps and shown in the Source column of the participants DataGrid as 'WxH · 59.94 fps'. Resets cleanly on every supervisor restart.
3. Drops counter: FrameProcessor.Stats already aggregated FramesDropped (closest-frame strategy when the receiver outpaces the processor) and FramesDuplicated; just plumbed _liveProcessor through IsoPipeline so GetStats() can read them. Exposed in the Live column under the in/out counters as a coral-tinted 'drop N'.
4. Console --version flag: prints engine version (with embedded git SHA), .NET version, OS, NDI runtime banner, expected prefix, exit-code legend, plus a wilddragon.net link. Useful for support tickets.
5. About dialog: chromeless modal with the dragon mark + version / .NET / OS / NDI runtime fields and a link to wilddragon.net. Triggered by clicking the rail logo.
6. Teams launcher Stop toggle: TeamsLauncher gains IsRunning() and StopAll(). The rail's Teams button now toggles — if Teams is up, ask to close all Teams windows via WM_CLOSE; otherwise launch as before. Confirms before stopping so we don't kill the user's call mid-transition.
Tests: 74/74 unit + 9/9 NDI integration green throughout. MSI builds clean and now embeds the dragon icon for ARP.
2026-05-08 13:50:19 -04:00
RenderOptions.BitmapScalingMode="HighQuality"/>
2026-05-14 12:46:24 -04:00
</Button>
<TextBlock Text="TeamsISO"
FontFamily="{StaticResource Wd.Font.Sans}"
FontSize="13"
FontWeight="Medium"
Foreground="{DynamicResource Wd.Text.Primary}"
VerticalAlignment="Center"
Margin="8,0,0,0"/>
</StackPanel>
feat(ui): rebrand to Wild Dragon + Microsoft Teams layout
Replaces the Stone theme with Wild Dragon branding (canvas #0A0A0A, accent cyan #97EDF0, secondary #9AE0FD, coral alert #FB819C — sourced from wilddragon.net) and reorganizes MainWindow into a Microsoft Teams-style three-column layout: a 72px left rail (logo + Participants/Settings nav + engine-status indicator), a center content area (header + participants card), and a right settings panel.
Adds InitialsConverter so participant avatars render real initials (Brendon Power -> 'BP', '(Local)' -> 'L') instead of a generic glyph. Drops the obsolete StoneTheme.xaml; the project now ships exactly one theme dictionary.
Typography: Inter (with Segoe UI Variable Display fallback) for the sans stack, JetBrains Mono (Cascadia Mono fallback) for machine names and timecodes — matching the wilddragon.net site.
Verified live against the running Teams meeting: app launches, participant 'Brendon Power' displays with avatar, settings panel surfaces NDI groups + Hide-Local toggle, engine status pill shows green/live.
2026-05-08 00:08:39 -04:00
2026-05-14 12:46:24 -04:00
<!-- Right cluster: three icon buttons. ⌘K opens the command
palette (Ctrl+K shortcut). The theme button cycles
dark ↔ light (Ctrl+T). The gear opens the settings
drawer. That's the entire chrome. -->
<StackPanel Grid.Column="2"
Orientation="Horizontal"
VerticalAlignment="Center"
Margin="0,0,10,0">
<Button Style="{StaticResource Wd.Button.Ghost}"
Click="OnCommandPaletteClick"
Padding="8,4"
Margin="0,0,2,0"
ToolTip="Command palette (Ctrl+K)"
FontFamily="{StaticResource Wd.Font.Mono}"
FontSize="11"
Foreground="{DynamicResource Wd.Text.Secondary}"
Content="⌘K"/>
<Button Style="{StaticResource Wd.Button.Ghost}"
Command="{Binding ToggleThemeCommand}"
Padding="6,4"
Margin="0,0,2,0"
polish(mainwindow): empty state, table widths, strings, theme tooltip
Walks the v2 polish punch list against MainWindow.
- Theme button tooltip is now "Theme (System / Dark / Light)" per the
v2 shape brief, replacing the previous "Toggle theme (Ctrl+T)".
- Participants table column widths match spec: Output 130px (was 150),
ISO pill 100px (was 110). The 24px state LED, 110px audio meter, and
52px row height already matched. The 106px Preview thumbnail column
and 32px gear-button column are intentional deviations (live thumbs
were restored at 4944de5; per-ISO override gear added at the same
time) and are now called out in the column-spec comment so a future
reader doesn't try to "fix" them.
- Empty-state placeholder finally renders when ParticipantCount == 0:
mono sentence "no ndi sources yet — open teams and start a meeting"
+ a tertiary Refresh discovery button — exactly the copy specified
by the shape brief's empty-states section. CountToVisibilityConverter
is now declared in MainWindow.Resources (it shipped as a class but
was never registered).
- OnClosing wraps WindowStateStore.Save in a try/catch so a serialization
or filesystem fault on shutdown can never block the window from
closing. Save itself already swallows its own IO errors; this is
defense-in-depth for anything that escapes.
- MessageBox copy in MainWindow.xaml.cs (Hide/show Teams, Launch Teams,
Stop Teams) moves to Properties/Strings.resx + a hand-written
Properties/Strings.Designer.cs accessor. ResourceManager reads it by
basename "TeamsISO.App.Properties.Strings"; LogicalName is set on the
EmbeddedResource so the manifest name is predictable regardless of
how MSBuild would otherwise compute it. Future-localization seam.
OnLaunchTeamsRightClick's confirmation dialog is intentional — it
guards a destructive mid-show action — and the code-behind comment
now says so; the palette also offers Stop Teams as the keyboard
surface, so the right-click affordance isn't the only one.
Build clean (0 warnings, 0 errors); 160 tests still pass (56 App +
104 Engine, Category!=ndi&requires!=ndi filter).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-15 19:26:23 -04:00
ToolTip="Theme (System / Dark / Light)">
2026-05-14 12:46:24 -04:00
<Path Data="M 8,2 C 8,2 4,4 4,8 C 4,12 8,14 8,14 C 5,14 2,11 2,8 C 2,5 5,2 8,2 Z"
Stroke="{DynamicResource Wd.Text.Secondary}"
StrokeThickness="1.4"
Fill="{DynamicResource Wd.Text.Secondary}"
Width="14" Height="14"
Stretch="None"/>
</Button>
<Button Style="{StaticResource Wd.Button.Ghost}"
Click="OnSettingsToggleClick"
Padding="6,4"
ToolTip="Settings">
<Path Data="M 8,1 L 8,3 M 8,13 L 8,15 M 1,8 L 3,8 M 13,8 L 15,8 M 3,3 L 4.5,4.5 M 11.5,11.5 L 13,13 M 3,13 L 4.5,11.5 M 11.5,4.5 L 13,3 M 8,5.5 C 9.4,5.5 10.5,6.6 10.5,8 C 10.5,9.4 9.4,10.5 8,10.5 C 6.6,10.5 5.5,9.4 5.5,8 C 5.5,6.6 6.6,5.5 8,5.5"
Stroke="{DynamicResource Wd.Text.Secondary}"
StrokeThickness="1.4"
feat(ui): rebrand to Wild Dragon + Microsoft Teams layout
Replaces the Stone theme with Wild Dragon branding (canvas #0A0A0A, accent cyan #97EDF0, secondary #9AE0FD, coral alert #FB819C — sourced from wilddragon.net) and reorganizes MainWindow into a Microsoft Teams-style three-column layout: a 72px left rail (logo + Participants/Settings nav + engine-status indicator), a center content area (header + participants card), and a right settings panel.
Adds InitialsConverter so participant avatars render real initials (Brendon Power -> 'BP', '(Local)' -> 'L') instead of a generic glyph. Drops the obsolete StoneTheme.xaml; the project now ships exactly one theme dictionary.
Typography: Inter (with Segoe UI Variable Display fallback) for the sans stack, JetBrains Mono (Cascadia Mono fallback) for machine names and timecodes — matching the wilddragon.net site.
Verified live against the running Teams meeting: app launches, participant 'Brendon Power' displays with avatar, settings panel surfaces NDI groups + Hide-Local toggle, engine status pill shows green/live.
2026-05-08 00:08:39 -04:00
Fill="Transparent"
2026-05-14 12:46:24 -04:00
Width="14" Height="14"
Stretch="None"/>
</Button>
</StackPanel>
</Grid>
</Border>
feat(ui): rebrand to Wild Dragon + Microsoft Teams layout
Replaces the Stone theme with Wild Dragon branding (canvas #0A0A0A, accent cyan #97EDF0, secondary #9AE0FD, coral alert #FB819C — sourced from wilddragon.net) and reorganizes MainWindow into a Microsoft Teams-style three-column layout: a 72px left rail (logo + Participants/Settings nav + engine-status indicator), a center content area (header + participants card), and a right settings panel.
Adds InitialsConverter so participant avatars render real initials (Brendon Power -> 'BP', '(Local)' -> 'L') instead of a generic glyph. Drops the obsolete StoneTheme.xaml; the project now ships exactly one theme dictionary.
Typography: Inter (with Segoe UI Variable Display fallback) for the sans stack, JetBrains Mono (Cascadia Mono fallback) for machine names and timecodes — matching the wilddragon.net site.
Verified live against the running Teams meeting: app launches, participant 'Brendon Power' displays with avatar, settings panel surfaces NDI groups + Hide-Local toggle, engine status pill shows green/live.
2026-05-08 00:08:39 -04:00
2026-05-14 12:46:24 -04:00
<!-- ═════════════════════ TRANSPORT STRIP (40px) ═════════════════════ -->
<Border Grid.Row="1"
Background="{DynamicResource Wd.Canvas}"
BorderBrush="{DynamicResource Wd.Border}"
BorderThickness="0,0,0,1">
<Grid Margin="20,0,20,0" VerticalAlignment="Center">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<StackPanel Grid.Column="0"
Orientation="Horizontal"
VerticalAlignment="Center">
<!-- Session timer — green dot + elapsed when at least one ISO is live -->
<StackPanel Orientation="Horizontal"
VerticalAlignment="Center"
Margin="0,0,20,0"
Visibility="{Binding IsSessionActive, Converter={StaticResource BoolToVis}}">
<Ellipse Width="7" Height="7"
Fill="{DynamicResource Wd.Accent.Cyan}"
VerticalAlignment="Center"/>
<TextBlock Text="{Binding SessionElapsed}"
FontFamily="{StaticResource Wd.Font.Mono}"
FontSize="12"
FontWeight="Medium"
Foreground="{DynamicResource Wd.Text.Primary}"
VerticalAlignment="Center"
Margin="8,0,0,0"/>
</StackPanel>
2026-05-08 01:05:26 -04:00
2026-05-14 12:46:24 -04:00
<!-- Participant counts — labels in tracked smallcaps, values in mono.
The space between value and next label uses a middle dot. -->
<StackPanel Orientation="Horizontal"
VerticalAlignment="Center"
Margin="0,0,20,0">
<TextBlock Text="PART"
FontFamily="{StaticResource Wd.Font.Sans}"
FontSize="10"
FontWeight="SemiBold"
Foreground="{DynamicResource Wd.Text.Tertiary}"
VerticalAlignment="Center"
Margin="0,0,6,0"/>
<TextBlock Text="{Binding ParticipantCount, Mode=OneWay, FallbackValue=0}"
FontFamily="{StaticResource Wd.Font.Mono}"
FontSize="12"
Foreground="{DynamicResource Wd.Text.Primary}"
VerticalAlignment="Center"/>
<TextBlock Text=" · "
FontFamily="{StaticResource Wd.Font.Mono}"
FontSize="12"
Foreground="{DynamicResource Wd.Text.Disabled}"
VerticalAlignment="Center"/>
<TextBlock Text="LIVE"
FontFamily="{StaticResource Wd.Font.Sans}"
FontSize="10"
FontWeight="SemiBold"
Foreground="{DynamicResource Wd.Text.Tertiary}"
VerticalAlignment="Center"
Margin="0,0,6,0"/>
<TextBlock Text="{Binding LiveCount, Mode=OneWay, FallbackValue=0}"
FontFamily="{StaticResource Wd.Font.Mono}"
FontSize="12"
Foreground="{DynamicResource Wd.Accent.CyanText}"
VerticalAlignment="Center"/>
</StackPanel>
2026-05-10 09:41:28 -04:00
2026-05-14 12:46:24 -04:00
<!-- Teams meeting pill — only present in-call -->
<StackPanel Orientation="Horizontal"
VerticalAlignment="Center"
Margin="0,0,20,0"
Visibility="{Binding IsTeamsInCall, Converter={StaticResource BoolToVis}}">
<TextBlock Text="CALL"
FontFamily="{StaticResource Wd.Font.Sans}"
FontSize="10"
FontWeight="SemiBold"
Foreground="{DynamicResource Wd.Accent.CyanText}"
VerticalAlignment="Center"
Margin="0,0,6,0"/>
<TextBlock Text="{Binding TeamsMeetingState}"
FontFamily="{StaticResource Wd.Font.Mono}"
FontSize="11"
Foreground="{DynamicResource Wd.Text.Secondary}"
VerticalAlignment="Center"
MaxWidth="240"
TextTrimming="CharacterEllipsis"/>
</StackPanel>
feat(ui): rebrand to Wild Dragon + Microsoft Teams layout
Replaces the Stone theme with Wild Dragon branding (canvas #0A0A0A, accent cyan #97EDF0, secondary #9AE0FD, coral alert #FB819C — sourced from wilddragon.net) and reorganizes MainWindow into a Microsoft Teams-style three-column layout: a 72px left rail (logo + Participants/Settings nav + engine-status indicator), a center content area (header + participants card), and a right settings panel.
Adds InitialsConverter so participant avatars render real initials (Brendon Power -> 'BP', '(Local)' -> 'L') instead of a generic glyph. Drops the obsolete StoneTheme.xaml; the project now ships exactly one theme dictionary.
Typography: Inter (with Segoe UI Variable Display fallback) for the sans stack, JetBrains Mono (Cascadia Mono fallback) for machine names and timecodes — matching the wilddragon.net site.
Verified live against the running Teams meeting: app launches, participant 'Brendon Power' displays with avatar, settings panel surfaces NDI groups + Hide-Local toggle, engine status pill shows green/live.
2026-05-08 00:08:39 -04:00
2026-05-14 12:46:24 -04:00
</StackPanel>
2026-05-07 11:40:49 -04:00
2026-05-14 12:46:24 -04:00
<!-- Control surface — only present when listening, right-aligned -->
<StackPanel Grid.Column="2"
Orientation="Horizontal"
VerticalAlignment="Center"
Visibility="{Binding IsControlSurfaceRunning, Converter={StaticResource BoolToVis}}">
<TextBlock Text="CTRL"
FontFamily="{StaticResource Wd.Font.Sans}"
FontSize="10"
FontWeight="SemiBold"
Foreground="{DynamicResource Wd.Text.Tertiary}"
VerticalAlignment="Center"
Margin="0,0,6,0"/>
<TextBlock Text="{Binding ControlSurfaceText}"
FontFamily="{StaticResource Wd.Font.Mono}"
FontSize="11"
Foreground="{DynamicResource Wd.Accent.CyanText}"
VerticalAlignment="Center"/>
</StackPanel>
</Grid>
</Border>
feat(ui): rebrand to Wild Dragon + Microsoft Teams layout
Replaces the Stone theme with Wild Dragon branding (canvas #0A0A0A, accent cyan #97EDF0, secondary #9AE0FD, coral alert #FB819C — sourced from wilddragon.net) and reorganizes MainWindow into a Microsoft Teams-style three-column layout: a 72px left rail (logo + Participants/Settings nav + engine-status indicator), a center content area (header + participants card), and a right settings panel.
Adds InitialsConverter so participant avatars render real initials (Brendon Power -> 'BP', '(Local)' -> 'L') instead of a generic glyph. Drops the obsolete StoneTheme.xaml; the project now ships exactly one theme dictionary.
Typography: Inter (with Segoe UI Variable Display fallback) for the sans stack, JetBrains Mono (Cascadia Mono fallback) for machine names and timecodes — matching the wilddragon.net site.
Verified live against the running Teams meeting: app launches, participant 'Brendon Power' displays with avatar, settings panel surfaces NDI groups + Hide-Local toggle, engine status pill shows green/live.
2026-05-08 00:08:39 -04:00
2026-05-14 12:46:24 -04:00
<!-- ═════════════════════ BODY ═════════════════════ -->
<Grid Grid.Row="2">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<!-- Alert banner — appears when AlertBanner.IsVisible flips on -->
<Border Grid.Row="0"
feat(ui): rebrand to Wild Dragon + Microsoft Teams layout
Replaces the Stone theme with Wild Dragon branding (canvas #0A0A0A, accent cyan #97EDF0, secondary #9AE0FD, coral alert #FB819C — sourced from wilddragon.net) and reorganizes MainWindow into a Microsoft Teams-style three-column layout: a 72px left rail (logo + Participants/Settings nav + engine-status indicator), a center content area (header + participants card), and a right settings panel.
Adds InitialsConverter so participant avatars render real initials (Brendon Power -> 'BP', '(Local)' -> 'L') instead of a generic glyph. Drops the obsolete StoneTheme.xaml; the project now ships exactly one theme dictionary.
Typography: Inter (with Segoe UI Variable Display fallback) for the sans stack, JetBrains Mono (Cascadia Mono fallback) for machine names and timecodes — matching the wilddragon.net site.
Verified live against the running Teams meeting: app launches, participant 'Brendon Power' displays with avatar, settings panel surfaces NDI groups + Hide-Local toggle, engine status pill shows green/live.
2026-05-08 00:08:39 -04:00
Background="{DynamicResource Wd.Accent.CoralBg}"
BorderBrush="{DynamicResource Wd.Accent.Coral}"
BorderThickness="0,0,0,1"
2026-05-14 12:46:24 -04:00
Padding="24,10"
feat(ui): rebrand to Wild Dragon + Microsoft Teams layout
Replaces the Stone theme with Wild Dragon branding (canvas #0A0A0A, accent cyan #97EDF0, secondary #9AE0FD, coral alert #FB819C — sourced from wilddragon.net) and reorganizes MainWindow into a Microsoft Teams-style three-column layout: a 72px left rail (logo + Participants/Settings nav + engine-status indicator), a center content area (header + participants card), and a right settings panel.
Adds InitialsConverter so participant avatars render real initials (Brendon Power -> 'BP', '(Local)' -> 'L') instead of a generic glyph. Drops the obsolete StoneTheme.xaml; the project now ships exactly one theme dictionary.
Typography: Inter (with Segoe UI Variable Display fallback) for the sans stack, JetBrains Mono (Cascadia Mono fallback) for machine names and timecodes — matching the wilddragon.net site.
Verified live against the running Teams meeting: app launches, participant 'Brendon Power' displays with avatar, settings panel surfaces NDI groups + Hide-Local toggle, engine status pill shows green/live.
2026-05-08 00:08:39 -04:00
Visibility="{Binding AlertBanner.IsVisible, Converter={StaticResource BoolToVis}}">
<DockPanel>
<Button DockPanel.Dock="Right"
Style="{StaticResource Wd.Button.Ghost}"
Content="Dismiss"
Command="{Binding AlertBanner.DismissCommand}"
Margin="12,0,0,0"/>
<StackPanel Orientation="Horizontal" VerticalAlignment="Center">
2026-05-14 12:46:24 -04:00
<Ellipse Width="7" Height="7"
feat(ui): rebrand to Wild Dragon + Microsoft Teams layout
Replaces the Stone theme with Wild Dragon branding (canvas #0A0A0A, accent cyan #97EDF0, secondary #9AE0FD, coral alert #FB819C — sourced from wilddragon.net) and reorganizes MainWindow into a Microsoft Teams-style three-column layout: a 72px left rail (logo + Participants/Settings nav + engine-status indicator), a center content area (header + participants card), and a right settings panel.
Adds InitialsConverter so participant avatars render real initials (Brendon Power -> 'BP', '(Local)' -> 'L') instead of a generic glyph. Drops the obsolete StoneTheme.xaml; the project now ships exactly one theme dictionary.
Typography: Inter (with Segoe UI Variable Display fallback) for the sans stack, JetBrains Mono (Cascadia Mono fallback) for machine names and timecodes — matching the wilddragon.net site.
Verified live against the running Teams meeting: app launches, participant 'Brendon Power' displays with avatar, settings panel surfaces NDI groups + Hide-Local toggle, engine status pill shows green/live.
2026-05-08 00:08:39 -04:00
Fill="{DynamicResource Wd.Accent.Coral}"
VerticalAlignment="Center"
2026-05-14 12:46:24 -04:00
Margin="0,0,10,0"/>
feat(ui): rebrand to Wild Dragon + Microsoft Teams layout
Replaces the Stone theme with Wild Dragon branding (canvas #0A0A0A, accent cyan #97EDF0, secondary #9AE0FD, coral alert #FB819C — sourced from wilddragon.net) and reorganizes MainWindow into a Microsoft Teams-style three-column layout: a 72px left rail (logo + Participants/Settings nav + engine-status indicator), a center content area (header + participants card), and a right settings panel.
Adds InitialsConverter so participant avatars render real initials (Brendon Power -> 'BP', '(Local)' -> 'L') instead of a generic glyph. Drops the obsolete StoneTheme.xaml; the project now ships exactly one theme dictionary.
Typography: Inter (with Segoe UI Variable Display fallback) for the sans stack, JetBrains Mono (Cascadia Mono fallback) for machine names and timecodes — matching the wilddragon.net site.
Verified live against the running Teams meeting: app launches, participant 'Brendon Power' displays with avatar, settings panel surfaces NDI groups + Hide-Local toggle, engine status pill shows green/live.
2026-05-08 00:08:39 -04:00
<TextBlock Text="{Binding AlertBanner.Message}"
Style="{StaticResource Wd.Text.Body}"
FontWeight="SemiBold"
VerticalAlignment="Center"/>
</StackPanel>
</DockPanel>
</Border>
2026-05-14 12:46:24 -04:00
<!-- Update banner — Forgejo release available -->
<Border Grid.Row="1"
Margin="20,12,20,0"
Padding="14,10"
Background="{DynamicResource Wd.Accent.CyanMuted}"
BorderBrush="{DynamicResource Wd.Accent.Cyan}"
BorderThickness="1"
CornerRadius="{StaticResource Radius.M}"
Visibility="{Binding UpdateBanner.IsVisible, Converter={StaticResource BoolToVis}}">
feat(ui): rebrand to Wild Dragon + Microsoft Teams layout
Replaces the Stone theme with Wild Dragon branding (canvas #0A0A0A, accent cyan #97EDF0, secondary #9AE0FD, coral alert #FB819C — sourced from wilddragon.net) and reorganizes MainWindow into a Microsoft Teams-style three-column layout: a 72px left rail (logo + Participants/Settings nav + engine-status indicator), a center content area (header + participants card), and a right settings panel.
Adds InitialsConverter so participant avatars render real initials (Brendon Power -> 'BP', '(Local)' -> 'L') instead of a generic glyph. Drops the obsolete StoneTheme.xaml; the project now ships exactly one theme dictionary.
Typography: Inter (with Segoe UI Variable Display fallback) for the sans stack, JetBrains Mono (Cascadia Mono fallback) for machine names and timecodes — matching the wilddragon.net site.
Verified live against the running Teams meeting: app launches, participant 'Brendon Power' displays with avatar, settings panel surfaces NDI groups + Hide-Local toggle, engine status pill shows green/live.
2026-05-08 00:08:39 -04:00
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
2026-05-10 09:41:28 -04:00
<ColumnDefinition Width="Auto"/>
feat(ui): rebrand to Wild Dragon + Microsoft Teams layout
Replaces the Stone theme with Wild Dragon branding (canvas #0A0A0A, accent cyan #97EDF0, secondary #9AE0FD, coral alert #FB819C — sourced from wilddragon.net) and reorganizes MainWindow into a Microsoft Teams-style three-column layout: a 72px left rail (logo + Participants/Settings nav + engine-status indicator), a center content area (header + participants card), and a right settings panel.
Adds InitialsConverter so participant avatars render real initials (Brendon Power -> 'BP', '(Local)' -> 'L') instead of a generic glyph. Drops the obsolete StoneTheme.xaml; the project now ships exactly one theme dictionary.
Typography: Inter (with Segoe UI Variable Display fallback) for the sans stack, JetBrains Mono (Cascadia Mono fallback) for machine names and timecodes — matching the wilddragon.net site.
Verified live against the running Teams meeting: app launches, participant 'Brendon Power' displays with avatar, settings panel surfaces NDI groups + Hide-Local toggle, engine status pill shows green/live.
2026-05-08 00:08:39 -04:00
</Grid.ColumnDefinitions>
2026-05-14 12:46:24 -04:00
<Ellipse Grid.Column="0"
Width="7" Height="7"
Fill="{DynamicResource Wd.Accent.Cyan}"
VerticalAlignment="Center"
Margin="0,0,12,0"/>
feat(ui): rebrand to Wild Dragon + Microsoft Teams layout
Replaces the Stone theme with Wild Dragon branding (canvas #0A0A0A, accent cyan #97EDF0, secondary #9AE0FD, coral alert #FB819C — sourced from wilddragon.net) and reorganizes MainWindow into a Microsoft Teams-style three-column layout: a 72px left rail (logo + Participants/Settings nav + engine-status indicator), a center content area (header + participants card), and a right settings panel.
Adds InitialsConverter so participant avatars render real initials (Brendon Power -> 'BP', '(Local)' -> 'L') instead of a generic glyph. Drops the obsolete StoneTheme.xaml; the project now ships exactly one theme dictionary.
Typography: Inter (with Segoe UI Variable Display fallback) for the sans stack, JetBrains Mono (Cascadia Mono fallback) for machine names and timecodes — matching the wilddragon.net site.
Verified live against the running Teams meeting: app launches, participant 'Brendon Power' displays with avatar, settings panel surfaces NDI groups + Hide-Local toggle, engine status pill shows green/live.
2026-05-08 00:08:39 -04:00
<TextBlock Grid.Column="1"
2026-05-14 12:46:24 -04:00
Text="{Binding UpdateBanner.Message}"
Style="{StaticResource Wd.Text.Body}"
FontSize="12"
feat(ui): rebuild MainWindow with Stone-theme design system
Adopts the design language from Dammyjay93/interface-design: warm Stone neutrals, accent orange (#EA580C), borders-only depth (no shadows), 8px spacing grid, all-caps section labels, mono typography for machine names and timecodes.
Themes/StoneTheme.xaml is the single source of truth for design tokens (color brushes, typography styles, spacing) plus restyled control templates (Button, TextBox, ComboBox, ComboBoxItem, CheckBox, DataGrid + DataGridColumnHeader / DataGridRow / DataGridCell, ScrollBar). MainWindow consumes the tokens and is laid out with a header (title + status pill), section-headed Settings sidebar (Output Format / NDI Network / Display), card-wrapped participant list, and a mono status footer.
Settings sidebar surfaces the new NDI group configuration (discovery + output) and a Hide-(Local) checkbox. The latter filters the user's own self-preview from the participants list at the MainViewModel layer (HideLocalSelf=true by default) so operators don't accidentally route their own preview as an ISO. Apply Changes round-trips both FrameProcessingSettings and NdiGroupSettings through the controller in one go.
2026-05-07 23:58:02 -04:00
VerticalAlignment="Center"/>
2026-05-14 12:46:24 -04:00
<Button Grid.Column="2"
Style="{StaticResource Wd.Button.Ghost}"
Content="Get update"
Command="{Binding UpdateBanner.OpenReleasePageCommand}"
Padding="14,4"
Margin="0,0,8,0"/>
<Button Grid.Column="3"
Style="{StaticResource Wd.Button.Ghost}"
Content="Dismiss"
Command="{Binding UpdateBanner.DismissCommand}"
Padding="14,4"/>
feat(ui): rebrand to Wild Dragon + Microsoft Teams layout
Replaces the Stone theme with Wild Dragon branding (canvas #0A0A0A, accent cyan #97EDF0, secondary #9AE0FD, coral alert #FB819C — sourced from wilddragon.net) and reorganizes MainWindow into a Microsoft Teams-style three-column layout: a 72px left rail (logo + Participants/Settings nav + engine-status indicator), a center content area (header + participants card), and a right settings panel.
Adds InitialsConverter so participant avatars render real initials (Brendon Power -> 'BP', '(Local)' -> 'L') instead of a generic glyph. Drops the obsolete StoneTheme.xaml; the project now ships exactly one theme dictionary.
Typography: Inter (with Segoe UI Variable Display fallback) for the sans stack, JetBrains Mono (Cascadia Mono fallback) for machine names and timecodes — matching the wilddragon.net site.
Verified live against the running Teams meeting: app launches, participant 'Brendon Power' displays with avatar, settings panel surfaces NDI groups + Hide-Local toggle, engine status pill shows green/live.
2026-05-08 00:08:39 -04:00
</Grid>
</Border>
2026-05-14 12:46:24 -04:00
<!-- Action toolbar — primary verbs above the participants table -->
<Border Grid.Row="2"
Padding="20,14,20,12">
<StackPanel Orientation="Horizontal" VerticalAlignment="Center">
<Button Style="{StaticResource Wd.Button.Primary}"
Command="{Binding EnableAllOnlineCommand}"
Content="Enable all"
Padding="14,7"
Margin="0,0,8,0"
ToolTip="Enable ISO routing for every online participant"/>
<Button Style="{StaticResource Wd.Button.Ghost}"
Command="{Binding RefreshDiscoveryCommand}"
Content="Refresh"
Padding="14,7"
Margin="0,0,8,0"
ToolTip="Refresh NDI discovery (Ctrl+R)"/>
<Button Style="{StaticResource Wd.Button.Ghost}"
Click="OnPresetsClick"
Content="Presets"
Padding="14,7"
Margin="0,0,8,0"
ToolTip="Open operator presets"/>
<Button Style="{StaticResource Wd.Button.Ghost}"
Command="{Binding StopAllIsosCommand}"
Content="Stop all"
Padding="14,7"
ToolTip="Disable every running ISO (Ctrl+Shift+S)"/>
<!-- Teams launch / hide — small icon buttons, after a separator -->
<Border Width="1"
Background="{DynamicResource Wd.Border}"
Margin="14,4,14,4"/>
<Button Style="{StaticResource Wd.Button.Ghost}"
Click="OnLaunchTeamsClick"
MouseRightButtonUp="OnLaunchTeamsRightClick"
Padding="10,7"
Margin="0,0,4,0"
ToolTip="Launch Microsoft Teams (right-click to stop)">
<Path Data="M 4,8 L 16,8 L 16,16 L 4,16 Z M 16,11 L 22,8 L 22,16 L 16,13 Z"
Stroke="{DynamicResource Wd.Text.Secondary}"
StrokeThickness="1.5"
Fill="Transparent"
StrokeLineJoin="Round"
Width="18" Height="14"
Stretch="Uniform"/>
</Button>
<Button Style="{StaticResource Wd.Button.Ghost}"
Click="OnToggleTeamsWindowClick"
Padding="10,7"
ToolTip="Hide / show Teams windows">
<Path Data="M 1,11 C 5,4 17,4 21,11 C 17,18 5,18 1,11 Z M 11,8 A 3,3 0 1 1 11,14 A 3,3 0 1 1 11,8 Z"
Stroke="{DynamicResource Wd.Text.Secondary}"
StrokeThickness="1.4"
Fill="Transparent"
Width="18" Height="14"
Stretch="Uniform"/>
</Button>
</StackPanel>
</Border>
feat(ui): toast feedback for settings actions; refresh _NEXT.md
Adds a small auto-dismissing pill notification at the bottom-center of the participants area: 'Settings saved' on Apply Changes, 'Transcoder topology applied — restart Teams to take effect' after the one-click NDI groups setup. ToastViewModel owns its own DispatcherTimer and resets the dismissal countdown on successive calls, so the most recent message is always the one visible. Hooked into MainViewModel and threaded into GlobalSettingsViewModel via constructor injection.
_NEXT.md rewritten to reflect the May 2026 hardening pass: separates engine / UI / networking / Phase E.1 / diagnostics / CI / tests sections, lists every shipped item, and re-prioritizes the remaining work (Phase E.2-E.3 embedded Teams, code-signing the MSI, refresh-discovery affordance, output thumbnail previews, settings panel UX, auto-disable on departure, operator presets).
2026-05-09 09:30:04 -04:00
feat(wpf): v2 task 39+40 - studio table redesign + Ctrl+K command palette
Task 39: 5-column participants table - state LED, name+codec caption, 5-bar audio meter, mono output name, ISO pill. Row height 52, full-row active-speaker tint (no left stripe). New converter LevelThresholdConverter, OutputName property on ParticipantViewModel.
Task 40: Ctrl+K / Ctrl+P command palette - chromeless centered floating window, fuzzy Contains match across Label/Category/Keywords, arrow nav, Enter invoke, Esc close. Quick/Teams/Network/App categories cover top operator verbs and theme switching.
Also: log startup exceptions to Serilog before the modal MessageBox fires - much better triage signal than user-pasted dialog text.
2026-05-15 11:15:00 -04:00
<!--
Participants table — v2 "Studio Terminal" layout.
polish(mainwindow): empty state, table widths, strings, theme tooltip
Walks the v2 polish punch list against MainWindow.
- Theme button tooltip is now "Theme (System / Dark / Light)" per the
v2 shape brief, replacing the previous "Toggle theme (Ctrl+T)".
- Participants table column widths match spec: Output 130px (was 150),
ISO pill 100px (was 110). The 24px state LED, 110px audio meter, and
52px row height already matched. The 106px Preview thumbnail column
and 32px gear-button column are intentional deviations (live thumbs
were restored at 4944de5; per-ISO override gear added at the same
time) and are now called out in the column-spec comment so a future
reader doesn't try to "fix" them.
- Empty-state placeholder finally renders when ParticipantCount == 0:
mono sentence "no ndi sources yet — open teams and start a meeting"
+ a tertiary Refresh discovery button — exactly the copy specified
by the shape brief's empty-states section. CountToVisibilityConverter
is now declared in MainWindow.Resources (it shipped as a class but
was never registered).
- OnClosing wraps WindowStateStore.Save in a try/catch so a serialization
or filesystem fault on shutdown can never block the window from
closing. Save itself already swallows its own IO errors; this is
defense-in-depth for anything that escapes.
- MessageBox copy in MainWindow.xaml.cs (Hide/show Teams, Launch Teams,
Stop Teams) moves to Properties/Strings.resx + a hand-written
Properties/Strings.Designer.cs accessor. ResourceManager reads it by
basename "TeamsISO.App.Properties.Strings"; LogicalName is set on the
EmbeddedResource so the manifest name is predictable regardless of
how MSBuild would otherwise compute it. Future-localization seam.
OnLaunchTeamsRightClick's confirmation dialog is intentional — it
guards a destructive mid-show action — and the code-behind comment
now says so; the palette also offers Stop Teams as the keyboard
surface, so the right-click affordance isn't the only one.
Build clean (0 warnings, 0 errors); 160 tests still pass (56 App +
104 Engine, Category!=ndi&requires!=ndi filter).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-15 19:26:23 -04:00
Spec columns (from docs/shapes/2026-05-13-…studio-terminal.md):
feat(wpf): v2 task 39+40 - studio table redesign + Ctrl+K command palette
Task 39: 5-column participants table - state LED, name+codec caption, 5-bar audio meter, mono output name, ISO pill. Row height 52, full-row active-speaker tint (no left stripe). New converter LevelThresholdConverter, OutputName property on ParticipantViewModel.
Task 40: Ctrl+K / Ctrl+P command palette - chromeless centered floating window, fuzzy Contains match across Label/Category/Keywords, arrow nav, Enter invoke, Esc close. Quick/Teams/Network/App categories cover top operator verbs and theme switching.
Also: log startup exceptions to Serilog before the modal MessageBox fires - much better triage signal than user-pasted dialog text.
2026-05-15 11:15:00 -04:00
1. State LED 24px — 8× 8 hard-edged square. Filled cyan
when LIVE; filled coral on ERROR;
filled amber on NO SIGNAL / STARTING;
hollow neutral when OFF.
2. Name + caption * — DisplayName (Inter 13/Medium) plus
source machine + state label below
in JetBrains Mono 11/Tertiary.
3. Audio meter 110px — five vertical hard-edged bars,
each lit when DisplayedAudioLevel
crosses its threshold (0.2, 0.4,
0.6, 0.8, 1.0). No averaging.
polish(mainwindow): empty state, table widths, strings, theme tooltip
Walks the v2 polish punch list against MainWindow.
- Theme button tooltip is now "Theme (System / Dark / Light)" per the
v2 shape brief, replacing the previous "Toggle theme (Ctrl+T)".
- Participants table column widths match spec: Output 130px (was 150),
ISO pill 100px (was 110). The 24px state LED, 110px audio meter, and
52px row height already matched. The 106px Preview thumbnail column
and 32px gear-button column are intentional deviations (live thumbs
were restored at 4944de5; per-ISO override gear added at the same
time) and are now called out in the column-spec comment so a future
reader doesn't try to "fix" them.
- Empty-state placeholder finally renders when ParticipantCount == 0:
mono sentence "no ndi sources yet — open teams and start a meeting"
+ a tertiary Refresh discovery button — exactly the copy specified
by the shape brief's empty-states section. CountToVisibilityConverter
is now declared in MainWindow.Resources (it shipped as a class but
was never registered).
- OnClosing wraps WindowStateStore.Save in a try/catch so a serialization
or filesystem fault on shutdown can never block the window from
closing. Save itself already swallows its own IO errors; this is
defense-in-depth for anything that escapes.
- MessageBox copy in MainWindow.xaml.cs (Hide/show Teams, Launch Teams,
Stop Teams) moves to Properties/Strings.resx + a hand-written
Properties/Strings.Designer.cs accessor. ResourceManager reads it by
basename "TeamsISO.App.Properties.Strings"; LogicalName is set on the
EmbeddedResource so the manifest name is predictable regardless of
how MSBuild would otherwise compute it. Future-localization seam.
OnLaunchTeamsRightClick's confirmation dialog is intentional — it
guards a destructive mid-show action — and the code-behind comment
now says so; the palette also offers Stop Teams as the keyboard
surface, so the right-click affordance isn't the only one.
Build clean (0 warnings, 0 errors); 160 tests still pass (56 App +
104 Engine, Category!=ndi&requires!=ndi filter).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-15 19:26:23 -04:00
4. Output name 130px — JetBrains Mono 12 — the NDI source
feat(wpf): v2 task 39+40 - studio table redesign + Ctrl+K command palette
Task 39: 5-column participants table - state LED, name+codec caption, 5-bar audio meter, mono output name, ISO pill. Row height 52, full-row active-speaker tint (no left stripe). New converter LevelThresholdConverter, OutputName property on ParticipantViewModel.
Task 40: Ctrl+K / Ctrl+P command palette - chromeless centered floating window, fuzzy Contains match across Label/Category/Keywords, arrow nav, Enter invoke, Esc close. Quick/Teams/Network/App categories cover top operator verbs and theme switching.
Also: log startup exceptions to Serilog before the modal MessageBox fires - much better triage signal than user-pasted dialog text.
2026-05-15 11:15:00 -04:00
name TeamsISO broadcasts as.
polish(mainwindow): empty state, table widths, strings, theme tooltip
Walks the v2 polish punch list against MainWindow.
- Theme button tooltip is now "Theme (System / Dark / Light)" per the
v2 shape brief, replacing the previous "Toggle theme (Ctrl+T)".
- Participants table column widths match spec: Output 130px (was 150),
ISO pill 100px (was 110). The 24px state LED, 110px audio meter, and
52px row height already matched. The 106px Preview thumbnail column
and 32px gear-button column are intentional deviations (live thumbs
were restored at 4944de5; per-ISO override gear added at the same
time) and are now called out in the column-spec comment so a future
reader doesn't try to "fix" them.
- Empty-state placeholder finally renders when ParticipantCount == 0:
mono sentence "no ndi sources yet — open teams and start a meeting"
+ a tertiary Refresh discovery button — exactly the copy specified
by the shape brief's empty-states section. CountToVisibilityConverter
is now declared in MainWindow.Resources (it shipped as a class but
was never registered).
- OnClosing wraps WindowStateStore.Save in a try/catch so a serialization
or filesystem fault on shutdown can never block the window from
closing. Save itself already swallows its own IO errors; this is
defense-in-depth for anything that escapes.
- MessageBox copy in MainWindow.xaml.cs (Hide/show Teams, Launch Teams,
Stop Teams) moves to Properties/Strings.resx + a hand-written
Properties/Strings.Designer.cs accessor. ResourceManager reads it by
basename "TeamsISO.App.Properties.Strings"; LogicalName is set on the
EmbeddedResource so the manifest name is predictable regardless of
how MSBuild would otherwise compute it. Future-localization seam.
OnLaunchTeamsRightClick's confirmation dialog is intentional — it
guards a destructive mid-show action — and the code-behind comment
now says so; the palette also offers Stop Teams as the keyboard
surface, so the right-click affordance isn't the only one.
Build clean (0 warnings, 0 errors); 160 tests still pass (56 App +
104 Engine, Category!=ndi&requires!=ndi filter).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-15 19:26:23 -04:00
5. ISO toggle pill 100px — LIVE = cyan-muted fill with cyan
feat(wpf): v2 task 39+40 - studio table redesign + Ctrl+K command palette
Task 39: 5-column participants table - state LED, name+codec caption, 5-bar audio meter, mono output name, ISO pill. Row height 52, full-row active-speaker tint (no left stripe). New converter LevelThresholdConverter, OutputName property on ParticipantViewModel.
Task 40: Ctrl+K / Ctrl+P command palette - chromeless centered floating window, fuzzy Contains match across Label/Category/Keywords, arrow nav, Enter invoke, Esc close. Quick/Teams/Network/App categories cover top operator verbs and theme switching.
Also: log startup exceptions to Serilog before the modal MessageBox fires - much better triage signal than user-pasted dialog text.
2026-05-15 11:15:00 -04:00
text; OFF = hollow neutral; ERROR
gets the existing trigger swap.
polish(mainwindow): empty state, table widths, strings, theme tooltip
Walks the v2 polish punch list against MainWindow.
- Theme button tooltip is now "Theme (System / Dark / Light)" per the
v2 shape brief, replacing the previous "Toggle theme (Ctrl+T)".
- Participants table column widths match spec: Output 130px (was 150),
ISO pill 100px (was 110). The 24px state LED, 110px audio meter, and
52px row height already matched. The 106px Preview thumbnail column
and 32px gear-button column are intentional deviations (live thumbs
were restored at 4944de5; per-ISO override gear added at the same
time) and are now called out in the column-spec comment so a future
reader doesn't try to "fix" them.
- Empty-state placeholder finally renders when ParticipantCount == 0:
mono sentence "no ndi sources yet — open teams and start a meeting"
+ a tertiary Refresh discovery button — exactly the copy specified
by the shape brief's empty-states section. CountToVisibilityConverter
is now declared in MainWindow.Resources (it shipped as a class but
was never registered).
- OnClosing wraps WindowStateStore.Save in a try/catch so a serialization
or filesystem fault on shutdown can never block the window from
closing. Save itself already swallows its own IO errors; this is
defense-in-depth for anything that escapes.
- MessageBox copy in MainWindow.xaml.cs (Hide/show Teams, Launch Teams,
Stop Teams) moves to Properties/Strings.resx + a hand-written
Properties/Strings.Designer.cs accessor. ResourceManager reads it by
basename "TeamsISO.App.Properties.Strings"; LogicalName is set on the
EmbeddedResource so the manifest name is predictable regardless of
how MSBuild would otherwise compute it. Future-localization seam.
OnLaunchTeamsRightClick's confirmation dialog is intentional — it
guards a destructive mid-show action — and the code-behind comment
now says so; the palette also offers Stop Teams as the keyboard
surface, so the right-click affordance isn't the only one.
Build clean (0 warnings, 0 errors); 160 tests still pass (56 App +
104 Engine, Category!=ndi&requires!=ndi filter).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-15 19:26:23 -04:00
Deliberate deviations from the spec (operator preference, see
4944de5 — "restore live thumbnail preview column"):
• A 106px live thumbnail column sits between State LED and
Name. Replaces the table's previous role as the only place
to see what the operator is broadcasting; the pop-out
preview window is the secondary view.
• A 32px ghost-button cell on the right edge of Name opens
the per-ISO override dialog (framerate / resolution /
aspect / audio). Hidden on hover-out.
feat(wpf): v2 task 39+40 - studio table redesign + Ctrl+K command palette
Task 39: 5-column participants table - state LED, name+codec caption, 5-bar audio meter, mono output name, ISO pill. Row height 52, full-row active-speaker tint (no left stripe). New converter LevelThresholdConverter, OutputName property on ParticipantViewModel.
Task 40: Ctrl+K / Ctrl+P command palette - chromeless centered floating window, fuzzy Contains match across Label/Category/Keywords, arrow nav, Enter invoke, Esc close. Quick/Teams/Network/App categories cover top operator verbs and theme switching.
Also: log startup exceptions to Serilog before the modal MessageBox fires - much better triage signal than user-pasted dialog text.
2026-05-15 11:15:00 -04:00
Row height 52 (down from 56). Active speaker = full-row
bg.active-speaker tint set by the global DataGridRow style
(avoids the impeccable side-stripe-border ban).
-->
2026-05-14 12:46:24 -04:00
<Border Grid.Row="3"
Margin="20,0,20,12"
BorderBrush="{DynamicResource Wd.Border}"
BorderThickness="1"
CornerRadius="{StaticResource Radius.M}"
Background="{DynamicResource Wd.Surface}">
polish(mainwindow): empty state, table widths, strings, theme tooltip
Walks the v2 polish punch list against MainWindow.
- Theme button tooltip is now "Theme (System / Dark / Light)" per the
v2 shape brief, replacing the previous "Toggle theme (Ctrl+T)".
- Participants table column widths match spec: Output 130px (was 150),
ISO pill 100px (was 110). The 24px state LED, 110px audio meter, and
52px row height already matched. The 106px Preview thumbnail column
and 32px gear-button column are intentional deviations (live thumbs
were restored at 4944de5; per-ISO override gear added at the same
time) and are now called out in the column-spec comment so a future
reader doesn't try to "fix" them.
- Empty-state placeholder finally renders when ParticipantCount == 0:
mono sentence "no ndi sources yet — open teams and start a meeting"
+ a tertiary Refresh discovery button — exactly the copy specified
by the shape brief's empty-states section. CountToVisibilityConverter
is now declared in MainWindow.Resources (it shipped as a class but
was never registered).
- OnClosing wraps WindowStateStore.Save in a try/catch so a serialization
or filesystem fault on shutdown can never block the window from
closing. Save itself already swallows its own IO errors; this is
defense-in-depth for anything that escapes.
- MessageBox copy in MainWindow.xaml.cs (Hide/show Teams, Launch Teams,
Stop Teams) moves to Properties/Strings.resx + a hand-written
Properties/Strings.Designer.cs accessor. ResourceManager reads it by
basename "TeamsISO.App.Properties.Strings"; LogicalName is set on the
EmbeddedResource so the manifest name is predictable regardless of
how MSBuild would otherwise compute it. Future-localization seam.
OnLaunchTeamsRightClick's confirmation dialog is intentional — it
guards a destructive mid-show action — and the code-behind comment
now says so; the palette also offers Stop Teams as the keyboard
surface, so the right-click affordance isn't the only one.
Build clean (0 warnings, 0 errors); 160 tests still pass (56 App +
104 Engine, Category!=ndi&requires!=ndi filter).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-15 19:26:23 -04:00
<Grid>
2026-05-14 12:46:24 -04:00
<DataGrid x:Name="ParticipantsGrid"
ItemsSource="{Binding ParticipantsView}"
AutoGenerateColumns="False"
HeadersVisibility="Column"
GridLinesVisibility="None"
RowBackground="Transparent"
AlternatingRowBackground="Transparent"
BorderThickness="0"
CanUserAddRows="False"
CanUserDeleteRows="False"
CanUserResizeRows="False"
SelectionMode="Single"
SelectionUnit="FullRow"
polish(mainwindow): empty state, table widths, strings, theme tooltip
Walks the v2 polish punch list against MainWindow.
- Theme button tooltip is now "Theme (System / Dark / Light)" per the
v2 shape brief, replacing the previous "Toggle theme (Ctrl+T)".
- Participants table column widths match spec: Output 130px (was 150),
ISO pill 100px (was 110). The 24px state LED, 110px audio meter, and
52px row height already matched. The 106px Preview thumbnail column
and 32px gear-button column are intentional deviations (live thumbs
were restored at 4944de5; per-ISO override gear added at the same
time) and are now called out in the column-spec comment so a future
reader doesn't try to "fix" them.
- Empty-state placeholder finally renders when ParticipantCount == 0:
mono sentence "no ndi sources yet — open teams and start a meeting"
+ a tertiary Refresh discovery button — exactly the copy specified
by the shape brief's empty-states section. CountToVisibilityConverter
is now declared in MainWindow.Resources (it shipped as a class but
was never registered).
- OnClosing wraps WindowStateStore.Save in a try/catch so a serialization
or filesystem fault on shutdown can never block the window from
closing. Save itself already swallows its own IO errors; this is
defense-in-depth for anything that escapes.
- MessageBox copy in MainWindow.xaml.cs (Hide/show Teams, Launch Teams,
Stop Teams) moves to Properties/Strings.resx + a hand-written
Properties/Strings.Designer.cs accessor. ResourceManager reads it by
basename "TeamsISO.App.Properties.Strings"; LogicalName is set on the
EmbeddedResource so the manifest name is predictable regardless of
how MSBuild would otherwise compute it. Future-localization seam.
OnLaunchTeamsRightClick's confirmation dialog is intentional — it
guards a destructive mid-show action — and the code-behind comment
now says so; the palette also offers Stop Teams as the keyboard
surface, so the right-click affordance isn't the only one.
Build clean (0 warnings, 0 errors); 160 tests still pass (56 App +
104 Engine, Category!=ndi&requires!=ndi filter).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-15 19:26:23 -04:00
RowHeight="52"
Visibility="{Binding ParticipantCount, Converter={StaticResource CountToVis}}">
2026-05-14 12:46:24 -04:00
<DataGrid.Columns>
feat(wpf): v2 task 39+40 - studio table redesign + Ctrl+K command palette
Task 39: 5-column participants table - state LED, name+codec caption, 5-bar audio meter, mono output name, ISO pill. Row height 52, full-row active-speaker tint (no left stripe). New converter LevelThresholdConverter, OutputName property on ParticipantViewModel.
Task 40: Ctrl+K / Ctrl+P command palette - chromeless centered floating window, fuzzy Contains match across Label/Category/Keywords, arrow nav, Enter invoke, Esc close. Quick/Teams/Network/App categories cover top operator verbs and theme switching.
Also: log startup exceptions to Serilog before the modal MessageBox fires - much better triage signal than user-pasted dialog text.
2026-05-15 11:15:00 -04:00
<!-- Col 1 — State LED. 8× 8 hard-edged Rectangle.
Default fill is hollow (transparent with stroke). DataTriggers
swap the fill based on StateLabel: LIVE → cyan filled,
NO SIGNAL / STARTING → amber filled, ERROR → coral filled.
No rounding — broadcast vocabulary uses sharp LEDs. -->
<DataGridTemplateColumn Header="" Width="24" IsReadOnly="True">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Rectangle Width="8" Height="8"
HorizontalAlignment="Center"
VerticalAlignment="Center"
StrokeThickness="1.5">
<Rectangle.Style>
<Style TargetType="Rectangle">
<Setter Property="Fill" Value="Transparent"/>
<Setter Property="Stroke" Value="{DynamicResource Wd.BorderStrong}"/>
<Style.Triggers>
<DataTrigger Binding="{Binding StateLabel}" Value="LIVE">
<Setter Property="Fill" Value="{DynamicResource Wd.Accent.Cyan}"/>
<Setter Property="Stroke" Value="{DynamicResource Wd.Accent.Cyan}"/>
</DataTrigger>
<DataTrigger Binding="{Binding StateLabel}" Value="ERROR">
<Setter Property="Fill" Value="{DynamicResource Wd.Accent.Coral}"/>
<Setter Property="Stroke" Value="{DynamicResource Wd.Accent.Coral}"/>
</DataTrigger>
<DataTrigger Binding="{Binding StateLabel}" Value="NO SIGNAL">
<Setter Property="Fill" Value="{DynamicResource Wd.Status.Warn}"/>
<Setter Property="Stroke" Value="{DynamicResource Wd.Status.Warn}"/>
</DataTrigger>
<DataTrigger Binding="{Binding StateLabel}" Value="STARTING">
<Setter Property="Fill" Value="{DynamicResource Wd.Status.Warn}"/>
<Setter Property="Stroke" Value="{DynamicResource Wd.Status.Warn}"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Rectangle.Style>
</Rectangle>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
2026-05-15 14:33:00 -04:00
<!-- Col 2 — Live preview thumbnail. 96× 54 (16:9). WriteableBitmap
fed from the engine's most recent ProcessedFrame at the 1Hz
stats tick. Em-dash placeholder when no pipeline is running. -->
<DataGridTemplateColumn Header="Preview" Width="106" IsReadOnly="True">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Grid VerticalAlignment="Center" HorizontalAlignment="Left" Margin="0,0,8,0">
<Border Width="96" Height="54"
Background="{DynamicResource Wd.SurfaceElevated}"
BorderBrush="{DynamicResource Wd.Border}"
BorderThickness="1"
CornerRadius="4"
Visibility="{Binding HasThumbnail, Converter={StaticResource BoolToVisInverse}}">
<TextBlock Text="—"
Foreground="{DynamicResource Wd.Text.Tertiary}"
FontFamily="{StaticResource Wd.Font.Mono}"
FontSize="11"
HorizontalAlignment="Center"
VerticalAlignment="Center"/>
</Border>
<Border Width="96" Height="54"
BorderBrush="{DynamicResource Wd.Border}"
BorderThickness="1"
CornerRadius="4"
ClipToBounds="True"
Visibility="{Binding HasThumbnail, Converter={StaticResource BoolToVis}}">
<Image Source="{Binding Thumbnail}"
Stretch="UniformToFill"
RenderOptions.BitmapScalingMode="LowQuality"/>
</Border>
</Grid>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<!-- Col 3 — Name + caption. Display name (Inter 13/Medium) above,
feat(wpf): v2 task 39+40 - studio table redesign + Ctrl+K command palette
Task 39: 5-column participants table - state LED, name+codec caption, 5-bar audio meter, mono output name, ISO pill. Row height 52, full-row active-speaker tint (no left stripe). New converter LevelThresholdConverter, OutputName property on ParticipantViewModel.
Task 40: Ctrl+K / Ctrl+P command palette - chromeless centered floating window, fuzzy Contains match across Label/Category/Keywords, arrow nav, Enter invoke, Esc close. Quick/Teams/Network/App categories cover top operator verbs and theme switching.
Also: log startup exceptions to Serilog before the modal MessageBox fires - much better triage signal than user-pasted dialog text.
2026-05-15 11:15:00 -04:00
source machine + state caption (Mono 11/Tertiary) below. -->
<DataGridTemplateColumn Header="Participant" Width="*" IsReadOnly="True">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<StackPanel VerticalAlignment="Center">
<TextBlock Text="{Binding DisplayName}"
FontFamily="{StaticResource Wd.Font.Sans}"
FontSize="13"
FontWeight="Medium"
Foreground="{DynamicResource Wd.Text.Primary}"
TextTrimming="CharacterEllipsis"/>
<StackPanel Orientation="Horizontal" Margin="0,2,0,0">
<TextBlock Text="{Binding SourceMachine}"
FontFamily="{StaticResource Wd.Font.Mono}"
FontSize="11"
Foreground="{DynamicResource Wd.Text.Tertiary}"
TextTrimming="CharacterEllipsis"
MaxWidth="180"/>
<TextBlock Text=" · "
FontFamily="{StaticResource Wd.Font.Mono}"
FontSize="11"
Foreground="{DynamicResource Wd.Text.Disabled}"/>
<TextBlock Text="{Binding StateLabel}"
FontFamily="{StaticResource Wd.Font.Mono}"
FontSize="11"
Foreground="{DynamicResource Wd.Text.Tertiary}"/>
</StackPanel>
</StackPanel>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<!-- Col 3 — Audio meter. 5 vertical bars; each lit when
DisplayedAudioLevel crosses its threshold. -->
<DataGridTemplateColumn Header="Audio" Width="110" IsReadOnly="True">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal"
VerticalAlignment="Center"
HorizontalAlignment="Left">
<Rectangle Width="6" Height="6"
Margin="0,0,3,0"
Fill="{DynamicResource Wd.Accent.Cyan}"
Opacity="{Binding DisplayedAudioLevel, Converter={StaticResource LevelGate}, ConverterParameter=0.2}"
VerticalAlignment="Bottom"/>
<Rectangle Width="6" Height="10"
Margin="0,0,3,0"
Fill="{DynamicResource Wd.Accent.Cyan}"
Opacity="{Binding DisplayedAudioLevel, Converter={StaticResource LevelGate}, ConverterParameter=0.4}"
VerticalAlignment="Bottom"/>
<Rectangle Width="6" Height="14"
Margin="0,0,3,0"
Fill="{DynamicResource Wd.Accent.Cyan}"
Opacity="{Binding DisplayedAudioLevel, Converter={StaticResource LevelGate}, ConverterParameter=0.6}"
VerticalAlignment="Bottom"/>
<Rectangle Width="6" Height="18"
Margin="0,0,3,0"
Fill="{DynamicResource Wd.Status.Warn}"
Opacity="{Binding DisplayedAudioLevel, Converter={StaticResource LevelGate}, ConverterParameter=0.8}"
VerticalAlignment="Bottom"/>
<Rectangle Width="6" Height="22"
Fill="{DynamicResource Wd.Accent.Coral}"
Opacity="{Binding DisplayedAudioLevel, Converter={StaticResource LevelGate}, ConverterParameter=0.95}"
VerticalAlignment="Bottom"/>
</StackPanel>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<!-- Col 4 — Output name (mono). The NDI source name TeamsISO
will broadcast this participant as. -->
polish(mainwindow): empty state, table widths, strings, theme tooltip
Walks the v2 polish punch list against MainWindow.
- Theme button tooltip is now "Theme (System / Dark / Light)" per the
v2 shape brief, replacing the previous "Toggle theme (Ctrl+T)".
- Participants table column widths match spec: Output 130px (was 150),
ISO pill 100px (was 110). The 24px state LED, 110px audio meter, and
52px row height already matched. The 106px Preview thumbnail column
and 32px gear-button column are intentional deviations (live thumbs
were restored at 4944de5; per-ISO override gear added at the same
time) and are now called out in the column-spec comment so a future
reader doesn't try to "fix" them.
- Empty-state placeholder finally renders when ParticipantCount == 0:
mono sentence "no ndi sources yet — open teams and start a meeting"
+ a tertiary Refresh discovery button — exactly the copy specified
by the shape brief's empty-states section. CountToVisibilityConverter
is now declared in MainWindow.Resources (it shipped as a class but
was never registered).
- OnClosing wraps WindowStateStore.Save in a try/catch so a serialization
or filesystem fault on shutdown can never block the window from
closing. Save itself already swallows its own IO errors; this is
defense-in-depth for anything that escapes.
- MessageBox copy in MainWindow.xaml.cs (Hide/show Teams, Launch Teams,
Stop Teams) moves to Properties/Strings.resx + a hand-written
Properties/Strings.Designer.cs accessor. ResourceManager reads it by
basename "TeamsISO.App.Properties.Strings"; LogicalName is set on the
EmbeddedResource so the manifest name is predictable regardless of
how MSBuild would otherwise compute it. Future-localization seam.
OnLaunchTeamsRightClick's confirmation dialog is intentional — it
guards a destructive mid-show action — and the code-behind comment
now says so; the palette also offers Stop Teams as the keyboard
surface, so the right-click affordance isn't the only one.
Build clean (0 warnings, 0 errors); 160 tests still pass (56 App +
104 Engine, Category!=ndi&requires!=ndi filter).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-15 19:26:23 -04:00
<DataGridTemplateColumn Header="Output" Width="130" IsReadOnly="True">
feat(wpf): v2 task 39+40 - studio table redesign + Ctrl+K command palette
Task 39: 5-column participants table - state LED, name+codec caption, 5-bar audio meter, mono output name, ISO pill. Row height 52, full-row active-speaker tint (no left stripe). New converter LevelThresholdConverter, OutputName property on ParticipantViewModel.
Task 40: Ctrl+K / Ctrl+P command palette - chromeless centered floating window, fuzzy Contains match across Label/Category/Keywords, arrow nav, Enter invoke, Esc close. Quick/Teams/Network/App categories cover top operator verbs and theme switching.
Also: log startup exceptions to Serilog before the modal MessageBox fires - much better triage signal than user-pasted dialog text.
2026-05-15 11:15:00 -04:00
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding OutputName}"
FontFamily="{StaticResource Wd.Font.Mono}"
FontSize="12"
Foreground="{DynamicResource Wd.Text.Secondary}"
VerticalAlignment="Center"
TextTrimming="CharacterEllipsis"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
feat: per-ISO framerate/resolution/aspect/audio overrides + thumbnail BMP
Engine: IsoAssignment record gets optional Override (FrameProcessingSettings?). IsoController hydrates _overrides dict from config.json on startup, uses override at EnableIsoAsync, persists with assignment, exposes GetIsoOverride + SetIsoOverrideAsync. SetIsoOverrideAsync hot-swaps a running pipeline (Disable + 150ms delay + Enable) when the override changes.
REST: POST /participants/{id}/override (body: framerate/resolution/aspect/audio enum strings, all optional, missing fall back to globals); DELETE /participants/{id}/override clears. GET /participants now includes per-row effective {framerate, resolution, aspect, audio, isOverride} plus top-level globals block.
Web /ui: per-card collapsible override panel with four selects + Apply / Clear. OVR pill + cyan inset edge mark overridden rows. Open-panel state survives WS re-renders.
Desktop: per-row gear column in the v2 DataGrid opens IsoOverrideDialog (420x360) with four combos. Clear button removes the override.
Thumbnail endpoint switched from WPF JpegBitmapEncoder (NREs from non-UI HttpListener threads) to pure-managed 32bpp BMP encoder. Nearest-neighbor downscale to 192-wide. /participants/{id}/thumbnail.bmp; legacy .jpg URL still works.
Known limitation: ParticipantTracker regenerates IDs for display-name-keyed participants across process restarts, orphaning the persisted override. Override works within a session; cross-restart persistence is best-effort until the tracker is taught to use stable keys. Filed as task 43.
2026-05-15 15:31:32 -04:00
<!-- Col 5a — Per-row gear: opens the ISO override editor for this
participant. Narrow (32px) so the table still fits inside a
1280px window after the toggle column. -->
<DataGridTemplateColumn Header="" Width="32" IsReadOnly="True">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Button Style="{StaticResource Wd.Button.Ghost}"
Click="OnIsoOverrideClick"
Padding="6,4"
VerticalAlignment="Center"
HorizontalAlignment="Center"
ToolTip="Override output settings for this participant">
<Path Data="M 8,1 L 8,3 M 8,13 L 8,15 M 1,8 L 3,8 M 13,8 L 15,8 M 3,3 L 4.5,4.5 M 11.5,11.5 L 13,13 M 3,13 L 4.5,11.5 M 11.5,4.5 L 13,3 M 8,5.5 C 9.4,5.5 10.5,6.6 10.5,8 C 10.5,9.4 9.4,10.5 8,10.5 C 6.6,10.5 5.5,9.4 5.5,8 C 5.5,6.6 6.6,5.5 8,5.5"
Stroke="{DynamicResource Wd.Text.Secondary}"
StrokeThickness="1.4"
Fill="Transparent"
Width="14" Height="14"
Stretch="None"/>
</Button>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
feat(wpf): v2 task 39+40 - studio table redesign + Ctrl+K command palette
Task 39: 5-column participants table - state LED, name+codec caption, 5-bar audio meter, mono output name, ISO pill. Row height 52, full-row active-speaker tint (no left stripe). New converter LevelThresholdConverter, OutputName property on ParticipantViewModel.
Task 40: Ctrl+K / Ctrl+P command palette - chromeless centered floating window, fuzzy Contains match across Label/Category/Keywords, arrow nav, Enter invoke, Esc close. Quick/Teams/Network/App categories cover top operator verbs and theme switching.
Also: log startup exceptions to Serilog before the modal MessageBox fires - much better triage signal than user-pasted dialog text.
2026-05-15 11:15:00 -04:00
<!-- Col 5 — ISO toggle pill. LIVE = cyan-muted fill + cyan border + cyan text.
OFF = hollow neutral. Error states use the existing IsoToggle style. -->
polish(mainwindow): empty state, table widths, strings, theme tooltip
Walks the v2 polish punch list against MainWindow.
- Theme button tooltip is now "Theme (System / Dark / Light)" per the
v2 shape brief, replacing the previous "Toggle theme (Ctrl+T)".
- Participants table column widths match spec: Output 130px (was 150),
ISO pill 100px (was 110). The 24px state LED, 110px audio meter, and
52px row height already matched. The 106px Preview thumbnail column
and 32px gear-button column are intentional deviations (live thumbs
were restored at 4944de5; per-ISO override gear added at the same
time) and are now called out in the column-spec comment so a future
reader doesn't try to "fix" them.
- Empty-state placeholder finally renders when ParticipantCount == 0:
mono sentence "no ndi sources yet — open teams and start a meeting"
+ a tertiary Refresh discovery button — exactly the copy specified
by the shape brief's empty-states section. CountToVisibilityConverter
is now declared in MainWindow.Resources (it shipped as a class but
was never registered).
- OnClosing wraps WindowStateStore.Save in a try/catch so a serialization
or filesystem fault on shutdown can never block the window from
closing. Save itself already swallows its own IO errors; this is
defense-in-depth for anything that escapes.
- MessageBox copy in MainWindow.xaml.cs (Hide/show Teams, Launch Teams,
Stop Teams) moves to Properties/Strings.resx + a hand-written
Properties/Strings.Designer.cs accessor. ResourceManager reads it by
basename "TeamsISO.App.Properties.Strings"; LogicalName is set on the
EmbeddedResource so the manifest name is predictable regardless of
how MSBuild would otherwise compute it. Future-localization seam.
OnLaunchTeamsRightClick's confirmation dialog is intentional — it
guards a destructive mid-show action — and the code-behind comment
now says so; the palette also offers Stop Teams as the keyboard
surface, so the right-click affordance isn't the only one.
Build clean (0 warnings, 0 errors); 160 tests still pass (56 App +
104 Engine, Category!=ndi&requires!=ndi filter).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-15 19:26:23 -04:00
<DataGridTemplateColumn Header="ISO" Width="100">
2026-05-14 12:46:24 -04:00
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Button Command="{Binding ToggleIsoCommand}"
Margin="0,0,12,0"
feat(wpf): v2 task 39+40 - studio table redesign + Ctrl+K command palette
Task 39: 5-column participants table - state LED, name+codec caption, 5-bar audio meter, mono output name, ISO pill. Row height 52, full-row active-speaker tint (no left stripe). New converter LevelThresholdConverter, OutputName property on ParticipantViewModel.
Task 40: Ctrl+K / Ctrl+P command palette - chromeless centered floating window, fuzzy Contains match across Label/Category/Keywords, arrow nav, Enter invoke, Esc close. Quick/Teams/Network/App categories cover top operator verbs and theme switching.
Also: log startup exceptions to Serilog before the modal MessageBox fires - much better triage signal than user-pasted dialog text.
2026-05-15 11:15:00 -04:00
Padding="14,6"
VerticalAlignment="Center">
2026-05-14 12:46:24 -04:00
<Button.Style>
<Style TargetType="Button" BasedOn="{StaticResource Wd.Button.IsoToggle}">
<Setter Property="Content" Value="Enable"/>
<Style.Triggers>
<DataTrigger Binding="{Binding IsEnabled}" Value="True">
<Setter Property="Content" Value="● LIVE"/>
<Setter Property="Background" Value="{DynamicResource Wd.Accent.CyanMuted}"/>
<Setter Property="BorderBrush" Value="{DynamicResource Wd.Accent.Cyan}"/>
<Setter Property="Foreground" Value="{DynamicResource Wd.Accent.CyanText}"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Button.Style>
</Button>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
polish(mainwindow): empty state, table widths, strings, theme tooltip
Walks the v2 polish punch list against MainWindow.
- Theme button tooltip is now "Theme (System / Dark / Light)" per the
v2 shape brief, replacing the previous "Toggle theme (Ctrl+T)".
- Participants table column widths match spec: Output 130px (was 150),
ISO pill 100px (was 110). The 24px state LED, 110px audio meter, and
52px row height already matched. The 106px Preview thumbnail column
and 32px gear-button column are intentional deviations (live thumbs
were restored at 4944de5; per-ISO override gear added at the same
time) and are now called out in the column-spec comment so a future
reader doesn't try to "fix" them.
- Empty-state placeholder finally renders when ParticipantCount == 0:
mono sentence "no ndi sources yet — open teams and start a meeting"
+ a tertiary Refresh discovery button — exactly the copy specified
by the shape brief's empty-states section. CountToVisibilityConverter
is now declared in MainWindow.Resources (it shipped as a class but
was never registered).
- OnClosing wraps WindowStateStore.Save in a try/catch so a serialization
or filesystem fault on shutdown can never block the window from
closing. Save itself already swallows its own IO errors; this is
defense-in-depth for anything that escapes.
- MessageBox copy in MainWindow.xaml.cs (Hide/show Teams, Launch Teams,
Stop Teams) moves to Properties/Strings.resx + a hand-written
Properties/Strings.Designer.cs accessor. ResourceManager reads it by
basename "TeamsISO.App.Properties.Strings"; LogicalName is set on the
EmbeddedResource so the manifest name is predictable regardless of
how MSBuild would otherwise compute it. Future-localization seam.
OnLaunchTeamsRightClick's confirmation dialog is intentional — it
guards a destructive mid-show action — and the code-behind comment
now says so; the palette also offers Stop Teams as the keyboard
surface, so the right-click affordance isn't the only one.
Build clean (0 warnings, 0 errors); 160 tests still pass (56 App +
104 Engine, Category!=ndi&requires!=ndi filter).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-15 19:26:23 -04:00
<!-- Empty-state placeholder. Renders when no NDI participants
have been discovered yet. Mono sentence + one tertiary
Refresh button — no illustration, no mascot, per the v2
shape brief's empty-states section. -->
<StackPanel HorizontalAlignment="Center"
VerticalAlignment="Center"
Visibility="{Binding ParticipantCount, Converter={StaticResource CountToVis}, ConverterParameter=empty}">
<TextBlock Text="no ndi sources yet — open teams and start a meeting"
FontFamily="{StaticResource Wd.Font.Mono}"
FontSize="12"
Foreground="{DynamicResource Wd.Text.Tertiary}"
HorizontalAlignment="Center"/>
<Button Style="{StaticResource Wd.Button.Ghost}"
Command="{Binding RefreshDiscoveryCommand}"
Content="Refresh discovery (Ctrl+R)"
Padding="14,7"
Margin="0,14,0,0"
HorizontalAlignment="Center"
FontFamily="{StaticResource Wd.Font.Mono}"
FontSize="11"
ToolTip="Rebuild the NDI finder"/>
</StackPanel>
</Grid>
2026-05-14 12:46:24 -04:00
</Border>
</Grid>
2026-05-07 11:40:49 -04:00
2026-05-14 12:46:24 -04:00
<!-- ═════════════════════ MEETING BAR (conditional) ═════════════════════ -->
<Border Grid.Row="3"
Background="{DynamicResource Wd.SurfaceElevated}"
feat(ui): rebrand to Wild Dragon + Microsoft Teams layout
Replaces the Stone theme with Wild Dragon branding (canvas #0A0A0A, accent cyan #97EDF0, secondary #9AE0FD, coral alert #FB819C — sourced from wilddragon.net) and reorganizes MainWindow into a Microsoft Teams-style three-column layout: a 72px left rail (logo + Participants/Settings nav + engine-status indicator), a center content area (header + participants card), and a right settings panel.
Adds InitialsConverter so participant avatars render real initials (Brendon Power -> 'BP', '(Local)' -> 'L') instead of a generic glyph. Drops the obsolete StoneTheme.xaml; the project now ships exactly one theme dictionary.
Typography: Inter (with Segoe UI Variable Display fallback) for the sans stack, JetBrains Mono (Cascadia Mono fallback) for machine names and timecodes — matching the wilddragon.net site.
Verified live against the running Teams meeting: app launches, participant 'Brendon Power' displays with avatar, settings panel surfaces NDI groups + Hide-Local toggle, engine status pill shows green/live.
2026-05-08 00:08:39 -04:00
BorderBrush="{DynamicResource Wd.Border}"
2026-05-14 12:46:24 -04:00
BorderThickness="0,1,0,0"
Padding="20,10,20,10"
Visibility="{Binding IsTeamsInCall, Converter={StaticResource BoolToVis}}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<StackPanel Grid.Column="0"
Orientation="Horizontal"
VerticalAlignment="Center">
<TextBlock Text="IN CALL"
FontFamily="{StaticResource Wd.Font.Sans}"
FontSize="10"
FontWeight="SemiBold"
Foreground="{DynamicResource Wd.Accent.CyanText}"
VerticalAlignment="Center"
Margin="0,0,10,0"/>
<TextBlock Text="{Binding TeamsMeetingState}"
FontFamily="{StaticResource Wd.Font.Mono}"
FontSize="12"
Foreground="{DynamicResource Wd.Text.Primary}"
VerticalAlignment="Center"
MaxWidth="500"
TextTrimming="CharacterEllipsis"/>
</StackPanel>
2026-05-10 09:41:28 -04:00
2026-05-14 12:46:24 -04:00
<!-- Quick-join field — paste a meeting URL, click Join -->
<StackPanel Grid.Column="1"
Orientation="Horizontal"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Visibility="{Binding IsTeamsInCall, Converter={StaticResource BoolToVisInverse}}">
<!-- Hidden when in call to keep the bar uncluttered -->
</StackPanel>
2026-05-10 09:41:28 -04:00
2026-05-14 12:46:24 -04:00
<StackPanel Grid.Column="2"
Orientation="Horizontal"
VerticalAlignment="Center">
<Button Style="{StaticResource Wd.Button.Ghost}"
Command="{Binding ToggleMuteCommand}"
Content="Mute"
Padding="14,6"
Margin="0,0,6,0"
MinWidth="64"
ToolTip="Toggle microphone mute in Teams"/>
<Button Style="{StaticResource Wd.Button.Ghost}"
Command="{Binding ToggleCameraCommand}"
Content="Cam"
Padding="14,6"
Margin="0,0,6,0"
MinWidth="64"
ToolTip="Toggle camera in Teams"/>
<Button Style="{StaticResource Wd.Button.Ghost}"
Command="{Binding LeaveCallCommand}"
Content="Leave"
Padding="14,6"
MinWidth="64"
Foreground="{DynamicResource Wd.Accent.Coral}"
BorderBrush="{DynamicResource Wd.Accent.Coral}"
ToolTip="Leave the Teams call"/>
</StackPanel>
</Grid>
</Border>
2026-05-10 09:41:28 -04:00
2026-05-14 12:46:24 -04:00
<!-- ═════════════════════ SETTINGS DRAWER (overlay) ═════════════════════
Slides in over the main content from the right when the header gear
is clicked. Backdrop scrim is a semi-transparent overlay over the
whole window; clicking it dismisses the drawer. Esc also dismisses
(handled in code-behind). The drawer carries the same settings
surface as v1 — OUTPUT / NETWORK / DISPLAY tabs with the existing
bindings — so the engine wiring is preserved through the shell
rebuild. -->
<Grid Grid.Row="0"
Grid.RowSpan="4"
x:Name="SettingsDrawerOverlay"
Visibility="Collapsed">
<!-- Scrim — click to dismiss -->
<Border Background="#80000000"
MouseLeftButtonDown="OnSettingsScrimClick"/>
<!-- Drawer panel -->
<Border HorizontalAlignment="Right"
Width="420"
Background="{DynamicResource Wd.Surface}"
BorderBrush="{DynamicResource Wd.Border}"
BorderThickness="1,0,0,0">
<DockPanel LastChildFill="True">
<!-- Drawer header -->
<Border DockPanel.Dock="Top"
Padding="20,18,16,16"
BorderBrush="{DynamicResource Wd.Border}"
BorderThickness="0,0,0,1">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<StackPanel Grid.Column="0">
<TextBlock Text="Settings"
Style="{StaticResource Wd.Text.Title}"
FontSize="18"/>
<TextBlock Text="Per-session global processing"
2026-05-10 09:41:28 -04:00
Style="{StaticResource Wd.Text.Subtle}"
Foreground="{DynamicResource Wd.Text.Tertiary}"
2026-05-14 12:46:24 -04:00
Margin="0,2,0,0"/>
2026-05-10 09:41:28 -04:00
</StackPanel>
2026-05-14 12:46:24 -04:00
<Button Grid.Column="1"
Style="{StaticResource Wd.Button.Ghost}"
Click="OnSettingsToggleClick"
Padding="8,4"
ToolTip="Close (Esc)">
<Path Data="M 0,0 L 10,10 M 10,0 L 0,10"
Stroke="{DynamicResource Wd.Text.Secondary}"
StrokeThickness="1.4"
Width="10" Height="10"
Stretch="None"/>
</Button>
</Grid>
</Border>
<!-- Drawer footer (Apply) -->
<Border DockPanel.Dock="Bottom"
Padding="20,12,20,16"
BorderBrush="{DynamicResource Wd.Border}"
BorderThickness="0,1,0,0">
<StackPanel Orientation="Horizontal"
HorizontalAlignment="Right">
<Button Style="{StaticResource Wd.Button.Ghost}"
Content="Close"
Click="OnSettingsToggleClick"
Padding="14,6"
Margin="0,0,8,0"/>
<Button Style="{StaticResource Wd.Button.Primary}"
Content="Apply changes"
Command="{Binding Settings.ApplyCommand}"
Padding="14,6"/>
</StackPanel>
</Border>
<!-- Drawer scrollable body — three tabs -->
<ScrollViewer VerticalScrollBarVisibility="Auto"
HorizontalScrollBarVisibility="Disabled">
<Border Padding="20,16,20,16">
<TabControl Style="{StaticResource Wd.TabControl}">
<!-- ─────── OUTPUT ─────── -->
<TabItem Header="OUTPUT" Style="{StaticResource Wd.TabItem}">
<StackPanel>
<TextBlock Text="Target framerate"
Style="{StaticResource Wd.Text.Subtle}"
Margin="0,0,0,4"/>
<ComboBox ItemsSource="{Binding Settings.AvailableFramerates}"
SelectedItem="{Binding Settings.Framerate}">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Converter={StaticResource EnumDesc}}"/>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
<TextBlock Text="Target resolution"
Style="{StaticResource Wd.Text.Subtle}"
Margin="0,12,0,4"/>
<ComboBox ItemsSource="{Binding Settings.AvailableResolutions}"
SelectedItem="{Binding Settings.Resolution}">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Converter={StaticResource EnumDesc}}"/>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
<TextBlock Text="Aspect mode"
Style="{StaticResource Wd.Text.Subtle}"
Margin="0,12,0,4"/>
<ComboBox ItemsSource="{Binding Settings.AvailableAspectModes}"
SelectedItem="{Binding Settings.Aspect}">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Converter={StaticResource EnumDesc}}"/>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
<TextBlock Text="Audio mode"
Style="{StaticResource Wd.Text.Subtle}"
Margin="0,12,0,4"/>
<ComboBox ItemsSource="{Binding Settings.AvailableAudioModes}"
SelectedItem="{Binding Settings.Audio}">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Converter={StaticResource EnumDesc}}"/>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
<Button Style="{StaticResource Wd.Button.Ghost}"
Content="Reset to defaults"
Command="{Binding Settings.ResetOutputDefaultsCommand}"
HorizontalAlignment="Stretch"
Margin="0,16,0,0"
Padding="0,8"/>
</StackPanel>
</TabItem>
<!-- ─────── NETWORK ─────── -->
<TabItem Header="NETWORK" Style="{StaticResource Wd.TabItem}">
<StackPanel>
<TextBlock Text="Discovery group"
Style="{StaticResource Wd.Text.Subtle}"
Margin="0,0,0,4"/>
<TextBox Text="{Binding Settings.DiscoveryGroups, UpdateSourceTrigger=PropertyChanged}"/>
<TextBlock Text="Output group"
Style="{StaticResource Wd.Text.Subtle}"
Margin="0,12,0,4"/>
<TextBox Text="{Binding Settings.OutputGroups, UpdateSourceTrigger=PropertyChanged}"/>
<Button Style="{StaticResource Wd.Button.Ghost}"
Content="Apply transcoder topology"
Command="{Binding Settings.ApplyTranscoderTopologyCommand}"
HorizontalAlignment="Stretch"
Margin="0,16,0,0"
Padding="0,9"/>
<TextBlock Text="Pins Teams' raw NDI broadcasts to a private 'teamsiso-input' group so they don't pollute the Public network. Restart Teams after applying."
Style="{StaticResource Wd.Text.Body}"
FontSize="11"
Foreground="{DynamicResource Wd.Text.Tertiary}"
TextWrapping="Wrap"
Margin="0,6,0,0"/>
<TextBlock Text="Output name template"
Style="{StaticResource Wd.Text.Subtle}"
Margin="0,16,0,4"/>
<TextBox Text="{Binding Settings.OutputNameTemplate, UpdateSourceTrigger=LostFocus}"
FontFamily="{StaticResource Wd.Font.Mono}"
FontSize="11"/>
<TextBlock Text="Tokens: {name}, {guid}, {machine}, {timestamp}. Default: TEAMSISO_{guid}."
Style="{StaticResource Wd.Text.Body}"
FontSize="11"
Foreground="{DynamicResource Wd.Text.Tertiary}"
TextWrapping="Wrap"
Margin="0,4,0,0"/>
</StackPanel>
</TabItem>
<!-- ─────── APP ─────── -->
<TabItem Header="APP" Style="{StaticResource Wd.TabItem}">
<StackPanel>
<CheckBox Content="Hide my self-preview from participants"
IsChecked="{Binding Settings.HideLocalSelf}"/>
<CheckBox Content="Auto-disable ISOs when a participant leaves"
IsChecked="{Binding Settings.AutoDisableOnDeparture}"
Margin="0,8,0,0"/>
<CheckBox Content="Auto-apply last preset on launch"
IsChecked="{Binding Settings.AutoApplyLastPreset}"
Margin="0,8,0,0"/>
<TextBlock Text="Sort participants by"
Style="{StaticResource Wd.Text.Subtle}"
Margin="0,14,0,4"/>
<ComboBox ItemsSource="{Binding Settings.AvailableSortModes}"
SelectedItem="{Binding Settings.ParticipantSort}"/>
<CheckBox Content="Minimize to system tray"
IsChecked="{Binding Settings.MinimizeToTray}"
Margin="0,14,0,0"/>
<Border Margin="0,16,0,8"
Height="1"
Background="{DynamicResource Wd.Border}"/>
<CheckBox Content="Launch Microsoft Teams on TeamsISO startup"
IsChecked="{Binding Settings.LaunchTeamsOnStartup}"/>
<CheckBox Content="Auto-hide Teams windows when launched"
IsChecked="{Binding Settings.AutoHideTeamsWindows}"
Margin="0,8,0,0"/>
<Border Margin="0,16,0,8"
Height="1"
Background="{DynamicResource Wd.Border}"/>
<CheckBox Content="Embed Teams window (experimental)"
IsChecked="{Binding Settings.EmbedTeamsWindow}"/>
<Button Style="{StaticResource Wd.Button.Ghost}"
Content="Open embed window"
Click="OnOpenEmbedWindowClick"
HorizontalAlignment="Stretch"
Margin="0,8,0,0"
Padding="0,8"
IsEnabled="{Binding Settings.EmbedTeamsWindow}"/>
<Border Margin="0,16,0,8"
Height="1"
Background="{DynamicResource Wd.Border}"/>
<CheckBox Content="Control surface (Stream Deck / Companion / web)"
IsChecked="{Binding Settings.ControlSurfaceEnabled}"/>
<CheckBox Content="LAN-reachable"
IsChecked="{Binding Settings.ControlSurfaceLanReachable}"
Margin="20,4,0,0"/>
<TextBlock Text="{Binding Settings.ControlSurfaceUrl}"
Style="{StaticResource Wd.Text.Mono}"
FontSize="11"
Foreground="{DynamicResource Wd.Accent.CyanText}"
Margin="20,4,0,0"/>
</StackPanel>
</TabItem>
</TabControl>
</Border>
</ScrollViewer>
</DockPanel>
</Border>
</Grid>
feat(ui): rebrand to Wild Dragon + Microsoft Teams layout
Replaces the Stone theme with Wild Dragon branding (canvas #0A0A0A, accent cyan #97EDF0, secondary #9AE0FD, coral alert #FB819C — sourced from wilddragon.net) and reorganizes MainWindow into a Microsoft Teams-style three-column layout: a 72px left rail (logo + Participants/Settings nav + engine-status indicator), a center content area (header + participants card), and a right settings panel.
Adds InitialsConverter so participant avatars render real initials (Brendon Power -> 'BP', '(Local)' -> 'L') instead of a generic glyph. Drops the obsolete StoneTheme.xaml; the project now ships exactly one theme dictionary.
Typography: Inter (with Segoe UI Variable Display fallback) for the sans stack, JetBrains Mono (Cascadia Mono fallback) for machine names and timecodes — matching the wilddragon.net site.
Verified live against the running Teams meeting: app launches, participant 'Brendon Power' displays with avatar, settings panel surfaces NDI groups + Hide-Local toggle, engine status pill shows green/live.
2026-05-08 00:08:39 -04:00
</Grid>
2026-05-07 11:09:56 -04:00
</Window>