51 changed files with 2575 additions and 1635 deletions
@ -1,5 +1,5 @@ |
|||
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> |
|||
<ItemGroup> |
|||
<PackageReference Include="Microsoft.CSharp" Version="4.3.0" /> |
|||
<PackageReference Include="Microsoft.CSharp" Version="4.5.0" /> |
|||
</ItemGroup> |
|||
</Project> |
|||
|
|||
@ -1,5 +1,5 @@ |
|||
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> |
|||
<ItemGroup> |
|||
<PackageReference Include="Microsoft.Reactive.Testing" Version="3.0.0" /> |
|||
<PackageReference Include="Microsoft.Reactive.Testing" Version="4.0.0" /> |
|||
</ItemGroup> |
|||
</Project> |
|||
|
|||
@ -1,5 +1,5 @@ |
|||
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> |
|||
<ItemGroup> |
|||
<PackageReference Include="reactiveui" Version="8.0.0" /> |
|||
<PackageReference Include="reactiveui" Version="8.7.1" /> |
|||
</ItemGroup> |
|||
</Project> |
|||
|
|||
@ -1,9 +1,9 @@ |
|||
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> |
|||
<ItemGroup> |
|||
<PackageReference Include="System.Reactive" Version="3.1.1" /> |
|||
<PackageReference Include="System.Reactive.Core" Version="3.1.1" /> |
|||
<PackageReference Include="System.Reactive.Interfaces" Version="3.1.1" /> |
|||
<PackageReference Include="System.Reactive.Linq" Version="3.1.1" /> |
|||
<PackageReference Include="System.Reactive.PlatformServices" Version="3.1.1" /> |
|||
<PackageReference Include="System.Reactive" Version="4.0.0" /> |
|||
<PackageReference Include="System.Reactive.Core" Version="4.0.0" /> |
|||
<PackageReference Include="System.Reactive.Interfaces" Version="4.0.0" /> |
|||
<PackageReference Include="System.Reactive.Linq" Version="4.0.0" /> |
|||
<PackageReference Include="System.Reactive.PlatformServices" Version="4.0.0" /> |
|||
</ItemGroup> |
|||
</Project> |
|||
|
|||
@ -1,5 +1,6 @@ |
|||
copy ..\samples\ControlCatalog.Desktop\bin\Debug\net461\Avalonia**.dll ~\.nuget\packages\avalonia\$args\lib\net461\ |
|||
copy ..\samples\ControlCatalog.NetCore\bin\Debug\netcoreapp2.0\Avalonia**.dll ~\.nuget\packages\avalonia\$args\lib\netcoreapp2.0\ |
|||
copy ..\samples\ControlCatalog.NetCore.\bin\Debug\netcoreapp2.0\Avalonia**.dll ~\.nuget\packages\avalonia\$args\lib\netstandard2.0\ |
|||
copy ..\samples\ControlCatalog.NetCore.\bin\Debug\netcoreapp2.0\Avalonia**.dll ~\.nuget\packages\avalonia.gtk3\$args\lib\netstandard2.0\ |
|||
copy ..\samples\ControlCatalog.NetCore.\bin\Debug\netcoreapp2.0\Avalonia**.dll ~\.nuget\packages\avalonia.win32\$args\lib\netstandard2.0\ |
|||
copy ..\samples\ControlCatalog.NetCore.\bin\Debug\netcoreapp2.0\Avalonia**.dll ~\.nuget\packages\avalonia.skia\$args\lib\netstandard2.0\ |
|||
copy ..\samples\ControlCatalog.NetCore\bin\Debug\netcoreapp2.0\Avalonia**.dll ~\.nuget\packages\avalonia\$args\lib\netstandard2.0\ |
|||
copy ..\samples\ControlCatalog.NetCore\bin\Debug\netcoreapp2.0\Avalonia.Gtk3.dll ~\.nuget\packages\avalonia.gtk3\$args\lib\netstandard2.0\ |
|||
copy ..\samples\ControlCatalog.NetCore\bin\Debug\netcoreapp2.0\Avalonia.Win32.dll ~\.nuget\packages\avalonia.win32\$args\lib\netstandard2.0\ |
|||
copy ..\samples\ControlCatalog.NetCore\bin\Debug\netcoreapp2.0\Avalonia.Skia.dll ~\.nuget\packages\avalonia.skia\$args\lib\netstandard2.0\ |
|||
|
|||
@ -0,0 +1,21 @@ |
|||
using System; |
|||
using Avalonia.Controls.Platform; |
|||
|
|||
namespace Avalonia.Controls |
|||
{ |
|||
/// <summary>
|
|||
/// Represents a <see cref="Menu"/> or <see cref="ContextMenu"/>.
|
|||
/// </summary>
|
|||
public interface IMenu : IMenuElement |
|||
{ |
|||
/// <summary>
|
|||
/// Gets the menu interaction handler.
|
|||
/// </summary>
|
|||
IMenuInteractionHandler InteractionHandler { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets a value indicating whether the menu is open.
|
|||
/// </summary>
|
|||
bool IsOpen { get; } |
|||
} |
|||
} |
|||
@ -0,0 +1,40 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using Avalonia.Input; |
|||
|
|||
namespace Avalonia.Controls |
|||
{ |
|||
/// <summary>
|
|||
/// Represents an <see cref="IMenu"/> or <see cref="IMenuItem"/>.
|
|||
/// </summary>
|
|||
public interface IMenuElement : IControl |
|||
{ |
|||
/// <summary>
|
|||
/// Gets or sets the currently selected submenu item.
|
|||
/// </summary>
|
|||
IMenuItem SelectedItem { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the submenu items.
|
|||
/// </summary>
|
|||
IEnumerable<IMenuItem> SubItems { get; } |
|||
|
|||
/// <summary>
|
|||
/// Opens the menu or menu item.
|
|||
/// </summary>
|
|||
void Open(); |
|||
|
|||
/// <summary>
|
|||
/// Closes the menu or menu item.
|
|||
/// </summary>
|
|||
void Close(); |
|||
|
|||
/// <summary>
|
|||
/// Moves the submenu selection in the specified direction.
|
|||
/// </summary>
|
|||
/// <param name="direction">The direction.</param>
|
|||
/// <param name="wrap">Whether to wrap after the first or last item.</param>
|
|||
/// <returns>True if the selection was moved; otherwise false.</returns>
|
|||
bool MoveSelection(NavigationDirection direction, bool wrap); |
|||
} |
|||
} |
|||
@ -0,0 +1,41 @@ |
|||
using System; |
|||
|
|||
namespace Avalonia.Controls |
|||
{ |
|||
/// <summary>
|
|||
/// Represents a <see cref="MenuItem"/>.
|
|||
/// </summary>
|
|||
public interface IMenuItem : IMenuElement |
|||
{ |
|||
/// <summary>
|
|||
/// Gets or sets a value that indicates whether the item has a submenu.
|
|||
/// </summary>
|
|||
bool HasSubMenu { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets a value indicating whether the mouse is currently over the menu item's submenu.
|
|||
/// </summary>
|
|||
bool IsPointerOverSubMenu { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets a value that indicates whether the submenu of the <see cref="MenuItem"/> is
|
|||
/// open.
|
|||
/// </summary>
|
|||
bool IsSubMenuOpen { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets a value that indicates whether the <see cref="MenuItem"/> is a top-level main menu item.
|
|||
/// </summary>
|
|||
bool IsTopLevel { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the parent <see cref="IMenuElement"/>.
|
|||
/// </summary>
|
|||
new IMenuElement Parent { get; } |
|||
|
|||
/// <summary>
|
|||
/// Raises a click event on the menu item.
|
|||
/// </summary>
|
|||
void RaiseClick(); |
|||
} |
|||
} |
|||
@ -0,0 +1,459 @@ |
|||
using System; |
|||
using Avalonia.Input; |
|||
using Avalonia.Input.Raw; |
|||
using Avalonia.Interactivity; |
|||
using Avalonia.LogicalTree; |
|||
using Avalonia.Rendering; |
|||
using Avalonia.Threading; |
|||
|
|||
namespace Avalonia.Controls.Platform |
|||
{ |
|||
/// <summary>
|
|||
/// Provides the default keyboard and pointer interaction for menus.
|
|||
/// </summary>
|
|||
public class DefaultMenuInteractionHandler : IMenuInteractionHandler |
|||
{ |
|||
private IDisposable _inputManagerSubscription; |
|||
private IRenderRoot _root; |
|||
|
|||
public DefaultMenuInteractionHandler() |
|||
: this(Input.InputManager.Instance, DefaultDelayRun) |
|||
{ |
|||
} |
|||
|
|||
public DefaultMenuInteractionHandler( |
|||
IInputManager inputManager, |
|||
Action<Action, TimeSpan> delayRun) |
|||
{ |
|||
InputManager = inputManager; |
|||
DelayRun = delayRun; |
|||
} |
|||
|
|||
public virtual void Attach(IMenu menu) |
|||
{ |
|||
if (Menu != null) |
|||
{ |
|||
throw new NotSupportedException("DefaultMenuInteractionHandler is already attached."); |
|||
} |
|||
|
|||
Menu = menu; |
|||
Menu.GotFocus += GotFocus; |
|||
Menu.LostFocus += LostFocus; |
|||
Menu.KeyDown += KeyDown; |
|||
Menu.PointerPressed += PointerPressed; |
|||
Menu.PointerReleased += PointerReleased; |
|||
Menu.AddHandler(AccessKeyHandler.AccessKeyPressedEvent, AccessKeyPressed); |
|||
Menu.AddHandler(Avalonia.Controls.Menu.MenuOpenedEvent, this.MenuOpened); |
|||
Menu.AddHandler(MenuItem.PointerEnterItemEvent, PointerEnter); |
|||
Menu.AddHandler(MenuItem.PointerLeaveItemEvent, PointerLeave); |
|||
|
|||
_root = Menu.VisualRoot; |
|||
|
|||
if (_root is InputElement inputRoot) |
|||
{ |
|||
inputRoot.AddHandler(InputElement.PointerPressedEvent, RootPointerPressed, RoutingStrategies.Tunnel); |
|||
} |
|||
|
|||
if (_root is WindowBase window) |
|||
{ |
|||
window.Deactivated += WindowDeactivated; |
|||
} |
|||
|
|||
_inputManagerSubscription = InputManager.Process.Subscribe(RawInput); |
|||
} |
|||
|
|||
public virtual void Detach(IMenu menu) |
|||
{ |
|||
if (Menu != menu) |
|||
{ |
|||
throw new NotSupportedException("DefaultMenuInteractionHandler is not attached to the menu."); |
|||
} |
|||
|
|||
Menu.GotFocus -= GotFocus; |
|||
Menu.LostFocus -= LostFocus; |
|||
Menu.KeyDown -= KeyDown; |
|||
Menu.PointerPressed -= PointerPressed; |
|||
Menu.PointerReleased -= PointerReleased; |
|||
Menu.RemoveHandler(AccessKeyHandler.AccessKeyPressedEvent, AccessKeyPressed); |
|||
Menu.RemoveHandler(Avalonia.Controls.Menu.MenuOpenedEvent, this.MenuOpened); |
|||
Menu.RemoveHandler(MenuItem.PointerEnterItemEvent, PointerEnter); |
|||
Menu.RemoveHandler(MenuItem.PointerLeaveItemEvent, PointerLeave); |
|||
|
|||
if (_root is InputElement inputRoot) |
|||
{ |
|||
inputRoot.RemoveHandler(InputElement.PointerPressedEvent, RootPointerPressed); |
|||
} |
|||
|
|||
if (_root is WindowBase root) |
|||
{ |
|||
root.Deactivated -= WindowDeactivated; |
|||
} |
|||
|
|||
_inputManagerSubscription.Dispose(); |
|||
|
|||
Menu = null; |
|||
_root = null; |
|||
} |
|||
|
|||
protected Action<Action, TimeSpan> DelayRun { get; } |
|||
|
|||
protected IInputManager InputManager { get; } |
|||
|
|||
protected IMenu Menu { get; private set; } |
|||
|
|||
protected static TimeSpan MenuShowDelay { get; } = TimeSpan.FromMilliseconds(400); |
|||
|
|||
protected internal virtual void GotFocus(object sender, GotFocusEventArgs e) |
|||
{ |
|||
var item = GetMenuItem(e.Source as IControl); |
|||
|
|||
if (item?.Parent != null) |
|||
{ |
|||
item.SelectedItem = item; |
|||
} |
|||
} |
|||
|
|||
protected internal virtual void LostFocus(object sender, RoutedEventArgs e) |
|||
{ |
|||
var item = GetMenuItem(e.Source as IControl); |
|||
|
|||
if (item != null) |
|||
{ |
|||
item.SelectedItem = null; |
|||
} |
|||
} |
|||
|
|||
protected internal virtual void KeyDown(object sender, KeyEventArgs e) |
|||
{ |
|||
var item = GetMenuItem(e.Source as IControl); |
|||
|
|||
if (item != null) |
|||
{ |
|||
KeyDown(item, e); |
|||
} |
|||
} |
|||
|
|||
protected internal virtual void KeyDown(IMenuItem item, KeyEventArgs e) |
|||
{ |
|||
Contract.Requires<ArgumentNullException>(item != null); |
|||
|
|||
switch (e.Key) |
|||
{ |
|||
case Key.Up: |
|||
case Key.Down: |
|||
if (item.IsTopLevel) |
|||
{ |
|||
if (item.HasSubMenu && !item.IsSubMenuOpen) |
|||
{ |
|||
Open(item, true); |
|||
e.Handled = true; |
|||
} |
|||
} |
|||
else |
|||
{ |
|||
goto default; |
|||
} |
|||
break; |
|||
|
|||
case Key.Left: |
|||
if (item.Parent is IMenuItem parent && !parent.IsTopLevel && parent.IsSubMenuOpen) |
|||
{ |
|||
parent.Close(); |
|||
parent.Focus(); |
|||
e.Handled = true; |
|||
} |
|||
else |
|||
{ |
|||
goto default; |
|||
} |
|||
break; |
|||
|
|||
case Key.Right: |
|||
if (!item.IsTopLevel && item.HasSubMenu) |
|||
{ |
|||
Open(item, true); |
|||
e.Handled = true; |
|||
} |
|||
else |
|||
{ |
|||
goto default; |
|||
} |
|||
break; |
|||
|
|||
case Key.Enter: |
|||
if (!item.HasSubMenu) |
|||
{ |
|||
Click(item); |
|||
} |
|||
else |
|||
{ |
|||
Open(item, true); |
|||
} |
|||
|
|||
e.Handled = true; |
|||
break; |
|||
|
|||
case Key.Escape: |
|||
if (item.Parent != null) |
|||
{ |
|||
item.Parent.Close(); |
|||
item.Parent.Focus(); |
|||
e.Handled = true; |
|||
} |
|||
break; |
|||
|
|||
default: |
|||
var direction = e.Key.ToNavigationDirection(); |
|||
|
|||
if (direction.HasValue && item.Parent?.MoveSelection(direction.Value, true) == true) |
|||
{ |
|||
// If the the parent is an IMenu which successfully moved its selection,
|
|||
// and the current menu is open then close the current menu and open the
|
|||
// new menu.
|
|||
if (item.IsSubMenuOpen && item.Parent is IMenu) |
|||
{ |
|||
item.Close(); |
|||
Open(item.Parent.SelectedItem, true); |
|||
} |
|||
e.Handled = true; |
|||
} |
|||
|
|||
break; |
|||
} |
|||
|
|||
if (!e.Handled && item.Parent is IMenuItem parentItem) |
|||
{ |
|||
KeyDown(parentItem, e); |
|||
} |
|||
} |
|||
|
|||
protected internal virtual void AccessKeyPressed(object sender, RoutedEventArgs e) |
|||
{ |
|||
var item = GetMenuItem(e.Source as IControl); |
|||
|
|||
if (item == null) |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
if (item.HasSubMenu) |
|||
{ |
|||
Open(item, true); |
|||
} |
|||
else |
|||
{ |
|||
Click(item); |
|||
} |
|||
|
|||
e.Handled = true; |
|||
} |
|||
|
|||
protected internal virtual void PointerEnter(object sender, PointerEventArgs e) |
|||
{ |
|||
var item = GetMenuItem(e.Source as IControl); |
|||
|
|||
if (item?.Parent == null) |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
if (item.IsTopLevel) |
|||
{ |
|||
if (item.Parent.SelectedItem?.IsSubMenuOpen == true) |
|||
{ |
|||
item.Parent.SelectedItem.Close(); |
|||
SelectItemAndAncestors(item); |
|||
Open(item, false); |
|||
} |
|||
else |
|||
{ |
|||
SelectItemAndAncestors(item); |
|||
} |
|||
} |
|||
else |
|||
{ |
|||
SelectItemAndAncestors(item); |
|||
|
|||
if (item.HasSubMenu) |
|||
{ |
|||
OpenWithDelay(item); |
|||
} |
|||
else if (item.Parent != null) |
|||
{ |
|||
foreach (var sibling in item.Parent.SubItems) |
|||
{ |
|||
if (sibling.IsSubMenuOpen) |
|||
{ |
|||
CloseWithDelay(sibling); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
protected internal virtual void PointerLeave(object sender, PointerEventArgs e) |
|||
{ |
|||
var item = GetMenuItem(e.Source as IControl); |
|||
|
|||
if (item?.Parent == null) |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
if (item.Parent.SelectedItem == item) |
|||
{ |
|||
if (item.IsTopLevel) |
|||
{ |
|||
if (!((IMenu)item.Parent).IsOpen) |
|||
{ |
|||
item.Parent.SelectedItem = null; |
|||
} |
|||
} |
|||
else if (!item.HasSubMenu) |
|||
{ |
|||
item.Parent.SelectedItem = null; |
|||
} |
|||
} |
|||
} |
|||
|
|||
protected internal virtual void PointerPressed(object sender, PointerPressedEventArgs e) |
|||
{ |
|||
var item = GetMenuItem(e.Source as IControl); |
|||
|
|||
if (e.MouseButton == MouseButton.Left && item?.HasSubMenu == true) |
|||
{ |
|||
Open(item, false); |
|||
e.Handled = true; |
|||
} |
|||
} |
|||
|
|||
protected internal virtual void PointerReleased(object sender, PointerReleasedEventArgs e) |
|||
{ |
|||
var item = GetMenuItem(e.Source as IControl); |
|||
|
|||
if (e.MouseButton == MouseButton.Left && item.HasSubMenu == false) |
|||
{ |
|||
Click(item); |
|||
e.Handled = true; |
|||
} |
|||
} |
|||
|
|||
protected internal virtual void MenuOpened(object sender, RoutedEventArgs e) |
|||
{ |
|||
if (e.Source == Menu) |
|||
{ |
|||
Menu.MoveSelection(NavigationDirection.First, true); |
|||
} |
|||
} |
|||
|
|||
protected internal virtual void RawInput(RawInputEventArgs e) |
|||
{ |
|||
var mouse = e as RawMouseEventArgs; |
|||
|
|||
if (mouse?.Type == RawMouseEventType.NonClientLeftButtonDown) |
|||
{ |
|||
Menu.Close(); |
|||
} |
|||
} |
|||
|
|||
protected internal virtual void RootPointerPressed(object sender, PointerPressedEventArgs e) |
|||
{ |
|||
if (Menu?.IsOpen == true) |
|||
{ |
|||
var control = e.Source as ILogical; |
|||
|
|||
if (!Menu.IsLogicalParentOf(control)) |
|||
{ |
|||
Menu.Close(); |
|||
} |
|||
} |
|||
} |
|||
|
|||
protected internal virtual void WindowDeactivated(object sender, EventArgs e) |
|||
{ |
|||
Menu.Close(); |
|||
} |
|||
|
|||
protected void Click(IMenuItem item) |
|||
{ |
|||
item.RaiseClick(); |
|||
CloseMenu(item); |
|||
} |
|||
|
|||
protected void CloseMenu(IMenuItem item) |
|||
{ |
|||
var current = (IMenuElement)item; |
|||
|
|||
while (current != null && !(current is IMenu)) |
|||
{ |
|||
current = (current as IMenuItem)?.Parent; |
|||
} |
|||
|
|||
current?.Close(); |
|||
} |
|||
|
|||
protected void CloseWithDelay(IMenuItem item) |
|||
{ |
|||
void Execute() |
|||
{ |
|||
if (item.Parent?.SelectedItem != item) |
|||
{ |
|||
item.Close(); |
|||
} |
|||
} |
|||
|
|||
DelayRun(Execute, MenuShowDelay); |
|||
} |
|||
|
|||
protected void Open(IMenuItem item, bool selectFirst) |
|||
{ |
|||
item.Open(); |
|||
|
|||
if (selectFirst) |
|||
{ |
|||
item.MoveSelection(NavigationDirection.First, true); |
|||
} |
|||
} |
|||
|
|||
protected void OpenWithDelay(IMenuItem item) |
|||
{ |
|||
void Execute() |
|||
{ |
|||
if (item.Parent?.SelectedItem == item) |
|||
{ |
|||
Open(item, false); |
|||
} |
|||
} |
|||
|
|||
DelayRun(Execute, MenuShowDelay); |
|||
} |
|||
|
|||
protected void SelectItemAndAncestors(IMenuItem item) |
|||
{ |
|||
var current = item; |
|||
|
|||
while (current?.Parent != null) |
|||
{ |
|||
current.Parent.SelectedItem = current; |
|||
current = current.Parent as IMenuItem; |
|||
} |
|||
} |
|||
|
|||
protected static IMenuItem GetMenuItem(IControl item) |
|||
{ |
|||
while (true) |
|||
{ |
|||
if (item == null) |
|||
return null; |
|||
if (item is IMenuItem menuItem) |
|||
return menuItem; |
|||
item = item.Parent; |
|||
} |
|||
} |
|||
|
|||
private static void DefaultDelayRun(Action action, TimeSpan timeSpan) |
|||
{ |
|||
DispatcherTimer.RunOnce(action, timeSpan); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,22 @@ |
|||
using System; |
|||
using Avalonia.Input; |
|||
|
|||
namespace Avalonia.Controls.Platform |
|||
{ |
|||
/// <summary>
|
|||
/// Handles user interaction for menus.
|
|||
/// </summary>
|
|||
public interface IMenuInteractionHandler |
|||
{ |
|||
/// <summary>
|
|||
/// Attaches the interaction handler to a menu.
|
|||
/// </summary>
|
|||
/// <param name="menu">The menu.</param>
|
|||
void Attach(IMenu menu); |
|||
|
|||
/// <summary>
|
|||
/// Detaches the interaction handler from the attached menu.
|
|||
/// </summary>
|
|||
void Detach(IMenu menu); |
|||
} |
|||
} |
|||
@ -0,0 +1,161 @@ |
|||
using System; |
|||
|
|||
namespace Avalonia.Controls.Primitives |
|||
{ |
|||
/// <summary>
|
|||
/// A <see cref="Panel"/> with uniform column and row sizes.
|
|||
/// </summary>
|
|||
public class UniformGrid : Panel |
|||
{ |
|||
/// <summary>
|
|||
/// Defines the <see cref="Rows"/> property.
|
|||
/// </summary>
|
|||
public static readonly StyledProperty<int> RowsProperty = |
|||
AvaloniaProperty.Register<UniformGrid, int>(nameof(Rows)); |
|||
|
|||
/// <summary>
|
|||
/// Defines the <see cref="Columns"/> property.
|
|||
/// </summary>
|
|||
public static readonly StyledProperty<int> ColumnsProperty = |
|||
AvaloniaProperty.Register<UniformGrid, int>(nameof(Columns)); |
|||
|
|||
/// <summary>
|
|||
/// Defines the <see cref="FirstColumn"/> property.
|
|||
/// </summary>
|
|||
public static readonly StyledProperty<int> FirstColumnProperty = |
|||
AvaloniaProperty.Register<UniformGrid, int>(nameof(FirstColumn)); |
|||
|
|||
private int _rows; |
|||
private int _columns; |
|||
|
|||
/// <summary>
|
|||
/// Specifies the row count. If set to 0, row count will be calculated automatically.
|
|||
/// </summary>
|
|||
public int Rows |
|||
{ |
|||
get => GetValue(RowsProperty); |
|||
set => SetValue(RowsProperty, value); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Specifies the column count. If set to 0, column count will be calculated automatically.
|
|||
/// </summary>
|
|||
public int Columns |
|||
{ |
|||
get => GetValue(ColumnsProperty); |
|||
set => SetValue(ColumnsProperty, value); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Specifies, for the first row, the column where the items should start.
|
|||
/// </summary>
|
|||
public int FirstColumn |
|||
{ |
|||
get => GetValue(FirstColumnProperty); |
|||
set => SetValue(FirstColumnProperty, value); |
|||
} |
|||
|
|||
protected override Size MeasureOverride(Size availableSize) |
|||
{ |
|||
UpdateRowsAndColumns(); |
|||
|
|||
var maxWidth = 0d; |
|||
var maxHeight = 0d; |
|||
|
|||
var childAvailableSize = new Size(availableSize.Width / _columns, availableSize.Height / _rows); |
|||
|
|||
foreach (var child in Children) |
|||
{ |
|||
child.Measure(childAvailableSize); |
|||
|
|||
if (child.DesiredSize.Width > maxWidth) |
|||
{ |
|||
maxWidth = child.DesiredSize.Width; |
|||
} |
|||
|
|||
if (child.DesiredSize.Height > maxHeight) |
|||
{ |
|||
maxHeight = child.DesiredSize.Height; |
|||
} |
|||
} |
|||
|
|||
return new Size(maxWidth * _columns, maxHeight * _rows); |
|||
} |
|||
|
|||
protected override Size ArrangeOverride(Size finalSize) |
|||
{ |
|||
var x = FirstColumn; |
|||
var y = 0; |
|||
|
|||
var width = finalSize.Width / _columns; |
|||
var height = finalSize.Height / _rows; |
|||
|
|||
foreach (var child in Children) |
|||
{ |
|||
if (!child.IsVisible) |
|||
{ |
|||
continue; |
|||
} |
|||
|
|||
child.Arrange(new Rect(x * width, y * height, width, height)); |
|||
|
|||
x++; |
|||
|
|||
if (x >= _columns) |
|||
{ |
|||
x = 0; |
|||
y++; |
|||
} |
|||
} |
|||
|
|||
return finalSize; |
|||
} |
|||
|
|||
private void UpdateRowsAndColumns() |
|||
{ |
|||
_rows = Rows; |
|||
_columns = Columns; |
|||
|
|||
if (FirstColumn >= Columns) |
|||
{ |
|||
FirstColumn = 0; |
|||
} |
|||
|
|||
var itemCount = FirstColumn; |
|||
|
|||
foreach (var child in Children) |
|||
{ |
|||
if (child.IsVisible) |
|||
{ |
|||
itemCount++; |
|||
} |
|||
} |
|||
|
|||
if (_rows == 0) |
|||
{ |
|||
if (_columns == 0) |
|||
{ |
|||
_rows = _columns = (int)Math.Ceiling(Math.Sqrt(itemCount)); |
|||
} |
|||
else |
|||
{ |
|||
_rows = Math.DivRem(itemCount, _columns, out int rem); |
|||
|
|||
if (rem != 0) |
|||
{ |
|||
_rows++; |
|||
} |
|||
} |
|||
} |
|||
else if (_columns == 0) |
|||
{ |
|||
_columns = Math.DivRem(itemCount, _rows, out int rem); |
|||
|
|||
if (rem != 0) |
|||
{ |
|||
_columns++; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -1,242 +0,0 @@ |
|||
// Copyright (c) The Avalonia Project. All rights reserved.
|
|||
// 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 Avalonia.VisualTree; |
|||
|
|||
namespace Avalonia.Input.Navigation |
|||
{ |
|||
/// <summary>
|
|||
/// The implementation for default directional navigation.
|
|||
/// </summary>
|
|||
public static class DirectionalNavigation |
|||
{ |
|||
/// <summary>
|
|||
/// Gets the next control in the specified navigation direction.
|
|||
/// </summary>
|
|||
/// <param name="element">The element.</param>
|
|||
/// <param name="direction">The navigation direction.</param>
|
|||
/// <returns>
|
|||
/// The next element in the specified direction, or null if <paramref name="element"/>
|
|||
/// was the last in the requested direction.
|
|||
/// </returns>
|
|||
public static IInputElement GetNext( |
|||
IInputElement element, |
|||
NavigationDirection direction) |
|||
{ |
|||
Contract.Requires<ArgumentNullException>(element != null); |
|||
Contract.Requires<ArgumentException>( |
|||
direction != NavigationDirection.Next && |
|||
direction != NavigationDirection.Previous); |
|||
|
|||
var container = element.GetVisualParent<IInputElement>(); |
|||
|
|||
if (container != null) |
|||
{ |
|||
var mode = KeyboardNavigation.GetDirectionalNavigation((InputElement)container); |
|||
|
|||
switch (mode) |
|||
{ |
|||
case KeyboardNavigationMode.Continue: |
|||
return GetNextInContainer(element, container, direction) ?? |
|||
GetFirstInNextContainer(element, element, direction); |
|||
case KeyboardNavigationMode.Cycle: |
|||
return GetNextInContainer(element, container, direction) ?? |
|||
GetFocusableDescendant(container, direction); |
|||
case KeyboardNavigationMode.Contained: |
|||
return GetNextInContainer(element, container, direction); |
|||
default: |
|||
return null; |
|||
} |
|||
} |
|||
else |
|||
{ |
|||
return GetFocusableDescendants(element).FirstOrDefault(); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Returns a value indicting whether the specified direction is forward.
|
|||
/// </summary>
|
|||
/// <param name="direction">The direction.</param>
|
|||
/// <returns>True if the direction is forward.</returns>
|
|||
private static bool IsForward(NavigationDirection direction) |
|||
{ |
|||
return direction == NavigationDirection.Next || |
|||
direction == NavigationDirection.Last || |
|||
direction == NavigationDirection.Right || |
|||
direction == NavigationDirection.Down; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets the first or last focusable descendant of the specified element.
|
|||
/// </summary>
|
|||
/// <param name="container">The element.</param>
|
|||
/// <param name="direction">The direction to search.</param>
|
|||
/// <returns>The element or null if not found.##</returns>
|
|||
private static IInputElement GetFocusableDescendant(IInputElement container, NavigationDirection direction) |
|||
{ |
|||
return IsForward(direction) ? |
|||
GetFocusableDescendants(container).FirstOrDefault() : |
|||
GetFocusableDescendants(container).LastOrDefault(); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets the focusable descendants of the specified element.
|
|||
/// </summary>
|
|||
/// <param name="element">The element.</param>
|
|||
/// <returns>The element's focusable descendants.</returns>
|
|||
private static IEnumerable<IInputElement> GetFocusableDescendants(IInputElement element) |
|||
{ |
|||
var children = element.GetVisualChildren().OfType<IInputElement>(); |
|||
|
|||
foreach (var child in children) |
|||
{ |
|||
if (child.CanFocus()) |
|||
{ |
|||
yield return child; |
|||
} |
|||
|
|||
if (child.CanFocusDescendants()) |
|||
{ |
|||
foreach (var descendant in GetFocusableDescendants(child)) |
|||
{ |
|||
yield return descendant; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets the next item that should be focused in the specified container.
|
|||
/// </summary>
|
|||
/// <param name="element">The starting element/</param>
|
|||
/// <param name="container">The container.</param>
|
|||
/// <param name="direction">The direction.</param>
|
|||
/// <returns>The next element, or null if the element is the last.</returns>
|
|||
private static IInputElement GetNextInContainer( |
|||
IInputElement element, |
|||
IInputElement container, |
|||
NavigationDirection direction) |
|||
{ |
|||
if (direction == NavigationDirection.Down) |
|||
{ |
|||
var descendant = GetFocusableDescendants(element).FirstOrDefault(); |
|||
|
|||
if (descendant != null) |
|||
{ |
|||
return descendant; |
|||
} |
|||
} |
|||
|
|||
if (container != null) |
|||
{ |
|||
var navigable = container as INavigableContainer; |
|||
|
|||
if (navigable != null) |
|||
{ |
|||
while (element != null) |
|||
{ |
|||
element = navigable.GetControl(direction, element); |
|||
|
|||
if (element != null && element.CanFocus()) |
|||
{ |
|||
break; |
|||
} |
|||
} |
|||
} |
|||
else |
|||
{ |
|||
// TODO: Do a spatial search here if the container doesn't implement
|
|||
// INavigableContainer.
|
|||
element = null; |
|||
} |
|||
|
|||
if (element != null && direction == NavigationDirection.Up) |
|||
{ |
|||
var descendant = GetFocusableDescendants(element).LastOrDefault(); |
|||
|
|||
if (descendant != null) |
|||
{ |
|||
return descendant; |
|||
} |
|||
} |
|||
|
|||
return element; |
|||
} |
|||
|
|||
return null; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets the first item that should be focused in the next container.
|
|||
/// </summary>
|
|||
/// <param name="element">The element being navigated away from.</param>
|
|||
/// <param name="container">The container.</param>
|
|||
/// <param name="direction">The direction of the search.</param>
|
|||
/// <returns>The first element, or null if there are no more elements.</returns>
|
|||
private static IInputElement GetFirstInNextContainer( |
|||
IInputElement element, |
|||
IInputElement container, |
|||
NavigationDirection direction) |
|||
{ |
|||
var parent = container.GetVisualParent<IInputElement>(); |
|||
var isForward = IsForward(direction); |
|||
IInputElement next = null; |
|||
|
|||
if (parent != null) |
|||
{ |
|||
if (!isForward && parent.CanFocus()) |
|||
{ |
|||
return parent; |
|||
} |
|||
|
|||
var siblings = parent.GetVisualChildren() |
|||
.OfType<IInputElement>() |
|||
.Where(FocusExtensions.CanFocusDescendants); |
|||
var sibling = isForward ? |
|||
siblings.SkipWhile(x => x != container).Skip(1).FirstOrDefault() : |
|||
siblings.TakeWhile(x => x != container).LastOrDefault(); |
|||
|
|||
if (sibling != null) |
|||
{ |
|||
if (sibling is ICustomKeyboardNavigation custom) |
|||
{ |
|||
var (handled, customNext) = custom.GetNext(element, direction); |
|||
|
|||
if (handled) |
|||
{ |
|||
return customNext; |
|||
} |
|||
} |
|||
|
|||
if (sibling.CanFocus()) |
|||
{ |
|||
next = sibling; |
|||
} |
|||
else |
|||
{ |
|||
next = isForward ? |
|||
GetFocusableDescendants(sibling).FirstOrDefault() : |
|||
GetFocusableDescendants(sibling).LastOrDefault(); |
|||
} |
|||
} |
|||
|
|||
if (next == null) |
|||
{ |
|||
next = GetFirstInNextContainer(element, parent, direction); |
|||
} |
|||
} |
|||
else |
|||
{ |
|||
next = isForward ? |
|||
GetFocusableDescendants(container).FirstOrDefault() : |
|||
GetFocusableDescendants(container).LastOrDefault(); |
|||
} |
|||
|
|||
return next; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,507 @@ |
|||
using System; |
|||
using Avalonia.Controls.Platform; |
|||
using Avalonia.Input; |
|||
using Moq; |
|||
using Xunit; |
|||
|
|||
namespace Avalonia.Controls.UnitTests.Platform |
|||
{ |
|||
public class DefaultMenuInteractionHandlerTests |
|||
{ |
|||
public class TopLevel |
|||
{ |
|||
[Fact] |
|||
public void Up_Opens_MenuItem_With_SubMenu() |
|||
{ |
|||
var target = new DefaultMenuInteractionHandler(); |
|||
var item = Mock.Of<IMenuItem>(x => x.IsTopLevel == true && x.HasSubMenu == true); |
|||
var e = new KeyEventArgs { Key = Key.Up, Source = item }; |
|||
|
|||
target.KeyDown(item, e); |
|||
|
|||
Mock.Get(item).Verify(x => x.Open()); |
|||
Mock.Get(item).Verify(x => x.MoveSelection(NavigationDirection.First, true)); |
|||
Assert.True(e.Handled); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Down_Opens_MenuItem_With_SubMenu() |
|||
{ |
|||
var target = new DefaultMenuInteractionHandler(); |
|||
var item = Mock.Of<IMenuItem>(x => x.IsTopLevel == true && x.HasSubMenu == true); |
|||
var e = new KeyEventArgs { Key = Key.Down, Source = item }; |
|||
|
|||
target.KeyDown(item, e); |
|||
|
|||
Mock.Get(item).Verify(x => x.Open()); |
|||
Mock.Get(item).Verify(x => x.MoveSelection(NavigationDirection.First, true)); |
|||
Assert.True(e.Handled); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Right_Selects_Next_MenuItem() |
|||
{ |
|||
var target = new DefaultMenuInteractionHandler(); |
|||
var menu = Mock.Of<IMenu>(x => x.MoveSelection(NavigationDirection.Right, true) == true); |
|||
var item = Mock.Of<IMenuItem>(x => x.IsTopLevel == true && x.Parent == menu); |
|||
var e = new KeyEventArgs { Key = Key.Right, Source = item }; |
|||
|
|||
target.KeyDown(item, e); |
|||
|
|||
Mock.Get(menu).Verify(x => x.MoveSelection(NavigationDirection.Right, true)); |
|||
Assert.True(e.Handled); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Left_Selects_Previous_MenuItem() |
|||
{ |
|||
var target = new DefaultMenuInteractionHandler(); |
|||
var menu = Mock.Of<IMenu>(x => x.MoveSelection(NavigationDirection.Left, true) == true); |
|||
var item = Mock.Of<IMenuItem>(x => x.IsTopLevel == true && x.Parent == menu); |
|||
var e = new KeyEventArgs { Key = Key.Left, Source = item }; |
|||
|
|||
target.KeyDown(item, e); |
|||
|
|||
Mock.Get(menu).Verify(x => x.MoveSelection(NavigationDirection.Left, true)); |
|||
Assert.True(e.Handled); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Enter_On_Item_With_No_SubMenu_Causes_Click() |
|||
{ |
|||
var target = new DefaultMenuInteractionHandler(); |
|||
var menu = Mock.Of<IMenu>(); |
|||
var item = Mock.Of<IMenuItem>(x => x.IsTopLevel == true && x.Parent == menu); |
|||
var e = new KeyEventArgs { Key = Key.Enter, Source = item }; |
|||
|
|||
target.KeyDown(item, e); |
|||
|
|||
Mock.Get(item).Verify(x => x.RaiseClick()); |
|||
Mock.Get(menu).Verify(x => x.Close()); |
|||
Assert.True(e.Handled); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Enter_On_Item_With_SubMenu_Opens_SubMenu() |
|||
{ |
|||
var target = new DefaultMenuInteractionHandler(); |
|||
var menu = Mock.Of<IMenu>(); |
|||
var item = Mock.Of<IMenuItem>(x => x.IsTopLevel == true && x.HasSubMenu == true && x.Parent == menu); |
|||
var e = new KeyEventArgs { Key = Key.Enter, Source = item }; |
|||
|
|||
target.KeyDown(item, e); |
|||
|
|||
Mock.Get(item).Verify(x => x.Open()); |
|||
Mock.Get(item).Verify(x => x.MoveSelection(NavigationDirection.First, true)); |
|||
Assert.True(e.Handled); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Escape_Closes_Parent_Menu() |
|||
{ |
|||
var target = new DefaultMenuInteractionHandler(); |
|||
var menu = Mock.Of<IMenu>(); |
|||
var item = Mock.Of<IMenuItem>(x => x.IsTopLevel == true && x.Parent == menu); |
|||
var e = new KeyEventArgs { Key = Key.Escape, Source = item }; |
|||
|
|||
target.KeyDown(item, e); |
|||
|
|||
Mock.Get(menu).Verify(x => x.Close()); |
|||
Assert.True(e.Handled); |
|||
} |
|||
|
|||
[Fact] |
|||
public void PointerEnter_Opens_Item_When_Old_Item_Is_Open() |
|||
{ |
|||
var target = new DefaultMenuInteractionHandler(); |
|||
var menu = new Mock<IMenu>(); |
|||
var item = Mock.Of<IMenuItem>(x => |
|||
x.IsSubMenuOpen == true && |
|||
x.IsTopLevel == true && |
|||
x.HasSubMenu == true && |
|||
x.Parent == menu.Object); |
|||
var nextItem = Mock.Of<IMenuItem>(x => |
|||
x.IsTopLevel == true && |
|||
x.HasSubMenu == true && |
|||
x.Parent == menu.Object); |
|||
var e = new PointerEventArgs { RoutedEvent = MenuItem.PointerEnterItemEvent, Source = nextItem }; |
|||
|
|||
menu.SetupGet(x => x.SelectedItem).Returns(item); |
|||
|
|||
target.PointerEnter(nextItem, e); |
|||
|
|||
Mock.Get(item).Verify(x => x.Close()); |
|||
menu.VerifySet(x => x.SelectedItem = nextItem); |
|||
Mock.Get(nextItem).Verify(x => x.Open()); |
|||
Mock.Get(nextItem).Verify(x => x.MoveSelection(NavigationDirection.First, true), Times.Never); |
|||
Assert.False(e.Handled); |
|||
|
|||
} |
|||
|
|||
[Fact] |
|||
public void PointerLeave_Deselects_Item_When_Menu_Not_Open() |
|||
{ |
|||
var target = new DefaultMenuInteractionHandler(); |
|||
var menu = new Mock<IMenu>(); |
|||
var item = Mock.Of<IMenuItem>(x => x.IsTopLevel == true && x.Parent == menu.Object); |
|||
var e = new PointerEventArgs { RoutedEvent = MenuItem.PointerLeaveItemEvent, Source = item }; |
|||
|
|||
menu.SetupGet(x => x.SelectedItem).Returns(item); |
|||
target.PointerLeave(item, e); |
|||
|
|||
menu.VerifySet(x => x.SelectedItem = null); |
|||
Assert.False(e.Handled); |
|||
} |
|||
|
|||
[Fact] |
|||
public void PointerLeave_Doesnt_Deselect_Item_When_Menu_Open() |
|||
{ |
|||
var target = new DefaultMenuInteractionHandler(); |
|||
var menu = new Mock<IMenu>(); |
|||
var item = Mock.Of<IMenuItem>(x => x.IsTopLevel == true && x.Parent == menu.Object); |
|||
var e = new PointerEventArgs { RoutedEvent = MenuItem.PointerLeaveItemEvent, Source = item }; |
|||
|
|||
menu.SetupGet(x => x.IsOpen).Returns(true); |
|||
menu.SetupGet(x => x.SelectedItem).Returns(item); |
|||
target.PointerLeave(item, e); |
|||
|
|||
menu.VerifySet(x => x.SelectedItem = null, Times.Never); |
|||
Assert.False(e.Handled); |
|||
} |
|||
} |
|||
|
|||
public class NonTopLevel |
|||
{ |
|||
[Fact] |
|||
public void Up_Selects_Previous_MenuItem() |
|||
{ |
|||
var target = new DefaultMenuInteractionHandler(); |
|||
var parentItem = Mock.Of<IMenuItem>(x => x.IsTopLevel == true && x.HasSubMenu == true); |
|||
var item = Mock.Of<IMenuItem>(x => x.Parent == parentItem); |
|||
var e = new KeyEventArgs { Key = Key.Up, Source = item }; |
|||
|
|||
target.KeyDown(item, e); |
|||
|
|||
Mock.Get(parentItem).Verify(x => x.MoveSelection(NavigationDirection.Up, true)); |
|||
Assert.True(e.Handled); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Down_Selects_Next_MenuItem() |
|||
{ |
|||
var target = new DefaultMenuInteractionHandler(); |
|||
var parentItem = Mock.Of<IMenuItem>(x => x.IsTopLevel == true && x.HasSubMenu == true); |
|||
var item = Mock.Of<IMenuItem>(x => x.Parent == parentItem); |
|||
var e = new KeyEventArgs { Key = Key.Down, Source = item }; |
|||
|
|||
target.KeyDown(item, e); |
|||
|
|||
Mock.Get(parentItem).Verify(x => x.MoveSelection(NavigationDirection.Down, true)); |
|||
Assert.True(e.Handled); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Left_Closes_Parent_SubMenu() |
|||
{ |
|||
var target = new DefaultMenuInteractionHandler(); |
|||
var parentItem = Mock.Of<IMenuItem>(x => x.HasSubMenu == true && x.IsSubMenuOpen == true); |
|||
var item = Mock.Of<IMenuItem>(x => x.Parent == parentItem); |
|||
var e = new KeyEventArgs { Key = Key.Left, Source = item }; |
|||
|
|||
target.KeyDown(item, e); |
|||
|
|||
Mock.Get(parentItem).Verify(x => x.Close()); |
|||
Mock.Get(parentItem).Verify(x => x.Focus()); |
|||
Assert.True(e.Handled); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Right_With_SubMenu_Items_Opens_SubMenu() |
|||
{ |
|||
var target = new DefaultMenuInteractionHandler(); |
|||
var parentItem = Mock.Of<IMenuItem>(x => x.IsTopLevel == true && x.HasSubMenu == true); |
|||
var item = Mock.Of<IMenuItem>(x => x.Parent == parentItem && x.HasSubMenu == true); |
|||
var e = new KeyEventArgs { Key = Key.Right, Source = item }; |
|||
|
|||
target.KeyDown(item, e); |
|||
|
|||
Mock.Get(item).Verify(x => x.Open()); |
|||
Mock.Get(item).Verify(x => x.MoveSelection(NavigationDirection.First, true)); |
|||
Assert.True(e.Handled); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Right_On_TopLevel_Child_Navigates_TopLevel_Selection() |
|||
{ |
|||
var target = new DefaultMenuInteractionHandler(); |
|||
var menu = new Mock<IMenu>(); |
|||
var parentItem = Mock.Of<IMenuItem>(x => |
|||
x.IsSubMenuOpen == true && |
|||
x.IsTopLevel == true && |
|||
x.HasSubMenu == true && |
|||
x.Parent == menu.Object); |
|||
var nextItem = Mock.Of<IMenuItem>(x => |
|||
x.IsTopLevel == true && |
|||
x.HasSubMenu == true && |
|||
x.Parent == menu.Object); |
|||
var item = Mock.Of<IMenuItem>(x => x.Parent == parentItem); |
|||
var e = new KeyEventArgs { Key = Key.Right, Source = item }; |
|||
|
|||
menu.Setup(x => x.MoveSelection(NavigationDirection.Right, true)) |
|||
.Callback(() => menu.SetupGet(x => x.SelectedItem).Returns(nextItem)) |
|||
.Returns(true); |
|||
|
|||
target.KeyDown(item, e); |
|||
|
|||
menu.Verify(x => x.MoveSelection(NavigationDirection.Right, true)); |
|||
Mock.Get(parentItem).Verify(x => x.Close()); |
|||
Mock.Get(nextItem).Verify(x => x.Open()); |
|||
Mock.Get(nextItem).Verify(x => x.MoveSelection(NavigationDirection.First, true)); |
|||
Assert.True(e.Handled); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Enter_On_Item_With_No_SubMenu_Causes_Click() |
|||
{ |
|||
var target = new DefaultMenuInteractionHandler(); |
|||
var menu = Mock.Of<IMenu>(); |
|||
var parentItem = Mock.Of<IMenuItem>(x => x.IsTopLevel == true && x.HasSubMenu == true && x.Parent == menu); |
|||
var item = Mock.Of<IMenuItem>(x => x.Parent == parentItem); |
|||
var e = new KeyEventArgs { Key = Key.Enter, Source = item }; |
|||
|
|||
target.KeyDown(item, e); |
|||
|
|||
Mock.Get(item).Verify(x => x.RaiseClick()); |
|||
Mock.Get(menu).Verify(x => x.Close()); |
|||
Assert.True(e.Handled); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Enter_On_Item_With_SubMenu_Opens_SubMenu() |
|||
{ |
|||
var target = new DefaultMenuInteractionHandler(); |
|||
var parentItem = Mock.Of<IMenuItem>(x => x.IsTopLevel == true && x.HasSubMenu == true); |
|||
var item = Mock.Of<IMenuItem>(x => x.Parent == parentItem && x.HasSubMenu == true); |
|||
var e = new KeyEventArgs { Key = Key.Enter, Source = item }; |
|||
|
|||
target.KeyDown(item, e); |
|||
|
|||
Mock.Get(item).Verify(x => x.Open()); |
|||
Mock.Get(item).Verify(x => x.MoveSelection(NavigationDirection.First, true)); |
|||
Assert.True(e.Handled); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Escape_Closes_Parent_MenuItem() |
|||
{ |
|||
var target = new DefaultMenuInteractionHandler(); |
|||
var parentItem = Mock.Of<IMenuItem>(x => x.IsTopLevel == true && x.HasSubMenu == true); |
|||
var item = Mock.Of<IMenuItem>(x => x.Parent == parentItem); |
|||
var e = new KeyEventArgs { Key = Key.Escape, Source = item }; |
|||
|
|||
target.KeyDown(item, e); |
|||
|
|||
Mock.Get(parentItem).Verify(x => x.Close()); |
|||
Mock.Get(parentItem).Verify(x => x.Focus()); |
|||
Assert.True(e.Handled); |
|||
} |
|||
|
|||
[Fact] |
|||
public void PointerEnter_Selects_Item() |
|||
{ |
|||
var target = new DefaultMenuInteractionHandler(); |
|||
var menu = Mock.Of<IMenu>(); |
|||
var parentItem = Mock.Of<IMenuItem>(x => x.IsTopLevel == true && x.HasSubMenu == true && x.Parent == menu); |
|||
var item = Mock.Of<IMenuItem>(x => x.Parent == parentItem); |
|||
var e = new PointerEventArgs { RoutedEvent = MenuItem.PointerEnterItemEvent, Source = item }; |
|||
|
|||
target.PointerEnter(item, e); |
|||
|
|||
Mock.Get(parentItem).VerifySet(x => x.SelectedItem = item); |
|||
Assert.False(e.Handled); |
|||
} |
|||
|
|||
[Fact] |
|||
public void PointerEnter_Opens_Submenu_After_Delay() |
|||
{ |
|||
var timer = new TestTimer(); |
|||
var target = new DefaultMenuInteractionHandler(null, timer.RunOnce); |
|||
var menu = Mock.Of<IMenu>(); |
|||
var parentItem = Mock.Of<IMenuItem>(x => x.IsTopLevel == true && x.HasSubMenu == true && x.Parent == menu); |
|||
var item = Mock.Of<IMenuItem>(x => x.Parent == parentItem && x.HasSubMenu == true); |
|||
var e = new PointerEventArgs { RoutedEvent = MenuItem.PointerEnterItemEvent, Source = item }; |
|||
|
|||
target.PointerEnter(item, e); |
|||
Mock.Get(item).Verify(x => x.Open(), Times.Never); |
|||
|
|||
timer.Pulse(); |
|||
Mock.Get(item).Verify(x => x.Open()); |
|||
|
|||
Assert.False(e.Handled); |
|||
} |
|||
|
|||
[Fact] |
|||
public void PointerEnter_Closes_Sibling_Submenu_After_Delay() |
|||
{ |
|||
var timer = new TestTimer(); |
|||
var target = new DefaultMenuInteractionHandler(null, timer.RunOnce); |
|||
var menu = Mock.Of<IMenu>(); |
|||
var parentItem = Mock.Of<IMenuItem>(x => x.IsTopLevel == true && x.HasSubMenu == true && x.Parent == menu); |
|||
var item = Mock.Of<IMenuItem>(x => x.Parent == parentItem); |
|||
var sibling = Mock.Of<IMenuItem>(x => x.Parent == parentItem && x.HasSubMenu == true && x.IsSubMenuOpen == true); |
|||
var e = new PointerEventArgs { RoutedEvent = MenuItem.PointerEnterItemEvent, Source = item }; |
|||
|
|||
Mock.Get(parentItem).SetupGet(x => x.SubItems).Returns(new[] { item, sibling }); |
|||
|
|||
target.PointerEnter(item, e); |
|||
Mock.Get(sibling).Verify(x => x.Close(), Times.Never); |
|||
|
|||
timer.Pulse(); |
|||
Mock.Get(sibling).Verify(x => x.Close()); |
|||
|
|||
Assert.False(e.Handled); |
|||
} |
|||
|
|||
[Fact] |
|||
public void PointerLeave_Deselects_Item() |
|||
{ |
|||
var target = new DefaultMenuInteractionHandler(); |
|||
var menu = Mock.Of<IMenu>(); |
|||
var parentItem = Mock.Of<IMenuItem>(x => x.IsTopLevel == true && x.HasSubMenu == true && x.Parent == menu); |
|||
var item = Mock.Of<IMenuItem>(x => x.Parent == parentItem); |
|||
var e = new PointerEventArgs { RoutedEvent = MenuItem.PointerLeaveItemEvent, Source = item }; |
|||
|
|||
Mock.Get(parentItem).SetupGet(x => x.SelectedItem).Returns(item); |
|||
target.PointerLeave(item, e); |
|||
|
|||
Mock.Get(parentItem).VerifySet(x => x.SelectedItem = null); |
|||
Assert.False(e.Handled); |
|||
} |
|||
|
|||
[Fact] |
|||
public void PointerLeave_Doesnt_Deselect_Sibling() |
|||
{ |
|||
var target = new DefaultMenuInteractionHandler(); |
|||
var menu = Mock.Of<IMenu>(); |
|||
var parentItem = Mock.Of<IMenuItem>(x => x.IsTopLevel == true && x.HasSubMenu == true && x.Parent == menu); |
|||
var item = Mock.Of<IMenuItem>(x => x.Parent == parentItem); |
|||
var sibling = Mock.Of<IMenuItem>(x => x.Parent == parentItem); |
|||
var e = new PointerEventArgs { RoutedEvent = MenuItem.PointerLeaveItemEvent, Source = item }; |
|||
|
|||
Mock.Get(parentItem).SetupGet(x => x.SelectedItem).Returns(sibling); |
|||
target.PointerLeave(item, e); |
|||
|
|||
Mock.Get(parentItem).VerifySet(x => x.SelectedItem = null, Times.Never); |
|||
Assert.False(e.Handled); |
|||
} |
|||
|
|||
[Fact] |
|||
public void PointerLeave_Doesnt_Deselect_Item_If_Pointer_Over_Submenu() |
|||
{ |
|||
var target = new DefaultMenuInteractionHandler(); |
|||
var menu = Mock.Of<IMenu>(); |
|||
var parentItem = Mock.Of<IMenuItem>(x => x.IsTopLevel == true && x.HasSubMenu == true && x.Parent == menu); |
|||
var item = Mock.Of<IMenuItem>(x => x.Parent == parentItem && x.HasSubMenu == true && x.IsPointerOverSubMenu == true); |
|||
var e = new PointerEventArgs { RoutedEvent = MenuItem.PointerLeaveItemEvent, Source = item }; |
|||
|
|||
target.PointerLeave(item, e); |
|||
|
|||
Mock.Get(parentItem).VerifySet(x => x.SelectedItem = null, Times.Never); |
|||
Assert.False(e.Handled); |
|||
} |
|||
|
|||
[Fact] |
|||
public void PointerReleased_On_Item_With_No_SubMenu_Causes_Click() |
|||
{ |
|||
var target = new DefaultMenuInteractionHandler(); |
|||
var menu = Mock.Of<IMenu>(); |
|||
var parentItem = Mock.Of<IMenuItem>(x => x.IsTopLevel == true && x.HasSubMenu == true && x.Parent == menu); |
|||
var item = Mock.Of<IMenuItem>(x => x.Parent == parentItem); |
|||
var e = new PointerReleasedEventArgs { MouseButton = MouseButton.Left, Source = item }; |
|||
|
|||
target.PointerReleased(item, e); |
|||
|
|||
Mock.Get(item).Verify(x => x.RaiseClick()); |
|||
Mock.Get(menu).Verify(x => x.Close()); |
|||
Assert.True(e.Handled); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Selection_Is_Correct_When_Pointer_Temporarily_Exits_Item_To_Select_SubItem() |
|||
{ |
|||
var timer = new TestTimer(); |
|||
var target = new DefaultMenuInteractionHandler(null, timer.RunOnce); |
|||
var menu = Mock.Of<IMenu>(); |
|||
var parentItem = Mock.Of<IMenuItem>(x => x.IsTopLevel == true && x.HasSubMenu == true && x.Parent == menu); |
|||
var item = Mock.Of<IMenuItem>(x => x.Parent == parentItem && x.HasSubMenu == true); |
|||
var childItem = Mock.Of<IMenuItem>(x => x.Parent == item); |
|||
var enter = new PointerEventArgs { RoutedEvent = MenuItem.PointerEnterItemEvent, Source = item }; |
|||
var leave = new PointerEventArgs { RoutedEvent = MenuItem.PointerLeaveItemEvent, Source = item }; |
|||
|
|||
// Pointer enters item; item is selected.
|
|||
target.PointerEnter(item, enter); |
|||
Assert.True(timer.ActionIsQueued); |
|||
Mock.Get(parentItem).VerifySet(x => x.SelectedItem = item); |
|||
Mock.Get(parentItem).ResetCalls(); |
|||
|
|||
// SubMenu shown after a delay.
|
|||
timer.Pulse(); |
|||
Mock.Get(item).Verify(x => x.Open()); |
|||
Mock.Get(item).SetupGet(x => x.IsSubMenuOpen).Returns(true); |
|||
Mock.Get(item).ResetCalls(); |
|||
|
|||
// Pointer briefly exits item, but submenu remains open.
|
|||
target.PointerLeave(item, leave); |
|||
Mock.Get(item).Verify(x => x.Close(), Times.Never); |
|||
Mock.Get(item).ResetCalls(); |
|||
|
|||
// Pointer enters child item; is selected.
|
|||
enter.Source = childItem; |
|||
target.PointerEnter(childItem, enter); |
|||
Mock.Get(item).VerifySet(x => x.SelectedItem = childItem); |
|||
Mock.Get(parentItem).VerifySet(x => x.SelectedItem = item); |
|||
Mock.Get(item).ResetCalls(); |
|||
Mock.Get(parentItem).ResetCalls(); |
|||
} |
|||
|
|||
[Fact] |
|||
public void PointerPressed_On_Item_With_SubMenu_Causes_Opens_Submenu() |
|||
{ |
|||
var target = new DefaultMenuInteractionHandler(); |
|||
var menu = Mock.Of<IMenu>(); |
|||
var parentItem = Mock.Of<IMenuItem>(x => x.IsTopLevel == true && x.HasSubMenu == true && x.Parent == menu); |
|||
var item = Mock.Of<IMenuItem>(x => x.Parent == parentItem && x.HasSubMenu == true); |
|||
var e = new PointerPressedEventArgs { MouseButton = MouseButton.Left, Source = item }; |
|||
|
|||
target.PointerPressed(item, e); |
|||
|
|||
Mock.Get(item).Verify(x => x.Open()); |
|||
Mock.Get(item).Verify(x => x.MoveSelection(NavigationDirection.First, true), Times.Never); |
|||
Assert.True(e.Handled); |
|||
} |
|||
} |
|||
|
|||
private class TestTimer |
|||
{ |
|||
private Action _action; |
|||
|
|||
public bool ActionIsQueued => _action != null; |
|||
|
|||
public void Pulse() |
|||
{ |
|||
_action(); |
|||
_action = null; |
|||
} |
|||
|
|||
public void RunOnce(Action action, TimeSpan timeSpan) |
|||
{ |
|||
if (_action != null) |
|||
{ |
|||
throw new NotSupportedException("Action already set."); |
|||
} |
|||
|
|||
_action = action; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,144 @@ |
|||
using Avalonia.Controls.Primitives; |
|||
using Xunit; |
|||
|
|||
namespace Avalonia.Controls.UnitTests.Primitives |
|||
{ |
|||
public class UniformGridTests |
|||
{ |
|||
[Fact] |
|||
public void Grid_Columns_Equals_Rows_For_Auto_Columns_And_Rows() |
|||
{ |
|||
var target = new UniformGrid() |
|||
{ |
|||
Children = |
|||
{ |
|||
new Border { Width = 50, Height = 70 }, |
|||
new Border { Width = 30, Height = 50 }, |
|||
new Border { Width = 80, Height = 90 } |
|||
} |
|||
}; |
|||
|
|||
target.Measure(Size.Infinity); |
|||
target.Arrange(new Rect(target.DesiredSize)); |
|||
|
|||
// 2 * 2 grid
|
|||
Assert.Equal(new Size(2 * 80, 2 * 90), target.Bounds.Size); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Grid_Expands_Vertically_For_Columns_With_Auto_Rows() |
|||
{ |
|||
var target = new UniformGrid() |
|||
{ |
|||
Columns = 2, |
|||
Children = |
|||
{ |
|||
new Border { Width = 50, Height = 70 }, |
|||
new Border { Width = 30, Height = 50 }, |
|||
new Border { Width = 80, Height = 90 }, |
|||
new Border { Width = 20, Height = 30 }, |
|||
new Border { Width = 40, Height = 60 } |
|||
} |
|||
}; |
|||
|
|||
target.Measure(Size.Infinity); |
|||
target.Arrange(new Rect(target.DesiredSize)); |
|||
|
|||
// 2 * 3 grid
|
|||
Assert.Equal(new Size(2 * 80, 3 * 90), target.Bounds.Size); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Grid_Extends_For_Columns_And_First_Column_With_Auto_Rows() |
|||
{ |
|||
var target = new UniformGrid() |
|||
{ |
|||
Columns = 3, |
|||
FirstColumn = 2, |
|||
Children = |
|||
{ |
|||
new Border { Width = 50, Height = 70 }, |
|||
new Border { Width = 30, Height = 50 }, |
|||
new Border { Width = 80, Height = 90 }, |
|||
new Border { Width = 20, Height = 30 }, |
|||
new Border { Width = 40, Height = 60 } |
|||
} |
|||
}; |
|||
|
|||
target.Measure(Size.Infinity); |
|||
target.Arrange(new Rect(target.DesiredSize)); |
|||
|
|||
// 3 * 3 grid
|
|||
Assert.Equal(new Size(3 * 80, 3 * 90), target.Bounds.Size); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Grid_Expands_Horizontally_For_Rows_With_Auto_Columns() |
|||
{ |
|||
var target = new UniformGrid() |
|||
{ |
|||
Rows = 2, |
|||
Children = |
|||
{ |
|||
new Border { Width = 50, Height = 70 }, |
|||
new Border { Width = 30, Height = 50 }, |
|||
new Border { Width = 80, Height = 90 }, |
|||
new Border { Width = 20, Height = 30 }, |
|||
new Border { Width = 40, Height = 60 } |
|||
} |
|||
}; |
|||
|
|||
target.Measure(Size.Infinity); |
|||
target.Arrange(new Rect(target.DesiredSize)); |
|||
|
|||
// 3 * 2 grid
|
|||
Assert.Equal(new Size(3 * 80, 2 * 90), target.Bounds.Size); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Grid_Size_Is_Limited_By_Rows_And_Columns() |
|||
{ |
|||
var target = new UniformGrid() |
|||
{ |
|||
Columns = 2, |
|||
Rows = 2, |
|||
Children = |
|||
{ |
|||
new Border { Width = 50, Height = 70 }, |
|||
new Border { Width = 30, Height = 50 }, |
|||
new Border { Width = 80, Height = 90 }, |
|||
new Border { Width = 20, Height = 30 }, |
|||
new Border { Width = 40, Height = 60 } |
|||
} |
|||
}; |
|||
|
|||
target.Measure(Size.Infinity); |
|||
target.Arrange(new Rect(target.DesiredSize)); |
|||
|
|||
// 2 * 2 grid
|
|||
Assert.Equal(new Size(2 * 80, 2 * 90), target.Bounds.Size); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Not_Visible_Children_Are_Ignored() |
|||
{ |
|||
var target = new UniformGrid() |
|||
{ |
|||
Children = |
|||
{ |
|||
new Border { Width = 50, Height = 70 }, |
|||
new Border { Width = 30, Height = 50 }, |
|||
new Border { Width = 80, Height = 90, IsVisible = false }, |
|||
new Border { Width = 20, Height = 30 }, |
|||
new Border { Width = 40, Height = 60 } |
|||
} |
|||
}; |
|||
|
|||
target.Measure(Size.Infinity); |
|||
target.Arrange(new Rect(target.DesiredSize)); |
|||
|
|||
// 2 * 2 grid
|
|||
Assert.Equal(new Size(2 * 50, 2 * 70), target.Bounds.Size); |
|||
} |
|||
} |
|||
} |
|||
@ -1,799 +0,0 @@ |
|||
// Copyright (c) The Avalonia Project. All rights reserved.
|
|||
// Licensed under the MIT license. See licence.md file in the project root for full license information.
|
|||
|
|||
using Avalonia.Controls; |
|||
using Xunit; |
|||
|
|||
namespace Avalonia.Input.UnitTests |
|||
{ |
|||
public class KeyboardNavigationTests_Arrows |
|||
{ |
|||
[Fact] |
|||
public void Down_Continue_Returns_Down_Control_In_Container() |
|||
{ |
|||
Button current; |
|||
Button next; |
|||
|
|||
var top = new StackPanel |
|||
{ |
|||
Children = |
|||
{ |
|||
new StackPanel |
|||
{ |
|||
[KeyboardNavigation.DirectionalNavigationProperty] = KeyboardNavigationMode.Continue, |
|||
Children = |
|||
{ |
|||
new Button { Name = "Button1" }, |
|||
(current = new Button { Name = "Button2" }), |
|||
(next = new Button { Name = "Button3" }), |
|||
} |
|||
}, |
|||
new StackPanel |
|||
{ |
|||
[KeyboardNavigation.DirectionalNavigationProperty] = KeyboardNavigationMode.Continue, |
|||
Children = |
|||
{ |
|||
new Button { Name = "Button4" }, |
|||
new Button { Name = "Button5" }, |
|||
new Button { Name = "Button6" }, |
|||
} |
|||
}, |
|||
} |
|||
}; |
|||
|
|||
var result = KeyboardNavigationHandler.GetNext(current, NavigationDirection.Down); |
|||
|
|||
Assert.Equal(next, result); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Down_Continue_Returns_First_Control_In_Down_Sibling_Container() |
|||
{ |
|||
Button current; |
|||
Button next; |
|||
|
|||
var top = new StackPanel |
|||
{ |
|||
Children = |
|||
{ |
|||
new StackPanel |
|||
{ |
|||
[KeyboardNavigation.DirectionalNavigationProperty] = KeyboardNavigationMode.Continue, |
|||
Children = |
|||
{ |
|||
new Button { Name = "Button1" }, |
|||
new Button { Name = "Button2" }, |
|||
(current = new Button { Name = "Button3" }), |
|||
} |
|||
}, |
|||
new StackPanel |
|||
{ |
|||
[KeyboardNavigation.DirectionalNavigationProperty] = KeyboardNavigationMode.Continue, |
|||
Children = |
|||
{ |
|||
(next = new Button { Name = "Button4" }), |
|||
new Button { Name = "Button5" }, |
|||
new Button { Name = "Button6" }, |
|||
} |
|||
}, |
|||
} |
|||
}; |
|||
|
|||
var result = KeyboardNavigationHandler.GetNext(current, NavigationDirection.Down); |
|||
|
|||
Assert.Equal(next, result); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Down_Continue_Returns_Down_Sibling() |
|||
{ |
|||
Button current; |
|||
Button next; |
|||
|
|||
var top = new StackPanel |
|||
{ |
|||
Children = |
|||
{ |
|||
new StackPanel |
|||
{ |
|||
[KeyboardNavigation.DirectionalNavigationProperty] = KeyboardNavigationMode.Continue, |
|||
Children = |
|||
{ |
|||
new Button { Name = "Button1" }, |
|||
new Button { Name = "Button2" }, |
|||
(current = new Button { Name = "Button3" }), |
|||
} |
|||
}, |
|||
(next = new Button { Name = "Button4" }), |
|||
} |
|||
}; |
|||
|
|||
var result = KeyboardNavigationHandler.GetNext(current, NavigationDirection.Down); |
|||
|
|||
Assert.Equal(next, result); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Down_Continue_Returns_First_Control_In_Down_Uncle_Container() |
|||
{ |
|||
Button current; |
|||
Button next; |
|||
|
|||
var top = new StackPanel |
|||
{ |
|||
Children = |
|||
{ |
|||
new StackPanel |
|||
{ |
|||
Children = |
|||
{ |
|||
new StackPanel |
|||
{ |
|||
[KeyboardNavigation.DirectionalNavigationProperty] = KeyboardNavigationMode.Continue, |
|||
Children = |
|||
{ |
|||
new Button { Name = "Button1" }, |
|||
new Button { Name = "Button2" }, |
|||
(current = new Button { Name = "Button3" }), |
|||
} |
|||
}, |
|||
}, |
|||
}, |
|||
new StackPanel |
|||
{ |
|||
[KeyboardNavigation.DirectionalNavigationProperty] = KeyboardNavigationMode.Continue, |
|||
Children = |
|||
{ |
|||
(next = new Button { Name = "Button4" }), |
|||
new Button { Name = "Button5" }, |
|||
new Button { Name = "Button6" }, |
|||
} |
|||
}, |
|||
} |
|||
}; |
|||
|
|||
var result = KeyboardNavigationHandler.GetNext(current, NavigationDirection.Down); |
|||
|
|||
Assert.Equal(next, result); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Down_Continue_Returns_Child_Of_Top_Level() |
|||
{ |
|||
Button next; |
|||
|
|||
var top = new StackPanel |
|||
{ |
|||
[KeyboardNavigation.DirectionalNavigationProperty] = KeyboardNavigationMode.Continue, |
|||
Children = |
|||
{ |
|||
(next = new Button { Name = "Button1" }), |
|||
} |
|||
}; |
|||
|
|||
var result = KeyboardNavigationHandler.GetNext(top, NavigationDirection.Down); |
|||
|
|||
Assert.Equal(next, result); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Down_Continue_Wraps() |
|||
{ |
|||
Button current; |
|||
Button next; |
|||
|
|||
var top = new StackPanel |
|||
{ |
|||
[KeyboardNavigation.DirectionalNavigationProperty] = KeyboardNavigationMode.Continue, |
|||
Children = |
|||
{ |
|||
new StackPanel |
|||
{ |
|||
[KeyboardNavigation.DirectionalNavigationProperty] = KeyboardNavigationMode.Continue, |
|||
Children = |
|||
{ |
|||
new StackPanel |
|||
{ |
|||
[KeyboardNavigation.DirectionalNavigationProperty] = KeyboardNavigationMode.Continue, |
|||
Children = |
|||
{ |
|||
(next = new Button { Name = "Button1" }), |
|||
new Button { Name = "Button2" }, |
|||
new Button { Name = "Button3" }, |
|||
} |
|||
}, |
|||
}, |
|||
}, |
|||
new StackPanel |
|||
{ |
|||
[KeyboardNavigation.DirectionalNavigationProperty] = KeyboardNavigationMode.Continue, |
|||
Children = |
|||
{ |
|||
new Button { Name = "Button4" }, |
|||
new Button { Name = "Button5" }, |
|||
(current = new Button { Name = "Button6" }), |
|||
} |
|||
}, |
|||
} |
|||
}; |
|||
|
|||
var result = KeyboardNavigationHandler.GetNext(current, NavigationDirection.Down); |
|||
|
|||
Assert.Equal(next, result); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Down_Cycle_Returns_Down_Control_In_Container() |
|||
{ |
|||
Button current; |
|||
Button next; |
|||
|
|||
var top = new StackPanel |
|||
{ |
|||
Children = |
|||
{ |
|||
new StackPanel |
|||
{ |
|||
[KeyboardNavigation.DirectionalNavigationProperty] = KeyboardNavigationMode.Cycle, |
|||
Children = |
|||
{ |
|||
new Button { Name = "Button1" }, |
|||
(current = new Button { Name = "Button2" }), |
|||
(next = new Button { Name = "Button3" }), |
|||
} |
|||
}, |
|||
new StackPanel |
|||
{ |
|||
Children = |
|||
{ |
|||
new Button { Name = "Button4" }, |
|||
new Button { Name = "Button5" }, |
|||
new Button { Name = "Button6" }, |
|||
} |
|||
}, |
|||
} |
|||
}; |
|||
|
|||
var result = KeyboardNavigationHandler.GetNext(current, NavigationDirection.Down); |
|||
|
|||
Assert.Equal(next, result); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Down_Cycle_Wraps_To_First() |
|||
{ |
|||
Button current; |
|||
Button next; |
|||
|
|||
var top = new StackPanel |
|||
{ |
|||
Children = |
|||
{ |
|||
new StackPanel |
|||
{ |
|||
[KeyboardNavigation.DirectionalNavigationProperty] = KeyboardNavigationMode.Cycle, |
|||
Children = |
|||
{ |
|||
(next = new Button { Name = "Button1" }), |
|||
new Button { Name = "Button2" }, |
|||
(current = new Button { Name = "Button3" }), |
|||
} |
|||
}, |
|||
new StackPanel |
|||
{ |
|||
Children = |
|||
{ |
|||
new Button { Name = "Button4" }, |
|||
new Button { Name = "Button5" }, |
|||
new Button { Name = "Button6" }, |
|||
} |
|||
}, |
|||
} |
|||
}; |
|||
|
|||
var result = KeyboardNavigationHandler.GetNext(current, NavigationDirection.Down); |
|||
|
|||
Assert.Equal(next, result); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Down_Contained_Returns_Down_Control_In_Container() |
|||
{ |
|||
Button current; |
|||
Button next; |
|||
|
|||
var top = new StackPanel |
|||
{ |
|||
Children = |
|||
{ |
|||
new StackPanel |
|||
{ |
|||
[KeyboardNavigation.DirectionalNavigationProperty] = KeyboardNavigationMode.Contained, |
|||
Children = |
|||
{ |
|||
new Button { Name = "Button1" }, |
|||
(current = new Button { Name = "Button2" }), |
|||
(next = new Button { Name = "Button3" }), |
|||
} |
|||
}, |
|||
new StackPanel |
|||
{ |
|||
[KeyboardNavigation.DirectionalNavigationProperty] = KeyboardNavigationMode.Contained, |
|||
Children = |
|||
{ |
|||
new Button { Name = "Button4" }, |
|||
new Button { Name = "Button5" }, |
|||
new Button { Name = "Button6" }, |
|||
} |
|||
}, |
|||
} |
|||
}; |
|||
|
|||
var result = KeyboardNavigationHandler.GetNext(current, NavigationDirection.Down); |
|||
|
|||
Assert.Equal(next, result); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Down_Contained_Stops_At_End() |
|||
{ |
|||
Button current; |
|||
|
|||
var top = new StackPanel |
|||
{ |
|||
Children = |
|||
{ |
|||
new StackPanel |
|||
{ |
|||
[KeyboardNavigation.DirectionalNavigationProperty] = KeyboardNavigationMode.Contained, |
|||
Children = |
|||
{ |
|||
new Button { Name = "Button1" }, |
|||
new Button { Name = "Button2" }, |
|||
(current = new Button { Name = "Button3" }), |
|||
} |
|||
}, |
|||
new StackPanel |
|||
{ |
|||
[KeyboardNavigation.DirectionalNavigationProperty] = KeyboardNavigationMode.Contained, |
|||
Children = |
|||
{ |
|||
new Button { Name = "Button4" }, |
|||
new Button { Name = "Button5" }, |
|||
new Button { Name = "Button6" }, |
|||
} |
|||
}, |
|||
} |
|||
}; |
|||
|
|||
var result = KeyboardNavigationHandler.GetNext(current, NavigationDirection.Down); |
|||
|
|||
Assert.Null(result); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Down_None_Does_Nothing() |
|||
{ |
|||
Button current; |
|||
|
|||
var top = new StackPanel |
|||
{ |
|||
Children = |
|||
{ |
|||
new StackPanel |
|||
{ |
|||
[KeyboardNavigation.DirectionalNavigationProperty] = KeyboardNavigationMode.None, |
|||
Children = |
|||
{ |
|||
new Button { Name = "Button1" }, |
|||
(current = new Button { Name = "Button2" }), |
|||
new Button { Name = "Button3" }, |
|||
} |
|||
}, |
|||
new StackPanel |
|||
{ |
|||
[KeyboardNavigation.DirectionalNavigationProperty] = KeyboardNavigationMode.Contained, |
|||
Children = |
|||
{ |
|||
new Button { Name = "Button4" }, |
|||
new Button { Name = "Button5" }, |
|||
new Button { Name = "Button6" }, |
|||
} |
|||
}, |
|||
} |
|||
}; |
|||
|
|||
var result = KeyboardNavigationHandler.GetNext(current, NavigationDirection.Down); |
|||
|
|||
Assert.Null(result); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Up_Continue_Returns_Up_Control_In_Container() |
|||
{ |
|||
Button current; |
|||
Button next; |
|||
|
|||
var top = new StackPanel |
|||
{ |
|||
Children = |
|||
{ |
|||
new StackPanel |
|||
{ |
|||
[KeyboardNavigation.DirectionalNavigationProperty] = KeyboardNavigationMode.Continue, |
|||
Children = |
|||
{ |
|||
new Button { Name = "Button1" }, |
|||
(next = new Button { Name = "Button2" }), |
|||
(current = new Button { Name = "Button3" }), |
|||
} |
|||
}, |
|||
new StackPanel |
|||
{ |
|||
[KeyboardNavigation.DirectionalNavigationProperty] = KeyboardNavigationMode.Contained, |
|||
Children = |
|||
{ |
|||
new Button { Name = "Button4" }, |
|||
new Button { Name = "Button5" }, |
|||
new Button { Name = "Button6" }, |
|||
} |
|||
}, |
|||
} |
|||
}; |
|||
|
|||
var result = KeyboardNavigationHandler.GetNext(current, NavigationDirection.Up); |
|||
|
|||
Assert.Equal(next, result); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Up_Continue_Returns_Last_Control_In_Up_Sibling_Container() |
|||
{ |
|||
Button current; |
|||
Button next; |
|||
|
|||
var top = new StackPanel |
|||
{ |
|||
Children = |
|||
{ |
|||
new StackPanel |
|||
{ |
|||
[KeyboardNavigation.DirectionalNavigationProperty] = KeyboardNavigationMode.Continue, |
|||
Children = |
|||
{ |
|||
new Button { Name = "Button1" }, |
|||
new Button { Name = "Button2" }, |
|||
(next = new Button { Name = "Button3" }), |
|||
} |
|||
}, |
|||
new StackPanel |
|||
{ |
|||
[KeyboardNavigation.DirectionalNavigationProperty] = KeyboardNavigationMode.Continue, |
|||
Children = |
|||
{ |
|||
(current = new Button { Name = "Button4" }), |
|||
new Button { Name = "Button5" }, |
|||
new Button { Name = "Button6" }, |
|||
} |
|||
}, |
|||
} |
|||
}; |
|||
|
|||
var result = KeyboardNavigationHandler.GetNext(current, NavigationDirection.Up); |
|||
|
|||
Assert.Equal(next, result); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Up_Continue_Returns_Last_Child_Of_Sibling() |
|||
{ |
|||
Button current; |
|||
Button next; |
|||
|
|||
var top = new StackPanel |
|||
{ |
|||
[KeyboardNavigation.DirectionalNavigationProperty] = KeyboardNavigationMode.Continue, |
|||
Children = |
|||
{ |
|||
new StackPanel |
|||
{ |
|||
[KeyboardNavigation.DirectionalNavigationProperty] = KeyboardNavigationMode.Continue, |
|||
Children = |
|||
{ |
|||
new Button { Name = "Button1" }, |
|||
new Button { Name = "Button2" }, |
|||
(next = new Button { Name = "Button3" }), |
|||
} |
|||
}, |
|||
(current = new Button { Name = "Button4" }), |
|||
} |
|||
}; |
|||
|
|||
var result = KeyboardNavigationHandler.GetNext(current, NavigationDirection.Up); |
|||
|
|||
Assert.Equal(next, result); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Up_Continue_Returns_Last_Control_In_Up_Nephew_Container() |
|||
{ |
|||
Button current; |
|||
Button next; |
|||
|
|||
var top = new StackPanel |
|||
{ |
|||
Children = |
|||
{ |
|||
new StackPanel |
|||
{ |
|||
Children = |
|||
{ |
|||
new StackPanel |
|||
{ |
|||
[KeyboardNavigation.DirectionalNavigationProperty] = KeyboardNavigationMode.Continue, |
|||
Children = |
|||
{ |
|||
new Button { Name = "Button1" }, |
|||
new Button { Name = "Button2" }, |
|||
(next = new Button { Name = "Button3" }), |
|||
} |
|||
}, |
|||
}, |
|||
}, |
|||
new StackPanel |
|||
{ |
|||
[KeyboardNavigation.DirectionalNavigationProperty] = KeyboardNavigationMode.Continue, |
|||
Children = |
|||
{ |
|||
(current = new Button { Name = "Button4" }), |
|||
new Button { Name = "Button5" }, |
|||
new Button { Name = "Button6" }, |
|||
} |
|||
}, |
|||
} |
|||
}; |
|||
|
|||
var result = KeyboardNavigationHandler.GetNext(current, NavigationDirection.Up); |
|||
|
|||
Assert.Equal(next, result); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Up_Continue_Wraps() |
|||
{ |
|||
Button current; |
|||
Button next; |
|||
|
|||
var top = new StackPanel |
|||
{ |
|||
Children = |
|||
{ |
|||
new StackPanel |
|||
{ |
|||
Children = |
|||
{ |
|||
new StackPanel |
|||
{ |
|||
[KeyboardNavigation.DirectionalNavigationProperty] = KeyboardNavigationMode.Continue, |
|||
Children = |
|||
{ |
|||
(current = new Button { Name = "Button1" }), |
|||
new Button { Name = "Button2" }, |
|||
new Button { Name = "Button3" }, |
|||
} |
|||
}, |
|||
}, |
|||
}, |
|||
new StackPanel |
|||
{ |
|||
[KeyboardNavigation.DirectionalNavigationProperty] = KeyboardNavigationMode.Continue, |
|||
Children = |
|||
{ |
|||
new Button { Name = "Button4" }, |
|||
new Button { Name = "Button5" }, |
|||
(next = new Button { Name = "Button6" }), |
|||
} |
|||
}, |
|||
} |
|||
}; |
|||
|
|||
var result = KeyboardNavigationHandler.GetNext(current, NavigationDirection.Up); |
|||
|
|||
Assert.Equal(next, result); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Up_Continue_Returns_Parent() |
|||
{ |
|||
Button current; |
|||
|
|||
var top = new Decorator |
|||
{ |
|||
Focusable = true, |
|||
[KeyboardNavigation.DirectionalNavigationProperty] = KeyboardNavigationMode.Continue, |
|||
Child = current = new Button |
|||
{ |
|||
Name = "Button", |
|||
} |
|||
}; |
|||
|
|||
var result = KeyboardNavigationHandler.GetNext(current, NavigationDirection.Up); |
|||
|
|||
Assert.Equal(top, result); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Up_Cycle_Returns_Up_Control_In_Container() |
|||
{ |
|||
Button current; |
|||
Button next; |
|||
|
|||
var top = new StackPanel |
|||
{ |
|||
Children = |
|||
{ |
|||
new StackPanel |
|||
{ |
|||
[KeyboardNavigation.DirectionalNavigationProperty] = KeyboardNavigationMode.Cycle, |
|||
Children = |
|||
{ |
|||
(next = new Button { Name = "Button1" }), |
|||
(current = new Button { Name = "Button2" }), |
|||
new Button { Name = "Button3" }, |
|||
} |
|||
}, |
|||
new StackPanel |
|||
{ |
|||
[KeyboardNavigation.DirectionalNavigationProperty] = KeyboardNavigationMode.Cycle, |
|||
Children = |
|||
{ |
|||
new Button { Name = "Button4" }, |
|||
new Button { Name = "Button5" }, |
|||
new Button { Name = "Button6" }, |
|||
} |
|||
}, |
|||
} |
|||
}; |
|||
|
|||
var result = KeyboardNavigationHandler.GetNext(current, NavigationDirection.Up); |
|||
|
|||
Assert.Equal(next, result); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Up_Cycle_Wraps_To_Last() |
|||
{ |
|||
Button current; |
|||
Button next; |
|||
|
|||
var top = new StackPanel |
|||
{ |
|||
Children = |
|||
{ |
|||
new StackPanel |
|||
{ |
|||
[KeyboardNavigation.DirectionalNavigationProperty] = KeyboardNavigationMode.Cycle, |
|||
Children = |
|||
{ |
|||
(current = new Button { Name = "Button1" }), |
|||
new Button { Name = "Button2" }, |
|||
(next = new Button { Name = "Button3" }), |
|||
} |
|||
}, |
|||
new StackPanel |
|||
{ |
|||
[KeyboardNavigation.DirectionalNavigationProperty] = KeyboardNavigationMode.Cycle, |
|||
Children = |
|||
{ |
|||
new Button { Name = "Button4" }, |
|||
new Button { Name = "Button5" }, |
|||
new Button { Name = "Button6" }, |
|||
} |
|||
}, |
|||
} |
|||
}; |
|||
|
|||
var result = KeyboardNavigationHandler.GetNext(current, NavigationDirection.Up); |
|||
|
|||
Assert.Equal(next, result); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Up_Contained_Returns_Up_Control_In_Container() |
|||
{ |
|||
Button current; |
|||
Button next; |
|||
|
|||
var top = new StackPanel |
|||
{ |
|||
Children = |
|||
{ |
|||
new StackPanel |
|||
{ |
|||
[KeyboardNavigation.DirectionalNavigationProperty] = KeyboardNavigationMode.Contained, |
|||
Children = |
|||
{ |
|||
(next = new Button { Name = "Button1" }), |
|||
(current = new Button { Name = "Button2" }), |
|||
new Button { Name = "Button3" }, |
|||
} |
|||
}, |
|||
new StackPanel |
|||
{ |
|||
[KeyboardNavigation.DirectionalNavigationProperty] = KeyboardNavigationMode.Contained, |
|||
Children = |
|||
{ |
|||
new Button { Name = "Button4" }, |
|||
new Button { Name = "Button5" }, |
|||
new Button { Name = "Button6" }, |
|||
} |
|||
}, |
|||
} |
|||
}; |
|||
|
|||
var result = KeyboardNavigationHandler.GetNext(current, NavigationDirection.Up); |
|||
|
|||
Assert.Equal(next, result); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Up_Contained_Stops_At_Beginning() |
|||
{ |
|||
Button current; |
|||
|
|||
var top = new StackPanel |
|||
{ |
|||
Children = |
|||
{ |
|||
new StackPanel |
|||
{ |
|||
[KeyboardNavigation.DirectionalNavigationProperty] = KeyboardNavigationMode.Contained, |
|||
Children = |
|||
{ |
|||
(current = new Button { Name = "Button1" }), |
|||
new Button { Name = "Button2" }, |
|||
new Button { Name = "Button3" }, |
|||
} |
|||
}, |
|||
new StackPanel |
|||
{ |
|||
[KeyboardNavigation.DirectionalNavigationProperty] = KeyboardNavigationMode.Contained, |
|||
Children = |
|||
{ |
|||
new Button { Name = "Button4" }, |
|||
new Button { Name = "Button5" }, |
|||
new Button { Name = "Button6" }, |
|||
} |
|||
}, |
|||
} |
|||
}; |
|||
|
|||
var result = KeyboardNavigationHandler.GetNext(current, NavigationDirection.Up); |
|||
|
|||
Assert.Null(result); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Up_Contained_Doesnt_Return_Child_Control() |
|||
{ |
|||
Decorator current; |
|||
|
|||
var top = new StackPanel |
|||
{ |
|||
[KeyboardNavigation.DirectionalNavigationProperty] = KeyboardNavigationMode.Contained, |
|||
Children = |
|||
{ |
|||
(current = new Decorator |
|||
{ |
|||
Focusable = true, |
|||
Child = new Button(), |
|||
}) |
|||
} |
|||
}; |
|||
|
|||
var result = KeyboardNavigationHandler.GetNext(current, NavigationDirection.Up); |
|||
|
|||
Assert.Null(result); |
|||
} |
|||
} |
|||
} |
|||
Loading…
Reference in new issue