51 changed files with 2575 additions and 1635 deletions
@ -1,5 +1,5 @@ |
|||||
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> |
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> |
||||
<ItemGroup> |
<ItemGroup> |
||||
<PackageReference Include="Microsoft.CSharp" Version="4.3.0" /> |
<PackageReference Include="Microsoft.CSharp" Version="4.5.0" /> |
||||
</ItemGroup> |
</ItemGroup> |
||||
</Project> |
</Project> |
||||
|
|||||
@ -1,5 +1,5 @@ |
|||||
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> |
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> |
||||
<ItemGroup> |
<ItemGroup> |
||||
<PackageReference Include="Microsoft.Reactive.Testing" Version="3.0.0" /> |
<PackageReference Include="Microsoft.Reactive.Testing" Version="4.0.0" /> |
||||
</ItemGroup> |
</ItemGroup> |
||||
</Project> |
</Project> |
||||
|
|||||
@ -1,5 +1,5 @@ |
|||||
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> |
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> |
||||
<ItemGroup> |
<ItemGroup> |
||||
<PackageReference Include="reactiveui" Version="8.0.0" /> |
<PackageReference Include="reactiveui" Version="8.7.1" /> |
||||
</ItemGroup> |
</ItemGroup> |
||||
</Project> |
</Project> |
||||
|
|||||
@ -1,9 +1,9 @@ |
|||||
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> |
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> |
||||
<ItemGroup> |
<ItemGroup> |
||||
<PackageReference Include="System.Reactive" Version="3.1.1" /> |
<PackageReference Include="System.Reactive" Version="4.0.0" /> |
||||
<PackageReference Include="System.Reactive.Core" Version="3.1.1" /> |
<PackageReference Include="System.Reactive.Core" Version="4.0.0" /> |
||||
<PackageReference Include="System.Reactive.Interfaces" Version="3.1.1" /> |
<PackageReference Include="System.Reactive.Interfaces" Version="4.0.0" /> |
||||
<PackageReference Include="System.Reactive.Linq" Version="3.1.1" /> |
<PackageReference Include="System.Reactive.Linq" Version="4.0.0" /> |
||||
<PackageReference Include="System.Reactive.PlatformServices" Version="3.1.1" /> |
<PackageReference Include="System.Reactive.PlatformServices" Version="4.0.0" /> |
||||
</ItemGroup> |
</ItemGroup> |
||||
</Project> |
</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\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\$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.Gtk3.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.Win32.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.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