123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692 |
- using System.Collections.Generic;
- using UnityEditor;
- using UnityEngine;
-
- namespace Unity.VisualScripting
- {
- public abstract class StateTransitionWidget<TStateTransition> : NodeWidget<StateCanvas, TStateTransition>, IStateTransitionWidget
- where TStateTransition : class, IStateTransition
- {
- protected StateTransitionWidget(StateCanvas canvas, TStateTransition transition) : base(canvas, transition) { }
-
-
- #region Model
-
- protected TStateTransition transition => element;
-
- protected IStateTransitionDebugData transitionDebugData => GetDebugData<IStateTransitionDebugData>();
-
- private StateTransitionDescription description;
-
- private StateTransitionAnalysis analysis => transition.Analysis<StateTransitionAnalysis>(context);
-
- protected override void CacheDescription()
- {
- description = transition.Description<StateTransitionDescription>();
-
- label.text = description.label;
- label.image = description.icon?[IconSize.Small];
- label.tooltip = description.tooltip;
-
- if (!revealLabel)
- {
- label.tooltip = label.text + ": " + label.tooltip;
- }
-
- Reposition();
- }
-
- #endregion
-
- #region Lifecycle
-
- public override void BeforeFrame()
- {
- base.BeforeFrame();
-
- if (showDroplets)
- {
- GraphGUI.UpdateDroplets(canvas, droplets, transitionDebugData.lastBranchFrame, ref lastBranchTime, ref dropTime);
- }
-
- if (currentInnerWidth != targetInnerWidth)
- {
- Reposition();
- }
- }
-
- #endregion
-
-
- #region Contents
-
- private GUIContent label { get; } = new GUIContent();
-
- #endregion
-
-
- #region Positioning
-
- private readonly List<IStateTransition> siblingStateTransitions = new List<IStateTransition>();
-
- private Rect sourcePosition;
-
- private Rect destinationPosition;
-
- private Edge sourceEdge;
-
- private Edge entryEdge;
-
- private Edge exitEdge;
-
- private Edge destinationEdge;
-
- private Vector2 sourceEdgeCenter;
-
- private Vector2 entryEdgeCenter;
-
- private Vector2 exitEdgeCenter;
-
- private Vector2 destinationEdgeCenter;
-
- private Vector2 middle;
-
- private Rect _position;
-
- private Rect _clippingPosition;
-
- private float targetInnerWidth;
-
- private float currentInnerWidth;
-
- private bool revealInitialized;
-
- private float minBend
- {
- get
- {
- if (transition.source != transition.destination)
- {
- return 15;
- }
- else
- {
- return (middle.y - canvas.Widget(transition.source).position.center.y) / 2;
- }
- }
- }
-
- private float relativeBend => 1 / 4f;
-
- Edge IStateTransitionWidget.sourceEdge => sourceEdge;
-
- public override IEnumerable<IWidget> positionDependencies
- {
- get
- {
- yield return canvas.Widget(transition.source);
- yield return canvas.Widget(transition.destination);
- }
- }
-
- public override IEnumerable<IWidget> positionDependers
- {
- get
- {
- // Return all sibling transitions. This is an asymetrical dependency / depender
- // relation (because the siblings are not included in the dependers) to force
- // repositioning of siblings while avoiding stack overflow.
-
- foreach (var graphTransition in canvas.graph.transitions)
- {
- var current = transition == graphTransition;
-
- var analog =
- transition.source == graphTransition.source &&
- transition.destination == graphTransition.destination;
-
- var inverted =
- transition.source == graphTransition.destination &&
- transition.destination == graphTransition.source;
-
- if (!current && (analog || inverted))
- {
- var widget = canvas.Widget(graphTransition);
-
- if (widget.isPositionValid) // Avoid stack overflow
- {
- yield return widget;
- }
- }
- }
- }
- }
-
- public Rect iconPosition { get; private set; }
-
- public Rect clipPosition { get; private set; }
-
- public Rect labelInnerPosition { get; private set; }
-
- public override Rect position
- {
- get { return _position; }
- set { }
- }
-
- public override Rect clippingPosition => _clippingPosition;
-
- public override void CachePositionFirstPass()
- {
- // Calculate the size immediately, because other transitions will rely on it for positioning
-
- targetInnerWidth = Styles.eventIcon.fixedWidth;
-
- var labelWidth = Styles.label.CalcSize(label).x;
- var labelHeight = EditorGUIUtility.singleLineHeight;
-
- if (revealLabel)
- {
- targetInnerWidth += Styles.spaceAroundIcon;
- targetInnerWidth += labelWidth;
- }
-
- if (!revealInitialized)
- {
- currentInnerWidth = targetInnerWidth;
- revealInitialized = true;
- }
-
- currentInnerWidth = Mathf.Lerp(currentInnerWidth, targetInnerWidth, canvas.repaintDeltaTime * Styles.revealSpeed);
-
- if (Mathf.Abs(targetInnerWidth - currentInnerWidth) < 1)
- {
- currentInnerWidth = targetInnerWidth;
- }
-
- var innerWidth = currentInnerWidth;
- var innerHeight = labelHeight;
-
- var edgeSize = InnerToEdgePosition(new Rect(0, 0, innerWidth, innerHeight)).size;
- var edgeWidth = edgeSize.x;
- var edgeHeight = edgeSize.y;
-
- _position.width = edgeWidth;
- _position.height = edgeHeight;
- }
-
- public override void CachePosition()
- {
- var innerWidth = innerPosition.width;
- var innerHeight = innerPosition.height;
- var edgeWidth = edgePosition.width;
- var edgeHeight = edgePosition.height;
- var labelWidth = Styles.label.CalcSize(label).x;
- var labelHeight = EditorGUIUtility.singleLineHeight;
-
- sourcePosition = canvas.Widget(transition.source).position;
- destinationPosition = canvas.Widget(transition.destination).position;
-
- Vector2 sourceClosestPoint;
- Vector2 destinationClosestPoint;
- LudiqGUIUtility.ClosestPoints(sourcePosition, destinationPosition, out sourceClosestPoint, out destinationClosestPoint);
-
- if (transition.destination != transition.source)
- {
- GraphGUI.GetConnectionEdge
- (
- sourceClosestPoint,
- destinationClosestPoint,
- out sourceEdge,
- out destinationEdge
- );
- }
- else
- {
- sourceEdge = Edge.Right;
- destinationEdge = Edge.Left;
- }
-
- sourceEdgeCenter = sourcePosition.GetEdgeCenter(sourceEdge);
- destinationEdgeCenter = destinationPosition.GetEdgeCenter(destinationEdge);
-
- siblingStateTransitions.Clear();
-
- var siblingIndex = 0;
-
- // Assign one common axis for transition for all siblings,
- // regardless of their inversion. The axis is arbitrarily
- // chosen as the axis for the first transition.
- var assignedTransitionAxis = false;
- var transitionAxis = Vector2.zero;
-
- foreach (var graphTransition in canvas.graph.transitions)
- {
- var current = transition == graphTransition;
-
- var analog =
- transition.source == graphTransition.source &&
- transition.destination == graphTransition.destination;
-
- var inverted =
- transition.source == graphTransition.destination &&
- transition.destination == graphTransition.source;
-
- if (current)
- {
- siblingIndex = siblingStateTransitions.Count;
- }
-
- if (current || analog || inverted)
- {
- if (!assignedTransitionAxis)
- {
- var siblingStateTransitionDrawer = canvas.Widget<IStateTransitionWidget>(graphTransition);
-
- transitionAxis = siblingStateTransitionDrawer.sourceEdge.Normal();
-
- assignedTransitionAxis = true;
- }
-
- siblingStateTransitions.Add(graphTransition);
- }
- }
-
- // Fix the edge case where the source and destination perfectly overlap
-
- if (transitionAxis == Vector2.zero)
- {
- transitionAxis = Vector2.right;
- }
-
- // Calculate the spread axis and origin for the set of siblings
-
- var spreadAxis = transitionAxis.Perpendicular1().Abs();
- var spreadOrigin = (sourceEdgeCenter + destinationEdgeCenter) / 2;
-
- if (transition.source == transition.destination)
- {
- spreadAxis = Vector2.up;
- spreadOrigin = sourcePosition.GetEdgeCenter(Edge.Bottom) - Vector2.down * 10;
- }
-
- if (BoltCore.Configuration.developerMode && BoltCore.Configuration.debug)
- {
- Handles.BeginGUI();
- Handles.color = Color.yellow;
- Handles.DrawLine(spreadOrigin + spreadAxis * -1000, spreadOrigin + spreadAxis * 1000);
- Handles.EndGUI();
- }
-
- // Calculate the offset of the current sibling by iterating over its predecessors
-
- var spreadOffset = 0f;
- var previousSpreadSize = 0f;
-
- for (var i = 0; i <= siblingIndex; i++)
- {
- var siblingSize = canvas.Widget<IStateTransitionWidget>(siblingStateTransitions[i]).outerPosition.size;
- var siblingSizeProjection = GraphGUI.SizeProjection(siblingSize, spreadOrigin, spreadAxis);
- spreadOffset += previousSpreadSize / 2 + siblingSizeProjection / 2;
- previousSpreadSize = siblingSizeProjection;
- }
-
- if (transition.source != transition.destination)
- {
- // Calculate the total spread size to center the sibling set
-
- var totalSpreadSize = 0f;
-
- for (var i = 0; i < siblingStateTransitions.Count; i++)
- {
- var siblingSize = canvas.Widget<IStateTransitionWidget>(siblingStateTransitions[i]).outerPosition.size;
- var siblingSizeProjection = GraphGUI.SizeProjection(siblingSize, spreadOrigin, spreadAxis);
- totalSpreadSize += siblingSizeProjection;
- }
-
- spreadOffset -= totalSpreadSize / 2;
- }
-
- // Finally, calculate the positions
-
- middle = spreadOrigin + spreadOffset * spreadAxis;
-
- var edgeX = middle.x - edgeWidth / 2;
- var edgeY = middle.y - edgeHeight / 2;
-
- _position = new Rect
- (
- edgeX,
- edgeY,
- edgeWidth,
- edgeHeight
- ).PixelPerfect();
-
- var innerX = innerPosition.x;
- var innerY = innerPosition.y;
-
- _clippingPosition = _position.Encompass(sourceEdgeCenter).Encompass(destinationEdgeCenter);
-
- if (transition.source != transition.destination)
- {
- entryEdge = destinationEdge;
- exitEdge = sourceEdge;
- }
- else
- {
- entryEdge = sourceEdge;
- exitEdge = destinationEdge;
- }
-
- entryEdgeCenter = edgePosition.GetEdgeCenter(entryEdge);
- exitEdgeCenter = edgePosition.GetEdgeCenter(exitEdge);
-
- var x = innerX;
-
- iconPosition = new Rect
- (
- x,
- innerY,
- Styles.eventIcon.fixedWidth,
- Styles.eventIcon.fixedHeight
- ).PixelPerfect();
-
- x += iconPosition.width;
-
- var clipWidth = innerWidth - (x - innerX);
-
- clipPosition = new Rect
- (
- x,
- edgeY,
- clipWidth,
- edgeHeight
- ).PixelPerfect();
-
- labelInnerPosition = new Rect
- (
- Styles.spaceAroundIcon,
- innerY - edgeY,
- labelWidth,
- labelHeight
- ).PixelPerfect();
- }
-
- #endregion
-
-
- #region Drawing
-
- protected virtual NodeColorMix baseColor => NodeColor.Gray;
-
- protected override NodeColorMix color
- {
- get
- {
- if (transitionDebugData.runtimeException != null)
- {
- return NodeColor.Red;
- }
-
- var color = baseColor;
-
- if (analysis.warnings.Count > 0)
- {
- var mostSevereWarning = Warning.MostSevereLevel(analysis.warnings);
-
- switch (mostSevereWarning)
- {
- case WarningLevel.Error:
- color = NodeColor.Red;
-
- break;
-
- case WarningLevel.Severe:
- color = NodeColor.Orange;
-
- break;
-
- case WarningLevel.Caution:
- color = NodeColor.Yellow;
-
- break;
- }
- }
-
- if (EditorApplication.isPlaying)
- {
- if (EditorApplication.isPaused)
- {
- if (EditorTimeBinding.frame == transitionDebugData.lastBranchFrame)
- {
- color = NodeColor.Blue;
- }
- }
- else
- {
- color.blue = Mathf.Lerp(1, 0, (EditorTimeBinding.time - transitionDebugData.lastBranchTime) / StateWidget<IState>.Styles.enterFadeDuration);
- }
- }
-
- return color;
- }
- }
-
- protected override NodeShape shape => NodeShape.Hex;
-
- private bool revealLabel
- {
- get
- {
- switch (BoltState.Configuration.transitionsReveal)
- {
- case StateRevealCondition.Always:
-
- return true;
- case StateRevealCondition.Never:
-
- return false;
- case StateRevealCondition.OnHover:
-
- return isMouseOver;
- case StateRevealCondition.OnHoverWithAlt:
-
- return isMouseOver && e.alt;
- case StateRevealCondition.WhenSelected:
-
- return selection.Contains(transition);
- case StateRevealCondition.OnHoverOrSelected:
-
- return isMouseOver || selection.Contains(transition);
- case StateRevealCondition.OnHoverWithAltOrSelected:
-
- return isMouseOver && e.alt || selection.Contains(transition);
- default:
-
- throw new UnexpectedEnumValueException<StateRevealCondition>(BoltState.Configuration.transitionsReveal);
- }
- }
- }
-
- private bool revealedLabel;
-
- private void CheckReveal()
- {
- var revealLabel = this.revealLabel;
-
- if (revealLabel != revealedLabel)
- {
- Reposition();
- }
-
- revealedLabel = revealLabel;
- }
-
- protected override bool dim
- {
- get
- {
- var dim = BoltCore.Configuration.dimInactiveNodes && !(transition.source.Analysis<StateAnalysis>(context).isEntered && analysis.isTraversed);
-
- if (isMouseOver || isSelected)
- {
- dim = false;
- }
-
- return dim;
- }
- }
-
- public override void DrawForeground()
- {
- BeginDim();
-
- base.DrawForeground();
-
- GUI.Label(iconPosition, label, Styles.eventIcon);
-
- GUI.BeginClip(clipPosition);
-
- GUI.Label(labelInnerPosition, label, invertForeground ? Styles.labelInverted : Styles.label);
-
- GUI.EndClip();
-
- EndDim();
-
- CheckReveal();
- }
-
- public override void DrawBackground()
- {
- BeginDim();
-
- base.DrawBackground();
-
- DrawConnection();
-
- if (showDroplets)
- {
- DrawDroplets();
- }
-
- EndDim();
- }
-
- private void DrawConnection()
- {
- GraphGUI.DrawConnectionArrow(Color.white, sourceEdgeCenter, entryEdgeCenter, sourceEdge, entryEdge, relativeBend, minBend);
-
- if (BoltState.Configuration.transitionsEndArrow)
- {
- GraphGUI.DrawConnectionArrow(Color.white, exitEdgeCenter, destinationEdgeCenter, exitEdge, destinationEdge, relativeBend, minBend);
- }
- else
- {
- GraphGUI.DrawConnection(Color.white, exitEdgeCenter, destinationEdgeCenter, exitEdge, destinationEdge, null, Vector2.zero, relativeBend, minBend);
- }
- }
-
- #endregion
-
-
- #region Selecting
-
- public override bool canSelect => true;
-
- #endregion
-
-
- #region Dragging
-
- public override bool canDrag => false;
-
- protected override bool snapToGrid => false;
-
- #endregion
-
-
- #region Deleting
-
- public override bool canDelete => true;
-
- #endregion
-
-
- #region Droplets
-
- private readonly List<float> droplets = new List<float>();
-
- private float dropTime;
-
- private float lastBranchTime;
-
- protected virtual bool showDroplets => BoltState.Configuration.animateTransitions;
-
- protected virtual Vector2 GetDropletSize()
- {
- return BoltFlow.Icons.valuePortConnected?[12].Size() ?? 12 * Vector2.one;
- }
-
- protected virtual void DrawDroplet(Rect position)
- {
- GUI.DrawTexture(position, BoltFlow.Icons.valuePortConnected?[12]);
- }
-
- private void DrawDroplets()
- {
- foreach (var droplet in droplets)
- {
- Vector2 position;
-
- if (droplet < 0.5f)
- {
- var t = droplet / 0.5f;
- position = GraphGUI.GetPointOnConnection(t, sourceEdgeCenter, entryEdgeCenter, sourceEdge, entryEdge, relativeBend, minBend);
- }
- else
- {
- var t = (droplet - 0.5f) / 0.5f;
- position = GraphGUI.GetPointOnConnection(t, exitEdgeCenter, destinationEdgeCenter, exitEdge, destinationEdge, relativeBend, minBend);
- }
-
- var size = GetDropletSize();
-
- using (LudiqGUI.color.Override(Color.white))
- {
- DrawDroplet(new Rect(position.x - size.x / 2, position.y - size.y / 2, size.x, size.y));
- }
- }
- }
-
- #endregion
-
-
- public static class Styles
- {
- static Styles()
- {
- label = new GUIStyle(BoltCore.Styles.nodeLabel);
- label.alignment = TextAnchor.MiddleCenter;
- label.imagePosition = ImagePosition.TextOnly;
-
- labelInverted = new GUIStyle(label);
- labelInverted.normal.textColor = ColorPalette.unityBackgroundDark;
-
- eventIcon = new GUIStyle();
- eventIcon.imagePosition = ImagePosition.ImageOnly;
- eventIcon.fixedHeight = 16;
- eventIcon.fixedWidth = 16;
- }
-
- public static readonly GUIStyle label;
-
- public static readonly GUIStyle labelInverted;
-
- public static readonly GUIStyle eventIcon;
-
- public static readonly float spaceAroundIcon = 5;
-
- public static readonly float revealSpeed = 15;
- }
- }
- }
|