@fps-games/editor 0.1.1-beta.6 → 0.1.1-beta.8
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 +26 -3
- package/dist/local-editor-harness.d.ts.map +1 -1
- package/dist/local-editor-harness.js +534 -36
- package/dist/local-editor-harness.js.map +1 -1
- package/node_modules/@fps-games/editor-babylon/dist/editor-sky.d.ts +33 -0
- package/node_modules/@fps-games/editor-babylon/dist/editor-sky.d.ts.map +1 -0
- package/node_modules/@fps-games/editor-babylon/dist/editor-sky.js +231 -0
- package/node_modules/@fps-games/editor-babylon/dist/editor-sky.js.map +1 -0
- package/node_modules/@fps-games/editor-babylon/dist/editor-world.d.ts +3 -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 +8 -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 +4 -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 +4 -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 +40 -0
- package/node_modules/@fps-games/editor-babylon/dist/infinite-grid.d.ts.map +1 -0
- package/node_modules/@fps-games/editor-babylon/dist/infinite-grid.js +237 -0
- package/node_modules/@fps-games/editor-babylon/dist/infinite-grid.js.map +1 -0
- package/node_modules/@fps-games/editor-babylon/dist/projection-selection-controller.d.ts +1 -1
- package/node_modules/@fps-games/editor-babylon/dist/projection-selection-controller.d.ts.map +1 -1
- package/node_modules/@fps-games/editor-babylon/dist/projection-selection-controller.js +15 -50
- package/node_modules/@fps-games/editor-babylon/dist/projection-selection-controller.js.map +1 -1
- package/node_modules/@fps-games/editor-babylon/dist/projection.d.ts +5 -0
- package/node_modules/@fps-games/editor-babylon/dist/projection.d.ts.map +1 -1
- package/node_modules/@fps-games/editor-babylon/dist/projection.js +61 -8
- 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 +18 -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 +142 -0
- 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 +3 -1
- package/node_modules/@fps-games/editor-babylon/dist/scene-view-input-controller.js.map +1 -1
- package/node_modules/@fps-games/editor-babylon/dist/scene-view-measurement-controller.d.ts +20 -0
- package/node_modules/@fps-games/editor-babylon/dist/scene-view-measurement-controller.d.ts.map +1 -0
- package/node_modules/@fps-games/editor-babylon/dist/scene-view-measurement-controller.js +187 -0
- package/node_modules/@fps-games/editor-babylon/dist/scene-view-measurement-controller.js.map +1 -0
- package/node_modules/@fps-games/editor-babylon/dist/scene-view-spatial-overlay-controller.d.ts +20 -0
- package/node_modules/@fps-games/editor-babylon/dist/scene-view-spatial-overlay-controller.d.ts.map +1 -0
- package/node_modules/@fps-games/editor-babylon/dist/scene-view-spatial-overlay-controller.js +188 -0
- package/node_modules/@fps-games/editor-babylon/dist/scene-view-spatial-overlay-controller.js.map +1 -0
- package/node_modules/@fps-games/editor-babylon/dist/types.d.ts +11 -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-context-menu.js +97 -46
- package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-context-menu.js.map +1 -1
- package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-hierarchy-actions.d.ts +5 -0
- 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 +57 -7
- 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 +61 -5
- 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-tree.js +0 -2
- package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-hierarchy-tree.js.map +1 -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 +6 -2
- 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-icons.d.ts +1 -1
- package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-icons.d.ts.map +1 -1
- package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-icons.js +58 -0
- package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-icons.js.map +1 -1
- package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-panels.d.ts +8 -1
- 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 +358 -53
- 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.d.ts +5 -0
- package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-shared.d.ts.map +1 -1
- package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-shared.js +28 -8
- 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 +4 -1
- 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-theme.d.ts.map +1 -1
- package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-theme.js +14 -0
- package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-theme.js.map +1 -1
- package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-tooltip.d.ts +11 -0
- package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-tooltip.d.ts.map +1 -0
- package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-tooltip.js +148 -0
- package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-tooltip.js.map +1 -0
- package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-types.d.ts +57 -1
- 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.d.ts +5 -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 +1108 -205
- 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/index.d.ts +2 -0
- package/node_modules/@fps-games/editor-core/dist/index.d.ts.map +1 -1
- package/node_modules/@fps-games/editor-core/dist/index.js +2 -0
- package/node_modules/@fps-games/editor-core/dist/index.js.map +1 -1
- package/node_modules/@fps-games/editor-core/dist/inspector.d.ts +6 -1
- package/node_modules/@fps-games/editor-core/dist/inspector.d.ts.map +1 -1
- package/node_modules/@fps-games/editor-core/dist/inspector.js +6 -1
- package/node_modules/@fps-games/editor-core/dist/inspector.js.map +1 -1
- package/node_modules/@fps-games/editor-core/dist/scene-graph.d.ts +7 -0
- package/node_modules/@fps-games/editor-core/dist/scene-graph.d.ts.map +1 -1
- package/node_modules/@fps-games/editor-core/dist/scene-graph.js.map +1 -1
- package/node_modules/@fps-games/editor-core/dist/scene-view-input.d.ts +1 -1
- package/node_modules/@fps-games/editor-core/dist/scene-view-input.d.ts.map +1 -1
- package/node_modules/@fps-games/editor-core/dist/selection-strategy.d.ts +12 -0
- package/node_modules/@fps-games/editor-core/dist/selection-strategy.d.ts.map +1 -0
- package/node_modules/@fps-games/editor-core/dist/selection-strategy.js +52 -0
- package/node_modules/@fps-games/editor-core/dist/selection-strategy.js.map +1 -0
- package/node_modules/@fps-games/editor-core/dist/viewport-tools.d.ts +94 -0
- package/node_modules/@fps-games/editor-core/dist/viewport-tools.d.ts.map +1 -0
- package/node_modules/@fps-games/editor-core/dist/viewport-tools.js +58 -0
- package/node_modules/@fps-games/editor-core/dist/viewport-tools.js.map +1 -0
- 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
|
@@ -1,18 +1,21 @@
|
|
|
1
1
|
import * as LocalEditorPanels from './local-editor-ui-panels';
|
|
2
2
|
import { createLocalEditorHierarchyController } from './local-editor-ui-hierarchy-controller';
|
|
3
3
|
import { createLocalEditorContextMenuController } from './local-editor-ui-context-menu';
|
|
4
|
+
import { createLocalEditorTooltipController } from './local-editor-ui-tooltip';
|
|
4
5
|
import { DEFAULT_EDITOR_TRANSFORM_OPERATION_SETTINGS, DEFAULT_EDITOR_TRANSFORM_TOOL_DESCRIPTORS, } from '@fps-games/editor-core';
|
|
5
|
-
export { createLocalEditorHierarchyBlankMenu, createLocalEditorHierarchyCopyShortcutAction, createLocalEditorHierarchyDeleteShortcutAction, createLocalEditorHierarchyDuplicateShortcutAction, createLocalEditorHierarchyNodeMenu, createLocalEditorHierarchyPasteShortcutAction, } from './local-editor-ui-hierarchy-actions';
|
|
6
|
+
export { createLocalEditorHierarchyBlankMenu, createLocalEditorHierarchyCopyShortcutAction, createLocalEditorHierarchyDeleteShortcutAction, createLocalEditorHierarchyDuplicateShortcutAction, createLocalEditorHierarchyNodeMenu, createLocalEditorHierarchyPasteShortcutAction, createLocalEditorHierarchySelectAllShortcutAction, } from './local-editor-ui-hierarchy-actions';
|
|
6
7
|
export { createLocalEditorHierarchyController, } from './local-editor-ui-hierarchy-controller';
|
|
7
8
|
export { canLocalEditorHierarchyNodeHaveChildren, createLocalEditorHierarchyTreeModel, isLocalEditorHierarchyNodeMovable, } from './local-editor-ui-hierarchy-tree';
|
|
8
9
|
export { createLocalEditorContextMenuController, } from './local-editor-ui-context-menu';
|
|
10
|
+
export { createLocalEditorTooltipController, } from './local-editor-ui-tooltip';
|
|
9
11
|
import { createLocalEditorWorkbenchInputRouter } from './local-editor-ui-input-router';
|
|
10
12
|
import { createLocalEditorPanelRegistry } from './local-editor-ui-panel-registry';
|
|
11
13
|
import * as LocalEditorShared from './local-editor-ui-shared';
|
|
12
14
|
import { createShortcutHelpPanel as createLocalEditorShortcutHelpPanel } from './local-editor-ui-shortcuts';
|
|
13
15
|
import { applyLocalEditorTheme, ensureLocalEditorTheme, LOCAL_EDITOR_THEME_CLASS, normalizeLocalEditorThemeName, } from './local-editor-ui-theme';
|
|
14
16
|
import { createDefaultLocalEditorWorkbenchLayout, createLocalEditorWorkbenchLayoutController, createLocalEditorWorkbench, createSceneHeaderToolbar, createWorkbenchPanelContent, } from './local-editor-ui-workbench';
|
|
15
|
-
|
|
17
|
+
import { createLocalEditorIcon } from './local-editor-ui-icons';
|
|
18
|
+
export { applyLocalEditorBrowserInspectorControlBinding, createLocalEditorBrowserInspectorControlRegistry, formatLocalEditorBrowserInspectorValue, resolveLocalEditorBrowserInspectorSectionStatus, resolveLocalEditorBrowserInspectorControlRegistration, } from './local-editor-ui-panels';
|
|
16
19
|
export { DEFAULT_LOCAL_EDITOR_THEME, LOCAL_EDITOR_THEME_CLASS, applyLocalEditorTheme, normalizeLocalEditorThemeName, } from './local-editor-ui-theme';
|
|
17
20
|
export { LOCAL_EDITOR_ICON_NAMES, createLocalEditorIcon, isLocalEditorIconName, resolveLocalEditorIconName, } from './local-editor-ui-icons';
|
|
18
21
|
function readInspectorInputValue(input) {
|
|
@@ -67,6 +70,84 @@ const PLACEMENT_MODE_ICONS = {
|
|
|
67
70
|
ground: 'place-ground',
|
|
68
71
|
surface: 'place-surface',
|
|
69
72
|
};
|
|
73
|
+
const PLACEMENT_MODE_DESCRIPTIONS = {
|
|
74
|
+
off: '关闭放置模式',
|
|
75
|
+
ground: '放置到 XZ 地面',
|
|
76
|
+
surface: '放置到场景表面',
|
|
77
|
+
};
|
|
78
|
+
const VIEW_PRESET_ICONS = {
|
|
79
|
+
perspective: 'view-perspective',
|
|
80
|
+
top: 'view-top',
|
|
81
|
+
front: 'view-front',
|
|
82
|
+
right: 'view-right',
|
|
83
|
+
};
|
|
84
|
+
const VIEW_PRESET_TOOLTIPS = {
|
|
85
|
+
perspective: '透视视图',
|
|
86
|
+
top: '顶视图 · 自动切换正交',
|
|
87
|
+
front: '前视图 · 自动切换正交',
|
|
88
|
+
right: '右视图 · 自动切换正交',
|
|
89
|
+
};
|
|
90
|
+
const VIEWPORT_OVERLAY_TOGGLE_LABELS = {
|
|
91
|
+
bounds: '包围盒',
|
|
92
|
+
dimensions: '尺寸摘要',
|
|
93
|
+
edgeLengths: '边长标注',
|
|
94
|
+
anchor: '锚点坐标',
|
|
95
|
+
};
|
|
96
|
+
const VIEWPORT_PROJECTION_LABELS = {
|
|
97
|
+
perspective: '透视',
|
|
98
|
+
orthographic: '正交',
|
|
99
|
+
};
|
|
100
|
+
const TRANSFORM_ACTION_GROUPS = [
|
|
101
|
+
{
|
|
102
|
+
title: '对齐到 Active Object',
|
|
103
|
+
hint: '需要至少 2 个选中对象',
|
|
104
|
+
actions: ['align-x', 'align-y', 'align-z', 'align-all'],
|
|
105
|
+
},
|
|
106
|
+
{
|
|
107
|
+
title: '均匀分布',
|
|
108
|
+
hint: '需要至少 3 个选中对象',
|
|
109
|
+
actions: ['distribute-x', 'distribute-y', 'distribute-z'],
|
|
110
|
+
},
|
|
111
|
+
];
|
|
112
|
+
const TRANSFORM_ACTION_LABELS = {
|
|
113
|
+
'align-x': '对齐 X',
|
|
114
|
+
'align-y': '对齐 Y',
|
|
115
|
+
'align-z': '对齐 Z',
|
|
116
|
+
'align-all': '对齐全',
|
|
117
|
+
'distribute-x': '分布 X',
|
|
118
|
+
'distribute-y': '分布 Y',
|
|
119
|
+
'distribute-z': '分布 Z',
|
|
120
|
+
};
|
|
121
|
+
function createTransformToolTooltip(descriptor) {
|
|
122
|
+
const toolLabel = descriptor.shortcut
|
|
123
|
+
? `${descriptor.label}工具 (${descriptor.shortcut})`
|
|
124
|
+
: `${descriptor.label}工具`;
|
|
125
|
+
return `${toolLabel} · ${LocalEditorShared.toTransformMouseHint(descriptor.tool)}`;
|
|
126
|
+
}
|
|
127
|
+
function createToolbarIconButton(doc, label, icon, tooltip = label, toolbarRole = 'command') {
|
|
128
|
+
return LocalEditorShared.createButton(doc, label, {
|
|
129
|
+
icon,
|
|
130
|
+
labelMode: 'icon-only',
|
|
131
|
+
variant: 'toolbar-icon',
|
|
132
|
+
tooltip,
|
|
133
|
+
ariaLabel: tooltip,
|
|
134
|
+
toolbarRole,
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
function setToolbarButtonTooltip(button, tooltip) {
|
|
138
|
+
button.dataset.editorTooltip = tooltip;
|
|
139
|
+
button.setAttribute('aria-label', tooltip);
|
|
140
|
+
}
|
|
141
|
+
function setToolbarButtonIcon(doc, button, icon) {
|
|
142
|
+
const currentIcon = button.querySelector('[data-editor-icon]');
|
|
143
|
+
if (currentIcon?.dataset.editorIcon === icon)
|
|
144
|
+
return;
|
|
145
|
+
const nextIcon = createLocalEditorIcon(doc, icon);
|
|
146
|
+
if (currentIcon)
|
|
147
|
+
currentIcon.replaceWith(nextIcon);
|
|
148
|
+
else
|
|
149
|
+
button.prepend(nextIcon);
|
|
150
|
+
}
|
|
70
151
|
function readInspectorVectorInputValue(input) {
|
|
71
152
|
const wrapper = input.closest('[data-inspector-vector-control]');
|
|
72
153
|
const values = {};
|
|
@@ -112,6 +193,377 @@ function readInspectorImmediateSource(input) {
|
|
|
112
193
|
return 'color';
|
|
113
194
|
return 'input';
|
|
114
195
|
}
|
|
196
|
+
function createCoordinateAxesOverlay(doc) {
|
|
197
|
+
const root = doc.createElement('div');
|
|
198
|
+
root.dataset.editorCoordinateAxesOverlay = 'true';
|
|
199
|
+
root.style.cssText = [
|
|
200
|
+
'position:absolute',
|
|
201
|
+
'right:14px',
|
|
202
|
+
'bottom:14px',
|
|
203
|
+
'width:112px',
|
|
204
|
+
'height:112px',
|
|
205
|
+
'display:none',
|
|
206
|
+
'z-index:2',
|
|
207
|
+
'box-sizing:border-box',
|
|
208
|
+
'border:1px solid color-mix(in srgb, var(--fps-editor-border) 78%, transparent)',
|
|
209
|
+
'border-radius:8px',
|
|
210
|
+
'background:color-mix(in srgb, var(--fps-editor-chrome) 58%, transparent)',
|
|
211
|
+
'box-shadow:var(--fps-editor-shadow-panel)',
|
|
212
|
+
'pointer-events:none',
|
|
213
|
+
'overflow:hidden',
|
|
214
|
+
].join(';');
|
|
215
|
+
const svg = doc.createElementNS('http://www.w3.org/2000/svg', 'svg');
|
|
216
|
+
svg.setAttribute('viewBox', '0 0 112 112');
|
|
217
|
+
svg.setAttribute('aria-hidden', 'true');
|
|
218
|
+
svg.style.cssText = 'display:block;width:100%;height:100%';
|
|
219
|
+
root.appendChild(svg);
|
|
220
|
+
const lineLayer = doc.createElementNS('http://www.w3.org/2000/svg', 'g');
|
|
221
|
+
const labelLayer = doc.createElementNS('http://www.w3.org/2000/svg', 'g');
|
|
222
|
+
svg.appendChild(lineLayer);
|
|
223
|
+
svg.appendChild(labelLayer);
|
|
224
|
+
const axisGroups = new Map();
|
|
225
|
+
const axisLines = new Map();
|
|
226
|
+
const axisLabels = new Map();
|
|
227
|
+
const axisDiscs = new Map();
|
|
228
|
+
for (const axis of ['x', 'y', 'z']) {
|
|
229
|
+
const group = doc.createElementNS('http://www.w3.org/2000/svg', 'g');
|
|
230
|
+
const line = doc.createElementNS('http://www.w3.org/2000/svg', 'line');
|
|
231
|
+
line.setAttribute('stroke-width', '3.2');
|
|
232
|
+
line.setAttribute('stroke-linecap', 'round');
|
|
233
|
+
line.setAttribute('x1', '56');
|
|
234
|
+
line.setAttribute('y1', '56');
|
|
235
|
+
const disc = doc.createElementNS('http://www.w3.org/2000/svg', 'circle');
|
|
236
|
+
disc.setAttribute('r', '7');
|
|
237
|
+
const label = doc.createElementNS('http://www.w3.org/2000/svg', 'text');
|
|
238
|
+
label.setAttribute('text-anchor', 'middle');
|
|
239
|
+
label.setAttribute('dominant-baseline', 'middle');
|
|
240
|
+
label.setAttribute('dy', '0.04em');
|
|
241
|
+
label.style.cssText = [
|
|
242
|
+
'fill:#172033',
|
|
243
|
+
'font-size:10px',
|
|
244
|
+
'font-weight:900',
|
|
245
|
+
'font-family:Arial, Helvetica, sans-serif',
|
|
246
|
+
'letter-spacing:0',
|
|
247
|
+
].join(';');
|
|
248
|
+
group.appendChild(disc);
|
|
249
|
+
group.appendChild(label);
|
|
250
|
+
lineLayer.appendChild(line);
|
|
251
|
+
labelLayer.appendChild(group);
|
|
252
|
+
axisGroups.set(axis, group);
|
|
253
|
+
axisLines.set(axis, line);
|
|
254
|
+
axisLabels.set(axis, label);
|
|
255
|
+
axisDiscs.set(axis, disc);
|
|
256
|
+
}
|
|
257
|
+
const centerDot = doc.createElementNS('http://www.w3.org/2000/svg', 'circle');
|
|
258
|
+
centerDot.setAttribute('cx', '56');
|
|
259
|
+
centerDot.setAttribute('cy', '56');
|
|
260
|
+
centerDot.setAttribute('r', '3.2');
|
|
261
|
+
centerDot.setAttribute('fill', 'color-mix(in srgb, var(--fps-editor-text) 82%, transparent)');
|
|
262
|
+
labelLayer.appendChild(centerDot);
|
|
263
|
+
return {
|
|
264
|
+
root,
|
|
265
|
+
lineLayer,
|
|
266
|
+
labelLayer,
|
|
267
|
+
axisGroups,
|
|
268
|
+
axisLines,
|
|
269
|
+
axisLabels,
|
|
270
|
+
axisDiscs,
|
|
271
|
+
centerDot,
|
|
272
|
+
};
|
|
273
|
+
}
|
|
274
|
+
function renderCoordinateAxesOverlay(overlay, state) {
|
|
275
|
+
if (!state?.axes?.length) {
|
|
276
|
+
overlay.root.style.display = 'none';
|
|
277
|
+
return;
|
|
278
|
+
}
|
|
279
|
+
overlay.root.style.display = '';
|
|
280
|
+
const center = 56;
|
|
281
|
+
const lineLength = 38;
|
|
282
|
+
const sortedAxes = [...state.axes].sort((left, right) => right.depth - left.depth);
|
|
283
|
+
for (const axis of sortedAxes) {
|
|
284
|
+
const group = overlay.axisGroups.get(axis.id);
|
|
285
|
+
const line = overlay.axisLines.get(axis.id);
|
|
286
|
+
const disc = overlay.axisDiscs.get(axis.id);
|
|
287
|
+
const label = overlay.axisLabels.get(axis.id);
|
|
288
|
+
if (!group || !line || !disc || !label)
|
|
289
|
+
continue;
|
|
290
|
+
const length = lineLength * axis.scale;
|
|
291
|
+
const endX = center + axis.x * length;
|
|
292
|
+
const endY = center + axis.y * length;
|
|
293
|
+
line.setAttribute('x2', formatSvgNumber(endX));
|
|
294
|
+
line.setAttribute('y2', formatSvgNumber(endY));
|
|
295
|
+
line.setAttribute('stroke', axis.color);
|
|
296
|
+
disc.setAttribute('cx', formatSvgNumber(endX));
|
|
297
|
+
disc.setAttribute('cy', formatSvgNumber(endY));
|
|
298
|
+
disc.setAttribute('fill', axis.color);
|
|
299
|
+
label.setAttribute('x', formatSvgNumber(endX));
|
|
300
|
+
label.setAttribute('y', formatSvgNumber(endY));
|
|
301
|
+
label.textContent = axis.label;
|
|
302
|
+
group.style.opacity = String(0.72 + axis.scale * 0.28);
|
|
303
|
+
overlay.lineLayer.appendChild(line);
|
|
304
|
+
overlay.labelLayer.appendChild(group);
|
|
305
|
+
}
|
|
306
|
+
overlay.labelLayer.appendChild(overlay.centerDot);
|
|
307
|
+
}
|
|
308
|
+
function createSpatialOverlay(doc) {
|
|
309
|
+
const root = doc.createElement('div');
|
|
310
|
+
root.classList.add(LOCAL_EDITOR_THEME_CLASS);
|
|
311
|
+
root.dataset.editorSpatialOverlay = 'true';
|
|
312
|
+
root.style.cssText = [
|
|
313
|
+
'position:absolute',
|
|
314
|
+
'top:38px',
|
|
315
|
+
'left:0',
|
|
316
|
+
'right:0',
|
|
317
|
+
'bottom:0',
|
|
318
|
+
'z-index:1',
|
|
319
|
+
'display:none',
|
|
320
|
+
'pointer-events:none',
|
|
321
|
+
'font-family:var(--fps-editor-font)',
|
|
322
|
+
'color:var(--fps-editor-text)',
|
|
323
|
+
'overflow:hidden',
|
|
324
|
+
].join(';');
|
|
325
|
+
const svg = doc.createElementNS('http://www.w3.org/2000/svg', 'svg');
|
|
326
|
+
svg.setAttribute('aria-hidden', 'true');
|
|
327
|
+
svg.style.cssText = [
|
|
328
|
+
'position:absolute',
|
|
329
|
+
'inset:0',
|
|
330
|
+
'width:100%',
|
|
331
|
+
'height:100%',
|
|
332
|
+
'overflow:hidden',
|
|
333
|
+
].join(';');
|
|
334
|
+
const lineLayer = doc.createElementNS('http://www.w3.org/2000/svg', 'g');
|
|
335
|
+
const markerLayer = doc.createElementNS('http://www.w3.org/2000/svg', 'g');
|
|
336
|
+
svg.appendChild(lineLayer);
|
|
337
|
+
svg.appendChild(markerLayer);
|
|
338
|
+
root.appendChild(svg);
|
|
339
|
+
const labelLayer = doc.createElement('div');
|
|
340
|
+
labelLayer.style.cssText = 'position:absolute;inset:0;pointer-events:none';
|
|
341
|
+
root.appendChild(labelLayer);
|
|
342
|
+
return {
|
|
343
|
+
root,
|
|
344
|
+
svg,
|
|
345
|
+
lineLayer,
|
|
346
|
+
markerLayer,
|
|
347
|
+
labelLayer,
|
|
348
|
+
lines: new Map(),
|
|
349
|
+
markers: new Map(),
|
|
350
|
+
labels: new Map(),
|
|
351
|
+
};
|
|
352
|
+
}
|
|
353
|
+
function renderSpatialOverlay(overlay, state) {
|
|
354
|
+
if (!state?.active) {
|
|
355
|
+
overlay.root.style.display = 'none';
|
|
356
|
+
clearSpatialOverlayElements(overlay);
|
|
357
|
+
return;
|
|
358
|
+
}
|
|
359
|
+
overlay.root.style.display = '';
|
|
360
|
+
const rect = overlay.root.getBoundingClientRect();
|
|
361
|
+
overlay.svg.setAttribute('width', formatSvgNumber(rect.width));
|
|
362
|
+
overlay.svg.setAttribute('height', formatSvgNumber(rect.height));
|
|
363
|
+
overlay.svg.setAttribute('viewBox', `0 0 ${formatSvgNumber(rect.width)} ${formatSvgNumber(rect.height)}`);
|
|
364
|
+
const visibleLineIds = new Set();
|
|
365
|
+
for (const lineState of state.lines) {
|
|
366
|
+
const line = ensureSpatialOverlayLine(overlay, lineState.id);
|
|
367
|
+
line.setAttribute('x1', formatSvgNumber(lineState.start.x - rect.left));
|
|
368
|
+
line.setAttribute('y1', formatSvgNumber(lineState.start.y - rect.top));
|
|
369
|
+
line.setAttribute('x2', formatSvgNumber(lineState.end.x - rect.left));
|
|
370
|
+
line.setAttribute('y2', formatSvgNumber(lineState.end.y - rect.top));
|
|
371
|
+
visibleLineIds.add(lineState.id);
|
|
372
|
+
}
|
|
373
|
+
for (const [id, line] of overlay.lines) {
|
|
374
|
+
if (!visibleLineIds.has(id)) {
|
|
375
|
+
line.remove();
|
|
376
|
+
overlay.lines.delete(id);
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
const visibleMarkerIds = new Set();
|
|
380
|
+
for (const markerState of state.markers) {
|
|
381
|
+
const marker = ensureSpatialOverlayMarker(overlay, markerState.id);
|
|
382
|
+
marker.setAttribute('cx', formatSvgNumber(markerState.position.x - rect.left));
|
|
383
|
+
marker.setAttribute('cy', formatSvgNumber(markerState.position.y - rect.top));
|
|
384
|
+
visibleMarkerIds.add(markerState.id);
|
|
385
|
+
}
|
|
386
|
+
for (const [id, marker] of overlay.markers) {
|
|
387
|
+
if (!visibleMarkerIds.has(id)) {
|
|
388
|
+
marker.remove();
|
|
389
|
+
overlay.markers.delete(id);
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
const visibleLabelIds = new Set();
|
|
393
|
+
for (const labelState of state.labels) {
|
|
394
|
+
const label = ensureSpatialOverlayLabel(overlay, labelState.id, labelState.kind);
|
|
395
|
+
label.textContent = labelState.text;
|
|
396
|
+
label.style.left = `${labelState.x - rect.left}px`;
|
|
397
|
+
label.style.top = `${labelState.y - rect.top}px`;
|
|
398
|
+
visibleLabelIds.add(labelState.id);
|
|
399
|
+
}
|
|
400
|
+
for (const [id, label] of overlay.labels) {
|
|
401
|
+
if (!visibleLabelIds.has(id)) {
|
|
402
|
+
label.remove();
|
|
403
|
+
overlay.labels.delete(id);
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
function clearSpatialOverlayElements(overlay) {
|
|
408
|
+
for (const line of overlay.lines.values())
|
|
409
|
+
line.remove();
|
|
410
|
+
for (const marker of overlay.markers.values())
|
|
411
|
+
marker.remove();
|
|
412
|
+
for (const label of overlay.labels.values())
|
|
413
|
+
label.remove();
|
|
414
|
+
overlay.lines.clear();
|
|
415
|
+
overlay.markers.clear();
|
|
416
|
+
overlay.labels.clear();
|
|
417
|
+
}
|
|
418
|
+
function ensureSpatialOverlayLine(overlay, id) {
|
|
419
|
+
const existing = overlay.lines.get(id);
|
|
420
|
+
if (existing)
|
|
421
|
+
return existing;
|
|
422
|
+
const line = overlay.root.ownerDocument.createElementNS('http://www.w3.org/2000/svg', 'line');
|
|
423
|
+
line.setAttribute('stroke', 'color-mix(in srgb, var(--fps-editor-drop-target) 82%, white)');
|
|
424
|
+
line.setAttribute('stroke-width', '1.4');
|
|
425
|
+
line.setAttribute('stroke-linecap', 'round');
|
|
426
|
+
line.setAttribute('vector-effect', 'non-scaling-stroke');
|
|
427
|
+
line.style.filter = 'drop-shadow(0 1px 2px rgba(0,0,0,0.45))';
|
|
428
|
+
overlay.lineLayer.appendChild(line);
|
|
429
|
+
overlay.lines.set(id, line);
|
|
430
|
+
return line;
|
|
431
|
+
}
|
|
432
|
+
function ensureSpatialOverlayMarker(overlay, id) {
|
|
433
|
+
const existing = overlay.markers.get(id);
|
|
434
|
+
if (existing)
|
|
435
|
+
return existing;
|
|
436
|
+
const marker = overlay.root.ownerDocument.createElementNS('http://www.w3.org/2000/svg', 'circle');
|
|
437
|
+
marker.setAttribute('r', '4.5');
|
|
438
|
+
marker.setAttribute('fill', 'var(--fps-editor-drop-target)');
|
|
439
|
+
marker.setAttribute('stroke', 'var(--fps-editor-panel)');
|
|
440
|
+
marker.setAttribute('stroke-width', '2');
|
|
441
|
+
marker.style.filter = 'drop-shadow(0 1px 2px rgba(0,0,0,0.5))';
|
|
442
|
+
overlay.markerLayer.appendChild(marker);
|
|
443
|
+
overlay.markers.set(id, marker);
|
|
444
|
+
return marker;
|
|
445
|
+
}
|
|
446
|
+
function ensureSpatialOverlayLabel(overlay, id, kind) {
|
|
447
|
+
const existing = overlay.labels.get(id);
|
|
448
|
+
if (existing)
|
|
449
|
+
return existing;
|
|
450
|
+
const label = overlay.root.ownerDocument.createElement('div');
|
|
451
|
+
label.dataset.editorSpatialOverlayLabel = kind;
|
|
452
|
+
label.style.cssText = [
|
|
453
|
+
'position:absolute',
|
|
454
|
+
'transform:translate(-50%, -50%)',
|
|
455
|
+
'max-width:220px',
|
|
456
|
+
'padding:2px 5px',
|
|
457
|
+
'border:1px solid color-mix(in srgb, var(--fps-editor-border) 82%, transparent)',
|
|
458
|
+
'border-radius:3px',
|
|
459
|
+
'background:color-mix(in srgb, var(--fps-editor-panel) 88%, transparent)',
|
|
460
|
+
'box-shadow:0 1px 4px rgba(0,0,0,0.22)',
|
|
461
|
+
'color:var(--fps-editor-text-strong)',
|
|
462
|
+
'font-size:10px',
|
|
463
|
+
'font-weight:900',
|
|
464
|
+
'line-height:1.25',
|
|
465
|
+
'letter-spacing:0',
|
|
466
|
+
'white-space:nowrap',
|
|
467
|
+
].join(';');
|
|
468
|
+
if (kind === 'anchor')
|
|
469
|
+
label.style.color = 'var(--fps-editor-drop-target)';
|
|
470
|
+
overlay.labelLayer.appendChild(label);
|
|
471
|
+
overlay.labels.set(id, label);
|
|
472
|
+
return label;
|
|
473
|
+
}
|
|
474
|
+
function createMeasurementOverlay(doc) {
|
|
475
|
+
const root = doc.createElement('div');
|
|
476
|
+
root.classList.add(LOCAL_EDITOR_THEME_CLASS);
|
|
477
|
+
root.dataset.editorMeasurementOverlay = 'true';
|
|
478
|
+
root.style.cssText = [
|
|
479
|
+
'position:absolute',
|
|
480
|
+
'top:38px',
|
|
481
|
+
'left:0',
|
|
482
|
+
'right:0',
|
|
483
|
+
'bottom:0',
|
|
484
|
+
'z-index:2',
|
|
485
|
+
'display:none',
|
|
486
|
+
'pointer-events:none',
|
|
487
|
+
'overflow:hidden',
|
|
488
|
+
'font-family:var(--fps-editor-font)',
|
|
489
|
+
].join(';');
|
|
490
|
+
const svg = doc.createElementNS('http://www.w3.org/2000/svg', 'svg');
|
|
491
|
+
svg.setAttribute('aria-hidden', 'true');
|
|
492
|
+
svg.style.cssText = 'position:absolute;inset:0;width:100%;height:100%;overflow:hidden';
|
|
493
|
+
const line = doc.createElementNS('http://www.w3.org/2000/svg', 'line');
|
|
494
|
+
line.setAttribute('stroke', 'var(--fps-editor-drop-target)');
|
|
495
|
+
line.setAttribute('stroke-width', '2');
|
|
496
|
+
line.setAttribute('stroke-linecap', 'round');
|
|
497
|
+
line.setAttribute('vector-effect', 'non-scaling-stroke');
|
|
498
|
+
line.style.filter = 'drop-shadow(0 1px 2px rgba(0,0,0,0.5))';
|
|
499
|
+
const startMarker = createMeasurementMarker(doc);
|
|
500
|
+
const endMarker = createMeasurementMarker(doc);
|
|
501
|
+
svg.appendChild(line);
|
|
502
|
+
svg.appendChild(startMarker);
|
|
503
|
+
svg.appendChild(endMarker);
|
|
504
|
+
root.appendChild(svg);
|
|
505
|
+
const label = doc.createElement('div');
|
|
506
|
+
label.dataset.editorMeasurementLabel = 'true';
|
|
507
|
+
label.style.cssText = [
|
|
508
|
+
'position:absolute',
|
|
509
|
+
'transform:translate(-50%, -50%)',
|
|
510
|
+
'padding:3px 6px',
|
|
511
|
+
'border:1px solid color-mix(in srgb, var(--fps-editor-drop-target) 72%, var(--fps-editor-border))',
|
|
512
|
+
'border-radius:3px',
|
|
513
|
+
'background:color-mix(in srgb, var(--fps-editor-panel) 90%, transparent)',
|
|
514
|
+
'box-shadow:0 1px 4px rgba(0,0,0,0.24)',
|
|
515
|
+
'color:var(--fps-editor-text-strong)',
|
|
516
|
+
'font-size:10px',
|
|
517
|
+
'font-weight:900',
|
|
518
|
+
'line-height:1.25',
|
|
519
|
+
'letter-spacing:0',
|
|
520
|
+
'white-space:nowrap',
|
|
521
|
+
].join(';');
|
|
522
|
+
root.appendChild(label);
|
|
523
|
+
return { root, svg, line, startMarker, endMarker, label };
|
|
524
|
+
}
|
|
525
|
+
function createMeasurementMarker(doc) {
|
|
526
|
+
const marker = doc.createElementNS('http://www.w3.org/2000/svg', 'circle');
|
|
527
|
+
marker.setAttribute('r', '4');
|
|
528
|
+
marker.setAttribute('fill', 'var(--fps-editor-drop-target)');
|
|
529
|
+
marker.setAttribute('stroke', 'var(--fps-editor-panel)');
|
|
530
|
+
marker.setAttribute('stroke-width', '2');
|
|
531
|
+
return marker;
|
|
532
|
+
}
|
|
533
|
+
function renderMeasurementOverlay(overlay, state) {
|
|
534
|
+
const start = state?.screenStart ?? null;
|
|
535
|
+
const end = state?.screenEnd ?? state?.screenPreview ?? null;
|
|
536
|
+
if (!start || !end) {
|
|
537
|
+
overlay.root.style.display = 'none';
|
|
538
|
+
return;
|
|
539
|
+
}
|
|
540
|
+
overlay.root.style.display = '';
|
|
541
|
+
const rect = overlay.root.getBoundingClientRect();
|
|
542
|
+
overlay.svg.setAttribute('width', formatSvgNumber(rect.width));
|
|
543
|
+
overlay.svg.setAttribute('height', formatSvgNumber(rect.height));
|
|
544
|
+
overlay.svg.setAttribute('viewBox', `0 0 ${formatSvgNumber(rect.width)} ${formatSvgNumber(rect.height)}`);
|
|
545
|
+
const startX = start.x - rect.left;
|
|
546
|
+
const startY = start.y - rect.top;
|
|
547
|
+
const endX = end.x - rect.left;
|
|
548
|
+
const endY = end.y - rect.top;
|
|
549
|
+
overlay.line.setAttribute('x1', formatSvgNumber(startX));
|
|
550
|
+
overlay.line.setAttribute('y1', formatSvgNumber(startY));
|
|
551
|
+
overlay.line.setAttribute('x2', formatSvgNumber(endX));
|
|
552
|
+
overlay.line.setAttribute('y2', formatSvgNumber(endY));
|
|
553
|
+
overlay.startMarker.setAttribute('cx', formatSvgNumber(startX));
|
|
554
|
+
overlay.startMarker.setAttribute('cy', formatSvgNumber(startY));
|
|
555
|
+
overlay.endMarker.setAttribute('cx', formatSvgNumber(endX));
|
|
556
|
+
overlay.endMarker.setAttribute('cy', formatSvgNumber(endY));
|
|
557
|
+
overlay.label.textContent = state?.label?.text ?? '';
|
|
558
|
+
overlay.label.style.display = state?.label ? '' : 'none';
|
|
559
|
+
if (state?.label) {
|
|
560
|
+
overlay.label.style.left = `${state.label.x - rect.left}px`;
|
|
561
|
+
overlay.label.style.top = `${state.label.y - rect.top}px`;
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
function formatSvgNumber(value) {
|
|
565
|
+
return Number.isFinite(value) ? value.toFixed(2) : '0';
|
|
566
|
+
}
|
|
115
567
|
export function createLocalEditorBrowserUi(options = {}) {
|
|
116
568
|
const doc = options.document ?? document;
|
|
117
569
|
const root = options.root ?? doc.body;
|
|
@@ -123,6 +575,10 @@ export function createLocalEditorBrowserUi(options = {}) {
|
|
|
123
575
|
const contextMenu = createLocalEditorContextMenuController(doc, (open) => {
|
|
124
576
|
inputRouter.setContextMenuOpen(open);
|
|
125
577
|
}, activeTheme);
|
|
578
|
+
const tooltipSurfaces = new Set();
|
|
579
|
+
const tooltipController = createLocalEditorTooltipController(doc, root, activeTheme, {
|
|
580
|
+
scope: () => tooltipSurfaces,
|
|
581
|
+
});
|
|
126
582
|
let currentState = null;
|
|
127
583
|
let inspectorFilter = '';
|
|
128
584
|
const workbenchLayout = createDefaultLocalEditorWorkbenchLayout();
|
|
@@ -161,26 +617,29 @@ export function createLocalEditorBrowserUi(options = {}) {
|
|
|
161
617
|
hostChrome.appendChild(hostModeLabel);
|
|
162
618
|
hostChrome.appendChild(enterEditorButton);
|
|
163
619
|
root.appendChild(hostChrome);
|
|
620
|
+
tooltipSurfaces.add(hostChrome);
|
|
164
621
|
const dirtyBadge = doc.createElement('span');
|
|
622
|
+
dirtyBadge.dataset.editorDirtyBadge = 'true';
|
|
623
|
+
dirtyBadge.dataset.editorTooltip = '有未保存更改';
|
|
624
|
+
dirtyBadge.setAttribute('role', 'status');
|
|
625
|
+
dirtyBadge.setAttribute('aria-label', '有未保存更改');
|
|
165
626
|
dirtyBadge.style.cssText = [
|
|
166
627
|
'display:none',
|
|
167
|
-
'
|
|
628
|
+
'width:8px',
|
|
629
|
+
'height:8px',
|
|
168
630
|
'align-items:center',
|
|
169
|
-
'
|
|
631
|
+
'justify-content:center',
|
|
632
|
+
'padding:0',
|
|
170
633
|
'border-radius:999px',
|
|
171
|
-
'background:var(--fps-editor-danger-
|
|
634
|
+
'background:var(--fps-editor-danger-strong)',
|
|
172
635
|
'border:1px solid var(--fps-editor-danger-border)',
|
|
173
|
-
'
|
|
174
|
-
'font-size:11px',
|
|
175
|
-
'font-weight:800',
|
|
636
|
+
'box-shadow:0 0 0 3px var(--fps-editor-danger-soft)',
|
|
176
637
|
].join(';');
|
|
177
|
-
dirtyBadge.textContent = '未保存';
|
|
178
638
|
const saveButton = LocalEditorShared.createButton(doc, '保存场景', { icon: 'save' });
|
|
179
639
|
const saveAndRunButton = LocalEditorShared.createButton(doc, '保存并运行', { icon: 'execute' });
|
|
180
640
|
const discardRunButton = LocalEditorShared.createButton(doc, '放弃并运行', { icon: 'discard' });
|
|
181
|
-
const localTestButton =
|
|
641
|
+
const localTestButton = createToolbarIconButton(doc, '本地测试', 'execute', '打开本地测试操作');
|
|
182
642
|
localTestButton.dataset.editorLocalTestToggle = 'true';
|
|
183
|
-
localTestButton.title = '打开本地测试操作';
|
|
184
643
|
const localTestMenu = doc.createElement('div');
|
|
185
644
|
localTestMenu.classList.add(LOCAL_EDITOR_THEME_CLASS);
|
|
186
645
|
localTestMenu.dataset.editorLocalTestMenu = 'true';
|
|
@@ -210,20 +669,12 @@ export function createLocalEditorBrowserUi(options = {}) {
|
|
|
210
669
|
'gap:4px',
|
|
211
670
|
].join(';');
|
|
212
671
|
localTestGroup.appendChild(localTestButton);
|
|
213
|
-
const undoButton =
|
|
214
|
-
const redoButton =
|
|
672
|
+
const undoButton = createToolbarIconButton(doc, '撤销', 'undo', '撤销');
|
|
673
|
+
const redoButton = createToolbarIconButton(doc, '重做', 'redo', '重做');
|
|
215
674
|
let helpOpen = false;
|
|
216
|
-
const
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
'min-width:120px',
|
|
220
|
-
'max-width:320px',
|
|
221
|
-
'overflow:hidden',
|
|
222
|
-
'text-overflow:ellipsis',
|
|
223
|
-
'white-space:nowrap',
|
|
224
|
-
'color:var(--fps-editor-muted)',
|
|
225
|
-
'font-size:11px',
|
|
226
|
-
].join(';');
|
|
675
|
+
const editorStatusButton = createToolbarIconButton(doc, '编辑器状态', 'status', '编辑器状态');
|
|
676
|
+
editorStatusButton.dataset.editorStatusButton = 'true';
|
|
677
|
+
editorStatusButton.style.cursor = 'help';
|
|
227
678
|
const workbench = createLocalEditorWorkbench(doc);
|
|
228
679
|
const hierarchyPanel = createWorkbenchPanelContent(doc);
|
|
229
680
|
const assetPanel = workbench.bottomDock;
|
|
@@ -231,6 +682,7 @@ export function createLocalEditorBrowserUi(options = {}) {
|
|
|
231
682
|
workbench.leftDock.appendChild(hierarchyPanel);
|
|
232
683
|
workbench.rightDock.appendChild(inspectorPanel);
|
|
233
684
|
root.appendChild(workbench.root);
|
|
685
|
+
tooltipSurfaces.add(workbench.root);
|
|
234
686
|
const workbenchLayoutController = createLocalEditorWorkbenchLayoutController(doc, workbench);
|
|
235
687
|
const sceneToolOverlay = createSceneHeaderToolbar(doc);
|
|
236
688
|
const sceneTitle = doc.createElement('div');
|
|
@@ -268,14 +720,15 @@ export function createLocalEditorBrowserUi(options = {}) {
|
|
|
268
720
|
const toolButtons = new Map();
|
|
269
721
|
const transformToolDescriptors = Object.values(DEFAULT_EDITOR_TRANSFORM_TOOL_DESCRIPTORS);
|
|
270
722
|
for (const descriptor of transformToolDescriptors) {
|
|
271
|
-
const
|
|
272
|
-
const button = LocalEditorShared.createButton(doc,
|
|
723
|
+
const tooltip = createTransformToolTooltip(descriptor);
|
|
724
|
+
const button = LocalEditorShared.createButton(doc, descriptor.label, {
|
|
273
725
|
icon: TRANSFORM_TOOL_ICONS[descriptor.tool],
|
|
726
|
+
labelMode: 'icon-only',
|
|
727
|
+
variant: 'toolbar-icon',
|
|
728
|
+
tooltip,
|
|
729
|
+
ariaLabel: tooltip,
|
|
274
730
|
});
|
|
275
731
|
button.dataset.transformTool = descriptor.tool;
|
|
276
|
-
button.title = descriptor.handles.length > 0
|
|
277
|
-
? `${descriptor.label} · ${descriptor.handles.map(handle => handle.label).join(' / ')}`
|
|
278
|
-
: descriptor.label;
|
|
279
732
|
toolButtons.set(descriptor.tool, button);
|
|
280
733
|
toolGroup.appendChild(button);
|
|
281
734
|
}
|
|
@@ -288,21 +741,13 @@ export function createLocalEditorBrowserUi(options = {}) {
|
|
|
288
741
|
'border-left:1px solid var(--fps-editor-divider)',
|
|
289
742
|
].join(';');
|
|
290
743
|
const spaceButtons = {
|
|
291
|
-
world:
|
|
292
|
-
local:
|
|
744
|
+
world: createToolbarIconButton(doc, '世界', TRANSFORM_SPACE_ICONS.world, '世界坐标空间'),
|
|
745
|
+
local: createToolbarIconButton(doc, '本地', TRANSFORM_SPACE_ICONS.local, '本地坐标空间'),
|
|
293
746
|
};
|
|
294
747
|
for (const [space, button] of Object.entries(spaceButtons)) {
|
|
295
748
|
button.dataset.transformSpace = space;
|
|
296
749
|
spaceGroup.appendChild(button);
|
|
297
750
|
}
|
|
298
|
-
const handleGroup = doc.createElement('div');
|
|
299
|
-
handleGroup.style.cssText = [
|
|
300
|
-
'display:flex',
|
|
301
|
-
'align-items:center',
|
|
302
|
-
'gap:4px',
|
|
303
|
-
'padding-left:8px',
|
|
304
|
-
'border-left:1px solid var(--fps-editor-divider)',
|
|
305
|
-
].join(';');
|
|
306
751
|
const snapGroup = doc.createElement('div');
|
|
307
752
|
snapGroup.style.cssText = [
|
|
308
753
|
'display:flex',
|
|
@@ -311,24 +756,71 @@ export function createLocalEditorBrowserUi(options = {}) {
|
|
|
311
756
|
'padding-left:8px',
|
|
312
757
|
'border-left:1px solid var(--fps-editor-divider)',
|
|
313
758
|
].join(';');
|
|
314
|
-
const snapButton =
|
|
759
|
+
const snapButton = createToolbarIconButton(doc, '吸附设置', 'snap', '吸附设置', 'settings');
|
|
315
760
|
snapButton.dataset.transformSnapToggle = 'true';
|
|
316
|
-
|
|
761
|
+
const snapSettingsPopover = doc.createElement('div');
|
|
762
|
+
snapSettingsPopover.classList.add(LOCAL_EDITOR_THEME_CLASS);
|
|
763
|
+
snapSettingsPopover.dataset.transformSnapSettingsPopover = 'true';
|
|
764
|
+
snapSettingsPopover.setAttribute('role', 'dialog');
|
|
765
|
+
snapSettingsPopover.setAttribute('aria-label', '吸附设置');
|
|
766
|
+
snapSettingsPopover.style.cssText = [
|
|
767
|
+
'position:fixed',
|
|
768
|
+
'z-index:2147483641',
|
|
769
|
+
'display:none',
|
|
770
|
+
'flex-direction:column',
|
|
771
|
+
'gap:8px',
|
|
772
|
+
'width:220px',
|
|
773
|
+
'padding:10px',
|
|
774
|
+
'border:1px solid var(--fps-editor-border)',
|
|
775
|
+
'border-radius:3px',
|
|
776
|
+
'background:var(--fps-editor-panel)',
|
|
777
|
+
'box-shadow:var(--fps-editor-shadow-popover)',
|
|
778
|
+
'pointer-events:auto',
|
|
779
|
+
].join(';');
|
|
780
|
+
const snapSettingsHeader = doc.createElement('label');
|
|
781
|
+
snapSettingsHeader.style.cssText = [
|
|
782
|
+
'display:flex',
|
|
783
|
+
'align-items:center',
|
|
784
|
+
'justify-content:space-between',
|
|
785
|
+
'gap:8px',
|
|
786
|
+
'color:var(--fps-editor-text)',
|
|
787
|
+
'font-size:12px',
|
|
788
|
+
'font-weight:900',
|
|
789
|
+
].join(';');
|
|
790
|
+
const snapSettingsHeaderText = doc.createElement('span');
|
|
791
|
+
snapSettingsHeaderText.textContent = '启用吸附';
|
|
792
|
+
const snapEnabledInput = doc.createElement('input');
|
|
793
|
+
snapEnabledInput.type = 'checkbox';
|
|
794
|
+
snapEnabledInput.dataset.transformSnapEnabled = 'true';
|
|
795
|
+
snapSettingsHeader.appendChild(snapSettingsHeaderText);
|
|
796
|
+
snapSettingsHeader.appendChild(snapEnabledInput);
|
|
797
|
+
snapSettingsPopover.appendChild(snapSettingsHeader);
|
|
317
798
|
const snapStepInputs = new Map();
|
|
318
|
-
for (const [kind, label,
|
|
319
|
-
['move', '
|
|
320
|
-
['rotate', '
|
|
321
|
-
['scale', '
|
|
799
|
+
for (const [kind, label, unit] of [
|
|
800
|
+
['move', '位移', '单位'],
|
|
801
|
+
['rotate', '旋转', '度'],
|
|
802
|
+
['scale', '缩放', '倍数'],
|
|
322
803
|
]) {
|
|
804
|
+
const row = doc.createElement('label');
|
|
805
|
+
row.style.cssText = [
|
|
806
|
+
'display:grid',
|
|
807
|
+
'grid-template-columns:54px 1fr 34px',
|
|
808
|
+
'align-items:center',
|
|
809
|
+
'gap:8px',
|
|
810
|
+
'color:var(--fps-editor-muted-strong)',
|
|
811
|
+
'font-size:11px',
|
|
812
|
+
'font-weight:800',
|
|
813
|
+
].join(';');
|
|
814
|
+
const rowLabel = doc.createElement('span');
|
|
815
|
+
rowLabel.textContent = label;
|
|
323
816
|
const input = doc.createElement('input');
|
|
324
817
|
input.type = 'number';
|
|
325
818
|
input.min = '0.0001';
|
|
326
819
|
input.step = kind === 'rotate' ? '1' : '0.1';
|
|
327
820
|
input.dataset.transformSnapStep = kind;
|
|
328
|
-
input.title = `${label} · ${title}`;
|
|
329
821
|
input.style.cssText = [
|
|
330
|
-
'width:46px',
|
|
331
822
|
'height:26px',
|
|
823
|
+
'min-width:0',
|
|
332
824
|
'box-sizing:border-box',
|
|
333
825
|
'border:1px solid var(--fps-editor-border)',
|
|
334
826
|
'border-radius:3px',
|
|
@@ -338,11 +830,16 @@ export function createLocalEditorBrowserUi(options = {}) {
|
|
|
338
830
|
'font-weight:800',
|
|
339
831
|
'padding:0 4px',
|
|
340
832
|
].join(';');
|
|
833
|
+
const rowUnit = doc.createElement('span');
|
|
834
|
+
rowUnit.textContent = unit;
|
|
835
|
+
rowUnit.style.cssText = 'color:var(--fps-editor-muted);font-size:10px;font-weight:800';
|
|
836
|
+
row.appendChild(rowLabel);
|
|
837
|
+
row.appendChild(input);
|
|
838
|
+
row.appendChild(rowUnit);
|
|
839
|
+
snapSettingsPopover.appendChild(row);
|
|
341
840
|
snapStepInputs.set(kind, input);
|
|
342
841
|
}
|
|
343
842
|
snapGroup.appendChild(snapButton);
|
|
344
|
-
for (const input of snapStepInputs.values())
|
|
345
|
-
snapGroup.appendChild(input);
|
|
346
843
|
const placementGroup = doc.createElement('div');
|
|
347
844
|
placementGroup.style.cssText = [
|
|
348
845
|
'display:flex',
|
|
@@ -351,22 +848,40 @@ export function createLocalEditorBrowserUi(options = {}) {
|
|
|
351
848
|
'padding-left:8px',
|
|
352
849
|
'border-left:1px solid var(--fps-editor-divider)',
|
|
353
850
|
].join(';');
|
|
354
|
-
const
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
851
|
+
const placementButton = createToolbarIconButton(doc, '放置模式', PLACEMENT_MODE_ICONS.off, '放置模式', 'settings');
|
|
852
|
+
placementButton.dataset.placementModeToggle = 'true';
|
|
853
|
+
const placementSettingsPopover = doc.createElement('div');
|
|
854
|
+
placementSettingsPopover.classList.add(LOCAL_EDITOR_THEME_CLASS);
|
|
855
|
+
placementSettingsPopover.dataset.placementSettingsPopover = 'true';
|
|
856
|
+
placementSettingsPopover.setAttribute('role', 'dialog');
|
|
857
|
+
placementSettingsPopover.setAttribute('aria-label', '放置模式');
|
|
858
|
+
placementSettingsPopover.style.cssText = [
|
|
859
|
+
'position:fixed',
|
|
860
|
+
'z-index:2147483641',
|
|
861
|
+
'display:none',
|
|
862
|
+
'flex-direction:column',
|
|
863
|
+
'gap:5px',
|
|
864
|
+
'width:180px',
|
|
865
|
+
'padding:8px',
|
|
866
|
+
'border:1px solid var(--fps-editor-border)',
|
|
867
|
+
'border-radius:3px',
|
|
868
|
+
'background:var(--fps-editor-panel)',
|
|
869
|
+
'box-shadow:var(--fps-editor-shadow-popover)',
|
|
870
|
+
'pointer-events:auto',
|
|
871
|
+
].join(';');
|
|
872
|
+
const placementButtons = Object.fromEntries(Object.keys(PLACEMENT_MODE_ICONS).map((mode) => {
|
|
873
|
+
const button = LocalEditorShared.createButton(doc, PLACEMENT_MODE_DESCRIPTIONS[mode], {
|
|
874
|
+
icon: PLACEMENT_MODE_ICONS[mode],
|
|
875
|
+
tooltip: PLACEMENT_MODE_DESCRIPTIONS[mode],
|
|
876
|
+
ariaLabel: PLACEMENT_MODE_DESCRIPTIONS[mode],
|
|
877
|
+
});
|
|
367
878
|
button.dataset.placementMode = mode;
|
|
368
|
-
|
|
369
|
-
|
|
879
|
+
button.style.justifyContent = 'flex-start';
|
|
880
|
+
button.style.width = '100%';
|
|
881
|
+
placementSettingsPopover.appendChild(button);
|
|
882
|
+
return [mode, button];
|
|
883
|
+
}));
|
|
884
|
+
placementGroup.appendChild(placementButton);
|
|
370
885
|
const actionGroup = doc.createElement('div');
|
|
371
886
|
actionGroup.style.cssText = [
|
|
372
887
|
'display:flex',
|
|
@@ -375,70 +890,73 @@ export function createLocalEditorBrowserUi(options = {}) {
|
|
|
375
890
|
'padding-left:8px',
|
|
376
891
|
'border-left:1px solid var(--fps-editor-divider)',
|
|
377
892
|
].join(';');
|
|
378
|
-
const
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
893
|
+
const transformActionButton = createToolbarIconButton(doc, '对齐与分布', 'arrange', '对齐与分布', 'settings');
|
|
894
|
+
transformActionButton.dataset.transformActionToggle = 'true';
|
|
895
|
+
const transformActionPopover = doc.createElement('div');
|
|
896
|
+
transformActionPopover.classList.add(LOCAL_EDITOR_THEME_CLASS);
|
|
897
|
+
transformActionPopover.dataset.transformActionPopover = 'true';
|
|
898
|
+
transformActionPopover.setAttribute('role', 'dialog');
|
|
899
|
+
transformActionPopover.setAttribute('aria-label', '对齐与分布');
|
|
900
|
+
transformActionPopover.style.cssText = [
|
|
901
|
+
'position:fixed',
|
|
902
|
+
'z-index:2147483641',
|
|
903
|
+
'display:none',
|
|
904
|
+
'flex-direction:column',
|
|
905
|
+
'gap:9px',
|
|
906
|
+
'width:236px',
|
|
907
|
+
'padding:10px',
|
|
383
908
|
'border:1px solid var(--fps-editor-border)',
|
|
384
909
|
'border-radius:3px',
|
|
385
|
-
'background:var(--fps-editor-
|
|
386
|
-
'
|
|
387
|
-
'
|
|
388
|
-
'font-weight:800',
|
|
389
|
-
'padding:0 4px',
|
|
910
|
+
'background:var(--fps-editor-panel)',
|
|
911
|
+
'box-shadow:var(--fps-editor-shadow-popover)',
|
|
912
|
+
'pointer-events:auto',
|
|
390
913
|
].join(';');
|
|
391
|
-
|
|
392
|
-
for (const
|
|
393
|
-
'
|
|
394
|
-
'
|
|
395
|
-
'
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
914
|
+
const transformActionButtons = new Map();
|
|
915
|
+
for (const group of TRANSFORM_ACTION_GROUPS) {
|
|
916
|
+
const section = doc.createElement('section');
|
|
917
|
+
section.style.cssText = 'display:flex;flex-direction:column;gap:5px';
|
|
918
|
+
const heading = doc.createElement('div');
|
|
919
|
+
heading.textContent = group.title;
|
|
920
|
+
heading.style.cssText = [
|
|
921
|
+
'color:var(--fps-editor-text-strong)',
|
|
922
|
+
'font-size:11px',
|
|
923
|
+
'font-weight:900',
|
|
924
|
+
].join(';');
|
|
925
|
+
const hint = doc.createElement('div');
|
|
926
|
+
hint.textContent = group.hint;
|
|
927
|
+
hint.style.cssText = [
|
|
928
|
+
'color:var(--fps-editor-muted)',
|
|
929
|
+
'font-size:10px',
|
|
930
|
+
'font-weight:700',
|
|
931
|
+
'margin-top:-3px',
|
|
932
|
+
].join(';');
|
|
933
|
+
section.appendChild(heading);
|
|
934
|
+
section.appendChild(hint);
|
|
935
|
+
for (const action of group.actions) {
|
|
936
|
+
const button = LocalEditorShared.createButton(doc, TRANSFORM_ACTION_LABELS[action], {
|
|
937
|
+
icon: 'arrange',
|
|
938
|
+
tooltip: TRANSFORM_ACTION_LABELS[action],
|
|
939
|
+
ariaLabel: TRANSFORM_ACTION_LABELS[action],
|
|
940
|
+
});
|
|
941
|
+
button.dataset.transformAction = action;
|
|
942
|
+
button.style.justifyContent = 'flex-start';
|
|
943
|
+
button.style.width = '100%';
|
|
944
|
+
transformActionButtons.set(action, button);
|
|
945
|
+
section.appendChild(button);
|
|
946
|
+
}
|
|
947
|
+
transformActionPopover.appendChild(section);
|
|
405
948
|
}
|
|
406
|
-
transformActionSelect.value = selectedTransformAction;
|
|
407
|
-
const transformActionButton = LocalEditorShared.createButton(doc, '执行', { icon: 'execute' });
|
|
408
|
-
transformActionButton.dataset.transformActionRun = 'true';
|
|
409
|
-
transformActionButton.title = '对当前多选执行 Transform 操作';
|
|
410
|
-
actionGroup.appendChild(transformActionSelect);
|
|
411
949
|
actionGroup.appendChild(transformActionButton);
|
|
412
|
-
const
|
|
413
|
-
sceneToolStatus.style.cssText = [
|
|
414
|
-
'max-width:220px',
|
|
415
|
-
'overflow:hidden',
|
|
416
|
-
'text-overflow:ellipsis',
|
|
417
|
-
'white-space:nowrap',
|
|
418
|
-
'color:var(--fps-editor-muted-strong)',
|
|
419
|
-
'font-size:11px',
|
|
420
|
-
].join(';');
|
|
421
|
-
const sceneMouseHint = doc.createElement('span');
|
|
422
|
-
sceneMouseHint.style.cssText = [
|
|
423
|
-
'padding-left:8px',
|
|
424
|
-
'border-left:1px solid var(--fps-editor-divider)',
|
|
425
|
-
'max-width:360px',
|
|
426
|
-
'overflow:hidden',
|
|
427
|
-
'text-overflow:ellipsis',
|
|
428
|
-
'white-space:nowrap',
|
|
429
|
-
'color:var(--fps-editor-muted)',
|
|
430
|
-
'font-size:11px',
|
|
431
|
-
].join(';');
|
|
432
|
-
sceneMouseHint.textContent = '左键选择 · 空白拖拽框选 · 中键平移 · Alt+左键环绕 · 右键飞行 · 滚轮缩放';
|
|
433
|
-
const themeToggleButton = LocalEditorShared.createButton(doc, '主题', { icon: 'theme' });
|
|
950
|
+
const themeToggleButton = createToolbarIconButton(doc, '主题', 'theme', '切换主题');
|
|
434
951
|
themeToggleButton.dataset.editorThemeToggle = 'true';
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
sceneHelpButton
|
|
952
|
+
const gridToggleButton = createToolbarIconButton(doc, '网格', 'grid', '显示 / 隐藏 Scene View 网格');
|
|
953
|
+
gridToggleButton.dataset.editorGridToggle = 'true';
|
|
954
|
+
const sceneHelpButton = createToolbarIconButton(doc, '快捷键', 'help', '快捷键与操作说明');
|
|
438
955
|
sceneQuickActions.appendChild(localTestGroup);
|
|
439
956
|
sceneQuickActions.appendChild(undoButton);
|
|
440
957
|
sceneQuickActions.appendChild(redoButton);
|
|
441
958
|
sceneQuickActions.appendChild(dirtyBadge);
|
|
959
|
+
sceneUtilityActions.appendChild(gridToggleButton);
|
|
442
960
|
sceneUtilityActions.appendChild(themeToggleButton);
|
|
443
961
|
sceneUtilityActions.appendChild(sceneHelpButton);
|
|
444
962
|
const cameraPreviewGroup = doc.createElement('div');
|
|
@@ -449,13 +967,79 @@ export function createLocalEditorBrowserUi(options = {}) {
|
|
|
449
967
|
'padding-left:8px',
|
|
450
968
|
'border-left:1px solid var(--fps-editor-divider)',
|
|
451
969
|
].join(';');
|
|
452
|
-
const sceneCameraButton =
|
|
970
|
+
const sceneCameraButton = createToolbarIconButton(doc, '从 Main Camera 查看场景', 'camera');
|
|
453
971
|
sceneCameraButton.dataset.sceneCameraPreviewToggle = 'true';
|
|
454
|
-
sceneCameraButton.title = '从 Main Camera 查看当前场景';
|
|
455
972
|
cameraPreviewGroup.appendChild(sceneCameraButton);
|
|
456
|
-
const
|
|
973
|
+
const viewportToolsGroup = doc.createElement('div');
|
|
974
|
+
viewportToolsGroup.style.cssText = [
|
|
975
|
+
'display:flex',
|
|
976
|
+
'align-items:center',
|
|
977
|
+
'gap:4px',
|
|
978
|
+
'padding-left:8px',
|
|
979
|
+
'border-left:1px solid var(--fps-editor-divider)',
|
|
980
|
+
].join(';');
|
|
981
|
+
const viewportPresetButtons = new Map();
|
|
982
|
+
for (const preset of ['perspective', 'top', 'front', 'right']) {
|
|
983
|
+
const button = createToolbarIconButton(doc, VIEW_PRESET_TOOLTIPS[preset], VIEW_PRESET_ICONS[preset], VIEW_PRESET_TOOLTIPS[preset]);
|
|
984
|
+
button.dataset.viewportViewPreset = preset;
|
|
985
|
+
viewportPresetButtons.set(preset, button);
|
|
986
|
+
viewportToolsGroup.appendChild(button);
|
|
987
|
+
}
|
|
988
|
+
const projectionToggleButton = createToolbarIconButton(doc, '投影模式', 'projection-perspective', '切换投影模式');
|
|
989
|
+
projectionToggleButton.dataset.viewportProjectionToggle = 'true';
|
|
990
|
+
viewportToolsGroup.appendChild(projectionToggleButton);
|
|
991
|
+
const overlaySettingsButton = createToolbarIconButton(doc, '视口信息层设置', 'view-overlay', '视口信息层设置', 'settings');
|
|
992
|
+
overlaySettingsButton.dataset.viewportOverlaySettingsToggle = 'true';
|
|
993
|
+
viewportToolsGroup.appendChild(overlaySettingsButton);
|
|
994
|
+
const measureButton = createToolbarIconButton(doc, '测量距离', 'measure', '测量 XZ 地面距离', 'toggle');
|
|
995
|
+
measureButton.dataset.viewportMeasureToggle = 'true';
|
|
996
|
+
viewportToolsGroup.appendChild(measureButton);
|
|
997
|
+
const clearMeasurementButton = createToolbarIconButton(doc, '清除测量结果', 'discard', '清除测量结果');
|
|
998
|
+
clearMeasurementButton.dataset.viewportMeasurementClear = 'true';
|
|
999
|
+
viewportToolsGroup.appendChild(clearMeasurementButton);
|
|
1000
|
+
const overlaySettingsPopover = doc.createElement('div');
|
|
1001
|
+
overlaySettingsPopover.classList.add(LOCAL_EDITOR_THEME_CLASS);
|
|
1002
|
+
overlaySettingsPopover.dataset.viewportOverlaySettingsPopover = 'true';
|
|
1003
|
+
overlaySettingsPopover.setAttribute('role', 'dialog');
|
|
1004
|
+
overlaySettingsPopover.setAttribute('aria-label', '视口信息层设置');
|
|
1005
|
+
overlaySettingsPopover.style.cssText = [
|
|
1006
|
+
'position:fixed',
|
|
1007
|
+
'z-index:2147483641',
|
|
1008
|
+
'display:none',
|
|
1009
|
+
'flex-direction:column',
|
|
1010
|
+
'gap:7px',
|
|
1011
|
+
'width:190px',
|
|
1012
|
+
'padding:8px',
|
|
1013
|
+
'border:1px solid var(--fps-editor-border)',
|
|
1014
|
+
'border-radius:3px',
|
|
1015
|
+
'background:var(--fps-editor-panel)',
|
|
1016
|
+
'box-shadow:var(--fps-editor-shadow-popover)',
|
|
1017
|
+
'pointer-events:auto',
|
|
1018
|
+
].join(';');
|
|
1019
|
+
const overlayInputs = new Map();
|
|
1020
|
+
for (const key of Object.keys(VIEWPORT_OVERLAY_TOGGLE_LABELS)) {
|
|
1021
|
+
const row = doc.createElement('label');
|
|
1022
|
+
row.style.cssText = [
|
|
1023
|
+
'display:flex',
|
|
1024
|
+
'align-items:center',
|
|
1025
|
+
'justify-content:space-between',
|
|
1026
|
+
'gap:10px',
|
|
1027
|
+
'color:var(--fps-editor-text)',
|
|
1028
|
+
'font-size:11px',
|
|
1029
|
+
'font-weight:800',
|
|
1030
|
+
].join(';');
|
|
1031
|
+
const text = doc.createElement('span');
|
|
1032
|
+
text.textContent = VIEWPORT_OVERLAY_TOGGLE_LABELS[key];
|
|
1033
|
+
const input = doc.createElement('input');
|
|
1034
|
+
input.type = 'checkbox';
|
|
1035
|
+
input.dataset.viewportOverlaySetting = key;
|
|
1036
|
+
row.appendChild(text);
|
|
1037
|
+
row.appendChild(input);
|
|
1038
|
+
overlaySettingsPopover.appendChild(row);
|
|
1039
|
+
overlayInputs.set(key, input);
|
|
1040
|
+
}
|
|
1041
|
+
const toolbarOverflowButton = createToolbarIconButton(doc, '更多', 'chevron-down', '显示隐藏的工具栏命令');
|
|
457
1042
|
toolbarOverflowButton.dataset.editorToolbarOverflowToggle = 'true';
|
|
458
|
-
toolbarOverflowButton.title = '显示隐藏的工具栏命令';
|
|
459
1043
|
toolbarOverflowButton.style.display = 'none';
|
|
460
1044
|
const toolbarOverflowMenu = doc.createElement('div');
|
|
461
1045
|
toolbarOverflowMenu.classList.add(LOCAL_EDITOR_THEME_CLASS);
|
|
@@ -477,23 +1061,37 @@ export function createLocalEditorBrowserUi(options = {}) {
|
|
|
477
1061
|
'box-shadow:var(--fps-editor-shadow-popover)',
|
|
478
1062
|
'pointer-events:auto',
|
|
479
1063
|
].join(';');
|
|
1064
|
+
const coordinateAxesOverlay = createCoordinateAxesOverlay(doc);
|
|
1065
|
+
const spatialOverlay = createSpatialOverlay(doc);
|
|
1066
|
+
const measurementOverlay = createMeasurementOverlay(doc);
|
|
480
1067
|
sceneToolOverlay.appendChild(sceneTitle);
|
|
481
1068
|
sceneToolOverlay.appendChild(sceneQuickActions);
|
|
482
|
-
sceneToolOverlay.appendChild(sceneUtilityActions);
|
|
483
|
-
sceneToolOverlay.appendChild(cameraPreviewGroup);
|
|
484
1069
|
sceneToolOverlay.appendChild(toolGroup);
|
|
485
1070
|
sceneToolOverlay.appendChild(spaceGroup);
|
|
486
|
-
sceneToolOverlay.appendChild(handleGroup);
|
|
487
1071
|
sceneToolOverlay.appendChild(snapGroup);
|
|
488
1072
|
sceneToolOverlay.appendChild(placementGroup);
|
|
489
1073
|
sceneToolOverlay.appendChild(actionGroup);
|
|
490
|
-
sceneToolOverlay.appendChild(
|
|
491
|
-
sceneToolOverlay.appendChild(
|
|
492
|
-
sceneToolOverlay.appendChild(
|
|
1074
|
+
sceneToolOverlay.appendChild(viewportToolsGroup);
|
|
1075
|
+
sceneToolOverlay.appendChild(sceneUtilityActions);
|
|
1076
|
+
sceneToolOverlay.appendChild(cameraPreviewGroup);
|
|
1077
|
+
sceneToolOverlay.appendChild(editorStatusButton);
|
|
493
1078
|
sceneToolOverlay.appendChild(toolbarOverflowButton);
|
|
494
1079
|
workbench.sceneHeader.appendChild(sceneToolOverlay);
|
|
1080
|
+
workbench.sceneFrame.appendChild(spatialOverlay.root);
|
|
1081
|
+
workbench.sceneFrame.appendChild(measurementOverlay.root);
|
|
1082
|
+
workbench.sceneFrame.appendChild(coordinateAxesOverlay.root);
|
|
495
1083
|
root.appendChild(localTestMenu);
|
|
496
1084
|
root.appendChild(toolbarOverflowMenu);
|
|
1085
|
+
root.appendChild(snapSettingsPopover);
|
|
1086
|
+
root.appendChild(placementSettingsPopover);
|
|
1087
|
+
root.appendChild(transformActionPopover);
|
|
1088
|
+
root.appendChild(overlaySettingsPopover);
|
|
1089
|
+
tooltipSurfaces.add(localTestMenu);
|
|
1090
|
+
tooltipSurfaces.add(toolbarOverflowMenu);
|
|
1091
|
+
tooltipSurfaces.add(snapSettingsPopover);
|
|
1092
|
+
tooltipSurfaces.add(placementSettingsPopover);
|
|
1093
|
+
tooltipSurfaces.add(transformActionPopover);
|
|
1094
|
+
tooltipSurfaces.add(overlaySettingsPopover);
|
|
497
1095
|
const boxSelectionOverlay = doc.createElement('div');
|
|
498
1096
|
boxSelectionOverlay.classList.add(LOCAL_EDITOR_THEME_CLASS);
|
|
499
1097
|
boxSelectionOverlay.style.cssText = [
|
|
@@ -506,23 +1104,32 @@ export function createLocalEditorBrowserUi(options = {}) {
|
|
|
506
1104
|
'pointer-events:none',
|
|
507
1105
|
].join(';');
|
|
508
1106
|
root.appendChild(boxSelectionOverlay);
|
|
1107
|
+
tooltipSurfaces.add(boxSelectionOverlay);
|
|
509
1108
|
const shortcutHelpPanel = createLocalEditorShortcutHelpPanel(doc);
|
|
510
1109
|
shortcutHelpPanel.classList.add(LOCAL_EDITOR_THEME_CLASS);
|
|
511
1110
|
root.appendChild(shortcutHelpPanel);
|
|
1111
|
+
tooltipSurfaces.add(shortcutHelpPanel);
|
|
512
1112
|
function applyThemeToSurfaces() {
|
|
513
1113
|
applyLocalEditorTheme(hostChrome, activeTheme);
|
|
514
1114
|
applyLocalEditorTheme(workbench.root, activeTheme);
|
|
1115
|
+
applyLocalEditorTheme(spatialOverlay.root, activeTheme);
|
|
1116
|
+
applyLocalEditorTheme(measurementOverlay.root, activeTheme);
|
|
515
1117
|
applyLocalEditorTheme(shortcutHelpPanel, activeTheme);
|
|
516
1118
|
applyLocalEditorTheme(boxSelectionOverlay, activeTheme);
|
|
517
1119
|
applyLocalEditorTheme(localTestMenu, activeTheme);
|
|
518
1120
|
applyLocalEditorTheme(toolbarOverflowMenu, activeTheme);
|
|
1121
|
+
applyLocalEditorTheme(snapSettingsPopover, activeTheme);
|
|
1122
|
+
applyLocalEditorTheme(placementSettingsPopover, activeTheme);
|
|
1123
|
+
applyLocalEditorTheme(transformActionPopover, activeTheme);
|
|
1124
|
+
applyLocalEditorTheme(overlaySettingsPopover, activeTheme);
|
|
1125
|
+
tooltipController.setTheme(activeTheme);
|
|
519
1126
|
contextMenu.setTheme?.(activeTheme);
|
|
520
1127
|
}
|
|
521
1128
|
function updateThemeToggleButton() {
|
|
522
1129
|
const nextTheme = activeTheme === 'dark' ? 'light' : 'dark';
|
|
523
1130
|
themeToggleButton.dataset.editorTheme = activeTheme;
|
|
524
1131
|
themeToggleButton.setAttribute('aria-pressed', activeTheme === 'light' ? 'true' : 'false');
|
|
525
|
-
themeToggleButton
|
|
1132
|
+
setToolbarButtonTooltip(themeToggleButton, nextTheme === 'light' ? '切换为浅色主题' : '切换为深色主题');
|
|
526
1133
|
}
|
|
527
1134
|
function setActiveTheme(theme) {
|
|
528
1135
|
activeTheme = normalizeLocalEditorThemeName(theme);
|
|
@@ -530,15 +1137,52 @@ export function createLocalEditorBrowserUi(options = {}) {
|
|
|
530
1137
|
updateThemeToggleButton();
|
|
531
1138
|
}
|
|
532
1139
|
let localTestMenuOpen = false;
|
|
1140
|
+
let snapSettingsOpen = false;
|
|
1141
|
+
let placementSettingsOpen = false;
|
|
1142
|
+
let transformActionOpen = false;
|
|
1143
|
+
let overlaySettingsOpen = false;
|
|
533
1144
|
function closeLocalTestMenu() {
|
|
534
1145
|
localTestMenuOpen = false;
|
|
535
1146
|
localTestMenu.style.display = 'none';
|
|
536
1147
|
LocalEditorShared.applyButtonActiveState(localTestButton, false);
|
|
537
1148
|
}
|
|
1149
|
+
function closeSnapSettingsPopover() {
|
|
1150
|
+
snapSettingsOpen = false;
|
|
1151
|
+
snapSettingsPopover.style.display = 'none';
|
|
1152
|
+
snapButton.setAttribute('aria-expanded', 'false');
|
|
1153
|
+
const enabled = currentState?.transformOperations?.settings.snap.enabled
|
|
1154
|
+
?? DEFAULT_EDITOR_TRANSFORM_OPERATION_SETTINGS.snap.enabled;
|
|
1155
|
+
LocalEditorShared.applyButtonActiveState(snapButton, enabled);
|
|
1156
|
+
}
|
|
1157
|
+
function closePlacementSettingsPopover() {
|
|
1158
|
+
placementSettingsOpen = false;
|
|
1159
|
+
placementSettingsPopover.style.display = 'none';
|
|
1160
|
+
placementButton.setAttribute('aria-expanded', 'false');
|
|
1161
|
+
const mode = currentState?.transformOperations?.settings.placementMode
|
|
1162
|
+
?? DEFAULT_EDITOR_TRANSFORM_OPERATION_SETTINGS.placementMode;
|
|
1163
|
+
LocalEditorShared.applyButtonActiveState(placementButton, mode !== 'off');
|
|
1164
|
+
}
|
|
1165
|
+
function closeTransformActionPopover() {
|
|
1166
|
+
transformActionOpen = false;
|
|
1167
|
+
transformActionPopover.style.display = 'none';
|
|
1168
|
+
transformActionButton.setAttribute('aria-expanded', 'false');
|
|
1169
|
+
LocalEditorShared.applyButtonActiveState(transformActionButton, false);
|
|
1170
|
+
}
|
|
1171
|
+
function closeOverlaySettingsPopover() {
|
|
1172
|
+
overlaySettingsOpen = false;
|
|
1173
|
+
overlaySettingsPopover.style.display = 'none';
|
|
1174
|
+
overlaySettingsButton.setAttribute('aria-expanded', 'false');
|
|
1175
|
+
LocalEditorShared.applyButtonActiveState(overlaySettingsButton, false);
|
|
1176
|
+
}
|
|
538
1177
|
function openLocalTestMenu() {
|
|
539
1178
|
if (!localTestActionsEnabled || localTestButton.style.display === 'none')
|
|
540
1179
|
return;
|
|
541
1180
|
closeToolbarOverflowMenu();
|
|
1181
|
+
closeSnapSettingsPopover();
|
|
1182
|
+
closePlacementSettingsPopover();
|
|
1183
|
+
closeTransformActionPopover();
|
|
1184
|
+
closeOverlaySettingsPopover();
|
|
1185
|
+
closeOverlaySettingsPopover();
|
|
542
1186
|
const win = doc.defaultView;
|
|
543
1187
|
const rect = localTestButton.getBoundingClientRect();
|
|
544
1188
|
const viewportWidth = win?.innerWidth ?? doc.documentElement.clientWidth;
|
|
@@ -548,6 +1192,106 @@ export function createLocalEditorBrowserUi(options = {}) {
|
|
|
548
1192
|
localTestMenuOpen = true;
|
|
549
1193
|
LocalEditorShared.applyButtonActiveState(localTestButton, true);
|
|
550
1194
|
}
|
|
1195
|
+
function openSnapSettingsPopover() {
|
|
1196
|
+
if (snapButton.disabled || snapGroup.style.display === 'none')
|
|
1197
|
+
return;
|
|
1198
|
+
closeToolbarOverflowMenu();
|
|
1199
|
+
closeLocalTestMenu();
|
|
1200
|
+
closePlacementSettingsPopover();
|
|
1201
|
+
closeTransformActionPopover();
|
|
1202
|
+
closeOverlaySettingsPopover();
|
|
1203
|
+
const win = doc.defaultView;
|
|
1204
|
+
const rect = snapButton.getBoundingClientRect();
|
|
1205
|
+
const viewportWidth = win?.innerWidth ?? doc.documentElement.clientWidth;
|
|
1206
|
+
snapSettingsPopover.style.display = 'flex';
|
|
1207
|
+
const popoverWidth = snapSettingsPopover.offsetWidth || 220;
|
|
1208
|
+
snapSettingsPopover.style.top = `${Math.max(8, rect.bottom + 5)}px`;
|
|
1209
|
+
snapSettingsPopover.style.left = `${Math.max(8, Math.min(rect.left, viewportWidth - popoverWidth - 8))}px`;
|
|
1210
|
+
snapSettingsOpen = true;
|
|
1211
|
+
snapButton.setAttribute('aria-expanded', 'true');
|
|
1212
|
+
LocalEditorShared.applyButtonActiveState(snapButton, true);
|
|
1213
|
+
}
|
|
1214
|
+
function toggleSnapSettingsPopover() {
|
|
1215
|
+
if (snapSettingsOpen)
|
|
1216
|
+
closeSnapSettingsPopover();
|
|
1217
|
+
else
|
|
1218
|
+
openSnapSettingsPopover();
|
|
1219
|
+
}
|
|
1220
|
+
function openPlacementSettingsPopover() {
|
|
1221
|
+
if (placementButton.disabled || placementGroup.style.display === 'none')
|
|
1222
|
+
return;
|
|
1223
|
+
closeToolbarOverflowMenu();
|
|
1224
|
+
closeLocalTestMenu();
|
|
1225
|
+
closeSnapSettingsPopover();
|
|
1226
|
+
closeTransformActionPopover();
|
|
1227
|
+
closeOverlaySettingsPopover();
|
|
1228
|
+
const win = doc.defaultView;
|
|
1229
|
+
const rect = placementButton.getBoundingClientRect();
|
|
1230
|
+
const viewportWidth = win?.innerWidth ?? doc.documentElement.clientWidth;
|
|
1231
|
+
placementSettingsPopover.style.display = 'flex';
|
|
1232
|
+
const popoverWidth = placementSettingsPopover.offsetWidth || 180;
|
|
1233
|
+
placementSettingsPopover.style.top = `${Math.max(8, rect.bottom + 5)}px`;
|
|
1234
|
+
placementSettingsPopover.style.left = `${Math.max(8, Math.min(rect.left, viewportWidth - popoverWidth - 8))}px`;
|
|
1235
|
+
placementSettingsOpen = true;
|
|
1236
|
+
placementButton.setAttribute('aria-expanded', 'true');
|
|
1237
|
+
LocalEditorShared.applyButtonActiveState(placementButton, true);
|
|
1238
|
+
}
|
|
1239
|
+
function togglePlacementSettingsPopover() {
|
|
1240
|
+
if (placementSettingsOpen)
|
|
1241
|
+
closePlacementSettingsPopover();
|
|
1242
|
+
else
|
|
1243
|
+
openPlacementSettingsPopover();
|
|
1244
|
+
}
|
|
1245
|
+
function openTransformActionPopover() {
|
|
1246
|
+
if (transformActionButton.disabled || actionGroup.style.display === 'none')
|
|
1247
|
+
return;
|
|
1248
|
+
closeToolbarOverflowMenu();
|
|
1249
|
+
closeLocalTestMenu();
|
|
1250
|
+
closeSnapSettingsPopover();
|
|
1251
|
+
closePlacementSettingsPopover();
|
|
1252
|
+
closeOverlaySettingsPopover();
|
|
1253
|
+
const win = doc.defaultView;
|
|
1254
|
+
const rect = transformActionButton.getBoundingClientRect();
|
|
1255
|
+
const viewportWidth = win?.innerWidth ?? doc.documentElement.clientWidth;
|
|
1256
|
+
transformActionPopover.style.display = 'flex';
|
|
1257
|
+
const popoverWidth = transformActionPopover.offsetWidth || 236;
|
|
1258
|
+
transformActionPopover.style.top = `${Math.max(8, rect.bottom + 5)}px`;
|
|
1259
|
+
transformActionPopover.style.left = `${Math.max(8, Math.min(rect.left, viewportWidth - popoverWidth - 8))}px`;
|
|
1260
|
+
transformActionOpen = true;
|
|
1261
|
+
transformActionButton.setAttribute('aria-expanded', 'true');
|
|
1262
|
+
LocalEditorShared.applyButtonActiveState(transformActionButton, true);
|
|
1263
|
+
}
|
|
1264
|
+
function toggleTransformActionPopover() {
|
|
1265
|
+
if (transformActionOpen)
|
|
1266
|
+
closeTransformActionPopover();
|
|
1267
|
+
else
|
|
1268
|
+
openTransformActionPopover();
|
|
1269
|
+
}
|
|
1270
|
+
function openOverlaySettingsPopover() {
|
|
1271
|
+
if (overlaySettingsButton.disabled || viewportToolsGroup.style.display === 'none')
|
|
1272
|
+
return;
|
|
1273
|
+
const win = doc.defaultView;
|
|
1274
|
+
const rect = overlaySettingsButton.getBoundingClientRect();
|
|
1275
|
+
const viewportWidth = win?.innerWidth ?? doc.documentElement.clientWidth;
|
|
1276
|
+
closeLocalTestMenu();
|
|
1277
|
+
closeSnapSettingsPopover();
|
|
1278
|
+
closePlacementSettingsPopover();
|
|
1279
|
+
closeTransformActionPopover();
|
|
1280
|
+
closeToolbarOverflowMenu();
|
|
1281
|
+
overlaySettingsPopover.style.display = 'flex';
|
|
1282
|
+
const popoverWidth = overlaySettingsPopover.offsetWidth || 190;
|
|
1283
|
+
overlaySettingsPopover.style.top = `${Math.max(8, rect.bottom + 5)}px`;
|
|
1284
|
+
overlaySettingsPopover.style.left = `${Math.max(8, Math.min(rect.left, viewportWidth - popoverWidth - 8))}px`;
|
|
1285
|
+
overlaySettingsOpen = true;
|
|
1286
|
+
overlaySettingsButton.setAttribute('aria-expanded', 'true');
|
|
1287
|
+
LocalEditorShared.applyButtonActiveState(overlaySettingsButton, true);
|
|
1288
|
+
}
|
|
1289
|
+
function toggleOverlaySettingsPopover() {
|
|
1290
|
+
if (overlaySettingsOpen)
|
|
1291
|
+
closeOverlaySettingsPopover();
|
|
1292
|
+
else
|
|
1293
|
+
openOverlaySettingsPopover();
|
|
1294
|
+
}
|
|
551
1295
|
const onLocalTestButtonClick = (event) => {
|
|
552
1296
|
event.stopPropagation();
|
|
553
1297
|
if (localTestMenuOpen)
|
|
@@ -575,30 +1319,26 @@ export function createLocalEditorBrowserUi(options = {}) {
|
|
|
575
1319
|
const toolbarOrder = [
|
|
576
1320
|
sceneTitle,
|
|
577
1321
|
sceneQuickActions,
|
|
578
|
-
sceneUtilityActions,
|
|
579
|
-
cameraPreviewGroup,
|
|
580
1322
|
toolGroup,
|
|
581
1323
|
spaceGroup,
|
|
582
|
-
handleGroup,
|
|
583
1324
|
snapGroup,
|
|
584
1325
|
placementGroup,
|
|
585
1326
|
actionGroup,
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
1327
|
+
viewportToolsGroup,
|
|
1328
|
+
sceneUtilityActions,
|
|
1329
|
+
cameraPreviewGroup,
|
|
1330
|
+
editorStatusButton,
|
|
589
1331
|
toolbarOverflowButton,
|
|
590
1332
|
];
|
|
591
1333
|
const toolbarOverflowItems = [
|
|
592
|
-
{ id: 'status', element:
|
|
593
|
-
{ id: '
|
|
594
|
-
{ id: '
|
|
1334
|
+
{ id: 'editor-status', element: editorStatusButton, kind: 'group' },
|
|
1335
|
+
{ id: 'scene-utilities', element: sceneUtilityActions, kind: 'group' },
|
|
1336
|
+
{ id: 'camera-preview', element: cameraPreviewGroup, kind: 'group' },
|
|
1337
|
+
{ id: 'viewport-tools', element: viewportToolsGroup, kind: 'group' },
|
|
595
1338
|
{ id: 'transform-actions', element: actionGroup, kind: 'group' },
|
|
596
1339
|
{ id: 'placement', element: placementGroup, kind: 'group' },
|
|
597
1340
|
{ id: 'snap', element: snapGroup, kind: 'group' },
|
|
598
|
-
{ id: 'handles', element: handleGroup, kind: 'group' },
|
|
599
1341
|
{ id: 'space', element: spaceGroup, kind: 'group' },
|
|
600
|
-
{ id: 'camera-preview', element: cameraPreviewGroup, kind: 'group' },
|
|
601
|
-
{ id: 'scene-utilities', element: sceneUtilityActions, kind: 'group' },
|
|
602
1342
|
];
|
|
603
1343
|
let toolbarOverflowOpen = false;
|
|
604
1344
|
let toolbarOverflowRaf = null;
|
|
@@ -636,6 +1376,8 @@ export function createLocalEditorBrowserUi(options = {}) {
|
|
|
636
1376
|
item.element.style.width = '100%';
|
|
637
1377
|
}
|
|
638
1378
|
function setToolbarButtonCompact(button, compact) {
|
|
1379
|
+
if (button.dataset.editorButtonLabelMode === 'icon-only')
|
|
1380
|
+
return;
|
|
639
1381
|
const label = button.querySelector('[data-editor-button-label]');
|
|
640
1382
|
if (!label)
|
|
641
1383
|
return;
|
|
@@ -668,6 +1410,10 @@ export function createLocalEditorBrowserUi(options = {}) {
|
|
|
668
1410
|
if (toolbarOverflowMenu.childElementCount === 0)
|
|
669
1411
|
return;
|
|
670
1412
|
closeLocalTestMenu();
|
|
1413
|
+
closeSnapSettingsPopover();
|
|
1414
|
+
closePlacementSettingsPopover();
|
|
1415
|
+
closeTransformActionPopover();
|
|
1416
|
+
closeOverlaySettingsPopover();
|
|
671
1417
|
const win = doc.defaultView;
|
|
672
1418
|
const rect = toolbarOverflowButton.getBoundingClientRect();
|
|
673
1419
|
const viewportWidth = win?.innerWidth ?? doc.documentElement.clientWidth;
|
|
@@ -744,8 +1490,20 @@ export function createLocalEditorBrowserUi(options = {}) {
|
|
|
744
1490
|
return;
|
|
745
1491
|
if (target.closest('[data-editor-toolbar-overflow-menu]') || target.closest('[data-editor-toolbar-overflow-toggle]'))
|
|
746
1492
|
return;
|
|
1493
|
+
if (target.closest('[data-transform-snap-settings-popover]') || target.closest('[data-transform-snap-toggle]'))
|
|
1494
|
+
return;
|
|
1495
|
+
if (target.closest('[data-placement-settings-popover]') || target.closest('[data-placement-mode-toggle]'))
|
|
1496
|
+
return;
|
|
1497
|
+
if (target.closest('[data-transform-action-popover]') || target.closest('[data-transform-action-toggle]'))
|
|
1498
|
+
return;
|
|
1499
|
+
if (target.closest('[data-viewport-overlay-settings-popover]') || target.closest('[data-viewport-overlay-settings-toggle]'))
|
|
1500
|
+
return;
|
|
747
1501
|
closeLocalTestMenu();
|
|
748
1502
|
closeToolbarOverflowMenu();
|
|
1503
|
+
closeSnapSettingsPopover();
|
|
1504
|
+
closePlacementSettingsPopover();
|
|
1505
|
+
closeTransformActionPopover();
|
|
1506
|
+
closeOverlaySettingsPopover();
|
|
749
1507
|
};
|
|
750
1508
|
const ResizeObserverCtor = doc.defaultView?.ResizeObserver;
|
|
751
1509
|
const toolbarResizeObserver = ResizeObserverCtor
|
|
@@ -776,10 +1534,46 @@ export function createLocalEditorBrowserUi(options = {}) {
|
|
|
776
1534
|
});
|
|
777
1535
|
undoButton.addEventListener('click', () => callbacks.onUndo?.());
|
|
778
1536
|
redoButton.addEventListener('click', () => callbacks.onRedo?.());
|
|
1537
|
+
gridToggleButton.addEventListener('click', () => {
|
|
1538
|
+
const visible = currentState?.grid?.visible ?? true;
|
|
1539
|
+
callbacks.onGridVisibleChange?.(!visible);
|
|
1540
|
+
});
|
|
779
1541
|
sceneCameraButton.addEventListener('click', () => {
|
|
780
1542
|
const enabled = currentState?.sceneCameraPreview?.enabled ?? false;
|
|
781
1543
|
callbacks.onSceneCameraPreviewToggle?.(!enabled);
|
|
782
1544
|
});
|
|
1545
|
+
viewportToolsGroup.addEventListener('click', (event) => {
|
|
1546
|
+
const target = event.target instanceof HTMLElement ? event.target : null;
|
|
1547
|
+
const preset = target?.closest('[data-viewport-view-preset]')?.dataset.viewportViewPreset;
|
|
1548
|
+
if (preset) {
|
|
1549
|
+
callbacks.onViewportViewPresetChange?.(preset);
|
|
1550
|
+
return;
|
|
1551
|
+
}
|
|
1552
|
+
const projectionToggle = target?.closest('[data-viewport-projection-toggle]');
|
|
1553
|
+
if (projectionToggle) {
|
|
1554
|
+
const currentMode = currentState?.viewportTools?.projectionMode ?? 'perspective';
|
|
1555
|
+
const nextMode = currentMode === 'orthographic' ? 'perspective' : 'orthographic';
|
|
1556
|
+
callbacks.onViewportProjectionModeChange?.(nextMode);
|
|
1557
|
+
return;
|
|
1558
|
+
}
|
|
1559
|
+
const overlayToggle = target?.closest('[data-viewport-overlay-settings-toggle]');
|
|
1560
|
+
if (overlayToggle) {
|
|
1561
|
+
event.stopPropagation();
|
|
1562
|
+
toggleOverlaySettingsPopover();
|
|
1563
|
+
return;
|
|
1564
|
+
}
|
|
1565
|
+
const measureToggle = target?.closest('[data-viewport-measure-toggle]');
|
|
1566
|
+
if (measureToggle) {
|
|
1567
|
+
const currentTool = currentState?.viewportTools?.activeUtilityTool ?? 'none';
|
|
1568
|
+
const nextTool = currentTool === 'measure-distance' ? 'none' : 'measure-distance';
|
|
1569
|
+
callbacks.onViewportUtilityToolChange?.(nextTool);
|
|
1570
|
+
return;
|
|
1571
|
+
}
|
|
1572
|
+
const clearMeasurement = target?.closest('[data-viewport-measurement-clear]');
|
|
1573
|
+
if (clearMeasurement) {
|
|
1574
|
+
callbacks.onViewportMeasurementClear?.();
|
|
1575
|
+
}
|
|
1576
|
+
});
|
|
783
1577
|
const setShortcutHelpOpen = (open) => {
|
|
784
1578
|
if (open)
|
|
785
1579
|
contextMenu.close();
|
|
@@ -812,12 +1606,18 @@ export function createLocalEditorBrowserUi(options = {}) {
|
|
|
812
1606
|
if (space)
|
|
813
1607
|
callbacks.onTransformSpaceChange?.(space);
|
|
814
1608
|
});
|
|
815
|
-
snapButton.addEventListener('click', () => {
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
callbacks.onTransformSnapEnabledChange?.(!enabled);
|
|
1609
|
+
snapButton.addEventListener('click', (event) => {
|
|
1610
|
+
event.stopPropagation();
|
|
1611
|
+
toggleSnapSettingsPopover();
|
|
819
1612
|
});
|
|
820
|
-
|
|
1613
|
+
snapSettingsPopover.addEventListener('change', (event) => {
|
|
1614
|
+
const enabledInput = event.target instanceof HTMLInputElement
|
|
1615
|
+
? event.target.closest('[data-transform-snap-enabled]')
|
|
1616
|
+
: null;
|
|
1617
|
+
if (enabledInput) {
|
|
1618
|
+
callbacks.onTransformSnapEnabledChange?.(enabledInput.checked);
|
|
1619
|
+
return;
|
|
1620
|
+
}
|
|
821
1621
|
const input = event.target instanceof HTMLInputElement ? event.target : null;
|
|
822
1622
|
const kind = input?.dataset.transformSnapStep;
|
|
823
1623
|
if (!input || !kind)
|
|
@@ -831,18 +1631,47 @@ export function createLocalEditorBrowserUi(options = {}) {
|
|
|
831
1631
|
callbacks.onTransformSnapStepChange?.({ kind, value });
|
|
832
1632
|
});
|
|
833
1633
|
placementGroup.addEventListener('click', (event) => {
|
|
1634
|
+
event.stopPropagation();
|
|
1635
|
+
togglePlacementSettingsPopover();
|
|
1636
|
+
});
|
|
1637
|
+
placementSettingsPopover.addEventListener('click', (event) => {
|
|
834
1638
|
const target = event.target instanceof HTMLElement ? event.target : null;
|
|
835
1639
|
const mode = target?.closest('[data-placement-mode]')?.dataset.placementMode;
|
|
836
|
-
if (mode)
|
|
837
|
-
|
|
1640
|
+
if (!mode)
|
|
1641
|
+
return;
|
|
1642
|
+
callbacks.onPlacementModeChange?.(mode);
|
|
1643
|
+
closePlacementSettingsPopover();
|
|
1644
|
+
});
|
|
1645
|
+
actionGroup.addEventListener('click', (event) => {
|
|
1646
|
+
event.stopPropagation();
|
|
1647
|
+
toggleTransformActionPopover();
|
|
838
1648
|
});
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
1649
|
+
transformActionPopover.addEventListener('click', (event) => {
|
|
1650
|
+
const target = event.target instanceof HTMLElement ? event.target : null;
|
|
1651
|
+
const action = target?.closest('[data-transform-action]')?.dataset.transformAction;
|
|
1652
|
+
if (!action)
|
|
1653
|
+
return;
|
|
1654
|
+
const transformOperations = currentState?.transformOperations;
|
|
1655
|
+
if (!transformOperations || !isTransformActionEnabled(action, transformOperations))
|
|
1656
|
+
return;
|
|
1657
|
+
callbacks.onTransformAction?.(action);
|
|
1658
|
+
closeTransformActionPopover();
|
|
843
1659
|
});
|
|
844
|
-
|
|
845
|
-
|
|
1660
|
+
overlaySettingsPopover.addEventListener('change', (event) => {
|
|
1661
|
+
const input = event.target instanceof HTMLInputElement
|
|
1662
|
+
? event.target.closest('[data-viewport-overlay-setting]')
|
|
1663
|
+
: null;
|
|
1664
|
+
const key = input?.dataset.viewportOverlaySetting;
|
|
1665
|
+
if (!input || !key)
|
|
1666
|
+
return;
|
|
1667
|
+
const currentOverlay = currentState?.viewportTools?.overlay;
|
|
1668
|
+
callbacks.onViewportOverlaySettingsChange?.({
|
|
1669
|
+
bounds: currentOverlay?.bounds ?? true,
|
|
1670
|
+
dimensions: currentOverlay?.dimensions ?? true,
|
|
1671
|
+
edgeLengths: currentOverlay?.edgeLengths ?? false,
|
|
1672
|
+
anchor: currentOverlay?.anchor ?? true,
|
|
1673
|
+
[key]: input.checked,
|
|
1674
|
+
});
|
|
846
1675
|
});
|
|
847
1676
|
assetPanel.addEventListener('click', (event) => {
|
|
848
1677
|
const target = event.target instanceof HTMLElement ? event.target : null;
|
|
@@ -930,6 +1759,26 @@ export function createLocalEditorBrowserUi(options = {}) {
|
|
|
930
1759
|
if (currentState?.mode !== 'editor' || currentState.busy)
|
|
931
1760
|
return;
|
|
932
1761
|
const key = event.key.toLowerCase();
|
|
1762
|
+
if (key === 'escape' && snapSettingsOpen) {
|
|
1763
|
+
event.preventDefault();
|
|
1764
|
+
closeSnapSettingsPopover();
|
|
1765
|
+
return;
|
|
1766
|
+
}
|
|
1767
|
+
if (key === 'escape' && placementSettingsOpen) {
|
|
1768
|
+
event.preventDefault();
|
|
1769
|
+
closePlacementSettingsPopover();
|
|
1770
|
+
return;
|
|
1771
|
+
}
|
|
1772
|
+
if (key === 'escape' && transformActionOpen) {
|
|
1773
|
+
event.preventDefault();
|
|
1774
|
+
closeTransformActionPopover();
|
|
1775
|
+
return;
|
|
1776
|
+
}
|
|
1777
|
+
if (key === 'escape' && overlaySettingsOpen) {
|
|
1778
|
+
event.preventDefault();
|
|
1779
|
+
closeOverlaySettingsPopover();
|
|
1780
|
+
return;
|
|
1781
|
+
}
|
|
933
1782
|
const primaryModifier = event.metaKey || event.ctrlKey;
|
|
934
1783
|
const handleDocumentShortcut = inputRouter.shouldHandleDocumentShortcut(event);
|
|
935
1784
|
const handleGlobalShortcut = inputRouter.shouldHandleGlobalShortcut(event);
|
|
@@ -975,7 +1824,10 @@ export function createLocalEditorBrowserUi(options = {}) {
|
|
|
975
1824
|
}
|
|
976
1825
|
if (key === 'escape') {
|
|
977
1826
|
event.preventDefault();
|
|
978
|
-
callbacks.
|
|
1827
|
+
if (callbacks.onCancelEditorIntent)
|
|
1828
|
+
callbacks.onCancelEditorIntent();
|
|
1829
|
+
else
|
|
1830
|
+
callbacks.onCancelActiveOperation?.();
|
|
979
1831
|
}
|
|
980
1832
|
};
|
|
981
1833
|
doc.addEventListener('keydown', onKeyDown);
|
|
@@ -1028,33 +1880,6 @@ export function createLocalEditorBrowserUi(options = {}) {
|
|
|
1028
1880
|
? CSS.escape(value)
|
|
1029
1881
|
: value.replace(/["\\]/g, '\\$&');
|
|
1030
1882
|
}
|
|
1031
|
-
function renderTransformHandleBadges(transformTool) {
|
|
1032
|
-
LocalEditorShared.clearElement(handleGroup);
|
|
1033
|
-
const tool = transformTool?.activeTool ?? 'select';
|
|
1034
|
-
const descriptor = DEFAULT_EDITOR_TRANSFORM_TOOL_DESCRIPTORS[tool];
|
|
1035
|
-
handleGroup.style.display = descriptor.handles.length > 0 ? 'flex' : 'none';
|
|
1036
|
-
for (const handle of descriptor.handles) {
|
|
1037
|
-
const badge = doc.createElement('span');
|
|
1038
|
-
badge.dataset.transformHandle = handle.constraint;
|
|
1039
|
-
badge.title = handle.description;
|
|
1040
|
-
badge.textContent = handle.label;
|
|
1041
|
-
badge.style.cssText = [
|
|
1042
|
-
'height:22px',
|
|
1043
|
-
'display:inline-flex',
|
|
1044
|
-
'align-items:center',
|
|
1045
|
-
'padding:0 7px',
|
|
1046
|
-
'border:1px solid var(--fps-editor-border)',
|
|
1047
|
-
'border-radius:3px',
|
|
1048
|
-
'background:var(--fps-editor-field)',
|
|
1049
|
-
'color:var(--fps-editor-muted-strong)',
|
|
1050
|
-
'font-size:11px',
|
|
1051
|
-
'font-weight:800',
|
|
1052
|
-
'white-space:nowrap',
|
|
1053
|
-
'pointer-events:none',
|
|
1054
|
-
].join(';');
|
|
1055
|
-
handleGroup.appendChild(badge);
|
|
1056
|
-
}
|
|
1057
|
-
}
|
|
1058
1883
|
function formatSnapStepInputValue(snap, kind) {
|
|
1059
1884
|
const value = kind === 'move'
|
|
1060
1885
|
? snap.moveStep
|
|
@@ -1077,7 +1902,7 @@ export function createLocalEditorBrowserUi(options = {}) {
|
|
|
1077
1902
|
contextMenu.close();
|
|
1078
1903
|
hostChrome.style.display = !inEditor && localTestActionsEnabled ? 'flex' : 'none';
|
|
1079
1904
|
enterEditorButton.disabled = disabled;
|
|
1080
|
-
for (const button of [saveButton, saveAndRunButton, discardRunButton, undoButton, redoButton, sceneHelpButton, sceneCameraButton]) {
|
|
1905
|
+
for (const button of [saveButton, saveAndRunButton, discardRunButton, undoButton, redoButton, sceneHelpButton, sceneCameraButton, gridToggleButton]) {
|
|
1081
1906
|
button.style.display = 'inline-flex';
|
|
1082
1907
|
button.disabled = disabled;
|
|
1083
1908
|
}
|
|
@@ -1091,13 +1916,63 @@ export function createLocalEditorBrowserUi(options = {}) {
|
|
|
1091
1916
|
const transformTool = state.transformTool ?? null;
|
|
1092
1917
|
workbench.root.style.display = inEditor ? '' : 'none';
|
|
1093
1918
|
sceneToolOverlay.style.display = inEditor ? 'flex' : 'none';
|
|
1919
|
+
if (!inEditor)
|
|
1920
|
+
closeSnapSettingsPopover();
|
|
1921
|
+
if (!inEditor)
|
|
1922
|
+
closePlacementSettingsPopover();
|
|
1923
|
+
if (!inEditor)
|
|
1924
|
+
closeTransformActionPopover();
|
|
1925
|
+
if (!inEditor)
|
|
1926
|
+
closeOverlaySettingsPopover();
|
|
1094
1927
|
const sceneCameraPreview = state.sceneCameraPreview ?? { enabled: false, available: false };
|
|
1095
1928
|
cameraPreviewGroup.style.display = inEditor ? 'flex' : 'none';
|
|
1096
1929
|
sceneCameraButton.disabled = disabled || !sceneCameraPreview.available;
|
|
1097
|
-
|
|
1930
|
+
const sceneCameraTooltip = sceneCameraPreview.available
|
|
1098
1931
|
? '从 Main Camera 查看当前场景'
|
|
1099
1932
|
: '当前场景没有可预览的 Main Camera';
|
|
1933
|
+
setToolbarButtonTooltip(sceneCameraButton, sceneCameraTooltip);
|
|
1100
1934
|
LocalEditorShared.applyButtonActiveState(sceneCameraButton, sceneCameraPreview.enabled);
|
|
1935
|
+
const viewportTools = state.viewportTools ?? null;
|
|
1936
|
+
const viewportControlsDisabled = disabled || sceneCameraPreview.enabled;
|
|
1937
|
+
const viewportControlsTooltip = sceneCameraPreview.enabled
|
|
1938
|
+
? '请先关闭 Scene Camera 预览,再使用视口工具'
|
|
1939
|
+
: '';
|
|
1940
|
+
viewportToolsGroup.style.display = inEditor ? 'flex' : 'none';
|
|
1941
|
+
for (const [preset, button] of viewportPresetButtons) {
|
|
1942
|
+
button.disabled = viewportControlsDisabled;
|
|
1943
|
+
setToolbarButtonTooltip(button, viewportControlsTooltip || VIEW_PRESET_TOOLTIPS[preset]);
|
|
1944
|
+
LocalEditorShared.applyButtonActiveState(button, viewportTools?.viewPreset === preset);
|
|
1945
|
+
}
|
|
1946
|
+
projectionToggleButton.disabled = viewportControlsDisabled;
|
|
1947
|
+
const projectionMode = viewportTools?.projectionMode ?? 'perspective';
|
|
1948
|
+
const nextProjectionMode = projectionMode === 'orthographic' ? 'perspective' : 'orthographic';
|
|
1949
|
+
setToolbarButtonIcon(doc, projectionToggleButton, projectionMode === 'orthographic' ? 'projection-ortho' : 'projection-perspective');
|
|
1950
|
+
setToolbarButtonTooltip(projectionToggleButton, viewportControlsTooltip || `投影模式:${VIEWPORT_PROJECTION_LABELS[projectionMode]}。切换为${VIEWPORT_PROJECTION_LABELS[nextProjectionMode]}`);
|
|
1951
|
+
LocalEditorShared.applyButtonActiveState(projectionToggleButton, projectionMode === 'orthographic');
|
|
1952
|
+
overlaySettingsButton.disabled = disabled;
|
|
1953
|
+
setToolbarButtonTooltip(overlaySettingsButton, '视口信息层设置');
|
|
1954
|
+
LocalEditorShared.applyButtonActiveState(overlaySettingsButton, overlaySettingsOpen);
|
|
1955
|
+
const overlaySettings = viewportTools?.overlay;
|
|
1956
|
+
for (const [key, input] of overlayInputs) {
|
|
1957
|
+
input.disabled = disabled;
|
|
1958
|
+
input.checked = overlaySettings?.[key] ?? false;
|
|
1959
|
+
}
|
|
1960
|
+
measureButton.disabled = viewportControlsDisabled;
|
|
1961
|
+
setToolbarButtonTooltip(measureButton, viewportControlsTooltip || '测量 XZ 地面距离');
|
|
1962
|
+
LocalEditorShared.applyButtonActiveState(measureButton, viewportTools?.activeUtilityTool === 'measure-distance');
|
|
1963
|
+
const hasMeasurement = !!(state.viewportMeasurement?.start
|
|
1964
|
+
|| state.viewportMeasurement?.end
|
|
1965
|
+
|| state.viewportMeasurement?.preview);
|
|
1966
|
+
clearMeasurementButton.style.display = hasMeasurement ? 'inline-flex' : 'none';
|
|
1967
|
+
clearMeasurementButton.disabled = disabled || !hasMeasurement;
|
|
1968
|
+
setToolbarButtonTooltip(clearMeasurementButton, '清除测量结果');
|
|
1969
|
+
LocalEditorShared.applyButtonActiveState(clearMeasurementButton, false);
|
|
1970
|
+
if (!inEditor || disabled || viewportToolsGroup.style.display === 'none')
|
|
1971
|
+
closeOverlaySettingsPopover();
|
|
1972
|
+
const gridState = state.grid ?? { visible: false, available: false };
|
|
1973
|
+
gridToggleButton.disabled = disabled || !gridState.available;
|
|
1974
|
+
setToolbarButtonTooltip(gridToggleButton, gridState.visible ? '隐藏 Scene View 网格' : '显示 Scene View 网格');
|
|
1975
|
+
LocalEditorShared.applyButtonActiveState(gridToggleButton, gridState.visible);
|
|
1101
1976
|
for (const [tool, button] of toolButtons) {
|
|
1102
1977
|
button.disabled = disabled;
|
|
1103
1978
|
LocalEditorShared.applyButtonActiveState(button, transformTool?.activeTool === tool);
|
|
@@ -1107,7 +1982,6 @@ export function createLocalEditorBrowserUi(options = {}) {
|
|
|
1107
1982
|
LocalEditorShared.applyButtonActiveState(button, transformTool?.activeSpace === space);
|
|
1108
1983
|
}
|
|
1109
1984
|
sceneHelpButton.disabled = disabled;
|
|
1110
|
-
renderTransformHandleBadges(transformTool);
|
|
1111
1985
|
const transformOperations = state.transformOperations ?? {
|
|
1112
1986
|
settings: DEFAULT_EDITOR_TRANSFORM_OPERATION_SETTINGS,
|
|
1113
1987
|
selectedCount: state.selectedIds.length,
|
|
@@ -1118,53 +1992,75 @@ export function createLocalEditorBrowserUi(options = {}) {
|
|
|
1118
1992
|
const operationSettings = transformOperations.settings;
|
|
1119
1993
|
snapGroup.style.display = inEditor ? 'flex' : 'none';
|
|
1120
1994
|
snapButton.disabled = disabled;
|
|
1121
|
-
|
|
1995
|
+
snapEnabledInput.disabled = disabled;
|
|
1996
|
+
snapEnabledInput.checked = operationSettings.snap.enabled;
|
|
1997
|
+
LocalEditorShared.applyButtonActiveState(snapButton, operationSettings.snap.enabled || snapSettingsOpen);
|
|
1998
|
+
setToolbarButtonTooltip(snapButton, `吸附设置 · 位移 ${formatSnapStepInputValue(operationSettings.snap, 'move')} · 旋转 ${formatSnapStepInputValue(operationSettings.snap, 'rotate')}° · 缩放 ${formatSnapStepInputValue(operationSettings.snap, 'scale')}`);
|
|
1122
1999
|
for (const [kind, input] of snapStepInputs) {
|
|
1123
2000
|
input.disabled = disabled;
|
|
1124
2001
|
if (doc.activeElement !== input)
|
|
1125
2002
|
input.value = formatSnapStepInputValue(operationSettings.snap, kind);
|
|
1126
2003
|
}
|
|
2004
|
+
if (!inEditor || disabled || snapGroup.style.display === 'none')
|
|
2005
|
+
closeSnapSettingsPopover();
|
|
1127
2006
|
placementGroup.style.display = inEditor ? 'flex' : 'none';
|
|
2007
|
+
placementButton.disabled = disabled;
|
|
2008
|
+
setToolbarButtonIcon(doc, placementButton, PLACEMENT_MODE_ICONS[operationSettings.placementMode]);
|
|
2009
|
+
setToolbarButtonTooltip(placementButton, `放置模式 · ${PLACEMENT_MODE_DESCRIPTIONS[operationSettings.placementMode]}`);
|
|
2010
|
+
LocalEditorShared.applyButtonActiveState(placementButton, operationSettings.placementMode !== 'off' || placementSettingsOpen);
|
|
1128
2011
|
for (const [mode, button] of Object.entries(placementButtons)) {
|
|
1129
2012
|
button.disabled = disabled;
|
|
1130
2013
|
LocalEditorShared.applyButtonActiveState(button, operationSettings.placementMode === mode);
|
|
1131
2014
|
}
|
|
2015
|
+
if (!inEditor || disabled || placementGroup.style.display === 'none')
|
|
2016
|
+
closePlacementSettingsPopover();
|
|
1132
2017
|
actionGroup.style.display = inEditor ? 'flex' : 'none';
|
|
1133
|
-
|
|
1134
|
-
transformActionButton.disabled = disabled || !
|
|
1135
|
-
transformActionButton
|
|
1136
|
-
|
|
1137
|
-
|
|
2018
|
+
const hasTransformAction = transformOperations.canAlign || transformOperations.canDistribute;
|
|
2019
|
+
transformActionButton.disabled = disabled || !hasTransformAction;
|
|
2020
|
+
setToolbarButtonTooltip(transformActionButton, hasTransformAction ? '对齐与分布' : '选择至少 2 个对象后可用');
|
|
2021
|
+
LocalEditorShared.applyButtonActiveState(transformActionButton, transformActionOpen);
|
|
2022
|
+
for (const [action, button] of transformActionButtons) {
|
|
2023
|
+
const enabled = isTransformActionEnabled(action, transformOperations);
|
|
2024
|
+
button.disabled = disabled || !enabled;
|
|
2025
|
+
button.style.opacity = button.disabled ? '0.45' : '1';
|
|
2026
|
+
button.style.cursor = button.disabled ? 'not-allowed' : 'pointer';
|
|
2027
|
+
}
|
|
2028
|
+
if (!inEditor || disabled || !hasTransformAction || actionGroup.style.display === 'none')
|
|
2029
|
+
closeTransformActionPopover();
|
|
1138
2030
|
undoButton.disabled = disabled || !state.session?.canUndo;
|
|
1139
2031
|
redoButton.disabled = disabled || !state.session?.canRedo;
|
|
1140
2032
|
LocalEditorShared.applyButtonActiveState(sceneHelpButton, inEditor && helpOpen);
|
|
1141
2033
|
shortcutHelpPanel.style.display = inEditor && helpOpen ? '' : 'none';
|
|
1142
2034
|
dirtyBadge.style.display = inEditor && state.session?.dirty ? 'inline-flex' : 'none';
|
|
1143
|
-
const
|
|
1144
|
-
? ` | dragging ${transformTool.draggingNodeId ?? 'selection'}`
|
|
1145
|
-
: '';
|
|
1146
|
-
status.textContent = [
|
|
2035
|
+
const editorStatusText = [
|
|
1147
2036
|
state.status,
|
|
1148
|
-
state.session?.dirty ? '
|
|
2037
|
+
state.session?.dirty ? '有未保存更改' : '',
|
|
1149
2038
|
LocalEditorShared.formatAuthoringSourceLabel(state.session?.source),
|
|
1150
2039
|
state.summary,
|
|
1151
2040
|
].filter(Boolean).join(' | ');
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
2041
|
+
const transformStatusText = transformTool?.dragPhase === 'dragging'
|
|
2042
|
+
? `正在拖拽 ${transformTool.draggingNodeId ?? '选择对象'}`
|
|
2043
|
+
: LocalEditorShared.toTransformToolStatusLabel(transformTool?.activeTool ?? 'select', transformTool?.activeSpace ?? 'world');
|
|
2044
|
+
const statusToneColor = state.statusTone === 'error'
|
|
1155
2045
|
? 'var(--fps-editor-danger-strong)'
|
|
1156
2046
|
: state.statusTone === 'warning'
|
|
1157
2047
|
? 'var(--fps-editor-warn)'
|
|
1158
2048
|
: state.statusTone === 'success'
|
|
1159
2049
|
? 'var(--fps-editor-success)'
|
|
1160
2050
|
: 'var(--fps-editor-muted)';
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
LocalEditorShared.toTransformMouseHint(transformTool?.activeTool ?? 'select'),
|
|
2051
|
+
setToolbarButtonTooltip(editorStatusButton, [
|
|
2052
|
+
`编辑器状态:${editorStatusText}`,
|
|
2053
|
+
state.statusDetails && state.statusDetails !== state.status ? state.statusDetails : '',
|
|
2054
|
+
transformStatusText,
|
|
1166
2055
|
LocalEditorShared.toTransformOperationStatusLabel(operationSettings),
|
|
1167
|
-
].join(' · ');
|
|
2056
|
+
].filter(Boolean).join(' · '));
|
|
2057
|
+
editorStatusButton.style.color = statusToneColor;
|
|
2058
|
+
editorStatusButton.style.borderColor = state.statusTone == null || state.statusTone === 'default'
|
|
2059
|
+
? 'var(--fps-editor-border)'
|
|
2060
|
+
: statusToneColor;
|
|
2061
|
+
renderCoordinateAxesOverlay(coordinateAxesOverlay, inEditor ? state.coordinateAxes ?? null : null);
|
|
2062
|
+
renderSpatialOverlay(spatialOverlay, inEditor ? state.viewportSpatialOverlay ?? null : null);
|
|
2063
|
+
renderMeasurementOverlay(measurementOverlay, inEditor ? state.viewportMeasurement ?? null : null);
|
|
1168
2064
|
const boxSelection = state.boxSelection;
|
|
1169
2065
|
if (inEditor && boxSelection?.active) {
|
|
1170
2066
|
boxSelectionOverlay.style.display = '';
|
|
@@ -1212,10 +2108,17 @@ export function createLocalEditorBrowserUi(options = {}) {
|
|
|
1212
2108
|
workbench.root.remove();
|
|
1213
2109
|
localTestMenu.remove();
|
|
1214
2110
|
toolbarOverflowMenu.remove();
|
|
2111
|
+
snapSettingsPopover.remove();
|
|
2112
|
+
placementSettingsPopover.remove();
|
|
2113
|
+
transformActionPopover.remove();
|
|
2114
|
+
overlaySettingsPopover.remove();
|
|
2115
|
+
spatialOverlay.root.remove();
|
|
2116
|
+
measurementOverlay.root.remove();
|
|
1215
2117
|
boxSelectionOverlay.remove();
|
|
1216
2118
|
shortcutHelpPanel.remove();
|
|
1217
2119
|
hierarchyController.dispose();
|
|
1218
2120
|
workbenchLayoutController.dispose();
|
|
2121
|
+
tooltipController.dispose();
|
|
1219
2122
|
contextMenu.dispose();
|
|
1220
2123
|
inputRouter.dispose();
|
|
1221
2124
|
doc.removeEventListener('keydown', onKeyDown);
|