using Microsoft.UI; using Microsoft.UI.Windowing; using Microsoft.UI.Xaml; using TeamsISO.App.WinUI.Services; using TeamsISO.App.WinUI.ViewModels; using Windows.Graphics; using Windows.UI; namespace TeamsISO.App.WinUI.Views; public sealed partial class MainWindow : Window { private MainViewModel? _viewModel; public MainWindow() { InitializeComponent(); Title = "TeamsISO"; ExtendsContentIntoTitleBar = true; SetTitleBar(AppTitleBar); AppWindow.TitleBar.ButtonBackgroundColor = Colors.Transparent; AppWindow.TitleBar.ButtonInactiveBackgroundColor = Colors.Transparent; AppWindow.TitleBar.ButtonHoverForegroundColor = Colors.White; AppWindow.Resize(new SizeInt32(1280, 780)); ThemeManager.Current.Themed += (_, theme) => ApplyResolvedTheme(theme); ApplyResolvedTheme(ThemeManager.Current.ResolveTheme()); } /// /// Hook the engine view-model in. Replaces the placeholder StackPanel /// inside ParticipantsHost with a live ListView. Rather than fight WinUI /// 3's DataTemplate compilation, we subscribe to the Participants /// collection and rebuild a simple StackPanel of row controls on /// every change. Less efficient than a virtualized ListView for huge /// lists, fine for the operator's ~10 max participants. /// public void AttachViewModel(MainViewModel viewModel) { _viewModel = viewModel; // Section header + in-call buttons → view-model commands. // The buttons exist in MainWindow.xaml with the matching x:Names. RefreshButton.Command = viewModel.RefreshDiscoveryCommand; EnableAllButton.Command = viewModel.EnableAllOnlineCommand; StopAllButton.Command = viewModel.StopAllIsosCommand; MarkerButton.Command = viewModel.DropRecordingMarkerCommand; // Status bar + participant count text refresh on VM property changes. ParticipantCountText.Text = viewModel.ParticipantCountText; StatusBarText.Text = viewModel.StatusText; viewModel.PropertyChanged += (_, e) => { DispatcherQueue.TryEnqueue(() => { switch (e.PropertyName) { case nameof(MainViewModel.ParticipantCountText): ParticipantCountText.Text = viewModel.ParticipantCountText; break; case nameof(MainViewModel.StatusText): StatusBarText.Text = viewModel.StatusText; break; } }); }; ParticipantsHost.Children.Clear(); var stack = new Microsoft.UI.Xaml.Controls.StackPanel { HorizontalAlignment = HorizontalAlignment.Stretch, VerticalAlignment = VerticalAlignment.Top, }; var scroll = new Microsoft.UI.Xaml.Controls.ScrollViewer { VerticalScrollBarVisibility = Microsoft.UI.Xaml.Controls.ScrollBarVisibility.Auto, Content = stack, }; ParticipantsHost.Children.Add(scroll); void Rebuild() { stack.Children.Clear(); foreach (var p in viewModel.Participants) { stack.Children.Add(BuildSimpleRow(p)); } } viewModel.Participants.CollectionChanged += (_, _) => { DispatcherQueue.TryEnqueue(Rebuild); }; Rebuild(); } /// /// 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. /// 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), }; 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 = Microsoft.UI.Xaml.GridLength.Auto }); var nameStack = new Microsoft.UI.Xaml.Controls.StackPanel { VerticalAlignment = VerticalAlignment.Center, Spacing = 2, }; var nameText = new Microsoft.UI.Xaml.Controls.TextBlock { Text = p.DisplayName, FontSize = 14, FontWeight = Microsoft.UI.Text.FontWeights.Medium, }; var codecText = new Microsoft.UI.Xaml.Controls.TextBlock { Text = p.SourceCodec, FontSize = 11, Opacity = 0.6, }; nameStack.Children.Add(nameText); nameStack.Children.Add(codecText); Microsoft.UI.Xaml.Controls.Grid.SetColumn(nameStack, 0); grid.Children.Add(nameStack); var outputText = new Microsoft.UI.Xaml.Controls.TextBlock { Text = p.OutputName, FontFamily = new Microsoft.UI.Xaml.Media.FontFamily("Consolas"), FontSize = 12, VerticalAlignment = VerticalAlignment.Center, }; Microsoft.UI.Xaml.Controls.Grid.SetColumn(outputText, 1); grid.Children.Add(outputText); var pillText = new Microsoft.UI.Xaml.Controls.TextBlock { Text = p.IsoStateLabel, FontSize = 11, FontWeight = Microsoft.UI.Text.FontWeights.SemiBold, HorizontalAlignment = HorizontalAlignment.Center, }; var pill = new Microsoft.UI.Xaml.Controls.Button { Command = p.ToggleIsoCommand, MinWidth = 80, Padding = new Thickness(14, 6, 14, 6), CornerRadius = new CornerRadius(999), VerticalAlignment = VerticalAlignment.Center, Content = pillText, }; Microsoft.UI.Xaml.Controls.Grid.SetColumn(pill, 2); grid.Children.Add(pill); p.PropertyChanged += (_, e) => { grid.DispatcherQueue.TryEnqueue(() => { switch (e.PropertyName) { case nameof(ParticipantViewModel.DisplayName): nameText.Text = p.DisplayName; break; case nameof(ParticipantViewModel.SourceCodec): codecText.Text = p.SourceCodec; break; case nameof(ParticipantViewModel.OutputName): outputText.Text = p.OutputName; break; case nameof(ParticipantViewModel.IsoStateLabel): case nameof(ParticipantViewModel.IsEnabled): pillText.Text = p.IsoStateLabel; break; } }); }; return grid; } /// 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) { var grid = new Microsoft.UI.Xaml.Controls.Grid { Height = 64, Padding = new Thickness(14, 0, 12, 0), BorderBrush = (Microsoft.UI.Xaml.Media.Brush)Application.Current.Resources["BorderSubtle"], BorderThickness = new Thickness(0, 0, 0, 1), }; grid.ColumnDefinitions.Add(new Microsoft.UI.Xaml.Controls.ColumnDefinition { Width = new Microsoft.UI.Xaml.GridLength(44) }); grid.ColumnDefinitions.Add(new Microsoft.UI.Xaml.Controls.ColumnDefinition { Width = new Microsoft.UI.Xaml.GridLength(2, Microsoft.UI.Xaml.GridUnitType.Star) }); grid.ColumnDefinitions.Add(new Microsoft.UI.Xaml.Controls.ColumnDefinition { Width = new Microsoft.UI.Xaml.GridLength(1.4, Microsoft.UI.Xaml.GridUnitType.Star) }); grid.ColumnDefinitions.Add(new Microsoft.UI.Xaml.Controls.ColumnDefinition { Width = new Microsoft.UI.Xaml.GridLength(1.6, Microsoft.UI.Xaml.GridUnitType.Star) }); grid.ColumnDefinitions.Add(new Microsoft.UI.Xaml.Controls.ColumnDefinition { Width = Microsoft.UI.Xaml.GridLength.Auto }); // Avatar var avatar = new Microsoft.UI.Xaml.Controls.Border { Width = 36, Height = 36, CornerRadius = new CornerRadius(18), Background = (Microsoft.UI.Xaml.Media.Brush)Application.Current.Resources["AccentCyanMuted"], VerticalAlignment = VerticalAlignment.Center, }; var initialsText = new Microsoft.UI.Xaml.Controls.TextBlock { Text = p.Initials, FontSize = 13, FontWeight = Microsoft.UI.Text.FontWeights.SemiBold, Foreground = (Microsoft.UI.Xaml.Media.Brush)Application.Current.Resources["AccentCyanText"], HorizontalAlignment = HorizontalAlignment.Center, VerticalAlignment = VerticalAlignment.Center, }; avatar.Child = initialsText; Microsoft.UI.Xaml.Controls.Grid.SetColumn(avatar, 0); grid.Children.Add(avatar); // Name + codec var nameStack = new Microsoft.UI.Xaml.Controls.StackPanel { Margin = new Thickness(12, 0, 0, 0), VerticalAlignment = VerticalAlignment.Center, Spacing = 2, }; var nameText = new Microsoft.UI.Xaml.Controls.TextBlock { Text = p.DisplayName, FontSize = 13, FontWeight = Microsoft.UI.Text.FontWeights.Medium, Foreground = (Microsoft.UI.Xaml.Media.Brush)Application.Current.Resources["FgPrimary"], }; var codecText = new Microsoft.UI.Xaml.Controls.TextBlock { Text = p.SourceCodec, FontSize = 11, Foreground = (Microsoft.UI.Xaml.Media.Brush)Application.Current.Resources["FgSecondary"], }; nameStack.Children.Add(nameText); nameStack.Children.Add(codecText); Microsoft.UI.Xaml.Controls.Grid.SetColumn(nameStack, 1); grid.Children.Add(nameStack); // Audio meter var meter = new Microsoft.UI.Xaml.Controls.ProgressBar { Maximum = 1.0, Value = p.DisplayedAudioLevel, Height = 4, Margin = new Thickness(12, 0, 12, 0), VerticalAlignment = VerticalAlignment.Center, }; Microsoft.UI.Xaml.Controls.Grid.SetColumn(meter, 2); grid.Children.Add(meter); // Output name var outputText = new Microsoft.UI.Xaml.Controls.TextBlock { Text = p.OutputName, FontFamily = new Microsoft.UI.Xaml.Media.FontFamily("Consolas"), FontSize = 12, Foreground = (Microsoft.UI.Xaml.Media.Brush)Application.Current.Resources["FgPrimary"], VerticalAlignment = VerticalAlignment.Center, }; Microsoft.UI.Xaml.Controls.Grid.SetColumn(outputText, 3); grid.Children.Add(outputText); // ISO toggle pill var pillText = new Microsoft.UI.Xaml.Controls.TextBlock { Text = p.IsoStateLabel, FontSize = 11, FontWeight = Microsoft.UI.Text.FontWeights.SemiBold, Foreground = (Microsoft.UI.Xaml.Media.Brush)Application.Current.Resources["FgPrimary"], HorizontalAlignment = HorizontalAlignment.Center, }; var pill = new Microsoft.UI.Xaml.Controls.Button { Command = p.ToggleIsoCommand, MinWidth = 80, Padding = new Thickness(14, 6, 14, 6), Background = (Microsoft.UI.Xaml.Media.Brush)Application.Current.Resources["BgSurface"], BorderBrush = (Microsoft.UI.Xaml.Media.Brush)Application.Current.Resources["BorderStrong"], BorderThickness = new Thickness(1), CornerRadius = new CornerRadius(999), VerticalAlignment = VerticalAlignment.Center, Content = pillText, }; Microsoft.UI.Xaml.Controls.Grid.SetColumn(pill, 4); grid.Children.Add(pill); // Per-row property-change subscription — refresh text as the // engine pushes updates. p.PropertyChanged += (_, e) => { grid.DispatcherQueue.TryEnqueue(() => { switch (e.PropertyName) { case nameof(ParticipantViewModel.DisplayName): nameText.Text = p.DisplayName; initialsText.Text = p.Initials; break; case nameof(ParticipantViewModel.SourceCodec): codecText.Text = p.SourceCodec; break; case nameof(ParticipantViewModel.DisplayedAudioLevel): meter.Value = p.DisplayedAudioLevel; break; case nameof(ParticipantViewModel.OutputName): outputText.Text = p.OutputName; break; case nameof(ParticipantViewModel.IsoStateLabel): case nameof(ParticipantViewModel.IsEnabled): pillText.Text = p.IsoStateLabel; break; } }); }; return grid; } private void OnThemeToggleClick(object sender, RoutedEventArgs e) { ThemeManager.Current.Toggle(); } private bool _drawerOpen; private void OnSettingsClick(object sender, RoutedEventArgs e) { _drawerOpen = !_drawerOpen; SettingsDrawerHost.Visibility = _drawerOpen ? Visibility.Visible : Visibility.Collapsed; if (_drawerOpen) { SettingsDrawerHost.CloseRequested -= OnDrawerCloseRequested; SettingsDrawerHost.CloseRequested += OnDrawerCloseRequested; } } private void OnDrawerCloseRequested(object? sender, System.EventArgs e) { _drawerOpen = false; SettingsDrawerHost.Visibility = Visibility.Collapsed; } private void ApplyResolvedTheme(ElementTheme theme) { if (Content is FrameworkElement root) { root.RequestedTheme = theme; } AppWindow.TitleBar.ButtonForegroundColor = ThemeManager.TitleBarForegroundFor(theme); AppWindow.TitleBar.ButtonHoverBackgroundColor = ThemeManager.TitleBarHoverBgFor(theme); AppWindow.TitleBar.ButtonPressedBackgroundColor = ThemeManager.TitleBarHoverBgFor(theme); ThemeToggleIcon.Glyph = theme == ElementTheme.Light ? "" : ""; } }