forked from Unity-Technologies/UnityCsReference
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathTreeViewGUI.cs
More file actions
652 lines (536 loc) · 23.2 KB
/
Copy pathTreeViewGUI.cs
File metadata and controls
652 lines (536 loc) · 23.2 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
// Unity C# reference source
// Copyright (c) Unity Technologies. For terms of use, see
// https://unity3d.com/legal/licenses/Unity_Reference_Only_License
using System;
using System.Collections.Generic;
using UnityEngine;
using UnityEditor.Experimental;
using UnityEditor.StyleSheets;
namespace UnityEditor.IMGUI.Controls
{
internal abstract class TreeViewGUI : ITreeViewGUI
{
protected TreeViewController m_TreeView;
protected PingData m_Ping = new PingData();
protected Rect m_DraggingInsertionMarkerRect;
protected Rect m_DraggingAncestorMarkerRect;
protected bool m_UseHorizontalScroll;
// Icon overlay
public float iconLeftPadding { get; set; }
public float iconRightPadding { get; set; }
public float iconTotalPadding { get { return iconLeftPadding + iconRightPadding; } }
public System.Action<TreeViewItem, Rect> iconOverlayGUI { get; set; } // Rect includes iconLeftPadding and iconRightPadding
public System.Action<TreeViewItem, Rect> labelOverlayGUI { get; set; }
private bool m_AnimateScrollBarOnExpandCollapse = true;
// Layout
private float m_LineHeight = -1;
public float k_LineHeight
{
get
{
if (m_LineHeight < 0)
m_LineHeight = new SVC<float>("--treeview-line-height", 16f);
return m_LineHeight;
}
set { m_LineHeight = value; }
}
public float k_BaseIndent = 2f;
public float k_IndentWidth = 14f;
public float k_IconWidth = 16f;
public float k_SpaceBetweenIconAndText = 2f;
public float k_TopRowMargin = 0f;
public float k_BottomRowMargin = 0f;
public float indentWidth { get { return k_IndentWidth + iconTotalPadding; } }
public float k_HalfDropBetweenHeight = 4f;
public float customFoldoutYOffset = 0f;
public float extraInsertionMarkerIndent = 0f;
public float extraSpaceBeforeIconAndLabel { get; set; }
public bool drawSelection { get; set; } = true;
public float halfDropBetweenHeight { get { return k_HalfDropBetweenHeight; } }
public virtual float topRowMargin { get { return k_TopRowMargin; } }
public virtual float bottomRowMargin { get { return k_BottomRowMargin; } }
// Styles
internal static class Styles
{
public static GUIStyle foldout = "IN Foldout";
public static GUIStyle insertion = "TV Insertion";
public static GUIStyle insertionRelativeToSibling = "TV InsertionRelativeToSibling";
public static GUIStyle ping = "TV Ping";
public static GUIStyle toolbarButton = "ToolbarButton";
public static GUIStyle lineStyle = "TV Line";
public static GUIStyle lineBoldStyle = "TV LineBold";
public static GUIStyle selectionStyle = "TV Selection";
public static GUIContent content = new GUIContent(EditorGUIUtility.FindTexture(EditorResources.folderIconName));
}
private GUIStyle m_FoldoutStyle;
protected GUIStyle foldoutStyle
{
get
{
return m_FoldoutStyle ?? Styles.foldout;
}
set { m_FoldoutStyle = value; }
}
private GUIStyle m_InsertionStyle;
protected GUIStyle insertionStyle
{
get
{
return m_InsertionStyle ?? Styles.insertion;
}
set { m_InsertionStyle = value; }
}
private GUIStyle m_InsertionRelativeToSiblingStyle;
protected GUIStyle insertionRelativeToSiblingStyle
{
get
{
return m_InsertionRelativeToSiblingStyle ?? Styles.insertionRelativeToSibling;
}
set { m_InsertionRelativeToSiblingStyle = value; }
}
private GUIStyle m_PingStyle;
protected GUIStyle pingStyle
{
get
{
return m_PingStyle ?? Styles.ping;
}
set { m_PingStyle = value; }
}
private GUIStyle m_ToolbarButtonStyle;
protected GUIStyle toolbarButtonStyle
{
get
{
return m_ToolbarButtonStyle ?? Styles.toolbarButton;
}
set { m_ToolbarButtonStyle = value; }
}
private GUIStyle m_LineStyle;
protected GUIStyle lineStyle
{
get
{
return m_LineStyle ?? Styles.lineStyle;
}
set { m_LineStyle = value; }
}
private GUIStyle m_SelectionStyle;
protected GUIStyle selectionStyle
{
get
{
return m_SelectionStyle ?? Styles.selectionStyle;
}
set { m_SelectionStyle = value; }
}
private GUIStyle renameStyle
{
get
{
GUIStyle renameStyle = new GUIStyle("PR TextField");
renameStyle.fontSize = lineStyle.fontSize; // the textField should have the same text size as the line
return renameStyle;
}
}
protected float foldoutStyleWidth
{
get { return foldoutStyle.fixedWidth; }
}
public TreeViewGUI(TreeViewController treeView)
{
m_TreeView = treeView;
}
public TreeViewGUI(TreeViewController treeView, bool useHorizontalScroll)
{
m_TreeView = treeView;
m_UseHorizontalScroll = useHorizontalScroll;
}
virtual public void OnInitialize()
{
var dragging = m_TreeView.dragging as TreeViewDragging;
if (dragging != null)
dragging.getIndentLevelForMouseCursor = GetIndentLevelForMouseCursor;
}
int GetIndentLevelForMouseCursor()
{
float contentStartX = k_BaseIndent + extraInsertionMarkerIndent + foldoutStyleWidth + lineStyle.margin.left;
float mousePosX = Event.current.mousePosition.x;
return Mathf.FloorToInt((mousePosX - contentStartX) / indentWidth);
}
internal Texture GetEffectiveIcon(TreeViewItem item, bool selected, bool focused)
{
var icon = GetIconForItem(item);
if (selected && focused)
{
var selIcon = GetIconForSelectedItem(item);
if (selIcon != null)
return selIcon;
}
return icon;
}
protected virtual Texture GetIconForItem(TreeViewItem item)
{
return item.icon;
}
internal virtual Texture GetIconForSelectedItem(TreeViewItem item)
{
return EditorUtility.GetIconInActiveState(GetIconForItem(item));
}
// ------------------
// Size section
// Calc correct width if horizontal scrollbar is wanted return new Vector2(1, height)
virtual public Vector2 GetTotalSize()
{
// Width is 1 to prevent showing horizontal scrollbar
float width = 1f;
if (m_UseHorizontalScroll)
{
var rows = m_TreeView.data.GetRows();
width = GetMaxWidth(rows);
}
// Height
float height = m_TreeView.data.rowCount * k_LineHeight + topRowMargin + bottomRowMargin;
if (m_AnimateScrollBarOnExpandCollapse && m_TreeView.expansionAnimator.isAnimating)
{
height -= m_TreeView.expansionAnimator.deltaHeight;
}
return new Vector2(width, height);
}
protected float GetMaxWidth(IList<TreeViewItem> rows)
{
float maxWidth = 1f;
foreach (TreeViewItem item in rows)
{
float width = 0f;
width += GetContentIndent(item);
if (item.icon != null)
width += k_IconWidth;
float minNameWidth, maxNameWidth;
lineStyle.CalcMinMaxWidth(GUIContent.Temp(item.displayName), out minNameWidth, out maxNameWidth);
width += maxNameWidth;
// Add some padding to the back
width += k_BaseIndent;
if (width > maxWidth)
maxWidth = width;
}
return maxWidth;
}
virtual public int GetNumRowsOnPageUpDown(TreeViewItem fromItem, bool pageUp, float heightOfTreeView)
{
return (int)Mathf.Floor(heightOfTreeView / k_LineHeight);
}
// Should return the row index of the first and last row thats fits in the pixel rect defined by top and height
virtual public void GetFirstAndLastRowVisible(out int firstRowVisible, out int lastRowVisible)
{
if (m_TreeView.data.rowCount == 0 || Mathf.Approximately(m_TreeView.visibleRect.height, 0.0f))
{
firstRowVisible = lastRowVisible = -1;
return;
}
float topPixel = m_TreeView.state.scrollPos.y;
float heightInPixels = m_TreeView.visibleRect.height;
firstRowVisible = (int)Mathf.Floor((topPixel - topRowMargin) / k_LineHeight);
lastRowVisible = firstRowVisible + (int)Mathf.Ceil(heightInPixels / k_LineHeight);
firstRowVisible = Mathf.Max(firstRowVisible, 0);
lastRowVisible = Mathf.Min(lastRowVisible, m_TreeView.data.rowCount - 1);
// Validate
if (firstRowVisible >= m_TreeView.data.rowCount && firstRowVisible > 0)
{
// Reset scroll if it was invalid, this can be the case if scroll y value was serialized and loading new tree data
m_TreeView.state.scrollPos.y = 0f;
GetFirstAndLastRowVisible(out firstRowVisible, out lastRowVisible);
}
}
// ---------------------
// OnGUI section
virtual public void BeginRowGUI()
{
// Reset
m_DraggingInsertionMarkerRect.x = -1;
m_DraggingAncestorMarkerRect.x = -1;
SyncFakeItem(); // After domain reload we ensure to reconstruct new Item state
// Input for rename overlay (repainted in EndRowGUI to ensure rendered on top)
if (Event.current.type != EventType.Repaint)
DoRenameOverlay();
}
void DrawDraggingInsertionMarkerIfNeeded()
{
if (Event.current.type != EventType.Repaint)
return;
// If an inheriting class already set the m_DraggingInsertionMarkerRect we don't overwrite it
var dragging = m_TreeView.dragging as TreeViewDragging;
if (dragging != null && dragging.insertionMarkerYPosition > 0 && m_DraggingInsertionMarkerRect.x == -1)
{
float xPos = GetContentIndent(dragging.insertRelativeToSibling) + extraInsertionMarkerIndent;
float yPos = dragging.insertionMarkerYPosition - insertionStyle.fixedHeight * 0.5f;
m_DraggingInsertionMarkerRect = new Rect(xPos, yPos, GUIClip.visibleRect.width - xPos, insertionStyle.fixedHeight);
}
// Draw row marker when dragging
if (m_DraggingInsertionMarkerRect.x >= 0)
{
insertionStyle.Draw(m_DraggingInsertionMarkerRect, false, false, false, false);
}
if (m_DraggingAncestorMarkerRect.x >= 0)
{
insertionRelativeToSiblingStyle.Draw(m_DraggingAncestorMarkerRect, GUIContent.none, false, false, false, false);
}
}
virtual public void EndRowGUI()
{
DrawDraggingInsertionMarkerIfNeeded();
// Render rename overlay last (input is handled in BeginRowGUI)
if (Event.current.type == EventType.Repaint)
DoRenameOverlay();
// Ping a Item
HandlePing();
}
virtual public void OnRowGUI(Rect rowRect, TreeViewItem item, int row, bool selected, bool focused)
{
DoItemGUI(rowRect, row, item, selected, focused, false);
}
// Override this method for custom rendering of background behind selection and drop effect rendering
protected virtual void DrawItemBackground(Rect rect, int row, TreeViewItem item, bool selected, bool focused)
{
if (item == m_TreeView.hoveredItem)
{
using (new GUI.BackgroundColorScope(GameObjectTreeViewGUI.GameObjectStyles.hoveredBackgroundColor))
{
GUI.Label(rect, GUIContent.none, GameObjectTreeViewGUI.GameObjectStyles.hoveredItemBackgroundStyle);
}
}
}
public virtual Rect GetRenameRect(Rect rowRect, int row, TreeViewItem item)
{
float offset = GetContentIndent(item) + extraSpaceBeforeIconAndLabel;
if (GetIconForItem(item) != null)
offset += k_SpaceBetweenIconAndText + k_IconWidth + iconTotalPadding;
// By default we top align the rename rect to follow the label style, foldout and controls alignment
return new Rect(rowRect.x + offset, rowRect.y, rowRect.width - offset,
rowRect.height);
}
virtual protected void DoItemGUI(Rect rect, int row, TreeViewItem item, bool selected, bool focused, bool useBoldFont)
{
EditorGUIUtility.SetIconSize(new Vector2(k_IconWidth, k_IconWidth)); // If not set we see icons scaling down if text is being cropped
int itemControlID = TreeViewController.GetItemControlID(item);
bool isRenamingThisItem = IsRenaming(item.id);
bool showFoldout = m_TreeView.data.IsExpandable(item);
// Adjust edit field if needed (on repaint since on layout rect.width is invalid when using GUILayout)
if (isRenamingThisItem && Event.current.type == EventType.Repaint)
{
GetRenameOverlay().editFieldRect = GetRenameRect(rect, row, item);
}
string label = item.displayName;
if (isRenamingThisItem)
{
selected = false;
label = "";
}
if (Event.current.type == EventType.Repaint)
{
// Draw background (can be overridden)
DrawItemBackground(rect, row, item, selected, focused);
// Draw selection
if (selected && drawSelection)
selectionStyle.Draw(rect, false, false, true, focused);
bool hasDragHandling = m_TreeView.dragging != null;
if (hasDragHandling)
{
// Draw drop marker
if (m_TreeView.dragging.GetDropTargetControlID() == itemControlID && m_TreeView.data.CanBeParent(item))
{
Styles.lineStyle.Draw(GetDropTargetRect(rect), GUIContent.none, true, true, false, false);
}
// Ancestor item marker is rendered after all rows in RowEndGUI - extra visual helper marker when previous sibling is far away from the cursor
var dragging = m_TreeView.dragging as TreeViewDragging;
if (dragging != null && dragging.GetAncestorControlID() == itemControlID && dragging.insertRelativeToSibling != null)
{
m_DraggingAncestorMarkerRect = rect;
m_DraggingAncestorMarkerRect.xMin += extraInsertionMarkerIndent + GetContentIndent(item);
m_DraggingAncestorMarkerRect.y = rect.yMax - insertionRelativeToSiblingStyle.fixedHeight * 0.5f;
}
}
}
// Do additional ui controls (menu button, prefab arrow etc)
OnAdditionalGUI(rect, row, item, selected, focused);
// Do row content (icon, label, controls etc)
OnContentGUI(rect, row, item, label, selected, focused, useBoldFont, false);
// Do foldout
if (showFoldout)
{
DoFoldout(rect, item, row);
}
EditorGUIUtility.SetIconSize(Vector2.zero);
}
protected virtual Rect GetDropTargetRect(Rect rect)
{
return rect;
}
float GetTopPixelOfRow(int row)
{
return row * k_LineHeight + topRowMargin;
}
public virtual Rect GetRowRect(int row, float rowWidth)
{
return new Rect(0, GetTopPixelOfRow(row), rowWidth, k_LineHeight);
}
public virtual Rect GetRectForFraming(int row)
{
return GetRowRect(row, 1); // We ignore width by default when framing (only y scroll is affected)
}
float GetFoldoutYPosition(float rectY)
{
// By default the arrow is aligned to the top to match text rendering
return rectY + customFoldoutYOffset;
}
protected virtual Rect DoFoldout(Rect rect, TreeViewItem item, int row)
{
float indent = GetFoldoutIndent(item);
Rect foldoutRect = new Rect(rect.x + indent, GetFoldoutYPosition(rect.y), foldoutStyleWidth, k_LineHeight);
FoldoutButton(foldoutRect, item, row, foldoutStyle);
return foldoutRect;
}
protected virtual void FoldoutButton(Rect foldoutRect, TreeViewItem item, int row, GUIStyle foldoutStyle)
{
var expansionAnimator = m_TreeView.expansionAnimator;
bool newExpandedValue;
EditorGUI.BeginChangeCheck();
{
bool expandedState = expansionAnimator.IsAnimating(item.id) ? expansionAnimator.isExpanding : m_TreeView.data.IsExpanded(item);
newExpandedValue = DoFoldoutButton(foldoutRect, expandedState, foldoutStyle);
}
if (EditorGUI.EndChangeCheck())
{
m_TreeView.UserInputChangedExpandedState(item, row, newExpandedValue);
}
}
protected virtual bool DoFoldoutButton(Rect foldoutRect, bool expandedState, GUIStyle foldoutStyle)
{
return GUI.Toggle(foldoutRect, expandedState, GUIContent.none, foldoutStyle);
}
protected virtual void OnAdditionalGUI(Rect rect, int row, TreeViewItem item, bool selected, bool focused)
{
}
protected virtual void OnContentGUI(Rect rect, int row, TreeViewItem item, string label, bool selected, bool focused, bool useBoldFont, bool isPinging)
{
if (Event.current.rawType != EventType.Repaint)
return;
if (!isPinging)
{
// The rect is assumed indented and sized after the content when pinging
float indent = GetContentIndent(item) + extraSpaceBeforeIconAndLabel;
rect.xMin += indent;
}
lineStyle = useBoldFont ? Styles.lineBoldStyle : Styles.lineStyle;
// Draw icon
Rect iconRect = rect;
iconRect.width = k_IconWidth;
iconRect.x += iconLeftPadding;
Texture icon = GetEffectiveIcon(item, selected, focused);
if (icon != null)
{
var iconColor = GUI.color.AlphaMultiplied(GUI.enabled ? 1f : 0.5f);
GUI.DrawTexture(iconRect, icon, ScaleMode.ScaleToFit, true, 0, iconColor, 0, 0);
}
if (iconOverlayGUI != null)
{
Rect iconOverlayRect = rect;
iconOverlayRect.width = k_IconWidth + iconTotalPadding;
iconOverlayGUI(item, iconOverlayRect);
}
// Draw text
if (icon != null)
rect.xMin += k_IconWidth + iconTotalPadding + k_SpaceBetweenIconAndText;
lineStyle.Draw(rect, label, false, false, selected, focused);
if (labelOverlayGUI != null)
{
labelOverlayGUI(item, rect);
}
}
// Ping Item
// -------------
virtual public void BeginPingItem(TreeViewItem item, float topPixelOfRow, float availableWidth)
{
if (item == null)
return;
// Setup ping
if (topPixelOfRow >= 0f)
{
m_Ping.isPinging = true;
m_Ping.m_PingStyle = pingStyle;
GUIContent cont = GUIContent.Temp(item.displayName);
Vector2 contentSize = m_Ping.m_PingStyle.CalcSize(cont);
m_Ping.m_ContentRect = new Rect(GetContentIndent(item) + extraSpaceBeforeIconAndLabel,
topPixelOfRow,
k_IconWidth + k_SpaceBetweenIconAndText + contentSize.x + iconTotalPadding,
contentSize.y);
m_Ping.m_AvailableWidth = availableWidth;
int row = m_TreeView.data.GetRow(item.id);
bool useBoldFont = item.displayName.Equals("Assets");
m_Ping.m_ContentDraw = (Rect r) =>
{
// get Item parameters from closure
OnContentGUI(r, row, item, item.displayName, false, false, useBoldFont, true);
};
m_TreeView.Repaint();
}
}
virtual public void EndPingItem()
{
m_Ping.isPinging = false;
}
void HandlePing()
{
m_Ping.HandlePing();
if (m_Ping.isPinging)
m_TreeView.Repaint();
}
//-------------------
// Rename section
protected RenameOverlay GetRenameOverlay()
{
return m_TreeView.state.renameOverlay;
}
virtual protected bool IsRenaming(int id)
{
return GetRenameOverlay().IsRenaming() && GetRenameOverlay().userData == id && !GetRenameOverlay().isWaitingForDelay;
}
virtual public bool BeginRename(TreeViewItem item, float delay)
{
return GetRenameOverlay().BeginRename(item.displayName, item.id, delay);
}
virtual public void EndRename()
{
// We give keyboard focus back to our tree view because the rename utility stole it (now we give it back)
if (GetRenameOverlay().HasKeyboardFocus())
m_TreeView.GrabKeyboardFocus();
RenameEnded();
ClearRenameAndNewItemState(); // Ensure clearing if RenameEnden is overrided
}
virtual protected void RenameEnded() {}
virtual public void DoRenameOverlay()
{
if (GetRenameOverlay().IsRenaming())
if (!GetRenameOverlay().OnGUI(renameStyle))
EndRename();
}
virtual protected void SyncFakeItem() {}
virtual protected void ClearRenameAndNewItemState()
{
m_TreeView.data.RemoveFakeItem();
GetRenameOverlay().Clear();
}
virtual public float GetFoldoutIndent(TreeViewItem item)
{
// Ignore depth when showing search results
if (m_TreeView.isSearching)
return k_BaseIndent;
return k_BaseIndent + item.depth * indentWidth;
}
virtual public float GetContentIndent(TreeViewItem item)
{
return GetFoldoutIndent(item) + foldoutStyleWidth + lineStyle.margin.left;
}
}
}