A game framework written with osu! in mind.
at master 15 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.Graphics; 9using osu.Framework.Graphics.UserInterface; 10using osu.Framework.Testing; 11using osuTK; 12using osuTK.Input; 13 14namespace osu.Framework.Tests.Visual.UserInterface 15{ 16 public class TestSceneNestedMenus : MenuTestScene 17 { 18 private const int max_depth = 5; 19 private const int max_count = 5; 20 21 private Random rng; 22 23 [SetUp] 24 public new void SetUp() => rng = new Random(1337); 25 26 protected override Menu CreateMenu() => new ClickOpenMenu(TimePerAction) 27 { 28 Anchor = Anchor.Centre, 29 Origin = Anchor.Centre, 30 Items = new[] 31 { 32 generateRandomMenuItem("First"), 33 generateRandomMenuItem("Second"), 34 generateRandomMenuItem("Third"), 35 } 36 }; 37 38 private class ClickOpenMenu : BasicMenu 39 { 40 protected override Menu CreateSubMenu() => new ClickOpenMenu(HoverOpenDelay, false); 41 42 public ClickOpenMenu(double timePerAction, bool topLevel = true) 43 : base(Direction.Vertical, topLevel) 44 { 45 HoverOpenDelay = timePerAction; 46 } 47 } 48 49 #region Test Cases 50 51 /// <summary> 52 /// Tests if the <see cref="Menu"/> respects <see cref="Menu.TopLevelMenu"/> = true, by not alowing it to be closed 53 /// when a click happens outside the <see cref="Menu"/>. 54 /// </summary> 55 [Test] 56 public void TestAlwaysOpen() 57 { 58 AddStep("Click outside", () => InputManager.Click(MouseButton.Left)); 59 AddAssert("Check AlwaysOpen = true", () => Menus.GetSubMenu(0).State == MenuState.Open); 60 } 61 62 /// <summary> 63 /// Tests if the hover state on <see cref="Menu.DrawableMenuItem"/>s is valid. 64 /// </summary> 65 [Test] 66 public void TestHoverState() 67 { 68 AddAssert("Check submenu closed", () => Menus.GetSubMenu(1)?.State != MenuState.Open); 69 AddStep("Hover item", () => InputManager.MoveMouseTo(Menus.GetMenuItems()[0])); 70 AddAssert("Check item hovered", () => Menus.GetMenuItems()[0].IsHovered); 71 } 72 73 /// <summary> 74 /// Tests if the <see cref="Menu"/> respects <see cref="Menu.TopLevelMenu"/> = true. 75 /// </summary> 76 [Test] 77 public void TestTopLevelMenu() 78 { 79 AddStep("Hover item", () => InputManager.MoveMouseTo(Menus.GetSubStructure(0).GetMenuItems()[0])); 80 AddAssert("Check closed", () => Menus.GetSubMenu(1)?.State != MenuState.Open); 81 AddAssert("Check closed", () => Menus.GetSubMenu(1)?.State != MenuState.Open); 82 AddStep("Click item", () => InputManager.Click(MouseButton.Left)); 83 AddAssert("Check open", () => Menus.GetSubMenu(1).State == MenuState.Open); 84 } 85 86 /// <summary> 87 /// Tests if clicking once on a menu that has <see cref="Menu.TopLevelMenu"/> opens it, and clicking a second time 88 /// closes it. 89 /// </summary> 90 [Test] 91 public void TestDoubleClick() 92 { 93 AddStep("Click item", () => ClickItem(0, 0)); 94 AddAssert("Check open", () => Menus.GetSubMenu(1).State == MenuState.Open); 95 AddStep("Click item", () => ClickItem(0, 0)); 96 AddAssert("Check closed", () => Menus.GetSubMenu(1)?.State != MenuState.Open); 97 } 98 99 /// <summary> 100 /// Tests whether click on <see cref="Menu.DrawableMenuItem"/>s causes sub-menus to instantly appear. 101 /// </summary> 102 [Test] 103 public void TestInstantOpen() 104 { 105 AddStep("Click item", () => ClickItem(0, 1)); 106 AddAssert("Check open", () => Menus.GetSubMenu(1).State == MenuState.Open); 107 AddStep("Click item", () => ClickItem(1, 0)); 108 AddAssert("Check open", () => Menus.GetSubMenu(2).State == MenuState.Open); 109 } 110 111 /// <summary> 112 /// Tests if clicking on an item that has no sub-menu causes the menu to close. 113 /// </summary> 114 [Test] 115 public void TestActionClick() 116 { 117 AddStep("Click item", () => ClickItem(0, 0)); 118 AddStep("Click item", () => ClickItem(1, 0)); 119 AddAssert("Check closed", () => Menus.GetSubMenu(1)?.State != MenuState.Open); 120 } 121 122 /// <summary> 123 /// Tests if hovering over menu items respects the <see cref="Menu.HoverOpenDelay"/>. 124 /// </summary> 125 [Test] 126 public void TestHoverOpen() 127 { 128 AddStep("Click item", () => ClickItem(0, 1)); 129 AddStep("Hover item", () => InputManager.MoveMouseTo(Menus.GetSubStructure(1).GetMenuItems()[0])); 130 AddAssert("Check closed", () => Menus.GetSubMenu(2)?.State != MenuState.Open); 131 AddAssert("Check open", () => Menus.GetSubMenu(2).State == MenuState.Open); 132 AddStep("Hover item", () => InputManager.MoveMouseTo(Menus.GetSubStructure(2).GetMenuItems()[0])); 133 AddAssert("Check closed", () => Menus.GetSubMenu(3)?.State != MenuState.Open); 134 AddAssert("Check open", () => Menus.GetSubMenu(3).State == MenuState.Open); 135 } 136 137 /// <summary> 138 /// Tests if hovering over a different item on the main <see cref="Menu"/> will instantly open another menu 139 /// and correctly changes the sub-menu items to the new items from the hovered item. 140 /// </summary> 141 [Test] 142 public void TestHoverChange() 143 { 144 IReadOnlyList<MenuItem> currentItems = null; 145 AddStep("Click item", () => { ClickItem(0, 0); }); 146 147 AddStep("Get items", () => { currentItems = Menus.GetSubMenu(1).Items; }); 148 149 AddAssert("Check open", () => Menus.GetSubMenu(1).State == MenuState.Open); 150 AddStep("Hover item", () => InputManager.MoveMouseTo(Menus.GetSubStructure(0).GetMenuItems()[1])); 151 AddAssert("Check open", () => Menus.GetSubMenu(1).State == MenuState.Open); 152 153 AddAssert("Check new items", () => !Menus.GetSubMenu(1).Items.SequenceEqual(currentItems)); 154 AddAssert("Check closed", () => 155 { 156 int currentSubMenu = 3; 157 158 while (true) 159 { 160 var subMenu = Menus.GetSubMenu(currentSubMenu); 161 if (subMenu == null) 162 break; 163 164 if (subMenu.State == MenuState.Open) 165 return false; 166 167 currentSubMenu++; 168 } 169 170 return true; 171 }); 172 } 173 174 /// <summary> 175 /// Tests whether hovering over a different item on a sub-menu opens a new sub-menu in a delayed fashion 176 /// and correctly changes the sub-menu items to the new items from the hovered item. 177 /// </summary> 178 [Test] 179 public void TestDelayedHoverChange() 180 { 181 AddStep("Click item", () => ClickItem(0, 2)); 182 AddStep("Hover item", () => InputManager.MoveMouseTo(Menus.GetSubStructure(1).GetMenuItems()[0])); 183 AddAssert("Check closed", () => Menus.GetSubMenu(2)?.State != MenuState.Open); 184 AddAssert("Check closed", () => Menus.GetSubMenu(2)?.State != MenuState.Open); 185 186 AddStep("Hover item", () => { InputManager.MoveMouseTo(Menus.GetSubStructure(1).GetMenuItems()[1]); }); 187 188 AddAssert("Check closed", () => Menus.GetSubMenu(2)?.State != MenuState.Open); 189 AddAssert("Check open", () => Menus.GetSubMenu(2).State == MenuState.Open); 190 191 AddAssert("Check closed", () => 192 { 193 int currentSubMenu = 3; 194 195 while (true) 196 { 197 var subMenu = Menus.GetSubMenu(currentSubMenu); 198 if (subMenu == null) 199 break; 200 201 if (subMenu.State == MenuState.Open) 202 return false; 203 204 currentSubMenu++; 205 } 206 207 return true; 208 }); 209 } 210 211 /// <summary> 212 /// Tests whether clicking on <see cref="Menu"/>s that have opened sub-menus don't close the sub-menus. 213 /// Then tests hovering in reverse order to make sure only the lower level menus close. 214 /// </summary> 215 [Test] 216 public void TestMenuClicksDontClose() 217 { 218 AddStep("Click item", () => ClickItem(0, 1)); 219 AddStep("Click item", () => ClickItem(1, 0)); 220 AddStep("Click item", () => ClickItem(2, 0)); 221 AddStep("Click item", () => ClickItem(3, 0)); 222 223 for (int i = 3; i >= 1; i--) 224 { 225 int menuIndex = i; 226 AddStep("Hover item", () => InputManager.MoveMouseTo(Menus.GetSubStructure(menuIndex).GetMenuItems()[0])); 227 AddAssert("Check submenu open", () => Menus.GetSubMenu(menuIndex + 1).State == MenuState.Open); 228 AddStep("Click item", () => InputManager.Click(MouseButton.Left)); 229 AddAssert("Check all open", () => 230 { 231 for (int j = 0; j <= menuIndex; j++) 232 { 233 int menuIndex2 = j; 234 if (Menus.GetSubMenu(menuIndex2)?.State != MenuState.Open) 235 return false; 236 } 237 238 return true; 239 }); 240 } 241 } 242 243 /// <summary> 244 /// Tests whether clicking on the <see cref="Menu"/> that has <see cref="Menu.TopLevelMenu"/> closes all sub menus. 245 /// </summary> 246 [Test] 247 public void TestMenuClickClosesSubMenus() 248 { 249 AddStep("Click item", () => ClickItem(0, 1)); 250 AddStep("Click item", () => ClickItem(1, 0)); 251 AddStep("Click item", () => ClickItem(2, 0)); 252 AddStep("Click item", () => ClickItem(3, 0)); 253 AddStep("Click item", () => ClickItem(0, 1)); 254 255 AddAssert("Check submenus closed", () => 256 { 257 for (int j = 1; j <= 3; j++) 258 { 259 int menuIndex2 = j; 260 if (Menus.GetSubMenu(menuIndex2).State == MenuState.Open) 261 return false; 262 } 263 264 return true; 265 }); 266 } 267 268 /// <summary> 269 /// Tests whether clicking on an action in a sub-menu closes all <see cref="Menu"/>s. 270 /// </summary> 271 [Test] 272 public void TestActionClickClosesMenus() 273 { 274 AddStep("Click item", () => ClickItem(0, 1)); 275 AddStep("Click item", () => ClickItem(1, 0)); 276 AddStep("Click item", () => ClickItem(2, 0)); 277 AddStep("Click item", () => ClickItem(3, 0)); 278 AddStep("Click item", () => ClickItem(4, 0)); 279 280 AddAssert("Check submenus closed", () => 281 { 282 for (int j = 1; j <= 3; j++) 283 { 284 int menuIndex2 = j; 285 if (Menus.GetSubMenu(menuIndex2).State == MenuState.Open) 286 return false; 287 } 288 289 return true; 290 }); 291 } 292 293 /// <summary> 294 /// Tests whether clicking outside the <see cref="Menu"/> structure closes all sub-menus. 295 /// </summary> 296 /// <param name="hoverPrevious">Whether the previous menu should first be hovered before clicking outside.</param> 297 [TestCase(false)] 298 [TestCase(true)] 299 public void TestClickingOutsideClosesMenus(bool hoverPrevious) 300 { 301 for (int i = 0; i <= 3; i++) 302 { 303 int i2 = i; 304 305 for (int j = 0; j <= i; j++) 306 { 307 int menuToOpen = j; 308 int itemToOpen = menuToOpen == 0 ? 1 : 0; 309 AddStep("Click item", () => ClickItem(menuToOpen, itemToOpen)); 310 } 311 312 if (hoverPrevious && i > 0) 313 AddStep("Hover previous", () => InputManager.MoveMouseTo(Menus.GetSubStructure(i2 - 1).GetMenuItems()[i2 > 1 ? 0 : 1])); 314 315 AddStep("Remove hover", () => InputManager.MoveMouseTo(Vector2.Zero)); 316 AddStep("Click outside", () => InputManager.Click(MouseButton.Left)); 317 AddAssert("Check submenus closed", () => 318 { 319 for (int j = 1; j <= i2 + 1; j++) 320 { 321 int menuIndex2 = j; 322 if (Menus.GetSubMenu(menuIndex2).State == MenuState.Open) 323 return false; 324 } 325 326 return true; 327 }); 328 } 329 } 330 331 /// <summary> 332 /// Opens some menus and then changes the selected item. 333 /// </summary> 334 [Test] 335 public void TestSelectedState() 336 { 337 AddStep("Click item", () => ClickItem(0, 2)); 338 AddAssert("Check open", () => Menus.GetSubMenu(1).State == MenuState.Open); 339 340 AddStep("Hover item", () => InputManager.MoveMouseTo(Menus.GetSubStructure(1).GetMenuItems()[1])); 341 AddAssert("Check closed 1", () => Menus.GetSubMenu(2)?.State != MenuState.Open); 342 AddAssert("Check open", () => Menus.GetSubMenu(2).State == MenuState.Open); 343 AddAssert("Check selected index 1", () => Menus.GetSubStructure(1).GetSelectedIndex() == 1); 344 345 AddStep("Change selection", () => Menus.GetSubStructure(1).SetSelectedState(0, MenuItemState.Selected)); 346 AddAssert("Check selected index", () => Menus.GetSubStructure(1).GetSelectedIndex() == 0); 347 348 AddStep("Change selection", () => Menus.GetSubStructure(1).SetSelectedState(2, MenuItemState.Selected)); 349 AddAssert("Check selected index 2", () => Menus.GetSubStructure(1).GetSelectedIndex() == 2); 350 351 AddStep("Close menus", () => Menus.GetSubMenu(0).Close()); 352 AddAssert("Check selected index 4", () => Menus.GetSubStructure(1).GetSelectedIndex() == -1); 353 } 354 355 #endregion 356 357 private MenuItem generateRandomMenuItem(string name = "Menu Item", int currDepth = 1) 358 { 359 var item = new MenuItem(name); 360 361 if (currDepth == max_depth) 362 return item; 363 364 int subCount = rng.Next(0, max_count); 365 var subItems = new List<MenuItem>(); 366 for (int i = 0; i < subCount; i++) 367 subItems.Add(generateRandomMenuItem(item.Text + $" #{i + 1}", currDepth + 1)); 368 369 item.Items = subItems; 370 return item; 371 } 372 } 373}