暂无描述
您最多选择25个主题 主题必须以字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符

StateTransitionWidget.cs 21KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692
  1. using System.Collections.Generic;
  2. using UnityEditor;
  3. using UnityEngine;
  4. namespace Unity.VisualScripting
  5. {
  6. public abstract class StateTransitionWidget<TStateTransition> : NodeWidget<StateCanvas, TStateTransition>, IStateTransitionWidget
  7. where TStateTransition : class, IStateTransition
  8. {
  9. protected StateTransitionWidget(StateCanvas canvas, TStateTransition transition) : base(canvas, transition) { }
  10. #region Model
  11. protected TStateTransition transition => element;
  12. protected IStateTransitionDebugData transitionDebugData => GetDebugData<IStateTransitionDebugData>();
  13. private StateTransitionDescription description;
  14. private StateTransitionAnalysis analysis => transition.Analysis<StateTransitionAnalysis>(context);
  15. protected override void CacheDescription()
  16. {
  17. description = transition.Description<StateTransitionDescription>();
  18. label.text = description.label;
  19. label.image = description.icon?[IconSize.Small];
  20. label.tooltip = description.tooltip;
  21. if (!revealLabel)
  22. {
  23. label.tooltip = label.text + ": " + label.tooltip;
  24. }
  25. Reposition();
  26. }
  27. #endregion
  28. #region Lifecycle
  29. public override void BeforeFrame()
  30. {
  31. base.BeforeFrame();
  32. if (showDroplets)
  33. {
  34. GraphGUI.UpdateDroplets(canvas, droplets, transitionDebugData.lastBranchFrame, ref lastBranchTime, ref dropTime);
  35. }
  36. if (currentInnerWidth != targetInnerWidth)
  37. {
  38. Reposition();
  39. }
  40. }
  41. #endregion
  42. #region Contents
  43. private GUIContent label { get; } = new GUIContent();
  44. #endregion
  45. #region Positioning
  46. private readonly List<IStateTransition> siblingStateTransitions = new List<IStateTransition>();
  47. private Rect sourcePosition;
  48. private Rect destinationPosition;
  49. private Edge sourceEdge;
  50. private Edge entryEdge;
  51. private Edge exitEdge;
  52. private Edge destinationEdge;
  53. private Vector2 sourceEdgeCenter;
  54. private Vector2 entryEdgeCenter;
  55. private Vector2 exitEdgeCenter;
  56. private Vector2 destinationEdgeCenter;
  57. private Vector2 middle;
  58. private Rect _position;
  59. private Rect _clippingPosition;
  60. private float targetInnerWidth;
  61. private float currentInnerWidth;
  62. private bool revealInitialized;
  63. private float minBend
  64. {
  65. get
  66. {
  67. if (transition.source != transition.destination)
  68. {
  69. return 15;
  70. }
  71. else
  72. {
  73. return (middle.y - canvas.Widget(transition.source).position.center.y) / 2;
  74. }
  75. }
  76. }
  77. private float relativeBend => 1 / 4f;
  78. Edge IStateTransitionWidget.sourceEdge => sourceEdge;
  79. public override IEnumerable<IWidget> positionDependencies
  80. {
  81. get
  82. {
  83. yield return canvas.Widget(transition.source);
  84. yield return canvas.Widget(transition.destination);
  85. }
  86. }
  87. public override IEnumerable<IWidget> positionDependers
  88. {
  89. get
  90. {
  91. // Return all sibling transitions. This is an asymetrical dependency / depender
  92. // relation (because the siblings are not included in the dependers) to force
  93. // repositioning of siblings while avoiding stack overflow.
  94. foreach (var graphTransition in canvas.graph.transitions)
  95. {
  96. var current = transition == graphTransition;
  97. var analog =
  98. transition.source == graphTransition.source &&
  99. transition.destination == graphTransition.destination;
  100. var inverted =
  101. transition.source == graphTransition.destination &&
  102. transition.destination == graphTransition.source;
  103. if (!current && (analog || inverted))
  104. {
  105. var widget = canvas.Widget(graphTransition);
  106. if (widget.isPositionValid) // Avoid stack overflow
  107. {
  108. yield return widget;
  109. }
  110. }
  111. }
  112. }
  113. }
  114. public Rect iconPosition { get; private set; }
  115. public Rect clipPosition { get; private set; }
  116. public Rect labelInnerPosition { get; private set; }
  117. public override Rect position
  118. {
  119. get { return _position; }
  120. set { }
  121. }
  122. public override Rect clippingPosition => _clippingPosition;
  123. public override void CachePositionFirstPass()
  124. {
  125. // Calculate the size immediately, because other transitions will rely on it for positioning
  126. targetInnerWidth = Styles.eventIcon.fixedWidth;
  127. var labelWidth = Styles.label.CalcSize(label).x;
  128. var labelHeight = EditorGUIUtility.singleLineHeight;
  129. if (revealLabel)
  130. {
  131. targetInnerWidth += Styles.spaceAroundIcon;
  132. targetInnerWidth += labelWidth;
  133. }
  134. if (!revealInitialized)
  135. {
  136. currentInnerWidth = targetInnerWidth;
  137. revealInitialized = true;
  138. }
  139. currentInnerWidth = Mathf.Lerp(currentInnerWidth, targetInnerWidth, canvas.repaintDeltaTime * Styles.revealSpeed);
  140. if (Mathf.Abs(targetInnerWidth - currentInnerWidth) < 1)
  141. {
  142. currentInnerWidth = targetInnerWidth;
  143. }
  144. var innerWidth = currentInnerWidth;
  145. var innerHeight = labelHeight;
  146. var edgeSize = InnerToEdgePosition(new Rect(0, 0, innerWidth, innerHeight)).size;
  147. var edgeWidth = edgeSize.x;
  148. var edgeHeight = edgeSize.y;
  149. _position.width = edgeWidth;
  150. _position.height = edgeHeight;
  151. }
  152. public override void CachePosition()
  153. {
  154. var innerWidth = innerPosition.width;
  155. var innerHeight = innerPosition.height;
  156. var edgeWidth = edgePosition.width;
  157. var edgeHeight = edgePosition.height;
  158. var labelWidth = Styles.label.CalcSize(label).x;
  159. var labelHeight = EditorGUIUtility.singleLineHeight;
  160. sourcePosition = canvas.Widget(transition.source).position;
  161. destinationPosition = canvas.Widget(transition.destination).position;
  162. Vector2 sourceClosestPoint;
  163. Vector2 destinationClosestPoint;
  164. LudiqGUIUtility.ClosestPoints(sourcePosition, destinationPosition, out sourceClosestPoint, out destinationClosestPoint);
  165. if (transition.destination != transition.source)
  166. {
  167. GraphGUI.GetConnectionEdge
  168. (
  169. sourceClosestPoint,
  170. destinationClosestPoint,
  171. out sourceEdge,
  172. out destinationEdge
  173. );
  174. }
  175. else
  176. {
  177. sourceEdge = Edge.Right;
  178. destinationEdge = Edge.Left;
  179. }
  180. sourceEdgeCenter = sourcePosition.GetEdgeCenter(sourceEdge);
  181. destinationEdgeCenter = destinationPosition.GetEdgeCenter(destinationEdge);
  182. siblingStateTransitions.Clear();
  183. var siblingIndex = 0;
  184. // Assign one common axis for transition for all siblings,
  185. // regardless of their inversion. The axis is arbitrarily
  186. // chosen as the axis for the first transition.
  187. var assignedTransitionAxis = false;
  188. var transitionAxis = Vector2.zero;
  189. foreach (var graphTransition in canvas.graph.transitions)
  190. {
  191. var current = transition == graphTransition;
  192. var analog =
  193. transition.source == graphTransition.source &&
  194. transition.destination == graphTransition.destination;
  195. var inverted =
  196. transition.source == graphTransition.destination &&
  197. transition.destination == graphTransition.source;
  198. if (current)
  199. {
  200. siblingIndex = siblingStateTransitions.Count;
  201. }
  202. if (current || analog || inverted)
  203. {
  204. if (!assignedTransitionAxis)
  205. {
  206. var siblingStateTransitionDrawer = canvas.Widget<IStateTransitionWidget>(graphTransition);
  207. transitionAxis = siblingStateTransitionDrawer.sourceEdge.Normal();
  208. assignedTransitionAxis = true;
  209. }
  210. siblingStateTransitions.Add(graphTransition);
  211. }
  212. }
  213. // Fix the edge case where the source and destination perfectly overlap
  214. if (transitionAxis == Vector2.zero)
  215. {
  216. transitionAxis = Vector2.right;
  217. }
  218. // Calculate the spread axis and origin for the set of siblings
  219. var spreadAxis = transitionAxis.Perpendicular1().Abs();
  220. var spreadOrigin = (sourceEdgeCenter + destinationEdgeCenter) / 2;
  221. if (transition.source == transition.destination)
  222. {
  223. spreadAxis = Vector2.up;
  224. spreadOrigin = sourcePosition.GetEdgeCenter(Edge.Bottom) - Vector2.down * 10;
  225. }
  226. if (BoltCore.Configuration.developerMode && BoltCore.Configuration.debug)
  227. {
  228. Handles.BeginGUI();
  229. Handles.color = Color.yellow;
  230. Handles.DrawLine(spreadOrigin + spreadAxis * -1000, spreadOrigin + spreadAxis * 1000);
  231. Handles.EndGUI();
  232. }
  233. // Calculate the offset of the current sibling by iterating over its predecessors
  234. var spreadOffset = 0f;
  235. var previousSpreadSize = 0f;
  236. for (var i = 0; i <= siblingIndex; i++)
  237. {
  238. var siblingSize = canvas.Widget<IStateTransitionWidget>(siblingStateTransitions[i]).outerPosition.size;
  239. var siblingSizeProjection = GraphGUI.SizeProjection(siblingSize, spreadOrigin, spreadAxis);
  240. spreadOffset += previousSpreadSize / 2 + siblingSizeProjection / 2;
  241. previousSpreadSize = siblingSizeProjection;
  242. }
  243. if (transition.source != transition.destination)
  244. {
  245. // Calculate the total spread size to center the sibling set
  246. var totalSpreadSize = 0f;
  247. for (var i = 0; i < siblingStateTransitions.Count; i++)
  248. {
  249. var siblingSize = canvas.Widget<IStateTransitionWidget>(siblingStateTransitions[i]).outerPosition.size;
  250. var siblingSizeProjection = GraphGUI.SizeProjection(siblingSize, spreadOrigin, spreadAxis);
  251. totalSpreadSize += siblingSizeProjection;
  252. }
  253. spreadOffset -= totalSpreadSize / 2;
  254. }
  255. // Finally, calculate the positions
  256. middle = spreadOrigin + spreadOffset * spreadAxis;
  257. var edgeX = middle.x - edgeWidth / 2;
  258. var edgeY = middle.y - edgeHeight / 2;
  259. _position = new Rect
  260. (
  261. edgeX,
  262. edgeY,
  263. edgeWidth,
  264. edgeHeight
  265. ).PixelPerfect();
  266. var innerX = innerPosition.x;
  267. var innerY = innerPosition.y;
  268. _clippingPosition = _position.Encompass(sourceEdgeCenter).Encompass(destinationEdgeCenter);
  269. if (transition.source != transition.destination)
  270. {
  271. entryEdge = destinationEdge;
  272. exitEdge = sourceEdge;
  273. }
  274. else
  275. {
  276. entryEdge = sourceEdge;
  277. exitEdge = destinationEdge;
  278. }
  279. entryEdgeCenter = edgePosition.GetEdgeCenter(entryEdge);
  280. exitEdgeCenter = edgePosition.GetEdgeCenter(exitEdge);
  281. var x = innerX;
  282. iconPosition = new Rect
  283. (
  284. x,
  285. innerY,
  286. Styles.eventIcon.fixedWidth,
  287. Styles.eventIcon.fixedHeight
  288. ).PixelPerfect();
  289. x += iconPosition.width;
  290. var clipWidth = innerWidth - (x - innerX);
  291. clipPosition = new Rect
  292. (
  293. x,
  294. edgeY,
  295. clipWidth,
  296. edgeHeight
  297. ).PixelPerfect();
  298. labelInnerPosition = new Rect
  299. (
  300. Styles.spaceAroundIcon,
  301. innerY - edgeY,
  302. labelWidth,
  303. labelHeight
  304. ).PixelPerfect();
  305. }
  306. #endregion
  307. #region Drawing
  308. protected virtual NodeColorMix baseColor => NodeColor.Gray;
  309. protected override NodeColorMix color
  310. {
  311. get
  312. {
  313. if (transitionDebugData.runtimeException != null)
  314. {
  315. return NodeColor.Red;
  316. }
  317. var color = baseColor;
  318. if (analysis.warnings.Count > 0)
  319. {
  320. var mostSevereWarning = Warning.MostSevereLevel(analysis.warnings);
  321. switch (mostSevereWarning)
  322. {
  323. case WarningLevel.Error:
  324. color = NodeColor.Red;
  325. break;
  326. case WarningLevel.Severe:
  327. color = NodeColor.Orange;
  328. break;
  329. case WarningLevel.Caution:
  330. color = NodeColor.Yellow;
  331. break;
  332. }
  333. }
  334. if (EditorApplication.isPlaying)
  335. {
  336. if (EditorApplication.isPaused)
  337. {
  338. if (EditorTimeBinding.frame == transitionDebugData.lastBranchFrame)
  339. {
  340. color = NodeColor.Blue;
  341. }
  342. }
  343. else
  344. {
  345. color.blue = Mathf.Lerp(1, 0, (EditorTimeBinding.time - transitionDebugData.lastBranchTime) / StateWidget<IState>.Styles.enterFadeDuration);
  346. }
  347. }
  348. return color;
  349. }
  350. }
  351. protected override NodeShape shape => NodeShape.Hex;
  352. private bool revealLabel
  353. {
  354. get
  355. {
  356. switch (BoltState.Configuration.transitionsReveal)
  357. {
  358. case StateRevealCondition.Always:
  359. return true;
  360. case StateRevealCondition.Never:
  361. return false;
  362. case StateRevealCondition.OnHover:
  363. return isMouseOver;
  364. case StateRevealCondition.OnHoverWithAlt:
  365. return isMouseOver && e.alt;
  366. case StateRevealCondition.WhenSelected:
  367. return selection.Contains(transition);
  368. case StateRevealCondition.OnHoverOrSelected:
  369. return isMouseOver || selection.Contains(transition);
  370. case StateRevealCondition.OnHoverWithAltOrSelected:
  371. return isMouseOver && e.alt || selection.Contains(transition);
  372. default:
  373. throw new UnexpectedEnumValueException<StateRevealCondition>(BoltState.Configuration.transitionsReveal);
  374. }
  375. }
  376. }
  377. private bool revealedLabel;
  378. private void CheckReveal()
  379. {
  380. var revealLabel = this.revealLabel;
  381. if (revealLabel != revealedLabel)
  382. {
  383. Reposition();
  384. }
  385. revealedLabel = revealLabel;
  386. }
  387. protected override bool dim
  388. {
  389. get
  390. {
  391. var dim = BoltCore.Configuration.dimInactiveNodes && !(transition.source.Analysis<StateAnalysis>(context).isEntered && analysis.isTraversed);
  392. if (isMouseOver || isSelected)
  393. {
  394. dim = false;
  395. }
  396. return dim;
  397. }
  398. }
  399. public override void DrawForeground()
  400. {
  401. BeginDim();
  402. base.DrawForeground();
  403. GUI.Label(iconPosition, label, Styles.eventIcon);
  404. GUI.BeginClip(clipPosition);
  405. GUI.Label(labelInnerPosition, label, invertForeground ? Styles.labelInverted : Styles.label);
  406. GUI.EndClip();
  407. EndDim();
  408. CheckReveal();
  409. }
  410. public override void DrawBackground()
  411. {
  412. BeginDim();
  413. base.DrawBackground();
  414. DrawConnection();
  415. if (showDroplets)
  416. {
  417. DrawDroplets();
  418. }
  419. EndDim();
  420. }
  421. private void DrawConnection()
  422. {
  423. GraphGUI.DrawConnectionArrow(Color.white, sourceEdgeCenter, entryEdgeCenter, sourceEdge, entryEdge, relativeBend, minBend);
  424. if (BoltState.Configuration.transitionsEndArrow)
  425. {
  426. GraphGUI.DrawConnectionArrow(Color.white, exitEdgeCenter, destinationEdgeCenter, exitEdge, destinationEdge, relativeBend, minBend);
  427. }
  428. else
  429. {
  430. GraphGUI.DrawConnection(Color.white, exitEdgeCenter, destinationEdgeCenter, exitEdge, destinationEdge, null, Vector2.zero, relativeBend, minBend);
  431. }
  432. }
  433. #endregion
  434. #region Selecting
  435. public override bool canSelect => true;
  436. #endregion
  437. #region Dragging
  438. public override bool canDrag => false;
  439. protected override bool snapToGrid => false;
  440. #endregion
  441. #region Deleting
  442. public override bool canDelete => true;
  443. #endregion
  444. #region Droplets
  445. private readonly List<float> droplets = new List<float>();
  446. private float dropTime;
  447. private float lastBranchTime;
  448. protected virtual bool showDroplets => BoltState.Configuration.animateTransitions;
  449. protected virtual Vector2 GetDropletSize()
  450. {
  451. return BoltFlow.Icons.valuePortConnected?[12].Size() ?? 12 * Vector2.one;
  452. }
  453. protected virtual void DrawDroplet(Rect position)
  454. {
  455. GUI.DrawTexture(position, BoltFlow.Icons.valuePortConnected?[12]);
  456. }
  457. private void DrawDroplets()
  458. {
  459. foreach (var droplet in droplets)
  460. {
  461. Vector2 position;
  462. if (droplet < 0.5f)
  463. {
  464. var t = droplet / 0.5f;
  465. position = GraphGUI.GetPointOnConnection(t, sourceEdgeCenter, entryEdgeCenter, sourceEdge, entryEdge, relativeBend, minBend);
  466. }
  467. else
  468. {
  469. var t = (droplet - 0.5f) / 0.5f;
  470. position = GraphGUI.GetPointOnConnection(t, exitEdgeCenter, destinationEdgeCenter, exitEdge, destinationEdge, relativeBend, minBend);
  471. }
  472. var size = GetDropletSize();
  473. using (LudiqGUI.color.Override(Color.white))
  474. {
  475. DrawDroplet(new Rect(position.x - size.x / 2, position.y - size.y / 2, size.x, size.y));
  476. }
  477. }
  478. }
  479. #endregion
  480. public static class Styles
  481. {
  482. static Styles()
  483. {
  484. label = new GUIStyle(BoltCore.Styles.nodeLabel);
  485. label.alignment = TextAnchor.MiddleCenter;
  486. label.imagePosition = ImagePosition.TextOnly;
  487. labelInverted = new GUIStyle(label);
  488. labelInverted.normal.textColor = ColorPalette.unityBackgroundDark;
  489. eventIcon = new GUIStyle();
  490. eventIcon.imagePosition = ImagePosition.ImageOnly;
  491. eventIcon.fixedHeight = 16;
  492. eventIcon.fixedWidth = 16;
  493. }
  494. public static readonly GUIStyle label;
  495. public static readonly GUIStyle labelInverted;
  496. public static readonly GUIStyle eventIcon;
  497. public static readonly float spaceAroundIcon = 5;
  498. public static readonly float revealSpeed = 15;
  499. }
  500. }
  501. }