From 9dd936cff737e3f8cbb2b7a69418effd63fc5478 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Wed, 23 Sep 2015 18:29:35 +0200 Subject: [PATCH 01/53] Trying to make Setter.Value depend on Property. But something is breaking in OmniXaml. --- .../Context/PerspexWiringContext.cs | 26 ++++++++++++------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/src/Markup/Perspex.Markup.Xaml/Context/PerspexWiringContext.cs b/src/Markup/Perspex.Markup.Xaml/Context/PerspexWiringContext.cs index b75b07b289..cee6e3689d 100644 --- a/src/Markup/Perspex.Markup.Xaml/Context/PerspexWiringContext.cs +++ b/src/Markup/Perspex.Markup.Xaml/Context/PerspexWiringContext.cs @@ -21,23 +21,33 @@ using Perspex.Metadata; using Perspex.Platform; using Perspex.Styling; using Splat; +using System; 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().WithMemberDependency(setter => setter.Value, setter => setter.Property)); + + return new TypeContext(typeRepository, xamlNamespaceRegistry, typeFactory); } private static XamlNamespaceRegistry CreateXamlNamespaceRegistry() @@ -113,9 +123,5 @@ namespace Perspex.Markup.Xaml.Context return contentPropertyProvider; } - - public ITypeContext TypeContext => _context.TypeContext; - - public ITypeFeatureProvider FeatureProvider => _context.FeatureProvider; } } \ No newline at end of file From b3af361686ae59879659f7fa3ad3192c8a633614 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Thu, 24 Sep 2015 00:06:04 +0200 Subject: [PATCH 02/53] Still trying to get property dependencies working. SelectedIndex isn't being set. --- samples/XamlTestApplication/Views/MainWindow.paml | 15 ++++++++++----- .../Context/PerspexWiringContext.cs | 7 ++++++- src/Markup/Perspex.Markup.Xaml/OmniXAML | 2 +- src/Perspex.Controls/ItemsControl.cs | 2 ++ .../Primitives/SelectingItemsControl.cs | 2 ++ 5 files changed, 21 insertions(+), 7 deletions(-) diff --git a/samples/XamlTestApplication/Views/MainWindow.paml b/samples/XamlTestApplication/Views/MainWindow.paml index 2c4525eb13..a85c949f69 100644 --- a/samples/XamlTestApplication/Views/MainWindow.paml +++ b/samples/XamlTestApplication/Views/MainWindow.paml @@ -2,8 +2,13 @@ xmlns="https://github.com/perspex" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="Perspex Test Application" Height="350" Width="525" SizeToContent="WidthAndHeight" > - - + + + Hello + World + + + \ No newline at end of file diff --git a/src/Markup/Perspex.Markup.Xaml/Context/PerspexWiringContext.cs b/src/Markup/Perspex.Markup.Xaml/Context/PerspexWiringContext.cs index cee6e3689d..fb8c6e5836 100644 --- a/src/Markup/Perspex.Markup.Xaml/Context/PerspexWiringContext.cs +++ b/src/Markup/Perspex.Markup.Xaml/Context/PerspexWiringContext.cs @@ -22,6 +22,7 @@ using Perspex.Platform; using Perspex.Styling; using Splat; using System; +using Perspex.Controls.Primitives; namespace Perspex.Markup.Xaml.Context { @@ -45,7 +46,11 @@ namespace Perspex.Markup.Xaml.Context var perspexPropertyBinder = new PerspexPropertyBinder(featureProvider.ConverterProvider); var typeRepository = new PerspexTypeRepository(xamlNamespaceRegistry, typeFactory, featureProvider, perspexPropertyBinder); - typeRepository.RegisterMetadata(new Metadata().WithMemberDependency(setter => setter.Value, setter => setter.Property)); + typeRepository.RegisterMetadata(new Metadata().WithMemberDependency(x => x.Value, x => x.Property)); + typeRepository.RegisterMetadata( + new Metadata() + .WithMemberDependency(x => x.SelectedIndex, x => x.Items) + .WithMemberDependency(x => x.SelectedItem, x => x.Items)); return new TypeContext(typeRepository, xamlNamespaceRegistry, typeFactory); } diff --git a/src/Markup/Perspex.Markup.Xaml/OmniXAML b/src/Markup/Perspex.Markup.Xaml/OmniXAML index 7b13ebcde0..1461be589a 160000 --- a/src/Markup/Perspex.Markup.Xaml/OmniXAML +++ b/src/Markup/Perspex.Markup.Xaml/OmniXAML @@ -1 +1 @@ -Subproject commit 7b13ebcde030df9f8d538917e57b3a34ea9edf32 +Subproject commit 1461be589a5d39d378fd177648c61a780271c742 diff --git a/src/Perspex.Controls/ItemsControl.cs b/src/Perspex.Controls/ItemsControl.cs index 83f86eba78..cdbf883d95 100644 --- a/src/Perspex.Controls/ItemsControl.cs +++ b/src/Perspex.Controls/ItemsControl.cs @@ -140,6 +140,8 @@ namespace Perspex.Controls /// The event args. protected virtual void ItemsChanged(PerspexPropertyChangedEventArgs e) { + System.Diagnostics.Debug.WriteLine($"{this.GetType().Name} set items"); + var incc = e.OldValue as INotifyCollectionChanged; if (incc != null) diff --git a/src/Perspex.Controls/Primitives/SelectingItemsControl.cs b/src/Perspex.Controls/Primitives/SelectingItemsControl.cs index 371a3a96a9..811cc7bb3e 100644 --- a/src/Perspex.Controls/Primitives/SelectingItemsControl.cs +++ b/src/Perspex.Controls/Primitives/SelectingItemsControl.cs @@ -299,6 +299,8 @@ namespace Perspex.Controls.Primitives /// The event args. private void SelectedIndexChanged(PerspexPropertyChangedEventArgs e) { + System.Diagnostics.Debug.WriteLine($"{this.GetType().Name} set selected index"); + var index = (int)e.OldValue; if (index != -1) From fc76076c8a0518c34d0f5c19e619d05330b7db84 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Fri, 25 Sep 2015 20:37:14 +0100 Subject: [PATCH 03/53] Added implementation of Watermark for Textbox. --- samples/TestApplication/Program.cs | 6 ++-- src/Perspex.Controls/TextBox.cs | 9 +++++ src/Perspex.Themes.Default/TextBoxStyle.cs | 38 +++++++++++++++------- 3 files changed, 40 insertions(+), 13 deletions(-) diff --git a/samples/TestApplication/Program.cs b/samples/TestApplication/Program.cs index bfbb8a17ce..33db018363 100644 --- a/samples/TestApplication/Program.cs +++ b/samples/TestApplication/Program.cs @@ -340,8 +340,10 @@ 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 { 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), diff --git a/src/Perspex.Controls/TextBox.cs b/src/Perspex.Controls/TextBox.cs index ad48cc49af..a9c0f4d5f2 100644 --- a/src/Perspex.Controls/TextBox.cs +++ b/src/Perspex.Controls/TextBox.cs @@ -39,6 +39,9 @@ namespace Perspex.Controls public static readonly PerspexProperty TextWrappingProperty = TextBlock.TextWrappingProperty.AddOwner(); + public static readonly PerspexProperty WatermarkProperty = + PerspexProperty.Register("Watermark"); + private TextPresenter _presenter; static TextBox() @@ -101,6 +104,12 @@ namespace Perspex.Controls set { SetValue(TextProperty, value); } } + public string Watermark + { + get { return GetValue(WatermarkProperty); } + set { SetValue(WatermarkProperty, value); } + } + public TextWrapping TextWrapping { get { return GetValue(TextWrappingProperty); } diff --git a/src/Perspex.Themes.Default/TextBoxStyle.cs b/src/Perspex.Themes.Default/TextBoxStyle.cs index 3b3bfca452..0df99a14ea 100644 --- a/src/Perspex.Themes.Default/TextBoxStyle.cs +++ b/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,19 +59,34 @@ namespace Perspex.Themes.Default [~Border.BackgroundProperty] = control[~TemplatedControl.BackgroundProperty], [~Border.BorderBrushProperty] = control[~TemplatedControl.BorderBrushProperty], [~Border.BorderThicknessProperty] = control[~TemplatedControl.BorderThicknessProperty], - Child = new ScrollViewer + + Child = new Panel { - [~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="waterMark", + Opacity=0.5, + [~TextBlock.TextProperty] = control[~TextBox.WatermarkProperty], + [~TextBlock.IsVisibleProperty] = control[~TextBox.TextProperty].Cast().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], + } + } } } }; From 7964321dd1bee273e812cd9eb9a3e2d3939a46c6 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Fri, 25 Sep 2015 21:31:08 +0100 Subject: [PATCH 04/53] Implemented UseFloatingWaterMark property. --- samples/TestApplication/Program.cs | 2 + src/Perspex.Controls/TextBox.cs | 9 ++++ src/Perspex.Themes.Default/TextBoxStyle.cs | 49 ++++++++++++++-------- 3 files changed, 42 insertions(+), 18 deletions(-) diff --git a/samples/TestApplication/Program.cs b/samples/TestApplication/Program.cs index 33db018363..023235ea71 100644 --- a/samples/TestApplication/Program.cs +++ b/samples/TestApplication/Program.cs @@ -343,6 +343,8 @@ namespace TestApplication 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 { Width = 200, Text="Lorem ipsum", ClearTextButton=true, Watermark="Watermark"}, 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 { diff --git a/src/Perspex.Controls/TextBox.cs b/src/Perspex.Controls/TextBox.cs index a9c0f4d5f2..2d00e50814 100644 --- a/src/Perspex.Controls/TextBox.cs +++ b/src/Perspex.Controls/TextBox.cs @@ -42,6 +42,9 @@ namespace Perspex.Controls public static readonly PerspexProperty WatermarkProperty = PerspexProperty.Register("Watermark"); + public static readonly PerspexProperty UseFloatingWatermarkProperty = + PerspexProperty.Register("UseFloatingWatermark"); + private TextPresenter _presenter; static TextBox() @@ -110,6 +113,12 @@ namespace Perspex.Controls set { SetValue(WatermarkProperty, value); } } + public bool UseFloatingWatermark + { + get { return GetValue(UseFloatingWatermarkProperty); } + set { SetValue(UseFloatingWatermarkProperty, value); } + } + public TextWrapping TextWrapping { get { return GetValue(TextWrappingProperty); } diff --git a/src/Perspex.Themes.Default/TextBoxStyle.cs b/src/Perspex.Themes.Default/TextBoxStyle.cs index 0df99a14ea..7f72c68c3f 100644 --- a/src/Perspex.Themes.Default/TextBoxStyle.cs +++ b/src/Perspex.Themes.Default/TextBoxStyle.cs @@ -60,35 +60,48 @@ namespace Perspex.Themes.Default [~Border.BorderBrushProperty] = control[~TemplatedControl.BorderBrushProperty], [~Border.BorderThicknessProperty] = control[~TemplatedControl.BorderThicknessProperty], - Child = new Panel + Child = new StackPanel { Children = new Controls.Controls { new TextBlock { - Name="waterMark", - Opacity=0.5, + Name = "floatingWatermark", + Foreground = SolidColorBrush.Parse("#007ACC"), + FontSize = 10, [~TextBlock.TextProperty] = control[~TextBox.WatermarkProperty], - [~TextBlock.IsVisibleProperty] = control[~TextBox.TextProperty].Cast().Select(x => (object)string.IsNullOrEmpty(x)) + [~TextBlock.IsVisibleProperty] = control[~TextBox.TextProperty].Cast().Select(x => (object)(!string.IsNullOrEmpty(x) && control.UseFloatingWatermark)) }, - - new ScrollViewer + new Panel { - [~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 = "watermark", + Opacity = 0.5, + [~TextBlock.TextProperty] = control[~TextBox.WatermarkProperty], + [~TextBlock.IsVisibleProperty] = control[~TextBox.TextProperty].Cast().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; From 362f7e78c0bd79fe695288e6865947772f4df98a Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Fri, 25 Sep 2015 21:32:21 +0100 Subject: [PATCH 05/53] implemented UseFloatingWatermark --- samples/TestApplication/Program.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/samples/TestApplication/Program.cs b/samples/TestApplication/Program.cs index 023235ea71..3056ef14f6 100644 --- a/samples/TestApplication/Program.cs +++ b/samples/TestApplication/Program.cs @@ -344,7 +344,6 @@ namespace TestApplication 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 { Width = 200, Text="Lorem ipsum", ClearTextButton=true, Watermark="Watermark"}, 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 { From d98247e352feef3203069940d6814838a805f333 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Sun, 27 Sep 2015 13:17:24 +0200 Subject: [PATCH 06/53] Updated OmniXAML --- src/Markup/Perspex.Markup.Xaml/OmniXAML | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Markup/Perspex.Markup.Xaml/OmniXAML b/src/Markup/Perspex.Markup.Xaml/OmniXAML index 1461be589a..1aa1e5080b 160000 --- a/src/Markup/Perspex.Markup.Xaml/OmniXAML +++ b/src/Markup/Perspex.Markup.Xaml/OmniXAML @@ -1 +1 @@ -Subproject commit 1461be589a5d39d378fd177648c61a780271c742 +Subproject commit 1aa1e5080b04ccf40baef4420ee29ffef9303811 From 5a40e57f2bc3e4721a3484046166d5761e9befc3 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Sun, 27 Sep 2015 13:17:42 +0200 Subject: [PATCH 07/53] Added extra TabControl test. --- tests/Perspex.Controls.UnitTests/TabControlTests.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/tests/Perspex.Controls.UnitTests/TabControlTests.cs b/tests/Perspex.Controls.UnitTests/TabControlTests.cs index 57ef725e04..927db0a76e 100644 --- a/tests/Perspex.Controls.UnitTests/TabControlTests.cs +++ b/tests/Perspex.Controls.UnitTests/TabControlTests.cs @@ -139,6 +139,7 @@ namespace Perspex.Controls.UnitTests new Item("Bar"), new TextBlock { Text = "Baz" }, new TabItem { Content = "Qux" }, + new TabItem { Content = new TextBlock { Text = "Bob" } } }; var target = new TabControl @@ -168,6 +169,10 @@ namespace Perspex.Controls.UnitTests target.SelectedIndex = 3; dataContext = ((TextBlock)target.GetLogicalChildren().Single()).DataContext; Assert.Equal("Qux", dataContext); + + target.SelectedIndex = 4; + dataContext = ((TextBlock)target.GetLogicalChildren().Single()).DataContext; + Assert.Equal("Base", dataContext); } private Control CreateTabControlTemplate(TabControl parent) From 57fbf27c9e44722c03809a5a43eab3e0817935ea Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Sun, 27 Sep 2015 15:13:11 +0200 Subject: [PATCH 08/53] Documentation. --- src/Perspex.Application/Application.cs | 3 ++ .../Metadata/XmlnsDefinitionAttribute.cs | 30 ++++++++++++++----- 2 files changed, 25 insertions(+), 8 deletions(-) diff --git a/src/Perspex.Application/Application.cs b/src/Perspex.Application/Application.cs index ac93f82fec..f0a3c1a8ee 100644 --- a/src/Perspex.Application/Application.cs +++ b/src/Perspex.Application/Application.cs @@ -119,6 +119,9 @@ namespace Perspex private set; } + /// + /// Gets the application clipboard. + /// public IClipboard Clipboard => _clipboard.Value; /// diff --git a/src/Perspex.Base/Metadata/XmlnsDefinitionAttribute.cs b/src/Perspex.Base/Metadata/XmlnsDefinitionAttribute.cs index 63371bf80f..8e9fddc50b 100644 --- a/src/Perspex.Base/Metadata/XmlnsDefinitionAttribute.cs +++ b/src/Perspex.Base/Metadata/XmlnsDefinitionAttribute.cs @@ -1,21 +1,35 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; +// Copyright (c) The Perspex Project. All rights reserved. +// Licensed under the MIT license. See licence.md file in the project root for full license information. + +using System; namespace Perspex.Metadata { + /// + /// Maps an XML namespace to a CLR namespace for use in XAML. + /// [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)] public class XmlnsDefinitionAttribute : Attribute { - public string XmlNamespace { get; set; } - public string ClrNamespace { get; set; } - + /// + /// Initializes a new instance of the class. + /// + /// The URL of the XML namespace. + /// The CLR namespace. public XmlnsDefinitionAttribute(string xmlNamespace, string clrNamespace) { XmlNamespace = xmlNamespace; ClrNamespace = clrNamespace; } + + /// + /// Gets or sets the URL of the XML namespace. + /// + public string XmlNamespace { get; set; } + + /// + /// Gets or sets the CLR namespace. + /// + public string ClrNamespace { get; set; } } } From 1b4c7787d1715736b8ecfb4accbd97c70bc9e780 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Sun, 27 Sep 2015 16:58:15 +0200 Subject: [PATCH 09/53] Enable DevTools on TestApplication. --- samples/TestApplication/Program.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/samples/TestApplication/Program.cs b/samples/TestApplication/Program.cs index bfbb8a17ce..a67f6881da 100644 --- a/samples/TestApplication/Program.cs +++ b/samples/TestApplication/Program.cs @@ -138,6 +138,7 @@ namespace TestApplication container.Classes.Add(":container"); + DevTools.Attach(window); window.Show(); Application.Current.Run(window); } From 5acef971f0f11677997940d5e45645aa6b10e2f1 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Sun, 27 Sep 2015 20:50:15 +0300 Subject: [PATCH 10/53] Added KeyGesture and KeyBindings --- .../Context/PerspexWiringContext.cs | 2 + .../Converters/KeyGestureConverter.cs | 30 +++++ .../Perspex.Markup.Xaml.csproj | 1 + src/Perspex.Input/IInputElement.cs | 4 + src/Perspex.Input/InputElement.cs | 3 + src/Perspex.Input/KeyBinding.cs | 40 ++++++ src/Perspex.Input/KeyGesture.cs | 116 ++++++++++++++++++ src/Perspex.Input/KeyboardDevice.cs | 14 +++ src/Perspex.Input/Perspex.Input.csproj | 2 + src/Perspex.Input/Properties/AssemblyInfo.cs | 2 + .../KeyGestureParseTests.cs | 28 +++++ .../Perspex.Input.UnitTests.csproj | 1 + 12 files changed, 243 insertions(+) create mode 100644 src/Markup/Perspex.Markup.Xaml/Converters/KeyGestureConverter.cs create mode 100644 src/Perspex.Input/KeyBinding.cs create mode 100644 src/Perspex.Input/KeyGesture.cs create mode 100644 tests/Perspex.Input.UnitTests/KeyGestureParseTests.cs diff --git a/src/Markup/Perspex.Markup.Xaml/Context/PerspexWiringContext.cs b/src/Markup/Perspex.Markup.Xaml/Context/PerspexWiringContext.cs index b2ab8c06ae..e8808ff285 100644 --- a/src/Markup/Perspex.Markup.Xaml/Context/PerspexWiringContext.cs +++ b/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); diff --git a/src/Markup/Perspex.Markup.Xaml/Converters/KeyGestureConverter.cs b/src/Markup/Perspex.Markup.Xaml/Converters/KeyGestureConverter.cs new file mode 100644 index 0000000000..ebf1159d76 --- /dev/null +++ b/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(); + } + } +} diff --git a/src/Markup/Perspex.Markup.Xaml/Perspex.Markup.Xaml.csproj b/src/Markup/Perspex.Markup.Xaml/Perspex.Markup.Xaml.csproj index 2bce8946ac..3ed9a50f31 100644 --- a/src/Markup/Perspex.Markup.Xaml/Perspex.Markup.Xaml.csproj +++ b/src/Markup/Perspex.Markup.Xaml/Perspex.Markup.Xaml.csproj @@ -39,6 +39,7 @@ Properties\SharedAssemblyInfo.cs + diff --git a/src/Perspex.Input/IInputElement.cs b/src/Perspex.Input/IInputElement.cs index fa75629f64..d6e5378f02 100644 --- a/src/Perspex.Input/IInputElement.cs +++ b/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 /// The position, in control coordinates. /// The at the specified position. IInputElement InputHitTest(Point p); + + + List KeyBindings { get; } } } diff --git a/src/Perspex.Input/InputElement.cs b/src/Perspex.Input/InputElement.cs index 2075bc09f2..1cba2af3c6 100644 --- a/src/Perspex.Input/InputElement.cs +++ b/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 KeyBindings { get; } = new List(); + /// /// Returns the input element that can be found within the current control at the specified /// position. diff --git a/src/Perspex.Input/KeyBinding.cs b/src/Perspex.Input/KeyBinding.cs new file mode 100644 index 0000000000..48b0eb3712 --- /dev/null +++ b/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 CommandProperty = + PerspexProperty.Register("Command"); + + public ICommand Command + { + get { return GetValue(CommandProperty); } + set { SetValue(CommandProperty, value); } + } + + public static PerspexProperty GestureProperty = + PerspexProperty.Register("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); + } + } + } +} diff --git a/src/Perspex.Input/KeyGesture.cs b/src/Perspex.Input/KeyGesture.cs new file mode 100644 index 0000000000..e298d63bcf --- /dev/null +++ b/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 + { + 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 KeySynonims = new Dictionary + { + {"+", 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(); + + 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(); + foreach (var flag in Enum.GetValues(typeof (InputModifiers)).Cast()) + { + 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; + } +} diff --git a/src/Perspex.Input/KeyboardDevice.cs b/src/Perspex.Input/KeyboardDevice.cs index 390e925ce5..8c2154eb8c 100644 --- a/src/Perspex.Input/KeyboardDevice.cs +++ b/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; } diff --git a/src/Perspex.Input/Perspex.Input.csproj b/src/Perspex.Input/Perspex.Input.csproj index 2bcf6f1e24..c853ea9bb1 100644 --- a/src/Perspex.Input/Perspex.Input.csproj +++ b/src/Perspex.Input/Perspex.Input.csproj @@ -66,6 +66,8 @@ + + diff --git a/src/Perspex.Input/Properties/AssemblyInfo.cs b/src/Perspex.Input/Properties/AssemblyInfo.cs index 04e306ea17..d83862fb4a 100644 --- a/src/Perspex.Input/Properties/AssemblyInfo.cs +++ b/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")] diff --git a/tests/Perspex.Input.UnitTests/KeyGestureParseTests.cs b/tests/Perspex.Input.UnitTests/KeyGestureParseTests.cs new file mode 100644 index 0000000000..0931c6f81b --- /dev/null +++ b/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 SampleData = new Dictionary + { + {"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)); + } + } +} diff --git a/tests/Perspex.Input.UnitTests/Perspex.Input.UnitTests.csproj b/tests/Perspex.Input.UnitTests/Perspex.Input.UnitTests.csproj index e63ccac17e..1aff689782 100644 --- a/tests/Perspex.Input.UnitTests/Perspex.Input.UnitTests.csproj +++ b/tests/Perspex.Input.UnitTests/Perspex.Input.UnitTests.csproj @@ -57,6 +57,7 @@ + From 8cbd9cb2d5945dfea3d92dbb94e5f60935a497e8 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Sun, 27 Sep 2015 21:47:35 +0300 Subject: [PATCH 11/53] Added XmlnsDefinition for Perspex.Controls.Shapes --- src/Perspex.Controls/Properties/AssemblyInfo.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Perspex.Controls/Properties/AssemblyInfo.cs b/src/Perspex.Controls/Properties/AssemblyInfo.cs index 4cda6c4aa5..bf1ddab5d3 100644 --- a/src/Perspex.Controls/Properties/AssemblyInfo.cs +++ b/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")] \ No newline at end of file +[assembly: XmlnsDefinition("https://github.com/perspex", "Perspex.Controls.Primitives")] +[assembly: XmlnsDefinition("https://github.com/perspex", "Perspex.Controls.Shapes")] \ No newline at end of file From 1d5b5858951d4649d54691cf21577d8bf25d6f27 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Mon, 28 Sep 2015 11:59:29 +0300 Subject: [PATCH 12/53] Implemented HotKey attached property. It doesn't work, however, because of broken ContentControl (doesn't sets child's parent for some reason) --- src/Perspex.Controls/HotkeyManager.cs | 117 ++++++++++++++++++ src/Perspex.Controls/Perspex.Controls.csproj | 2 + src/Perspex.Controls/Utils/AncestorFinder.cs | 77 ++++++++++++ src/Perspex.Input/HotkeyManager.cs | 45 +++++++ src/Perspex.Input/Perspex.Input.csproj | 1 + .../Perspex.Controls.UnitTests.csproj | 2 + .../Utils/AncestorFinderTests.cs | 40 ++++++ .../Utils/HotKeyManagerTests.cs | 56 +++++++++ 8 files changed, 340 insertions(+) create mode 100644 src/Perspex.Controls/HotkeyManager.cs create mode 100644 src/Perspex.Controls/Utils/AncestorFinder.cs create mode 100644 src/Perspex.Input/HotkeyManager.cs create mode 100644 tests/Perspex.Controls.UnitTests/Utils/AncestorFinderTests.cs create mode 100644 tests/Perspex.Controls.UnitTests/Utils/HotKeyManagerTests.cs diff --git a/src/Perspex.Controls/HotkeyManager.cs b/src/Perspex.Controls/HotkeyManager.cs new file mode 100644 index 0000000000..6b7fb117c1 --- /dev/null +++ b/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 HotKeyProperty + = PerspexProperty.RegisterAttached("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); + } +} diff --git a/src/Perspex.Controls/Perspex.Controls.csproj b/src/Perspex.Controls/Perspex.Controls.csproj index 6bcd55b76c..12a8f6f7e6 100644 --- a/src/Perspex.Controls/Perspex.Controls.csproj +++ b/src/Perspex.Controls/Perspex.Controls.csproj @@ -42,6 +42,7 @@ Properties\SharedAssemblyInfo.cs + @@ -132,6 +133,7 @@ + diff --git a/src/Perspex.Controls/Utils/AncestorFinder.cs b/src/Perspex.Controls/Utils/AncestorFinder.cs new file mode 100644 index 0000000000..59c418ee94 --- /dev/null +++ b/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 Observable => _subject; + private readonly Subject _subject = new Subject(); + + 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 Create(IControl control, Type ancestorType) + { + return new AnonymousObservable(observer => + { + var finder = new FinderNode(control, ancestorType.GetTypeInfo()); + var subscription = finder.Observable.Subscribe(observer); + finder.Init(); + + return Disposable.Create(() => + { + subscription.Dispose(); + finder.Dispose(); + }); + }); + + + } + } +} diff --git a/src/Perspex.Input/HotkeyManager.cs b/src/Perspex.Input/HotkeyManager.cs new file mode 100644 index 0000000000..c79b7e280f --- /dev/null +++ b/src/Perspex.Input/HotkeyManager.cs @@ -0,0 +1,45 @@ +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 HotkeyManager + { + public static PerspexProperty HotKey + = PerspexProperty.RegisterAttached("HotKey", typeof (HotkeyManager)); + + class HotkeyCommandWrapper : ICommand + { + public Visual Visual; + public PerspexProperty Property; + + ICommand GetCommand() => (ICommand) Visual.GetValue(Property); + + 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; + } + + + static HotkeyManager() + { + HotKey.Changed.Subscribe(args => + { + if (args.OldValue != null) + { + + } + + + }); + } + + } +} diff --git a/src/Perspex.Input/Perspex.Input.csproj b/src/Perspex.Input/Perspex.Input.csproj index c853ea9bb1..b3727b3d85 100644 --- a/src/Perspex.Input/Perspex.Input.csproj +++ b/src/Perspex.Input/Perspex.Input.csproj @@ -66,6 +66,7 @@ + diff --git a/tests/Perspex.Controls.UnitTests/Perspex.Controls.UnitTests.csproj b/tests/Perspex.Controls.UnitTests/Perspex.Controls.UnitTests.csproj index ac46237a33..a41cec398c 100644 --- a/tests/Perspex.Controls.UnitTests/Perspex.Controls.UnitTests.csproj +++ b/tests/Perspex.Controls.UnitTests/Perspex.Controls.UnitTests.csproj @@ -112,6 +112,8 @@ + + diff --git a/tests/Perspex.Controls.UnitTests/Utils/AncestorFinderTests.cs b/tests/Perspex.Controls.UnitTests/Utils/AncestorFinderTests.cs new file mode 100644 index 0000000000..d44d42edb4 --- /dev/null +++ b/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); + } + + + } +} diff --git a/tests/Perspex.Controls.UnitTests/Utils/HotKeyManagerTests.cs b/tests/Perspex.Controls.UnitTests/Utils/HotKeyManagerTests.cs new file mode 100644 index 0000000000..9b6bdd6434 --- /dev/null +++ b/tests/Perspex.Controls.UnitTests/Utils/HotKeyManagerTests.cs @@ -0,0 +1,56 @@ +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(); + PerspexLocator.CurrentMutable + .Bind().ToConstant(windowImpl.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(); + HotKeyManager.SetHotKey(button, gesture1); + + //ContentPresenter's parent management is broken for now, so I'm setting parent property directly + button.SetValue(Control.ParentProperty, tl); + 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); + + } + } + } +} From 23896f772db5668c3011d64f9ecab7f0029dab68 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Mon, 28 Sep 2015 12:44:50 +0300 Subject: [PATCH 13/53] HotKey property landed to button and menuitem --- src/Perspex.Controls/Button.cs | 12 +++++++ src/Perspex.Controls/MenuItem.cs | 13 ++++++++ src/Perspex.Input/HotkeyManager.cs | 45 -------------------------- src/Perspex.Input/Perspex.Input.csproj | 1 - 4 files changed, 25 insertions(+), 46 deletions(-) delete mode 100644 src/Perspex.Input/HotkeyManager.cs diff --git a/src/Perspex.Controls/Button.cs b/src/Perspex.Controls/Button.cs index 50a4871837..41736206c9 100644 --- a/src/Perspex.Controls/Button.cs +++ b/src/Perspex.Controls/Button.cs @@ -44,6 +44,9 @@ namespace Perspex.Controls public static readonly PerspexProperty CommandProperty = PerspexProperty.Register(nameof(Command)); + public static readonly PerspexProperty HotKeyProperty = + HotKeyManager.HotKeyProperty.AddOwner