dragon-iso/src/TeamsISO.App/MainWindow.xaml
Zac Gaetano b542d01835 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

366 lines
19 KiB
XML

<Window x:Class="TeamsISO.App.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vm="clr-namespace:TeamsISO.App.ViewModels"
xmlns:conv="clr-namespace:TeamsISO.App.Converters"
Title="TeamsISO"
Height="760" Width="1180"
MinHeight="600" MinWidth="960"
Background="{DynamicResource Stone.Canvas}"
UseLayoutRounding="True"
TextOptions.TextFormattingMode="Ideal"
TextOptions.TextRenderingMode="ClearType">
<Window.Resources>
<conv:BoolToVisibilityConverter x:Key="BoolToVis"/>
<conv:EnumDescriptionConverter x:Key="EnumDesc"/>
</Window.Resources>
<DockPanel LastChildFill="True">
<!-- ════════ Alert banner (top) ════════ -->
<Border DockPanel.Dock="Top"
Background="{DynamicResource Status.Bad.Surface}"
BorderBrush="{DynamicResource Status.Bad}"
BorderThickness="0,0,0,1"
Padding="20,10"
Visibility="{Binding AlertBanner.IsVisible, Converter={StaticResource BoolToVis}}">
<DockPanel>
<Button DockPanel.Dock="Right"
Style="{StaticResource Button.Ghost}"
Content="Dismiss"
Command="{Binding AlertBanner.DismissCommand}"
Margin="12,0,0,0"/>
<StackPanel Orientation="Horizontal" VerticalAlignment="Center">
<Ellipse Width="8" Height="8"
Fill="{DynamicResource Status.Bad}"
VerticalAlignment="Center"
Margin="0,0,10,0"/>
<TextBlock Text="{Binding AlertBanner.Message}"
Style="{StaticResource Text.Body}"
FontWeight="SemiBold"
VerticalAlignment="Center"/>
</StackPanel>
</DockPanel>
</Border>
<!-- ════════ Header ════════ -->
<Border DockPanel.Dock="Top"
Background="{DynamicResource Stone.Canvas}"
BorderBrush="{DynamicResource Stone.Border}"
BorderThickness="0,0,0,1"
Padding="24,18">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<StackPanel Orientation="Horizontal" VerticalAlignment="Center">
<TextBlock Text="TeamsISO"
Style="{StaticResource Text.Display}"
FontWeight="Bold"
VerticalAlignment="Center"/>
<TextBlock Text="Per-participant NDI ISO controller"
Style="{StaticResource Text.Body}"
Foreground="{DynamicResource Text.Tertiary}"
Margin="14,0,0,0"
VerticalAlignment="Center"/>
</StackPanel>
<!-- Engine status pill, right-aligned -->
<Border Grid.Column="1"
Style="{StaticResource Pill.Online}"
VerticalAlignment="Center">
<StackPanel Orientation="Horizontal">
<Ellipse Width="7" Height="7"
Fill="{DynamicResource Status.Good}"
VerticalAlignment="Center"/>
<TextBlock Text="{Binding StatusText}"
Style="{StaticResource Text.Body}"
FontSize="12"
Foreground="{DynamicResource Text.Primary}"
Margin="8,0,0,0"
VerticalAlignment="Center"/>
</StackPanel>
</Border>
</Grid>
</Border>
<!-- ════════ Footer / status bar ════════ -->
<Border DockPanel.Dock="Bottom"
Background="{DynamicResource Stone.Canvas}"
BorderBrush="{DynamicResource Stone.Border}"
BorderThickness="0,1,0,0"
Padding="24,10">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<TextBlock Text="{Binding StatusText}"
Style="{StaticResource Text.Mono}"
Foreground="{DynamicResource Text.Tertiary}"
VerticalAlignment="Center"/>
<TextBlock Grid.Column="1"
Text="Wild Dragon LLC · 1.0.0-alpha"
Style="{StaticResource Text.Mono}"
Foreground="{DynamicResource Text.Tertiary}"
VerticalAlignment="Center"/>
</Grid>
</Border>
<!-- ════════ Settings panel (right) ════════ -->
<Border DockPanel.Dock="Right"
Width="360"
Background="{DynamicResource Stone.Surface}"
BorderBrush="{DynamicResource Stone.Border}"
BorderThickness="1,0,0,0">
<ScrollViewer VerticalScrollBarVisibility="Auto" Padding="0">
<StackPanel Margin="24,24,24,32">
<TextBlock Text="Settings"
Style="{StaticResource Text.Heading}"
FontSize="16"
Margin="0,0,0,20"/>
<!-- · · Output Format · · -->
<TextBlock Text="OUTPUT FORMAT" Style="{StaticResource Text.Caption}"/>
<TextBlock Text="Target Framerate"
Style="{StaticResource Text.Body}"
Foreground="{DynamicResource Text.Secondary}"
Margin="0,8,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 Text.Body}"
Foreground="{DynamicResource Text.Secondary}"
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 Text.Body}"
Foreground="{DynamicResource Text.Secondary}"
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 Text.Body}"
Foreground="{DynamicResource Text.Secondary}"
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>
<!-- · · NDI Network · · -->
<TextBlock Text="NDI NETWORK"
Style="{StaticResource Text.Caption}"
Margin="0,28,0,0"/>
<TextBlock Text="Discovery group"
Style="{StaticResource Text.Body}"
Foreground="{DynamicResource Text.Secondary}"
Margin="0,8,0,4"/>
<TextBox Text="{Binding Settings.DiscoveryGroups, UpdateSourceTrigger=PropertyChanged}"/>
<TextBlock Text="Receive sources from this group. Empty = Public."
Style="{StaticResource Text.Body}"
FontSize="11"
Foreground="{DynamicResource Text.Tertiary}"
Margin="0,4,0,0"/>
<TextBlock Text="Output group"
Style="{StaticResource Text.Body}"
Foreground="{DynamicResource Text.Secondary}"
Margin="0,12,0,4"/>
<TextBox Text="{Binding Settings.OutputGroups, UpdateSourceTrigger=PropertyChanged}"/>
<TextBlock Text="Broadcast TeamsISO outputs on this group. Empty = Public."
Style="{StaticResource Text.Body}"
FontSize="11"
Foreground="{DynamicResource Text.Tertiary}"
Margin="0,4,0,0"/>
<Border Background="{DynamicResource Stone.SurfaceElevated}"
BorderBrush="{DynamicResource Stone.Border}"
BorderThickness="1"
CornerRadius="{StaticResource Radius.M}"
Padding="12,10"
Margin="0,12,0,0">
<TextBlock Style="{StaticResource Text.Body}"
FontSize="11"
Foreground="{DynamicResource Text.Secondary}"
TextWrapping="Wrap"
Text="Group changes apply on next launch — running pipelines aren't restarted to avoid orphaning your live ISOs."/>
</Border>
<!-- · · Display · · -->
<TextBlock Text="DISPLAY"
Style="{StaticResource Text.Caption}"
Margin="0,28,0,0"/>
<CheckBox Content="Hide my self-preview from participants"
IsChecked="{Binding Settings.HideLocalSelf}"
Margin="0,8,0,0"/>
<Button Style="{StaticResource Button.Primary}"
Content="Apply Changes"
Command="{Binding Settings.ApplyCommand}"
HorizontalAlignment="Stretch"
Margin="0,28,0,0"
Padding="0,10"/>
</StackPanel>
</ScrollViewer>
</Border>
<!-- ════════ Main content: participants ════════ -->
<Grid Margin="32,28,32,28">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<!-- Section header with count -->
<StackPanel Grid.Row="0" Orientation="Horizontal" VerticalAlignment="Center">
<TextBlock Text="Participants"
Style="{StaticResource Text.Heading}"
FontSize="18"/>
<Border Background="{DynamicResource Stone.Surface}"
BorderBrush="{DynamicResource Stone.Border}"
BorderThickness="1"
CornerRadius="999"
Padding="9,2"
Margin="12,0,0,0"
VerticalAlignment="Center">
<TextBlock Text="{Binding Participants.Count}"
Style="{StaticResource Text.Mono}"
Foreground="{DynamicResource Text.Secondary}"
FontSize="11"/>
</Border>
</StackPanel>
<TextBlock Grid.Row="1"
Text="Toggle the ISO column to spin up an isolated, normalized NDI output for any participant."
Style="{StaticResource Text.Body}"
Foreground="{DynamicResource Text.Tertiary}"
Margin="0,6,0,18"/>
<!-- Participants table inside a card -->
<Border Grid.Row="2"
Style="{StaticResource Card}"
Padding="0">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<DataGrid Grid.Row="1"
ItemsSource="{Binding Participants}"
Margin="4,4,4,4">
<DataGrid.Columns>
<DataGridTemplateColumn Header="Display Name" Width="2*">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal" VerticalAlignment="Center">
<Border Width="28" Height="28"
CornerRadius="14"
Background="{DynamicResource Accent.OrangeMuted}">
<TextBlock Text="●"
FontSize="9"
Foreground="{DynamicResource Accent.Orange}"
HorizontalAlignment="Center"
VerticalAlignment="Center"/>
</Border>
<TextBlock Text="{Binding DisplayName}"
Style="{StaticResource Text.Body}"
FontWeight="Medium"
Margin="12,0,0,0"
VerticalAlignment="Center"/>
</StackPanel>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTemplateColumn Header="Source" Width="*">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding SourceMachine}"
Style="{StaticResource Text.Mono}"
VerticalAlignment="Center"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTemplateColumn Header="Output Name" Width="2*">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBox Text="{Binding CustomName, UpdateSourceTrigger=PropertyChanged}"
Padding="8,6"
Margin="0,0,12,0"
VerticalAlignment="Center"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTemplateColumn Header="ISO" Width="120">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Button Command="{Binding ToggleIsoCommand}"
Margin="0,0,12,0">
<Button.Style>
<Style TargetType="Button" BasedOn="{StaticResource Button.IsoToggle}">
<Setter Property="Content" Value="Enable"/>
<Setter Property="Foreground" Value="{DynamicResource Text.Secondary}"/>
<Style.Triggers>
<DataTrigger Binding="{Binding IsEnabled}" Value="True">
<Setter Property="Content" Value="● Live"/>
<Setter Property="Background" Value="#1F3A1F"/>
<Setter Property="BorderBrush" Value="{DynamicResource Status.Good}"/>
<Setter Property="Foreground" Value="{DynamicResource Status.Good}"/>
</DataTrigger>
<DataTrigger Binding="{Binding IsProcessing}" Value="True">
<Setter Property="Content" Value="…"/>
<Setter Property="IsEnabled" Value="False"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Button.Style>
</Button>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
</Grid>
</Border>
</Grid>
</DockPanel>
</Window>