Re-organize all projects (#4166)

This commit is contained in:
DrSmugleaf
2021-06-09 22:19:39 +02:00
committed by GitHub
parent 9f50e4061b
commit ff1a2d97ea
1773 changed files with 5258 additions and 5508 deletions

View File

@@ -0,0 +1,237 @@
#nullable enable
using System;
using System.Collections.Generic;
using System.Linq;
using Content.Client.Interactable.Components;
using Content.Client.Resources;
using Content.Client.Stylesheets;
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.ContextMenu.UI
{
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
{
Children =
{
new LayoutContainer
{
Children = { new SpriteView { Sprite = SpriteComp } }
},
new Label
{
Text = Loc.GetString(UserInterfaceManager.DebugMonitors.Visible ? $"{ContextEntity.Name} ({ContextEntity.Uid})" : ContextEntity.Name)
}
}, Margin = new Thickness(0,0,10,0)
}
);
}
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.192dpi.png"),
TextureScale = (0.5f, 0.5f),
Stretch = TextureRect.StretchMode.KeepCentered,
}
}
}
}, Margin = new Thickness(0,0,10,0)
}
);
}
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;
private const int MarginSizeBetweenElements = 2;
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, MarginSizeBetweenElements),
PanelOverride = new StyleBoxFlat {BackgroundColor = DefaultColor}
});
}
public void RemoveFromMenu(ContextMenuElement element)
{
if (element.Parent != null)
{
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 * MarginSizeBetweenElements;
return listSize;
}
}
}

View File

@@ -0,0 +1,352 @@
#nullable enable
using System;
using System.Linq;
using System.Threading;
using Content.Client.Examine;
using Content.Client.Interactable;
using Content.Client.Items.Managers;
using Content.Client.Verbs;
using Content.Client.Viewport;
using Content.Shared;
using Content.Shared.Input;
using Content.Shared.Verbs;
using Robust.Client.GameObjects;
using Robust.Client.Graphics;
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 DrawDepth = Content.Shared.DrawDepth.DrawDepth;
using Timer = Robust.Shared.Timing.Timer;
namespace Content.Client.ContextMenu.UI
{
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!;
[Dependency] private readonly IEyeManager _eyeManager = 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();
args.Handle();
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);
// BUG: This assumes that the main viewport is the viewport that the context menu is active on.
// This is not necessarily true but we currently have no way to find the viewport (reliably)
// from the input event.
//
// This might be particularly important in the future with a more advanced mapping mode.
var renderScale = _eyeManager.MainViewport.GetRenderScale();
e.OutlineComponent?.UpdateInRange(inRange, renderScale);
}
}
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;
var renderScale = _eyeManager.MainViewport.GetRenderScale();
e.OutlineComponent?.OnMouseEnter(localPlayer.InRangeUnobstructed(entity, ignoreInsideBlocker: true), renderScale);
if (e.SpriteComp != null)
{
e.SpriteComp.DrawDepth = (int) 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);
args.Handle();
return;
}
if (args.Function == ContentKeyFunctions.ExamineEntity)
{
_systemManager.GetEntitySystem<ExamineSystem>().DoExamine(entity);
args.Handle();
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();
args.Handle();
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;
}
}
}

View 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.ContextMenu.UI
{
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.Position, 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();
}
}
}

View File

@@ -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.ContextMenu.UI
{
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;
}
}
}
}