Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
67 changes: 67 additions & 0 deletions osu.Framework/Graphics/Containers/ContainerExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@
using osuTK;
using System;
using System.Collections.Generic;
using System.Linq;
using osu.Framework.Extensions.EnumExtensions;
using osu.Framework.Extensions.ObjectExtensions;

namespace osu.Framework.Graphics.Containers
{
Expand Down Expand Up @@ -92,5 +94,70 @@ public static TContainer WithChildren<TContainer, TChild>(this TContainer contai

return container;
}

/// <summary>
/// Searches the subtree for <see cref="ITabbableContainer"/>s and moves focus to the <see cref="ITabbableContainer"/> before/after the one currently focused.
/// </summary>
/// <param name="target">Container to search for valid focus targets in.</param>
/// <param name="reverse">Whether to traverse the container's children in reverse when looking for the next target.</param>
/// <param name="requireFocusedChild">
/// Determines the behaviour when the currently focused drawable isn't rooted at this container.
/// If true, then focus will not be moved.
/// If false, then focus will be moved to the first valid child.
/// </param>
/// <returns>Whether focus was moved to a new <see cref="ITabbableContainer"/>.</returns>
public static bool MoveFocusToNextTabStop(this CompositeDrawable target, bool reverse = false, bool requireFocusedChild = true)
{
var currentlyFocused = target.GetContainingInputManager()?.FocusedDrawable;

if (currentlyFocused == null && requireFocusedChild)
return false;

var focusManager = target.GetContainingFocusManager().AsNonNull();

Stack<Drawable> stack = new Stack<Drawable>();
stack.Push(target); // Extra push for circular tabbing
stack.Push(target);

// If we don't have a currently focused child we pretend we've already encountered our target child to move focus to the first valid target.
bool started = currentlyFocused == null;
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Highlighting this line because it's the only line that doesn't match the original implementation. Should act the same as before unless requireFocusedChild is false.


while (stack.Count > 0)
{
var drawable = stack.Pop();

if (!started)
started = ReferenceEquals(drawable, currentlyFocused);
else if (drawable is ITabbableContainer tabbable && tabbable.CanBeTabbedTo && focusManager.ChangeFocus(drawable))
return true;

if (drawable is CompositeDrawable composite)
{
var newChildren = composite.InternalChildren.ToList();
int bound = reverse ? newChildren.Count : 0;

if (!started)
{
// Find currently focused element, to know starting point
int index = newChildren.IndexOf(currentlyFocused);
if (index != -1)
bound = reverse ? index + 1 : index;
}

if (reverse)
{
for (int i = 0; i < bound; i++)
stack.Push(newChildren[i]);
}
else
{
for (int i = newChildren.Count - 1; i >= bound; i--)
stack.Push(newChildren[i]);
}
}
}

return false;
}
}
}
51 changes: 1 addition & 50 deletions osu.Framework/Graphics/Containers/TabbableContainer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,6 @@

#nullable disable

using System.Collections.Generic;
using System.Linq;
using osu.Framework.Extensions.ObjectExtensions;
using osu.Framework.Input.Events;
using osuTK.Input;

Expand Down Expand Up @@ -44,54 +41,8 @@ protected override bool OnKeyDown(KeyDownEvent e)
if (TabbableContentContainer == null || e.Key != Key.Tab)
return false;

moveToNextTabStop(TabbableContentContainer, e.ShiftPressed);
TabbableContentContainer.MoveFocusToNextTabStop(e.ShiftPressed);
return true;
}

private void moveToNextTabStop(CompositeDrawable target, bool reverse)
{
var focusManager = GetContainingFocusManager().AsNonNull();

Stack<Drawable> stack = new Stack<Drawable>();
stack.Push(target); // Extra push for circular tabbing
stack.Push(target);

bool started = false;

while (stack.Count > 0)
{
var drawable = stack.Pop();

if (!started)
started = ReferenceEquals(drawable, this);
else if (drawable is ITabbableContainer tabbable && tabbable.CanBeTabbedTo && focusManager.ChangeFocus(drawable))
return;

if (drawable is CompositeDrawable composite)
{
var newChildren = composite.InternalChildren.ToList();
int bound = reverse ? newChildren.Count : 0;

if (!started)
{
// Find self, to know starting point
int index = newChildren.IndexOf(this);
if (index != -1)
bound = reverse ? index + 1 : index;
}

if (reverse)
{
for (int i = 0; i < bound; i++)
stack.Push(newChildren[i]);
}
else
{
for (int i = newChildren.Count - 1; i >= bound; i--)
stack.Push(newChildren[i]);
}
}
}
}
}
}
Loading