Нет описания
Вы не можете выбрать более 25 тем Темы должны начинаться с буквы или цифры, могут содержать дефисы(-) и должны содержать не более 35 символов.

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using UnityEditor;
  5. using UnityEngine;
  6. namespace Unity.VisualScripting
  7. {
  8. public abstract class StateWidget<TState> : NodeWidget<StateCanvas, TState>, IStateWidget
  9. where TState : class, IState
  10. {
  11. protected StateWidget(StateCanvas canvas, TState state) : base(canvas, state)
  12. {
  13. minResizeSize = new Vector2(State.DefaultWidth, 0);
  14. }
  15. public virtual bool canForceEnter => true;
  16. public virtual bool canForceExit => true;
  17. public virtual bool canToggleStart => true;
  18. #region Model
  19. protected TState state => element;
  20. protected IStateDebugData stateDebugData => GetDebugData<IStateDebugData>();
  21. protected State.Data stateData => reference.hasData ? reference.GetElementData<State.Data>(state) : null;
  22. IState IStateWidget.state => state;
  23. protected StateDescription description { get; private set; }
  24. protected StateAnalysis analysis => state.Analysis<StateAnalysis>(context);
  25. protected override void CacheDescription()
  26. {
  27. description = state.Description<StateDescription>();
  28. title = description.title;
  29. summary = description.summary;
  30. titleContent.text = " " + title;
  31. titleContent.image = description.icon?[IconSize.Small];
  32. summaryContent.text = summary;
  33. Reposition();
  34. }
  35. #endregion
  36. #region Lifecycle
  37. public override void BeforeFrame()
  38. {
  39. base.BeforeFrame();
  40. if (currentContentOuterHeight != targetContentOuterHeight)
  41. {
  42. Reposition();
  43. }
  44. }
  45. public override void HandleInput()
  46. {
  47. if (e.IsMouseDrag(MouseButton.Left) &&
  48. e.ctrlOrCmd &&
  49. !canvas.isCreatingTransition)
  50. {
  51. if (state.canBeSource)
  52. {
  53. canvas.StartTransition(state);
  54. }
  55. else
  56. {
  57. Debug.LogWarning("Cannot create a transition from this state.\n");
  58. }
  59. e.Use();
  60. }
  61. else if (e.IsMouseDrag(MouseButton.Left) && canvas.isCreatingTransition)
  62. {
  63. e.Use();
  64. }
  65. else if (e.IsMouseUp(MouseButton.Left) && canvas.isCreatingTransition)
  66. {
  67. var source = canvas.transitionSource;
  68. var destination = (canvas.hoveredWidget as IStateWidget)?.state;
  69. if (destination == null)
  70. {
  71. canvas.CompleteTransitionToNewState();
  72. }
  73. else if (destination == source)
  74. {
  75. canvas.CancelTransition();
  76. }
  77. else if (destination.canBeDestination)
  78. {
  79. canvas.EndTransition(destination);
  80. }
  81. else
  82. {
  83. Debug.LogWarning("Cannot create a transition to this state.\n");
  84. canvas.CancelTransition();
  85. }
  86. e.Use();
  87. }
  88. base.HandleInput();
  89. }
  90. #endregion
  91. #region Contents
  92. protected virtual string title { get; set; }
  93. protected virtual string summary { get; set; }
  94. private GUIContent titleContent { get; } = new GUIContent();
  95. private GUIContent summaryContent { get; } = new GUIContent();
  96. #endregion
  97. #region Positioning
  98. public override IEnumerable<IWidget> positionDependers => state.transitions.Select(transition => (IWidget)canvas.Widget(transition));
  99. public Rect titlePosition { get; private set; }
  100. public Rect summaryPosition { get; private set; }
  101. public Rect contentOuterPosition { get; private set; }
  102. public Rect contentBackgroundPosition { get; private set; }
  103. public Rect contentInnerPosition { get; private set; }
  104. private float targetContentOuterHeight;
  105. private float currentContentOuterHeight;
  106. private bool revealInitialized;
  107. private Rect _position;
  108. public override Rect position
  109. {
  110. get { return _position; }
  111. set
  112. {
  113. state.position = value.position;
  114. state.width = value.width;
  115. }
  116. }
  117. public override void CachePosition()
  118. {
  119. var edgeOrigin = state.position;
  120. var edgeX = edgeOrigin.x;
  121. var edgeY = edgeOrigin.y;
  122. var edgeWidth = state.width;
  123. var innerOrigin = EdgeToInnerPosition(new Rect(edgeOrigin, Vector2.zero)).position;
  124. var innerX = innerOrigin.x;
  125. var innerY = innerOrigin.y;
  126. var innerWidth = EdgeToInnerPosition(new Rect(0, 0, edgeWidth, 0)).width;
  127. var innerHeight = 0f;
  128. var y = innerY;
  129. if (showTitle)
  130. {
  131. using (LudiqGUIUtility.iconSize.Override(IconSize.Small))
  132. {
  133. titlePosition = new Rect
  134. (
  135. innerX,
  136. y,
  137. innerWidth,
  138. Styles.title.CalcHeight(titleContent, innerWidth)
  139. );
  140. y += titlePosition.height;
  141. innerHeight += titlePosition.height;
  142. }
  143. }
  144. if (showTitle && showSummary)
  145. {
  146. y += Styles.spaceBetweenTitleAndSummary;
  147. innerHeight += Styles.spaceBetweenTitleAndSummary;
  148. }
  149. if (showSummary)
  150. {
  151. summaryPosition = new Rect
  152. (
  153. innerX,
  154. y,
  155. innerWidth,
  156. Styles.summary.CalcHeight(summaryContent, innerWidth)
  157. );
  158. y += summaryPosition.height;
  159. innerHeight += summaryPosition.height;
  160. }
  161. if (showContent)
  162. {
  163. var contentInnerWidth = edgeWidth - Styles.contentBackground.padding.left - Styles.contentBackground.padding.right;
  164. targetContentOuterHeight = revealContent ? (Styles.spaceBeforeContent + Styles.contentBackground.padding.top + GetContentHeight(contentInnerWidth) + Styles.contentBackground.padding.bottom) : 0;
  165. if (!revealInitialized)
  166. {
  167. currentContentOuterHeight = targetContentOuterHeight;
  168. revealInitialized = true;
  169. }
  170. currentContentOuterHeight = Mathf.Lerp(currentContentOuterHeight, targetContentOuterHeight, canvas.repaintDeltaTime * Styles.contentRevealSpeed);
  171. if (Mathf.Abs(targetContentOuterHeight - currentContentOuterHeight) < 1)
  172. {
  173. currentContentOuterHeight = targetContentOuterHeight;
  174. }
  175. contentOuterPosition = new Rect
  176. (
  177. edgeX,
  178. y,
  179. edgeWidth,
  180. currentContentOuterHeight
  181. );
  182. contentBackgroundPosition = new Rect
  183. (
  184. 0,
  185. Styles.spaceBeforeContent,
  186. edgeWidth,
  187. currentContentOuterHeight - Styles.spaceBeforeContent
  188. );
  189. contentInnerPosition = new Rect
  190. (
  191. Styles.contentBackground.padding.left,
  192. Styles.spaceBeforeContent + Styles.contentBackground.padding.top,
  193. contentInnerWidth,
  194. contentBackgroundPosition.height - Styles.contentBackground.padding.top
  195. );
  196. y += contentOuterPosition.height;
  197. innerHeight += contentOuterPosition.height;
  198. }
  199. var edgeHeight = InnerToEdgePosition(new Rect(0, 0, 0, innerHeight)).height;
  200. _position = new Rect
  201. (
  202. edgeX,
  203. edgeY,
  204. edgeWidth,
  205. edgeHeight
  206. );
  207. }
  208. protected virtual float GetContentHeight(float width) => 0;
  209. #endregion
  210. #region Drawing
  211. protected virtual bool showTitle => true;
  212. protected virtual bool showSummary => !StringUtility.IsNullOrWhiteSpace(summary);
  213. protected virtual bool showContent => false;
  214. protected virtual NodeColorMix baseColor => NodeColor.Gray;
  215. protected override NodeColorMix color
  216. {
  217. get
  218. {
  219. if (stateDebugData.runtimeException != null)
  220. {
  221. return NodeColor.Red;
  222. }
  223. var color = baseColor;
  224. if (state.isStart)
  225. {
  226. color = NodeColor.Green;
  227. }
  228. if (stateData?.isActive ?? false)
  229. {
  230. color = NodeColor.Blue;
  231. }
  232. else if (EditorApplication.isPaused)
  233. {
  234. if (EditorTimeBinding.frame == stateDebugData.lastEnterFrame)
  235. {
  236. color = NodeColor.Blue;
  237. }
  238. }
  239. else
  240. {
  241. color.blue = Mathf.Lerp(1, 0, (EditorTimeBinding.time - stateDebugData.lastExitTime) / Styles.enterFadeDuration);
  242. }
  243. return color;
  244. }
  245. }
  246. protected override NodeShape shape => NodeShape.Square;
  247. private bool revealContent
  248. {
  249. get
  250. {
  251. switch (BoltState.Configuration.statesReveal)
  252. {
  253. case StateRevealCondition.Always:
  254. return true;
  255. case StateRevealCondition.Never:
  256. return false;
  257. case StateRevealCondition.OnHover:
  258. return isMouseOver;
  259. case StateRevealCondition.OnHoverWithAlt:
  260. return isMouseOver && e.alt;
  261. case StateRevealCondition.WhenSelected:
  262. return selection.Contains(state);
  263. case StateRevealCondition.OnHoverOrSelected:
  264. return isMouseOver || selection.Contains(state);
  265. case StateRevealCondition.OnHoverWithAltOrSelected:
  266. return isMouseOver && e.alt || selection.Contains(state);
  267. default:
  268. throw new UnexpectedEnumValueException<StateRevealCondition>(BoltState.Configuration.statesReveal);
  269. }
  270. }
  271. }
  272. private bool revealedContent;
  273. private void CheckReveal()
  274. {
  275. var revealContent = this.revealContent;
  276. if (revealContent != revealedContent)
  277. {
  278. Reposition();
  279. }
  280. revealedContent = revealContent;
  281. }
  282. protected override bool dim
  283. {
  284. get
  285. {
  286. var dim = BoltCore.Configuration.dimInactiveNodes && !analysis.isEntered;
  287. if (isMouseOver || isSelected)
  288. {
  289. dim = false;
  290. }
  291. return dim;
  292. }
  293. }
  294. public override void DrawForeground()
  295. {
  296. BeginDim();
  297. base.DrawForeground();
  298. if (showTitle)
  299. {
  300. DrawTitle();
  301. }
  302. if (showSummary)
  303. {
  304. DrawSummary();
  305. }
  306. if (showContent)
  307. {
  308. DrawContentWrapped();
  309. }
  310. EndDim();
  311. CheckReveal();
  312. }
  313. private void DrawTitle()
  314. {
  315. using (LudiqGUIUtility.iconSize.Override(IconSize.Small))
  316. {
  317. GUI.Label(titlePosition, titleContent, invertForeground ? Styles.titleInverted : Styles.title);
  318. }
  319. }
  320. private void DrawSummary()
  321. {
  322. GUI.Label(summaryPosition, summaryContent, invertForeground ? Styles.summaryInverted : Styles.summary);
  323. }
  324. private void DrawContentWrapped()
  325. {
  326. GUI.BeginClip(contentOuterPosition);
  327. DrawContentBackground();
  328. DrawContent();
  329. GUI.EndClip();
  330. }
  331. protected virtual void DrawContentBackground()
  332. {
  333. if (e.IsRepaint)
  334. {
  335. Styles.contentBackground.Draw(contentBackgroundPosition, false, false, false, false);
  336. }
  337. }
  338. protected virtual void DrawContent() { }
  339. #endregion
  340. #region Selecting
  341. public override bool canSelect => true;
  342. #endregion
  343. #region Dragging
  344. protected override bool snapToGrid => BoltCore.Configuration.snapToGrid;
  345. public override bool canDrag => true;
  346. public override void ExpandDragGroup(HashSet<IGraphElement> dragGroup)
  347. {
  348. if (BoltCore.Configuration.carryChildren)
  349. {
  350. foreach (var transition in state.outgoingTransitions)
  351. {
  352. if (dragGroup.Contains(transition.destination))
  353. {
  354. continue;
  355. }
  356. dragGroup.Add(transition.destination);
  357. canvas.Widget(transition.destination).ExpandDragGroup(dragGroup);
  358. }
  359. }
  360. }
  361. #endregion
  362. #region Deleting
  363. public override bool canDelete => true;
  364. #endregion
  365. #region Resizing
  366. public override bool canResizeHorizontal => true;
  367. #endregion
  368. #region Clipboard
  369. public override void ExpandCopyGroup(HashSet<IGraphElement> copyGroup)
  370. {
  371. copyGroup.UnionWith(state.transitions.Cast<IGraphElement>());
  372. }
  373. #endregion
  374. #region Actions
  375. protected override IEnumerable<DropdownOption> contextOptions
  376. {
  377. get
  378. {
  379. if (Application.isPlaying && reference.hasData)
  380. {
  381. if (canForceEnter)
  382. {
  383. yield return new DropdownOption((Action)ForceEnter, "Force Enter");
  384. }
  385. if (canForceExit)
  386. {
  387. yield return new DropdownOption((Action)ForceExit, "Force Exit");
  388. }
  389. }
  390. if (canToggleStart)
  391. {
  392. yield return new DropdownOption((Action)ToggleStart, "Toggle Start");
  393. }
  394. if (state.canBeSource)
  395. {
  396. yield return new DropdownOption((Action)MakeTransition, "Make Transition");
  397. }
  398. if (state.canBeSource && state.canBeDestination)
  399. {
  400. yield return new DropdownOption((Action)MakeSelfTransition, "Make Self Transition");
  401. }
  402. foreach (var baseOption in base.contextOptions)
  403. {
  404. yield return baseOption;
  405. }
  406. }
  407. }
  408. private void ForceEnter()
  409. {
  410. using (var flow = Flow.New(reference))
  411. {
  412. state.OnEnter(flow, StateEnterReason.Forced);
  413. }
  414. }
  415. private void ForceExit()
  416. {
  417. using (var flow = Flow.New(reference))
  418. {
  419. state.OnExit(flow, StateExitReason.Forced);
  420. }
  421. }
  422. protected void MakeTransition()
  423. {
  424. canvas.StartTransition(state);
  425. }
  426. protected void MakeSelfTransition()
  427. {
  428. canvas.StartTransition(state);
  429. canvas.EndTransition(state);
  430. }
  431. protected void ToggleStart()
  432. {
  433. UndoUtility.RecordEditedObject("Toggle State Start");
  434. state.isStart = !state.isStart;
  435. }
  436. #endregion
  437. public static class Styles
  438. {
  439. static Styles()
  440. {
  441. title = new GUIStyle(BoltCore.Styles.nodeLabel);
  442. title.fontSize = 12;
  443. title.alignment = TextAnchor.MiddleCenter;
  444. title.wordWrap = true;
  445. summary = new GUIStyle(BoltCore.Styles.nodeLabel);
  446. summary.fontSize = 10;
  447. summary.alignment = TextAnchor.MiddleCenter;
  448. summary.wordWrap = true;
  449. titleInverted = new GUIStyle(title);
  450. titleInverted.normal.textColor = ColorPalette.unityBackgroundDark;
  451. summaryInverted = new GUIStyle(summary);
  452. summaryInverted.normal.textColor = ColorPalette.unityBackgroundDark;
  453. contentBackground = new GUIStyle("In BigTitle");
  454. contentBackground.padding = new RectOffset(0, 0, 4, 4);
  455. }
  456. public static readonly GUIStyle title;
  457. public static readonly GUIStyle summary;
  458. public static readonly GUIStyle titleInverted;
  459. public static readonly GUIStyle summaryInverted;
  460. public static readonly GUIStyle contentBackground;
  461. public static readonly float spaceBeforeContent = 5;
  462. public static readonly float spaceBetweenTitleAndSummary = 0;
  463. public static readonly float enterFadeDuration = 0.5f;
  464. public static readonly float contentRevealSpeed = 15;
  465. }
  466. }
  467. }