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 NUnit.Framework;
6using osu.Framework.Extensions.Color4Extensions;
7using osu.Framework.Graphics;
8using osu.Framework.Graphics.Containers;
9using osu.Framework.Graphics.Shapes;
10using osu.Framework.Graphics.Sprites;
11using osu.Framework.Input;
12using osu.Framework.Input.Events;
13using osu.Framework.Testing;
14using osuTK;
15using osuTK.Graphics;
16using osuTK.Input;
17
18namespace osu.Framework.Tests.Visual.Drawables
19{
20 public class TestSceneFocus : ManualInputManagerTestScene
21 {
22 private FocusOverlay overlay;
23 private RequestingFocusBox requestingFocus;
24
25 private FocusBox focusTopLeft;
26 private FocusBox focusBottomLeft;
27 private FocusBox focusBottomRight;
28
29 public TestSceneFocus()
30 {
31 RelativeSizeAxes = Axes.Both;
32 }
33
34 [SetUp]
35 public new void SetUp() => Schedule(() =>
36 {
37 Children = new Drawable[]
38 {
39 focusTopLeft = new FocusBox
40 {
41 Anchor = Anchor.TopLeft,
42 Origin = Anchor.TopLeft,
43 },
44 requestingFocus = new RequestingFocusBox
45 {
46 Anchor = Anchor.TopRight,
47 Origin = Anchor.TopRight,
48 },
49 focusBottomLeft = new FocusBox
50 {
51 Anchor = Anchor.BottomLeft,
52 Origin = Anchor.BottomLeft,
53 },
54 focusBottomRight = new FocusBox
55 {
56 Anchor = Anchor.BottomRight,
57 Origin = Anchor.BottomRight,
58 },
59 overlay = new FocusOverlay
60 {
61 Anchor = Anchor.Centre,
62 Origin = Anchor.Centre,
63 }
64 };
65 });
66
67 [Test]
68 public void FocusedOverlayTakesFocusOnShow()
69 {
70 AddAssert("overlay not visible", () => overlay.State.Value == Visibility.Hidden);
71 checkNotFocused(() => overlay);
72
73 AddStep("show overlay", () => overlay.Show());
74 checkFocused(() => overlay);
75
76 AddStep("hide overlay", () => overlay.Hide());
77 checkNotFocused(() => overlay);
78 }
79
80 [Test]
81 public void FocusedOverlayLosesFocusOnClickAway()
82 {
83 AddAssert("overlay not visible", () => overlay.State.Value == Visibility.Hidden);
84 checkNotFocused(() => overlay);
85
86 AddStep("show overlay", () => overlay.Show());
87 checkFocused(() => overlay);
88
89 AddStep("click away", () =>
90 {
91 InputManager.MoveMouseTo(Vector2.One);
92 InputManager.Click(MouseButton.Left);
93 });
94
95 checkNotFocused(() => overlay);
96 checkFocused(() => requestingFocus);
97 }
98
99 [Test]
100 public void RequestsFocusKeepsFocusOnClickAway()
101 {
102 checkFocused(() => requestingFocus);
103
104 AddStep("click away", () =>
105 {
106 InputManager.MoveMouseTo(Vector2.One);
107 InputManager.Click(MouseButton.Left);
108 });
109
110 checkFocused(() => requestingFocus);
111 }
112
113 [Test]
114 public void RequestsFocusLosesFocusOnClickingFocused()
115 {
116 checkFocused(() => requestingFocus);
117
118 AddStep("click top left", () =>
119 {
120 InputManager.MoveMouseTo(focusTopLeft);
121 InputManager.Click(MouseButton.Left);
122 });
123
124 checkFocused(() => focusTopLeft);
125
126 AddStep("click bottom right", () =>
127 {
128 InputManager.MoveMouseTo(focusBottomRight);
129 InputManager.Click(MouseButton.Left);
130 });
131
132 checkFocused(() => focusBottomRight);
133 }
134
135 [Test]
136 public void ShowOverlayInteractions()
137 {
138 AddStep("click bottom left", () =>
139 {
140 InputManager.MoveMouseTo(focusBottomLeft);
141 InputManager.Click(MouseButton.Left);
142 });
143
144 checkFocused(() => focusBottomLeft);
145
146 AddStep("show overlay", () => overlay.Show());
147
148 checkFocused(() => overlay);
149 checkNotFocused(() => focusBottomLeft);
150
151 // click is blocked by overlay so doesn't select bottom left first click
152 AddStep("click", () => InputManager.Click(MouseButton.Left));
153 checkFocused(() => requestingFocus);
154
155 // second click selects bottom left
156 AddStep("click", () => InputManager.Click(MouseButton.Left));
157 checkFocused(() => focusBottomLeft);
158
159 // further click has no effect
160 AddStep("click", () => InputManager.Click(MouseButton.Left));
161 checkFocused(() => focusBottomLeft);
162 }
163
164 [Test]
165 public void InputPropagation()
166 {
167 AddStep("Focus bottom left", () =>
168 {
169 InputManager.MoveMouseTo(focusBottomLeft);
170 InputManager.Click(MouseButton.Left);
171 });
172 AddStep("Press a key (blocking)", () =>
173 {
174 InputManager.PressKey(Key.A);
175 InputManager.ReleaseKey(Key.A);
176 });
177 AddAssert("Received the key", () =>
178 focusBottomLeft.KeyDownCount == 1 && focusBottomLeft.KeyUpCount == 1 &&
179 focusBottomRight.KeyDownCount == 0 && focusBottomRight.KeyUpCount == 0);
180 AddStep("Press a joystick (non blocking)", () =>
181 {
182 InputManager.PressJoystickButton(JoystickButton.Button1);
183 InputManager.ReleaseJoystickButton(JoystickButton.Button1);
184 });
185 AddAssert("Received the joystick button", () =>
186 focusBottomLeft.JoystickPressCount == 1 && focusBottomLeft.JoystickReleaseCount == 1 &&
187 focusBottomRight.JoystickPressCount == 1 && focusBottomRight.JoystickReleaseCount == 1);
188 }
189
190 private void checkFocused(Func<Drawable> d) => AddAssert("check focus", () => d().HasFocus);
191 private void checkNotFocused(Func<Drawable> d) => AddAssert("check not focus", () => !d().HasFocus);
192
193 private class FocusOverlay : FocusedOverlayContainer
194 {
195 private readonly Box box;
196 private readonly SpriteText stateText;
197
198 public FocusOverlay()
199 {
200 RelativeSizeAxes = Axes.Both;
201
202 Children = new Drawable[]
203 {
204 new Box
205 {
206 RelativeSizeAxes = Axes.Both,
207 Colour = Color4.Gray.Opacity(0.5f),
208 },
209 box = new Box
210 {
211 RelativeSizeAxes = Axes.Both,
212 Size = new Vector2(0.4f),
213 Anchor = Anchor.Centre,
214 Origin = Anchor.Centre,
215 Colour = Color4.Blue,
216 },
217 new SpriteText
218 {
219 Text = "FocusedOverlay",
220 Anchor = Anchor.Centre,
221 Origin = Anchor.Centre,
222 },
223 stateText = new SpriteText
224 {
225 Text = "FocusedOverlay",
226 Anchor = Anchor.BottomCentre,
227 Origin = Anchor.BottomCentre,
228 }
229 };
230
231 this.FadeTo(0.2f);
232 }
233
234 protected override void PopIn()
235 {
236 base.PopIn();
237 stateText.Text = State.ToString();
238 }
239
240 protected override void PopOut()
241 {
242 base.PopOut();
243 stateText.Text = State.ToString();
244 }
245
246 public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => true;
247
248 protected override bool OnClick(ClickEvent e)
249 {
250 if (!box.ReceivePositionalInputAt(e.ScreenSpaceMousePosition))
251 {
252 Hide();
253 return true;
254 }
255
256 return base.OnClick(e);
257 }
258
259 protected override void OnFocus(FocusEvent e)
260 {
261 base.OnFocus(e);
262 this.FadeTo(1);
263 }
264
265 protected override void OnFocusLost(FocusLostEvent e)
266 {
267 base.OnFocusLost(e);
268 this.FadeTo(0.2f);
269 }
270 }
271
272 public class RequestingFocusBox : FocusBox
273 {
274 public override bool RequestsFocus => true;
275
276 public RequestingFocusBox()
277 {
278 Box.Colour = Color4.Green;
279
280 AddInternal(new SpriteText
281 {
282 Text = "RequestsFocus",
283 Anchor = Anchor.Centre,
284 Origin = Anchor.Centre,
285 });
286 }
287 }
288
289 public class FocusBox : CompositeDrawable
290 {
291 protected Box Box;
292 public int KeyDownCount, KeyUpCount, JoystickPressCount, JoystickReleaseCount;
293
294 public FocusBox()
295 {
296 AddInternal(Box = new Box
297 {
298 RelativeSizeAxes = Axes.Both,
299 Alpha = 0.5f,
300 Colour = Color4.Red
301 });
302
303 RelativeSizeAxes = Axes.Both;
304 Size = new Vector2(0.4f);
305 }
306
307 protected override bool OnClick(ClickEvent e) => true;
308
309 public override bool AcceptsFocus => true;
310
311 protected override void OnFocus(FocusEvent e)
312 {
313 base.OnFocus(e);
314 Box.FadeTo(1);
315 }
316
317 protected override void OnFocusLost(FocusLostEvent e)
318 {
319 base.OnFocusLost(e);
320 Box.FadeTo(0.5f);
321 }
322
323 // only KeyDown is blocking
324 protected override bool OnKeyDown(KeyDownEvent e)
325 {
326 ++KeyDownCount;
327 return true;
328 }
329
330 protected override void OnKeyUp(KeyUpEvent e)
331 {
332 ++KeyUpCount;
333 base.OnKeyUp(e);
334 }
335
336 protected override bool OnJoystickPress(JoystickPressEvent e)
337 {
338 ++JoystickPressCount;
339 return base.OnJoystickPress(e);
340 }
341
342 protected override void OnJoystickRelease(JoystickReleaseEvent e)
343 {
344 ++JoystickReleaseCount;
345 base.OnJoystickRelease(e);
346 }
347 }
348 }
349}