暫無描述
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

StickyNote.cs 16KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469
  1. using System.Collections.Generic;
  2. using UnityEditor.Experimental.GraphView;
  3. using UnityEngine;
  4. using UnityEngine.UIElements;
  5. using UnityEditor.ShaderGraph.Drawing.Interfaces;
  6. namespace UnityEditor.ShaderGraph.Drawing
  7. {
  8. class StickyNodeChangeEvent : EventBase<StickyNodeChangeEvent>
  9. {
  10. public static StickyNodeChangeEvent GetPooled(StickyNote target, Change change)
  11. {
  12. var evt = GetPooled();
  13. evt.target = target;
  14. evt.change = change;
  15. return evt;
  16. }
  17. public enum Change
  18. {
  19. title,
  20. contents,
  21. theme,
  22. textSize,
  23. }
  24. public Change change { get; protected set; }
  25. }
  26. class StickyNote : GraphElement, ISGResizable
  27. {
  28. GraphData m_Graph;
  29. public new StickyNoteData userData
  30. {
  31. get => (StickyNoteData)base.userData;
  32. set => base.userData = value;
  33. }
  34. public enum Theme
  35. {
  36. Classic,
  37. Black,
  38. Orange,
  39. Green,
  40. Blue,
  41. Red,
  42. Purple,
  43. Teal
  44. }
  45. Theme m_Theme = Theme.Classic;
  46. public Theme theme
  47. {
  48. get
  49. {
  50. return m_Theme;
  51. }
  52. set
  53. {
  54. if (m_Theme != value)
  55. {
  56. m_Theme = value;
  57. UpdateThemeClasses();
  58. }
  59. }
  60. }
  61. public enum TextSize
  62. {
  63. Small,
  64. Medium,
  65. Large,
  66. Huge
  67. }
  68. TextSize m_TextSize = TextSize.Medium;
  69. public TextSize textSize
  70. {
  71. get { return m_TextSize; }
  72. set
  73. {
  74. if (m_TextSize != value)
  75. {
  76. m_TextSize = value;
  77. UpdateSizeClasses();
  78. }
  79. }
  80. }
  81. public virtual void OnStartResize()
  82. {
  83. }
  84. public virtual void OnResized()
  85. {
  86. userData.position = new Rect(resolvedStyle.left, resolvedStyle.top, style.width.value.value, style.height.value.value);
  87. }
  88. public bool CanResizePastParentBounds()
  89. {
  90. return true;
  91. }
  92. Vector2 AllExtraSpace(VisualElement element)
  93. {
  94. return new Vector2(
  95. element.resolvedStyle.marginLeft + element.resolvedStyle.marginRight + element.resolvedStyle.paddingLeft + element.resolvedStyle.paddingRight + element.resolvedStyle.borderRightWidth + element.resolvedStyle.borderLeftWidth,
  96. element.resolvedStyle.marginTop + element.resolvedStyle.marginBottom + element.resolvedStyle.paddingTop + element.resolvedStyle.paddingBottom + element.resolvedStyle.borderBottomWidth + element.resolvedStyle.borderTopWidth
  97. );
  98. }
  99. void OnFitToText(DropdownMenuAction a)
  100. {
  101. FitText(false);
  102. }
  103. public void FitText(bool onlyIfSmaller)
  104. {
  105. Vector2 preferredTitleSize = Vector2.zero;
  106. if (!string.IsNullOrEmpty(m_Title.text))
  107. preferredTitleSize = m_Title.MeasureTextSize(m_Title.text, 0, MeasureMode.Undefined, 0, MeasureMode.Undefined); // This is the size of the string with the current title font and such
  108. preferredTitleSize += AllExtraSpace(m_Title);
  109. preferredTitleSize.x += m_Title.ChangeCoordinatesTo(this, Vector2.zero).x + resolvedStyle.width - m_Title.ChangeCoordinatesTo(this, new Vector2(m_Title.layout.width, 0)).x;
  110. Vector2 preferredContentsSizeOneLine = m_Contents.MeasureTextSize(m_Contents.text, 0, MeasureMode.Undefined, 0, MeasureMode.Undefined);
  111. Vector2 contentExtraSpace = AllExtraSpace(m_Contents);
  112. preferredContentsSizeOneLine += contentExtraSpace;
  113. Vector2 extraSpace = new Vector2(resolvedStyle.width, resolvedStyle.height) - m_Contents.ChangeCoordinatesTo(this, new Vector2(m_Contents.layout.width, m_Contents.layout.height));
  114. extraSpace += m_Title.ChangeCoordinatesTo(this, Vector2.zero);
  115. preferredContentsSizeOneLine += extraSpace;
  116. float width = 0;
  117. float height = 0;
  118. // The content in one line is smaller than the current width.
  119. // Set the width to fit both title and content.
  120. // Set the height to have only one line in the content
  121. if (preferredContentsSizeOneLine.x < Mathf.Max(preferredTitleSize.x, resolvedStyle.width))
  122. {
  123. width = Mathf.Max(preferredContentsSizeOneLine.x, preferredTitleSize.x);
  124. height = preferredContentsSizeOneLine.y + preferredTitleSize.y;
  125. }
  126. else // The width is not enough for the content: keep the width or use the title width if bigger.
  127. {
  128. width = Mathf.Max(preferredTitleSize.x + extraSpace.x, resolvedStyle.width);
  129. float contextWidth = width - extraSpace.x - contentExtraSpace.x;
  130. Vector2 preferredContentsSize = m_Contents.MeasureTextSize(m_Contents.text, contextWidth, MeasureMode.Exactly, 0, MeasureMode.Undefined);
  131. preferredContentsSize += contentExtraSpace;
  132. height = preferredTitleSize.y + preferredContentsSize.y + extraSpace.y;
  133. }
  134. if (!onlyIfSmaller || resolvedStyle.width < width)
  135. style.width = width;
  136. if (!onlyIfSmaller || resolvedStyle.height < height)
  137. style.height = height;
  138. OnResized();
  139. }
  140. void UpdateThemeClasses()
  141. {
  142. foreach (Theme value in System.Enum.GetValues(typeof(Theme)))
  143. {
  144. if (m_Theme != value)
  145. {
  146. RemoveFromClassList("theme-" + value.ToString().ToLower());
  147. }
  148. else
  149. {
  150. AddToClassList("theme-" + value.ToString().ToLower());
  151. }
  152. }
  153. }
  154. void UpdateSizeClasses()
  155. {
  156. foreach (TextSize value in System.Enum.GetValues(typeof(TextSize)))
  157. {
  158. if (m_TextSize != value)
  159. {
  160. RemoveFromClassList("size-" + value.ToString().ToLower());
  161. }
  162. else
  163. {
  164. AddToClassList("size-" + value.ToString().ToLower());
  165. }
  166. }
  167. }
  168. public static readonly Vector2 defaultSize = new Vector2(200, 160);
  169. public StickyNote(Rect position, GraphData graph) : this("UXML/StickyNote", position, graph)
  170. {
  171. styleSheets.Add(Resources.Load<StyleSheet>("Selectable"));
  172. styleSheets.Add(Resources.Load<StyleSheet>("StickyNote"));
  173. RegisterCallback<AttachToPanelEvent>(OnAttachToPanel);
  174. }
  175. public string displayName => $"{m_Title.text} (Sticky Note)";
  176. public StickyNote(string uiFile, Rect position, GraphData graph)
  177. {
  178. m_Graph = graph;
  179. var tpl = Resources.Load<VisualTreeAsset>(uiFile);
  180. tpl.CloneTree(this);
  181. capabilities = Capabilities.Movable | Capabilities.Deletable | Capabilities.Ascendable | Capabilities.Selectable | Capabilities.Copiable | Capabilities.Groupable;
  182. m_Title = this.Q<Label>(name: "title");
  183. if (m_Title != null)
  184. {
  185. m_Title.RegisterCallback<MouseDownEvent>(OnTitleMouseDown);
  186. }
  187. m_TitleField = this.Q<TextField>(name: "title-field");
  188. if (m_TitleField != null)
  189. {
  190. m_TitleField.style.display = DisplayStyle.None;
  191. m_TitleField.Q("unity-text-input").RegisterCallback<BlurEvent>(OnTitleBlur, TrickleDown.TrickleDown);
  192. m_TitleField.RegisterCallback<ChangeEvent<string>>(OnTitleChange);
  193. }
  194. m_Contents = this.Q<Label>(name: "contents");
  195. if (m_Contents != null)
  196. {
  197. m_ContentsField = m_Contents.Q<TextField>(name: "contents-field");
  198. if (m_ContentsField != null)
  199. {
  200. m_ContentsField.style.display = DisplayStyle.None;
  201. m_ContentsField.multiline = true;
  202. m_ContentsField.Q("unity-text-input").RegisterCallback<BlurEvent>(OnContentsBlur, TrickleDown.TrickleDown);
  203. }
  204. m_Contents.RegisterCallback<MouseDownEvent>(OnContentsMouseDown);
  205. }
  206. SetPosition(new Rect(position.x, position.y, defaultSize.x, defaultSize.y));
  207. AddToClassList("sticky-note");
  208. AddToClassList("selectable");
  209. UpdateThemeClasses();
  210. UpdateSizeClasses();
  211. // Manually set the layer of the sticky note so it's always on top. This used to be in the uss
  212. // but that causes issues with re-laying out at times that can do weird things to selection.
  213. this.layer = -100;
  214. this.AddManipulator(new ContextualMenuManipulator(BuildContextualMenu));
  215. }
  216. public void BuildContextualMenu(ContextualMenuPopulateEvent evt)
  217. {
  218. if (evt.target is StickyNote)
  219. {
  220. /*foreach (Theme value in System.Enum.GetValues(typeof(Theme)))
  221. {
  222. evt.menu.AppendAction("Theme/" + value.ToString(), OnChangeTheme, e => DropdownMenu.MenuAction.StatusFlags.Normal, value);
  223. }*/
  224. if (theme == Theme.Black)
  225. evt.menu.AppendAction("Light Theme", OnChangeTheme, e => DropdownMenuAction.Status.Normal, Theme.Classic);
  226. else
  227. evt.menu.AppendAction("Dark Theme", OnChangeTheme, e => DropdownMenuAction.Status.Normal, Theme.Black);
  228. evt.menu.AppendSeparator();
  229. foreach (TextSize value in System.Enum.GetValues(typeof(TextSize)))
  230. {
  231. evt.menu.AppendAction(value.ToString() + " Text Size", OnChangeSize, e => DropdownMenuAction.Status.Normal, value);
  232. }
  233. evt.menu.AppendSeparator();
  234. evt.menu.AppendAction("Fit To Text", OnFitToText, e => DropdownMenuAction.Status.Normal);
  235. evt.menu.AppendSeparator();
  236. evt.menu.AppendAction("Delete", OnDelete, e => DropdownMenuAction.Status.Normal);
  237. evt.menu.AppendSeparator();
  238. }
  239. }
  240. void OnDelete(DropdownMenuAction menuAction)
  241. {
  242. m_Graph.owner.RegisterCompleteObjectUndo("Delete Sticky Note");
  243. m_Graph.RemoveStickyNote(userData);
  244. }
  245. void OnTitleChange(EventBase e)
  246. {
  247. //m_Graph.owner.RegisterCompleteObjectUndo("Title Changed");
  248. //title = m_TitleField.value;
  249. //userData.title = title;
  250. }
  251. public PropertySheet GetInspectorContent()
  252. {
  253. var sheet = new PropertySheet();
  254. return sheet;
  255. }
  256. const string fitTextClass = "fit-text";
  257. public override void SetPosition(Rect rect)
  258. {
  259. style.left = rect.x;
  260. style.top = rect.y;
  261. style.width = rect.width;
  262. style.height = rect.height;
  263. }
  264. public override Rect GetPosition()
  265. {
  266. return new Rect(resolvedStyle.left, resolvedStyle.top, resolvedStyle.width, resolvedStyle.height);
  267. }
  268. public string contents
  269. {
  270. get { return m_Contents.text; }
  271. set
  272. {
  273. if (m_Contents != null)
  274. {
  275. m_Contents.text = value;
  276. }
  277. }
  278. }
  279. public new string title
  280. {
  281. get { return m_Title.text; }
  282. set
  283. {
  284. if (m_Title != null)
  285. {
  286. m_Title.text = value;
  287. if (!string.IsNullOrEmpty(m_Title.text))
  288. {
  289. m_Title.RemoveFromClassList("empty");
  290. }
  291. else
  292. {
  293. m_Title.AddToClassList("empty");
  294. }
  295. //UpdateTitleHeight();
  296. }
  297. }
  298. }
  299. void OnChangeTheme(DropdownMenuAction action)
  300. {
  301. theme = (Theme)action.userData;
  302. NotifyChange(StickyNodeChangeEvent.Change.theme);
  303. }
  304. void OnChangeSize(DropdownMenuAction action)
  305. {
  306. textSize = (TextSize)action.userData;
  307. NotifyChange(StickyNodeChangeEvent.Change.textSize);
  308. //panel.InternalValidateLayout();
  309. FitText(true);
  310. }
  311. void OnAttachToPanel(AttachToPanelEvent e)
  312. {
  313. //UpdateTitleHeight();
  314. }
  315. void OnTitleBlur(BlurEvent e)
  316. {
  317. //bool changed = m_Title.text != m_TitleField.value;
  318. title = m_TitleField.value;
  319. m_TitleField.style.display = DisplayStyle.None;
  320. m_Title.UnregisterCallback<GeometryChangedEvent>(OnTitleRelayout);
  321. //Notify change
  322. //if( changed)
  323. {
  324. NotifyChange(StickyNodeChangeEvent.Change.title);
  325. }
  326. }
  327. void OnContentsBlur(BlurEvent e)
  328. {
  329. bool changed = m_Contents.text != m_ContentsField.value;
  330. m_Contents.text = m_ContentsField.value;
  331. m_ContentsField.style.display = DisplayStyle.None;
  332. //Notify change
  333. if (changed)
  334. {
  335. NotifyChange(StickyNodeChangeEvent.Change.contents);
  336. }
  337. }
  338. void OnTitleRelayout(GeometryChangedEvent e)
  339. {
  340. UpdateTitleFieldRect();
  341. }
  342. void UpdateTitleFieldRect()
  343. {
  344. Rect rect = m_Title.layout;
  345. m_Title.parent.ChangeCoordinatesTo(m_TitleField.parent, rect);
  346. m_TitleField.style.left = rect.xMin - 1;
  347. m_TitleField.style.right = rect.yMin + m_Title.resolvedStyle.marginTop;
  348. m_TitleField.style.width = rect.width - m_Title.resolvedStyle.marginLeft - m_Title.resolvedStyle.marginRight;
  349. m_TitleField.style.height = rect.height - m_Title.resolvedStyle.marginTop - m_Title.resolvedStyle.marginBottom;
  350. }
  351. void OnTitleMouseDown(MouseDownEvent e)
  352. {
  353. if (e.clickCount == 2)
  354. {
  355. m_TitleField.RemoveFromClassList("empty");
  356. m_TitleField.value = m_Title.text;
  357. m_TitleField.style.display = DisplayStyle.Flex;
  358. UpdateTitleFieldRect();
  359. m_Title.RegisterCallback<GeometryChangedEvent>(OnTitleRelayout);
  360. m_TitleField.Q("unity-text-input").Focus();
  361. m_TitleField.SelectAll();
  362. e.StopPropagation();
  363. focusController.IgnoreEvent(e);
  364. }
  365. }
  366. void NotifyChange(StickyNodeChangeEvent.Change change)
  367. {
  368. m_Graph.owner.RegisterCompleteObjectUndo($"Change Sticky Note {change.ToString()}");
  369. if (change == StickyNodeChangeEvent.Change.title)
  370. {
  371. userData.title = title;
  372. }
  373. else if (change == StickyNodeChangeEvent.Change.contents)
  374. {
  375. userData.content = contents;
  376. }
  377. else if (change == StickyNodeChangeEvent.Change.textSize)
  378. {
  379. userData.textSize = (int)textSize;
  380. }
  381. else if (change == StickyNodeChangeEvent.Change.theme)
  382. {
  383. userData.theme = (int)theme;
  384. }
  385. }
  386. public System.Action<StickyNodeChangeEvent.Change> OnChange;
  387. void OnContentsMouseDown(MouseDownEvent e)
  388. {
  389. if (e.clickCount == 2)
  390. {
  391. m_ContentsField.value = m_Contents.text;
  392. m_ContentsField.style.display = DisplayStyle.Flex;
  393. m_ContentsField.Q("unity-text-input").Focus();
  394. e.StopPropagation();
  395. focusController.IgnoreEvent(e);
  396. }
  397. }
  398. Label m_Title;
  399. protected TextField m_TitleField;
  400. Label m_Contents;
  401. protected TextField m_ContentsField;
  402. }
  403. }