ContextMenu (#3286)
* ContextMenu * Updating to WPF. * Updating to WPF. * Margins
This commit is contained in:
committed by
GitHub
parent
51182c8469
commit
f30a4d8a52
45
Content.Client/Commands/GroupingContextMenuCommand.cs
Normal file
45
Content.Client/Commands/GroupingContextMenuCommand.cs
Normal file
@@ -0,0 +1,45 @@
|
||||
#nullable enable
|
||||
using Content.Client.GameObjects.EntitySystems;
|
||||
using Content.Shared;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.Console;
|
||||
using Robust.Shared.IoC;
|
||||
using ContextMenuView = Content.Client.UserInterface.ContextMenu.ContextMenuView;
|
||||
|
||||
namespace Content.Client.Commands
|
||||
{
|
||||
public class GroupingContextMenuCommand : IConsoleCommand
|
||||
{
|
||||
public string Command => "contextmenug";
|
||||
|
||||
public string Description => "Sets the contextmenu-groupingtype.";
|
||||
|
||||
public string Help => ($"Usage: contextmenug <0:{ContextMenuView.GroupingTypesCount}>");
|
||||
public void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
if (args.Length != 1)
|
||||
{
|
||||
shell.WriteLine(Help);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!int.TryParse(args[0], out var id))
|
||||
{
|
||||
shell.WriteLine($"{args[0]} is not a valid integer.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (id < 0 ||id > ContextMenuView.GroupingTypesCount - 1)
|
||||
{
|
||||
shell.WriteLine($"{args[0]} is not a valid integer.");
|
||||
return;
|
||||
}
|
||||
|
||||
var configurationManager = IoCManager.Resolve<IConfigurationManager>();
|
||||
var cvar = CCVars.ContextMenuGroupingType;
|
||||
|
||||
configurationManager.SetCVar(cvar, id);
|
||||
shell.WriteLine($"Context Menu Grouping set to type: {configurationManager.GetCVar(cvar)}");
|
||||
}
|
||||
}
|
||||
}
|
||||
336
Content.Client/GameObjects/EntitySystems/ContextMenuPresenter.cs
Normal file
336
Content.Client/GameObjects/EntitySystems/ContextMenuPresenter.cs
Normal file
@@ -0,0 +1,336 @@
|
||||
#nullable enable
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using Content.Client.State;
|
||||
using Content.Client.UserInterface;
|
||||
using Content.Client.UserInterface.ContextMenu;
|
||||
using Content.Client.Utility;
|
||||
using Content.Shared;
|
||||
using Content.Shared.GameObjects.Verbs;
|
||||
using Content.Shared.Input;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Client.Input;
|
||||
using Robust.Client.Player;
|
||||
using Robust.Client.State;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.Containers;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Input;
|
||||
using Robust.Shared.Input.Binding;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Timing;
|
||||
using Timer = Robust.Shared.Timing.Timer;
|
||||
namespace Content.Client.GameObjects.EntitySystems
|
||||
{
|
||||
public class ContextMenuPresenter : IDisposable
|
||||
{
|
||||
[Dependency] private readonly IEntitySystemManager _systemManager = default!;
|
||||
[Dependency] private readonly IItemSlotManager _itemSlotManager = default!;
|
||||
[Dependency] private readonly IEntityManager _entityManager = default!;
|
||||
[Dependency] private readonly IPlayerManager _playerManager = default!;
|
||||
[Dependency] private readonly IStateManager _stateManager = default!;
|
||||
[Dependency] private readonly IInputManager _inputManager = default!;
|
||||
[Dependency] private readonly IConfigurationManager _cfg = default!;
|
||||
[Dependency] private readonly IGameTiming _gameTiming = default!;
|
||||
|
||||
private readonly IContextMenuView _contextMenuView;
|
||||
private readonly VerbSystem _verbSystem;
|
||||
|
||||
private bool _playerCanSeeThroughContainers;
|
||||
|
||||
private MapCoordinates _mapCoordinates;
|
||||
private CancellationTokenSource? _cancellationTokenSource;
|
||||
|
||||
public ContextMenuPresenter(VerbSystem verbSystem)
|
||||
{
|
||||
IoCManager.InjectDependencies(this);
|
||||
|
||||
_verbSystem = verbSystem;
|
||||
_verbSystem.ToggleContextMenu += SystemOnToggleContextMenu;
|
||||
_verbSystem.ToggleContainerVisibility += SystemOnToggleContainerVisibility;
|
||||
|
||||
_contextMenuView = new ContextMenuView();
|
||||
_contextMenuView.OnKeyBindDownSingle += OnKeyBindDownSingle;
|
||||
_contextMenuView.OnMouseEnteredSingle += OnMouseEnteredSingle;
|
||||
_contextMenuView.OnMouseExitedSingle += OnMouseExitedSingle;
|
||||
_contextMenuView.OnMouseHoveringSingle += OnMouseHoveringSingle;
|
||||
|
||||
_contextMenuView.OnKeyBindDownStack += OnKeyBindDownStack;
|
||||
_contextMenuView.OnMouseEnteredStack += OnMouseEnteredStack;
|
||||
|
||||
_contextMenuView.OnExitedTree += OnExitedTree;
|
||||
_contextMenuView.OnCloseRootMenu += OnCloseRootMenu;
|
||||
_contextMenuView.OnCloseChildMenu += OnCloseChildMenu;
|
||||
|
||||
_cfg.OnValueChanged(CCVars.ContextMenuGroupingType, _contextMenuView.OnGroupingContextMenuChanged, true);
|
||||
}
|
||||
|
||||
#region View Events
|
||||
private void OnCloseChildMenu(object? sender, int depth)
|
||||
{
|
||||
_contextMenuView.CloseContextPopups(depth);
|
||||
}
|
||||
|
||||
private void OnCloseRootMenu(object? sender, EventArgs e)
|
||||
{
|
||||
_contextMenuView.CloseContextPopups();
|
||||
}
|
||||
|
||||
private void OnExitedTree(object? sender, ContextMenuElement e)
|
||||
{
|
||||
_contextMenuView.UpdateParents(e);
|
||||
}
|
||||
|
||||
private void OnMouseEnteredStack(object? sender, StackContextElement e)
|
||||
{
|
||||
var realGlobalPosition = e.GlobalPosition;
|
||||
|
||||
_cancellationTokenSource?.Cancel();
|
||||
_cancellationTokenSource = new();
|
||||
|
||||
Timer.Spawn(e.HoverDelay, () =>
|
||||
{
|
||||
_verbSystem.CloseGroupMenu();
|
||||
|
||||
if (_contextMenuView.Menus.Count == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
OnCloseChildMenu(sender, e.ParentMenu?.Depth ?? 0);
|
||||
|
||||
var filteredEntities = e.ContextEntities.Where(entity => !entity.Deleted);
|
||||
if (filteredEntities.Any())
|
||||
{
|
||||
_contextMenuView.AddChildMenu(filteredEntities, realGlobalPosition, e);
|
||||
}
|
||||
}, _cancellationTokenSource.Token);
|
||||
}
|
||||
|
||||
private void OnKeyBindDownStack(object? sender, (GUIBoundKeyEventArgs, StackContextElement) e)
|
||||
{
|
||||
var (args, stack) = e;
|
||||
var firstEntity = stack.ContextEntities.FirstOrDefault(ent => !ent.Deleted);
|
||||
|
||||
if (firstEntity == null) return;
|
||||
|
||||
if (args.Function == EngineKeyFunctions.Use || args.Function == ContentKeyFunctions.TryPullObject || args.Function == ContentKeyFunctions.MovePulledObject)
|
||||
{
|
||||
var inputSys = _systemManager.GetEntitySystem<InputSystem>();
|
||||
|
||||
var func = args.Function;
|
||||
var funcId = _inputManager.NetworkBindMap.KeyFunctionID(func);
|
||||
|
||||
var message = new FullInputCmdMessage(_gameTiming.CurTick, _gameTiming.TickFraction, funcId,
|
||||
BoundKeyState.Down, firstEntity.Transform.Coordinates, args.PointerLocation, firstEntity.Uid);
|
||||
|
||||
var session = _playerManager.LocalPlayer?.Session;
|
||||
if (session != null)
|
||||
{
|
||||
inputSys.HandleInputCommand(session, func, message);
|
||||
}
|
||||
CloseAllMenus();
|
||||
return;
|
||||
}
|
||||
|
||||
if (_itemSlotManager.OnButtonPressed(args, firstEntity))
|
||||
{
|
||||
CloseAllMenus();
|
||||
}
|
||||
}
|
||||
|
||||
private void OnMouseHoveringSingle(object? sender, SingleContextElement e)
|
||||
{
|
||||
if (!e.DrawOutline) return;
|
||||
|
||||
var localPlayer = _playerManager.LocalPlayer;
|
||||
if (localPlayer?.ControlledEntity != null)
|
||||
{
|
||||
var inRange =
|
||||
localPlayer.InRangeUnobstructed(e.ContextEntity, ignoreInsideBlocker: true);
|
||||
e.OutlineComponent?.UpdateInRange(inRange);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnMouseEnteredSingle(object? sender, SingleContextElement e)
|
||||
{
|
||||
_cancellationTokenSource?.Cancel();
|
||||
|
||||
var entity = e.ContextEntity;
|
||||
_verbSystem.CloseGroupMenu();
|
||||
|
||||
OnCloseChildMenu(sender, e.ParentMenu?.Depth ?? 0);
|
||||
|
||||
if (entity.Deleted) return;
|
||||
|
||||
var localPlayer = _playerManager.LocalPlayer;
|
||||
if (localPlayer?.ControlledEntity == null) return;
|
||||
|
||||
e.OutlineComponent?.OnMouseEnter(localPlayer.InRangeUnobstructed(entity, ignoreInsideBlocker: true));
|
||||
if (e.SpriteComp != null)
|
||||
{
|
||||
e.SpriteComp.DrawDepth = (int) Shared.GameObjects.DrawDepth.HighlightedItems;
|
||||
}
|
||||
e.DrawOutline = true;
|
||||
}
|
||||
|
||||
private void OnMouseExitedSingle(object? sender, SingleContextElement e)
|
||||
{
|
||||
if (!e.ContextEntity.Deleted)
|
||||
{
|
||||
if (e.SpriteComp != null)
|
||||
{
|
||||
e.SpriteComp.DrawDepth = e.OriginalDrawDepth;
|
||||
}
|
||||
e.OutlineComponent?.OnMouseLeave();
|
||||
}
|
||||
e.DrawOutline = false;
|
||||
}
|
||||
|
||||
private void OnKeyBindDownSingle(object? sender, (GUIBoundKeyEventArgs, SingleContextElement) valueTuple)
|
||||
{
|
||||
var (args, single) = valueTuple;
|
||||
var entity = single.ContextEntity;
|
||||
if (args.Function == ContentKeyFunctions.OpenContextMenu)
|
||||
{
|
||||
_verbSystem.OnContextButtonPressed(entity);
|
||||
return;
|
||||
}
|
||||
|
||||
if (args.Function == ContentKeyFunctions.ExamineEntity)
|
||||
{
|
||||
_systemManager.GetEntitySystem<ExamineSystem>().DoExamine(entity);
|
||||
return;
|
||||
}
|
||||
|
||||
if (args.Function == EngineKeyFunctions.Use || args.Function == ContentKeyFunctions.Point ||
|
||||
args.Function == ContentKeyFunctions.TryPullObject || args.Function == ContentKeyFunctions.MovePulledObject)
|
||||
{
|
||||
var inputSys = _systemManager.GetEntitySystem<InputSystem>();
|
||||
|
||||
var func = args.Function;
|
||||
var funcId = _inputManager.NetworkBindMap.KeyFunctionID(func);
|
||||
|
||||
var message = new FullInputCmdMessage(_gameTiming.CurTick, _gameTiming.TickFraction, funcId,
|
||||
BoundKeyState.Down, entity.Transform.Coordinates, args.PointerLocation, entity.Uid);
|
||||
|
||||
var session = _playerManager.LocalPlayer?.Session;
|
||||
if (session != null)
|
||||
{
|
||||
inputSys.HandleInputCommand(session, func, message);
|
||||
}
|
||||
|
||||
CloseAllMenus();
|
||||
return;
|
||||
}
|
||||
|
||||
if (_itemSlotManager.OnButtonPressed(args, single.ContextEntity))
|
||||
{
|
||||
CloseAllMenus();
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Model Updates
|
||||
private void SystemOnToggleContainerVisibility(object? sender, bool args)
|
||||
{
|
||||
_playerCanSeeThroughContainers = args;
|
||||
}
|
||||
|
||||
private void SystemOnToggleContextMenu(object? sender, PointerInputCmdHandler.PointerInputCmdArgs args)
|
||||
{
|
||||
if (_stateManager.CurrentState is not GameScreenBase)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var playerEntity = _playerManager.LocalPlayer?.ControlledEntity;
|
||||
if (playerEntity == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_mapCoordinates = args.Coordinates.ToMap(_entityManager);
|
||||
if (!_verbSystem.TryGetContextEntities(playerEntity, _mapCoordinates, out var entities))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
entities = entities.Where(CanSeeOnContextMenu).ToList();
|
||||
if (entities.Count > 0)
|
||||
{
|
||||
_contextMenuView.AddRootMenu(entities);
|
||||
}
|
||||
}
|
||||
|
||||
public void HandleMoveEvent(MoveEvent ev)
|
||||
{
|
||||
if (_contextMenuView.Elements.Count == 0) return;
|
||||
var entity = ev.Sender;
|
||||
if (_contextMenuView.Elements.ContainsKey(entity))
|
||||
{
|
||||
if (!entity.Transform.MapPosition.InRange(_mapCoordinates, 1.0f))
|
||||
{
|
||||
_contextMenuView.RemoveEntity(entity);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Update()
|
||||
{
|
||||
if (_contextMenuView.Elements.Count == 0) return;
|
||||
|
||||
foreach (var entity in _contextMenuView.Elements.Keys.ToList())
|
||||
{
|
||||
if (entity.Deleted || !_playerCanSeeThroughContainers && entity.IsInContainer())
|
||||
{
|
||||
_contextMenuView.RemoveEntity(entity);
|
||||
}
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
private bool CanSeeOnContextMenu(IEntity entity)
|
||||
{
|
||||
if (!entity.TryGetComponent(out ISpriteComponent? spriteComponent) || !spriteComponent.Visible)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (entity.GetAllComponents<IShowContextMenu>().Any(s => !s.ShowContextMenu(entity)))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return _playerCanSeeThroughContainers || !entity.TryGetContainer(out var container) || container.ShowContents;
|
||||
}
|
||||
|
||||
private void CloseAllMenus()
|
||||
{
|
||||
_contextMenuView.CloseContextPopups();
|
||||
_verbSystem.CloseGroupMenu();
|
||||
_verbSystem.CloseVerbMenu();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_verbSystem.ToggleContextMenu -= SystemOnToggleContextMenu;
|
||||
_verbSystem.ToggleContainerVisibility -= SystemOnToggleContainerVisibility;
|
||||
|
||||
_contextMenuView.OnKeyBindDownSingle -= OnKeyBindDownSingle;
|
||||
_contextMenuView.OnMouseEnteredSingle -= OnMouseEnteredSingle;
|
||||
_contextMenuView.OnMouseExitedSingle -= OnMouseExitedSingle;
|
||||
_contextMenuView.OnMouseHoveringSingle -= OnMouseHoveringSingle;
|
||||
|
||||
_contextMenuView.OnKeyBindDownStack -= OnKeyBindDownStack;
|
||||
_contextMenuView.OnMouseEnteredStack -= OnMouseEnteredStack;
|
||||
|
||||
_contextMenuView.OnExitedTree -= OnExitedTree;
|
||||
_contextMenuView.OnCloseRootMenu -= OnCloseRootMenu;
|
||||
_contextMenuView.OnCloseChildMenu -= OnCloseChildMenu;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,34 +1,27 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Threading;
|
||||
using Content.Client.State;
|
||||
using Content.Client.UserInterface;
|
||||
using Content.Client.Utility;
|
||||
using Content.Shared.GameObjects.EntitySystemMessages;
|
||||
using Content.Shared.GameObjects.Verbs;
|
||||
using Content.Shared.GameTicking;
|
||||
using Content.Shared.Input;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.Input;
|
||||
using Robust.Client.Player;
|
||||
using Robust.Client.ResourceManagement;
|
||||
using Robust.Client.State;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Client.Utility;
|
||||
using Robust.Shared.Containers;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Input;
|
||||
using Robust.Shared.Input.Binding;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Localization;
|
||||
using Robust.Shared.Log;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Timing;
|
||||
using Robust.Shared.Utility;
|
||||
using Timer = Robust.Shared.Timing.Timer;
|
||||
|
||||
@@ -37,52 +30,67 @@ namespace Content.Client.GameObjects.EntitySystems
|
||||
[UsedImplicitly]
|
||||
public sealed class VerbSystem : SharedVerbSystem, IResettingEntitySystem
|
||||
{
|
||||
[Dependency] private readonly IStateManager _stateManager = default!;
|
||||
[Dependency] private readonly IPlayerManager _playerManager = default!;
|
||||
[Dependency] private readonly IInputManager _inputManager = default!;
|
||||
[Dependency] private readonly IItemSlotManager _itemSlotManager = default!;
|
||||
[Dependency] private readonly IGameTiming _gameTiming = default!;
|
||||
[Dependency] private readonly IUserInterfaceManager _userInterfaceManager = default!;
|
||||
|
||||
private EntityList _currentEntityList;
|
||||
private ContextMenuPresenter _contextMenuPresenter;
|
||||
public event EventHandler<PointerInputCmdHandler.PointerInputCmdArgs> ToggleContextMenu;
|
||||
public event EventHandler<bool> ToggleContainerVisibility;
|
||||
|
||||
private VerbPopup _currentVerbListRoot;
|
||||
private VerbPopup _currentGroupList;
|
||||
|
||||
private EntityUid _currentEntity;
|
||||
|
||||
private bool IsAnyContextMenuOpen => _currentEntityList != null || _currentVerbListRoot != null;
|
||||
|
||||
private bool _playerCanSeeThroughContainers;
|
||||
|
||||
// TODO: Move presenter out of the system
|
||||
// TODO: Separate the rest of the UI from the logic
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
IoCManager.InjectDependencies(this);
|
||||
|
||||
SubscribeNetworkEvent<VerbSystemMessages.VerbsResponseMessage>(FillEntityPopup);
|
||||
SubscribeNetworkEvent<PlayerContainerVisibilityMessage>(HandleContainerVisibilityMessage);
|
||||
|
||||
IoCManager.InjectDependencies(this);
|
||||
_contextMenuPresenter = new ContextMenuPresenter(this);
|
||||
SubscribeLocalEvent<MoveEvent>(_contextMenuPresenter.HandleMoveEvent);
|
||||
|
||||
CommandBinds.Builder
|
||||
.Bind(ContentKeyFunctions.OpenContextMenu,
|
||||
new PointerInputCmdHandler(OnOpenContextMenu))
|
||||
new PointerInputCmdHandler(HandleOpenContextMenu))
|
||||
.Register<VerbSystem>();
|
||||
}
|
||||
|
||||
public override void Shutdown()
|
||||
{
|
||||
UnsubscribeLocalEvent<MoveEvent>();
|
||||
_contextMenuPresenter?.Dispose();
|
||||
|
||||
CommandBinds.Unregister<VerbSystem>();
|
||||
base.Shutdown();
|
||||
}
|
||||
|
||||
public void Reset()
|
||||
{
|
||||
_playerCanSeeThroughContainers = false;
|
||||
ToggleContainerVisibility?.Invoke(this, false);
|
||||
}
|
||||
|
||||
private bool HandleOpenContextMenu(in PointerInputCmdHandler.PointerInputCmdArgs args)
|
||||
{
|
||||
if (args.State == BoundKeyState.Down)
|
||||
{
|
||||
ToggleContextMenu?.Invoke(this, args);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
private void HandleContainerVisibilityMessage(PlayerContainerVisibilityMessage ev)
|
||||
{
|
||||
_playerCanSeeThroughContainers = ev.CanSeeThrough;
|
||||
ToggleContainerVisibility?.Invoke(this, ev.CanSeeThrough);
|
||||
}
|
||||
|
||||
public override void Update(float frameTime)
|
||||
{
|
||||
base.Update(frameTime);
|
||||
_contextMenuPresenter?.Update();
|
||||
}
|
||||
|
||||
public void OpenContextMenu(IEntity entity, ScreenCoordinates screenCoordinates)
|
||||
@@ -99,7 +107,7 @@ namespace Content.Client.GameObjects.EntitySystems
|
||||
|
||||
if (!entity.Uid.IsClientSide())
|
||||
{
|
||||
_currentVerbListRoot.List.AddChild(new Label {Text = "Waiting on Server..."});
|
||||
_currentVerbListRoot.List.AddChild(new Label { Text = Loc.GetString("Waiting on Server...") });
|
||||
RaiseNetworkEvent(new VerbSystemMessages.RequestVerbsMessage(_currentEntity));
|
||||
}
|
||||
|
||||
@@ -107,84 +115,7 @@ namespace Content.Client.GameObjects.EntitySystems
|
||||
_currentVerbListRoot.Open(box);
|
||||
}
|
||||
|
||||
public bool CanSeeOnContextMenu(IEntity entity)
|
||||
{
|
||||
if (!entity.TryGetComponent(out SpriteComponent sprite) || !sprite.Visible)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (entity.GetAllComponents<IShowContextMenu>().Any(s => !s.ShowContextMenu(entity)))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!_playerCanSeeThroughContainers &&
|
||||
entity.TryGetContainer(out var container) &&
|
||||
!container.ShowContents)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private bool OnOpenContextMenu(in PointerInputCmdHandler.PointerInputCmdArgs args)
|
||||
{
|
||||
if (IsAnyContextMenuOpen)
|
||||
{
|
||||
CloseAllMenus();
|
||||
return true;
|
||||
}
|
||||
|
||||
if (_stateManager.CurrentState is not GameScreenBase)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var mapCoordinates = args.Coordinates.ToMap(EntityManager);
|
||||
var playerEntity = _playerManager.LocalPlayer?.ControlledEntity;
|
||||
|
||||
if (playerEntity == null || !TryGetContextEntities(playerEntity, mapCoordinates, out var entities))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
_currentEntityList = new EntityList();
|
||||
_currentEntityList.OnPopupHide += CloseAllMenus;
|
||||
var first = true;
|
||||
foreach (var entity in entities)
|
||||
{
|
||||
if (!CanSeeOnContextMenu(entity))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!first)
|
||||
{
|
||||
_currentEntityList.List.AddChild(new PanelContainer
|
||||
{
|
||||
MinSize = (0, 2),
|
||||
PanelOverride = new StyleBoxFlat {BackgroundColor = Color.FromHex("#333")}
|
||||
});
|
||||
}
|
||||
|
||||
var debugEnabled = _userInterfaceManager.DebugMonitors.Visible;
|
||||
_currentEntityList.List.AddChild(new EntityButton(this, entity, debugEnabled));
|
||||
first = false;
|
||||
}
|
||||
|
||||
_userInterfaceManager.ModalRoot.AddChild(_currentEntityList);
|
||||
|
||||
_currentEntityList.List.Measure(Vector2.Infinity);
|
||||
var size = _currentEntityList.List.DesiredSize;
|
||||
var box = UIBox2.FromDimensions(_userInterfaceManager.MousePositionScaled, size);
|
||||
_currentEntityList.Open(box);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private void OnContextButtonPressed(IEntity entity)
|
||||
public void OnContextButtonPressed(IEntity entity)
|
||||
{
|
||||
OpenContextMenu(entity, new ScreenCoordinates(_userInterfaceManager.MousePositionScaled));
|
||||
}
|
||||
@@ -286,7 +217,7 @@ namespace Content.Client.GameObjects.EntitySystems
|
||||
vBox.AddChild(new PanelContainer
|
||||
{
|
||||
MinSize = (0, 2),
|
||||
PanelOverride = new StyleBoxFlat {BackgroundColor = Color.FromHex("#333")}
|
||||
PanelOverride = new StyleBoxFlat { BackgroundColor = Color.FromHex("#333") }
|
||||
});
|
||||
}
|
||||
|
||||
@@ -308,7 +239,7 @@ namespace Content.Client.GameObjects.EntitySystems
|
||||
vBox.AddChild(new PanelContainer
|
||||
{
|
||||
MinSize = (0, 2),
|
||||
PanelOverride = new StyleBoxFlat {BackgroundColor = Color.FromHex("#333")}
|
||||
PanelOverride = new StyleBoxFlat { BackgroundColor = Color.FromHex("#333") }
|
||||
});
|
||||
}
|
||||
|
||||
@@ -321,7 +252,7 @@ namespace Content.Client.GameObjects.EntitySystems
|
||||
else
|
||||
{
|
||||
var panel = new PanelContainer();
|
||||
panel.AddChild(new Label {Text = "No verbs!"});
|
||||
panel.AddChild(new Label { Text = Loc.GetString("No verbs!") });
|
||||
vBox.AddChild(panel);
|
||||
}
|
||||
}
|
||||
@@ -330,7 +261,7 @@ namespace Content.Client.GameObjects.EntitySystems
|
||||
{
|
||||
var button = new VerbButton
|
||||
{
|
||||
Text = data.Text,
|
||||
Text = Loc.GetString(data.Text),
|
||||
Disabled = data.Disabled
|
||||
};
|
||||
|
||||
@@ -364,31 +295,25 @@ namespace Content.Client.GameObjects.EntitySystems
|
||||
|
||||
return new VerbGroupButton(this, verbButtons, icon)
|
||||
{
|
||||
Text = text,
|
||||
Text = Loc.GetString(text),
|
||||
};
|
||||
}
|
||||
|
||||
private void CloseVerbMenu()
|
||||
public void CloseVerbMenu()
|
||||
{
|
||||
_currentVerbListRoot?.Dispose();
|
||||
_currentVerbListRoot = null;
|
||||
_currentEntity = EntityUid.Invalid;
|
||||
}
|
||||
|
||||
private void CloseEntityList()
|
||||
{
|
||||
_currentEntityList?.Dispose();
|
||||
_currentEntityList = null;
|
||||
}
|
||||
|
||||
private void CloseAllMenus()
|
||||
{
|
||||
CloseVerbMenu();
|
||||
CloseEntityList();
|
||||
// CloseContextPopups();
|
||||
CloseGroupMenu();
|
||||
}
|
||||
|
||||
private void CloseGroupMenu()
|
||||
public void CloseGroupMenu()
|
||||
{
|
||||
_currentGroupList?.Dispose();
|
||||
_currentGroupList = null;
|
||||
@@ -399,20 +324,6 @@ namespace Content.Client.GameObjects.EntitySystems
|
||||
return _playerManager.LocalPlayer.ControlledEntity;
|
||||
}
|
||||
|
||||
private sealed class EntityList : Popup
|
||||
{
|
||||
public VBoxContainer List { get; }
|
||||
|
||||
public EntityList()
|
||||
{
|
||||
AddChild(new PanelContainer
|
||||
{
|
||||
Children = {(List = new VBoxContainer())},
|
||||
PanelOverride = new StyleBoxFlat {BackgroundColor = Color.FromHex("#111E")}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class VerbPopup : Popup
|
||||
{
|
||||
public VBoxContainer List { get; }
|
||||
@@ -427,102 +338,6 @@ namespace Content.Client.GameObjects.EntitySystems
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class EntityButton : Control
|
||||
{
|
||||
private readonly VerbSystem _master;
|
||||
private readonly IEntity _entity;
|
||||
|
||||
public EntityButton(VerbSystem master, IEntity entity, bool showUid)
|
||||
{
|
||||
_master = master;
|
||||
_entity = entity;
|
||||
|
||||
MouseFilter = MouseFilterMode.Stop;
|
||||
|
||||
var control = new HBoxContainer {SeparationOverride = 6};
|
||||
if (entity.TryGetComponent(out ISpriteComponent sprite))
|
||||
{
|
||||
control.AddChild(new SpriteView {Sprite = sprite});
|
||||
}
|
||||
|
||||
var text = entity.Name;
|
||||
if (showUid)
|
||||
{
|
||||
text = $"{text} ({entity.Uid})";
|
||||
}
|
||||
|
||||
control.AddChild(new Label
|
||||
{
|
||||
Margin = new Thickness(4, 0),
|
||||
Text = text
|
||||
});
|
||||
|
||||
AddChild(control);
|
||||
}
|
||||
|
||||
protected override void KeyBindDown(GUIBoundKeyEventArgs args)
|
||||
{
|
||||
base.KeyBindDown(args);
|
||||
|
||||
if (args.Function == ContentKeyFunctions.OpenContextMenu)
|
||||
{
|
||||
_master.OnContextButtonPressed(_entity);
|
||||
return;
|
||||
}
|
||||
|
||||
if (args.Function == EngineKeyFunctions.Use ||
|
||||
args.Function == ContentKeyFunctions.Point ||
|
||||
args.Function == ContentKeyFunctions.TryPullObject ||
|
||||
args.Function == ContentKeyFunctions.MovePulledObject)
|
||||
{
|
||||
// TODO: Remove an entity from the menu when it is deleted
|
||||
if (_entity.Deleted)
|
||||
{
|
||||
_master.CloseAllMenus();
|
||||
return;
|
||||
}
|
||||
|
||||
var inputSys = _master.EntitySystemManager.GetEntitySystem<InputSystem>();
|
||||
|
||||
var func = args.Function;
|
||||
var funcId = _master._inputManager.NetworkBindMap.KeyFunctionID(args.Function);
|
||||
|
||||
var message = new FullInputCmdMessage(_master._gameTiming.CurTick, _master._gameTiming.TickFraction,
|
||||
funcId, BoundKeyState.Down,
|
||||
_entity.Transform.Coordinates,
|
||||
args.PointerLocation, _entity.Uid);
|
||||
|
||||
// client side command handlers will always be sent the local player session.
|
||||
var session = _master._playerManager.LocalPlayer.Session;
|
||||
inputSys.HandleInputCommand(session, func, message);
|
||||
|
||||
_master.CloseAllMenus();
|
||||
return;
|
||||
}
|
||||
|
||||
if (args.Function == ContentKeyFunctions.ExamineEntity)
|
||||
{
|
||||
Get<ExamineSystem>().DoExamine(_entity);
|
||||
return;
|
||||
}
|
||||
|
||||
if (_master._itemSlotManager.OnButtonPressed(args, _entity))
|
||||
{
|
||||
_master.CloseAllMenus();
|
||||
}
|
||||
}
|
||||
|
||||
protected override void Draw(DrawingHandleScreen handle)
|
||||
{
|
||||
base.Draw(handle);
|
||||
|
||||
if (UserInterfaceManager.CurrentlyHovered == this)
|
||||
{
|
||||
handle.DrawRect(PixelSizeBox, Color.DarkSlateGray);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class VerbButton : BaseButton
|
||||
{
|
||||
private readonly Label _label;
|
||||
@@ -611,7 +426,7 @@ namespace Content.Client.GameObjects.EntitySystems
|
||||
|
||||
(_label = new Label
|
||||
{
|
||||
HorizontalExpand = true
|
||||
SizeFlagsHorizontal = SizeFlags.FillExpand
|
||||
}),
|
||||
|
||||
// Padding
|
||||
|
||||
233
Content.Client/UserInterface/ContextMenu/ContextMenuElement.cs
Normal file
233
Content.Client/UserInterface/ContextMenu/ContextMenuElement.cs
Normal file
@@ -0,0 +1,233 @@
|
||||
#nullable enable
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Content.Client.GameObjects.Components;
|
||||
using Content.Client.UserInterface.Stylesheets;
|
||||
using Content.Client.Utility;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.ResourceManagement;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Localization;
|
||||
using Robust.Shared.Maths;
|
||||
using Vector2 = Robust.Shared.Maths.Vector2;
|
||||
|
||||
namespace Content.Client.UserInterface.ContextMenu
|
||||
{
|
||||
public abstract class ContextMenuElement : Control
|
||||
{
|
||||
private static readonly Color HoverColor = Color.DarkSlateGray;
|
||||
protected internal readonly ContextMenuPopup? ParentMenu;
|
||||
|
||||
protected ContextMenuElement(ContextMenuPopup? parentMenu)
|
||||
{
|
||||
ParentMenu = parentMenu;
|
||||
MouseFilter = MouseFilterMode.Stop;
|
||||
}
|
||||
protected override void Draw(DrawingHandleScreen handle)
|
||||
{
|
||||
base.Draw(handle);
|
||||
|
||||
if (UserInterfaceManager.CurrentlyHovered == this)
|
||||
{
|
||||
handle.DrawRect(PixelSizeBox, HoverColor);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class SingleContextElement : ContextMenuElement
|
||||
{
|
||||
public event Action? OnMouseHovering;
|
||||
public event Action? OnExitedTree;
|
||||
|
||||
public IEntity ContextEntity{ get; }
|
||||
public readonly StackContextElement? Pre;
|
||||
|
||||
public ISpriteComponent? SpriteComp { get; }
|
||||
public InteractionOutlineComponent? OutlineComponent { get; }
|
||||
public int OriginalDrawDepth { get; }
|
||||
public bool DrawOutline { get; set; }
|
||||
|
||||
public SingleContextElement(IEntity entity, StackContextElement? pre, ContextMenuPopup? parentMenu) : base(parentMenu)
|
||||
{
|
||||
Pre = pre;
|
||||
ContextEntity = entity;
|
||||
if (ContextEntity.TryGetComponent(out ISpriteComponent? sprite))
|
||||
{
|
||||
SpriteComp = sprite;
|
||||
OriginalDrawDepth = SpriteComp.DrawDepth;
|
||||
}
|
||||
OutlineComponent = ContextEntity.GetComponentOrNull<InteractionOutlineComponent>();
|
||||
|
||||
AddChild(
|
||||
new HBoxContainer
|
||||
{
|
||||
SeparationOverride = 6,
|
||||
Children =
|
||||
{
|
||||
new LayoutContainer
|
||||
{
|
||||
Children = { new SpriteView { Sprite = SpriteComp } }
|
||||
},
|
||||
new Label
|
||||
{
|
||||
Text = Loc.GetString(UserInterfaceManager.DebugMonitors.Visible ? $"{ContextEntity.Name} ({ContextEntity.Uid})" : ContextEntity.Name)
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
protected override void Draw(DrawingHandleScreen handle)
|
||||
{
|
||||
base.Draw(handle);
|
||||
if (UserInterfaceManager.CurrentlyHovered == this)
|
||||
{
|
||||
OnMouseHovering?.Invoke();
|
||||
}
|
||||
}
|
||||
|
||||
protected override void ExitedTree()
|
||||
{
|
||||
OnExitedTree?.Invoke();
|
||||
base.ExitedTree();
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class StackContextElement : ContextMenuElement
|
||||
{
|
||||
public event Action? OnExitedTree;
|
||||
public readonly TimeSpan HoverDelay = TimeSpan.FromSeconds(0.2);
|
||||
|
||||
public HashSet<IEntity> ContextEntities { get; }
|
||||
public readonly StackContextElement? Pre;
|
||||
|
||||
private readonly SpriteView _spriteView;
|
||||
private readonly Label _label;
|
||||
|
||||
public int EntitiesCount => ContextEntities.Count;
|
||||
|
||||
public StackContextElement(IEnumerable<IEntity> entities, StackContextElement? pre, ContextMenuPopup? parentMenu)
|
||||
: base(parentMenu)
|
||||
{
|
||||
Pre = pre;
|
||||
ContextEntities = new(entities);
|
||||
_spriteView = new SpriteView
|
||||
{
|
||||
Sprite = ContextEntities.First().GetComponent<ISpriteComponent>()
|
||||
};
|
||||
_label = new Label
|
||||
{
|
||||
Text = Loc.GetString(ContextEntities.Count.ToString()),
|
||||
StyleClasses = { StyleNano.StyleClassContextMenuCount }
|
||||
};
|
||||
|
||||
LayoutContainer.SetAnchorPreset(_label, LayoutContainer.LayoutPreset.BottomRight);
|
||||
LayoutContainer.SetGrowHorizontal(_label, LayoutContainer.GrowDirection.Begin);
|
||||
LayoutContainer.SetGrowVertical(_label, LayoutContainer.GrowDirection.Begin);
|
||||
|
||||
AddChild(
|
||||
new HBoxContainer()
|
||||
{
|
||||
SeparationOverride = 6,
|
||||
Children =
|
||||
{
|
||||
new LayoutContainer { Children = { _spriteView, _label } },
|
||||
new HBoxContainer()
|
||||
{
|
||||
SeparationOverride = 6,
|
||||
Children =
|
||||
{
|
||||
new Label
|
||||
{
|
||||
Text = Loc.GetString(ContextEntities.First().Name)
|
||||
},
|
||||
new TextureRect
|
||||
{
|
||||
Texture = IoCManager.Resolve<IResourceCache>().GetTexture("/Textures/Interface/VerbIcons/group.svg.96dpi.png"),
|
||||
Stretch = TextureRect.StretchMode.KeepCentered,
|
||||
}
|
||||
},
|
||||
}
|
||||
},
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
protected override void ExitedTree()
|
||||
{
|
||||
OnExitedTree?.Invoke();
|
||||
base.ExitedTree();
|
||||
}
|
||||
|
||||
public void RemoveEntity(IEntity entity)
|
||||
{
|
||||
ContextEntities.Remove(entity);
|
||||
|
||||
_label.Text = Loc.GetString(ContextEntities.Count.ToString());
|
||||
_spriteView.Sprite = ContextEntities.FirstOrDefault(e => !e.Deleted)?.GetComponent<ISpriteComponent>();
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class ContextMenuPopup : Popup
|
||||
{
|
||||
private static readonly Color DefaultColor = Color.FromHex("#1116");
|
||||
private static readonly Color MarginColor = Color.FromHex("#222E");
|
||||
private const int MaxItemsBeforeScroll = 10;
|
||||
|
||||
public VBoxContainer List { get; }
|
||||
public int Depth { get; }
|
||||
|
||||
public ContextMenuPopup(int depth = 0)
|
||||
{
|
||||
Depth = depth;
|
||||
AddChild(new ScrollContainer
|
||||
{
|
||||
HScrollEnabled = false,
|
||||
Children = { new PanelContainer
|
||||
{
|
||||
Children = { (List = new VBoxContainer()) },
|
||||
PanelOverride = new StyleBoxFlat { BackgroundColor = MarginColor }
|
||||
}}
|
||||
});
|
||||
}
|
||||
|
||||
public void AddToMenu(ContextMenuElement element)
|
||||
{
|
||||
List.AddChild(new PanelContainer
|
||||
{
|
||||
Children = { element },
|
||||
Margin = new Thickness(0,0,0, 2),
|
||||
PanelOverride = new StyleBoxFlat {BackgroundColor = DefaultColor}
|
||||
});
|
||||
}
|
||||
|
||||
public void RemoveFromMenu(ContextMenuElement element)
|
||||
{
|
||||
List.RemoveChild(element.Parent!);
|
||||
InvalidateMeasure();
|
||||
}
|
||||
|
||||
protected override Vector2 MeasureOverride(Vector2 availableSize)
|
||||
{
|
||||
if (List.ChildCount == 0)
|
||||
{
|
||||
return Vector2.Zero;
|
||||
}
|
||||
|
||||
List.Measure(availableSize);
|
||||
var listSize = List.DesiredSize;
|
||||
|
||||
if (List.ChildCount < MaxItemsBeforeScroll)
|
||||
{
|
||||
return listSize;
|
||||
}
|
||||
listSize.Y = MaxItemsBeforeScroll * 32 + MaxItemsBeforeScroll * 2;
|
||||
return listSize;
|
||||
}
|
||||
}
|
||||
}
|
||||
265
Content.Client/UserInterface/ContextMenu/ContextMenuView.cs
Normal file
265
Content.Client/UserInterface/ContextMenu/ContextMenuView.cs
Normal file
@@ -0,0 +1,265 @@
|
||||
#nullable enable
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Maths;
|
||||
|
||||
namespace Content.Client.UserInterface.ContextMenu
|
||||
{
|
||||
public interface IContextMenuView : IDisposable
|
||||
{
|
||||
Dictionary<IEntity, ContextMenuElement> Elements { get; set; }
|
||||
Stack<ContextMenuPopup> Menus { get; }
|
||||
event EventHandler<(GUIBoundKeyEventArgs, SingleContextElement)>? OnKeyBindDownSingle;
|
||||
event EventHandler<SingleContextElement>? OnMouseEnteredSingle;
|
||||
event EventHandler<SingleContextElement>? OnMouseExitedSingle;
|
||||
event EventHandler<SingleContextElement>? OnMouseHoveringSingle;
|
||||
|
||||
event EventHandler<(GUIBoundKeyEventArgs, StackContextElement)>? OnKeyBindDownStack;
|
||||
event EventHandler<StackContextElement>? OnMouseEnteredStack;
|
||||
|
||||
event EventHandler<ContextMenuElement>? OnExitedTree;
|
||||
|
||||
event EventHandler? OnCloseRootMenu;
|
||||
event EventHandler<int>? OnCloseChildMenu;
|
||||
|
||||
void UpdateParents(ContextMenuElement element);
|
||||
void RemoveEntity(IEntity element);
|
||||
void AddRootMenu(List<IEntity> entities);
|
||||
void AddChildMenu(IEnumerable<IEntity> entities, Vector2 position, StackContextElement? stack);
|
||||
void CloseContextPopups(int depth);
|
||||
void CloseContextPopups();
|
||||
|
||||
void OnGroupingContextMenuChanged(int obj);
|
||||
}
|
||||
|
||||
public partial class ContextMenuView : IContextMenuView
|
||||
{
|
||||
[Dependency] private readonly IUserInterfaceManager _userInterfaceManager = default!;
|
||||
|
||||
public Stack<ContextMenuPopup> Menus { get; }
|
||||
public Dictionary<IEntity, ContextMenuElement> Elements { get; set; }
|
||||
|
||||
public event EventHandler<(GUIBoundKeyEventArgs, SingleContextElement)>? OnKeyBindDownSingle;
|
||||
public event EventHandler<SingleContextElement>? OnMouseEnteredSingle;
|
||||
public event EventHandler<SingleContextElement>? OnMouseExitedSingle;
|
||||
public event EventHandler<SingleContextElement>? OnMouseHoveringSingle;
|
||||
|
||||
public event EventHandler<(GUIBoundKeyEventArgs, StackContextElement)>? OnKeyBindDownStack;
|
||||
public event EventHandler<StackContextElement>? OnMouseEnteredStack;
|
||||
|
||||
public event EventHandler<ContextMenuElement>? OnExitedTree;
|
||||
|
||||
public event EventHandler? OnCloseRootMenu;
|
||||
public event EventHandler<int>? OnCloseChildMenu;
|
||||
|
||||
public ContextMenuView()
|
||||
{
|
||||
IoCManager.InjectDependencies(this);
|
||||
Menus = new Stack<ContextMenuPopup>();
|
||||
Elements = new Dictionary<IEntity, ContextMenuElement>();
|
||||
}
|
||||
|
||||
public void AddRootMenu(List<IEntity> entities)
|
||||
{
|
||||
Elements = new Dictionary<IEntity, ContextMenuElement>(entities.Count);
|
||||
|
||||
var rootContextMenu = new ContextMenuPopup();
|
||||
rootContextMenu.OnPopupHide += () => OnCloseRootMenu?.Invoke(this, EventArgs.Empty);
|
||||
Menus.Push(rootContextMenu);
|
||||
|
||||
var entitySpriteStates = GroupEntities(entities);
|
||||
var orderedStates = entitySpriteStates.ToList();
|
||||
orderedStates.Sort((x, y) => string.CompareOrdinal(x.First().Prototype!.Name, y.First().Prototype!.Name));
|
||||
AddToUI(orderedStates);
|
||||
|
||||
_userInterfaceManager.ModalRoot.AddChild(rootContextMenu);
|
||||
var size = rootContextMenu.List.CombinedMinimumSize;
|
||||
var box = UIBox2.FromDimensions(_userInterfaceManager.MousePositionScaled, size);
|
||||
rootContextMenu.Open(box);
|
||||
}
|
||||
public void AddChildMenu(IEnumerable<IEntity> entities, Vector2 position, StackContextElement? stack)
|
||||
{
|
||||
if (stack == null) return;
|
||||
var newDepth = stack.ParentMenu?.Depth + 1 ?? 1;
|
||||
var childContextMenu = new ContextMenuPopup(newDepth);
|
||||
Menus.Push(childContextMenu);
|
||||
|
||||
var orderedStates = GroupEntities(entities, newDepth);
|
||||
AddToUI(orderedStates, stack);
|
||||
|
||||
_userInterfaceManager.ModalRoot.AddChild(childContextMenu);
|
||||
var size = childContextMenu.List.CombinedMinimumSize;
|
||||
childContextMenu.Open(UIBox2.FromDimensions(position + (stack.Width, 0), size));
|
||||
}
|
||||
|
||||
private void AddToUI(List<List<IEntity>> entities, StackContextElement? stack = null)
|
||||
{
|
||||
if (entities.Count == 1)
|
||||
{
|
||||
foreach (var entity in entities[0])
|
||||
{
|
||||
AddSingleContextElement(entity, stack);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (var entity in entities)
|
||||
{
|
||||
if (entity.Count == 1)
|
||||
{
|
||||
AddSingleContextElement(entity[0], stack);
|
||||
}
|
||||
else
|
||||
{
|
||||
AddStackContextElement(entity, stack);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
private void AddSingleContextElement(IEntity entity, StackContextElement? pre)
|
||||
{
|
||||
if (Menus.TryPeek(out var menu))
|
||||
{
|
||||
var single = new SingleContextElement(entity, pre, menu);
|
||||
|
||||
single.OnKeyBindDown += args => OnKeyBindDownSingle?.Invoke(this, (args, single));
|
||||
single.OnMouseEntered += _ => OnMouseEnteredSingle?.Invoke(this, single);
|
||||
single.OnMouseExited += _ => OnMouseExitedSingle?.Invoke(this, single);
|
||||
single.OnMouseHovering += () => OnMouseHoveringSingle?.Invoke(this, single);
|
||||
single.OnExitedTree += () => OnExitedTree?.Invoke(this, single);
|
||||
|
||||
UpdateElements(entity, single);
|
||||
menu.AddToMenu(single);
|
||||
}
|
||||
}
|
||||
private void AddStackContextElement(IEnumerable<IEntity> entities, StackContextElement? pre)
|
||||
{
|
||||
if (Menus.TryPeek(out var menu))
|
||||
{
|
||||
var stack = new StackContextElement(entities, pre, menu);
|
||||
|
||||
stack.OnKeyBindDown += args => OnKeyBindDownStack?.Invoke(this, (args, stack));
|
||||
stack.OnMouseEntered += _ => OnMouseEnteredStack?.Invoke(this, stack);
|
||||
stack.OnExitedTree += () => OnExitedTree?.Invoke(this, stack);
|
||||
|
||||
foreach (var entity in entities)
|
||||
{
|
||||
UpdateElements(entity, stack);
|
||||
}
|
||||
menu.AddToMenu(stack);
|
||||
}
|
||||
}
|
||||
private void UpdateElements(IEntity entity, ContextMenuElement element)
|
||||
{
|
||||
if (Elements.ContainsKey(entity))
|
||||
{
|
||||
Elements[entity] = element;
|
||||
}
|
||||
else
|
||||
{
|
||||
Elements.Add(entity, element);
|
||||
}
|
||||
}
|
||||
|
||||
private void RemoveFromUI(ContextMenuElement element)
|
||||
{
|
||||
var menu = element.ParentMenu;
|
||||
if (menu != null)
|
||||
{
|
||||
menu.RemoveFromMenu(element);
|
||||
if (menu.List.ChildCount == 0)
|
||||
{
|
||||
OnCloseChildMenu?.Invoke(this, menu.Depth - 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
public void RemoveEntity(IEntity entity)
|
||||
{
|
||||
var element = Elements[entity];
|
||||
switch (element)
|
||||
{
|
||||
case SingleContextElement singleContextElement:
|
||||
RemoveFromUI(singleContextElement);
|
||||
UpdateBranch(entity, singleContextElement.Pre);
|
||||
break;
|
||||
case StackContextElement stackContextElement:
|
||||
stackContextElement.RemoveEntity(entity);
|
||||
if (stackContextElement.EntitiesCount == 0)
|
||||
{
|
||||
RemoveFromUI(stackContextElement);
|
||||
}
|
||||
UpdateBranch(entity, stackContextElement.Pre);
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException(nameof(element));
|
||||
}
|
||||
Elements.Remove(entity);
|
||||
}
|
||||
private void UpdateBranch(IEntity entity, StackContextElement? stack)
|
||||
{
|
||||
while (stack != null)
|
||||
{
|
||||
stack.RemoveEntity(entity);
|
||||
if (stack.EntitiesCount == 0)
|
||||
{
|
||||
RemoveFromUI(stack);
|
||||
}
|
||||
|
||||
stack = stack.Pre;
|
||||
}
|
||||
}
|
||||
|
||||
public void UpdateParents(ContextMenuElement element)
|
||||
{
|
||||
switch (element)
|
||||
{
|
||||
case SingleContextElement singleContextElement:
|
||||
if (singleContextElement.Pre != null)
|
||||
{
|
||||
Elements[singleContextElement.ContextEntity] = singleContextElement.Pre;
|
||||
}
|
||||
|
||||
break;
|
||||
case StackContextElement stackContextElement:
|
||||
if (stackContextElement.Pre != null)
|
||||
{
|
||||
foreach (var entity in stackContextElement.ContextEntities)
|
||||
{
|
||||
Elements[entity] = stackContextElement.Pre;
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException(nameof(element));
|
||||
}
|
||||
}
|
||||
|
||||
public void CloseContextPopups()
|
||||
{
|
||||
while (Menus.Count > 0)
|
||||
{
|
||||
Menus.Pop().Dispose();
|
||||
}
|
||||
|
||||
Elements.Clear();
|
||||
}
|
||||
public void CloseContextPopups(int depth)
|
||||
{
|
||||
while (Menus.Count > 0 && Menus.Peek().Depth > depth)
|
||||
{
|
||||
Menus.Pop().Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
CloseContextPopups();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,118 @@
|
||||
#nullable enable
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Shared.GameObjects;
|
||||
|
||||
namespace Content.Client.UserInterface.ContextMenu
|
||||
{
|
||||
public partial class ContextMenuView
|
||||
{
|
||||
public const int GroupingTypesCount = 2;
|
||||
private int GroupingContextMenuType { get; set; }
|
||||
public void OnGroupingContextMenuChanged(int obj)
|
||||
{
|
||||
CloseContextPopups();
|
||||
GroupingContextMenuType = obj;
|
||||
}
|
||||
|
||||
private List<List<IEntity>> GroupEntities(IEnumerable<IEntity> entities, int depth = 0)
|
||||
{
|
||||
if (GroupingContextMenuType == 0)
|
||||
{
|
||||
var newEntities = entities.GroupBy(e => e, new PrototypeContextMenuComparer()).ToList();
|
||||
return newEntities.Select(grp => grp.ToList()).ToList();
|
||||
}
|
||||
else
|
||||
{
|
||||
var newEntities = entities.GroupBy(e => e, new PrototypeAndStatesContextMenuComparer(depth)).ToList();
|
||||
return newEntities.Select(grp => grp.ToList()).ToList();
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class PrototypeAndStatesContextMenuComparer : IEqualityComparer<IEntity>
|
||||
{
|
||||
private static readonly List<Func<IEntity, IEntity, bool>> EqualsList = new()
|
||||
{
|
||||
(a, b) => a.Prototype!.ID == b.Prototype!.ID,
|
||||
(a, b) =>
|
||||
{
|
||||
var xStates = a.GetComponent<ISpriteComponent>().AllLayers.Where(e => e.Visible).Select(s => s.RsiState.Name);
|
||||
var yStates = b.GetComponent<ISpriteComponent>().AllLayers.Where(e => e.Visible).Select(s => s.RsiState.Name);
|
||||
|
||||
return xStates.OrderBy(t => t).SequenceEqual(yStates.OrderBy(t => t));
|
||||
},
|
||||
};
|
||||
private static readonly List<Func<IEntity, int>> GetHashCodeList = new()
|
||||
{
|
||||
e => EqualityComparer<string>.Default.GetHashCode(e.Prototype!.ID),
|
||||
e =>
|
||||
{
|
||||
var hash = 0;
|
||||
foreach (var element in e.GetComponent<ISpriteComponent>().AllLayers.Where(obj => obj.Visible).Select(s => s.RsiState.Name))
|
||||
{
|
||||
hash ^= EqualityComparer<string>.Default.GetHashCode(element!);
|
||||
}
|
||||
return hash;
|
||||
},
|
||||
};
|
||||
|
||||
private static int Count => EqualsList.Count - 1;
|
||||
|
||||
private readonly int _depth;
|
||||
public PrototypeAndStatesContextMenuComparer(int step = 0)
|
||||
{
|
||||
_depth = step > Count ? Count : step;
|
||||
}
|
||||
|
||||
public bool Equals(IEntity? x, IEntity? y)
|
||||
{
|
||||
if (x == null)
|
||||
{
|
||||
return y == null;
|
||||
}
|
||||
|
||||
return y != null && EqualsList[_depth](x, y);
|
||||
}
|
||||
|
||||
public int GetHashCode(IEntity e)
|
||||
{
|
||||
return GetHashCodeList[_depth](e);
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class PrototypeContextMenuComparer : IEqualityComparer<IEntity>
|
||||
{
|
||||
public bool Equals(IEntity? x, IEntity? y)
|
||||
{
|
||||
if (x == null)
|
||||
{
|
||||
return y == null;
|
||||
}
|
||||
if (y != null)
|
||||
{
|
||||
if (x.Prototype?.ID == y.Prototype?.ID)
|
||||
{
|
||||
var xStates = x.GetComponent<ISpriteComponent>().AllLayers.Where(e => e.Visible).Select(s => s.RsiState.Name);
|
||||
var yStates = y.GetComponent<ISpriteComponent>().AllLayers.Where(e => e.Visible).Select(s => s.RsiState.Name);
|
||||
|
||||
return xStates.OrderBy(t => t).SequenceEqual(yStates.OrderBy(t => t));
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public int GetHashCode(IEntity e)
|
||||
{
|
||||
var hash = EqualityComparer<string>.Default.GetHashCode(e.Prototype?.ID!);
|
||||
foreach (var element in e.GetComponent<ISpriteComponent>().AllLayers.Where(obj => obj.Visible).Select(s => s.RsiState.Name))
|
||||
{
|
||||
hash ^= EqualityComparer<string>.Default.GetHashCode(element!);
|
||||
}
|
||||
|
||||
return hash;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
using System.Linq;
|
||||
#nullable enable
|
||||
using System.Linq;
|
||||
using Content.Client.GameObjects.EntitySystems;
|
||||
using Content.Client.UserInterface.Controls;
|
||||
using Content.Client.Utility;
|
||||
@@ -30,6 +31,7 @@ namespace Content.Client.UserInterface.Stylesheets
|
||||
public const string StyleClassHotbarSlotNumber = "hotbarSlotNumber";
|
||||
public const string StyleClassActionSearchBox = "actionSearchBox";
|
||||
public const string StyleClassActionMenuItemRevoked = "actionMenuItemRevoked";
|
||||
public const string StyleClassContextMenuCount = "contextMenuCount";
|
||||
|
||||
public const string StyleClassSliderRed = "Red";
|
||||
public const string StyleClassSliderGreen = "Green";
|
||||
@@ -635,6 +637,13 @@ namespace Content.Client.UserInterface.Stylesheets
|
||||
new StyleProperty("font", notoSans15)
|
||||
}),
|
||||
|
||||
// small number for the context menu
|
||||
new StyleRule(new SelectorElement(typeof(Label), new[] {StyleClassContextMenuCount}, null, null), new[]
|
||||
{
|
||||
new StyleProperty("font", notoSans10),
|
||||
new StyleProperty(Label.StylePropertyAlignMode, Label.AlignMode.Right),
|
||||
}),
|
||||
|
||||
// hotbar slot
|
||||
new StyleRule(new SelectorElement(typeof(RichTextLabel), new[] {StyleClassHotbarSlotNumber}, null, null), new[]
|
||||
{
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
|
||||
using Robust.Shared;
|
||||
using Robust.Shared.Configuration;
|
||||
|
||||
@@ -269,5 +270,10 @@ namespace Content.Shared
|
||||
|
||||
public static readonly CVarDef<bool> AdminOocEnabled =
|
||||
CVarDef.Create("ooc.enabled_admin", true, CVar.NOTIFY);
|
||||
|
||||
/*
|
||||
* Context Menu Grouping Types
|
||||
*/
|
||||
public static readonly CVarDef<int> ContextMenuGroupingType = CVarDef.Create("context_menu", 0, CVar.CLIENTONLY);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,8 +22,9 @@ namespace Content.Shared.GameObjects
|
||||
Objects = DrawDepthTag.Default,
|
||||
Items = DrawDepthTag.Default + 1,
|
||||
Mobs = DrawDepthTag.Default + 2,
|
||||
Effects = DrawDepthTag.Default + 3,
|
||||
Ghosts = DrawDepthTag.Default + 4,
|
||||
Overlays = DrawDepthTag.Default + 5,
|
||||
HighlightedItems = DrawDepthTag.Default + 3,
|
||||
Effects = DrawDepthTag.Default + 4,
|
||||
Ghosts = DrawDepthTag.Default + 5,
|
||||
Overlays = DrawDepthTag.Default + 6,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@ namespace Content.Shared.GameObjects.Verbs
|
||||
/// <param name="contextEntities"></param>
|
||||
/// <param name="buffer">Whether we should slightly extend out the ignored range for the ray predicated</param>
|
||||
/// <returns></returns>
|
||||
protected bool TryGetContextEntities(IEntity player, MapCoordinates targetPos, [NotNullWhen(true)] out List<IEntity>? contextEntities, bool buffer = false)
|
||||
public bool TryGetContextEntities(IEntity player, MapCoordinates targetPos, [NotNullWhen(true)] out List<IEntity>? contextEntities, bool buffer = false)
|
||||
{
|
||||
contextEntities = null;
|
||||
var length = buffer ? 1.0f: 0.5f;
|
||||
|
||||
Reference in New Issue
Block a user