From 3fcad576a1620882309b50d0d19cffde3a7f1c36 Mon Sep 17 00:00:00 2001 From: David Svez Date: Sun, 23 Aug 2026 17:03:49 -0500 Subject: [PATCH] Fix GUI traversal during child removal --- Source/Engine/Tests/TestContainerControl.cs | 42 +++++++++++++++++++++ Source/Engine/UI/GUI/ContainerControl.cs | 28 +++++++------- 2 files changed, 56 insertions(+), 14 deletions(-) diff --git a/Source/Engine/Tests/TestContainerControl.cs b/Source/Engine/Tests/TestContainerControl.cs index 3b3c4856a..ce2b4503e 100644 --- a/Source/Engine/Tests/TestContainerControl.cs +++ b/Source/Engine/Tests/TestContainerControl.cs @@ -26,6 +26,29 @@ namespace FlaxEngine.Tests } } + private sealed class RemovingControl : MyControl + { + private readonly Control[] _controlsToRemove; + + public RemovingControl(float x, float y, float width, float height, params Control[] controlsToRemove) + : base(x, y, width, height) + { + _controlsToRemove = controlsToRemove; + } + + public override void OnMouseEnter(Float2 location) + { + for (int i = 0; i < _controlsToRemove.Length; i++) + { + var control = _controlsToRemove[i]; + if (control.Parent == Parent) + control.Parent = null; + } + + base.OnMouseEnter(location); + } + } + [Test] public void TestChildren() { @@ -53,6 +76,25 @@ namespace FlaxEngine.Tests Assert.AreEqual(cc1.GetChildAt(new Vector2(15, 5)), cc2); Assert.AreEqual(cc1.GetChildAtRecursive(new Vector2(35, 25)), c3); } + + [Test] + public void TestMouseMoveAllowsChildrenRemoval() + { + var container = new MyContainerControl(0, 0, 100, 100); + var first = new MyControl(0, 0, 100, 100); + var second = new MyControl(0, 0, 100, 100); + var removing = new RemovingControl(0, 0, 100, 100, first, second); + container.AddChild(first); + container.AddChild(second); + container.AddChild(removing); + + // The top-most child removes multiple siblings during input dispatch. + // Traversal must not use the now-stale next index. + container.OnMouseMove(new Float2(50, 50)); + + Assert.AreEqual(1, container.ChildrenCount); + Assert.AreEqual(removing, container.GetChild(0)); + } } } #endif diff --git a/Source/Engine/UI/GUI/ContainerControl.cs b/Source/Engine/UI/GUI/ContainerControl.cs index 55fcecf80..aa0078c19 100644 --- a/Source/Engine/UI/GUI/ContainerControl.cs +++ b/Source/Engine/UI/GUI/ContainerControl.cs @@ -910,7 +910,7 @@ namespace FlaxEngine.GUI return false; } } - for (int i = _children.Count - 1; i >= 0 && _children.Count > 0; i--) + for (int i = _children.Count - 1; i >= 0 && i < _children.Count; i--) { var child = _children[i]; if (child.Visible) @@ -928,7 +928,7 @@ namespace FlaxEngine.GUI public override void OnMouseEnter(Float2 location) { // Check all children collisions with mouse and fire events for them - for (int i = _children.Count - 1; i >= 0 && _children.Count > 0; i--) + for (int i = _children.Count - 1; i >= 0 && i < _children.Count; i--) { var child = _children[i]; if (child.Visible && child.Enabled) @@ -948,7 +948,7 @@ namespace FlaxEngine.GUI public override void OnMouseMove(Float2 location) { // Check all children collisions with mouse and fire events for them - for (int i = _children.Count - 1; i >= 0 && _children.Count > 0; i--) + for (int i = _children.Count - 1; i >= 0 && i < _children.Count; i--) { var child = _children[i]; if (child.Visible && child.Enabled) @@ -998,7 +998,7 @@ namespace FlaxEngine.GUI public override bool OnMouseWheel(Float2 location, float delta) { // Check all children collisions with mouse and fire events for them - for (int i = _children.Count - 1; i >= 0 && _children.Count > 0; i--) + for (int i = _children.Count - 1; i >= 0 && i < _children.Count; i--) { var child = _children[i]; if (child.Visible && child.Enabled) @@ -1019,7 +1019,7 @@ namespace FlaxEngine.GUI public override bool OnMouseDown(Float2 location, MouseButton button) { // Check all children collisions with mouse and fire events for them - for (int i = _children.Count - 1; i >= 0 && _children.Count > 0; i--) + for (int i = _children.Count - 1; i >= 0 && i < _children.Count; i--) { var child = _children[i]; if (child.Visible && child.Enabled) @@ -1040,7 +1040,7 @@ namespace FlaxEngine.GUI public override bool OnMouseUp(Float2 location, MouseButton button) { // Check all children collisions with mouse and fire events for them - for (int i = _children.Count - 1; i >= 0 && _children.Count > 0; i--) + for (int i = _children.Count - 1; i >= 0 && i < _children.Count; i--) { var child = _children[i]; if (child.Visible && child.Enabled) @@ -1061,7 +1061,7 @@ namespace FlaxEngine.GUI public override bool OnMouseDoubleClick(Float2 location, MouseButton button) { // Check all children collisions with mouse and fire events for them - for (int i = _children.Count - 1; i >= 0 && _children.Count > 0; i--) + for (int i = _children.Count - 1; i >= 0 && i < _children.Count; i--) { var child = _children[i]; if (child.Visible && child.Enabled) @@ -1096,7 +1096,7 @@ namespace FlaxEngine.GUI /// public override void OnTouchEnter(Float2 location, int pointerId) { - for (int i = _children.Count - 1; i >= 0 && _children.Count > 0; i--) + for (int i = _children.Count - 1; i >= 0 && i < _children.Count; i--) { var child = _children[i]; if (child.Visible && child.Enabled && !child.IsTouchPointerOver(pointerId)) @@ -1114,7 +1114,7 @@ namespace FlaxEngine.GUI /// public override bool OnTouchDown(Float2 location, int pointerId) { - for (int i = _children.Count - 1; i >= 0 && _children.Count > 0; i--) + for (int i = _children.Count - 1; i >= 0 && i < _children.Count; i--) { var child = _children[i]; if (child.Visible && child.Enabled) @@ -1139,7 +1139,7 @@ namespace FlaxEngine.GUI /// public override void OnTouchMove(Float2 location, int pointerId) { - for (int i = _children.Count - 1; i >= 0 && _children.Count > 0; i--) + for (int i = _children.Count - 1; i >= 0 && i < _children.Count; i--) { var child = _children[i]; if (child.Visible && child.Enabled) @@ -1168,7 +1168,7 @@ namespace FlaxEngine.GUI /// public override bool OnTouchUp(Float2 location, int pointerId) { - for (int i = _children.Count - 1; i >= 0 && _children.Count > 0; i--) + for (int i = _children.Count - 1; i >= 0 && i < _children.Count; i--) { var child = _children[i]; if (child.Visible && child.Enabled && child.IsTouchPointerOver(pointerId)) @@ -1250,7 +1250,7 @@ namespace FlaxEngine.GUI var result = base.OnDragEnter(ref location, data); // Check all children collisions with mouse and fire events for them - for (int i = _children.Count - 1; i >= 0 && _children.Count > 0; i--) + for (int i = _children.Count - 1; i >= 0 && i < _children.Count; i--) { var child = _children[i]; if (child.Visible && child.Enabled) @@ -1275,7 +1275,7 @@ namespace FlaxEngine.GUI var result = base.OnDragMove(ref location, data); // Check all children collisions with mouse and fire events for them - for (int i = _children.Count - 1; i >= 0 && _children.Count > 0; i--) + for (int i = _children.Count - 1; i >= 0 && i < _children.Count; i--) { var child = _children[i]; if (child.Visible && child.Enabled) @@ -1333,7 +1333,7 @@ namespace FlaxEngine.GUI var result = base.OnDragDrop(ref location, data); // Check all children collisions with mouse and fire events for them - for (int i = _children.Count - 1; i >= 0 && _children.Count > 0; i--) + for (int i = _children.Count - 1; i >= 0 && i < _children.Count; i--) { var child = _children[i]; if (child.Visible && child.Enabled)