@fps-games/editor 0.1.2-beta.1 → 0.1.3-beta.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/local-editor-harness.d.ts +44 -8
- package/dist/local-editor-harness.d.ts.map +1 -1
- package/dist/local-editor-harness.js +436 -170
- package/dist/local-editor-harness.js.map +1 -1
- package/dist/local-editor-scene-render-scheduler.d.ts +8 -1
- package/dist/local-editor-scene-render-scheduler.d.ts.map +1 -1
- package/dist/local-editor-scene-render-scheduler.js +23 -2
- package/dist/local-editor-scene-render-scheduler.js.map +1 -1
- package/dist/local-editor-scene-view-interaction-runtime.d.ts +22 -0
- package/dist/local-editor-scene-view-interaction-runtime.d.ts.map +1 -0
- package/dist/local-editor-scene-view-interaction-runtime.js +80 -0
- package/dist/local-editor-scene-view-interaction-runtime.js.map +1 -0
- package/node_modules/@fps-games/editor-babylon/dist/editor-sky.d.ts +2 -2
- package/node_modules/@fps-games/editor-babylon/dist/editor-sky.d.ts.map +1 -1
- package/node_modules/@fps-games/editor-babylon/dist/editor-sky.js +31 -15
- package/node_modules/@fps-games/editor-babylon/dist/editor-sky.js.map +1 -1
- package/node_modules/@fps-games/editor-babylon/dist/editor-world.d.ts +10 -0
- package/node_modules/@fps-games/editor-babylon/dist/editor-world.d.ts.map +1 -1
- package/node_modules/@fps-games/editor-babylon/dist/editor-world.js +31 -9
- package/node_modules/@fps-games/editor-babylon/dist/editor-world.js.map +1 -1
- package/node_modules/@fps-games/editor-babylon/dist/index.d.ts +1 -0
- package/node_modules/@fps-games/editor-babylon/dist/index.d.ts.map +1 -1
- package/node_modules/@fps-games/editor-babylon/dist/index.js +1 -0
- package/node_modules/@fps-games/editor-babylon/dist/index.js.map +1 -1
- package/node_modules/@fps-games/editor-babylon/dist/infinite-grid.d.ts +24 -0
- package/node_modules/@fps-games/editor-babylon/dist/infinite-grid.d.ts.map +1 -1
- package/node_modules/@fps-games/editor-babylon/dist/infinite-grid.js +241 -48
- package/node_modules/@fps-games/editor-babylon/dist/infinite-grid.js.map +1 -1
- package/node_modules/@fps-games/editor-babylon/dist/material-property-adapter.d.ts +3 -1
- package/node_modules/@fps-games/editor-babylon/dist/material-property-adapter.d.ts.map +1 -1
- package/node_modules/@fps-games/editor-babylon/dist/material-property-adapter.js +166 -0
- package/node_modules/@fps-games/editor-babylon/dist/material-property-adapter.js.map +1 -1
- package/node_modules/@fps-games/editor-babylon/dist/projection.d.ts +12 -1
- package/node_modules/@fps-games/editor-babylon/dist/projection.d.ts.map +1 -1
- package/node_modules/@fps-games/editor-babylon/dist/projection.js +231 -32
- package/node_modules/@fps-games/editor-babylon/dist/projection.js.map +1 -1
- package/node_modules/@fps-games/editor-babylon/dist/scene-view-camera-controller.d.ts +4 -0
- package/node_modules/@fps-games/editor-babylon/dist/scene-view-camera-controller.d.ts.map +1 -1
- package/node_modules/@fps-games/editor-babylon/dist/scene-view-camera-controller.js +37 -23
- package/node_modules/@fps-games/editor-babylon/dist/scene-view-camera-controller.js.map +1 -1
- package/node_modules/@fps-games/editor-babylon/dist/scene-view-input-controller.d.ts +1 -0
- package/node_modules/@fps-games/editor-babylon/dist/scene-view-input-controller.d.ts.map +1 -1
- package/node_modules/@fps-games/editor-babylon/dist/scene-view-input-controller.js +100 -8
- package/node_modules/@fps-games/editor-babylon/dist/scene-view-input-controller.js.map +1 -1
- package/node_modules/@fps-games/editor-babylon/dist/shadow-preview-controller.d.ts +22 -0
- package/node_modules/@fps-games/editor-babylon/dist/shadow-preview-controller.d.ts.map +1 -0
- package/node_modules/@fps-games/editor-babylon/dist/shadow-preview-controller.js +71 -0
- package/node_modules/@fps-games/editor-babylon/dist/shadow-preview-controller.js.map +1 -0
- package/node_modules/@fps-games/editor-babylon/dist/transform-gizmo-controller.d.ts +6 -1
- package/node_modules/@fps-games/editor-babylon/dist/transform-gizmo-controller.d.ts.map +1 -1
- package/node_modules/@fps-games/editor-babylon/dist/transform-gizmo-controller.js +107 -79
- package/node_modules/@fps-games/editor-babylon/dist/transform-gizmo-controller.js.map +1 -1
- package/node_modules/@fps-games/editor-babylon/dist/transform-solver.d.ts +30 -0
- package/node_modules/@fps-games/editor-babylon/dist/transform-solver.d.ts.map +1 -0
- package/node_modules/@fps-games/editor-babylon/dist/transform-solver.js +120 -0
- package/node_modules/@fps-games/editor-babylon/dist/transform-solver.js.map +1 -0
- package/node_modules/@fps-games/editor-babylon/dist/types.d.ts +3 -1
- package/node_modules/@fps-games/editor-babylon/dist/types.d.ts.map +1 -1
- package/node_modules/@fps-games/editor-babylon/package.json +5 -4
- package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-hierarchy-actions.d.ts.map +1 -1
- package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-hierarchy-actions.js +28 -0
- package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-hierarchy-actions.js.map +1 -1
- package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-hierarchy-controller.d.ts.map +1 -1
- package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-hierarchy-controller.js +103 -1
- package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-hierarchy-controller.js.map +1 -1
- package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-hierarchy-view.d.ts +3 -1
- package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-hierarchy-view.d.ts.map +1 -1
- package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-hierarchy-view.js +49 -4
- package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-hierarchy-view.js.map +1 -1
- package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-input-router.d.ts +4 -1
- package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-input-router.d.ts.map +1 -1
- package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-input-router.js +12 -3
- package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-input-router.js.map +1 -1
- package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-panel-registry.d.ts +3 -1
- package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-panel-registry.d.ts.map +1 -1
- package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-panel-registry.js +7 -0
- package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-panel-registry.js.map +1 -1
- package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-panels.d.ts +8 -3
- package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-panels.d.ts.map +1 -1
- package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-panels.js +645 -20
- package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-panels.js.map +1 -1
- package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-shared.js +1 -1
- package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-shared.js.map +1 -1
- package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-shortcuts.d.ts.map +1 -1
- package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-shortcuts.js +7 -6
- package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-shortcuts.js.map +1 -1
- package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-types.d.ts +150 -3
- package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-types.d.ts.map +1 -1
- package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-workbench.d.ts +6 -0
- package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-workbench.d.ts.map +1 -1
- package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-workbench.js +1 -0
- package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-workbench.js.map +1 -1
- package/node_modules/@fps-games/editor-browser/dist/local-editor-ui.d.ts +3 -4
- package/node_modules/@fps-games/editor-browser/dist/local-editor-ui.d.ts.map +1 -1
- package/node_modules/@fps-games/editor-browser/dist/local-editor-ui.js +1067 -87
- package/node_modules/@fps-games/editor-browser/dist/local-editor-ui.js.map +1 -1
- package/node_modules/@fps-games/editor-browser/package.json +2 -2
- package/node_modules/@fps-games/editor-core/dist/scene-view-input.d.ts +7 -0
- package/node_modules/@fps-games/editor-core/dist/scene-view-input.d.ts.map +1 -1
- package/node_modules/@fps-games/editor-core/dist/transform-gizmo.d.ts +22 -0
- package/node_modules/@fps-games/editor-core/dist/transform-gizmo.d.ts.map +1 -1
- package/node_modules/@fps-games/editor-core/dist/transform-gizmo.js.map +1 -1
- package/node_modules/@fps-games/editor-core/package.json +2 -2
- package/node_modules/@fps-games/editor-forge-play/package.json +2 -2
- package/node_modules/@fps-games/editor-protocol/dist/index.d.ts +61 -0
- package/node_modules/@fps-games/editor-protocol/dist/index.d.ts.map +1 -1
- package/node_modules/@fps-games/editor-protocol/dist/index.js +157 -0
- package/node_modules/@fps-games/editor-protocol/dist/index.js.map +1 -1
- package/node_modules/@fps-games/editor-protocol/package.json +1 -1
- package/package.json +8 -6
|
@@ -5,6 +5,7 @@ import { createLocalEditorTooltipController } from './local-editor-ui-tooltip';
|
|
|
5
5
|
import { DEFAULT_EDITOR_TRANSFORM_OPERATION_SETTINGS, DEFAULT_EDITOR_TRANSFORM_TOOL_DESCRIPTORS, } from '@fps-games/editor-core';
|
|
6
6
|
export { createLocalEditorHierarchyBlankMenu, createLocalEditorHierarchyCopyShortcutAction, createLocalEditorHierarchyDeleteShortcutAction, createLocalEditorHierarchyDuplicateShortcutAction, createLocalEditorHierarchyNodeMenu, createLocalEditorHierarchyPasteShortcutAction, createLocalEditorHierarchySelectAllShortcutAction, } from './local-editor-ui-hierarchy-actions';
|
|
7
7
|
export { createLocalEditorHierarchyController, } from './local-editor-ui-hierarchy-controller';
|
|
8
|
+
export { resolveLocalEditorHierarchySearchRows, } from './local-editor-ui-hierarchy-view';
|
|
8
9
|
export { canLocalEditorHierarchyNodeHaveChildren, createLocalEditorHierarchyTreeModel, isLocalEditorHierarchyNodeMovable, } from './local-editor-ui-hierarchy-tree';
|
|
9
10
|
export { createLocalEditorContextMenuController, } from './local-editor-ui-context-menu';
|
|
10
11
|
export { createLocalEditorTooltipController, } from './local-editor-ui-tooltip';
|
|
@@ -15,33 +16,15 @@ import { createShortcutHelpPanel as createLocalEditorShortcutHelpPanel } from '.
|
|
|
15
16
|
import { applyLocalEditorTheme, ensureLocalEditorTheme, LOCAL_EDITOR_THEME_CLASS, normalizeLocalEditorThemeName, } from './local-editor-ui-theme';
|
|
16
17
|
import { createDefaultLocalEditorWorkbenchLayout, createLocalEditorWorkbenchLayoutController, createLocalEditorWorkbench, createSceneHeaderToolbar, createWorkbenchPanelContent, } from './local-editor-ui-workbench';
|
|
17
18
|
import { createLocalEditorIcon } from './local-editor-ui-icons';
|
|
18
|
-
export { applyLocalEditorBrowserInspectorControlBinding, createLocalEditorBrowserInspectorControlRegistry, formatLocalEditorBrowserInspectorNumberValue, formatLocalEditorBrowserInspectorValue, resolveLocalEditorBrowserInspectorSectionStatus, resolveLocalEditorBrowserInspectorControlRegistration, } from './local-editor-ui-panels';
|
|
19
|
+
export { applyLocalEditorBrowserInspectorControlBinding, createLocalEditorBrowserInspectorControlRegistry, formatLocalEditorBrowserInspectorNumberValue, formatLocalEditorBrowserInspectorValue, parseLocalEditorBrowserInspectorNumberValue, resolveLocalEditorBrowserInspectorSectionStatus, resolveLocalEditorBrowserInspectorControlRegistration, } from './local-editor-ui-panels';
|
|
19
20
|
export { DEFAULT_LOCAL_EDITOR_THEME, LOCAL_EDITOR_THEME_CLASS, applyLocalEditorTheme, normalizeLocalEditorThemeName, } from './local-editor-ui-theme';
|
|
20
21
|
export { LOCAL_EDITOR_ICON_NAMES, createLocalEditorIcon, isLocalEditorIconName, resolveLocalEditorIconName, } from './local-editor-ui-icons';
|
|
21
|
-
const
|
|
22
|
-
|
|
23
|
-
const normalized = value.trim();
|
|
24
|
-
if (normalized === '')
|
|
25
|
-
return mode === 'final' ? 0 : null;
|
|
26
|
-
if (normalized === '-'
|
|
27
|
-
|| normalized === '+'
|
|
28
|
-
|| normalized === '.'
|
|
29
|
-
|| normalized === '-.'
|
|
30
|
-
|| normalized === '+.') {
|
|
31
|
-
return null;
|
|
32
|
-
}
|
|
33
|
-
if (mode === 'live' && (/[.]$/.test(normalized) || /e[+-]?$/i.test(normalized)))
|
|
34
|
-
return null;
|
|
35
|
-
if (!/^[+-]?(?:\d+(?:\.\d*)?|\.\d+)(?:e[+-]?\d+)?$/i.test(normalized))
|
|
36
|
-
return null;
|
|
37
|
-
const numeric = Number(normalized);
|
|
38
|
-
return Number.isFinite(numeric) ? numeric : null;
|
|
39
|
-
}
|
|
40
|
-
function readInspectorInputValue(input, mode = 'final') {
|
|
22
|
+
const PENDING_NUMERIC_PROPERTY_INPUT = Symbol('pending-numeric-property-input');
|
|
23
|
+
function readInspectorInputValue(input, source) {
|
|
41
24
|
const control = input.dataset.serializedControl;
|
|
42
25
|
const valueType = input.dataset.serializedValueType;
|
|
43
26
|
if ((control === 'vec2' || control === 'vec3') && input instanceof HTMLInputElement) {
|
|
44
|
-
return readInspectorVectorInputValue(input,
|
|
27
|
+
return readInspectorVectorInputValue(input, source) ?? PENDING_NUMERIC_PROPERTY_INPUT;
|
|
45
28
|
}
|
|
46
29
|
if (control === 'enum' && input instanceof HTMLSelectElement) {
|
|
47
30
|
const option = input.selectedOptions.item(0);
|
|
@@ -60,7 +43,9 @@ function readInspectorInputValue(input, mode = 'final') {
|
|
|
60
43
|
if (control === 'boolean' || valueType === 'boolean')
|
|
61
44
|
return input.value === 'true';
|
|
62
45
|
if (control === 'number' || valueType === 'number') {
|
|
63
|
-
|
|
46
|
+
const mode = source === 'input' && readInspectorCommitMode(input) === 'live' ? 'live' : 'final';
|
|
47
|
+
return LocalEditorPanels.parseLocalEditorBrowserInspectorNumberValue(input.value, mode)
|
|
48
|
+
?? PENDING_NUMERIC_PROPERTY_INPUT;
|
|
64
49
|
}
|
|
65
50
|
if (control === 'color' && input instanceof HTMLInputElement && input.type === 'color') {
|
|
66
51
|
const value = input.value.replace('#', '');
|
|
@@ -168,9 +153,10 @@ function setToolbarButtonIcon(doc, button, icon) {
|
|
|
168
153
|
else
|
|
169
154
|
button.prepend(nextIcon);
|
|
170
155
|
}
|
|
171
|
-
function readInspectorVectorInputValue(input,
|
|
156
|
+
function readInspectorVectorInputValue(input, source) {
|
|
172
157
|
const wrapper = input.closest('[data-inspector-vector-control]');
|
|
173
158
|
const values = {};
|
|
159
|
+
const mode = source === 'input' && readInspectorCommitMode(input) === 'live' ? 'live' : 'final';
|
|
174
160
|
const fields = wrapper
|
|
175
161
|
? Array.from(wrapper.querySelectorAll('input[data-serialized-vector-axis]'))
|
|
176
162
|
: [input];
|
|
@@ -178,21 +164,21 @@ function readInspectorVectorInputValue(input, mode) {
|
|
|
178
164
|
const axis = field.dataset.serializedVectorAxis;
|
|
179
165
|
if (!axis)
|
|
180
166
|
continue;
|
|
181
|
-
const numeric = parseLocalEditorBrowserInspectorNumberValue(field.value, mode);
|
|
167
|
+
const numeric = LocalEditorPanels.parseLocalEditorBrowserInspectorNumberValue(field.value, mode);
|
|
182
168
|
if (numeric == null)
|
|
183
|
-
return
|
|
169
|
+
return null;
|
|
184
170
|
values[axis] = numeric;
|
|
185
171
|
}
|
|
186
172
|
return values;
|
|
187
173
|
}
|
|
188
|
-
function createInspectorPropertyInput(input, source
|
|
174
|
+
function createInspectorPropertyInput(input, source) {
|
|
189
175
|
if (!input.dataset.serializedPath || !input.dataset.serializedTargetId)
|
|
190
176
|
return null;
|
|
191
177
|
const targetIds = input.dataset.serializedTargetIds
|
|
192
178
|
? input.dataset.serializedTargetIds.split(',').filter(Boolean)
|
|
193
179
|
: undefined;
|
|
194
|
-
const value = readInspectorInputValue(input,
|
|
195
|
-
if (value ===
|
|
180
|
+
const value = readInspectorInputValue(input, source);
|
|
181
|
+
if (value === PENDING_NUMERIC_PROPERTY_INPUT)
|
|
196
182
|
return null;
|
|
197
183
|
return {
|
|
198
184
|
targetId: input.dataset.serializedTargetId,
|
|
@@ -206,16 +192,63 @@ function createInspectorPropertyInput(input, source, numberParseMode = 'final')
|
|
|
206
192
|
source: input.dataset.serializedEditSource ?? source,
|
|
207
193
|
};
|
|
208
194
|
}
|
|
209
|
-
function
|
|
210
|
-
const control = input.dataset.
|
|
211
|
-
const valueType = input.dataset.
|
|
212
|
-
if (
|
|
213
|
-
return
|
|
214
|
-
|
|
195
|
+
function readRenderingInputValue(input, source) {
|
|
196
|
+
const control = input.dataset.renderingControl;
|
|
197
|
+
const valueType = input.dataset.renderingValueType;
|
|
198
|
+
if (input instanceof HTMLInputElement && input.type === 'checkbox')
|
|
199
|
+
return input.checked;
|
|
200
|
+
if (input instanceof HTMLInputElement && input.type === 'color') {
|
|
201
|
+
const value = input.value.replace('#', '');
|
|
202
|
+
const numeric = Number.parseInt(value, 16);
|
|
203
|
+
if (!Number.isFinite(numeric))
|
|
204
|
+
return null;
|
|
205
|
+
return {
|
|
206
|
+
r: ((numeric >> 16) & 255) / 255,
|
|
207
|
+
g: ((numeric >> 8) & 255) / 255,
|
|
208
|
+
b: (numeric & 255) / 255,
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
if (control === 'number' || valueType === 'number') {
|
|
212
|
+
const mode = source === 'input' && readRenderingCommitMode(input) === 'live' ? 'live' : 'final';
|
|
213
|
+
return LocalEditorPanels.parseLocalEditorBrowserInspectorNumberValue(input.value, mode)
|
|
214
|
+
?? PENDING_NUMERIC_PROPERTY_INPUT;
|
|
215
|
+
}
|
|
216
|
+
if (control === 'string-list' || valueType === 'string-list') {
|
|
217
|
+
return input.value
|
|
218
|
+
.split(/\r?\n/)
|
|
219
|
+
.map(value => value.trim())
|
|
220
|
+
.filter(Boolean);
|
|
221
|
+
}
|
|
222
|
+
if (control === 'boolean' || valueType === 'boolean')
|
|
223
|
+
return input.value === 'true';
|
|
224
|
+
return input.value;
|
|
225
|
+
}
|
|
226
|
+
function createRenderingPropertyInput(input, source) {
|
|
227
|
+
const sectionId = input.dataset.renderingSectionId;
|
|
228
|
+
const systemId = input.dataset.renderingSystemId;
|
|
229
|
+
const path = input.dataset.renderingPath;
|
|
230
|
+
if (!sectionId || !systemId || !path)
|
|
231
|
+
return null;
|
|
232
|
+
const value = readRenderingInputValue(input, source);
|
|
233
|
+
if (value === PENDING_NUMERIC_PROPERTY_INPUT)
|
|
234
|
+
return null;
|
|
235
|
+
return {
|
|
236
|
+
sectionId,
|
|
237
|
+
systemId,
|
|
238
|
+
path,
|
|
239
|
+
value,
|
|
240
|
+
control: input.dataset.renderingControl,
|
|
241
|
+
valueType: input.dataset.renderingValueType,
|
|
242
|
+
commitMode: input.dataset.renderingCommitMode,
|
|
243
|
+
source,
|
|
244
|
+
};
|
|
215
245
|
}
|
|
216
246
|
function readInspectorCommitMode(input) {
|
|
217
247
|
return input.dataset.serializedCommitMode ?? 'live';
|
|
218
248
|
}
|
|
249
|
+
function readRenderingCommitMode(input) {
|
|
250
|
+
return input.dataset.renderingCommitMode ?? 'live';
|
|
251
|
+
}
|
|
219
252
|
function readInspectorImmediateSource(input) {
|
|
220
253
|
if (input instanceof HTMLSelectElement)
|
|
221
254
|
return 'select';
|
|
@@ -225,6 +258,15 @@ function readInspectorImmediateSource(input) {
|
|
|
225
258
|
return 'color';
|
|
226
259
|
return 'input';
|
|
227
260
|
}
|
|
261
|
+
function readRenderingImmediateSource(input) {
|
|
262
|
+
if (input instanceof HTMLInputElement && input.type === 'checkbox')
|
|
263
|
+
return 'toggle';
|
|
264
|
+
if (input instanceof HTMLInputElement && input.type === 'color')
|
|
265
|
+
return 'color';
|
|
266
|
+
if (input instanceof HTMLTextAreaElement)
|
|
267
|
+
return 'list';
|
|
268
|
+
return 'input';
|
|
269
|
+
}
|
|
228
270
|
function createSceneFrameRateOverlay(doc) {
|
|
229
271
|
const root = doc.createElement('div');
|
|
230
272
|
root.classList.add(LOCAL_EDITOR_THEME_CLASS);
|
|
@@ -725,6 +767,170 @@ function renderMeasurementOverlay(overlay, state) {
|
|
|
725
767
|
function formatSvgNumber(value) {
|
|
726
768
|
return Number.isFinite(value) ? value.toFixed(2) : '0';
|
|
727
769
|
}
|
|
770
|
+
function isLocalEditorAssetBrowserTab(value) {
|
|
771
|
+
return value === 'all' || value === 'models' || value === 'materials' || value === 'textures';
|
|
772
|
+
}
|
|
773
|
+
function createAssetBrowserMenuItems(state, asset) {
|
|
774
|
+
const assetKind = resolveBrowserAssetKind(asset);
|
|
775
|
+
const placeable = asset.placeable !== false && asset.disabled !== true;
|
|
776
|
+
const activeId = state.activeId;
|
|
777
|
+
const items = [];
|
|
778
|
+
if (assetKind === 'material') {
|
|
779
|
+
items.push({
|
|
780
|
+
id: 'asset.edit-material',
|
|
781
|
+
label: '编辑材质球',
|
|
782
|
+
disabled: !asset.material,
|
|
783
|
+
disabledReason: '该材质资产缺少可编辑参数。',
|
|
784
|
+
});
|
|
785
|
+
}
|
|
786
|
+
else if (assetKind === 'texture' && asset.preview) {
|
|
787
|
+
items.push({
|
|
788
|
+
id: 'asset.preview',
|
|
789
|
+
label: '查看贴图预览',
|
|
790
|
+
});
|
|
791
|
+
}
|
|
792
|
+
if (assetKind === 'material') {
|
|
793
|
+
items.push({
|
|
794
|
+
id: 'asset.apply-material',
|
|
795
|
+
label: '应用到当前 GameObject',
|
|
796
|
+
separatorBefore: true,
|
|
797
|
+
disabled: !activeId,
|
|
798
|
+
disabledReason: '请先在 Hierarchy 或 Scene View 中选择一个 GameObject。',
|
|
799
|
+
});
|
|
800
|
+
}
|
|
801
|
+
else if (assetKind === 'texture') {
|
|
802
|
+
items.push({
|
|
803
|
+
id: 'asset.add-to-scene',
|
|
804
|
+
label: '作为地贴添加到场景',
|
|
805
|
+
separatorBefore: asset.preview ? true : undefined,
|
|
806
|
+
disabled: !placeable,
|
|
807
|
+
disabledReason: '该贴图当前不可放置。',
|
|
808
|
+
});
|
|
809
|
+
items.push({
|
|
810
|
+
id: 'asset.place',
|
|
811
|
+
label: '进入地面放置模式',
|
|
812
|
+
disabled: !placeable,
|
|
813
|
+
disabledReason: '该贴图当前不可放置。',
|
|
814
|
+
});
|
|
815
|
+
items.push({
|
|
816
|
+
id: 'asset.use-base-texture',
|
|
817
|
+
label: '用作当前材质 Base 图',
|
|
818
|
+
disabled: true,
|
|
819
|
+
disabledReason: 'Base 图材质字段将在下一步接入。',
|
|
820
|
+
separatorBefore: true,
|
|
821
|
+
});
|
|
822
|
+
items.push({
|
|
823
|
+
id: 'asset.use-emission-mask',
|
|
824
|
+
label: '用作当前材质自发光遮罩',
|
|
825
|
+
disabled: true,
|
|
826
|
+
disabledReason: '自发光遮罩绑定将在下一步接入。',
|
|
827
|
+
});
|
|
828
|
+
}
|
|
829
|
+
else {
|
|
830
|
+
items.push({
|
|
831
|
+
id: 'asset.add-to-scene',
|
|
832
|
+
label: '添加到场景',
|
|
833
|
+
disabled: !placeable,
|
|
834
|
+
disabledReason: '该模型当前不可放置。',
|
|
835
|
+
});
|
|
836
|
+
items.push({
|
|
837
|
+
id: 'asset.place',
|
|
838
|
+
label: '进入放置模式',
|
|
839
|
+
disabled: !placeable,
|
|
840
|
+
disabledReason: '该模型当前不可放置。',
|
|
841
|
+
});
|
|
842
|
+
items.push({
|
|
843
|
+
id: 'asset.add-as-child',
|
|
844
|
+
label: '添加为当前节点子对象',
|
|
845
|
+
disabled: true,
|
|
846
|
+
disabledReason: '子对象添加动作将在资产 patch 中接入。',
|
|
847
|
+
separatorBefore: true,
|
|
848
|
+
});
|
|
849
|
+
items.push({
|
|
850
|
+
id: 'asset.replace-selected-model',
|
|
851
|
+
label: '替换当前模型',
|
|
852
|
+
disabled: true,
|
|
853
|
+
disabledReason: '模型替换动作将在资产 patch 中接入。',
|
|
854
|
+
});
|
|
855
|
+
}
|
|
856
|
+
items.push({
|
|
857
|
+
id: 'asset.copy-id',
|
|
858
|
+
label: '复制 Asset ID',
|
|
859
|
+
separatorBefore: true,
|
|
860
|
+
});
|
|
861
|
+
return items;
|
|
862
|
+
}
|
|
863
|
+
function resolveBrowserAssetKind(asset) {
|
|
864
|
+
const kind = `${asset.kind ?? ''}`.trim().toLowerCase();
|
|
865
|
+
const id = `${asset.id ?? ''}`.trim().toLowerCase();
|
|
866
|
+
const assetId = `${asset.assetId ?? ''}`.trim().toLowerCase();
|
|
867
|
+
if (kind === 'material' || id.startsWith('material:') || assetId.startsWith('mat_'))
|
|
868
|
+
return 'material';
|
|
869
|
+
if (kind === 'texture' || kind === 'image' || id.startsWith('texture:') || assetId.startsWith('texture_'))
|
|
870
|
+
return 'texture';
|
|
871
|
+
return 'model';
|
|
872
|
+
}
|
|
873
|
+
function createAssetBrowserActionInput(actionId, asset) {
|
|
874
|
+
return {
|
|
875
|
+
actionId,
|
|
876
|
+
assetId: asset.assetId ?? asset.id,
|
|
877
|
+
browserAssetId: asset.id,
|
|
878
|
+
assetKind: asset.kind,
|
|
879
|
+
};
|
|
880
|
+
}
|
|
881
|
+
function copyTextToClipboard(win, text) {
|
|
882
|
+
void win?.navigator?.clipboard?.writeText?.(text);
|
|
883
|
+
}
|
|
884
|
+
function readInspectorAssetPickerOptions(property) {
|
|
885
|
+
const title = readControlOptionString(property, 'pickerTitle') || property.label;
|
|
886
|
+
const rawCandidates = property.controlOptions?.candidates;
|
|
887
|
+
const candidates = Array.isArray(rawCandidates)
|
|
888
|
+
? rawCandidates.flatMap(readInspectorAssetPickerOption)
|
|
889
|
+
: [];
|
|
890
|
+
return { title, candidates };
|
|
891
|
+
}
|
|
892
|
+
function readInspectorAssetPickerOption(value) {
|
|
893
|
+
if (!value || typeof value !== 'object' || Array.isArray(value))
|
|
894
|
+
return [];
|
|
895
|
+
const record = value;
|
|
896
|
+
const label = typeof record.label === 'string' ? record.label : '';
|
|
897
|
+
const optionValue = typeof record.value === 'string' ? record.value : '';
|
|
898
|
+
if (!label && !optionValue)
|
|
899
|
+
return [];
|
|
900
|
+
return [{
|
|
901
|
+
label: label || optionValue,
|
|
902
|
+
value: optionValue,
|
|
903
|
+
...(typeof record.meta === 'string' ? { meta: record.meta } : {}),
|
|
904
|
+
...(typeof record.kind === 'string' ? { kind: record.kind } : {}),
|
|
905
|
+
...(isAssetPreview(record.preview) ? { preview: record.preview } : {}),
|
|
906
|
+
}];
|
|
907
|
+
}
|
|
908
|
+
function readControlOptionString(property, key) {
|
|
909
|
+
const value = property.controlOptions?.[key];
|
|
910
|
+
return typeof value === 'string' ? value : '';
|
|
911
|
+
}
|
|
912
|
+
function isAssetPreview(value) {
|
|
913
|
+
if (!value || typeof value !== 'object' || Array.isArray(value))
|
|
914
|
+
return false;
|
|
915
|
+
const record = value;
|
|
916
|
+
if (record.kind === 'image')
|
|
917
|
+
return typeof record.url === 'string' && record.url.length > 0;
|
|
918
|
+
return record.kind === 'material-sphere';
|
|
919
|
+
}
|
|
920
|
+
function findInspectorPropertyForInput(state, input) {
|
|
921
|
+
const path = input.dataset.serializedPath;
|
|
922
|
+
if (!path)
|
|
923
|
+
return null;
|
|
924
|
+
const objects = [state.inspectorObject, state.inspectorMultiObject].filter(Boolean);
|
|
925
|
+
for (const object of objects) {
|
|
926
|
+
for (const section of object.sections) {
|
|
927
|
+
const property = section.properties.find(candidate => candidate.path === path);
|
|
928
|
+
if (property)
|
|
929
|
+
return property;
|
|
930
|
+
}
|
|
931
|
+
}
|
|
932
|
+
return null;
|
|
933
|
+
}
|
|
728
934
|
export function createLocalEditorBrowserUi(options = {}) {
|
|
729
935
|
const doc = options.document ?? document;
|
|
730
936
|
const root = options.root ?? doc.body;
|
|
@@ -732,7 +938,9 @@ export function createLocalEditorBrowserUi(options = {}) {
|
|
|
732
938
|
const localTestActionsEnabled = options.localTestActions === true;
|
|
733
939
|
ensureLocalEditorTheme(doc);
|
|
734
940
|
let activeTheme = normalizeLocalEditorThemeName(options.theme);
|
|
735
|
-
const inputRouter = createLocalEditorWorkbenchInputRouter(doc
|
|
941
|
+
const inputRouter = createLocalEditorWorkbenchInputRouter(doc, {
|
|
942
|
+
isShortcutReserved: options.input?.isShortcutReserved,
|
|
943
|
+
});
|
|
736
944
|
const contextMenu = createLocalEditorContextMenuController(doc, (open) => {
|
|
737
945
|
inputRouter.setContextMenuOpen(open);
|
|
738
946
|
}, activeTheme);
|
|
@@ -742,6 +950,7 @@ export function createLocalEditorBrowserUi(options = {}) {
|
|
|
742
950
|
});
|
|
743
951
|
let currentState = null;
|
|
744
952
|
let inspectorFilter = '';
|
|
953
|
+
let assetBrowserTab = 'all';
|
|
745
954
|
const workbenchLayout = createDefaultLocalEditorWorkbenchLayout();
|
|
746
955
|
const panelRegistry = createLocalEditorPanelRegistry(workbenchLayout);
|
|
747
956
|
const hostChrome = doc.createElement('div');
|
|
@@ -833,15 +1042,26 @@ export function createLocalEditorBrowserUi(options = {}) {
|
|
|
833
1042
|
const undoButton = createToolbarIconButton(doc, '撤销', 'undo', '撤销');
|
|
834
1043
|
const redoButton = createToolbarIconButton(doc, '重做', 'redo', '重做');
|
|
835
1044
|
let helpOpen = false;
|
|
1045
|
+
let assetPreviewOpen = false;
|
|
1046
|
+
let previewedAsset = null;
|
|
1047
|
+
let assetPreviewMode = 'preview';
|
|
1048
|
+
let assetPickerOpen = false;
|
|
1049
|
+
let assetPickerFilter = '';
|
|
1050
|
+
let assetPickerInput = null;
|
|
1051
|
+
let assetPickerProperty = null;
|
|
836
1052
|
const editorStatusButton = createToolbarIconButton(doc, '编辑器状态', 'status', '编辑器状态');
|
|
837
1053
|
editorStatusButton.dataset.editorStatusButton = 'true';
|
|
838
1054
|
editorStatusButton.style.cursor = 'help';
|
|
839
1055
|
const workbench = createLocalEditorWorkbench(doc);
|
|
840
1056
|
const hierarchyPanel = createWorkbenchPanelContent(doc);
|
|
841
1057
|
const assetPanel = workbench.bottomDock;
|
|
1058
|
+
const rightDockTabs = doc.createElement('div');
|
|
842
1059
|
const inspectorPanel = createWorkbenchPanelContent(doc);
|
|
1060
|
+
const renderingPanel = createWorkbenchPanelContent(doc);
|
|
843
1061
|
workbench.leftDock.appendChild(hierarchyPanel);
|
|
1062
|
+
workbench.rightDock.appendChild(rightDockTabs);
|
|
844
1063
|
workbench.rightDock.appendChild(inspectorPanel);
|
|
1064
|
+
workbench.rightDock.appendChild(renderingPanel);
|
|
845
1065
|
root.appendChild(workbench.root);
|
|
846
1066
|
tooltipSurfaces.add(workbench.root);
|
|
847
1067
|
const workbenchLayoutController = createLocalEditorWorkbenchLayoutController(doc, workbench);
|
|
@@ -1120,17 +1340,17 @@ export function createLocalEditorBrowserUi(options = {}) {
|
|
|
1120
1340
|
sceneUtilityActions.appendChild(gridToggleButton);
|
|
1121
1341
|
sceneUtilityActions.appendChild(themeToggleButton);
|
|
1122
1342
|
sceneUtilityActions.appendChild(sceneHelpButton);
|
|
1123
|
-
const
|
|
1124
|
-
|
|
1343
|
+
const cameraPreviewGroup = doc.createElement('div');
|
|
1344
|
+
cameraPreviewGroup.style.cssText = [
|
|
1125
1345
|
'display:flex',
|
|
1126
1346
|
'align-items:center',
|
|
1127
1347
|
'gap:4px',
|
|
1128
1348
|
'padding-left:8px',
|
|
1129
1349
|
'border-left:1px solid var(--fps-editor-divider)',
|
|
1130
1350
|
].join(';');
|
|
1131
|
-
const
|
|
1132
|
-
|
|
1133
|
-
|
|
1351
|
+
const sceneCameraButton = createToolbarIconButton(doc, '从 Main Camera 查看场景', 'camera');
|
|
1352
|
+
sceneCameraButton.dataset.sceneCameraPreviewToggle = 'true';
|
|
1353
|
+
cameraPreviewGroup.appendChild(sceneCameraButton);
|
|
1134
1354
|
const viewportToolsGroup = doc.createElement('div');
|
|
1135
1355
|
viewportToolsGroup.style.cssText = [
|
|
1136
1356
|
'display:flex',
|
|
@@ -1235,7 +1455,7 @@ export function createLocalEditorBrowserUi(options = {}) {
|
|
|
1235
1455
|
sceneToolOverlay.appendChild(actionGroup);
|
|
1236
1456
|
sceneToolOverlay.appendChild(viewportToolsGroup);
|
|
1237
1457
|
sceneToolOverlay.appendChild(sceneUtilityActions);
|
|
1238
|
-
sceneToolOverlay.appendChild(
|
|
1458
|
+
sceneToolOverlay.appendChild(cameraPreviewGroup);
|
|
1239
1459
|
sceneToolOverlay.appendChild(editorStatusButton);
|
|
1240
1460
|
sceneToolOverlay.appendChild(toolbarOverflowButton);
|
|
1241
1461
|
workbench.sceneHeader.appendChild(sceneToolOverlay);
|
|
@@ -1272,6 +1492,164 @@ export function createLocalEditorBrowserUi(options = {}) {
|
|
|
1272
1492
|
shortcutHelpPanel.classList.add(LOCAL_EDITOR_THEME_CLASS);
|
|
1273
1493
|
root.appendChild(shortcutHelpPanel);
|
|
1274
1494
|
tooltipSurfaces.add(shortcutHelpPanel);
|
|
1495
|
+
const assetPreviewModal = doc.createElement('div');
|
|
1496
|
+
assetPreviewModal.classList.add(LOCAL_EDITOR_THEME_CLASS);
|
|
1497
|
+
assetPreviewModal.dataset.editorAssetPreviewModal = 'true';
|
|
1498
|
+
assetPreviewModal.dataset.editorWorkbenchRegion = 'modal';
|
|
1499
|
+
assetPreviewModal.style.cssText = [
|
|
1500
|
+
'position:fixed',
|
|
1501
|
+
'inset:0',
|
|
1502
|
+
'z-index:2147483642',
|
|
1503
|
+
'display:none',
|
|
1504
|
+
'align-items:center',
|
|
1505
|
+
'justify-content:center',
|
|
1506
|
+
'padding:32px',
|
|
1507
|
+
'background:rgba(0,0,0,0.45)',
|
|
1508
|
+
'font-family:var(--fps-editor-font)',
|
|
1509
|
+
'color:var(--fps-editor-text)',
|
|
1510
|
+
'pointer-events:auto',
|
|
1511
|
+
].join(';');
|
|
1512
|
+
const assetPreviewDialog = doc.createElement('div');
|
|
1513
|
+
assetPreviewDialog.setAttribute('role', 'dialog');
|
|
1514
|
+
assetPreviewDialog.setAttribute('aria-modal', 'true');
|
|
1515
|
+
assetPreviewDialog.setAttribute('aria-label', '资产预览');
|
|
1516
|
+
assetPreviewDialog.style.cssText = [
|
|
1517
|
+
'width:min(420px,calc(100vw - 64px))',
|
|
1518
|
+
'max-height:calc(100vh - 64px)',
|
|
1519
|
+
'display:flex',
|
|
1520
|
+
'flex-direction:column',
|
|
1521
|
+
'gap:12px',
|
|
1522
|
+
'padding:14px',
|
|
1523
|
+
'border:1px solid var(--fps-editor-border)',
|
|
1524
|
+
'border-radius:4px',
|
|
1525
|
+
'background:var(--fps-editor-panel)',
|
|
1526
|
+
'box-shadow:var(--fps-editor-shadow-modal)',
|
|
1527
|
+
'overflow:auto',
|
|
1528
|
+
].join(';');
|
|
1529
|
+
const assetPreviewHeader = doc.createElement('div');
|
|
1530
|
+
assetPreviewHeader.style.cssText = 'display:flex;align-items:center;justify-content:space-between;gap:12px';
|
|
1531
|
+
const assetPreviewTitle = doc.createElement('h2');
|
|
1532
|
+
assetPreviewTitle.style.cssText = [
|
|
1533
|
+
'min-width:0',
|
|
1534
|
+
'margin:0',
|
|
1535
|
+
'overflow:hidden',
|
|
1536
|
+
'text-overflow:ellipsis',
|
|
1537
|
+
'white-space:nowrap',
|
|
1538
|
+
'font-size:13px',
|
|
1539
|
+
'font-weight:900',
|
|
1540
|
+
'color:var(--fps-editor-text-strong)',
|
|
1541
|
+
].join(';');
|
|
1542
|
+
const assetPreviewCloseButton = LocalEditorShared.createButton(doc, '关闭');
|
|
1543
|
+
assetPreviewCloseButton.dataset.editorAssetPreviewClose = 'true';
|
|
1544
|
+
assetPreviewHeader.appendChild(assetPreviewTitle);
|
|
1545
|
+
assetPreviewHeader.appendChild(assetPreviewCloseButton);
|
|
1546
|
+
const assetPreviewBody = doc.createElement('div');
|
|
1547
|
+
assetPreviewBody.style.cssText = [
|
|
1548
|
+
'display:flex',
|
|
1549
|
+
'align-items:center',
|
|
1550
|
+
'justify-content:center',
|
|
1551
|
+
'min-height:240px',
|
|
1552
|
+
'border:1px solid var(--fps-editor-divider)',
|
|
1553
|
+
'border-radius:4px',
|
|
1554
|
+
'background:var(--fps-editor-panel-soft)',
|
|
1555
|
+
].join(';');
|
|
1556
|
+
const assetPreviewMeta = doc.createElement('div');
|
|
1557
|
+
assetPreviewMeta.style.cssText = [
|
|
1558
|
+
'min-height:16px',
|
|
1559
|
+
'overflow:hidden',
|
|
1560
|
+
'text-overflow:ellipsis',
|
|
1561
|
+
'white-space:nowrap',
|
|
1562
|
+
'color:var(--fps-editor-muted)',
|
|
1563
|
+
'font-size:11px',
|
|
1564
|
+
'font-weight:800',
|
|
1565
|
+
].join(';');
|
|
1566
|
+
assetPreviewDialog.appendChild(assetPreviewHeader);
|
|
1567
|
+
assetPreviewDialog.appendChild(assetPreviewBody);
|
|
1568
|
+
assetPreviewDialog.appendChild(assetPreviewMeta);
|
|
1569
|
+
assetPreviewModal.appendChild(assetPreviewDialog);
|
|
1570
|
+
root.appendChild(assetPreviewModal);
|
|
1571
|
+
tooltipSurfaces.add(assetPreviewModal);
|
|
1572
|
+
const assetPickerModal = doc.createElement('div');
|
|
1573
|
+
assetPickerModal.classList.add(LOCAL_EDITOR_THEME_CLASS);
|
|
1574
|
+
assetPickerModal.dataset.editorAssetPickerModal = 'true';
|
|
1575
|
+
assetPickerModal.dataset.editorWorkbenchRegion = 'modal';
|
|
1576
|
+
assetPickerModal.style.cssText = [
|
|
1577
|
+
'position:fixed',
|
|
1578
|
+
'inset:0',
|
|
1579
|
+
'z-index:2147483642',
|
|
1580
|
+
'display:none',
|
|
1581
|
+
'align-items:center',
|
|
1582
|
+
'justify-content:center',
|
|
1583
|
+
'padding:32px',
|
|
1584
|
+
'background:rgba(0,0,0,0.45)',
|
|
1585
|
+
'font-family:var(--fps-editor-font)',
|
|
1586
|
+
'color:var(--fps-editor-text)',
|
|
1587
|
+
'pointer-events:auto',
|
|
1588
|
+
].join(';');
|
|
1589
|
+
const assetPickerDialog = doc.createElement('div');
|
|
1590
|
+
assetPickerDialog.setAttribute('role', 'dialog');
|
|
1591
|
+
assetPickerDialog.setAttribute('aria-modal', 'true');
|
|
1592
|
+
assetPickerDialog.setAttribute('aria-label', '资产选择');
|
|
1593
|
+
assetPickerDialog.style.cssText = [
|
|
1594
|
+
'width:min(620px,calc(100vw - 64px))',
|
|
1595
|
+
'max-height:calc(100vh - 64px)',
|
|
1596
|
+
'display:flex',
|
|
1597
|
+
'flex-direction:column',
|
|
1598
|
+
'gap:10px',
|
|
1599
|
+
'padding:14px',
|
|
1600
|
+
'border:1px solid var(--fps-editor-border)',
|
|
1601
|
+
'border-radius:4px',
|
|
1602
|
+
'background:var(--fps-editor-panel)',
|
|
1603
|
+
'box-shadow:var(--fps-editor-shadow-modal)',
|
|
1604
|
+
'overflow:hidden',
|
|
1605
|
+
].join(';');
|
|
1606
|
+
const assetPickerHeader = doc.createElement('div');
|
|
1607
|
+
assetPickerHeader.style.cssText = 'display:flex;align-items:center;justify-content:space-between;gap:12px';
|
|
1608
|
+
const assetPickerTitle = doc.createElement('h2');
|
|
1609
|
+
assetPickerTitle.style.cssText = [
|
|
1610
|
+
'min-width:0',
|
|
1611
|
+
'margin:0',
|
|
1612
|
+
'overflow:hidden',
|
|
1613
|
+
'text-overflow:ellipsis',
|
|
1614
|
+
'white-space:nowrap',
|
|
1615
|
+
'font-size:13px',
|
|
1616
|
+
'font-weight:900',
|
|
1617
|
+
'color:var(--fps-editor-text-strong)',
|
|
1618
|
+
].join(';');
|
|
1619
|
+
const assetPickerCloseButton = LocalEditorShared.createButton(doc, '关闭');
|
|
1620
|
+
assetPickerCloseButton.dataset.editorAssetPickerClose = 'true';
|
|
1621
|
+
assetPickerHeader.appendChild(assetPickerTitle);
|
|
1622
|
+
assetPickerHeader.appendChild(assetPickerCloseButton);
|
|
1623
|
+
const assetPickerSearch = doc.createElement('input');
|
|
1624
|
+
assetPickerSearch.type = 'search';
|
|
1625
|
+
assetPickerSearch.dataset.editorAssetPickerSearch = 'true';
|
|
1626
|
+
assetPickerSearch.placeholder = '搜索资产';
|
|
1627
|
+
assetPickerSearch.style.cssText = [
|
|
1628
|
+
'width:100%',
|
|
1629
|
+
'height:30px',
|
|
1630
|
+
'border:1px solid var(--fps-editor-border)',
|
|
1631
|
+
'border-radius:3px',
|
|
1632
|
+
'background:var(--fps-editor-field)',
|
|
1633
|
+
'color:var(--fps-editor-text)',
|
|
1634
|
+
'font-size:12px',
|
|
1635
|
+
'padding:0 8px',
|
|
1636
|
+
].join(';');
|
|
1637
|
+
const assetPickerList = doc.createElement('div');
|
|
1638
|
+
assetPickerList.style.cssText = [
|
|
1639
|
+
'min-height:180px',
|
|
1640
|
+
'max-height:min(430px,calc(100vh - 196px))',
|
|
1641
|
+
'overflow:auto',
|
|
1642
|
+
'display:grid',
|
|
1643
|
+
'grid-template-columns:repeat(auto-fill,minmax(176px,1fr))',
|
|
1644
|
+
'gap:7px',
|
|
1645
|
+
'padding-right:2px',
|
|
1646
|
+
].join(';');
|
|
1647
|
+
assetPickerDialog.appendChild(assetPickerHeader);
|
|
1648
|
+
assetPickerDialog.appendChild(assetPickerSearch);
|
|
1649
|
+
assetPickerDialog.appendChild(assetPickerList);
|
|
1650
|
+
assetPickerModal.appendChild(assetPickerDialog);
|
|
1651
|
+
root.appendChild(assetPickerModal);
|
|
1652
|
+
tooltipSurfaces.add(assetPickerModal);
|
|
1275
1653
|
function applyThemeToSurfaces() {
|
|
1276
1654
|
applyLocalEditorTheme(hostChrome, activeTheme);
|
|
1277
1655
|
applyLocalEditorTheme(workbench.root, activeTheme);
|
|
@@ -1279,6 +1657,8 @@ export function createLocalEditorBrowserUi(options = {}) {
|
|
|
1279
1657
|
applyLocalEditorTheme(measurementOverlay.root, activeTheme);
|
|
1280
1658
|
applyLocalEditorTheme(sceneFrameRateOverlay.root, activeTheme);
|
|
1281
1659
|
applyLocalEditorTheme(shortcutHelpPanel, activeTheme);
|
|
1660
|
+
applyLocalEditorTheme(assetPreviewModal, activeTheme);
|
|
1661
|
+
applyLocalEditorTheme(assetPickerModal, activeTheme);
|
|
1282
1662
|
applyLocalEditorTheme(boxSelectionOverlay, activeTheme);
|
|
1283
1663
|
applyLocalEditorTheme(localTestMenu, activeTheme);
|
|
1284
1664
|
applyLocalEditorTheme(toolbarOverflowMenu, activeTheme);
|
|
@@ -1490,14 +1870,14 @@ export function createLocalEditorBrowserUi(options = {}) {
|
|
|
1490
1870
|
actionGroup,
|
|
1491
1871
|
viewportToolsGroup,
|
|
1492
1872
|
sceneUtilityActions,
|
|
1493
|
-
|
|
1873
|
+
cameraPreviewGroup,
|
|
1494
1874
|
editorStatusButton,
|
|
1495
1875
|
toolbarOverflowButton,
|
|
1496
1876
|
];
|
|
1497
1877
|
const toolbarOverflowItems = [
|
|
1498
1878
|
{ id: 'editor-status', element: editorStatusButton, kind: 'group' },
|
|
1499
1879
|
{ id: 'scene-utilities', element: sceneUtilityActions, kind: 'group' },
|
|
1500
|
-
{ id: '
|
|
1880
|
+
{ id: 'camera-preview', element: cameraPreviewGroup, kind: 'group' },
|
|
1501
1881
|
{ id: 'viewport-tools', element: viewportToolsGroup, kind: 'group' },
|
|
1502
1882
|
{ id: 'transform-actions', element: actionGroup, kind: 'group' },
|
|
1503
1883
|
{ id: 'placement', element: placementGroup, kind: 'group' },
|
|
@@ -1702,9 +2082,9 @@ export function createLocalEditorBrowserUi(options = {}) {
|
|
|
1702
2082
|
const visible = currentState?.grid?.visible ?? true;
|
|
1703
2083
|
callbacks.onGridVisibleChange?.(!visible);
|
|
1704
2084
|
});
|
|
1705
|
-
|
|
1706
|
-
const enabled = currentState?.
|
|
1707
|
-
callbacks.
|
|
2085
|
+
sceneCameraButton.addEventListener('click', () => {
|
|
2086
|
+
const enabled = currentState?.sceneCameraPreview?.enabled ?? false;
|
|
2087
|
+
callbacks.onSceneCameraPreviewToggle?.(!enabled);
|
|
1708
2088
|
});
|
|
1709
2089
|
const toggleCoordinateAxesProjection = (event) => {
|
|
1710
2090
|
event.preventDefault();
|
|
@@ -1763,10 +2143,414 @@ export function createLocalEditorBrowserUi(options = {}) {
|
|
|
1763
2143
|
if (open)
|
|
1764
2144
|
contextMenu.close();
|
|
1765
2145
|
helpOpen = open;
|
|
1766
|
-
|
|
2146
|
+
syncInputModalOpen();
|
|
1767
2147
|
shortcutHelpPanel.style.display = helpOpen ? '' : 'none';
|
|
1768
2148
|
LocalEditorShared.applyButtonActiveState(sceneHelpButton, helpOpen);
|
|
1769
2149
|
};
|
|
2150
|
+
const openAssetPreviewModal = (asset) => {
|
|
2151
|
+
if (!asset.preview)
|
|
2152
|
+
return;
|
|
2153
|
+
contextMenu.close();
|
|
2154
|
+
previewedAsset = asset;
|
|
2155
|
+
assetPreviewMode = 'preview';
|
|
2156
|
+
assetPreviewDialog.setAttribute('aria-label', '资产预览');
|
|
2157
|
+
assetPreviewOpen = true;
|
|
2158
|
+
renderAssetPreviewModal();
|
|
2159
|
+
syncInputModalOpen();
|
|
2160
|
+
assetPreviewCloseButton.focus({ preventScroll: true });
|
|
2161
|
+
};
|
|
2162
|
+
const openAssetMaterialEditorModal = (asset) => {
|
|
2163
|
+
if (!asset.material)
|
|
2164
|
+
return;
|
|
2165
|
+
contextMenu.close();
|
|
2166
|
+
previewedAsset = asset;
|
|
2167
|
+
assetPreviewMode = 'material-editor';
|
|
2168
|
+
assetPreviewDialog.setAttribute('aria-label', '编辑材质球');
|
|
2169
|
+
assetPreviewOpen = true;
|
|
2170
|
+
renderAssetPreviewModal();
|
|
2171
|
+
syncInputModalOpen();
|
|
2172
|
+
assetPreviewCloseButton.focus({ preventScroll: true });
|
|
2173
|
+
};
|
|
2174
|
+
const closeAssetPreviewModal = () => {
|
|
2175
|
+
if (!assetPreviewOpen)
|
|
2176
|
+
return;
|
|
2177
|
+
assetPreviewOpen = false;
|
|
2178
|
+
previewedAsset = null;
|
|
2179
|
+
assetPreviewMode = 'preview';
|
|
2180
|
+
renderAssetPreviewModal();
|
|
2181
|
+
syncInputModalOpen();
|
|
2182
|
+
};
|
|
2183
|
+
const openInspectorAssetPicker = (input, property) => {
|
|
2184
|
+
contextMenu.close();
|
|
2185
|
+
if (helpOpen)
|
|
2186
|
+
setShortcutHelpOpen(false);
|
|
2187
|
+
assetPickerInput = input;
|
|
2188
|
+
assetPickerProperty = property;
|
|
2189
|
+
assetPickerFilter = '';
|
|
2190
|
+
assetPickerOpen = true;
|
|
2191
|
+
closeAssetPreviewModal();
|
|
2192
|
+
renderAssetPickerModal();
|
|
2193
|
+
syncInputModalOpen();
|
|
2194
|
+
assetPickerSearch.focus({ preventScroll: true });
|
|
2195
|
+
assetPickerSearch.select();
|
|
2196
|
+
};
|
|
2197
|
+
const closeInspectorAssetPicker = () => {
|
|
2198
|
+
if (!assetPickerOpen)
|
|
2199
|
+
return;
|
|
2200
|
+
assetPickerOpen = false;
|
|
2201
|
+
assetPickerInput = null;
|
|
2202
|
+
assetPickerProperty = null;
|
|
2203
|
+
assetPickerFilter = '';
|
|
2204
|
+
renderAssetPickerModal();
|
|
2205
|
+
syncInputModalOpen();
|
|
2206
|
+
};
|
|
2207
|
+
function syncInputModalOpen() {
|
|
2208
|
+
inputRouter.setModalOpen(currentState?.mode === 'editor' && (helpOpen || assetPreviewOpen || assetPickerOpen));
|
|
2209
|
+
}
|
|
2210
|
+
function resolvePreviewedAssetFromState() {
|
|
2211
|
+
if (!previewedAsset)
|
|
2212
|
+
return null;
|
|
2213
|
+
const refreshed = currentState?.assets.find(asset => asset.id === previewedAsset?.id)
|
|
2214
|
+
?? currentState?.assets.find(asset => (asset.assetId ?? asset.id) === (previewedAsset?.assetId ?? previewedAsset?.id))
|
|
2215
|
+
?? null;
|
|
2216
|
+
if (refreshed)
|
|
2217
|
+
previewedAsset = refreshed;
|
|
2218
|
+
return previewedAsset;
|
|
2219
|
+
}
|
|
2220
|
+
function renderAssetPreviewModal() {
|
|
2221
|
+
const asset = resolvePreviewedAssetFromState();
|
|
2222
|
+
if (!assetPreviewOpen || !asset || (assetPreviewMode === 'preview' && !asset.preview) || (assetPreviewMode === 'material-editor' && !asset.material)) {
|
|
2223
|
+
assetPreviewModal.style.display = 'none';
|
|
2224
|
+
assetPreviewTitle.textContent = '';
|
|
2225
|
+
assetPreviewMeta.textContent = '';
|
|
2226
|
+
while (assetPreviewBody.firstChild)
|
|
2227
|
+
assetPreviewBody.removeChild(assetPreviewBody.firstChild);
|
|
2228
|
+
return;
|
|
2229
|
+
}
|
|
2230
|
+
if (assetPreviewMode === 'material-editor' && asset.material) {
|
|
2231
|
+
renderAssetMaterialEditorModal(asset);
|
|
2232
|
+
assetPreviewModal.style.display = 'flex';
|
|
2233
|
+
return;
|
|
2234
|
+
}
|
|
2235
|
+
assetPreviewDialog.style.width = 'min(420px,calc(100vw - 64px))';
|
|
2236
|
+
assetPreviewBody.style.cssText = [
|
|
2237
|
+
'display:flex',
|
|
2238
|
+
'align-items:center',
|
|
2239
|
+
'justify-content:center',
|
|
2240
|
+
'min-height:240px',
|
|
2241
|
+
'border:1px solid var(--fps-editor-divider)',
|
|
2242
|
+
'border-radius:4px',
|
|
2243
|
+
'background:var(--fps-editor-panel-soft)',
|
|
2244
|
+
].join(';');
|
|
2245
|
+
assetPreviewTitle.textContent = asset.label;
|
|
2246
|
+
assetPreviewMeta.textContent = [
|
|
2247
|
+
asset.kind ? `Kind: ${asset.kind}` : '',
|
|
2248
|
+
asset.meta ?? asset.assetId ?? asset.id,
|
|
2249
|
+
].filter(Boolean).join(' · ');
|
|
2250
|
+
while (assetPreviewBody.firstChild)
|
|
2251
|
+
assetPreviewBody.removeChild(assetPreviewBody.firstChild);
|
|
2252
|
+
assetPreviewBody.appendChild(LocalEditorPanels.createAssetPreviewElement(doc, asset, 'modal'));
|
|
2253
|
+
assetPreviewModal.style.display = 'flex';
|
|
2254
|
+
}
|
|
2255
|
+
function renderAssetMaterialEditorModal(asset) {
|
|
2256
|
+
const material = asset.material;
|
|
2257
|
+
const profile = material.profile ?? {};
|
|
2258
|
+
const baseColor = profile.baseColor ?? {};
|
|
2259
|
+
const emission = profile.emission ?? {};
|
|
2260
|
+
const readonly = material.readonly === true;
|
|
2261
|
+
const materialKind = material.materialKind === 'standard' ? 'standard' : 'pbr';
|
|
2262
|
+
assetPreviewDialog.style.width = 'min(780px,calc(100vw - 64px))';
|
|
2263
|
+
assetPreviewTitle.textContent = '编辑材质球';
|
|
2264
|
+
assetPreviewMeta.textContent = [
|
|
2265
|
+
material.name || asset.label,
|
|
2266
|
+
materialKind === 'standard' ? 'Standard' : 'PBR',
|
|
2267
|
+
readonly ? '只读系统材质' : '共享资产参数',
|
|
2268
|
+
material.id,
|
|
2269
|
+
].filter(Boolean).join(' · ');
|
|
2270
|
+
while (assetPreviewBody.firstChild)
|
|
2271
|
+
assetPreviewBody.removeChild(assetPreviewBody.firstChild);
|
|
2272
|
+
assetPreviewBody.style.cssText = [
|
|
2273
|
+
'display:grid',
|
|
2274
|
+
'grid-template-columns:minmax(180px,250px) minmax(0,1fr)',
|
|
2275
|
+
'gap:14px',
|
|
2276
|
+
'align-items:start',
|
|
2277
|
+
'min-height:360px',
|
|
2278
|
+
'border:1px solid var(--fps-editor-divider)',
|
|
2279
|
+
'border-radius:4px',
|
|
2280
|
+
'background:var(--fps-editor-panel-soft)',
|
|
2281
|
+
'padding:12px',
|
|
2282
|
+
'box-sizing:border-box',
|
|
2283
|
+
].join(';');
|
|
2284
|
+
const previewColumn = doc.createElement('div');
|
|
2285
|
+
previewColumn.style.cssText = 'display:flex;flex-direction:column;gap:10px;min-width:0';
|
|
2286
|
+
previewColumn.appendChild(LocalEditorPanels.createAssetPreviewElement(doc, asset, 'modal'));
|
|
2287
|
+
const previewMeta = doc.createElement('div');
|
|
2288
|
+
previewMeta.textContent = readonly
|
|
2289
|
+
? '系统默认材质不可编辑。'
|
|
2290
|
+
: '修改会影响所有使用该材质球的物体。';
|
|
2291
|
+
previewMeta.style.cssText = 'color:var(--fps-editor-muted);font-size:11px;font-weight:800;line-height:1.45';
|
|
2292
|
+
previewColumn.appendChild(previewMeta);
|
|
2293
|
+
const form = doc.createElement('div');
|
|
2294
|
+
form.style.cssText = 'display:grid;grid-template-columns:minmax(96px,140px) minmax(0,1fr);gap:7px 10px;align-items:center;min-width:0';
|
|
2295
|
+
appendMaterialEditorReadonlyRow(form, '名称', material.name || asset.label);
|
|
2296
|
+
appendMaterialEditorReadonlyRow(form, '类型', materialKind === 'standard' ? 'Standard 标准材质球' : 'PBR 标准材质球');
|
|
2297
|
+
appendMaterialEditorColorField(form, '基础颜色', 'profile.baseColor.color', baseColor.color ?? { r: 1, g: 1, b: 1 }, readonly);
|
|
2298
|
+
appendMaterialEditorTextureField(form, '基础贴图', 'profile.baseColor.texture.url', baseColor.texture?.url ?? '', readonly);
|
|
2299
|
+
appendMaterialEditorNumberField(form, '亮度', 'profile.baseColor.brightness', baseColor.brightness ?? 1, readonly, 0, 2, 0.05);
|
|
2300
|
+
appendMaterialEditorNumberField(form, '饱和度', 'profile.baseColor.saturation', baseColor.saturation ?? 1, readonly, 0, 2, 0.05);
|
|
2301
|
+
appendMaterialEditorNumberField(form, '对比度', 'profile.baseColor.contrast', baseColor.contrast ?? 1, readonly, 0, 2, 0.05);
|
|
2302
|
+
appendMaterialEditorNumberField(form, '色相', 'profile.baseColor.hue', baseColor.hue ?? 0, readonly, -180, 180, 1);
|
|
2303
|
+
if (materialKind === 'pbr') {
|
|
2304
|
+
appendMaterialEditorNumberField(form, '金属度', 'profile.metallic', profile.metallic ?? 0, readonly, 0, 1, 0.05);
|
|
2305
|
+
appendMaterialEditorNumberField(form, '粗糙度', 'profile.roughness', profile.roughness ?? 1, readonly, 0, 1, 0.05);
|
|
2306
|
+
}
|
|
2307
|
+
appendMaterialEditorColorField(form, '自发光颜色', 'profile.emission.color', emission.color ?? { r: 0, g: 0, b: 0 }, readonly);
|
|
2308
|
+
appendMaterialEditorNumberField(form, '自发光强度', 'profile.emission.intensity', emission.intensity ?? 0, readonly, 0, undefined, 0.05);
|
|
2309
|
+
appendMaterialEditorTextureField(form, '遮罩贴图', 'profile.emission.maskTexture.url', emission.maskTexture?.url ?? '', readonly);
|
|
2310
|
+
assetPreviewBody.appendChild(previewColumn);
|
|
2311
|
+
assetPreviewBody.appendChild(form);
|
|
2312
|
+
}
|
|
2313
|
+
function appendMaterialEditorReadonlyRow(container, label, value) {
|
|
2314
|
+
appendMaterialEditorLabel(container, label);
|
|
2315
|
+
const output = doc.createElement('div');
|
|
2316
|
+
output.textContent = value;
|
|
2317
|
+
output.style.cssText = [
|
|
2318
|
+
'min-height:30px',
|
|
2319
|
+
'display:flex',
|
|
2320
|
+
'align-items:center',
|
|
2321
|
+
'min-width:0',
|
|
2322
|
+
'padding:0 8px',
|
|
2323
|
+
'border:1px solid var(--fps-editor-border)',
|
|
2324
|
+
'border-radius:3px',
|
|
2325
|
+
'background:var(--fps-editor-field)',
|
|
2326
|
+
'color:var(--fps-editor-muted-strong)',
|
|
2327
|
+
'font-size:12px',
|
|
2328
|
+
'font-weight:900',
|
|
2329
|
+
'overflow:hidden',
|
|
2330
|
+
'text-overflow:ellipsis',
|
|
2331
|
+
'white-space:nowrap',
|
|
2332
|
+
].join(';');
|
|
2333
|
+
container.appendChild(output);
|
|
2334
|
+
}
|
|
2335
|
+
function appendMaterialEditorColorField(container, label, fieldPath, color, readonly) {
|
|
2336
|
+
appendMaterialEditorLabel(container, label);
|
|
2337
|
+
const input = doc.createElement('input');
|
|
2338
|
+
input.type = 'color';
|
|
2339
|
+
input.value = materialEditorColorToHex(color);
|
|
2340
|
+
input.disabled = readonly;
|
|
2341
|
+
input.dataset.editorAssetMaterialField = fieldPath;
|
|
2342
|
+
input.dataset.editorAssetMaterialFieldKind = 'color';
|
|
2343
|
+
input.style.cssText = [
|
|
2344
|
+
'width:48px',
|
|
2345
|
+
'height:32px',
|
|
2346
|
+
'padding:2px',
|
|
2347
|
+
'border:1px solid var(--fps-editor-accent)',
|
|
2348
|
+
'border-radius:3px',
|
|
2349
|
+
'background:var(--fps-editor-field)',
|
|
2350
|
+
readonly ? 'opacity:0.62' : 'cursor:pointer',
|
|
2351
|
+
].join(';');
|
|
2352
|
+
container.appendChild(input);
|
|
2353
|
+
}
|
|
2354
|
+
function appendMaterialEditorNumberField(container, label, fieldPath, value, readonly, min, max, step = 0.05) {
|
|
2355
|
+
appendMaterialEditorLabel(container, label);
|
|
2356
|
+
const input = doc.createElement('input');
|
|
2357
|
+
input.type = 'number';
|
|
2358
|
+
input.value = formatMaterialEditorNumber(value);
|
|
2359
|
+
if (min != null)
|
|
2360
|
+
input.min = String(min);
|
|
2361
|
+
if (max != null)
|
|
2362
|
+
input.max = String(max);
|
|
2363
|
+
input.step = String(step);
|
|
2364
|
+
input.disabled = readonly;
|
|
2365
|
+
input.dataset.editorAssetMaterialField = fieldPath;
|
|
2366
|
+
input.dataset.editorAssetMaterialFieldKind = 'number';
|
|
2367
|
+
input.style.cssText = createMaterialEditorFieldCss(readonly);
|
|
2368
|
+
container.appendChild(input);
|
|
2369
|
+
}
|
|
2370
|
+
function appendMaterialEditorTextureField(container, label, fieldPath, value, readonly) {
|
|
2371
|
+
appendMaterialEditorLabel(container, label);
|
|
2372
|
+
const select = doc.createElement('select');
|
|
2373
|
+
select.disabled = readonly;
|
|
2374
|
+
select.dataset.editorAssetMaterialField = fieldPath;
|
|
2375
|
+
select.dataset.editorAssetMaterialFieldKind = 'texture';
|
|
2376
|
+
select.style.cssText = createMaterialEditorFieldCss(readonly);
|
|
2377
|
+
for (const option of createMaterialEditorTextureOptions(value)) {
|
|
2378
|
+
const optionElement = doc.createElement('option');
|
|
2379
|
+
optionElement.value = option.value;
|
|
2380
|
+
optionElement.textContent = option.label;
|
|
2381
|
+
optionElement.title = option.meta ?? option.value;
|
|
2382
|
+
select.appendChild(optionElement);
|
|
2383
|
+
}
|
|
2384
|
+
select.value = value;
|
|
2385
|
+
container.appendChild(select);
|
|
2386
|
+
}
|
|
2387
|
+
function appendMaterialEditorLabel(container, text) {
|
|
2388
|
+
const label = doc.createElement('div');
|
|
2389
|
+
label.textContent = text;
|
|
2390
|
+
label.style.cssText = 'color:var(--fps-editor-muted-strong);font-size:12px;font-weight:900;line-height:1.2';
|
|
2391
|
+
container.appendChild(label);
|
|
2392
|
+
}
|
|
2393
|
+
function createMaterialEditorTextureOptions(currentValue) {
|
|
2394
|
+
const options = [{ label: '继承 / 无', value: '', meta: '清空贴图引用' }];
|
|
2395
|
+
const seen = new Set(['']);
|
|
2396
|
+
for (const asset of currentState?.assets ?? []) {
|
|
2397
|
+
if (resolveBrowserAssetKind(asset) !== 'texture')
|
|
2398
|
+
continue;
|
|
2399
|
+
const value = asset.preview?.kind === 'image'
|
|
2400
|
+
? asset.preview.url
|
|
2401
|
+
: asset.external?.assetUrl ?? asset.external?.assetPath ?? '';
|
|
2402
|
+
if (!value || seen.has(value))
|
|
2403
|
+
continue;
|
|
2404
|
+
seen.add(value);
|
|
2405
|
+
options.push({ label: asset.label, value, meta: asset.meta ?? asset.assetId ?? asset.id });
|
|
2406
|
+
}
|
|
2407
|
+
if (currentValue && !seen.has(currentValue)) {
|
|
2408
|
+
options.push({ label: currentValue, value: currentValue, meta: '当前贴图 URL' });
|
|
2409
|
+
}
|
|
2410
|
+
return options;
|
|
2411
|
+
}
|
|
2412
|
+
function createMaterialEditorFieldCss(readonly) {
|
|
2413
|
+
return [
|
|
2414
|
+
'width:100%',
|
|
2415
|
+
'min-width:0',
|
|
2416
|
+
'height:32px',
|
|
2417
|
+
'padding:0 8px',
|
|
2418
|
+
'border:1px solid var(--fps-editor-accent)',
|
|
2419
|
+
'border-radius:3px',
|
|
2420
|
+
'background:var(--fps-editor-field)',
|
|
2421
|
+
'color:var(--fps-editor-text-strong)',
|
|
2422
|
+
'font-size:12px',
|
|
2423
|
+
'font-weight:800',
|
|
2424
|
+
'box-sizing:border-box',
|
|
2425
|
+
readonly ? 'opacity:0.62' : '',
|
|
2426
|
+
].join(';');
|
|
2427
|
+
}
|
|
2428
|
+
function formatMaterialEditorNumber(value) {
|
|
2429
|
+
if (!Number.isFinite(value))
|
|
2430
|
+
return '0';
|
|
2431
|
+
return Number.isInteger(value) ? String(value) : value.toFixed(4).replace(/0+$/, '').replace(/\.$/, '');
|
|
2432
|
+
}
|
|
2433
|
+
function materialEditorColorToHex(color) {
|
|
2434
|
+
const toHex = (value) => {
|
|
2435
|
+
const normalized = Number.isFinite(value) ? value : 0;
|
|
2436
|
+
const byte = Math.round(Math.min(255, Math.max(0, normalized <= 1 ? normalized * 255 : normalized)));
|
|
2437
|
+
return byte.toString(16).padStart(2, '0');
|
|
2438
|
+
};
|
|
2439
|
+
return `#${toHex(color.r)}${toHex(color.g)}${toHex(color.b)}`;
|
|
2440
|
+
}
|
|
2441
|
+
function materialEditorHexToColor(value) {
|
|
2442
|
+
const match = value.trim().match(/^#?([0-9a-f]{6})$/i);
|
|
2443
|
+
if (!match)
|
|
2444
|
+
return null;
|
|
2445
|
+
const hex = match[1];
|
|
2446
|
+
return {
|
|
2447
|
+
r: Number.parseInt(hex.slice(0, 2), 16) / 255,
|
|
2448
|
+
g: Number.parseInt(hex.slice(2, 4), 16) / 255,
|
|
2449
|
+
b: Number.parseInt(hex.slice(4, 6), 16) / 255,
|
|
2450
|
+
};
|
|
2451
|
+
}
|
|
2452
|
+
function dispatchMaterialEditorFieldChange(input) {
|
|
2453
|
+
const asset = resolvePreviewedAssetFromState();
|
|
2454
|
+
const material = asset?.material;
|
|
2455
|
+
const fieldPath = input.dataset.editorAssetMaterialField;
|
|
2456
|
+
if (!asset || !material || !fieldPath || material.readonly === true)
|
|
2457
|
+
return;
|
|
2458
|
+
const kind = input.dataset.editorAssetMaterialFieldKind;
|
|
2459
|
+
let value;
|
|
2460
|
+
if (kind === 'color' && input instanceof HTMLInputElement) {
|
|
2461
|
+
value = materialEditorHexToColor(input.value);
|
|
2462
|
+
if (!value)
|
|
2463
|
+
return;
|
|
2464
|
+
}
|
|
2465
|
+
else if (kind === 'number' && input instanceof HTMLInputElement) {
|
|
2466
|
+
value = Number(input.value);
|
|
2467
|
+
if (!Number.isFinite(value))
|
|
2468
|
+
return;
|
|
2469
|
+
}
|
|
2470
|
+
else if (kind === 'texture') {
|
|
2471
|
+
value = input.value.trim() || null;
|
|
2472
|
+
}
|
|
2473
|
+
else {
|
|
2474
|
+
value = input.value;
|
|
2475
|
+
}
|
|
2476
|
+
callbacks.onAssetAction?.({
|
|
2477
|
+
actionId: 'asset.edit-material-field',
|
|
2478
|
+
assetId: material.id || asset.assetId || asset.id,
|
|
2479
|
+
browserAssetId: asset.id,
|
|
2480
|
+
assetKind: asset.kind,
|
|
2481
|
+
fieldPath,
|
|
2482
|
+
value,
|
|
2483
|
+
});
|
|
2484
|
+
}
|
|
2485
|
+
function renderAssetPickerModal() {
|
|
2486
|
+
if (!assetPickerOpen || !assetPickerProperty || !assetPickerInput) {
|
|
2487
|
+
assetPickerModal.style.display = 'none';
|
|
2488
|
+
assetPickerTitle.textContent = '';
|
|
2489
|
+
assetPickerSearch.value = '';
|
|
2490
|
+
while (assetPickerList.firstChild)
|
|
2491
|
+
assetPickerList.removeChild(assetPickerList.firstChild);
|
|
2492
|
+
return;
|
|
2493
|
+
}
|
|
2494
|
+
const options = readInspectorAssetPickerOptions(assetPickerProperty);
|
|
2495
|
+
assetPickerTitle.textContent = options.title;
|
|
2496
|
+
assetPickerSearch.value = assetPickerFilter;
|
|
2497
|
+
while (assetPickerList.firstChild)
|
|
2498
|
+
assetPickerList.removeChild(assetPickerList.firstChild);
|
|
2499
|
+
const normalizedFilter = assetPickerFilter.trim().toLowerCase();
|
|
2500
|
+
const candidates = options.candidates.filter(candidate => {
|
|
2501
|
+
if (!normalizedFilter)
|
|
2502
|
+
return true;
|
|
2503
|
+
return [candidate.label, candidate.value, candidate.meta ?? '', candidate.kind ?? '']
|
|
2504
|
+
.some(value => value.toLowerCase().includes(normalizedFilter));
|
|
2505
|
+
});
|
|
2506
|
+
if (candidates.length === 0) {
|
|
2507
|
+
const empty = doc.createElement('div');
|
|
2508
|
+
empty.textContent = '没有匹配的资产。';
|
|
2509
|
+
empty.style.cssText = 'color:var(--fps-editor-muted);font-size:12px;font-weight:800;padding:10px';
|
|
2510
|
+
assetPickerList.appendChild(empty);
|
|
2511
|
+
}
|
|
2512
|
+
for (const candidate of candidates) {
|
|
2513
|
+
assetPickerList.appendChild(createInspectorAssetPickerCandidateButton(candidate, assetPickerInput.value));
|
|
2514
|
+
}
|
|
2515
|
+
assetPickerModal.style.display = 'flex';
|
|
2516
|
+
}
|
|
2517
|
+
function createInspectorAssetPickerCandidateButton(candidate, currentValue) {
|
|
2518
|
+
const button = doc.createElement('button');
|
|
2519
|
+
button.type = 'button';
|
|
2520
|
+
button.dataset.editorAssetPickerCandidate = candidate.value;
|
|
2521
|
+
const selected = candidate.value === currentValue;
|
|
2522
|
+
button.style.cssText = [
|
|
2523
|
+
'min-width:0',
|
|
2524
|
+
'min-height:56px',
|
|
2525
|
+
'display:flex',
|
|
2526
|
+
'align-items:center',
|
|
2527
|
+
'gap:8px',
|
|
2528
|
+
'padding:7px 8px',
|
|
2529
|
+
`border:1px solid ${selected ? 'var(--fps-editor-accent-strong)' : 'var(--fps-editor-border)'}`,
|
|
2530
|
+
'border-radius:3px',
|
|
2531
|
+
`background:${selected ? 'var(--fps-editor-accent-soft)' : 'var(--fps-editor-asset-card-bg)'}`,
|
|
2532
|
+
'color:var(--fps-editor-text)',
|
|
2533
|
+
'text-align:left',
|
|
2534
|
+
'cursor:pointer',
|
|
2535
|
+
].join(';');
|
|
2536
|
+
button.appendChild(LocalEditorPanels.createAssetPreviewElement(doc, {
|
|
2537
|
+
id: candidate.value || '__empty__',
|
|
2538
|
+
label: candidate.label,
|
|
2539
|
+
preview: candidate.preview,
|
|
2540
|
+
}, 'grid'));
|
|
2541
|
+
const body = doc.createElement('div');
|
|
2542
|
+
body.style.cssText = 'min-width:0;flex:1';
|
|
2543
|
+
const label = doc.createElement('div');
|
|
2544
|
+
label.textContent = candidate.label;
|
|
2545
|
+
label.style.cssText = 'font-weight:900;overflow:hidden;text-overflow:ellipsis;white-space:nowrap';
|
|
2546
|
+
const meta = doc.createElement('div');
|
|
2547
|
+
meta.textContent = candidate.meta ?? candidate.value;
|
|
2548
|
+
meta.style.cssText = 'color:var(--fps-editor-muted);font-size:11px;font-weight:800;overflow:hidden;text-overflow:ellipsis;white-space:nowrap';
|
|
2549
|
+
body.appendChild(label);
|
|
2550
|
+
body.appendChild(meta);
|
|
2551
|
+
button.appendChild(body);
|
|
2552
|
+
return button;
|
|
2553
|
+
}
|
|
1770
2554
|
sceneHelpButton.addEventListener('click', () => {
|
|
1771
2555
|
setShortcutHelpOpen(!helpOpen);
|
|
1772
2556
|
});
|
|
@@ -1779,6 +2563,44 @@ export function createLocalEditorBrowserUi(options = {}) {
|
|
|
1779
2563
|
return;
|
|
1780
2564
|
setShortcutHelpOpen(false);
|
|
1781
2565
|
});
|
|
2566
|
+
assetPreviewModal.addEventListener('click', (event) => {
|
|
2567
|
+
const target = event.target instanceof HTMLElement ? event.target : null;
|
|
2568
|
+
if (target?.closest('[data-editor-asset-preview-close]')) {
|
|
2569
|
+
closeAssetPreviewModal();
|
|
2570
|
+
return;
|
|
2571
|
+
}
|
|
2572
|
+
if (target === assetPreviewModal)
|
|
2573
|
+
closeAssetPreviewModal();
|
|
2574
|
+
});
|
|
2575
|
+
assetPreviewBody.addEventListener('change', (event) => {
|
|
2576
|
+
if (assetPreviewMode !== 'material-editor')
|
|
2577
|
+
return;
|
|
2578
|
+
const target = event.target instanceof HTMLElement ? event.target : null;
|
|
2579
|
+
const input = target?.closest('[data-editor-asset-material-field]');
|
|
2580
|
+
if (!input)
|
|
2581
|
+
return;
|
|
2582
|
+
dispatchMaterialEditorFieldChange(input);
|
|
2583
|
+
});
|
|
2584
|
+
assetPickerModal.addEventListener('click', (event) => {
|
|
2585
|
+
const target = event.target instanceof HTMLElement ? event.target : null;
|
|
2586
|
+
if (target?.closest('[data-editor-asset-picker-close]')) {
|
|
2587
|
+
closeInspectorAssetPicker();
|
|
2588
|
+
return;
|
|
2589
|
+
}
|
|
2590
|
+
const candidateButton = target?.closest('[data-editor-asset-picker-candidate]');
|
|
2591
|
+
if (candidateButton && assetPickerInput) {
|
|
2592
|
+
assetPickerInput.value = candidateButton.dataset.editorAssetPickerCandidate ?? '';
|
|
2593
|
+
assetPickerInput.dispatchEvent(new Event('change', { bubbles: true }));
|
|
2594
|
+
closeInspectorAssetPicker();
|
|
2595
|
+
return;
|
|
2596
|
+
}
|
|
2597
|
+
if (target === assetPickerModal)
|
|
2598
|
+
closeInspectorAssetPicker();
|
|
2599
|
+
});
|
|
2600
|
+
assetPickerSearch.addEventListener('input', () => {
|
|
2601
|
+
assetPickerFilter = assetPickerSearch.value;
|
|
2602
|
+
renderAssetPickerModal();
|
|
2603
|
+
});
|
|
1782
2604
|
toolGroup.addEventListener('click', (event) => {
|
|
1783
2605
|
const target = event.target instanceof HTMLElement ? event.target : null;
|
|
1784
2606
|
const tool = target?.closest('[data-transform-tool]')?.dataset.transformTool;
|
|
@@ -1868,11 +2690,70 @@ export function createLocalEditorBrowserUi(options = {}) {
|
|
|
1868
2690
|
render(currentState);
|
|
1869
2691
|
return;
|
|
1870
2692
|
}
|
|
2693
|
+
const assetBrowserTabButton = target?.closest('[data-editor-asset-browser-tab]');
|
|
2694
|
+
const nextAssetBrowserTab = assetBrowserTabButton?.dataset.editorAssetBrowserTab;
|
|
2695
|
+
if (isLocalEditorAssetBrowserTab(nextAssetBrowserTab)) {
|
|
2696
|
+
assetBrowserTab = nextAssetBrowserTab;
|
|
2697
|
+
if (currentState)
|
|
2698
|
+
render(currentState);
|
|
2699
|
+
return;
|
|
2700
|
+
}
|
|
1871
2701
|
const assetButton = target?.closest('[data-editor-asset-id]');
|
|
1872
2702
|
if (assetButton?.dataset.editorAssetId) {
|
|
1873
|
-
callbacks.
|
|
2703
|
+
callbacks.onSelectAsset?.(assetButton.dataset.editorAssetId);
|
|
2704
|
+
return;
|
|
2705
|
+
}
|
|
2706
|
+
});
|
|
2707
|
+
assetPanel.addEventListener('contextmenu', (event) => {
|
|
2708
|
+
const target = event.target instanceof HTMLElement ? event.target : null;
|
|
2709
|
+
const assetButton = target?.closest('[data-editor-asset-id]');
|
|
2710
|
+
const browserAssetId = assetButton?.dataset.editorAssetId;
|
|
2711
|
+
if (!browserAssetId || !currentState)
|
|
2712
|
+
return;
|
|
2713
|
+
const asset = currentState.assets.find(candidate => candidate.id === browserAssetId);
|
|
2714
|
+
if (!asset)
|
|
2715
|
+
return;
|
|
2716
|
+
event.preventDefault();
|
|
2717
|
+
event.stopPropagation();
|
|
2718
|
+
callbacks.onSelectAsset?.(browserAssetId);
|
|
2719
|
+
contextMenu.open({
|
|
2720
|
+
x: event.clientX,
|
|
2721
|
+
y: event.clientY,
|
|
2722
|
+
items: createAssetBrowserMenuItems(currentState, asset),
|
|
2723
|
+
onAction(item) {
|
|
2724
|
+
if (item.id === 'asset.preview') {
|
|
2725
|
+
openAssetPreviewModal(asset);
|
|
2726
|
+
return;
|
|
2727
|
+
}
|
|
2728
|
+
if (item.id === 'asset.edit-material') {
|
|
2729
|
+
openAssetMaterialEditorModal(asset);
|
|
2730
|
+
return;
|
|
2731
|
+
}
|
|
2732
|
+
if (item.id === 'asset.copy-id') {
|
|
2733
|
+
copyTextToClipboard(doc.defaultView, asset.assetId ?? asset.id);
|
|
2734
|
+
return;
|
|
2735
|
+
}
|
|
2736
|
+
callbacks.onAssetAction?.(createAssetBrowserActionInput(item.id, asset));
|
|
2737
|
+
},
|
|
2738
|
+
});
|
|
2739
|
+
});
|
|
2740
|
+
workbench.rightDock.addEventListener('click', (event) => {
|
|
2741
|
+
const target = event.target instanceof HTMLElement ? event.target : null;
|
|
2742
|
+
const openTabButton = target?.closest('[data-editor-open-right-dock-tab]');
|
|
2743
|
+
const openTab = openTabButton?.dataset.editorOpenRightDockTab;
|
|
2744
|
+
if (openTab === 'inspector' || openTab === 'rendering') {
|
|
2745
|
+
panelRegistry.setRightDockTab(openTab);
|
|
2746
|
+
if (currentState)
|
|
2747
|
+
render(currentState);
|
|
1874
2748
|
return;
|
|
1875
2749
|
}
|
|
2750
|
+
const tabButton = target?.closest('[data-editor-right-dock-tab]');
|
|
2751
|
+
const tab = tabButton?.dataset.editorRightDockTab;
|
|
2752
|
+
if (tab === 'inspector' || tab === 'rendering') {
|
|
2753
|
+
panelRegistry.setRightDockTab(tab);
|
|
2754
|
+
if (currentState)
|
|
2755
|
+
render(currentState);
|
|
2756
|
+
}
|
|
1876
2757
|
});
|
|
1877
2758
|
const hierarchyController = createLocalEditorHierarchyController({
|
|
1878
2759
|
doc,
|
|
@@ -1889,6 +2770,8 @@ export function createLocalEditorBrowserUi(options = {}) {
|
|
|
1889
2770
|
onBeforeOpenContextMenu: () => {
|
|
1890
2771
|
if (helpOpen)
|
|
1891
2772
|
setShortcutHelpOpen(false);
|
|
2773
|
+
if (assetPreviewOpen)
|
|
2774
|
+
closeAssetPreviewModal();
|
|
1892
2775
|
},
|
|
1893
2776
|
});
|
|
1894
2777
|
assetPanel.addEventListener('input', (event) => {
|
|
@@ -1911,10 +2794,38 @@ export function createLocalEditorBrowserUi(options = {}) {
|
|
|
1911
2794
|
return;
|
|
1912
2795
|
if (readInspectorCommitMode(input) !== 'live')
|
|
1913
2796
|
return;
|
|
1914
|
-
const propertyInput = createInspectorPropertyInput(input, 'input'
|
|
2797
|
+
const propertyInput = createInspectorPropertyInput(input, 'input');
|
|
1915
2798
|
if (propertyInput)
|
|
1916
2799
|
callbacks.onPropertyInput?.(propertyInput);
|
|
1917
2800
|
});
|
|
2801
|
+
inspectorPanel.addEventListener('click', (event) => {
|
|
2802
|
+
const target = event.target instanceof HTMLElement ? event.target : null;
|
|
2803
|
+
const copyButton = target?.closest('[data-editor-inspector-asset-copy]');
|
|
2804
|
+
if (copyButton) {
|
|
2805
|
+
const control = copyButton.closest('[data-editor-inspector-asset-picker-control]');
|
|
2806
|
+
const input = control?.querySelector('input[data-serialized-path]');
|
|
2807
|
+
const value = copyButton.dataset.editorInspectorAssetCopyValue;
|
|
2808
|
+
if (!input || !value)
|
|
2809
|
+
return;
|
|
2810
|
+
event.preventDefault();
|
|
2811
|
+
event.stopPropagation();
|
|
2812
|
+
input.value = value;
|
|
2813
|
+
input.dispatchEvent(new Event('change', { bubbles: true }));
|
|
2814
|
+
return;
|
|
2815
|
+
}
|
|
2816
|
+
const pickerButton = target?.closest('[data-editor-inspector-asset-picker]');
|
|
2817
|
+
if (!pickerButton)
|
|
2818
|
+
return;
|
|
2819
|
+
const control = pickerButton.closest('[data-editor-inspector-asset-picker-control]');
|
|
2820
|
+
const input = control?.querySelector('input[data-serialized-path]');
|
|
2821
|
+
if (!input || !currentState)
|
|
2822
|
+
return;
|
|
2823
|
+
const property = findInspectorPropertyForInput(currentState, input);
|
|
2824
|
+
if (!property)
|
|
2825
|
+
return;
|
|
2826
|
+
event.preventDefault();
|
|
2827
|
+
openInspectorAssetPicker(input, property);
|
|
2828
|
+
});
|
|
1918
2829
|
inspectorPanel.addEventListener('change', (event) => {
|
|
1919
2830
|
const input = event.target instanceof HTMLInputElement || event.target instanceof HTMLSelectElement
|
|
1920
2831
|
? event.target
|
|
@@ -1925,7 +2836,7 @@ export function createLocalEditorBrowserUi(options = {}) {
|
|
|
1925
2836
|
if (readInspectorCommitMode(input) !== 'change')
|
|
1926
2837
|
return;
|
|
1927
2838
|
}
|
|
1928
|
-
const propertyInput = createInspectorPropertyInput(input, readInspectorImmediateSource(input)
|
|
2839
|
+
const propertyInput = createInspectorPropertyInput(input, readInspectorImmediateSource(input));
|
|
1929
2840
|
if (propertyInput)
|
|
1930
2841
|
callbacks.onPropertyInput?.(propertyInput);
|
|
1931
2842
|
});
|
|
@@ -1933,27 +2844,65 @@ export function createLocalEditorBrowserUi(options = {}) {
|
|
|
1933
2844
|
const input = event.target instanceof HTMLInputElement ? event.target : null;
|
|
1934
2845
|
if (!input?.dataset.serializedPath || !input.dataset.serializedTargetId)
|
|
1935
2846
|
return;
|
|
1936
|
-
if (
|
|
1937
|
-
if (currentState)
|
|
1938
|
-
render(currentState);
|
|
2847
|
+
if (readInspectorCommitMode(input) !== 'blur')
|
|
1939
2848
|
return;
|
|
1940
|
-
|
|
1941
|
-
|
|
1942
|
-
|
|
1943
|
-
|
|
1944
|
-
|
|
1945
|
-
|
|
1946
|
-
|
|
1947
|
-
|
|
2849
|
+
const propertyInput = createInspectorPropertyInput(input, 'input');
|
|
2850
|
+
if (propertyInput)
|
|
2851
|
+
callbacks.onPropertyInput?.(propertyInput);
|
|
2852
|
+
});
|
|
2853
|
+
renderingPanel.addEventListener('input', (event) => {
|
|
2854
|
+
const input = event.target instanceof HTMLInputElement || event.target instanceof HTMLTextAreaElement
|
|
2855
|
+
? event.target
|
|
2856
|
+
: null;
|
|
2857
|
+
if (!input?.dataset.editorRenderingProperty)
|
|
2858
|
+
return;
|
|
2859
|
+
if (input instanceof HTMLInputElement && (input.type === 'checkbox' || input.type === 'color'))
|
|
2860
|
+
return;
|
|
2861
|
+
if (readRenderingCommitMode(input) !== 'live')
|
|
1948
2862
|
return;
|
|
2863
|
+
const propertyInput = createRenderingPropertyInput(input, input instanceof HTMLTextAreaElement ? 'list' : 'input');
|
|
2864
|
+
if (propertyInput)
|
|
2865
|
+
callbacks.onRenderingPropertyChange?.(propertyInput);
|
|
2866
|
+
});
|
|
2867
|
+
renderingPanel.addEventListener('change', (event) => {
|
|
2868
|
+
const input = event.target instanceof HTMLInputElement || event.target instanceof HTMLTextAreaElement
|
|
2869
|
+
? event.target
|
|
2870
|
+
: null;
|
|
2871
|
+
if (!input?.dataset.editorRenderingProperty)
|
|
2872
|
+
return;
|
|
2873
|
+
if ((input instanceof HTMLInputElement && (input.type === 'text' || input.type === 'number')) || input instanceof HTMLTextAreaElement) {
|
|
2874
|
+
if (readRenderingCommitMode(input) !== 'change')
|
|
2875
|
+
return;
|
|
1949
2876
|
}
|
|
1950
|
-
|
|
2877
|
+
const propertyInput = createRenderingPropertyInput(input, readRenderingImmediateSource(input));
|
|
2878
|
+
if (propertyInput)
|
|
2879
|
+
callbacks.onRenderingPropertyChange?.(propertyInput);
|
|
2880
|
+
});
|
|
2881
|
+
renderingPanel.addEventListener('focusout', (event) => {
|
|
2882
|
+
const input = event.target instanceof HTMLInputElement || event.target instanceof HTMLTextAreaElement
|
|
2883
|
+
? event.target
|
|
2884
|
+
: null;
|
|
2885
|
+
if (!input?.dataset.editorRenderingProperty)
|
|
1951
2886
|
return;
|
|
1952
|
-
|
|
2887
|
+
if (readRenderingCommitMode(input) !== 'blur')
|
|
2888
|
+
return;
|
|
2889
|
+
const propertyInput = createRenderingPropertyInput(input, input instanceof HTMLTextAreaElement ? 'list' : 'input');
|
|
1953
2890
|
if (propertyInput)
|
|
1954
|
-
callbacks.
|
|
1955
|
-
|
|
1956
|
-
|
|
2891
|
+
callbacks.onRenderingPropertyChange?.(propertyInput);
|
|
2892
|
+
});
|
|
2893
|
+
renderingPanel.addEventListener('click', (event) => {
|
|
2894
|
+
const target = event.target instanceof Element
|
|
2895
|
+
? event.target.closest('[data-editor-rendering-action]')
|
|
2896
|
+
: null;
|
|
2897
|
+
if (!(target instanceof HTMLButtonElement))
|
|
2898
|
+
return;
|
|
2899
|
+
if (target.disabled)
|
|
2900
|
+
return;
|
|
2901
|
+
const actionId = target.dataset.editorRenderingAction;
|
|
2902
|
+
if (!actionId)
|
|
2903
|
+
return;
|
|
2904
|
+
event.preventDefault();
|
|
2905
|
+
callbacks.onRenderingAction?.({ actionId });
|
|
1957
2906
|
});
|
|
1958
2907
|
const onKeyDown = (event) => {
|
|
1959
2908
|
if (event.defaultPrevented)
|
|
@@ -1961,6 +2910,16 @@ export function createLocalEditorBrowserUi(options = {}) {
|
|
|
1961
2910
|
if (currentState?.mode !== 'editor' || currentState.busy)
|
|
1962
2911
|
return;
|
|
1963
2912
|
const key = event.key.toLowerCase();
|
|
2913
|
+
if (key === 'escape' && assetPreviewOpen) {
|
|
2914
|
+
event.preventDefault();
|
|
2915
|
+
closeAssetPreviewModal();
|
|
2916
|
+
return;
|
|
2917
|
+
}
|
|
2918
|
+
if (key === 'escape' && assetPickerOpen) {
|
|
2919
|
+
event.preventDefault();
|
|
2920
|
+
closeInspectorAssetPicker();
|
|
2921
|
+
return;
|
|
2922
|
+
}
|
|
1964
2923
|
if (key === 'escape' && snapSettingsOpen) {
|
|
1965
2924
|
event.preventDefault();
|
|
1966
2925
|
closeSnapSettingsPopover();
|
|
@@ -2050,12 +3009,14 @@ export function createLocalEditorBrowserUi(options = {}) {
|
|
|
2050
3009
|
`[data-serialized-target-id="${cssEscape(active.dataset.serializedTargetId)}"]`,
|
|
2051
3010
|
].join(''),
|
|
2052
3011
|
value: active.value,
|
|
2053
|
-
preserveValue: hasPendingInspectorNumberValue(active),
|
|
2054
3012
|
};
|
|
2055
3013
|
}
|
|
2056
3014
|
if (active.dataset.editorInspectorSearch != null) {
|
|
2057
3015
|
return { selector: 'input[data-editor-inspector-search]', value: active.value };
|
|
2058
3016
|
}
|
|
3017
|
+
if (active.dataset.editorHierarchySearch != null) {
|
|
3018
|
+
return { selector: 'input[data-editor-hierarchy-search]', value: active.value };
|
|
3019
|
+
}
|
|
2059
3020
|
if (active.dataset.editorAssetFilter != null) {
|
|
2060
3021
|
return { selector: 'input[data-editor-asset-filter]', value: active.value };
|
|
2061
3022
|
}
|
|
@@ -2068,8 +3029,6 @@ export function createLocalEditorBrowserUi(options = {}) {
|
|
|
2068
3029
|
if (!input)
|
|
2069
3030
|
return;
|
|
2070
3031
|
input.focus({ preventScroll: true });
|
|
2071
|
-
if (snapshot.value != null && snapshot.preserveValue)
|
|
2072
|
-
input.value = snapshot.value;
|
|
2073
3032
|
if (snapshot.value != null && input.value === snapshot.value) {
|
|
2074
3033
|
const position = input.value.length;
|
|
2075
3034
|
try {
|
|
@@ -2107,7 +3066,7 @@ export function createLocalEditorBrowserUi(options = {}) {
|
|
|
2107
3066
|
contextMenu.close();
|
|
2108
3067
|
hostChrome.style.display = !inEditor && localTestActionsEnabled ? 'flex' : 'none';
|
|
2109
3068
|
enterEditorButton.disabled = disabled;
|
|
2110
|
-
for (const button of [saveButton, saveAndRunButton, discardRunButton, undoButton, redoButton, sceneHelpButton,
|
|
3069
|
+
for (const button of [saveButton, saveAndRunButton, discardRunButton, undoButton, redoButton, sceneHelpButton, sceneCameraButton, gridToggleButton]) {
|
|
2111
3070
|
button.style.display = 'inline-flex';
|
|
2112
3071
|
button.disabled = disabled;
|
|
2113
3072
|
}
|
|
@@ -2117,11 +3076,17 @@ export function createLocalEditorBrowserUi(options = {}) {
|
|
|
2117
3076
|
closeLocalTestMenu();
|
|
2118
3077
|
if (!inEditor)
|
|
2119
3078
|
helpOpen = false;
|
|
2120
|
-
|
|
3079
|
+
if (!inEditor || disabled) {
|
|
3080
|
+
assetPreviewOpen = false;
|
|
3081
|
+
previewedAsset = null;
|
|
3082
|
+
assetPreviewMode = 'preview';
|
|
3083
|
+
assetPickerOpen = false;
|
|
3084
|
+
assetPickerInput = null;
|
|
3085
|
+
assetPickerProperty = null;
|
|
3086
|
+
}
|
|
3087
|
+
syncInputModalOpen();
|
|
2121
3088
|
const transformTool = state.transformTool ?? null;
|
|
2122
3089
|
workbench.root.style.display = inEditor ? '' : 'none';
|
|
2123
|
-
if (inEditor)
|
|
2124
|
-
workbenchLayoutController.refresh();
|
|
2125
3090
|
sceneToolOverlay.style.display = inEditor ? 'flex' : 'none';
|
|
2126
3091
|
if (!inEditor)
|
|
2127
3092
|
closeSnapSettingsPopover();
|
|
@@ -2131,20 +3096,20 @@ export function createLocalEditorBrowserUi(options = {}) {
|
|
|
2131
3096
|
closeTransformActionPopover();
|
|
2132
3097
|
if (!inEditor)
|
|
2133
3098
|
closeOverlaySettingsPopover();
|
|
2134
|
-
const
|
|
2135
|
-
|
|
2136
|
-
|
|
2137
|
-
const
|
|
3099
|
+
const sceneCameraPreview = state.sceneCameraPreview ?? { enabled: false, available: false };
|
|
3100
|
+
cameraPreviewGroup.style.display = inEditor ? 'flex' : 'none';
|
|
3101
|
+
sceneCameraButton.disabled = disabled || !sceneCameraPreview.available;
|
|
3102
|
+
const sceneCameraTooltip = sceneCameraPreview.available
|
|
2138
3103
|
? '从 Main Camera 查看当前场景'
|
|
2139
3104
|
: '当前场景没有可预览的 Main Camera';
|
|
2140
|
-
setToolbarButtonTooltip(
|
|
2141
|
-
LocalEditorShared.applyButtonActiveState(
|
|
3105
|
+
setToolbarButtonTooltip(sceneCameraButton, sceneCameraTooltip);
|
|
3106
|
+
LocalEditorShared.applyButtonActiveState(sceneCameraButton, sceneCameraPreview.enabled);
|
|
2142
3107
|
const viewportTools = state.viewportTools ?? null;
|
|
2143
|
-
const viewportControlsDisabled = disabled ||
|
|
2144
|
-
const viewportControlsTooltip =
|
|
3108
|
+
const viewportControlsDisabled = disabled || sceneCameraPreview.enabled;
|
|
3109
|
+
const viewportControlsTooltip = sceneCameraPreview.enabled
|
|
2145
3110
|
? '当前为 Main Camera 预览,请关闭后再使用视口工具'
|
|
2146
3111
|
: '';
|
|
2147
|
-
const viewportProjectionTooltip =
|
|
3112
|
+
const viewportProjectionTooltip = sceneCameraPreview.enabled
|
|
2148
3113
|
? '当前为 Main Camera 预览,投影由 Main Camera 决定;关闭预览后可切换编辑视口投影'
|
|
2149
3114
|
: '';
|
|
2150
3115
|
viewportToolsGroup.style.display = inEditor ? 'flex' : 'none';
|
|
@@ -2241,6 +3206,8 @@ export function createLocalEditorBrowserUi(options = {}) {
|
|
|
2241
3206
|
redoButton.disabled = disabled || !state.session?.canRedo;
|
|
2242
3207
|
LocalEditorShared.applyButtonActiveState(sceneHelpButton, inEditor && helpOpen);
|
|
2243
3208
|
shortcutHelpPanel.style.display = inEditor && helpOpen ? '' : 'none';
|
|
3209
|
+
renderAssetPreviewModal();
|
|
3210
|
+
renderAssetPickerModal();
|
|
2244
3211
|
dirtyBadge.style.display = inEditor && state.session?.dirty ? 'inline-flex' : 'none';
|
|
2245
3212
|
const editorStatusText = [
|
|
2246
3213
|
state.status,
|
|
@@ -2287,12 +3254,22 @@ export function createLocalEditorBrowserUi(options = {}) {
|
|
|
2287
3254
|
if (!inEditor)
|
|
2288
3255
|
return;
|
|
2289
3256
|
const hierarchyDescriptor = panelRegistry.getActivePanel('left');
|
|
2290
|
-
const
|
|
3257
|
+
const rightDescriptor = panelRegistry.getActivePanel('right');
|
|
3258
|
+
const activeRightTab = panelRegistry.getRightDockTab();
|
|
2291
3259
|
hierarchyPanel.dataset.editorPanelId = hierarchyDescriptor?.id ?? 'hierarchy';
|
|
2292
|
-
inspectorPanel.dataset.editorPanelId =
|
|
3260
|
+
inspectorPanel.dataset.editorPanelId = activeRightTab === 'inspector' ? rightDescriptor?.id ?? 'inspector' : 'inspector';
|
|
3261
|
+
renderingPanel.dataset.editorPanelId = activeRightTab === 'rendering' ? rightDescriptor?.id ?? 'rendering' : 'rendering';
|
|
3262
|
+
inspectorPanel.style.display = activeRightTab === 'inspector' ? '' : 'none';
|
|
3263
|
+
renderingPanel.style.display = activeRightTab === 'rendering' ? '' : 'none';
|
|
2293
3264
|
hierarchyController.render(state);
|
|
2294
|
-
LocalEditorPanels.renderWorkbenchBottomDockPanel(doc, assetPanel, state, panelRegistry.getBottomDockTab(), panelRegistry.getPanels('bottom'));
|
|
2295
|
-
LocalEditorPanels.
|
|
3265
|
+
LocalEditorPanels.renderWorkbenchBottomDockPanel(doc, assetPanel, state, panelRegistry.getBottomDockTab(), panelRegistry.getPanels('bottom'), assetBrowserTab);
|
|
3266
|
+
LocalEditorPanels.renderWorkbenchRightDockTabs(doc, rightDockTabs, activeRightTab, panelRegistry.getPanels('right'));
|
|
3267
|
+
if (activeRightTab === 'inspector') {
|
|
3268
|
+
LocalEditorPanels.renderInspectorPanel(doc, inspectorPanel, state, inspectorFilter, options.inspector);
|
|
3269
|
+
}
|
|
3270
|
+
else {
|
|
3271
|
+
LocalEditorPanels.renderRenderingPanel(doc, renderingPanel, state);
|
|
3272
|
+
}
|
|
2296
3273
|
restoreEditableFocus(doc, focusSnapshot);
|
|
2297
3274
|
};
|
|
2298
3275
|
return {
|
|
@@ -2328,8 +3305,11 @@ export function createLocalEditorBrowserUi(options = {}) {
|
|
|
2328
3305
|
overlaySettingsPopover.remove();
|
|
2329
3306
|
spatialOverlay.root.remove();
|
|
2330
3307
|
measurementOverlay.root.remove();
|
|
3308
|
+
sceneFrameRateOverlay.root.remove();
|
|
2331
3309
|
boxSelectionOverlay.remove();
|
|
2332
3310
|
shortcutHelpPanel.remove();
|
|
3311
|
+
assetPreviewModal.remove();
|
|
3312
|
+
assetPickerModal.remove();
|
|
2333
3313
|
hierarchyController.dispose();
|
|
2334
3314
|
workbenchLayoutController.dispose();
|
|
2335
3315
|
tooltipController.dispose();
|