feat(winui3): colored ISO pills + active-speaker accent on rows
Some checks failed
CI / build-and-test (push) Has been cancelled

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.
This commit is contained in:
Zac Gaetano 2026-05-13 21:37:36 -04:00
parent f7249c31c2
commit 9ae14c8ee9

View file

@ -213,22 +213,35 @@ public sealed partial class MainWindow : Window
} }
/// <summary> /// <summary>
/// Minimal participant row — name + ISO state + toggle button. Drops /// Participant row — name + ISO state pill, with the cyan-accent
/// the brushed avatar / theme-resource lookups that may have been /// left border when the participant is the active speaker, and the
/// triggering the crash. The full visual row template comes back /// pill background flipping green/coral/amber based on ISO state.
/// after we've verified the binding path works. /// Imperative construction so we sidestep the WinUI 3 DataTemplate
/// parser path that crashes on this build host.
/// </summary> /// </summary>
private static Microsoft.UI.Xaml.Controls.Grid BuildSimpleRow(ParticipantViewModel p) private static Microsoft.UI.Xaml.Controls.Grid BuildSimpleRow(ParticipantViewModel p)
{ {
var grid = new Microsoft.UI.Xaml.Controls.Grid var grid = new Microsoft.UI.Xaml.Controls.Grid
{ {
Height = 56, 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(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 }); 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 var nameStack = new Microsoft.UI.Xaml.Controls.StackPanel
{ {
VerticalAlignment = VerticalAlignment.Center, VerticalAlignment = VerticalAlignment.Center,
@ -248,7 +261,7 @@ public sealed partial class MainWindow : Window
}; };
nameStack.Children.Add(nameText); nameStack.Children.Add(nameText);
nameStack.Children.Add(codecText); nameStack.Children.Add(codecText);
Microsoft.UI.Xaml.Controls.Grid.SetColumn(nameStack, 0); Microsoft.UI.Xaml.Controls.Grid.SetColumn(nameStack, 2);
grid.Children.Add(nameStack); grid.Children.Add(nameStack);
var outputText = new Microsoft.UI.Xaml.Controls.TextBlock var outputText = new Microsoft.UI.Xaml.Controls.TextBlock
@ -258,7 +271,7 @@ public sealed partial class MainWindow : Window
FontSize = 12, FontSize = 12,
VerticalAlignment = VerticalAlignment.Center, VerticalAlignment = VerticalAlignment.Center,
}; };
Microsoft.UI.Xaml.Controls.Grid.SetColumn(outputText, 1); Microsoft.UI.Xaml.Controls.Grid.SetColumn(outputText, 3);
grid.Children.Add(outputText); grid.Children.Add(outputText);
var pillText = new Microsoft.UI.Xaml.Controls.TextBlock var pillText = new Microsoft.UI.Xaml.Controls.TextBlock
@ -277,7 +290,8 @@ public sealed partial class MainWindow : Window
VerticalAlignment = VerticalAlignment.Center, VerticalAlignment = VerticalAlignment.Center,
Content = pillText, 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); grid.Children.Add(pill);
p.PropertyChanged += (_, e) => p.PropertyChanged += (_, e) =>
@ -298,6 +312,12 @@ public sealed partial class MainWindow : Window
case nameof(ParticipantViewModel.IsoStateLabel): case nameof(ParticipantViewModel.IsoStateLabel):
case nameof(ParticipantViewModel.IsEnabled): case nameof(ParticipantViewModel.IsEnabled):
pillText.Text = p.IsoStateLabel; pillText.Text = p.IsoStateLabel;
ApplyIsoPillStyling(pill, pillText, p.IsoStateLabel);
break;
case nameof(ParticipantViewModel.IsActiveSpeaker):
accent.Visibility = p.IsActiveSpeaker
? Visibility.Visible
: Visibility.Collapsed;
break; break;
} }
}); });
@ -306,6 +326,49 @@ public sealed partial class MainWindow : Window
return grid; return grid;
} }
/// <summary>
/// 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.
/// </summary>
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;
}
/// <summary>Full rich row template — replaces BuildSimpleRow once we've verified the simple version doesn't crash.</summary> /// <summary>Full rich row template — replaces BuildSimpleRow once we've verified the simple version doesn't crash.</summary>
private static Microsoft.UI.Xaml.Controls.Grid BuildParticipantRow(ParticipantViewModel p) private static Microsoft.UI.Xaml.Controls.Grid BuildParticipantRow(ParticipantViewModel p)
{ {