feat(winui3): keyboard shortcuts (F1, Ctrl+M, Ctrl+Shift+S, Ctrl+R, 1-9, Esc)
Some checks failed
CI / build-and-test (push) Failing after 27s

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 <Window.InputBindings> verbatim so the
operator's muscle memory transfers across hosts.

Wire-up note: WinUI 3 KeyboardAccelerator uses
TypedEventHandler<KeyboardAccelerator, KeyboardAcceleratorInvokedEvent
Args>, not System.EventHandler<T> 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.
This commit is contained in:
Zac Gaetano 2026-05-13 21:33:02 -04:00
parent 7ac56c2661
commit 7c269f2c40

View file

@ -29,6 +29,120 @@ public sealed partial class MainWindow : Window
ThemeManager.Current.Themed += (_, theme) => ApplyResolvedTheme(theme);
ApplyResolvedTheme(ThemeManager.Current.ResolveTheme());
WireKeyboardShortcuts();
}
/// <summary>
/// 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.
/// </summary>
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<Microsoft.UI.Xaml.Input.KeyboardAccelerator,
Microsoft.UI.Xaml.Input.KeyboardAcceleratorInvokedEventArgs> 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}";
}
}
/// <summary>