Browse Source

Implemented directional navigation.

pull/72/merge
Steven Kirk 11 years ago
parent
commit
884c6539ce
  1. 24
      Perspex.Controls/Control.cs
  2. 43
      Perspex.Controls/ControlExtensions.cs
  3. 26
      Perspex.Controls/MenuItem.cs
  4. 8
      Perspex.Controls/Mixins/SelectableMixin.cs
  5. 5
      Perspex.Controls/Presenters/ItemsPresenter.cs
  6. 10
      Perspex.Controls/TopLevel.cs
  7. 11
      Perspex.Input/IKeyboardNavigationHandler.cs
  8. 52
      Perspex.Input/KeyboardNavigationHandler.cs
  9. 5
      Perspex.SceneGraph/IVisual.cs
  10. 16
      Perspex.SceneGraph/Visual.cs

24
Perspex.Controls/Control.cs

@ -300,30 +300,6 @@ namespace Perspex.Controls
}
}
/// <summary>
/// Tries to being the control into view.
/// </summary>
public void BringIntoView()
{
this.BringIntoView(new Rect(this.Bounds.Size));
}
/// <summary>
/// Tries to being the specified area on the control into view.
/// </summary>
/// <param name="rect">The area of the control to being into view.</param>
public void BringIntoView(Rect rect)
{
var ev = new RequestBringIntoViewEventArgs
{
RoutedEvent = RequestBringIntoViewEvent,
TargetObject = this,
TargetRect = rect,
};
this.RaiseEvent(ev);
}
/// <summary>
/// Sets the control's logical parent.
/// </summary>

43
Perspex.Controls/ControlExtensions.cs

@ -6,18 +6,49 @@
namespace Perspex.Controls
{
using System;
using System.Collections.Generic;
using System.Linq;
using Perspex.Controls;
using Perspex.Controls.Primitives;
using Perspex.LogicalTree;
using Perspex.Styling;
using Perspex.VisualTree;
/// <summary>
/// Adds common functionality to <see cref="IControl"/>.
/// </summary>
public static class ControlExtensions
{
public static T FindControl<T>(this Control control, string name) where T : Control
/// <summary>
/// Tries to being the control into view.
/// </summary>
/// <param name="control">The control.</param>
public static void BringIntoView(this IControl control)
{
control.BringIntoView(new Rect(control.Bounds.Size));
}
/// <summary>
/// Tries to being the control into view.
/// </summary>
/// <param name="control">The control.</param>
/// <param name="rect">The area of the control to being into view.</param>
public static void BringIntoView(this IControl control, Rect rect)
{
var ev = new RequestBringIntoViewEventArgs
{
RoutedEvent = Control.RequestBringIntoViewEvent,
TargetObject = control,
TargetRect = rect,
};
control.RaiseEvent(ev);
}
/// <summary>
/// Finds the named control in the specified control.
/// </summary>
/// <typeparam name="T">The type of the control to find.</typeparam>
/// <param name="control">The control.</param>
/// <param name="name">The name of the control to find.</param>
/// <returns>The control or null if not found.</returns>
public static T FindControl<T>(this IControl control, string name) where T : IControl
{
return control.GetLogicalDescendents()
.OfType<T>()

26
Perspex.Controls/MenuItem.cs

@ -8,6 +8,7 @@ namespace Perspex.Controls
{
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Windows.Input;
using Perspex.Controls.Mixins;
@ -73,6 +74,15 @@ namespace Perspex.Controls
public static readonly RoutedEvent<RoutedEventArgs> SubmenuOpenedEvent =
RoutedEvent.Register<MenuItem, RoutedEventArgs>(nameof(SubmenuOpened), RoutingStrategies.Bubble);
/// <summary>
/// The default value for the <see cref="ItemsPanel"/> property.
/// </summary>
private static readonly ItemsPanelTemplate DefaultPanel =
new ItemsPanelTemplate(() => new StackPanel
{
[KeyboardNavigation.DirectionalNavigationProperty] = KeyboardNavigationMode.Cycle,
});
/// <summary>
/// The timer used to display submenus.
/// </summary>
@ -90,6 +100,7 @@ namespace Perspex.Controls
{
SelectableMixin.Attach<MenuItem>(IsSelectedProperty);
FocusableProperty.OverrideDefaultValue<MenuItem>(true);
ItemsPanelProperty.OverrideDefaultValue<MenuItem>(DefaultPanel);
ClickEvent.AddClassHandler<MenuItem>(x => x.OnClick);
SubmenuOpenedEvent.AddClassHandler<MenuItem>(x => x.OnSubmenuOpened);
IsSubMenuOpenProperty.Changed.AddClassHandler<MenuItem>(x => x.SubMenuOpenChanged);
@ -222,21 +233,6 @@ namespace Perspex.Controls
switch (e.Key)
{
case Key.Up:
passStraightToParent = !this.IsSubMenuOpen;
break;
case Key.Down:
if (this.IsTopLevel && this.HasSubMenu && !this.IsSubMenuOpen)
{
this.SelectedIndex = 0;
this.IsSubMenuOpen = true;
e.Handled = true;
}
passStraightToParent = !this.IsSubMenuOpen;
break;
case Key.Left:
if (!this.IsTopLevel && this.IsSubMenuOpen)
{

8
Perspex.Controls/Mixins/SelectableMixin.cs

@ -26,6 +26,9 @@ namespace Perspex.Controls.Mixins
/// <item>
/// Adds a 'selected' class to selected controls.
/// </item>
/// <item>
/// Requests that the control is scrolled into view when focused.
/// </item>
/// </list>
/// <para>
/// Mixins apply themselves to classes and not instances, and as such should be created in
@ -53,6 +56,11 @@ namespace Perspex.Controls.Mixins
if ((bool)x.NewValue)
{
sender.Classes.Add("selected");
if (((IVisual)sender).IsAttachedToVisualTree)
{
sender.BringIntoView();
}
}
else
{

5
Perspex.Controls/Presenters/ItemsPresenter.cs

@ -141,6 +141,11 @@ namespace Perspex.Controls.Presenters
this.Panel = this.ItemsPanel.Build();
this.Panel.SetValue(TemplatedParentProperty, this.TemplatedParent);
if (!this.Panel.IsSet(KeyboardNavigation.DirectionalNavigationProperty))
{
KeyboardNavigation.SetDirectionalNavigation(this.Panel, KeyboardNavigationMode.Contained);
}
this.AddVisualChild(this.Panel);
var logicalHost = this.FindReparentingHost();

10
Perspex.Controls/TopLevel.cs

@ -149,6 +149,16 @@ namespace Perspex.Controls
this.renderManager.RenderNeeded.Subscribe(_ => this.HandleRenderNeeded());
}
if (this.keyboardNavigationHandler != null)
{
this.keyboardNavigationHandler.SetOwner(this);
}
if (this.accessKeyHandler != null)
{
this.accessKeyHandler.SetOwner(this);
}
styler?.ApplyStyles(this);
this.GetObservable(ClientSizeProperty).Skip(1).Subscribe(x => this.PlatformImpl.ClientSize = x);

11
Perspex.Input/IKeyboardNavigationHandler.cs

@ -21,15 +21,10 @@ namespace Perspex.Input
void SetOwner(IInputRoot owner);
/// <summary>
/// Moves the focus to the next control in tab order.
/// Moves the focus in the specified direction.
/// </summary>
/// <param name="element">The current element.</param>
void TabNext(IInputElement element);
/// <summary>
/// Moves the focus to the previous control in tab order.
/// </summary>
/// <param name="element">The current element.</param>
void TabPrevious(IInputElement element);
/// <param name="direction">The direction to move.</param>
void Move(IInputElement element, FocusNavigationDirection direction);
}
}

52
Perspex.Input/KeyboardNavigationHandler.cs

@ -98,30 +98,15 @@ namespace Perspex.Input
}
/// <summary>
/// Moves the focus to the next control in tab order.
/// Moves the focus in the specified direction.
/// </summary>
/// <param name="element">The current element.</param>
public void TabNext(IInputElement element)
/// <param name="direction">The direction to move.</param>
public void Move(IInputElement element, FocusNavigationDirection direction)
{
Contract.Requires<ArgumentNullException>(element != null);
var next = GetNext(element, FocusNavigationDirection.Next);
if (next != null)
{
FocusManager.Instance.Focus(next, true);
}
}
/// <summary>
/// Moves the focus to the previous control in tab order.
/// </summary>
/// <param name="element">The current element.</param>
public void TabPrevious(IInputElement element)
{
Contract.Requires<ArgumentNullException>(element != null);
var next = GetNext(element, FocusNavigationDirection.Previous);
var next = GetNext(element, direction);
if (next != null)
{
@ -312,15 +297,34 @@ namespace Perspex.Input
{
var current = FocusManager.Instance.Current;
if (e.Key == Key.Tab && current != null)
if (current != null)
{
if ((KeyboardDevice.Instance.Modifiers & ModifierKeys.Shift) == 0)
FocusNavigationDirection? direction = null;
switch (e.Key)
{
this.TabNext(current);
case Key.Tab:
direction = (KeyboardDevice.Instance.Modifiers & ModifierKeys.Shift) == 0 ?
FocusNavigationDirection.Next : FocusNavigationDirection.Previous;
break;
case Key.Up:
direction = FocusNavigationDirection.Up;
break;
case Key.Down:
direction = FocusNavigationDirection.Down;
break;
case Key.Left:
direction = FocusNavigationDirection.Left;
break;
case Key.Right:
direction = FocusNavigationDirection.Right;
break;
}
else
if (direction.HasValue)
{
this.TabPrevious(current);
this.Move(current, direction.Value);
e.Handled = true;
}
}
}

5
Perspex.SceneGraph/IVisual.cs

@ -32,6 +32,11 @@ namespace Perspex
/// </summary>
bool ClipToBounds { get; }
/// <summary>
/// Gets a value indicating whether this scene graph node is attached to a visual root.
/// </summary>
bool IsAttachedToVisualTree { get; }
/// <summary>
/// Gets a value indicating whether this scene graph node and all its parents are visible.
/// </summary>

16
Perspex.SceneGraph/Visual.cs

@ -83,6 +83,11 @@ namespace Perspex
/// </summary>
private Visual visualParent;
/// <summary>
/// Whether the element is attached to the visual tree.
/// </summary>
private bool isAttachedToVisualTree;
/// <summary>
/// The logger for visual-level events.
/// </summary>
@ -185,6 +190,15 @@ namespace Perspex
set { this.SetValue(ZIndexProperty, value); }
}
/// <summary>
/// Gets a value indicating whether this scene graph node is attached to a visual root.
/// </summary>
bool IVisual.IsAttachedToVisualTree
{
get { return this.isAttachedToVisualTree; }
}
/// <summary>
/// Gets the scene graph node's child nodes.
/// </summary>
@ -487,6 +501,7 @@ namespace Perspex
{
this.visualLogger.Verbose("Attached to visual tree");
this.isAttachedToVisualTree = true;
this.OnAttachedToVisualTree(root);
if (this.visualChildren != null)
@ -507,6 +522,7 @@ namespace Perspex
{
this.visualLogger.Verbose("Detached from visual tree");
this.isAttachedToVisualTree = false;
this.OnDetachedFromVisualTree(root);
if (this.visualChildren != null)

Loading…
Cancel
Save