From fc55c8e0d3ff484158ab54463fa6ad8deba16cb2 Mon Sep 17 00:00:00 2001 From: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com> Date: Tue, 4 Feb 2025 00:19:22 +1100 Subject: [PATCH] Add saved BUI positions (#5650) --- RELEASE-NOTES.md | 4 +- .../EntitySystems/UserInterfaceSystem.cs | 61 +++++++++++++++++++ .../UserInterface/BoundUserInterfaceExt.cs | 40 +++++++++++- .../CustomControls/BaseWindow.cs | 13 +++- .../UserInterface/BoundUserInterface.cs | 2 +- .../Systems/SharedUserInterfaceSystem.cs | 52 +++++++++++++--- 6 files changed, 156 insertions(+), 16 deletions(-) diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md index 90c9366f47a..361d04eb011 100644 --- a/RELEASE-NOTES.md +++ b/RELEASE-NOTES.md @@ -35,11 +35,11 @@ END TEMPLATE--> ### Breaking changes -*None yet* +* RemoveChild is called after OnClose for BaseWindow. ### New features -*None yet* +* BUIs now have their positions saved when closed and re-used when opened when using the `CreateWindow` helper or via manually registering it via RegisterControl. ### Bugfixes diff --git a/Robust.Client/GameObjects/EntitySystems/UserInterfaceSystem.cs b/Robust.Client/GameObjects/EntitySystems/UserInterfaceSystem.cs index 7c290cb4e9f..4e7fb31f2ad 100644 --- a/Robust.Client/GameObjects/EntitySystems/UserInterfaceSystem.cs +++ b/Robust.Client/GameObjects/EntitySystems/UserInterfaceSystem.cs @@ -1,10 +1,18 @@ +using System; +using System.Collections.Generic; +using System.Numerics; +using Robust.Client.UserInterface; using Robust.Shared.GameObjects; using Robust.Shared.Prototypes; +using Robust.Shared.Utility; namespace Robust.Client.GameObjects; public sealed class UserInterfaceSystem : SharedUserInterfaceSystem { + private Dictionary> _savedPositions = new(); + private Dictionary _registeredControls = new(); + public override void Initialize() { base.Initialize(); @@ -17,6 +25,59 @@ public override void Shutdown() ProtoManager.PrototypesReloaded -= OnProtoReload; } + protected override void OnUserInterfaceShutdown(Entity ent, ref ComponentShutdown args) + { + base.OnUserInterfaceShutdown(ent, ref args); + _savedPositions.Remove(ent.Owner); + } + + /// + public override void OpenUi(Entity entity, Enum key, bool predicted = false) + { + var player = Player.LocalEntity; + + if (player == null) + return; + + OpenUi(entity, key, player.Value, predicted); + } + + protected override void SavePosition(BoundUserInterface bui) + { + if (!_registeredControls.Remove(bui, out var control)) + return; + + var keyed = _savedPositions[bui.Owner]; + keyed[bui.UiKey] = control.Position; + } + + /// + /// Registers a control so it will later have its position stored by when the BUI is closed. + /// + public void RegisterControl(BoundUserInterface bui, Control control) + { + DebugTools.Assert(!_registeredControls.ContainsKey(bui)); + _registeredControls[bui] = control; + _savedPositions.GetOrNew(bui.Owner); + } + + public override bool TryGetPosition(Entity entity, Enum key, out Vector2 position) + { + position = default; + + if (!_savedPositions.TryGetValue(entity.Owner, out var keyed)) + { + return false; + } + + if (!keyed.TryGetValue(key, out position)) + { + return false; + } + + return true; + } + private void OnProtoReload(PrototypesReloadedEventArgs obj) { var player = Player.LocalEntity; diff --git a/Robust.Client/UserInterface/BoundUserInterfaceExt.cs b/Robust.Client/UserInterface/BoundUserInterfaceExt.cs index eb59385a367..54a2b635fe1 100644 --- a/Robust.Client/UserInterface/BoundUserInterfaceExt.cs +++ b/Robust.Client/UserInterface/BoundUserInterfaceExt.cs @@ -1,4 +1,5 @@ using System; +using Robust.Client.GameObjects; using Robust.Client.UserInterface.CustomControls; using Robust.Shared.GameObjects; @@ -6,14 +7,47 @@ namespace Robust.Client.UserInterface; public static class BoundUserInterfaceExt { + private static T GetWindow(BoundUserInterface bui) where T : BaseWindow, new() + { + var window = bui.CreateDisposableControl(); + window.OnClose += bui.Close; + var system = bui.EntMan.System(); + system.RegisterControl(bui, window); + return window; + } + /// /// Helper method to create a window and also handle closing the BUI when it's closed. /// public static T CreateWindow(this BoundUserInterface bui) where T : BaseWindow, new() { - var window = bui.CreateDisposableControl(); - window.OpenCentered(); - window.OnClose += bui.Close; + var window = GetWindow(bui); + + if (bui.EntMan.System().TryGetPosition(bui.Owner, bui.UiKey, out var position)) + { + window.Open(position); + } + else + { + window.OpenCentered(); + } + + return window; + } + + public static T CreateWindowCenteredLeft(this BoundUserInterface bui) where T : BaseWindow, new() + { + var window = GetWindow(bui); + + if (bui.EntMan.System().TryGetPosition(bui.Owner, bui.UiKey, out var position)) + { + window.Open(position); + } + else + { + window.OpenCenteredLeft(); + } + return window; } diff --git a/Robust.Client/UserInterface/CustomControls/BaseWindow.cs b/Robust.Client/UserInterface/CustomControls/BaseWindow.cs index 2e416f871ec..fa650834dff 100644 --- a/Robust.Client/UserInterface/CustomControls/BaseWindow.cs +++ b/Robust.Client/UserInterface/CustomControls/BaseWindow.cs @@ -5,6 +5,7 @@ using Robust.Shared.Input; using Robust.Shared.IoC; using Robust.Shared.Log; +using Robust.Shared.Map; using Robust.Shared.Maths; using Robust.Shared.Timing; @@ -36,8 +37,8 @@ public virtual void Close() return; } - Parent.RemoveChild(this); OnClose?.Invoke(); + Parent.RemoveChild(this); } protected internal override void KeyBindDown(GUIBoundKeyEventArgs args) @@ -229,6 +230,16 @@ public void Open() OnOpen?.Invoke(); } + /// + /// Opens the window and places it at the specified position. + /// + public void Open(Vector2 position) + { + Measure(Vector2Helpers.Infinity); + Open(); + LayoutContainer.SetPosition(this, position); + } + public void OpenCentered() => OpenCenteredAt(new Vector2(0.5f, 0.5f)); public void OpenToLeft() => OpenCenteredAt(new Vector2(0, 0.5f)); diff --git a/Robust.Shared/GameObjects/Components/UserInterface/BoundUserInterface.cs b/Robust.Shared/GameObjects/Components/UserInterface/BoundUserInterface.cs index 9bdc7b2086e..ea96e6f701a 100644 --- a/Robust.Shared/GameObjects/Components/UserInterface/BoundUserInterface.cs +++ b/Robust.Shared/GameObjects/Components/UserInterface/BoundUserInterface.cs @@ -11,7 +11,7 @@ namespace Robust.Shared.GameObjects /// public abstract class BoundUserInterface : IDisposable { - [Dependency] protected readonly IEntityManager EntMan = default!; + [Dependency] protected internal readonly IEntityManager EntMan = default!; [Dependency] protected readonly ISharedPlayerManager PlayerManager = default!; protected readonly SharedUserInterfaceSystem UiSystem; diff --git a/Robust.Shared/GameObjects/Systems/SharedUserInterfaceSystem.cs b/Robust.Shared/GameObjects/Systems/SharedUserInterfaceSystem.cs index 540867900e3..70f2e077376 100644 --- a/Robust.Shared/GameObjects/Systems/SharedUserInterfaceSystem.cs +++ b/Robust.Shared/GameObjects/Systems/SharedUserInterfaceSystem.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Linq; +using System.Numerics; using System.Runtime.InteropServices; using JetBrains.Annotations; using Robust.Shared.Collections; @@ -45,6 +46,8 @@ public abstract class SharedUserInterfaceSystem : EntitySystem /// private ValueList _keys = new(); + private ValueList _entList = new(); + public override void Initialize() { base.Initialize(); @@ -86,6 +89,11 @@ public override void Initialize() SubscribeLocalEvent(OnActorShutdown); } + private void AddQueued(BoundUserInterface bui, bool value) + { + _queuedBuis.Add((bui, value)); + } + /// /// Validates the received message, and then pass it onto systems/components /// @@ -232,7 +240,7 @@ private void CloseUiInternal(Entity ent, Enum key, Enti if (ent.Comp.ClientOpenInterfaces.TryGetValue(key, out var cBui)) { - _queuedBuis.Add((cBui, false)); + AddQueued(cBui, false); } if (ent.Comp.Actors.Count == 0) @@ -274,18 +282,17 @@ private void OnUserInterfaceStartup(Entity ent, ref Comp // PlayerAttachedEvent will catch some of these. foreach (var (key, bui) in ent.Comp.ClientOpenInterfaces) { - _queuedBuis.Add((bui, true)); + AddQueued(bui, true); } } - private void OnUserInterfaceShutdown(Entity ent, ref ComponentShutdown args) + protected virtual void OnUserInterfaceShutdown(Entity ent, ref ComponentShutdown args) { - var actors = new List(); foreach (var (key, acts) in ent.Comp.Actors) { - actors.Clear(); - actors.AddRange(acts); - foreach (var actor in actors) + _entList.Clear(); + _entList.AddRange(acts); + foreach (var actor in _entList) { CloseUiInternal(ent!, key, actor); DebugTools.Assert(!acts.Contains(actor)); @@ -456,7 +463,7 @@ private void OnUserInterfaceHandleState(Entity ent, ref } var bui = ent.Comp.ClientOpenInterfaces[key]; - _queuedBuis.Add((bui, false)); + AddQueued(bui, false); } } @@ -537,7 +544,7 @@ private void EnsureClientBui(Entity entity, Enum key, In if (!open) return; - _queuedBuis.Add((boundUserInterface, true)); + AddQueued(boundUserInterface, true); } /// @@ -653,6 +660,14 @@ public bool TryOpenUi(Entity entity, Enum key, EntityUi return true; } + /// + /// Opens the UI for the local client. Does nothing on server. + /// + public virtual void OpenUi(Entity entity, Enum key, bool predicted = false) + { + + } + public void OpenUi(Entity entity, Enum key, EntityUid? actor, bool predicted = false) { if (actor == null || !UIQuery.Resolve(entity.Owner, ref entity.Comp, false)) @@ -690,6 +705,23 @@ public void OpenUi(Entity entity, Enum key, ICommonSess OpenUi(entity, key, actorEnt.Value, predicted); } + /// + /// Tries to return the saved position of a user interface. + /// + public virtual bool TryGetPosition(Entity entity, Enum key, out Vector2 position) + { + position = Vector2.Zero; + return false; + } + + /// + /// Saves a position for the BUI. + /// + protected virtual void SavePosition(BoundUserInterface bui) + { + + } + /// /// Sets a BUI state and networks it to all clients. /// @@ -1056,6 +1088,7 @@ public override void Update(float frameTime) } #endif } + // Close BUI else { if (UIQuery.TryComp(bui.Owner, out var uiComp)) @@ -1063,6 +1096,7 @@ public override void Update(float frameTime) uiComp.ClientOpenInterfaces.Remove(bui.UiKey); } + SavePosition(bui); bui.Dispose(); } }