Skip to content

Commit 2ab82fe

Browse files
committed
Added profile clone window
1 parent 6b7fb91 commit 2ab82fe

File tree

3 files changed

+320
-14
lines changed

3 files changed

+320
-14
lines changed

Assets/MixedRealityToolkit/Inspectors/Profiles/BaseMixedRealityProfileInspector.cs

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

102-
if (!renderedProfile.IsCustomProfile && profile.IsCustomProfile)
102+
if (GUILayout.Button(new GUIContent("Clone", "Replace with a copy of the default profile."), EditorStyles.miniButton, GUILayout.Width(42f)))
103103
{
104-
if (GUILayout.Button(new GUIContent("</>", "Replace with a copy of the default profile."), EditorStyles.miniButton, GUILayout.Width(32f)))
105-
{
106-
profileToCopy = renderedProfile;
107-
var profileTypeName = property.objectReferenceValue.GetType().Name;
108-
Debug.Assert(profileTypeName != null, "No Type Found");
109-
110-
ScriptableObject instance = CreateInstance(profileTypeName);
111-
var newProfile = instance.CreateAsset(AssetDatabase.GetAssetPath(Selection.activeObject)) as BaseMixedRealityProfile;
112-
property.objectReferenceValue = newProfile;
113-
property.serializedObject.ApplyModifiedProperties();
114-
PasteProfileValuesDelay(newProfile);
115-
changed = true;
116-
}
104+
MixedRealityProfileCloneWindow.OpenWindow(profile, renderedProfile, property);
117105
}
118106
}
119107

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

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)