Skip to content

Commit 45da85f

Browse files
authored
Context menu UI backend refactor & better UX (#13318)
closes space-wizards/space-station-14#9209
1 parent 17be16f commit 45da85f

14 files changed

+217
-186
lines changed

Content.Client/Administration/UI/CustomControls/PlayerListControl.xaml.cs

+3-1
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,12 @@
22
using Content.Client.Administration.Systems;
33
using Content.Client.UserInterface.Controls;
44
using Content.Client.Verbs;
5+
using Content.Client.Verbs.UI;
56
using Content.Shared.Administration;
67
using Content.Shared.Input;
78
using Robust.Client.AutoGenerated;
89
using Robust.Client.Graphics;
10+
using Robust.Client.UserInterface;
911
using Robust.Client.UserInterface.Controls;
1012
using Robust.Client.UserInterface.XAML;
1113
using Robust.Shared.Input;
@@ -56,7 +58,7 @@ private void PlayerListItemPressed(BaseButton.ButtonEventArgs args, ListData dat
5658
}
5759
else if (args.Event.Function == EngineKeyFunctions.UseSecondary && selectedPlayer.EntityUid != null)
5860
{
59-
_verbSystem.VerbMenu.OpenVerbMenu(selectedPlayer.EntityUid.Value);
61+
IoCManager.Resolve<IUserInterfaceManager>().GetUIController<VerbMenuUIController>().OpenVerbMenu(selectedPlayer.EntityUid.Value);
6062
}
6163
}
6264

Content.Client/CombatMode/CombatModeComponent.cs

+3-2
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
1+
using Content.Client.ContextMenu.UI;
12
using Content.Client.Verbs;
23
using Content.Shared.CombatMode;
34
using Content.Shared.Targeting;
45
using Robust.Client.Player;
6+
using Robust.Client.UserInterface;
57

68
namespace Content.Client.CombatMode
79
{
@@ -38,8 +40,7 @@ private void UpdateHud()
3840
return;
3941
}
4042

41-
var verbs = IoCManager.Resolve<IEntitySystemManager>().GetEntitySystem<VerbSystem>();
42-
verbs.CloseAllMenus();
43+
IoCManager.Resolve<IUserInterfaceManager>().GetUIController<ContextMenuUIController>().Close();
4344
}
4445
}
4546
}

Content.Client/Commands/GroupingEntityMenuCommand.cs

+2-2
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ public sealed class GroupingEntityMenuCommand : IConsoleCommand
1212

1313
public string Description => "Sets the entity menu grouping type.";
1414

15-
public string Help => $"Usage: entitymenug <0:{EntityMenuPresenter.GroupingTypesCount}>";
15+
public string Help => $"Usage: entitymenug <0:{EntityMenuUIController.GroupingTypesCount}>";
1616
public void Execute(IConsoleShell shell, string argStr, string[] args)
1717
{
1818
if (args.Length != 1)
@@ -27,7 +27,7 @@ public void Execute(IConsoleShell shell, string argStr, string[] args)
2727
return;
2828
}
2929

30-
if (id < 0 ||id > EntityMenuPresenter.GroupingTypesCount - 1)
30+
if (id < 0 ||id > EntityMenuUIController.GroupingTypesCount - 1)
3131
{
3232
shell.WriteLine($"{args[0]} is not a valid integer.");
3333
return;

Content.Client/ContextMenu/UI/ContextMenuPopup.xaml.cs

+6-6
Original file line numberDiff line numberDiff line change
@@ -31,14 +31,14 @@ public sealed partial class ContextMenuPopup : Popup
3131
/// </summary>
3232
public GridContainer MenuBody = new();
3333

34-
private ContextMenuPresenter _presenter;
34+
private ContextMenuUIController _uiController;
3535

36-
public ContextMenuPopup (ContextMenuPresenter presenter, ContextMenuElement? parentElement) : base()
36+
public ContextMenuPopup (ContextMenuUIController uiController, ContextMenuElement? parentElement) : base()
3737
{
3838
RobustXamlLoader.Load(this);
3939
MenuPanel.SetOnlyStyleClass(StyleClassContextMenuPopup);
4040

41-
_presenter = presenter;
41+
_uiController = uiController;
4242
ParentElement = parentElement;
4343

4444
// TODO xaml controls now have the access options -> re-xamlify all this.
@@ -52,7 +52,7 @@ public ContextMenuPopup (ContextMenuPresenter presenter, ContextMenuElement? par
5252
MenuPanel.MaxHeight = MaxItemsBeforeScroll * (ContextMenuElement.ElementHeight + 2 * ContextMenuElement.ElementMargin) + styleSize.Y;
5353

5454
UserInterfaceManager.ModalRoot.AddChild(this);
55-
MenuBody.OnChildRemoved += ctrl => _presenter.OnRemoveElement(this, ctrl);
55+
MenuBody.OnChildRemoved += ctrl => _uiController.OnRemoveElement(this, ctrl);
5656
MenuBody.VSeparationOverride = 0;
5757
MenuBody.HSeparationOverride = 0;
5858

@@ -67,13 +67,13 @@ public ContextMenuPopup (ContextMenuPresenter presenter, ContextMenuElement? par
6767
OnPopupHide += () =>
6868
{
6969
if (ParentElement != null)
70-
_presenter.CloseSubMenus(ParentElement.ParentMenu);
70+
_uiController.CloseSubMenus(ParentElement.ParentMenu);
7171
};
7272
}
7373

7474
protected override void Dispose(bool disposing)
7575
{
76-
MenuBody.OnChildRemoved -= ctrl => _presenter.OnRemoveElement(this, ctrl);
76+
MenuBody.OnChildRemoved -= ctrl => _uiController.OnRemoveElement(this, ctrl);
7777
ParentElement = null;
7878
base.Dispose(disposing);
7979
}

Content.Client/ContextMenu/UI/ContextMenuPresenter.cs Content.Client/ContextMenu/UI/ContextMenuUIController.cs

+34-21
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,26 @@
1-
using System;
2-
using System.Collections.Generic;
31
using System.Threading;
2+
using Content.Client.Gameplay;
43
using Robust.Client.UserInterface;
5-
using Robust.Shared.Log;
6-
using Robust.Shared.Maths;
4+
using Robust.Client.UserInterface.Controllers;
75
using Timer = Robust.Shared.Timing.Timer;
86
namespace Content.Client.ContextMenu.UI
97
{
108
/// <summary>
11-
/// This class handles all the logic associated with showing a context menu.
9+
/// This class handles all the logic associated with showing a context menu, as well as all the state for the
10+
/// entire context menu stack, including verb and entity menus. It does not currently support multiple
11+
/// open context menus.
1212
/// </summary>
1313
/// <remarks>
1414
/// This largely involves setting up timers to open and close sub-menus when hovering over other menu elements.
1515
/// </remarks>
16-
[Virtual]
17-
public class ContextMenuPresenter : IDisposable
16+
public sealed class ContextMenuUIController : UIController, IOnStateEntered<GameplayState>, IOnStateExited<GameplayState>
1817
{
1918
public static readonly TimeSpan HoverDelay = TimeSpan.FromSeconds(0.2);
2019

21-
public ContextMenuPopup RootMenu;
20+
/// <summary>
21+
/// Root menu of the entire context menu.
22+
/// </summary>
23+
public ContextMenuPopup RootMenu = default!;
2224
public Stack<ContextMenuPopup> Menus { get; } = new();
2325

2426
/// <summary>
@@ -31,30 +33,35 @@ public class ContextMenuPresenter : IDisposable
3133
/// </summary>
3234
public CancellationTokenSource? CancelClose;
3335

34-
public ContextMenuPresenter()
36+
public Action? OnContextClosed;
37+
public Action<ContextMenuElement>? OnContextMouseEntered;
38+
public Action<ContextMenuElement>? OnContextMouseExited;
39+
public Action<ContextMenuElement>? OnSubMenuOpened;
40+
public Action<ContextMenuElement, GUIBoundKeyEventArgs>? OnContextKeyEvent;
41+
42+
public void OnStateEntered(GameplayState state)
3543
{
3644
RootMenu = new(this, null);
37-
RootMenu.OnPopupHide += RootMenu.MenuBody.DisposeAllChildren;
45+
RootMenu.OnPopupHide += Close;
3846
Menus.Push(RootMenu);
3947
}
4048

41-
/// <summary>
42-
/// Dispose of all UI elements.
43-
/// </summary>
44-
public virtual void Dispose()
49+
public void OnStateExited(GameplayState state)
4550
{
46-
RootMenu.OnPopupHide -= RootMenu.MenuBody.DisposeAllChildren;
51+
Close();
52+
RootMenu.OnPopupHide -= Close;
4753
RootMenu.Dispose();
4854
}
4955

5056
/// <summary>
5157
/// Close and clear the root menu. This will also dispose any sub-menus.
5258
/// </summary>
53-
public virtual void Close()
59+
public void Close()
5460
{
55-
RootMenu.Close();
61+
RootMenu.MenuBody.DisposeAllChildren();
5662
CancelOpen?.Cancel();
5763
CancelClose?.Cancel();
64+
OnContextClosed?.Invoke();
5865
}
5966

6067
/// <summary>
@@ -82,7 +89,7 @@ public void CloseSubMenus(ContextMenuPopup? menu)
8289
/// <summary>
8390
/// Start a timer to open this element's sub-menu.
8491
/// </summary>
85-
public virtual void OnMouseEntered(ContextMenuElement element)
92+
private void OnMouseEntered(ContextMenuElement element)
8693
{
8794
if (!Menus.TryPeek(out var topMenu))
8895
{
@@ -100,6 +107,7 @@ public virtual void OnMouseEntered(ContextMenuElement element)
100107
CancelOpen?.Cancel();
101108
CancelOpen = new();
102109
Timer.Spawn(HoverDelay, () => OpenSubMenu(element), CancelOpen.Token);
110+
OnContextMouseEntered?.Invoke(element);
103111
}
104112

105113
/// <summary>
@@ -108,7 +116,7 @@ public virtual void OnMouseEntered(ContextMenuElement element)
108116
/// <remarks>
109117
/// Note that this timer will be aborted when entering the actual sub-menu itself.
110118
/// </remarks>
111-
public virtual void OnMouseExited(ContextMenuElement element)
119+
private void OnMouseExited(ContextMenuElement element)
112120
{
113121
CancelOpen?.Cancel();
114122

@@ -118,17 +126,21 @@ public virtual void OnMouseExited(ContextMenuElement element)
118126
CancelClose?.Cancel();
119127
CancelClose = new();
120128
Timer.Spawn(HoverDelay, () => CloseSubMenus(element.ParentMenu), CancelClose.Token);
129+
OnContextMouseExited?.Invoke(element);
121130
}
122131

123-
public virtual void OnKeyBindDown(ContextMenuElement element, GUIBoundKeyEventArgs args) { }
132+
private void OnKeyBindDown(ContextMenuElement element, GUIBoundKeyEventArgs args)
133+
{
134+
OnContextKeyEvent?.Invoke(element, args);
135+
}
124136

125137
/// <summary>
126138
/// Opens a new sub menu, and close the old one.
127139
/// </summary>
128140
/// <remarks>
129141
/// If the given element has no sub-menu, just close the current one.
130142
/// </remarks>
131-
public virtual void OpenSubMenu(ContextMenuElement element)
143+
public void OpenSubMenu(ContextMenuElement element)
132144
{
133145
if (!Menus.TryPeek(out var topMenu))
134146
{
@@ -164,6 +176,7 @@ public virtual void OpenSubMenu(ContextMenuElement element)
164176
element.SubMenu.SetPositionLast();
165177

166178
Menus.Push(element.SubMenu);
179+
OnSubMenuOpened?.Invoke(element);
167180
}
168181

169182
/// <summary>

Content.Client/ContextMenu/UI/EntityMenuElement.cs

+6-6
Original file line numberDiff line numberDiff line change
@@ -45,12 +45,12 @@ public EntityMenuElement(EntityUid? entity = null)
4545
LayoutContainer.SetGrowVertical(CountLabel, LayoutContainer.GrowDirection.Begin);
4646

4747
Entity = entity;
48-
if (Entity != null)
49-
{
50-
Count = 1;
51-
CountLabel.Visible = false;
52-
UpdateEntity();
53-
}
48+
if (Entity == null)
49+
return;
50+
51+
Count = 1;
52+
CountLabel.Visible = false;
53+
UpdateEntity();
5454
}
5555

5656
protected override void Dispose(bool disposing)

Content.Client/ContextMenu/UI/EntityMenuPresenterGrouping.cs

+3-2
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,17 @@
11
using Content.Shared.IdentityManagement;
22
using Robust.Client.GameObjects;
33
using System.Linq;
4+
using Robust.Client.UserInterface.Controllers;
45

56
namespace Content.Client.ContextMenu.UI
67
{
7-
public sealed partial class EntityMenuPresenter : ContextMenuPresenter
8+
public sealed partial class EntityMenuUIController
89
{
910
public const int GroupingTypesCount = 2;
1011
private int GroupingContextMenuType { get; set; }
1112
public void OnGroupingChanged(int obj)
1213
{
13-
Close();
14+
_context.Close();
1415
GroupingContextMenuType = obj;
1516
}
1617

0 commit comments

Comments
 (0)