123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303 |
- using System.Runtime.InteropServices;
- using Avalonia;
- using Avalonia.Controls;
- using Avalonia.Input;
- using Avalonia.Interactivity;
- using Avalonia.Media;
- using Avalonia.Styling;
- using Avalonia.Threading;
- using PicView.Avalonia.Functions;
- using PicView.Avalonia.Input;
- using PicView.Avalonia.UI;
- using PicView.Core.Localization;
- using R3;
- namespace PicView.Avalonia.CustomControls;
- /// <summary>
- /// A custom TextBox control for managing key bindings.
- /// </summary>
- public class KeybindTextBox : TextBox
- {
- public static readonly AvaloniaProperty<KeyGesture?> KeybindProperty =
- AvaloniaProperty.Register<KeybindTextBox, KeyGesture?>(nameof(Keybind));
- public static readonly AvaloniaProperty<string?> MethodNameProperty =
- AvaloniaProperty.Register<KeybindTextBox, string?>(nameof(MethodName));
- public static readonly AvaloniaProperty<bool?> AltProperty =
- AvaloniaProperty.Register<KeybindTextBox, bool?>(nameof(Alt));
-
- private readonly CompositeDisposable _disposables = new();
- public KeybindTextBox()
- {
- SubscribeToMethodNameChanges();
- SetupKeyEventHandlers();
- GotFocus += OnGotFocus;
- LostFocus += OnLostFocus;
- }
- protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e)
- {
- base.OnDetachedFromVisualTree(e);
- _disposables.Dispose();
- }
- protected override Type StyleKeyOverride => typeof(TextBox);
- public KeyGesture? Keybind
- {
- get => GetValue(KeybindProperty) as KeyGesture;
- set => SetValue(KeybindProperty, value);
- }
- public string MethodName
- {
- get => (string)(GetValue(MethodNameProperty) ?? "");
- set => SetValue(MethodNameProperty, value);
- }
- public bool Alt
- {
- get => (bool)(GetValue(AltProperty) ?? false);
- set => SetValue(AltProperty, value);
- }
- private void SubscribeToMethodNameChanges()
- {
- this.GetObservable(MethodNameProperty).ToObservable()
- .Subscribe(_ => Text = GetFunctionKey())
- .AddTo(_disposables);
- }
- private void SetupKeyEventHandlers()
- {
- if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
- {
- var keyUp = Observable.FromEventHandler<KeyEventArgs>(handler => KeyUp += handler, handler => KeyUp -= handler);
- keyUp.Select(e => e.e)
- .ObserveOn(UIHelper.GetFrameProvider)
- .SubscribeAwait(async (e, _) => await AssociateKey(e))
- .AddTo(_disposables);
- // On macOS, we only get KeyUp because the option to select a different character
- // when a key is held down interferes with keyboard shortcuts
- }
- else
- {
- var keyDown = Observable.FromEventHandler<KeyEventArgs>(handler => KeyDown += handler, handler => KeyDown -= handler);
- keyDown.Select(e => e.e)
- .ObserveOn(UIHelper.GetFrameProvider)
- .SubscribeAwait(async (e, _) => await AssociateKey(e))
- .AddTo(_disposables);
-
- var keyUp = Observable.FromEventHandler<KeyEventArgs>(handler => KeyUp += handler, handler => KeyUp -= handler);
- keyUp.Select(e => e.e)
- .ObserveOn(UIHelper.GetFrameProvider)
- .Subscribe(_ => KeyUpHandler())
- .AddTo(_disposables);
- }
- }
- protected override void OnKeyDown(KeyEventArgs e)
- {
- // Disable keyboard behavior #248
-
- // Fix tab
- if (e.Key == Key.Tab)
- {
- _ = AssociateKey(e);
- }
- }
- private void OnGotFocus(object? sender, GotFocusEventArgs e)
- {
- if (IsReadOnly)
- {
- ApplyReadOnlyBorderColor();
- return;
- }
- ApplyEditableForegroundColor();
- Text = TranslationManager.Translation.PressKey;
- CaretIndex = 0;
- MainKeyboardShortcuts.IsEscKeyEnabled = false;
- }
- private void OnLostFocus(object? sender, RoutedEventArgs e)
- {
- ApplyDefaultForegroundColor();
- Text = GetFunctionKey();
- MainKeyboardShortcuts.IsEscKeyEnabled = true;
- }
- private void KeyUpHandler()
- {
- ApplyDefaultForegroundColor();
- Text = GetFunctionKey();
- }
- private void ApplyReadOnlyBorderColor()
- {
- if (this.TryFindResource("MainBorderColor", ThemeVariant.Default, out var borderColor))
- {
- var borderBrush = new SolidColorBrush((Color)(borderColor ?? Color.Parse("#FFf6f4f4")));
- BorderBrush = borderBrush;
- }
- }
- private void ApplyEditableForegroundColor()
- {
- if (this.TryFindResource("MainTextColorFaded", ThemeVariant.Default, out var color))
- {
- Foreground = new SolidColorBrush((Color)(color ?? Color.Parse("#d6d4d4")));
- }
- }
- private void ApplyDefaultForegroundColor()
- {
- if (this.TryFindResource("MainTextColor", ThemeVariant.Default, out var color))
- {
- Foreground = new SolidColorBrush((Color)(color ?? Color.Parse("#FFf6f4f4")));
- }
- }
- private async Task AssociateKey(KeyEventArgs e)
- {
- switch (e.Key)
- {
- case Key.LeftShift:
- case Key.RightShift:
- case Key.LeftCtrl:
- case Key.RightCtrl:
- case Key.LeftAlt:
- case Key.RightAlt:
- case Key.LWin:
- case Key.RWin:
- return;
- }
- KeybindingManager.CustomShortcuts.Remove(new KeyGesture(e.Key, e.KeyModifiers));
- var function = await FunctionsMapper.GetFunctionByName(MethodName);
- if (function == null)
- {
- return;
- }
- if (e.Key == Key.Escape)
- {
- e.Handled = true;
- MainKeyboardShortcuts.IsEscKeyEnabled = false;
- await Dispatcher.UIThread.InvokeAsync(() => { Text = string.Empty; });
- Remove();
- await Save();
- return;
- }
- // Handle whether it's an alternative key or not
- if (Alt)
- {
- if (KeybindingManager.CustomShortcuts.ContainsValue(function))
- {
- // If the main key is not present, add a new entry with the alternative key
- var altKey = (Key)Enum.Parse(typeof(Key), e.Key.ToString());
- var keyGesture = new KeyGesture(altKey, e.KeyModifiers);
- KeybindingManager.CustomShortcuts[keyGesture] = function;
- }
- else
- {
- // Update the key and function name in the CustomShortcuts dictionary
- var keyGesture = new KeyGesture(e.Key, e.KeyModifiers);
- KeybindingManager.CustomShortcuts[keyGesture] = function;
- }
- }
- else
- {
- // Remove if it already contains
- if (KeybindingManager.CustomShortcuts.ContainsValue(function))
- {
- Remove();
- }
- var keyGesture = new KeyGesture(e.Key, e.KeyModifiers);
- KeybindingManager.CustomShortcuts[keyGesture] = function;
- }
- if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
- {
- KeyUpHandler();
- }
- await Save();
- return;
- async Task Save()
- {
- await KeybindingManager.UpdateKeyBindingsFile();
- }
- void Remove()
- {
- var keys = KeybindingManager.CustomShortcuts.Where(x => x.Value?.Method?.Name == MethodName)
- ?.Select(x => x.Key).ToList() ?? null;
- if (keys is not null)
- {
- KeybindingManager.CustomShortcuts.Remove(Alt ? keys.LastOrDefault() : keys.FirstOrDefault());
- }
- }
- }
- private string GetFunctionKey()
- {
- if (string.IsNullOrEmpty(MethodName))
- {
- return string.Empty;
- }
- if (IsReadOnly)
- {
- switch (MethodName)
- {
- case "ScrollUpInternal":
- var rotateRightKey = KeybindingManager.CustomShortcuts.Where(x => x.Value?.Method?.Name == "Up")
- ?.Select(x => x.Key).ToList() ?? null;
- return rotateRightKey is not { Count: > 0 } ? string.Empty :
- Alt ? rotateRightKey.LastOrDefault().ToString() : rotateRightKey.FirstOrDefault().ToString();
- case "ScrollDownInternal":
- var rotateLeftKey = KeybindingManager.CustomShortcuts.Where(x => x.Value?.Method?.Name == "Down")
- ?.Select(x => x.Key).ToList() ?? null;
- return rotateLeftKey is not { Count: > 0 } ? string.Empty :
- Alt ? rotateLeftKey.LastOrDefault().ToString() : rotateLeftKey.FirstOrDefault().ToString();
- }
- }
- // Find the key associated with the specified function
- var keys = KeybindingManager.CustomShortcuts.Where(x => x.Value?.Method?.Name == MethodName)?.Select(x => x.Key)
- .ToList() ?? null;
- if (keys is null)
- {
- return string.Empty;
- }
- return keys.Count switch
- {
- <= 0 => string.Empty,
- 1 => Alt ? string.Empty : FormatPlus(keys.FirstOrDefault().ToString()),
- _ => Alt ? FormatPlus(keys.LastOrDefault().ToString()) : FormatPlus(keys.FirstOrDefault().ToString())
- };
- string FormatPlus(string value)
- {
- return string.IsNullOrEmpty(value) ? string.Empty : value.Replace("+", " + ");
- }
- }
- }
|