-
Notifications
You must be signed in to change notification settings - Fork 2.5k
/
Copy pathEditorGUI.cs
9147 lines (7933 loc) · 417 KB
/
EditorGUI.cs
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
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
// 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 System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.Text.RegularExpressions;
using UnityEditor.SceneManagement;
using UnityEngine;
using UnityEngine.Scripting;
using UnityEngine.Rendering;
using UnityEditorInternal;
using UnityEditor.Scripting.ScriptCompilation;
using Object = UnityEngine.Object;
using Event = UnityEngine.Event;
using UnityEditor.StyleSheets;
using UnityEditor.VersionControl;
using UnityEngine.Internal;
using VirtualTexturing = UnityEngine.Rendering.VirtualTexturing;
using PreviewMaterialType = UnityEditor.EditorGUIUtility.PreviewType;
using System.Linq;
using System.Reflection;
using Unity.Profiling;
using UnityEditor.Rendering;
using UnityEngine.Experimental.Rendering;
using UnityEngine.UIElements;
using UnityEditor.UIElements;
using UnityEngine.Bindings;
namespace UnityEditor
{
// Class for editor-only GUI. This class contains general purpose 2D additions to UnityGUI.
// These work pretty much like the normal GUI functions - and also have matching implementations in [[EditorGUILayout]]
[SuppressMessage("ReSharper", "NotAccessedField.Local")]
public sealed partial class EditorGUI
{
private static RecycledTextEditor activeEditor;
private static DelayedTextEditor s_DelayedTextEditorInternal;
internal static DelayedTextEditor s_DelayedTextEditor => s_DelayedTextEditorInternal ??= new();
internal static RecycledTextEditor s_RecycledEditorInternal;
internal static RecycledTextEditor s_RecycledEditor => s_RecycledEditorInternal ??= new();
internal static string s_OriginalText = "";
internal static string s_RecycledCurrentEditingString;
private static bool bKeyEventActive = false;
internal static bool s_DragToPosition = false;
internal static bool s_Dragged = false;
internal static bool s_SelectAllOnMouseUp = true;
private const double kFoldoutExpandTimeout = 0.7;
private static double s_FoldoutDestTime;
private static int s_DragUpdatedOverID = 0;
private const int kSearchFieldTextLimit = 1024;
private static bool s_SearchFieldTextLimitApproved = false;
private static int s_SavekeyboardControl = 0;
private static readonly int s_FoldoutHash = "Foldout".GetHashCode();
private static readonly int s_TagFieldHash = "s_TagFieldHash".GetHashCode();
private static readonly int s_PPtrHash = "s_PPtrHash".GetHashCode();
private static readonly int s_ObjectFieldHash = "s_ObjectFieldHash".GetHashCode();
private static readonly int s_ToggleHash = "s_ToggleHash".GetHashCode();
private static readonly int s_ColorHash = "s_ColorHash".GetHashCode();
private static readonly int s_CurveHash = "s_CurveHash".GetHashCode();
private static readonly int s_LayerMaskField = "s_LayerMaskField".GetHashCode();
private static readonly int s_MaskField = "s_MaskField".GetHashCode();
private static readonly int s_EnumFlagsField = "s_EnumFlagsField".GetHashCode();
private static readonly int s_GenericField = "s_GenericField".GetHashCode();
private static readonly int s_PopupHash = "EditorPopup".GetHashCode();
private static readonly int s_KeyEventFieldHash = "KeyEventField".GetHashCode();
private static readonly int s_TextFieldHash = "EditorTextField".GetHashCode();
private static readonly int s_SearchFieldHash = "EditorSearchField".GetHashCode();
private static readonly int s_TextAreaHash = "EditorTextField".GetHashCode();
private static readonly int s_PasswordFieldHash = "PasswordField".GetHashCode();
private static readonly int s_FloatFieldHash = "EditorTextField".GetHashCode();
private static readonly int s_DelayedTextFieldHash = "DelayedEditorTextField".GetHashCode();
private static readonly int s_ArraySizeFieldHash = "ArraySizeField".GetHashCode();
private static readonly int s_SliderHash = "EditorSlider".GetHashCode();
private static readonly int s_SliderKnobHash = "EditorSliderKnob".GetHashCode();
private static readonly int s_MinMaxSliderHash = "EditorMinMaxSlider".GetHashCode();
private static readonly int s_TitlebarHash = "GenericTitlebar".GetHashCode();
private static readonly int s_ProgressBarHash = "s_ProgressBarHash".GetHashCode();
private static readonly int s_SelectableLabelHash = "s_SelectableLabel".GetHashCode();
private static readonly int s_SortingLayerFieldHash = "s_SortingLayerFieldHash".GetHashCode();
private static readonly int s_TextFieldDropDownHash = "s_TextFieldDropDown".GetHashCode();
private enum DragCandidateState
{
NotDragging,
InitiatedDragging,
CurrentlyDragging
}
private static DragCandidateState s_DragCandidateState = DragCandidateState.NotDragging;
private const float kDragDeadzone = 16;
private static Vector2 s_DragStartPos;
private static double s_DragStartValue = 0;
private static long s_DragStartIntValue = 0;
private static double s_DragSensitivity = 0;
private static readonly GUIContent s_LargerChar = EditorGUIUtility.TextContent("W");
// kMiniLabelW is used for all the PrefixLabelWidths irrsepective of the character,to fix the case 1297283
internal static float kMiniLabelW => EditorGUI.CalcPrefixLabelWidth(s_LargerChar);
internal const float kPrefixPaddingRight = 2;
internal const float kLabelW = 80;
internal const float kSpacing = 5;
internal const float kSpacingSubLabel = 4;
internal const float kSliderMinW = 50;
internal const float kSliderMaxW = 100;
internal const float kSingleLineHeight = 18f;
internal const float kSingleSmallLineHeight = 16f;
internal const float kStructHeaderLineHeight = 18;
internal const float kObjectFieldThumbnailHeight = 64;
internal const float kObjectFieldMiniThumbnailHeight = 18f;
internal const float kObjectFieldMiniThumbnailWidth = 32f;
internal const float kLabelWidthRatio = 0.45f;
internal const float kLabelWidthPadding = 3f;
internal const float kLabelWidthMargin = 40f;
internal const float kMinLabelWidth = 120f;
internal static string kFloatFieldFormatString = UINumericFieldsUtils.k_FloatFieldFormatString;
internal static string kDoubleFieldFormatString = UINumericFieldsUtils.k_DoubleFieldFormatString;
internal static string kIntFieldFormatString = UINumericFieldsUtils.k_IntFieldFormatString;
internal static int ms_IndentLevel = 0;
internal const float kIndentPerLevel = 15;
internal const int kControlVerticalSpacingLegacy = 2;
internal const int kDefaultSpacing = 6;
internal static readonly SVC<float> kControlVerticalSpacing = new SVC<float>("--theme-control-vertical-spacing", 2.0f);
internal static readonly SVC<float> kVerticalSpacingMultiField = new SVC<float>("--theme-multifield-vertical-spacing", 0.0f);
internal static string s_UnitString = "";
internal const int kInspTitlebarIconWidth = 16;
internal const int kInspTitlebarFoldoutIconWidth = 13;
internal static readonly SVC<float> kWindowToolbarHeight = new SVC<float>("--window-toolbar-height", 21f);
internal const int kTabButtonHeight = 22;
internal const int kLargeButtonHeight = 24;
private const string kEnabledPropertyName = "m_Enabled";
private const string k_MultiEditValueString = "<>";
private const float kDropDownArrowMargin = 2;
private const float kDropDownArrowWidth = 12;
private const float kDropDownArrowHeight = 12;
private static readonly float[] s_Vector2Floats = {0, 0};
private static readonly int[] s_Vector2Ints = { 0, 0 };
private static readonly GUIContent[] s_XYLabels = {EditorGUIUtility.TextContent("X"), EditorGUIUtility.TextContent("Y")};
private static readonly float[] s_Vector3Floats = {0, 0, 0};
private static readonly int[] s_Vector3Ints = { 0, 0, 0 };
private static readonly GUIContent[] s_XYZLabels = {EditorGUIUtility.TextContent("X"), EditorGUIUtility.TextContent("Y"), EditorGUIUtility.TextContent("Z")};
private static readonly float[] s_Vector4Floats = {0, 0, 0, 0};
private static readonly GUIContent[] s_XYZWLabels = {EditorGUIUtility.TextContent("X"), EditorGUIUtility.TextContent("Y"), EditorGUIUtility.TextContent("Z"), EditorGUIUtility.TextContent("W")};
private const float kQuaternionFloatPrecision = 1e-6f;
private static readonly GUIContent[] s_WHLabels = {EditorGUIUtility.TextContent("W"), EditorGUIUtility.TextContent("H")};
private static readonly GUIContent s_CenterLabel = EditorGUIUtility.TrTextContent("Center");
private static readonly GUIContent s_ExtentLabel = EditorGUIUtility.TrTextContent("Extent");
private static readonly GUIContent s_PositionLabel = EditorGUIUtility.TrTextContent("Position");
private static readonly GUIContent s_SizeLabel = EditorGUIUtility.TrTextContent("Size");
internal static GUIContent s_PleasePressAKey = EditorGUIUtility.TrTextContent("[Please press a key]");
internal static readonly GUIContent s_ClipingPlanesLabel = EditorGUIUtility.TrTextContent("Clipping Planes", "The distances from the Camera where rendering starts and stops.");
internal static readonly GUIContent[] s_NearAndFarLabels = { EditorGUIUtility.TrTextContent("Near", "The closest point to the Camera where drawing occurs."), EditorGUIUtility.TrTextContent("Far", "The furthest point from the Camera that drawing occurs.") };
internal const float kNearFarLabelsWidth = 35f;
private static int s_ColorPickID;
private static int s_CurveID;
internal static Color kCurveColor = Color.green;
internal static Color kCurveBGColor = new Color(0.337f, 0.337f, 0.337f, 1f);
internal static EditorGUIUtility.SkinnedColor kSplitLineSkinnedColor = new EditorGUIUtility.SkinnedColor(new Color(0.6f, 0.6f, 0.6f, 1.333f), new Color(0.12f, 0.12f, 0.12f, 1.333f));
internal static Color k_OverrideMarginColor = new Color(1f / 255f, 153f / 255f, 235f / 255f, 0.75f);
internal static Color k_OverrideMarginColorSelected = new Color(239f / 255f, 239f / 255f, 239f / 239f, 1f);
internal static Color k_OverrideMarginColorNotApplicable = new Color(1f / 255f, 153f / 255f, 235f / 255f, 0.35f);
internal static Color k_LiveModifiedMarginLightThemeColor = new Color(183f / 255f, 60f / 255f, 21f / 255f, 1f);
internal static Color k_LiveModifiedMarginDarkThemeColor = new Color(255f / 255f, 165f / 255f, 60f / 255f, 1f);
private const int kInspTitlebarSpacing = 4;
private static readonly GUIContent s_PropertyFieldTempContent = new GUIContent();
private static GUIContent s_IconDropDown;
private static Material s_IconTextureInactive;
private static bool s_HasPrefixLabel;
private static readonly GUIContent s_PrefixLabel = new GUIContent((string)null);
private static Rect s_PrefixTotalRect;
private static Rect s_PrefixRect;
private static GUIStyle s_PrefixStyle;
private static GUIStyle s_IconButtonStyle;
private static Color s_PrefixGUIColor;
private static string s_LabelHighlightContext;
private static Color s_LabelHighlightColor;
private static Color s_LabelHighlightSelectionColor;
private static string[] m_FlagNames;
private static int[] m_FlagValues;
internal static float lineHeight { get; set; } = kSingleLineHeight;
// Makes the following controls give the appearance of editing multiple different values.
public static bool showMixedValue { get; set; }
private static readonly GUIContent s_MixedValueContent = EditorGUIUtility.TrTextContent("\u2014", "Mixed Values");
internal static GUIContent mixedValueContent => s_MixedValueContent;
private static readonly Color s_MixedValueContentColor = new Color(1, 1, 1, 0.5f);
private static Color s_MixedValueContentColorTemp = Color.white;
internal static SavedBool s_ShowRepaintDots = new SavedBool("ShowRepaintDots", true);
internal static readonly Regex s_ATagRegex = new Regex(@"(?<=\b="")[^""]*");
internal static readonly Regex s_LinkTagRegex = new Regex(@"(?<=\b=')[^']*");
static class Styles
{
public static Texture2D prefabOverlayAddedIcon = EditorGUIUtility.LoadIcon("PrefabOverlayAdded Icon");
public static Texture2D prefabOverlayRemovedIcon = EditorGUIUtility.LoadIcon("PrefabOverlayRemoved Icon");
public static readonly GUIStyle linkButton = "FloatFieldLinkButton";
public static Texture2D repaintDot = EditorGUIUtility.LoadIcon("RepaintDot");
public static string revertPropertyValueIdenticalToSource = L10n.Tr("Revert (identical value to Prefab '{0}')");
}
static EditorGUI()
{
hyperLinkClicked += EditorGUI_OpenFileOnHyperLinkClicked;
}
internal static void BeginHandleMixedValueContentColor()
{
s_MixedValueContentColorTemp = GUI.contentColor;
GUI.contentColor = showMixedValue ? (GUI.contentColor * s_MixedValueContentColor) : GUI.contentColor;
}
internal static void EndHandleMixedValueContentColor()
{
GUI.contentColor = s_MixedValueContentColorTemp;
}
[RequiredByNativeCode]
internal static bool IsEditingTextField()
{
return RecycledTextEditor.s_ActuallyEditing && activeEditor != null;
}
internal static void EndEditingActiveTextField()
{
activeEditor?.EndEditing();
}
public static void FocusTextInControl(string name)
{
GUI.FocusControl(name);
EditorGUIUtility.editingTextField = true;
}
// STACKS
internal static void ClearStacks()
{
s_EnabledStack.Clear();
s_IsInsideListStack.Clear();
GUI.isInsideList = false;
s_ChangedStack.Clear();
s_PropertyStack.Clear();
MaterialProperty.ClearStack();
ScriptAttributeUtility.s_DrawerStack.Clear();
s_FoldoutHeaderGroupActive = 0;
}
private static readonly Stack<PropertyGUIData> s_PropertyStack = new Stack<PropertyGUIData>();
private static readonly Stack<bool> s_EnabledStack = new Stack<bool>();
private static readonly Stack<(bool insideList, int depth)> s_IsInsideListStack = new Stack<(bool insideList, int depth)>();
// @TODO: API soon to be deprecated but still in a grace period; documentation states that users
// are encouraged to use EditorGUI.DisabledScope instead. Uncomment next line when appropriate.
// [System.Obsolete("Use DisabledScope instead", false)]
public class DisabledGroupScope : GUI.Scope
{
public DisabledGroupScope(bool disabled)
{
BeginDisabledGroup(disabled);
}
protected override void CloseScope()
{
EndDisabledGroup();
}
}
// Create a group of controls that can be disabled.
// @TODO: API soon to be deprecated but still in a grace period; documentation states that users
// are encouraged to use EditorGUI.DisabledScope instead. Uncomment next line when appropriate.
// [System.Obsolete("Use DisabledScope instead", false)]
public static void BeginDisabledGroup(bool disabled)
{
BeginDisabled(disabled);
}
// Ends a disabled group started with BeginDisabledGroup.
// @TODO: API soon to be deprecated but still in a grace period; documentation states that users
// are encouraged to use EditorGUI.DisabledScope instead. Uncomment next line when appropriate.
// [System.Obsolete("Use DisabledScope instead", false)]
public static void EndDisabledGroup()
{
EndDisabled();
}
public struct DisabledScope : IDisposable
{
bool m_Disposed;
public DisabledScope(bool disabled)
{
m_Disposed = false;
BeginDisabled(disabled);
}
public void Dispose()
{
if (m_Disposed)
return;
m_Disposed = true;
if (!GUIUtility.guiIsExiting)
EndDisabled();
}
}
internal class DisabledGuiViewInputScope : GUI.Scope
{
GUIView m_View;
bool m_WasDisabled;
public DisabledGuiViewInputScope(GUIView view, bool disabled)
{
m_View = view;
if (m_View != null)
{
m_WasDisabled = view.disableInputEvents;
m_View.disableInputEvents = disabled;
}
}
protected override void CloseScope()
{
if (m_View != null)
m_View.disableInputEvents = m_WasDisabled;
}
}
internal struct LabelHighlightScope : IDisposable
{
bool m_Disposed;
public LabelHighlightScope(string labelHighlightContext, Color selectionColor, Color textColor)
{
m_Disposed = false;
BeginLabelHighlight(labelHighlightContext, selectionColor, textColor);
}
public void Dispose()
{
if (m_Disposed)
return;
m_Disposed = true;
if (!GUIUtility.guiIsExiting)
EndLabelHighlight();
}
}
internal struct CursorColorScope : IDisposable
{
private Color oldCursorColor;
public CursorColorScope(Color color)
{
oldCursorColor = GUI.skin.settings.cursorColor;
GUI.skin.settings.cursorColor = color;
}
public void Dispose()
{
GUI.skin.settings.cursorColor = oldCursorColor;
}
}
// Create a group of controls that can be disabled.
internal static void BeginDisabled(bool disabled)
{
s_EnabledStack.Push(GUI.enabled);
GUI.enabled &= !disabled;
}
// Ends a disabled group started with BeginDisabled.
internal static void EndDisabled()
{
// Stack might have been cleared with ClearStack(), check before pop.
if (s_EnabledStack.Count > 0)
GUI.enabled = s_EnabledStack.Pop();
}
internal static void BeginIsInsideList(int depth)
{
s_IsInsideListStack.Push((GUI.isInsideList, depth));
GUI.isInsideList = true;
}
internal static int GetInsideListDepth()
{
if (s_IsInsideListStack.Count > 0)
return s_IsInsideListStack.Peek().depth;
return -1;
}
internal static void EndIsInsideList()
{
// Stack might have been cleared with ClearStack(), check before pop.
if (s_IsInsideListStack.Count > 0)
GUI.isInsideList = s_IsInsideListStack.Pop().insideList;
else
GUI.isInsideList = false;
}
private static readonly Stack<bool> s_ChangedStack = new Stack<bool>();
public class ChangeCheckScope : GUI.Scope
{
bool m_ChangeChecked;
bool m_Changed;
public bool changed
{
get
{
if (!m_ChangeChecked)
{
m_ChangeChecked = true;
m_Changed = EndChangeCheck();
}
return m_Changed;
}
}
public ChangeCheckScope()
{
BeginChangeCheck();
}
protected override void CloseScope()
{
if (!m_ChangeChecked)
EndChangeCheck();
}
}
// Check if any control was changed inside a block of code.
public static void BeginChangeCheck()
{
s_ChangedStack.Push(GUI.changed);
GUI.changed = false;
}
// Ends a change check started with BeginChangeCheck ().
// Note: BeginChangeCheck/EndChangeCheck supports nesting
// For ex.,
// BeginChangeCheck()
// BeginChangeCheck()
// <GUI control changes>
// EndChangeCheck() <-- will return true
// EndChangeCheck() <-- will return true
public static bool EndChangeCheck()
{
// as we allow external code to clear stacks through ClearStacks(),
// we must be resilient to stacks having been unexpectedly emptied,
// when that happens, it is reasonable to assume things indeed have changed
if (s_ChangedStack.Count == 0)
{
GUI.changed = true;
return true;
}
bool changed = GUI.changed;
GUI.changed |= s_ChangedStack.Pop();
return changed;
}
public struct MixedValueScope : IDisposable
{
bool m_DefaultMixedValue;
public MixedValueScope(bool newMixedValue)
{
m_DefaultMixedValue = showMixedValue;
showMixedValue = newMixedValue;
}
void IDisposable.Dispose()
{
showMixedValue = m_DefaultMixedValue;
}
}
internal class RecycledTextEditor : TextEditor
{
internal static bool s_ActuallyEditing = false; // internal so we can save this state.
internal static bool s_EditingWasCompleted = false; // internal so we can save this state.
internal static bool s_AllowContextCutOrPaste = true; // e.g. selectable labels only allow for copying
private long[] s_OriginalLongValues;
private double[] s_OriginalDoubleValues;
IMECompositionMode m_IMECompositionModeBackup;
public long[] GetOriginalLongValues()
{
return s_OriginalLongValues;
}
public double[] GetOriginalDoubleValues()
{
return s_OriginalDoubleValues;
}
internal bool IsEditingControl(int id)
{
return GUIUtility.keyboardControl == id && controlID == id && s_ActuallyEditing && GUIView.current.hasFocus;
}
public virtual void BeginEditing(int id, string newText, Rect position, GUIStyle style, bool multiline, bool passwordField)
{
if (IsEditingControl(id))
{
return;
}
activeEditor?.EndEditing();
activeEditor = this;
controlID = id;
text = s_OriginalText = newText;
isMultiline = multiline;
this.position = position;
this.style = style;
isPasswordField = passwordField;
s_ActuallyEditing = true;
scrollOffset = Vector2.zero;
UnityEditor.Undo.IncrementCurrentGroup();
m_IMECompositionModeBackup = Input.imeCompositionMode;
Input.imeCompositionMode = IMECompositionMode.On;
if (EditorGUI.s_PropertyStack.Count > 0)
{
var property = EditorGUI.s_PropertyStack.Peek().property;
switch (property.propertyType)
{
case SerializedPropertyType.Integer:
s_OriginalLongValues = new long[property.serializedObject.targetObjectsCount];
property.allLongValues.CopyTo(s_OriginalLongValues, 0);
break;
case SerializedPropertyType.Float:
s_OriginalDoubleValues = new double[property.serializedObject.targetObjectsCount];
property.allDoubleValues.CopyTo(s_OriginalDoubleValues, 0);
break;
default:
s_OriginalDoubleValues = null;
s_OriginalLongValues = null;
break;
}
}
}
public virtual void EndEditing()
{
if (activeEditor == this)
{
activeEditor = null;
}
controlID = 0;
s_ActuallyEditing = false;
s_EditingWasCompleted = false;
s_AllowContextCutOrPaste = true;
UnityEditor.Undo.IncrementCurrentGroup();
Input.imeCompositionMode = m_IMECompositionModeBackup;
}
}
// There can be two way something can get focus
internal sealed class DelayedTextEditor : RecycledTextEditor
{
private int controlThatHadFocus = 0, messageControl = 0;
internal string controlThatHadFocusValue = "";
private GUIView viewThatHadFocus;
private bool m_CommitCommandSentOnLostFocus;
private const string CommitCommand = "DelayedControlShouldCommit";
private bool m_IgnoreBeginGUI = false;
public void BeginGUI()
{
if (m_IgnoreBeginGUI)
{
return;
}
if (GUIUtility.keyboardControl == controlID)
{
controlThatHadFocus = GUIUtility.keyboardControl;
controlThatHadFocusValue = text;
viewThatHadFocus = GUIView.current;
}
else
{
controlThatHadFocus = 0;
}
}
public void EndGUI(EventType type)
{
int sendID = 0;
if (controlThatHadFocus != 0 && controlThatHadFocus != GUIUtility.keyboardControl)
{
sendID = controlThatHadFocus;
controlThatHadFocus = 0;
}
if (sendID != 0 && !m_CommitCommandSentOnLostFocus)
{
messageControl = sendID;
// Debug.Log ("" + messageControl + " lost focus to " + GUIUtility.keyboardControl+ " in " + type+". Sending Message. value:" + controlThatHadFocusValue);
m_IgnoreBeginGUI = true;
// Explicitly set the keyboardControl for the view that had focus in preparation for the following SendEvent,
// but only if the current view is the view that had focus.
// This is necessary as GUIView::OnInputEvent (native) will load the old keyboardControl for nested OnGUI calls.
if (GUIView.current == viewThatHadFocus)
viewThatHadFocus.SetKeyboardControl(GUIUtility.keyboardControl);
viewThatHadFocus.SendEvent(EditorGUIUtility.CommandEvent(CommitCommand));
m_IgnoreBeginGUI = false;
// Debug.Log ("Afterwards: " + GUIUtility.keyboardControl);
messageControl = 0;
}
}
public override void EndEditing()
{
//The following block handles the case where a different window is focus while editing delayed text box
if (Event.current == null)
{
// We set this flag because of a bug that was trigger when you switched focus to another window really fast
// right after focusing on the text box. For some reason keyboardControl was changed and the commit message
// was being sent twice which caused layout issues.
m_CommitCommandSentOnLostFocus = true;
m_IgnoreBeginGUI = true;
messageControl = controlID;
var temp = GUIUtility.keyboardControl;
if (viewThatHadFocus != null)
{
viewThatHadFocus.SetKeyboardControl(0);
viewThatHadFocus.SendEvent(EditorGUIUtility.CommandEvent(CommitCommand));
viewThatHadFocus.SetKeyboardControl(temp);
}
m_IgnoreBeginGUI = false;
messageControl = 0;
}
base.EndEditing();
}
public string OnGUI(int id, string value, out bool changed)
{
Event evt = Event.current;
if (evt.type == EventType.ExecuteCommand && evt.commandName == CommitCommand && id == messageControl)
{
m_CommitCommandSentOnLostFocus = false;
// Only set changed to true if the value has actually changed. Otherwise EditorGUI.EndChangeCheck will report false positives,
// which could for example cause unwanted undo's to be registered (in the case of e.g. editing terrain resolution, this can cause several seconds of delay)
if (!showMixedValue || controlThatHadFocusValue != k_MultiEditValueString)
changed = value != controlThatHadFocusValue;
else
changed = false;
evt.Use();
messageControl = 0;
return controlThatHadFocusValue;
}
changed = false;
return value;
}
}
internal sealed class PopupMenuEvent
{
public string commandName;
public GUIView receiver;
public PopupMenuEvent(string cmd, GUIView v)
{
commandName = cmd;
receiver = v;
}
public void SendEvent()
{
if (receiver)
{
receiver.SendEvent(EditorGUIUtility.CommandEvent(commandName));
}
else
{
Debug.LogError("BUG: We don't have a receiver set up, please report");
}
}
}
static void ShowTextEditorPopupMenu()
{
GenericMenu pm = new GenericMenu();
var enabled = GUI.enabled;
// Cut
if (RecycledTextEditor.s_AllowContextCutOrPaste)
{
if ((s_RecycledEditor.hasSelection || s_DelayedTextEditor.hasSelection) && !s_RecycledEditor.isPasswordField && enabled && !EditorGUI.showMixedValue)
pm.AddItem(EditorGUIUtility.TrTextContent("Cut"), false, new PopupMenuEvent(EventCommandNames.Cut, GUIView.current).SendEvent);
else
pm.AddDisabledItem(EditorGUIUtility.TrTextContent("Cut"));
}
// Copy -- when GUI is disabled, allow Copy even with no selection (will copy everything)
if (((s_RecycledEditor.hasSelection || s_DelayedTextEditor.hasSelection) || !enabled) && !s_RecycledEditor.isPasswordField && !EditorGUI.showMixedValue)
pm.AddItem(EditorGUIUtility.TrTextContent("Copy"), false, new PopupMenuEvent(EventCommandNames.Copy, GUIView.current).SendEvent);
else
pm.AddDisabledItem(EditorGUIUtility.TrTextContent("Copy"));
// Paste
if (s_RecycledEditor.CanPaste() && RecycledTextEditor.s_AllowContextCutOrPaste && enabled)
{
pm.AddItem(EditorGUIUtility.TrTextContent("Paste"), false, new PopupMenuEvent(EventCommandNames.Paste, GUIView.current).SendEvent);
}
else
{
// pm.AddDisabledItem (EditorGUIUtility.TrTextContent ("Paste"));
}
pm.ShowAsContext();
}
// Is the platform-dependent "action" modifier key held down? (RO)
public static bool actionKey
{
get
{
if (Event.current == null)
{
return false;
}
if (Application.platform == RuntimePlatform.OSXEditor)
{
return Event.current.command;
}
else
{
return Event.current.control;
}
}
}
[RequiredByNativeCode]
internal static void BeginCollectTooltips()
{
isCollectingTooltips = true;
}
[RequiredByNativeCode]
internal static void EndCollectTooltips()
{
isCollectingTooltips = false;
}
public static void DropShadowLabel(Rect position, string text)
{
DoDropShadowLabel(position, EditorGUIUtility.TempContent(text), "PreOverlayLabel", .6f);
}
public static void DropShadowLabel(Rect position, GUIContent content)
{
DoDropShadowLabel(position, content, "PreOverlayLabel", .6f);
}
public static void DropShadowLabel(Rect position, string text, GUIStyle style)
{
DoDropShadowLabel(position, EditorGUIUtility.TempContent(text), style, .6f);
}
// Draws a label with a drop shadow.
public static void DropShadowLabel(Rect position, GUIContent content, GUIStyle style)
{
DoDropShadowLabel(position, content, style, .6f);
}
internal static void DoDropShadowLabel(Rect position, GUIContent content, GUIStyle style, float shadowOpa)
{
if (Event.current.type != EventType.Repaint)
{
return;
}
DrawLabelShadow(position, content, style, shadowOpa);
style.Draw(position, content, false, false, false, false);
}
internal static void DrawLabelShadow(Rect position, GUIContent content, GUIStyle style, float shadowOpa)
{
Color temp = GUI.color, temp2 = GUI.contentColor, temp3 = GUI.backgroundColor;
// Draw only background
GUI.contentColor = new Color(0, 0, 0, 0);
style.Draw(position, content, false, false, false, false);
// Blur foreground
position.y += 1;
GUI.backgroundColor = new Color(0, 0, 0, 0);
GUI.contentColor = temp2;
Draw4(position, content, 1, GUI.color.a * shadowOpa, style);
Draw4(position, content, 2, GUI.color.a * shadowOpa * .42f, style);
// Draw final foreground
GUI.color = temp;
GUI.backgroundColor = temp3;
}
private static void Draw4(Rect position, GUIContent content, float offset, float alpha, GUIStyle style)
{
GUI.color = new Color(0, 0, 0, alpha);
position.y -= offset;
style.Draw(position, content, false, false, false, false);
position.y += offset * 2;
style.Draw(position, content, false, false, false, false);
position.y -= offset;
position.x -= offset;
style.Draw(position, content, false, false, false, false);
position.x += offset * 2;
style.Draw(position, content, false, false, false, false);
}
private static string ValidateTextLimit(String editorText)
{
string title = "Search String Character Limit Exceeded";
string fullMessage = string.Format("You have entered {0} characters. The character limit is {1} characters because of risk of Editor slowdown. Please confirm that you would like to continue (not recommended) or clear the field", editorText.Length, kSearchFieldTextLimit);
bool selectedContinueOption = EditorUtility.DisplayDialog(title, fullMessage, "Continue", "Cancel");
if (selectedContinueOption)
s_SearchFieldTextLimitApproved = true;
else
editorText = editorText.Substring(0, kSearchFieldTextLimit);
GUIUtility.SetKeyboardControlToLastControlId();
return editorText;
}
static bool IsPrintableChar(char c)
{
if (c < 32)
{
return false;
}
return true;
}
internal static bool MightBePrintableKey(Event evt)
{
if (evt.command || evt.control)
return false;
if (evt.keyCode >= KeyCode.Mouse0 && evt.keyCode <= KeyCode.Mouse6)
return false;
if (evt.keyCode >= KeyCode.JoystickButton0 && evt.keyCode <= KeyCode.Joystick8Button19)
return false;
if (evt.keyCode >= KeyCode.F1 && evt.keyCode <= KeyCode.F15)
return false;
switch (evt.keyCode)
{
case KeyCode.AltGr:
case KeyCode.Backspace:
case KeyCode.CapsLock:
case KeyCode.Clear:
case KeyCode.Delete:
case KeyCode.DownArrow:
case KeyCode.End:
case KeyCode.Escape:
case KeyCode.Help:
case KeyCode.Home:
case KeyCode.Insert:
case KeyCode.LeftAlt:
case KeyCode.LeftArrow:
case KeyCode.LeftCommand: // same as LeftApple
case KeyCode.LeftControl:
case KeyCode.LeftShift:
case KeyCode.LeftWindows:
case KeyCode.Menu:
case KeyCode.Numlock:
case KeyCode.PageDown:
case KeyCode.PageUp:
case KeyCode.Pause:
case KeyCode.Print:
case KeyCode.RightAlt:
case KeyCode.RightArrow:
case KeyCode.RightCommand: // same as RightApple
case KeyCode.RightControl:
case KeyCode.RightShift:
case KeyCode.RightWindows:
case KeyCode.ScrollLock:
case KeyCode.SysReq:
case KeyCode.UpArrow:
return false;
case KeyCode.None:
return IsPrintableChar(evt.character);
default:
return true;
}
}
static EventType GetEventTypeForControlAllowDisabledContextMenuPaste(Event evt, int id)
{
// UI is enabled: regular code path
var wasEnabled = GUI.enabled;
if (wasEnabled)
return evt.GetTypeForControl(id);
// UI is disabled: get type as if it was enabled
GUI.enabled = true;
var type = evt.GetTypeForControl(id);
GUI.enabled = false;
// these events are always processed, no matter the enabled/disabled state (IMGUI::GetEventType)
if (type == EventType.Repaint || type == EventType.Layout || type == EventType.Used)
return type;
// allow context / right click, and "Copy" commands
if (type == EventType.ContextClick)
return type;
if (type == EventType.MouseDown && evt.button == 1)
return type;
if ((type == EventType.ValidateCommand || type == EventType.ExecuteCommand) && evt.commandName == EventCommandNames.Copy)
return type;
// ignore all other events for disabled controls
return EventType.Ignore;
}
internal static string DoTextField(RecycledTextEditor editor, int id, Rect position, string text, GUIStyle style, string allowedletters, out bool changed, bool reset, bool multiline, bool passwordField)
{
return DoTextField(editor, id, position, text, style, allowedletters, out changed, reset, multiline, passwordField, null);
}
// Should we select all text from the current field when the mouse goes up?
// (We need to keep track of this to support both SwipeSelection & initial click selects all)
internal static string DoTextField(RecycledTextEditor editor, int id, Rect position, string text, GUIStyle style, string allowedletters, out bool changed, bool reset, bool multiline, bool passwordField, GUIStyle cancelButtonStyle, bool checkTextLimit = false)
{
Event evt = Event.current;
// If the text field represents multiple values, the text should always start out being empty when editing it.
// This empty text will not be saved when simply clicking in the text field, or tabbing to it,
// since GUI.changed is only set to true if the user alters the string.
// Nevertheless, we also backup and return the original value if nothing changed.
// It's just nice that output is the same as input when nothing changed,
// even if the output should really be ignored when GUI.changed is false.
string origText = text;
// We assume the text is actually valid, but we do not want to change the returned value if nothing was changed
// So we should only check for null string on the internal text and not affect the origText which will be returned if nothing changed
if (text == null)
{
text = string.Empty;
}
if (showMixedValue)
{
text = k_MultiEditValueString;
}
// If we have keyboard control and our window have focus, we need to sync up the editor.
if (HasKeyboardFocus(id) && Event.current.type != EventType.Layout)
{
// If the editor is already set up, we just need to sync position, etc...
if (editor.IsEditingControl(id))
{
// Fast path flag to ensure that we only update the scroll offset if the text area grew (dynamic height)
bool requireUpdateScrollOffset = editor.position.height != position.height;
editor.position = position;