using Avalonia.Controls; using Avalonia.Input; using Avalonia.Threading; namespace Lumi.Companion.App; public sealed class HotkeyCaptureBinding { private readonly Button _button; private readonly DispatcherTimer _timeout; private readonly HashSet _pressed = []; private string _hotkey = ""; private string _previous = ""; private KeyModifiers _modifiers; private Key? _primary; private bool _capturing; public HotkeyCaptureBinding(Button button) { _button = button; _button.HorizontalContentAlignment = Avalonia.Layout.HorizontalAlignment.Left; _button.Focusable = true; _timeout = new DispatcherTimer { Interval = TimeSpan.FromSeconds(7) }; _timeout.Tick += (_, _) => CancelCapture(); _button.Click += (_, _) => BeginCapture(); _button.KeyDown += OnKeyDown; _button.KeyUp += OnKeyUp; } public string Hotkey { get => _hotkey; set { _hotkey = string.IsNullOrWhiteSpace(value) ? "Unassigned" : value.Trim(); if (!_capturing) UpdateContent(); } } private void OnKeyDown(object? sender, KeyEventArgs e) { if (!_capturing) { return; } e.Handled = true; if (e.Key == Key.Escape) { CancelCapture(); return; } _pressed.Add(e.Key); _modifiers |= e.KeyModifiers; if (!IsModifier(e.Key)) _primary = e.Key; _button.Content = _primary is null ? "Hold modifiers, then press a key…" : $"{Format(_modifiers, _primary.Value)} · release to save"; } private void OnKeyUp(object? sender, KeyEventArgs e) { if (!_capturing) { return; } e.Handled = true; _pressed.Remove(e.Key); if (_primary is not null && !_pressed.Any(key => !IsModifier(key))) { _hotkey = Format(_modifiers, _primary.Value); FinishCapture(); } } private void BeginCapture() { if (_capturing) return; _capturing = true; _previous = _hotkey; _pressed.Clear(); _modifiers = KeyModifiers.None; _primary = null; _button.Content = "Press a shortcut…"; _button.Focus(); _timeout.Start(); } private void CancelCapture() { if (!_capturing) return; _hotkey = _previous; FinishCapture(); } private void FinishCapture() { _capturing = false; _timeout.Stop(); _pressed.Clear(); UpdateContent(); } private void UpdateContent() => _button.Content = $"{_hotkey} (Set hotkey)"; private static bool IsModifier(Key key) => key is Key.LeftCtrl or Key.RightCtrl or Key.LeftAlt or Key.RightAlt or Key.LeftShift or Key.RightShift or Key.LWin or Key.RWin; internal static string Format(KeyModifiers modifiers, Key key) { var parts = new List(5); if (modifiers.HasFlag(KeyModifiers.Control)) parts.Add("Ctrl"); if (modifiers.HasFlag(KeyModifiers.Alt)) parts.Add("Alt"); if (modifiers.HasFlag(KeyModifiers.Shift)) parts.Add("Shift"); if (modifiers.HasFlag(KeyModifiers.Meta)) parts.Add("Win"); parts.Add(KeyName(key)); return string.Join("+", parts); } private static string KeyName(Key key) => key switch { Key.PageUp => "PageUp", Key.PageDown => "PageDown", Key.Left => "Left", Key.Right => "Right", Key.Up => "Up", Key.Down => "Down", Key.Return => "Enter", Key.Space => "Space", Key.Back => "Backspace", _ => key.ToString() }; }