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.Linq;
5using System;
6using osu.Framework.Graphics;
7using osu.Framework.Graphics.Containers;
8using osu.Framework.Input;
9using osu.Framework.Input.Events;
10using osu.Framework.Input.Handlers;
11using osu.Framework.Input.StateChanges;
12using osu.Framework.Platform;
13using osuTK;
14using osuTK.Input;
15
16namespace osu.Framework.Testing.Input
17{
18 public class ManualInputManager : PassThroughInputManager
19 {
20 private readonly ManualInputHandler handler;
21
22 protected override Container<Drawable> Content => content;
23
24 private bool showVisualCursorGuide = true;
25
26 /// <summary>
27 /// Whether to show a visible cursor tracking position and clicks.
28 /// Generally should be enabled unless it blocks the test's content.
29 /// </summary>
30 public bool ShowVisualCursorGuide
31 {
32 get => showVisualCursorGuide;
33 set
34 {
35 if (value == showVisualCursorGuide)
36 return;
37
38 showVisualCursorGuide = value;
39 testCursor.State.Value = value ? Visibility.Visible : Visibility.Hidden;
40 }
41 }
42
43 private readonly Container content;
44
45 private readonly TestCursorContainer testCursor;
46
47 private readonly LocalPlatformActionContainer platformActionContainer;
48
49 public override bool UseParentInput
50 {
51 get => base.UseParentInput;
52 set
53 {
54 base.UseParentInput = value;
55 platformActionContainer.ShouldHandle = !value;
56 }
57 }
58
59 public ManualInputManager()
60 {
61 AddHandler(handler = new ManualInputHandler());
62
63 InternalChildren = new Drawable[]
64 {
65 platformActionContainer = new LocalPlatformActionContainer().WithChild(content = new Container { RelativeSizeAxes = Axes.Both }),
66 testCursor = new TestCursorContainer(),
67 };
68
69 UseParentInput = true;
70 }
71
72 public void Input(IInput input)
73 {
74 UseParentInput = false;
75 handler.EnqueueInput(input);
76 }
77
78 /// <summary>
79 /// Press a key down. Release with <see cref="ReleaseKey"/>.
80 /// </summary>
81 /// <remarks>
82 /// To press and release a key immediately, use <see cref="Key"/>.
83 /// </remarks>
84 /// <param name="key">The key to press.</param>
85 public void PressKey(Key key) => Input(new KeyboardKeyInput(key, true));
86
87 /// <summary>
88 /// Release a pressed key.
89 /// </summary>
90 /// <param name="key">The key to release.</param>
91 public void ReleaseKey(Key key) => Input(new KeyboardKeyInput(key, false));
92
93 /// <summary>
94 /// Press and release the specified key.
95 /// </summary>
96 /// <param name="key">The key to actuate.</param>
97 public void Key(Key key)
98 {
99 PressKey(key);
100 ReleaseKey(key);
101 }
102
103 /// <summary>
104 /// Press and release the keys in the specified <see cref="PlatformAction"/>.
105 /// </summary>
106 /// <param name="action">The platform action to actuate.</param>
107 public void Keys(PlatformAction action)
108 {
109 var binding = Host.PlatformKeyBindings.First(b => (PlatformAction)b.Action == action);
110
111 foreach (var k in binding.KeyCombination.Keys)
112 PressKey((Key)k);
113
114 foreach (var k in binding.KeyCombination.Keys)
115 ReleaseKey((Key)k);
116 }
117
118 public void ScrollBy(Vector2 delta, bool isPrecise = false) => Input(new MouseScrollRelativeInput { Delta = delta, IsPrecise = isPrecise });
119 public void ScrollHorizontalBy(float delta, bool isPrecise = false) => ScrollBy(new Vector2(delta, 0), isPrecise);
120 public void ScrollVerticalBy(float delta, bool isPrecise = false) => ScrollBy(new Vector2(0, delta), isPrecise);
121
122 public void MoveMouseTo(Drawable drawable, Vector2? offset = null) => MoveMouseTo(drawable.ToScreenSpace(drawable.LayoutRectangle.Centre) + (offset ?? Vector2.Zero));
123 public void MoveMouseTo(Vector2 position) => Input(new MousePositionAbsoluteInput { Position = position });
124
125 public void MoveTouchTo(Touch touch) => Input(new TouchInput(touch, CurrentState.Touch.IsActive(touch.Source)));
126
127 public new bool TriggerClick() =>
128 throw new InvalidOperationException($"To trigger a click via a {nameof(ManualInputManager)} use {nameof(Click)} instead.");
129
130 /// <summary>
131 /// Press and release the specified button.
132 /// </summary>
133 /// <param name="button">The button to press and release.</param>
134 public void Click(MouseButton button)
135 {
136 PressButton(button);
137 ReleaseButton(button);
138 }
139
140 /// <summary>
141 /// Press a mouse button down. Release with <see cref="ReleaseButton"/>.
142 /// </summary>
143 /// <remarks>
144 /// To press and release a mouse button immediately, use <see cref="Click"/>.
145 /// </remarks>
146 /// <param name="button">The button to press.</param>
147 public void PressButton(MouseButton button) => Input(new MouseButtonInput(button, true));
148
149 /// <summary>
150 /// Release a pressed mouse button.
151 /// </summary>
152 /// <param name="button">The button to release.</param>
153 public void ReleaseButton(MouseButton button) => Input(new MouseButtonInput(button, false));
154
155 public void PressJoystickButton(JoystickButton button) => Input(new JoystickButtonInput(button, true));
156 public void ReleaseJoystickButton(JoystickButton button) => Input(new JoystickButtonInput(button, false));
157
158 public void BeginTouch(Touch touch) => Input(new TouchInput(touch, true));
159 public void EndTouch(Touch touch) => Input(new TouchInput(touch, false));
160
161 public void PressMidiKey(MidiKey key, byte velocity) => Input(new MidiKeyInput(key, velocity, true));
162 public void ReleaseMidiKey(MidiKey key, byte velocity) => Input(new MidiKeyInput(key, velocity, false));
163
164 public void PressTabletPenButton(TabletPenButton penButton) => Input(new TabletPenButtonInput(penButton, true));
165 public void ReleaseTabletPenButton(TabletPenButton penButton) => Input(new TabletPenButtonInput(penButton, false));
166
167 public void PressTabletAuxiliaryButton(TabletAuxiliaryButton auxiliaryButton) => Input(new TabletAuxiliaryButtonInput(auxiliaryButton, true));
168 public void ReleaseTabletAuxiliaryButton(TabletAuxiliaryButton auxiliaryButton) => Input(new TabletAuxiliaryButtonInput(auxiliaryButton, false));
169
170 private class LocalPlatformActionContainer : PlatformActionContainer
171 {
172 public bool ShouldHandle;
173
174 protected override bool Handle(UIEvent e)
175 {
176 if (!ShouldHandle)
177 return false;
178
179 return base.Handle(e);
180 }
181 }
182
183 private class ManualInputHandler : InputHandler
184 {
185 public override bool Initialize(GameHost host) => true;
186 public override bool IsActive => true;
187
188 public void EnqueueInput(IInput input)
189 {
190 PendingInputs.Enqueue(input);
191 }
192 }
193 }
194}