A game framework written with osu! in mind.
at master 8.5 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.EnumExtensions; 8using osu.Framework.Graphics; 9using osu.Framework.Graphics.Containers; 10using osu.Framework.Graphics.Cursor; 11using osu.Framework.Graphics.Shapes; 12using osu.Framework.Graphics.UserInterface; 13using osu.Framework.Utils; 14using osu.Framework.Testing; 15using osuTK; 16using osuTK.Graphics; 17using osuTK.Input; 18 19namespace osu.Framework.Tests.Visual.UserInterface 20{ 21 public class TestSceneContextMenu : ManualInputManagerTestScene 22 { 23 protected override Container<Drawable> Content => contextMenuContainer ?? base.Content; 24 25 private readonly TestContextMenuContainer contextMenuContainer; 26 27 public TestSceneContextMenu() 28 { 29 base.Content.Add(contextMenuContainer = new TestContextMenuContainer { RelativeSizeAxes = Axes.Both }); 30 } 31 32 [SetUp] 33 public void Setup() => Schedule(Clear); 34 35 [Test] 36 public void TestMenuOpenedOnClick() 37 { 38 Drawable box = null; 39 40 addBoxStep(b => box = b, 1); 41 clickBoxStep(() => box); 42 43 assertMenuState(true); 44 } 45 46 [Test] 47 public void TestMenuClosedOnClickOutside() 48 { 49 Drawable box = null; 50 51 addBoxStep(b => box = b, 1); 52 clickBoxStep(() => box); 53 54 clickOutsideStep(); 55 assertMenuState(false); 56 } 57 58 [Test] 59 public void TestMenuTransferredToNewTarget() 60 { 61 Drawable box1 = null; 62 Drawable box2 = null; 63 64 addBoxStep(b => 65 { 66 box1 = b.With(d => 67 { 68 d.X = -100; 69 d.Colour = Color4.Green; 70 }); 71 }, 1); 72 addBoxStep(b => 73 { 74 box2 = b.With(d => 75 { 76 d.X = 100; 77 d.Colour = Color4.Red; 78 }); 79 }, 1); 80 81 clickBoxStep(() => box1); 82 clickBoxStep(() => box2); 83 84 assertMenuState(true); 85 assertMenuInCentre(() => box2); 86 } 87 88 [Test] 89 public void TestMenuHiddenWhenTargetHidden() 90 { 91 Drawable box = null; 92 93 addBoxStep(b => box = b, 1); 94 clickBoxStep(() => box); 95 96 AddStep("hide box", () => box.Hide()); 97 assertMenuState(false); 98 } 99 100 [Test] 101 public void TestMenuTracksMovement() 102 { 103 Drawable box = null; 104 105 addBoxStep(b => box = b, 1); 106 clickBoxStep(() => box); 107 108 AddStep("move box", () => box.X += 100); 109 assertMenuInCentre(() => box); 110 } 111 112 [TestCase(Anchor.TopLeft)] 113 [TestCase(Anchor.TopCentre)] 114 [TestCase(Anchor.TopRight)] 115 [TestCase(Anchor.CentreLeft)] 116 [TestCase(Anchor.CentreRight)] 117 [TestCase(Anchor.BottomLeft)] 118 [TestCase(Anchor.BottomCentre)] 119 [TestCase(Anchor.BottomRight)] 120 public void TestMenuOnScreenWhenTargetPartlyOffScreen(Anchor anchor) 121 { 122 Drawable box = null; 123 124 addBoxStep(b => box = b, 5); 125 clickBoxStep(() => box); 126 127 AddStep($"move box to {anchor.ToString()}", () => 128 { 129 box.Anchor = anchor; 130 box.X -= 5; 131 box.Y -= 5; 132 }); 133 134 assertMenuOnScreen(true); 135 } 136 137 [TestCase(Anchor.TopLeft)] 138 [TestCase(Anchor.TopCentre)] 139 [TestCase(Anchor.TopRight)] 140 [TestCase(Anchor.CentreLeft)] 141 [TestCase(Anchor.CentreRight)] 142 [TestCase(Anchor.BottomLeft)] 143 [TestCase(Anchor.BottomCentre)] 144 [TestCase(Anchor.BottomRight)] 145 public void TestMenuNotOnScreenWhenTargetSignificantlyOffScreen(Anchor anchor) 146 { 147 Drawable box = null; 148 149 addBoxStep(b => box = b, 5); 150 clickBoxStep(() => box); 151 152 AddStep($"move box to {anchor.ToString()}", () => 153 { 154 box.Anchor = anchor; 155 156 if (anchor.HasFlagFast(Anchor.x0)) 157 box.X -= contextMenuContainer.CurrentMenu.DrawWidth + 10; 158 else if (anchor.HasFlagFast(Anchor.x2)) 159 box.X += 10; 160 161 if (anchor.HasFlagFast(Anchor.y0)) 162 box.Y -= contextMenuContainer.CurrentMenu.DrawHeight + 10; 163 else if (anchor.HasFlagFast(Anchor.y2)) 164 box.Y += 10; 165 }); 166 167 assertMenuOnScreen(false); 168 } 169 170 [Test] 171 public void TestReturnNullInNestedDrawableOpensParentMenu() 172 { 173 Drawable box2 = null; 174 175 addBoxStep(_ => { }, 2); 176 addBoxStep(b => box2 = b, null); 177 178 clickBoxStep(() => box2); 179 assertMenuState(true); 180 assertMenuItems(2); 181 } 182 183 [Test] 184 public void TestReturnEmptyInNestedDrawableBlocksMenuOpening() 185 { 186 Drawable box2 = null; 187 188 addBoxStep(_ => { }, 2); 189 addBoxStep(b => box2 = b); 190 191 clickBoxStep(() => box2); 192 assertMenuState(false); 193 } 194 195 private void clickBoxStep(Func<Drawable> getBoxFunc) 196 { 197 AddStep("right-click box", () => 198 { 199 InputManager.MoveMouseTo(getBoxFunc()); 200 InputManager.Click(MouseButton.Right); 201 }); 202 } 203 204 private void clickOutsideStep() 205 { 206 AddStep("click outside", () => 207 { 208 InputManager.MoveMouseTo(InputManager.ScreenSpaceDrawQuad.TopLeft); 209 InputManager.Click(MouseButton.Right); 210 }); 211 } 212 213 private void addBoxStep(Action<Drawable> boxFunc, int actionCount) => addBoxStep(boxFunc, Enumerable.Repeat(new Action(() => { }), actionCount).ToArray()); 214 215 private void addBoxStep(Action<Drawable> boxFunc, params Action[] actions) 216 { 217 AddStep("add box", () => 218 { 219 var box = new BoxWithContextMenu(actions) 220 { 221 Anchor = Anchor.Centre, 222 Origin = Anchor.Centre, 223 Size = new Vector2(200), 224 }; 225 226 Add(box); 227 boxFunc?.Invoke(box); 228 }); 229 } 230 231 private void assertMenuState(bool opened) 232 => AddAssert($"menu {(opened ? "opened" : "closed")}", () => (contextMenuContainer.CurrentMenu?.State == MenuState.Open) == opened); 233 234 private void assertMenuInCentre(Func<Drawable> getBoxFunc) 235 => AddAssert("menu in centre of box", () => Precision.AlmostEquals(contextMenuContainer.CurrentMenu.ScreenSpaceDrawQuad.TopLeft, getBoxFunc().ScreenSpaceDrawQuad.Centre)); 236 237 private void assertMenuOnScreen(bool expected) => AddAssert($"menu {(expected ? "on" : "off")} screen", () => 238 { 239 var inputQuad = InputManager.ScreenSpaceDrawQuad; 240 var menuQuad = contextMenuContainer.CurrentMenu.ScreenSpaceDrawQuad; 241 242 bool result = inputQuad.Contains(menuQuad.TopLeft + new Vector2(1, 1)) 243 && inputQuad.Contains(menuQuad.TopRight + new Vector2(-1, 1)) 244 && inputQuad.Contains(menuQuad.BottomLeft + new Vector2(1, -1)) 245 && inputQuad.Contains(menuQuad.BottomRight + new Vector2(-1, -1)); 246 247 return result == expected; 248 }); 249 250 private void assertMenuItems(int expectedCount) => AddAssert($"menu contains {expectedCount} item(s)", () => contextMenuContainer.CurrentMenu.Items.Count == expectedCount); 251 252 private class BoxWithContextMenu : Box, IHasContextMenu 253 { 254 private readonly Action[] actions; 255 256 public BoxWithContextMenu(Action[] actions) 257 { 258 this.actions = actions; 259 } 260 261 public MenuItem[] ContextMenuItems => actions?.Select((a, i) => new MenuItem($"Item {i}", a)).ToArray(); 262 } 263 264 private class TestContextMenuContainer : BasicContextMenuContainer 265 { 266 public Menu CurrentMenu { get; private set; } 267 268 protected override Menu CreateMenu() => CurrentMenu = base.CreateMenu(); 269 } 270 } 271}