VFXComponentBoard.cs 36 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049
  1. using System;
  2. using System.Linq;
  3. using System.Collections.Generic;
  4. using System.Globalization;
  5. using UnityEditor.Experimental;
  6. using UnityEditor.Experimental.GraphView;
  7. using UnityEditor.SceneManagement;
  8. using UnityEditor.UIElements;
  9. using UnityEditor.VFX.UIElements;
  10. using UnityEngine;
  11. using UnityEngine.VFX;
  12. using UnityEngine.UIElements;
  13. using PositionType = UnityEngine.UIElements.Position;
  14. namespace UnityEditor.VFX.UI
  15. {
  16. static class BoardPreferenceHelper
  17. {
  18. public enum Board
  19. {
  20. blackboard,
  21. componentBoard
  22. }
  23. const string rectPreferenceFormat = "vfx-{0}-rect";
  24. const string visiblePreferenceFormat = "vfx-{0}-visible";
  25. public static bool IsVisible(Board board, bool defaultState)
  26. {
  27. return EditorPrefs.GetBool(string.Format(visiblePreferenceFormat, board), defaultState);
  28. }
  29. public static void SetVisible(Board board, bool value)
  30. {
  31. EditorPrefs.SetBool(string.Format(visiblePreferenceFormat, board), value);
  32. }
  33. public static Rect LoadPosition(Board board, Rect defaultPosition)
  34. {
  35. string str = EditorPrefs.GetString(string.Format(rectPreferenceFormat, board));
  36. Rect blackBoardPosition = defaultPosition;
  37. if (!string.IsNullOrEmpty(str))
  38. {
  39. var rectValues = str.Split(',');
  40. if (rectValues.Length == 4)
  41. {
  42. float x, y, width, height;
  43. if (float.TryParse(rectValues[0], NumberStyles.Float, CultureInfo.InvariantCulture, out x) &&
  44. float.TryParse(rectValues[1], NumberStyles.Float, CultureInfo.InvariantCulture, out y) &&
  45. float.TryParse(rectValues[2], NumberStyles.Float, CultureInfo.InvariantCulture, out width) &&
  46. float.TryParse(rectValues[3], NumberStyles.Float, CultureInfo.InvariantCulture, out height))
  47. {
  48. blackBoardPosition = new Rect(x, y, width, height);
  49. }
  50. }
  51. }
  52. return blackBoardPosition;
  53. }
  54. public static void SavePosition(Board board, Rect r)
  55. {
  56. EditorPrefs.SetString(string.Format(rectPreferenceFormat, board), string.Format(CultureInfo.InvariantCulture, "{0},{1},{2},{3}", r.x, r.y, r.width, r.height));
  57. }
  58. public static readonly Vector2 sizeMargin = Vector2.one * 30;
  59. public static bool ValidatePosition(GraphElement element, VFXView view, Rect defaultPosition)
  60. {
  61. Rect viewrect = view.contentRect;
  62. Rect rect = element.GetPosition();
  63. bool changed = false;
  64. if (!viewrect.Contains(rect.position))
  65. {
  66. Vector2 newPosition = defaultPosition.position;
  67. if (!viewrect.Contains(defaultPosition.position))
  68. {
  69. newPosition = sizeMargin;
  70. }
  71. rect.position = newPosition;
  72. changed = true;
  73. }
  74. Vector2 maxSizeInView = viewrect.max - rect.position - sizeMargin;
  75. float newWidth = Mathf.Max(element.resolvedStyle.minWidth.value, Mathf.Min(rect.width, maxSizeInView.x));
  76. float newHeight = Mathf.Max(element.resolvedStyle.minHeight.value, Mathf.Min(rect.height, maxSizeInView.y));
  77. if (Mathf.Abs(newWidth - rect.width) > 1)
  78. {
  79. rect.width = newWidth;
  80. changed = true;
  81. }
  82. if (Mathf.Abs(newHeight - rect.height) > 1)
  83. {
  84. rect.height = newHeight;
  85. changed = true;
  86. }
  87. if (changed)
  88. {
  89. element.SetPosition(rect);
  90. }
  91. return false;
  92. }
  93. }
  94. class VFXComponentBoard : GraphElement, IControlledElement<VFXViewController>, IVFXMovable, IVFXResizable
  95. {
  96. VFXViewController m_Controller;
  97. Controller IControlledElement.controller
  98. {
  99. get { return m_Controller; }
  100. }
  101. public VFXViewController controller
  102. {
  103. get { return m_Controller; }
  104. set
  105. {
  106. if (m_Controller != value)
  107. {
  108. if (m_Controller != null)
  109. {
  110. m_Controller.UnregisterHandler(this);
  111. }
  112. Clear();
  113. m_Controller = value;
  114. if (m_Controller != null)
  115. {
  116. m_Controller.RegisterHandler(this);
  117. }
  118. }
  119. }
  120. }
  121. VFXView m_View;
  122. VFXUIDebug m_DebugUI;
  123. public VFXComponentBoard(VFXView view)
  124. {
  125. m_View = view;
  126. var tpl = VFXView.LoadUXML("VFXComponentBoard");
  127. tpl.CloneTree(contentContainer);
  128. contentContainer.AddStyleSheetPath("VFXComponentBoard");
  129. m_RootElement = this.Query<VisualElement>("component-container");
  130. m_SubtitleIcon = this.Query<Image>("subTitle-icon");
  131. m_Subtitle = this.Query<Label>("subTitleLabel");
  132. m_SubtitleIcon.image = EditorGUIUtility.LoadIcon(EditorResources.iconsPath + "console.warnicon.sml.png");
  133. m_Stop = this.Query<Button>("stop");
  134. m_Stop.clickable.clicked += EffectStop;
  135. m_Play = this.Query<Button>("play");
  136. m_Play.clickable.clicked += EffectPlay;
  137. m_Step = this.Query<Button>("step");
  138. m_Step.clickable.clicked += EffectStep;
  139. m_Restart = this.Query<Button>("restart");
  140. m_Restart.clickable.clicked += EffectRestart;
  141. m_PlayRateSlider = this.Query<Slider>("play-rate-slider");
  142. m_PlayRateSlider.lowValue = Mathf.Pow(VisualEffectControl.minSlider, 1 / VisualEffectControl.sliderPower);
  143. m_PlayRateSlider.highValue = Mathf.Pow(VisualEffectControl.maxSlider, 1 / VisualEffectControl.sliderPower);
  144. m_PlayRateSlider.RegisterValueChangedCallback(evt => OnEffectSlider(evt.newValue));
  145. m_PlayRateField = this.Query<IntegerField>("play-rate-field");
  146. m_PlayRateField.RegisterCallback<ChangeEvent<int>>(OnPlayRateField);
  147. m_PlayRateMenu = this.Query<Button>("play-rate-menu");
  148. m_PlayRateMenu.AddStyleSheetPathWithSkinVariant("VFXControls");
  149. m_PlayRateMenu.clickable.clicked += OnPlayRateMenu;
  150. m_ParticleCount = this.Query<Label>("particle-count");
  151. Button button = this.Query<Button>("on-play-button");
  152. button.clickable.clicked += () => SendEvent(VisualEffectAsset.PlayEventName);
  153. button = this.Query<Button>("on-stop-button");
  154. button.clickable.clicked += () => SendEvent(VisualEffectAsset.StopEventName);
  155. m_EventsContainer = this.Query("events-container");
  156. m_DebugModes = this.Query<Button>("debug-modes");
  157. m_DebugModes.clickable.clicked += OnDebugModes;
  158. m_RecordBoundsButton = this.Query<Button>("record");
  159. m_RecordBoundsImage = this.Query<Image>("record-icon");
  160. m_RecordBoundsButton.clickable.clicked += OnRecordBoundsButton;
  161. m_RecordIcon = VFXView.LoadImage("d_Record");
  162. m_BoundsActionLabel = this.Query<Label>("bounds-label");
  163. m_BoundsToolContainer = this.Query("bounds-tool-container");
  164. m_BackgroundDefaultColor = m_BoundsToolContainer.style.backgroundColor;
  165. m_SystemBoundsContainer = this.Query<VFXBoundsSelector>("system-bounds-container");
  166. m_SystemBoundsContainer.RegisterCallback<MouseDownEvent>(OnMouseClickBoundsContainer);
  167. m_ApplyBoundsButton = this.Query<Button>("apply-bounds-button");
  168. m_ApplyBoundsButton.clickable.clicked += ApplyCurrentBounds;
  169. Detach();
  170. this.AddManipulator(new Dragger { clampToParentEdges = true });
  171. capabilities |= Capabilities.Movable;
  172. RegisterCallback<MouseDownEvent>(OnMouseClick);
  173. style.position = PositionType.Absolute;
  174. SetPosition(BoardPreferenceHelper.LoadPosition(BoardPreferenceHelper.Board.componentBoard, defaultRect));
  175. }
  176. public void ValidatePosition()
  177. {
  178. BoardPreferenceHelper.ValidatePosition(this, m_View, defaultRect);
  179. }
  180. static readonly Rect defaultRect = new Rect(200, 100, 300, 300);
  181. public override Rect GetPosition()
  182. {
  183. return new Rect(resolvedStyle.left, resolvedStyle.top, resolvedStyle.width, resolvedStyle.height);
  184. }
  185. public override void SetPosition(Rect newPos)
  186. {
  187. style.left = newPos.xMin;
  188. style.top = newPos.yMin;
  189. style.width = newPos.width;
  190. style.height = newPos.height;
  191. }
  192. void OnMouseClick(MouseDownEvent e)
  193. {
  194. m_View.SetBoardToFront(this);
  195. }
  196. void OnMouseClickBoundsContainer(MouseDownEvent e)
  197. {
  198. if (e.button == (int)MouseButton.LeftMouse)
  199. {
  200. bool needClearSelection = false;
  201. foreach (var elem in m_SystemBoundsContainer.Children())
  202. {
  203. var systemBound = elem as VFXComponentBoardBoundsSystemUI;
  204. if (systemBound != null)
  205. needClearSelection |= systemBound.Unselect();
  206. }
  207. }
  208. }
  209. void OnPlayRateMenu()
  210. {
  211. GenericMenu menu = new GenericMenu();
  212. foreach (var value in VisualEffectControl.setPlaybackValues)
  213. {
  214. menu.AddItem(EditorGUIUtility.TextContent(string.Format("{0}%", value)), false, SetPlayRate, value);
  215. }
  216. menu.DropDown(m_PlayRateMenu.worldBound);
  217. }
  218. void OnPlayRateField(ChangeEvent<int> e)
  219. {
  220. SetPlayRate(e.newValue);
  221. }
  222. void SetPlayRate(object value)
  223. {
  224. if (m_AttachedComponent == null)
  225. return;
  226. float rate = (float)((int)value) * VisualEffectControl.valueToPlayRate;
  227. m_AttachedComponent.playRate = rate;
  228. UpdatePlayRate();
  229. }
  230. void OnDebugModes()
  231. {
  232. GenericMenu menu = new GenericMenu();
  233. foreach (VFXUIDebug.Modes mode in Enum.GetValues(typeof(VFXUIDebug.Modes)))
  234. {
  235. menu.AddItem(EditorGUIUtility.TextContent(mode.ToString()), false, SetDebugMode, mode);
  236. }
  237. menu.DropDown(m_DebugModes.worldBound);
  238. }
  239. void SetDebugMode(object mode)
  240. {
  241. m_DebugUI.SetDebugMode((VFXUIDebug.Modes)mode, this);
  242. }
  243. private VFXBoundsRecorder m_BoundsRecorder;
  244. void OnRecordBoundsButton()
  245. {
  246. if (m_BoundsRecorder != null)
  247. {
  248. m_BoundsRecorder.ToggleRecording();
  249. }
  250. UpdateRecordingButton();
  251. }
  252. void UpdateRecordingButton()
  253. {
  254. bool hasSomethingToRecord = m_BoundsRecorder != null && m_BoundsRecorder.NeedsAnyToBeRecorded();
  255. m_RecordBoundsButton.SetEnabled(hasSomethingToRecord);
  256. if (hasSomethingToRecord && m_BoundsRecorder.isRecording)
  257. {
  258. float remainder = Time.realtimeSinceStartup % 1.0f;
  259. if (remainder < 0.22f)
  260. {
  261. m_RecordBoundsImage.style.backgroundImage = null;
  262. }
  263. else
  264. {
  265. m_RecordBoundsImage.style.backgroundImage = m_RecordIcon;
  266. }
  267. m_BoundsToolContainer.style.backgroundColor = m_BackgroundRecordingColor;
  268. m_BoundsActionLabel.text = "Recording in progress...";
  269. }
  270. else
  271. {
  272. m_RecordBoundsImage.style.backgroundImage = m_RecordIcon;
  273. m_BoundsToolContainer.style.backgroundColor = m_BackgroundDefaultColor;
  274. m_BoundsActionLabel.text = "Bounds Recording";
  275. }
  276. if (!hasSomethingToRecord && m_BoundsRecorder.isRecording)
  277. m_BoundsRecorder.ToggleRecording();
  278. }
  279. public void DeactivateBoundsRecordingIfNeeded()
  280. {
  281. if (m_BoundsRecorder != null && m_BoundsRecorder.isRecording)
  282. m_BoundsRecorder.ToggleRecording();
  283. }
  284. void ApplyCurrentBounds()
  285. {
  286. if (m_View.IsAssetEditable())
  287. m_BoundsRecorder.ApplyCurrentBounds();
  288. }
  289. void DeleteBoundsRecorder()
  290. {
  291. if (m_BoundsRecorder != null)
  292. {
  293. m_BoundsRecorder.isRecording = false;
  294. m_BoundsRecorder = null;
  295. }
  296. }
  297. void UpdateBoundsRecorder()
  298. {
  299. if (m_AttachedComponent != null && m_View.controller.graph != null)
  300. {
  301. controller.RecompileExpressionGraphIfNeeded();
  302. bool wasRecording = false;
  303. if (m_BoundsRecorder != null)
  304. {
  305. wasRecording = m_BoundsRecorder.isRecording;
  306. m_BoundsRecorder.CleanUp();
  307. }
  308. m_BoundsRecorder = new VFXBoundsRecorder(m_AttachedComponent, m_View);
  309. if (wasRecording && !m_View.controller.isReentrant) //If this is called during an Undo/Redo, toggling the recording will cause a reentrant invalidation
  310. {
  311. m_BoundsRecorder.ToggleRecording();
  312. }
  313. var systemNames = m_BoundsRecorder.systemNames;
  314. if (m_SystemBoundsContainer != null)
  315. {
  316. foreach (var elem in m_SystemBoundsContainer.Children())
  317. {
  318. if (elem is VFXComponentBoardBoundsSystemUI)
  319. {
  320. (elem as VFXComponentBoardBoundsSystemUI).ReleaseBoundsRecorder();
  321. }
  322. }
  323. m_SystemBoundsContainer.Clear();
  324. m_SystemBoundsContainer.AddStyleSheetPath("VFXComponentBoard-bounds-list");
  325. }
  326. foreach (var system in systemNames)
  327. {
  328. var tpl = VFXView.LoadUXML("VFXComponentBoard-bounds-list");
  329. tpl.CloneTree(m_SystemBoundsContainer);
  330. VFXComponentBoardBoundsSystemUI newUI = m_SystemBoundsContainer.Children().Last() as VFXComponentBoardBoundsSystemUI;
  331. if (newUI != null)
  332. {
  333. newUI.Setup(system, m_BoundsRecorder);
  334. }
  335. }
  336. }
  337. }
  338. void OnEffectSlider(float f)
  339. {
  340. if (m_AttachedComponent != null)
  341. {
  342. m_AttachedComponent.playRate = VisualEffectControl.valueToPlayRate * Mathf.Pow(f, VisualEffectControl.sliderPower);
  343. UpdatePlayRate();
  344. }
  345. }
  346. void EffectStop()
  347. {
  348. if (m_AttachedComponent != null)
  349. m_AttachedComponent.ControlStop();
  350. if (m_DebugUI != null)
  351. m_DebugUI.Notify(VFXUIDebug.Events.VFXStop);
  352. }
  353. void EffectPlay()
  354. {
  355. if (m_AttachedComponent != null)
  356. m_AttachedComponent.ControlPlayPause();
  357. if (m_DebugUI != null)
  358. m_DebugUI.Notify(VFXUIDebug.Events.VFXPlayPause);
  359. }
  360. void EffectStep()
  361. {
  362. if (m_AttachedComponent != null)
  363. m_AttachedComponent.ControlStep();
  364. if (m_DebugUI != null)
  365. m_DebugUI.Notify(VFXUIDebug.Events.VFXStep);
  366. }
  367. void EffectRestart()
  368. {
  369. if (m_AttachedComponent != null)
  370. m_AttachedComponent.ControlRestart();
  371. if (m_DebugUI != null)
  372. m_DebugUI.Notify(VFXUIDebug.Events.VFXReset);
  373. }
  374. public void OnVisualEffectComponentChanged(IEnumerable<VisualEffect> visualEffects)
  375. {
  376. if (m_AttachedComponent != null
  377. && visualEffects.Contains(m_AttachedComponent)
  378. && m_AttachedComponent.visualEffectAsset != controller.graph.visualEffectResource.asset)
  379. {
  380. //The Visual Effect Asset has been changed and is no longer valid, we don't want to modify capacity on the wrong graph. We have to detach.
  381. m_View.attachedComponent = null;
  382. }
  383. }
  384. VisualEffect m_AttachedComponent;
  385. public VisualEffect GetAttachedComponent()
  386. {
  387. return m_AttachedComponent;
  388. }
  389. bool m_LastKnownPauseState;
  390. void UpdatePlayButton()
  391. {
  392. if (m_AttachedComponent == null)
  393. return;
  394. if (m_LastKnownPauseState != m_AttachedComponent.pause)
  395. {
  396. m_LastKnownPauseState = m_AttachedComponent.pause;
  397. if (m_LastKnownPauseState)
  398. {
  399. m_Play.AddToClassList("paused");
  400. }
  401. else
  402. {
  403. m_Play.RemoveFromClassList("paused");
  404. }
  405. }
  406. }
  407. public void Detach()
  408. {
  409. m_RootElement.SetEnabled(false);
  410. m_Subtitle.text = "Select a Game Object running this VFX";
  411. m_SubtitleIcon.style.display = DisplayStyle.Flex;
  412. if (m_AttachedComponent != null)
  413. {
  414. m_AttachedComponent.playRate = 1;
  415. m_AttachedComponent.pause = false;
  416. }
  417. m_AttachedComponent = null;
  418. if (m_UpdateItem != null)
  419. {
  420. m_UpdateItem.Pause();
  421. }
  422. if (m_EventsContainer != null)
  423. m_EventsContainer.Clear();
  424. m_Events.Clear();
  425. if (m_DebugUI != null)
  426. {
  427. m_DebugUI.SetDebugMode(VFXUIDebug.Modes.None, this, true);
  428. }
  429. DeleteBoundsRecorder();
  430. RefreshInitializeErrors();
  431. }
  432. public void RefreshInitializeErrors()
  433. {
  434. var viewContexts = m_View.GetAllContexts();
  435. List<VFXContextUI> contextsToRefresh = new List<VFXContextUI>();
  436. foreach (var context in viewContexts)
  437. {
  438. if (context.controller.model is VFXBasicInitialize)
  439. {
  440. contextsToRefresh.Add(context);
  441. }
  442. }
  443. foreach (var context in contextsToRefresh)
  444. {
  445. context.controller.model.RefreshErrors(m_View.controller.graph);
  446. }
  447. }
  448. public bool Attach(VisualEffect effect = null)
  449. {
  450. VisualEffect target = effect != null ? effect : Selection.activeGameObject?.GetComponent<VisualEffect>();
  451. if (target != null && m_View.controller?.graph != null && m_AttachedComponent != target)
  452. {
  453. if (m_AttachedComponent != null)
  454. {
  455. m_AttachedComponent.playRate = 1;
  456. }
  457. m_AttachedComponent = target;
  458. m_Subtitle.text = m_AttachedComponent.name;
  459. m_LastKnownPauseState = !m_AttachedComponent.pause;
  460. m_AttachedComponent.playRate = m_LastKnownPlayRate >= 0 ? m_LastKnownPlayRate : 1;
  461. UpdatePlayButton();
  462. if (m_UpdateItem == null)
  463. m_UpdateItem = schedule.Execute(Update).Every(100);
  464. else
  465. m_UpdateItem.Resume();
  466. UpdateEventList();
  467. var debugMode = VFXUIDebug.Modes.None;
  468. if (m_DebugUI != null)
  469. {
  470. debugMode = m_DebugUI.GetDebugMode();
  471. m_DebugUI.Clear();
  472. }
  473. m_DebugUI = new VFXUIDebug(m_View);
  474. m_DebugUI.SetVisualEffect(m_AttachedComponent);
  475. m_DebugUI.SetDebugMode(debugMode, this, true);
  476. m_RootElement.SetEnabled(true);
  477. m_SubtitleIcon.style.display = DisplayStyle.None;
  478. UpdateBoundsRecorder();
  479. UpdateRecordingButton();
  480. RefreshInitializeErrors();
  481. return true;
  482. }
  483. return false;
  484. }
  485. public void SendEvent(string name)
  486. {
  487. if (m_AttachedComponent != null)
  488. {
  489. m_AttachedComponent.SendEvent(name);
  490. }
  491. }
  492. IVisualElementScheduledItem m_UpdateItem;
  493. float m_LastKnownPlayRate = -1;
  494. int m_LastKnownParticleCount = -1;
  495. void Update()
  496. {
  497. if (m_AttachedComponent == null || controller == null)
  498. {
  499. Detach();
  500. return;
  501. }
  502. string path = m_AttachedComponent.name;
  503. UnityEngine.Transform current = m_AttachedComponent.transform.parent;
  504. while (current != null)
  505. {
  506. path = current.name + " > " + path;
  507. current = current.parent;
  508. }
  509. if (EditorSceneManager.loadedSceneCount > 1)
  510. {
  511. path = m_AttachedComponent.gameObject.scene.name + " : " + path;
  512. }
  513. if (m_Subtitle.text != path)
  514. m_Subtitle.text = path;
  515. if (m_ParticleCount != null)
  516. {
  517. int newParticleCount = 0;//m_AttachedComponent.aliveParticleCount
  518. if (m_LastKnownParticleCount != newParticleCount)
  519. {
  520. m_LastKnownParticleCount = newParticleCount;
  521. m_ParticleCount.text = m_LastKnownParticleCount.ToString();
  522. }
  523. }
  524. UpdatePlayRate();
  525. UpdatePlayButton();
  526. UpdateBoundsModes();
  527. m_ApplyBoundsButton.SetEnabled(m_BoundsRecorder.bounds.Any() && m_View.IsAssetEditable());
  528. UpdateRecordingButton();
  529. }
  530. void UpdatePlayRate()
  531. {
  532. if (Math.Abs(m_LastKnownPlayRate - m_AttachedComponent.playRate) > 1e-4)
  533. {
  534. m_LastKnownPlayRate = m_AttachedComponent.playRate;
  535. SetPlayrateSlider(m_AttachedComponent.playRate);
  536. }
  537. }
  538. void SetPlayrateSlider(float value)
  539. {
  540. float playRateValue = value * VisualEffectControl.playRateToValue;
  541. m_PlayRateSlider.value = Mathf.Pow(playRateValue, 1 / VisualEffectControl.sliderPower);
  542. if (m_PlayRateField != null && !m_PlayRateField.HasFocus())
  543. m_PlayRateField.value = Mathf.RoundToInt(playRateValue);
  544. }
  545. void ToggleAttach()
  546. {
  547. if (!object.ReferenceEquals(m_AttachedComponent, null))
  548. {
  549. Detach();
  550. }
  551. else
  552. {
  553. Attach();
  554. }
  555. }
  556. void Select()
  557. {
  558. if (m_AttachedComponent != null)
  559. {
  560. Selection.activeObject = m_AttachedComponent;
  561. }
  562. }
  563. VisualElement m_EventsContainer;
  564. VisualElement m_RootElement;
  565. Label m_Subtitle;
  566. Image m_SubtitleIcon;
  567. Button m_Stop;
  568. Button m_Play;
  569. Button m_Step;
  570. Button m_Restart;
  571. Slider m_PlayRateSlider;
  572. IntegerField m_PlayRateField;
  573. Button m_PlayRateMenu;
  574. Button m_DebugModes;
  575. Button m_RecordBoundsButton;
  576. Image m_RecordBoundsImage;
  577. Texture2D m_RecordIcon;
  578. Button m_ApplyBoundsButton;
  579. VFXBoundsSelector m_SystemBoundsContainer;
  580. VisualElement m_BoundsToolContainer;
  581. Label m_BoundsActionLabel;
  582. StyleColor m_BackgroundRecordingColor = new StyleColor(new Color(0.325f, 0.125f, 0.125f));
  583. StyleColor m_BackgroundDefaultColor;
  584. Label m_ParticleCount;
  585. public new void Clear()
  586. {
  587. Detach();
  588. }
  589. void IControlledElement.OnControllerChanged(ref ControllerChangedEvent e)
  590. {
  591. UpdateEventList();
  592. if (e.change != VFXViewController.Change.ui)
  593. UpdateBoundsRecorder();
  594. }
  595. static readonly string[] staticEventNames = new string[] { VisualEffectAsset.PlayEventName, VisualEffectAsset.StopEventName };
  596. static bool IsDefaultEvent(string evt)
  597. {
  598. return evt == VisualEffectAsset.PlayEventName || evt == VisualEffectAsset.StopEventName;
  599. }
  600. IEnumerable<string> GetEventNames()
  601. {
  602. return controller?.contexts.SelectMany(x => this.RecurseGetEventNames(x.model)) ?? Enumerable.Empty<string>();
  603. }
  604. IEnumerable<string> RecurseGetEventNames(VFXContext context)
  605. {
  606. switch (context)
  607. {
  608. case VFXBasicEvent basicEvent when !IsDefaultEvent(name):
  609. yield return basicEvent.eventName;
  610. break;
  611. case VFXSubgraphContext subgraphContext when subgraphContext.subChildren != null:
  612. {
  613. foreach (var eventName in subgraphContext.subChildren.OfType<VFXContext>().SelectMany(RecurseGetEventNames))
  614. {
  615. yield return eventName;
  616. }
  617. break;
  618. }
  619. }
  620. }
  621. public void UpdateEventList()
  622. {
  623. if (m_AttachedComponent == null)
  624. {
  625. if (m_EventsContainer != null)
  626. m_EventsContainer.Clear();
  627. m_Events.Clear();
  628. }
  629. else
  630. {
  631. var eventNames = GetEventNames().ToArray();
  632. foreach (var removed in m_Events.Keys.Except(eventNames).ToArray())
  633. {
  634. var ui = m_Events[removed];
  635. m_EventsContainer.Remove(ui);
  636. m_Events.Remove(removed);
  637. }
  638. foreach (var added in eventNames.Except(m_Events.Keys).ToArray())
  639. {
  640. var tpl = VFXView.LoadUXML("VFXComponentBoard-event");
  641. tpl.CloneTree(m_EventsContainer);
  642. VFXComponentBoardEventUI newUI = m_EventsContainer.Children().Last() as VFXComponentBoardEventUI;
  643. if (newUI != null)
  644. {
  645. newUI.Setup();
  646. newUI.name = added;
  647. m_Events.Add(added, newUI);
  648. }
  649. }
  650. if (!m_Events.Values.Any(t => t.nameHasFocus))
  651. {
  652. SortEventList();
  653. }
  654. }
  655. }
  656. internal void ResetPlayRate()
  657. {
  658. m_LastKnownPlayRate = -1f;
  659. SetPlayrateSlider(1f);
  660. }
  661. void SortEventList()
  662. {
  663. var eventNames = m_Events.Keys.OrderBy(t => t);
  664. //Sort events
  665. VFXComponentBoardEventUI prev = null;
  666. foreach (var eventName in eventNames)
  667. {
  668. VFXComponentBoardEventUI current = m_Events[eventName];
  669. if (current != null)
  670. {
  671. if (prev == null)
  672. {
  673. current.SendToBack();
  674. }
  675. else
  676. {
  677. current.PlaceInFront(prev);
  678. }
  679. prev = current;
  680. }
  681. }
  682. }
  683. void UpdateBoundsModes()
  684. {
  685. bool systemNamesChanged = false;
  686. foreach (var elem in m_SystemBoundsContainer.Children())
  687. {
  688. VFXComponentBoardBoundsSystemUI boundsModeElem = elem as VFXComponentBoardBoundsSystemUI;
  689. if (boundsModeElem != null)
  690. {
  691. if (boundsModeElem.HasSystemBeenRenamed())
  692. {
  693. systemNamesChanged = true;
  694. break;
  695. }
  696. boundsModeElem.UpdateLabel();
  697. }
  698. }
  699. if (systemNamesChanged)
  700. UpdateBoundsRecorder();
  701. }
  702. Dictionary<string, VFXComponentBoardEventUI> m_Events = new Dictionary<string, VFXComponentBoardEventUI>();
  703. public override void UpdatePresenterPosition()
  704. {
  705. BoardPreferenceHelper.SavePosition(BoardPreferenceHelper.Board.componentBoard, GetPosition());
  706. }
  707. public void OnMoved()
  708. {
  709. BoardPreferenceHelper.SavePosition(BoardPreferenceHelper.Board.componentBoard, GetPosition());
  710. }
  711. void IVFXResizable.OnStartResize() { }
  712. public void OnResized()
  713. {
  714. BoardPreferenceHelper.SavePosition(BoardPreferenceHelper.Board.componentBoard, GetPosition());
  715. }
  716. }
  717. class VFXComponentBoardEventUIFactory : UxmlFactory<VFXComponentBoardEventUI>
  718. { }
  719. class VFXComponentBoardEventUI : VisualElement
  720. {
  721. public VFXComponentBoardEventUI()
  722. {
  723. }
  724. public void Setup()
  725. {
  726. m_EventName = this.Query<TextField>("event-name");
  727. m_EventName.isDelayed = true;
  728. m_EventName.RegisterCallback<ChangeEvent<string>>(OnChangeName);
  729. m_EventSend = this.Query<Button>("event-send");
  730. m_EventSend.clickable.clicked += OnSend;
  731. }
  732. void OnChangeName(ChangeEvent<string> e)
  733. {
  734. var board = GetFirstAncestorOfType<VFXComponentBoard>();
  735. if (board != null)
  736. {
  737. board.controller.ChangeEventName(m_Name, e.newValue);
  738. }
  739. }
  740. public bool nameHasFocus
  741. {
  742. get { return m_EventName.HasFocus(); }
  743. }
  744. public new string name
  745. {
  746. get
  747. {
  748. return m_Name;
  749. }
  750. set
  751. {
  752. m_Name = value;
  753. if (m_EventName != null)
  754. {
  755. if (!m_EventName.HasFocus())
  756. m_EventName.SetValueWithoutNotify(m_Name);
  757. }
  758. }
  759. }
  760. string m_Name;
  761. TextField m_EventName;
  762. Button m_EventSend;
  763. void OnSend()
  764. {
  765. var board = GetFirstAncestorOfType<VFXComponentBoard>();
  766. if (board != null)
  767. {
  768. board.SendEvent(m_Name);
  769. }
  770. }
  771. }
  772. class VFXComponentBoardBoundsSystemUIFactory : UxmlFactory<VFXComponentBoardBoundsSystemUI>
  773. { }
  774. class VFXComponentBoardBoundsSystemUI : VisualElement
  775. {
  776. public VFXComponentBoardBoundsSystemUI()
  777. {
  778. }
  779. ~VFXComponentBoardBoundsSystemUI()
  780. {
  781. if (m_BoundsRecorder != null)
  782. {
  783. m_BoundsRecorder = null;
  784. }
  785. }
  786. public void Setup(string systemName, VFXBoundsRecorder boundsRecorder)
  787. {
  788. m_BoundsRecorder = boundsRecorder;
  789. m_CurrentMode = m_BoundsRecorder.GetSystemBoundsSettingMode(systemName);
  790. m_SystemName = systemName;
  791. m_SystemNameButton = this.Query<VFXBoundsRecorderField>("system-field");
  792. var initContextUI = m_BoundsRecorder.GetInitContextController(m_SystemName);
  793. m_SystemNameButton.Setup(initContextUI, m_BoundsRecorder.view);
  794. m_SystemNameButton.text = m_SystemName;
  795. InitBoundsModeElement();
  796. m_Colors = new Dictionary<string, StyleColor>()
  797. {
  798. {"included", m_SystemNameButton.style.color},
  799. {"excluded", new StyleColor(Color.gray * 0.8f) }
  800. };
  801. if (!m_BoundsRecorder.NeedsToBeRecorded(m_SystemName, out VFXBoundsRecorder.ExclusionCause cause))
  802. {
  803. m_SystemNameButton.text = $"{m_SystemName} {VFXBoundsRecorder.exclusionCauseString[cause]}";
  804. m_SystemNameButton.tooltip =
  805. $"This system will not be taken into account in the recording because {VFXBoundsRecorder.exclusionCauseTooltip[cause]}";
  806. m_SystemNameButton.style.color = m_Colors["excluded"];
  807. m_SystemNameButton.SetEnabled(false);
  808. }
  809. }
  810. void InitBoundsModeElement()
  811. {
  812. m_BoundsMode = new VFXEnumField(s_EmptyEnumLabel, typeof(BoundsSettingMode));
  813. m_BoundsMode.OnValueChanged += OnValueChanged;
  814. m_BoundsMode.SetValue((int)m_CurrentMode);
  815. m_BoundsMode.AddToClassList("bounds-mode");
  816. Add(m_BoundsMode);
  817. }
  818. private List<string> m_BoundsModes = new List<string> { "Manual", "Recorded", "Automatic" };
  819. public void UpdateLabel()
  820. {
  821. m_CurrentMode = m_BoundsRecorder.GetSystemBoundsSettingMode(m_SystemName);
  822. m_BoundsMode.SetValue((int)m_CurrentMode);
  823. OnValueChanged();
  824. if (!m_BoundsRecorder.NeedsToBeRecorded(m_SystemName, out VFXBoundsRecorder.ExclusionCause cause))
  825. {
  826. m_SystemNameButton.text = $"{m_SystemName} {VFXBoundsRecorder.exclusionCauseString[cause]}";
  827. m_SystemNameButton.tooltip =
  828. $"This system will not be taken into account in the recording because {VFXBoundsRecorder.exclusionCauseTooltip[cause]}";
  829. m_SystemNameButton.style.color = m_Colors["excluded"];
  830. m_SystemNameButton.SetEnabled(false);
  831. }
  832. else
  833. {
  834. m_SystemNameButton.text = m_SystemName;
  835. m_SystemNameButton.tooltip = "";
  836. m_SystemNameButton.SetEnabled(true);
  837. m_SystemNameButton.style.color = m_Colors["included"];
  838. }
  839. }
  840. public bool HasSystemBeenRenamed()
  841. {
  842. return !m_BoundsRecorder.systemNames.Contains(m_SystemName);
  843. }
  844. void SetSystemBoundMode(object mode)
  845. {
  846. m_CurrentMode = (BoundsSettingMode)mode;
  847. m_BoundsMode.SetValue((int)mode);
  848. m_BoundsRecorder.ModifyMode(m_SystemName, (BoundsSettingMode)mode);
  849. }
  850. void OnValueChanged()
  851. {
  852. if (m_CurrentMode != (BoundsSettingMode)m_BoundsMode.value)
  853. {
  854. m_CurrentMode = (BoundsSettingMode)m_BoundsMode.value;
  855. m_BoundsRecorder.ModifyMode(m_SystemName, m_CurrentMode);
  856. }
  857. }
  858. public void ReleaseBoundsRecorder()
  859. {
  860. m_BoundsRecorder.isRecording = false;
  861. m_BoundsRecorder = null;
  862. }
  863. public bool Unselect()
  864. {
  865. return m_SystemNameButton.Unselect();
  866. }
  867. string m_SystemName;
  868. VFXBoundsRecorderField m_SystemNameButton;
  869. VFXEnumField m_BoundsMode;
  870. BoundsSettingMode m_CurrentMode;
  871. VFXBoundsRecorder m_BoundsRecorder;
  872. Dictionary<string, StyleColor> m_Colors;
  873. private static Label s_EmptyEnumLabel = new Label();
  874. static class BoundsSystemContents
  875. {
  876. public static Dictionary<BoundsSettingMode, GUIContent> modesContent =
  877. new Dictionary<BoundsSettingMode, GUIContent>()
  878. {
  879. {
  880. BoundsSettingMode.Automatic,
  881. new GUIContent(BoundsSettingMode.Automatic.ToString(),
  882. "Systems with the Automatic bounds setting will not be affected by the recording.")
  883. },
  884. {
  885. BoundsSettingMode.Manual,
  886. new GUIContent(BoundsSettingMode.Manual.ToString(),
  887. "Systems with the Manual bounds setting will not be affected by the recording.")
  888. },
  889. {
  890. BoundsSettingMode.Recorded,
  891. new GUIContent(BoundsSettingMode.Recorded.ToString(),
  892. "")
  893. },
  894. };
  895. }
  896. }
  897. }