MenuOptions.cs
17.5 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
using System;
using UnityEditor.SceneManagement;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.UI;
using UnityEngine.SceneManagement;
using UnityEditor.Experimental.SceneManagement;
namespace UnityEditor.UI
{
/// <summary>
/// This script adds the UI menu options to the Unity Editor.
/// </summary>
static internal class MenuOptions
{
private const string kUILayerName = "UI";
private const string kStandardSpritePath = "UI/Skin/UISprite.psd";
private const string kBackgroundSpritePath = "UI/Skin/Background.psd";
private const string kInputFieldBackgroundPath = "UI/Skin/InputFieldBackground.psd";
private const string kKnobPath = "UI/Skin/Knob.psd";
private const string kCheckmarkPath = "UI/Skin/Checkmark.psd";
private const string kDropdownArrowPath = "UI/Skin/DropdownArrow.psd";
private const string kMaskPath = "UI/Skin/UIMask.psd";
static private DefaultControls.Resources s_StandardResources;
static private DefaultControls.Resources GetStandardResources()
{
if (s_StandardResources.standard == null)
{
s_StandardResources.standard = AssetDatabase.GetBuiltinExtraResource<Sprite>(kStandardSpritePath);
s_StandardResources.background = AssetDatabase.GetBuiltinExtraResource<Sprite>(kBackgroundSpritePath);
s_StandardResources.inputField = AssetDatabase.GetBuiltinExtraResource<Sprite>(kInputFieldBackgroundPath);
s_StandardResources.knob = AssetDatabase.GetBuiltinExtraResource<Sprite>(kKnobPath);
s_StandardResources.checkmark = AssetDatabase.GetBuiltinExtraResource<Sprite>(kCheckmarkPath);
s_StandardResources.dropdown = AssetDatabase.GetBuiltinExtraResource<Sprite>(kDropdownArrowPath);
s_StandardResources.mask = AssetDatabase.GetBuiltinExtraResource<Sprite>(kMaskPath);
}
return s_StandardResources;
}
private class DefaultEditorFactory : DefaultControls.IFactoryControls
{
public static DefaultEditorFactory Default = new DefaultEditorFactory();
public GameObject CreateGameObject(string name, params Type[] components)
{
return ObjectFactory.CreateGameObject(name, components);
}
}
private class FactorySwapToEditor : IDisposable
{
DefaultControls.IFactoryControls factory;
public FactorySwapToEditor()
{
factory = DefaultControls.factory;
DefaultControls.factory = DefaultEditorFactory.Default;
}
public void Dispose()
{
DefaultControls.factory = factory;
}
}
private static void SetPositionVisibleinSceneView(RectTransform canvasRTransform, RectTransform itemTransform)
{
SceneView sceneView = SceneView.lastActiveSceneView;
// Couldn't find a SceneView. Don't set position.
if (sceneView == null || sceneView.camera == null)
return;
// Create world space Plane from canvas position.
Vector2 localPlanePosition;
Camera camera = sceneView.camera;
Vector3 position = Vector3.zero;
if (RectTransformUtility.ScreenPointToLocalPointInRectangle(canvasRTransform, new Vector2(camera.pixelWidth / 2, camera.pixelHeight / 2), camera, out localPlanePosition))
{
// Adjust for canvas pivot
localPlanePosition.x = localPlanePosition.x + canvasRTransform.sizeDelta.x * canvasRTransform.pivot.x;
localPlanePosition.y = localPlanePosition.y + canvasRTransform.sizeDelta.y * canvasRTransform.pivot.y;
localPlanePosition.x = Mathf.Clamp(localPlanePosition.x, 0, canvasRTransform.sizeDelta.x);
localPlanePosition.y = Mathf.Clamp(localPlanePosition.y, 0, canvasRTransform.sizeDelta.y);
// Adjust for anchoring
position.x = localPlanePosition.x - canvasRTransform.sizeDelta.x * itemTransform.anchorMin.x;
position.y = localPlanePosition.y - canvasRTransform.sizeDelta.y * itemTransform.anchorMin.y;
Vector3 minLocalPosition;
minLocalPosition.x = canvasRTransform.sizeDelta.x * (0 - canvasRTransform.pivot.x) + itemTransform.sizeDelta.x * itemTransform.pivot.x;
minLocalPosition.y = canvasRTransform.sizeDelta.y * (0 - canvasRTransform.pivot.y) + itemTransform.sizeDelta.y * itemTransform.pivot.y;
Vector3 maxLocalPosition;
maxLocalPosition.x = canvasRTransform.sizeDelta.x * (1 - canvasRTransform.pivot.x) - itemTransform.sizeDelta.x * itemTransform.pivot.x;
maxLocalPosition.y = canvasRTransform.sizeDelta.y * (1 - canvasRTransform.pivot.y) - itemTransform.sizeDelta.y * itemTransform.pivot.y;
position.x = Mathf.Clamp(position.x, minLocalPosition.x, maxLocalPosition.x);
position.y = Mathf.Clamp(position.y, minLocalPosition.y, maxLocalPosition.y);
}
itemTransform.anchoredPosition = position;
itemTransform.localRotation = Quaternion.identity;
itemTransform.localScale = Vector3.one;
}
private static void PlaceUIElementRoot(GameObject element, MenuCommand menuCommand)
{
GameObject parent = menuCommand.context as GameObject;
bool explicitParentChoice = true;
if (parent == null)
{
parent = GetOrCreateCanvasGameObject();
explicitParentChoice = false;
// If in Prefab Mode, Canvas has to be part of Prefab contents,
// otherwise use Prefab root instead.
PrefabStage prefabStage = PrefabStageUtility.GetCurrentPrefabStage();
if (prefabStage != null && !prefabStage.IsPartOfPrefabContents(parent))
parent = prefabStage.prefabContentsRoot;
}
if (parent.GetComponentsInParent<Canvas>(true).Length == 0)
{
// Create canvas under context GameObject,
// and make that be the parent which UI element is added under.
GameObject canvas = MenuOptions.CreateNewUI();
Undo.SetTransformParent(canvas.transform, parent.transform, "");
parent = canvas;
}
GameObjectUtility.EnsureUniqueNameForSibling(element);
SetParentAndAlign(element, parent);
if (!explicitParentChoice) // not a context click, so center in sceneview
SetPositionVisibleinSceneView(parent.GetComponent<RectTransform>(), element.GetComponent<RectTransform>());
// This call ensure any change made to created Objects after they where registered will be part of the Undo.
Undo.RegisterFullObjectHierarchyUndo(parent == null ? element : parent, "");
// We have to fix up the undo name since the name of the object was only known after reparenting it.
Undo.SetCurrentGroupName("Create " + element.name);
Selection.activeGameObject = element;
}
private static void SetParentAndAlign(GameObject child, GameObject parent)
{
if (parent == null)
return;
Undo.SetTransformParent(child.transform, parent.transform, "");
RectTransform rectTransform = child.transform as RectTransform;
if (rectTransform)
{
rectTransform.anchoredPosition = Vector2.zero;
Vector3 localPosition = rectTransform.localPosition;
localPosition.z = 0;
rectTransform.localPosition = localPosition;
}
else
{
child.transform.localPosition = Vector3.zero;
}
child.transform.localRotation = Quaternion.identity;
child.transform.localScale = Vector3.one;
SetLayerRecursively(child, parent.layer);
}
private static void SetLayerRecursively(GameObject go, int layer)
{
go.layer = layer;
Transform t = go.transform;
for (int i = 0; i < t.childCount; i++)
SetLayerRecursively(t.GetChild(i).gameObject, layer);
}
// Graphic elements
[MenuItem("GameObject/UI/Text", false, 2000)]
static public void AddText(MenuCommand menuCommand)
{
GameObject go;
using (new FactorySwapToEditor())
go = DefaultControls.CreateText(GetStandardResources());
PlaceUIElementRoot(go, menuCommand);
}
[MenuItem("GameObject/UI/Image", false, 2001)]
static public void AddImage(MenuCommand menuCommand)
{
GameObject go;
using (new FactorySwapToEditor())
go = DefaultControls.CreateImage(GetStandardResources());
PlaceUIElementRoot(go, menuCommand);
}
[MenuItem("GameObject/UI/Raw Image", false, 2002)]
static public void AddRawImage(MenuCommand menuCommand)
{
GameObject go;
using (new FactorySwapToEditor())
go = DefaultControls.CreateRawImage(GetStandardResources());
PlaceUIElementRoot(go, menuCommand);
}
// Controls
// Button and toggle are controls you just click on.
[MenuItem("GameObject/UI/Button", false, 2030)]
static public void AddButton(MenuCommand menuCommand)
{
GameObject go;
using (new FactorySwapToEditor())
go = DefaultControls.CreateButton(GetStandardResources());
PlaceUIElementRoot(go, menuCommand);
}
[MenuItem("GameObject/UI/Toggle", false, 2031)]
static public void AddToggle(MenuCommand menuCommand)
{
GameObject go;
using (new FactorySwapToEditor())
go = DefaultControls.CreateToggle(GetStandardResources());
PlaceUIElementRoot(go, menuCommand);
}
// Slider and Scrollbar modify a number
[MenuItem("GameObject/UI/Slider", false, 2033)]
static public void AddSlider(MenuCommand menuCommand)
{
GameObject go;
using (new FactorySwapToEditor())
go = DefaultControls.CreateSlider(GetStandardResources());
PlaceUIElementRoot(go, menuCommand);
}
[MenuItem("GameObject/UI/Scrollbar", false, 2034)]
static public void AddScrollbar(MenuCommand menuCommand)
{
GameObject go;
using (new FactorySwapToEditor())
go = DefaultControls.CreateScrollbar(GetStandardResources());
PlaceUIElementRoot(go, menuCommand);
}
// More advanced controls below
[MenuItem("GameObject/UI/Dropdown", false, 2035)]
static public void AddDropdown(MenuCommand menuCommand)
{
GameObject go;
using (new FactorySwapToEditor())
go = DefaultControls.CreateDropdown(GetStandardResources());
PlaceUIElementRoot(go, menuCommand);
}
[MenuItem("GameObject/UI/Input Field", false, 2036)]
public static void AddInputField(MenuCommand menuCommand)
{
GameObject go;
using (new FactorySwapToEditor())
go = DefaultControls.CreateInputField(GetStandardResources());
PlaceUIElementRoot(go, menuCommand);
}
// Containers
[MenuItem("GameObject/UI/Canvas", false, 2060)]
static public void AddCanvas(MenuCommand menuCommand)
{
var go = CreateNewUI();
SetParentAndAlign(go, menuCommand.context as GameObject);
if (go.transform.parent as RectTransform)
{
RectTransform rect = go.transform as RectTransform;
rect.anchorMin = Vector2.zero;
rect.anchorMax = Vector2.one;
rect.anchoredPosition = Vector2.zero;
rect.sizeDelta = Vector2.zero;
}
Selection.activeGameObject = go;
}
[MenuItem("GameObject/UI/Panel", false, 2061)]
static public void AddPanel(MenuCommand menuCommand)
{
GameObject go;
using (new FactorySwapToEditor())
go = DefaultControls.CreatePanel(GetStandardResources());
PlaceUIElementRoot(go, menuCommand);
// Panel is special, we need to ensure there's no padding after repositioning.
RectTransform rect = go.GetComponent<RectTransform>();
rect.anchoredPosition = Vector2.zero;
rect.sizeDelta = Vector2.zero;
}
[MenuItem("GameObject/UI/Scroll View", false, 2062)]
static public void AddScrollView(MenuCommand menuCommand)
{
GameObject go;
using (new FactorySwapToEditor())
go = DefaultControls.CreateScrollView(GetStandardResources());
PlaceUIElementRoot(go, menuCommand);
}
// Helper methods
static public GameObject CreateNewUI()
{
// Root for the UI
var root = ObjectFactory.CreateGameObject("Canvas", typeof(Canvas), typeof(CanvasScaler), typeof(GraphicRaycaster));
root.layer = LayerMask.NameToLayer(kUILayerName);
Canvas canvas = root.GetComponent<Canvas>();
canvas.renderMode = RenderMode.ScreenSpaceOverlay;
// Works for all stages.
StageUtility.PlaceGameObjectInCurrentStage(root);
bool customScene = false;
PrefabStage prefabStage = PrefabStageUtility.GetCurrentPrefabStage();
if (prefabStage != null)
{
Undo.SetTransformParent(root.transform, prefabStage.prefabContentsRoot.transform, "");
customScene = true;
}
Undo.SetCurrentGroupName("Create " + root.name);
// If there is no event system add one...
// No need to place event system in custom scene as these are temporary anyway.
// It can be argued for or against placing it in the user scenes,
// but let's not modify scene user is not currently looking at.
if (!customScene)
CreateEventSystem(false);
return root;
}
[MenuItem("GameObject/UI/Event System", false, 2100)]
public static void CreateEventSystem(MenuCommand menuCommand)
{
GameObject parent = menuCommand.context as GameObject;
CreateEventSystem(true, parent);
}
private static void CreateEventSystem(bool select)
{
CreateEventSystem(select, null);
}
private static void CreateEventSystem(bool select, GameObject parent)
{
StageHandle stage = parent == null ? StageUtility.GetCurrentStageHandle() : StageUtility.GetStageHandle(parent);
var esys = stage.FindComponentOfType<EventSystem>();
if (esys == null)
{
var eventSystem = ObjectFactory.CreateGameObject("EventSystem");
if (parent == null)
StageUtility.PlaceGameObjectInCurrentStage(eventSystem);
else
SetParentAndAlign(eventSystem, parent);
esys = ObjectFactory.AddComponent<EventSystem>(eventSystem);
ObjectFactory.AddComponent<StandaloneInputModule>(eventSystem);
Undo.RegisterCreatedObjectUndo(eventSystem, "Create " + eventSystem.name);
}
if (select && esys != null)
{
Selection.activeGameObject = esys.gameObject;
}
}
// Helper function that returns a Canvas GameObject; preferably a parent of the selection, or other existing Canvas.
static public GameObject GetOrCreateCanvasGameObject()
{
GameObject selectedGo = Selection.activeGameObject;
// Try to find a gameobject that is the selected GO or one if its parents.
Canvas canvas = (selectedGo != null) ? selectedGo.GetComponentInParent<Canvas>() : null;
if (IsValidCanvas(canvas))
return canvas.gameObject;
// No canvas in selection or its parents? Then use any valid canvas.
// We have to find all loaded Canvases, not just the ones in main scenes.
Canvas[] canvasArray = StageUtility.GetCurrentStageHandle().FindComponentsOfType<Canvas>();
for (int i = 0; i < canvasArray.Length; i++)
if (IsValidCanvas(canvasArray[i]))
return canvasArray[i].gameObject;
// No canvas in the scene at all? Then create a new one.
return MenuOptions.CreateNewUI();
}
static bool IsValidCanvas(Canvas canvas)
{
if (canvas == null || !canvas.gameObject.activeInHierarchy)
return false;
// It's important that the non-editable canvas from a prefab scene won't be rejected,
// but canvases not visible in the Hierarchy at all do. Don't check for HideAndDontSave.
if (EditorUtility.IsPersistent(canvas) || (canvas.hideFlags & HideFlags.HideInHierarchy) != 0)
return false;
if (StageUtility.GetStageHandle(canvas.gameObject) != StageUtility.GetCurrentStageHandle())
return false;
return true;
}
}
}