From 6fa126f82770ac03b7d30ee20fcfb1da695a7ef2 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Thu, 16 Apr 2015 11:07:04 +0200 Subject: [PATCH] Show keyboard focus adorner. --- Perspex.Base/Collections/PerspexList.cs | 5 +++ Perspex.Controls/Control.cs | 39 +++++++++++++++++++++ Perspex.Controls/Primitives/AdornerLayer.cs | 2 +- Perspex.Controls/TextBox.cs | 2 +- Perspex.Diagnostics/DevTools.cs | 4 --- Perspex.Input/FocusManager.cs | 8 +++-- Perspex.Input/GotFocusEventArgs.cs | 19 ++++++++++ Perspex.Input/IFocusManager.cs | 9 ++++- Perspex.Input/IInputElement.cs | 2 ++ Perspex.Input/InputElement.cs | 18 +++++++--- Perspex.Input/KeyboardNavigation.cs | 4 +-- Perspex.Input/Perspex.Input.csproj | 1 + 12 files changed, 98 insertions(+), 15 deletions(-) create mode 100644 Perspex.Input/GotFocusEventArgs.cs diff --git a/Perspex.Base/Collections/PerspexList.cs b/Perspex.Base/Collections/PerspexList.cs index 48e34e5ffc..aff10b13e6 100644 --- a/Perspex.Base/Collections/PerspexList.cs +++ b/Perspex.Base/Collections/PerspexList.cs @@ -37,6 +37,11 @@ namespace Perspex.Collections this.inner = new List(items); } + public PerspexList(params T[] items) + { + this.inner = new List(items); + } + public event NotifyCollectionChangedEventHandler CollectionChanged; public event PropertyChangedEventHandler PropertyChanged; diff --git a/Perspex.Controls/Control.cs b/Perspex.Controls/Control.cs index 8d4714b2f3..dbc0762d3d 100644 --- a/Perspex.Controls/Control.cs +++ b/Perspex.Controls/Control.cs @@ -11,6 +11,8 @@ namespace Perspex.Controls using System.Linq; using System.Reactive.Linq; using Perspex.Collections; + using Perspex.Controls.Primitives; + using Perspex.Controls.Shapes; using Perspex.Input; using Perspex.Interactivity; using Perspex.Media; @@ -41,6 +43,8 @@ namespace Perspex.Controls private DataTemplates dataTemplates; + private Rectangle focusAdorner; + private string id; private Styles styles; @@ -185,6 +189,41 @@ namespace Perspex.Controls this.RaiseEvent(ev); } + protected override void OnGotFocus(GotFocusEventArgs e) + { + base.OnGotFocus(e); + + if (this.IsFocused && e.KeyboardNavigated) + { + var adornerLayer = AdornerLayer.GetAdornerLayer(this); + + if (adornerLayer != null) + { + this.focusAdorner = new Rectangle + { + Stroke = Brushes.Black, + StrokeThickness = 1, + StrokeDashArray = new PerspexList(1, 2), + Margin = new Thickness(3), + }; + AdornerLayer.SetAdornedElement(this.focusAdorner, this); + adornerLayer.Children.Add(this.focusAdorner); + } + } + } + + protected override void OnLostFocus(RoutedEventArgs e) + { + base.OnLostFocus(e); + + if (this.focusAdorner != null) + { + var adornerLayer = AdornerLayer.GetAdornerLayer(this); + adornerLayer.Children.Remove(this.focusAdorner); + this.focusAdorner = null; + } + } + protected static void PseudoClass(PerspexProperty property, string className) { PseudoClass(property, x => x, className); diff --git a/Perspex.Controls/Primitives/AdornerLayer.cs b/Perspex.Controls/Primitives/AdornerLayer.cs index 02be0fd492..e2c9291bbd 100644 --- a/Perspex.Controls/Primitives/AdornerLayer.cs +++ b/Perspex.Controls/Primitives/AdornerLayer.cs @@ -51,7 +51,7 @@ namespace Perspex.Controls.Primitives { var transform = adorned.TransformToVisual(parent); var position = new Point(0, 0) * transform; - child.Arrange(new Rect(position, child.DesiredSize.Value)); + child.Arrange(new Rect(position, adorned.Bounds.Size)); } else { diff --git a/Perspex.Controls/TextBox.cs b/Perspex.Controls/TextBox.cs index 25b50879b6..340e7a96c5 100644 --- a/Perspex.Controls/TextBox.cs +++ b/Perspex.Controls/TextBox.cs @@ -114,7 +114,7 @@ namespace Perspex.Controls this.presenter = this.GetTemplateChild("textPresenter"); } - protected override void OnGotFocus(RoutedEventArgs e) + protected override void OnGotFocus(GotFocusEventArgs e) { base.OnGotFocus(e); this.presenter.ShowCaret(); diff --git a/Perspex.Diagnostics/DevTools.cs b/Perspex.Diagnostics/DevTools.cs index b99b11f2b8..f1cba06010 100644 --- a/Perspex.Diagnostics/DevTools.cs +++ b/Perspex.Diagnostics/DevTools.cs @@ -216,13 +216,9 @@ namespace Perspex.Diagnostics if (layer != null) { - var size = node.Control.GetObservable(Visual.BoundsProperty).Select(x => x.Size); - this.adorner = new Rectangle { Fill = new SolidColorBrush(0x80a0c5e8), - [!Layoutable.WidthProperty] = size.Select(x => (object)x.Width), - [!Layoutable.HeightProperty] = size.Select(x => (object)x.Height), [AdornerLayer.AdornedElementProperty] = node.Control, }; diff --git a/Perspex.Input/FocusManager.cs b/Perspex.Input/FocusManager.cs index ccf9be29a4..64a8ec84d4 100644 --- a/Perspex.Input/FocusManager.cs +++ b/Perspex.Input/FocusManager.cs @@ -39,7 +39,10 @@ namespace Perspex.Input /// Focuses a control. /// /// The control to focus. - public void Focus(IInputElement control) + /// + /// Whether the control was focused by a keypress (e.g. the Tab key). + /// + public void Focus(IInputElement control, bool keyboardNavigated = false) { Contract.Requires(control != null); @@ -74,11 +77,12 @@ namespace Perspex.Input if (next != null) { - next.RaiseEvent(new RoutedEventArgs + next.RaiseEvent(new GotFocusEventArgs { RoutedEvent = InputElement.GotFocusEvent, Source = next, OriginalSource = next, + KeyboardNavigated = keyboardNavigated, }); } } diff --git a/Perspex.Input/GotFocusEventArgs.cs b/Perspex.Input/GotFocusEventArgs.cs new file mode 100644 index 0000000000..bf393ed4d1 --- /dev/null +++ b/Perspex.Input/GotFocusEventArgs.cs @@ -0,0 +1,19 @@ +// ----------------------------------------------------------------------- +// +// Copyright 2015 MIT Licence. See licence.md for more information. +// +// ----------------------------------------------------------------------- + +namespace Perspex.Input +{ + using Perspex.Interactivity; + + public class GotFocusEventArgs : RoutedEventArgs + { + /// + /// Gets or sets a value indicating whether the control was focused by a keypress (e.g. + /// the Tab key). + /// + public bool KeyboardNavigated { get; set; } + } +} diff --git a/Perspex.Input/IFocusManager.cs b/Perspex.Input/IFocusManager.cs index 1b0f61a0ae..8b67ad19c4 100644 --- a/Perspex.Input/IFocusManager.cs +++ b/Perspex.Input/IFocusManager.cs @@ -18,7 +18,14 @@ namespace Perspex.Input IFocusScope Scope { get; } - void Focus(IInputElement focusable); + /// + /// Focuses a control. + /// + /// The control to focus. + /// + /// Whether the control was focused by a keypress (e.g. the Tab key). + /// + void Focus(IInputElement focusable, bool keyboardNavigated = false); /// /// Notifies the focus manager of a change in focus scope. diff --git a/Perspex.Input/IInputElement.cs b/Perspex.Input/IInputElement.cs index 3f24fa4f05..bc66fa2990 100644 --- a/Perspex.Input/IInputElement.cs +++ b/Perspex.Input/IInputElement.cs @@ -37,6 +37,8 @@ namespace Perspex.Input bool IsPointerOver { get; } + bool IsTabFocused { get; } + void Focus(); IInputElement InputHitTest(Point p); diff --git a/Perspex.Input/InputElement.cs b/Perspex.Input/InputElement.cs index a557db86b6..d40aeeab0d 100644 --- a/Perspex.Input/InputElement.cs +++ b/Perspex.Input/InputElement.cs @@ -23,7 +23,7 @@ namespace Perspex.Input PerspexProperty.Register("IsEnabledCore", true); public static readonly PerspexProperty IsFocusedProperty = - PerspexProperty.Register("IsFocused", false); + PerspexProperty.Register("IsFocused"); public static readonly PerspexProperty IsHitTestVisibleProperty = PerspexProperty.Register("IsHitTestVisible", true); @@ -31,8 +31,11 @@ namespace Perspex.Input public static readonly PerspexProperty IsPointerOverProperty = PerspexProperty.Register("IsPointerOver"); - public static readonly RoutedEvent GotFocusEvent = - RoutedEvent.Register("GotFocus", RoutingStrategies.Bubble); + public static readonly PerspexProperty IsTabFocusedProperty = + PerspexProperty.Register("IsTabFocused"); + + public static readonly RoutedEvent GotFocusEvent = + RoutedEvent.Register("GotFocus", RoutingStrategies.Bubble); public static readonly RoutedEvent LostFocusEvent = RoutedEvent.Register("LostFocus", RoutingStrategies.Bubble); @@ -172,6 +175,11 @@ namespace Perspex.Input get { return this.IsEnabledCore; } } + bool IInputElement.IsTabFocused + { + get { return this.GetValue(IsTabFocusedProperty); } + } + protected bool IsEnabledCore { get { return this.GetValue(IsEnabledCoreProperty); } @@ -193,14 +201,16 @@ namespace Perspex.Input this.UpdateIsEnabledCore(); } - protected virtual void OnGotFocus(RoutedEventArgs e) + protected virtual void OnGotFocus(GotFocusEventArgs e) { this.IsFocused = e.OriginalSource == this; + this.SetValue(IsTabFocusedProperty, e.KeyboardNavigated); } protected virtual void OnLostFocus(RoutedEventArgs e) { this.IsFocused = false; + this.SetValue(IsTabFocusedProperty, false); } protected virtual void OnKeyDown(KeyEventArgs e) diff --git a/Perspex.Input/KeyboardNavigation.cs b/Perspex.Input/KeyboardNavigation.cs index fcf5ff9826..51da7b0c36 100644 --- a/Perspex.Input/KeyboardNavigation.cs +++ b/Perspex.Input/KeyboardNavigation.cs @@ -26,7 +26,7 @@ namespace Perspex.Input if (next != null) { - FocusManager.Instance.Focus(next); + FocusManager.Instance.Focus(next, true); } } @@ -42,7 +42,7 @@ namespace Perspex.Input if (next != null) { - FocusManager.Instance.Focus(next); + FocusManager.Instance.Focus(next, true); } } } diff --git a/Perspex.Input/Perspex.Input.csproj b/Perspex.Input/Perspex.Input.csproj index 9603ecde49..68d90711f3 100644 --- a/Perspex.Input/Perspex.Input.csproj +++ b/Perspex.Input/Perspex.Input.csproj @@ -74,6 +74,7 @@ +