3 using System.Collections.Generic;
4 using System.Collections.Immutable;
7 using Microsoft.Xna.Framework;
8 using Microsoft.Xna.Framework.Graphics;
9 using Microsoft.Xna.Framework.Input;
13 internal sealed
class CircuitBoxUI
15 private readonly Camera camera;
16 private static readonly Vector2 gridSize =
new Vector2(128f);
17 public readonly CircuitBox CircuitBox;
18 private bool componentMenuOpen;
19 private float componentMenuOpenState;
21 private GUICustomComponent? circuitComponent;
22 private GUIFrame? componentMenu;
23 private GUIButton? toggleMenuButton;
24 private GUIFrame? selectedWireFrame;
25 private GUIListBox? componentList;
26 private GUITextBlock? inventoryIndicatorText;
27 private readonly Sprite? cursorSprite = GUIStyle.CursorSprite[
CursorState.Default];
29 private Option<RectangleF> selection = Option.None;
30 private string searchTerm =
string.Empty;
32 public static Option<CircuitBoxWireRenderer> DraggedWire = Option.None;
34 public readonly CircuitBoxMouseDragSnapshotHandler MouseSnapshotHandler;
36 public List<CircuitBoxWireRenderer> VirtualWires =
new();
38 public bool Locked => CircuitBox.IsLocked();
40 public CircuitBoxUI(CircuitBox box)
49 MouseSnapshotHandler =
new CircuitBoxMouseDragSnapshotHandler(
this);
54 public void CreateGUI(GUIFrame parent)
56 GUIFrame paddedFrame =
new GUIFrame(
new RectTransform(
new Vector2(0.97f, 0.95f), parent.RectTransform,
Anchor.Center), style:
null);
57 circuitComponent =
new GUICustomComponent(
new RectTransform(Vector2.One, paddedFrame.RectTransform), onDraw: (spriteBatch, component) =>
59 Rectangle prevScissorRect = spriteBatch.GraphicsDevice.ScissorRectangle;
61 spriteBatch.GraphicsDevice.ScissorRectangle = component.Rect;
63 spriteBatch.Begin(SpriteSortMode.Deferred, samplerState: GUI.SamplerState, rasterizerState: GameMain.ScissorTestEnable, transformMatrix: camera.Transform);
64 DrawCircuits(spriteBatch);
67 spriteBatch.Begin(SpriteSortMode.Deferred, samplerState: GUI.SamplerState, rasterizerState: GameMain.ScissorTestEnable);
68 DrawHUD(spriteBatch, component.Rect);
71 spriteBatch.GraphicsDevice.ScissorRectangle = prevScissorRect;
72 spriteBatch.Begin(SpriteSortMode.Deferred, samplerState: GUI.SamplerState, rasterizerState: GameMain.ScissorTestEnable);
75 GUIScissorComponent menuContainer =
new GUIScissorComponent(
new RectTransform(Vector2.One, paddedFrame.RectTransform, anchor:
Anchor.Center))
80 componentMenuOpen =
true;
81 componentMenu =
new GUIFrame(
new RectTransform(
new Vector2(1f, 0.4f), menuContainer.Content.RectTransform,
Anchor.BottomRight));
82 toggleMenuButton =
new GUIButton(
new RectTransform(
new Point(300, 30), GUI.Canvas) { MinSize = new Point(0, 15) }, style:
"UIToggleButtonVertical")
84 OnClicked = (btn, userdata) =>
86 componentMenuOpen = !componentMenuOpen;
87 if (Locked) { componentMenuOpen =
false; }
89 foreach (GUIComponent child
in btn.Children)
91 child.SpriteEffects = componentMenuOpen ? SpriteEffects.None : SpriteEffects.FlipVertically;
98 GUILayoutGroup menuLayout =
new GUILayoutGroup(
new RectTransform(Vector2.One, componentMenu.RectTransform), childAnchor:
Anchor.TopCenter) { RelativeSpacing = 0.02f };
99 GUILayoutGroup headerLayout =
new GUILayoutGroup(
new RectTransform(
new Vector2(1f, 0.2f), menuLayout.RectTransform), isHorizontal:
true);
101 GUILayoutGroup labelLayout =
new GUILayoutGroup(
new RectTransform(
new Vector2(0.33f, 1f), headerLayout.RectTransform), isHorizontal:
true);
103 GUILayoutGroup searchBarLayout =
new GUILayoutGroup(
new RectTransform(
new Vector2(0.33f, 1f), headerLayout.RectTransform), childAnchor:
Anchor.CenterLeft, isHorizontal:
true);
104 GUITextBlock searchBarLabel =
new GUITextBlock(
new RectTransform(
new Vector2(0.15f, 1f), searchBarLayout.RectTransform),
"Filter");
105 GUITextBox searchbar =
new GUITextBox(
new RectTransform(
new Vector2(0.85f, 1f), searchBarLayout.RectTransform),
string.Empty, createClearButton:
true);
107 new GUIFrame(
new RectTransform(
new Vector2(0.5f, 0.01f), menuLayout.RectTransform), style:
"HorizontalLine");
109 componentList =
new GUIListBox(
new RectTransform(
new Vector2(0.95f, 0.65f), menuLayout.RectTransform))
111 PlaySoundOnSelect =
true,
112 UseGridLayout =
true,
113 OnSelected = (_, o) =>
115 if (o is not ItemPrefab prefab) {
return false; }
117 CircuitBox.HeldComponent = Option.Some(prefab);
122 GUILayoutGroup inventoryLayout =
new GUILayoutGroup(
new RectTransform(
new Vector2(0.33f, 1f), headerLayout.RectTransform), isHorizontal:
true, childAnchor:
Anchor.Center);
123 GUILayoutGroup indicatorLayout =
new GUILayoutGroup(
new RectTransform(
new Vector2(0.2f, 1f), inventoryLayout.RectTransform), isHorizontal:
true, childAnchor:
Anchor.CenterLeft);
124 GUIImage indicatorIcon =
new GUIImage(
new RectTransform(
new Vector2(0.5f, 0.8f), indicatorLayout.RectTransform, scaleBasis:
ScaleBasis.BothHeight), style:
"CircuitIndicatorIcon");
125 inventoryIndicatorText =
new GUITextBlock(
new RectTransform(
new Vector2(0.5f, 1f), indicatorLayout.RectTransform), GetInventoryText(), font: GUIStyle.SubHeadingFont);
127 int gapSize = GUI.IntScale(8);
128 selectedWireFrame = SubEditorScreen.CreateWiringPanel(Point.Zero, SelectWire);
129 selectedWireFrame.RectTransform.AbsoluteOffset =
new Point(parent.Rect.X - (selectedWireFrame.Rect.Width + gapSize), parent.Rect.Y);
131 foreach (ItemPrefab prefab
in ItemPrefab.Prefabs.OrderBy(
static p => p.Name))
133 if (!prefab.Tags.Contains(
"circuitboxcomponent")) {
continue; }
135 CreateComponentElement(prefab, componentList.Content.RectTransform);
138 searchbar.OnTextChanged += (tb, s) =>
141 UpdateComponentList();
144 int buttonHeight = (int)(GUIStyle.ItemFrameMargin.Y * 0.4f);
145 var settingsIcon =
new GUIButton(
new RectTransform(
new Point(buttonHeight), parent.RectTransform,
Anchor.TopLeft) { AbsoluteOffset = new Point(buttonHeight / 4), MinSize = new Point(buttonHeight) },
146 style:
"GUIButtonSettings")
148 OnClicked = (btn, userdata) =>
150 GUIContextMenu.CreateContextMenu(
151 new ContextMenuOption(
"circuitboxsetting.resetview", isEnabled:
true, onSelected: ResetCamera)
153 Tooltip = TextManager.Get(
"circuitboxsettingdescription.resetview")
155 new ContextMenuOption(
"circuitboxsetting.find", isEnabled:
true,
156 new ContextMenuOption(
"circuitboxsetting.focusinput", isEnabled:
true, onSelected: () => FindInputOutput(CircuitBoxInputOutputNode.Type.Input))
158 Tooltip = TextManager.Get(
"circuitboxsettingdescription.focusinput")
160 new ContextMenuOption(
"circuitboxsetting.focusoutput", isEnabled:
true, onSelected: () => FindInputOutput(CircuitBoxInputOutputNode.Type.Output))
162 Tooltip = TextManager.Get(
"circuitboxsettingdescription.focusoutput")
164 new ContextMenuOption(
"circuitboxsetting.focuscircuits", isEnabled: CircuitBox.Components.Any(), onSelected: FindCircuit)
166 Tooltip = TextManager.Get(
"circuitboxsettingdescription.focuscircuits")
173 camera.TargetPos = Vector2.One;
176 void FindInputOutput(CircuitBoxInputOutputNode.Type type)
178 var input = CircuitBox.InputOutputNodes.FirstOrDefault(n => n.NodeType == type);
179 if (input is
null) {
return; }
181 camera.TargetPos = input.Position;
186 var closestComponent = CircuitBox.Components.MinBy(c => Vector2.DistanceSquared(c.Position, camera.Position));
187 if (closestComponent is
null) {
return; }
189 camera.TargetPos = closestComponent.Position;
196 MouseSnapshotHandler.UpdateConnections();
199 foreach (var node
in CircuitBox.Components) { node.OnUICreated(); }
201 foreach (var node
in CircuitBox.InputOutputNodes) { node.OnUICreated(); }
203 foreach (var wire
in CircuitBox.Wires) { wire.Update(); }
206 private string GetInventoryText() =>
207 CircuitBox.ComponentContainer is { } container
208 ? $
"{container.Inventory.AllItems.Count()}/{container.Capacity}"
211 public void UpdateComponentList()
213 if (inventoryIndicatorText is { } text)
215 text.Text = GetInventoryText();
218 if (componentList is
null) {
return; }
220 var playerInventory = CircuitBox.GetSortedCircuitBoxItemsFromPlayer(
Character.Controlled);
222 foreach (GUIComponent child
in componentList.Content.Children)
224 if (child.UserData is not ItemPrefab prefab) {
continue; }
226 child.Enabled = !CircuitBox.IsFull && (!CircuitBox.IsInGame() || CircuitBox.GetApplicableResourcePlayerHas(prefab, playerInventory).IsSome());
228 if (child.GetChild<GUILayoutGroup>()?.GetChild<GUIImage>() is { } image)
230 image.Enabled = child.Enabled;
233 child.ToolTip = child.Enabled
235 : RichString.Rich(TextManager.GetWithVariable(
new Identifier(
"CircuitBoxUIComponentNotAvailable"),
new Identifier(
"[item]"), prefab.Name));
237 if (
string.IsNullOrWhiteSpace(searchTerm))
239 child.Visible =
true;
243 child.Visible = prefab.Name.Contains(searchTerm, StringComparison.OrdinalIgnoreCase);
247 private static bool SelectWire(GUIComponent component,
object obj)
249 if (obj is not ItemPrefab prefab) {
return false; }
251 CircuitBoxWire.SelectedWirePrefab = prefab;
255 private static void CreateComponentElement(ItemPrefab prefab, RectTransform parent)
257 GUIFrame itemFrame =
new GUIFrame(
new RectTransform(
new Vector2(0.1f, 0.9f), parent) { MinSize =
new Point(0, 50) }, style:
"GUITextBox")
262 itemFrame.RectTransform.MinSize =
new Point(0, itemFrame.Rect.Width);
263 itemFrame.RectTransform.MaxSize =
new Point(
int.MaxValue, itemFrame.Rect.Width);
264 itemFrame.ToolTip = prefab.Name;
266 GUILayoutGroup paddedFrame =
new GUILayoutGroup(
new RectTransform(
new Vector2(0.8f, 0.8f), itemFrame.RectTransform,
Anchor.Center), childAnchor:
Anchor.TopCenter)
269 RelativeSpacing = 0.03f,
276 if (prefab.InventoryIcon !=
null)
278 icon = prefab.InventoryIcon;
279 iconColor = prefab.InventoryIconColor;
283 icon = prefab.Sprite;
284 iconColor = prefab.SpriteColor;
287 GUIImage? img =
null;
290 img =
new GUIImage(
new RectTransform(
new Vector2(1.0f, 0.8f), paddedFrame.RectTransform,
Anchor.TopCenter), icon)
292 CanBeFocused =
false,
293 LoadAsynchronously =
true,
294 DisabledColor = Color.DarkGray * 0.8f,
299 GUITextBlock textBlock =
new GUITextBlock(
new RectTransform(
new Vector2(1.0f, 0.0f), paddedFrame.RectTransform,
Anchor.BottomCenter),
300 text: prefab.Name, textAlignment: Alignment.Center, font: GUIStyle.SmallFont)
305 textBlock.Text = ToolBox.LimitString(textBlock.Text, textBlock.Font, textBlock.Rect.Width);
306 paddedFrame.Recalculate();
310 img.Scale = Math.Min(Math.Min(img.Rect.Width / img.Sprite.size.X, img.Rect.Height / img.Sprite.size.Y), 1.5f);
311 img.RectTransform.NonScaledSize =
new Point((
int)(img.Sprite.size.X * img.Scale), img.Rect.Height);
317 private void DrawHUD(SpriteBatch spriteBatch, Rectangle screenRect)
319 float scale = GUI.Scale / 1.5f;
320 Vector2 offset =
new Vector2(20, 40) * scale;
322 foreach (var (character, cursor) in CircuitBox.ActiveCursors)
324 if (!cursor.IsActive) {
continue; }
326 Vector2 cursorWorldPos = camera.WorldToScreen(cursor.DrawPosition);
328 if (cursor.Info.DragStart.TryUnwrap(out Vector2 dragStart))
330 DrawSelection(spriteBatch, dragStart, cursor.DrawPosition, cursor.Color);
333 if (cursor.HeldPrefab.TryUnwrap(out ItemPrefab? otherHeldPrefab))
335 otherHeldPrefab.Sprite.Draw(spriteBatch, cursorWorldPos);
338 cursorSprite?.Draw(spriteBatch, cursorWorldPos, cursor.Color, 0f, scale);
339 GUI.DrawString(spriteBatch, cursorWorldPos + offset, character.Name, cursor.Color, Color.Black, GUI.IntScale(4), GUIStyle.SmallFont);
342 if (selection.TryUnwrap(out RectangleF rect))
344 Vector2 pos1 = rect.Location;
345 Vector2 pos2 =
new Vector2(rect.Location.X + rect.Size.X, rect.Location.Y + rect.Size.Y);
346 DrawSelection(spriteBatch, pos1, pos2, GUIStyle.Blue);
349 if (CircuitBox.HeldComponent.TryUnwrap(out ItemPrefab? component))
351 component.Sprite.Draw(spriteBatch, PlayerInput.MousePosition);
353 if (PlayerInput.PrimaryMouseButtonHeld() && MouseSnapshotHandler.LastConnectorUnderCursor.IsSome())
355 CircuitBoxWire.SelectedWirePrefab.Sprite.Draw(spriteBatch, PlayerInput.MousePosition, CircuitBoxWire.SelectedWirePrefab.SpriteColor, scale: camera.Zoom);
358 foreach (var c
in CircuitBox.Components)
360 c.DrawHUD(spriteBatch, camera);
363 foreach (var n
in CircuitBox.InputOutputNodes)
365 n.DrawHUD(spriteBatch, camera);
370 LocalizedString lockedText = TextManager.Get(
"CircuitBoxLocked")
371 .Fallback(TextManager.Get(
"ConnectionLocked"), useDefaultLanguageIfFound:
false);
373 Vector2 size = GUIStyle.LargeFont.MeasureString(lockedText);
374 Vector2 pos =
new Vector2(screenRect.Center.X - size.X / 2, screenRect.Top + screenRect.Height * 0.05f);
375 GUI.DrawString(spriteBatch, pos, lockedText, Color.Red, Color.Black, 8, GUIStyle.LargeFont);
379 private void DrawSelection(SpriteBatch spriteBatch, Vector2 pos1, Vector2 pos2, Color color)
381 Vector2 location = camera.WorldToScreen(pos1);
382 location.Y = -location.Y;
383 Vector2 location2 = camera.WorldToScreen(pos2);
384 location2.Y = -location2.Y;
385 MapEntity.DrawSelectionRect(spriteBatch, location,
new Vector2(-(location.X - location2.X), location.Y - location2.Y), color);
388 private const float lineBaseWidth = 1f;
389 private static float lineWidth;
391 public static void DrawRectangleWithBorder(SpriteBatch spriteBatch, RectangleF rect, Color fillColor, Color borderColor)
393 GUI.DrawFilledRectangle(spriteBatch, rect, fillColor);
394 DrawRectangleOnlyBorder(spriteBatch, rect, borderColor);
397 private static void DrawRectangleOnlyBorder(SpriteBatch spriteBatch, RectangleF rect, Color borderColor)
399 Vector2 topRight =
new Vector2(rect.Right, rect.Top),
400 topLeft =
new Vector2(rect.Left, rect.Top),
401 bottomRight =
new Vector2(rect.Right, rect.Bottom),
402 bottomLeft =
new Vector2(rect.Left, rect.Bottom);
404 Vector2 offset =
new Vector2(0f, lineWidth / 2f);
406 spriteBatch.DrawLine(topRight, topLeft, borderColor, thickness: lineWidth);
407 spriteBatch.DrawLine(topLeft - offset, bottomLeft + offset, borderColor, thickness: lineWidth);
408 spriteBatch.DrawLine(bottomLeft, bottomRight, borderColor, thickness: lineWidth);
409 spriteBatch.DrawLine(bottomRight + offset, topRight - offset, borderColor, thickness: lineWidth);
412 private void DrawCircuits(SpriteBatch spriteBatch)
414 camera.UpdateTransform(interpolate:
true, updateListener:
false);
415 SubEditorScreen.DrawOutOfBoundsArea(spriteBatch, camera, CircuitBoxSizes.PlayableAreaSize, GUIStyle.Red * 0.33f);
416 SubEditorScreen.DrawGrid(spriteBatch, camera, gridSize.X, gridSize.Y, zoomTreshold:
false);
417 lineWidth = lineBaseWidth / camera.Zoom;
419 Vector2 mousePos = GetCursorPosition();
420 mousePos.Y = -mousePos.Y;
422 foreach (var label
in CircuitBox.Labels)
424 if (label.IsSelected)
426 label.DrawSelection(spriteBatch, GetSelectionColor(label));
429 label.Draw(spriteBatch, label.Position, label.Color);
432 foreach (CircuitBoxWire wire
in CircuitBox.Wires)
434 wire.Renderer.Draw(spriteBatch, GetSelectionColor(wire));
437 foreach (var node
in CircuitBox.Components)
441 node.DrawSelection(spriteBatch, GetSelectionColor(node));
444 node.Draw(spriteBatch, node.Position, node.Item.Prefab.SignalComponentColor * CircuitBoxNode.Opacity);
447 foreach (var ioNode
in CircuitBox.InputOutputNodes)
449 if (ioNode.IsSelected)
451 ioNode.DrawSelection(spriteBatch, GetSelectionColor(ioNode));
454 Color color = ioNode.NodeType is CircuitBoxInputOutputNode.Type.Input ? GUIStyle.Green : GUIStyle.Red;
455 ioNode.Draw(spriteBatch, ioNode.Position, color * CircuitBoxNode.Opacity);
458 if (MouseSnapshotHandler.IsDragging)
460 var draggedNodes = MouseSnapshotHandler.GetMoveAffectedComponents();
461 Vector2 dragOffset = MouseSnapshotHandler.GetDragAmount(GetCursorPosition());
462 foreach (CircuitBoxNode moveable
in draggedNodes)
464 Color color = moveable
switch
466 CircuitBoxComponent node => node.Item.Prefab.SignalComponentColor,
467 CircuitBoxLabelNode label => label.Color,
468 CircuitBoxInputOutputNode ioNode => ioNode.NodeType is CircuitBoxInputOutputNode.Type.Input ? GUIStyle.Green : GUIStyle.Red,
471 moveable.Draw(spriteBatch, moveable.Position + dragOffset, color * 0.5f);
475 if (MouseSnapshotHandler.IsResizing && MouseSnapshotHandler.LastResizeAffectedNode.TryUnwrap(out var resize))
477 var (dir, node) = resize;
478 Vector2 dragOffset = MouseSnapshotHandler.GetDragAmount(GetCursorPosition());
480 var rect = node.Rect;
482 rect.Y -= rect.Height;
484 if (dir.HasFlag(CircuitBoxResizeDirection.Down))
486 rect.Height -= dragOffset.Y;
487 rect.Height = Math.Max(rect.Height, CircuitBoxLabelNode.MinSize.Y + CircuitBoxSizes.NodeHeaderHeight);
490 if (dir.HasFlag(CircuitBoxResizeDirection.Right))
492 rect.Width += dragOffset.X;
493 rect.Width = Math.Max(rect.Width, CircuitBoxLabelNode.MinSize.X);
496 if (dir.HasFlag(CircuitBoxResizeDirection.Left))
498 float oldWidth = rect.Width;
499 rect.Width -= dragOffset.X;
500 rect.Width = Math.Max(rect.Width, CircuitBoxLabelNode.MinSize.X);
502 float actualResize = rect.Width - oldWidth;
503 rect.X -= actualResize;
506 DrawRectangleOnlyBorder(spriteBatch, rect, GUIStyle.Yellow);
509 if (DraggedWire.TryUnwrap(out CircuitBoxWireRenderer? draggedWire))
511 draggedWire.Draw(spriteBatch, GUIStyle.Yellow);
515 private Color GetSelectionColor(CircuitBoxNode node) => GetSelectionColor(node.SelectedBy, node.IsSelectedByMe);
517 private Color GetSelectionColor(CircuitBoxWire wire) => GetSelectionColor(wire.SelectedBy, wire.IsSelectedByMe);
519 private Color GetSelectionColor(ushort selectedBy,
bool isSelectedByMe)
524 return GUIStyle.Yellow;
528 foreach (var (_, cursor) in CircuitBox.ActiveCursors)
530 if (cursor.Info.CharacterID == selectedBy)
536 return GUIStyle.Yellow;
539 private Vector2 cursorPos;
540 public Vector2 GetCursorPosition() => cursorPos;
541 public Option<Vector2> GetDragStart() => selection.Select(
static f => f.Location);
543 public void Update(
float deltaTime)
545 cursorPos = camera.ScreenToWorld(PlayerInput.MousePosition);
546 foreach (CircuitBoxWire wire
in CircuitBox.Wires)
551 bool foundSelected =
false;
552 foreach (var node
in CircuitBox.Components)
554 if (!node.IsSelectedByMe) {
continue; }
556 foundSelected =
true;
557 if (circuitComponent is not
null)
559 node.UpdateEditing(circuitComponent.RectTransform);
567 CircuitBoxComponent.RemoveEditingHUD();
570 bool isMouseOn = GUI.MouseOn == circuitComponent;
577 camera.MoveCamera(deltaTime, allowMove:
true, allowZoom: isMouseOn, allowInput: isMouseOn, followSub:
false);
579 if (camera.TargetPos != Vector2.Zero && MathUtils.NearlyEqual(camera.Position, camera.TargetPos, 0.01f))
581 camera.TargetPos = Vector2.Zero;
586 if (PlayerInput.PrimaryMouseButtonDown())
588 if (CircuitBox.HeldComponent.IsNone())
590 MouseSnapshotHandler.StartDragging();
594 MouseSnapshotHandler.ClearSnapshot();
598 if (PlayerInput.DoubleClicked() && MouseSnapshotHandler.FindWireUnderCursor(cursorPos).IsNone())
600 var topmostNode = GetTopmostNode(MouseSnapshotHandler.FindNodesUnderCursor(cursorPos));
601 if (topmostNode is CircuitBoxLabelNode label && circuitComponent is not
null)
603 label.PromptEditText(circuitComponent);
607 if (PlayerInput.MidButtonHeld() || (PlayerInput.IsAltDown() && PlayerInput.PrimaryMouseButtonHeld()))
609 Vector2 moveSpeed = PlayerInput.MouseSpeed / camera.Zoom;
610 moveSpeed.X = -moveSpeed.X;
611 camera.Position += moveSpeed;
614 if (PlayerInput.PrimaryMouseButtonHeld())
616 MouseSnapshotHandler.UpdateDrag(GetCursorPosition());
619 if (MouseSnapshotHandler.IsWiring && MouseSnapshotHandler.LastConnectorUnderCursor.TryUnwrap(out var c))
621 Vector2 start = c.Rect.Center,
622 end = GetCursorPosition();
628 (start, end) = (end, start);
631 if (DraggedWire.TryUnwrap(out var wire))
633 wire.Recompute(start, end, CircuitBoxWire.SelectedWirePrefab.SpriteColor);
637 DraggedWire = Option.Some(
new CircuitBoxWireRenderer(Option.None, start, end, GUIStyle.Red, CircuitBox.WireSprite));
642 DraggedWire = Option.None;
645 if (PlayerInput.SecondaryMouseButtonClicked())
650 if (PlayerInput.PrimaryMouseButtonClicked())
652 bool selectedNode =
false;
653 if (MouseSnapshotHandler.IsResizing && MouseSnapshotHandler.LastResizeAffectedNode.TryUnwrap(out var r))
656 CircuitBox.ResizeNode(node, dir, MouseSnapshotHandler.GetDragAmount(cursorPos));
659 if (CircuitBox.HeldComponent.TryUnwrap(out ItemPrefab? prefab))
661 CircuitBox.AddComponent(prefab, cursorPos);
665 if (MouseSnapshotHandler.IsDragging && PlayerInput.PrimaryMouseButtonReleased())
667 CircuitBox.MoveComponent(MouseSnapshotHandler.GetDragAmount(cursorPos), MouseSnapshotHandler.GetMoveAffectedComponents());
669 else if (!MouseSnapshotHandler.IsWiring)
671 selectedNode = TrySelectComponentsUnderCursor();
675 if (MouseSnapshotHandler.IsWiring && MouseSnapshotHandler.LastConnectorUnderCursor.TryUnwrap(out var one))
677 if (MouseSnapshotHandler.FindConnectorUnderCursor(cursorPos).TryUnwrap(out var two))
679 CircuitBox.AddWire(one, two);
683 if (MouseSnapshotHandler.LastWireUnderCursor.TryUnwrap(out var wire) && !MouseSnapshotHandler.IsDragging && !selectedNode)
685 CircuitBox.SelectWires(ImmutableArray.Create(wire), !PlayerInput.IsShiftDown());
687 else if (CircuitBox.Wires.Any(
static wire => wire.IsSelectedByMe))
689 CircuitBox.SelectWires(ImmutableArray<CircuitBoxWire>.Empty, !PlayerInput.IsShiftDown());
692 CircuitBox.HeldComponent = Option.None;
693 MouseSnapshotHandler.EndDragging();
696 if (MouseSnapshotHandler.GetLastComponentsUnderCursor().IsEmpty && MouseSnapshotHandler.LastConnectorUnderCursor.IsNone())
702 bool hitDeleteCombo = PlayerInput.KeyHit(Keys.Delete) || (PlayerInput.IsCtrlDown() && PlayerInput.KeyHit(Keys.D));
704 if (GUI.KeyboardDispatcher.Subscriber is
null && hitDeleteCombo)
706 CircuitBox.RemoveComponents(CircuitBox.Components.Where(
static node => node.IsSelectedByMe).ToArray());
707 CircuitBox.RemoveWires(CircuitBox.Wires.Where(
static wire => wire.IsSelectedByMe).ToImmutableArray());
708 CircuitBox.RemoveLabel(CircuitBox.Labels.Where(
static label => label.IsSelectedByMe).ToImmutableArray());
712 if (componentMenu is { } menu && toggleMenuButton is { } button)
714 button.Enabled = !Locked;
715 componentMenuOpenState = componentMenuOpen && !Locked ? Math.Min(componentMenuOpenState + deltaTime * 5.0f, 1.0f) : Math.Max(componentMenuOpenState - deltaTime * 5.0f, 0.0f);
717 menu.RectTransform.ScreenSpaceOffset = Vector2.Lerp(
new Vector2(0.0f, menu.Rect.Height - 10), Vector2.Zero, componentMenuOpenState).ToPoint();
718 button.RectTransform.AbsoluteOffset =
new Point(menu.Rect.X + ((menu.Rect.Width / 2) - (button.Rect.Width / 2)), menu.Rect.Y - button.Rect.Height);
721 if (selectedWireFrame is { } wireFrame)
723 wireFrame.Visible = !Locked;
726 camera.Position = Vector2.Clamp(camera.Position,
727 new Vector2(-CircuitBoxSizes.PlayableAreaSize / 2f),
728 new Vector2(CircuitBoxSizes.PlayableAreaSize / 2f));
731 public void SetMenuVisibility(
bool state)
732 => componentMenuOpen = state;
736 if (!PlayerInput.IsAltDown() && PlayerInput.PrimaryMouseButtonDown())
738 selection = Option.Some(
new RectangleF(GetCursorPosition(), Vector2.Zero));
741 if (!selection.TryUnwrap(out RectangleF rect)) {
return; }
743 if (!PlayerInput.PrimaryMouseButtonHeld())
745 selection = Option.None;
746 RectangleF selectionRect =
Submarine.AbsRectF(rect.Location, rect.Size);
748 float treshold = 12f / camera.Zoom;
749 if (selectionRect.Size.X < treshold || selectionRect.Size.Y < treshold) {
return; }
751 CircuitBox.SelectComponents(MouseSnapshotHandler.Nodes.Where(n => selectionRect.Intersects(n.Rect)).ToImmutableHashSet(), !PlayerInput.IsShiftDown());
755 RectangleF oldRect = rect;
756 rect.Size = camera.ScreenToWorld(PlayerInput.MousePosition) - rect.Location;
757 if (rect.Equals(oldRect)) {
return; }
759 selection = Option.Some(rect);
763 private bool TrySelectComponentsUnderCursor()
765 CircuitBoxNode? foundNode = GetTopmostNode(MouseSnapshotHandler.GetLastComponentsUnderCursor());
767 if (foundNode is CircuitBoxLabelNode && MouseSnapshotHandler.LastWireUnderCursor.IsSome())
772 CircuitBox.SelectComponents(foundNode is
null ? ImmutableArray<CircuitBoxNode>.Empty : ImmutableArray.Create(foundNode), !PlayerInput.IsShiftDown());
773 return foundNode is not
null;
776 private void OpenContextMenu()
778 var wireOption = MouseSnapshotHandler.FindWireUnderCursor(cursorPos);
779 var wireSelection = CircuitBox.Wires.Where(
static w => w.IsSelectedByMe).ToImmutableArray();
780 var nodeOption = GetTopmostNode(MouseSnapshotHandler.FindNodesUnderCursor(cursorPos));
781 var nodeSelection = CircuitBox.Components.Where(
static n => n.IsSelectedByMe).ToImmutableArray();
782 var labels = CircuitBox.Labels.Where(
static l => l.IsSelectedByMe).ToImmutableArray();
784 var option =
new ContextMenuOption(TextManager.Get(
"delete"), isEnabled: (wireOption.IsSome() || nodeOption is CircuitBoxComponent or CircuitBoxLabelNode) && !Locked, () =>
786 if (wireOption.TryUnwrap(out var wire))
788 CircuitBox.RemoveWires(wire.IsSelected ? wireSelection : ImmutableArray.Create(wire));
794 case CircuitBoxComponent node:
795 CircuitBox.RemoveComponents(node.IsSelected ? nodeSelection : ImmutableArray.Create(node));
797 case CircuitBoxLabelNode label:
798 CircuitBox.RemoveLabel(label.IsSelected ? labels : ImmutableArray.Create(label));
803 var editLabel =
new ContextMenuOption(TextManager.Get(
"circuitboxeditlabel"), isEnabled: nodeOption is CircuitBoxLabelNode && !Locked, () =>
805 if (circuitComponent is null) { return; }
806 if (nodeOption is not CircuitBoxLabelNode label) {
return; }
808 label.PromptEditText(circuitComponent);
811 var editConnections =
new ContextMenuOption(TextManager.Get(
"circuitboxrenameconnections"), isEnabled: nodeOption is CircuitBoxInputOutputNode && !Locked, () =>
813 if (circuitComponent is null) { return; }
814 if (nodeOption is not CircuitBoxInputOutputNode io) {
return; }
816 io.PromptEdit(circuitComponent);
819 var addLabelOption =
new ContextMenuOption(TextManager.Get(
"circuitboxaddlabel"), isEnabled: !Locked, () =>
821 CircuitBox.AddLabel(cursorPos);
824 ContextMenuOption[] allOptions = { addLabelOption, editLabel, editConnections, option };
827 if (nodeOption is CircuitBoxComponent comp)
829 GUIContextMenu.CreateContextMenu(PlayerInput.MousePosition, comp.Item.Name, comp.Item.Prefab.SignalComponentColor, allOptions);
834 if (wireOption.TryUnwrap(out var foundWire))
836 GUIContextMenu.CreateContextMenu(PlayerInput.MousePosition, foundWire.UsedItemPrefab.Name, foundWire.Color, allOptions);
840 GUIContextMenu.CreateContextMenu(allOptions);
843 public CircuitBoxNode? GetTopmostNode(ImmutableHashSet<CircuitBoxNode> nodes)
845 CircuitBoxNode? foundNode =
null;
847 var allNodes = MouseSnapshotHandler.Nodes.ToImmutableArray();
849 for (
int i = allNodes.Length - 1; i >= 0; i--)
851 CircuitBoxNode node = allNodes[i];
853 if (nodes.Contains(node))
863 public void AddToGUIUpdateList()
865 toggleMenuButton?.AddToGUIUpdateList();
866 selectedWireFrame?.AddToGUIUpdateList();
@ Character
Characters only