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}