Skip to content

Commit

Permalink
Add support for click-and-dragging atoms (OpenDreamProject#1602)
Browse files Browse the repository at this point in the history
* Remove `ClickMapManager`
We haven't used these click maps in ages

* File-scoped namespaces

* Add support for click-dragging atoms

* A file-scoped namespace

* Remove some duplicated code
  • Loading branch information
wixoaGit authored Jan 3, 2024
1 parent d86fa80 commit 1515f0d
Show file tree
Hide file tree
Showing 11 changed files with 350 additions and 369 deletions.
1 change: 0 additions & 1 deletion DMCompiler/DMStandard/Types/Atoms/_Atom.dm
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,6 @@
set opendream_unimplemented = TRUE

proc/MouseDrop(over_object,src_location,over_location,src_control,over_control,params)
set opendream_unimplemented = TRUE

proc/MouseEntered(location,control,params)
set opendream_unimplemented = TRUE
Expand Down
1 change: 0 additions & 1 deletion DMCompiler/DMStandard/Types/Client.dm
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,6 @@
src_object.MouseDrag(over_object,src_location,over_location,src_control,over_control,params)

proc/MouseDrop(atom/src_object,over_object,src_location,over_location,src_control,over_control,params)
set opendream_unimplemented = TRUE
src_object.MouseDrop(over_object,src_location,over_location,src_control,over_control,params)

proc/MouseEntered(atom/object,location,control,params)
Expand Down
18 changes: 8 additions & 10 deletions OpenDreamClient/ClientContentIoC.cs
Original file line number Diff line number Diff line change
@@ -1,17 +1,15 @@
using OpenDreamClient.Audio;
using OpenDreamClient.Input;
using OpenDreamClient.Interface;
using OpenDreamClient.Resources;
using OpenDreamClient.States;

namespace OpenDreamClient {
public static class ClientContentIoC {
public static void Register() {
IoCManager.Register<IDreamInterfaceManager, DreamInterfaceManager>();
IoCManager.Register<IClickMapManager, ClickMapManager>();
IoCManager.Register<IDreamResourceManager, DreamResourceManager>();
IoCManager.Register<DreamUserInterfaceStateManager>();
IoCManager.Register<IDreamSoundEngine, DreamSoundEngine>();
}
namespace OpenDreamClient;

public static class ClientContentIoC {
public static void Register() {
IoCManager.Register<IDreamInterfaceManager, DreamInterfaceManager>();
IoCManager.Register<IDreamResourceManager, DreamResourceManager>();
IoCManager.Register<DreamUserInterfaceStateManager>();
IoCManager.Register<IDreamSoundEngine, DreamSoundEngine>();
}
}
91 changes: 0 additions & 91 deletions OpenDreamClient/Input/ClickMapManager.cs

This file was deleted.

210 changes: 134 additions & 76 deletions OpenDreamClient/Input/MouseInputSystem.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,113 +4,171 @@
using OpenDreamClient.Rendering;
using OpenDreamShared.Dream;
using OpenDreamShared.Input;
using Robust.Client.GameObjects;
using Robust.Client.Graphics;
using Robust.Client.Input;
using Robust.Client.UserInterface;
using Robust.Shared.Input;
using Robust.Shared.Input.Binding;
using Robust.Shared.Map;

namespace OpenDreamClient.Input {
internal sealed class MouseInputSystem : SharedMouseInputSystem {
[Dependency] private readonly IInputManager _inputManager = default!;
[Dependency] private readonly IUserInterfaceManager _userInterfaceManager = default!;
[Dependency] private readonly IMapManager _mapManager = default!;
[Dependency] private readonly IOverlayManager _overlayManager = default!;
[Dependency] private readonly EntityLookupSystem _lookupSystem = default!;
[Dependency] private readonly IEntityManager _entityManager = default!;

private DreamViewOverlay? _dreamViewOverlay;
private ContextMenuPopup _contextMenu = default!;

public override void Initialize() {
_contextMenu = new ContextMenuPopup();
_userInterfaceManager.ModalRoot.AddChild(_contextMenu);
}
namespace OpenDreamClient.Input;

internal sealed class MouseInputSystem : SharedMouseInputSystem {
[Dependency] private readonly IInputManager _inputManager = default!;
[Dependency] private readonly IUserInterfaceManager _userInterfaceManager = default!;
[Dependency] private readonly IMapManager _mapManager = default!;
[Dependency] private readonly IOverlayManager _overlayManager = default!;
[Dependency] private readonly EntityLookupSystem _lookupSystem = default!;
[Dependency] private readonly IEntityManager _entityManager = default!;
[Dependency] private readonly MapSystem _mapSystem = default!;

private DreamViewOverlay? _dreamViewOverlay;
private ContextMenuPopup _contextMenu = default!;
private EntityClickInformation? _selectedEntity;

private sealed class EntityClickInformation(AtomReference atom, ScreenCoordinates initialMousePos, ClickParams clickParams) {
public readonly AtomReference Atom = atom;
public readonly ScreenCoordinates InitialMousePos = initialMousePos;
public readonly ClickParams ClickParams = clickParams;
public bool IsDrag; // If the current click is considered a drag (if the mouse has moved after the click)
}

public override void Initialize() {
UpdatesOutsidePrediction = true;

_contextMenu = new ContextMenuPopup();
_userInterfaceManager.ModalRoot.AddChild(_contextMenu);
}

public override void Update(float frameTime) {
if (_selectedEntity == null)
return;

public override void Shutdown() {
CommandBinds.Unregister<MouseInputSystem>();
if (!_selectedEntity.IsDrag) {
var currentMousePos = _inputManager.MouseScreenPosition.Position;
var distance = (currentMousePos - _selectedEntity.InitialMousePos.Position).Length();

if (distance > 3f)
_selectedEntity.IsDrag = true;
}
}

public override void Shutdown() {
CommandBinds.Unregister<MouseInputSystem>();
}

public bool HandleViewportClick(ScalingViewport viewport, GUIBoundKeyEventArgs args) {
UIBox2i viewportBox = viewport.GetDrawBox();
if (!viewportBox.Contains((int)args.RelativePixelPosition.X, (int)args.RelativePixelPosition.Y))
return false; // Click was outside of the viewport
public bool HandleViewportEvent(ScalingViewport viewport, GUIBoundKeyEventArgs args) {
if (args.State == BoundKeyState.Down)
return OnPress(viewport, args);
else
return OnRelease(viewport, args);
}

bool middle = args.Function == OpenDreamKeyFunctions.MouseMiddle;
bool shift = _inputManager.IsKeyDown(Keyboard.Key.Shift);
bool ctrl = _inputManager.IsKeyDown(Keyboard.Key.Control);
bool alt = _inputManager.IsKeyDown(Keyboard.Key.Alt);
public void HandleStatClick(string atomRef, bool isMiddle) {
bool shift = _inputManager.IsKeyDown(Keyboard.Key.Shift);
bool ctrl = _inputManager.IsKeyDown(Keyboard.Key.Control);
bool alt = _inputManager.IsKeyDown(Keyboard.Key.Alt);

Vector2 screenLocPos = (args.RelativePixelPosition - viewportBox.TopLeft) / viewportBox.Size * viewport.ViewportSize;
RaiseNetworkEvent(new StatClickedEvent(atomRef, isMiddle, shift, ctrl, alt));
}

var screenLocY = viewport.ViewportSize.Y - screenLocPos.Y; // Flip the Y
ScreenLocation screenLoc = new ScreenLocation((int) screenLocPos.X, (int) screenLocY, 32); // TODO: icon_size other than 32
private (AtomReference Atom, Vector2i IconPosition)? GetAtomUnderMouse(ScalingViewport viewport, GUIBoundKeyEventArgs args) {
_dreamViewOverlay ??= _overlayManager.GetOverlay<DreamViewOverlay>();
if(_dreamViewOverlay.MouseMap == null)
return null;

MapCoordinates mapCoords = viewport.ScreenToMap(args.PointerLocation.Position);
RendererMetaData? entity = GetEntityUnderMouse(screenLocPos);
if (entity == null)
return false;
UIBox2i viewportBox = viewport.GetDrawBox();
if (!viewportBox.Contains((int)args.RelativePixelPosition.X, (int)args.RelativePixelPosition.Y))
return null; // Was outside of the viewport

if (entity.ClickUid == EntityUid.Invalid && args.Function != EngineKeyFunctions.UIRightClick) { // Turf was clicked and not a right-click
// Grid coordinates are half a meter off from entity coordinates
mapCoords = new MapCoordinates(mapCoords.Position + new Vector2(0.5f), mapCoords.MapId);
var mapCoords = viewport.ScreenToMap(args.PointerLocation.Position);
var mousePos = (args.RelativePixelPosition - viewportBox.TopLeft) / viewportBox.Size * viewport.ViewportSize;
var lookupColor = _dreamViewOverlay.MouseMap.GetPixel((int)mousePos.X, (int)mousePos.Y);
var underMouse = _dreamViewOverlay.MouseMapLookup.GetValueOrDefault(lookupColor);
if (underMouse == null)
return null;

if (_mapManager.TryFindGridAt(mapCoords, out _, out var grid)){
Vector2i position = grid.CoordinatesToTile(grid.MapToGrid(mapCoords));
MapCoordinates worldPosition = grid.GridTileToWorld(position);
Vector2i turfIconPosition = (Vector2i) ((mapCoords.Position - position) * EyeManager.PixelsPerMeter);
RaiseNetworkEvent(new TurfClickedEvent(position, (int)worldPosition.MapId, screenLoc, middle, shift, ctrl, alt, turfIconPosition));
}
if (underMouse.ClickUid == EntityUid.Invalid) { // A turf
// Grid coordinates are half a meter off from entity coordinates
mapCoords = new MapCoordinates(mapCoords.Position + new Vector2(0.5f), mapCoords.MapId);

return true;
if (_mapManager.TryFindGridAt(mapCoords, out var gridEntity, out var grid)) {
Vector2i position = _mapSystem.CoordinatesToTile(gridEntity, grid, _mapSystem.MapToGrid(gridEntity, mapCoords));
Vector2i turfIconPosition = (Vector2i) ((mapCoords.Position - position) * EyeManager.PixelsPerMeter);
MapCoordinates worldPosition = _mapSystem.GridTileToWorld(gridEntity, grid, position);

return (new(position, (int)worldPosition.MapId), turfIconPosition);
}

if (args.Function == EngineKeyFunctions.UIRightClick) { //either turf or atom was clicked, and it was a right-click
var entities = _lookupSystem.GetEntitiesInRange(mapCoords, 0.01f);
return null;
} else {
Vector2i iconPosition = (Vector2i) ((mapCoords.Position - underMouse.Position) * EyeManager.PixelsPerMeter);

return (new(_entityManager.GetNetEntity(underMouse.ClickUid)), iconPosition);
}
}

//TODO filter entities by the valid verbs that exist on them
//they should only show up if there is a verb attached to usr which matches the filter in world syntax
//ie, obj|turf in world
//note that popup_menu = 0 overrides this behaviour, as does verb invisibility (urgh), and also hidden
//because BYOND sure loves redundancy
private bool OnPress(ScalingViewport viewport, GUIBoundKeyEventArgs args) {
if (args.Function == EngineKeyFunctions.UIRightClick) { //either turf or atom was clicked, and it was a right-click
var mapCoords = viewport.ScreenToMap(args.PointerLocation.Position);
var entities = _lookupSystem.GetEntitiesInRange(mapCoords, 0.01f);

_contextMenu.RepopulateEntities(entities);
if(_contextMenu.EntityCount == 0)
return true; //don't open a 1x1 empty context menu
//TODO filter entities by the valid verbs that exist on them
//they should only show up if there is a verb attached to usr which matches the filter in world syntax
//ie, obj|turf in world
//note that popup_menu = 0 overrides this behaviour, as does verb invisibility (urgh), and also hidden
//because BYOND sure loves redundancy

_contextMenu.Measure(_userInterfaceManager.ModalRoot.Size);
Vector2 contextMenuLocation = args.PointerLocation.Position / _userInterfaceManager.ModalRoot.UIScale; // Take scaling into account
_contextMenu.Open(UIBox2.FromDimensions(contextMenuLocation, _contextMenu.DesiredSize));
_contextMenu.RepopulateEntities(entities);
if(_contextMenu.EntityCount == 0)
return true; //don't open a 1x1 empty context menu

return true;
}
_contextMenu.Measure(_userInterfaceManager.ModalRoot.Size);
Vector2 contextMenuLocation = args.PointerLocation.Position / _userInterfaceManager.ModalRoot.UIScale; // Take scaling into account
_contextMenu.Open(UIBox2.FromDimensions(contextMenuLocation, _contextMenu.DesiredSize));

// TODO: Take icon transformations into account
Vector2i iconPosition = (Vector2i) ((mapCoords.Position - entity.Position) * EyeManager.PixelsPerMeter);
NetEntity ent = _entityManager.GetNetEntity(entity.ClickUid);
RaiseNetworkEvent(new EntityClickedEvent(ent, screenLoc, middle, shift, ctrl, alt, iconPosition));
return true;
}

public void HandleStatClick(string atomRef, bool isMiddle) {
bool shift = _inputManager.IsKeyDown(Keyboard.Key.Shift);
bool ctrl = _inputManager.IsKeyDown(Keyboard.Key.Control);
bool alt = _inputManager.IsKeyDown(Keyboard.Key.Alt);
var underMouse = GetAtomUnderMouse(viewport, args);
if (underMouse == null)
return false;

RaiseNetworkEvent(new StatClickedEvent(atomRef, isMiddle, shift, ctrl, alt));
}
var atom = underMouse.Value.Atom;
var clickParams = CreateClickParams(viewport, args, underMouse.Value.IconPosition);

private RendererMetaData? GetEntityUnderMouse(Vector2 mousePos) {
_dreamViewOverlay ??= _overlayManager.GetOverlay<DreamViewOverlay>();
if(_dreamViewOverlay.MouseMap == null)
return null;
_selectedEntity = new(atom, args.PointerLocation, clickParams);
return true;
}

private bool OnRelease(ScalingViewport viewport, GUIBoundKeyEventArgs args) {
if (_selectedEntity == null)
return false;

Color lookupColor = _dreamViewOverlay.MouseMap.GetPixel((int)mousePos.X, (int)mousePos.Y);
if(!_dreamViewOverlay.MouseMapLookup.TryGetValue(lookupColor, out var result))
return null;
if (!_selectedEntity.IsDrag) {
RaiseNetworkEvent(new AtomClickedEvent(_selectedEntity.Atom, _selectedEntity.ClickParams));
} else {
var overAtom = GetAtomUnderMouse(viewport, args);

return result;
RaiseNetworkEvent(new AtomDraggedEvent(_selectedEntity.Atom, overAtom?.Atom, _selectedEntity.ClickParams));
}

_selectedEntity = null;
return true;
}

private ClickParams CreateClickParams(ScalingViewport viewport, GUIBoundKeyEventArgs args, Vector2i iconPos) {
bool middle = args.Function == OpenDreamKeyFunctions.MouseMiddle;
bool shift = _inputManager.IsKeyDown(Keyboard.Key.Shift);
bool ctrl = _inputManager.IsKeyDown(Keyboard.Key.Control);
bool alt = _inputManager.IsKeyDown(Keyboard.Key.Alt);
UIBox2i viewportBox = viewport.GetDrawBox();
Vector2 screenLocPos = (args.RelativePixelPosition - viewportBox.TopLeft) / viewportBox.Size * viewport.ViewportSize;
float screenLocY = viewport.ViewportSize.Y - screenLocPos.Y; // Flip the Y
ScreenLocation screenLoc = new ScreenLocation((int) screenLocPos.X, (int) screenLocY, 32); // TODO: icon_size other than 32

// TODO: Take icon transformations into account for iconPos
return new(screenLoc, middle, shift, ctrl, alt, iconPos.X, iconPos.Y);
}
}
Loading

0 comments on commit 1515f0d

Please sign in to comment.