Client LuaCsForBarotrauma
CircuitBoxMouseDragSnapshotHandler.cs
1 #nullable enable
2 
3 using System;
4 using System.Collections.Generic;
5 using System.Collections.Immutable;
6 using System.Linq;
7 using Microsoft.Xna.Framework;
8 
9 namespace Barotrauma
10 {
17  internal sealed class CircuitBoxMouseDragSnapshotHandler
18  {
19  public IEnumerable<CircuitBoxNode> Nodes
20  {
21  get
22  {
23  var cb = circuitBoxUi.CircuitBox;
24 
25  foreach (var label in cb.Labels) { yield return label; }
26  foreach (var component in cb.Components) { yield return component; }
27  foreach (var node in cb.InputOutputNodes) { yield return node; }
28  }
29  }
30 
31  private IReadOnlyList<CircuitBoxWire> Wires => circuitBoxUi.CircuitBox.Wires;
32 
33  // List of all connections in the circuit box
34  private ImmutableArray<CircuitBoxConnection> connections = ImmutableArray<CircuitBoxConnection>.Empty;
35 
36  // Nodes that were under cursor when dragging started
37  private ImmutableHashSet<CircuitBoxNode> lastNodesUnderCursor = ImmutableHashSet<CircuitBoxNode>.Empty,
38  // Nodes that were selected when dragging started
39  lastSelectedComponents = ImmutableHashSet<CircuitBoxNode>.Empty,
40  // Nodes that should be moved when dragging
41  moveAffectedComponents = ImmutableHashSet<CircuitBoxNode>.Empty;
42 
43  public Option<(CircuitBoxResizeDirection, CircuitBoxNode)> LastResizeAffectedNode = Option.None;
44 
45  public ImmutableHashSet<CircuitBoxNode> GetLastComponentsUnderCursor() => lastNodesUnderCursor;
46  public ImmutableHashSet<CircuitBoxNode> GetMoveAffectedComponents() => moveAffectedComponents;
47 
48  public Option<CircuitBoxConnection> LastConnectorUnderCursor = Option.None;
49  public Option<CircuitBoxWire> LastWireUnderCursor = Option.None;
50 
54  public bool IsDragging { get; private set; }
55 
59  public bool IsWiring { get; private set; }
60 
64  public bool IsResizing { get; private set; }
65 
66  private Vector2 startClick = Vector2.Zero;
67  private readonly CircuitBoxUI circuitBoxUi;
68 
72  private const float dragTreshold = 16f;
73 
74  public CircuitBoxMouseDragSnapshotHandler(CircuitBoxUI ui)
75  {
76  circuitBoxUi = ui;
77  }
78 
82  public void StartDragging()
83  {
84  Vector2 cursorPos = circuitBoxUi.GetCursorPosition();
85  SnapshotNodesUnderCursor(cursorPos);
86  SnapshotSelectedNodes();
87  SnapshotMoveAffectedNodes();
88  startClick = cursorPos;
89  }
90 
91  public void ClearSnapshot()
92  {
93  lastNodesUnderCursor = ImmutableHashSet<CircuitBoxNode>.Empty;
94  lastSelectedComponents = ImmutableHashSet<CircuitBoxNode>.Empty;
95  moveAffectedComponents = ImmutableHashSet<CircuitBoxNode>.Empty;
96  LastConnectorUnderCursor = Option.None;
97  LastWireUnderCursor = Option.None;
98  LastResizeAffectedNode = Option.None;
99  }
100 
104  public void UpdateConnections()
105  {
106  var builder = ImmutableArray.CreateBuilder<CircuitBoxConnection>();
107 
108  builder.AddRange(circuitBoxUi.CircuitBox.Inputs);
109  builder.AddRange(circuitBoxUi.CircuitBox.Outputs);
110 
111  foreach (var node in Nodes)
112  {
113  builder.AddRange(node.Connectors);
114  }
115 
116  connections = builder.ToImmutable();
117  }
118 
122  public Option<CircuitBoxConnection> FindConnectorUnderCursor(Vector2 cursorPos)
123  {
124  foreach (var connection in connections)
125  {
126  if (connection.Contains(cursorPos))
127  {
128  return Option.Some(connection);
129  }
130  }
131 
132  return Option.None;
133  }
134 
138  public Option<CircuitBoxWire> FindWireUnderCursor(Vector2 cursorPos)
139  {
140  foreach (CircuitBoxWire wire in Wires)
141  {
142  if (wire is { IsSelected: true, IsSelectedByMe: false }) { continue; }
143  if (wire.Renderer.Contains(cursorPos))
144  {
145  return Option.Some(wire);
146  }
147  }
148 
149  return Option.None;
150  }
151 
155  public ImmutableHashSet<CircuitBoxNode> FindNodesUnderCursor(Vector2 cursorPos)
156  {
157  var builder = ImmutableHashSet.CreateBuilder<CircuitBoxNode>();
158  foreach (var node in Nodes)
159  {
160  if (node is { IsSelected: true, IsSelectedByMe: false }) { continue; }
161  if (node.Rect.Contains(cursorPos))
162  {
163  builder.Add(node);
164  }
165  }
166 
167  return builder.ToImmutable();
168  }
169 
173  private void SnapshotNodesUnderCursor(Vector2 cursorPos)
174  {
175  lastNodesUnderCursor = FindNodesUnderCursor(cursorPos);
176  LastConnectorUnderCursor = FindConnectorUnderCursor(cursorPos);
177  LastWireUnderCursor = FindWireUnderCursor(cursorPos);
178  LastResizeAffectedNode = FindResizeBorderUnderCursor(lastNodesUnderCursor, cursorPos);
179  }
180 
181  private Option<(CircuitBoxResizeDirection, CircuitBoxNode)> FindResizeBorderUnderCursor(ImmutableHashSet<CircuitBoxNode> nodes, Vector2 cursorPos)
182  {
183  if (!nodes.Any()) { return Option.None; }
184 
185  var node = circuitBoxUi.GetTopmostNode(nodes);
186  if (node is null || !node.IsResizable) { return Option.None; }
187 
188  const float borderSize = 32f;
189 
190  var rect = node.Rect;
191  RectangleF bottomBorder = new(rect.X, rect.Top, rect.Width, borderSize);
192  RectangleF rightBorder = new(rect.Right - borderSize, rect.Y, borderSize, rect.Height);
193  RectangleF leftBorder = new(rect.X, rect.Y, borderSize, rect.Height);
194 
195  bool hoverBottom = bottomBorder.Contains(cursorPos),
196  hoverRight = rightBorder.Contains(cursorPos),
197  hoverLeft = leftBorder.Contains(cursorPos);
198 
199  var dir = CircuitBoxResizeDirection.None;
200 
201  if (hoverBottom) { dir |= CircuitBoxResizeDirection.Down; }
202  if (hoverRight) { dir |= CircuitBoxResizeDirection.Right; }
203  if (hoverLeft) { dir |= CircuitBoxResizeDirection.Left; }
204 
205  if (dir is CircuitBoxResizeDirection.None)
206  {
207  return Option.None;
208  }
209 
210  return Option.Some((dir, node));
211  }
212 
218  private void SnapshotSelectedNodes()
219  {
220  lastSelectedComponents = Nodes.Where(static n => n is { IsSelected: true, IsSelectedByMe: true }).ToImmutableHashSet();
221  }
222 
226  private void SnapshotMoveAffectedNodes()
227  {
228  bool moveSelection = lastNodesUnderCursor.Any(node => lastSelectedComponents.Contains(node));
229 
230  /*
231  * If the user is dragging a selection, we should move all selected nodes (true).
232  *
233  * But for convenience, if the user is dragging a single node that is not part of the selection,
234  * we should move that node only instead and leave the selection alone. (false)
235  */
236  moveAffectedComponents = moveSelection switch
237  {
238  true => lastSelectedComponents,
239  false => circuitBoxUi.GetTopmostNode(lastNodesUnderCursor) switch
240  {
241  null => ImmutableHashSet<CircuitBoxNode>.Empty,
242  var node => ImmutableHashSet.Create(node)
243  }
244  };
245  }
246 
247  public Vector2 GetDragAmount(Vector2 mousePos) => mousePos - startClick;
248 
252  public void EndDragging()
253  {
254  startClick = Vector2.Zero;
255  IsDragging = false;
256  IsWiring = false;
257  IsResizing = false;
258  lastNodesUnderCursor = ImmutableHashSet<CircuitBoxNode>.Empty;
259  }
260 
261  public void UpdateDrag(Vector2 cursorPos)
262  {
263  // if there are no connectors under cursor, we can't be wiring anything
264  if (LastConnectorUnderCursor.IsNone())
265  {
266  IsWiring = false;
267  }
268 
269  // if there are no nodes under cursor, we can't be dragging anything
270  if (lastNodesUnderCursor.IsEmpty)
271  {
272  IsDragging = false;
273  }
274 
275  if (LastResizeAffectedNode.IsNone())
276  {
277  IsResizing = false;
278  }
279 
280  // startClick is set to zero when the user releases the mouse button, so we should be neither dragging nor wiring in this state
281  if (startClick == Vector2.Zero)
282  {
283  IsDragging = false;
284  IsWiring = false;
285  IsResizing = false;
286  return;
287  }
288 
289  if (circuitBoxUi.Locked) { return; }
290  bool isDragThresholdExceeded = Vector2.DistanceSquared(startClick, cursorPos) > dragTreshold * dragTreshold;
291 
292  if (LastConnectorUnderCursor.IsSome())
293  {
294  IsWiring |= isDragThresholdExceeded;
295  }
296  else if (LastResizeAffectedNode.IsSome())
297  {
298  IsResizing |= isDragThresholdExceeded;
299  }
300  else
301  {
302  IsDragging |= isDragThresholdExceeded;
303  }
304  }
305  }
306 }