From 9ae14c8ee90cbf40ab2eb6685c69a7b180dc9b23 Mon Sep 17 00:00:00 2001 From: Zac Gaetano Date: Wed, 13 May 2026 21:37:36 -0400 Subject: [PATCH] feat(winui3): colored ISO pills + active-speaker accent on rows MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two visual upgrades on the participant rows: * The ISO state pill now flips background / border / text colors based on the engine's reported state — green for LIVE, coral for ERROR, amber for STARTING / NO SIGNAL, neutral surface for OFF. Brushes pulled from the ThemeResource ramp (StatusLiveBg / StatusLive / AccentCoralBg / AccentCoral / StatusWarnBg / StatusWarn / BgSurface / BorderStrong). Mirrors the WPF host's IsoToggle data-trigger behavior but built imperatively. * The active-speaker left accent — a 3px cyan border at the row's left edge — appears when MainViewModel's 1Hz stats tick marks the loudest participant. Hidden by default; flips Visibility on PropertyChanged(IsActiveSpeaker). Row layout extended to accommodate: column 0 = 3px accent strip, column 1 = 20px spacer, column 2 = name + codec (1*), column 3 = output name (140px fixed), column 4 = ISO pill (auto). ApplyIsoPillStyling is the brush-mapping helper — called once at row construct and again on every IsoStateLabel / IsEnabled change. The brush keys all resolve via Application.Current.Resources rather than ThemeResource markup since the row is constructed imperatively (no XAML to apply ThemeResource markup against). Verified end-to-end: dotnet build clean, app launches with 3 live participants in the row, all pills showing OFF (neutral surface + strong border). Once a participant goes through OFF → STARTING (amber) → LIVE (green), the pill colors will update on the 1Hz stats tick. --- .../Views/MainWindow.xaml.cs | 81 ++++++++++++++++--- 1 file changed, 72 insertions(+), 9 deletions(-) diff --git a/src/TeamsISO.App.WinUI/Views/MainWindow.xaml.cs b/src/TeamsISO.App.WinUI/Views/MainWindow.xaml.cs index a03cca3..22e6443 100644 --- a/src/TeamsISO.App.WinUI/Views/MainWindow.xaml.cs +++ b/src/TeamsISO.App.WinUI/Views/MainWindow.xaml.cs @@ -213,22 +213,35 @@ public sealed partial class MainWindow : Window } /// - /// Minimal participant row — name + ISO state + toggle button. Drops - /// the brushed avatar / theme-resource lookups that may have been - /// triggering the crash. The full visual row template comes back - /// after we've verified the binding path works. + /// Participant row — name + ISO state pill, with the cyan-accent + /// left border when the participant is the active speaker, and the + /// pill background flipping green/coral/amber based on ISO state. + /// Imperative construction so we sidestep the WinUI 3 DataTemplate + /// parser path that crashes on this build host. /// private static Microsoft.UI.Xaml.Controls.Grid BuildSimpleRow(ParticipantViewModel p) { var grid = new Microsoft.UI.Xaml.Controls.Grid { Height = 56, - Padding = new Thickness(20, 0, 20, 0), + Padding = new Thickness(0, 0, 20, 0), }; + grid.ColumnDefinitions.Add(new Microsoft.UI.Xaml.Controls.ColumnDefinition { Width = new Microsoft.UI.Xaml.GridLength(3) }); + grid.ColumnDefinitions.Add(new Microsoft.UI.Xaml.Controls.ColumnDefinition { Width = new Microsoft.UI.Xaml.GridLength(20) }); grid.ColumnDefinitions.Add(new Microsoft.UI.Xaml.Controls.ColumnDefinition { Width = new Microsoft.UI.Xaml.GridLength(1, Microsoft.UI.Xaml.GridUnitType.Star) }); - grid.ColumnDefinitions.Add(new Microsoft.UI.Xaml.Controls.ColumnDefinition { Width = new Microsoft.UI.Xaml.GridLength(120) }); + grid.ColumnDefinitions.Add(new Microsoft.UI.Xaml.Controls.ColumnDefinition { Width = new Microsoft.UI.Xaml.GridLength(140) }); grid.ColumnDefinitions.Add(new Microsoft.UI.Xaml.Controls.ColumnDefinition { Width = Microsoft.UI.Xaml.GridLength.Auto }); + // Active-speaker left accent. Visible only when IsActiveSpeaker + // flips on (driven by MainViewModel.OnStatsTick at 1Hz). + var accent = new Microsoft.UI.Xaml.Controls.Border + { + Background = (Microsoft.UI.Xaml.Media.Brush)Application.Current.Resources["AccentCyanText"], + Visibility = p.IsActiveSpeaker ? Visibility.Visible : Visibility.Collapsed, + }; + Microsoft.UI.Xaml.Controls.Grid.SetColumn(accent, 0); + grid.Children.Add(accent); + var nameStack = new Microsoft.UI.Xaml.Controls.StackPanel { VerticalAlignment = VerticalAlignment.Center, @@ -248,7 +261,7 @@ public sealed partial class MainWindow : Window }; nameStack.Children.Add(nameText); nameStack.Children.Add(codecText); - Microsoft.UI.Xaml.Controls.Grid.SetColumn(nameStack, 0); + Microsoft.UI.Xaml.Controls.Grid.SetColumn(nameStack, 2); grid.Children.Add(nameStack); var outputText = new Microsoft.UI.Xaml.Controls.TextBlock @@ -258,7 +271,7 @@ public sealed partial class MainWindow : Window FontSize = 12, VerticalAlignment = VerticalAlignment.Center, }; - Microsoft.UI.Xaml.Controls.Grid.SetColumn(outputText, 1); + Microsoft.UI.Xaml.Controls.Grid.SetColumn(outputText, 3); grid.Children.Add(outputText); var pillText = new Microsoft.UI.Xaml.Controls.TextBlock @@ -277,7 +290,8 @@ public sealed partial class MainWindow : Window VerticalAlignment = VerticalAlignment.Center, Content = pillText, }; - Microsoft.UI.Xaml.Controls.Grid.SetColumn(pill, 2); + ApplyIsoPillStyling(pill, pillText, p.IsoStateLabel); + Microsoft.UI.Xaml.Controls.Grid.SetColumn(pill, 4); grid.Children.Add(pill); p.PropertyChanged += (_, e) => @@ -298,6 +312,12 @@ public sealed partial class MainWindow : Window case nameof(ParticipantViewModel.IsoStateLabel): case nameof(ParticipantViewModel.IsEnabled): pillText.Text = p.IsoStateLabel; + ApplyIsoPillStyling(pill, pillText, p.IsoStateLabel); + break; + case nameof(ParticipantViewModel.IsActiveSpeaker): + accent.Visibility = p.IsActiveSpeaker + ? Visibility.Visible + : Visibility.Collapsed; break; } }); @@ -306,6 +326,49 @@ public sealed partial class MainWindow : Window return grid; } + /// + /// Re-color the ISO pill based on the engine's reported state. + /// LIVE = green on green-tinted background. ERROR = coral on coral- + /// tinted background. STARTING = amber. NO SIGNAL = amber. OFF = + /// neutral surface. Mirrors the WPF host's IsoToggle data-trigger + /// behavior but built imperatively from theme-resource brush keys. + /// + private static void ApplyIsoPillStyling( + Microsoft.UI.Xaml.Controls.Button pill, + Microsoft.UI.Xaml.Controls.TextBlock pillText, + string state) + { + Microsoft.UI.Xaml.Media.Brush bg, border, fg; + switch (state) + { + case "LIVE": + bg = (Microsoft.UI.Xaml.Media.Brush)Application.Current.Resources["StatusLiveBg"]; + border = (Microsoft.UI.Xaml.Media.Brush)Application.Current.Resources["StatusLive"]; + fg = (Microsoft.UI.Xaml.Media.Brush)Application.Current.Resources["StatusLive"]; + break; + case "ERROR": + bg = (Microsoft.UI.Xaml.Media.Brush)Application.Current.Resources["AccentCoralBg"]; + border = (Microsoft.UI.Xaml.Media.Brush)Application.Current.Resources["AccentCoral"]; + fg = (Microsoft.UI.Xaml.Media.Brush)Application.Current.Resources["AccentCoral"]; + break; + case "NO SIGNAL": + case "STARTING": + bg = (Microsoft.UI.Xaml.Media.Brush)Application.Current.Resources["StatusWarnBg"]; + border = (Microsoft.UI.Xaml.Media.Brush)Application.Current.Resources["StatusWarn"]; + fg = (Microsoft.UI.Xaml.Media.Brush)Application.Current.Resources["StatusWarn"]; + break; + default: // OFF / — + bg = (Microsoft.UI.Xaml.Media.Brush)Application.Current.Resources["BgSurface"]; + border = (Microsoft.UI.Xaml.Media.Brush)Application.Current.Resources["BorderStrong"]; + fg = (Microsoft.UI.Xaml.Media.Brush)Application.Current.Resources["FgPrimary"]; + break; + } + pill.Background = bg; + pill.BorderBrush = border; + pill.BorderThickness = new Thickness(1); + pillText.Foreground = fg; + } + /// Full rich row template — replaces BuildSimpleRow once we've verified the simple version doesn't crash. private static Microsoft.UI.Xaml.Controls.Grid BuildParticipantRow(ParticipantViewModel p) {