Browse Source

Merge branch 'master' into direct-perspexproperties

pull/227/head
Steven Kirk 11 years ago
parent
commit
986dc84ed4
  1. 8
      samples/TestApplication/Program.cs
  2. 5
      samples/XamlTestApplication/Views/MainWindow.paml
  3. 12
      src/Gtk/Perspex.Gtk/WindowImpl.cs
  4. 32
      src/Markup/Perspex.Markup.Xaml/Context/PerspexWiringContext.cs
  5. 30
      src/Markup/Perspex.Markup.Xaml/Converters/KeyGestureConverter.cs
  6. 1
      src/Markup/Perspex.Markup.Xaml/Perspex.Markup.Xaml.csproj
  7. 12
      src/Perspex.Controls/Button.cs
  8. 2
      src/Perspex.Controls/DropDown.cs
  9. 117
      src/Perspex.Controls/HotkeyManager.cs
  10. 2
      src/Perspex.Controls/ItemsControl.cs
  11. 13
      src/Perspex.Controls/MenuItem.cs
  12. 2
      src/Perspex.Controls/Perspex.Controls.csproj
  13. 2
      src/Perspex.Controls/Platform/ITopLevelImpl.cs
  14. 2
      src/Perspex.Controls/Primitives/SelectingItemsControl.cs
  15. 3
      src/Perspex.Controls/Properties/AssemblyInfo.cs
  16. 6
      src/Perspex.Controls/TextBlock.cs
  17. 18
      src/Perspex.Controls/TextBox.cs
  18. 2
      src/Perspex.Controls/TopLevel.cs
  19. 77
      src/Perspex.Controls/Utils/AncestorFinder.cs
  20. 4
      src/Perspex.Input/IInputElement.cs
  21. 3
      src/Perspex.Input/InputElement.cs
  22. 40
      src/Perspex.Input/KeyBinding.cs
  23. 116
      src/Perspex.Input/KeyGesture.cs
  24. 14
      src/Perspex.Input/KeyboardDevice.cs
  25. 2
      src/Perspex.Input/Perspex.Input.csproj
  26. 2
      src/Perspex.Input/Properties/AssemblyInfo.cs
  27. 7
      src/Perspex.SceneGraph/Media/FormattedText.cs
  28. 53
      src/Perspex.Themes.Default/TextBoxStyle.cs
  29. 16
      src/Windows/Perspex.Win32/WindowImpl.cs
  30. 2
      tests/Perspex.Controls.UnitTests/Perspex.Controls.UnitTests.csproj
  31. 40
      tests/Perspex.Controls.UnitTests/Utils/AncestorFinderTests.cs
  32. 72
      tests/Perspex.Controls.UnitTests/Utils/HotKeyManagerTests.cs
  33. 28
      tests/Perspex.Input.UnitTests/KeyGestureParseTests.cs
  34. 1
      tests/Perspex.Input.UnitTests/Perspex.Input.UnitTests.csproj

8
samples/TestApplication/Program.cs

@ -138,6 +138,7 @@ namespace TestApplication
container.Classes.Add(":container");
DevTools.Attach(window);
window.Show();
Application.Current.Run(window);
}
@ -340,8 +341,11 @@ namespace TestApplication
Foreground = SolidColorBrush.Parse("#727272"),
Margin = new Thickness(0, 0, 0, 10)
},
new TextBox { Text = "Lorem ipsum dolor sit amet, consectetur adipiscing elit.", Width = 200 },
new TextBox { AcceptsReturn = true, TextWrapping = TextWrapping.Wrap, Width = 200, Height = 150, Text = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus magna. Cras in mi at felis aliquet congue. Ut a est eget ligula molestie gravida. Curabitur massa. Donec eleifend, libero at sagittis mollis, tellus est malesuada tellus, at luctus turpis elit sit amet quam. Vivamus pretium ornare est." },
new TextBox { Text = "Lorem ipsum dolor sit amet, consectetur adipiscing elit.", Width = 200},
new TextBox { Width = 200, Watermark="Watermark"},
new TextBox { Width = 200, Watermark="Floating Watermark", UseFloatingWatermark = true },
new TextBox { AcceptsReturn = true, TextWrapping = TextWrapping.Wrap, Width = 200, Height = 150, Text = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus magna. Cras in mi at felis aliquet congue. Ut a est eget ligula molestie gravida. Curabitur massa. Donec eleifend, libero at sagittis mollis, tellus est malesuada tellus, at luctus turpis elit sit amet quam. Vivamus pretium ornare est." },
new TextBlock
{
Margin = new Thickness(0, 40, 0, 0),

5
samples/XamlTestApplication/Views/MainWindow.paml

@ -2,7 +2,6 @@
xmlns="https://github.com/perspex"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Perspex Test Application" Height="350" Width="525" SizeToContent="WidthAndHeight" >
<Grid RowDefinitions="Auto,*,Auto" ColumnDefinitions="*,*">
<TabControl Grid.Row="1" Grid.ColumnSpan="2" Padding="5">
<TabControl.Transition>
@ -49,7 +48,7 @@
</TabItem>
<TabItem Header="Lists">
<StackPanel Orientation="Horizontal" VerticalAlignment="Center" HorizontalAlignment="Center">
<ListBox>
<ListBox SelectedIndex="0">
<ListBoxItem>
<StackPanel>
<TextBlock Text="Item 1" FontSize="24" />
@ -63,7 +62,7 @@
</StackPanel>
</ListBoxItem>
</ListBox>
<DropDown VerticalAlignment="Center">
<DropDown VerticalAlignment="Center" SelectedIndex="0">
<StackPanel>
<TextBlock Text="Item 1" FontSize="24" />
<TextBlock Text="Item 1 Value" />

12
src/Gtk/Perspex.Gtk/WindowImpl.cs

@ -16,7 +16,7 @@ namespace Perspex.Gtk
public class WindowImpl : Gtk.Window, IWindowImpl
{
private TopLevel _owner;
private IInputRoot _inputRoot;
private IPlatformHandle _windowHandle;
@ -105,9 +105,9 @@ namespace Perspex.Gtk
return new Point(point.X + x, point.Y + y);
}
public void SetOwner(TopLevel owner)
public void SetInputRoot(IInputRoot inputRoot)
{
_owner = owner;
_inputRoot = inputRoot;
}
public void SetTitle(string title)
@ -158,7 +158,7 @@ namespace Perspex.Gtk
var e = new RawMouseEventArgs(
GtkMouseDevice.Instance,
evnt.Time,
_owner,
_inputRoot,
evnt.Button == 0
? RawMouseEventType.LeftButtonDown
: evnt.Button == 1 ? RawMouseEventType.RightButtonDown : RawMouseEventType.MiddleButtonDown,
@ -172,7 +172,7 @@ namespace Perspex.Gtk
var e = new RawMouseEventArgs(
GtkMouseDevice.Instance,
evnt.Time,
_owner,
_inputRoot,
evnt.Button == 0
? RawMouseEventType.LeftButtonUp
: evnt.Button == 1 ? RawMouseEventType.RightButtonUp : RawMouseEventType.MiddleButtonUp,
@ -241,7 +241,7 @@ namespace Perspex.Gtk
var e = new RawMouseEventArgs(
GtkMouseDevice.Instance,
evnt.Time,
_owner,
_inputRoot,
RawMouseEventType.Move,
position, GetModifierKeys(evnt.State));
Input(e);

32
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;
@ -21,23 +22,37 @@ using Perspex.Media.Imaging;
using Perspex.Metadata;
using Perspex.Platform;
using Perspex.Styling;
using Perspex.Controls.Primitives;
namespace Perspex.Markup.Xaml.Context
{
public class PerspexWiringContext : IWiringContext
public class PerspexWiringContext : WiringContext
{
private readonly WiringContext _context;
private const string PerspexNs = "https://github.com/perspex";
public PerspexWiringContext(ITypeFactory typeFactory)
: this(typeFactory, new TypeFeatureProvider(GetContentPropertyProvider(), GetConverterProvider()))
{
var featureProvider = new TypeFeatureProvider(GetContentPropertyProvider(), GetConverterProvider());
}
public PerspexWiringContext(ITypeFactory typeFactory, TypeFeatureProvider featureProvider)
: base(CreateTypeContext(typeFactory, featureProvider), featureProvider)
{
}
private static ITypeContext CreateTypeContext(ITypeFactory typeFactory, TypeFeatureProvider featureProvider)
{
var xamlNamespaceRegistry = CreateXamlNamespaceRegistry();
var perspexPropertyBinder = new PerspexPropertyBinder(featureProvider.ConverterProvider);
var xamlTypeRepository = new PerspexTypeRepository(xamlNamespaceRegistry, typeFactory, featureProvider, perspexPropertyBinder);
var typeContext = new TypeContext(xamlTypeRepository, xamlNamespaceRegistry, typeFactory);
_context = new WiringContext(typeContext, featureProvider);
var typeRepository = new PerspexTypeRepository(xamlNamespaceRegistry, typeFactory, featureProvider, perspexPropertyBinder);
typeRepository.RegisterMetadata(new Metadata<Setter>().WithMemberDependency(x => x.Value, x => x.Property));
typeRepository.RegisterMetadata(
new Metadata<SelectingItemsControl>()
.WithMemberDependency(x => x.SelectedIndex, x => x.Items)
.WithMemberDependency(x => x.SelectedItem, x => x.Items));
return new TypeContext(typeRepository, xamlNamespaceRegistry, typeFactory);
}
private static XamlNamespaceRegistry CreateXamlNamespaceRegistry()
@ -88,6 +103,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);
@ -114,9 +130,5 @@ namespace Perspex.Markup.Xaml.Context
return contentPropertyProvider;
}
public ITypeContext TypeContext => _context.TypeContext;
public ITypeFeatureProvider FeatureProvider => _context.FeatureProvider;
}
}

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" />

12
src/Perspex.Controls/Button.cs

@ -44,6 +44,9 @@ namespace Perspex.Controls
public static readonly PerspexProperty<ICommand> CommandProperty =
PerspexProperty.Register<Button, ICommand>(nameof(Command));
public static readonly PerspexProperty<KeyGesture> HotKeyProperty =
HotKeyManager.HotKeyProperty.AddOwner<Button>();
/// <summary>
/// Defines the <see cref="CommandParameter"/> property.
/// </summary>
@ -100,6 +103,15 @@ namespace Perspex.Controls
set { SetValue(CommandProperty, value); }
}
/// <summary>
/// Gets or sets an <see cref="KeyGesture"/> associated with this control
/// </summary>
public KeyGesture HotKey
{
get { return GetValue(HotKeyProperty); }
set { SetValue(HotKeyProperty, value); }
}
/// <summary>
/// Gets or sets a parameter to be passed to the <see cref="Command"/>.
/// </summary>

2
src/Perspex.Controls/DropDown.cs

@ -144,6 +144,8 @@ namespace Perspex.Controls
if (control != null)
{
control.Measure(Size.Infinity);
SelectionBoxItem = new Rectangle
{
Width = control.DesiredSize.Width,

117
src/Perspex.Controls/HotkeyManager.cs

@ -0,0 +1,117 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Input;
using Perspex.Controls;
using Perspex.Controls.Utils;
using Perspex.Input;
namespace Perspex.Controls
{
public class HotKeyManager
{
public static PerspexProperty<KeyGesture> HotKeyProperty
= PerspexProperty.RegisterAttached<Control, KeyGesture>("HotKey", typeof (HotKeyManager));
class HotkeyCommandWrapper : ICommand
{
public HotkeyCommandWrapper(IControl control)
{
Control = control;
}
public IControl Control;
private ICommand GetCommand() => Control.GetValue(Button.CommandProperty);
public bool CanExecute(object parameter) => GetCommand()?.CanExecute(parameter) ?? false;
public void Execute(object parameter) => GetCommand()?.Execute(parameter);
//Implementation isn't needed in this case
public event EventHandler CanExecuteChanged;
}
class Manager
{
private readonly IControl _control;
private TopLevel _root;
private IDisposable _parentSub;
private IDisposable _hotkeySub;
private KeyGesture _hotkey;
private HotkeyCommandWrapper _wrapper;
private KeyBinding _binding;
public Manager(IControl control)
{
_control = control;
_wrapper = new HotkeyCommandWrapper(_control);
}
public void Init()
{
_hotkeySub = _control.GetObservable(HotKeyProperty).Subscribe(OnHotkeyChanged);
_parentSub = AncestorFinder.Create(_control, typeof (TopLevel)).Subscribe(OnParentChanged);
}
private void OnParentChanged(IControl control)
{
Unregister();
_root = (TopLevel) control;
Register();
}
private void OnHotkeyChanged(KeyGesture hotkey)
{
if (hotkey == null)
//Subscription will be recreated by static property watcher
Stop();
else
{
Unregister();
_hotkey = hotkey;
Register();
}
}
void Unregister()
{
if (_root != null && _binding != null)
_root.KeyBindings.Remove(_binding);
_binding = null;
}
void Register()
{
if (_root != null && _hotkey != null)
{
_binding = new KeyBinding() {Gesture = _hotkey, Command = _wrapper};
_root.KeyBindings.Add(_binding);
}
}
void Stop()
{
Unregister();
_parentSub.Dispose();
_hotkeySub.Dispose();
}
}
static HotKeyManager()
{
HotKeyProperty.Changed.Subscribe(args =>
{
var control = args.Sender as IControl;
if (args.OldValue != null|| control == null)
return;
new Manager(control).Init();
});
}
public static void SetHotKey(PerspexObject target, KeyGesture value) => target.SetValue(HotKeyProperty, value);
public static KeyGesture GetHotKey(PerspexObject target) => target.GetValue(HotKeyProperty);
}
}

2
src/Perspex.Controls/ItemsControl.cs

@ -156,6 +156,8 @@ namespace Perspex.Controls
/// <param name="e">The event args.</param>
protected virtual void ItemsChanged(PerspexPropertyChangedEventArgs e)
{
System.Diagnostics.Debug.WriteLine($"{this.GetType().Name} set items");
var incc = e.OldValue as INotifyCollectionChanged;
if (incc != null)

13
src/Perspex.Controls/MenuItem.cs

@ -28,6 +28,9 @@ namespace Perspex.Controls
public static readonly PerspexProperty<ICommand> CommandProperty =
Button.CommandProperty.AddOwner<MenuItem>();
public static readonly PerspexProperty<KeyGesture> HotKeyProperty =
HotKeyManager.HotKeyProperty.AddOwner<Button>();
/// <summary>
/// Defines the <see cref="CommandParameter"/> property.
/// </summary>
@ -130,6 +133,16 @@ namespace Perspex.Controls
set { SetValue(CommandProperty, value); }
}
/// <summary>
/// Gets or sets an <see cref="KeyGesture"/> associated with this control
/// </summary>
public KeyGesture HotKey
{
get { return GetValue(HotKeyProperty); }
set { SetValue(HotKeyProperty, value); }
}
/// <summary>
/// Gets or sets the parameter to pass to the <see cref="Command"/> property of a
/// <see cref="MenuItem"/>.

2
src/Perspex.Controls/Perspex.Controls.csproj

@ -42,6 +42,7 @@
<Link>Properties\SharedAssemblyInfo.cs</Link>
</Compile>
<Compile Include="DockPanel.cs" />
<Compile Include="HotkeyManager.cs" />
<Compile Include="SystemDialog.cs" />
<Compile Include="Generators\ITreeItemContainerGenerator.cs" />
<Compile Include="Generators\ItemContainers.cs" />
@ -132,6 +133,7 @@
<Compile Include="UserControl.cs" />
<Compile Include="Templates\TemplateExtensions.cs" />
<Compile Include="TextWrapping.cs" />
<Compile Include="Utils\AncestorFinder.cs" />
<Compile Include="Utils\IEnumerableUtils.cs" />
<Compile Include="Utils\StringUtils.cs" />
<Compile Include="TopLevel.cs" />

2
src/Perspex.Controls/Platform/ITopLevelImpl.cs

@ -30,7 +30,7 @@ namespace Perspex.Platform
void Invalidate(Rect rect);
void SetOwner(TopLevel owner);
void SetInputRoot(IInputRoot inputRoot);
Point PointToScreen(Point point);

2
src/Perspex.Controls/Primitives/SelectingItemsControl.cs

@ -295,6 +295,8 @@ namespace Perspex.Controls.Primitives
/// <param name="e">The event args.</param>
private void SelectedIndexChanged(PerspexPropertyChangedEventArgs e)
{
System.Diagnostics.Debug.WriteLine($"{this.GetType().Name} set selected index");
var index = (int)e.OldValue;
if (index != -1)

3
src/Perspex.Controls/Properties/AssemblyInfo.cs

@ -9,4 +9,5 @@ using Perspex.Metadata;
[assembly: InternalsVisibleTo("Perspex.Controls.UnitTests")]
[assembly: XmlnsDefinition("https://github.com/perspex", "Perspex.Controls")]
[assembly: XmlnsDefinition("https://github.com/perspex", "Perspex.Controls.Primitives")]
[assembly: XmlnsDefinition("https://github.com/perspex", "Perspex.Controls.Primitives")]
[assembly: XmlnsDefinition("https://github.com/perspex", "Perspex.Controls.Shapes")]

6
src/Perspex.Controls/TextBlock.cs

@ -235,9 +235,9 @@ namespace Perspex.Controls
protected virtual FormattedText CreateFormattedText(Size constraint)
{
var result = new FormattedText(
Text,
FontFamily,
FontSize,
Text ?? string.Empty,
FontFamily ?? "Ariel",
FontSize > 0 ? FontSize : 12,
FontStyle,
TextAlignment,
FontWeight);

18
src/Perspex.Controls/TextBox.cs

@ -39,6 +39,12 @@ namespace Perspex.Controls
public static readonly PerspexProperty<TextWrapping> TextWrappingProperty =
TextBlock.TextWrappingProperty.AddOwner<TextBox>();
public static readonly PerspexProperty<string> WatermarkProperty =
PerspexProperty.Register<TextBox, string>("Watermark");
public static readonly PerspexProperty<bool> UseFloatingWatermarkProperty =
PerspexProperty.Register<TextBox, bool>("UseFloatingWatermark");
private TextPresenter _presenter;
static TextBox()
@ -101,6 +107,18 @@ namespace Perspex.Controls
set { SetValue(TextProperty, value); }
}
public string Watermark
{
get { return GetValue(WatermarkProperty); }
set { SetValue(WatermarkProperty, value); }
}
public bool UseFloatingWatermark
{
get { return GetValue(UseFloatingWatermarkProperty); }
set { SetValue(UseFloatingWatermarkProperty, value); }
}
public TextWrapping TextWrapping
{
get { return GetValue(TextWrappingProperty); }

2
src/Perspex.Controls/TopLevel.cs

@ -94,7 +94,7 @@ namespace Perspex.Controls
LayoutManager = TryGetService<ILayoutManager>(dependencyResolver);
_renderManager = TryGetService<IRenderManager>(dependencyResolver);
PlatformImpl.SetOwner(this);
PlatformImpl.SetInputRoot(this);
PlatformImpl.Activated = HandleActivated;
PlatformImpl.Deactivated = HandleDeactivated;
PlatformImpl.Closed = HandleClosed;

77
src/Perspex.Controls/Utils/AncestorFinder.cs

@ -0,0 +1,77 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reactive;
using System.Reactive.Disposables;
using System.Reactive.Subjects;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
namespace Perspex.Controls.Utils
{
public static class AncestorFinder
{
class FinderNode : IDisposable
{
private readonly IControl _control;
private readonly TypeInfo _ancestorType;
public IObservable<IControl> Observable => _subject;
private readonly Subject<IControl> _subject = new Subject<IControl>();
private FinderNode _child;
private IDisposable _disposable;
public FinderNode(IControl control, TypeInfo ancestorType)
{
_control = control;
_ancestorType = ancestorType;
}
public void Init()
{
_disposable = _control.GetObservable(Control.ParentProperty).Subscribe(OnValueChanged);
}
private void OnValueChanged(IControl next)
{
if (next == null || _ancestorType.IsAssignableFrom(next.GetType().GetTypeInfo()))
_subject.OnNext(next);
else
{
_child?.Dispose();
_child = new FinderNode(next, _ancestorType);
_child.Observable.Subscribe(OnChildValueChanged);
_child.Init();
}
}
private void OnChildValueChanged(IControl control) => _subject.OnNext(control);
public void Dispose()
{
_disposable.Dispose();
}
}
public static IObservable<IControl> Create(IControl control, Type ancestorType)
{
return new AnonymousObservable<IControl>(observer =>
{
var finder = new FinderNode(control, ancestorType.GetTypeInfo());
var subscription = finder.Observable.Subscribe(observer);
finder.Init();
return Disposable.Create(() =>
{
subscription.Dispose();
finder.Dispose();
});
});
}
}
}

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;
@ -336,6 +337,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")]

7
src/Perspex.SceneGraph/Media/FormattedText.cs

@ -29,9 +29,10 @@ namespace Perspex.Media
TextAlignment textAlignment,
FontWeight fontWeight)
{
//TODO: Find out why it was null in the first place. Demo project - AvalonStudio
//https://github.com/VitalElement/AvalonStudio/commit/787fb9396feb74e6ca6bd4e08436269a349df9c6
text = text ?? "";
Contract.Requires<ArgumentNullException>(text != null);
Contract.Requires<ArgumentNullException>(fontFamilyName != null);
Contract.Requires<ArgumentException>(fontSize > 0);
Text = text;
FontFamilyName = fontFamilyName;
FontSize = fontSize;

53
src/Perspex.Themes.Default/TextBoxStyle.cs

@ -8,6 +8,7 @@ using Perspex.Controls.Primitives;
using Perspex.Controls.Templates;
using Perspex.Media;
using Perspex.Styling;
using System.Reactive.Linq;
namespace Perspex.Themes.Default
{
@ -58,21 +59,49 @@ namespace Perspex.Themes.Default
[~Border.BackgroundProperty] = control[~TemplatedControl.BackgroundProperty],
[~Border.BorderBrushProperty] = control[~TemplatedControl.BorderBrushProperty],
[~Border.BorderThicknessProperty] = control[~TemplatedControl.BorderThicknessProperty],
Child = new ScrollViewer
Child = new StackPanel
{
[~ScrollViewer.CanScrollHorizontallyProperty] = control[~ScrollViewer.CanScrollHorizontallyProperty],
[~ScrollViewer.HorizontalScrollBarVisibilityProperty] = control[~ScrollViewer.HorizontalScrollBarVisibilityProperty],
[~ScrollViewer.VerticalScrollBarVisibilityProperty] = control[~ScrollViewer.VerticalScrollBarVisibilityProperty],
Content = new TextPresenter
Children = new Controls.Controls
{
Name = "textPresenter",
[~TextPresenter.CaretIndexProperty] = control[~TextBox.CaretIndexProperty],
[~TextPresenter.SelectionStartProperty] = control[~TextBox.SelectionStartProperty],
[~TextPresenter.SelectionEndProperty] = control[~TextBox.SelectionEndProperty],
[~TextBlock.TextProperty] = control[~TextBox.TextProperty],
[~TextBlock.TextWrappingProperty] = control[~TextBox.TextWrappingProperty],
new TextBlock
{
Name = "floatingWatermark",
Foreground = SolidColorBrush.Parse("#007ACC"),
FontSize = 10,
[~TextBlock.TextProperty] = control[~TextBox.WatermarkProperty],
[~TextBlock.IsVisibleProperty] = control[~TextBox.TextProperty].Cast<string>().Select(x => (object)(!string.IsNullOrEmpty(x) && control.UseFloatingWatermark))
},
new Panel
{
Children = new Controls.Controls
{
new TextBlock
{
Name = "watermark",
Opacity = 0.5,
[~TextBlock.TextProperty] = control[~TextBox.WatermarkProperty],
[~TextBlock.IsVisibleProperty] = control[~TextBox.TextProperty].Cast<string>().Select(x => (object)string.IsNullOrEmpty(x))
},
new ScrollViewer
{
[~ScrollViewer.CanScrollHorizontallyProperty] = control[~ScrollViewer.CanScrollHorizontallyProperty],
[~ScrollViewer.HorizontalScrollBarVisibilityProperty] = control[~ScrollViewer.HorizontalScrollBarVisibilityProperty],
[~ScrollViewer.VerticalScrollBarVisibilityProperty] = control[~ScrollViewer.VerticalScrollBarVisibilityProperty],
Content = new TextPresenter
{
Name = "textPresenter",
[~TextPresenter.CaretIndexProperty] = control[~TextBox.CaretIndexProperty],
[~TextPresenter.SelectionStartProperty] = control[~TextBox.SelectionStartProperty],
[~TextPresenter.SelectionEndProperty] = control[~TextBox.SelectionEndProperty],
[~TextBlock.TextProperty] = control[~TextBox.TextProperty],
[~TextBlock.TextWrappingProperty] = control[~TextBox.TextWrappingProperty],
}
}
}
}
}
}
},
};
return result;

16
src/Windows/Perspex.Win32/WindowImpl.cs

@ -31,10 +31,12 @@ namespace Perspex.Win32
private IntPtr _hwnd;
private TopLevel _owner;
private IInputRoot _owner;
private bool _trackingMouse;
private bool _isActive;
public WindowImpl()
{
CreateWindow();
@ -163,9 +165,9 @@ namespace Perspex.Win32
return new Point(p.X, p.Y);
}
public void SetOwner(TopLevel owner)
public void SetInputRoot(IInputRoot inputRoot)
{
_owner = owner;
_owner = inputRoot;
}
public void SetTitle(string title)
@ -181,13 +183,13 @@ namespace Perspex.Win32
public virtual IDisposable ShowDialog()
{
var disabled = s_instances.Where(x => x != this && x.IsEnabled).ToList();
TopLevel activated = null;
WindowImpl activated = null;
foreach (var window in disabled)
{
if (window._owner.IsActive)
if (window._isActive)
{
activated = window._owner;
activated = window;
}
window.IsEnabled = false;
@ -253,6 +255,7 @@ namespace Perspex.Win32
{
case UnmanagedMethods.WindowActivate.WA_ACTIVE:
case UnmanagedMethods.WindowActivate.WA_CLICKACTIVE:
_isActive = true;
if (Activated != null)
{
Activated();
@ -261,6 +264,7 @@ namespace Perspex.Win32
break;
case UnmanagedMethods.WindowActivate.WA_INACTIVE:
_isActive = false;
if (Deactivated != null)
{
Deactivated();

2
tests/Perspex.Controls.UnitTests/Perspex.Controls.UnitTests.csproj

@ -112,6 +112,8 @@
<Compile Include="Templates\TemplateExtensionsTests.cs" />
<Compile Include="TestTemplatedControl.cs" />
<Compile Include="TestRoot.cs" />
<Compile Include="Utils\AncestorFinderTests.cs" />
<Compile Include="Utils\HotKeyManagerTests.cs" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\Perspex.Animation\Perspex.Animation.csproj">

40
tests/Perspex.Controls.UnitTests/Utils/AncestorFinderTests.cs

@ -0,0 +1,40 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Perspex.Controls.Utils;
using Xunit;
namespace Perspex.Controls.UnitTests.Utils
{
public class AncestorFinderTests
{
[Fact]
public void SanityCheck()
{
var child = new Control();
var parent = new Decorator();
var grandParent = new Border();
var grandParent2 = new Border();
IVisual currentParent = null;
var subscription = AncestorFinder.Create(child, typeof (Border)).Subscribe(s => currentParent = s);
Assert.Null(currentParent);
parent.Child = child;
Assert.Null(currentParent);
grandParent.Child = parent;
Assert.Equal(grandParent, currentParent);
grandParent.Child = null;
grandParent2.Child = parent;
Assert.Equal(grandParent2, currentParent);
subscription.Dispose();
parent.Child = null;
Assert.Equal(grandParent2, currentParent);
}
}
}

72
tests/Perspex.Controls.UnitTests/Utils/HotKeyManagerTests.cs

@ -0,0 +1,72 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Moq;
using Perspex.Controls.Presenters;
using Perspex.Controls.Templates;
using Perspex.Input;
using Perspex.Platform;
using Perspex.Styling;
using Xunit;
namespace Perspex.Controls.UnitTests.Utils
{
public class HotKeyManagerTests
{
[Fact]
public void HotKeyManager_Should_Register_And_Unregister_Key_Binding()
{
using (PerspexLocator.EnterScope())
{
var windowImpl = new Mock<IWindowImpl>();
var styler = new Mock<Styler>();
PerspexLocator.CurrentMutable
.Bind<IWindowImpl>().ToConstant(windowImpl.Object)
.Bind<IStyler>().ToConstant(styler.Object);
var gesture1 = new KeyGesture {Key = Key.A, Modifiers = InputModifiers.Control};
var gesture2 = new KeyGesture {Key = Key.B, Modifiers = InputModifiers.Control};
var tl = new Window();
var button = new Button();
tl.Content = button;
tl.Template = CreateWindowTemplate();
tl.ApplyTemplate();
HotKeyManager.SetHotKey(button, gesture1);
Assert.Equal(gesture1, tl.KeyBindings[0].Gesture);
HotKeyManager.SetHotKey(button, gesture2);
Assert.Equal(gesture2, tl.KeyBindings[0].Gesture);
button.SetValue(Control.ParentProperty, null);
Assert.Empty(tl.KeyBindings);
button.SetValue(Control.ParentProperty, tl);
Assert.Equal(gesture2, tl.KeyBindings[0].Gesture);
HotKeyManager.SetHotKey(button, null);
Assert.Empty(tl.KeyBindings);
}
}
private ControlTemplate CreateWindowTemplate()
{
return new ControlTemplate<Window>(parent =>
{
return new ContentPresenter
{
Name = "contentPresenter",
[~ContentPresenter.ContentProperty] = parent[~ContentControl.ContentProperty],
};
});
}
}
}

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