@fps-games/editor 0.1.1-beta.7 → 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 +14 -2
- package/dist/local-editor-harness.d.ts.map +1 -1
- package/dist/local-editor-harness.js +330 -2
- package/dist/local-editor-harness.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/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 +4 -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-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 +42 -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-types.d.ts +16 -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 +1 -1
- 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 +502 -6
- 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/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/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
|
@@ -75,6 +75,28 @@ const PLACEMENT_MODE_DESCRIPTIONS = {
|
|
|
75
75
|
ground: '放置到 XZ 地面',
|
|
76
76
|
surface: '放置到场景表面',
|
|
77
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
|
+
};
|
|
78
100
|
const TRANSFORM_ACTION_GROUPS = [
|
|
79
101
|
{
|
|
80
102
|
title: '对齐到 Active Object',
|
|
@@ -283,6 +305,262 @@ function renderCoordinateAxesOverlay(overlay, state) {
|
|
|
283
305
|
}
|
|
284
306
|
overlay.labelLayer.appendChild(overlay.centerDot);
|
|
285
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
|
+
}
|
|
286
564
|
function formatSvgNumber(value) {
|
|
287
565
|
return Number.isFinite(value) ? value.toFixed(2) : '0';
|
|
288
566
|
}
|
|
@@ -692,6 +970,74 @@ export function createLocalEditorBrowserUi(options = {}) {
|
|
|
692
970
|
const sceneCameraButton = createToolbarIconButton(doc, '从 Main Camera 查看场景', 'camera');
|
|
693
971
|
sceneCameraButton.dataset.sceneCameraPreviewToggle = 'true';
|
|
694
972
|
cameraPreviewGroup.appendChild(sceneCameraButton);
|
|
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
|
+
}
|
|
695
1041
|
const toolbarOverflowButton = createToolbarIconButton(doc, '更多', 'chevron-down', '显示隐藏的工具栏命令');
|
|
696
1042
|
toolbarOverflowButton.dataset.editorToolbarOverflowToggle = 'true';
|
|
697
1043
|
toolbarOverflowButton.style.display = 'none';
|
|
@@ -716,29 +1062,36 @@ export function createLocalEditorBrowserUi(options = {}) {
|
|
|
716
1062
|
'pointer-events:auto',
|
|
717
1063
|
].join(';');
|
|
718
1064
|
const coordinateAxesOverlay = createCoordinateAxesOverlay(doc);
|
|
1065
|
+
const spatialOverlay = createSpatialOverlay(doc);
|
|
1066
|
+
const measurementOverlay = createMeasurementOverlay(doc);
|
|
719
1067
|
sceneToolOverlay.appendChild(sceneTitle);
|
|
720
1068
|
sceneToolOverlay.appendChild(sceneQuickActions);
|
|
721
|
-
sceneToolOverlay.appendChild(sceneUtilityActions);
|
|
722
|
-
sceneToolOverlay.appendChild(cameraPreviewGroup);
|
|
723
1069
|
sceneToolOverlay.appendChild(toolGroup);
|
|
724
1070
|
sceneToolOverlay.appendChild(spaceGroup);
|
|
725
1071
|
sceneToolOverlay.appendChild(snapGroup);
|
|
726
1072
|
sceneToolOverlay.appendChild(placementGroup);
|
|
727
1073
|
sceneToolOverlay.appendChild(actionGroup);
|
|
1074
|
+
sceneToolOverlay.appendChild(viewportToolsGroup);
|
|
1075
|
+
sceneToolOverlay.appendChild(sceneUtilityActions);
|
|
1076
|
+
sceneToolOverlay.appendChild(cameraPreviewGroup);
|
|
728
1077
|
sceneToolOverlay.appendChild(editorStatusButton);
|
|
729
1078
|
sceneToolOverlay.appendChild(toolbarOverflowButton);
|
|
730
1079
|
workbench.sceneHeader.appendChild(sceneToolOverlay);
|
|
1080
|
+
workbench.sceneFrame.appendChild(spatialOverlay.root);
|
|
1081
|
+
workbench.sceneFrame.appendChild(measurementOverlay.root);
|
|
731
1082
|
workbench.sceneFrame.appendChild(coordinateAxesOverlay.root);
|
|
732
1083
|
root.appendChild(localTestMenu);
|
|
733
1084
|
root.appendChild(toolbarOverflowMenu);
|
|
734
1085
|
root.appendChild(snapSettingsPopover);
|
|
735
1086
|
root.appendChild(placementSettingsPopover);
|
|
736
1087
|
root.appendChild(transformActionPopover);
|
|
1088
|
+
root.appendChild(overlaySettingsPopover);
|
|
737
1089
|
tooltipSurfaces.add(localTestMenu);
|
|
738
1090
|
tooltipSurfaces.add(toolbarOverflowMenu);
|
|
739
1091
|
tooltipSurfaces.add(snapSettingsPopover);
|
|
740
1092
|
tooltipSurfaces.add(placementSettingsPopover);
|
|
741
1093
|
tooltipSurfaces.add(transformActionPopover);
|
|
1094
|
+
tooltipSurfaces.add(overlaySettingsPopover);
|
|
742
1095
|
const boxSelectionOverlay = doc.createElement('div');
|
|
743
1096
|
boxSelectionOverlay.classList.add(LOCAL_EDITOR_THEME_CLASS);
|
|
744
1097
|
boxSelectionOverlay.style.cssText = [
|
|
@@ -759,6 +1112,8 @@ export function createLocalEditorBrowserUi(options = {}) {
|
|
|
759
1112
|
function applyThemeToSurfaces() {
|
|
760
1113
|
applyLocalEditorTheme(hostChrome, activeTheme);
|
|
761
1114
|
applyLocalEditorTheme(workbench.root, activeTheme);
|
|
1115
|
+
applyLocalEditorTheme(spatialOverlay.root, activeTheme);
|
|
1116
|
+
applyLocalEditorTheme(measurementOverlay.root, activeTheme);
|
|
762
1117
|
applyLocalEditorTheme(shortcutHelpPanel, activeTheme);
|
|
763
1118
|
applyLocalEditorTheme(boxSelectionOverlay, activeTheme);
|
|
764
1119
|
applyLocalEditorTheme(localTestMenu, activeTheme);
|
|
@@ -766,6 +1121,7 @@ export function createLocalEditorBrowserUi(options = {}) {
|
|
|
766
1121
|
applyLocalEditorTheme(snapSettingsPopover, activeTheme);
|
|
767
1122
|
applyLocalEditorTheme(placementSettingsPopover, activeTheme);
|
|
768
1123
|
applyLocalEditorTheme(transformActionPopover, activeTheme);
|
|
1124
|
+
applyLocalEditorTheme(overlaySettingsPopover, activeTheme);
|
|
769
1125
|
tooltipController.setTheme(activeTheme);
|
|
770
1126
|
contextMenu.setTheme?.(activeTheme);
|
|
771
1127
|
}
|
|
@@ -784,6 +1140,7 @@ export function createLocalEditorBrowserUi(options = {}) {
|
|
|
784
1140
|
let snapSettingsOpen = false;
|
|
785
1141
|
let placementSettingsOpen = false;
|
|
786
1142
|
let transformActionOpen = false;
|
|
1143
|
+
let overlaySettingsOpen = false;
|
|
787
1144
|
function closeLocalTestMenu() {
|
|
788
1145
|
localTestMenuOpen = false;
|
|
789
1146
|
localTestMenu.style.display = 'none';
|
|
@@ -811,6 +1168,12 @@ export function createLocalEditorBrowserUi(options = {}) {
|
|
|
811
1168
|
transformActionButton.setAttribute('aria-expanded', 'false');
|
|
812
1169
|
LocalEditorShared.applyButtonActiveState(transformActionButton, false);
|
|
813
1170
|
}
|
|
1171
|
+
function closeOverlaySettingsPopover() {
|
|
1172
|
+
overlaySettingsOpen = false;
|
|
1173
|
+
overlaySettingsPopover.style.display = 'none';
|
|
1174
|
+
overlaySettingsButton.setAttribute('aria-expanded', 'false');
|
|
1175
|
+
LocalEditorShared.applyButtonActiveState(overlaySettingsButton, false);
|
|
1176
|
+
}
|
|
814
1177
|
function openLocalTestMenu() {
|
|
815
1178
|
if (!localTestActionsEnabled || localTestButton.style.display === 'none')
|
|
816
1179
|
return;
|
|
@@ -818,6 +1181,8 @@ export function createLocalEditorBrowserUi(options = {}) {
|
|
|
818
1181
|
closeSnapSettingsPopover();
|
|
819
1182
|
closePlacementSettingsPopover();
|
|
820
1183
|
closeTransformActionPopover();
|
|
1184
|
+
closeOverlaySettingsPopover();
|
|
1185
|
+
closeOverlaySettingsPopover();
|
|
821
1186
|
const win = doc.defaultView;
|
|
822
1187
|
const rect = localTestButton.getBoundingClientRect();
|
|
823
1188
|
const viewportWidth = win?.innerWidth ?? doc.documentElement.clientWidth;
|
|
@@ -834,6 +1199,7 @@ export function createLocalEditorBrowserUi(options = {}) {
|
|
|
834
1199
|
closeLocalTestMenu();
|
|
835
1200
|
closePlacementSettingsPopover();
|
|
836
1201
|
closeTransformActionPopover();
|
|
1202
|
+
closeOverlaySettingsPopover();
|
|
837
1203
|
const win = doc.defaultView;
|
|
838
1204
|
const rect = snapButton.getBoundingClientRect();
|
|
839
1205
|
const viewportWidth = win?.innerWidth ?? doc.documentElement.clientWidth;
|
|
@@ -858,6 +1224,7 @@ export function createLocalEditorBrowserUi(options = {}) {
|
|
|
858
1224
|
closeLocalTestMenu();
|
|
859
1225
|
closeSnapSettingsPopover();
|
|
860
1226
|
closeTransformActionPopover();
|
|
1227
|
+
closeOverlaySettingsPopover();
|
|
861
1228
|
const win = doc.defaultView;
|
|
862
1229
|
const rect = placementButton.getBoundingClientRect();
|
|
863
1230
|
const viewportWidth = win?.innerWidth ?? doc.documentElement.clientWidth;
|
|
@@ -882,6 +1249,7 @@ export function createLocalEditorBrowserUi(options = {}) {
|
|
|
882
1249
|
closeLocalTestMenu();
|
|
883
1250
|
closeSnapSettingsPopover();
|
|
884
1251
|
closePlacementSettingsPopover();
|
|
1252
|
+
closeOverlaySettingsPopover();
|
|
885
1253
|
const win = doc.defaultView;
|
|
886
1254
|
const rect = transformActionButton.getBoundingClientRect();
|
|
887
1255
|
const viewportWidth = win?.innerWidth ?? doc.documentElement.clientWidth;
|
|
@@ -899,6 +1267,31 @@ export function createLocalEditorBrowserUi(options = {}) {
|
|
|
899
1267
|
else
|
|
900
1268
|
openTransformActionPopover();
|
|
901
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
|
+
}
|
|
902
1295
|
const onLocalTestButtonClick = (event) => {
|
|
903
1296
|
event.stopPropagation();
|
|
904
1297
|
if (localTestMenuOpen)
|
|
@@ -926,24 +1319,26 @@ export function createLocalEditorBrowserUi(options = {}) {
|
|
|
926
1319
|
const toolbarOrder = [
|
|
927
1320
|
sceneTitle,
|
|
928
1321
|
sceneQuickActions,
|
|
929
|
-
sceneUtilityActions,
|
|
930
|
-
cameraPreviewGroup,
|
|
931
1322
|
toolGroup,
|
|
932
1323
|
spaceGroup,
|
|
933
1324
|
snapGroup,
|
|
934
1325
|
placementGroup,
|
|
935
1326
|
actionGroup,
|
|
1327
|
+
viewportToolsGroup,
|
|
1328
|
+
sceneUtilityActions,
|
|
1329
|
+
cameraPreviewGroup,
|
|
936
1330
|
editorStatusButton,
|
|
937
1331
|
toolbarOverflowButton,
|
|
938
1332
|
];
|
|
939
1333
|
const toolbarOverflowItems = [
|
|
940
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' },
|
|
941
1338
|
{ id: 'transform-actions', element: actionGroup, kind: 'group' },
|
|
942
1339
|
{ id: 'placement', element: placementGroup, kind: 'group' },
|
|
943
1340
|
{ id: 'snap', element: snapGroup, kind: 'group' },
|
|
944
1341
|
{ id: 'space', element: spaceGroup, kind: 'group' },
|
|
945
|
-
{ id: 'camera-preview', element: cameraPreviewGroup, kind: 'group' },
|
|
946
|
-
{ id: 'scene-utilities', element: sceneUtilityActions, kind: 'group' },
|
|
947
1342
|
];
|
|
948
1343
|
let toolbarOverflowOpen = false;
|
|
949
1344
|
let toolbarOverflowRaf = null;
|
|
@@ -1018,6 +1413,7 @@ export function createLocalEditorBrowserUi(options = {}) {
|
|
|
1018
1413
|
closeSnapSettingsPopover();
|
|
1019
1414
|
closePlacementSettingsPopover();
|
|
1020
1415
|
closeTransformActionPopover();
|
|
1416
|
+
closeOverlaySettingsPopover();
|
|
1021
1417
|
const win = doc.defaultView;
|
|
1022
1418
|
const rect = toolbarOverflowButton.getBoundingClientRect();
|
|
1023
1419
|
const viewportWidth = win?.innerWidth ?? doc.documentElement.clientWidth;
|
|
@@ -1100,11 +1496,14 @@ export function createLocalEditorBrowserUi(options = {}) {
|
|
|
1100
1496
|
return;
|
|
1101
1497
|
if (target.closest('[data-transform-action-popover]') || target.closest('[data-transform-action-toggle]'))
|
|
1102
1498
|
return;
|
|
1499
|
+
if (target.closest('[data-viewport-overlay-settings-popover]') || target.closest('[data-viewport-overlay-settings-toggle]'))
|
|
1500
|
+
return;
|
|
1103
1501
|
closeLocalTestMenu();
|
|
1104
1502
|
closeToolbarOverflowMenu();
|
|
1105
1503
|
closeSnapSettingsPopover();
|
|
1106
1504
|
closePlacementSettingsPopover();
|
|
1107
1505
|
closeTransformActionPopover();
|
|
1506
|
+
closeOverlaySettingsPopover();
|
|
1108
1507
|
};
|
|
1109
1508
|
const ResizeObserverCtor = doc.defaultView?.ResizeObserver;
|
|
1110
1509
|
const toolbarResizeObserver = ResizeObserverCtor
|
|
@@ -1143,6 +1542,38 @@ export function createLocalEditorBrowserUi(options = {}) {
|
|
|
1143
1542
|
const enabled = currentState?.sceneCameraPreview?.enabled ?? false;
|
|
1144
1543
|
callbacks.onSceneCameraPreviewToggle?.(!enabled);
|
|
1145
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
|
+
});
|
|
1146
1577
|
const setShortcutHelpOpen = (open) => {
|
|
1147
1578
|
if (open)
|
|
1148
1579
|
contextMenu.close();
|
|
@@ -1226,6 +1657,22 @@ export function createLocalEditorBrowserUi(options = {}) {
|
|
|
1226
1657
|
callbacks.onTransformAction?.(action);
|
|
1227
1658
|
closeTransformActionPopover();
|
|
1228
1659
|
});
|
|
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
|
+
});
|
|
1675
|
+
});
|
|
1229
1676
|
assetPanel.addEventListener('click', (event) => {
|
|
1230
1677
|
const target = event.target instanceof HTMLElement ? event.target : null;
|
|
1231
1678
|
const tabButton = target?.closest('[data-editor-dock-tab]');
|
|
@@ -1327,6 +1774,11 @@ export function createLocalEditorBrowserUi(options = {}) {
|
|
|
1327
1774
|
closeTransformActionPopover();
|
|
1328
1775
|
return;
|
|
1329
1776
|
}
|
|
1777
|
+
if (key === 'escape' && overlaySettingsOpen) {
|
|
1778
|
+
event.preventDefault();
|
|
1779
|
+
closeOverlaySettingsPopover();
|
|
1780
|
+
return;
|
|
1781
|
+
}
|
|
1330
1782
|
const primaryModifier = event.metaKey || event.ctrlKey;
|
|
1331
1783
|
const handleDocumentShortcut = inputRouter.shouldHandleDocumentShortcut(event);
|
|
1332
1784
|
const handleGlobalShortcut = inputRouter.shouldHandleGlobalShortcut(event);
|
|
@@ -1470,6 +1922,8 @@ export function createLocalEditorBrowserUi(options = {}) {
|
|
|
1470
1922
|
closePlacementSettingsPopover();
|
|
1471
1923
|
if (!inEditor)
|
|
1472
1924
|
closeTransformActionPopover();
|
|
1925
|
+
if (!inEditor)
|
|
1926
|
+
closeOverlaySettingsPopover();
|
|
1473
1927
|
const sceneCameraPreview = state.sceneCameraPreview ?? { enabled: false, available: false };
|
|
1474
1928
|
cameraPreviewGroup.style.display = inEditor ? 'flex' : 'none';
|
|
1475
1929
|
sceneCameraButton.disabled = disabled || !sceneCameraPreview.available;
|
|
@@ -1478,6 +1932,43 @@ export function createLocalEditorBrowserUi(options = {}) {
|
|
|
1478
1932
|
: '当前场景没有可预览的 Main Camera';
|
|
1479
1933
|
setToolbarButtonTooltip(sceneCameraButton, sceneCameraTooltip);
|
|
1480
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();
|
|
1481
1972
|
const gridState = state.grid ?? { visible: false, available: false };
|
|
1482
1973
|
gridToggleButton.disabled = disabled || !gridState.available;
|
|
1483
1974
|
setToolbarButtonTooltip(gridToggleButton, gridState.visible ? '隐藏 Scene View 网格' : '显示 Scene View 网格');
|
|
@@ -1568,6 +2059,8 @@ export function createLocalEditorBrowserUi(options = {}) {
|
|
|
1568
2059
|
? 'var(--fps-editor-border)'
|
|
1569
2060
|
: statusToneColor;
|
|
1570
2061
|
renderCoordinateAxesOverlay(coordinateAxesOverlay, inEditor ? state.coordinateAxes ?? null : null);
|
|
2062
|
+
renderSpatialOverlay(spatialOverlay, inEditor ? state.viewportSpatialOverlay ?? null : null);
|
|
2063
|
+
renderMeasurementOverlay(measurementOverlay, inEditor ? state.viewportMeasurement ?? null : null);
|
|
1571
2064
|
const boxSelection = state.boxSelection;
|
|
1572
2065
|
if (inEditor && boxSelection?.active) {
|
|
1573
2066
|
boxSelectionOverlay.style.display = '';
|
|
@@ -1618,6 +2111,9 @@ export function createLocalEditorBrowserUi(options = {}) {
|
|
|
1618
2111
|
snapSettingsPopover.remove();
|
|
1619
2112
|
placementSettingsPopover.remove();
|
|
1620
2113
|
transformActionPopover.remove();
|
|
2114
|
+
overlaySettingsPopover.remove();
|
|
2115
|
+
spatialOverlay.root.remove();
|
|
2116
|
+
measurementOverlay.root.remove();
|
|
1621
2117
|
boxSelectionOverlay.remove();
|
|
1622
2118
|
shortcutHelpPanel.remove();
|
|
1623
2119
|
hierarchyController.dispose();
|