-
Notifications
You must be signed in to change notification settings - Fork 166
Expand file tree
/
Copy pathSubsystemWizardWindow.cs
More file actions
419 lines (376 loc) · 17.4 KB
/
Copy pathSubsystemWizardWindow.cs
File metadata and controls
419 lines (376 loc) · 17.4 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
// Copyright (c) Mixed Reality Toolkit Contributors
// Licensed under the BSD 3-Clause
using MixedReality.Toolkit.Editor;
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using UnityEngine;
using UnityEditor;
namespace MixedReality.Toolkit.Tools
{
/// <summary>
/// Editor window to guide developers through the process of creating a
/// Unity XR Subsystem for use with MRTK3.
/// </summary>
internal class SubsystemWizardWindow : EditorWindow
{
private static SubsystemWizardWindow window = null;
private SubsystemGenerator subsystemGenerator = null;
private static readonly Vector2 WindowSizeWithoutLogo = new Vector2(600, 510);
private static readonly Vector2 WindowSizeWithLogo = new Vector2(600, 580);
[MenuItem("Mixed Reality/MRTK3/Utilities/Subsystem Wizard...", false)]
private static void Init()
{
if (window != null)
{
window.Show();
return;
}
window = GetWindow<SubsystemWizardWindow>();
window.subsystemGenerator = new SubsystemGenerator();
window.titleContent = new GUIContent("MRTK3 Subsystem Wizard", EditorGUIUtility.IconContent("d_CustomTool").image); ;
if (InspectorUIUtility.IsMixedRealityToolkitLogoAssetPresent())
{
window.minSize = WindowSizeWithLogo;
}
else
{
window.minSize = WindowSizeWithoutLogo;
}
}
/// <summary>
/// A function called by Unity to render and handle GUI events.
/// </summary>
private void OnGUI()
{
if (window == null)
{
Init();
}
using (new EditorGUILayout.VerticalScope())
{
RenderCommonElements();
switch (subsystemGenerator.State)
{
case SubsystemWizardState.Start:
RenderWizardStartPage();
break;
case SubsystemWizardState.PreGenerate:
RenderWizardPreGeneratePage();
break;
case SubsystemWizardState.Complete:
RenderWizardCompletePage();
break;
default:
// Unknown / unexpected state
Debug.LogWarning($"[Subsystem Wizard] Unknown state: {subsystemGenerator.State}");
break;
}
// Do not crowd the bottom of the window.
EditorGUILayout.Space(12);
}
}
/// <summary>
/// Renders common wizard UI elements.
/// </summary>
private void RenderCommonElements()
{
EditorGUILayout.Space(2);
if (!InspectorUIUtility.RenderMixedRealityToolkitLogo())
{
// Only add additional space if the text fallback is used in RenderMixedRealityToolkitLogo().
EditorGUILayout.Space(3);
}
EditorGUILayout.LabelField(
"MRTK3 Subsystem Wizard",
MRTKEditorStyles.ProductNameStyle);
EditorGUILayout.Space(9);
using (new EditorGUILayout.HorizontalScope())
{
GUILayout.FlexibleSpace();
InspectorUIUtility.RenderDocumentationButton("https://aka.ms/MRTK3"); // todo
GUILayout.FlexibleSpace();
}
EditorGUILayout.Space(12);
}
/// <summary>
/// Renders the contents of the provided errors list.
/// </summary>
/// <param name="errors">The collection of errors to be rendered.</param>
private void RenderErrorLog(List<string> errors)
{
EditorGUILayout.LabelField("Errors");
EditorGUILayout.Space(4);
StringBuilder sb = new StringBuilder();
foreach (string s in errors)
{
sb.AppendLine(s);
}
EditorGUILayout.HelpBox(sb.ToString(), MessageType.Error);
}
/// <summary>
/// Renders the start page of the wizard. This is the page that collects the
/// user's desired names.
/// </summary>
private void RenderWizardStartPage()
{
List<string> errors = new List<string>();
using (new EditorGUI.IndentLevelScope())
{
EditorGUILayout.LabelField("Welcome to the MRTK3 Subsystem Wizard!", EditorStyles.boldLabel);
EditorGUILayout.LabelField("This tool will guide you through the creation of a new subsystem to extend the functionality of MRTK3.");
EditorGUILayout.Space(6);
EditorGUILayout.LabelField("Please enter your organization or project name.", EditorStyles.boldLabel);
subsystemGenerator.OrganizationName = EditorGUILayout.TextField(
"Organization name", subsystemGenerator.OrganizationName);
EditorGUILayout.Space(4);
EditorGUILayout.LabelField("Please enter the name for the subsystem base class.", EditorStyles.boldLabel);
subsystemGenerator.BaseClassName = EditorGUILayout.TextField(
"Base class name", subsystemGenerator.BaseClassName);
EditorGUILayout.Space(4);
EditorGUILayout.LabelField("Subsystems can optionally support configuration via the MRTK3 project settings.", EditorStyles.boldLabel);
float labelWidth = EditorGUIUtility.labelWidth;
EditorGUIUtility.labelWidth = 200f;
subsystemGenerator.CreateConfiguration = EditorGUILayout.Toggle("Add subsystem configuration",
subsystemGenerator.CreateConfiguration);
EditorGUIUtility.labelWidth = labelWidth;
EditorGUILayout.Space(6);
EditorGUILayout.LabelField("Generated names", EditorStyles.boldLabel);
using (new EditorGUI.IndentLevelScope())
{
using (new EditorGUILayout.HorizontalScope())
{
EditorGUILayout.LabelField("Namespace: ", EditorStyles.boldLabel);
EditorGUILayout.LabelField(subsystemGenerator.SubsystemNamespace);
}
EditorGUILayout.Space(4);
using (new EditorGUILayout.HorizontalScope())
{
EditorGUILayout.LabelField("Implementation class: ", EditorStyles.boldLabel);
EditorGUILayout.LabelField(subsystemGenerator.SubsystemName);
}
EditorGUILayout.Space(4);
using (new EditorGUILayout.HorizontalScope())
{
EditorGUILayout.LabelField("Display name: ", EditorStyles.boldLabel);
EditorGUILayout.LabelField(subsystemGenerator.DisplayName);
}
EditorGUILayout.Space(6);
}
// Validate the namespace.
if (!subsystemGenerator.ValidateNamespace(out string error))
{
errors.Add(error);
}
// Validate the subsystem base class name.
if (!subsystemGenerator.ValidateBaseClassName(out error))
{
errors.Add(error);
}
// Validate the subsystem name.
if (!subsystemGenerator.ValidateSubsystemName(out error))
{
errors.Add(error);
}
if (errors.Count > 0)
{
RenderErrorLog(errors);
}
GUILayout.FlexibleSpace();
}
using (new EditorGUI.DisabledGroupScope(errors.Count > 0))
{
if (GUILayout.Button("Next"))
{
subsystemGenerator.State = SubsystemWizardState.PreGenerate;
}
}
}
/// <summary>
/// Renders the wizard ui indicating that it is ready to generate the user's
/// subsystem. It also provides the option to skip generating select files.
/// </summary>
private void RenderWizardPreGeneratePage()
{
List<string> errors = new List<string>();
// Validate the template files we will use,
subsystemGenerator.ValidateTemplates(
errors,
out FileInfo descriptorTemplate,
out FileInfo interfaceTemplate,
out FileInfo baseClassTemplate,
out FileInfo derivedClassTemplate,
out FileInfo configTemplate,
out FileInfo applyConfigTemplate);
using (new EditorGUI.IndentLevelScope())
{
if (subsystemGenerator.DontCreateBaseClass ||
subsystemGenerator.DontCreateImplementationClass ||
subsystemGenerator.DontCreateDescriptor ||
subsystemGenerator.DontCreateInterface)
{
EditorGUILayout.HelpBox(
"Skipping generation of one or more files may result in compilation errors, such as one or more missing types.",
MessageType.Warning);
}
EditorGUILayout.LabelField(
$"The new subsystem will be created in your project's {Path.Combine(MRTKFiles.GetOrCreateGeneratedFolderPath(), subsystemGenerator.SubsystemName)} folder.",
EditorStyles.boldLabel);
EditorGUILayout.Space(6);
using (new EditorGUILayout.HorizontalScope())
{
EditorGUILayout.LabelField("Subsystem class:", GUILayout.Width(160));
EditorGUILayout.LabelField($"{subsystemGenerator.SubsystemName}.cs", GUILayout.Width(400));
subsystemGenerator.DontCreateImplementationClass = EditorGUILayout.ToggleLeft(
"Skip",
subsystemGenerator.DontCreateImplementationClass);
}
using (new EditorGUILayout.HorizontalScope())
{
EditorGUILayout.LabelField("Subsystem interface:", GUILayout.Width(160));
EditorGUILayout.LabelField($"{subsystemGenerator.InterfaceName}.cs", GUILayout.Width(400));
subsystemGenerator.DontCreateInterface = EditorGUILayout.ToggleLeft(
"Skip",
subsystemGenerator.DontCreateInterface);
}
using (new EditorGUILayout.HorizontalScope())
{
EditorGUILayout.LabelField("Subsystem base class:", GUILayout.Width(160));
EditorGUILayout.LabelField($"{subsystemGenerator.BaseClassName}.cs", GUILayout.Width(400));
subsystemGenerator.DontCreateBaseClass = EditorGUILayout.ToggleLeft(
"Skip",
subsystemGenerator.DontCreateBaseClass);
}
using (new EditorGUILayout.HorizontalScope())
{
EditorGUILayout.LabelField("Subsystem descriptor:", GUILayout.Width(160));
EditorGUILayout.LabelField($"{subsystemGenerator.DescriptorName}.cs", GUILayout.Width(400));
subsystemGenerator.DontCreateDescriptor = EditorGUILayout.ToggleLeft(
"Skip",
subsystemGenerator.DontCreateDescriptor);
}
if (subsystemGenerator.CreateConfiguration)
{
using (new EditorGUILayout.HorizontalScope())
{
EditorGUILayout.LabelField("Configuration class:", GUILayout.Width(160));
EditorGUILayout.LabelField($"{subsystemGenerator.ConfigurationName}.cs", GUILayout.Width(400));
}
}
EditorGUILayout.Space(6);
EditorGUILayout.LabelField("Please click `Next` to continue or `Go back` to make changes.");
if (errors.Count > 0)
{
RenderErrorLog(errors);
}
GUILayout.FlexibleSpace();
}
using (new EditorGUILayout.HorizontalScope())
{
if (GUILayout.Button("Go back"))
{
subsystemGenerator.State = SubsystemWizardState.Start;
}
using (new EditorGUI.DisabledGroupScope(errors.Count > 0))
{
if (GUILayout.Button("Next"))
{
try
{
subsystemGenerator.Generate(
descriptorTemplate,
interfaceTemplate,
baseClassTemplate,
derivedClassTemplate,
configTemplate,
applyConfigTemplate);
subsystemGenerator.State = SubsystemWizardState.Complete;
}
catch (Exception e)
{
EditorUtility.DisplayDialog(
"MRTK3 Subsystem Wizard",
$"Unable to create {subsystemGenerator.SubsystemName}\n\n{e.Message} ({e.GetType()})",
"Ok");
}
}
}
}
}
private Vector2 scrollPos;
/// <summary>
/// Renders the wizard complete page and provides users with recommended next steps.
/// </summary>
private void RenderWizardCompletePage()
{
StringBuilder sb = new StringBuilder();
int step = 1;
sb.AppendLine($" {step}. In the Project view, navigate to {Path.Combine(MRTKFiles.GetOrCreateGeneratedFolderPath(), subsystemGenerator.SubsystemName)}");
step++;
sb.AppendLine($" {step}. Open {subsystemGenerator.DescriptorName}.cs");
step++;
sb.AppendLine($" {step}. Define subsystem specific properties in the {subsystemGenerator.BaseClassName}CInfo class");
step++;
sb.AppendLine($" {step}. Add and initialize subsystem specific properties in the {subsystemGenerator.DescriptorName} class");
step++;
sb.AppendLine($" {step}. Open {subsystemGenerator.InterfaceName}.cs");
step++;
sb.AppendLine($" {step}. Define subsystem specific properties and/or methods");
step++;
sb.AppendLine($" {step}. Open {subsystemGenerator.BaseClassName}.cs");
step++;
sb.AppendLine($" {step}. Implement {subsystemGenerator.InterfaceName} in the abstract Provider class");
step++;
sb.AppendLine($" {step}. Implement {subsystemGenerator.InterfaceName} in the {subsystemGenerator.BaseClassName} class");
step++;
sb.AppendLine($" {step}. Open {subsystemGenerator.SubsystemName}.cs");
step++;
sb.AppendLine($" {step}. Implement {subsystemGenerator.InterfaceName} in the {subsystemGenerator.SubsystemName}Provider class");
step++;
sb.AppendLine($" {step}. Implement {subsystemGenerator.InterfaceName} in the {subsystemGenerator.SubsystemName} class");
step++;
if (subsystemGenerator.CreateConfiguration)
{
sb.AppendLine($" {step}. Open {subsystemGenerator.ConfigurationName}.cs");
step++;
sb.AppendLine($" {step}. Add subsystem configuration properties");
step++;
sb.AppendLine($" {step}. Return to {subsystemGenerator.SubsystemName}.cs");
step++;
sb.AppendLine($" {step}. Initialize the subsystem configuration");
step++;
}
using (new EditorGUI.IndentLevelScope())
{
EditorGUILayout.LabelField($"Your new subsystem {subsystemGenerator.SubsystemName} has been successfully created.");
EditorGUILayout.Space(6);
EditorGUILayout.LabelField("Next steps", EditorStyles.boldLabel);
scrollPos = EditorGUILayout.BeginScrollView(
scrollPos,
GUILayout.Height(300));
EditorGUILayout.TextArea(
sb.ToString(),
GUILayout.ExpandHeight(true));
EditorGUILayout.EndScrollView();
EditorGUILayout.Space(4);
if (GUILayout.Button("Enable your subsystem"))
{
SettingsService.OpenProjectSettings("MRTK3");
}
GUILayout.FlexibleSpace();
}
using (new EditorGUILayout.HorizontalScope())
{
if (GUILayout.Button("Start Over"))
{
subsystemGenerator.Reset();
}
if (GUILayout.Button("Close"))
{
window.Close();
}
}
}
}
}