@fps-games/editor 0.1.1-beta.6 → 0.1.1-beta.7
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 +14 -3
- package/dist/local-editor-harness.d.ts.map +1 -1
- package/dist/local-editor-harness.js +205 -35
- 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 +2 -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 +2 -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/types.d.ts +7 -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 +16 -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 +42 -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 +606 -199
- 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 +1 -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 +1 -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/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/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,62 @@ 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 TRANSFORM_ACTION_GROUPS = [
|
|
79
|
+
{
|
|
80
|
+
title: '对齐到 Active Object',
|
|
81
|
+
hint: '需要至少 2 个选中对象',
|
|
82
|
+
actions: ['align-x', 'align-y', 'align-z', 'align-all'],
|
|
83
|
+
},
|
|
84
|
+
{
|
|
85
|
+
title: '均匀分布',
|
|
86
|
+
hint: '需要至少 3 个选中对象',
|
|
87
|
+
actions: ['distribute-x', 'distribute-y', 'distribute-z'],
|
|
88
|
+
},
|
|
89
|
+
];
|
|
90
|
+
const TRANSFORM_ACTION_LABELS = {
|
|
91
|
+
'align-x': '对齐 X',
|
|
92
|
+
'align-y': '对齐 Y',
|
|
93
|
+
'align-z': '对齐 Z',
|
|
94
|
+
'align-all': '对齐全',
|
|
95
|
+
'distribute-x': '分布 X',
|
|
96
|
+
'distribute-y': '分布 Y',
|
|
97
|
+
'distribute-z': '分布 Z',
|
|
98
|
+
};
|
|
99
|
+
function createTransformToolTooltip(descriptor) {
|
|
100
|
+
const toolLabel = descriptor.shortcut
|
|
101
|
+
? `${descriptor.label}工具 (${descriptor.shortcut})`
|
|
102
|
+
: `${descriptor.label}工具`;
|
|
103
|
+
return `${toolLabel} · ${LocalEditorShared.toTransformMouseHint(descriptor.tool)}`;
|
|
104
|
+
}
|
|
105
|
+
function createToolbarIconButton(doc, label, icon, tooltip = label, toolbarRole = 'command') {
|
|
106
|
+
return LocalEditorShared.createButton(doc, label, {
|
|
107
|
+
icon,
|
|
108
|
+
labelMode: 'icon-only',
|
|
109
|
+
variant: 'toolbar-icon',
|
|
110
|
+
tooltip,
|
|
111
|
+
ariaLabel: tooltip,
|
|
112
|
+
toolbarRole,
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
function setToolbarButtonTooltip(button, tooltip) {
|
|
116
|
+
button.dataset.editorTooltip = tooltip;
|
|
117
|
+
button.setAttribute('aria-label', tooltip);
|
|
118
|
+
}
|
|
119
|
+
function setToolbarButtonIcon(doc, button, icon) {
|
|
120
|
+
const currentIcon = button.querySelector('[data-editor-icon]');
|
|
121
|
+
if (currentIcon?.dataset.editorIcon === icon)
|
|
122
|
+
return;
|
|
123
|
+
const nextIcon = createLocalEditorIcon(doc, icon);
|
|
124
|
+
if (currentIcon)
|
|
125
|
+
currentIcon.replaceWith(nextIcon);
|
|
126
|
+
else
|
|
127
|
+
button.prepend(nextIcon);
|
|
128
|
+
}
|
|
70
129
|
function readInspectorVectorInputValue(input) {
|
|
71
130
|
const wrapper = input.closest('[data-inspector-vector-control]');
|
|
72
131
|
const values = {};
|
|
@@ -112,6 +171,121 @@ function readInspectorImmediateSource(input) {
|
|
|
112
171
|
return 'color';
|
|
113
172
|
return 'input';
|
|
114
173
|
}
|
|
174
|
+
function createCoordinateAxesOverlay(doc) {
|
|
175
|
+
const root = doc.createElement('div');
|
|
176
|
+
root.dataset.editorCoordinateAxesOverlay = 'true';
|
|
177
|
+
root.style.cssText = [
|
|
178
|
+
'position:absolute',
|
|
179
|
+
'right:14px',
|
|
180
|
+
'bottom:14px',
|
|
181
|
+
'width:112px',
|
|
182
|
+
'height:112px',
|
|
183
|
+
'display:none',
|
|
184
|
+
'z-index:2',
|
|
185
|
+
'box-sizing:border-box',
|
|
186
|
+
'border:1px solid color-mix(in srgb, var(--fps-editor-border) 78%, transparent)',
|
|
187
|
+
'border-radius:8px',
|
|
188
|
+
'background:color-mix(in srgb, var(--fps-editor-chrome) 58%, transparent)',
|
|
189
|
+
'box-shadow:var(--fps-editor-shadow-panel)',
|
|
190
|
+
'pointer-events:none',
|
|
191
|
+
'overflow:hidden',
|
|
192
|
+
].join(';');
|
|
193
|
+
const svg = doc.createElementNS('http://www.w3.org/2000/svg', 'svg');
|
|
194
|
+
svg.setAttribute('viewBox', '0 0 112 112');
|
|
195
|
+
svg.setAttribute('aria-hidden', 'true');
|
|
196
|
+
svg.style.cssText = 'display:block;width:100%;height:100%';
|
|
197
|
+
root.appendChild(svg);
|
|
198
|
+
const lineLayer = doc.createElementNS('http://www.w3.org/2000/svg', 'g');
|
|
199
|
+
const labelLayer = doc.createElementNS('http://www.w3.org/2000/svg', 'g');
|
|
200
|
+
svg.appendChild(lineLayer);
|
|
201
|
+
svg.appendChild(labelLayer);
|
|
202
|
+
const axisGroups = new Map();
|
|
203
|
+
const axisLines = new Map();
|
|
204
|
+
const axisLabels = new Map();
|
|
205
|
+
const axisDiscs = new Map();
|
|
206
|
+
for (const axis of ['x', 'y', 'z']) {
|
|
207
|
+
const group = doc.createElementNS('http://www.w3.org/2000/svg', 'g');
|
|
208
|
+
const line = doc.createElementNS('http://www.w3.org/2000/svg', 'line');
|
|
209
|
+
line.setAttribute('stroke-width', '3.2');
|
|
210
|
+
line.setAttribute('stroke-linecap', 'round');
|
|
211
|
+
line.setAttribute('x1', '56');
|
|
212
|
+
line.setAttribute('y1', '56');
|
|
213
|
+
const disc = doc.createElementNS('http://www.w3.org/2000/svg', 'circle');
|
|
214
|
+
disc.setAttribute('r', '7');
|
|
215
|
+
const label = doc.createElementNS('http://www.w3.org/2000/svg', 'text');
|
|
216
|
+
label.setAttribute('text-anchor', 'middle');
|
|
217
|
+
label.setAttribute('dominant-baseline', 'middle');
|
|
218
|
+
label.setAttribute('dy', '0.04em');
|
|
219
|
+
label.style.cssText = [
|
|
220
|
+
'fill:#172033',
|
|
221
|
+
'font-size:10px',
|
|
222
|
+
'font-weight:900',
|
|
223
|
+
'font-family:Arial, Helvetica, sans-serif',
|
|
224
|
+
'letter-spacing:0',
|
|
225
|
+
].join(';');
|
|
226
|
+
group.appendChild(disc);
|
|
227
|
+
group.appendChild(label);
|
|
228
|
+
lineLayer.appendChild(line);
|
|
229
|
+
labelLayer.appendChild(group);
|
|
230
|
+
axisGroups.set(axis, group);
|
|
231
|
+
axisLines.set(axis, line);
|
|
232
|
+
axisLabels.set(axis, label);
|
|
233
|
+
axisDiscs.set(axis, disc);
|
|
234
|
+
}
|
|
235
|
+
const centerDot = doc.createElementNS('http://www.w3.org/2000/svg', 'circle');
|
|
236
|
+
centerDot.setAttribute('cx', '56');
|
|
237
|
+
centerDot.setAttribute('cy', '56');
|
|
238
|
+
centerDot.setAttribute('r', '3.2');
|
|
239
|
+
centerDot.setAttribute('fill', 'color-mix(in srgb, var(--fps-editor-text) 82%, transparent)');
|
|
240
|
+
labelLayer.appendChild(centerDot);
|
|
241
|
+
return {
|
|
242
|
+
root,
|
|
243
|
+
lineLayer,
|
|
244
|
+
labelLayer,
|
|
245
|
+
axisGroups,
|
|
246
|
+
axisLines,
|
|
247
|
+
axisLabels,
|
|
248
|
+
axisDiscs,
|
|
249
|
+
centerDot,
|
|
250
|
+
};
|
|
251
|
+
}
|
|
252
|
+
function renderCoordinateAxesOverlay(overlay, state) {
|
|
253
|
+
if (!state?.axes?.length) {
|
|
254
|
+
overlay.root.style.display = 'none';
|
|
255
|
+
return;
|
|
256
|
+
}
|
|
257
|
+
overlay.root.style.display = '';
|
|
258
|
+
const center = 56;
|
|
259
|
+
const lineLength = 38;
|
|
260
|
+
const sortedAxes = [...state.axes].sort((left, right) => right.depth - left.depth);
|
|
261
|
+
for (const axis of sortedAxes) {
|
|
262
|
+
const group = overlay.axisGroups.get(axis.id);
|
|
263
|
+
const line = overlay.axisLines.get(axis.id);
|
|
264
|
+
const disc = overlay.axisDiscs.get(axis.id);
|
|
265
|
+
const label = overlay.axisLabels.get(axis.id);
|
|
266
|
+
if (!group || !line || !disc || !label)
|
|
267
|
+
continue;
|
|
268
|
+
const length = lineLength * axis.scale;
|
|
269
|
+
const endX = center + axis.x * length;
|
|
270
|
+
const endY = center + axis.y * length;
|
|
271
|
+
line.setAttribute('x2', formatSvgNumber(endX));
|
|
272
|
+
line.setAttribute('y2', formatSvgNumber(endY));
|
|
273
|
+
line.setAttribute('stroke', axis.color);
|
|
274
|
+
disc.setAttribute('cx', formatSvgNumber(endX));
|
|
275
|
+
disc.setAttribute('cy', formatSvgNumber(endY));
|
|
276
|
+
disc.setAttribute('fill', axis.color);
|
|
277
|
+
label.setAttribute('x', formatSvgNumber(endX));
|
|
278
|
+
label.setAttribute('y', formatSvgNumber(endY));
|
|
279
|
+
label.textContent = axis.label;
|
|
280
|
+
group.style.opacity = String(0.72 + axis.scale * 0.28);
|
|
281
|
+
overlay.lineLayer.appendChild(line);
|
|
282
|
+
overlay.labelLayer.appendChild(group);
|
|
283
|
+
}
|
|
284
|
+
overlay.labelLayer.appendChild(overlay.centerDot);
|
|
285
|
+
}
|
|
286
|
+
function formatSvgNumber(value) {
|
|
287
|
+
return Number.isFinite(value) ? value.toFixed(2) : '0';
|
|
288
|
+
}
|
|
115
289
|
export function createLocalEditorBrowserUi(options = {}) {
|
|
116
290
|
const doc = options.document ?? document;
|
|
117
291
|
const root = options.root ?? doc.body;
|
|
@@ -123,6 +297,10 @@ export function createLocalEditorBrowserUi(options = {}) {
|
|
|
123
297
|
const contextMenu = createLocalEditorContextMenuController(doc, (open) => {
|
|
124
298
|
inputRouter.setContextMenuOpen(open);
|
|
125
299
|
}, activeTheme);
|
|
300
|
+
const tooltipSurfaces = new Set();
|
|
301
|
+
const tooltipController = createLocalEditorTooltipController(doc, root, activeTheme, {
|
|
302
|
+
scope: () => tooltipSurfaces,
|
|
303
|
+
});
|
|
126
304
|
let currentState = null;
|
|
127
305
|
let inspectorFilter = '';
|
|
128
306
|
const workbenchLayout = createDefaultLocalEditorWorkbenchLayout();
|
|
@@ -161,26 +339,29 @@ export function createLocalEditorBrowserUi(options = {}) {
|
|
|
161
339
|
hostChrome.appendChild(hostModeLabel);
|
|
162
340
|
hostChrome.appendChild(enterEditorButton);
|
|
163
341
|
root.appendChild(hostChrome);
|
|
342
|
+
tooltipSurfaces.add(hostChrome);
|
|
164
343
|
const dirtyBadge = doc.createElement('span');
|
|
344
|
+
dirtyBadge.dataset.editorDirtyBadge = 'true';
|
|
345
|
+
dirtyBadge.dataset.editorTooltip = '有未保存更改';
|
|
346
|
+
dirtyBadge.setAttribute('role', 'status');
|
|
347
|
+
dirtyBadge.setAttribute('aria-label', '有未保存更改');
|
|
165
348
|
dirtyBadge.style.cssText = [
|
|
166
349
|
'display:none',
|
|
167
|
-
'
|
|
350
|
+
'width:8px',
|
|
351
|
+
'height:8px',
|
|
168
352
|
'align-items:center',
|
|
169
|
-
'
|
|
353
|
+
'justify-content:center',
|
|
354
|
+
'padding:0',
|
|
170
355
|
'border-radius:999px',
|
|
171
|
-
'background:var(--fps-editor-danger-
|
|
356
|
+
'background:var(--fps-editor-danger-strong)',
|
|
172
357
|
'border:1px solid var(--fps-editor-danger-border)',
|
|
173
|
-
'
|
|
174
|
-
'font-size:11px',
|
|
175
|
-
'font-weight:800',
|
|
358
|
+
'box-shadow:0 0 0 3px var(--fps-editor-danger-soft)',
|
|
176
359
|
].join(';');
|
|
177
|
-
dirtyBadge.textContent = '未保存';
|
|
178
360
|
const saveButton = LocalEditorShared.createButton(doc, '保存场景', { icon: 'save' });
|
|
179
361
|
const saveAndRunButton = LocalEditorShared.createButton(doc, '保存并运行', { icon: 'execute' });
|
|
180
362
|
const discardRunButton = LocalEditorShared.createButton(doc, '放弃并运行', { icon: 'discard' });
|
|
181
|
-
const localTestButton =
|
|
363
|
+
const localTestButton = createToolbarIconButton(doc, '本地测试', 'execute', '打开本地测试操作');
|
|
182
364
|
localTestButton.dataset.editorLocalTestToggle = 'true';
|
|
183
|
-
localTestButton.title = '打开本地测试操作';
|
|
184
365
|
const localTestMenu = doc.createElement('div');
|
|
185
366
|
localTestMenu.classList.add(LOCAL_EDITOR_THEME_CLASS);
|
|
186
367
|
localTestMenu.dataset.editorLocalTestMenu = 'true';
|
|
@@ -210,20 +391,12 @@ export function createLocalEditorBrowserUi(options = {}) {
|
|
|
210
391
|
'gap:4px',
|
|
211
392
|
].join(';');
|
|
212
393
|
localTestGroup.appendChild(localTestButton);
|
|
213
|
-
const undoButton =
|
|
214
|
-
const redoButton =
|
|
394
|
+
const undoButton = createToolbarIconButton(doc, '撤销', 'undo', '撤销');
|
|
395
|
+
const redoButton = createToolbarIconButton(doc, '重做', 'redo', '重做');
|
|
215
396
|
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(';');
|
|
397
|
+
const editorStatusButton = createToolbarIconButton(doc, '编辑器状态', 'status', '编辑器状态');
|
|
398
|
+
editorStatusButton.dataset.editorStatusButton = 'true';
|
|
399
|
+
editorStatusButton.style.cursor = 'help';
|
|
227
400
|
const workbench = createLocalEditorWorkbench(doc);
|
|
228
401
|
const hierarchyPanel = createWorkbenchPanelContent(doc);
|
|
229
402
|
const assetPanel = workbench.bottomDock;
|
|
@@ -231,6 +404,7 @@ export function createLocalEditorBrowserUi(options = {}) {
|
|
|
231
404
|
workbench.leftDock.appendChild(hierarchyPanel);
|
|
232
405
|
workbench.rightDock.appendChild(inspectorPanel);
|
|
233
406
|
root.appendChild(workbench.root);
|
|
407
|
+
tooltipSurfaces.add(workbench.root);
|
|
234
408
|
const workbenchLayoutController = createLocalEditorWorkbenchLayoutController(doc, workbench);
|
|
235
409
|
const sceneToolOverlay = createSceneHeaderToolbar(doc);
|
|
236
410
|
const sceneTitle = doc.createElement('div');
|
|
@@ -268,14 +442,15 @@ export function createLocalEditorBrowserUi(options = {}) {
|
|
|
268
442
|
const toolButtons = new Map();
|
|
269
443
|
const transformToolDescriptors = Object.values(DEFAULT_EDITOR_TRANSFORM_TOOL_DESCRIPTORS);
|
|
270
444
|
for (const descriptor of transformToolDescriptors) {
|
|
271
|
-
const
|
|
272
|
-
const button = LocalEditorShared.createButton(doc,
|
|
445
|
+
const tooltip = createTransformToolTooltip(descriptor);
|
|
446
|
+
const button = LocalEditorShared.createButton(doc, descriptor.label, {
|
|
273
447
|
icon: TRANSFORM_TOOL_ICONS[descriptor.tool],
|
|
448
|
+
labelMode: 'icon-only',
|
|
449
|
+
variant: 'toolbar-icon',
|
|
450
|
+
tooltip,
|
|
451
|
+
ariaLabel: tooltip,
|
|
274
452
|
});
|
|
275
453
|
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
454
|
toolButtons.set(descriptor.tool, button);
|
|
280
455
|
toolGroup.appendChild(button);
|
|
281
456
|
}
|
|
@@ -288,21 +463,13 @@ export function createLocalEditorBrowserUi(options = {}) {
|
|
|
288
463
|
'border-left:1px solid var(--fps-editor-divider)',
|
|
289
464
|
].join(';');
|
|
290
465
|
const spaceButtons = {
|
|
291
|
-
world:
|
|
292
|
-
local:
|
|
466
|
+
world: createToolbarIconButton(doc, '世界', TRANSFORM_SPACE_ICONS.world, '世界坐标空间'),
|
|
467
|
+
local: createToolbarIconButton(doc, '本地', TRANSFORM_SPACE_ICONS.local, '本地坐标空间'),
|
|
293
468
|
};
|
|
294
469
|
for (const [space, button] of Object.entries(spaceButtons)) {
|
|
295
470
|
button.dataset.transformSpace = space;
|
|
296
471
|
spaceGroup.appendChild(button);
|
|
297
472
|
}
|
|
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
473
|
const snapGroup = doc.createElement('div');
|
|
307
474
|
snapGroup.style.cssText = [
|
|
308
475
|
'display:flex',
|
|
@@ -311,24 +478,71 @@ export function createLocalEditorBrowserUi(options = {}) {
|
|
|
311
478
|
'padding-left:8px',
|
|
312
479
|
'border-left:1px solid var(--fps-editor-divider)',
|
|
313
480
|
].join(';');
|
|
314
|
-
const snapButton =
|
|
481
|
+
const snapButton = createToolbarIconButton(doc, '吸附设置', 'snap', '吸附设置', 'settings');
|
|
315
482
|
snapButton.dataset.transformSnapToggle = 'true';
|
|
316
|
-
|
|
483
|
+
const snapSettingsPopover = doc.createElement('div');
|
|
484
|
+
snapSettingsPopover.classList.add(LOCAL_EDITOR_THEME_CLASS);
|
|
485
|
+
snapSettingsPopover.dataset.transformSnapSettingsPopover = 'true';
|
|
486
|
+
snapSettingsPopover.setAttribute('role', 'dialog');
|
|
487
|
+
snapSettingsPopover.setAttribute('aria-label', '吸附设置');
|
|
488
|
+
snapSettingsPopover.style.cssText = [
|
|
489
|
+
'position:fixed',
|
|
490
|
+
'z-index:2147483641',
|
|
491
|
+
'display:none',
|
|
492
|
+
'flex-direction:column',
|
|
493
|
+
'gap:8px',
|
|
494
|
+
'width:220px',
|
|
495
|
+
'padding:10px',
|
|
496
|
+
'border:1px solid var(--fps-editor-border)',
|
|
497
|
+
'border-radius:3px',
|
|
498
|
+
'background:var(--fps-editor-panel)',
|
|
499
|
+
'box-shadow:var(--fps-editor-shadow-popover)',
|
|
500
|
+
'pointer-events:auto',
|
|
501
|
+
].join(';');
|
|
502
|
+
const snapSettingsHeader = doc.createElement('label');
|
|
503
|
+
snapSettingsHeader.style.cssText = [
|
|
504
|
+
'display:flex',
|
|
505
|
+
'align-items:center',
|
|
506
|
+
'justify-content:space-between',
|
|
507
|
+
'gap:8px',
|
|
508
|
+
'color:var(--fps-editor-text)',
|
|
509
|
+
'font-size:12px',
|
|
510
|
+
'font-weight:900',
|
|
511
|
+
].join(';');
|
|
512
|
+
const snapSettingsHeaderText = doc.createElement('span');
|
|
513
|
+
snapSettingsHeaderText.textContent = '启用吸附';
|
|
514
|
+
const snapEnabledInput = doc.createElement('input');
|
|
515
|
+
snapEnabledInput.type = 'checkbox';
|
|
516
|
+
snapEnabledInput.dataset.transformSnapEnabled = 'true';
|
|
517
|
+
snapSettingsHeader.appendChild(snapSettingsHeaderText);
|
|
518
|
+
snapSettingsHeader.appendChild(snapEnabledInput);
|
|
519
|
+
snapSettingsPopover.appendChild(snapSettingsHeader);
|
|
317
520
|
const snapStepInputs = new Map();
|
|
318
|
-
for (const [kind, label,
|
|
319
|
-
['move', '
|
|
320
|
-
['rotate', '
|
|
321
|
-
['scale', '
|
|
521
|
+
for (const [kind, label, unit] of [
|
|
522
|
+
['move', '位移', '单位'],
|
|
523
|
+
['rotate', '旋转', '度'],
|
|
524
|
+
['scale', '缩放', '倍数'],
|
|
322
525
|
]) {
|
|
526
|
+
const row = doc.createElement('label');
|
|
527
|
+
row.style.cssText = [
|
|
528
|
+
'display:grid',
|
|
529
|
+
'grid-template-columns:54px 1fr 34px',
|
|
530
|
+
'align-items:center',
|
|
531
|
+
'gap:8px',
|
|
532
|
+
'color:var(--fps-editor-muted-strong)',
|
|
533
|
+
'font-size:11px',
|
|
534
|
+
'font-weight:800',
|
|
535
|
+
].join(';');
|
|
536
|
+
const rowLabel = doc.createElement('span');
|
|
537
|
+
rowLabel.textContent = label;
|
|
323
538
|
const input = doc.createElement('input');
|
|
324
539
|
input.type = 'number';
|
|
325
540
|
input.min = '0.0001';
|
|
326
541
|
input.step = kind === 'rotate' ? '1' : '0.1';
|
|
327
542
|
input.dataset.transformSnapStep = kind;
|
|
328
|
-
input.title = `${label} · ${title}`;
|
|
329
543
|
input.style.cssText = [
|
|
330
|
-
'width:46px',
|
|
331
544
|
'height:26px',
|
|
545
|
+
'min-width:0',
|
|
332
546
|
'box-sizing:border-box',
|
|
333
547
|
'border:1px solid var(--fps-editor-border)',
|
|
334
548
|
'border-radius:3px',
|
|
@@ -338,11 +552,16 @@ export function createLocalEditorBrowserUi(options = {}) {
|
|
|
338
552
|
'font-weight:800',
|
|
339
553
|
'padding:0 4px',
|
|
340
554
|
].join(';');
|
|
555
|
+
const rowUnit = doc.createElement('span');
|
|
556
|
+
rowUnit.textContent = unit;
|
|
557
|
+
rowUnit.style.cssText = 'color:var(--fps-editor-muted);font-size:10px;font-weight:800';
|
|
558
|
+
row.appendChild(rowLabel);
|
|
559
|
+
row.appendChild(input);
|
|
560
|
+
row.appendChild(rowUnit);
|
|
561
|
+
snapSettingsPopover.appendChild(row);
|
|
341
562
|
snapStepInputs.set(kind, input);
|
|
342
563
|
}
|
|
343
564
|
snapGroup.appendChild(snapButton);
|
|
344
|
-
for (const input of snapStepInputs.values())
|
|
345
|
-
snapGroup.appendChild(input);
|
|
346
565
|
const placementGroup = doc.createElement('div');
|
|
347
566
|
placementGroup.style.cssText = [
|
|
348
567
|
'display:flex',
|
|
@@ -351,22 +570,40 @@ export function createLocalEditorBrowserUi(options = {}) {
|
|
|
351
570
|
'padding-left:8px',
|
|
352
571
|
'border-left:1px solid var(--fps-editor-divider)',
|
|
353
572
|
].join(';');
|
|
354
|
-
const
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
573
|
+
const placementButton = createToolbarIconButton(doc, '放置模式', PLACEMENT_MODE_ICONS.off, '放置模式', 'settings');
|
|
574
|
+
placementButton.dataset.placementModeToggle = 'true';
|
|
575
|
+
const placementSettingsPopover = doc.createElement('div');
|
|
576
|
+
placementSettingsPopover.classList.add(LOCAL_EDITOR_THEME_CLASS);
|
|
577
|
+
placementSettingsPopover.dataset.placementSettingsPopover = 'true';
|
|
578
|
+
placementSettingsPopover.setAttribute('role', 'dialog');
|
|
579
|
+
placementSettingsPopover.setAttribute('aria-label', '放置模式');
|
|
580
|
+
placementSettingsPopover.style.cssText = [
|
|
581
|
+
'position:fixed',
|
|
582
|
+
'z-index:2147483641',
|
|
583
|
+
'display:none',
|
|
584
|
+
'flex-direction:column',
|
|
585
|
+
'gap:5px',
|
|
586
|
+
'width:180px',
|
|
587
|
+
'padding:8px',
|
|
588
|
+
'border:1px solid var(--fps-editor-border)',
|
|
589
|
+
'border-radius:3px',
|
|
590
|
+
'background:var(--fps-editor-panel)',
|
|
591
|
+
'box-shadow:var(--fps-editor-shadow-popover)',
|
|
592
|
+
'pointer-events:auto',
|
|
593
|
+
].join(';');
|
|
594
|
+
const placementButtons = Object.fromEntries(Object.keys(PLACEMENT_MODE_ICONS).map((mode) => {
|
|
595
|
+
const button = LocalEditorShared.createButton(doc, PLACEMENT_MODE_DESCRIPTIONS[mode], {
|
|
596
|
+
icon: PLACEMENT_MODE_ICONS[mode],
|
|
597
|
+
tooltip: PLACEMENT_MODE_DESCRIPTIONS[mode],
|
|
598
|
+
ariaLabel: PLACEMENT_MODE_DESCRIPTIONS[mode],
|
|
599
|
+
});
|
|
367
600
|
button.dataset.placementMode = mode;
|
|
368
|
-
|
|
369
|
-
|
|
601
|
+
button.style.justifyContent = 'flex-start';
|
|
602
|
+
button.style.width = '100%';
|
|
603
|
+
placementSettingsPopover.appendChild(button);
|
|
604
|
+
return [mode, button];
|
|
605
|
+
}));
|
|
606
|
+
placementGroup.appendChild(placementButton);
|
|
370
607
|
const actionGroup = doc.createElement('div');
|
|
371
608
|
actionGroup.style.cssText = [
|
|
372
609
|
'display:flex',
|
|
@@ -375,70 +612,73 @@ export function createLocalEditorBrowserUi(options = {}) {
|
|
|
375
612
|
'padding-left:8px',
|
|
376
613
|
'border-left:1px solid var(--fps-editor-divider)',
|
|
377
614
|
].join(';');
|
|
378
|
-
const
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
615
|
+
const transformActionButton = createToolbarIconButton(doc, '对齐与分布', 'arrange', '对齐与分布', 'settings');
|
|
616
|
+
transformActionButton.dataset.transformActionToggle = 'true';
|
|
617
|
+
const transformActionPopover = doc.createElement('div');
|
|
618
|
+
transformActionPopover.classList.add(LOCAL_EDITOR_THEME_CLASS);
|
|
619
|
+
transformActionPopover.dataset.transformActionPopover = 'true';
|
|
620
|
+
transformActionPopover.setAttribute('role', 'dialog');
|
|
621
|
+
transformActionPopover.setAttribute('aria-label', '对齐与分布');
|
|
622
|
+
transformActionPopover.style.cssText = [
|
|
623
|
+
'position:fixed',
|
|
624
|
+
'z-index:2147483641',
|
|
625
|
+
'display:none',
|
|
626
|
+
'flex-direction:column',
|
|
627
|
+
'gap:9px',
|
|
628
|
+
'width:236px',
|
|
629
|
+
'padding:10px',
|
|
383
630
|
'border:1px solid var(--fps-editor-border)',
|
|
384
631
|
'border-radius:3px',
|
|
385
|
-
'background:var(--fps-editor-
|
|
386
|
-
'
|
|
387
|
-
'
|
|
388
|
-
'font-weight:800',
|
|
389
|
-
'padding:0 4px',
|
|
632
|
+
'background:var(--fps-editor-panel)',
|
|
633
|
+
'box-shadow:var(--fps-editor-shadow-popover)',
|
|
634
|
+
'pointer-events:auto',
|
|
390
635
|
].join(';');
|
|
391
|
-
|
|
392
|
-
for (const
|
|
393
|
-
'
|
|
394
|
-
'
|
|
395
|
-
'
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
636
|
+
const transformActionButtons = new Map();
|
|
637
|
+
for (const group of TRANSFORM_ACTION_GROUPS) {
|
|
638
|
+
const section = doc.createElement('section');
|
|
639
|
+
section.style.cssText = 'display:flex;flex-direction:column;gap:5px';
|
|
640
|
+
const heading = doc.createElement('div');
|
|
641
|
+
heading.textContent = group.title;
|
|
642
|
+
heading.style.cssText = [
|
|
643
|
+
'color:var(--fps-editor-text-strong)',
|
|
644
|
+
'font-size:11px',
|
|
645
|
+
'font-weight:900',
|
|
646
|
+
].join(';');
|
|
647
|
+
const hint = doc.createElement('div');
|
|
648
|
+
hint.textContent = group.hint;
|
|
649
|
+
hint.style.cssText = [
|
|
650
|
+
'color:var(--fps-editor-muted)',
|
|
651
|
+
'font-size:10px',
|
|
652
|
+
'font-weight:700',
|
|
653
|
+
'margin-top:-3px',
|
|
654
|
+
].join(';');
|
|
655
|
+
section.appendChild(heading);
|
|
656
|
+
section.appendChild(hint);
|
|
657
|
+
for (const action of group.actions) {
|
|
658
|
+
const button = LocalEditorShared.createButton(doc, TRANSFORM_ACTION_LABELS[action], {
|
|
659
|
+
icon: 'arrange',
|
|
660
|
+
tooltip: TRANSFORM_ACTION_LABELS[action],
|
|
661
|
+
ariaLabel: TRANSFORM_ACTION_LABELS[action],
|
|
662
|
+
});
|
|
663
|
+
button.dataset.transformAction = action;
|
|
664
|
+
button.style.justifyContent = 'flex-start';
|
|
665
|
+
button.style.width = '100%';
|
|
666
|
+
transformActionButtons.set(action, button);
|
|
667
|
+
section.appendChild(button);
|
|
668
|
+
}
|
|
669
|
+
transformActionPopover.appendChild(section);
|
|
405
670
|
}
|
|
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
671
|
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' });
|
|
672
|
+
const themeToggleButton = createToolbarIconButton(doc, '主题', 'theme', '切换主题');
|
|
434
673
|
themeToggleButton.dataset.editorThemeToggle = 'true';
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
sceneHelpButton
|
|
674
|
+
const gridToggleButton = createToolbarIconButton(doc, '网格', 'grid', '显示 / 隐藏 Scene View 网格');
|
|
675
|
+
gridToggleButton.dataset.editorGridToggle = 'true';
|
|
676
|
+
const sceneHelpButton = createToolbarIconButton(doc, '快捷键', 'help', '快捷键与操作说明');
|
|
438
677
|
sceneQuickActions.appendChild(localTestGroup);
|
|
439
678
|
sceneQuickActions.appendChild(undoButton);
|
|
440
679
|
sceneQuickActions.appendChild(redoButton);
|
|
441
680
|
sceneQuickActions.appendChild(dirtyBadge);
|
|
681
|
+
sceneUtilityActions.appendChild(gridToggleButton);
|
|
442
682
|
sceneUtilityActions.appendChild(themeToggleButton);
|
|
443
683
|
sceneUtilityActions.appendChild(sceneHelpButton);
|
|
444
684
|
const cameraPreviewGroup = doc.createElement('div');
|
|
@@ -449,13 +689,11 @@ export function createLocalEditorBrowserUi(options = {}) {
|
|
|
449
689
|
'padding-left:8px',
|
|
450
690
|
'border-left:1px solid var(--fps-editor-divider)',
|
|
451
691
|
].join(';');
|
|
452
|
-
const sceneCameraButton =
|
|
692
|
+
const sceneCameraButton = createToolbarIconButton(doc, '从 Main Camera 查看场景', 'camera');
|
|
453
693
|
sceneCameraButton.dataset.sceneCameraPreviewToggle = 'true';
|
|
454
|
-
sceneCameraButton.title = '从 Main Camera 查看当前场景';
|
|
455
694
|
cameraPreviewGroup.appendChild(sceneCameraButton);
|
|
456
|
-
const toolbarOverflowButton =
|
|
695
|
+
const toolbarOverflowButton = createToolbarIconButton(doc, '更多', 'chevron-down', '显示隐藏的工具栏命令');
|
|
457
696
|
toolbarOverflowButton.dataset.editorToolbarOverflowToggle = 'true';
|
|
458
|
-
toolbarOverflowButton.title = '显示隐藏的工具栏命令';
|
|
459
697
|
toolbarOverflowButton.style.display = 'none';
|
|
460
698
|
const toolbarOverflowMenu = doc.createElement('div');
|
|
461
699
|
toolbarOverflowMenu.classList.add(LOCAL_EDITOR_THEME_CLASS);
|
|
@@ -477,23 +715,30 @@ export function createLocalEditorBrowserUi(options = {}) {
|
|
|
477
715
|
'box-shadow:var(--fps-editor-shadow-popover)',
|
|
478
716
|
'pointer-events:auto',
|
|
479
717
|
].join(';');
|
|
718
|
+
const coordinateAxesOverlay = createCoordinateAxesOverlay(doc);
|
|
480
719
|
sceneToolOverlay.appendChild(sceneTitle);
|
|
481
720
|
sceneToolOverlay.appendChild(sceneQuickActions);
|
|
482
721
|
sceneToolOverlay.appendChild(sceneUtilityActions);
|
|
483
722
|
sceneToolOverlay.appendChild(cameraPreviewGroup);
|
|
484
723
|
sceneToolOverlay.appendChild(toolGroup);
|
|
485
724
|
sceneToolOverlay.appendChild(spaceGroup);
|
|
486
|
-
sceneToolOverlay.appendChild(handleGroup);
|
|
487
725
|
sceneToolOverlay.appendChild(snapGroup);
|
|
488
726
|
sceneToolOverlay.appendChild(placementGroup);
|
|
489
727
|
sceneToolOverlay.appendChild(actionGroup);
|
|
490
|
-
sceneToolOverlay.appendChild(
|
|
491
|
-
sceneToolOverlay.appendChild(sceneMouseHint);
|
|
492
|
-
sceneToolOverlay.appendChild(status);
|
|
728
|
+
sceneToolOverlay.appendChild(editorStatusButton);
|
|
493
729
|
sceneToolOverlay.appendChild(toolbarOverflowButton);
|
|
494
730
|
workbench.sceneHeader.appendChild(sceneToolOverlay);
|
|
731
|
+
workbench.sceneFrame.appendChild(coordinateAxesOverlay.root);
|
|
495
732
|
root.appendChild(localTestMenu);
|
|
496
733
|
root.appendChild(toolbarOverflowMenu);
|
|
734
|
+
root.appendChild(snapSettingsPopover);
|
|
735
|
+
root.appendChild(placementSettingsPopover);
|
|
736
|
+
root.appendChild(transformActionPopover);
|
|
737
|
+
tooltipSurfaces.add(localTestMenu);
|
|
738
|
+
tooltipSurfaces.add(toolbarOverflowMenu);
|
|
739
|
+
tooltipSurfaces.add(snapSettingsPopover);
|
|
740
|
+
tooltipSurfaces.add(placementSettingsPopover);
|
|
741
|
+
tooltipSurfaces.add(transformActionPopover);
|
|
497
742
|
const boxSelectionOverlay = doc.createElement('div');
|
|
498
743
|
boxSelectionOverlay.classList.add(LOCAL_EDITOR_THEME_CLASS);
|
|
499
744
|
boxSelectionOverlay.style.cssText = [
|
|
@@ -506,9 +751,11 @@ export function createLocalEditorBrowserUi(options = {}) {
|
|
|
506
751
|
'pointer-events:none',
|
|
507
752
|
].join(';');
|
|
508
753
|
root.appendChild(boxSelectionOverlay);
|
|
754
|
+
tooltipSurfaces.add(boxSelectionOverlay);
|
|
509
755
|
const shortcutHelpPanel = createLocalEditorShortcutHelpPanel(doc);
|
|
510
756
|
shortcutHelpPanel.classList.add(LOCAL_EDITOR_THEME_CLASS);
|
|
511
757
|
root.appendChild(shortcutHelpPanel);
|
|
758
|
+
tooltipSurfaces.add(shortcutHelpPanel);
|
|
512
759
|
function applyThemeToSurfaces() {
|
|
513
760
|
applyLocalEditorTheme(hostChrome, activeTheme);
|
|
514
761
|
applyLocalEditorTheme(workbench.root, activeTheme);
|
|
@@ -516,13 +763,17 @@ export function createLocalEditorBrowserUi(options = {}) {
|
|
|
516
763
|
applyLocalEditorTheme(boxSelectionOverlay, activeTheme);
|
|
517
764
|
applyLocalEditorTheme(localTestMenu, activeTheme);
|
|
518
765
|
applyLocalEditorTheme(toolbarOverflowMenu, activeTheme);
|
|
766
|
+
applyLocalEditorTheme(snapSettingsPopover, activeTheme);
|
|
767
|
+
applyLocalEditorTheme(placementSettingsPopover, activeTheme);
|
|
768
|
+
applyLocalEditorTheme(transformActionPopover, activeTheme);
|
|
769
|
+
tooltipController.setTheme(activeTheme);
|
|
519
770
|
contextMenu.setTheme?.(activeTheme);
|
|
520
771
|
}
|
|
521
772
|
function updateThemeToggleButton() {
|
|
522
773
|
const nextTheme = activeTheme === 'dark' ? 'light' : 'dark';
|
|
523
774
|
themeToggleButton.dataset.editorTheme = activeTheme;
|
|
524
775
|
themeToggleButton.setAttribute('aria-pressed', activeTheme === 'light' ? 'true' : 'false');
|
|
525
|
-
themeToggleButton
|
|
776
|
+
setToolbarButtonTooltip(themeToggleButton, nextTheme === 'light' ? '切换为浅色主题' : '切换为深色主题');
|
|
526
777
|
}
|
|
527
778
|
function setActiveTheme(theme) {
|
|
528
779
|
activeTheme = normalizeLocalEditorThemeName(theme);
|
|
@@ -530,15 +781,43 @@ export function createLocalEditorBrowserUi(options = {}) {
|
|
|
530
781
|
updateThemeToggleButton();
|
|
531
782
|
}
|
|
532
783
|
let localTestMenuOpen = false;
|
|
784
|
+
let snapSettingsOpen = false;
|
|
785
|
+
let placementSettingsOpen = false;
|
|
786
|
+
let transformActionOpen = false;
|
|
533
787
|
function closeLocalTestMenu() {
|
|
534
788
|
localTestMenuOpen = false;
|
|
535
789
|
localTestMenu.style.display = 'none';
|
|
536
790
|
LocalEditorShared.applyButtonActiveState(localTestButton, false);
|
|
537
791
|
}
|
|
792
|
+
function closeSnapSettingsPopover() {
|
|
793
|
+
snapSettingsOpen = false;
|
|
794
|
+
snapSettingsPopover.style.display = 'none';
|
|
795
|
+
snapButton.setAttribute('aria-expanded', 'false');
|
|
796
|
+
const enabled = currentState?.transformOperations?.settings.snap.enabled
|
|
797
|
+
?? DEFAULT_EDITOR_TRANSFORM_OPERATION_SETTINGS.snap.enabled;
|
|
798
|
+
LocalEditorShared.applyButtonActiveState(snapButton, enabled);
|
|
799
|
+
}
|
|
800
|
+
function closePlacementSettingsPopover() {
|
|
801
|
+
placementSettingsOpen = false;
|
|
802
|
+
placementSettingsPopover.style.display = 'none';
|
|
803
|
+
placementButton.setAttribute('aria-expanded', 'false');
|
|
804
|
+
const mode = currentState?.transformOperations?.settings.placementMode
|
|
805
|
+
?? DEFAULT_EDITOR_TRANSFORM_OPERATION_SETTINGS.placementMode;
|
|
806
|
+
LocalEditorShared.applyButtonActiveState(placementButton, mode !== 'off');
|
|
807
|
+
}
|
|
808
|
+
function closeTransformActionPopover() {
|
|
809
|
+
transformActionOpen = false;
|
|
810
|
+
transformActionPopover.style.display = 'none';
|
|
811
|
+
transformActionButton.setAttribute('aria-expanded', 'false');
|
|
812
|
+
LocalEditorShared.applyButtonActiveState(transformActionButton, false);
|
|
813
|
+
}
|
|
538
814
|
function openLocalTestMenu() {
|
|
539
815
|
if (!localTestActionsEnabled || localTestButton.style.display === 'none')
|
|
540
816
|
return;
|
|
541
817
|
closeToolbarOverflowMenu();
|
|
818
|
+
closeSnapSettingsPopover();
|
|
819
|
+
closePlacementSettingsPopover();
|
|
820
|
+
closeTransformActionPopover();
|
|
542
821
|
const win = doc.defaultView;
|
|
543
822
|
const rect = localTestButton.getBoundingClientRect();
|
|
544
823
|
const viewportWidth = win?.innerWidth ?? doc.documentElement.clientWidth;
|
|
@@ -548,6 +827,78 @@ export function createLocalEditorBrowserUi(options = {}) {
|
|
|
548
827
|
localTestMenuOpen = true;
|
|
549
828
|
LocalEditorShared.applyButtonActiveState(localTestButton, true);
|
|
550
829
|
}
|
|
830
|
+
function openSnapSettingsPopover() {
|
|
831
|
+
if (snapButton.disabled || snapGroup.style.display === 'none')
|
|
832
|
+
return;
|
|
833
|
+
closeToolbarOverflowMenu();
|
|
834
|
+
closeLocalTestMenu();
|
|
835
|
+
closePlacementSettingsPopover();
|
|
836
|
+
closeTransformActionPopover();
|
|
837
|
+
const win = doc.defaultView;
|
|
838
|
+
const rect = snapButton.getBoundingClientRect();
|
|
839
|
+
const viewportWidth = win?.innerWidth ?? doc.documentElement.clientWidth;
|
|
840
|
+
snapSettingsPopover.style.display = 'flex';
|
|
841
|
+
const popoverWidth = snapSettingsPopover.offsetWidth || 220;
|
|
842
|
+
snapSettingsPopover.style.top = `${Math.max(8, rect.bottom + 5)}px`;
|
|
843
|
+
snapSettingsPopover.style.left = `${Math.max(8, Math.min(rect.left, viewportWidth - popoverWidth - 8))}px`;
|
|
844
|
+
snapSettingsOpen = true;
|
|
845
|
+
snapButton.setAttribute('aria-expanded', 'true');
|
|
846
|
+
LocalEditorShared.applyButtonActiveState(snapButton, true);
|
|
847
|
+
}
|
|
848
|
+
function toggleSnapSettingsPopover() {
|
|
849
|
+
if (snapSettingsOpen)
|
|
850
|
+
closeSnapSettingsPopover();
|
|
851
|
+
else
|
|
852
|
+
openSnapSettingsPopover();
|
|
853
|
+
}
|
|
854
|
+
function openPlacementSettingsPopover() {
|
|
855
|
+
if (placementButton.disabled || placementGroup.style.display === 'none')
|
|
856
|
+
return;
|
|
857
|
+
closeToolbarOverflowMenu();
|
|
858
|
+
closeLocalTestMenu();
|
|
859
|
+
closeSnapSettingsPopover();
|
|
860
|
+
closeTransformActionPopover();
|
|
861
|
+
const win = doc.defaultView;
|
|
862
|
+
const rect = placementButton.getBoundingClientRect();
|
|
863
|
+
const viewportWidth = win?.innerWidth ?? doc.documentElement.clientWidth;
|
|
864
|
+
placementSettingsPopover.style.display = 'flex';
|
|
865
|
+
const popoverWidth = placementSettingsPopover.offsetWidth || 180;
|
|
866
|
+
placementSettingsPopover.style.top = `${Math.max(8, rect.bottom + 5)}px`;
|
|
867
|
+
placementSettingsPopover.style.left = `${Math.max(8, Math.min(rect.left, viewportWidth - popoverWidth - 8))}px`;
|
|
868
|
+
placementSettingsOpen = true;
|
|
869
|
+
placementButton.setAttribute('aria-expanded', 'true');
|
|
870
|
+
LocalEditorShared.applyButtonActiveState(placementButton, true);
|
|
871
|
+
}
|
|
872
|
+
function togglePlacementSettingsPopover() {
|
|
873
|
+
if (placementSettingsOpen)
|
|
874
|
+
closePlacementSettingsPopover();
|
|
875
|
+
else
|
|
876
|
+
openPlacementSettingsPopover();
|
|
877
|
+
}
|
|
878
|
+
function openTransformActionPopover() {
|
|
879
|
+
if (transformActionButton.disabled || actionGroup.style.display === 'none')
|
|
880
|
+
return;
|
|
881
|
+
closeToolbarOverflowMenu();
|
|
882
|
+
closeLocalTestMenu();
|
|
883
|
+
closeSnapSettingsPopover();
|
|
884
|
+
closePlacementSettingsPopover();
|
|
885
|
+
const win = doc.defaultView;
|
|
886
|
+
const rect = transformActionButton.getBoundingClientRect();
|
|
887
|
+
const viewportWidth = win?.innerWidth ?? doc.documentElement.clientWidth;
|
|
888
|
+
transformActionPopover.style.display = 'flex';
|
|
889
|
+
const popoverWidth = transformActionPopover.offsetWidth || 236;
|
|
890
|
+
transformActionPopover.style.top = `${Math.max(8, rect.bottom + 5)}px`;
|
|
891
|
+
transformActionPopover.style.left = `${Math.max(8, Math.min(rect.left, viewportWidth - popoverWidth - 8))}px`;
|
|
892
|
+
transformActionOpen = true;
|
|
893
|
+
transformActionButton.setAttribute('aria-expanded', 'true');
|
|
894
|
+
LocalEditorShared.applyButtonActiveState(transformActionButton, true);
|
|
895
|
+
}
|
|
896
|
+
function toggleTransformActionPopover() {
|
|
897
|
+
if (transformActionOpen)
|
|
898
|
+
closeTransformActionPopover();
|
|
899
|
+
else
|
|
900
|
+
openTransformActionPopover();
|
|
901
|
+
}
|
|
551
902
|
const onLocalTestButtonClick = (event) => {
|
|
552
903
|
event.stopPropagation();
|
|
553
904
|
if (localTestMenuOpen)
|
|
@@ -579,23 +930,17 @@ export function createLocalEditorBrowserUi(options = {}) {
|
|
|
579
930
|
cameraPreviewGroup,
|
|
580
931
|
toolGroup,
|
|
581
932
|
spaceGroup,
|
|
582
|
-
handleGroup,
|
|
583
933
|
snapGroup,
|
|
584
934
|
placementGroup,
|
|
585
935
|
actionGroup,
|
|
586
|
-
|
|
587
|
-
sceneMouseHint,
|
|
588
|
-
status,
|
|
936
|
+
editorStatusButton,
|
|
589
937
|
toolbarOverflowButton,
|
|
590
938
|
];
|
|
591
939
|
const toolbarOverflowItems = [
|
|
592
|
-
{ id: 'status', element:
|
|
593
|
-
{ id: 'mouse-hint', element: sceneMouseHint, kind: 'text' },
|
|
594
|
-
{ id: 'tool-status', element: sceneToolStatus, kind: 'text' },
|
|
940
|
+
{ id: 'editor-status', element: editorStatusButton, kind: 'group' },
|
|
595
941
|
{ id: 'transform-actions', element: actionGroup, kind: 'group' },
|
|
596
942
|
{ id: 'placement', element: placementGroup, kind: 'group' },
|
|
597
943
|
{ id: 'snap', element: snapGroup, kind: 'group' },
|
|
598
|
-
{ id: 'handles', element: handleGroup, kind: 'group' },
|
|
599
944
|
{ id: 'space', element: spaceGroup, kind: 'group' },
|
|
600
945
|
{ id: 'camera-preview', element: cameraPreviewGroup, kind: 'group' },
|
|
601
946
|
{ id: 'scene-utilities', element: sceneUtilityActions, kind: 'group' },
|
|
@@ -636,6 +981,8 @@ export function createLocalEditorBrowserUi(options = {}) {
|
|
|
636
981
|
item.element.style.width = '100%';
|
|
637
982
|
}
|
|
638
983
|
function setToolbarButtonCompact(button, compact) {
|
|
984
|
+
if (button.dataset.editorButtonLabelMode === 'icon-only')
|
|
985
|
+
return;
|
|
639
986
|
const label = button.querySelector('[data-editor-button-label]');
|
|
640
987
|
if (!label)
|
|
641
988
|
return;
|
|
@@ -668,6 +1015,9 @@ export function createLocalEditorBrowserUi(options = {}) {
|
|
|
668
1015
|
if (toolbarOverflowMenu.childElementCount === 0)
|
|
669
1016
|
return;
|
|
670
1017
|
closeLocalTestMenu();
|
|
1018
|
+
closeSnapSettingsPopover();
|
|
1019
|
+
closePlacementSettingsPopover();
|
|
1020
|
+
closeTransformActionPopover();
|
|
671
1021
|
const win = doc.defaultView;
|
|
672
1022
|
const rect = toolbarOverflowButton.getBoundingClientRect();
|
|
673
1023
|
const viewportWidth = win?.innerWidth ?? doc.documentElement.clientWidth;
|
|
@@ -744,8 +1094,17 @@ export function createLocalEditorBrowserUi(options = {}) {
|
|
|
744
1094
|
return;
|
|
745
1095
|
if (target.closest('[data-editor-toolbar-overflow-menu]') || target.closest('[data-editor-toolbar-overflow-toggle]'))
|
|
746
1096
|
return;
|
|
1097
|
+
if (target.closest('[data-transform-snap-settings-popover]') || target.closest('[data-transform-snap-toggle]'))
|
|
1098
|
+
return;
|
|
1099
|
+
if (target.closest('[data-placement-settings-popover]') || target.closest('[data-placement-mode-toggle]'))
|
|
1100
|
+
return;
|
|
1101
|
+
if (target.closest('[data-transform-action-popover]') || target.closest('[data-transform-action-toggle]'))
|
|
1102
|
+
return;
|
|
747
1103
|
closeLocalTestMenu();
|
|
748
1104
|
closeToolbarOverflowMenu();
|
|
1105
|
+
closeSnapSettingsPopover();
|
|
1106
|
+
closePlacementSettingsPopover();
|
|
1107
|
+
closeTransformActionPopover();
|
|
749
1108
|
};
|
|
750
1109
|
const ResizeObserverCtor = doc.defaultView?.ResizeObserver;
|
|
751
1110
|
const toolbarResizeObserver = ResizeObserverCtor
|
|
@@ -776,6 +1135,10 @@ export function createLocalEditorBrowserUi(options = {}) {
|
|
|
776
1135
|
});
|
|
777
1136
|
undoButton.addEventListener('click', () => callbacks.onUndo?.());
|
|
778
1137
|
redoButton.addEventListener('click', () => callbacks.onRedo?.());
|
|
1138
|
+
gridToggleButton.addEventListener('click', () => {
|
|
1139
|
+
const visible = currentState?.grid?.visible ?? true;
|
|
1140
|
+
callbacks.onGridVisibleChange?.(!visible);
|
|
1141
|
+
});
|
|
779
1142
|
sceneCameraButton.addEventListener('click', () => {
|
|
780
1143
|
const enabled = currentState?.sceneCameraPreview?.enabled ?? false;
|
|
781
1144
|
callbacks.onSceneCameraPreviewToggle?.(!enabled);
|
|
@@ -812,12 +1175,18 @@ export function createLocalEditorBrowserUi(options = {}) {
|
|
|
812
1175
|
if (space)
|
|
813
1176
|
callbacks.onTransformSpaceChange?.(space);
|
|
814
1177
|
});
|
|
815
|
-
snapButton.addEventListener('click', () => {
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
callbacks.onTransformSnapEnabledChange?.(!enabled);
|
|
1178
|
+
snapButton.addEventListener('click', (event) => {
|
|
1179
|
+
event.stopPropagation();
|
|
1180
|
+
toggleSnapSettingsPopover();
|
|
819
1181
|
});
|
|
820
|
-
|
|
1182
|
+
snapSettingsPopover.addEventListener('change', (event) => {
|
|
1183
|
+
const enabledInput = event.target instanceof HTMLInputElement
|
|
1184
|
+
? event.target.closest('[data-transform-snap-enabled]')
|
|
1185
|
+
: null;
|
|
1186
|
+
if (enabledInput) {
|
|
1187
|
+
callbacks.onTransformSnapEnabledChange?.(enabledInput.checked);
|
|
1188
|
+
return;
|
|
1189
|
+
}
|
|
821
1190
|
const input = event.target instanceof HTMLInputElement ? event.target : null;
|
|
822
1191
|
const kind = input?.dataset.transformSnapStep;
|
|
823
1192
|
if (!input || !kind)
|
|
@@ -831,18 +1200,31 @@ export function createLocalEditorBrowserUi(options = {}) {
|
|
|
831
1200
|
callbacks.onTransformSnapStepChange?.({ kind, value });
|
|
832
1201
|
});
|
|
833
1202
|
placementGroup.addEventListener('click', (event) => {
|
|
1203
|
+
event.stopPropagation();
|
|
1204
|
+
togglePlacementSettingsPopover();
|
|
1205
|
+
});
|
|
1206
|
+
placementSettingsPopover.addEventListener('click', (event) => {
|
|
834
1207
|
const target = event.target instanceof HTMLElement ? event.target : null;
|
|
835
1208
|
const mode = target?.closest('[data-placement-mode]')?.dataset.placementMode;
|
|
836
|
-
if (mode)
|
|
837
|
-
|
|
1209
|
+
if (!mode)
|
|
1210
|
+
return;
|
|
1211
|
+
callbacks.onPlacementModeChange?.(mode);
|
|
1212
|
+
closePlacementSettingsPopover();
|
|
838
1213
|
});
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
render(currentState);
|
|
1214
|
+
actionGroup.addEventListener('click', (event) => {
|
|
1215
|
+
event.stopPropagation();
|
|
1216
|
+
toggleTransformActionPopover();
|
|
843
1217
|
});
|
|
844
|
-
|
|
845
|
-
|
|
1218
|
+
transformActionPopover.addEventListener('click', (event) => {
|
|
1219
|
+
const target = event.target instanceof HTMLElement ? event.target : null;
|
|
1220
|
+
const action = target?.closest('[data-transform-action]')?.dataset.transformAction;
|
|
1221
|
+
if (!action)
|
|
1222
|
+
return;
|
|
1223
|
+
const transformOperations = currentState?.transformOperations;
|
|
1224
|
+
if (!transformOperations || !isTransformActionEnabled(action, transformOperations))
|
|
1225
|
+
return;
|
|
1226
|
+
callbacks.onTransformAction?.(action);
|
|
1227
|
+
closeTransformActionPopover();
|
|
846
1228
|
});
|
|
847
1229
|
assetPanel.addEventListener('click', (event) => {
|
|
848
1230
|
const target = event.target instanceof HTMLElement ? event.target : null;
|
|
@@ -930,6 +1312,21 @@ export function createLocalEditorBrowserUi(options = {}) {
|
|
|
930
1312
|
if (currentState?.mode !== 'editor' || currentState.busy)
|
|
931
1313
|
return;
|
|
932
1314
|
const key = event.key.toLowerCase();
|
|
1315
|
+
if (key === 'escape' && snapSettingsOpen) {
|
|
1316
|
+
event.preventDefault();
|
|
1317
|
+
closeSnapSettingsPopover();
|
|
1318
|
+
return;
|
|
1319
|
+
}
|
|
1320
|
+
if (key === 'escape' && placementSettingsOpen) {
|
|
1321
|
+
event.preventDefault();
|
|
1322
|
+
closePlacementSettingsPopover();
|
|
1323
|
+
return;
|
|
1324
|
+
}
|
|
1325
|
+
if (key === 'escape' && transformActionOpen) {
|
|
1326
|
+
event.preventDefault();
|
|
1327
|
+
closeTransformActionPopover();
|
|
1328
|
+
return;
|
|
1329
|
+
}
|
|
933
1330
|
const primaryModifier = event.metaKey || event.ctrlKey;
|
|
934
1331
|
const handleDocumentShortcut = inputRouter.shouldHandleDocumentShortcut(event);
|
|
935
1332
|
const handleGlobalShortcut = inputRouter.shouldHandleGlobalShortcut(event);
|
|
@@ -975,7 +1372,10 @@ export function createLocalEditorBrowserUi(options = {}) {
|
|
|
975
1372
|
}
|
|
976
1373
|
if (key === 'escape') {
|
|
977
1374
|
event.preventDefault();
|
|
978
|
-
callbacks.
|
|
1375
|
+
if (callbacks.onCancelEditorIntent)
|
|
1376
|
+
callbacks.onCancelEditorIntent();
|
|
1377
|
+
else
|
|
1378
|
+
callbacks.onCancelActiveOperation?.();
|
|
979
1379
|
}
|
|
980
1380
|
};
|
|
981
1381
|
doc.addEventListener('keydown', onKeyDown);
|
|
@@ -1028,33 +1428,6 @@ export function createLocalEditorBrowserUi(options = {}) {
|
|
|
1028
1428
|
? CSS.escape(value)
|
|
1029
1429
|
: value.replace(/["\\]/g, '\\$&');
|
|
1030
1430
|
}
|
|
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
1431
|
function formatSnapStepInputValue(snap, kind) {
|
|
1059
1432
|
const value = kind === 'move'
|
|
1060
1433
|
? snap.moveStep
|
|
@@ -1077,7 +1450,7 @@ export function createLocalEditorBrowserUi(options = {}) {
|
|
|
1077
1450
|
contextMenu.close();
|
|
1078
1451
|
hostChrome.style.display = !inEditor && localTestActionsEnabled ? 'flex' : 'none';
|
|
1079
1452
|
enterEditorButton.disabled = disabled;
|
|
1080
|
-
for (const button of [saveButton, saveAndRunButton, discardRunButton, undoButton, redoButton, sceneHelpButton, sceneCameraButton]) {
|
|
1453
|
+
for (const button of [saveButton, saveAndRunButton, discardRunButton, undoButton, redoButton, sceneHelpButton, sceneCameraButton, gridToggleButton]) {
|
|
1081
1454
|
button.style.display = 'inline-flex';
|
|
1082
1455
|
button.disabled = disabled;
|
|
1083
1456
|
}
|
|
@@ -1091,13 +1464,24 @@ export function createLocalEditorBrowserUi(options = {}) {
|
|
|
1091
1464
|
const transformTool = state.transformTool ?? null;
|
|
1092
1465
|
workbench.root.style.display = inEditor ? '' : 'none';
|
|
1093
1466
|
sceneToolOverlay.style.display = inEditor ? 'flex' : 'none';
|
|
1467
|
+
if (!inEditor)
|
|
1468
|
+
closeSnapSettingsPopover();
|
|
1469
|
+
if (!inEditor)
|
|
1470
|
+
closePlacementSettingsPopover();
|
|
1471
|
+
if (!inEditor)
|
|
1472
|
+
closeTransformActionPopover();
|
|
1094
1473
|
const sceneCameraPreview = state.sceneCameraPreview ?? { enabled: false, available: false };
|
|
1095
1474
|
cameraPreviewGroup.style.display = inEditor ? 'flex' : 'none';
|
|
1096
1475
|
sceneCameraButton.disabled = disabled || !sceneCameraPreview.available;
|
|
1097
|
-
|
|
1476
|
+
const sceneCameraTooltip = sceneCameraPreview.available
|
|
1098
1477
|
? '从 Main Camera 查看当前场景'
|
|
1099
1478
|
: '当前场景没有可预览的 Main Camera';
|
|
1479
|
+
setToolbarButtonTooltip(sceneCameraButton, sceneCameraTooltip);
|
|
1100
1480
|
LocalEditorShared.applyButtonActiveState(sceneCameraButton, sceneCameraPreview.enabled);
|
|
1481
|
+
const gridState = state.grid ?? { visible: false, available: false };
|
|
1482
|
+
gridToggleButton.disabled = disabled || !gridState.available;
|
|
1483
|
+
setToolbarButtonTooltip(gridToggleButton, gridState.visible ? '隐藏 Scene View 网格' : '显示 Scene View 网格');
|
|
1484
|
+
LocalEditorShared.applyButtonActiveState(gridToggleButton, gridState.visible);
|
|
1101
1485
|
for (const [tool, button] of toolButtons) {
|
|
1102
1486
|
button.disabled = disabled;
|
|
1103
1487
|
LocalEditorShared.applyButtonActiveState(button, transformTool?.activeTool === tool);
|
|
@@ -1107,7 +1491,6 @@ export function createLocalEditorBrowserUi(options = {}) {
|
|
|
1107
1491
|
LocalEditorShared.applyButtonActiveState(button, transformTool?.activeSpace === space);
|
|
1108
1492
|
}
|
|
1109
1493
|
sceneHelpButton.disabled = disabled;
|
|
1110
|
-
renderTransformHandleBadges(transformTool);
|
|
1111
1494
|
const transformOperations = state.transformOperations ?? {
|
|
1112
1495
|
settings: DEFAULT_EDITOR_TRANSFORM_OPERATION_SETTINGS,
|
|
1113
1496
|
selectedCount: state.selectedIds.length,
|
|
@@ -1118,53 +1501,73 @@ export function createLocalEditorBrowserUi(options = {}) {
|
|
|
1118
1501
|
const operationSettings = transformOperations.settings;
|
|
1119
1502
|
snapGroup.style.display = inEditor ? 'flex' : 'none';
|
|
1120
1503
|
snapButton.disabled = disabled;
|
|
1121
|
-
|
|
1504
|
+
snapEnabledInput.disabled = disabled;
|
|
1505
|
+
snapEnabledInput.checked = operationSettings.snap.enabled;
|
|
1506
|
+
LocalEditorShared.applyButtonActiveState(snapButton, operationSettings.snap.enabled || snapSettingsOpen);
|
|
1507
|
+
setToolbarButtonTooltip(snapButton, `吸附设置 · 位移 ${formatSnapStepInputValue(operationSettings.snap, 'move')} · 旋转 ${formatSnapStepInputValue(operationSettings.snap, 'rotate')}° · 缩放 ${formatSnapStepInputValue(operationSettings.snap, 'scale')}`);
|
|
1122
1508
|
for (const [kind, input] of snapStepInputs) {
|
|
1123
1509
|
input.disabled = disabled;
|
|
1124
1510
|
if (doc.activeElement !== input)
|
|
1125
1511
|
input.value = formatSnapStepInputValue(operationSettings.snap, kind);
|
|
1126
1512
|
}
|
|
1513
|
+
if (!inEditor || disabled || snapGroup.style.display === 'none')
|
|
1514
|
+
closeSnapSettingsPopover();
|
|
1127
1515
|
placementGroup.style.display = inEditor ? 'flex' : 'none';
|
|
1516
|
+
placementButton.disabled = disabled;
|
|
1517
|
+
setToolbarButtonIcon(doc, placementButton, PLACEMENT_MODE_ICONS[operationSettings.placementMode]);
|
|
1518
|
+
setToolbarButtonTooltip(placementButton, `放置模式 · ${PLACEMENT_MODE_DESCRIPTIONS[operationSettings.placementMode]}`);
|
|
1519
|
+
LocalEditorShared.applyButtonActiveState(placementButton, operationSettings.placementMode !== 'off' || placementSettingsOpen);
|
|
1128
1520
|
for (const [mode, button] of Object.entries(placementButtons)) {
|
|
1129
1521
|
button.disabled = disabled;
|
|
1130
1522
|
LocalEditorShared.applyButtonActiveState(button, operationSettings.placementMode === mode);
|
|
1131
1523
|
}
|
|
1524
|
+
if (!inEditor || disabled || placementGroup.style.display === 'none')
|
|
1525
|
+
closePlacementSettingsPopover();
|
|
1132
1526
|
actionGroup.style.display = inEditor ? 'flex' : 'none';
|
|
1133
|
-
|
|
1134
|
-
transformActionButton.disabled = disabled || !
|
|
1135
|
-
transformActionButton
|
|
1136
|
-
|
|
1137
|
-
|
|
1527
|
+
const hasTransformAction = transformOperations.canAlign || transformOperations.canDistribute;
|
|
1528
|
+
transformActionButton.disabled = disabled || !hasTransformAction;
|
|
1529
|
+
setToolbarButtonTooltip(transformActionButton, hasTransformAction ? '对齐与分布' : '选择至少 2 个对象后可用');
|
|
1530
|
+
LocalEditorShared.applyButtonActiveState(transformActionButton, transformActionOpen);
|
|
1531
|
+
for (const [action, button] of transformActionButtons) {
|
|
1532
|
+
const enabled = isTransformActionEnabled(action, transformOperations);
|
|
1533
|
+
button.disabled = disabled || !enabled;
|
|
1534
|
+
button.style.opacity = button.disabled ? '0.45' : '1';
|
|
1535
|
+
button.style.cursor = button.disabled ? 'not-allowed' : 'pointer';
|
|
1536
|
+
}
|
|
1537
|
+
if (!inEditor || disabled || !hasTransformAction || actionGroup.style.display === 'none')
|
|
1538
|
+
closeTransformActionPopover();
|
|
1138
1539
|
undoButton.disabled = disabled || !state.session?.canUndo;
|
|
1139
1540
|
redoButton.disabled = disabled || !state.session?.canRedo;
|
|
1140
1541
|
LocalEditorShared.applyButtonActiveState(sceneHelpButton, inEditor && helpOpen);
|
|
1141
1542
|
shortcutHelpPanel.style.display = inEditor && helpOpen ? '' : 'none';
|
|
1142
1543
|
dirtyBadge.style.display = inEditor && state.session?.dirty ? 'inline-flex' : 'none';
|
|
1143
|
-
const
|
|
1144
|
-
? ` | dragging ${transformTool.draggingNodeId ?? 'selection'}`
|
|
1145
|
-
: '';
|
|
1146
|
-
status.textContent = [
|
|
1544
|
+
const editorStatusText = [
|
|
1147
1545
|
state.status,
|
|
1148
|
-
state.session?.dirty ? '
|
|
1546
|
+
state.session?.dirty ? '有未保存更改' : '',
|
|
1149
1547
|
LocalEditorShared.formatAuthoringSourceLabel(state.session?.source),
|
|
1150
1548
|
state.summary,
|
|
1151
1549
|
].filter(Boolean).join(' | ');
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1550
|
+
const transformStatusText = transformTool?.dragPhase === 'dragging'
|
|
1551
|
+
? `正在拖拽 ${transformTool.draggingNodeId ?? '选择对象'}`
|
|
1552
|
+
: LocalEditorShared.toTransformToolStatusLabel(transformTool?.activeTool ?? 'select', transformTool?.activeSpace ?? 'world');
|
|
1553
|
+
const statusToneColor = state.statusTone === 'error'
|
|
1155
1554
|
? 'var(--fps-editor-danger-strong)'
|
|
1156
1555
|
: state.statusTone === 'warning'
|
|
1157
1556
|
? 'var(--fps-editor-warn)'
|
|
1158
1557
|
: state.statusTone === 'success'
|
|
1159
1558
|
? 'var(--fps-editor-success)'
|
|
1160
1559
|
: 'var(--fps-editor-muted)';
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
LocalEditorShared.toTransformMouseHint(transformTool?.activeTool ?? 'select'),
|
|
1560
|
+
setToolbarButtonTooltip(editorStatusButton, [
|
|
1561
|
+
`编辑器状态:${editorStatusText}`,
|
|
1562
|
+
state.statusDetails && state.statusDetails !== state.status ? state.statusDetails : '',
|
|
1563
|
+
transformStatusText,
|
|
1166
1564
|
LocalEditorShared.toTransformOperationStatusLabel(operationSettings),
|
|
1167
|
-
].join(' · ');
|
|
1565
|
+
].filter(Boolean).join(' · '));
|
|
1566
|
+
editorStatusButton.style.color = statusToneColor;
|
|
1567
|
+
editorStatusButton.style.borderColor = state.statusTone == null || state.statusTone === 'default'
|
|
1568
|
+
? 'var(--fps-editor-border)'
|
|
1569
|
+
: statusToneColor;
|
|
1570
|
+
renderCoordinateAxesOverlay(coordinateAxesOverlay, inEditor ? state.coordinateAxes ?? null : null);
|
|
1168
1571
|
const boxSelection = state.boxSelection;
|
|
1169
1572
|
if (inEditor && boxSelection?.active) {
|
|
1170
1573
|
boxSelectionOverlay.style.display = '';
|
|
@@ -1212,10 +1615,14 @@ export function createLocalEditorBrowserUi(options = {}) {
|
|
|
1212
1615
|
workbench.root.remove();
|
|
1213
1616
|
localTestMenu.remove();
|
|
1214
1617
|
toolbarOverflowMenu.remove();
|
|
1618
|
+
snapSettingsPopover.remove();
|
|
1619
|
+
placementSettingsPopover.remove();
|
|
1620
|
+
transformActionPopover.remove();
|
|
1215
1621
|
boxSelectionOverlay.remove();
|
|
1216
1622
|
shortcutHelpPanel.remove();
|
|
1217
1623
|
hierarchyController.dispose();
|
|
1218
1624
|
workbenchLayoutController.dispose();
|
|
1625
|
+
tooltipController.dispose();
|
|
1219
1626
|
contextMenu.dispose();
|
|
1220
1627
|
inputRouter.dispose();
|
|
1221
1628
|
doc.removeEventListener('keydown', onKeyDown);
|