Browse Source

Added KeyGesture and KeyBindings

Nikita Tsukanov 10 years ago
parent
commit
5acef971f0

+ 2 - 0
src/Markup/Perspex.Markup.Xaml/Context/PerspexWiringContext.cs

@@ -12,6 +12,7 @@ using OmniXaml.Builder;
 using OmniXaml.TypeConversion;
 using OmniXaml.Typing;
 using Perspex.Controls;
+using Perspex.Input;
 using Perspex.Markup.Xaml.Templates;
 using Perspex.Markup.Xaml.Converters;
 using Perspex.Markup.Xaml.DataBinding;
@@ -88,6 +89,7 @@ namespace Perspex.Markup.Xaml.Context
                 new TypeConverterRegistration(typeof(Thickness), new ThicknessTypeConverter()),
                 new TypeConverterRegistration(typeof(Selector), new SelectorTypeConverter()),
                 new TypeConverterRegistration(typeof(TimeSpan), new TimeSpanTypeConverter()),
+                new TypeConverterRegistration(typeof(KeyGesture), new KeyGestureConverter())
             };
 
             typeConverterProvider.AddAll(converters);

+ 30 - 0
src/Markup/Perspex.Markup.Xaml/Converters/KeyGestureConverter.cs

@@ -0,0 +1,30 @@
+using System;
+using System.Globalization;
+using OmniXaml.TypeConversion;
+using Perspex.Input;
+
+namespace Perspex.Markup.Xaml.Converters
+{
+    class KeyGestureConverter : ITypeConverter
+    {
+        public bool CanConvertFrom(IXamlTypeConverterContext context, Type sourceType)
+        {
+            return sourceType == typeof(string);
+        }
+
+        public bool CanConvertTo(IXamlTypeConverterContext context, Type destinationType)
+        {
+            return false;
+        }
+
+        public object ConvertFrom(IXamlTypeConverterContext context, CultureInfo culture, object value)
+        {
+            return KeyGesture.Parse((string)value);
+        }
+
+        public object ConvertTo(IXamlTypeConverterContext context, CultureInfo culture, object value, Type destinationType)
+        {
+            throw new NotImplementedException();
+        }
+    }
+}

+ 1 - 0
src/Markup/Perspex.Markup.Xaml/Perspex.Markup.Xaml.csproj

@@ -39,6 +39,7 @@
       <Link>Properties\SharedAssemblyInfo.cs</Link>
     </Compile>
     <Compile Include="Converters\ColorTypeConverter.cs" />
+    <Compile Include="Converters\KeyGestureConverter.cs" />
     <Compile Include="Converters\RelativePointTypeConverter.cs" />
     <Compile Include="Converters\PointTypeConverter.cs" />
     <Compile Include="Converters\ClassesTypeConverter.cs" />

+ 4 - 0
src/Perspex.Input/IInputElement.cs

@@ -3,6 +3,7 @@
 
 using System.Diagnostics.Contracts;
 using System;
+using System.Collections.Generic;
 using Perspex.Interactivity;
 
 namespace Perspex.Input
@@ -119,5 +120,8 @@ namespace Perspex.Input
         /// <param name="p">The position, in control coordinates.</param>
         /// <returns>The <see cref="IInputElement"/> at the specified position.</returns>
         IInputElement InputHitTest(Point p);
+
+
+        List<KeyBinding> KeyBindings { get; }
     }
 }

+ 3 - 0
src/Perspex.Input/InputElement.cs

@@ -2,6 +2,7 @@
 // Licensed under the MIT license. See licence.md file in the project root for full license information.
 
 using System;
+using System.Collections.Generic;
 using System.Linq;
 using Perspex.Interactivity;
 using Perspex.Rendering;
@@ -333,6 +334,8 @@ namespace Perspex.Input
             set { SetValue(IsEnabledCoreProperty, value); }
         }
 
+        public List<KeyBinding> KeyBindings { get; } = new List<KeyBinding>();
+
         /// <summary>
         /// Returns the input element that can be found within the current control at the specified
         /// position.

+ 40 - 0
src/Perspex.Input/KeyBinding.cs

@@ -0,0 +1,40 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.Windows.Input;
+
+namespace Perspex.Input
+{
+    public class KeyBinding : PerspexObject
+    {
+        public static PerspexProperty<ICommand> CommandProperty =
+            PerspexProperty.Register<KeyBinding, ICommand>("Command");
+
+        public ICommand Command
+        {
+            get { return GetValue(CommandProperty); }
+            set { SetValue(CommandProperty, value); }
+        }
+
+        public static PerspexProperty<KeyGesture> GestureProperty =
+            PerspexProperty.Register<KeyBinding, KeyGesture>("Gesture");
+
+        public KeyGesture Gesture
+        {
+            get { return GetValue(GestureProperty); }
+            set { SetValue(GestureProperty, value); }
+        }
+
+        public void TryHandle(KeyEventArgs args)
+        {
+            if (Gesture?.Matches(args) == true)
+            {
+                args.Handled = true;
+                if (Command?.CanExecute(null) == true)
+                    Command.Execute(null);
+            }
+        }
+    }
+}

+ 116 - 0
src/Perspex.Input/KeyGesture.cs

@@ -0,0 +1,116 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Perspex.Input
+{
+    public sealed class KeyGesture : IEquatable<KeyGesture>
+    {
+        public bool Equals(KeyGesture other)
+        {
+            if (ReferenceEquals(null, other)) return false;
+            if (ReferenceEquals(this, other)) return true;
+            return Key == other.Key && Modifiers == other.Modifiers;
+        }
+
+        public override bool Equals(object obj)
+        {
+            if (ReferenceEquals(null, obj)) return false;
+            if (ReferenceEquals(this, obj)) return true;
+            return obj is KeyGesture && Equals((KeyGesture) obj);
+        }
+
+        public override int GetHashCode()
+        {
+            unchecked
+            {
+                return ((int) Key*397) ^ (int) Modifiers;
+            }
+        }
+
+        public static bool operator ==(KeyGesture left, KeyGesture right)
+        {
+            return Equals(left, right);
+        }
+
+        public static bool operator !=(KeyGesture left, KeyGesture right)
+        {
+            return !Equals(left, right);
+        }
+
+        public Key Key { get; set; }
+
+        public InputModifiers Modifiers { get; set; }
+
+        
+        static Dictionary<string, Key> KeySynonims = new Dictionary<string, Key>
+        {
+            {"+", Key.OemPlus },
+            {"-", Key.OemMinus},
+            {".", Key.OemPeriod }
+        };
+
+        //TODO: Move that to external key parser
+        static Key ParseKey(string key)
+        {
+            Key rv;
+            if (KeySynonims.TryGetValue(key.ToLower(), out rv))
+                return rv;
+            return (Key)Enum.Parse(typeof (Key), key, true);
+        }
+
+        static InputModifiers ParseModifier(string modifier)
+        {
+            if (modifier.Equals("ctrl", StringComparison.OrdinalIgnoreCase))
+                return InputModifiers.Control;
+            return (InputModifiers) Enum.Parse(typeof (InputModifiers), modifier, true);
+        }
+
+        public static KeyGesture Parse(string gesture)
+        {
+            //string.Split can't be used here because "Ctrl++" is a perfectly valid key gesture
+
+            var parts = new List<string>();
+
+            var cstart = 0;
+            for (var c = 0; c <= gesture.Length; c++)
+            {
+                var ch = c == gesture.Length ? '\0' : gesture[c];
+                if (c == gesture.Length || (ch == '+' && cstart != c))
+                {
+                    parts.Add(gesture.Substring(cstart, c - cstart));
+                    cstart = c + 1;
+                }
+            }
+            for (var c = 0; c < parts.Count; c++)
+                parts[c] = parts[c].Trim();
+
+            var rv = new KeyGesture();
+
+            for (var c = 0; c < parts.Count; c++)
+            {
+                if (c == parts.Count - 1)
+                    rv.Key = ParseKey(parts[c]);
+                else
+                    rv.Modifiers |= ParseModifier(parts[c]);
+            }
+            return rv;
+        }
+
+        public override string ToString()
+        {
+            var parts = new List<string>();
+            foreach (var flag in Enum.GetValues(typeof (InputModifiers)).Cast<InputModifiers>())
+            {
+                if (Modifiers.HasFlag(flag) && flag != InputModifiers.None)
+                    parts.Add(flag.ToString());
+            }
+            parts.Add(Key.ToString());
+            return string.Join(" + ", parts);
+        }
+
+        public bool Matches(KeyEventArgs keyEvent) => keyEvent.Key == Key && keyEvent.Modifiers == Modifiers;
+    }
+}

+ 14 - 0
src/Perspex.Input/KeyboardDevice.cs

@@ -104,6 +104,20 @@ namespace Perspex.Input
                                 Source = element,
                             };
 
+                            IVisual currentHandler = element;
+                            while (currentHandler != null && !ev.Handled && keyInput.Type == RawKeyEventType.KeyDown)
+                            {
+                                var bindings = (currentHandler as IInputElement)?.KeyBindings;
+                                if(bindings!=null)
+                                    foreach (var binding in bindings)
+                                    {
+                                        if(ev.Handled)
+                                            break;
+                                        binding.TryHandle(ev);
+                                    }
+                                currentHandler = currentHandler.VisualParent;
+                            }
+
                             element.RaiseEvent(ev);
                             break;
                     }

+ 2 - 0
src/Perspex.Input/Perspex.Input.csproj

@@ -66,6 +66,8 @@
     </Compile>
     <Compile Include="Cursors.cs" />
     <Compile Include="AccessKeyHandler.cs" />
+    <Compile Include="KeyBinding.cs" />
+    <Compile Include="KeyGesture.cs" />
     <Compile Include="Platform\IClipboard.cs" />
     <Compile Include="Platform\IStandardCursorFactory.cs" />
     <Compile Include="Raw\RawTextInputEventArgs.cs" />

+ 2 - 0
src/Perspex.Input/Properties/AssemblyInfo.cs

@@ -2,5 +2,7 @@
 // Licensed under the MIT license. See licence.md file in the project root for full license information.
 
 using System.Reflection;
+using Perspex.Metadata;
 
 [assembly: AssemblyTitle("Perspex.Input")]
+[assembly: XmlnsDefinition("https://github.com/perspex", "Perspex.Input")]

+ 28 - 0
tests/Perspex.Input.UnitTests/KeyGestureParseTests.cs

@@ -0,0 +1,28 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using Xunit;
+
+namespace Perspex.Input.UnitTests
+{
+    public class KeyGestureParseTests
+    {
+        private static readonly Dictionary<string, KeyGesture> SampleData = new Dictionary<string, KeyGesture>
+        {
+            {"Ctrl+A", new KeyGesture {Key = Key.A, Modifiers = InputModifiers.Control}},
+            {"  \tShift\t+Alt +B", new KeyGesture {Key = Key.B, Modifiers = InputModifiers.Shift|InputModifiers.Alt} },
+            {"Control++", new KeyGesture {Key = Key.OemPlus, Modifiers = InputModifiers.Control} }
+        };
+            
+            
+            
+        [Fact]
+        public void Key_Gesture_Is_Able_To_Parse_Sample_Data()
+        {
+            foreach (var d in SampleData)
+                Assert.Equal(d.Value, KeyGesture.Parse(d.Key));
+        }
+    }
+}

+ 1 - 0
tests/Perspex.Input.UnitTests/Perspex.Input.UnitTests.csproj

@@ -57,6 +57,7 @@
   <ItemGroup>
     <Compile Include="KeyboardNavigationTests_Arrows.cs" />
     <Compile Include="KeyboardNavigationTests_Tab.cs" />
+    <Compile Include="KeyGestureParseTests.cs" />
     <Compile Include="Properties\AssemblyInfo.cs" />
   </ItemGroup>
   <ItemGroup>