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