From 7c269f2c403ed533a2d4d621b35880177d02a288 Mon Sep 17 00:00:00 2001 From: Zac Gaetano Date: Wed, 13 May 2026 21:33:02 -0400 Subject: [PATCH] feat(winui3): keyboard shortcuts (F1, Ctrl+M, Ctrl+Shift+S, Ctrl+R, 1-9, Esc) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds the operator's shortcut surface to the WinUI 3 host via KeyboardAccelerator attached to the window's content root: * F1 — open the keyboard-cheat-sheet HelpDialog as a ContentDialog. * Ctrl+M — drop a recording marker (invokes MainViewModel.DropRecordingMarkerCommand, which fans out to every active recorder via IIsoController.AddRecordingMarker). * Ctrl+Shift+S — panic stop (invokes StopAllIsosCommand). * Ctrl+R — refresh NDI discovery. * 1-9 + NumPad 1-9 — toggle ISO for the Nth visible participant (invokes ToggleByIndexCommand with the digit as the parameter). * Esc — dismiss the settings drawer when open. Mirrors the WPF host's verbatim so the operator's muscle memory transfers across hosts. Wire-up note: WinUI 3 KeyboardAccelerator uses TypedEventHandler, not System.EventHandler like XAML islands suggest in some docs. The Bind local fn takes the correct type explicitly so the compiler doesn't trip on the conversion. Verified: dotnet build clean, app launches and accelerators register without crashing the XAML parser. --- .../Views/MainWindow.xaml.cs | 114 ++++++++++++++++++ 1 file changed, 114 insertions(+) diff --git a/src/TeamsISO.App.WinUI/Views/MainWindow.xaml.cs b/src/TeamsISO.App.WinUI/Views/MainWindow.xaml.cs index d30c5a3..a03cca3 100644 --- a/src/TeamsISO.App.WinUI/Views/MainWindow.xaml.cs +++ b/src/TeamsISO.App.WinUI/Views/MainWindow.xaml.cs @@ -29,6 +29,120 @@ public sealed partial class MainWindow : Window ThemeManager.Current.Themed += (_, theme) => ApplyResolvedTheme(theme); ApplyResolvedTheme(ThemeManager.Current.ResolveTheme()); + + WireKeyboardShortcuts(); + } + + /// + /// Attaches the window-scoped keyboard accelerators that match the + /// WPF host's KeyBindings: F1 help, Ctrl+M marker, Ctrl+Shift+S + /// panic stop, Ctrl+R refresh, 1-9 + NumPad 1-9 toggle ISO by visible + /// row index. WinUI 3 KeyboardAccelerators live on UIElement, so we + /// attach them to the content root. + /// + private void WireKeyboardShortcuts() + { + if (Content is not Microsoft.UI.Xaml.UIElement root) return; + + void Bind(Windows.System.VirtualKey key, + Windows.System.VirtualKeyModifiers mods, + Windows.Foundation.TypedEventHandler handler) + { + var acc = new Microsoft.UI.Xaml.Input.KeyboardAccelerator + { + Key = key, + Modifiers = mods, + ScopeOwner = root, + }; + acc.Invoked += handler; + root.KeyboardAccelerators.Add(acc); + } + + // F1 — help dialog + Bind(Windows.System.VirtualKey.F1, + Windows.System.VirtualKeyModifiers.None, + (s, e) => { e.Handled = true; _ = ShowHelpDialogAsync(); }); + + // Ctrl+M — drop a recording marker + Bind(Windows.System.VirtualKey.M, + Windows.System.VirtualKeyModifiers.Control, + (s, e) => + { + e.Handled = true; + if (_viewModel?.DropRecordingMarkerCommand.CanExecute(null) == true) + _viewModel.DropRecordingMarkerCommand.Execute(null); + }); + + // Ctrl+Shift+S — panic stop all ISOs + Bind(Windows.System.VirtualKey.S, + Windows.System.VirtualKeyModifiers.Control | Windows.System.VirtualKeyModifiers.Shift, + (s, e) => + { + e.Handled = true; + if (_viewModel?.StopAllIsosCommand.CanExecute(null) == true) + _viewModel.StopAllIsosCommand.Execute(null); + }); + + // Ctrl+R — refresh NDI discovery + Bind(Windows.System.VirtualKey.R, + Windows.System.VirtualKeyModifiers.Control, + (s, e) => + { + e.Handled = true; + if (_viewModel?.RefreshDiscoveryCommand.CanExecute(null) == true) + _viewModel.RefreshDiscoveryCommand.Execute(null); + }); + + // 1-9 and NumPad1-9 — toggle ISO for the Nth visible participant + for (var i = 1; i <= 9; i++) + { + var idx = i.ToString(); + Bind((Windows.System.VirtualKey)(0x30 + i), // VK '1'..'9' + Windows.System.VirtualKeyModifiers.None, + (s, e) => + { + e.Handled = true; + if (_viewModel?.ToggleByIndexCommand.CanExecute(idx) == true) + _viewModel.ToggleByIndexCommand.Execute(idx); + }); + Bind((Windows.System.VirtualKey)(0x60 + i), // VK NumPad1..NumPad9 + Windows.System.VirtualKeyModifiers.None, + (s, e) => + { + e.Handled = true; + if (_viewModel?.ToggleByIndexCommand.CanExecute(idx) == true) + _viewModel.ToggleByIndexCommand.Execute(idx); + }); + } + + // Esc — dismiss the settings drawer if open + Bind(Windows.System.VirtualKey.Escape, + Windows.System.VirtualKeyModifiers.None, + (s, e) => + { + if (_drawerOpen) + { + e.Handled = true; + OnDrawerCloseRequested(this, System.EventArgs.Empty); + } + }); + } + + private async System.Threading.Tasks.Task ShowHelpDialogAsync() + { + try + { + var dlg = new HelpDialog + { + XamlRoot = (Content as Microsoft.UI.Xaml.FrameworkElement)?.XamlRoot, + }; + await dlg.ShowAsync(); + } + catch (System.Exception ex) + { + StatusBarText.Text = $"Help failed: {ex.Message}"; + } } ///