A game framework written with osu! in mind.
at master 16 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; 5using System.Diagnostics; 6using System.Linq; 7using NUnit.Framework; 8using osu.Framework.Graphics; 9using osu.Framework.Graphics.Containers; 10using osu.Framework.Graphics.Shapes; 11using osu.Framework.Input.Events; 12using osu.Framework.Utils; 13using osu.Framework.Testing; 14using osuTK; 15using osuTK.Graphics; 16using osuTK.Input; 17 18namespace osu.Framework.Tests.Visual.Containers 19{ 20 public class TestSceneScrollContainer : ManualInputManagerTestScene 21 { 22 private ScrollContainer<Drawable> scrollContainer; 23 24 [SetUp] 25 public void Setup() => Schedule(Clear); 26 27 [TestCase(0)] 28 [TestCase(100)] 29 public void TestScrollTo(float clampExtension) 30 { 31 const float container_height = 100; 32 const float box_height = 400; 33 34 AddStep("Create scroll container", () => 35 { 36 Add(scrollContainer = new BasicScrollContainer 37 { 38 Anchor = Anchor.Centre, 39 Origin = Anchor.Centre, 40 Size = new Vector2(container_height), 41 ClampExtension = clampExtension, 42 Child = new Box { Size = new Vector2(100, box_height) } 43 }); 44 }); 45 46 scrollTo(-100, box_height - container_height, clampExtension); 47 checkPosition(0); 48 49 scrollTo(100, box_height - container_height, clampExtension); 50 checkPosition(100); 51 52 scrollTo(300, box_height - container_height, clampExtension); 53 checkPosition(300); 54 55 scrollTo(400, box_height - container_height, clampExtension); 56 checkPosition(300); 57 58 scrollTo(500, box_height - container_height, clampExtension); 59 checkPosition(300); 60 } 61 62 private FillFlowContainer fill; 63 64 [Test] 65 public void TestScrollIntoView() 66 { 67 const float item_height = 25; 68 69 AddStep("Create scroll container", () => 70 { 71 Add(scrollContainer = new BasicScrollContainer 72 { 73 Anchor = Anchor.Centre, 74 Origin = Anchor.Centre, 75 Size = new Vector2(item_height * 4), 76 Child = fill = new FillFlowContainer 77 { 78 RelativeSizeAxes = Axes.X, 79 AutoSizeAxes = Axes.Y, 80 Direction = FillDirection.Vertical, 81 }, 82 }); 83 84 for (int i = 0; i < 8; i++) 85 { 86 fill.Add(new Box 87 { 88 Colour = new Color4(RNG.NextSingle(1), RNG.NextSingle(1), RNG.NextSingle(1), 1), 89 RelativeSizeAxes = Axes.X, 90 Height = item_height, 91 }); 92 } 93 }); 94 95 // simple last item (hits bottom of view) 96 scrollIntoView(7, item_height * 4); 97 98 // position doesn't change when item in view 99 scrollIntoView(6, item_height * 4); 100 101 // scroll in reverse without overscrolling 102 scrollIntoView(1, item_height); 103 104 // scroll forwards with small (non-zero) view 105 // current position will change on restore size 106 scrollIntoView(7, item_height * 7, heightAdjust: 15, expectedPostAdjustPosition: 100); 107 108 // scroll backwards with small (non-zero) view 109 // current position won't change on restore size 110 scrollIntoView(2, item_height * 2, heightAdjust: 15, expectedPostAdjustPosition: item_height * 2); 111 112 // test forwards scroll with zero container height 113 scrollIntoView(7, item_height * 7, heightAdjust: 0, expectedPostAdjustPosition: item_height * 4); 114 115 // test backwards scroll with zero container height 116 scrollIntoView(2, item_height * 2, heightAdjust: 0, expectedPostAdjustPosition: item_height * 2); 117 } 118 119 [TestCase(false)] 120 [TestCase(true)] 121 public void TestDraggingScroll(bool withClampExtension) 122 { 123 AddStep("Create scroll container", () => 124 { 125 Add(scrollContainer = new BasicScrollContainer 126 { 127 Anchor = Anchor.Centre, 128 Origin = Anchor.Centre, 129 Size = new Vector2(200), 130 ClampExtension = withClampExtension ? 100 : 0, 131 Child = new Box { Size = new Vector2(200, 300) } 132 }); 133 }); 134 135 AddStep("Click and drag scrollcontainer", () => 136 { 137 InputManager.MoveMouseTo(scrollContainer); 138 InputManager.PressButton(MouseButton.Left); 139 // Required for the dragging state to be set correctly. 140 InputManager.MoveMouseTo(scrollContainer.ToScreenSpace(scrollContainer.LayoutRectangle.Centre + new Vector2(10f))); 141 }); 142 143 AddStep("Move mouse up", () => InputManager.MoveMouseTo(scrollContainer.ScreenSpaceDrawQuad.Centre - new Vector2(0, 400))); 144 checkPosition(withClampExtension ? 200 : 100); 145 AddStep("Move mouse down", () => InputManager.MoveMouseTo(scrollContainer.ScreenSpaceDrawQuad.Centre + new Vector2(0, 400))); 146 checkPosition(withClampExtension ? -100 : 0); 147 AddStep("Release mouse button", () => InputManager.ReleaseButton(MouseButton.Left)); 148 checkPosition(0); 149 } 150 151 [Test] 152 public void TestContentAnchors() 153 { 154 AddStep("Create scroll container with centre-left content", () => 155 { 156 Add(scrollContainer = new BasicScrollContainer 157 { 158 Anchor = Anchor.Centre, 159 Origin = Anchor.Centre, 160 Size = new Vector2(300), 161 ScrollContent = 162 { 163 Anchor = Anchor.CentreLeft, 164 Origin = Anchor.CentreLeft, 165 }, 166 Child = new Box { Size = new Vector2(300, 400) } 167 }); 168 }); 169 170 AddStep("Scroll to 0", () => scrollContainer.ScrollTo(0, false)); 171 AddAssert("Content position at top", () => Precision.AlmostEquals(scrollContainer.ScreenSpaceDrawQuad.TopLeft, scrollContainer.ScrollContent.ScreenSpaceDrawQuad.TopLeft)); 172 } 173 174 [Test] 175 public void TestClampedScrollbar() 176 { 177 AddStep("Create scroll container", () => 178 { 179 Add(scrollContainer = new ClampedScrollbarScrollContainer 180 { 181 Anchor = Anchor.Centre, 182 Origin = Anchor.Centre, 183 Size = new Vector2(500), 184 Child = new FillFlowContainer 185 { 186 AutoSizeAxes = Axes.Both, 187 Direction = FillDirection.Vertical, 188 Children = new[] 189 { 190 new Box { Size = new Vector2(500) }, 191 new Box { Size = new Vector2(500) }, 192 new Box { Size = new Vector2(500) }, 193 } 194 } 195 }); 196 }); 197 198 AddStep("scroll to end", () => scrollContainer.ScrollToEnd(false)); 199 checkScrollbarPosition(250); 200 201 AddStep("scroll to start", () => scrollContainer.ScrollToStart(false)); 202 checkScrollbarPosition(0); 203 } 204 205 [Test] 206 public void TestClampedScrollbarDrag() 207 { 208 ClampedScrollbarScrollContainer clampedContainer = null; 209 210 AddStep("Create scroll container", () => 211 { 212 Add(scrollContainer = clampedContainer = new ClampedScrollbarScrollContainer 213 { 214 Anchor = Anchor.Centre, 215 Origin = Anchor.Centre, 216 Size = new Vector2(500), 217 Child = new FillFlowContainer 218 { 219 AutoSizeAxes = Axes.Both, 220 Direction = FillDirection.Vertical, 221 Children = new[] 222 { 223 new Box { Size = new Vector2(500) }, 224 new Box { Size = new Vector2(500) }, 225 new Box { Size = new Vector2(500) }, 226 } 227 } 228 }); 229 }); 230 231 AddStep("Click scroll bar", () => 232 { 233 InputManager.MoveMouseTo(clampedContainer.Scrollbar); 234 InputManager.PressButton(MouseButton.Left); 235 }); 236 237 // Position at mouse down 238 checkScrollbarPosition(0); 239 240 AddStep("begin drag", () => 241 { 242 // Required for the dragging state to be set correctly. 243 InputManager.MoveMouseTo(clampedContainer.Scrollbar.ToScreenSpace(clampedContainer.Scrollbar.LayoutRectangle.Centre + new Vector2(0, -10f))); 244 }); 245 246 AddStep("Move mouse up", () => InputManager.MoveMouseTo(scrollContainer.ScreenSpaceDrawQuad.TopRight - new Vector2(0, 20))); 247 checkScrollbarPosition(0); 248 AddStep("Move mouse down", () => InputManager.MoveMouseTo(scrollContainer.ScreenSpaceDrawQuad.BottomRight + new Vector2(0, 20))); 249 checkScrollbarPosition(250); 250 AddStep("Release mouse button", () => InputManager.ReleaseButton(MouseButton.Left)); 251 checkScrollbarPosition(250); 252 } 253 254 [Test] 255 public void TestHandleKeyboardRepeatAfterRemoval() 256 { 257 AddStep("create scroll container", () => 258 { 259 Add(scrollContainer = new RepeatCountingScrollContainer 260 { 261 Anchor = Anchor.Centre, 262 Origin = Anchor.Centre, 263 Size = new Vector2(500), 264 Child = new FillFlowContainer 265 { 266 AutoSizeAxes = Axes.Both, 267 Direction = FillDirection.Vertical, 268 Children = new[] 269 { 270 new Box { Size = new Vector2(500) }, 271 new Box { Size = new Vector2(500) }, 272 new Box { Size = new Vector2(500) }, 273 } 274 } 275 }); 276 }); 277 278 AddStep("move mouse to scroll container", () => InputManager.MoveMouseTo(scrollContainer)); 279 AddStep("press page down and remove scroll container", () => InputManager.PressKey(Key.PageDown)); 280 AddStep("remove scroll container", () => 281 { 282 Remove(scrollContainer); 283 ((RepeatCountingScrollContainer)scrollContainer).RepeatCount = 0; 284 }); 285 286 AddWaitStep("wait for repeats", 5); 287 } 288 289 [Test] 290 public void TestEmptyScrollContainerDoesNotHandleScrollAndDrag() 291 { 292 AddStep("create scroll container", () => 293 { 294 Add(scrollContainer = new InputHandlingScrollContainer 295 { 296 Anchor = Anchor.Centre, 297 Origin = Anchor.Centre, 298 Size = new Vector2(500), 299 }); 300 }); 301 302 AddStep("Perform scroll", () => 303 { 304 InputManager.MoveMouseTo(scrollContainer); 305 InputManager.ScrollVerticalBy(50); 306 }); 307 308 AddAssert("Scroll was not handled", () => 309 { 310 var inputHandlingScrollContainer = (InputHandlingScrollContainer)scrollContainer; 311 return inputHandlingScrollContainer.ScrollHandled.HasValue && !inputHandlingScrollContainer.ScrollHandled.Value; 312 }); 313 314 AddStep("Perform drag", () => 315 { 316 InputManager.MoveMouseTo(scrollContainer); 317 InputManager.PressButton(MouseButton.Left); 318 InputManager.MoveMouseTo(scrollContainer, new Vector2(50)); 319 }); 320 321 AddAssert("Drag was not handled", () => 322 { 323 var inputHandlingScrollContainer = (InputHandlingScrollContainer)scrollContainer; 324 return inputHandlingScrollContainer.DragHandled.HasValue && !inputHandlingScrollContainer.DragHandled.Value; 325 }); 326 } 327 328 private void scrollIntoView(int index, float expectedPosition, float? heightAdjust = null, float? expectedPostAdjustPosition = null) 329 { 330 if (heightAdjust != null) 331 AddStep("set container height zero", () => scrollContainer.Height = heightAdjust.Value); 332 333 AddStep($"scroll {index} into view", () => scrollContainer.ScrollIntoView(fill.Skip(index).First())); 334 AddUntilStep($"{index} is visible", () => !fill.Skip(index).First().IsMaskedAway); 335 checkPosition(expectedPosition); 336 337 if (heightAdjust != null) 338 { 339 Debug.Assert(expectedPostAdjustPosition != null, nameof(expectedPostAdjustPosition) + " != null"); 340 341 AddStep("restore height", () => scrollContainer.Height = 100); 342 checkPosition(expectedPostAdjustPosition.Value); 343 } 344 } 345 346 private void scrollTo(float position, float scrollContentHeight, float extension) 347 { 348 float clampedTarget = Math.Clamp(position, -extension, scrollContentHeight + extension); 349 350 float immediateScrollPosition = 0; 351 352 AddStep($"scroll to {position}", () => 353 { 354 scrollContainer.ScrollTo(position, false); 355 immediateScrollPosition = scrollContainer.Current; 356 }); 357 358 AddAssert($"immediately scrolled to {clampedTarget}", () => Precision.AlmostEquals(clampedTarget, immediateScrollPosition, 1)); 359 } 360 361 private void checkPosition(float expected) => AddUntilStep($"position at {expected}", () => Precision.AlmostEquals(expected, scrollContainer.Current, 1)); 362 363 private void checkScrollbarPosition(float expected) => 364 AddUntilStep($"scrollbar position at {expected}", () => Precision.AlmostEquals(expected, scrollContainer.InternalChildren[1].DrawPosition.Y, 1)); 365 366 private class RepeatCountingScrollContainer : BasicScrollContainer 367 { 368 public int RepeatCount { get; set; } 369 370 protected override bool OnKeyDown(KeyDownEvent e) 371 { 372 if (e.Repeat) 373 RepeatCount++; 374 375 return base.OnKeyDown(e); 376 } 377 } 378 379 private class ClampedScrollbarScrollContainer : BasicScrollContainer 380 { 381 public new ScrollbarContainer Scrollbar => base.Scrollbar; 382 383 protected override ScrollbarContainer CreateScrollbar(Direction direction) => new ClampedScrollbar(direction); 384 385 private class ClampedScrollbar : BasicScrollbar 386 { 387 protected internal override float MinimumDimSize => 250; 388 389 public ClampedScrollbar(Direction direction) 390 : base(direction) 391 { 392 } 393 } 394 } 395 396 private class InputHandlingScrollContainer : BasicScrollContainer 397 { 398 public bool? ScrollHandled { get; private set; } 399 public bool? DragHandled { get; private set; } 400 401 protected override bool OnScroll(ScrollEvent e) 402 { 403 ScrollHandled = base.OnScroll(e); 404 return ScrollHandled.Value; 405 } 406 407 protected override bool OnDragStart(DragStartEvent e) 408 { 409 DragHandled = base.OnDragStart(e); 410 return DragHandled.Value; 411 } 412 } 413 } 414}