A game about forced loneliness, made by TACStudios
1using System;
2using System.Collections.Generic;
3using System.Linq;
4using UnityEditor;
5using UnityEngine;
6
7namespace Unity.VisualScripting
8{
9 [Widget(typeof(IUnit))]
10 public class UnitWidget<TUnit> : NodeWidget<FlowCanvas, TUnit>, IUnitWidget where TUnit : class, IUnit
11 {
12 public UnitWidget(FlowCanvas canvas, TUnit unit) : base(canvas, unit)
13 {
14 unit.onPortsChanged += CacheDefinition;
15 unit.onPortsChanged += SubWidgetsChanged;
16 }
17
18 public override void Dispose()
19 {
20 base.Dispose();
21
22 unit.onPortsChanged -= CacheDefinition;
23 unit.onPortsChanged -= SubWidgetsChanged;
24 }
25
26 public override IEnumerable<IWidget> subWidgets => unit.ports.Select(port => canvas.Widget(port));
27
28
29 #region Model
30
31 protected TUnit unit => element;
32
33 IUnit IUnitWidget.unit => unit;
34
35 protected IUnitDebugData unitDebugData => GetDebugData<IUnitDebugData>();
36
37 private UnitDescription description;
38
39 private UnitAnalysis analysis => unit.Analysis<UnitAnalysis>(context);
40
41 protected readonly List<IUnitPortWidget> ports = new List<IUnitPortWidget>();
42
43 protected readonly List<IUnitPortWidget> inputs = new List<IUnitPortWidget>();
44
45 protected readonly List<IUnitPortWidget> outputs = new List<IUnitPortWidget>();
46
47 private readonly List<string> settingNames = new List<string>();
48
49 protected IEnumerable<Metadata> settings
50 {
51 get
52 {
53 foreach (var settingName in settingNames)
54 {
55 yield return metadata[settingName];
56 }
57 }
58 }
59
60 protected override void CacheItemFirstTime()
61 {
62 base.CacheItemFirstTime();
63 CacheDefinition();
64 }
65
66 protected virtual void CacheDefinition()
67 {
68 inputs.Clear();
69 outputs.Clear();
70 ports.Clear();
71 inputs.AddRange(unit.inputs.Select(port => canvas.Widget<IUnitPortWidget>(port)));
72 outputs.AddRange(unit.outputs.Select(port => canvas.Widget<IUnitPortWidget>(port)));
73 ports.AddRange(inputs);
74 ports.AddRange(outputs);
75
76 Reposition();
77 }
78
79 protected override void CacheDescription()
80 {
81 description = unit.Description<UnitDescription>();
82
83 titleContent.text = description.shortTitle;
84 titleContent.tooltip = description.summary;
85 surtitleContent.text = description.surtitle;
86 subtitleContent.text = description.subtitle;
87
88 Reposition();
89 }
90
91 protected override void CacheMetadata()
92 {
93 settingNames.Clear();
94
95 settingNames.AddRange(metadata.valueType
96 .GetMembers()
97 .Where(mi => mi.HasAttribute<UnitHeaderInspectableAttribute>())
98 .OrderBy(mi => mi.GetAttributes<Attribute>().OfType<IInspectableAttribute>().FirstOrDefault()?.order ?? int.MaxValue)
99 .ThenBy(mi => mi.MetadataToken)
100 .Select(mi => mi.Name));
101
102 lock (settingLabelsContents)
103 {
104 settingLabelsContents.Clear();
105
106 foreach (var setting in settings)
107 {
108 var settingLabel = setting.GetAttribute<UnitHeaderInspectableAttribute>().label;
109
110 GUIContent settingContent;
111
112 if (string.IsNullOrEmpty(settingLabel))
113 {
114 settingContent = null;
115 }
116 else
117 {
118 settingContent = new GUIContent(settingLabel);
119 }
120
121 settingLabelsContents.Add(setting, settingContent);
122 }
123 }
124
125 Reposition();
126 }
127
128 public virtual Inspector GetPortInspector(IUnitPort port, Metadata metadata)
129 {
130 return metadata.Inspector();
131 }
132
133 #endregion
134
135
136 #region Lifecycle
137
138 public override bool foregroundRequiresInput => showSettings || unit.valueInputs.Any(vip => vip.hasDefaultValue);
139
140 public override void HandleInput()
141 {
142 if (canvas.isCreatingConnection)
143 {
144 if (e.IsMouseDown(MouseButton.Left))
145 {
146 var source = canvas.connectionSource;
147 var destination = source.CompatiblePort(unit);
148
149 if (destination != null)
150 {
151 UndoUtility.RecordEditedObject("Connect Nodes");
152 source.ValidlyConnectTo(destination);
153 canvas.connectionSource = null;
154 canvas.Widget(source.unit).Reposition();
155 canvas.Widget(destination.unit).Reposition();
156 GUI.changed = true;
157 }
158
159 e.Use();
160 }
161 else if (e.IsMouseDown(MouseButton.Right))
162 {
163 canvas.CancelConnection();
164 e.Use();
165 }
166 }
167
168 base.HandleInput();
169 }
170
171 #endregion
172
173
174 #region Contents
175
176 protected readonly GUIContent titleContent = new GUIContent();
177
178 protected readonly GUIContent surtitleContent = new GUIContent();
179
180 protected readonly GUIContent subtitleContent = new GUIContent();
181
182 protected readonly Dictionary<Metadata, GUIContent> settingLabelsContents = new Dictionary<Metadata, GUIContent>();
183
184 #endregion
185
186
187 #region Positioning
188
189 protected override bool snapToGrid => BoltCore.Configuration.snapToGrid;
190
191 public override IEnumerable<IWidget> positionDependers => ports.Cast<IWidget>();
192
193 protected Rect _position;
194
195 public override Rect position
196 {
197 get { return _position; }
198 set { unit.position = value.position; }
199 }
200
201 public Rect titlePosition { get; private set; }
202
203 public Rect surtitlePosition { get; private set; }
204
205 public Rect subtitlePosition { get; private set; }
206
207 public Rect iconPosition { get; private set; }
208
209 public List<Rect> iconsPositions { get; private set; } = new List<Rect>();
210
211 public Dictionary<Metadata, Rect> settingsPositions { get; } = new Dictionary<Metadata, Rect>();
212
213 public Rect headerAddonPosition { get; private set; }
214
215 public Rect portsBackgroundPosition { get; private set; }
216
217 public override void CachePosition()
218 {
219 // Width
220
221 var inputsWidth = 0f;
222 var outputsWidth = 0f;
223
224 foreach (var input in inputs)
225 {
226 inputsWidth = Mathf.Max(inputsWidth, input.GetInnerWidth());
227 }
228
229 foreach (var output in outputs)
230 {
231 outputsWidth = Mathf.Max(outputsWidth, output.GetInnerWidth());
232 }
233
234 var portsWidth = 0f;
235
236 portsWidth += inputsWidth;
237 portsWidth += Styles.spaceBetweenInputsAndOutputs;
238 portsWidth += outputsWidth;
239
240 settingsPositions.Clear();
241
242 var settingsWidth = 0f;
243
244 if (showSettings)
245 {
246 foreach (var setting in settings)
247 {
248 var settingWidth = 0f;
249
250 var settingLabelContent = settingLabelsContents[setting];
251
252 if (settingLabelContent != null)
253 {
254 settingWidth += Styles.settingLabel.CalcSize(settingLabelContent).x;
255 }
256
257 settingWidth += setting.Inspector().GetAdaptiveWidth();
258
259 settingWidth = Mathf.Min(settingWidth, Styles.maxSettingsWidth);
260
261 settingsPositions.Add(setting, new Rect(0, 0, settingWidth, 0));
262
263 settingsWidth = Mathf.Max(settingsWidth, settingWidth);
264 }
265 }
266
267 var headerAddonWidth = 0f;
268
269 if (showHeaderAddon)
270 {
271 headerAddonWidth = GetHeaderAddonWidth();
272 }
273
274 var titleWidth = Styles.title.CalcSize(titleContent).x;
275
276 var headerTextWidth = titleWidth;
277
278 var surtitleWidth = 0f;
279
280 if (showSurtitle)
281 {
282 surtitleWidth = Styles.surtitle.CalcSize(surtitleContent).x;
283 headerTextWidth = Mathf.Max(headerTextWidth, surtitleWidth);
284 }
285
286 var subtitleWidth = 0f;
287
288 if (showSubtitle)
289 {
290 subtitleWidth = Styles.subtitle.CalcSize(subtitleContent).x;
291 headerTextWidth = Mathf.Max(headerTextWidth, subtitleWidth);
292 }
293
294 var iconsWidth = 0f;
295
296 if (showIcons)
297 {
298 var iconsColumns = Mathf.Ceil((float)description.icons.Length / Styles.iconsPerColumn);
299 iconsWidth = iconsColumns * Styles.iconsSize + ((iconsColumns - 1) * Styles.iconsSpacing);
300 }
301
302 var headerWidth = Mathf.Max(headerTextWidth + iconsWidth, Mathf.Max(settingsWidth, headerAddonWidth)) + Styles.iconSize + Styles.spaceAfterIcon;
303
304 var innerWidth = Mathf.Max(portsWidth, headerWidth);
305
306 var edgeWidth = InnerToEdgePosition(new Rect(0, 0, innerWidth, 0)).width;
307
308 // Height & Positioning
309
310 var edgeOrigin = unit.position;
311 var edgeX = edgeOrigin.x;
312 var edgeY = edgeOrigin.y;
313 var innerOrigin = EdgeToInnerPosition(new Rect(edgeOrigin, Vector2.zero)).position;
314 var innerX = innerOrigin.x;
315 var innerY = innerOrigin.y;
316
317 iconPosition = new Rect
318 (
319 innerX,
320 innerY,
321 Styles.iconSize,
322 Styles.iconSize
323 );
324
325 var headerTextX = iconPosition.xMax + Styles.spaceAfterIcon;
326
327 var y = innerY;
328
329 var headerHeight = 0f;
330
331 var surtitleHeight = 0f;
332
333 if (showSurtitle)
334 {
335 surtitleHeight = Styles.surtitle.CalcHeight(surtitleContent, headerTextWidth);
336
337 surtitlePosition = new Rect
338 (
339 headerTextX,
340 y,
341 headerTextWidth,
342 surtitleHeight
343 );
344
345 headerHeight += surtitleHeight;
346 y += surtitleHeight;
347
348 headerHeight += Styles.spaceAfterSurtitle;
349 y += Styles.spaceAfterSurtitle;
350 }
351
352 var titleHeight = 0f;
353
354 if (showTitle)
355 {
356 titleHeight = Styles.title.CalcHeight(titleContent, headerTextWidth);
357
358 titlePosition = new Rect
359 (
360 headerTextX,
361 y,
362 headerTextWidth,
363 titleHeight
364 );
365
366 headerHeight += titleHeight;
367 y += titleHeight;
368 }
369
370 var subtitleHeight = 0f;
371
372 if (showSubtitle)
373 {
374 headerHeight += Styles.spaceBeforeSubtitle;
375 y += Styles.spaceBeforeSubtitle;
376
377 subtitleHeight = Styles.subtitle.CalcHeight(subtitleContent, headerTextWidth);
378
379 subtitlePosition = new Rect
380 (
381 headerTextX,
382 y,
383 headerTextWidth,
384 subtitleHeight
385 );
386
387 headerHeight += subtitleHeight;
388 y += subtitleHeight;
389 }
390
391 iconsPositions.Clear();
392
393 if (showIcons)
394 {
395 var iconRow = 0;
396 var iconCol = 0;
397
398 for (int i = 0; i < description.icons.Length; i++)
399 {
400 var iconPosition = new Rect
401 (
402 innerX + innerWidth - ((iconCol + 1) * Styles.iconsSize) - ((iconCol) * Styles.iconsSpacing),
403 innerY + (iconRow * (Styles.iconsSize + Styles.iconsSpacing)),
404 Styles.iconsSize,
405 Styles.iconsSize
406 );
407
408 iconsPositions.Add(iconPosition);
409
410 iconRow++;
411
412 if (iconRow % Styles.iconsPerColumn == 0)
413 {
414 iconCol++;
415 iconRow = 0;
416 }
417 }
418 }
419
420 var settingsHeight = 0f;
421
422 if (showSettings)
423 {
424 headerHeight += Styles.spaceBeforeSettings;
425
426 foreach (var setting in settings)
427 {
428 var settingWidth = settingsPositions[setting].width;
429
430 using (LudiqGUIUtility.currentInspectorWidth.Override(settingWidth))
431 {
432 var settingHeight = LudiqGUI.GetInspectorHeight(null, setting, settingWidth, settingLabelsContents[setting] ?? GUIContent.none);
433
434 var settingPosition = new Rect
435 (
436 headerTextX,
437 y,
438 settingWidth,
439 settingHeight
440 );
441
442 settingsPositions[setting] = settingPosition;
443
444 settingsHeight += settingHeight;
445 y += settingHeight;
446
447 settingsHeight += Styles.spaceBetweenSettings;
448 y += Styles.spaceBetweenSettings;
449 }
450 }
451
452 settingsHeight -= Styles.spaceBetweenSettings;
453 y -= Styles.spaceBetweenSettings;
454
455 headerHeight += settingsHeight;
456
457 headerHeight += Styles.spaceAfterSettings;
458 y += Styles.spaceAfterSettings;
459 }
460
461 if (showHeaderAddon)
462 {
463 var headerAddonHeight = GetHeaderAddonHeight(headerAddonWidth);
464
465 headerAddonPosition = new Rect
466 (
467 headerTextX,
468 y,
469 headerAddonWidth,
470 headerAddonHeight
471 );
472
473 headerHeight += headerAddonHeight;
474 y += headerAddonHeight;
475 }
476
477 if (headerHeight < Styles.iconSize)
478 {
479 var difference = Styles.iconSize - headerHeight;
480 var centeringOffset = difference / 2;
481
482 if (showTitle)
483 {
484 var _titlePosition = titlePosition;
485 _titlePosition.y += centeringOffset;
486 titlePosition = _titlePosition;
487 }
488
489 if (showSubtitle)
490 {
491 var _subtitlePosition = subtitlePosition;
492 _subtitlePosition.y += centeringOffset;
493 subtitlePosition = _subtitlePosition;
494 }
495
496 if (showSettings)
497 {
498 foreach (var setting in settings)
499 {
500 var _settingPosition = settingsPositions[setting];
501 _settingPosition.y += centeringOffset;
502 settingsPositions[setting] = _settingPosition;
503 }
504 }
505
506 if (showHeaderAddon)
507 {
508 var _headerAddonPosition = headerAddonPosition;
509 _headerAddonPosition.y += centeringOffset;
510 headerAddonPosition = _headerAddonPosition;
511 }
512
513 headerHeight = Styles.iconSize;
514 }
515
516 y = innerY + headerHeight;
517
518 var innerHeight = 0f;
519
520 innerHeight += headerHeight;
521
522 if (showPorts)
523 {
524 innerHeight += Styles.spaceBeforePorts;
525 y += Styles.spaceBeforePorts;
526
527 var portsBackgroundY = y;
528 var portsBackgroundHeight = 0f;
529
530 portsBackgroundHeight += Styles.portsBackground.padding.top;
531 innerHeight += Styles.portsBackground.padding.top;
532 y += Styles.portsBackground.padding.top;
533
534 var portStartY = y;
535
536 var inputsHeight = 0f;
537 var outputsHeight = 0f;
538
539 foreach (var input in inputs)
540 {
541 input.y = y;
542
543 var inputHeight = input.GetHeight();
544
545 inputsHeight += inputHeight;
546 y += inputHeight;
547
548 inputsHeight += Styles.spaceBetweenPorts;
549 y += Styles.spaceBetweenPorts;
550 }
551
552 if (inputs.Count > 0)
553 {
554 inputsHeight -= Styles.spaceBetweenPorts;
555 y -= Styles.spaceBetweenPorts;
556 }
557
558 y = portStartY;
559
560 foreach (var output in outputs)
561 {
562 output.y = y;
563
564 var outputHeight = output.GetHeight();
565
566 outputsHeight += outputHeight;
567 y += outputHeight;
568
569 outputsHeight += Styles.spaceBetweenPorts;
570 y += Styles.spaceBetweenPorts;
571 }
572
573 if (outputs.Count > 0)
574 {
575 outputsHeight -= Styles.spaceBetweenPorts;
576 y -= Styles.spaceBetweenPorts;
577 }
578
579 var portsHeight = Math.Max(inputsHeight, outputsHeight);
580
581 portsBackgroundHeight += portsHeight;
582 innerHeight += portsHeight;
583 y = portStartY + portsHeight;
584
585 portsBackgroundHeight += Styles.portsBackground.padding.bottom;
586 innerHeight += Styles.portsBackground.padding.bottom;
587 y += Styles.portsBackground.padding.bottom;
588
589 portsBackgroundPosition = new Rect
590 (
591 edgeX,
592 portsBackgroundY,
593 edgeWidth,
594 portsBackgroundHeight
595 );
596 }
597
598 var edgeHeight = InnerToEdgePosition(new Rect(0, 0, 0, innerHeight)).height;
599
600 _position = new Rect
601 (
602 edgeX,
603 edgeY,
604 edgeWidth,
605 edgeHeight
606 );
607 }
608
609 protected virtual float GetHeaderAddonWidth()
610 {
611 return 0;
612 }
613
614 protected virtual float GetHeaderAddonHeight(float width)
615 {
616 return 0;
617 }
618
619 #endregion
620
621
622 #region Drawing
623
624 protected virtual NodeColorMix baseColor => NodeColor.Gray;
625
626 protected override NodeColorMix color
627 {
628 get
629 {
630 if (unitDebugData.runtimeException != null)
631 {
632 return NodeColor.Red;
633 }
634
635 var color = baseColor;
636
637 if (analysis.warnings.Count > 0)
638 {
639 var mostSevereWarning = Warning.MostSevereLevel(analysis.warnings);
640
641 switch (mostSevereWarning)
642 {
643 case WarningLevel.Error:
644 color = NodeColor.Red;
645
646 break;
647
648 case WarningLevel.Severe:
649 color = NodeColor.Orange;
650
651 break;
652
653 case WarningLevel.Caution:
654 color = NodeColor.Yellow;
655
656 break;
657 }
658 }
659
660 if (EditorApplication.isPaused)
661 {
662 if (EditorTimeBinding.frame == unitDebugData.lastInvokeFrame)
663 {
664 return NodeColor.Blue;
665 }
666 }
667 else
668 {
669 var mix = color;
670 mix.blue = Mathf.Lerp(1, 0, (EditorTimeBinding.time - unitDebugData.lastInvokeTime) / Styles.invokeFadeDuration);
671
672 return mix;
673 }
674
675 return color;
676 }
677 }
678
679 protected override NodeShape shape => NodeShape.Square;
680
681 protected virtual bool showTitle => !string.IsNullOrEmpty(description.shortTitle);
682
683 protected virtual bool showSurtitle => !string.IsNullOrEmpty(description.surtitle);
684
685 protected virtual bool showSubtitle => !string.IsNullOrEmpty(description.subtitle);
686
687 protected virtual bool showIcons => description.icons.Length > 0;
688
689 protected virtual bool showSettings => settingNames.Count > 0;
690
691 protected virtual bool showHeaderAddon => false;
692
693 protected virtual bool showPorts => ports.Count > 0;
694
695 protected override bool dim
696 {
697 get
698 {
699 var dim = BoltCore.Configuration.dimInactiveNodes && !analysis.isEntered;
700
701 if (isMouseOver || isSelected)
702 {
703 dim = false;
704 }
705
706 if (BoltCore.Configuration.dimIncompatibleNodes && canvas.isCreatingConnection)
707 {
708 dim = !unit.ports.Any(p => canvas.connectionSource == p || canvas.connectionSource.CanValidlyConnectTo(p));
709 }
710
711 return dim;
712 }
713 }
714
715 public override void DrawForeground()
716 {
717 BeginDim();
718
719 base.DrawForeground();
720
721 DrawIcon();
722
723 if (showSurtitle)
724 {
725 DrawSurtitle();
726 }
727
728 if (showTitle)
729 {
730 DrawTitle();
731 }
732
733 if (showSubtitle)
734 {
735 DrawSubtitle();
736 }
737
738 if (showIcons)
739 {
740 DrawIcons();
741 }
742
743 if (showSettings)
744 {
745 DrawSettings();
746 }
747
748 if (showHeaderAddon)
749 {
750 DrawHeaderAddon();
751 }
752
753 if (showPorts)
754 {
755 DrawPortsBackground();
756 }
757
758 EndDim();
759 }
760
761 protected void DrawIcon()
762 {
763 var icon = description.icon ?? BoltFlow.Icons.unit;
764
765 if (icon != null && icon[(int)iconPosition.width])
766 {
767 GUI.DrawTexture(iconPosition, icon[(int)iconPosition.width]);
768 }
769 }
770
771 protected void DrawTitle()
772 {
773 GUI.Label(titlePosition, titleContent, invertForeground ? Styles.titleInverted : Styles.title);
774 }
775
776 protected void DrawSurtitle()
777 {
778 GUI.Label(surtitlePosition, surtitleContent, invertForeground ? Styles.surtitleInverted : Styles.surtitle);
779 }
780
781 protected void DrawSubtitle()
782 {
783 GUI.Label(subtitlePosition, subtitleContent, invertForeground ? Styles.subtitleInverted : Styles.subtitle);
784 }
785
786 protected void DrawIcons()
787 {
788 for (int i = 0; i < description.icons.Length; i++)
789 {
790 var icon = description.icons[i];
791 var position = iconsPositions[i];
792
793 GUI.DrawTexture(position, icon?[(int)position.width]);
794 }
795 }
796
797 private void DrawSettings()
798 {
799 if (graph.zoom < FlowCanvas.inspectorZoomThreshold)
800 {
801 return;
802 }
803
804 EditorGUI.BeginDisabledGroup(!e.IsRepaint && isMouseThrough && !isMouseOver);
805
806 EditorGUI.BeginChangeCheck();
807
808 foreach (var setting in settings)
809 {
810 DrawSetting(setting);
811 }
812
813 if (EditorGUI.EndChangeCheck())
814 {
815 unit.Define();
816 Reposition();
817 }
818
819 EditorGUI.EndDisabledGroup();
820 }
821
822 protected void DrawSetting(Metadata setting)
823 {
824 var settingPosition = settingsPositions[setting];
825
826 using (LudiqGUIUtility.currentInspectorWidth.Override(settingPosition.width))
827 using (Inspector.expandTooltip.Override(false))
828 {
829 var label = settingLabelsContents[setting];
830
831 if (label == null)
832 {
833 LudiqGUI.Inspector(setting, settingPosition, GUIContent.none);
834 }
835 else
836 {
837 using (Inspector.defaultLabelStyle.Override(Styles.settingLabel))
838 using (LudiqGUIUtility.labelWidth.Override(Styles.settingLabel.CalcSize(label).x))
839 {
840 LudiqGUI.Inspector(setting, settingPosition, label);
841 }
842 }
843 }
844 }
845
846 protected virtual void DrawHeaderAddon() { }
847
848 protected void DrawPortsBackground()
849 {
850 if (canvas.showRelations)
851 {
852 foreach (var relation in unit.relations)
853 {
854 var start = ports.Single(pw => pw.port == relation.source).handlePosition.center;
855 var end = ports.Single(pw => pw.port == relation.destination).handlePosition.center;
856
857 var startTangent = start;
858 var endTangent = end;
859
860 if (relation.source is IUnitInputPort &&
861 relation.destination is IUnitInputPort)
862 {
863 //startTangent -= new Vector2(20, 0);
864 endTangent -= new Vector2(32, 0);
865 }
866 else
867 {
868 startTangent += new Vector2(innerPosition.width / 2, 0);
869 endTangent += new Vector2(-innerPosition.width / 2, 0);
870 }
871
872 Handles.DrawBezier
873 (
874 start,
875 end,
876 startTangent,
877 endTangent,
878 new Color(0.136f, 0.136f, 0.136f, 1.0f),
879 null,
880 3
881 );
882 }
883 }
884 else
885 {
886 if (e.IsRepaint)
887 {
888 Styles.portsBackground.Draw(portsBackgroundPosition, false, false, false, false);
889 }
890 }
891 }
892
893 #endregion
894
895
896 #region Selecting
897
898 public override bool canSelect => true;
899
900 #endregion
901
902
903 #region Dragging
904
905 public override bool canDrag => true;
906
907 public override void ExpandDragGroup(HashSet<IGraphElement> dragGroup)
908 {
909 if (BoltCore.Configuration.carryChildren)
910 {
911 foreach (var output in unit.outputs)
912 {
913 foreach (var connection in output.connections)
914 {
915 if (dragGroup.Contains(connection.destination.unit))
916 {
917 continue;
918 }
919
920 dragGroup.Add(connection.destination.unit);
921
922 canvas.Widget(connection.destination.unit).ExpandDragGroup(dragGroup);
923 }
924 }
925 }
926 }
927
928 #endregion
929
930
931 #region Deleting
932
933 public override bool canDelete => true;
934
935 #endregion
936
937
938 #region Clipboard
939
940 public override void ExpandCopyGroup(HashSet<IGraphElement> copyGroup)
941 {
942 copyGroup.UnionWith(unit.connections.Cast<IGraphElement>());
943 }
944
945 #endregion
946
947
948 #region Context
949
950 protected override IEnumerable<DropdownOption> contextOptions
951 {
952 get
953 {
954 yield return new DropdownOption((Action)ReplaceUnit, "Replace...");
955
956 foreach (var baseOption in base.contextOptions)
957 {
958 yield return baseOption;
959 }
960 }
961 }
962
963 private void ReplaceUnit()
964 {
965 UnitWidgetHelper.ReplaceUnit(unit, reference, context, selection, e);
966 }
967
968 #endregion
969
970
971 public static class Styles
972 {
973 static Styles()
974 {
975 // Disabling word wrap because Unity's CalcSize and CalcHeight
976 // are broken w.r.t. pixel-perfection and matrix
977
978 title = new GUIStyle(BoltCore.Styles.nodeLabel);
979 title.padding = new RectOffset(0, 5, 0, 2);
980 title.margin = new RectOffset(0, 0, 0, 0);
981 title.fontSize = 12;
982 title.alignment = TextAnchor.MiddleLeft;
983 title.wordWrap = false;
984
985 surtitle = new GUIStyle(BoltCore.Styles.nodeLabel);
986 surtitle.padding = new RectOffset(0, 5, 0, 0);
987 surtitle.margin = new RectOffset(0, 0, 0, 0);
988 surtitle.fontSize = 10;
989 surtitle.alignment = TextAnchor.MiddleLeft;
990 surtitle.wordWrap = false;
991
992 subtitle = new GUIStyle(surtitle);
993 subtitle.padding.bottom = 2;
994
995 titleInverted = new GUIStyle(title);
996 titleInverted.normal.textColor = ColorPalette.unityBackgroundDark;
997
998 surtitleInverted = new GUIStyle(surtitle);
999 surtitleInverted.normal.textColor = ColorPalette.unityBackgroundDark;
1000
1001 subtitleInverted = new GUIStyle(subtitle);
1002 subtitleInverted.normal.textColor = ColorPalette.unityBackgroundDark;
1003
1004 if (EditorGUIUtility.isProSkin)
1005 {
1006 portsBackground = new GUIStyle("In BigTitle")
1007 {
1008 padding = new RectOffset(0, 0, 6, 5)
1009 };
1010 }
1011 else
1012 {
1013 TextureResolution[] textureResolution = { 2 };
1014 var createTextureOptions = CreateTextureOptions.Scalable;
1015 EditorTexture normalTexture = BoltCore.Resources.LoadTexture($"NodePortsBackground.png", textureResolution, createTextureOptions);
1016
1017 portsBackground = new GUIStyle
1018 {
1019 normal = { background = normalTexture.Single() },
1020 padding = new RectOffset(0, 0, 6, 5)
1021 };
1022 }
1023
1024 settingLabel = new GUIStyle(BoltCore.Styles.nodeLabel);
1025 settingLabel.padding.left = 0;
1026 settingLabel.padding.right = 5;
1027 settingLabel.wordWrap = false;
1028 settingLabel.clipping = TextClipping.Clip;
1029 }
1030
1031 public static readonly GUIStyle title;
1032
1033 public static readonly GUIStyle surtitle;
1034
1035 public static readonly GUIStyle subtitle;
1036
1037 public static readonly GUIStyle titleInverted;
1038
1039 public static readonly GUIStyle surtitleInverted;
1040
1041 public static readonly GUIStyle subtitleInverted;
1042
1043 public static readonly GUIStyle settingLabel;
1044
1045 public static readonly float spaceAroundLineIcon = 5;
1046
1047 public static readonly float spaceBeforePorts = 5;
1048
1049 public static readonly float spaceBetweenInputsAndOutputs = 8;
1050
1051 public static readonly float spaceBeforeSettings = 2;
1052
1053 public static readonly float spaceBetweenSettings = 3;
1054
1055 public static readonly float spaceBetweenPorts = 3;
1056
1057 public static readonly float spaceAfterSettings = 0;
1058
1059 public static readonly float maxSettingsWidth = 150;
1060
1061 public static readonly GUIStyle portsBackground;
1062
1063 public static readonly float iconSize = IconSize.Medium;
1064
1065 public static readonly float iconsSize = IconSize.Small;
1066
1067 public static readonly float iconsSpacing = 3;
1068
1069 public static readonly int iconsPerColumn = 2;
1070
1071 public static readonly float spaceAfterIcon = 6;
1072
1073 public static readonly float spaceAfterSurtitle = 2;
1074
1075 public static readonly float spaceBeforeSubtitle = 0;
1076
1077 public static readonly float invokeFadeDuration = 0.5f;
1078 }
1079 }
1080
1081 internal class UnitWidgetHelper
1082 {
1083 internal static void ReplaceUnit(IUnit unit, GraphReference reference, IGraphContext context, GraphSelection selection, EventWrapper eventWrapper)
1084 {
1085 var oldUnit = unit;
1086 var unitPosition = oldUnit.position;
1087 var preservation = UnitPreservation.Preserve(oldUnit);
1088
1089 var options = new UnitOptionTree(new GUIContent("Node"));
1090 options.filter = UnitOptionFilter.Any;
1091 options.filter.NoConnection = false;
1092 options.reference = reference;
1093
1094 var activatorPosition = new Rect(eventWrapper.mousePosition, new Vector2(200, 1));
1095
1096 LudiqGUI.FuzzyDropdown
1097 (
1098 activatorPosition,
1099 options,
1100 null,
1101 delegate (object _option)
1102 {
1103 var option = (IUnitOption)_option;
1104
1105 context.BeginEdit();
1106 UndoUtility.RecordEditedObject("Replace Node");
1107 var graph = oldUnit.graph;
1108 oldUnit.graph.units.Remove(oldUnit);
1109 var newUnit = option.InstantiateUnit();
1110 newUnit.guid = Guid.NewGuid();
1111 newUnit.position = unitPosition;
1112 graph.units.Add(newUnit);
1113 preservation.RestoreTo(newUnit);
1114 option.PreconfigureUnit(newUnit);
1115 selection.Select(newUnit);
1116 GUI.changed = true;
1117 context.EndEdit();
1118 }
1119 );
1120 }
1121 }
1122}