A game framework written with osu! in mind.
at master 671 lines 25 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.Linq; 6using NUnit.Framework; 7using osu.Framework.Extensions; 8using osu.Framework.Graphics; 9using osu.Framework.Graphics.Containers; 10using osu.Framework.Graphics.Cursor; 11using osu.Framework.Graphics.Shapes; 12using osu.Framework.Graphics.Sprites; 13using osu.Framework.Graphics.UserInterface; 14using osu.Framework.Input.Events; 15using osu.Framework.Testing; 16using osuTK; 17using osuTK.Graphics; 18using osuTK.Input; 19 20namespace osu.Framework.Tests.Visual.UserInterface 21{ 22 public class TestScenePopoverContainer : ManualInputManagerTestScene 23 { 24 private Container[,] cells; 25 private Container popoverWrapper; 26 private PopoverContainer popoverContainer; 27 private GridContainer gridContainer; 28 29 [SetUpSteps] 30 public void SetUpSteps() 31 { 32 AddStep("create popover container", () => 33 { 34 Child = popoverWrapper = new Container 35 { 36 RelativeSizeAxes = Axes.Both, 37 Anchor = Anchor.Centre, 38 Origin = Anchor.Centre, 39 Masking = true, 40 BorderThickness = 5, 41 BorderColour = Colour4.White, 42 Children = new Drawable[] 43 { 44 new Box 45 { 46 RelativeSizeAxes = Axes.Both, 47 AlwaysPresent = true, 48 Colour = Colour4.Transparent 49 }, 50 popoverContainer = new PopoverContainer 51 { 52 RelativeSizeAxes = Axes.Both, 53 Padding = new MarginPadding(5), 54 Children = new Drawable[] 55 { 56 new ClickableContainer 57 { 58 RelativeSizeAxes = Axes.Both, 59 Size = new Vector2(0.5f), 60 Children = new Drawable[] 61 { 62 new Box 63 { 64 Colour = Color4.Blue, 65 RelativeSizeAxes = Axes.Both, 66 }, 67 new TextFlowContainer 68 { 69 AutoSizeAxes = Axes.X, 70 TextAnchor = Anchor.TopCentre, 71 Anchor = Anchor.Centre, 72 Origin = Anchor.Centre, 73 Text = "click blocking container between\nPopover creator and PopoverContainer" 74 } 75 } 76 }, 77 gridContainer = new GridContainer 78 { 79 RelativeSizeAxes = Axes.Both 80 } 81 } 82 } 83 } 84 }; 85 86 cells = new Container[3, 3]; 87 88 for (int r = 0; r < 3; r++) 89 { 90 for (int c = 0; c < 3; c++) 91 cells[r, c] = new Container { RelativeSizeAxes = Axes.Both }; 92 } 93 94 gridContainer.Content = cells.ToJagged(); 95 }); 96 } 97 98 [Test] 99 public void TestShowHide() 100 { 101 createContent(button => new BasicPopover 102 { 103 Child = new SpriteText 104 { 105 Text = $"{button.Anchor} popover" 106 } 107 }); 108 109 AddStep("click button", () => 110 { 111 InputManager.MoveMouseTo(this.ChildrenOfType<DrawableWithPopover>().First()); 112 InputManager.Click(MouseButton.Left); 113 }); 114 AddAssert("popover shown", () => this.ChildrenOfType<Popover>().Any(popover => popover.State.Value == Visibility.Visible)); 115 116 AddStep("click popover", () => 117 { 118 InputManager.MoveMouseTo(this.ChildrenOfType<Popover>().Single().Body); 119 InputManager.Click(MouseButton.Left); 120 }); 121 AddAssert("popover still visible", () => this.ChildrenOfType<Popover>().Single().State.Value == Visibility.Visible); 122 123 AddStep("click away", () => 124 { 125 InputManager.MoveMouseTo(this.ChildrenOfType<DrawableWithPopover>().First().ScreenSpaceDrawQuad.BottomRight + new Vector2(10)); 126 InputManager.Click(MouseButton.Left); 127 }); 128 AddAssert("all hidden", () => this.ChildrenOfType<Popover>().All(popover => popover.State.Value != Visibility.Visible)); 129 } 130 131 [Test] 132 public void TestHideViaKeyboard() 133 { 134 createContent(button => new BasicPopover 135 { 136 Child = new SpriteText 137 { 138 Text = $"{button.Anchor} popover" 139 } 140 }); 141 142 AddStep("click button", () => 143 { 144 InputManager.MoveMouseTo(this.ChildrenOfType<DrawableWithPopover>().First()); 145 InputManager.Click(MouseButton.Left); 146 }); 147 AddAssert("popover shown", () => this.ChildrenOfType<Popover>().Any(popover => popover.State.Value == Visibility.Visible)); 148 149 AddStep("press Escape", () => InputManager.Key(Key.Escape)); 150 AddAssert("all hidden", () => this.ChildrenOfType<Popover>().All(popover => popover.State.Value != Visibility.Visible)); 151 } 152 153 [Test] 154 public void TestShowHideViaExtensionMethod() 155 { 156 createContent(button => new BasicPopover 157 { 158 Child = new SpriteText 159 { 160 Text = $"{button.Anchor} popover" 161 } 162 }); 163 164 AddStep("show popover manually", () => this.ChildrenOfType<DrawableWithPopover>().First().ShowPopover()); 165 AddAssert("popover shown", () => this.ChildrenOfType<Popover>().Any(popover => popover.State.Value == Visibility.Visible)); 166 167 AddStep("hide popover manually", () => popoverContainer.HidePopover()); 168 AddAssert("all hidden", () => this.ChildrenOfType<Popover>().All(popover => popover.State.Value != Visibility.Visible)); 169 } 170 171 [Test] 172 public void TestClickBetweenMultiple() 173 { 174 createContent(button => new BasicPopover 175 { 176 Name = button.Anchor.ToString(), 177 Child = new SpriteText 178 { 179 Text = $"{button.Anchor} popover" 180 } 181 }); 182 183 AddStep("click button", () => 184 { 185 InputManager.MoveMouseTo(this.ChildrenOfType<DrawableWithPopover>().First()); 186 InputManager.Click(MouseButton.Left); 187 }); 188 189 AddAssert("first shown", () => this.ChildrenOfType<Popover>().Single().Name == Anchor.TopLeft.ToString()); 190 191 AddStep("click last button", () => 192 { 193 InputManager.MoveMouseTo(this.ChildrenOfType<DrawableWithPopover>().Last()); 194 InputManager.Click(MouseButton.Left); 195 }); 196 197 AddAssert("last shown", () => this.ChildrenOfType<Popover>().Single().Name == Anchor.BottomRight.ToString()); 198 } 199 200 [Test] 201 public void TestDragAwayDoesntHide() 202 { 203 createContent(button => new BasicPopover 204 { 205 Child = new SpriteText 206 { 207 Text = $"{button.Anchor} popover" 208 } 209 }); 210 211 AddStep("click button", () => 212 { 213 InputManager.MoveMouseTo(this.ChildrenOfType<DrawableWithPopover>().First()); 214 InputManager.Click(MouseButton.Left); 215 }); 216 217 AddAssert("popover shown", () => this.ChildrenOfType<Popover>().Any(popover => popover.State.Value == Visibility.Visible)); 218 219 AddStep("mousedown popover", () => 220 { 221 InputManager.MoveMouseTo(this.ChildrenOfType<Popover>().Single().Body); 222 InputManager.PressButton(MouseButton.Left); 223 }); 224 AddAssert("popover still visible", () => this.ChildrenOfType<Popover>().Single().State.Value == Visibility.Visible); 225 226 AddStep("move away", () => InputManager.MoveMouseTo(this.ChildrenOfType<DrawableWithPopover>().Last())); 227 228 AddStep("release button", () => InputManager.ReleaseButton(MouseButton.Left)); 229 230 AddAssert("popover remains", () => this.ChildrenOfType<Popover>().Any(popover => popover.State.Value == Visibility.Visible)); 231 } 232 233 [Test] 234 public void TestInteractiveContent() 235 { 236 createContent(button => 237 { 238 TextBox textBox; 239 240 return new AnimatedPopover 241 { 242 Child = new FillFlowContainer 243 { 244 Direction = FillDirection.Vertical, 245 Width = 200, 246 AutoSizeAxes = Axes.Y, 247 Spacing = new Vector2(5), 248 Children = new Drawable[] 249 { 250 textBox = new BasicTextBox 251 { 252 PlaceholderText = $"{button.Anchor} text box", 253 Height = 30, 254 RelativeSizeAxes = Axes.X 255 }, 256 new BasicButton 257 { 258 RelativeSizeAxes = Axes.X, 259 Height = 30, 260 Text = "Clear", 261 Action = () => textBox.Text = string.Empty 262 } 263 } 264 } 265 }; 266 }); 267 268 AddStep("click button", () => 269 { 270 InputManager.MoveMouseTo(this.ChildrenOfType<DrawableWithPopover>().First()); 271 InputManager.Click(MouseButton.Left); 272 }); 273 274 AddAssert("popover shown", () => this.ChildrenOfType<Popover>().Any(popover => popover.State.Value == Visibility.Visible)); 275 276 AddStep("click textbox", () => 277 { 278 InputManager.MoveMouseTo(this.ChildrenOfType<TextBox>().First()); 279 InputManager.Click(MouseButton.Left); 280 }); 281 282 AddAssert("textbox is focused", () => InputManager.FocusedDrawable is TextBox); 283 AddAssert("popover still shown", () => this.ChildrenOfType<Popover>().Any(popover => popover.State.Value == Visibility.Visible)); 284 AddStep("click in popover", () => 285 { 286 InputManager.MoveMouseTo(this.ChildrenOfType<Popover>().First().Body.ScreenSpaceDrawQuad.TopLeft + Vector2.One); 287 InputManager.Click(MouseButton.Left); 288 }); 289 290 AddAssert("popover is focused", () => InputManager.FocusedDrawable is Popover); 291 AddAssert("popover still shown", () => this.ChildrenOfType<Popover>().Any(popover => popover.State.Value == Visibility.Visible)); 292 } 293 294 [Test] 295 public void TestAutomaticLayouting() 296 { 297 DrawableWithPopover target = null; 298 299 AddStep("add button", () => popoverContainer.Child = target = new DrawableWithPopover 300 { 301 Width = 200, 302 Height = 30, 303 RelativePositionAxes = Axes.Both, 304 Text = "open", 305 CreateContent = _ => new BasicPopover 306 { 307 Child = new SpriteText 308 { 309 Text = "This popover follows its associated UI component", 310 Size = new Vector2(400) 311 } 312 } 313 }); 314 315 AddSliderStep("move X", 0f, 1, 0, x => 316 { 317 if (target != null) 318 target.X = x; 319 }); 320 321 AddSliderStep("move Y", 0f, 1, 0, y => 322 { 323 if (target != null) 324 target.Y = y; 325 }); 326 327 AddSliderStep("container width", 0f, 1, 1, width => 328 { 329 if (popoverWrapper != null) 330 popoverWrapper.Width = width; 331 }); 332 333 AddSliderStep("container height", 0f, 1, 1, height => 334 { 335 if (popoverWrapper != null) 336 popoverWrapper.Height = height; 337 }); 338 } 339 340 [Test] 341 public void TestAutoSize() 342 { 343 AddStep("create content", () => 344 { 345 popoverWrapper.RelativeSizeAxes = popoverContainer.RelativeSizeAxes = Axes.X; 346 popoverWrapper.AutoSizeAxes = popoverContainer.AutoSizeAxes = Axes.Y; 347 348 popoverContainer.Child = new Container 349 { 350 RelativeSizeAxes = Axes.X, 351 Height = 200, 352 Child = new DrawableWithPopover 353 { 354 Width = 200, 355 Height = 30, 356 Text = "open", 357 CreateContent = _ => new BasicPopover 358 { 359 Child = new SpriteText 360 { 361 Text = "I'm in an auto-sized container!" 362 } 363 } 364 } 365 }; 366 }); 367 368 AddSliderStep("change content height", 100, 500, 200, height => 369 { 370 if (popoverContainer?.Children.Count == 1) 371 popoverContainer.Child.Height = height; 372 }); 373 } 374 375 [Test] 376 public void TestExternalPopoverControl() 377 { 378 TextBoxWithPopover target = null; 379 380 AddStep("create content", () => 381 { 382 popoverContainer.Child = target = new TextBoxWithPopover 383 { 384 Width = 200, 385 Height = 30, 386 PlaceholderText = "focus to show popover" 387 }; 388 }); 389 390 AddStep("click text box", () => 391 { 392 InputManager.MoveMouseTo(target); 393 InputManager.Click(MouseButton.Left); 394 }); 395 AddAssert("popover shown", () => this.ChildrenOfType<Popover>().Any()); 396 397 AddStep("take away text box focus", () => InputManager.ChangeFocus(null)); 398 AddAssert("popover hidden", () => !this.ChildrenOfType<Popover>().Any()); 399 } 400 401 [Test] 402 public void TestPopoverCleanupOnTargetDisposal() 403 { 404 DrawableWithPopover target = null; 405 406 AddStep("add button", () => popoverContainer.Child = target = new DrawableWithPopover 407 { 408 Width = 200, 409 Height = 30, 410 Anchor = Anchor.Centre, 411 Origin = Anchor.Centre, 412 Text = "open", 413 CreateContent = _ => new BasicPopover 414 { 415 Child = new SpriteText 416 { 417 Text = "This popover should be cleaned up when its button is removed", 418 } 419 } 420 }); 421 422 AddStep("click button", () => 423 { 424 InputManager.MoveMouseTo(target); 425 InputManager.Click(MouseButton.Left); 426 }); 427 AddAssert("popover created", () => this.ChildrenOfType<Popover>().Any()); 428 429 AddStep("dispose of button", () => popoverContainer.Clear()); 430 AddUntilStep("no popover present", () => !this.ChildrenOfType<Popover>().Any()); 431 } 432 433 [Test] 434 public void TestPopoverCleanupOnTargetHide() 435 { 436 DrawableWithPopover target = null; 437 438 AddStep("add button", () => popoverContainer.Child = target = new DrawableWithPopover 439 { 440 Width = 200, 441 Height = 30, 442 Anchor = Anchor.Centre, 443 Origin = Anchor.Centre, 444 Text = "open", 445 CreateContent = _ => new BasicPopover 446 { 447 Child = new SpriteText 448 { 449 Text = "This popover should be cleaned up when its button is hidden", 450 } 451 } 452 }); 453 454 AddStep("click button", () => 455 { 456 InputManager.MoveMouseTo(target); 457 InputManager.Click(MouseButton.Left); 458 }); 459 AddAssert("popover created", () => this.ChildrenOfType<Popover>().Any()); 460 461 AddStep("hide button", () => target.Hide()); 462 AddUntilStep("no popover present", () => !this.ChildrenOfType<Popover>().Any()); 463 } 464 465 [Test] 466 public void TestPopoverEventHandling() 467 { 468 EventHandlingContainer eventHandlingContainer = null; 469 DrawableWithPopover target = null; 470 471 AddStep("add button", () => popoverContainer.Child = eventHandlingContainer = new EventHandlingContainer 472 { 473 RelativeSizeAxes = Axes.Both, 474 Child = target = new DrawableWithPopover 475 { 476 Width = 200, 477 Height = 30, 478 Anchor = Anchor.Centre, 479 Origin = Anchor.Centre, 480 Text = "open", 481 CreateContent = _ => new BasicPopover 482 { 483 Child = new SpriteText 484 { 485 Text = "This popover should be handle hover and click events", 486 } 487 } 488 } 489 }); 490 491 AddStep("click button", () => 492 { 493 InputManager.MoveMouseTo(target); 494 InputManager.Click(MouseButton.Left); 495 }); 496 497 AddAssert("container received hover", () => eventHandlingContainer.HoverReceived); 498 499 AddAssert("popover created", () => this.ChildrenOfType<Popover>().Any()); 500 501 AddStep("mouse over popover", () => 502 { 503 eventHandlingContainer.Reset(); 504 InputManager.MoveMouseTo(this.ChildrenOfType<Popover>().Single().Body); 505 }); 506 507 AddAssert("container did not receive hover", () => !eventHandlingContainer.HoverReceived); 508 509 AddStep("click on popover", () => InputManager.Click(MouseButton.Left)); 510 AddAssert("container did not receive click", () => !eventHandlingContainer.ClickReceived); 511 512 AddStep("dismiss popover", () => 513 { 514 InputManager.MoveMouseTo(eventHandlingContainer.ScreenSpaceDrawQuad.TopLeft + new Vector2(10)); 515 InputManager.Click(MouseButton.Left); 516 }); 517 518 AddAssert("container received hover", () => eventHandlingContainer.HoverReceived); 519 AddStep("click again", () => InputManager.Click(MouseButton.Left)); 520 AddAssert("container received click", () => eventHandlingContainer.ClickReceived); 521 } 522 523 private void createContent(Func<DrawableWithPopover, Popover> creationFunc) 524 => AddStep("create content", () => 525 { 526 for (int i = 0; i < 3; ++i) 527 { 528 for (int j = 0; j < 3; ++j) 529 { 530 Anchor popoverAnchor = 0; 531 popoverAnchor |= (Anchor)((int)Anchor.x0 << i); 532 popoverAnchor |= (Anchor)((int)Anchor.y0 << j); 533 534 cells[j, i].Child = new DrawableWithPopover 535 { 536 Width = 200, 537 Height = 30, 538 Text = $"open {popoverAnchor}", 539 Anchor = popoverAnchor, 540 Origin = popoverAnchor, 541 CreateContent = creationFunc 542 }; 543 } 544 } 545 }); 546 547 private class AnimatedPopover : BasicPopover 548 { 549 protected override void PopIn() => this.FadeIn(300, Easing.OutQuint); 550 protected override void PopOut() => this.FadeOut(300, Easing.OutQuint); 551 } 552 553 private class DrawableWithPopover : CircularContainer, IHasPopover 554 { 555 public Func<DrawableWithPopover, Popover> CreateContent { get; set; } 556 557 public string Text 558 { 559 set => spriteText.Text = value; 560 } 561 562 private readonly SpriteText spriteText; 563 564 public DrawableWithPopover() 565 { 566 Masking = true; 567 BorderThickness = 4; 568 BorderColour = FrameworkColour.YellowGreenDark; 569 570 Children = new Drawable[] 571 { 572 new Box 573 { 574 RelativeSizeAxes = Axes.Both, 575 Colour = FrameworkColour.GreenDark 576 }, 577 spriteText = new SpriteText 578 { 579 Anchor = Anchor.Centre, 580 Origin = Anchor.Centre, 581 Font = FontUsage.Default.With(italics: true) 582 } 583 }; 584 } 585 586 public Popover GetPopover() => CreateContent.Invoke(this); 587 588 protected override bool OnClick(ClickEvent e) 589 { 590 this.ShowPopover(); 591 return true; 592 } 593 } 594 595 private class TextBoxWithPopover : BasicTextBox, IHasPopover 596 { 597 protected override void OnFocus(FocusEvent e) 598 { 599 base.OnFocus(e); 600 this.ShowPopover(); 601 } 602 603 protected override void OnFocusLost(FocusLostEvent e) 604 { 605 base.OnFocusLost(e); 606 this.HidePopover(); 607 } 608 609 public Popover GetPopover() => new BasicPopover 610 { 611 Child = new SpriteText 612 { 613 Text = "the text box has focus now!" 614 } 615 }; 616 } 617 618 private class EventHandlingContainer : Container 619 { 620 private readonly Box colourBox; 621 622 public bool ClickReceived { get; private set; } 623 public bool HoverReceived { get; private set; } 624 625 protected override Container<Drawable> Content { get; } 626 627 public EventHandlingContainer() 628 { 629 AddInternal(new Container 630 { 631 RelativeSizeAxes = Axes.Both, 632 Children = new Drawable[] 633 { 634 colourBox = new Box 635 { 636 Colour = Color4.Black, 637 RelativeSizeAxes = Axes.Both, 638 }, 639 Content = new Container { RelativeSizeAxes = Axes.Both }, 640 } 641 }); 642 } 643 644 public void Reset() 645 { 646 ClickReceived = HoverReceived = false; 647 colourBox.FadeColour(Color4.Black); 648 } 649 650 protected override bool OnClick(ClickEvent e) 651 { 652 ClickReceived = true; 653 colourBox.FlashColour(Color4.White, 200); 654 return true; 655 } 656 657 protected override bool OnHover(HoverEvent e) 658 { 659 HoverReceived = true; 660 colourBox.FadeColour(Color4.DarkSlateBlue, 200); 661 return true; 662 } 663 664 protected override void OnHoverLost(HoverLostEvent e) 665 { 666 colourBox.FadeColour(Color4.Black, 200); 667 base.OnHoverLost(e); 668 } 669 } 670 } 671}