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