Context menu UI backend refactor & better UX (#13318)
closes https://github.com/space-wizards/space-station-14/issues/9209
This commit is contained in:
@@ -2,10 +2,12 @@ using System.Linq;
|
|||||||
using Content.Client.Administration.Systems;
|
using Content.Client.Administration.Systems;
|
||||||
using Content.Client.UserInterface.Controls;
|
using Content.Client.UserInterface.Controls;
|
||||||
using Content.Client.Verbs;
|
using Content.Client.Verbs;
|
||||||
|
using Content.Client.Verbs.UI;
|
||||||
using Content.Shared.Administration;
|
using Content.Shared.Administration;
|
||||||
using Content.Shared.Input;
|
using Content.Shared.Input;
|
||||||
using Robust.Client.AutoGenerated;
|
using Robust.Client.AutoGenerated;
|
||||||
using Robust.Client.Graphics;
|
using Robust.Client.Graphics;
|
||||||
|
using Robust.Client.UserInterface;
|
||||||
using Robust.Client.UserInterface.Controls;
|
using Robust.Client.UserInterface.Controls;
|
||||||
using Robust.Client.UserInterface.XAML;
|
using Robust.Client.UserInterface.XAML;
|
||||||
using Robust.Shared.Input;
|
using Robust.Shared.Input;
|
||||||
@@ -56,7 +58,7 @@ namespace Content.Client.Administration.UI.CustomControls
|
|||||||
}
|
}
|
||||||
else if (args.Event.Function == EngineKeyFunctions.UseSecondary && selectedPlayer.EntityUid != null)
|
else if (args.Event.Function == EngineKeyFunctions.UseSecondary && selectedPlayer.EntityUid != null)
|
||||||
{
|
{
|
||||||
_verbSystem.VerbMenu.OpenVerbMenu(selectedPlayer.EntityUid.Value);
|
IoCManager.Resolve<IUserInterfaceManager>().GetUIController<VerbMenuUIController>().OpenVerbMenu(selectedPlayer.EntityUid.Value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
|
using Content.Client.ContextMenu.UI;
|
||||||
using Content.Client.Verbs;
|
using Content.Client.Verbs;
|
||||||
using Content.Shared.CombatMode;
|
using Content.Shared.CombatMode;
|
||||||
using Content.Shared.Targeting;
|
using Content.Shared.Targeting;
|
||||||
using Robust.Client.Player;
|
using Robust.Client.Player;
|
||||||
|
using Robust.Client.UserInterface;
|
||||||
|
|
||||||
namespace Content.Client.CombatMode
|
namespace Content.Client.CombatMode
|
||||||
{
|
{
|
||||||
@@ -38,8 +40,7 @@ namespace Content.Client.CombatMode
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var verbs = IoCManager.Resolve<IEntitySystemManager>().GetEntitySystem<VerbSystem>();
|
IoCManager.Resolve<IUserInterfaceManager>().GetUIController<ContextMenuUIController>().Close();
|
||||||
verbs.CloseAllMenus();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ namespace Content.Client.Commands
|
|||||||
|
|
||||||
public string Description => "Sets the entity menu grouping type.";
|
public string Description => "Sets the entity menu grouping type.";
|
||||||
|
|
||||||
public string Help => $"Usage: entitymenug <0:{EntityMenuPresenter.GroupingTypesCount}>";
|
public string Help => $"Usage: entitymenug <0:{EntityMenuUIController.GroupingTypesCount}>";
|
||||||
public void Execute(IConsoleShell shell, string argStr, string[] args)
|
public void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||||
{
|
{
|
||||||
if (args.Length != 1)
|
if (args.Length != 1)
|
||||||
@@ -27,7 +27,7 @@ namespace Content.Client.Commands
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (id < 0 ||id > EntityMenuPresenter.GroupingTypesCount - 1)
|
if (id < 0 ||id > EntityMenuUIController.GroupingTypesCount - 1)
|
||||||
{
|
{
|
||||||
shell.WriteLine($"{args[0]} is not a valid integer.");
|
shell.WriteLine($"{args[0]} is not a valid integer.");
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -31,14 +31,14 @@ namespace Content.Client.ContextMenu.UI
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public GridContainer MenuBody = new();
|
public GridContainer MenuBody = new();
|
||||||
|
|
||||||
private ContextMenuPresenter _presenter;
|
private ContextMenuUIController _uiController;
|
||||||
|
|
||||||
public ContextMenuPopup (ContextMenuPresenter presenter, ContextMenuElement? parentElement) : base()
|
public ContextMenuPopup (ContextMenuUIController uiController, ContextMenuElement? parentElement) : base()
|
||||||
{
|
{
|
||||||
RobustXamlLoader.Load(this);
|
RobustXamlLoader.Load(this);
|
||||||
MenuPanel.SetOnlyStyleClass(StyleClassContextMenuPopup);
|
MenuPanel.SetOnlyStyleClass(StyleClassContextMenuPopup);
|
||||||
|
|
||||||
_presenter = presenter;
|
_uiController = uiController;
|
||||||
ParentElement = parentElement;
|
ParentElement = parentElement;
|
||||||
|
|
||||||
// TODO xaml controls now have the access options -> re-xamlify all this.
|
// TODO xaml controls now have the access options -> re-xamlify all this.
|
||||||
@@ -52,7 +52,7 @@ namespace Content.Client.ContextMenu.UI
|
|||||||
MenuPanel.MaxHeight = MaxItemsBeforeScroll * (ContextMenuElement.ElementHeight + 2 * ContextMenuElement.ElementMargin) + styleSize.Y;
|
MenuPanel.MaxHeight = MaxItemsBeforeScroll * (ContextMenuElement.ElementHeight + 2 * ContextMenuElement.ElementMargin) + styleSize.Y;
|
||||||
|
|
||||||
UserInterfaceManager.ModalRoot.AddChild(this);
|
UserInterfaceManager.ModalRoot.AddChild(this);
|
||||||
MenuBody.OnChildRemoved += ctrl => _presenter.OnRemoveElement(this, ctrl);
|
MenuBody.OnChildRemoved += ctrl => _uiController.OnRemoveElement(this, ctrl);
|
||||||
MenuBody.VSeparationOverride = 0;
|
MenuBody.VSeparationOverride = 0;
|
||||||
MenuBody.HSeparationOverride = 0;
|
MenuBody.HSeparationOverride = 0;
|
||||||
|
|
||||||
@@ -67,13 +67,13 @@ namespace Content.Client.ContextMenu.UI
|
|||||||
OnPopupHide += () =>
|
OnPopupHide += () =>
|
||||||
{
|
{
|
||||||
if (ParentElement != null)
|
if (ParentElement != null)
|
||||||
_presenter.CloseSubMenus(ParentElement.ParentMenu);
|
_uiController.CloseSubMenus(ParentElement.ParentMenu);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void Dispose(bool disposing)
|
protected override void Dispose(bool disposing)
|
||||||
{
|
{
|
||||||
MenuBody.OnChildRemoved -= ctrl => _presenter.OnRemoveElement(this, ctrl);
|
MenuBody.OnChildRemoved -= ctrl => _uiController.OnRemoveElement(this, ctrl);
|
||||||
ParentElement = null;
|
ParentElement = null;
|
||||||
base.Dispose(disposing);
|
base.Dispose(disposing);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,24 +1,26 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
|
using Content.Client.Gameplay;
|
||||||
using Robust.Client.UserInterface;
|
using Robust.Client.UserInterface;
|
||||||
using Robust.Shared.Log;
|
using Robust.Client.UserInterface.Controllers;
|
||||||
using Robust.Shared.Maths;
|
|
||||||
using Timer = Robust.Shared.Timing.Timer;
|
using Timer = Robust.Shared.Timing.Timer;
|
||||||
namespace Content.Client.ContextMenu.UI
|
namespace Content.Client.ContextMenu.UI
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// This class handles all the logic associated with showing a context menu.
|
/// This class handles all the logic associated with showing a context menu, as well as all the state for the
|
||||||
|
/// entire context menu stack, including verb and entity menus. It does not currently support multiple
|
||||||
|
/// open context menus.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <remarks>
|
/// <remarks>
|
||||||
/// This largely involves setting up timers to open and close sub-menus when hovering over other menu elements.
|
/// This largely involves setting up timers to open and close sub-menus when hovering over other menu elements.
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
[Virtual]
|
public sealed class ContextMenuUIController : UIController, IOnStateEntered<GameplayState>, IOnStateExited<GameplayState>
|
||||||
public class ContextMenuPresenter : IDisposable
|
|
||||||
{
|
{
|
||||||
public static readonly TimeSpan HoverDelay = TimeSpan.FromSeconds(0.2);
|
public static readonly TimeSpan HoverDelay = TimeSpan.FromSeconds(0.2);
|
||||||
|
|
||||||
public ContextMenuPopup RootMenu;
|
/// <summary>
|
||||||
|
/// Root menu of the entire context menu.
|
||||||
|
/// </summary>
|
||||||
|
public ContextMenuPopup RootMenu = default!;
|
||||||
public Stack<ContextMenuPopup> Menus { get; } = new();
|
public Stack<ContextMenuPopup> Menus { get; } = new();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -31,30 +33,35 @@ namespace Content.Client.ContextMenu.UI
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public CancellationTokenSource? CancelClose;
|
public CancellationTokenSource? CancelClose;
|
||||||
|
|
||||||
public ContextMenuPresenter()
|
public Action? OnContextClosed;
|
||||||
|
public Action<ContextMenuElement>? OnContextMouseEntered;
|
||||||
|
public Action<ContextMenuElement>? OnContextMouseExited;
|
||||||
|
public Action<ContextMenuElement>? OnSubMenuOpened;
|
||||||
|
public Action<ContextMenuElement, GUIBoundKeyEventArgs>? OnContextKeyEvent;
|
||||||
|
|
||||||
|
public void OnStateEntered(GameplayState state)
|
||||||
{
|
{
|
||||||
RootMenu = new(this, null);
|
RootMenu = new(this, null);
|
||||||
RootMenu.OnPopupHide += RootMenu.MenuBody.DisposeAllChildren;
|
RootMenu.OnPopupHide += Close;
|
||||||
Menus.Push(RootMenu);
|
Menus.Push(RootMenu);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
public void OnStateExited(GameplayState state)
|
||||||
/// Dispose of all UI elements.
|
|
||||||
/// </summary>
|
|
||||||
public virtual void Dispose()
|
|
||||||
{
|
{
|
||||||
RootMenu.OnPopupHide -= RootMenu.MenuBody.DisposeAllChildren;
|
Close();
|
||||||
|
RootMenu.OnPopupHide -= Close;
|
||||||
RootMenu.Dispose();
|
RootMenu.Dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Close and clear the root menu. This will also dispose any sub-menus.
|
/// Close and clear the root menu. This will also dispose any sub-menus.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public virtual void Close()
|
public void Close()
|
||||||
{
|
{
|
||||||
RootMenu.Close();
|
RootMenu.MenuBody.DisposeAllChildren();
|
||||||
CancelOpen?.Cancel();
|
CancelOpen?.Cancel();
|
||||||
CancelClose?.Cancel();
|
CancelClose?.Cancel();
|
||||||
|
OnContextClosed?.Invoke();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -82,7 +89,7 @@ namespace Content.Client.ContextMenu.UI
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Start a timer to open this element's sub-menu.
|
/// Start a timer to open this element's sub-menu.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public virtual void OnMouseEntered(ContextMenuElement element)
|
private void OnMouseEntered(ContextMenuElement element)
|
||||||
{
|
{
|
||||||
if (!Menus.TryPeek(out var topMenu))
|
if (!Menus.TryPeek(out var topMenu))
|
||||||
{
|
{
|
||||||
@@ -100,6 +107,7 @@ namespace Content.Client.ContextMenu.UI
|
|||||||
CancelOpen?.Cancel();
|
CancelOpen?.Cancel();
|
||||||
CancelOpen = new();
|
CancelOpen = new();
|
||||||
Timer.Spawn(HoverDelay, () => OpenSubMenu(element), CancelOpen.Token);
|
Timer.Spawn(HoverDelay, () => OpenSubMenu(element), CancelOpen.Token);
|
||||||
|
OnContextMouseEntered?.Invoke(element);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -108,7 +116,7 @@ namespace Content.Client.ContextMenu.UI
|
|||||||
/// <remarks>
|
/// <remarks>
|
||||||
/// Note that this timer will be aborted when entering the actual sub-menu itself.
|
/// Note that this timer will be aborted when entering the actual sub-menu itself.
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
public virtual void OnMouseExited(ContextMenuElement element)
|
private void OnMouseExited(ContextMenuElement element)
|
||||||
{
|
{
|
||||||
CancelOpen?.Cancel();
|
CancelOpen?.Cancel();
|
||||||
|
|
||||||
@@ -118,9 +126,13 @@ namespace Content.Client.ContextMenu.UI
|
|||||||
CancelClose?.Cancel();
|
CancelClose?.Cancel();
|
||||||
CancelClose = new();
|
CancelClose = new();
|
||||||
Timer.Spawn(HoverDelay, () => CloseSubMenus(element.ParentMenu), CancelClose.Token);
|
Timer.Spawn(HoverDelay, () => CloseSubMenus(element.ParentMenu), CancelClose.Token);
|
||||||
|
OnContextMouseExited?.Invoke(element);
|
||||||
}
|
}
|
||||||
|
|
||||||
public virtual void OnKeyBindDown(ContextMenuElement element, GUIBoundKeyEventArgs args) { }
|
private void OnKeyBindDown(ContextMenuElement element, GUIBoundKeyEventArgs args)
|
||||||
|
{
|
||||||
|
OnContextKeyEvent?.Invoke(element, args);
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Opens a new sub menu, and close the old one.
|
/// Opens a new sub menu, and close the old one.
|
||||||
@@ -128,7 +140,7 @@ namespace Content.Client.ContextMenu.UI
|
|||||||
/// <remarks>
|
/// <remarks>
|
||||||
/// If the given element has no sub-menu, just close the current one.
|
/// If the given element has no sub-menu, just close the current one.
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
public virtual void OpenSubMenu(ContextMenuElement element)
|
public void OpenSubMenu(ContextMenuElement element)
|
||||||
{
|
{
|
||||||
if (!Menus.TryPeek(out var topMenu))
|
if (!Menus.TryPeek(out var topMenu))
|
||||||
{
|
{
|
||||||
@@ -164,6 +176,7 @@ namespace Content.Client.ContextMenu.UI
|
|||||||
element.SubMenu.SetPositionLast();
|
element.SubMenu.SetPositionLast();
|
||||||
|
|
||||||
Menus.Push(element.SubMenu);
|
Menus.Push(element.SubMenu);
|
||||||
|
OnSubMenuOpened?.Invoke(element);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -45,12 +45,12 @@ namespace Content.Client.ContextMenu.UI
|
|||||||
LayoutContainer.SetGrowVertical(CountLabel, LayoutContainer.GrowDirection.Begin);
|
LayoutContainer.SetGrowVertical(CountLabel, LayoutContainer.GrowDirection.Begin);
|
||||||
|
|
||||||
Entity = entity;
|
Entity = entity;
|
||||||
if (Entity != null)
|
if (Entity == null)
|
||||||
{
|
return;
|
||||||
Count = 1;
|
|
||||||
CountLabel.Visible = false;
|
Count = 1;
|
||||||
UpdateEntity();
|
CountLabel.Visible = false;
|
||||||
}
|
UpdateEntity();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void Dispose(bool disposing)
|
protected override void Dispose(bool disposing)
|
||||||
|
|||||||
@@ -1,16 +1,17 @@
|
|||||||
using Content.Shared.IdentityManagement;
|
using Content.Shared.IdentityManagement;
|
||||||
using Robust.Client.GameObjects;
|
using Robust.Client.GameObjects;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using Robust.Client.UserInterface.Controllers;
|
||||||
|
|
||||||
namespace Content.Client.ContextMenu.UI
|
namespace Content.Client.ContextMenu.UI
|
||||||
{
|
{
|
||||||
public sealed partial class EntityMenuPresenter : ContextMenuPresenter
|
public sealed partial class EntityMenuUIController
|
||||||
{
|
{
|
||||||
public const int GroupingTypesCount = 2;
|
public const int GroupingTypesCount = 2;
|
||||||
private int GroupingContextMenuType { get; set; }
|
private int GroupingContextMenuType { get; set; }
|
||||||
public void OnGroupingChanged(int obj)
|
public void OnGroupingChanged(int obj)
|
||||||
{
|
{
|
||||||
Close();
|
_context.Close();
|
||||||
GroupingContextMenuType = obj;
|
GroupingContextMenuType = obj;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,9 @@
|
|||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Content.Client.CombatMode;
|
using Content.Client.CombatMode;
|
||||||
using Content.Client.Examine;
|
using Content.Client.Examine;
|
||||||
using Content.Client.Gameplay;
|
using Content.Client.Gameplay;
|
||||||
using Content.Client.Verbs;
|
using Content.Client.Verbs;
|
||||||
using Content.Client.Viewport;
|
using Content.Client.Verbs.UI;
|
||||||
using Content.Shared.CCVar;
|
using Content.Shared.CCVar;
|
||||||
using Content.Shared.CombatMode;
|
using Content.Shared.CombatMode;
|
||||||
using Content.Shared.Examine;
|
using Content.Shared.Examine;
|
||||||
@@ -15,14 +14,11 @@ using Robust.Client.Input;
|
|||||||
using Robust.Client.Player;
|
using Robust.Client.Player;
|
||||||
using Robust.Client.State;
|
using Robust.Client.State;
|
||||||
using Robust.Client.UserInterface;
|
using Robust.Client.UserInterface;
|
||||||
|
using Robust.Client.UserInterface.Controllers;
|
||||||
using Robust.Shared.Configuration;
|
using Robust.Shared.Configuration;
|
||||||
using Robust.Shared.GameObjects;
|
|
||||||
using Robust.Shared.Input;
|
using Robust.Shared.Input;
|
||||||
using Robust.Shared.Input.Binding;
|
using Robust.Shared.Input.Binding;
|
||||||
using Robust.Shared.IoC;
|
|
||||||
using Robust.Shared.Log;
|
|
||||||
using Robust.Shared.Map;
|
using Robust.Shared.Map;
|
||||||
using Robust.Shared.Maths;
|
|
||||||
using Robust.Shared.Timing;
|
using Robust.Shared.Timing;
|
||||||
|
|
||||||
namespace Content.Client.ContextMenu.UI
|
namespace Content.Client.ContextMenu.UI
|
||||||
@@ -31,11 +27,11 @@ namespace Content.Client.ContextMenu.UI
|
|||||||
/// This class handles the displaying of the entity context menu.
|
/// This class handles the displaying of the entity context menu.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <remarks>
|
/// <remarks>
|
||||||
/// In addition to the normal <see cref="ContextMenuPresenter"/> functionality, this also provides functions get
|
/// This also provides functions to get
|
||||||
/// a list of entities near the mouse position, add them to the context menu grouped by prototypes, and remove
|
/// a list of entities near the mouse position, add them to the context menu grouped by prototypes, and remove
|
||||||
/// them from the menu as they move out of sight.
|
/// them from the menu as they move out of sight.
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
public sealed partial class EntityMenuPresenter : ContextMenuPresenter
|
public sealed partial class EntityMenuUIController : UIController, IOnStateEntered<GameplayState>, IOnStateExited<GameplayState>
|
||||||
{
|
{
|
||||||
[Dependency] private readonly IEntitySystemManager _systemManager = default!;
|
[Dependency] private readonly IEntitySystemManager _systemManager = default!;
|
||||||
[Dependency] private readonly IEntityManager _entityManager = default!;
|
[Dependency] private readonly IEntityManager _entityManager = default!;
|
||||||
@@ -46,11 +42,15 @@ namespace Content.Client.ContextMenu.UI
|
|||||||
[Dependency] private readonly IGameTiming _gameTiming = default!;
|
[Dependency] private readonly IGameTiming _gameTiming = default!;
|
||||||
[Dependency] private readonly IUserInterfaceManager _userInterfaceManager = default!;
|
[Dependency] private readonly IUserInterfaceManager _userInterfaceManager = default!;
|
||||||
[Dependency] private readonly IEyeManager _eyeManager = default!;
|
[Dependency] private readonly IEyeManager _eyeManager = default!;
|
||||||
|
[Dependency] private readonly ContextMenuUIController _context = default!;
|
||||||
|
[Dependency] private readonly VerbMenuUIController _verb = default!;
|
||||||
|
|
||||||
private readonly VerbSystem _verbSystem;
|
[UISystemDependency] private readonly VerbSystem _verbSystem = default!;
|
||||||
private readonly ExamineSystem _examineSystem;
|
[UISystemDependency] private readonly ExamineSystem _examineSystem = default!;
|
||||||
private readonly TransformSystem _xform;
|
[UISystemDependency] private readonly TransformSystem _xform = default!;
|
||||||
private readonly SharedCombatModeSystem _combatMode;
|
[UISystemDependency] private readonly CombatModeSystem _combatMode = default!;
|
||||||
|
|
||||||
|
private bool _updating;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// This maps the currently displayed entities to the actual GUI elements.
|
/// This maps the currently displayed entities to the actual GUI elements.
|
||||||
@@ -60,27 +60,26 @@ namespace Content.Client.ContextMenu.UI
|
|||||||
/// </remarks>
|
/// </remarks>
|
||||||
public Dictionary<EntityUid, EntityMenuElement> Elements = new();
|
public Dictionary<EntityUid, EntityMenuElement> Elements = new();
|
||||||
|
|
||||||
public EntityMenuPresenter(VerbSystem verbSystem) : base()
|
public void OnStateEntered(GameplayState state)
|
||||||
{
|
{
|
||||||
IoCManager.InjectDependencies(this);
|
_updating = true;
|
||||||
|
|
||||||
_verbSystem = verbSystem;
|
|
||||||
_examineSystem = _entityManager.EntitySysManager.GetEntitySystem<ExamineSystem>();
|
|
||||||
_combatMode = _entityManager.EntitySysManager.GetEntitySystem<CombatModeSystem>();
|
|
||||||
_xform = _entityManager.EntitySysManager.GetEntitySystem<TransformSystem>();
|
|
||||||
|
|
||||||
_cfg.OnValueChanged(CCVars.EntityMenuGroupingType, OnGroupingChanged, true);
|
_cfg.OnValueChanged(CCVars.EntityMenuGroupingType, OnGroupingChanged, true);
|
||||||
|
_context.OnContextMouseEntered += OnMouseEntered;
|
||||||
|
_context.OnContextKeyEvent += OnKeyBindDown;
|
||||||
|
|
||||||
CommandBinds.Builder
|
CommandBinds.Builder
|
||||||
.Bind(EngineKeyFunctions.UseSecondary, new PointerInputCmdHandler(HandleOpenEntityMenu, outsidePrediction: true))
|
.Bind(EngineKeyFunctions.UseSecondary, new PointerInputCmdHandler(HandleOpenEntityMenu, outsidePrediction: true))
|
||||||
.Register<EntityMenuPresenter>();
|
.Register<EntityMenuUIController>();
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void Dispose()
|
public void OnStateExited(GameplayState state)
|
||||||
{
|
{
|
||||||
base.Dispose();
|
_updating = false;
|
||||||
Elements.Clear();
|
Elements.Clear();
|
||||||
CommandBinds.Unregister<EntityMenuPresenter>();
|
_cfg.UnsubValueChanged(CCVars.EntityMenuGroupingType, OnGroupingChanged);
|
||||||
|
_context.OnContextMouseEntered -= OnMouseEntered;
|
||||||
|
_context.OnContextKeyEvent -= OnKeyBindDown;
|
||||||
|
CommandBinds.Unregister<EntityMenuUIController>();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -89,8 +88,8 @@ namespace Content.Client.ContextMenu.UI
|
|||||||
public void OpenRootMenu(List<EntityUid> entities)
|
public void OpenRootMenu(List<EntityUid> entities)
|
||||||
{
|
{
|
||||||
// close any old menus first.
|
// close any old menus first.
|
||||||
if (RootMenu.Visible)
|
if (_context.RootMenu.Visible)
|
||||||
Close();
|
_context.Close();
|
||||||
|
|
||||||
var entitySpriteStates = GroupEntities(entities);
|
var entitySpriteStates = GroupEntities(entities);
|
||||||
var orderedStates = entitySpriteStates.ToList();
|
var orderedStates = entitySpriteStates.ToList();
|
||||||
@@ -99,12 +98,30 @@ namespace Content.Client.ContextMenu.UI
|
|||||||
AddToUI(orderedStates);
|
AddToUI(orderedStates);
|
||||||
|
|
||||||
var box = UIBox2.FromDimensions(_userInterfaceManager.MousePositionScaled.Position, (1, 1));
|
var box = UIBox2.FromDimensions(_userInterfaceManager.MousePositionScaled.Position, (1, 1));
|
||||||
RootMenu.Open(box);
|
_context.RootMenu.Open(box);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void OnKeyBindDown(ContextMenuElement element, GUIBoundKeyEventArgs args)
|
public void OnMouseEntered(ContextMenuElement element)
|
||||||
|
{
|
||||||
|
if (element is not EntityMenuElement entityElement)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// get an entity associated with this element
|
||||||
|
var entity = entityElement.Entity;
|
||||||
|
|
||||||
|
// if there is none, this is a group, so don't open verbs
|
||||||
|
if (entity == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// Deleted() automatically checks for null & existence.
|
||||||
|
if (_entityManager.Deleted(entity))
|
||||||
|
return;
|
||||||
|
|
||||||
|
_verb.OpenVerbMenu(entity.Value, popup: element.SubMenu);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnKeyBindDown(ContextMenuElement element, GUIBoundKeyEventArgs args)
|
||||||
{
|
{
|
||||||
base.OnKeyBindDown(element, args);
|
|
||||||
if (element is not EntityMenuElement entityElement)
|
if (element is not EntityMenuElement entityElement)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
@@ -116,14 +133,6 @@ namespace Content.Client.ContextMenu.UI
|
|||||||
if (_entityManager.Deleted(entity))
|
if (_entityManager.Deleted(entity))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
// open verb menu?
|
|
||||||
if (args.Function == EngineKeyFunctions.UseSecondary)
|
|
||||||
{
|
|
||||||
_verbSystem.VerbMenu.OpenVerbMenu(entity.Value);
|
|
||||||
args.Handle();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// do examination?
|
// do examination?
|
||||||
if (args.Function == ContentKeyFunctions.ExamineEntity)
|
if (args.Function == ContentKeyFunctions.ExamineEntity)
|
||||||
{
|
{
|
||||||
@@ -154,9 +163,8 @@ namespace Content.Client.ContextMenu.UI
|
|||||||
inputSys.HandleInputCommand(session, func, message);
|
inputSys.HandleInputCommand(session, func, message);
|
||||||
}
|
}
|
||||||
|
|
||||||
_verbSystem.CloseAllMenus();
|
_context.Close();
|
||||||
args.Handle();
|
args.Handle();
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -182,9 +190,12 @@ namespace Content.Client.ContextMenu.UI
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Check that entities in the context menu are still visible. If not, remove them from the context menu.
|
/// Check that entities in the context menu are still visible. If not, remove them from the context menu.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void Update()
|
public override void FrameUpdate(FrameEventArgs args)
|
||||||
{
|
{
|
||||||
if (!RootMenu.Visible)
|
if (!_updating || _context.RootMenu == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (!_context.RootMenu.Visible)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (_playerManager.LocalPlayer?.ControlledEntity is not { } player ||
|
if (_playerManager.LocalPlayer?.ControlledEntity is not { } player ||
|
||||||
@@ -229,7 +240,8 @@ namespace Content.Client.ContextMenu.UI
|
|||||||
foreach (var entity in entityGroups[0])
|
foreach (var entity in entityGroups[0])
|
||||||
{
|
{
|
||||||
var element = new EntityMenuElement(entity);
|
var element = new EntityMenuElement(entity);
|
||||||
AddElement(RootMenu, element);
|
element.SubMenu = new ContextMenuPopup(_context, element);
|
||||||
|
_context.AddElement(_context.RootMenu, element);
|
||||||
Elements.TryAdd(entity, element);
|
Elements.TryAdd(entity, element);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
@@ -245,7 +257,8 @@ namespace Content.Client.ContextMenu.UI
|
|||||||
|
|
||||||
// this group only has a single entity, add a simple menu element
|
// this group only has a single entity, add a simple menu element
|
||||||
var element = new EntityMenuElement(group[0]);
|
var element = new EntityMenuElement(group[0]);
|
||||||
AddElement(RootMenu, element);
|
element.SubMenu = new ContextMenuPopup(_context, element);
|
||||||
|
_context.AddElement(_context.RootMenu, element);
|
||||||
Elements.TryAdd(group[0], element);
|
Elements.TryAdd(group[0], element);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -257,17 +270,18 @@ namespace Content.Client.ContextMenu.UI
|
|||||||
private void AddGroupToUI(List<EntityUid> group)
|
private void AddGroupToUI(List<EntityUid> group)
|
||||||
{
|
{
|
||||||
EntityMenuElement element = new();
|
EntityMenuElement element = new();
|
||||||
ContextMenuPopup subMenu = new(this, element);
|
ContextMenuPopup subMenu = new(_context, element);
|
||||||
|
|
||||||
foreach (var entity in group)
|
foreach (var entity in group)
|
||||||
{
|
{
|
||||||
var subElement = new EntityMenuElement(entity);
|
var subElement = new EntityMenuElement(entity);
|
||||||
AddElement(subMenu, subElement);
|
subElement.SubMenu = new ContextMenuPopup(_context, subElement);
|
||||||
|
_context.AddElement(subMenu, subElement);
|
||||||
Elements.TryAdd(entity, subElement);
|
Elements.TryAdd(entity, subElement);
|
||||||
}
|
}
|
||||||
|
|
||||||
UpdateElement(element);
|
UpdateElement(element);
|
||||||
AddElement(RootMenu, element);
|
_context.AddElement(_context.RootMenu, element);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -291,13 +305,9 @@ namespace Content.Client.ContextMenu.UI
|
|||||||
if (parent is EntityMenuElement e)
|
if (parent is EntityMenuElement e)
|
||||||
UpdateElement(e);
|
UpdateElement(e);
|
||||||
|
|
||||||
// if the verb menu is open and targeting this entity, close it.
|
|
||||||
if (_verbSystem.VerbMenu.CurrentTarget == entity)
|
|
||||||
_verbSystem.VerbMenu.Close();
|
|
||||||
|
|
||||||
// If this was the last entity, close the entity menu
|
// If this was the last entity, close the entity menu
|
||||||
if (RootMenu.MenuBody.ChildCount == 0)
|
if (_context.RootMenu.MenuBody.ChildCount == 0)
|
||||||
Close();
|
_context.Close();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -376,17 +386,5 @@ namespace Content.Client.ContextMenu.UI
|
|||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void OpenSubMenu(ContextMenuElement element)
|
|
||||||
{
|
|
||||||
base.OpenSubMenu(element);
|
|
||||||
|
|
||||||
// In case the verb menu is currently open, ensure that it is shown ABOVE the entity menu.
|
|
||||||
if (_verbSystem.VerbMenu.Menus.TryPeek(out var menu) && menu.Visible)
|
|
||||||
{
|
|
||||||
menu.ParentElement?.ParentMenu?.SetPositionLast();
|
|
||||||
menu.SetPositionLast();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -14,6 +14,8 @@ using Robust.Shared.GameStates;
|
|||||||
using Robust.Shared.Map;
|
using Robust.Shared.Map;
|
||||||
using Robust.Shared.Timing;
|
using Robust.Shared.Timing;
|
||||||
using System.Diagnostics.CodeAnalysis;
|
using System.Diagnostics.CodeAnalysis;
|
||||||
|
using Content.Client.Verbs.UI;
|
||||||
|
using Robust.Client.UserInterface;
|
||||||
|
|
||||||
namespace Content.Client.Hands.Systems
|
namespace Content.Client.Hands.Systems
|
||||||
{
|
{
|
||||||
@@ -22,11 +24,11 @@ namespace Content.Client.Hands.Systems
|
|||||||
{
|
{
|
||||||
[Dependency] private readonly IGameTiming _gameTiming = default!;
|
[Dependency] private readonly IGameTiming _gameTiming = default!;
|
||||||
[Dependency] private readonly IPlayerManager _playerManager = default!;
|
[Dependency] private readonly IPlayerManager _playerManager = default!;
|
||||||
|
[Dependency] private readonly IUserInterfaceManager _ui = default!;
|
||||||
|
|
||||||
[Dependency] private readonly SharedContainerSystem _containerSystem = default!;
|
[Dependency] private readonly SharedContainerSystem _containerSystem = default!;
|
||||||
[Dependency] private readonly StrippableSystem _stripSys = default!;
|
[Dependency] private readonly StrippableSystem _stripSys = default!;
|
||||||
[Dependency] private readonly ExamineSystem _examine = default!;
|
[Dependency] private readonly ExamineSystem _examine = default!;
|
||||||
[Dependency] private readonly VerbSystem _verbs = default!;
|
|
||||||
|
|
||||||
public event Action<string, HandLocation>? OnPlayerAddHand;
|
public event Action<string, HandLocation>? OnPlayerAddHand;
|
||||||
public event Action<string>? OnPlayerRemoveHand;
|
public event Action<string>? OnPlayerRemoveHand;
|
||||||
@@ -240,9 +242,9 @@ namespace Content.Client.Hands.Systems
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
_verbs.VerbMenu.OpenVerbMenu(entity);
|
_ui.GetUIController<VerbMenuUIController>().OpenVerbMenu(entity);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void UIHandAltActivateItem(string handName)
|
public void UIHandAltActivateItem(string handName)
|
||||||
{
|
{
|
||||||
RaisePredictiveEvent(new RequestHandAltInteractEvent(handName));
|
RaisePredictiveEvent(new RequestHandAltInteractEvent(handName));
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ using Content.Client.Examine;
|
|||||||
using Content.Client.Storage;
|
using Content.Client.Storage;
|
||||||
using Content.Client.UserInterface.Controls;
|
using Content.Client.UserInterface.Controls;
|
||||||
using Content.Client.Verbs;
|
using Content.Client.Verbs;
|
||||||
|
using Content.Client.Verbs.UI;
|
||||||
using Content.Shared.Clothing.Components;
|
using Content.Shared.Clothing.Components;
|
||||||
using Content.Shared.Hands.Components;
|
using Content.Shared.Hands.Components;
|
||||||
using Content.Shared.Interaction;
|
using Content.Shared.Interaction;
|
||||||
@@ -12,6 +13,7 @@ using Content.Shared.Inventory.Events;
|
|||||||
using JetBrains.Annotations;
|
using JetBrains.Annotations;
|
||||||
using Robust.Client.GameObjects;
|
using Robust.Client.GameObjects;
|
||||||
using Robust.Client.Player;
|
using Robust.Client.Player;
|
||||||
|
using Robust.Client.UserInterface;
|
||||||
using Robust.Shared.Containers;
|
using Robust.Shared.Containers;
|
||||||
using Robust.Shared.Input.Binding;
|
using Robust.Shared.Input.Binding;
|
||||||
using Robust.Shared.Prototypes;
|
using Robust.Shared.Prototypes;
|
||||||
@@ -23,10 +25,10 @@ namespace Content.Client.Inventory
|
|||||||
{
|
{
|
||||||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||||
[Dependency] private readonly IPlayerManager _playerManager = default!;
|
[Dependency] private readonly IPlayerManager _playerManager = default!;
|
||||||
|
[Dependency] private readonly IUserInterfaceManager _ui = default!;
|
||||||
|
|
||||||
[Dependency] private readonly ClientClothingSystem _clothingVisualsSystem = default!;
|
[Dependency] private readonly ClientClothingSystem _clothingVisualsSystem = default!;
|
||||||
[Dependency] private readonly ExamineSystem _examine = default!;
|
[Dependency] private readonly ExamineSystem _examine = default!;
|
||||||
[Dependency] private readonly VerbSystem _verbs = default!;
|
|
||||||
|
|
||||||
public Action<SlotData>? EntitySlotUpdate = null;
|
public Action<SlotData>? EntitySlotUpdate = null;
|
||||||
public Action<SlotData>? OnSlotAdded = null;
|
public Action<SlotData>? OnSlotAdded = null;
|
||||||
@@ -270,7 +272,7 @@ namespace Content.Client.Inventory
|
|||||||
if (!TryGetSlotEntity(uid, slot, out var item))
|
if (!TryGetSlotEntity(uid, slot, out var item))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
_verbs.VerbMenu.OpenVerbMenu(item.Value);
|
_ui.GetUIController<VerbMenuUIController>().OpenVerbMenu(item.Value);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void UIInventoryActivateItem(string slot, EntityUid uid)
|
public void UIInventoryActivateItem(string slot, EntityUid uid)
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ using Content.Client.Examine;
|
|||||||
using Content.Client.Storage.UI;
|
using Content.Client.Storage.UI;
|
||||||
using Content.Client.UserInterface.Controls;
|
using Content.Client.UserInterface.Controls;
|
||||||
using Content.Client.Verbs;
|
using Content.Client.Verbs;
|
||||||
|
using Content.Client.Verbs.UI;
|
||||||
using Content.Shared.Input;
|
using Content.Shared.Input;
|
||||||
using Content.Shared.Interaction;
|
using Content.Shared.Interaction;
|
||||||
using JetBrains.Annotations;
|
using JetBrains.Annotations;
|
||||||
@@ -72,7 +73,7 @@ namespace Content.Client.Storage
|
|||||||
}
|
}
|
||||||
else if (args.Function == EngineKeyFunctions.UseSecondary)
|
else if (args.Function == EngineKeyFunctions.UseSecondary)
|
||||||
{
|
{
|
||||||
entitySys.GetEntitySystem<VerbSystem>().VerbMenu.OpenVerbMenu(entity);
|
IoCManager.Resolve<IUserInterfaceManager>().GetUIController<VerbMenuUIController>().OpenVerbMenu(entity);
|
||||||
}
|
}
|
||||||
else if (args.Function == ContentKeyFunctions.ActivateItemInWorld)
|
else if (args.Function == ContentKeyFunctions.ActivateItemInWorld)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ using Content.Client.Administration.UI.Tabs.PlayerTab;
|
|||||||
using Content.Client.Gameplay;
|
using Content.Client.Gameplay;
|
||||||
using Content.Client.UserInterface.Controls;
|
using Content.Client.UserInterface.Controls;
|
||||||
using Content.Client.Verbs;
|
using Content.Client.Verbs;
|
||||||
|
using Content.Client.Verbs.UI;
|
||||||
using Content.Shared.Input;
|
using Content.Shared.Input;
|
||||||
using JetBrains.Annotations;
|
using JetBrains.Annotations;
|
||||||
using Robust.Client.Console;
|
using Robust.Client.Console;
|
||||||
@@ -26,8 +27,7 @@ public sealed class AdminUIController : UIController, IOnStateEntered<GameplaySt
|
|||||||
[Dependency] private readonly IClientConGroupController _conGroups = default!;
|
[Dependency] private readonly IClientConGroupController _conGroups = default!;
|
||||||
[Dependency] private readonly IClientConsoleHost _conHost = default!;
|
[Dependency] private readonly IClientConsoleHost _conHost = default!;
|
||||||
[Dependency] private readonly IInputManager _input = default!;
|
[Dependency] private readonly IInputManager _input = default!;
|
||||||
|
[Dependency] private readonly VerbMenuUIController _verb = default!;
|
||||||
[UISystemDependency] private readonly VerbSystem _verbs = default!;
|
|
||||||
|
|
||||||
private AdminMenuWindow? _window;
|
private AdminMenuWindow? _window;
|
||||||
private MenuButton? AdminButton => UIManager.GetActiveUIWidgetOrNull<MenuBar.Widgets.GameTopMenuBar>()?.AdminButton;
|
private MenuButton? AdminButton => UIManager.GetActiveUIWidgetOrNull<MenuBar.Widgets.GameTopMenuBar>()?.AdminButton;
|
||||||
@@ -135,7 +135,7 @@ public sealed class AdminUIController : UIController, IOnStateEntered<GameplaySt
|
|||||||
if (function == EngineKeyFunctions.UIClick)
|
if (function == EngineKeyFunctions.UIClick)
|
||||||
_conHost.ExecuteCommand($"vv {uid}");
|
_conHost.ExecuteCommand($"vv {uid}");
|
||||||
else if (function == EngineKeyFunctions.UseSecondary)
|
else if (function == EngineKeyFunctions.UseSecondary)
|
||||||
_verbs.VerbMenu.OpenVerbMenu(uid, true);
|
_verb.OpenVerbMenu(uid, true);
|
||||||
else
|
else
|
||||||
return;
|
return;
|
||||||
|
|
||||||
@@ -153,7 +153,7 @@ public sealed class AdminUIController : UIController, IOnStateEntered<GameplaySt
|
|||||||
if (function == EngineKeyFunctions.UIClick)
|
if (function == EngineKeyFunctions.UIClick)
|
||||||
_conHost.ExecuteCommand($"vv {uid}");
|
_conHost.ExecuteCommand($"vv {uid}");
|
||||||
else if (function == EngineKeyFunctions.UseSecondary)
|
else if (function == EngineKeyFunctions.UseSecondary)
|
||||||
_verbs.VerbMenu.OpenVerbMenu(uid, true);
|
_verb.OpenVerbMenu(uid, true);
|
||||||
else
|
else
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
|||||||
@@ -3,10 +3,12 @@ using System.Collections.Generic;
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Content.Client.CombatMode;
|
using Content.Client.CombatMode;
|
||||||
using Content.Client.ContextMenu.UI;
|
using Content.Client.ContextMenu.UI;
|
||||||
|
using Content.Client.Gameplay;
|
||||||
using Content.Shared.Input;
|
using Content.Shared.Input;
|
||||||
using Content.Shared.Verbs;
|
using Content.Shared.Verbs;
|
||||||
using Robust.Client.Player;
|
using Robust.Client.Player;
|
||||||
using Robust.Client.UserInterface;
|
using Robust.Client.UserInterface;
|
||||||
|
using Robust.Client.UserInterface.Controllers;
|
||||||
using Robust.Shared.GameObjects;
|
using Robust.Shared.GameObjects;
|
||||||
using Robust.Shared.Input;
|
using Robust.Shared.Input;
|
||||||
using Robust.Shared.IoC;
|
using Robust.Shared.IoC;
|
||||||
@@ -20,34 +22,53 @@ namespace Content.Client.Verbs.UI
|
|||||||
/// This class handles the displaying of the verb menu.
|
/// This class handles the displaying of the verb menu.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <remarks>
|
/// <remarks>
|
||||||
/// In addition to the normal <see cref="ContextMenuPresenter"/> functionality, this also provides functions
|
/// In addition to the normal <see cref="ContextMenuUIController"/> functionality, this also provides functions
|
||||||
/// open a verb menu for a given entity, add verbs to it, and add server-verbs when the server response is
|
/// open a verb menu for a given entity, add verbs to it, and add server-verbs when the server response is
|
||||||
/// received.
|
/// received.
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
public sealed class VerbMenuPresenter : ContextMenuPresenter
|
public sealed class VerbMenuUIController : UIController, IOnStateEntered<GameplayState>, IOnStateExited<GameplayState>
|
||||||
{
|
{
|
||||||
[Dependency] private readonly IPlayerManager _playerManager = default!;
|
[Dependency] private readonly IPlayerManager _playerManager = default!;
|
||||||
[Dependency] private readonly IUserInterfaceManager _userInterfaceManager = default!;
|
[Dependency] private readonly IUserInterfaceManager _userInterfaceManager = default!;
|
||||||
|
[Dependency] private readonly ContextMenuUIController _context = default!;
|
||||||
|
|
||||||
private readonly CombatModeSystem _combatMode;
|
[UISystemDependency] private readonly CombatModeSystem _combatMode = default!;
|
||||||
private readonly VerbSystem _verbSystem;
|
[UISystemDependency] private readonly VerbSystem _verbSystem = default!;
|
||||||
|
|
||||||
public EntityUid CurrentTarget;
|
public EntityUid CurrentTarget;
|
||||||
public SortedSet<Verb> CurrentVerbs = new();
|
public SortedSet<Verb> CurrentVerbs = new();
|
||||||
|
|
||||||
public VerbMenuPresenter(CombatModeSystem combatMode, VerbSystem verbSystem)
|
/// <summary>
|
||||||
|
/// Separate from <see cref="ContextMenuUIController.RootMenu"/>, since we can open a verb menu as a submenu
|
||||||
|
/// of an entity menu element. If that happens, we need to be aware and close it properly.
|
||||||
|
/// </summary>
|
||||||
|
public ContextMenuPopup? OpenMenu = null;
|
||||||
|
|
||||||
|
public void OnStateEntered(GameplayState state)
|
||||||
{
|
{
|
||||||
IoCManager.InjectDependencies(this);
|
_context.OnContextKeyEvent += OnKeyBindDown;
|
||||||
_combatMode = combatMode;
|
_context.OnContextClosed += Close;
|
||||||
_verbSystem = verbSystem;
|
_verbSystem.OnVerbsResponse += HandleVerbsResponse;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnStateExited(GameplayState state)
|
||||||
|
{
|
||||||
|
_context.OnContextKeyEvent -= OnKeyBindDown;
|
||||||
|
_context.OnContextClosed -= Close;
|
||||||
|
if (_verbSystem != null)
|
||||||
|
_verbSystem.OnVerbsResponse -= HandleVerbsResponse;
|
||||||
|
Close();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Open a verb menu and fill it work verbs applicable to the given target entity.
|
/// Open a verb menu and fill it with verbs applicable to the given target entity.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="target">Entity to get verbs on.</param>
|
/// <param name="target">Entity to get verbs on.</param>
|
||||||
/// <param name="force">Used to force showing all verbs (mostly for admins).</param>
|
/// <param name="force">Used to force showing all verbs (mostly for admins).</param>
|
||||||
public void OpenVerbMenu(EntityUid target, bool force = false)
|
/// <param name="popup">
|
||||||
|
/// If this is not null, verbs will be placed into the given popup instead.
|
||||||
|
/// </param>
|
||||||
|
public void OpenVerbMenu(EntityUid target, bool force = false, ContextMenuPopup? popup=null)
|
||||||
{
|
{
|
||||||
if (_playerManager.LocalPlayer?.ControlledEntity is not {Valid: true} user ||
|
if (_playerManager.LocalPlayer?.ControlledEntity is not {Valid: true} user ||
|
||||||
_combatMode.IsInCombatMode(user))
|
_combatMode.IsInCombatMode(user))
|
||||||
@@ -55,53 +76,59 @@ namespace Content.Client.Verbs.UI
|
|||||||
|
|
||||||
Close();
|
Close();
|
||||||
|
|
||||||
|
var menu = popup ?? _context.RootMenu;
|
||||||
|
menu.MenuBody.DisposeAllChildren();
|
||||||
|
|
||||||
CurrentTarget = target;
|
CurrentTarget = target;
|
||||||
CurrentVerbs = _verbSystem.GetVerbs(target, user, Verb.VerbTypes, force);
|
CurrentVerbs = _verbSystem.GetVerbs(target, user, Verb.VerbTypes, force);
|
||||||
|
OpenMenu = menu;
|
||||||
|
|
||||||
// Fill in client-side verbs.
|
// Fill in client-side verbs.
|
||||||
FillVerbPopup();
|
FillVerbPopup(menu);
|
||||||
|
|
||||||
// Add indicator that some verbs may be missing.
|
// Add indicator that some verbs may be missing.
|
||||||
// I long for the day when verbs will all be predicted and this becomes unnecessary.
|
// I long for the day when verbs will all be predicted and this becomes unnecessary.
|
||||||
if (!target.IsClientSide())
|
if (!target.IsClientSide())
|
||||||
{
|
{
|
||||||
AddElement(RootMenu, new ContextMenuElement(Loc.GetString("verb-system-waiting-on-server-text")));
|
_context.AddElement(menu, new ContextMenuElement(Loc.GetString("verb-system-waiting-on-server-text")));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Show the menu
|
// if popup isn't null (ie we are opening out of an entity menu element),
|
||||||
RootMenu.SetPositionLast();
|
// assume that that is going to handle opening the submenu properly
|
||||||
|
if (popup != null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// Show the menu at mouse pos
|
||||||
|
menu.SetPositionLast();
|
||||||
var box = UIBox2.FromDimensions(_userInterfaceManager.MousePositionScaled.Position, (1, 1));
|
var box = UIBox2.FromDimensions(_userInterfaceManager.MousePositionScaled.Position, (1, 1));
|
||||||
RootMenu.Open(box);
|
menu.Open(box);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Fill the verb pop-up using the verbs stored in <see cref="CurrentVerbs"/>
|
/// Fill the verb pop-up using the verbs stored in <see cref="CurrentVerbs"/>
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private void FillVerbPopup()
|
private void FillVerbPopup(ContextMenuPopup popup)
|
||||||
{
|
{
|
||||||
if (RootMenu == null)
|
|
||||||
return;
|
|
||||||
|
|
||||||
HashSet<string> listedCategories = new();
|
HashSet<string> listedCategories = new();
|
||||||
foreach (var verb in CurrentVerbs)
|
foreach (var verb in CurrentVerbs)
|
||||||
{
|
{
|
||||||
if (verb.Category == null)
|
if (verb.Category == null)
|
||||||
{
|
{
|
||||||
var element = new VerbMenuElement(verb);
|
var element = new VerbMenuElement(verb);
|
||||||
AddElement(RootMenu, element);
|
_context.AddElement(popup, element);
|
||||||
}
|
}
|
||||||
|
|
||||||
else if (listedCategories.Add(verb.Category.Text))
|
else if (listedCategories.Add(verb.Category.Text))
|
||||||
AddVerbCategory(verb.Category);
|
AddVerbCategory(verb.Category, popup);
|
||||||
}
|
}
|
||||||
|
|
||||||
RootMenu.InvalidateMeasure();
|
popup.InvalidateMeasure();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Add a verb category button to the pop-up
|
/// Add a verb category button to the pop-up
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void AddVerbCategory(VerbCategory category)
|
public void AddVerbCategory(VerbCategory category, ContextMenuPopup popup)
|
||||||
{
|
{
|
||||||
// Get a list of the verbs in this category
|
// Get a list of the verbs in this category
|
||||||
List<Verb> verbsInCategory = new();
|
List<Verb> verbsInCategory = new();
|
||||||
@@ -119,10 +146,10 @@ namespace Content.Client.Verbs.UI
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
var element = new VerbMenuElement(category, verbsInCategory[0].TextStyleClass);
|
var element = new VerbMenuElement(category, verbsInCategory[0].TextStyleClass);
|
||||||
AddElement(RootMenu, element);
|
_context.AddElement(popup, element);
|
||||||
|
|
||||||
// Create the pop-up that appears when hovering over this element
|
// Create the pop-up that appears when hovering over this element
|
||||||
element.SubMenu = new ContextMenuPopup(this, element);
|
element.SubMenu = new ContextMenuPopup(_context, element);
|
||||||
foreach (var verb in verbsInCategory)
|
foreach (var verb in verbsInCategory)
|
||||||
{
|
{
|
||||||
var subElement = new VerbMenuElement(verb)
|
var subElement = new VerbMenuElement(verb)
|
||||||
@@ -130,7 +157,7 @@ namespace Content.Client.Verbs.UI
|
|||||||
IconVisible = drawIcons,
|
IconVisible = drawIcons,
|
||||||
TextVisible = !category.IconsOnly
|
TextVisible = !category.IconsOnly
|
||||||
};
|
};
|
||||||
AddElement(element.SubMenu, subElement);
|
_context.AddElement(element.SubMenu, subElement);
|
||||||
}
|
}
|
||||||
|
|
||||||
element.SubMenu.MenuBody.Columns = category.Columns;
|
element.SubMenu.MenuBody.Columns = category.Columns;
|
||||||
@@ -139,23 +166,23 @@ namespace Content.Client.Verbs.UI
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Add verbs from the server to <see cref="CurrentVerbs"/> and update the verb menu.
|
/// Add verbs from the server to <see cref="CurrentVerbs"/> and update the verb menu.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void AddServerVerbs(List<Verb>? verbs)
|
public void AddServerVerbs(List<Verb>? verbs, ContextMenuPopup popup)
|
||||||
{
|
{
|
||||||
RootMenu.MenuBody.DisposeAllChildren();
|
popup.MenuBody.DisposeAllChildren();
|
||||||
|
|
||||||
// Verbs may be null if the server does not think we can see the target entity. This **should** not happen.
|
// Verbs may be null if the server does not think we can see the target entity. This **should** not happen.
|
||||||
if (verbs == null)
|
if (verbs == null)
|
||||||
{
|
{
|
||||||
// remove "waiting for server..." and inform user that something went wrong.
|
// remove "waiting for server..." and inform user that something went wrong.
|
||||||
AddElement(RootMenu, new ContextMenuElement(Loc.GetString("verb-system-null-server-response")));
|
_context.AddElement(popup, new ContextMenuElement(Loc.GetString("verb-system-null-server-response")));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
CurrentVerbs.UnionWith(verbs);
|
CurrentVerbs.UnionWith(verbs);
|
||||||
FillVerbPopup();
|
FillVerbPopup(popup);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void OnKeyBindDown(ContextMenuElement element, GUIBoundKeyEventArgs args)
|
public void OnKeyBindDown(ContextMenuElement element, GUIBoundKeyEventArgs args)
|
||||||
{
|
{
|
||||||
if (args.Function != EngineKeyFunctions.Use && args.Function != ContentKeyFunctions.ActivateItemInWorld)
|
if (args.Function != EngineKeyFunctions.Use && args.Function != ContentKeyFunctions.ActivateItemInWorld)
|
||||||
return;
|
return;
|
||||||
@@ -185,7 +212,7 @@ namespace Content.Client.Verbs.UI
|
|||||||
if (verbElement.SubMenu.MenuBody.ChildCount != 1
|
if (verbElement.SubMenu.MenuBody.ChildCount != 1
|
||||||
|| verbElement.SubMenu.MenuBody.Children.First() is not VerbMenuElement verbMenuElement)
|
|| verbElement.SubMenu.MenuBody.Children.First() is not VerbMenuElement verbMenuElement)
|
||||||
{
|
{
|
||||||
OpenSubMenu(verbElement);
|
_context.OpenSubMenu(verbElement);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -200,11 +227,11 @@ namespace Content.Client.Verbs.UI
|
|||||||
if (verbElement.SubMenu == null)
|
if (verbElement.SubMenu == null)
|
||||||
{
|
{
|
||||||
var popupElement = new ConfirmationMenuElement(verb, "Confirm");
|
var popupElement = new ConfirmationMenuElement(verb, "Confirm");
|
||||||
verbElement.SubMenu = new ContextMenuPopup(this, verbElement);
|
verbElement.SubMenu = new ContextMenuPopup(_context, verbElement);
|
||||||
AddElement(verbElement.SubMenu, popupElement);
|
_context.AddElement(verbElement.SubMenu, popupElement);
|
||||||
}
|
}
|
||||||
|
|
||||||
OpenSubMenu(verbElement);
|
_context.OpenSubMenu(verbElement);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -212,11 +239,28 @@ namespace Content.Client.Verbs.UI
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void Close()
|
||||||
|
{
|
||||||
|
if (OpenMenu == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
OpenMenu.Close();
|
||||||
|
OpenMenu = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void HandleVerbsResponse(VerbsResponseEvent msg)
|
||||||
|
{
|
||||||
|
if (OpenMenu == null || !OpenMenu.Visible || CurrentTarget != msg.Entity)
|
||||||
|
return;
|
||||||
|
|
||||||
|
AddServerVerbs(msg.Verbs, OpenMenu);
|
||||||
|
}
|
||||||
|
|
||||||
private void ExecuteVerb(Verb verb)
|
private void ExecuteVerb(Verb verb)
|
||||||
{
|
{
|
||||||
_verbSystem.ExecuteVerb(CurrentTarget, verb);
|
_verbSystem.ExecuteVerb(CurrentTarget, verb);
|
||||||
if (verb.CloseMenu)
|
if (verb.CloseMenu)
|
||||||
_verbSystem.CloseAllMenus();
|
_context.Close();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -17,6 +17,7 @@ using Robust.Shared.Map;
|
|||||||
using Robust.Shared.Utility;
|
using Robust.Shared.Utility;
|
||||||
using System.Diagnostics.CodeAnalysis;
|
using System.Diagnostics.CodeAnalysis;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using Robust.Client.UserInterface;
|
||||||
|
|
||||||
namespace Content.Client.Verbs
|
namespace Content.Client.Verbs
|
||||||
{
|
{
|
||||||
@@ -36,9 +37,6 @@ namespace Content.Client.Verbs
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public const float EntityMenuLookupSize = 0.25f;
|
public const float EntityMenuLookupSize = 0.25f;
|
||||||
|
|
||||||
public EntityMenuPresenter EntityMenu = default!;
|
|
||||||
public VerbMenuPresenter VerbMenu = default!;
|
|
||||||
|
|
||||||
[Dependency] private readonly IEyeManager _eyeManager = default!;
|
[Dependency] private readonly IEyeManager _eyeManager = default!;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -46,41 +44,13 @@ namespace Content.Client.Verbs
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public MenuVisibility Visibility;
|
public MenuVisibility Visibility;
|
||||||
|
|
||||||
|
public Action<VerbsResponseEvent>? OnVerbsResponse;
|
||||||
|
|
||||||
public override void Initialize()
|
public override void Initialize()
|
||||||
{
|
{
|
||||||
base.Initialize();
|
base.Initialize();
|
||||||
|
|
||||||
UpdatesOutsidePrediction = true;
|
|
||||||
|
|
||||||
SubscribeNetworkEvent<RoundRestartCleanupEvent>(Reset);
|
|
||||||
SubscribeNetworkEvent<VerbsResponseEvent>(HandleVerbResponse);
|
SubscribeNetworkEvent<VerbsResponseEvent>(HandleVerbResponse);
|
||||||
|
|
||||||
EntityMenu = new(this);
|
|
||||||
VerbMenu = new(_combatMode, this);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Reset(RoundRestartCleanupEvent ev)
|
|
||||||
{
|
|
||||||
CloseAllMenus();
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void Shutdown()
|
|
||||||
{
|
|
||||||
base.Shutdown();
|
|
||||||
EntityMenu?.Dispose();
|
|
||||||
VerbMenu?.Dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void FrameUpdate(float frameTime)
|
|
||||||
{
|
|
||||||
base.FrameUpdate(frameTime);
|
|
||||||
EntityMenu?.Update();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void CloseAllMenus()
|
|
||||||
{
|
|
||||||
EntityMenu.Close();
|
|
||||||
VerbMenu.Close();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -259,10 +229,7 @@ namespace Content.Client.Verbs
|
|||||||
|
|
||||||
private void HandleVerbResponse(VerbsResponseEvent msg)
|
private void HandleVerbResponse(VerbsResponseEvent msg)
|
||||||
{
|
{
|
||||||
if (!VerbMenu.RootMenu.Visible || VerbMenu.CurrentTarget != msg.Entity)
|
OnVerbsResponse?.Invoke(msg);
|
||||||
return;
|
|
||||||
|
|
||||||
VerbMenu.AddServerVerbs(msg.Verbs);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user