Browse Source

Added KeyGesture and KeyBindings

pull/227/head
Nikita Tsukanov 11 years ago
parent
commit
5acef971f0
  1. 2
      src/Markup/Perspex.Markup.Xaml/Context/PerspexWiringContext.cs
  2. 30
      src/Markup/Perspex.Markup.Xaml/Converters/KeyGestureConverter.cs
  3. 1
      src/Markup/Perspex.Markup.Xaml/Perspex.Markup.Xaml.csproj
  4. 4
      src/Perspex.Input/IInputElement.cs
  5. 3
      src/Perspex.Input/InputElement.cs
  6. 40
      src/Perspex.Input/KeyBinding.cs
  7. 116
      src/Perspex.Input/KeyGesture.cs
  8. 14
      src/Perspex.Input/KeyboardDevice.cs
  9. 2
      src/Perspex.Input/Perspex.Input.csproj
  10. 2
      src/Perspex.Input/Properties/AssemblyInfo.cs
  11. 28
      tests/Perspex.Input.UnitTests/KeyGestureParseTests.cs
  12. 1
      tests/Perspex.Input.UnitTests/Perspex.Input.UnitTests.csproj

2
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
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
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
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
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
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
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
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
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
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
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
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>

Loading…
Cancel
Save