A game framework written with osu! in mind.
at master 14 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 NUnit.Framework; 6using osu.Framework.Graphics; 7using osu.Framework.Graphics.Containers; 8using osu.Framework.Graphics.Shapes; 9using osu.Framework.Layout; 10using osu.Framework.Testing; 11using osu.Framework.Tests.Visual; 12using osu.Framework.Utils; 13using osuTK; 14 15namespace osu.Framework.Tests.Layout 16{ 17 [HeadlessTest] 18 public class TestSceneContainerLayout : FrameworkTestScene 19 { 20 /// <summary> 21 /// Tests that auto-size is updated when a child becomes alive. 22 /// </summary> 23 [Test] 24 public void TestContainerAutoSizeUpdatesWhenChildBecomesAlive() 25 { 26 Box box = null; 27 Container parent = null; 28 29 AddStep("create test", () => 30 { 31 Child = parent = new Container 32 { 33 RemoveCompletedTransforms = false, 34 AutoSizeAxes = Axes.Both, 35 Child = box = new Box 36 { 37 Size = new Vector2(200), 38 LifetimeStart = double.MaxValue 39 } 40 }; 41 }); 42 43 AddStep("make child alive", () => box.LifetimeStart = double.MinValue); 44 45 AddAssert("parent has size 200", () => Precision.AlmostEquals(new Vector2(200), parent.DrawSize)); 46 } 47 48 /// <summary> 49 /// Tests that auto-size is updated when a child is removed through death. 50 /// </summary> 51 [Test] 52 public void TestContainerAutoSizeUpdatesWhenChildBecomesDead() 53 { 54 Box box = null; 55 Container parent = null; 56 57 AddStep("create test", () => 58 { 59 Child = parent = new Container 60 { 61 AutoSizeAxes = Axes.Both, 62 Child = box = new Box { Size = new Vector2(200) } 63 }; 64 }); 65 66 AddStep("make child dead", () => box.Expire()); 67 68 AddAssert("parent has size 0", () => Precision.AlmostEquals(Vector2.Zero, parent.DrawSize)); 69 } 70 71 /// <summary> 72 /// Tests that auto-size is updated when a child becomes dead and doesn't get removed. 73 /// </summary> 74 [Test] 75 public void TestContainerAutoSizeUpdatesWhenChildBecomesDeadWithoutRemoval() 76 { 77 Box box = null; 78 Container parent = null; 79 80 AddStep("create test", () => 81 { 82 Child = parent = new Container 83 { 84 RemoveCompletedTransforms = false, 85 AutoSizeAxes = Axes.Both, 86 Child = box = new TestBox1 { Size = new Vector2(200) } 87 }; 88 }); 89 90 AddStep("make child dead", () => box.Expire()); 91 92 AddAssert("parent has size 0", () => Precision.AlmostEquals(Vector2.Zero, parent.DrawSize)); 93 } 94 95 /// <summary> 96 /// Tests that auto-size properly captures a child's presence change. 97 /// </summary> 98 [Test] 99 public void TestAddHiddenChildAndFadeIn() 100 { 101 Container content = null; 102 Box child = null; 103 104 AddStep("create test", () => 105 { 106 Child = content = new Container 107 { 108 RelativeSizeAxes = Axes.X, 109 AutoSizeAxes = Axes.Y, 110 }; 111 }); 112 113 AddStep("add child", () => LoadComponentAsync(child = new Box 114 { 115 RelativeSizeAxes = Axes.X, 116 Height = 500, 117 }, d => 118 { 119 content.Add(d); 120 d.FadeInFromZero(50000); 121 })); 122 123 AddUntilStep("wait for child load", () => child.IsLoaded); 124 125 AddUntilStep("content height matches box height", () => Precision.AlmostEquals(content.DrawHeight, child.DrawHeight)); 126 } 127 128 /// <summary> 129 /// Tests that a parent container is not re-auto-sized when a child's size changes along the bypassed axes. 130 /// </summary> 131 /// <param name="axes">The bypassed axes that are bypassed.</param> 132 [TestCase(Axes.X)] 133 [TestCase(Axes.Y)] 134 [TestCase(Axes.Both)] 135 public void TestParentNotInvalidatedByBypassedSize(Axes axes) 136 { 137 Box child = null; 138 139 bool autoSized = false; 140 141 AddStep("create test", () => 142 { 143 Child = new Container 144 { 145 AutoSizeAxes = Axes.Both, 146 Child = child = new Box { BypassAutoSizeAxes = axes } 147 }.With(c => c.OnAutoSize += () => autoSized = true); 148 }); 149 150 AddUntilStep("wait for autosize", () => autoSized); 151 152 AddStep("adjust child size", () => 153 { 154 autoSized = false; 155 156 if (axes == Axes.Both) 157 child.Size = new Vector2(50); 158 else if (axes == Axes.X) 159 child.Width = 50; 160 else if (axes == Axes.Y) 161 child.Height = 50; 162 }); 163 164 AddWaitStep("wait for autosize", 1); 165 166 AddAssert("not autosized", () => !autoSized); 167 } 168 169 /// <summary> 170 /// Tests that a parent container is not re-auto-sized when a child's position changes along the bypassed axes. 171 /// </summary> 172 /// <param name="axes">The bypassed axes that are bypassed.</param> 173 [TestCase(Axes.X)] 174 [TestCase(Axes.Y)] 175 [TestCase(Axes.Both)] 176 public void TestParentNotInvalidatedByBypassedPosition(Axes axes) 177 { 178 Box child = null; 179 180 bool autoSized = false; 181 182 AddStep("create test", () => 183 { 184 Child = new Container 185 { 186 AutoSizeAxes = Axes.Both, 187 Child = child = new Box { BypassAutoSizeAxes = axes } 188 }.With(c => c.OnAutoSize += () => autoSized = true); 189 }); 190 191 AddUntilStep("wait for autosize", () => autoSized); 192 193 AddStep("adjust child size", () => 194 { 195 autoSized = false; 196 197 if (axes == Axes.Both) 198 child.Position = new Vector2(50); 199 else if (axes == Axes.X) 200 child.X = 50; 201 else if (axes == Axes.Y) 202 child.Y = 50; 203 }); 204 205 AddWaitStep("wait for autosize", 1); 206 207 AddAssert("not autosized", () => !autoSized); 208 } 209 210 [TestCase(Axes.X)] 211 [TestCase(Axes.Y)] 212 [TestCase(Axes.Both)] 213 public void TestParentSizeNotInvalidatedWhenChildGeometryInvalidated(Axes axes) 214 { 215 Drawable child = null; 216 217 Invalidation invalidation = Invalidation.None; 218 219 AddStep("create test", () => 220 { 221 Child = new TestContainer1 222 { 223 Child = child = new Box { Size = new Vector2(200) } 224 }.With(c => c.Invalidated += i => invalidation = i); 225 }); 226 227 AddStep("move child", () => 228 { 229 invalidation = Invalidation.None; 230 231 if (axes == Axes.Both) 232 child.Position = new Vector2(10); 233 else if (axes == Axes.X) 234 child.X = 10; 235 else if (axes == Axes.Y) 236 child.Y = 10; 237 }); 238 239 AddAssert("parent only invalidated with geometry", () => invalidation == Invalidation.MiscGeometry); 240 } 241 242 [TestCase(Axes.X)] 243 [TestCase(Axes.Y)] 244 [TestCase(Axes.Both)] 245 public void TestParentGeometryNotInvalidatedWhenChildSizeInvalidated(Axes axes) 246 { 247 Drawable child = null; 248 249 Invalidation invalidation = Invalidation.None; 250 251 AddStep("create test", () => 252 { 253 Child = new TestContainer1 254 { 255 Child = child = new Box { Size = new Vector2(200) } 256 }.With(c => c.Invalidated += i => invalidation = i); 257 }); 258 259 AddStep("move child", () => 260 { 261 invalidation = Invalidation.None; 262 263 if (axes == Axes.Both) 264 child.Size = new Vector2(10); 265 else if (axes == Axes.X) 266 child.Width = 10; 267 else if (axes == Axes.Y) 268 child.Height = 10; 269 }); 270 271 AddAssert("parent only invalidated with size", () => invalidation == Invalidation.DrawSize); 272 } 273 274 /// <summary> 275 /// Tests that a child is not invalidated by its parent when not alive. 276 /// </summary> 277 [Test] 278 public void TestChildNotInvalidatedWhenNotAlive() 279 { 280 Container parent = null; 281 bool invalidated = false; 282 283 AddStep("create test", () => 284 { 285 Drawable child; 286 287 Child = parent = new Container 288 { 289 Size = new Vector2(200), 290 Child = child = new Box 291 { 292 RelativeSizeAxes = Axes.Both, 293 LifetimeStart = double.MaxValue 294 } 295 }; 296 297 // Trigger a validation of draw size. 298 Assert.That(child.DrawSize, Is.EqualTo(new Vector2(200))); 299 300 child.Invalidated += _ => invalidated = true; 301 }); 302 303 AddStep("resize parent", () => parent.Size = new Vector2(400)); 304 AddAssert("child not invalidated", () => !invalidated); 305 } 306 307 /// <summary> 308 /// Tests that a loaded child is invalidated when it becomes alive. 309 /// </summary> 310 [Test] 311 public void TestChildInvalidatedWhenMadeAlive() 312 { 313 Container parent = null; 314 Drawable child = null; 315 bool invalidated = false; 316 317 AddStep("create test", () => 318 { 319 Child = parent = new Container 320 { 321 Size = new Vector2(200), 322 Child = child = new Box { RelativeSizeAxes = Axes.Both } 323 }; 324 }); 325 326 AddStep("make child dead", () => 327 { 328 child.LifetimeStart = double.MaxValue; 329 child.Invalidated += _ => invalidated = true; 330 }); 331 332 // See above: won't cause an invalidation 333 AddStep("resize parent", () => parent.Size = new Vector2(400)); 334 335 AddStep("make child alive", () => child.LifetimeStart = double.MinValue); 336 AddAssert("child invalidated", () => invalidated); 337 338 // Final check to make sure that the correct invalidation occurred 339 AddAssert("child size matches parent", () => child.DrawSize == parent.Size); 340 } 341 342 /// <summary> 343 /// Tests that non-alive children always receive Parent invalidations. 344 /// </summary> 345 [Test] 346 public void TestNonAliveChildReceivesParentInvalidations() 347 { 348 Container parent = null; 349 bool invalidated = false; 350 351 AddStep("create test", () => 352 { 353 Drawable child; 354 355 Child = parent = new Container 356 { 357 Size = new Vector2(200), 358 Child = child = new Box 359 { 360 RelativeSizeAxes = Axes.Both, 361 LifetimeStart = double.MaxValue 362 } 363 }; 364 365 child.Invalidated += _ => invalidated = true; 366 }); 367 368 AddStep("invalidate parent", () => 369 { 370 invalidated = false; 371 parent.Invalidate(Invalidation.Parent); 372 }); 373 374 AddAssert("child invalidated", () => invalidated); 375 } 376 377 /// <summary> 378 /// Tests that DrawNode invalidations never propagate. 379 /// </summary> 380 [Test] 381 public void TestDrawNodeInvalidationsNeverPropagate() 382 { 383 Container parent = null; 384 bool invalidated = false; 385 386 AddStep("create test", () => 387 { 388 Drawable child; 389 390 Child = parent = new Container 391 { 392 Size = new Vector2(200), 393 Child = child = new Box { RelativeSizeAxes = Axes.Both } 394 }; 395 396 child.Invalidated += _ => invalidated = true; 397 }); 398 399 AddStep("invalidate parent", () => 400 { 401 invalidated = false; 402 parent.Invalidate(Invalidation.DrawNode); 403 }); 404 405 AddAssert("child not invalidated", () => !invalidated); 406 } 407 408 private class TestBox1 : Box 409 { 410 public override bool RemoveWhenNotAlive => false; 411 } 412 413 private class TestContainer1 : Container 414 { 415 public new Action<Invalidation> Invalidated; 416 417 protected override bool OnInvalidate(Invalidation invalidation, InvalidationSource source) 418 { 419 Invalidated?.Invoke(invalidation); 420 return base.OnInvalidate(invalidation, source); 421 } 422 } 423 } 424}