A game framework written with osu! in mind.
1// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
2// See the LICENCE file in the repository root for full licence text.
3
4using System.Collections.Generic;
5using System.Linq;
6using osu.Framework.Input.Events;
7using osuTK.Input;
8
9namespace osu.Framework.Graphics.Containers
10{
11 public class TabbableContainer : TabbableContainer<Drawable>
12 {
13 }
14
15 /// <summary>
16 /// This interface is used for recognizing <see cref="TabbableContainer{T}"/> of any type without reflection.
17 /// </summary>
18 public interface ITabbableContainer
19 {
20 /// <summary>
21 /// Whether this <see cref="ITabbableContainer"/> can be tabbed to.
22 /// </summary>
23 bool CanBeTabbedTo { get; }
24 }
25
26 public class TabbableContainer<T> : Container<T>, ITabbableContainer
27 where T : Drawable
28 {
29 /// <summary>
30 /// Whether this <see cref="TabbableContainer{T}"/> can be tabbed to.
31 /// </summary>
32 public virtual bool CanBeTabbedTo => true;
33
34 /// <summary>
35 /// Allows for tabbing between multiple levels within the TabbableContentContainer.
36 /// </summary>
37 public CompositeDrawable TabbableContentContainer { private get; set; }
38
39 protected override bool OnKeyDown(KeyDownEvent e)
40 {
41 if (TabbableContentContainer == null || e.Key != Key.Tab)
42 return false;
43
44 var nextTab = nextTabStop(TabbableContentContainer, e.ShiftPressed);
45 if (nextTab != null) GetContainingInputManager().ChangeFocus(nextTab);
46 return true;
47 }
48
49 private Drawable nextTabStop(CompositeDrawable target, bool reverse)
50 {
51 Stack<Drawable> stack = new Stack<Drawable>();
52 stack.Push(target); // Extra push for circular tabbing
53 stack.Push(target);
54
55 bool started = false;
56
57 while (stack.Count > 0)
58 {
59 var drawable = stack.Pop();
60
61 if (!started)
62 started = ReferenceEquals(drawable, this);
63 else if (drawable is ITabbableContainer tabbable && tabbable.CanBeTabbedTo)
64 return drawable;
65
66 if (drawable is CompositeDrawable composite)
67 {
68 var newChildren = composite.InternalChildren.ToList();
69 int bound = reverse ? newChildren.Count : 0;
70
71 if (!started)
72 {
73 // Find self, to know starting point
74 int index = newChildren.IndexOf(this);
75 if (index != -1)
76 bound = reverse ? index + 1 : index;
77 }
78
79 if (reverse)
80 {
81 for (int i = 0; i < bound; i++)
82 stack.Push(newChildren[i]);
83 }
84 else
85 {
86 for (int i = newChildren.Count - 1; i >= bound; i--)
87 stack.Push(newChildren[i]);
88 }
89 }
90 }
91
92 return null;
93 }
94 }
95}