A game framework written with osu! in mind.
at master 123 lines 5.5 kB view raw
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}