Skip to content

Commit

Permalink
feat(focus): Add support for FindLastFocusableElement
Browse files Browse the repository at this point in the history
  • Loading branch information
MartinZikmund committed May 2, 2020
1 parent 66c514b commit 2049304
Show file tree
Hide file tree
Showing 8 changed files with 155 additions and 16 deletions.
19 changes: 19 additions & 0 deletions src/Uno.UI/Extensions/UIViewExtensions.iOSmacOS.cs
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,25 @@ public static IEnumerable<_View> FindSubviews(this _View view, Func<_View, bool>
}
}

public static IEnumerable<_View> FindSubviewsReverse(this _View view, Func<_View, bool> selector, int maxDepth = 20)
{
for (int i = view.Subviews.Length - 1; i >= 0; i--)
{
var sub = view.Subviews[i];
if (selector(sub))
{
yield return sub;
}
else if (maxDepth > 0)
{
foreach (var subResult in sub.FindSubviewsReverse(selector, maxDepth - 1))
{
yield return subResult;
}
}
}
}

/// <summary>
/// Enumerates all the children for a specified view.
/// </summary>
Expand Down
74 changes: 72 additions & 2 deletions src/Uno.UI/Extensions/ViewExtensions.Android.cs
Original file line number Diff line number Diff line change
Expand Up @@ -128,8 +128,8 @@ public static IEnumerable<View> GetParents(this View view)
/// <summary>
/// Gets an enumerator containing all the children of a View group
/// </summary>
/// <param name="group"></param>
/// <returns></returns>
/// <param name="group">View group</param>
/// <returns>Children in default order</returns>
public static IEnumerable<View> GetChildren(this ViewGroup group)
{
var shadowProvider = group as Controls.IShadowChildrenProvider;
Expand All @@ -146,6 +146,33 @@ public static IEnumerable<View> GetChildren(this ViewGroup group)
}
}

/// <summary>
/// Gets an reverse enumerator containing all the children of a View group
/// </summary>
/// <param name="group">View group</param>
/// <returns>Children in reverse order</returns>
public static IEnumerable<View> GetChildrenReverse(this ViewGroup group)
{
var shadowProvider = group as Controls.IShadowChildrenProvider;

if (shadowProvider != null)
{
// To avoid calling ChildCount/GetChildAt too much during enumeration, use
// a fast path that relies on a shadowed list of the children in BindableView.
for (int i = shadowProvider.ChildrenShadow.Count - 1; i >= 0; i--)
{
yield return shadowProvider.ChildrenShadow[i];
}
}
else
{
foreach (var child in GetChildrenReverseSlow(group))
{
yield return child;
}
}
}

/// <summary>
/// Finds the first child <see cref="View"/> of the provided <see cref="ViewGroup"/>.
/// </summary>
Expand Down Expand Up @@ -188,6 +215,19 @@ private static IEnumerable<View> GetChildrenSlow(ViewGroup group)
}
}

/// <summary>
/// A reverse enumerator for ViewGroup children that uses interop calls.
/// </summary>
private static IEnumerable<View> GetChildrenReverseSlow(ViewGroup group)
{
var count = group.ChildCount;

for (int i = count - 1; i >= 0; i++)
{
yield return group.GetChildAt(i);
}
}

/// <summary>
/// Gets a filtered enumerator containing children of the view group
/// </summary>
Expand Down Expand Up @@ -236,6 +276,36 @@ public static IEnumerable<View> EnumerateAllChildren(this ViewGroup view, Func<V
}
}

/// <summary>
/// Enumerates all the children for a specified view group in reverse order.
/// </summary>
/// <param name="view">The view group to get the children from</param>
/// <param name="selector">The selector function</param>
/// <param name="maxDepth">The depth to stop looking for children.</param>
/// <returns>A lazy enumerable of views in reverse order</returns>
public static IEnumerable<View> EnumerateAllChildrenReverse(this ViewGroup view, Func<View, bool> selector, int maxDepth = 20)
{
foreach (var sub in view.GetChildrenReverse())
{
if (selector(sub))
{
yield return sub;
}
else if (maxDepth > 0)
{
var childGroup = sub as ViewGroup;

if (childGroup != null)
{
foreach (var subResult in childGroup.EnumerateAllChildrenReverse(selector, maxDepth - 1))
{
yield return subResult;
}
}
}
}
}

/// <summary>
/// Enumerates all the children for a specified view group.
/// </summary>
Expand Down
18 changes: 14 additions & 4 deletions src/Uno.UI/UI/Xaml/Input/FocusManager.Android.cs
Original file line number Diff line number Diff line change
Expand Up @@ -214,24 +214,34 @@ public static View InnerFindNextFocusableElement(FocusNavigationDirection focusN
}
}

public static DependencyObject InnerFindFirstFocusableElement(DependencyObject searchScope)
private static DependencyObject InnerFindFirstFocusableElement(DependencyObject searchScope)
{
if (searchScope == null)
{
searchScope = Window.Current.Content;
}

if (IsFocusableView(searchScope as View))
if (!(searchScope is ViewGroup searchViewGroup))
{
return null;
}

return searchViewGroup.EnumerateAllChildren(IsFocusableView, maxDepth: 300).FirstOrDefault() as DependencyObject;
}

private static DependencyObject InnerFindLastFocusableElement(DependencyObject searchScope)
{
if (searchScope == null)
{
return searchScope;
searchScope = Window.Current.Content;
}

if (!(searchScope is ViewGroup searchViewGroup))
{
return null;
}

return searchViewGroup.EnumerateAllChildren(IsFocusableView, maxDepth: 300).FirstOrDefault() as DependencyObject;
return searchViewGroup.EnumerateAllChildrenReverse(IsFocusableView, maxDepth: 300).FirstOrDefault() as DependencyObject;
}

private static void FocusNative(Control control)
Expand Down
12 changes: 11 additions & 1 deletion src/Uno.UI/UI/Xaml/Input/FocusManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ public static UIElement FindNextFocusableElement(FocusNavigationDirection focusN
}

/// <summary>
/// Retrieves the first element that can receive focus based on the specified scope.
/// Retrieves the last element that can receive focus based on the specified scope.
/// </summary>
/// <param name="searchScope">The root object from which to search. If null, the search scope is the current window.</param>
/// <returns>The first focusable object.</returns>
Expand All @@ -59,6 +59,16 @@ public static DependencyObject FindFirstFocusableElement(DependencyObject search
return InnerFindFirstFocusableElement(searchScope);
}

/// <summary>
/// Retrieves the last element that can receive focus based on the specified scope.
/// </summary>
/// <param name="searchScope">The root object from which to search. If null, the search scope is the current window.</param>
/// <returns>The first focusable object.</returns>
public static DependencyObject FindLastFocusableElement(DependencyObject searchScope)
{
return InnerFindLastFocusableElement(searchScope);
}

internal static bool SetFocusedElement(DependencyObject newFocus, FocusNavigationDirection focusNavigationDirection, FocusState focusState)
{
var control = newFocus as Control; // For now only called for Control
Expand Down
18 changes: 14 additions & 4 deletions src/Uno.UI/UI/Xaml/Input/FocusManager.iOS.cs
Original file line number Diff line number Diff line change
Expand Up @@ -208,7 +208,7 @@ public static UIView InnerFindNextFocusableElement(FocusNavigationDirection focu
}
}

public static DependencyObject InnerFindFirstFocusableElement(DependencyObject searchScope)
private static DependencyObject InnerFindFirstFocusableElement(DependencyObject searchScope)
{
if (searchScope == null)
{
Expand All @@ -220,12 +220,22 @@ public static DependencyObject InnerFindFirstFocusableElement(DependencyObject s
return null;
}

if (IsFocusableView(searchView))
return searchView.FindSubviews(selector: IsFocusableView, maxDepth: 100).FirstOrDefault() as DependencyObject;
}

private static DependencyObject InnerFindLastFocusableElement(DependencyObject searchScope)
{
if (searchScope == null)
{
searchScope = Window.Current.Content;
}

if (!(searchScope is UIView searchView))
{
return searchScope;
return null;
}

return searchView.FindSubviews(selector: IsFocusableView, maxDepth: 100).FirstOrDefault() as DependencyObject;
return searchView.FindSubviewsReverse(selector: IsFocusableView, maxDepth: 100).FirstOrDefault() as DependencyObject;
}

private static void FocusNative(Control control) => control.BecomeFirstResponder();
Expand Down
18 changes: 14 additions & 4 deletions src/Uno.UI/UI/Xaml/Input/FocusManager.macOS.cs
Original file line number Diff line number Diff line change
Expand Up @@ -213,7 +213,7 @@ public static NSView InnerFindNextFocusableElement(FocusNavigationDirection focu
}
}

public static DependencyObject InnerFindFirstFocusableElement(DependencyObject searchScope)
private static DependencyObject InnerFindFirstFocusableElement(DependencyObject searchScope)
{
if (searchScope == null)
{
Expand All @@ -225,12 +225,22 @@ public static DependencyObject InnerFindFirstFocusableElement(DependencyObject s
return null;
}

if (IsFocusableView(searchView))
return searchView.FindSubviews(selector: IsFocusableView, maxDepth: 100).FirstOrDefault() as DependencyObject;
}

private static DependencyObject InnerFindLastFocusableElement(DependencyObject searchScope)
{
if (searchScope == null)
{
searchScope = Window.Current.Content;
}

if (!(searchScope is NSView searchView))
{
return searchScope;
return null;
}

return searchView.FindSubviews(selector: IsFocusableView, maxDepth: 100).FirstOrDefault() as DependencyObject;
return searchView.FindSubviewsReverse(selector: IsFocusableView, maxDepth: 100).FirstOrDefault() as DependencyObject;
}

private static void FocusNative(Control control) => control.BecomeFirstResponder();
Expand Down
5 changes: 5 additions & 0 deletions src/Uno.UI/UI/Xaml/Input/FocusManager.net.cs
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,11 @@ public static DependencyObject InnerFindFirstFocusableElement(DependencyObject s
throw new NotImplementedException();
}

private static DependencyObject InnerFindLastFocusableElement(DependencyObject searchScope)
{
throw new NotImplementedException();
}

public static void OnFocusChanged(View control, FocusState state)
{
throw new NotImplementedException();
Expand Down
7 changes: 6 additions & 1 deletion src/Uno.UI/UI/Xaml/Input/FocusManager.wasm.cs
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,12 @@ private static UIElement InnerFindNextFocusableElement(FocusNavigationDirection
return null;
}

public static DependencyObject InnerFindFirstFocusableElement(DependencyObject searchScope)
private static DependencyObject InnerFindFirstFocusableElement(DependencyObject searchScope)
{
return null;
}

private static DependencyObject InnerFindLastFocusableElement(DependencyObject searchScope)
{
return null;
}
Expand Down

0 comments on commit 2049304

Please sign in to comment.