Skip to content

Commit 0c8bac9

Browse files
authored
Merge pull request #6657 from minetoblend/feature/extract-tab-movement-logic
Move TabbableContainer focus movement logic into helper function
2 parents 648c18e + fec6321 commit 0c8bac9

File tree

2 files changed

+68
-50
lines changed

2 files changed

+68
-50
lines changed

osu.Framework/Graphics/Containers/ContainerExtensions.cs

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,9 @@
44
using osuTK;
55
using System;
66
using System.Collections.Generic;
7+
using System.Linq;
78
using osu.Framework.Extensions.EnumExtensions;
9+
using osu.Framework.Extensions.ObjectExtensions;
810

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

9395
return container;
9496
}
97+
98+
/// <summary>
99+
/// Searches the subtree for <see cref="ITabbableContainer"/>s and moves focus to the <see cref="ITabbableContainer"/> before/after the one currently focused.
100+
/// </summary>
101+
/// <param name="target">Container to search for valid focus targets in.</param>
102+
/// <param name="reverse">Whether to traverse the container's children in reverse when looking for the next target.</param>
103+
/// <param name="requireFocusedChild">
104+
/// Determines the behaviour when the currently focused drawable isn't rooted at this container.
105+
/// If true, then focus will not be moved.
106+
/// If false, then focus will be moved to the first valid child.
107+
/// </param>
108+
/// <returns>Whether focus was moved to a new <see cref="ITabbableContainer"/>.</returns>
109+
public static bool MoveFocusToNextTabStop(this CompositeDrawable target, bool reverse = false, bool requireFocusedChild = true)
110+
{
111+
var currentlyFocused = target.GetContainingInputManager()?.FocusedDrawable;
112+
113+
if (currentlyFocused == null && requireFocusedChild)
114+
return false;
115+
116+
var focusManager = target.GetContainingFocusManager().AsNonNull();
117+
118+
Stack<Drawable> stack = new Stack<Drawable>();
119+
stack.Push(target); // Extra push for circular tabbing
120+
stack.Push(target);
121+
122+
// 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.
123+
bool started = currentlyFocused == null;
124+
125+
while (stack.Count > 0)
126+
{
127+
var drawable = stack.Pop();
128+
129+
if (!started)
130+
started = ReferenceEquals(drawable, currentlyFocused);
131+
else if (drawable is ITabbableContainer tabbable && tabbable.CanBeTabbedTo && focusManager.ChangeFocus(drawable))
132+
return true;
133+
134+
if (drawable is CompositeDrawable composite)
135+
{
136+
var newChildren = composite.InternalChildren.ToList();
137+
int bound = reverse ? newChildren.Count : 0;
138+
139+
if (!started)
140+
{
141+
// Find currently focused element, to know starting point
142+
int index = newChildren.IndexOf(currentlyFocused);
143+
if (index != -1)
144+
bound = reverse ? index + 1 : index;
145+
}
146+
147+
if (reverse)
148+
{
149+
for (int i = 0; i < bound; i++)
150+
stack.Push(newChildren[i]);
151+
}
152+
else
153+
{
154+
for (int i = newChildren.Count - 1; i >= bound; i--)
155+
stack.Push(newChildren[i]);
156+
}
157+
}
158+
}
159+
160+
return false;
161+
}
95162
}
96163
}

osu.Framework/Graphics/Containers/TabbableContainer.cs

Lines changed: 1 addition & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,6 @@
33

44
#nullable disable
55

6-
using System.Collections.Generic;
7-
using System.Linq;
8-
using osu.Framework.Extensions.ObjectExtensions;
96
using osu.Framework.Input.Events;
107
using osuTK.Input;
118

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

47-
moveToNextTabStop(TabbableContentContainer, e.ShiftPressed);
44+
TabbableContentContainer.MoveFocusToNextTabStop(e.ShiftPressed);
4845
return true;
4946
}
50-
51-
private void moveToNextTabStop(CompositeDrawable target, bool reverse)
52-
{
53-
var focusManager = GetContainingFocusManager().AsNonNull();
54-
55-
Stack<Drawable> stack = new Stack<Drawable>();
56-
stack.Push(target); // Extra push for circular tabbing
57-
stack.Push(target);
58-
59-
bool started = false;
60-
61-
while (stack.Count > 0)
62-
{
63-
var drawable = stack.Pop();
64-
65-
if (!started)
66-
started = ReferenceEquals(drawable, this);
67-
else if (drawable is ITabbableContainer tabbable && tabbable.CanBeTabbedTo && focusManager.ChangeFocus(drawable))
68-
return;
69-
70-
if (drawable is CompositeDrawable composite)
71-
{
72-
var newChildren = composite.InternalChildren.ToList();
73-
int bound = reverse ? newChildren.Count : 0;
74-
75-
if (!started)
76-
{
77-
// Find self, to know starting point
78-
int index = newChildren.IndexOf(this);
79-
if (index != -1)
80-
bound = reverse ? index + 1 : index;
81-
}
82-
83-
if (reverse)
84-
{
85-
for (int i = 0; i < bound; i++)
86-
stack.Push(newChildren[i]);
87-
}
88-
else
89-
{
90-
for (int i = newChildren.Count - 1; i >= bound; i--)
91-
stack.Push(newChildren[i]);
92-
}
93-
}
94-
}
95-
}
9647
}
9748
}

0 commit comments

Comments
 (0)