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.Diagnostics;
6using System.Linq;
7using osu.Framework.Graphics.Containers;
8using osu.Framework.Input;
9
10namespace osu.Framework.Graphics.Cursor
11{
12 public abstract class CursorEffectContainer<TSelf, TTarget> : Container
13 where TSelf : CursorEffectContainer<TSelf, TTarget>
14 where TTarget : class, IDrawable
15 {
16 public override bool HandlePositionalInput => true;
17
18 private InputManager inputManager;
19
20 protected override void LoadComplete()
21 {
22 base.LoadComplete();
23 inputManager = GetContainingInputManager();
24 }
25
26 private readonly HashSet<IDrawable> childDrawables = new HashSet<IDrawable>();
27 private readonly HashSet<IDrawable> nestedTtcChildDrawables = new HashSet<IDrawable>();
28 private readonly List<IDrawable> newChildDrawables = new List<IDrawable>();
29 private readonly List<TTarget> targetChildren = new List<TTarget>();
30
31 private void findTargetChildren()
32 {
33 Debug.Assert(childDrawables.Count == 0, $"{nameof(childDrawables)} should be empty but has {childDrawables.Count} elements.");
34 Debug.Assert(nestedTtcChildDrawables.Count == 0, $"{nameof(nestedTtcChildDrawables)} should be empty but has {nestedTtcChildDrawables.Count} elements.");
35 Debug.Assert(newChildDrawables.Count == 0, $"{nameof(newChildDrawables)} should be empty but has {newChildDrawables.Count} elements.");
36 Debug.Assert(targetChildren.Count == 0, $"{nameof(targetChildren)} should be empty but has {targetChildren.Count} elements.");
37
38 // Skip all drawables in the hierarchy prior to (and including) ourself.
39 var targetCandidates = inputManager.PositionalInputQueue;
40
41 int selfIndex = targetCandidates.IndexOf(this);
42
43 if (selfIndex < 0) return;
44
45 childDrawables.Add(this);
46
47 // keep track of all hovered drawables below this and nested effect containers
48 // so we can decide which ones are valid candidates for receiving our effect and so
49 // we know when we can abort our search.
50 for (int i = selfIndex - 1; i >= 0; i--)
51 {
52 var candidate = targetCandidates[i] as TTarget;
53
54 if (candidate == null)
55 continue;
56
57 if (!candidate.IsHovered)
58 continue;
59
60 // Children of drawables we are responsible for transitively also fall into our subtree,
61 // and therefore we need to handle them. If they are not children of any drawables we handle,
62 // it means that we iterated beyond our subtree and may terminate.
63 IDrawable parent = candidate.Parent;
64
65 // We keep track of all drawables we found while traversing the parent chain upwards.
66 newChildDrawables.Clear();
67 newChildDrawables.Add(candidate);
68
69 // When we encounter a drawable we already encountered before, then there is no need
70 // to keep going upward, since we already recorded it previously. At that point we know
71 // the drawables we found are in fact children of ours.
72 while (!childDrawables.Contains(parent))
73 {
74 // If we reach to the root node (i.e. parent == null), then we found a drawable
75 // which is no longer a child of ours and we may terminate.
76 if (parent == null)
77 return;
78
79 newChildDrawables.Add(parent);
80 parent = parent.Parent;
81 }
82
83 // Assuming we did _not_ end up terminating, then all found drawables are children of ours
84 // and need to be added.
85 childDrawables.UnionWith(newChildDrawables);
86
87 // Keep track of child drawables whose effects are managed by a nested effect container.
88 // Note, that nested effect containers themselves could implement TTarget and
89 // are still our own responsibility to handle.
90 nestedTtcChildDrawables.UnionWith(
91 ((IEnumerable<IDrawable>)newChildDrawables).Reverse()
92 .SkipWhile(d => d.Parent == this || !(d.Parent is TSelf) && !nestedTtcChildDrawables.Contains(d.Parent)));
93
94 // Ignore drawables whose effects are managed by a nested effect container.
95 if (nestedTtcChildDrawables.Contains(candidate))
96 continue;
97
98 // We found a valid candidate; keep track of it
99 targetChildren.Add(candidate);
100 }
101 }
102
103 protected IEnumerable<TTarget> FindTargets()
104 {
105 findTargetChildren();
106
107 // Clean up
108 childDrawables.Clear();
109 nestedTtcChildDrawables.Clear();
110 newChildDrawables.Clear();
111
112 if (targetChildren.Count == 0)
113 return Enumerable.Empty<TTarget>();
114
115 List<TTarget> result = new List<TTarget>(targetChildren);
116 result.Reverse();
117
118 targetChildren.Clear();
119
120 return result;
121 }
122 }
123}