CatStation/Content.Client/UserInterface/Systems/Storage/StorageUIController.cs
Dmitry 25c5e59248
Upstream (#3172)
Signed-off-by: Prole <172158352+Prole0@users.noreply.github.com>
Co-authored-by: PJBot <pieterjan.briers+bot@gmail.com>
Co-authored-by: ScarKy0 <106310278+ScarKy0@users.noreply.github.com>
Co-authored-by: Samuka-C <47865393+Samuka-C@users.noreply.github.com>
Co-authored-by: ArtisticRoomba <145879011+ArtisticRoomba@users.noreply.github.com>
Co-authored-by: Partmedia <kevinz5000@gmail.com>
Co-authored-by: slarticodefast <161409025+slarticodefast@users.noreply.github.com>
Co-authored-by: themias <89101928+themias@users.noreply.github.com>
Co-authored-by: Victor Shen <71985089+Vexerot@users.noreply.github.com>
Co-authored-by: Ed <96445749+TheShuEd@users.noreply.github.com>
Co-authored-by: Milon <milonpl.git@proton.me>
Co-authored-by: Kirus59 <145689588+Kirus59@users.noreply.github.com>
Co-authored-by: Nemanja <98561806+EmoGarbage404@users.noreply.github.com>
Co-authored-by: Stomf <5dorkydorks@gmail.com>
Co-authored-by: drakewill-CRL <46307022+drakewill-CRL@users.noreply.github.com>
Co-authored-by: PraxisMapper <praxismapper@gmail.com>
Co-authored-by: EmoGarbage404 <retron404@gmail.com>
Co-authored-by: lzk <124214523+lzk228@users.noreply.github.com>
Co-authored-by: Princess Cheeseballs <66055347+Pronana@users.noreply.github.com>
Co-authored-by: IProduceWidgets <107586145+IProduceWidgets@users.noreply.github.com>
Co-authored-by: TytosB <54259736+TytosB@users.noreply.github.com>
Co-authored-by: abadaba695 <spacestation13thingy@gmail.com>
Co-authored-by: kosticia <kosticia46@gmail.com>
Co-authored-by: Thinbug <101073555+Thinbug0@users.noreply.github.com>
Co-authored-by: pathetic meowmeow <uhhadd@gmail.com>
Co-authored-by: SlamBamActionman <83650252+SlamBamActionman@users.noreply.github.com>
Co-authored-by: Boaz1111 <149967078+Boaz1111@users.noreply.github.com>
Co-authored-by: ActiveMammmoth <140334666+ActiveMammmoth@users.noreply.github.com>
Co-authored-by: Myra <vasilis@pikachu.systems>
Co-authored-by: Whatstone <166147148+whatston3@users.noreply.github.com>
Co-authored-by: Pieter-Jan Briers <pieterjan.briers+git@gmail.com>
Co-authored-by: K-Dynamic <20566341+K-Dynamic@users.noreply.github.com>
Co-authored-by: Gentleman-Bird <dcgreen406@gmail.com>
Co-authored-by: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com>
Co-authored-by: BIGZi0348 <svalker0348@gmail.com>
Co-authored-by: LaCumbiaDelCoronavirus <90893484+LaCumbiaDelCoronavirus@users.noreply.github.com>
Co-authored-by: imatsoup <93290208+imatsoup@users.noreply.github.com>
Co-authored-by: Matthew Herber <32679887+happyrobot33@users.noreply.github.com>
Co-authored-by: Ertanic <36124833+Ertanic@users.noreply.github.com>
Co-authored-by: MissKay1994 <15877268+MissKay1994@users.noreply.github.com>
Co-authored-by: Errant <35878406+Errant-4@users.noreply.github.com>
Co-authored-by: eoineoineoin <helloworld@eoinrul.es>
Co-authored-by: Tiniest Shark <head.rebel@yahoo.com>
Co-authored-by: nikitosych <boriszyn@gmail.com>
Co-authored-by: Tayrtahn <tayrtahn@gmail.com>
Co-authored-by: Perry Fraser <perryprog@users.noreply.github.com>
Co-authored-by: YoungThug <ramialanbagy@gmail.com>
Co-authored-by: beck-thompson <107373427+beck-thompson@users.noreply.github.com>
Co-authored-by: metalgearsloth <comedian_vs_clown@hotmail.com>
Co-authored-by: Southbridge <7013162+southbridge-fur@users.noreply.github.com>
Co-authored-by: Vladislav Suchkov <20380250+murolem@users.noreply.github.com>
Co-authored-by: Prole <172158352+Prole0@users.noreply.github.com>
Co-authored-by: Unkn0wn_Gh0st <shadowstalkermll@gmail.com>
Co-authored-by: 3nderall <101940324+3nderall@users.noreply.github.com>
Co-authored-by: Radezolid <snappednexus@gmail.com>
Co-authored-by: J <billsmith116@gmail.com>
Co-authored-by: Ghagliiarghii <68826635+Ghagliiarghii@users.noreply.github.com>
Co-authored-by: chromiumboy <50505512+chromiumboy@users.noreply.github.com>
Co-authored-by: youtissoum <51883137+youtissoum@users.noreply.github.com>
Co-authored-by: Minemoder5000 <minemoder50000@gmail.com>
Co-authored-by: Spanky <scott@wearejacob.com>
Co-authored-by: Spessmann <156740760+Spessmann@users.noreply.github.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: brainfood1183 <113240905+brainfood1183@users.noreply.github.com>
Co-authored-by: Deerstop <edainturner@gmail.com>
Co-authored-by: B_Kirill <153602297+B-Kirill@users.noreply.github.com>
Co-authored-by: archee1 <archee3@hotmail.co.uk>
Co-authored-by: Cojoke <83733158+Cojoke-dot@users.noreply.github.com>
Co-authored-by: Quantum-cross <7065792+Quantum-cross@users.noreply.github.com>
Co-authored-by: poklj <compgeek223@gmail.com>
Co-authored-by: Krunklehorn <42424291+Krunklehorn@users.noreply.github.com>
Co-authored-by: OnyxTheBrave <131422822+OnyxTheBrave@users.noreply.github.com>
Co-authored-by: UpAndLeaves <92269094+Alpha-Two@users.noreply.github.com>
Co-authored-by: Flareguy <78941145+Flareguy@users.noreply.github.com>
Co-authored-by: Zalycon <84675130+Zalycon@users.noreply.github.com>
Co-authored-by: deltanedas <39013340+deltanedas@users.noreply.github.com>
Co-authored-by: Verm <32827189+Vermidia@users.noreply.github.com>
Co-authored-by: nikthechampiongr <32041239+nikthechampiongr@users.noreply.github.com>
Co-authored-by: ScarKy0 <scarky0@onet.eu>
Co-authored-by: Dmitry <57028746+dimm00n@users.noreply.github.com>
2025-05-24 11:00:17 +03:00

438 lines
16 KiB
C#

using System.Linq;
using System.Numerics;
using Content.Client.Examine;
using Content.Client.Hands.Systems;
using Content.Client.Interaction;
using Content.Client.Storage;
using Content.Client.Storage.Systems;
using Content.Client.UserInterface.Systems.Hotbar.Widgets;
using Content.Client.UserInterface.Systems.Info;
using Content.Client.UserInterface.Systems.Storage.Controls;
using Content.Client.Verbs.UI;
using Content.Shared.CCVar;
using Content.Shared.Input;
using Content.Shared.Interaction;
using Content.Shared.Storage;
using Robust.Client.GameObjects;
using Robust.Client.Input;
using Robust.Client.Player;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controllers;
using Robust.Client.UserInterface.Controls;
using Robust.Shared.Configuration;
using Robust.Shared.Input;
using Robust.Shared.Timing;
namespace Content.Client.UserInterface.Systems.Storage;
public sealed class StorageUIController : UIController, IOnSystemChanged<StorageSystem>
{
/*
* Things are a bit over the shop but essentially
* - Clicking into storagewindow is handled via storagewindow
* - Clicking out of it is via ItemGridPiece
* - Dragging around is handled here
* - Drawing is handled via ItemGridPiece
* - StorageSystem handles any sim stuff around open windows.
*/
[Dependency] private readonly IConfigurationManager _configuration = default!;
[Dependency] private readonly IInputManager _input = default!;
[Dependency] private readonly IPlayerManager _player = default!;
[Dependency] private readonly CloseRecentWindowUIController _closeRecentWindowUIController = default!;
[UISystemDependency] private readonly StorageSystem _storage = default!;
[UISystemDependency] private readonly UserInterfaceSystem _ui = default!;
private readonly DragDropHelper<ItemGridPiece> _menuDragHelper;
public ItemGridPiece? DraggingGhost => _menuDragHelper.Dragged;
public Angle DraggingRotation = Angle.Zero;
public bool StaticStorageUIEnabled;
public bool OpaqueStorageWindow;
private int _openStorageLimit = -1;
public bool IsDragging => _menuDragHelper.IsDragging;
public ItemGridPiece? CurrentlyDragging => _menuDragHelper.Dragged;
public bool WindowTitle { get; private set; } = false;
public StorageUIController()
{
_menuDragHelper = new DragDropHelper<ItemGridPiece>(OnMenuBeginDrag, OnMenuContinueDrag, OnMenuEndDrag);
}
public override void Initialize()
{
base.Initialize();
_configuration.OnValueChanged(CCVars.StaticStorageUI, OnStaticStorageChanged, true);
_configuration.OnValueChanged(CCVars.OpaqueStorageWindow, OnOpaqueWindowChanged, true);
_configuration.OnValueChanged(CCVars.StorageWindowTitle, OnStorageWindowTitle, true);
_configuration.OnValueChanged(CCVars.StorageLimit, OnStorageLimitChanged, true);
}
private void OnStorageLimitChanged(int obj)
{
_openStorageLimit = obj;
}
private void OnStorageWindowTitle(bool obj)
{
WindowTitle = obj;
}
private void OnOpaqueWindowChanged(bool obj)
{
OpaqueStorageWindow = obj;
}
private void OnStaticStorageChanged(bool obj)
{
StaticStorageUIEnabled = obj;
}
public StorageWindow CreateStorageWindow(StorageBoundUserInterface sBui)
{
var window = new StorageWindow();
window.MouseFilter = Control.MouseFilterMode.Pass;
window.OnPiecePressed += (args, piece) =>
{
OnPiecePressed(args, window, piece);
};
window.OnPieceUnpressed += (args, piece) =>
{
OnPieceUnpressed(args, window, piece);
};
if (StaticStorageUIEnabled)
{
var hotbar = UIManager.GetActiveUIWidgetOrNull<HotbarGui>();
// this lambda handles the nested storage case
// during nested storage, a parent window hides and a child window is
// immediately inserted to the end of the list
// we can reorder the newly inserted to the same index as the invisible
// window in order to prevent an invisible window from being replaced
// with a visible one in a different position
Action<Control?, Control> reorder = (parent, child) =>
{
if (parent is null)
return;
var parentChildren = parent.Children.ToList();
var invisibleIndex = parentChildren.FindIndex(c => c.Visible == false);
if (invisibleIndex == -1)
return;
child.SetPositionInParent(invisibleIndex);
};
if (hotbar != null)
{
hotbar.DoubleStorageContainer.Visible = _openStorageLimit == 2;
hotbar.SingleStorageContainer.Visible = _openStorageLimit != 2;
}
if (_openStorageLimit == 2)
{
if (hotbar?.LeftStorageContainer.Children.Any(c => c.Visible) == false) // we're comparing booleans because it's bool? and not bool from the optional chaining
{
hotbar?.LeftStorageContainer.AddChild(window);
reorder(hotbar?.LeftStorageContainer, window);
}
else
{
hotbar?.RightStorageContainer.AddChild(window);
reorder(hotbar?.RightStorageContainer, window);
}
}
else
{
hotbar?.SingleStorageContainer.AddChild(window);
reorder(hotbar?.SingleStorageContainer, window);
}
_closeRecentWindowUIController.SetMostRecentlyInteractedWindow(window);
}
else
{
// Open at parent position if it's open.
if (_ui.TryGetOpenUi<StorageBoundUserInterface>(EntityManager.GetComponent<TransformComponent>(sBui.Owner).ParentUid,
StorageComponent.StorageUiKey.Key, out var bui) && bui.Position != null)
{
window.Open(bui.Position.Value);
}
// Open at the saved position if it exists.
else if (_ui.TryGetPosition(sBui.Owner, StorageComponent.StorageUiKey.Key, out var pos))
{
window.Open(pos);
}
// Open at the default position.
else
{
window.OpenCenteredLeft();
}
}
_ui.RegisterControl(sBui, window);
return window;
}
public void OnSystemLoaded(StorageSystem system)
{
_input.FirstChanceOnKeyEvent += OnMiddleMouse;
}
public void OnSystemUnloaded(StorageSystem system)
{
_input.FirstChanceOnKeyEvent -= OnMiddleMouse;
}
/// One might ask, Hey Emo, why are you parsing raw keyboard input just to rotate a rectangle?
/// The answer is, that input bindings regarding mouse inputs are always intercepted by the UI,
/// thus, if i want to be able to rotate my damn piece anywhere on the screen,
/// I have to side-step all of the input handling. Cheers.
private void OnMiddleMouse(KeyEventArgs keyEvent, KeyEventType type)
{
if (keyEvent.Handled)
return;
if (type != KeyEventType.Down)
return;
//todo there's gotta be a method for this in InputManager just expose it to content I BEG.
if (!_input.TryGetKeyBinding(ContentKeyFunctions.RotateStoredItem, out var binding))
return;
if (binding.BaseKey != keyEvent.Key)
return;
if (keyEvent.Shift &&
!(binding.Mod1 == Keyboard.Key.Shift ||
binding.Mod2 == Keyboard.Key.Shift ||
binding.Mod3 == Keyboard.Key.Shift))
return;
if (keyEvent.Alt &&
!(binding.Mod1 == Keyboard.Key.Alt ||
binding.Mod2 == Keyboard.Key.Alt ||
binding.Mod3 == Keyboard.Key.Alt))
return;
if (keyEvent.Control &&
!(binding.Mod1 == Keyboard.Key.Control ||
binding.Mod2 == Keyboard.Key.Control ||
binding.Mod3 == Keyboard.Key.Control))
return;
if (!IsDragging && EntityManager.System<HandsSystem>().GetActiveHandEntity() == null)
return;
//clamp it to a cardinal.
DraggingRotation = (DraggingRotation + Math.PI / 2f).GetCardinalDir().ToAngle();
if (DraggingGhost != null)
DraggingGhost.Location.Rotation = DraggingRotation;
if (IsDragging || UIManager.CurrentlyHovered is StorageWindow)
keyEvent.Handle();
}
private void OnPiecePressed(GUIBoundKeyEventArgs args, StorageWindow window, ItemGridPiece control)
{
if (IsDragging || !window.IsOpen)
return;
if (args.Function == ContentKeyFunctions.MoveStoredItem)
{
DraggingRotation = control.Location.Rotation;
_menuDragHelper.MouseDown(control);
_menuDragHelper.Update(0f);
args.Handle();
}
else if (args.Function == ContentKeyFunctions.SaveItemLocation)
{
if (window.StorageEntity is not {} storage)
return;
EntityManager.RaisePredictiveEvent(new StorageSaveItemLocationEvent(
EntityManager.GetNetEntity(control.Entity),
EntityManager.GetNetEntity(storage)));
args.Handle();
}
else if (args.Function == ContentKeyFunctions.ExamineEntity)
{
EntityManager.System<ExamineSystem>().DoExamine(control.Entity);
args.Handle();
}
else if (args.Function == EngineKeyFunctions.UseSecondary)
{
UIManager.GetUIController<VerbMenuUIController>().OpenVerbMenu(control.Entity);
args.Handle();
}
else if (args.Function == ContentKeyFunctions.ActivateItemInWorld)
{
EntityManager.RaisePredictiveEvent(
new InteractInventorySlotEvent(EntityManager.GetNetEntity(control.Entity), altInteract: false));
args.Handle();
}
else if (args.Function == ContentKeyFunctions.AltActivateItemInWorld)
{
EntityManager.RaisePredictiveEvent(new InteractInventorySlotEvent(EntityManager.GetNetEntity(control.Entity), altInteract: true));
args.Handle();
}
window.FlagDirty();
}
private void OnPieceUnpressed(GUIBoundKeyEventArgs args, StorageWindow window, ItemGridPiece control)
{
if (args.Function != ContentKeyFunctions.MoveStoredItem)
return;
// Want to get the control under the dragged control.
// This means we can drag the original control around (and not hide the original).
control.MouseFilter = Control.MouseFilterMode.Ignore;
var targetControl = UIManager.MouseGetControl(args.PointerLocation);
var targetStorage = targetControl as StorageWindow;
control.MouseFilter = Control.MouseFilterMode.Pass;
var localPlayer = _player.LocalEntity;
window.RemoveGrid(control);
window.FlagDirty();
// If we tried to drag it on top of another grid piece then cancel out.
if (targetControl is ItemGridPiece || window.StorageEntity is not { } sourceStorage || localPlayer == null)
{
window.Reclaim(control.Location, control);
args.Handle();
_menuDragHelper.EndDrag();
return;
}
if (_menuDragHelper.IsDragging && DraggingGhost is { } draggingGhost)
{
var dragEnt = draggingGhost.Entity;
var dragLoc = draggingGhost.Location;
// Dragging in the same storage
// The existing ItemGridPiece just stops rendering but still exists so check if it's hovered.
if (targetStorage == window)
{
var position = targetStorage.GetMouseGridPieceLocation(dragEnt, dragLoc);
var newLocation = new ItemStorageLocation(DraggingRotation, position);
if (!_storage.ItemFitsInGridLocation(dragEnt, sourceStorage, newLocation))
{
window.Reclaim(control.Location, control);
}
else
{
EntityManager.RaisePredictiveEvent(new StorageSetItemLocationEvent(
EntityManager.GetNetEntity(draggingGhost.Entity),
EntityManager.GetNetEntity(sourceStorage),
newLocation));
window.Reclaim(newLocation, control);
}
}
// Dragging to new storage
else if (targetStorage?.StorageEntity != null && targetStorage != window)
{
var position = targetStorage.GetMouseGridPieceLocation(dragEnt, dragLoc);
var newLocation = new ItemStorageLocation(DraggingRotation, position);
// Check it fits and we can move to hand (no free transfers).
if (_storage.ItemFitsInGridLocation(
(dragEnt, null),
(targetStorage.StorageEntity.Value, null),
newLocation))
{
// Can drop and move.
EntityManager.RaisePredictiveEvent(new StorageTransferItemEvent(
EntityManager.GetNetEntity(dragEnt),
EntityManager.GetNetEntity(targetStorage.StorageEntity.Value),
newLocation));
targetStorage.Reclaim(newLocation, control);
DraggingRotation = Angle.Zero;
}
else
{
// Cancel it (rather than dropping).
window.Reclaim(dragLoc, control);
}
}
targetStorage?.FlagDirty();
}
// If we just clicked, then take it out of the bag.
else
{
EntityManager.RaisePredictiveEvent(new StorageInteractWithItemEvent(
EntityManager.GetNetEntity(control.Entity),
EntityManager.GetNetEntity(sourceStorage)));
}
_menuDragHelper.EndDrag();
args.Handle();
}
private bool OnMenuBeginDrag()
{
if (_menuDragHelper.Dragged is not { } dragged)
return false;
DraggingGhost!.Orphan();
DraggingRotation = dragged.Location.Rotation;
UIManager.PopupRoot.AddChild(DraggingGhost);
SetDraggingRotation();
return true;
}
private bool OnMenuContinueDrag(float frameTime)
{
if (DraggingGhost == null)
return false;
var player = _player.LocalEntity;
// If the attached storage is closed then stop dragging
if (player == null ||
!_storage.TryGetStorageLocation(DraggingGhost.Entity, out var container, out _, out _) ||
!_ui.IsUiOpen(container.Owner, StorageComponent.StorageUiKey.Key, player.Value))
{
DraggingGhost.Orphan();
return false;
}
SetDraggingRotation();
return true;
}
private void SetDraggingRotation()
{
if (DraggingGhost == null)
return;
var offset = ItemGridPiece.GetCenterOffset(
(DraggingGhost.Entity, null),
new ItemStorageLocation(DraggingRotation, Vector2i.Zero),
EntityManager);
// I don't know why it divides the position by 2. Hope this helps! -emo
LayoutContainer.SetPosition(DraggingGhost, UIManager.MousePositionScaled.Position / 2 - offset );
}
private void OnMenuEndDrag()
{
if (DraggingGhost == null)
return;
DraggingRotation = Angle.Zero;
}
public override void FrameUpdate(FrameEventArgs args)
{
base.FrameUpdate(args);
_menuDragHelper.Update(args.DeltaSeconds);
}
}