mirror of
https://github.com/space-wizards/RobustToolbox.git
synced 2026-09-01 17:47:24 +02:00
469 lines
17 KiB
C#
469 lines
17 KiB
C#
using System.Diagnostics.CodeAnalysis;
|
|
using System.Numerics;
|
|
using NUnit.Framework;
|
|
using Robust.Client.UserInterface;
|
|
using Robust.Client.UserInterface.Controls;
|
|
using Robust.Client.UserInterface.CustomControls;
|
|
using Robust.Client.UserInterface.XAML.Proxy;
|
|
using Robust.Shared.Input;
|
|
using Robust.Shared.IoC;
|
|
using Robust.Shared.Map;
|
|
using Robust.Shared.Maths;
|
|
|
|
namespace Robust.UnitTesting.Client.UserInterface
|
|
{
|
|
[TestFixture]
|
|
public sealed class UserInterfaceManagerTest : RobustUnitTest
|
|
{
|
|
public override UnitTestProject Project => UnitTestProject.Client;
|
|
|
|
private IUserInterfaceManagerInternal _userInterfaceManager = default!;
|
|
|
|
[OneTimeSetUp]
|
|
public void Setup()
|
|
{
|
|
_userInterfaceManager = IoCManager.Resolve<IUserInterfaceManagerInternal>();
|
|
_userInterfaceManager.InitializeTesting();
|
|
IoCManager.Resolve<IXamlProxyManager>().Initialize();
|
|
}
|
|
|
|
[Test]
|
|
[SuppressMessage("ReSharper", "AccessToModifiedClosure")]
|
|
public void TestMouseDown()
|
|
{
|
|
// We create 4 controls.
|
|
// Control 2 is set to stop mouse events,
|
|
// Control 3 pass,
|
|
// Control 4 ignore.
|
|
// We check that 4 and 1 do not receive events, that 3 receives before 2, and that positions are correct.
|
|
var control1 = new LayoutContainer
|
|
{
|
|
MinSize = new Vector2(50, 50)
|
|
};
|
|
var control2 = new LayoutContainer
|
|
{
|
|
MinSize = new Vector2(50, 50),
|
|
MouseFilter = Control.MouseFilterMode.Stop
|
|
};
|
|
var control3 = new LayoutContainer
|
|
{
|
|
MinSize = new Vector2(50, 50),
|
|
MouseFilter = Control.MouseFilterMode.Pass
|
|
};
|
|
var control4 = new LayoutContainer
|
|
{
|
|
MinSize = new Vector2(50, 50),
|
|
MouseFilter = Control.MouseFilterMode.Ignore
|
|
};
|
|
|
|
_userInterfaceManager.RootControl.AddChild(control1);
|
|
control1.AddChild(control2);
|
|
// Offsets to test relative positioning on the events.
|
|
LayoutContainer.SetPosition(control2, new Vector2(5, 5));
|
|
control2.AddChild(control3);
|
|
LayoutContainer.SetPosition(control3, new Vector2(5, 5));
|
|
control3.AddChild(control4);
|
|
LayoutContainer.SetPosition(control4, new Vector2(5, 5));
|
|
|
|
control1.Arrange(new UIBox2(0, 0, 50, 50));
|
|
|
|
var mouseEvent = new BoundKeyEventArgs(EngineKeyFunctions.Use, BoundKeyState.Down,
|
|
new ScreenCoordinates(30, 30, WindowId.Main), true);
|
|
|
|
var control2Fired = false;
|
|
var control3Fired = false;
|
|
|
|
control1.OnKeyBindDown += _ => Assert.Fail("Control 1 should not get a mouse event.");
|
|
|
|
void Control2MouseDown(GUIBoundKeyEventArgs ev)
|
|
{
|
|
Assert.That(control2Fired, NUnit.Framework.Is.False);
|
|
Assert.That(control3Fired, NUnit.Framework.Is.True);
|
|
|
|
Assert.That(ev.RelativePosition, NUnit.Framework.Is.EqualTo(new Vector2(25, 25)));
|
|
|
|
control2Fired = true;
|
|
}
|
|
|
|
control2.OnKeyBindDown += Control2MouseDown;
|
|
|
|
control3.OnKeyBindDown += ev =>
|
|
{
|
|
Assert.That(control2Fired, NUnit.Framework.Is.False);
|
|
Assert.That(control3Fired, NUnit.Framework.Is.False);
|
|
|
|
Assert.That(ev.RelativePosition, NUnit.Framework.Is.EqualTo(new Vector2(20, 20)));
|
|
|
|
control3Fired = true;
|
|
};
|
|
|
|
control4.OnKeyBindDown += _ => Assert.Fail("Control 4 should not get a mouse event.");
|
|
|
|
_userInterfaceManager.KeyBindDown(mouseEvent);
|
|
_userInterfaceManager.KeyBindUp(mouseEvent);
|
|
|
|
Assert.Multiple(() =>
|
|
{
|
|
Assert.That(control2Fired, NUnit.Framework.Is.True);
|
|
Assert.That(control3Fired, NUnit.Framework.Is.True);
|
|
});
|
|
|
|
// Step two: instead of relying on stop for control2 to prevent the event reaching control1,
|
|
// handle the event in control2.
|
|
|
|
control2Fired = false;
|
|
control3Fired = false;
|
|
|
|
control2.OnKeyBindDown -= Control2MouseDown;
|
|
control2.OnKeyBindDown += ev =>
|
|
{
|
|
Assert.That(control2Fired, NUnit.Framework.Is.False);
|
|
Assert.That(control3Fired, NUnit.Framework.Is.True);
|
|
|
|
Assert.That(ev.RelativePosition, NUnit.Framework.Is.EqualTo(new Vector2(25, 25)));
|
|
|
|
control2Fired = true;
|
|
ev.Handle();
|
|
};
|
|
control2.MouseFilter = Control.MouseFilterMode.Pass;
|
|
|
|
_userInterfaceManager.KeyBindDown(mouseEvent);
|
|
_userInterfaceManager.KeyBindUp(mouseEvent);
|
|
|
|
Assert.Multiple(() =>
|
|
{
|
|
Assert.That(control2Fired, NUnit.Framework.Is.True);
|
|
Assert.That(control3Fired, NUnit.Framework.Is.True);
|
|
});
|
|
|
|
control1.Orphan();
|
|
}
|
|
|
|
[Test]
|
|
public void TestGrabKeyboardFocus()
|
|
{
|
|
Assert.That(_userInterfaceManager.KeyboardFocused, NUnit.Framework.Is.Null);
|
|
var control1 = new Control {CanKeyboardFocus = true};
|
|
|
|
control1.GrabKeyboardFocus();
|
|
Assert.That(_userInterfaceManager.KeyboardFocused, NUnit.Framework.Is.EqualTo(control1));
|
|
Assert.That(control1.HasKeyboardFocus(), NUnit.Framework.Is.EqualTo(true));
|
|
|
|
control1.ReleaseKeyboardFocus();
|
|
Assert.That(_userInterfaceManager.KeyboardFocused, NUnit.Framework.Is.Null);
|
|
}
|
|
|
|
[Test]
|
|
public void TestGrabKeyboardFocusSteal()
|
|
{
|
|
Assert.That(_userInterfaceManager.KeyboardFocused, NUnit.Framework.Is.Null);
|
|
var control1 = new Control {CanKeyboardFocus = true};
|
|
var control2 = new Control {CanKeyboardFocus = true};
|
|
|
|
control1.GrabKeyboardFocus();
|
|
control2.GrabKeyboardFocus();
|
|
Assert.That(_userInterfaceManager.KeyboardFocused, NUnit.Framework.Is.EqualTo(control2));
|
|
control2.ReleaseKeyboardFocus();
|
|
Assert.That(_userInterfaceManager.KeyboardFocused, NUnit.Framework.Is.Null);
|
|
}
|
|
|
|
[Test]
|
|
public void TestGrabKeyboardFocusOtherRelease()
|
|
{
|
|
Assert.That(_userInterfaceManager.KeyboardFocused, NUnit.Framework.Is.Null);
|
|
var control1 = new Control {CanKeyboardFocus = true};
|
|
var control2 = new Control {CanKeyboardFocus = true};
|
|
|
|
control1.GrabKeyboardFocus();
|
|
control2.ReleaseKeyboardFocus();
|
|
Assert.That(_userInterfaceManager.KeyboardFocused, NUnit.Framework.Is.EqualTo(control1));
|
|
_userInterfaceManager.ReleaseKeyboardFocus();
|
|
Assert.That(_userInterfaceManager.KeyboardFocused, NUnit.Framework.Is.Null);
|
|
}
|
|
|
|
[Test]
|
|
public void TestGrabKeyboardFocusNull()
|
|
{
|
|
Assert.That(() => _userInterfaceManager.GrabKeyboardFocus(null!), Throws.ArgumentNullException);
|
|
Assert.That(() => _userInterfaceManager.ReleaseKeyboardFocus(null!), Throws.ArgumentNullException);
|
|
}
|
|
|
|
[Test]
|
|
public void TestGrabKeyboardFocusBlocked()
|
|
{
|
|
var control = new Control();
|
|
Assert.That(() => _userInterfaceManager.GrabKeyboardFocus(control), Throws.ArgumentException);
|
|
}
|
|
|
|
[Test]
|
|
public void TestGrabKeyboardFocusOnClick()
|
|
{
|
|
var control = new Control
|
|
{
|
|
CanKeyboardFocus = true,
|
|
KeyboardFocusOnClick = true,
|
|
MinSize = new Vector2(50, 50),
|
|
MouseFilter = Control.MouseFilterMode.Stop
|
|
};
|
|
|
|
_userInterfaceManager.RootControl.AddChild(control);
|
|
|
|
_userInterfaceManager.RootControl.Arrange(new UIBox2(0, 0, 50, 50));
|
|
|
|
_userInterfaceManager.HandleCanFocusDown(new ScreenCoordinates(30, 30, WindowId.Main), out _);
|
|
|
|
Assert.That(_userInterfaceManager.KeyboardFocused, NUnit.Framework.Is.EqualTo(control));
|
|
_userInterfaceManager.ReleaseKeyboardFocus();
|
|
Assert.That(_userInterfaceManager.KeyboardFocused, NUnit.Framework.Is.Null);
|
|
|
|
control.Orphan();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Assert that indeed nothing happens when the control has focus modes off.
|
|
/// </summary>
|
|
[Test]
|
|
public void TestNotGrabKeyboardFocusOnClick()
|
|
{
|
|
var control = new Control
|
|
{
|
|
MinSize = new Vector2(50, 50),
|
|
MouseFilter = Control.MouseFilterMode.Stop
|
|
};
|
|
|
|
_userInterfaceManager.RootControl.AddChild(control);
|
|
|
|
var pos = new ScreenCoordinates(30, 30, WindowId.Main);
|
|
|
|
var mouseEvent = new GUIBoundKeyEventArgs(EngineKeyFunctions.Use, BoundKeyState.Down,
|
|
pos, true, pos.Position / 1 - control.GlobalPosition, pos.Position - control.GlobalPixelPosition);
|
|
|
|
_userInterfaceManager.KeyBindDown(mouseEvent);
|
|
_userInterfaceManager.KeyBindUp(mouseEvent);
|
|
|
|
Assert.That(_userInterfaceManager.KeyboardFocused, NUnit.Framework.Is.Null);
|
|
|
|
control.Orphan();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Assert that windows correctly re-position themselves if their parent is re-sized.
|
|
/// </summary>
|
|
[Test]
|
|
public void TestWindowTracksRelativePositionOnResize()
|
|
{
|
|
_userInterfaceManager.RootControl.Arrange(new UIBox2(0, 0, 100, 100));
|
|
|
|
var window = new TestWindow
|
|
{
|
|
SetSize = new Vector2(20, 20),
|
|
};
|
|
|
|
_userInterfaceManager.WindowRoot.AddChild(window);
|
|
|
|
var initialPos = new Vector2(40, 40);
|
|
|
|
LayoutContainer.SetPosition(window, initialPos);
|
|
Assert.That(window.Position, Is.EqualTo(initialPos));
|
|
|
|
_userInterfaceManager.WindowRoot.InvalidateArrange();
|
|
_userInterfaceManager.RootControl.Arrange(new UIBox2(0, 0, 200, 200));
|
|
|
|
// 40,40 x 2 + 10
|
|
Assert.That(window.Position, Is.EqualTo(new Vector2(90, 90)));
|
|
|
|
window.Orphan();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Assert that window open/close state and events behave consistently.
|
|
/// </summary>
|
|
[Test]
|
|
public void TestWindowOpenCloseLifecycle()
|
|
{
|
|
var window = new DefaultWindow();
|
|
var closeCount = 0;
|
|
window.OnClose += () => closeCount++;
|
|
|
|
Assert.That(window.IsOpen, Is.False);
|
|
Assert.That(window.Parent, Is.Null);
|
|
|
|
window.Close();
|
|
Assert.That(closeCount, Is.EqualTo(0));
|
|
|
|
window.Open();
|
|
Assert.Multiple(() =>
|
|
{
|
|
Assert.That(window.IsOpen, Is.True);
|
|
Assert.That(window.Parent, Is.SameAs(_userInterfaceManager.WindowRoot));
|
|
Assert.That(_userInterfaceManager.WindowRoot.Children, Does.Contain(window));
|
|
});
|
|
|
|
window.Close();
|
|
Assert.Multiple(() =>
|
|
{
|
|
Assert.That(window.IsOpen, Is.False);
|
|
Assert.That(window.Parent, Is.Null);
|
|
Assert.That(closeCount, Is.EqualTo(1));
|
|
});
|
|
|
|
window.Close();
|
|
Assert.That(closeCount, Is.EqualTo(1));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Assert that a DefaultWindow close button still works after the window exits the UI tree and is reopened.
|
|
/// </summary>
|
|
[Test]
|
|
public void TestWindowCloseButtonWorksAfterReopen()
|
|
{
|
|
_userInterfaceManager.RootControl.Arrange(new UIBox2(0, 0, 100, 100));
|
|
|
|
var window = new DefaultWindow();
|
|
var closeButton = UiTestHelpers.FindDescendant<TextureButton>(
|
|
window,
|
|
control => control.HasStyleClass(DefaultWindow.StyleClassWindowCloseButton));
|
|
|
|
closeButton.MinSize = new Vector2(10, 10);
|
|
|
|
var closeCount = 0;
|
|
window.OnClose += () => closeCount++;
|
|
|
|
window.Open();
|
|
Assert.That(window.IsOpen, Is.True);
|
|
|
|
UiTestHelpers.ClickButton(closeButton);
|
|
Assert.Multiple(() =>
|
|
{
|
|
Assert.That(window.IsOpen, Is.False);
|
|
Assert.That(closeCount, Is.EqualTo(1));
|
|
});
|
|
|
|
window.Open();
|
|
Assert.That(window.IsOpen, Is.True);
|
|
|
|
UiTestHelpers.ClickButton(closeButton);
|
|
Assert.Multiple(() =>
|
|
{
|
|
Assert.That(window.IsOpen, Is.False);
|
|
Assert.That(closeCount, Is.EqualTo(2));
|
|
});
|
|
}
|
|
|
|
/// <summary>
|
|
/// Assert that windows can be moved by dragging them.
|
|
/// </summary>
|
|
[Test]
|
|
public void TestWindowCanBeMovedByDragging()
|
|
{
|
|
_userInterfaceManager.RootControl.Arrange(new UIBox2(0, 0, 100, 100));
|
|
|
|
var window = new TestWindow
|
|
{
|
|
SetSize = new Vector2(40, 40),
|
|
MouseFilter = Control.MouseFilterMode.Stop,
|
|
};
|
|
|
|
_userInterfaceManager.WindowRoot.AddChild(window);
|
|
window.Arrange(UIBox2.FromDimensions(new Vector2(20, 20), new Vector2(40, 40)));
|
|
|
|
window.TestKeyBindDown(CreateGuiEvent(BoundKeyState.Down, new Vector2(30, 30), new Vector2(10, 10)));
|
|
window.TestMouseMove(CreateMouseMoveEvent(window, new Vector2(60, 60)));
|
|
window.TestKeyBindUp(CreateGuiEvent(BoundKeyState.Up, new Vector2(60, 60), new Vector2(40, 40)));
|
|
|
|
Assert.That(window.Position, Is.EqualTo(new Vector2(50, 50)));
|
|
|
|
window.Orphan();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Assert that resizable windows can be resized by dragging their bottom-right corner.
|
|
/// </summary>
|
|
[Test]
|
|
public void TestWindowCanBeResizedByDragging()
|
|
{
|
|
_userInterfaceManager.RootControl.Arrange(new UIBox2(0, 0, 100, 100));
|
|
|
|
var window = new TestWindow
|
|
{
|
|
SetSize = new Vector2(40, 40),
|
|
MinSize = new Vector2(10, 10),
|
|
MouseFilter = Control.MouseFilterMode.Stop,
|
|
};
|
|
|
|
_userInterfaceManager.WindowRoot.AddChild(window);
|
|
window.Arrange(UIBox2.FromDimensions(new Vector2(20, 20), new Vector2(40, 40)));
|
|
|
|
window.TestKeyBindDown(CreateGuiEvent(BoundKeyState.Down, new Vector2(59, 59), new Vector2(39, 39)));
|
|
window.TestMouseMove(CreateMouseMoveEvent(window, new Vector2(79, 84)));
|
|
window.TestKeyBindUp(CreateGuiEvent(BoundKeyState.Up, new Vector2(79, 84), new Vector2(59, 64)));
|
|
|
|
Assert.Multiple(() =>
|
|
{
|
|
Assert.That(window.Position, Is.EqualTo(new Vector2(20, 20)));
|
|
Assert.That(window.SetSize, Is.EqualTo(new Vector2(60, 65)));
|
|
});
|
|
|
|
window.Orphan();
|
|
}
|
|
|
|
private static GUIBoundKeyEventArgs CreateGuiEvent(
|
|
BoundKeyState state,
|
|
Vector2 globalPosition,
|
|
Vector2 relativePosition)
|
|
{
|
|
return new GUIBoundKeyEventArgs(
|
|
EngineKeyFunctions.UIClick,
|
|
state,
|
|
new ScreenCoordinates(globalPosition, WindowId.Main),
|
|
true,
|
|
relativePosition,
|
|
relativePosition);
|
|
}
|
|
|
|
private static GUIMouseMoveEventArgs CreateMouseMoveEvent(Control control, Vector2 globalPosition)
|
|
{
|
|
return new GUIMouseMoveEventArgs(
|
|
Vector2.Zero,
|
|
control,
|
|
globalPosition,
|
|
new ScreenCoordinates(globalPosition, WindowId.Main),
|
|
globalPosition - control.Position,
|
|
globalPosition - control.PixelPosition);
|
|
}
|
|
|
|
private sealed class TestWindow : BaseWindow
|
|
{
|
|
public void TestKeyBindDown(GUIBoundKeyEventArgs args)
|
|
{
|
|
KeyBindDown(args);
|
|
}
|
|
|
|
public void TestKeyBindUp(GUIBoundKeyEventArgs args)
|
|
{
|
|
KeyBindUp(args);
|
|
}
|
|
|
|
public void TestMouseMove(GUIMouseMoveEventArgs args)
|
|
{
|
|
MouseMove(args);
|
|
}
|
|
|
|
protected override DragMode GetDragModeFor(Vector2 relativeMousePos)
|
|
{
|
|
if (Resizable &&
|
|
relativeMousePos.X > Size.X - 5 &&
|
|
relativeMousePos.Y > Size.Y - 5)
|
|
{
|
|
return DragMode.Bottom | DragMode.Right;
|
|
}
|
|
|
|
if (relativeMousePos.Y < 20)
|
|
return DragMode.Move;
|
|
|
|
return DragMode.None;
|
|
}
|
|
}
|
|
}
|
|
}
|