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;
5using System.Collections.Generic;
6using System.Linq;
7using JetBrains.Annotations;
8using osu.Framework.Graphics;
9using osu.Framework.Input.Events;
10using osu.Framework.Input.StateChanges;
11using osu.Framework.Input.States;
12using osu.Framework.Logging;
13
14namespace osu.Framework.Input
15{
16 /// <summary>
17 /// Manages state and events for a single button.
18 /// </summary>
19 public abstract class ButtonEventManager<TButton>
20 {
21 /// <summary>
22 /// The button this <see cref="ButtonEventManager{TButton}"/> manages.
23 /// </summary>
24 public readonly TButton Button;
25
26 /// <summary>
27 /// The input queue for propagating button up events.
28 /// This is created from <see cref="InputQueue"/> when the button is pressed.
29 /// </summary>
30 [CanBeNull]
31 protected List<Drawable> ButtonDownInputQueue { get; private set; }
32
33 /// <summary>
34 /// The input queue.
35 /// </summary>
36 [NotNull]
37 protected IEnumerable<Drawable> InputQueue => GetInputQueue.Invoke() ?? Enumerable.Empty<Drawable>();
38
39 /// <summary>
40 /// A function to retrieve the input queue.
41 /// </summary>
42 internal Func<IEnumerable<Drawable>> GetInputQueue;
43
44 protected ButtonEventManager(TButton button)
45 {
46 Button = button;
47 }
48
49 /// <summary>
50 /// Handles the button state changing.
51 /// </summary>
52 /// <param name="state">The current <see cref="InputState"/>.</param>
53 /// <param name="kind">The type of change in the button's state.</param>
54 public void HandleButtonStateChange(InputState state, ButtonStateChangeKind kind)
55 {
56 if (kind == ButtonStateChangeKind.Pressed)
57 handleButtonDown(state);
58 else
59 handleButtonUp(state);
60 }
61
62 /// <summary>
63 /// Handles the button being pressed.
64 /// </summary>
65 /// <param name="state">The current <see cref="InputState"/>.</param>
66 /// <returns>Whether the event was handled.</returns>
67 private bool handleButtonDown(InputState state)
68 {
69 List<Drawable> inputQueue = InputQueue.ToList();
70 Drawable handledBy = HandleButtonDown(state, inputQueue);
71
72 if (handledBy != null)
73 {
74 // only drawables up to the one that handled mouse down should handle mouse up, so remove all subsequent drawables from the queue (for future use).
75 var count = inputQueue.IndexOf(handledBy) + 1;
76 inputQueue.RemoveRange(count, inputQueue.Count - count);
77 }
78
79 ButtonDownInputQueue = inputQueue;
80
81 return handledBy != null;
82 }
83
84 /// <summary>
85 /// Handles the button being pressed.
86 /// </summary>
87 /// <param name="state">The current <see cref="InputState"/>.</param>
88 /// <param name="targets">The list of possible targets that can handle the event.</param>
89 /// <returns>The <see cref="Drawable"/> that handled the event.</returns>
90 protected abstract Drawable HandleButtonDown(InputState state, List<Drawable> targets);
91
92 /// <summary>
93 /// Handles the button being released.
94 /// </summary>
95 /// <param name="state">The current <see cref="InputState"/>.</param>
96 private void handleButtonUp(InputState state)
97 {
98 HandleButtonUp(state, ButtonDownInputQueue);
99 ButtonDownInputQueue = null;
100 }
101
102 /// <summary>
103 /// Handles the button being released.
104 /// </summary>
105 /// <param name="state">The current <see cref="InputState"/>.</param>
106 /// <param name="targets">The list of targets that must handle the event. This will contain targets up to the target that handled the button down event.</param>
107 protected abstract void HandleButtonUp(InputState state, List<Drawable> targets);
108
109 /// <summary>
110 /// Triggers events on drawables in <paramref name="drawables"/> until it is handled.
111 /// </summary>
112 /// <param name="drawables">The drawables in the queue.</param>
113 /// <param name="e">The event.</param>
114 /// <returns>The drawable which handled the event or null if none.</returns>
115 protected Drawable PropagateButtonEvent(IEnumerable<Drawable> drawables, UIEvent e)
116 {
117 var handledBy = drawables.FirstOrDefault(target => target.TriggerEvent(e));
118
119 if (handledBy != null)
120 Logger.Log($"{e} handled by {handledBy}.", LoggingTarget.Runtime, LogLevel.Debug);
121
122 return handledBy;
123 }
124 }
125}