Skip to content

Commit c03ca6b

Browse files
fix(ui): use shared dropdown helpers and auto-refresh on external changes
BootstrapEditorUI now calls EditorUtils.WithNoneOption / ResolveDropdownValue / NormalizeDropdownSelection instead of its own local copies, and PanelUI's Package dropdown now uses the same helpers (previously missed in the earlier #631 fix - it still showed the stale package name after a rename). Also add a 500ms polling check on the Bootstrap inspector that compares a signature of the external data sources (packages, asmdefs, scenes, classes, methods, AOT files, dynamic keys) and rebuilds the inspector when they change. Combined with the existing undo/redo handler and a new EditorApplication.focusChanged listener, this means a package rename in the AssetBundle Collector propagates to the Bootstrap inspector without the user having to reselect the asset. Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com> Signed-off-by: JasonXuDeveloper - 傑 <jason@xgamedev.net>
1 parent c8b4561 commit c03ca6b

2 files changed

Lines changed: 101 additions & 101 deletions

File tree

UnityProject/Packages/com.jasonxudeveloper.jengine.ui/Editor/Internal/BootstrapEditorUI.cs

Lines changed: 97 additions & 97 deletions
Original file line numberDiff line numberDiff line change
@@ -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));

UnityProject/Packages/com.jasonxudeveloper.jengine.ui/Editor/Internal/PanelUI.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -177,14 +177,14 @@ private static VisualElement CreatePackageSettingsSection(Settings settings)
177177
var section = new JSection("Package Settings");
178178

179179
// Package Name
180-
var packageChoices = EditorUtils.GetAvailableYooAssetPackages();
180+
var packageOptions = EditorUtils.WithNoneOption(EditorUtils.GetAvailableYooAssetPackages());
181181
var packageNameField = new JDropdown(
182-
packageChoices.Count > 0 ? packageChoices : new List<string> { settings.packageName },
183-
settings.packageName
182+
packageOptions,
183+
EditorUtils.ResolveDropdownValue(settings.packageName, packageOptions)
184184
);
185185
packageNameField.OnValueChanged(value =>
186186
{
187-
settings.packageName = value;
187+
settings.packageName = EditorUtils.NormalizeDropdownSelection(value);
188188
settings.Save();
189189
});
190190
section.Add(new JFormField("Package", packageNameField));

0 commit comments

Comments
 (0)