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}