@@ -54,29 +54,9 @@ internal static class BootstrapEditorUI
5454 private static VisualElement _fallbackContainer ;
5555 private static VisualElement _currentRoot ;
5656
57- // Sentinel shown when the serialized value is missing or no longer present in the
58- // current choices (e.g. a YooAsset package was renamed). Guarantees the dropdown
59- // has >=2 entries and gives the user an explicit "clear" option.
60- private const string NoneOption = "None" ;
61-
62- private static List < string > BuildOptionsWithNone ( IList < string > choices )
63- {
64- var options = new List < string > { NoneOption } ;
65- if ( choices != null ) options . AddRange ( choices ) ;
66- return options ;
67- }
68-
69- private static string ResolveCurrentOption ( string storedValue , List < string > options )
70- {
71- return string . IsNullOrEmpty ( storedValue ) || ! options . Contains ( storedValue )
72- ? NoneOption
73- : storedValue ;
74- }
75-
76- private static string NormalizeSelection ( string value )
77- {
78- return value == NoneOption ? string . Empty : value ;
79- }
57+ // Signature of the last-seen external data (available packages / scenes / etc.) so the
58+ // inspector can detect renames and additions made in other windows and rebuild.
59+ private static string _lastExternalDataSignature ;
8060
8161 /// <summary>
8262 /// Creates the enhanced Bootstrap inspector.
@@ -86,8 +66,9 @@ public static VisualElement CreateInspector(SerializedObject serializedObject, B
8666 _serializedObject = serializedObject ;
8767 _bootstrap = bootstrap ;
8868
89- // Unregister previous undo callback if exists
69+ // Unregister previous callbacks if they exist (inspector may be recreated).
9070 Undo . undoRedoPerformed -= OnUndoRedo ;
71+ EditorApplication . focusChanged -= OnEditorFocusChanged ;
9172
9273 var root = new VisualElement ( ) ;
9374 _currentRoot = root ;
@@ -101,87 +82,106 @@ public static VisualElement CreateInspector(SerializedObject serializedObject, B
10182 root . style . paddingRight = Tokens . Spacing . MD ;
10283 root . style . paddingBottom = Tokens . Spacing . MD ;
10384
104- // Centered container for compact inspector layout
105- var container = new JContainer ( ContainerSize . Xs ) ;
85+ BuildContent ( root ) ;
86+
87+ // Rebuild on undo/redo and on editor focus regained (picks up renames made in
88+ // other windows like the AssetBundle Collector).
89+ Undo . undoRedoPerformed += OnUndoRedo ;
90+ EditorApplication . focusChanged += OnEditorFocusChanged ;
91+
92+ // Seed the signature and poll periodically so renames made in another Unity window
93+ // (not just another app) propagate without the user having to reselect the asset.
94+ _lastExternalDataSignature = ComputeExternalDataSignature ( ) ;
95+ root . schedule . Execute ( CheckForExternalDataChanges ) . Every ( 500 ) ;
96+
97+ // Cleanup callback when element is detached.
98+ root . RegisterCallback < DetachFromPanelEvent > ( OnDetachFromPanel ) ;
99+
100+ return root ;
101+ }
106102
103+ /// <summary>
104+ /// Builds the inspector content into the given root. Used by both initial create and
105+ /// subsequent rebuilds (undo/redo, focus refresh).
106+ /// </summary>
107+ private static void BuildContent ( VisualElement root )
108+ {
109+ var container = new JContainer ( ContainerSize . Xs ) ;
107110 var content = new JStack ( GapSize . Sm ) ;
108111
109- // Header
110112 content . Add ( CreateHeader ( ) ) ;
111-
112113#if UNITY_EDITOR
113- // Development Settings
114114 content . Add ( CreateDevelopmentSettingsSection ( ) ) ;
115115#endif
116-
117- // Server Settings
118116 content . Add ( CreateServerSettingsSection ( ) ) ;
119-
120- // Asset Settings
121117 content . Add ( CreateAssetSettingsSection ( ) ) ;
122-
123- // Security Settings
124118 content . Add ( CreateSecuritySettingsSection ( ) ) ;
125-
126- // UI Settings
127119 content . Add ( CreateUISettingsSection ( ) ) ;
128-
129- // Text Settings
130120 content . Add ( CreateTextSettingsSection ( ) ) ;
131121
132122 container . Add ( content ) ;
133123 root . Add ( container ) ;
124+ }
134125
135- // Register undo/redo callback
136- Undo . undoRedoPerformed += OnUndoRedo ;
137-
138- // Cleanup callback when element is detached (no closure)
139- root . RegisterCallback < DetachFromPanelEvent , Undo . UndoRedoCallback > ( OnDetachFromPanel , OnUndoRedo ) ;
126+ private static void RebuildContent ( )
127+ {
128+ if ( _currentRoot == null || _serializedObject == null || _bootstrap == null )
129+ return ;
140130
141- return root ;
131+ _serializedObject . Update ( ) ;
132+ _currentRoot . Clear ( ) ;
133+ BuildContent ( _currentRoot ) ;
142134 }
143135
144136 /// <summary>
145137 /// Called when element is detached from panel. Cleanup callback.
146138 /// </summary>
147- private static void OnDetachFromPanel ( DetachFromPanelEvent evt , Undo . UndoRedoCallback undoCallback )
139+ private static void OnDetachFromPanel ( DetachFromPanelEvent evt )
148140 {
149- Undo . undoRedoPerformed -= undoCallback ;
141+ Undo . undoRedoPerformed -= OnUndoRedo ;
142+ EditorApplication . focusChanged -= OnEditorFocusChanged ;
150143 }
151144
152- /// <summary>
153- /// Called when undo/redo is performed. Rebuilds the inspector UI.
154- /// </summary>
155- private static void OnUndoRedo ( )
156- {
157- if ( _currentRoot == null || _serializedObject == null || _bootstrap == null )
158- return ;
145+ private static void OnUndoRedo ( ) => RebuildContent ( ) ;
159146
160- // Update serialized object to reflect undo/redo changes
161- _serializedObject . Update ( ) ;
162-
163- // Rebuild the entire UI
164- _currentRoot . Clear ( ) ;
165-
166- // Centered container for compact inspector layout
167- var container = new JContainer ( ContainerSize . Xs ) ;
168-
169- // Recreate content
170- var content = new JStack ( GapSize . Sm ) ;
147+ private static void OnEditorFocusChanged ( bool hasFocus )
148+ {
149+ if ( hasFocus ) RebuildContent ( ) ;
150+ }
171151
172- content . Add ( CreateHeader ( ) ) ;
152+ private static void CheckForExternalDataChanges ( )
153+ {
154+ if ( _bootstrap == null ) return ;
155+ var signature = ComputeExternalDataSignature ( ) ;
156+ if ( signature == _lastExternalDataSignature ) return ;
157+ _lastExternalDataSignature = signature ;
158+ RebuildContent ( ) ;
159+ }
173160
174- #if UNITY_EDITOR
175- content . Add ( CreateDevelopmentSettingsSection ( ) ) ;
176- #endif
177- content . Add ( CreateServerSettingsSection ( ) ) ;
178- content . Add ( CreateAssetSettingsSection ( ) ) ;
179- content . Add ( CreateSecuritySettingsSection ( ) ) ;
180- content . Add ( CreateUISettingsSection ( ) ) ;
181- content . Add ( CreateTextSettingsSection ( ) ) ;
161+ /// <summary>
162+ /// Concatenates the external data the dropdowns depend on (packages, asmdefs, scenes,
163+ /// classes/methods for the current assembly, AOT files, dynamic keys) into a signature
164+ /// string. When any of these change — e.g. a YooAsset package is renamed — the
165+ /// inspector rebuilds so stale values fall back to "None".
166+ /// </summary>
167+ private static string ComputeExternalDataSignature ( )
168+ {
169+ var sb = new System . Text . StringBuilder ( ) ;
170+ AppendList ( sb , EditorUtils . GetAvailableYooAssetPackages ( ) ) ;
171+ AppendList ( sb , EditorUtils . GetAvailableAsmdefFiles ( ) ) ;
172+ AppendList ( sb , EditorUtils . GetAvailableHotScenes ( ) ) ;
173+ AppendList ( sb , EditorUtils . GetAvailableHotClasses ( _bootstrap . hotCodeName ) ) ;
174+ AppendList ( sb , EditorUtils . GetAvailableHotMethods ( _bootstrap . hotCodeName , _bootstrap . hotUpdateClassName ) ) ;
175+ AppendList ( sb , EditorUtils . GetAvailableAOTDataFiles ( ) ) ;
176+ AppendList ( sb , EditorUtils . GetAvailableDynamicSecretKeys ( ) ) ;
177+ return sb . ToString ( ) ;
178+ }
182179
183- container . Add ( content ) ;
184- _currentRoot . Add ( container ) ;
180+ private static void AppendList ( System . Text . StringBuilder sb , List < string > items )
181+ {
182+ sb . Append ( '|' ) ;
183+ if ( items == null ) return ;
184+ foreach ( var item in items ) sb . Append ( item ) . Append ( ';' ) ;
185185 }
186186
187187 private static VisualElement CreateHeader ( )
@@ -300,79 +300,79 @@ private static VisualElement CreateAssetSettingsSection()
300300 section . Add ( new JFormField ( "Platform" , targetPlatformField ) ) ;
301301
302302 // Package Name
303- var packageOptions = BuildOptionsWithNone ( EditorUtils . GetAvailableYooAssetPackages ( ) ) ;
303+ var packageOptions = EditorUtils . WithNoneOption ( EditorUtils . GetAvailableYooAssetPackages ( ) ) ;
304304 var packageNameField = new JDropdown (
305305 packageOptions ,
306- ResolveCurrentOption ( _bootstrap . packageName , packageOptions )
306+ EditorUtils . ResolveDropdownValue ( _bootstrap . packageName , packageOptions )
307307 ) ;
308308 packageNameField . OnValueChanged ( value =>
309309 {
310- _serializedObject . FindProperty ( nameof ( _bootstrap . packageName ) ) . stringValue = NormalizeSelection ( value ) ;
310+ _serializedObject . FindProperty ( nameof ( _bootstrap . packageName ) ) . stringValue = EditorUtils . NormalizeDropdownSelection ( value ) ;
311311 _serializedObject . ApplyModifiedProperties ( ) ;
312312 } ) ;
313313 section . Add ( new JFormField ( "Package" , packageNameField ) ) ;
314314
315315 // Hot Code Assembly
316- var hotCodeOptions = BuildOptionsWithNone ( EditorUtils . GetAvailableAsmdefFiles ( ) ) ;
316+ var hotCodeOptions = EditorUtils . WithNoneOption ( EditorUtils . GetAvailableAsmdefFiles ( ) ) ;
317317 var hotCodeField = new JDropdown (
318318 hotCodeOptions ,
319- ResolveCurrentOption ( _bootstrap . hotCodeName , hotCodeOptions )
319+ EditorUtils . ResolveDropdownValue ( _bootstrap . hotCodeName , hotCodeOptions )
320320 ) ;
321321 hotCodeField . OnValueChanged ( value =>
322322 {
323- _serializedObject . FindProperty ( nameof ( _bootstrap . hotCodeName ) ) . stringValue = NormalizeSelection ( value ) ;
323+ _serializedObject . FindProperty ( nameof ( _bootstrap . hotCodeName ) ) . stringValue = EditorUtils . NormalizeDropdownSelection ( value ) ;
324324 _serializedObject . ApplyModifiedProperties ( ) ;
325325 } ) ;
326326 section . Add ( new JFormField ( "Code Assembly" , hotCodeField ) ) ;
327327
328328 // Hot Scene
329- var hotSceneOptions = BuildOptionsWithNone ( EditorUtils . GetAvailableHotScenes ( ) ) ;
329+ var hotSceneOptions = EditorUtils . WithNoneOption ( EditorUtils . GetAvailableHotScenes ( ) ) ;
330330 var hotSceneField = new JDropdown (
331331 hotSceneOptions ,
332- ResolveCurrentOption ( _bootstrap . selectedHotScene , hotSceneOptions )
332+ EditorUtils . ResolveDropdownValue ( _bootstrap . selectedHotScene , hotSceneOptions )
333333 ) ;
334334 hotSceneField . OnValueChanged ( value =>
335335 {
336- _serializedObject . FindProperty ( nameof ( _bootstrap . selectedHotScene ) ) . stringValue = NormalizeSelection ( value ) ;
336+ _serializedObject . FindProperty ( nameof ( _bootstrap . selectedHotScene ) ) . stringValue = EditorUtils . NormalizeDropdownSelection ( value ) ;
337337 _serializedObject . ApplyModifiedProperties ( ) ;
338338 } ) ;
339339 section . Add ( new JFormField ( "Scene" , hotSceneField ) ) ;
340340
341341 // Hot Update Entry Class
342- var hotClassOptions = BuildOptionsWithNone ( EditorUtils . GetAvailableHotClasses ( _bootstrap . hotCodeName ) ) ;
342+ var hotClassOptions = EditorUtils . WithNoneOption ( EditorUtils . GetAvailableHotClasses ( _bootstrap . hotCodeName ) ) ;
343343 var hotClassField = new JDropdown (
344344 hotClassOptions ,
345- ResolveCurrentOption ( _bootstrap . hotUpdateClassName , hotClassOptions )
345+ EditorUtils . ResolveDropdownValue ( _bootstrap . hotUpdateClassName , hotClassOptions )
346346 ) ;
347347 hotClassField . OnValueChanged ( value =>
348348 {
349- _serializedObject . FindProperty ( nameof ( _bootstrap . hotUpdateClassName ) ) . stringValue = NormalizeSelection ( value ) ;
349+ _serializedObject . FindProperty ( nameof ( _bootstrap . hotUpdateClassName ) ) . stringValue = EditorUtils . NormalizeDropdownSelection ( value ) ;
350350 _serializedObject . ApplyModifiedProperties ( ) ;
351351 } ) ;
352352 section . Add ( new JFormField ( "Entry Class" , hotClassField ) ) ;
353353
354354 // Hot Update Entry Method
355- var hotMethodOptions = BuildOptionsWithNone ( EditorUtils . GetAvailableHotMethods ( _bootstrap . hotCodeName , _bootstrap . hotUpdateClassName ) ) ;
355+ var hotMethodOptions = EditorUtils . WithNoneOption ( EditorUtils . GetAvailableHotMethods ( _bootstrap . hotCodeName , _bootstrap . hotUpdateClassName ) ) ;
356356 var hotMethodField = new JDropdown (
357357 hotMethodOptions ,
358- ResolveCurrentOption ( _bootstrap . hotUpdateMethodName , hotMethodOptions )
358+ EditorUtils . ResolveDropdownValue ( _bootstrap . hotUpdateMethodName , hotMethodOptions )
359359 ) ;
360360 hotMethodField . OnValueChanged ( value =>
361361 {
362- _serializedObject . FindProperty ( nameof ( _bootstrap . hotUpdateMethodName ) ) . stringValue = NormalizeSelection ( value ) ;
362+ _serializedObject . FindProperty ( nameof ( _bootstrap . hotUpdateMethodName ) ) . stringValue = EditorUtils . NormalizeDropdownSelection ( value ) ;
363363 _serializedObject . ApplyModifiedProperties ( ) ;
364364 } ) ;
365365 section . Add ( new JFormField ( "Entry Method" , hotMethodField ) ) ;
366366
367367 // AOT DLL List File
368- var aotOptions = BuildOptionsWithNone ( EditorUtils . GetAvailableAOTDataFiles ( ) ) ;
368+ var aotOptions = EditorUtils . WithNoneOption ( EditorUtils . GetAvailableAOTDataFiles ( ) ) ;
369369 var aotField = new JDropdown (
370370 aotOptions ,
371- ResolveCurrentOption ( _bootstrap . aotDllListFilePath , aotOptions )
371+ EditorUtils . ResolveDropdownValue ( _bootstrap . aotDllListFilePath , aotOptions )
372372 ) ;
373373 aotField . OnValueChanged ( value =>
374374 {
375- _serializedObject . FindProperty ( nameof ( _bootstrap . aotDllListFilePath ) ) . stringValue = NormalizeSelection ( value ) ;
375+ _serializedObject . FindProperty ( nameof ( _bootstrap . aotDllListFilePath ) ) . stringValue = EditorUtils . NormalizeDropdownSelection ( value ) ;
376376 _serializedObject . ApplyModifiedProperties ( ) ;
377377 } ) ;
378378 section . Add ( new JFormField ( "AOT DLL List" , aotField ) ) ;
@@ -385,14 +385,14 @@ private static VisualElement CreateSecuritySettingsSection()
385385 var section = new JSection ( "Security Settings" ) ;
386386
387387 // Dynamic Secret Key
388- var dynamicKeyOptions = BuildOptionsWithNone ( EditorUtils . GetAvailableDynamicSecretKeys ( ) ) ;
388+ var dynamicKeyOptions = EditorUtils . WithNoneOption ( EditorUtils . GetAvailableDynamicSecretKeys ( ) ) ;
389389 var dynamicKeyField = new JDropdown (
390390 dynamicKeyOptions ,
391- ResolveCurrentOption ( _bootstrap . dynamicSecretKeyPath , dynamicKeyOptions )
391+ EditorUtils . ResolveDropdownValue ( _bootstrap . dynamicSecretKeyPath , dynamicKeyOptions )
392392 ) ;
393393 dynamicKeyField . OnValueChanged ( value =>
394394 {
395- _serializedObject . FindProperty ( nameof ( _bootstrap . dynamicSecretKeyPath ) ) . stringValue = NormalizeSelection ( value ) ;
395+ _serializedObject . FindProperty ( nameof ( _bootstrap . dynamicSecretKeyPath ) ) . stringValue = EditorUtils . NormalizeDropdownSelection ( value ) ;
396396 _serializedObject . ApplyModifiedProperties ( ) ;
397397 } ) ;
398398 section . Add ( new JFormField ( "Secret Key" , dynamicKeyField ) ) ;
0 commit comments