A game about forced loneliness, made by TACStudios
1using System.Collections.Generic;
2using System.IO;
3using System.Linq;
4using Unity.VisualScripting.Analytics;
5using UnityEditor;
6using UnityEngine;
7
8namespace Unity.VisualScripting
9{
10 [Widget(typeof(IUnitPort))]
11 public abstract class UnitPortWidget<TPort> : Widget<FlowCanvas, TPort>, IUnitPortWidget where TPort : class, IUnitPort
12 {
13 protected UnitPortWidget(FlowCanvas canvas, TPort port) : base(canvas, port) { }
14
15
16 #region Model
17
18 public TPort port => item;
19
20 public IUnit unit => port.unit;
21
22 // Usually very efficient, but cached because it's used so often
23 private IUnitWidget _unitWidget;
24
25 public IUnitWidget unitWidget
26 {
27 get
28 {
29 if (_unitWidget == null)
30 {
31 _unitWidget = canvas.Widget<IUnitWidget>(unit);
32 }
33
34 return _unitWidget;
35 }
36 }
37
38 IUnitPort IUnitPortWidget.port => port;
39
40 protected UnitPortDescription description { get; private set; }
41
42 public Metadata inspectorMetadata { get; private set; }
43
44 protected Inspector inspector { get; private set; }
45
46 public override Metadata FetchMetadata()
47 {
48 return description.getMetadata(unitWidget.metadata);
49 }
50
51 public virtual Metadata FetchInspectorMetadata()
52 {
53 return null;
54 }
55
56 protected override void CacheDescription()
57 {
58 description = port.Description<UnitPortDescription>();
59
60 labelContent.text = description.label;
61
62 Reposition();
63 }
64
65 protected override void CacheMetadata()
66 {
67 base.CacheMetadata();
68
69 inspectorMetadata = FetchInspectorMetadata();
70
71 if (inspectorMetadata != null)
72 {
73 inspector = unitWidget.GetPortInspector(port, inspectorMetadata);
74 }
75 else
76 {
77 inspector = null;
78 }
79 }
80
81 #endregion
82
83
84 #region Lifecycle
85
86 public override bool foregroundRequiresInput => showInspector;
87
88 public bool wouldDisconnect { get; private set; }
89
90 public bool willDisconnect => wouldDisconnect && isMouseOver;
91
92 protected virtual bool canStartConnection => true;
93
94 public override void HandleInput()
95 {
96 if (!canvas.isCreatingConnection)
97 {
98 if (e.IsMouseDown(MouseButton.Left))
99 {
100 if (canStartConnection)
101 {
102 StartConnection();
103 }
104
105 e.Use();
106 }
107 else if (e.IsMouseDown(MouseButton.Right))
108 {
109 wouldDisconnect = true;
110 }
111 else if (e.IsMouseUp(MouseButton.Right))
112 {
113 if (isMouseOver)
114 {
115 HotkeyUsageAnalytics.HotkeyUsed(HotkeyUsageAnalytics.Hotkey.RmbRemoveConnections);
116
117 RemoveConnections();
118 }
119
120 wouldDisconnect = false;
121 e.Use();
122 }
123 }
124 else
125 {
126 var source = canvas.connectionSource;
127 var isSource = source == port;
128
129 if (!isSource && e.IsMouseDown(MouseButton.Left))
130 {
131 var destination = port;
132 FinishConnection(source, destination);
133 e.Use();
134 }
135 else if (isSource && e.IsMouseUp(MouseButton.Left))
136 {
137 IUnitPort destination = null;
138
139 var hovered = canvas.hoveredWidget;
140
141 if (hovered is IUnitPortWidget)
142 {
143 destination = ((IUnitPortWidget)hovered).port;
144 }
145 else if (hovered is IUnitWidget)
146 {
147 destination = source.CompatiblePort(((IUnitWidget)hovered).unit);
148 }
149
150 if (destination != null)
151 {
152 if (destination != source)
153 {
154 FinishConnection(source, destination);
155 }
156 }
157 else
158 {
159 if (canvas.isMouseOverBackground)
160 {
161 canvas.NewUnitContextual();
162 }
163 else if (!canvas.isMouseOver)
164 {
165 canvas.CancelConnection();
166 }
167 }
168
169 e.Use();
170 }
171 else if (isSource && e.IsMouseDrag(MouseButton.Left))
172 {
173 e.Use();
174 }
175 else if (isSource && e.IsMouseDown(MouseButton.Right))
176 {
177 canvas.CancelConnection();
178 e.Use();
179 }
180 }
181 }
182
183 private void StartConnection()
184 {
185 canvas.connectionSource = port;
186 window.Focus();
187 }
188
189 private void RemoveConnections()
190 {
191 UndoUtility.RecordEditedObject("Disconnect Port");
192
193 foreach (var connectedPort in port.connectedPorts)
194 {
195 canvas.Widget(connectedPort.unit).Reposition();
196 }
197
198 unitWidget.Reposition();
199
200 port.Disconnect();
201
202 e.Use();
203
204 GUI.changed = true;
205 }
206
207 private void FinishConnection(IUnitPort source, IUnitPort destination)
208 {
209 if (source.CanValidlyConnectTo(destination))
210 {
211 UndoUtility.RecordEditedObject("Connect Nodes");
212 source.ValidlyConnectTo(destination);
213 canvas.connectionSource = null;
214 canvas.Widget(source.unit).Reposition();
215 canvas.Widget(destination.unit).Reposition();
216 GUI.changed = true;
217 }
218 else
219 {
220 Debug.LogWarningFormat
221 (
222 "Cannot connect this {0} to this {1}.\n",
223 source.GetType().HumanName().ToLower(),
224 destination.GetType().HumanName().ToLower()
225 );
226 }
227 }
228
229 #endregion
230
231
232 #region Contents
233
234 private readonly GUIContent labelContent = new GUIContent();
235
236 #endregion
237
238
239 #region Positioning
240
241 public override IEnumerable<IWidget> positionDependencies => ((IWidget)unitWidget).Yield();
242
243 public override IEnumerable<IWidget> positionDependers => port.connections.Select(connection => (IWidget)canvas.Widget(connection));
244
245 protected abstract Edge edge { get; }
246
247 public float y { get; set; }
248
249 private Rect _position;
250
251 public override Rect position
252 {
253 get { return _position; }
254 set { }
255 }
256
257 public Rect handlePosition { get; private set; }
258
259 public Rect labelPosition { get; private set; }
260
261 public Rect iconPosition { get; private set; }
262
263 public Rect inspectorPosition { get; private set; }
264
265 public Rect identifierPosition { get; private set; }
266
267 public Rect surroundPosition { get; private set; }
268
269 public override Rect hotArea
270 {
271 get
272 {
273 if (canvas.isCreatingConnection)
274 {
275 if (canvas.connectionSource == port || canvas.connectionSource.CanValidlyConnectTo(port))
276 {
277 return Styles.easierGrabOffset.Add(identifierPosition);
278 }
279
280 return Rect.zero;
281 }
282
283 return Styles.easierGrabOffset.Add(handlePosition);
284 }
285 }
286
287 public override void CachePosition()
288 {
289 var unitPosition = unitWidget.position;
290
291 var x = unitPosition.GetEdgeCenter(edge).x;
292 var outside = edge.Normal().x;
293 var inside = -outside;
294 var flip = inside < 0;
295
296 var handlePosition = new Rect
297 (
298 x + (Styles.handleSize.x + Styles.spaceBetweenEdgeAndHandle) * outside,
299 y + (EditorGUIUtility.singleLineHeight - Styles.handleSize.y) / 2,
300 Styles.handleSize.x,
301 Styles.handleSize.y
302 );
303
304 if (flip)
305 {
306 handlePosition.x -= handlePosition.width;
307 }
308
309 this.handlePosition = handlePosition;
310
311 _position = handlePosition;
312 identifierPosition = handlePosition;
313
314 x += Styles.spaceAfterEdge * inside;
315
316 if (showIcon)
317 {
318 var iconPosition = new Rect
319 (
320 x,
321 y - 1,
322 Styles.iconSize,
323 Styles.iconSize
324 ).PixelPerfect();
325
326 if (flip)
327 {
328 iconPosition.x -= iconPosition.width;
329 }
330
331 x += iconPosition.width * inside;
332
333 _position = _position.Encompass(iconPosition);
334 identifierPosition = identifierPosition.Encompass(iconPosition);
335
336 this.iconPosition = iconPosition;
337 }
338
339 if (showIcon && showLabel)
340 {
341 x += Styles.spaceBetweenIconAndLabel * inside;
342 }
343
344 if (showIcon && !showLabel && showInspector)
345 {
346 x += Styles.spaceBetweenIconAndInspector * inside;
347 }
348
349 if (showLabel)
350 {
351 var labelPosition = new Rect
352 (
353 x,
354 y,
355 GetLabelWidth(),
356 GetLabelHeight()
357 );
358
359 if (flip)
360 {
361 labelPosition.x -= labelPosition.width;
362 }
363
364 x += labelPosition.width * inside;
365
366 _position = _position.Encompass(labelPosition);
367 identifierPosition = identifierPosition.Encompass(labelPosition);
368
369 this.labelPosition = labelPosition;
370 }
371
372 if (showLabel && showInspector)
373 {
374 x += Styles.spaceBetweenLabelAndInspector * inside;
375 }
376
377 if (showInspector)
378 {
379 var inspectorPosition = new Rect
380 (
381 x,
382 y,
383 GetInspectorWidth(),
384 GetInspectorHeight()
385 );
386
387 if (flip)
388 {
389 inspectorPosition.x -= inspectorPosition.width;
390 }
391
392 x += inspectorPosition.width * inside;
393
394 _position = _position.Encompass(inspectorPosition);
395
396 this.inspectorPosition = inspectorPosition;
397 }
398
399 surroundPosition = Styles.surroundPadding.Add(identifierPosition);
400 }
401
402 public float GetInnerWidth()
403 {
404 var width = 0f;
405
406 if (showIcon)
407 {
408 width += Styles.iconSize;
409 }
410
411 if (showIcon && showLabel)
412 {
413 width += Styles.spaceBetweenIconAndLabel;
414 }
415
416 if (showIcon && !showLabel && showInspector)
417 {
418 width += Styles.spaceBetweenIconAndInspector;
419 }
420
421 if (showLabel)
422 {
423 width += GetLabelWidth();
424 }
425
426 if (showLabel && showInspector)
427 {
428 width += Styles.spaceBetweenLabelAndInspector;
429 }
430
431 if (showInspector)
432 {
433 width += GetInspectorWidth();
434 }
435
436 return width;
437 }
438
439 private float GetInspectorWidth()
440 {
441 var width = inspector.GetAdaptiveWidth();
442
443 width = Mathf.Min(width, Styles.maxInspectorWidth);
444
445 if (!showLabel)
446 {
447 width = Mathf.Max(width, Styles.labellessInspectorMinWidth);
448 }
449
450 return width;
451 }
452
453 private float GetLabelWidth()
454 {
455 return Mathf.Min(Styles.label.CalcSize(labelContent).x, Styles.maxLabelWidth);
456 }
457
458 public float GetHeight()
459 {
460 var height = EditorGUIUtility.singleLineHeight;
461
462 if (showIcon)
463 {
464 height = Mathf.Max(height, Styles.iconSize);
465 }
466
467 if (showLabel)
468 {
469 height = Mathf.Max(height, GetLabelHeight());
470 }
471
472 if (showInspector)
473 {
474 height = Mathf.Max(height, GetInspectorHeight());
475 }
476
477 return height;
478 }
479
480 private float GetLabelHeight()
481 {
482 return EditorGUIUtility.singleLineHeight;
483 }
484
485 private float GetInspectorHeight()
486 {
487 var width = GetInspectorWidth();
488
489 using (LudiqGUIUtility.currentInspectorWidth.Override(width))
490 {
491 return inspector.GetCachedHeight(width, GUIContent.none, null);
492 }
493 }
494
495 public override float zIndex
496 {
497 get { return unitWidget.zIndex + 0.5f; }
498 set { }
499 }
500
501 #endregion
502
503
504 #region Drawing
505
506 public override bool canClip => base.canClip && canvas.connectionSource != port;
507
508 protected virtual bool showInspector => false;
509
510 protected bool showIcon => description.icon != null;
511
512 protected bool showLabel => description.showLabel;
513
514 public virtual Color color => Color.white;
515
516 protected abstract Texture handleTextureConnected { get; }
517
518 protected abstract Texture handleTextureUnconnected { get; }
519
520 protected virtual bool colorIfActive => true;
521
522 protected override bool dim
523 {
524 get
525 {
526 var dim = BoltCore.Configuration.dimInactiveNodes && !unit.Analysis<UnitAnalysis>(context).isEntered;
527
528 if (unitWidget.isMouseOver || unitWidget.isSelected)
529 {
530 dim = false;
531 }
532
533 if (BoltCore.Configuration.dimIncompatibleNodes && canvas.isCreatingConnection)
534 {
535 dim = canvas.connectionSource != port && !canvas.connectionSource.CanValidlyConnectTo(port);
536 }
537
538 return dim;
539 }
540 }
541
542 public override void DrawBackground() { }
543
544 public override void DrawForeground()
545 {
546 if (BoltCore.Configuration.developerMode && BoltCore.Configuration.debug)
547 {
548 EditorGUI.DrawRect(clippingPosition, new Color(0, 0, 0, 0.1f));
549 }
550
551 BeginDim();
552
553 DrawHandle();
554
555 if (showIcon)
556 {
557 DrawIcon();
558 }
559
560 if (showLabel)
561 {
562 DrawLabel();
563 }
564
565 if (showInspector && graph.zoom >= FlowCanvas.inspectorZoomThreshold)
566 {
567 DrawInspector();
568 }
569
570 EndDim();
571 }
572
573 public override void DrawOverlay()
574 {
575 base.DrawOverlay();
576
577 var surroundFromPort = canvas.isCreatingConnection &&
578 isMouseOver &&
579 canvas.connectionSource.CanValidlyConnectTo(port);
580
581 var surroundFromUnit = canvas.isCreatingConnection &&
582 unitWidget.isMouseOver &&
583 canvas.connectionSource.CompatiblePort(unit) == port;
584
585 if (surroundFromPort || surroundFromUnit)
586 {
587 DrawSurround();
588 }
589
590 if (canvas.connectionSource == port)
591 {
592 DrawConnectionSource();
593 }
594 }
595
596 private void GetConnectionsNoAlloc(HashSet<IUnitConnection> connections)
597 {
598 connections.Clear();
599
600 var graph = unit.graph;
601
602 // Unit might have been removed from graph, but still drawn this frame.
603 if (graph == null)
604 {
605 return;
606 }
607
608 var controlInput = port as ControlInput;
609 var controlOutput = port as ControlOutput;
610 var valueInput = port as ValueInput;
611 var valueOutput = port as ValueOutput;
612 var input = port as IUnitInputPort;
613 var output = port as IUnitOutputPort;
614
615 if (controlInput != null)
616 {
617 foreach (var connection in graph.controlConnections.WithDestinationNoAlloc(controlInput))
618 {
619 connections.Add(connection);
620 }
621 }
622
623 if (controlOutput != null)
624 {
625 foreach (var connection in graph.controlConnections.WithSourceNoAlloc(controlOutput))
626 {
627 connections.Add(connection);
628 }
629 }
630
631 if (valueInput != null)
632 {
633 foreach (var connection in graph.valueConnections.WithDestinationNoAlloc(valueInput))
634 {
635 connections.Add(connection);
636 }
637 }
638
639 if (valueOutput != null)
640 {
641 foreach (var connection in graph.valueConnections.WithSourceNoAlloc(valueOutput))
642 {
643 connections.Add(connection);
644 }
645 }
646
647 if (input != null)
648 {
649 foreach (var connection in graph.invalidConnections.WithDestinationNoAlloc(input))
650 {
651 connections.Add(connection);
652 }
653 }
654
655 if (output != null)
656 {
657 foreach (var connection in graph.invalidConnections.WithSourceNoAlloc(output))
658 {
659 connections.Add(connection);
660 }
661 }
662 }
663
664 private void DrawHandle()
665 {
666 // Trying to be very speed / memory efficient in this method
667
668 if (!e.IsRepaint)
669 {
670 return;
671 }
672
673 var color = Color.white;
674
675 var highlight = false;
676
677 var invalid = false;
678
679 var willDisconnect = false;
680
681 var connections = HashSetPool<IUnitConnection>.New();
682
683 GetConnectionsNoAlloc(connections);
684
685 var isConnected = connections.Count > 0;
686
687 if (isConnected)
688 {
689 foreach (var connection in connections)
690 {
691 if (connection is InvalidConnection)
692 {
693 invalid = true;
694 }
695
696 var sourceWidget = canvas.Widget<IUnitPortWidget>(connection.source);
697 var destinationWidget = canvas.Widget<IUnitPortWidget>(connection.destination);
698
699 if (sourceWidget.isMouseOver || destinationWidget.isMouseOver)
700 {
701 highlight = true;
702 }
703
704 if (sourceWidget.willDisconnect || destinationWidget.willDisconnect)
705 {
706 willDisconnect = true;
707 }
708 }
709 }
710
711 if (isMouseOver)
712 {
713 highlight = true;
714 }
715
716 if (willDisconnect)
717 {
718 color = UnitConnectionStyles.disconnectColor;
719 }
720 else if (highlight)
721 {
722 color = UnitConnectionStyles.highlightColor;
723 }
724 else if (invalid)
725 {
726 color = UnitConnectionStyles.invalidColor;
727 }
728 else if (canvas.isCreatingConnection && (canvas.connectionSource == port || canvas.connectionSource.CanValidlyConnectTo(port)))
729 {
730 color = this.color;
731 }
732 else if (isConnected)
733 {
734 Color? resolvedColor = null;
735
736 foreach (var connection in connections)
737 {
738 var connectionColor = canvas.Widget<IUnitConnectionWidget>(connection).color;
739
740 if (resolvedColor == null)
741 {
742 resolvedColor = connectionColor;
743 }
744 else if (resolvedColor != connectionColor)
745 {
746 resolvedColor = this.color;
747
748 break;
749 }
750 }
751
752 color = resolvedColor.Value;
753 }
754
755 if (colorIfActive)
756 {
757 foreach (var connection in connections)
758 {
759 var connectionEditorData = reference.GetElementDebugData<IUnitConnectionDebugData>(connection);
760
761 if (EditorApplication.isPaused)
762 {
763 if (EditorTimeBinding.frame == connectionEditorData.lastInvokeFrame)
764 {
765 color = UnitConnectionStyles.activeColor;
766
767 break;
768 }
769 }
770 else
771 {
772 color = Color.Lerp(UnitConnectionStyles.activeColor, color, (EditorTimeBinding.time - connectionEditorData.lastInvokeTime) / UnitWidget<IUnit>.Styles.invokeFadeDuration);
773 }
774 }
775 }
776
777 var handlePosition = this.handlePosition;
778
779 if (highlight)
780 {
781 var widthExpansion = handlePosition.width * (Styles.highlightScaling - 1);
782 var heightExpansion = handlePosition.height * (Styles.highlightScaling - 1);
783 handlePosition.width += widthExpansion;
784 handlePosition.height += heightExpansion;
785 handlePosition.x -= widthExpansion / 2;
786 handlePosition.y -= heightExpansion / 2;
787 }
788
789 if (highlight ||
790 isConnected ||
791 canvas.connectionSource == port ||
792 canvas.isCreatingConnection && canvas.connectionSource.CanValidlyConnectTo(port))
793 {
794 using (LudiqGUI.color.Override(color.WithAlphaMultiplied(LudiqGUI.color.value.a * 0.85f))) // Full color is a bit hard on the eyes
795 {
796 if (handleTextureConnected != null)
797 {
798 GUI.DrawTexture(handlePosition, handleTextureConnected);
799 }
800 }
801 }
802 else
803 {
804 if (handleTextureUnconnected != null)
805 {
806 GUI.DrawTexture(handlePosition, handleTextureUnconnected);
807 }
808 }
809
810 HashSetPool<IUnitConnection>.Free(connections);
811 }
812
813 private void DrawIcon()
814 {
815 if (description != null && description.icon[Styles.iconSize])
816 {
817 GUI.DrawTexture(iconPosition, description.icon?[Styles.iconSize]);
818 }
819 }
820
821 private void DrawLabel()
822 {
823 GUI.Label(labelPosition, description.label, Styles.label);
824 }
825
826 private void DrawInspector()
827 {
828 EditorGUI.BeginChangeCheck();
829
830 using (LudiqGUIUtility.currentInspectorWidth.Override(inspectorPosition.width))
831 using (Inspector.adaptiveWidth.Override(true))
832 {
833 inspector.Draw(inspectorPosition, GUIContent.none);
834 }
835
836 if (EditorGUI.EndChangeCheck())
837 {
838 unitWidget.Reposition();
839 }
840 }
841
842 private void DrawConnectionSource()
843 {
844 var start = handlePosition.GetEdgeCenter(edge);
845
846 if (window.IsFocused())
847 {
848 canvas.connectionEnd = mousePosition;
849 }
850
851 GraphGUI.DrawConnection
852 (
853 color,
854 start,
855 canvas.connectionEnd,
856 edge,
857 null,
858 handleTextureConnected,
859 Styles.handleSize,
860 UnitConnectionStyles.relativeBend,
861 UnitConnectionStyles.minBend
862 );
863 }
864
865 private void DrawSurround()
866 {
867 if (e.controlType == EventType.Repaint)
868 {
869 Styles.surround.Draw(surroundPosition, false, false, false, false);
870 }
871 }
872
873 #endregion
874
875
876 public static class Styles
877 {
878 private static byte[] t;
879 private static Texture2D tx;
880 static Styles()
881 {
882 label = new GUIStyle(EditorStyles.label);
883 label.wordWrap = false;
884 label.imagePosition = ImagePosition.TextOnly;
885 label.padding = new RectOffset(0, 0, 0, 0);
886
887 TextureResolution[] textureResolution = { 2 };
888
889 surround = new GUIStyle
890 {
891 normal =
892 {
893 background = BoltCore.Resources.LoadTexture($"Surround.png", textureResolution, CreateTextureOptions.Scalable).Single()
894 }
895 };
896 }
897
898 public const float highlightScaling = 1f;
899
900 public static readonly Vector2 handleSize = new Vector2(9, 12);
901
902 public static readonly float spaceBetweenEdgeAndHandle = 5;
903
904 public static readonly float spaceAfterEdge = 5;
905
906 public static readonly float spaceBetweenIconAndLabel = 5;
907
908 public static readonly float spaceBetweenIconAndInspector = 5;
909
910 public static readonly float spaceBetweenLabelAndInspector = 5;
911
912 public static readonly float labellessInspectorMinWidth = 75;
913
914 public static readonly float maxInspectorWidth = 200;
915
916 public static readonly float maxLabelWidth = 150;
917
918 public static readonly int iconSize = IconSize.Small;
919
920 public static readonly GUIStyle label;
921
922 public static readonly GUIStyle surround;
923
924 public static readonly RectOffset easierGrabOffset = new RectOffset(5, 5, 4, 4);
925
926 public static readonly RectOffset surroundPadding = new RectOffset(3, 3, 2, 2);
927 }
928 }
929}