#nullable enable using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; using Microsoft.Xna.Framework; namespace Barotrauma { /// /// This class handles a couple things: /// - Figuring out which components should be moved when dragging a certain part of the UI. /// - Finding components, connectors and wires under cursor. /// - Determines whether the user is dragging something. /// internal sealed class CircuitBoxMouseDragSnapshotHandler { public IEnumerable Nodes => circuitBoxUi.CircuitBox.Components.Union(circuitBoxUi.CircuitBox.InputOutputNodes); private IReadOnlyList Wires => circuitBoxUi.CircuitBox.Wires; // List of all connections in the circuit box private ImmutableArray connections = ImmutableArray.Empty; // Nodes that were under cursor when dragging started private ImmutableHashSet lastNodesUnderCursor = ImmutableHashSet.Empty, // Nodes that were selected when dragging started lastSelectedComponents = ImmutableHashSet.Empty, // Nodes that should be moved when dragging moveAffectedComponents = ImmutableHashSet.Empty; public ImmutableHashSet GetLastComponentsUnderCursor() => lastNodesUnderCursor; public ImmutableHashSet GetMoveAffectedComponents() => moveAffectedComponents; public Option LastConnectorUnderCursor = Option.None; public Option LastWireUnderCursor = Option.None; /// /// If the user is currently dragging a node /// public bool IsDragging { get; private set; } /// /// If the user is currently dragging a wire /// public bool IsWiring { get; private set; } private Vector2 startClick = Vector2.Zero; private readonly CircuitBoxUI circuitBoxUi; /// /// How far the user has to drag the mouse while holding down the button before dragging starts /// private const float dragTreshold = 16f; public CircuitBoxMouseDragSnapshotHandler(CircuitBoxUI ui) { circuitBoxUi = ui; } /// /// Called when the user holds down the mouse button /// public void StartDragging() { Vector2 cursorPos = circuitBoxUi.GetCursorPosition(); SnapshotNodesUnderCursor(cursorPos); SnapshotSelectedNodes(); SnapshotMoveAffectedNodes(); startClick = cursorPos; } /// /// Finds all connections and gathers them into a single list for easier iteration. /// public void UpdateConnections() { var builder = ImmutableArray.CreateBuilder(); builder.AddRange(circuitBoxUi.CircuitBox.Inputs); builder.AddRange(circuitBoxUi.CircuitBox.Outputs); foreach (var node in Nodes) { builder.AddRange(node.Connectors); } connections = builder.ToImmutable(); } /// /// Finds a possible connector under the cursor. /// public Option FindConnectorUnderCursor(Vector2 cursorPos) { foreach (var connection in connections) { if (connection.Contains(cursorPos)) { return Option.Some(connection); } } return Option.None; } /// /// Finds a possible wire under the cursor. /// public Option FindWireUnderCursor(Vector2 cursorPos) { foreach (CircuitBoxWire wire in Wires) { if (wire is { IsSelected: true, IsSelectedByMe: false }) { continue; } if (wire.Renderer.Contains(cursorPos)) { return Option.Some(wire); } } return Option.None; } /// /// Find all nodes that are currently under the cursor that are not selected by someone else. /// public ImmutableHashSet FindNodesUnderCursor(Vector2 cursorPos) { var builder = ImmutableHashSet.CreateBuilder(); foreach (var node in Nodes) { if (node is { IsSelected: true, IsSelectedByMe: false }) { continue; } if (node.Rect.Contains(cursorPos)) { builder.Add(node); } } return builder.ToImmutable(); } /// /// Finds and stores all nodes, connectors and wires that are under the cursor when dragging starts. /// private void SnapshotNodesUnderCursor(Vector2 cursorPos) { lastNodesUnderCursor = FindNodesUnderCursor(cursorPos); LastConnectorUnderCursor = FindConnectorUnderCursor(cursorPos); LastWireUnderCursor = FindWireUnderCursor(cursorPos); } /// /// Stores all nodes that are currently selected when dragging starts. /// There's no real way to change your selection while dragging so this is kinda pointless /// but we snapshot it anyway just in case. /// private void SnapshotSelectedNodes() { lastSelectedComponents = Nodes.Where(static n => n is { IsSelected: true, IsSelectedByMe: true }).ToImmutableHashSet(); } /// /// Stores all nodes that should be moved when dragging starts. /// private void SnapshotMoveAffectedNodes() { bool moveSelection = lastNodesUnderCursor.Any(node => lastSelectedComponents.Contains(node)); /* * If the user is dragging a selection, we should move all selected nodes (true). * * But for convenience, if the user is dragging a single node that is not part of the selection, * we should move that node only instead and leave the selection alone. (false) */ moveAffectedComponents = moveSelection switch { true => lastSelectedComponents, false => circuitBoxUi.GetTopmostNode(lastNodesUnderCursor) switch { null => ImmutableHashSet.Empty, var node => ImmutableHashSet.Create(node) } }; } public Vector2 GetDragAmount(Vector2 mousePos) => mousePos - startClick; /// /// Called when the user releases the mouse button /// public void EndDragging() { startClick = Vector2.Zero; IsDragging = false; IsWiring = false; lastNodesUnderCursor = ImmutableHashSet.Empty; } public void UpdateDrag(Vector2 cursorPos) { // if there are no connectors under cursor, we can't be wiring anything if (LastConnectorUnderCursor.IsNone()) { IsWiring = false; } // if there are no nodes under cursor, we can't be dragging anything if (lastNodesUnderCursor.IsEmpty) { IsDragging = false; } // startClick is set to zero when the user releases the mouse button, so we should be neither dragging nor wiring in this state if (startClick == Vector2.Zero) { IsDragging = false; IsWiring = false; return; } bool isDragTresholdExceeded = Vector2.DistanceSquared(startClick, cursorPos) > dragTreshold * dragTreshold; if (LastConnectorUnderCursor.IsNone()) { IsDragging |= isDragTresholdExceeded; } else { IsWiring |= isDragTresholdExceeded; } } } }