瀏覽代碼

Platform-specific key gestures

Nikita Tsukanov 7 年之前
父節點
當前提交
e00f0f0385

+ 1 - 0
src/Android/Avalonia.Android/AndroidPlatform.cs

@@ -54,6 +54,7 @@ namespace Avalonia.Android
                 .Bind<IPlatformIconLoader>().ToSingleton<PlatformIconLoader>()
                 .Bind<IRenderTimer>().ToConstant(new DefaultRenderTimer(60))
                 .Bind<IRenderLoop>().ToConstant(new RenderLoop())
+                .Bind<PlatformHotkeyConfiguration>().ToSingleton<PlatformHotkeyConfiguration>()
                 .Bind<IAssetLoader>().ToConstant(new AssetLoader(app.GetType().Assembly));
 
             SkiaPlatform.Initialize();

+ 208 - 155
src/Avalonia.Controls/TextBox.cs

@@ -368,186 +368,239 @@ namespace Avalonia.Controls
             string text = Text ?? string.Empty;
             int caretIndex = CaretIndex;
             bool movement = false;
+            bool selection = false;
             bool handled = false;
             var modifiers = e.Modifiers;
 
-            switch (e.Key)
-            {
-                case Key.A:
-                    if (modifiers == InputModifiers.Control)
-                    {
-                        SelectAll();
-                        handled = true;
-                    }
-                    break;
-                case Key.C:
-                    if (modifiers == InputModifiers.Control)
-                    {
-                        if (!IsPasswordBox)
-                        {
-                            Copy();
-                        }
-                        handled = true;
-                    }
-                    break;
+            var keymap = AvaloniaLocator.Current.GetService<PlatformHotkeyConfiguration>();
 
-                case Key.X:
-                    if (modifiers == InputModifiers.Control)
-                    {
-                        if (!IsPasswordBox)
-                        {
-                            Copy();
-                            DeleteSelection();
-                        }
-                        handled = true;
-                    }
-                    break;
+            bool Match(List<KeyGesture> gestures) => gestures.Any(g => g.Matches(e));
+            bool DetectSelection() => e.Modifiers.HasFlag(keymap.SelectionModifiers);
 
-                case Key.V:
-                    if (modifiers == InputModifiers.Control)
-                    {
-                        Paste();
-                        handled = true;
-                    }
+            if (Match(keymap.SelectAll))
+            {
+                SelectAll();
+                handled = true;
+            }
+            else if (Match(keymap.Copy))
+            {
+                if (!IsPasswordBox)
+                {
+                    Copy();
+                }
 
-                    break;
+                handled = true;
+            }
+            else if (Match(keymap.Cut))
+            {
+                if (!IsPasswordBox)
+                {
+                    Copy();
+                    DeleteSelection();
+                }
 
-                case Key.Z:
-                    if (modifiers == InputModifiers.Control)
-                    {
-                        try
-                        {
-                            _isUndoingRedoing = true;
-                            _undoRedoHelper.Undo();
-                        }
-                        finally
-                        {
-                            _isUndoingRedoing = false;
-                        }
-                        handled = true;
-                    }
-                    break;
-                case Key.Y:
-                    if (modifiers == InputModifiers.Control)
-                    {
-                        try
-                        {
-                            _isUndoingRedoing = true;
-                            _undoRedoHelper.Redo();
-                        }
-                        finally
-                        {
-                            _isUndoingRedoing = false;
-                        }
-                        handled = true;
-                    }
-                    break;
-                case Key.Left:
-                    MoveHorizontal(-1, modifiers);
-                    movement = true;
-                    break;
+                handled = true;
+            }
+            else if (Match(keymap.Paste))
+            {
 
-                case Key.Right:
-                    MoveHorizontal(1, modifiers);
-                    movement = true;
-                    break;
+                Paste();
+                handled = true;
+            }
+            else if (Match(keymap.Undo))
+            {
 
-                case Key.Up:
-                    movement = MoveVertical(-1, modifiers);
-                    break;
+                try
+                {
+                    _isUndoingRedoing = true;
+                    _undoRedoHelper.Undo();
+                }
+                finally
+                {
+                    _isUndoingRedoing = false;
+                }
 
-                case Key.Down:
-                    movement = MoveVertical(1, modifiers);
-                    break;
+                handled = true;
+            }
+            else if (Match(keymap.Redo))
+            {
+                try
+                {
+                    _isUndoingRedoing = true;
+                    _undoRedoHelper.Redo();
+                }
+                finally
+                {
+                    _isUndoingRedoing = false;
+                }
 
-                case Key.Home:
-                    MoveHome(modifiers);
-                    movement = true;
-                    break;
+                handled = true;
+            }
+            else if (Match(keymap.MoveCursorToTheStartOfDocument))
+            {
+                MoveHome(true);
+                movement = true;
+                selection = false;
+                handled = true;
+            }
+            else if (Match(keymap.MoveCursorToTheEndOfDocument))
+            {
+                MoveEnd(true);
+                movement = true;
+                selection = false;
+                handled = true;
+            }
+            else if (Match(keymap.MoveCursorToTheStartOfLine))
+            {
+                MoveHome(false);
+                movement = true;
+                selection = false;
+                handled = true;
+                
+            }
+            else if (Match(keymap.MoveCursorToTheEndOfLine))
+            {
+                MoveEnd(false);
+                movement = true;
+                selection = false;
+                handled = true;
+            }
+            else if (Match(keymap.MoveCursorToTheStartOfDocumentWithSelection))
+            {
+                MoveHome(true);
+                movement = true;
+                selection = true;
+                handled = true;
+            }
+            else if (Match(keymap.MoveCursorToTheEndOfDocumentWithSelection))
+            {
+                MoveEnd(true);
+                movement = true;
+                selection = true;
+                handled = true;
+            }
+            else if (Match(keymap.MoveCursorToTheStartOfLineWithSelection))
+            {
+                MoveHome(false);
+                movement = true;
+                selection = true;
+                handled = true;
+                
+            }
+            else if (Match(keymap.MoveCursorToTheEndOfLineWithSelection))
+            {
+                MoveEnd(false);
+                movement = true;
+                selection = true;
+                handled = true;
+            }
+            else
+                switch (e.Key)
+                {
+                    case Key.Left:
+                        MoveHorizontal(-1, modifiers);
+                        movement = true;
+                        selection = DetectSelection();
+                        break;
 
-                case Key.End:
-                    MoveEnd(modifiers);
-                    movement = true;
-                    break;
+                    case Key.Right:
+                        MoveHorizontal(1, modifiers);
+                        movement = true;
+                        selection = DetectSelection();
+                        break;
 
-                case Key.Back:
-                    if (modifiers == InputModifiers.Control && SelectionStart == SelectionEnd)
-                    {
-                        SetSelectionForControlBackspace(modifiers);
-                    }
+                    case Key.Up:
+                        movement = MoveVertical(-1, modifiers);
+                        selection = DetectSelection();
+                        break;
 
-                    if (!DeleteSelection() && CaretIndex > 0)
-                    {
-                        var removedCharacters = 1;
-                        // handle deleting /r/n
-                        // you don't ever want to leave a dangling /r around. So, if deleting /n, check to see if 
-                        // a /r should also be deleted.
-                        if (CaretIndex > 1 &&
-                            text[CaretIndex - 1] == '\n' &&
-                            text[CaretIndex - 2] == '\r')
+                    case Key.Down:
+                        movement = MoveVertical(1, modifiers);
+                        selection = DetectSelection();
+                        break;
+
+                    case Key.Back:
+                        if (modifiers == keymap.CommandModifiers && SelectionStart == SelectionEnd)
                         {
-                            removedCharacters = 2;
+                            SetSelectionForControlBackspace(modifiers);
                         }
 
-                        SetTextInternal(text.Substring(0, caretIndex - removedCharacters) + text.Substring(caretIndex));
-                        CaretIndex -= removedCharacters;
-                        SelectionStart = SelectionEnd = CaretIndex;
-                    }
-                    handled = true;
-                    break;
+                        if (!DeleteSelection() && CaretIndex > 0)
+                        {
+                            var removedCharacters = 1;
+                            // handle deleting /r/n
+                            // you don't ever want to leave a dangling /r around. So, if deleting /n, check to see if 
+                            // a /r should also be deleted.
+                            if (CaretIndex > 1 &&
+                                text[CaretIndex - 1] == '\n' &&
+                                text[CaretIndex - 2] == '\r')
+                            {
+                                removedCharacters = 2;
+                            }
 
-                case Key.Delete:
-                    if (modifiers == InputModifiers.Control && SelectionStart == SelectionEnd)
-                    {
-                        SetSelectionForControlDelete(modifiers);
-                    }
+                            SetTextInternal(text.Substring(0, caretIndex - removedCharacters) +
+                                            text.Substring(caretIndex));
+                            CaretIndex -= removedCharacters;
+                            SelectionStart = SelectionEnd = CaretIndex;
+                        }
 
-                    if (!DeleteSelection() && caretIndex < text.Length)
-                    {
-                        var removedCharacters = 1;
-                        // handle deleting /r/n
-                        // you don't ever want to leave a dangling /r around. So, if deleting /n, check to see if 
-                        // a /r should also be deleted.
-                        if (CaretIndex < text.Length - 1 &&
-                            text[caretIndex + 1] == '\n' &&
-                            text[caretIndex] == '\r')
+                        handled = true;
+                        break;
+
+                    case Key.Delete:
+                        if (modifiers == keymap.CommandModifiers && SelectionStart == SelectionEnd)
                         {
-                            removedCharacters = 2;
+                            SetSelectionForControlDelete(modifiers);
                         }
 
-                        SetTextInternal(text.Substring(0, caretIndex) + text.Substring(caretIndex + removedCharacters));
-                    }
-                    handled = true;
-                    break;
+                        if (!DeleteSelection() && caretIndex < text.Length)
+                        {
+                            var removedCharacters = 1;
+                            // handle deleting /r/n
+                            // you don't ever want to leave a dangling /r around. So, if deleting /n, check to see if 
+                            // a /r should also be deleted.
+                            if (CaretIndex < text.Length - 1 &&
+                                text[caretIndex + 1] == '\n' &&
+                                text[caretIndex] == '\r')
+                            {
+                                removedCharacters = 2;
+                            }
+
+                            SetTextInternal(text.Substring(0, caretIndex) +
+                                            text.Substring(caretIndex + removedCharacters));
+                        }
 
-                case Key.Enter:
-                    if (AcceptsReturn)
-                    {
-                        HandleTextInput(NewLine);
                         handled = true;
-                    }
+                        break;
 
-                    break;
+                    case Key.Enter:
+                        if (AcceptsReturn)
+                        {
+                            HandleTextInput(NewLine);
+                            handled = true;
+                        }
 
-                case Key.Tab:
-                    if (AcceptsTab)
-                    {
-                        HandleTextInput("\t");
-                        handled = true;
-                    }
-                    else
-                    {
-                        base.OnKeyDown(e);
-                    }
+                        break;
 
-                    break;
+                    case Key.Tab:
+                        if (AcceptsTab)
+                        {
+                            HandleTextInput("\t");
+                            handled = true;
+                        }
+                        else
+                        {
+                            base.OnKeyDown(e);
+                        }
 
-                default:
-                    handled = false;
-                    break;
-            }
+                        break;
+
+                    default:
+                        handled = false;
+                        break;
+                }
 
-            if (movement && ((modifiers & InputModifiers.Shift) != 0))
+            if (movement && selection)
             {
                 SelectionEnd = CaretIndex;
             }
@@ -732,12 +785,12 @@ namespace Avalonia.Controls
             }
         }
 
-        private void MoveHome(InputModifiers modifiers)
+        private void MoveHome(bool document)
         {
             var text = Text ?? string.Empty;
             var caretIndex = CaretIndex;
 
-            if ((modifiers & InputModifiers.Control) != 0)
+            if (document)
             {
                 caretIndex = 0;
             }
@@ -762,12 +815,12 @@ namespace Avalonia.Controls
             CaretIndex = caretIndex;
         }
 
-        private void MoveEnd(InputModifiers modifiers)
+        private void MoveEnd(bool document)
         {
             var text = Text ?? string.Empty;
             var caretIndex = CaretIndex;
 
-            if ((modifiers & InputModifiers.Control) != 0)
+            if (document)
             {
                 caretIndex = text.Length;
             }

+ 18 - 0
src/Avalonia.Input/Key.cs

@@ -1020,5 +1020,23 @@ namespace Avalonia.Input
         /// The key is used with another key to create a single combined character.
         /// </summary>
         DeadCharProcessed = 172,
+        
+        
+        /// <summary>
+        /// OSX Platform-specific Fn+Left key
+        /// </summary>
+        FnLeftArrow = 10001,
+        /// <summary>
+        /// OSX Platform-specific Fn+Right key
+        /// </summary>
+        FnRightArrow = 10002,
+        /// <summary>
+        /// OSX Platform-specific Fn+Up key
+        /// </summary>
+        FnUpArrow = 10003,
+        /// <summary>
+        /// OSX Platform-specific Fn+Down key
+        /// </summary>
+        FnDownArrow = 10004,
     }
 }

+ 11 - 0
src/Avalonia.Input/KeyGesture.cs

@@ -6,6 +6,17 @@ namespace Avalonia.Input
 {
     public sealed class KeyGesture : IEquatable<KeyGesture>
     {
+        public KeyGesture()
+        {
+            
+        }
+
+        public KeyGesture(Key key, InputModifiers modifiers = InputModifiers.None)
+        {
+            Key = key;
+            Modifiers = modifiers;
+        }
+        
         public bool Equals(KeyGesture other)
         {
             if (ReferenceEquals(null, other)) return false;

+ 94 - 0
src/Avalonia.Input/Platform/PlatformHotkeyConfiguration.cs

@@ -0,0 +1,94 @@
+using System.Collections.Generic;
+
+namespace Avalonia.Input.Platform
+{
+    public class PlatformHotkeyConfiguration
+    {
+        public PlatformHotkeyConfiguration() : this(InputModifiers.Control)
+        {
+            
+        }
+        
+        public PlatformHotkeyConfiguration(InputModifiers commandModifiers, InputModifiers selectionModifiers = InputModifiers.Shift)
+        {
+            CommandModifiers = commandModifiers;
+            SelectionModifiers = selectionModifiers;
+            Copy = new List<KeyGesture>
+            {
+                new KeyGesture(Key.C, commandModifiers)
+            };
+            Cut = new List<KeyGesture>
+            {
+                new KeyGesture(Key.X, commandModifiers)
+            };
+            Paste = new List<KeyGesture>
+            {
+                new KeyGesture(Key.V, commandModifiers)
+            };
+            Undo = new List<KeyGesture>
+            {
+                new KeyGesture(Key.Z, commandModifiers)
+            };
+            Redo = new List<KeyGesture>
+            {
+                new KeyGesture(Key.Y, commandModifiers),
+                new KeyGesture(Key.Z, commandModifiers | selectionModifiers)
+            };
+            SelectAll = new List<KeyGesture>
+            {
+                new KeyGesture(Key.A, commandModifiers)
+            };
+            MoveCursorToTheStartOfLine = new List<KeyGesture>
+            {
+                new KeyGesture(Key.Home)
+            };
+            MoveCursorToTheEndOfLine = new List<KeyGesture>
+            {
+                new KeyGesture(Key.End)
+            };
+            MoveCursorToTheStartOfDocument = new List<KeyGesture>
+            {
+                new KeyGesture(Key.Home, commandModifiers)
+            };
+            MoveCursorToTheEndOfDocument = new List<KeyGesture>
+            {
+                new KeyGesture(Key.End, commandModifiers)
+            };
+            MoveCursorToTheStartOfLineWithSelection = new List<KeyGesture>
+            {
+                new KeyGesture(Key.Home, selectionModifiers)
+            };
+            MoveCursorToTheEndOfLineWithSelection = new List<KeyGesture>
+            {
+                new KeyGesture(Key.End, selectionModifiers)
+            };
+            MoveCursorToTheStartOfDocumentWithSelection = new List<KeyGesture>
+            {
+                new KeyGesture(Key.Home, commandModifiers | selectionModifiers)
+            };
+            MoveCursorToTheEndOfDocumentWithSelection = new List<KeyGesture>
+            {
+                new KeyGesture(Key.End, commandModifiers | selectionModifiers)
+            };
+        }
+        
+        public InputModifiers CommandModifiers { get; set; }
+        public InputModifiers SelectionModifiers { get; set; }
+        public List<KeyGesture> Copy { get; set; }
+        public List<KeyGesture> Cut { get; set; }
+        public List<KeyGesture> Paste { get; set; }
+        public List<KeyGesture> Undo { get; set; }
+        public List<KeyGesture> Redo { get; set; }
+        public List<KeyGesture> SelectAll { get; set; }
+        public List<KeyGesture> MoveCursorToTheStartOfLine { get; set; }
+        public List<KeyGesture> MoveCursorToTheEndOfLine { get; set; }
+        public List<KeyGesture> MoveCursorToTheStartOfDocument { get; set; }
+        public List<KeyGesture> MoveCursorToTheEndOfDocument { get; set; }
+        public List<KeyGesture> MoveCursorToTheStartOfLineWithSelection { get; set; }
+        public List<KeyGesture> MoveCursorToTheEndOfLineWithSelection { get; set; }
+        public List<KeyGesture> MoveCursorToTheStartOfDocumentWithSelection { get; set; }
+        public List<KeyGesture> MoveCursorToTheEndOfDocumentWithSelection { get; set; }
+        
+        
+    }
+}

+ 1 - 0
src/Gtk/Avalonia.Gtk3/Gtk3Platform.cs

@@ -81,6 +81,7 @@ namespace Avalonia.Gtk3
                 .Bind<ISystemDialogImpl>().ToSingleton<SystemDialog>()
                 .Bind<IRenderLoop>().ToConstant(new RenderLoop())
                 .Bind<IRenderTimer>().ToConstant(new DefaultRenderTimer(60))
+                .Bind<PlatformHotkeyConfiguration>().ToSingleton<PlatformHotkeyConfiguration>()
                 .Bind<IPlatformIconLoader>().ToConstant(new PlatformIconLoader());
             if (useGpu)
                 EglGlPlatformFeature.TryInitialize();

+ 2 - 0
src/Linux/Avalonia.LinuxFramebuffer/LinuxFramebufferPlatform.cs

@@ -5,6 +5,7 @@ using Avalonia.Controls;
 using Avalonia.Controls.Embedding;
 using Avalonia.Controls.Platform;
 using Avalonia.Input;
+using Avalonia.Input.Platform;
 using Avalonia.LinuxFramebuffer;
 using Avalonia.Platform;
 using Avalonia.Rendering;
@@ -36,6 +37,7 @@ namespace Avalonia.LinuxFramebuffer
                 .Bind<IPlatformSettings>().ToSingleton<PlatformSettings>()
                 .Bind<IPlatformThreadingInterface>().ToConstant(Threading)
                 .Bind<IRenderLoop>().ToConstant(new RenderLoop())
+                .Bind<PlatformHotkeyConfiguration>().ToSingleton<PlatformHotkeyConfiguration>()
                 .Bind<IRenderTimer>().ToConstant(Threading);
         }
 

+ 1 - 0
src/OSX/Avalonia.MonoMac/MonoMacPlatform.cs

@@ -37,6 +37,7 @@ namespace Avalonia.MonoMac
                 .Bind<IClipboard>().ToSingleton<ClipboardImpl>()
                 .Bind<IRenderLoop>().ToConstant(s_renderLoop)
                 .Bind<IRenderTimer>().ToConstant(s_renderTimer)
+                .Bind<PlatformHotkeyConfiguration>().ToConstant(new PlatformHotkeyConfiguration(InputModifiers.Windows))
                 .Bind<IPlatformThreadingInterface>().ToConstant(PlatformThreadingInterface.Instance)
                 /*.Bind<IPlatformDragSource>().ToTransient<DragSource>()*/;
         }

+ 1 - 0
src/Windows/Avalonia.Win32/Win32Platform.cs

@@ -89,6 +89,7 @@ namespace Avalonia.Win32
                 .Bind<IRenderTimer>().ToConstant(new RenderTimer(60))
                 .Bind<ISystemDialogImpl>().ToSingleton<SystemDialogImpl>()
                 .Bind<IWindowingPlatform>().ToConstant(s_instance)
+                .Bind<PlatformHotkeyConfiguration>().ToSingleton<PlatformHotkeyConfiguration>()
                 .Bind<IPlatformIconLoader>().ToConstant(s_instance);
             Win32GlManager.Initialize();
             UseDeferredRendering = deferredRendering;

+ 1 - 0
src/iOS/Avalonia.iOS/iOSPlatform.cs

@@ -42,6 +42,7 @@ namespace Avalonia.iOS
                 .Bind<IPlatformIconLoader>().ToSingleton<PlatformIconLoader>()
                 .Bind<IWindowingPlatform>().ToSingleton<WindowingPlatformImpl>()
                 .Bind<IRenderTimer>().ToSingleton<DisplayLinkRenderTimer>()
+                .Bind<PlatformHotkeyConfiguration>().ToSingleton<PlatformHotkeyConfiguration>()
                 .Bind<IRenderLoop>().ToSingleton<RenderLoop>();
         }
     }

+ 2 - 0
tests/Avalonia.UnitTests/UnitTestApplication.cs

@@ -11,6 +11,7 @@ using Avalonia.Rendering;
 using Avalonia.Threading;
 using System.Reactive.Disposables;
 using System.Reactive.Concurrency;
+using Avalonia.Input.Platform;
 
 namespace Avalonia.UnitTests
 {
@@ -58,6 +59,7 @@ namespace Avalonia.UnitTests
                 .Bind<IStandardCursorFactory>().ToConstant(Services.StandardCursorFactory)
                 .Bind<IStyler>().ToConstant(Services.Styler)
                 .Bind<IWindowingPlatform>().ToConstant(Services.WindowingPlatform)
+                .Bind<PlatformHotkeyConfiguration>().ToSingleton<PlatformHotkeyConfiguration>()
                 .Bind<IApplicationLifecycle>().ToConstant(this);
             var styles = Services.Theme?.Invoke();