A game framework written with osu! in mind.
at master 21 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.Collections.Generic; 6using System.Linq; 7using NUnit.Framework; 8using osu.Framework.Bindables; 9using osu.Framework.Extensions.IEnumerableExtensions; 10using osu.Framework.Graphics; 11using osu.Framework.Graphics.Containers; 12using osu.Framework.Graphics.Shapes; 13using osu.Framework.Graphics.UserInterface; 14using osu.Framework.Input; 15using osu.Framework.Localisation; 16using osuTK; 17 18namespace osu.Framework.Tests.Visual.UserInterface 19{ 20 public class TestSceneTabControl : FrameworkTestScene 21 { 22 private readonly TestEnum[] items; 23 24 private FillFlowContainer tabControlContainer; 25 26 private StyledTabControl pinnedAndAutoSort; 27 private StyledTabControl switchingTabControl; 28 private PlatformActionContainer platformActionContainer; 29 private StyledTabControlWithoutDropdown withoutDropdownTabControl; 30 private StyledTabControl removeAllTabControl; 31 private StyledMultilineTabControl multilineTabControl; 32 private StyledTabControl simpleTabcontrol; 33 private StyledTabControl simpleTabcontrolNoSwitchOnRemove; 34 private BasicTabControl<TestEnum?> basicTabControl; 35 36 public TestSceneTabControl() 37 { 38 items = (TestEnum[])Enum.GetValues(typeof(TestEnum)); 39 } 40 41 [SetUp] 42 public void Setup() => Schedule(() => 43 { 44 Clear(); 45 46 Add(tabControlContainer = new FillFlowContainer 47 { 48 RelativeSizeAxes = Axes.Both, 49 Direction = FillDirection.Full, 50 Spacing = new Vector2(50), 51 Children = new Drawable[] 52 { 53 simpleTabcontrol = new StyledTabControl 54 { 55 Size = new Vector2(200, 30), 56 }, 57 simpleTabcontrolNoSwitchOnRemove = new StyledTabControl 58 { 59 Size = new Vector2(200, 30), 60 SwitchTabOnRemove = false 61 }, 62 multilineTabControl = new StyledMultilineTabControl 63 { 64 Size = new Vector2(200, 60), 65 }, 66 pinnedAndAutoSort = new StyledTabControl 67 { 68 Size = new Vector2(200, 30), 69 AutoSort = true 70 }, 71 platformActionContainer = new PlatformActionContainer 72 { 73 RelativeSizeAxes = Axes.None, 74 Size = new Vector2(200, 30), 75 Child = switchingTabControl = new StyledTabControl 76 { 77 RelativeSizeAxes = Axes.Both, 78 IsSwitchable = true, 79 } 80 }, 81 removeAllTabControl = new StyledTabControl 82 { 83 Size = new Vector2(200, 30) 84 }, 85 withoutDropdownTabControl = new StyledTabControlWithoutDropdown 86 { 87 Size = new Vector2(200, 30) 88 }, 89 basicTabControl = new BasicTabControl<TestEnum?> 90 { 91 Size = new Vector2(200, 20) 92 } 93 } 94 }); 95 96 foreach (var item in items) 97 { 98 simpleTabcontrol.AddItem(item); 99 simpleTabcontrolNoSwitchOnRemove.AddItem(item); 100 multilineTabControl.AddItem(item); 101 switchingTabControl.AddItem(item); 102 withoutDropdownTabControl.AddItem(item); 103 basicTabControl.AddItem(item); 104 } 105 106 items.Take(7).ForEach(item => pinnedAndAutoSort.AddItem(item)); 107 pinnedAndAutoSort.PinItem(TestEnum.Test5); 108 }); 109 110 [Test] 111 public void Basic() 112 { 113 var nextTest = new Func<TestEnum>(() => items.FirstOrDefault(test => !pinnedAndAutoSort.Items.Contains(test))); 114 115 Stack<TestEnum> pinned = new Stack<TestEnum>(); 116 117 AddStep("AddItem", () => 118 { 119 var item = nextTest.Invoke(); 120 if (!pinnedAndAutoSort.Items.Contains(item)) 121 pinnedAndAutoSort.AddItem(item); 122 }); 123 124 AddStep("RemoveItem", () => 125 { 126 if (pinnedAndAutoSort.Items.Any()) 127 { 128 pinnedAndAutoSort.RemoveItem(pinnedAndAutoSort.Items.First()); 129 } 130 }); 131 132 AddStep("PinItem", () => 133 { 134 var item = nextTest.Invoke(); 135 136 if (!pinnedAndAutoSort.Items.Contains(item)) 137 { 138 pinned.Push(item); 139 pinnedAndAutoSort.AddItem(item); 140 pinnedAndAutoSort.PinItem(item); 141 } 142 }); 143 144 AddStep("UnpinItem", () => 145 { 146 if (pinned.Count > 0) pinnedAndAutoSort.UnpinItem(pinned.Pop()); 147 }); 148 149 AddStep("Set first tab", () => switchingTabControl.Current.Value = switchingTabControl.Items.First()); 150 AddStep("Switch forward", () => platformActionContainer.TriggerPressed(PlatformAction.DocumentNext)); 151 AddAssert("Ensure second tab", () => switchingTabControl.Current.Value == switchingTabControl.Items.ElementAt(1)); 152 153 AddStep("Switch backward", () => platformActionContainer.TriggerPressed(PlatformAction.DocumentPrevious)); 154 AddAssert("Ensure first Tab", () => switchingTabControl.Current.Value == switchingTabControl.Items.First()); 155 156 AddStep("Switch backward", () => platformActionContainer.TriggerPressed(PlatformAction.DocumentPrevious)); 157 AddAssert("Ensure last tab", () => switchingTabControl.Current.Value == switchingTabControl.Items.Last()); 158 159 AddStep("Switch forward", () => platformActionContainer.TriggerPressed(PlatformAction.DocumentNext)); 160 AddAssert("Ensure first tab", () => switchingTabControl.Current.Value == switchingTabControl.Items.First()); 161 162 AddStep("Add all items", () => items.ForEach(item => removeAllTabControl.AddItem(item))); 163 AddAssert("Ensure all items", () => removeAllTabControl.Items.Count == items.Length); 164 165 AddStep("Remove all items", () => removeAllTabControl.Clear()); 166 AddAssert("Ensure no items", () => !removeAllTabControl.Items.Any()); 167 168 AddAssert("Ensure any items", () => withoutDropdownTabControl.Items.Any()); 169 AddStep("Remove all items", () => withoutDropdownTabControl.Clear()); 170 AddAssert("Ensure no items", () => !withoutDropdownTabControl.Items.Any()); 171 172 AddAssert("Ensure not all items visible on singleline", () => simpleTabcontrol.VisibleItems.Count() < items.Length); 173 AddAssert("Ensure all items visible on multiline", () => multilineTabControl.VisibleItems.Count() == items.Length); 174 } 175 176 [Test] 177 public void TestLeasedBindable() 178 { 179 LeasedBindable<TestEnum?> leased = null; 180 181 AddStep("change value to test0", () => simpleTabcontrol.Current.Value = TestEnum.Test0); 182 AddStep("lease bindable", () => leased = simpleTabcontrol.Current.BeginLease(true)); 183 AddStep("change value to test1", () => leased.Value = TestEnum.Test1); 184 AddAssert("value changed", () => simpleTabcontrol.Current.Value == TestEnum.Test1); 185 AddAssert("tab changed", () => simpleTabcontrol.SelectedTab.Value == TestEnum.Test1); 186 AddStep("end lease", () => leased.UnbindAll()); 187 } 188 189 [Test] 190 public void TestTabSelectedWhenDisabledBindableIsBound() 191 { 192 Bindable<TestEnum?> bindable; 193 194 AddStep("add tabcontrol", () => 195 { 196 bindable = new Bindable<TestEnum?> { Value = TestEnum.Test2 }; 197 198 simpleTabcontrol = new StyledTabControl 199 { 200 Size = new Vector2(200, 30) 201 }; 202 203 foreach (var item in items) 204 simpleTabcontrol.AddItem(item); 205 206 bindable.Disabled = true; 207 simpleTabcontrol.Current = bindable; 208 209 Child = simpleTabcontrol; 210 }); 211 212 AddAssert("test2 selected", () => simpleTabcontrol.SelectedTab.Value == TestEnum.Test2); 213 } 214 215 [Test] 216 public void TestClicksBlockedWhenBindableDisabled() 217 { 218 AddStep("add tabcontrol", () => 219 { 220 Child = simpleTabcontrol = new StyledTabControl { Size = new Vector2(200, 30) }; 221 222 foreach (var item in items) 223 simpleTabcontrol.AddItem(item); 224 225 simpleTabcontrol.Current = new Bindable<TestEnum?> 226 { 227 Value = TestEnum.Test0, 228 Disabled = true 229 }; 230 }); 231 232 AddStep("click a tab", () => simpleTabcontrol.TabMap[TestEnum.Test2].TriggerClick()); 233 AddAssert("test0 still selected", () => simpleTabcontrol.SelectedTab.Value == TestEnum.Test0); 234 } 235 236 [TestCase(true)] 237 [TestCase(false)] 238 public void SelectNull(bool autoSort) 239 { 240 AddStep($"Set autosort to {autoSort}", () => simpleTabcontrol.AutoSort = autoSort); 241 AddStep("select item 1", () => simpleTabcontrol.Current.Value = simpleTabcontrol.Items.ElementAt(1)); 242 AddAssert("item 1 is selected", () => simpleTabcontrol.Current.Value == simpleTabcontrol.Items.ElementAt(1)); 243 AddStep("select item null", () => simpleTabcontrol.Current.Value = null); 244 AddAssert("null is selected", () => simpleTabcontrol.Current.Value == null); 245 } 246 247 [Test] 248 public void TestRemovingTabMovesOutFromDropdown() 249 { 250 AddStep("Remove test3", () => simpleTabcontrol.RemoveItem(TestEnum.Test3)); 251 AddAssert("Test 4 is visible", () => simpleTabcontrol.TabMap[TestEnum.Test4].IsPresent); 252 253 AddUntilStep("Remove all visible items", () => 254 { 255 simpleTabcontrol.RemoveItem(simpleTabcontrol.Items.First(d => simpleTabcontrol.TabMap[d].IsPresent)); 256 return !simpleTabcontrol.Dropdown.Items.Any(); 257 }); 258 } 259 260 [Test] 261 public void TestRemovingSelectedTabSwitchesSelection() 262 { 263 AddStep("Select tab 2", () => simpleTabcontrol.Current.Value = TestEnum.Test2); 264 AddStep("Remove tab 2", () => simpleTabcontrol.RemoveItem(TestEnum.Test2)); 265 AddAssert("Ensure selection switches to next tab", () => simpleTabcontrol.SelectedTab.Value == TestEnum.Test3); 266 267 AddStep("Select last tab", () => simpleTabcontrol.Current.Value = simpleTabcontrol.Items.Last()); 268 AddStep("Remove selected tab", () => simpleTabcontrol.RemoveItem(simpleTabcontrol.SelectedTab.Value)); 269 AddAssert("Ensure selection switches to previous tab", () => simpleTabcontrol.SelectedTab.Value == simpleTabcontrol.Items.Last()); 270 271 AddStep("Remove all tabs", () => 272 { 273 var itemsForDelete = new List<TestEnum?>(simpleTabcontrol.Items); 274 itemsForDelete.ForEach(item => simpleTabcontrol.RemoveItem(item)); 275 }); 276 AddAssert("Ensure selected tab is null", () => simpleTabcontrol.SelectedTab == null); 277 } 278 279 /// <summary> 280 /// Tests that the selection is not switched on a <see cref="TabControl{T}"/> that has <see cref="TabControl{T}.SwitchTabOnRemove"/> set to <c>false</c>. 281 /// </summary> 282 [Test] 283 public void TestRemovingSelectedTabDoesNotSwitchSelectionIfNotSwitchTabOnRemove() 284 { 285 AddStep("Select tab 2", () => simpleTabcontrolNoSwitchOnRemove.Current.Value = TestEnum.Test2); 286 AddStep("Remove tab 2", () => simpleTabcontrolNoSwitchOnRemove.RemoveItem(TestEnum.Test2)); 287 AddAssert("Ensure has not switched", () => simpleTabcontrolNoSwitchOnRemove.SelectedTab.Value == TestEnum.Test2); 288 } 289 290 [Test] 291 public void TestRemovingUnswitchableTab() 292 { 293 AddStep("Set last tab unswitchable", () => ((StyledTabControl.TestTabItem)simpleTabcontrol.SwitchableTabs.Last()).SetSwitchable(false)); 294 295 AddStep("Select last tab", () => simpleTabcontrol.Current.Value = simpleTabcontrol.Items.Last()); 296 AddStep("Remove selected tab", () => simpleTabcontrol.RemoveItem(simpleTabcontrol.SelectedTab.Value)); 297 AddAssert("Ensure selection switches to previous tab", () => simpleTabcontrol.SelectedTab.Value == simpleTabcontrol.Items.Last()); 298 299 AddStep("Set first tab unswitchable", () => ((StyledTabControl.TestTabItem)simpleTabcontrol.SwitchableTabs.First()).SetSwitchable(false)); 300 301 AddStep("Select first tab", () => simpleTabcontrol.Current.Value = simpleTabcontrol.Items.First()); 302 AddStep("Remove selected tab", () => simpleTabcontrol.RemoveItem(simpleTabcontrol.SelectedTab.Value)); 303 AddAssert("Ensure selection switches to next tab", () => simpleTabcontrol.SelectedTab.Value == simpleTabcontrol.Items.First()); 304 } 305 306 [Test] 307 public void TestUnswitchableNotSelectedOnRemoveAll() 308 { 309 AddStep("Set middle tab unswitchable", () => ((StyledTabControl.TestTabItem)simpleTabcontrol.SwitchableTabs.Skip(5).First()).SetSwitchable(false)); 310 311 AddStep("Remove all switchable tabs", () => 312 { 313 var itemsForDelete = new List<TestEnum?>(); 314 315 foreach (var kvp in simpleTabcontrol.TabMap) 316 { 317 if (kvp.Value.IsSwitchable) 318 itemsForDelete.Add(kvp.Key); 319 } 320 321 itemsForDelete.ForEach(item => simpleTabcontrol.RemoveItem(item)); 322 }); 323 324 AddAssert("Unswitchable tab still present", () => simpleTabcontrol.Items.Count == 1); 325 AddAssert("Ensure selected tab is null", () => simpleTabcontrol.SelectedTab == null); 326 } 327 328 [Test] 329 public void TestItemsImmediatelyUpdatedAfterAdd() 330 { 331 TabControlWithNoDropdown tabControl = null; 332 333 AddStep("create tab control", () => 334 { 335 tabControl = new TabControlWithNoDropdown { Size = new Vector2(200, 30) }; 336 337 foreach (var item in items) 338 tabControl.AddItem(item); 339 }); 340 341 AddAssert("contained items match added items", () => tabControl.Items.SequenceEqual(items)); 342 } 343 344 [Test] 345 public void TestItemsAddedWhenSet() 346 { 347 TabControlWithNoDropdown tabControl = null; 348 349 AddStep("create tab control", () => 350 { 351 tabControl = new TabControlWithNoDropdown 352 { 353 Size = new Vector2(200, 30), 354 Items = items 355 }; 356 }); 357 358 AddAssert("contained items match added items", () => tabControl.Items.SequenceEqual(items)); 359 } 360 361 [TestCase(false, null)] 362 [TestCase(true, TestEnum.Test0)] 363 public void TestInitialSelection(bool selectFirstByDefault, TestEnum? expectedInitialSelection) 364 { 365 StyledTabControl tabControl = null; 366 367 AddStep("create tab control", () => 368 { 369 tabControlContainer.Add(tabControl = new StyledTabControl 370 { 371 Size = new Vector2(200, 30), 372 Items = items.Cast<TestEnum?>().ToList(), 373 SelectFirstTabByDefault = selectFirstByDefault 374 }); 375 }); 376 377 AddUntilStep("wait for loaded", () => tabControl.IsLoaded); 378 AddAssert("initial selection is correct", () => tabControl.Current.Value == expectedInitialSelection); 379 } 380 381 [TestCase(true, TestEnum.Test1, true)] 382 [TestCase(false, TestEnum.Test1, true)] 383 [TestCase(true, TestEnum.Test9, true)] 384 [TestCase(false, TestEnum.Test9, false)] 385 public void TestInitialSort(bool autoSort, TestEnum? initialItem, bool expected) 386 { 387 StyledTabControl tabControlWithBindable = null; 388 Bindable<TestEnum?> testBindable = new Bindable<TestEnum?> { Value = initialItem }; 389 390 AddStep("create tab control", () => 391 { 392 tabControlContainer.Add(tabControlWithBindable = new StyledTabControl 393 { 394 Size = new Vector2(200, 20), 395 Items = items.Cast<TestEnum?>().ToList(), 396 AutoSort = autoSort, 397 Current = { BindTarget = testBindable } 398 }); 399 }); 400 401 AddUntilStep("wait for loaded", () => tabControlWithBindable.IsLoaded); 402 AddAssert($"Current selection {(expected ? "visible" : "not visible")}", () => tabControlWithBindable.SelectedTab.IsPresent == expected); 403 } 404 405 private class StyledTabControlWithoutDropdown : TabControl<TestEnum> 406 { 407 protected override Dropdown<TestEnum> CreateDropdown() => null; 408 409 protected override TabItem<TestEnum> CreateTabItem(TestEnum value) 410 => new BasicTabControl<TestEnum>.BasicTabItem(value); 411 } 412 413 private class StyledMultilineTabControl : TabControl<TestEnum> 414 { 415 protected override Dropdown<TestEnum> CreateDropdown() => null; 416 417 protected override TabItem<TestEnum> CreateTabItem(TestEnum value) 418 => new BasicTabControl<TestEnum>.BasicTabItem(value); 419 420 protected override TabFillFlowContainer CreateTabFlow() => base.CreateTabFlow().With(f => { f.AllowMultiline = true; }); 421 } 422 423 public class StyledTabControl : TabControl<TestEnum?> 424 { 425 public new IReadOnlyDictionary<TestEnum?, TabItem<TestEnum?>> TabMap => base.TabMap; 426 427 public new TabItem<TestEnum?> SelectedTab => base.SelectedTab; 428 429 public new Dropdown<TestEnum?> Dropdown => base.Dropdown; 430 431 protected override Dropdown<TestEnum?> CreateDropdown() => new StyledDropdown(); 432 433 public TabItem<TestEnum?> CreateTabItem(TestEnum? value, bool isSwitchable) 434 => new TestTabItem(value); 435 436 protected override TabItem<TestEnum?> CreateTabItem(TestEnum? value) => CreateTabItem(value, true); 437 438 public class TestTabItem : BasicTabControl<TestEnum?>.BasicTabItem 439 { 440 public TestTabItem(TestEnum? value) 441 : base(value) 442 { 443 } 444 445 private bool switchable = true; 446 447 public void SetSwitchable(bool isSwitchable) => switchable = isSwitchable; 448 449 public override bool IsSwitchable => switchable; 450 } 451 } 452 453 private class StyledDropdown : Dropdown<TestEnum?> 454 { 455 protected override DropdownMenu CreateMenu() => new StyledDropdownMenu(); 456 457 protected override DropdownHeader CreateHeader() => new StyledDropdownHeader(); 458 459 public StyledDropdown() 460 { 461 Menu.Anchor = Anchor.TopRight; 462 Menu.Origin = Anchor.TopRight; 463 Header.Anchor = Anchor.TopRight; 464 Header.Origin = Anchor.TopRight; 465 } 466 467 private class StyledDropdownMenu : BasicDropdown<TestEnum?>.BasicDropdownMenu 468 { 469 public StyledDropdownMenu() 470 { 471 ScrollbarVisible = false; 472 CornerRadius = 4; 473 } 474 } 475 } 476 477 private class StyledDropdownHeader : DropdownHeader 478 { 479 protected internal override LocalisableString Label { get; set; } 480 481 public StyledDropdownHeader() 482 { 483 Background.Hide(); // don't need a background 484 485 RelativeSizeAxes = Axes.None; 486 AutoSizeAxes = Axes.X; 487 488 Foreground.RelativeSizeAxes = Axes.None; 489 Foreground.AutoSizeAxes = Axes.Both; 490 491 Foreground.Children = new[] 492 { 493 new Box { Width = 20, Height = 20 } 494 }; 495 } 496 } 497 498 private class TabControlWithNoDropdown : BasicTabControl<TestEnum> 499 { 500 protected override Dropdown<TestEnum> CreateDropdown() => null; 501 } 502 503 public enum TestEnum 504 { 505 Test0, 506 Test1, 507 Test2, 508 Test3, 509 Test4, 510 Test5, 511 Test6, 512 Test7, 513 Test8, 514 Test9, 515 Test10, 516 Test11, 517 Test12 518 } 519 } 520}