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+ }
0 commit comments