Skip to content

Commit f83e6d4

Browse files
authored
Merge pull request #3610 from Railboy/mrtk_inspector_upgrade_pt4
Mrtk Inspector Upgrade (Part 4)
2 parents ebb11f1 + 5e9e600 commit f83e6d4

File tree

3 files changed

+333
-14
lines changed

3 files changed

+333
-14
lines changed

Assets/MixedRealityToolkit/Inspectors/Profiles/BaseMixedRealityProfileInspector.cs

Lines changed: 2 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -130,21 +130,9 @@ private static bool RenderProfileInternal(SerializedProperty property, GUIConten
130130
Debug.Assert(renderedProfile != null);
131131
Debug.Assert(profile != null, "No profile was set in OnEnable. Did you forget to call base.OnEnable in a derived profile class?");
132132

133-
if (!renderedProfile.IsCustomProfile && profile.IsCustomProfile)
133+
if (GUILayout.Button(new GUIContent("Clone", "Replace with a copy of the default profile."), EditorStyles.miniButton, GUILayout.Width(42f)))
134134
{
135-
if (GUILayout.Button(new GUIContent("Clone", "Replace with a copy of the default profile."), EditorStyles.miniButton, GUILayout.Width(42f)))
136-
{
137-
profileToCopy = renderedProfile;
138-
var profileTypeName = property.objectReferenceValue.GetType().Name;
139-
Debug.Assert(profileTypeName != null, "No Type Found");
140-
141-
ScriptableObject instance = CreateInstance(profileTypeName);
142-
var newProfile = instance.CreateAsset(AssetDatabase.GetAssetPath(Selection.activeObject)) as BaseMixedRealityProfile;
143-
property.objectReferenceValue = newProfile;
144-
property.serializedObject.ApplyModifiedProperties();
145-
PasteProfileValuesDelay(newProfile);
146-
changed = true;
147-
}
135+
MixedRealityProfileCloneWindow.OpenWindow(profile, renderedProfile, property);
148136
}
149137
}
150138

Lines changed: 320 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,320 @@
1+
using Microsoft.MixedReality.Toolkit.Core.Definitions;
2+
using Microsoft.MixedReality.Toolkit.Core.Extensions.EditorClassExtensions;
3+
using System.Collections.Generic;
4+
using System.Reflection;
5+
using UnityEditor;
6+
using UnityEngine;
7+
8+
namespace Microsoft.MixedReality.Toolkit.Core.Inspectors.Profiles
9+
{
10+
public class MixedRealityProfileCloneWindow : EditorWindow
11+
{
12+
public enum ProfileCloneBehavior
13+
{
14+
UseExisting, // Use the existing reference
15+
CloneExisting, // Create a clone of the sub-profile
16+
UseSubstitution, // Manually select a profile
17+
LeaveEmpty, // Set the reference to null
18+
}
19+
20+
private struct SubProfileAction
21+
{
22+
public SubProfileAction(ProfileCloneBehavior behavior, SerializedProperty property, Object substitutionReference, System.Type profileType)
23+
{
24+
Behavior = behavior;
25+
Property = property;
26+
SubstitutionReference = substitutionReference;
27+
ProfileType = profileType;
28+
29+
CloneName = (SubstitutionReference != null) ? "New " + SubstitutionReference.name : "New " + profileType.Name;
30+
}
31+
32+
public ProfileCloneBehavior Behavior;
33+
public SerializedProperty Property;
34+
public string CloneName;
35+
public Object SubstitutionReference;
36+
public System.Type ProfileType;
37+
}
38+
39+
private const string IsCustomProfileProperty = "isCustomProfile";
40+
private static readonly Vector2 MinWindowSizeBasic = new Vector2(500, 140);
41+
private const float SubProfileSizeMultiplier = 70f;
42+
private static MixedRealityProfileCloneWindow cloneWindow;
43+
44+
private BaseMixedRealityProfile parentProfile;
45+
private BaseMixedRealityProfile childProfile;
46+
private SerializedProperty childProperty;
47+
private SerializedObject childSerializedObject;
48+
private Object targetFolder;
49+
private string childProfileTypeName;
50+
private string childProfileAssetName;
51+
private List<SubProfileAction> subProfileActions = new List<SubProfileAction>();
52+
53+
public static void OpenWindow(BaseMixedRealityProfile parentProfile, BaseMixedRealityProfile childProfile, SerializedProperty childProperty)
54+
{
55+
if (cloneWindow != null)
56+
{
57+
cloneWindow.Close();
58+
}
59+
60+
cloneWindow = (MixedRealityProfileCloneWindow)GetWindow<MixedRealityProfileCloneWindow>(true, "Clone Profile", true);
61+
cloneWindow.Initialize(parentProfile, childProfile, childProperty);
62+
cloneWindow.Show(true);
63+
}
64+
65+
private void Initialize(BaseMixedRealityProfile parentProfile, BaseMixedRealityProfile childProfile, SerializedProperty childProperty)
66+
{
67+
this.childProperty = childProperty;
68+
this.parentProfile = parentProfile;
69+
this.childProfile = childProfile;
70+
71+
childSerializedObject = new SerializedObject(childProperty.objectReferenceValue);
72+
childProfileTypeName = childProperty.objectReferenceValue.GetType().Name;
73+
childProfileAssetName = "New " + childProfileTypeName;
74+
75+
// Find all the serialized properties for sub-profiles
76+
SerializedProperty iterator = childSerializedObject.GetIterator();
77+
System.Type basePropertyType = typeof(BaseMixedRealityProfile);
78+
79+
while (iterator.Next(true))
80+
{
81+
SerializedProperty subProfileProperty = childSerializedObject.FindProperty(iterator.name);
82+
83+
if (subProfileProperty == null)
84+
continue;
85+
86+
if (!subProfileProperty.type.Contains("PPtr<$")) // Not an object reference type
87+
continue;
88+
89+
string subProfileTypeName = subProfileProperty.type.Replace("PPtr<$", string.Empty).Replace(">", string.Empty).Trim();
90+
System.Type subProfileType = FindProfileType(subProfileTypeName);
91+
if (subProfileType == null)
92+
continue;
93+
94+
if (!basePropertyType.IsAssignableFrom(subProfileType))
95+
continue;
96+
97+
subProfileActions.Add(new SubProfileAction(
98+
ProfileCloneBehavior.UseExisting,
99+
subProfileProperty,
100+
subProfileProperty.objectReferenceValue,
101+
subProfileType));
102+
}
103+
104+
Vector2 minWindowSize = MinWindowSizeBasic;
105+
minWindowSize.y = Mathf.Max(minWindowSize.y, subProfileActions.Count * SubProfileSizeMultiplier);
106+
cloneWindow.minSize = minWindowSize;
107+
108+
// If there are no sub profiles, limit the max so the window isn't spawned too large
109+
if (subProfileActions.Count <= 0)
110+
cloneWindow.maxSize = minWindowSize;
111+
}
112+
113+
private void OnGUI()
114+
{
115+
if (cloneWindow == null || parentProfile == null || childProfile == null)
116+
{
117+
Close();
118+
return;
119+
}
120+
121+
EditorGUILayout.BeginVertical(EditorStyles.helpBox);
122+
EditorGUILayout.ObjectField("Cloning profile", childProfile, typeof(BaseMixedRealityProfile), false);
123+
EditorGUILayout.ObjectField("from parent profile", parentProfile, typeof(BaseMixedRealityProfile), false);
124+
EditorGUILayout.EndVertical();
125+
EditorGUILayout.Space();
126+
127+
if (subProfileActions.Count > 0)
128+
{
129+
EditorGUILayout.HelpBox("This profile has sub-profiles. By defult your clone will reference the existing profiles. If you want to specify a different profile, or if you want to clone the sub-profile, use the options below.", MessageType.Info);
130+
131+
EditorGUILayout.BeginVertical();
132+
133+
for (int i = 0; i < subProfileActions.Count; i++)
134+
{
135+
GUI.color = Color.white;
136+
EditorGUILayout.Space();
137+
138+
SubProfileAction action = subProfileActions[i];
139+
140+
action.Behavior = (ProfileCloneBehavior)EditorGUILayout.EnumPopup(action.Property.displayName, action.Behavior);
141+
142+
switch (action.Behavior)
143+
{
144+
case ProfileCloneBehavior.UseExisting:
145+
GUI.color = Color.Lerp(Color.white, Color.clear, 0.5f);
146+
EditorGUILayout.ObjectField("Existing", action.Property.objectReferenceValue, action.ProfileType, false);
147+
break;
148+
149+
case ProfileCloneBehavior.UseSubstitution:
150+
action.SubstitutionReference = EditorGUILayout.ObjectField("Substitution", action.SubstitutionReference, action.ProfileType, false);
151+
break;
152+
153+
case ProfileCloneBehavior.CloneExisting:
154+
if (action.Property.objectReferenceValue == null)
155+
{
156+
EditorGUILayout.LabelField("Can't clone profile - none is set.");
157+
}
158+
else
159+
{
160+
action.CloneName = EditorGUILayout.TextField("Clone name", action.CloneName);
161+
}
162+
break;
163+
164+
case ProfileCloneBehavior.LeaveEmpty:
165+
// Add one line for formatting reasons
166+
EditorGUILayout.LabelField(" ");
167+
break;
168+
}
169+
subProfileActions[i] = action;
170+
}
171+
172+
EditorGUILayout.EndVertical();
173+
}
174+
175+
GUI.color = Color.white;
176+
// Space between props and buttons at botton
177+
GUILayout.FlexibleSpace();
178+
179+
// Get the selected folder in the project window
180+
GetSelectedPathOrFallback(ref targetFolder);
181+
targetFolder = EditorGUILayout.ObjectField("Target Folder", targetFolder, typeof(Object), false);
182+
childProfileAssetName = EditorGUILayout.TextField("Profile Name", childProfileAssetName);
183+
184+
EditorGUILayout.BeginHorizontal();
185+
186+
if (GUILayout.Button("Clone"))
187+
{
188+
CloneMainProfile();
189+
}
190+
if (GUILayout.Button("Cancel"))
191+
{
192+
cloneWindow.Close();
193+
}
194+
195+
EditorGUILayout.EndHorizontal();
196+
197+
Repaint();
198+
}
199+
200+
private void CloneMainProfile()
201+
{
202+
var newChildProfile = CloneProfile(parentProfile, childProfile, childProfileTypeName, childProperty, targetFolder);
203+
SerializedObject newChildSerializedObject = new SerializedObject(newChildProfile);
204+
// First paste all values outright
205+
PasteProfileValues(parentProfile, childProfile, newChildSerializedObject);
206+
207+
// Then over-write with substitutions or clones
208+
foreach (SubProfileAction action in subProfileActions)
209+
{
210+
SerializedProperty actionProperty = newChildSerializedObject.FindProperty(action.Property.name);
211+
212+
switch (action.Behavior)
213+
{
214+
case ProfileCloneBehavior.UseExisting:
215+
// Do nothing
216+
break;
217+
218+
case ProfileCloneBehavior.UseSubstitution:
219+
// Apply the chosen reference to the new property
220+
actionProperty.objectReferenceValue = action.SubstitutionReference;
221+
break;
222+
223+
case ProfileCloneBehavior.CloneExisting:
224+
// Clone the profile, then apply the new reference
225+
226+
// If the property reference is null, skip this step, the user was warned
227+
if (action.Property.objectReferenceValue == null)
228+
break;
229+
230+
// If for some reason it's the wrong type, bail now
231+
BaseMixedRealityProfile subProfileToClone = (BaseMixedRealityProfile)action.Property.objectReferenceValue;
232+
if (subProfileToClone == null)
233+
break;
234+
235+
// Clone the sub profile
236+
var newSubProfile = CloneProfile(newChildProfile, subProfileToClone, action.ProfileType.Name, actionProperty, targetFolder, action.CloneName);
237+
SerializedObject newSubProfileSerializedObject = new SerializedObject(newSubProfile);
238+
// Paste values from existing profile
239+
PasteProfileValues(newChildProfile, newSubProfile, newSubProfileSerializedObject);
240+
newSubProfileSerializedObject.ApplyModifiedProperties();
241+
break;
242+
243+
case ProfileCloneBehavior.LeaveEmpty:
244+
actionProperty.objectReferenceValue = null;
245+
break;
246+
}
247+
}
248+
249+
newChildSerializedObject.ApplyModifiedProperties();
250+
251+
Selection.activeObject = newChildProfile;
252+
cloneWindow.Close();
253+
}
254+
255+
private static BaseMixedRealityProfile CloneProfile(BaseMixedRealityProfile parentProfile, BaseMixedRealityProfile profileToClone, string childProfileTypeName, SerializedProperty childProperty, Object targetFolder, string profileName = null)
256+
{
257+
ScriptableObject instance = CreateInstance(childProfileTypeName);
258+
instance.name = string.IsNullOrEmpty(profileName) ? childProfileTypeName : profileName;
259+
260+
string fileName = instance.name;
261+
string path = AssetDatabase.GetAssetPath(targetFolder);
262+
Debug.Log("Creating asset in path " + targetFolder);
263+
264+
var newChildProfile = instance.CreateAsset(path, fileName) as BaseMixedRealityProfile;
265+
childProperty.objectReferenceValue = newChildProfile;
266+
childProperty.serializedObject.ApplyModifiedProperties();
267+
268+
return newChildProfile;
269+
}
270+
271+
private static void PasteProfileValues(BaseMixedRealityProfile parentProfile, BaseMixedRealityProfile profileToCopy, SerializedObject targetProfile)
272+
{
273+
Undo.RecordObject(parentProfile, "Paste Profile Values");
274+
275+
bool targetIsCustom = targetProfile.FindProperty(IsCustomProfileProperty).boolValue;
276+
string originalName = targetProfile.targetObject.name;
277+
EditorUtility.CopySerialized(profileToCopy, targetProfile.targetObject);
278+
targetProfile.Update();
279+
targetProfile.FindProperty(IsCustomProfileProperty).boolValue = targetIsCustom;
280+
targetProfile.ApplyModifiedProperties();
281+
targetProfile.targetObject.name = originalName;
282+
283+
AssetDatabase.SaveAssets();
284+
}
285+
286+
public static void GetSelectedPathOrFallback(ref Object folderObject)
287+
{
288+
foreach (Object obj in Selection.GetFiltered(typeof(Object), SelectionMode.Assets))
289+
{
290+
string path = AssetDatabase.GetAssetPath(obj);
291+
if (!string.IsNullOrEmpty(path) && System.IO.Directory.Exists(path))
292+
{
293+
folderObject = obj;
294+
return;
295+
}
296+
}
297+
298+
if (folderObject == null)
299+
folderObject = AssetDatabase.LoadAssetAtPath("Assets", typeof(Object));
300+
}
301+
302+
private static System.Type FindProfileType(string profileTypeName)
303+
{
304+
System.Type type = null;
305+
foreach (Assembly assembly in System.AppDomain.CurrentDomain.GetAssemblies())
306+
{
307+
foreach (System.Type checkType in assembly.GetTypes())
308+
{
309+
if (checkType.Name == profileTypeName)
310+
{
311+
type = checkType;
312+
break;
313+
}
314+
}
315+
}
316+
317+
return type;
318+
}
319+
}
320+
}

Assets/MixedRealityToolkit/Inspectors/Profiles/MixedRealityProfileCloneWindow.cs.meta

Lines changed: 11 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)