@fps-games/editor 0.1.1-beta.9 → 0.1.2-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 +48 -4
- package/dist/local-editor-harness.d.ts.map +1 -1
- package/dist/local-editor-harness.js +428 -88
- package/dist/local-editor-harness.js.map +1 -1
- package/dist/local-editor-scene-render-scheduler.d.ts +26 -0
- package/dist/local-editor-scene-render-scheduler.d.ts.map +1 -0
- package/dist/local-editor-scene-render-scheduler.js +139 -0
- package/dist/local-editor-scene-render-scheduler.js.map +1 -0
- package/dist/local-editor-viewport-render-coordinator.d.ts +17 -0
- package/dist/local-editor-viewport-render-coordinator.d.ts.map +1 -0
- package/dist/local-editor-viewport-render-coordinator.js +102 -0
- package/dist/local-editor-viewport-render-coordinator.js.map +1 -0
- package/node_modules/@fps-games/editor-babylon/dist/editor-sky.d.ts +2 -0
- 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 +9 -3
- 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.map +1 -1
- package/node_modules/@fps-games/editor-babylon/dist/editor-world.js +2 -0
- 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.map +1 -1
- package/node_modules/@fps-games/editor-babylon/dist/infinite-grid.js +167 -27
- package/node_modules/@fps-games/editor-babylon/dist/infinite-grid.js.map +1 -1
- package/node_modules/@fps-games/editor-babylon/dist/main-camera-preview.d.ts +25 -0
- package/node_modules/@fps-games/editor-babylon/dist/main-camera-preview.d.ts.map +1 -0
- package/node_modules/@fps-games/editor-babylon/dist/main-camera-preview.js +162 -0
- package/node_modules/@fps-games/editor-babylon/dist/main-camera-preview.js.map +1 -0
- package/node_modules/@fps-games/editor-babylon/dist/projection.d.ts +28 -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 +169 -25
- package/node_modules/@fps-games/editor-babylon/dist/projection.js.map +1 -1
- package/node_modules/@fps-games/editor-babylon/dist/scene-camera-preview.d.ts +2 -22
- package/node_modules/@fps-games/editor-babylon/dist/scene-camera-preview.d.ts.map +1 -1
- package/node_modules/@fps-games/editor-babylon/dist/scene-camera-preview.js +1 -121
- package/node_modules/@fps-games/editor-babylon/dist/scene-camera-preview.js.map +1 -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 +68 -30
- package/node_modules/@fps-games/editor-babylon/dist/transform-gizmo-controller.js.map +1 -1
- package/node_modules/@fps-games/editor-babylon/dist/types.d.ts +1 -0
- package/node_modules/@fps-games/editor-babylon/dist/types.d.ts.map +1 -1
- package/node_modules/@fps-games/editor-babylon/package.json +4 -4
- package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-hierarchy-actions.d.ts +2 -1
- 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 +139 -34
- 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 +2 -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 +2 -0
- 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-panels.d.ts +1 -0
- 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 +96 -14
- 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-tooltip.js +1 -1
- package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-types.d.ts +56 -2
- 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 +1 -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 +3 -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 +4 -2
- 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 +253 -39
- 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/viewport-tools.d.ts +6 -6
- package/node_modules/@fps-games/editor-core/dist/viewport-tools.js +3 -3
- package/node_modules/@fps-games/editor-core/dist/viewport-tools.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/package.json +1 -1
- package/package.json +6 -6
|
@@ -15,14 +15,33 @@ import { createShortcutHelpPanel as createLocalEditorShortcutHelpPanel } from '.
|
|
|
15
15
|
import { applyLocalEditorTheme, ensureLocalEditorTheme, LOCAL_EDITOR_THEME_CLASS, normalizeLocalEditorThemeName, } from './local-editor-ui-theme';
|
|
16
16
|
import { createDefaultLocalEditorWorkbenchLayout, createLocalEditorWorkbenchLayoutController, createLocalEditorWorkbench, createSceneHeaderToolbar, createWorkbenchPanelContent, } from './local-editor-ui-workbench';
|
|
17
17
|
import { createLocalEditorIcon } from './local-editor-ui-icons';
|
|
18
|
-
export { applyLocalEditorBrowserInspectorControlBinding, createLocalEditorBrowserInspectorControlRegistry, formatLocalEditorBrowserInspectorValue, resolveLocalEditorBrowserInspectorSectionStatus, resolveLocalEditorBrowserInspectorControlRegistration, } from './local-editor-ui-panels';
|
|
18
|
+
export { applyLocalEditorBrowserInspectorControlBinding, createLocalEditorBrowserInspectorControlRegistry, formatLocalEditorBrowserInspectorNumberValue, formatLocalEditorBrowserInspectorValue, resolveLocalEditorBrowserInspectorSectionStatus, resolveLocalEditorBrowserInspectorControlRegistration, } from './local-editor-ui-panels';
|
|
19
19
|
export { DEFAULT_LOCAL_EDITOR_THEME, LOCAL_EDITOR_THEME_CLASS, applyLocalEditorTheme, normalizeLocalEditorThemeName, } from './local-editor-ui-theme';
|
|
20
20
|
export { LOCAL_EDITOR_ICON_NAMES, createLocalEditorIcon, isLocalEditorIconName, resolveLocalEditorIconName, } from './local-editor-ui-icons';
|
|
21
|
-
|
|
21
|
+
const INSPECTOR_INPUT_PENDING = Symbol('inspector-input-pending');
|
|
22
|
+
export function parseLocalEditorBrowserInspectorNumberValue(value, mode = 'final') {
|
|
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
41
|
const control = input.dataset.serializedControl;
|
|
23
42
|
const valueType = input.dataset.serializedValueType;
|
|
24
43
|
if ((control === 'vec2' || control === 'vec3') && input instanceof HTMLInputElement) {
|
|
25
|
-
return readInspectorVectorInputValue(input);
|
|
44
|
+
return readInspectorVectorInputValue(input, mode);
|
|
26
45
|
}
|
|
27
46
|
if (control === 'enum' && input instanceof HTMLSelectElement) {
|
|
28
47
|
const option = input.selectedOptions.item(0);
|
|
@@ -40,8 +59,9 @@ function readInspectorInputValue(input) {
|
|
|
40
59
|
return input.checked;
|
|
41
60
|
if (control === 'boolean' || valueType === 'boolean')
|
|
42
61
|
return input.value === 'true';
|
|
43
|
-
if (control === 'number' || valueType === 'number')
|
|
44
|
-
return
|
|
62
|
+
if (control === 'number' || valueType === 'number') {
|
|
63
|
+
return parseLocalEditorBrowserInspectorNumberValue(input.value, mode) ?? INSPECTOR_INPUT_PENDING;
|
|
64
|
+
}
|
|
45
65
|
if (control === 'color' && input instanceof HTMLInputElement && input.type === 'color') {
|
|
46
66
|
const value = input.value.replace('#', '');
|
|
47
67
|
const numeric = Number.parseInt(value, 16);
|
|
@@ -148,7 +168,7 @@ function setToolbarButtonIcon(doc, button, icon) {
|
|
|
148
168
|
else
|
|
149
169
|
button.prepend(nextIcon);
|
|
150
170
|
}
|
|
151
|
-
function readInspectorVectorInputValue(input) {
|
|
171
|
+
function readInspectorVectorInputValue(input, mode) {
|
|
152
172
|
const wrapper = input.closest('[data-inspector-vector-control]');
|
|
153
173
|
const values = {};
|
|
154
174
|
const fields = wrapper
|
|
@@ -158,22 +178,27 @@ function readInspectorVectorInputValue(input) {
|
|
|
158
178
|
const axis = field.dataset.serializedVectorAxis;
|
|
159
179
|
if (!axis)
|
|
160
180
|
continue;
|
|
161
|
-
const numeric =
|
|
162
|
-
|
|
181
|
+
const numeric = parseLocalEditorBrowserInspectorNumberValue(field.value, mode);
|
|
182
|
+
if (numeric == null)
|
|
183
|
+
return INSPECTOR_INPUT_PENDING;
|
|
184
|
+
values[axis] = numeric;
|
|
163
185
|
}
|
|
164
186
|
return values;
|
|
165
187
|
}
|
|
166
|
-
function createInspectorPropertyInput(input, source) {
|
|
188
|
+
function createInspectorPropertyInput(input, source, numberParseMode = 'final') {
|
|
167
189
|
if (!input.dataset.serializedPath || !input.dataset.serializedTargetId)
|
|
168
190
|
return null;
|
|
169
191
|
const targetIds = input.dataset.serializedTargetIds
|
|
170
192
|
? input.dataset.serializedTargetIds.split(',').filter(Boolean)
|
|
171
193
|
: undefined;
|
|
194
|
+
const value = readInspectorInputValue(input, numberParseMode);
|
|
195
|
+
if (value === INSPECTOR_INPUT_PENDING)
|
|
196
|
+
return null;
|
|
172
197
|
return {
|
|
173
198
|
targetId: input.dataset.serializedTargetId,
|
|
174
199
|
targetIds,
|
|
175
200
|
path: input.dataset.serializedPath,
|
|
176
|
-
value
|
|
201
|
+
value,
|
|
177
202
|
control: input.dataset.serializedControl,
|
|
178
203
|
valueType: input.dataset.serializedValueType,
|
|
179
204
|
commitMode: input.dataset.serializedCommitMode,
|
|
@@ -181,6 +206,13 @@ function createInspectorPropertyInput(input, source) {
|
|
|
181
206
|
source: input.dataset.serializedEditSource ?? source,
|
|
182
207
|
};
|
|
183
208
|
}
|
|
209
|
+
function hasPendingInspectorNumberValue(input, mode = 'live') {
|
|
210
|
+
const control = input.dataset.serializedControl;
|
|
211
|
+
const valueType = input.dataset.serializedValueType;
|
|
212
|
+
if (control !== 'number' && valueType !== 'number' && control !== 'vec2' && control !== 'vec3')
|
|
213
|
+
return false;
|
|
214
|
+
return readInspectorInputValue(input, mode) === INSPECTOR_INPUT_PENDING;
|
|
215
|
+
}
|
|
184
216
|
function readInspectorCommitMode(input) {
|
|
185
217
|
return input.dataset.serializedCommitMode ?? 'live';
|
|
186
218
|
}
|
|
@@ -193,6 +225,96 @@ function readInspectorImmediateSource(input) {
|
|
|
193
225
|
return 'color';
|
|
194
226
|
return 'input';
|
|
195
227
|
}
|
|
228
|
+
function createSceneFrameRateOverlay(doc) {
|
|
229
|
+
const root = doc.createElement('div');
|
|
230
|
+
root.classList.add(LOCAL_EDITOR_THEME_CLASS);
|
|
231
|
+
root.dataset.editorSceneFrameRateOverlay = 'true';
|
|
232
|
+
root.style.cssText = [
|
|
233
|
+
'position:absolute',
|
|
234
|
+
'right:14px',
|
|
235
|
+
'bottom:134px',
|
|
236
|
+
'z-index:3',
|
|
237
|
+
'display:none',
|
|
238
|
+
'align-items:center',
|
|
239
|
+
'gap:6px',
|
|
240
|
+
'width:112px',
|
|
241
|
+
'height:30px',
|
|
242
|
+
'padding:0 8px',
|
|
243
|
+
'box-sizing:border-box',
|
|
244
|
+
'border:1px solid color-mix(in srgb, var(--fps-editor-border) 78%, transparent)',
|
|
245
|
+
'border-radius:6px',
|
|
246
|
+
'background:color-mix(in srgb, var(--fps-editor-chrome) 76%, transparent)',
|
|
247
|
+
'box-shadow:var(--fps-editor-shadow-panel)',
|
|
248
|
+
'font-family:var(--fps-editor-font)',
|
|
249
|
+
'font-size:11px',
|
|
250
|
+
'font-weight:900',
|
|
251
|
+
'letter-spacing:0',
|
|
252
|
+
'color:var(--fps-editor-text-strong)',
|
|
253
|
+
'pointer-events:none',
|
|
254
|
+
'backdrop-filter:blur(5px)',
|
|
255
|
+
'overflow:hidden',
|
|
256
|
+
].join(';');
|
|
257
|
+
const label = doc.createElement('span');
|
|
258
|
+
label.textContent = 'FPS';
|
|
259
|
+
label.style.cssText = [
|
|
260
|
+
'color:var(--fps-editor-muted-strong)',
|
|
261
|
+
'font-size:10px',
|
|
262
|
+
'line-height:1',
|
|
263
|
+
].join(';');
|
|
264
|
+
const fpsValue = doc.createElement('span');
|
|
265
|
+
fpsValue.textContent = '--';
|
|
266
|
+
fpsValue.style.cssText = [
|
|
267
|
+
'width:26px',
|
|
268
|
+
'flex:0 0 26px',
|
|
269
|
+
'font-variant-numeric:tabular-nums',
|
|
270
|
+
'font-size:13px',
|
|
271
|
+
'line-height:1',
|
|
272
|
+
].join(';');
|
|
273
|
+
const modeLabel = doc.createElement('span');
|
|
274
|
+
modeLabel.textContent = 'IDLE';
|
|
275
|
+
modeLabel.style.cssText = [
|
|
276
|
+
'margin-left:auto',
|
|
277
|
+
'padding:2px 4px',
|
|
278
|
+
'flex:0 0 auto',
|
|
279
|
+
'border:1px solid var(--fps-editor-border-soft)',
|
|
280
|
+
'border-radius:4px',
|
|
281
|
+
'background:color-mix(in srgb, var(--fps-editor-field) 72%, transparent)',
|
|
282
|
+
'color:var(--fps-editor-muted)',
|
|
283
|
+
'font-size:9px',
|
|
284
|
+
'line-height:1',
|
|
285
|
+
].join(';');
|
|
286
|
+
root.appendChild(label);
|
|
287
|
+
root.appendChild(fpsValue);
|
|
288
|
+
root.appendChild(modeLabel);
|
|
289
|
+
return { root, fpsValue, modeLabel };
|
|
290
|
+
}
|
|
291
|
+
function renderSceneFrameRateOverlay(overlay, stats) {
|
|
292
|
+
if (!stats) {
|
|
293
|
+
overlay.root.style.display = 'none';
|
|
294
|
+
return;
|
|
295
|
+
}
|
|
296
|
+
overlay.root.style.display = 'flex';
|
|
297
|
+
const fps = Number.isFinite(stats.fps) ? Math.round(stats.fps) : null;
|
|
298
|
+
const displayFps = stats.mode === 'idle' ? 0 : fps;
|
|
299
|
+
overlay.fpsValue.textContent = displayFps == null ? '--' : String(displayFps);
|
|
300
|
+
overlay.modeLabel.textContent = stats.mode === 'continuous' ? 'LIVE' : 'IDLE';
|
|
301
|
+
overlay.modeLabel.style.color = stats.mode === 'continuous'
|
|
302
|
+
? 'var(--fps-editor-success)'
|
|
303
|
+
: 'var(--fps-editor-muted)';
|
|
304
|
+
overlay.fpsValue.style.color = displayFps == null || stats.mode === 'idle'
|
|
305
|
+
? 'var(--fps-editor-muted)'
|
|
306
|
+
: displayFps >= 50
|
|
307
|
+
? 'var(--fps-editor-success)'
|
|
308
|
+
: displayFps >= 30
|
|
309
|
+
? 'var(--fps-editor-warn)'
|
|
310
|
+
: 'var(--fps-editor-danger-strong)';
|
|
311
|
+
overlay.root.title = [
|
|
312
|
+
`Scene FPS: ${displayFps == null ? '--' : displayFps}`,
|
|
313
|
+
`mode: ${stats.mode}`,
|
|
314
|
+
stats.lastFrameMs == null ? '' : `frame: ${stats.lastFrameMs.toFixed(1)}ms`,
|
|
315
|
+
stats.activeReasons.length ? `active: ${stats.activeReasons.join(', ')}` : '',
|
|
316
|
+
].filter(Boolean).join(' | ');
|
|
317
|
+
}
|
|
196
318
|
function createCoordinateAxesOverlay(doc) {
|
|
197
319
|
const root = doc.createElement('div');
|
|
198
320
|
root.dataset.editorCoordinateAxesOverlay = 'true';
|
|
@@ -260,6 +382,30 @@ function createCoordinateAxesOverlay(doc) {
|
|
|
260
382
|
centerDot.setAttribute('r', '3.2');
|
|
261
383
|
centerDot.setAttribute('fill', 'color-mix(in srgb, var(--fps-editor-text) 82%, transparent)');
|
|
262
384
|
labelLayer.appendChild(centerDot);
|
|
385
|
+
const projectionIcon = doc.createElementNS('http://www.w3.org/2000/svg', 'g');
|
|
386
|
+
projectionIcon.setAttribute('transform', 'translate(8 8)');
|
|
387
|
+
projectionIcon.setAttribute('role', 'button');
|
|
388
|
+
projectionIcon.setAttribute('tabindex', '0');
|
|
389
|
+
projectionIcon.dataset.coordinateAxesProjectionToggle = 'true';
|
|
390
|
+
projectionIcon.style.cssText = 'opacity:0.92;pointer-events:auto;cursor:pointer;outline:none';
|
|
391
|
+
const projectionIconFrame = doc.createElementNS('http://www.w3.org/2000/svg', 'rect');
|
|
392
|
+
projectionIconFrame.setAttribute('x', '0');
|
|
393
|
+
projectionIconFrame.setAttribute('y', '0');
|
|
394
|
+
projectionIconFrame.setAttribute('width', '18');
|
|
395
|
+
projectionIconFrame.setAttribute('height', '18');
|
|
396
|
+
projectionIconFrame.setAttribute('rx', '4');
|
|
397
|
+
projectionIconFrame.setAttribute('fill', 'color-mix(in srgb, var(--fps-editor-chrome) 78%, transparent)');
|
|
398
|
+
projectionIconFrame.setAttribute('stroke', 'color-mix(in srgb, var(--fps-editor-border) 82%, transparent)');
|
|
399
|
+
projectionIconFrame.setAttribute('stroke-width', '1');
|
|
400
|
+
const projectionIconShape = doc.createElementNS('http://www.w3.org/2000/svg', 'path');
|
|
401
|
+
projectionIconShape.setAttribute('fill', 'none');
|
|
402
|
+
projectionIconShape.setAttribute('stroke', 'color-mix(in srgb, var(--fps-editor-accent) 82%, #ffffff)');
|
|
403
|
+
projectionIconShape.setAttribute('stroke-width', '1.6');
|
|
404
|
+
projectionIconShape.setAttribute('stroke-linejoin', 'round');
|
|
405
|
+
projectionIconShape.setAttribute('stroke-linecap', 'round');
|
|
406
|
+
projectionIcon.appendChild(projectionIconFrame);
|
|
407
|
+
projectionIcon.appendChild(projectionIconShape);
|
|
408
|
+
svg.appendChild(projectionIcon);
|
|
263
409
|
return {
|
|
264
410
|
root,
|
|
265
411
|
lineLayer,
|
|
@@ -269,6 +415,8 @@ function createCoordinateAxesOverlay(doc) {
|
|
|
269
415
|
axisLabels,
|
|
270
416
|
axisDiscs,
|
|
271
417
|
centerDot,
|
|
418
|
+
projectionIcon,
|
|
419
|
+
projectionIconShape,
|
|
272
420
|
};
|
|
273
421
|
}
|
|
274
422
|
function renderCoordinateAxesOverlay(overlay, state) {
|
|
@@ -277,6 +425,7 @@ function renderCoordinateAxesOverlay(overlay, state) {
|
|
|
277
425
|
return;
|
|
278
426
|
}
|
|
279
427
|
overlay.root.style.display = '';
|
|
428
|
+
renderCoordinateAxesProjectionIcon(overlay, state.projectionMode, state.projectionToggleDisabled === true);
|
|
280
429
|
const center = 56;
|
|
281
430
|
const lineLength = 38;
|
|
282
431
|
const sortedAxes = [...state.axes].sort((left, right) => right.depth - left.depth);
|
|
@@ -305,6 +454,18 @@ function renderCoordinateAxesOverlay(overlay, state) {
|
|
|
305
454
|
}
|
|
306
455
|
overlay.labelLayer.appendChild(overlay.centerDot);
|
|
307
456
|
}
|
|
457
|
+
function renderCoordinateAxesProjectionIcon(overlay, mode, disabled) {
|
|
458
|
+
overlay.projectionIcon.dataset.viewportProjectionMode = mode;
|
|
459
|
+
overlay.projectionIcon.setAttribute('aria-label', disabled
|
|
460
|
+
? `编辑视口投影:${VIEWPORT_PROJECTION_LABELS[mode]},Main Camera 预览中不可切换`
|
|
461
|
+
: `编辑视口投影:${VIEWPORT_PROJECTION_LABELS[mode]},点击切换`);
|
|
462
|
+
overlay.projectionIcon.setAttribute('aria-disabled', disabled ? 'true' : 'false');
|
|
463
|
+
overlay.projectionIcon.style.opacity = disabled ? '0.46' : '0.92';
|
|
464
|
+
overlay.projectionIcon.style.cursor = disabled ? 'not-allowed' : 'pointer';
|
|
465
|
+
overlay.projectionIconShape.setAttribute('d', mode === 'orthographic'
|
|
466
|
+
? 'M5 6.5H12.5V14H5ZM7.5 4H15V11.5'
|
|
467
|
+
: 'M4.5 14H13.5L10.5 4H7.5ZM7 10.5H11');
|
|
468
|
+
}
|
|
308
469
|
function createSpatialOverlay(doc) {
|
|
309
470
|
const root = doc.createElement('div');
|
|
310
471
|
root.classList.add(LOCAL_EDITOR_THEME_CLASS);
|
|
@@ -959,17 +1120,17 @@ export function createLocalEditorBrowserUi(options = {}) {
|
|
|
959
1120
|
sceneUtilityActions.appendChild(gridToggleButton);
|
|
960
1121
|
sceneUtilityActions.appendChild(themeToggleButton);
|
|
961
1122
|
sceneUtilityActions.appendChild(sceneHelpButton);
|
|
962
|
-
const
|
|
963
|
-
|
|
1123
|
+
const mainCameraPreviewGroup = doc.createElement('div');
|
|
1124
|
+
mainCameraPreviewGroup.style.cssText = [
|
|
964
1125
|
'display:flex',
|
|
965
1126
|
'align-items:center',
|
|
966
1127
|
'gap:4px',
|
|
967
1128
|
'padding-left:8px',
|
|
968
1129
|
'border-left:1px solid var(--fps-editor-divider)',
|
|
969
1130
|
].join(';');
|
|
970
|
-
const
|
|
971
|
-
|
|
972
|
-
|
|
1131
|
+
const mainCameraPreviewButton = createToolbarIconButton(doc, '从 Main Camera 查看场景', 'camera');
|
|
1132
|
+
mainCameraPreviewButton.dataset.mainCameraPreviewToggle = 'true';
|
|
1133
|
+
mainCameraPreviewGroup.appendChild(mainCameraPreviewButton);
|
|
973
1134
|
const viewportToolsGroup = doc.createElement('div');
|
|
974
1135
|
viewportToolsGroup.style.cssText = [
|
|
975
1136
|
'display:flex',
|
|
@@ -1062,6 +1223,7 @@ export function createLocalEditorBrowserUi(options = {}) {
|
|
|
1062
1223
|
'pointer-events:auto',
|
|
1063
1224
|
].join(';');
|
|
1064
1225
|
const coordinateAxesOverlay = createCoordinateAxesOverlay(doc);
|
|
1226
|
+
const sceneFrameRateOverlay = createSceneFrameRateOverlay(doc);
|
|
1065
1227
|
const spatialOverlay = createSpatialOverlay(doc);
|
|
1066
1228
|
const measurementOverlay = createMeasurementOverlay(doc);
|
|
1067
1229
|
sceneToolOverlay.appendChild(sceneTitle);
|
|
@@ -1073,12 +1235,13 @@ export function createLocalEditorBrowserUi(options = {}) {
|
|
|
1073
1235
|
sceneToolOverlay.appendChild(actionGroup);
|
|
1074
1236
|
sceneToolOverlay.appendChild(viewportToolsGroup);
|
|
1075
1237
|
sceneToolOverlay.appendChild(sceneUtilityActions);
|
|
1076
|
-
sceneToolOverlay.appendChild(
|
|
1238
|
+
sceneToolOverlay.appendChild(mainCameraPreviewGroup);
|
|
1077
1239
|
sceneToolOverlay.appendChild(editorStatusButton);
|
|
1078
1240
|
sceneToolOverlay.appendChild(toolbarOverflowButton);
|
|
1079
1241
|
workbench.sceneHeader.appendChild(sceneToolOverlay);
|
|
1080
1242
|
workbench.sceneFrame.appendChild(spatialOverlay.root);
|
|
1081
1243
|
workbench.sceneFrame.appendChild(measurementOverlay.root);
|
|
1244
|
+
workbench.sceneFrame.appendChild(sceneFrameRateOverlay.root);
|
|
1082
1245
|
workbench.sceneFrame.appendChild(coordinateAxesOverlay.root);
|
|
1083
1246
|
root.appendChild(localTestMenu);
|
|
1084
1247
|
root.appendChild(toolbarOverflowMenu);
|
|
@@ -1114,6 +1277,7 @@ export function createLocalEditorBrowserUi(options = {}) {
|
|
|
1114
1277
|
applyLocalEditorTheme(workbench.root, activeTheme);
|
|
1115
1278
|
applyLocalEditorTheme(spatialOverlay.root, activeTheme);
|
|
1116
1279
|
applyLocalEditorTheme(measurementOverlay.root, activeTheme);
|
|
1280
|
+
applyLocalEditorTheme(sceneFrameRateOverlay.root, activeTheme);
|
|
1117
1281
|
applyLocalEditorTheme(shortcutHelpPanel, activeTheme);
|
|
1118
1282
|
applyLocalEditorTheme(boxSelectionOverlay, activeTheme);
|
|
1119
1283
|
applyLocalEditorTheme(localTestMenu, activeTheme);
|
|
@@ -1326,14 +1490,14 @@ export function createLocalEditorBrowserUi(options = {}) {
|
|
|
1326
1490
|
actionGroup,
|
|
1327
1491
|
viewportToolsGroup,
|
|
1328
1492
|
sceneUtilityActions,
|
|
1329
|
-
|
|
1493
|
+
mainCameraPreviewGroup,
|
|
1330
1494
|
editorStatusButton,
|
|
1331
1495
|
toolbarOverflowButton,
|
|
1332
1496
|
];
|
|
1333
1497
|
const toolbarOverflowItems = [
|
|
1334
1498
|
{ id: 'editor-status', element: editorStatusButton, kind: 'group' },
|
|
1335
1499
|
{ id: 'scene-utilities', element: sceneUtilityActions, kind: 'group' },
|
|
1336
|
-
{ id: 'camera-preview', element:
|
|
1500
|
+
{ id: 'main-camera-preview', element: mainCameraPreviewGroup, kind: 'group' },
|
|
1337
1501
|
{ id: 'viewport-tools', element: viewportToolsGroup, kind: 'group' },
|
|
1338
1502
|
{ id: 'transform-actions', element: actionGroup, kind: 'group' },
|
|
1339
1503
|
{ id: 'placement', element: placementGroup, kind: 'group' },
|
|
@@ -1538,9 +1702,30 @@ export function createLocalEditorBrowserUi(options = {}) {
|
|
|
1538
1702
|
const visible = currentState?.grid?.visible ?? true;
|
|
1539
1703
|
callbacks.onGridVisibleChange?.(!visible);
|
|
1540
1704
|
});
|
|
1541
|
-
|
|
1542
|
-
const enabled = currentState?.
|
|
1543
|
-
callbacks.
|
|
1705
|
+
mainCameraPreviewButton.addEventListener('click', () => {
|
|
1706
|
+
const enabled = currentState?.mainCameraPreview?.enabled ?? false;
|
|
1707
|
+
callbacks.onMainCameraPreviewToggle?.(!enabled);
|
|
1708
|
+
});
|
|
1709
|
+
const toggleCoordinateAxesProjection = (event) => {
|
|
1710
|
+
event.preventDefault();
|
|
1711
|
+
event.stopPropagation();
|
|
1712
|
+
const axesState = currentState?.coordinateAxes ?? null;
|
|
1713
|
+
if (!axesState || axesState.projectionToggleDisabled || currentState?.busy)
|
|
1714
|
+
return;
|
|
1715
|
+
const currentMode = axesState.projectionMode;
|
|
1716
|
+
const nextMode = currentMode === 'orthographic' ? 'perspective' : 'orthographic';
|
|
1717
|
+
callbacks.onViewportProjectionModeChange?.(nextMode);
|
|
1718
|
+
if (event.type === 'click')
|
|
1719
|
+
coordinateAxesOverlay.projectionIcon.blur();
|
|
1720
|
+
};
|
|
1721
|
+
coordinateAxesOverlay.projectionIcon.addEventListener('pointerdown', (event) => {
|
|
1722
|
+
event.preventDefault();
|
|
1723
|
+
});
|
|
1724
|
+
coordinateAxesOverlay.projectionIcon.addEventListener('click', toggleCoordinateAxesProjection);
|
|
1725
|
+
coordinateAxesOverlay.projectionIcon.addEventListener('keydown', (event) => {
|
|
1726
|
+
if (event.key !== 'Enter' && event.key !== ' ')
|
|
1727
|
+
return;
|
|
1728
|
+
toggleCoordinateAxesProjection(event);
|
|
1544
1729
|
});
|
|
1545
1730
|
viewportToolsGroup.addEventListener('click', (event) => {
|
|
1546
1731
|
const target = event.target instanceof HTMLElement ? event.target : null;
|
|
@@ -1666,10 +1851,10 @@ export function createLocalEditorBrowserUi(options = {}) {
|
|
|
1666
1851
|
return;
|
|
1667
1852
|
const currentOverlay = currentState?.viewportTools?.overlay;
|
|
1668
1853
|
callbacks.onViewportOverlaySettingsChange?.({
|
|
1669
|
-
bounds: currentOverlay?.bounds ??
|
|
1670
|
-
dimensions: currentOverlay?.dimensions ??
|
|
1854
|
+
bounds: currentOverlay?.bounds ?? false,
|
|
1855
|
+
dimensions: currentOverlay?.dimensions ?? false,
|
|
1671
1856
|
edgeLengths: currentOverlay?.edgeLengths ?? false,
|
|
1672
|
-
anchor: currentOverlay?.anchor ??
|
|
1857
|
+
anchor: currentOverlay?.anchor ?? false,
|
|
1673
1858
|
[key]: input.checked,
|
|
1674
1859
|
});
|
|
1675
1860
|
});
|
|
@@ -1696,6 +1881,7 @@ export function createLocalEditorBrowserUi(options = {}) {
|
|
|
1696
1881
|
inputRouter,
|
|
1697
1882
|
contextMenu,
|
|
1698
1883
|
getState: () => currentState,
|
|
1884
|
+
contextActions: options.hierarchy?.contextActions,
|
|
1699
1885
|
requestRender: () => {
|
|
1700
1886
|
if (currentState)
|
|
1701
1887
|
render(currentState);
|
|
@@ -1725,7 +1911,7 @@ export function createLocalEditorBrowserUi(options = {}) {
|
|
|
1725
1911
|
return;
|
|
1726
1912
|
if (readInspectorCommitMode(input) !== 'live')
|
|
1727
1913
|
return;
|
|
1728
|
-
const propertyInput = createInspectorPropertyInput(input, 'input');
|
|
1914
|
+
const propertyInput = createInspectorPropertyInput(input, 'input', 'live');
|
|
1729
1915
|
if (propertyInput)
|
|
1730
1916
|
callbacks.onPropertyInput?.(propertyInput);
|
|
1731
1917
|
});
|
|
@@ -1739,7 +1925,7 @@ export function createLocalEditorBrowserUi(options = {}) {
|
|
|
1739
1925
|
if (readInspectorCommitMode(input) !== 'change')
|
|
1740
1926
|
return;
|
|
1741
1927
|
}
|
|
1742
|
-
const propertyInput = createInspectorPropertyInput(input, readInspectorImmediateSource(input));
|
|
1928
|
+
const propertyInput = createInspectorPropertyInput(input, readInspectorImmediateSource(input), 'final');
|
|
1743
1929
|
if (propertyInput)
|
|
1744
1930
|
callbacks.onPropertyInput?.(propertyInput);
|
|
1745
1931
|
});
|
|
@@ -1747,11 +1933,27 @@ export function createLocalEditorBrowserUi(options = {}) {
|
|
|
1747
1933
|
const input = event.target instanceof HTMLInputElement ? event.target : null;
|
|
1748
1934
|
if (!input?.dataset.serializedPath || !input.dataset.serializedTargetId)
|
|
1749
1935
|
return;
|
|
1750
|
-
if (
|
|
1936
|
+
if (hasPendingInspectorNumberValue(input, 'final')) {
|
|
1937
|
+
if (currentState)
|
|
1938
|
+
render(currentState);
|
|
1751
1939
|
return;
|
|
1752
|
-
|
|
1940
|
+
}
|
|
1941
|
+
const commitMode = readInspectorCommitMode(input);
|
|
1942
|
+
if (commitMode === 'live' && hasPendingInspectorNumberValue(input, 'live')) {
|
|
1943
|
+
const propertyInput = createInspectorPropertyInput(input, 'input', 'final');
|
|
1944
|
+
if (propertyInput)
|
|
1945
|
+
callbacks.onPropertyInput?.(propertyInput);
|
|
1946
|
+
else if (currentState)
|
|
1947
|
+
render(currentState);
|
|
1948
|
+
return;
|
|
1949
|
+
}
|
|
1950
|
+
if (commitMode !== 'blur')
|
|
1951
|
+
return;
|
|
1952
|
+
const propertyInput = createInspectorPropertyInput(input, 'input', 'final');
|
|
1753
1953
|
if (propertyInput)
|
|
1754
1954
|
callbacks.onPropertyInput?.(propertyInput);
|
|
1955
|
+
else if (currentState)
|
|
1956
|
+
render(currentState);
|
|
1755
1957
|
});
|
|
1756
1958
|
const onKeyDown = (event) => {
|
|
1757
1959
|
if (event.defaultPrevented)
|
|
@@ -1848,6 +2050,7 @@ export function createLocalEditorBrowserUi(options = {}) {
|
|
|
1848
2050
|
`[data-serialized-target-id="${cssEscape(active.dataset.serializedTargetId)}"]`,
|
|
1849
2051
|
].join(''),
|
|
1850
2052
|
value: active.value,
|
|
2053
|
+
preserveValue: hasPendingInspectorNumberValue(active),
|
|
1851
2054
|
};
|
|
1852
2055
|
}
|
|
1853
2056
|
if (active.dataset.editorInspectorSearch != null) {
|
|
@@ -1865,6 +2068,8 @@ export function createLocalEditorBrowserUi(options = {}) {
|
|
|
1865
2068
|
if (!input)
|
|
1866
2069
|
return;
|
|
1867
2070
|
input.focus({ preventScroll: true });
|
|
2071
|
+
if (snapshot.value != null && snapshot.preserveValue)
|
|
2072
|
+
input.value = snapshot.value;
|
|
1868
2073
|
if (snapshot.value != null && input.value === snapshot.value) {
|
|
1869
2074
|
const position = input.value.length;
|
|
1870
2075
|
try {
|
|
@@ -1902,7 +2107,7 @@ export function createLocalEditorBrowserUi(options = {}) {
|
|
|
1902
2107
|
contextMenu.close();
|
|
1903
2108
|
hostChrome.style.display = !inEditor && localTestActionsEnabled ? 'flex' : 'none';
|
|
1904
2109
|
enterEditorButton.disabled = disabled;
|
|
1905
|
-
for (const button of [saveButton, saveAndRunButton, discardRunButton, undoButton, redoButton, sceneHelpButton,
|
|
2110
|
+
for (const button of [saveButton, saveAndRunButton, discardRunButton, undoButton, redoButton, sceneHelpButton, mainCameraPreviewButton, gridToggleButton]) {
|
|
1906
2111
|
button.style.display = 'inline-flex';
|
|
1907
2112
|
button.disabled = disabled;
|
|
1908
2113
|
}
|
|
@@ -1915,6 +2120,8 @@ export function createLocalEditorBrowserUi(options = {}) {
|
|
|
1915
2120
|
inputRouter.setModalOpen(inEditor && helpOpen);
|
|
1916
2121
|
const transformTool = state.transformTool ?? null;
|
|
1917
2122
|
workbench.root.style.display = inEditor ? '' : 'none';
|
|
2123
|
+
if (inEditor)
|
|
2124
|
+
workbenchLayoutController.refresh();
|
|
1918
2125
|
sceneToolOverlay.style.display = inEditor ? 'flex' : 'none';
|
|
1919
2126
|
if (!inEditor)
|
|
1920
2127
|
closeSnapSettingsPopover();
|
|
@@ -1924,18 +2131,21 @@ export function createLocalEditorBrowserUi(options = {}) {
|
|
|
1924
2131
|
closeTransformActionPopover();
|
|
1925
2132
|
if (!inEditor)
|
|
1926
2133
|
closeOverlaySettingsPopover();
|
|
1927
|
-
const
|
|
1928
|
-
|
|
1929
|
-
|
|
1930
|
-
const
|
|
2134
|
+
const mainCameraPreview = state.mainCameraPreview ?? { enabled: false, available: false };
|
|
2135
|
+
mainCameraPreviewGroup.style.display = inEditor ? 'flex' : 'none';
|
|
2136
|
+
mainCameraPreviewButton.disabled = disabled || !mainCameraPreview.available;
|
|
2137
|
+
const mainCameraPreviewTooltip = mainCameraPreview.available
|
|
1931
2138
|
? '从 Main Camera 查看当前场景'
|
|
1932
2139
|
: '当前场景没有可预览的 Main Camera';
|
|
1933
|
-
setToolbarButtonTooltip(
|
|
1934
|
-
LocalEditorShared.applyButtonActiveState(
|
|
2140
|
+
setToolbarButtonTooltip(mainCameraPreviewButton, mainCameraPreviewTooltip);
|
|
2141
|
+
LocalEditorShared.applyButtonActiveState(mainCameraPreviewButton, mainCameraPreview.enabled);
|
|
1935
2142
|
const viewportTools = state.viewportTools ?? null;
|
|
1936
|
-
const viewportControlsDisabled = disabled ||
|
|
1937
|
-
const viewportControlsTooltip =
|
|
1938
|
-
? '
|
|
2143
|
+
const viewportControlsDisabled = disabled || mainCameraPreview.enabled;
|
|
2144
|
+
const viewportControlsTooltip = mainCameraPreview.enabled
|
|
2145
|
+
? '当前为 Main Camera 预览,请关闭后再使用视口工具'
|
|
2146
|
+
: '';
|
|
2147
|
+
const viewportProjectionTooltip = mainCameraPreview.enabled
|
|
2148
|
+
? '当前为 Main Camera 预览,投影由 Main Camera 决定;关闭预览后可切换编辑视口投影'
|
|
1939
2149
|
: '';
|
|
1940
2150
|
viewportToolsGroup.style.display = inEditor ? 'flex' : 'none';
|
|
1941
2151
|
for (const [preset, button] of viewportPresetButtons) {
|
|
@@ -1947,7 +2157,7 @@ export function createLocalEditorBrowserUi(options = {}) {
|
|
|
1947
2157
|
const projectionMode = viewportTools?.projectionMode ?? 'perspective';
|
|
1948
2158
|
const nextProjectionMode = projectionMode === 'orthographic' ? 'perspective' : 'orthographic';
|
|
1949
2159
|
setToolbarButtonIcon(doc, projectionToggleButton, projectionMode === 'orthographic' ? 'projection-ortho' : 'projection-perspective');
|
|
1950
|
-
setToolbarButtonTooltip(projectionToggleButton,
|
|
2160
|
+
setToolbarButtonTooltip(projectionToggleButton, viewportProjectionTooltip || `投影模式:${VIEWPORT_PROJECTION_LABELS[projectionMode]}。切换为${VIEWPORT_PROJECTION_LABELS[nextProjectionMode]}`);
|
|
1951
2161
|
LocalEditorShared.applyButtonActiveState(projectionToggleButton, projectionMode === 'orthographic');
|
|
1952
2162
|
overlaySettingsButton.disabled = disabled;
|
|
1953
2163
|
setToolbarButtonTooltip(overlaySettingsButton, '视口信息层设置');
|
|
@@ -2059,6 +2269,7 @@ export function createLocalEditorBrowserUi(options = {}) {
|
|
|
2059
2269
|
? 'var(--fps-editor-border)'
|
|
2060
2270
|
: statusToneColor;
|
|
2061
2271
|
renderCoordinateAxesOverlay(coordinateAxesOverlay, inEditor ? state.coordinateAxes ?? null : null);
|
|
2272
|
+
renderSceneFrameRateOverlay(sceneFrameRateOverlay, inEditor ? state.sceneFrameStats ?? null : null);
|
|
2062
2273
|
renderSpatialOverlay(spatialOverlay, inEditor ? state.viewportSpatialOverlay ?? null : null);
|
|
2063
2274
|
renderMeasurementOverlay(measurementOverlay, inEditor ? state.viewportMeasurement ?? null : null);
|
|
2064
2275
|
const boxSelection = state.boxSelection;
|
|
@@ -2088,6 +2299,9 @@ export function createLocalEditorBrowserUi(options = {}) {
|
|
|
2088
2299
|
update(state) {
|
|
2089
2300
|
render(state);
|
|
2090
2301
|
},
|
|
2302
|
+
updateSceneFrameStats(stats) {
|
|
2303
|
+
renderSceneFrameRateOverlay(sceneFrameRateOverlay, currentState?.mode === 'editor' ? stats : null);
|
|
2304
|
+
},
|
|
2091
2305
|
setTheme(theme) {
|
|
2092
2306
|
setActiveTheme(theme);
|
|
2093
2307
|
},
|