@fps-games/editor 0.1.5-beta.5 → 0.1.6-beta.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/build-info.json +16 -16
- package/dist/local-editor-agent-actions.d.ts +8 -1
- package/dist/local-editor-agent-actions.d.ts.map +1 -1
- package/dist/local-editor-agent-actions.js +171 -12
- package/dist/local-editor-agent-actions.js.map +1 -1
- package/dist/local-editor-agent-context.d.ts +29 -1
- package/dist/local-editor-agent-context.d.ts.map +1 -1
- package/dist/local-editor-agent-context.js +403 -0
- package/dist/local-editor-agent-context.js.map +1 -1
- package/dist/local-editor-harness.d.ts +103 -5
- package/dist/local-editor-harness.d.ts.map +1 -1
- package/dist/local-editor-harness.js +1982 -101
- package/dist/local-editor-harness.js.map +1 -1
- package/dist/local-editor-marker-graph.d.ts +92 -0
- package/dist/local-editor-marker-graph.d.ts.map +1 -0
- package/dist/local-editor-marker-graph.js +1432 -0
- package/dist/local-editor-marker-graph.js.map +1 -0
- package/dist/local-editor-performance-stats.js +1 -1
- package/dist/local-editor-performance-stats.js.map +1 -1
- package/dist/playable-babylon-facade.d.ts +2 -2
- package/dist/playable-babylon-facade.d.ts.map +1 -1
- package/dist/playable-babylon-facade.js +1 -1
- package/dist/playable-babylon-facade.js.map +1 -1
- package/dist/playable-local-editor-host.d.ts +52 -7
- package/dist/playable-local-editor-host.d.ts.map +1 -1
- package/dist/playable-local-editor-host.js +23 -1
- package/dist/playable-local-editor-host.js.map +1 -1
- package/dist/playable-sdk.d.ts +4 -2
- package/dist/playable-sdk.d.ts.map +1 -1
- package/dist/playable-sdk.js +2 -1
- package/dist/playable-sdk.js.map +1 -1
- package/node_modules/@fps-games/babylon-renderer/package.json +1 -1
- package/node_modules/@fps-games/editor-babylon/dist/camera-controller.d.ts.map +1 -1
- package/node_modules/@fps-games/editor-babylon/dist/camera-controller.js +11 -0
- package/node_modules/@fps-games/editor-babylon/dist/camera-controller.js.map +1 -1
- package/node_modules/@fps-games/editor-babylon/dist/editor-sky.d.ts +3 -1
- package/node_modules/@fps-games/editor-babylon/dist/editor-sky.d.ts.map +1 -1
- package/node_modules/@fps-games/editor-babylon/dist/editor-sky.js +114 -18
- package/node_modules/@fps-games/editor-babylon/dist/editor-sky.js.map +1 -1
- package/node_modules/@fps-games/editor-babylon/dist/editor-world.d.ts +1 -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 +3 -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 +3 -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 +3 -0
- package/node_modules/@fps-games/editor-babylon/dist/index.js.map +1 -1
- package/node_modules/@fps-games/editor-babylon/dist/marker-overlay.d.ts +52 -0
- package/node_modules/@fps-games/editor-babylon/dist/marker-overlay.d.ts.map +1 -0
- package/node_modules/@fps-games/editor-babylon/dist/marker-overlay.js +1395 -0
- package/node_modules/@fps-games/editor-babylon/dist/marker-overlay.js.map +1 -0
- package/node_modules/@fps-games/editor-babylon/dist/material-property-adapter.d.ts.map +1 -1
- package/node_modules/@fps-games/editor-babylon/dist/material-property-adapter.js +4 -65
- package/node_modules/@fps-games/editor-babylon/dist/material-property-adapter.js.map +1 -1
- package/node_modules/@fps-games/editor-babylon/dist/material-slot-source-resolver.d.ts +28 -0
- package/node_modules/@fps-games/editor-babylon/dist/material-slot-source-resolver.d.ts.map +1 -0
- package/node_modules/@fps-games/editor-babylon/dist/material-slot-source-resolver.js +235 -0
- package/node_modules/@fps-games/editor-babylon/dist/material-slot-source-resolver.js.map +1 -0
- package/node_modules/@fps-games/editor-babylon/dist/prefab-comparison-preview-controller.d.ts +37 -0
- package/node_modules/@fps-games/editor-babylon/dist/prefab-comparison-preview-controller.d.ts.map +1 -0
- package/node_modules/@fps-games/editor-babylon/dist/prefab-comparison-preview-controller.js +451 -0
- package/node_modules/@fps-games/editor-babylon/dist/prefab-comparison-preview-controller.js.map +1 -0
- package/node_modules/@fps-games/editor-babylon/dist/projection-asset-container-importer.d.ts.map +1 -1
- package/node_modules/@fps-games/editor-babylon/dist/projection-asset-container-importer.js +14 -3
- package/node_modules/@fps-games/editor-babylon/dist/projection-asset-container-importer.js.map +1 -1
- package/node_modules/@fps-games/editor-babylon/dist/projection-pick-resolver.d.ts +1 -0
- package/node_modules/@fps-games/editor-babylon/dist/projection-pick-resolver.d.ts.map +1 -1
- package/node_modules/@fps-games/editor-babylon/dist/projection-pick-resolver.js +20 -4
- package/node_modules/@fps-games/editor-babylon/dist/projection-pick-resolver.js.map +1 -1
- package/node_modules/@fps-games/editor-babylon/dist/projection-source-owner-path.d.ts +3 -6
- package/node_modules/@fps-games/editor-babylon/dist/projection-source-owner-path.d.ts.map +1 -1
- package/node_modules/@fps-games/editor-babylon/dist/projection-source-owner-path.js +4 -37
- package/node_modules/@fps-games/editor-babylon/dist/projection-source-owner-path.js.map +1 -1
- package/node_modules/@fps-games/editor-babylon/dist/projection.d.ts +44 -1
- package/node_modules/@fps-games/editor-babylon/dist/projection.d.ts.map +1 -1
- package/node_modules/@fps-games/editor-babylon/dist/projection.js +295 -41
- package/node_modules/@fps-games/editor-babylon/dist/projection.js.map +1 -1
- package/node_modules/@fps-games/editor-babylon/package.json +5 -5
- package/node_modules/@fps-games/editor-browser/dist/index.d.ts +5 -2
- package/node_modules/@fps-games/editor-browser/dist/index.d.ts.map +1 -1
- package/node_modules/@fps-games/editor-browser/dist/index.js +2 -0
- package/node_modules/@fps-games/editor-browser/dist/index.js.map +1 -1
- package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-asset-kind.d.ts +5 -0
- package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-asset-kind.d.ts.map +1 -0
- package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-asset-kind.js +23 -0
- package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-asset-kind.js.map +1 -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 +69 -0
- package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-hierarchy-actions.js.map +1 -1
- package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-hierarchy-attach-dialog.d.ts.map +1 -1
- package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-hierarchy-attach-dialog.js +1 -0
- package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-hierarchy-attach-dialog.js.map +1 -1
- package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-hierarchy-controller.d.ts +2 -0
- 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 +38 -1
- package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-hierarchy-controller.js.map +1 -1
- package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-hierarchy-tree.d.ts +1 -1
- package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-hierarchy-tree.d.ts.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 +8 -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 +7 -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-panel-registry.js +1 -1
- package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-panel-registry.js.map +1 -1
- package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-panels.d.ts +4 -0
- package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-panels.d.ts.map +1 -1
- package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-panels.js +1471 -29
- 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-prefab-stage.d.ts +52 -0
- package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-prefab-stage.d.ts.map +1 -0
- package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-prefab-stage.js +131 -0
- package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-prefab-stage.js.map +1 -0
- package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-primitives.d.ts +1 -1
- package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-primitives.d.ts.map +1 -1
- 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 +9 -2
- 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-types.d.ts +234 -3
- package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-types.d.ts.map +1 -1
- package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-workbench.d.ts +10 -1
- package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-workbench.d.ts.map +1 -1
- package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-workbench.js +3 -1
- package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-workbench.js.map +1 -1
- package/node_modules/@fps-games/editor-browser/dist/local-editor-ui.d.ts +4 -3
- 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 +1274 -55
- package/node_modules/@fps-games/editor-browser/dist/local-editor-ui.js.map +1 -1
- package/node_modules/@fps-games/editor-browser/package.json +3 -3
- package/node_modules/@fps-games/editor-core/dist/scene-graph.d.ts +1 -1
- package/node_modules/@fps-games/editor-core/dist/scene-graph.d.ts.map +1 -1
- package/node_modules/@fps-games/editor-core/dist/viewport-tools.d.ts +2 -0
- package/node_modules/@fps-games/editor-core/dist/viewport-tools.d.ts.map +1 -1
- package/node_modules/@fps-games/editor-core/dist/viewport-tools.js +1 -0
- package/node_modules/@fps-games/editor-core/dist/viewport-tools.js.map +1 -1
- package/node_modules/@fps-games/editor-core/package.json +2 -2
- package/node_modules/@fps-games/editor-forge-play/package.json +2 -2
- package/node_modules/@fps-games/editor-playable-sdk/dist/asset-library/catalog.d.ts.map +1 -1
- package/node_modules/@fps-games/editor-playable-sdk/dist/asset-library/catalog.js +17 -3
- package/node_modules/@fps-games/editor-playable-sdk/dist/asset-library/catalog.js.map +1 -1
- package/node_modules/@fps-games/editor-playable-sdk/dist/asset-library/document.d.ts.map +1 -1
- package/node_modules/@fps-games/editor-playable-sdk/dist/asset-library/document.js +2 -1
- package/node_modules/@fps-games/editor-playable-sdk/dist/asset-library/document.js.map +1 -1
- package/node_modules/@fps-games/editor-playable-sdk/dist/asset-library/types.d.ts +3 -2
- package/node_modules/@fps-games/editor-playable-sdk/dist/asset-library/types.d.ts.map +1 -1
- package/node_modules/@fps-games/editor-playable-sdk/dist/asset-placement/document.d.ts +1 -0
- package/node_modules/@fps-games/editor-playable-sdk/dist/asset-placement/document.d.ts.map +1 -1
- package/node_modules/@fps-games/editor-playable-sdk/dist/asset-placement/document.js +68 -2
- package/node_modules/@fps-games/editor-playable-sdk/dist/asset-placement/document.js.map +1 -1
- package/node_modules/@fps-games/editor-playable-sdk/dist/asset-placement/index.d.ts +1 -1
- package/node_modules/@fps-games/editor-playable-sdk/dist/asset-placement/index.d.ts.map +1 -1
- package/node_modules/@fps-games/editor-playable-sdk/dist/asset-placement/index.js +1 -1
- package/node_modules/@fps-games/editor-playable-sdk/dist/asset-placement/index.js.map +1 -1
- package/node_modules/@fps-games/editor-playable-sdk/dist/document/helpers.d.ts +56 -1
- package/node_modules/@fps-games/editor-playable-sdk/dist/document/helpers.d.ts.map +1 -1
- package/node_modules/@fps-games/editor-playable-sdk/dist/document/helpers.js +531 -0
- package/node_modules/@fps-games/editor-playable-sdk/dist/document/helpers.js.map +1 -1
- package/node_modules/@fps-games/editor-playable-sdk/dist/document/types.d.ts +103 -2
- package/node_modules/@fps-games/editor-playable-sdk/dist/document/types.d.ts.map +1 -1
- package/node_modules/@fps-games/editor-playable-sdk/dist/field-schema/index.d.ts +1 -1
- package/node_modules/@fps-games/editor-playable-sdk/dist/field-schema/index.d.ts.map +1 -1
- package/node_modules/@fps-games/editor-playable-sdk/dist/field-schema/index.js +1 -1
- package/node_modules/@fps-games/editor-playable-sdk/dist/field-schema/index.js.map +1 -1
- package/node_modules/@fps-games/editor-playable-sdk/dist/field-schema/schema.d.ts.map +1 -1
- package/node_modules/@fps-games/editor-playable-sdk/dist/field-schema/schema.js +9 -1
- package/node_modules/@fps-games/editor-playable-sdk/dist/field-schema/schema.js.map +1 -1
- package/node_modules/@fps-games/editor-playable-sdk/dist/field-schema/validators.d.ts +1 -0
- package/node_modules/@fps-games/editor-playable-sdk/dist/field-schema/validators.d.ts.map +1 -1
- package/node_modules/@fps-games/editor-playable-sdk/dist/field-schema/validators.js +3 -0
- package/node_modules/@fps-games/editor-playable-sdk/dist/field-schema/validators.js.map +1 -1
- package/node_modules/@fps-games/editor-playable-sdk/dist/local-harness/lifecycle.d.ts.map +1 -1
- package/node_modules/@fps-games/editor-playable-sdk/dist/local-harness/lifecycle.js +3 -7
- package/node_modules/@fps-games/editor-playable-sdk/dist/local-harness/lifecycle.js.map +1 -1
- package/node_modules/@fps-games/editor-playable-sdk/dist/material-binding/binding.d.ts.map +1 -1
- package/node_modules/@fps-games/editor-playable-sdk/dist/material-binding/binding.js +6 -0
- package/node_modules/@fps-games/editor-playable-sdk/dist/material-binding/binding.js.map +1 -1
- package/node_modules/@fps-games/editor-playable-sdk/dist/material-binding/types.d.ts +2 -10
- package/node_modules/@fps-games/editor-playable-sdk/dist/material-binding/types.d.ts.map +1 -1
- package/node_modules/@fps-games/editor-playable-sdk/dist/mutation/field-patch.d.ts.map +1 -1
- package/node_modules/@fps-games/editor-playable-sdk/dist/mutation/field-patch.js +8 -0
- package/node_modules/@fps-games/editor-playable-sdk/dist/mutation/field-patch.js.map +1 -1
- package/node_modules/@fps-games/editor-playable-sdk/dist/runtime-preview/nodes.d.ts.map +1 -1
- package/node_modules/@fps-games/editor-playable-sdk/dist/runtime-preview/nodes.js +66 -31
- package/node_modules/@fps-games/editor-playable-sdk/dist/runtime-preview/nodes.js.map +1 -1
- package/node_modules/@fps-games/editor-playable-sdk/dist/runtime-preview/types.d.ts +2 -1
- package/node_modules/@fps-games/editor-playable-sdk/dist/runtime-preview/types.d.ts.map +1 -1
- package/node_modules/@fps-games/editor-playable-sdk/dist/scene-source/protocol.d.ts.map +1 -1
- package/node_modules/@fps-games/editor-playable-sdk/dist/scene-source/protocol.js +40 -2
- package/node_modules/@fps-games/editor-playable-sdk/dist/scene-source/protocol.js.map +1 -1
- package/node_modules/@fps-games/editor-playable-sdk/dist/serialized-property/descriptors.d.ts +1 -0
- package/node_modules/@fps-games/editor-playable-sdk/dist/serialized-property/descriptors.d.ts.map +1 -1
- package/node_modules/@fps-games/editor-playable-sdk/dist/serialized-property/descriptors.js +20 -0
- package/node_modules/@fps-games/editor-playable-sdk/dist/serialized-property/descriptors.js.map +1 -1
- package/node_modules/@fps-games/editor-playable-sdk/dist/serialized-property/inspector.d.ts.map +1 -1
- package/node_modules/@fps-games/editor-playable-sdk/dist/serialized-property/inspector.js +68 -27
- package/node_modules/@fps-games/editor-playable-sdk/dist/serialized-property/inspector.js.map +1 -1
- package/node_modules/@fps-games/editor-playable-sdk/dist/serialized-property/object.d.ts.map +1 -1
- package/node_modules/@fps-games/editor-playable-sdk/dist/serialized-property/object.js +7 -4
- package/node_modules/@fps-games/editor-playable-sdk/dist/serialized-property/object.js.map +1 -1
- package/node_modules/@fps-games/editor-playable-sdk/dist/session/hierarchy.d.ts.map +1 -1
- package/node_modules/@fps-games/editor-playable-sdk/dist/session/hierarchy.js +2 -1
- package/node_modules/@fps-games/editor-playable-sdk/dist/session/hierarchy.js.map +1 -1
- package/node_modules/@fps-games/editor-playable-sdk/package.json +3 -3
- package/node_modules/@fps-games/editor-protocol/dist/agent.d.ts +57 -3
- package/node_modules/@fps-games/editor-protocol/dist/agent.d.ts.map +1 -1
- package/node_modules/@fps-games/editor-protocol/dist/agent.js.map +1 -1
- package/node_modules/@fps-games/editor-protocol/dist/index.d.ts +19 -0
- package/node_modules/@fps-games/editor-protocol/dist/index.d.ts.map +1 -1
- package/node_modules/@fps-games/editor-protocol/dist/index.js +1 -0
- package/node_modules/@fps-games/editor-protocol/dist/index.js.map +1 -1
- package/node_modules/@fps-games/editor-protocol/dist/marker-graph.d.ts +128 -0
- package/node_modules/@fps-games/editor-protocol/dist/marker-graph.d.ts.map +1 -0
- package/node_modules/@fps-games/editor-protocol/dist/marker-graph.js +2 -0
- package/node_modules/@fps-games/editor-protocol/dist/marker-graph.js.map +1 -0
- package/node_modules/@fps-games/editor-protocol/package.json +1 -1
- package/package.json +8 -8
|
@@ -2,6 +2,7 @@ import { createLocalEditorIcon, isLocalEditorIconName, } from './local-editor-ui
|
|
|
2
2
|
import { createBadge, createAssetList, createDockTab, createEditorInputStyle, createEmptyState, createListItemBlock, createPanelHeader, createPropertyRow, createToolbarButton, createTreeView, createTreeViewItem, } from './local-editor-ui-primitives.js';
|
|
3
3
|
import { configureLocalEditorDockPanelContent, createLocalEditorDockPanelViewport, renderLocalEditorDockTabStrip, } from './local-editor-ui-dock-panel.js';
|
|
4
4
|
import { clearElement, createButton, toTitle } from './local-editor-ui-shared.js';
|
|
5
|
+
import { resolveLocalEditorAssetBrowserTab, resolveLocalEditorBrowserAssetKind, } from './local-editor-ui-asset-kind.js';
|
|
5
6
|
import { createLocalEditorBrowserInspectorVectorCopyPayload, } from './local-editor-inspector-clipboard.js';
|
|
6
7
|
import { resolveLocalEditorBrowserInspectorVectorGroupPathFromProperties, } from './local-editor-inspector-groups.js';
|
|
7
8
|
const BOTTOM_DOCK_TAB_ICONS = {
|
|
@@ -11,6 +12,7 @@ const BOTTOM_DOCK_TAB_ICONS = {
|
|
|
11
12
|
const RIGHT_DOCK_TAB_ICONS = {
|
|
12
13
|
inspector: 'inspector',
|
|
13
14
|
rendering: 'world',
|
|
15
|
+
markers: 'view-overlay',
|
|
14
16
|
};
|
|
15
17
|
const LEFT_DOCK_TAB_ICONS = {
|
|
16
18
|
hierarchy: 'hierarchy',
|
|
@@ -18,6 +20,7 @@ const LEFT_DOCK_TAB_ICONS = {
|
|
|
18
20
|
const ASSET_BROWSER_TABS = [
|
|
19
21
|
{ id: 'all', label: '全部', title: '显示全部项目资产' },
|
|
20
22
|
{ id: 'models', label: '模型', title: '显示模型资产' },
|
|
23
|
+
{ id: 'prefabs', label: '预制体', title: '显示预制体资产' },
|
|
21
24
|
{ id: 'materials', label: '材质', title: '显示项目材质资产' },
|
|
22
25
|
{ id: 'textures', label: '贴图', title: '显示贴图资产' },
|
|
23
26
|
];
|
|
@@ -27,7 +30,11 @@ export function renderHierarchyPanel(doc, panel, state, rename, drop) {
|
|
|
27
30
|
createGroupButton.dataset.editorHierarchyCreateGroup = 'true';
|
|
28
31
|
createGroupButton.style.padding = '3px 7px';
|
|
29
32
|
createGroupButton.style.fontSize = '11px';
|
|
30
|
-
|
|
33
|
+
const createMarkerButton = createToolbarButton(doc, '+ Marker', 'view-overlay');
|
|
34
|
+
createMarkerButton.dataset.editorHierarchyCreateMarker = 'true';
|
|
35
|
+
createMarkerButton.style.padding = '3px 7px';
|
|
36
|
+
createMarkerButton.style.fontSize = '11px';
|
|
37
|
+
panel.appendChild(createPanelHeader(doc, 'Hierarchy', [createGroupButton, createMarkerButton], 'hierarchy'));
|
|
31
38
|
const list = createTreeView(doc);
|
|
32
39
|
for (const item of state.hierarchy) {
|
|
33
40
|
const isRenaming = rename?.id === item.id;
|
|
@@ -143,6 +150,7 @@ export function renderWorkbenchRightDockTabs(doc, panel, activeTab, panels = [])
|
|
|
143
150
|
fallbackTabs: [
|
|
144
151
|
{ id: 'inspector', title: '检查器', area: 'right' },
|
|
145
152
|
{ id: 'rendering', title: '渲染', area: 'right' },
|
|
153
|
+
{ id: 'markers', title: '标记', area: 'right' },
|
|
146
154
|
],
|
|
147
155
|
iconById: RIGHT_DOCK_TAB_ICONS,
|
|
148
156
|
panel,
|
|
@@ -150,6 +158,30 @@ export function renderWorkbenchRightDockTabs(doc, panel, activeTab, panels = [])
|
|
|
150
158
|
tabStyle: tabHeaderButtonStyle,
|
|
151
159
|
});
|
|
152
160
|
}
|
|
161
|
+
export function renderMarkerPanel(doc, panel, state) {
|
|
162
|
+
clearElement(panel);
|
|
163
|
+
configureLocalEditorDockPanelContent(panel);
|
|
164
|
+
const markerPanel = state.markerPanel ?? null;
|
|
165
|
+
panel.appendChild(createPanelHeader(doc, markerPanel?.title ?? 'Markers', [], 'view-overlay'));
|
|
166
|
+
const viewport = createLocalEditorDockPanelViewport(doc, 'editorMarkersScrollViewport', { gap: '8px' });
|
|
167
|
+
if (!markerPanel) {
|
|
168
|
+
viewport.appendChild(createEmptyState(doc, '当前项目没有提供 marker graph capability。'));
|
|
169
|
+
panel.appendChild(viewport);
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
viewport.appendChild(createMarkerPanelSummary(doc, markerPanel));
|
|
173
|
+
viewport.appendChild(createMarkerRelationCreateBlock(doc, markerPanel));
|
|
174
|
+
viewport.appendChild(createMarkerRelationListBlock(doc, markerPanel.relations ?? []));
|
|
175
|
+
viewport.appendChild(createMarkerTypeCreateBlock(doc));
|
|
176
|
+
const list = doc.createElement('div');
|
|
177
|
+
list.style.cssText = 'display:flex;flex-direction:column;gap:6px';
|
|
178
|
+
for (const markerType of markerPanel.markerTypes)
|
|
179
|
+
list.appendChild(createMarkerTypeRow(doc, markerType));
|
|
180
|
+
if (markerPanel.markerTypes.length === 0)
|
|
181
|
+
list.appendChild(createEmptyState(doc, '还没有 marker type。'));
|
|
182
|
+
viewport.appendChild(list);
|
|
183
|
+
panel.appendChild(viewport);
|
|
184
|
+
}
|
|
153
185
|
export function renderWorkbenchLeftDockTabs(doc, panel, activeTab, panels = []) {
|
|
154
186
|
renderLocalEditorDockTabStrip(doc, {
|
|
155
187
|
activeId: activeTab,
|
|
@@ -164,6 +196,481 @@ export function renderWorkbenchLeftDockTabs(doc, panel, activeTab, panels = [])
|
|
|
164
196
|
tabStyle: tabHeaderButtonStyle,
|
|
165
197
|
});
|
|
166
198
|
}
|
|
199
|
+
function createMarkerPanelSummary(doc, panel) {
|
|
200
|
+
const block = createListItemBlock(doc);
|
|
201
|
+
block.style.cssText = [
|
|
202
|
+
block.style.cssText,
|
|
203
|
+
'display:flex',
|
|
204
|
+
'flex-direction:column',
|
|
205
|
+
'gap:7px',
|
|
206
|
+
].filter(Boolean).join(';');
|
|
207
|
+
const counters = doc.createElement('div');
|
|
208
|
+
counters.style.cssText = 'display:flex;align-items:center;gap:5px;flex-wrap:wrap';
|
|
209
|
+
counters.appendChild(createBadge(doc, `${panel.markersCount ?? 0} markers`, { compact: true, icon: 'view-overlay' }));
|
|
210
|
+
counters.appendChild(createBadge(doc, `${panel.relationsCount ?? 0} relations`, { compact: true, icon: 'hierarchy' }));
|
|
211
|
+
counters.appendChild(createBadge(doc, `${panel.markerTypes.length} types`, { compact: true, icon: 'object' }));
|
|
212
|
+
block.appendChild(counters);
|
|
213
|
+
if (panel.summary || panel.status) {
|
|
214
|
+
const summary = doc.createElement('div');
|
|
215
|
+
summary.textContent = panel.status ?? panel.summary ?? '';
|
|
216
|
+
const statusColor = panel.statusTone === 'error'
|
|
217
|
+
? 'var(--fps-editor-danger-text)'
|
|
218
|
+
: panel.statusTone === 'warning'
|
|
219
|
+
? 'var(--fps-editor-warn)'
|
|
220
|
+
: panel.statusTone === 'success'
|
|
221
|
+
? 'var(--fps-editor-success)'
|
|
222
|
+
: 'var(--fps-editor-muted)';
|
|
223
|
+
summary.style.cssText = [
|
|
224
|
+
`color:${statusColor}`,
|
|
225
|
+
'font-size:11px',
|
|
226
|
+
'font-weight:700',
|
|
227
|
+
'line-height:1.45',
|
|
228
|
+
].join(';');
|
|
229
|
+
block.appendChild(summary);
|
|
230
|
+
}
|
|
231
|
+
return block;
|
|
232
|
+
}
|
|
233
|
+
function createMarkerRelationCreateBlock(doc, panel) {
|
|
234
|
+
const block = createListItemBlock(doc);
|
|
235
|
+
block.dataset.editorMarkerRelationCreate = 'true';
|
|
236
|
+
block.style.cssText = [
|
|
237
|
+
block.style.cssText,
|
|
238
|
+
'display:flex',
|
|
239
|
+
'flex-direction:column',
|
|
240
|
+
'gap:7px',
|
|
241
|
+
].filter(Boolean).join(';');
|
|
242
|
+
const header = createMarkerPanelSectionHeader(doc, '关系');
|
|
243
|
+
header.appendChild(createBadge(doc, panel.activeMarkerId ? '当前标记' : '未选中标记', {
|
|
244
|
+
compact: true,
|
|
245
|
+
tone: panel.activeMarkerId ? 'success' : 'default',
|
|
246
|
+
}));
|
|
247
|
+
block.appendChild(header);
|
|
248
|
+
if (panel.markersVisible === false) {
|
|
249
|
+
block.appendChild(createMarkerRelationVisibilityHint(doc, '标记视图已关闭,关系线暂不显示'));
|
|
250
|
+
}
|
|
251
|
+
const endpoints = panel.relationEndpointOptions ?? [];
|
|
252
|
+
const relationTypes = panel.relationTypes ?? [];
|
|
253
|
+
const grid = doc.createElement('div');
|
|
254
|
+
grid.style.cssText = 'display:grid;grid-template-columns:72px minmax(0,1fr);gap:6px 8px;align-items:center';
|
|
255
|
+
grid.appendChild(createMarkerTypeFieldLabel(doc, '起点'));
|
|
256
|
+
grid.appendChild(createMarkerRelationEndpointSelect(doc, 'from', endpoints, panel.activeMarkerId ?? null));
|
|
257
|
+
grid.appendChild(createMarkerTypeFieldLabel(doc, '关系'));
|
|
258
|
+
grid.appendChild(createMarkerRelationTypeSelect(doc, relationTypes));
|
|
259
|
+
grid.appendChild(createMarkerTypeFieldLabel(doc, '终点'));
|
|
260
|
+
grid.appendChild(createMarkerRelationEndpointSelect(doc, 'to', endpoints, null));
|
|
261
|
+
grid.appendChild(createMarkerTypeFieldLabel(doc, '方向'));
|
|
262
|
+
grid.appendChild(createMarkerRelationDirectionSelect(doc));
|
|
263
|
+
block.appendChild(grid);
|
|
264
|
+
if (panel.relationPick?.active) {
|
|
265
|
+
block.appendChild(createMarkerRelationPickStatus(doc, panel));
|
|
266
|
+
}
|
|
267
|
+
const actions = doc.createElement('div');
|
|
268
|
+
actions.style.cssText = 'display:flex;justify-content:flex-end;gap:6px;flex-wrap:wrap';
|
|
269
|
+
const selectedRelationCount = (panel.relations ?? []).filter(relation => relation.selected).length;
|
|
270
|
+
const deleteConnectedButton = createToolbarButton(doc, '删除当前标记全部关系', 'discard');
|
|
271
|
+
deleteConnectedButton.type = 'button';
|
|
272
|
+
deleteConnectedButton.dataset.editorMarkerRelationAction = 'delete-connected';
|
|
273
|
+
if (panel.activeMarkerId)
|
|
274
|
+
deleteConnectedButton.dataset.editorMarkerRelationMarkerId = panel.activeMarkerId;
|
|
275
|
+
deleteConnectedButton.disabled = !panel.activeMarkerId || selectedRelationCount === 0;
|
|
276
|
+
deleteConnectedButton.title = deleteConnectedButton.disabled
|
|
277
|
+
? '当前标记没有可删除的一跳关系。'
|
|
278
|
+
: '删除当前标记的一跳关系,不删除标记或对象。';
|
|
279
|
+
deleteConnectedButton.dataset.editorTooltip = deleteConnectedButton.title;
|
|
280
|
+
deleteConnectedButton.style.padding = '4px 8px';
|
|
281
|
+
actions.appendChild(deleteConnectedButton);
|
|
282
|
+
const pickButton = createToolbarButton(doc, '视口拾取', 'view-overlay');
|
|
283
|
+
pickButton.type = 'button';
|
|
284
|
+
pickButton.dataset.editorMarkerRelationAction = 'pick-start';
|
|
285
|
+
pickButton.disabled = endpoints.length === 0 || relationTypes.length === 0;
|
|
286
|
+
pickButton.title = pickButton.disabled
|
|
287
|
+
? '至少需要一个端点和一个关系类型。'
|
|
288
|
+
: '进入视口拾取关系端点模式。';
|
|
289
|
+
pickButton.dataset.editorTooltip = pickButton.title;
|
|
290
|
+
pickButton.style.padding = '4px 8px';
|
|
291
|
+
actions.appendChild(pickButton);
|
|
292
|
+
if (panel.relationPick?.active) {
|
|
293
|
+
const cancelPickButton = createToolbarButton(doc, '取消拾取', 'discard');
|
|
294
|
+
cancelPickButton.type = 'button';
|
|
295
|
+
cancelPickButton.dataset.editorMarkerRelationAction = 'pick-cancel';
|
|
296
|
+
cancelPickButton.title = '退出关系端点拾取。';
|
|
297
|
+
cancelPickButton.dataset.editorTooltip = cancelPickButton.title;
|
|
298
|
+
cancelPickButton.style.padding = '4px 8px';
|
|
299
|
+
actions.appendChild(cancelPickButton);
|
|
300
|
+
}
|
|
301
|
+
const button = createToolbarButton(doc, '创建关系', 'hierarchy');
|
|
302
|
+
button.type = 'button';
|
|
303
|
+
button.dataset.editorMarkerRelationAction = 'create';
|
|
304
|
+
button.disabled = endpoints.length < 2 || relationTypes.length === 0;
|
|
305
|
+
button.title = button.disabled
|
|
306
|
+
? '至少需要两个端点和一个关系类型。'
|
|
307
|
+
: '创建 marker 关系';
|
|
308
|
+
button.dataset.editorTooltip = button.title;
|
|
309
|
+
button.style.padding = '4px 8px';
|
|
310
|
+
actions.appendChild(button);
|
|
311
|
+
block.appendChild(actions);
|
|
312
|
+
return block;
|
|
313
|
+
}
|
|
314
|
+
function createMarkerRelationVisibilityHint(doc, text) {
|
|
315
|
+
const hint = doc.createElement('div');
|
|
316
|
+
hint.textContent = text;
|
|
317
|
+
hint.style.cssText = [
|
|
318
|
+
'color:var(--fps-editor-warn)',
|
|
319
|
+
'border:1px solid rgba(251, 191, 36, 0.45)',
|
|
320
|
+
'border-radius:6px',
|
|
321
|
+
'background:rgba(251, 191, 36, 0.10)',
|
|
322
|
+
'padding:6px 8px',
|
|
323
|
+
'font-size:11px',
|
|
324
|
+
'font-weight:800',
|
|
325
|
+
'line-height:1.35',
|
|
326
|
+
].join(';');
|
|
327
|
+
return hint;
|
|
328
|
+
}
|
|
329
|
+
function createMarkerRelationPickStatus(doc, panel) {
|
|
330
|
+
const relationPick = panel.relationPick;
|
|
331
|
+
const status = doc.createElement('div');
|
|
332
|
+
const tone = relationPick?.statusTone ?? 'success';
|
|
333
|
+
const color = tone === 'warning'
|
|
334
|
+
? 'var(--fps-editor-warn)'
|
|
335
|
+
: tone === 'error'
|
|
336
|
+
? 'var(--fps-editor-danger-text)'
|
|
337
|
+
: tone === 'success'
|
|
338
|
+
? 'var(--fps-editor-success)'
|
|
339
|
+
: 'var(--fps-editor-muted)';
|
|
340
|
+
status.textContent = relationPick?.status
|
|
341
|
+
?? (relationPick?.from
|
|
342
|
+
? '正在拾取关系终点。'
|
|
343
|
+
: '正在拾取关系起点。');
|
|
344
|
+
status.style.cssText = [
|
|
345
|
+
`color:${color}`,
|
|
346
|
+
'border:1px solid var(--fps-editor-border)',
|
|
347
|
+
'border-radius:6px',
|
|
348
|
+
'background:rgba(42, 125, 255, 0.10)',
|
|
349
|
+
'padding:6px 8px',
|
|
350
|
+
'font-size:11px',
|
|
351
|
+
'font-weight:800',
|
|
352
|
+
'line-height:1.35',
|
|
353
|
+
].join(';');
|
|
354
|
+
return status;
|
|
355
|
+
}
|
|
356
|
+
function createMarkerRelationListBlock(doc, relations) {
|
|
357
|
+
const block = createListItemBlock(doc);
|
|
358
|
+
block.style.cssText = [
|
|
359
|
+
block.style.cssText,
|
|
360
|
+
'display:flex',
|
|
361
|
+
'flex-direction:column',
|
|
362
|
+
'gap:7px',
|
|
363
|
+
].filter(Boolean).join(';');
|
|
364
|
+
const header = createMarkerPanelSectionHeader(doc, '关系列表');
|
|
365
|
+
header.appendChild(createBadge(doc, `${relations.length}`, { compact: true, icon: 'hierarchy' }));
|
|
366
|
+
block.appendChild(header);
|
|
367
|
+
if (relations.length === 0) {
|
|
368
|
+
block.appendChild(createEmptyState(doc, '还没有关系。'));
|
|
369
|
+
return block;
|
|
370
|
+
}
|
|
371
|
+
const list = doc.createElement('div');
|
|
372
|
+
list.style.cssText = 'display:flex;flex-direction:column;gap:6px';
|
|
373
|
+
const sortedRelations = [...relations].sort((left, right) => {
|
|
374
|
+
if (left.selected !== right.selected)
|
|
375
|
+
return left.selected ? -1 : 1;
|
|
376
|
+
return left.typeLabel.localeCompare(right.typeLabel) || left.id.localeCompare(right.id);
|
|
377
|
+
});
|
|
378
|
+
for (const relation of sortedRelations)
|
|
379
|
+
list.appendChild(createMarkerRelationRow(doc, relation));
|
|
380
|
+
block.appendChild(list);
|
|
381
|
+
return block;
|
|
382
|
+
}
|
|
383
|
+
function createMarkerRelationRow(doc, relation) {
|
|
384
|
+
const row = doc.createElement('div');
|
|
385
|
+
row.dataset.editorMarkerRelationRow = relation.id;
|
|
386
|
+
row.dataset.editorMarkerRelationAction = 'select';
|
|
387
|
+
row.tabIndex = 0;
|
|
388
|
+
row.style.cssText = [
|
|
389
|
+
`border:1px solid ${relation.active ? 'var(--fps-editor-accent-strong)' : 'var(--fps-editor-border)'}`,
|
|
390
|
+
'border-radius:6px',
|
|
391
|
+
`background:${relation.active ? 'rgba(42, 125, 255, 0.16)' : 'var(--fps-editor-surface-subtle)'}`,
|
|
392
|
+
'padding:7px',
|
|
393
|
+
'display:flex',
|
|
394
|
+
'flex-direction:column',
|
|
395
|
+
'gap:6px',
|
|
396
|
+
'cursor:pointer',
|
|
397
|
+
relation.selected ? 'box-shadow:inset 3px 0 0 var(--fps-editor-accent)' : '',
|
|
398
|
+
relation.active ? 'outline:1px solid rgba(90, 170, 255, 0.35)' : '',
|
|
399
|
+
].filter(Boolean).join(';');
|
|
400
|
+
row.title = '点击高亮这条关系线';
|
|
401
|
+
row.dataset.editorTooltip = row.title;
|
|
402
|
+
const header = doc.createElement('div');
|
|
403
|
+
header.style.cssText = 'display:flex;align-items:center;gap:6px;min-width:0';
|
|
404
|
+
const title = doc.createElement('div');
|
|
405
|
+
title.textContent = relation.typeLabel;
|
|
406
|
+
title.style.cssText = 'flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:var(--fps-editor-text-strong);font-size:12px;font-weight:900';
|
|
407
|
+
header.appendChild(title);
|
|
408
|
+
header.appendChild(createBadge(doc, relation.directed ? '有方向' : '无方向', { compact: true }));
|
|
409
|
+
if (relation.selected)
|
|
410
|
+
header.appendChild(createBadge(doc, '一跳', { compact: true, tone: 'success' }));
|
|
411
|
+
if (relation.active)
|
|
412
|
+
header.appendChild(createBadge(doc, '高亮', { compact: true, tone: 'success' }));
|
|
413
|
+
row.appendChild(header);
|
|
414
|
+
const body = doc.createElement('div');
|
|
415
|
+
body.style.cssText = 'display:grid;grid-template-columns:minmax(0,1fr) auto minmax(0,1fr);align-items:center;gap:6px;font-size:11px;font-weight:800';
|
|
416
|
+
body.appendChild(createMarkerRelationEndpointPill(doc, relation.from));
|
|
417
|
+
const arrow = doc.createElement('div');
|
|
418
|
+
arrow.textContent = relation.directed ? '->' : '--';
|
|
419
|
+
arrow.style.cssText = 'color:var(--fps-editor-muted);font-family:var(--fps-editor-mono-font);font-weight:900';
|
|
420
|
+
body.appendChild(arrow);
|
|
421
|
+
body.appendChild(createMarkerRelationEndpointPill(doc, relation.to));
|
|
422
|
+
row.appendChild(body);
|
|
423
|
+
if (relation.status) {
|
|
424
|
+
const status = doc.createElement('div');
|
|
425
|
+
status.textContent = relation.status;
|
|
426
|
+
status.style.cssText = [
|
|
427
|
+
`color:${relation.statusTone === 'warning' ? 'var(--fps-editor-warn)' : 'var(--fps-editor-danger-text)'}`,
|
|
428
|
+
'font-size:11px',
|
|
429
|
+
'font-weight:800',
|
|
430
|
+
'line-height:1.35',
|
|
431
|
+
].join(';');
|
|
432
|
+
row.appendChild(status);
|
|
433
|
+
}
|
|
434
|
+
const actions = doc.createElement('div');
|
|
435
|
+
actions.style.cssText = 'display:flex;justify-content:flex-end;gap:6px;flex-wrap:wrap';
|
|
436
|
+
const reverseButton = createToolbarButton(doc, '反转', 'rotate');
|
|
437
|
+
reverseButton.type = 'button';
|
|
438
|
+
reverseButton.dataset.editorMarkerRelationAction = 'reverse';
|
|
439
|
+
reverseButton.style.padding = '4px 8px';
|
|
440
|
+
reverseButton.title = '交换关系起点和终点。';
|
|
441
|
+
reverseButton.dataset.editorTooltip = reverseButton.title;
|
|
442
|
+
actions.appendChild(reverseButton);
|
|
443
|
+
const deleteButton = createToolbarButton(doc, '删除', 'discard');
|
|
444
|
+
deleteButton.type = 'button';
|
|
445
|
+
deleteButton.dataset.editorMarkerRelationAction = 'delete';
|
|
446
|
+
deleteButton.style.padding = '4px 8px';
|
|
447
|
+
actions.appendChild(deleteButton);
|
|
448
|
+
row.appendChild(actions);
|
|
449
|
+
return row;
|
|
450
|
+
}
|
|
451
|
+
function createMarkerRelationEndpointPill(doc, endpoint) {
|
|
452
|
+
const pill = doc.createElement('div');
|
|
453
|
+
const kindLabel = endpoint.kind === 'marker'
|
|
454
|
+
? '标记'
|
|
455
|
+
: endpoint.kind === 'scene-object'
|
|
456
|
+
? '对象'
|
|
457
|
+
: endpoint.kind;
|
|
458
|
+
const hidden = endpoint.visibility === 'hidden';
|
|
459
|
+
const label = `${kindLabel} · ${endpoint.label || endpoint.id}${hidden ? ' · 已隐藏' : ''}`;
|
|
460
|
+
pill.textContent = label;
|
|
461
|
+
pill.title = endpoint.status ? `${label} · ${endpoint.status}` : label;
|
|
462
|
+
pill.dataset.editorTooltip = pill.title;
|
|
463
|
+
pill.style.cssText = [
|
|
464
|
+
'min-width:0',
|
|
465
|
+
'overflow:hidden',
|
|
466
|
+
'text-overflow:ellipsis',
|
|
467
|
+
'white-space:nowrap',
|
|
468
|
+
'border:1px solid var(--fps-editor-border)',
|
|
469
|
+
'border-radius:999px',
|
|
470
|
+
'padding:3px 6px',
|
|
471
|
+
`color:${endpoint.disabled ? 'var(--fps-editor-warn)' : hidden ? 'var(--fps-editor-muted)' : 'var(--fps-editor-text)'}`,
|
|
472
|
+
`background:${hidden ? 'var(--fps-editor-surface-subtle)' : 'var(--fps-editor-surface)'}`,
|
|
473
|
+
hidden ? 'opacity:0.72' : '',
|
|
474
|
+
].join(';');
|
|
475
|
+
return pill;
|
|
476
|
+
}
|
|
477
|
+
function createMarkerRelationEndpointSelect(doc, field, endpoints, preferredMarkerId) {
|
|
478
|
+
const select = doc.createElement('select');
|
|
479
|
+
select.dataset.editorMarkerRelationField = field;
|
|
480
|
+
select.style.cssText = createInspectorInputStyle();
|
|
481
|
+
for (const endpoint of endpoints) {
|
|
482
|
+
const option = doc.createElement('option');
|
|
483
|
+
option.value = encodeMarkerRelationEndpointValue(endpoint.kind, endpoint.id);
|
|
484
|
+
option.textContent = formatMarkerRelationEndpointOptionLabel(endpoint);
|
|
485
|
+
option.dataset.editorMarkerRelationEndpointKind = endpoint.kind;
|
|
486
|
+
option.dataset.editorMarkerRelationEndpointId = endpoint.id;
|
|
487
|
+
option.disabled = endpoint.disabled === true;
|
|
488
|
+
if (preferredMarkerId && endpoint.kind === 'marker' && endpoint.id === preferredMarkerId)
|
|
489
|
+
option.selected = true;
|
|
490
|
+
select.appendChild(option);
|
|
491
|
+
}
|
|
492
|
+
select.disabled = endpoints.length === 0;
|
|
493
|
+
return select;
|
|
494
|
+
}
|
|
495
|
+
function createMarkerRelationTypeSelect(doc, relationTypes) {
|
|
496
|
+
const select = doc.createElement('select');
|
|
497
|
+
select.dataset.editorMarkerRelationField = 'type';
|
|
498
|
+
select.style.cssText = createInspectorInputStyle();
|
|
499
|
+
for (const relationType of relationTypes) {
|
|
500
|
+
const option = doc.createElement('option');
|
|
501
|
+
option.value = relationType.type;
|
|
502
|
+
option.textContent = relationType.label || relationType.type;
|
|
503
|
+
option.dataset.editorMarkerRelationDirected = String(relationType.directed ?? true);
|
|
504
|
+
if (relationType.description)
|
|
505
|
+
option.title = relationType.description;
|
|
506
|
+
select.appendChild(option);
|
|
507
|
+
}
|
|
508
|
+
select.disabled = relationTypes.length === 0;
|
|
509
|
+
return select;
|
|
510
|
+
}
|
|
511
|
+
function createMarkerRelationDirectionSelect(doc) {
|
|
512
|
+
const select = doc.createElement('select');
|
|
513
|
+
select.dataset.editorMarkerRelationField = 'directed';
|
|
514
|
+
select.style.cssText = createInspectorInputStyle();
|
|
515
|
+
for (const [value, label] of [
|
|
516
|
+
['default', '按类型默认'],
|
|
517
|
+
['true', '有方向'],
|
|
518
|
+
['false', '无方向'],
|
|
519
|
+
]) {
|
|
520
|
+
const option = doc.createElement('option');
|
|
521
|
+
option.value = value;
|
|
522
|
+
option.textContent = label;
|
|
523
|
+
select.appendChild(option);
|
|
524
|
+
}
|
|
525
|
+
return select;
|
|
526
|
+
}
|
|
527
|
+
function createMarkerPanelSectionHeader(doc, text) {
|
|
528
|
+
const header = doc.createElement('div');
|
|
529
|
+
header.style.cssText = 'display:flex;align-items:center;gap:6px;min-width:0;color:var(--fps-editor-text-strong);font-size:11px;font-weight:900;text-transform:uppercase;letter-spacing:0';
|
|
530
|
+
const title = doc.createElement('div');
|
|
531
|
+
title.textContent = text;
|
|
532
|
+
title.style.cssText = 'flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap';
|
|
533
|
+
header.appendChild(title);
|
|
534
|
+
return header;
|
|
535
|
+
}
|
|
536
|
+
function formatMarkerRelationEndpointOptionLabel(endpoint) {
|
|
537
|
+
const kindLabel = endpoint.kind === 'marker'
|
|
538
|
+
? '标记'
|
|
539
|
+
: endpoint.kind === 'scene-object'
|
|
540
|
+
? '对象'
|
|
541
|
+
: endpoint.kind;
|
|
542
|
+
return `${kindLabel} · ${endpoint.label || endpoint.id}${endpoint.status ? ` · ${endpoint.status}` : ''}`;
|
|
543
|
+
}
|
|
544
|
+
function encodeMarkerRelationEndpointValue(kind, id) {
|
|
545
|
+
return `${kind}:${encodeURIComponent(id)}`;
|
|
546
|
+
}
|
|
547
|
+
function createMarkerTypeCreateBlock(doc) {
|
|
548
|
+
const block = createListItemBlock(doc);
|
|
549
|
+
block.dataset.editorMarkerTypeCreate = 'true';
|
|
550
|
+
block.style.cssText = [
|
|
551
|
+
block.style.cssText,
|
|
552
|
+
'display:flex',
|
|
553
|
+
'flex-direction:column',
|
|
554
|
+
'gap:7px',
|
|
555
|
+
].filter(Boolean).join(';');
|
|
556
|
+
const header = doc.createElement('div');
|
|
557
|
+
header.textContent = 'New Marker Type';
|
|
558
|
+
header.style.cssText = 'color:var(--fps-editor-text-strong);font-size:11px;font-weight:900;text-transform:uppercase;letter-spacing:0';
|
|
559
|
+
block.appendChild(header);
|
|
560
|
+
block.appendChild(createMarkerTypeFieldGrid(doc, {
|
|
561
|
+
prefix: 'create',
|
|
562
|
+
type: '',
|
|
563
|
+
label: '',
|
|
564
|
+
kind: 'region',
|
|
565
|
+
description: '',
|
|
566
|
+
typeReadOnly: false,
|
|
567
|
+
}));
|
|
568
|
+
const actions = doc.createElement('div');
|
|
569
|
+
actions.style.cssText = 'display:flex;justify-content:flex-end';
|
|
570
|
+
const button = createToolbarButton(doc, 'Create', 'object');
|
|
571
|
+
button.type = 'button';
|
|
572
|
+
button.dataset.editorMarkerTypeAction = 'create';
|
|
573
|
+
button.style.padding = '4px 8px';
|
|
574
|
+
actions.appendChild(button);
|
|
575
|
+
block.appendChild(actions);
|
|
576
|
+
return block;
|
|
577
|
+
}
|
|
578
|
+
function createMarkerTypeRow(doc, markerType) {
|
|
579
|
+
const block = createListItemBlock(doc);
|
|
580
|
+
block.dataset.editorMarkerTypeRow = markerType.type;
|
|
581
|
+
block.style.cssText = [
|
|
582
|
+
block.style.cssText,
|
|
583
|
+
'display:flex',
|
|
584
|
+
'flex-direction:column',
|
|
585
|
+
'gap:7px',
|
|
586
|
+
].filter(Boolean).join(';');
|
|
587
|
+
const header = doc.createElement('div');
|
|
588
|
+
header.style.cssText = 'display:flex;align-items:center;gap:6px;min-width:0';
|
|
589
|
+
const title = doc.createElement('div');
|
|
590
|
+
title.textContent = markerType.type;
|
|
591
|
+
title.style.cssText = 'flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:var(--fps-editor-text-strong);font-size:12px;font-weight:900';
|
|
592
|
+
header.appendChild(title);
|
|
593
|
+
if (markerType.builtIn)
|
|
594
|
+
header.appendChild(createBadge(doc, 'BUILT IN', { compact: true, tone: 'warning' }));
|
|
595
|
+
if ((markerType.usedByCount ?? 0) > 0)
|
|
596
|
+
header.appendChild(createBadge(doc, `${markerType.usedByCount} used`, { compact: true }));
|
|
597
|
+
block.appendChild(header);
|
|
598
|
+
block.appendChild(createMarkerTypeFieldGrid(doc, {
|
|
599
|
+
prefix: 'row',
|
|
600
|
+
type: markerType.type,
|
|
601
|
+
label: markerType.label,
|
|
602
|
+
kind: markerType.kind ?? '',
|
|
603
|
+
description: markerType.description ?? '',
|
|
604
|
+
typeReadOnly: true,
|
|
605
|
+
}));
|
|
606
|
+
const actions = doc.createElement('div');
|
|
607
|
+
actions.style.cssText = 'display:flex;align-items:center;justify-content:flex-end;gap:6px';
|
|
608
|
+
const updateButton = createToolbarButton(doc, 'Update', 'save');
|
|
609
|
+
updateButton.type = 'button';
|
|
610
|
+
updateButton.dataset.editorMarkerTypeAction = 'update';
|
|
611
|
+
updateButton.style.padding = '4px 8px';
|
|
612
|
+
actions.appendChild(updateButton);
|
|
613
|
+
const deleteButton = createToolbarButton(doc, 'Delete', 'discard');
|
|
614
|
+
deleteButton.type = 'button';
|
|
615
|
+
deleteButton.dataset.editorMarkerTypeAction = 'delete';
|
|
616
|
+
deleteButton.disabled = markerType.deletable === false;
|
|
617
|
+
deleteButton.title = markerType.deletable === false
|
|
618
|
+
? (markerType.builtIn ? 'Built-in marker types cannot be deleted.' : 'Marker types currently in use cannot be deleted.')
|
|
619
|
+
: 'Delete marker type';
|
|
620
|
+
deleteButton.dataset.editorTooltip = deleteButton.title;
|
|
621
|
+
deleteButton.style.padding = '4px 8px';
|
|
622
|
+
actions.appendChild(deleteButton);
|
|
623
|
+
block.appendChild(actions);
|
|
624
|
+
return block;
|
|
625
|
+
}
|
|
626
|
+
function createMarkerTypeFieldGrid(doc, input) {
|
|
627
|
+
const grid = doc.createElement('div');
|
|
628
|
+
grid.style.cssText = 'display:grid;grid-template-columns:72px minmax(0,1fr);gap:6px 8px;align-items:center';
|
|
629
|
+
grid.appendChild(createMarkerTypeFieldLabel(doc, 'Type'));
|
|
630
|
+
grid.appendChild(createMarkerTypeTextInput(doc, input.prefix, 'type', input.type, input.typeReadOnly));
|
|
631
|
+
grid.appendChild(createMarkerTypeFieldLabel(doc, 'Label'));
|
|
632
|
+
grid.appendChild(createMarkerTypeTextInput(doc, input.prefix, 'label', input.label, false));
|
|
633
|
+
grid.appendChild(createMarkerTypeFieldLabel(doc, 'Kind'));
|
|
634
|
+
grid.appendChild(createMarkerTypeTextInput(doc, input.prefix, 'kind', input.kind, false));
|
|
635
|
+
grid.appendChild(createMarkerTypeFieldLabel(doc, 'Description'));
|
|
636
|
+
grid.appendChild(createMarkerTypeTextarea(doc, input.prefix, 'description', input.description));
|
|
637
|
+
return grid;
|
|
638
|
+
}
|
|
639
|
+
function createMarkerTypeFieldLabel(doc, text) {
|
|
640
|
+
const label = doc.createElement('div');
|
|
641
|
+
label.textContent = text;
|
|
642
|
+
label.style.cssText = 'color:var(--fps-editor-muted);font-size:11px;font-weight:800';
|
|
643
|
+
return label;
|
|
644
|
+
}
|
|
645
|
+
function createMarkerTypeTextInput(doc, prefix, field, value, readOnly) {
|
|
646
|
+
const input = doc.createElement('input');
|
|
647
|
+
input.type = 'text';
|
|
648
|
+
input.value = value;
|
|
649
|
+
input.dataset.editorMarkerTypeField = field;
|
|
650
|
+
input.dataset.editorMarkerTypeFieldPrefix = prefix;
|
|
651
|
+
input.readOnly = readOnly;
|
|
652
|
+
input.style.cssText = [
|
|
653
|
+
createInspectorInputStyle(),
|
|
654
|
+
readOnly ? 'opacity:0.72' : '',
|
|
655
|
+
].filter(Boolean).join(';');
|
|
656
|
+
return input;
|
|
657
|
+
}
|
|
658
|
+
function createMarkerTypeTextarea(doc, prefix, field, value) {
|
|
659
|
+
const input = doc.createElement('textarea');
|
|
660
|
+
input.value = value;
|
|
661
|
+
input.rows = 2;
|
|
662
|
+
input.dataset.editorMarkerTypeField = field;
|
|
663
|
+
input.dataset.editorMarkerTypeFieldPrefix = prefix;
|
|
664
|
+
input.style.cssText = [
|
|
665
|
+
createInspectorInputStyle(),
|
|
666
|
+
'height:auto',
|
|
667
|
+
'min-height:46px',
|
|
668
|
+
'resize:vertical',
|
|
669
|
+
'padding:6px',
|
|
670
|
+
'line-height:1.35',
|
|
671
|
+
].join(';');
|
|
672
|
+
return input;
|
|
673
|
+
}
|
|
167
674
|
export function renderRenderingPanel(doc, panel, state) {
|
|
168
675
|
clearElement(panel);
|
|
169
676
|
configureLocalEditorDockPanelContent(panel);
|
|
@@ -926,8 +1433,17 @@ function renderAssetBrowserContent(doc, panel, state, variant = 'list', activeTa
|
|
|
926
1433
|
export function createAssetPreviewElement(doc, asset, variant) {
|
|
927
1434
|
const size = variant === 'modal' ? 220 : variant === 'grid' ? 46 : 24;
|
|
928
1435
|
const preview = asset.preview;
|
|
929
|
-
if (!preview)
|
|
930
|
-
|
|
1436
|
+
if (!preview) {
|
|
1437
|
+
const kind = resolveLocalEditorBrowserAssetKind(asset);
|
|
1438
|
+
const icon = kind === 'prefab'
|
|
1439
|
+
? 'prefab'
|
|
1440
|
+
: kind === 'material'
|
|
1441
|
+
? 'material-slot'
|
|
1442
|
+
: kind === 'texture'
|
|
1443
|
+
? 'asset'
|
|
1444
|
+
: 'object';
|
|
1445
|
+
return createLocalEditorIcon(doc, icon, { size: variant === 'grid' ? 18 : 16 });
|
|
1446
|
+
}
|
|
931
1447
|
const frame = doc.createElement('div');
|
|
932
1448
|
frame.style.cssText = [
|
|
933
1449
|
`width:${size}px`,
|
|
@@ -1072,19 +1588,7 @@ function appendAssetBrowserTabs(doc, panel, state, activeTab) {
|
|
|
1072
1588
|
function filterAssetsByBrowserTab(assets, tab) {
|
|
1073
1589
|
if (tab === 'all')
|
|
1074
1590
|
return [...assets];
|
|
1075
|
-
return assets.filter(asset =>
|
|
1076
|
-
}
|
|
1077
|
-
function resolveAssetBrowserTab(asset) {
|
|
1078
|
-
const kind = `${asset.kind ?? ''}`.trim().toLowerCase();
|
|
1079
|
-
const id = `${asset.id ?? ''}`.trim().toLowerCase();
|
|
1080
|
-
const assetId = `${asset.assetId ?? ''}`.trim().toLowerCase();
|
|
1081
|
-
if (kind === 'material' || kind === 'materials' || id.startsWith('material:') || assetId.startsWith('material:')) {
|
|
1082
|
-
return 'materials';
|
|
1083
|
-
}
|
|
1084
|
-
if (kind === 'texture' || kind === 'image' || kind === 'textures' || id.startsWith('texture:') || assetId.startsWith('texture:')) {
|
|
1085
|
-
return 'textures';
|
|
1086
|
-
}
|
|
1087
|
-
return 'models';
|
|
1591
|
+
return assets.filter(asset => resolveLocalEditorAssetBrowserTab(asset) === tab);
|
|
1088
1592
|
}
|
|
1089
1593
|
function renderHistoryPanel(doc, panel, history) {
|
|
1090
1594
|
configureLocalEditorDockPanelContent(panel);
|
|
@@ -1145,7 +1649,7 @@ export function renderInspectorPanel(doc, panel, state, filter = '', options = {
|
|
|
1145
1649
|
const title = doc.createElement('h2');
|
|
1146
1650
|
title.style.cssText = 'display:flex;align-items:center;gap:6px;font-size:13px;margin:-8px -8px 8px;padding:7px 8px;border-bottom:1px solid var(--fps-editor-divider);background:var(--fps-editor-chrome-dark);font-weight:800;color:var(--fps-editor-text-strong)';
|
|
1147
1651
|
title.appendChild(createLocalEditorIcon(doc, 'inspector'));
|
|
1148
|
-
title.appendChild(createHeadingLabel(doc, 'Inspector'));
|
|
1652
|
+
title.appendChild(createHeadingLabel(doc, options.title ?? 'Inspector'));
|
|
1149
1653
|
panel.appendChild(title);
|
|
1150
1654
|
const search = createInspectorSearchInput(doc, filter);
|
|
1151
1655
|
panel.appendChild(search);
|
|
@@ -1157,7 +1661,7 @@ export function renderInspectorPanel(doc, panel, state, filter = '', options = {
|
|
|
1157
1661
|
: state.inspectorObject ?? (state.serializedObject ? createLegacyInspectorObject(state.serializedObject) : null);
|
|
1158
1662
|
if (!inspectorObject) {
|
|
1159
1663
|
const empty = doc.createElement('div');
|
|
1160
|
-
empty.textContent = '请从层级树或 Scene View 中选择一个 GameObject。';
|
|
1664
|
+
empty.textContent = options.emptyText ?? '请从层级树或 Scene View 中选择一个 GameObject 或 Marker。';
|
|
1161
1665
|
empty.style.cssText = 'color:var(--fps-editor-muted);line-height:1.45';
|
|
1162
1666
|
viewport.appendChild(empty);
|
|
1163
1667
|
panel.appendChild(viewport);
|
|
@@ -1165,7 +1669,7 @@ export function renderInspectorPanel(doc, panel, state, filter = '', options = {
|
|
|
1165
1669
|
return;
|
|
1166
1670
|
}
|
|
1167
1671
|
const controlRegistry = createLocalEditorBrowserInspectorControlRegistry(options.controls, options.controlConflict);
|
|
1168
|
-
appendInspectorSummary(doc, viewport, inspectorObject, selectionCount);
|
|
1672
|
+
appendInspectorSummary(doc, viewport, inspectorObject, selectionCount, options.summaryTitle);
|
|
1169
1673
|
const visibleSections = filterInspectorSections(inspectorObject.sections.filter(section => section.placement !== 'summary'), filter);
|
|
1170
1674
|
if (visibleSections.length === 0) {
|
|
1171
1675
|
if (!filter.trim() && inspectorObject.sections.length === 0) {
|
|
@@ -1220,17 +1724,21 @@ function createInspectorSearchInput(doc, value) {
|
|
|
1220
1724
|
].join(';');
|
|
1221
1725
|
return input;
|
|
1222
1726
|
}
|
|
1223
|
-
function appendInspectorSummary(doc, panel, inspectorObject, selectionCount) {
|
|
1727
|
+
function appendInspectorSummary(doc, panel, inspectorObject, selectionCount, summaryTitle) {
|
|
1224
1728
|
const block = createInspectorComponentBlock(doc);
|
|
1225
1729
|
const header = doc.createElement('div');
|
|
1226
1730
|
header.style.cssText = 'display:flex;align-items:center;justify-content:space-between;gap:8px;margin:0 0 8px';
|
|
1227
1731
|
const title = doc.createElement('h3');
|
|
1228
|
-
|
|
1732
|
+
const targetKind = inspectorObject.selection.targetKind;
|
|
1733
|
+
const markerLike = targetKind === 'marker' || targetKind === 'marker-graph';
|
|
1734
|
+
title.textContent = summaryTitle ?? (selectionCount > 1
|
|
1735
|
+
? markerLike ? 'Markers' : 'Selection'
|
|
1736
|
+
: targetKind === 'marker-graph' ? 'Marker Graph' : targetKind === 'marker' ? 'Marker' : 'GameObject');
|
|
1229
1737
|
title.style.cssText = 'font-size:12px;margin:0;font-weight:900;color:var(--fps-editor-text-strong)';
|
|
1230
1738
|
header.appendChild(title);
|
|
1231
1739
|
header.appendChild(createInspectorAccessBadge(doc, 'readonly'));
|
|
1232
1740
|
block.appendChild(header);
|
|
1233
|
-
appendReadOnlyRow(doc, block, selectionCount > 1 ? 'Selected' : 'Name', inspectorObject.label ?? inspectorObject.activeId ?? inspectorObject.targetIds[0] ?? '无');
|
|
1741
|
+
appendReadOnlyRow(doc, block, selectionCount > 1 ? 'Selected' : targetKind === 'marker' ? 'Label' : 'Name', inspectorObject.label ?? inspectorObject.activeId ?? inspectorObject.targetIds[0] ?? '无');
|
|
1234
1742
|
appendReadOnlyRow(doc, block, 'Active ID', inspectorObject.activeId ?? '无');
|
|
1235
1743
|
if (selectionCount > 1)
|
|
1236
1744
|
appendReadOnlyRow(doc, block, 'Count', String(selectionCount));
|
|
@@ -1505,6 +2013,15 @@ function readInspectorControlOptionNumber(property, key) {
|
|
|
1505
2013
|
const value = property.controlOptions?.[key];
|
|
1506
2014
|
return typeof value === 'number' && Number.isFinite(value) ? value : 0;
|
|
1507
2015
|
}
|
|
2016
|
+
function readInspectorControlOptionBoolean(property, key) {
|
|
2017
|
+
return property.controlOptions?.[key] === true;
|
|
2018
|
+
}
|
|
2019
|
+
function readInspectorControlOptionRecord(property, key) {
|
|
2020
|
+
const value = property.controlOptions?.[key];
|
|
2021
|
+
return value && typeof value === 'object' && !Array.isArray(value)
|
|
2022
|
+
? value
|
|
2023
|
+
: null;
|
|
2024
|
+
}
|
|
1508
2025
|
function readInspectorAssetPickerPreview(property, key) {
|
|
1509
2026
|
const value = property.controlOptions?.[key];
|
|
1510
2027
|
return isLocalEditorBrowserAssetPreview(value) ? value : undefined;
|
|
@@ -1590,6 +2107,20 @@ const builtinInspectorControlRegistrations = [
|
|
|
1590
2107
|
customControl: 'rendering-action-button',
|
|
1591
2108
|
render: ({ doc, property }) => createInspectorRenderingActionButtonControl(doc, property),
|
|
1592
2109
|
},
|
|
2110
|
+
{
|
|
2111
|
+
id: 'builtin.inspector-action-button',
|
|
2112
|
+
order: 93,
|
|
2113
|
+
control: 'custom',
|
|
2114
|
+
customControl: 'inspector-action-button',
|
|
2115
|
+
render: ({ doc, target, property }) => createInspectorActionButtonControl(doc, target, property),
|
|
2116
|
+
},
|
|
2117
|
+
{
|
|
2118
|
+
id: 'builtin.marker-relation-editor',
|
|
2119
|
+
order: 91,
|
|
2120
|
+
control: 'custom',
|
|
2121
|
+
customControl: 'marker-relation-editor',
|
|
2122
|
+
render: ({ doc, property }) => createInspectorMarkerRelationEditorControl(doc, property),
|
|
2123
|
+
},
|
|
1593
2124
|
{
|
|
1594
2125
|
id: 'builtin.asset-picker-card',
|
|
1595
2126
|
order: 95,
|
|
@@ -1598,9 +2129,16 @@ const builtinInspectorControlRegistrations = [
|
|
|
1598
2129
|
render: ({ doc, target, property, bindInput }) => createInspectorAssetPickerCardControl(doc, target, property, bindInput),
|
|
1599
2130
|
},
|
|
1600
2131
|
{
|
|
1601
|
-
id: 'builtin.
|
|
2132
|
+
id: 'builtin.ground-decal-layout',
|
|
1602
2133
|
order: 96,
|
|
1603
2134
|
control: 'custom',
|
|
2135
|
+
customControl: 'ground-decal-layout',
|
|
2136
|
+
render: ({ doc, target, property, bindInput }) => createInspectorGroundDecalLayoutControl(doc, target, property, bindInput),
|
|
2137
|
+
},
|
|
2138
|
+
{
|
|
2139
|
+
id: 'builtin.material-slot-list',
|
|
2140
|
+
order: 97,
|
|
2141
|
+
control: 'custom',
|
|
1604
2142
|
customControl: 'material-slot-list',
|
|
1605
2143
|
render: ({ doc, property }) => createInspectorMaterialSlotListControl(doc, property),
|
|
1606
2144
|
},
|
|
@@ -1627,7 +2165,7 @@ function appendInspectorPropertyRow(doc, parent, inspectorObject, property, cont
|
|
|
1627
2165
|
const control = renderInspectorControl(doc, controlRegistry, inspectorObject, property);
|
|
1628
2166
|
const access = getInspectorPropertyAccess(property);
|
|
1629
2167
|
const effect = getInspectorPropertyEffect(property);
|
|
1630
|
-
if (property.customControl === 'material-slot-list') {
|
|
2168
|
+
if (property.customControl === 'material-slot-list' || property.customControl === 'marker-relation-editor') {
|
|
1631
2169
|
control.dataset.editorInspectorAccess = access;
|
|
1632
2170
|
control.dataset.editorInspectorEffect = effect;
|
|
1633
2171
|
control.title = createInspectorStatusTitle(access, effect, property.tooltip ?? property.disabledReason);
|
|
@@ -1710,6 +2248,8 @@ function localEditorBrowserInspectorControlSupports(registration, context) {
|
|
|
1710
2248
|
return registration.supports?.(context) ?? true;
|
|
1711
2249
|
}
|
|
1712
2250
|
function normalizeInspectorRenderingActionProperty(property) {
|
|
2251
|
+
if (property.customControl && property.customControl !== 'rendering-action-button')
|
|
2252
|
+
return property;
|
|
1713
2253
|
const actionId = readInspectorRenderingActionId(property);
|
|
1714
2254
|
if (!actionId)
|
|
1715
2255
|
return property;
|
|
@@ -1739,6 +2279,8 @@ function readInspectorRenderingActionId(property) {
|
|
|
1739
2279
|
function isInspectorCustomControlFallback(customControl, property) {
|
|
1740
2280
|
if (customControl !== 'rendering-action-button')
|
|
1741
2281
|
return false;
|
|
2282
|
+
if (property.customControl)
|
|
2283
|
+
return false;
|
|
1742
2284
|
return !!readInspectorRenderingActionId(property);
|
|
1743
2285
|
}
|
|
1744
2286
|
function appendInspectorVectorInputs(doc, parent, inspectorObject, label, properties, layout = 'block', options = {}) {
|
|
@@ -1760,10 +2302,13 @@ function appendInspectorVectorInputs(doc, parent, inspectorObject, label, proper
|
|
|
1760
2302
|
actions.style.cssText = 'display:inline-flex;align-items:center;gap:5px;flex:0 0 auto';
|
|
1761
2303
|
labelElement.appendChild(actions);
|
|
1762
2304
|
const groupPath = resolveLocalEditorBrowserInspectorVectorGroupPathFromProperties(properties);
|
|
1763
|
-
const
|
|
2305
|
+
const forceLocked = properties.some(property => readInspectorControlOptionBoolean(property, 'forceVectorLock'));
|
|
2306
|
+
const locked = forceLocked || (!!groupPath && options.lockedVectorGroups?.has(groupPath) === true);
|
|
1764
2307
|
if (groupPath && access === 'editable') {
|
|
1765
|
-
const lockButton = createInspectorVectorHeaderButton(doc, locked ? `Unlock ${label} axes` : `Lock ${label} axes`, locked);
|
|
1766
|
-
|
|
2308
|
+
const lockButton = createInspectorVectorHeaderButton(doc, forceLocked ? `${label} axes are locked` : locked ? `Unlock ${label} axes` : `Lock ${label} axes`, locked);
|
|
2309
|
+
if (!forceLocked)
|
|
2310
|
+
lockButton.dataset.editorInspectorVectorLock = groupPath;
|
|
2311
|
+
lockButton.disabled = forceLocked;
|
|
1767
2312
|
lockButton.dataset.editorInspectorVectorLockState = locked ? 'locked' : 'unlocked';
|
|
1768
2313
|
lockButton.setAttribute('aria-pressed', locked ? 'true' : 'false');
|
|
1769
2314
|
lockButton.appendChild(createLocalEditorIcon(doc, 'lock', { size: 12, strokeWidth: 2.2 }));
|
|
@@ -2001,7 +2546,8 @@ function createInspectorTextControl(doc, target, property) {
|
|
|
2001
2546
|
function createInspectorBooleanControl(doc, target, property) {
|
|
2002
2547
|
const input = createInspectorInputBase(doc, target, property);
|
|
2003
2548
|
input.type = 'checkbox';
|
|
2004
|
-
input.checked = property.
|
|
2549
|
+
input.checked = property.value === true;
|
|
2550
|
+
input.indeterminate = property.mixed === true;
|
|
2005
2551
|
input.style.cssText = [
|
|
2006
2552
|
'width:16px',
|
|
2007
2553
|
'height:16px',
|
|
@@ -2069,7 +2615,11 @@ function createInspectorColorControl(doc, target, property) {
|
|
|
2069
2615
|
}
|
|
2070
2616
|
function createInspectorOpenRightDockTabControl(doc, property) {
|
|
2071
2617
|
const targetTabOption = readInspectorControlOptionString(property, 'targetTab');
|
|
2072
|
-
const targetTab = targetTabOption === 'inspector'
|
|
2618
|
+
const targetTab = targetTabOption === 'inspector'
|
|
2619
|
+
? 'inspector'
|
|
2620
|
+
: targetTabOption === 'markers'
|
|
2621
|
+
? 'markers'
|
|
2622
|
+
: 'rendering';
|
|
2073
2623
|
const label = readInspectorControlOptionString(property, 'label')
|
|
2074
2624
|
|| (typeof property.value === 'string' ? property.value : property.label);
|
|
2075
2625
|
const button = createToolbarButton(doc, label, RIGHT_DOCK_TAB_ICONS[targetTab]);
|
|
@@ -2110,6 +2660,56 @@ function createInspectorRenderingActionButtonControl(doc, property) {
|
|
|
2110
2660
|
].filter(Boolean).join(';');
|
|
2111
2661
|
return button;
|
|
2112
2662
|
}
|
|
2663
|
+
function createInspectorActionButtonControl(doc, target, property) {
|
|
2664
|
+
const actionId = readInspectorControlOptionString(property, 'actionId')
|
|
2665
|
+
|| (typeof property.value === 'string' ? property.value : '');
|
|
2666
|
+
const iconName = readInspectorControlOptionString(property, 'icon');
|
|
2667
|
+
const icon = isLocalEditorIconName(iconName) ? iconName : 'asset';
|
|
2668
|
+
const label = readInspectorControlOptionString(property, 'label')
|
|
2669
|
+
|| (typeof property.value === 'string' ? property.value : property.label);
|
|
2670
|
+
const button = createToolbarButton(doc, label, icon);
|
|
2671
|
+
button.type = 'button';
|
|
2672
|
+
if (actionId)
|
|
2673
|
+
button.dataset.editorInspectorAction = actionId;
|
|
2674
|
+
button.dataset.editorInspectorActionTargetId = target.activeId ?? '';
|
|
2675
|
+
button.dataset.editorInspectorActionTargetIds = JSON.stringify(target.targetIds);
|
|
2676
|
+
button.dataset.editorInspectorActionPath = property.path;
|
|
2677
|
+
const params = readInspectorControlOptionRecord(property, 'params');
|
|
2678
|
+
if (params)
|
|
2679
|
+
button.dataset.editorInspectorActionParams = JSON.stringify(params);
|
|
2680
|
+
button.disabled = property.readOnly === true
|
|
2681
|
+
|| property.effect === 'unsupported'
|
|
2682
|
+
|| property.controlOptions?.disabled === true
|
|
2683
|
+
|| !actionId;
|
|
2684
|
+
button.title = property.tooltip ?? label;
|
|
2685
|
+
button.dataset.editorTooltip = button.title;
|
|
2686
|
+
button.style.cssText = [
|
|
2687
|
+
button.style.cssText,
|
|
2688
|
+
'width:100%',
|
|
2689
|
+
'justify-content:center',
|
|
2690
|
+
'height:28px',
|
|
2691
|
+
].filter(Boolean).join(';');
|
|
2692
|
+
return button;
|
|
2693
|
+
}
|
|
2694
|
+
function createInspectorMarkerRelationEditorControl(doc, property) {
|
|
2695
|
+
const panel = readInspectorMarkerRelationPanelState(property);
|
|
2696
|
+
const wrapper = doc.createElement('div');
|
|
2697
|
+
wrapper.style.cssText = 'display:flex;flex-direction:column;gap:8px;width:100%;min-width:0';
|
|
2698
|
+
if (!panel) {
|
|
2699
|
+
wrapper.appendChild(createEmptyState(doc, '当前 marker 没有可编辑的关系数据。'));
|
|
2700
|
+
return wrapper;
|
|
2701
|
+
}
|
|
2702
|
+
wrapper.appendChild(createMarkerRelationCreateBlock(doc, panel));
|
|
2703
|
+
wrapper.appendChild(createMarkerRelationListBlock(doc, (panel.relations ?? []).filter(relation => relation.selected)));
|
|
2704
|
+
return wrapper;
|
|
2705
|
+
}
|
|
2706
|
+
function readInspectorMarkerRelationPanelState(property) {
|
|
2707
|
+
const value = property.controlOptions?.markerPanel ?? property.value;
|
|
2708
|
+
if (!value || typeof value !== 'object' || Array.isArray(value))
|
|
2709
|
+
return null;
|
|
2710
|
+
const panel = value;
|
|
2711
|
+
return Array.isArray(panel.markerTypes) ? panel : null;
|
|
2712
|
+
}
|
|
2113
2713
|
function createInspectorMaterialSlotListControl(doc, property) {
|
|
2114
2714
|
const wrapper = doc.createElement('div');
|
|
2115
2715
|
wrapper.dataset.editorInspectorMaterialSlotList = 'true';
|
|
@@ -2277,6 +2877,846 @@ function readStringField(record, key) {
|
|
|
2277
2877
|
const value = record[key];
|
|
2278
2878
|
return typeof value === 'string' ? value : '';
|
|
2279
2879
|
}
|
|
2880
|
+
const inspectorGroundDecalLayoutImageCache = new Map();
|
|
2881
|
+
const INSPECTOR_GROUND_DECAL_LAYOUT_MIN_SIZE = 0.02;
|
|
2882
|
+
const INSPECTOR_GROUND_DECAL_LAYOUT_MAX_SIZE = 1.5;
|
|
2883
|
+
function createInspectorGroundDecalLayoutControl(doc, _target, property, bindInput) {
|
|
2884
|
+
const editable = isInspectorPropertyEditable(property);
|
|
2885
|
+
const layers = readInspectorGroundDecalLayoutLayers(property);
|
|
2886
|
+
const composite = isInspectorGroundDecalLayoutCompositeValue(property.value);
|
|
2887
|
+
const textureSize = readInspectorGroundDecalLayoutTextureSize(property.value);
|
|
2888
|
+
const mask = readInspectorGroundDecalLayoutMask(property.value);
|
|
2889
|
+
const aspectRatio = Math.max(0.2, Math.min(4, readInspectorControlOptionNumber(property, 'aspectRatio') || 1));
|
|
2890
|
+
const surfaceMaxWidth = Math.max(96, Math.round(220 * aspectRatio));
|
|
2891
|
+
const wrapper = doc.createElement('div');
|
|
2892
|
+
wrapper.dataset.editorInspectorGroundDecalLayout = 'true';
|
|
2893
|
+
wrapper.style.cssText = [
|
|
2894
|
+
'display:flex',
|
|
2895
|
+
'flex-direction:column',
|
|
2896
|
+
'gap:8px',
|
|
2897
|
+
'min-width:0',
|
|
2898
|
+
].join(';');
|
|
2899
|
+
const input = doc.createElement('input');
|
|
2900
|
+
input.type = 'hidden';
|
|
2901
|
+
input.value = serializeInspectorGroundDecalLayoutValue(layers, composite);
|
|
2902
|
+
bindInput(input, { source: 'custom' });
|
|
2903
|
+
input.dataset.serializedJsonValue = 'true';
|
|
2904
|
+
wrapper.appendChild(input);
|
|
2905
|
+
const surface = doc.createElement('div');
|
|
2906
|
+
surface.dataset.editorInspectorGroundDecalLayoutSurface = 'true';
|
|
2907
|
+
surface.style.cssText = [
|
|
2908
|
+
'position:relative',
|
|
2909
|
+
`width:min(100%, ${surfaceMaxWidth}px)`,
|
|
2910
|
+
'max-width:100%',
|
|
2911
|
+
'align-self:center',
|
|
2912
|
+
`aspect-ratio:${aspectRatio} / 1`,
|
|
2913
|
+
'border:1px solid var(--fps-editor-editable-border)',
|
|
2914
|
+
'border-radius:4px',
|
|
2915
|
+
'background:linear-gradient(90deg, rgba(255,255,255,0.05) 1px, transparent 1px), linear-gradient(0deg, rgba(255,255,255,0.05) 1px, transparent 1px), var(--fps-editor-field)',
|
|
2916
|
+
'background-size:20px 20px',
|
|
2917
|
+
'overflow:hidden',
|
|
2918
|
+
editable ? 'cursor:grab' : 'cursor:not-allowed',
|
|
2919
|
+
].join(';');
|
|
2920
|
+
wrapper.appendChild(surface);
|
|
2921
|
+
const previewCanvas = doc.createElement('canvas');
|
|
2922
|
+
previewCanvas.width = textureSize.width;
|
|
2923
|
+
previewCanvas.height = textureSize.height;
|
|
2924
|
+
previewCanvas.dataset.editorInspectorGroundDecalLayoutPreview = 'true';
|
|
2925
|
+
previewCanvas.style.cssText = [
|
|
2926
|
+
'position:absolute',
|
|
2927
|
+
'inset:0',
|
|
2928
|
+
'width:100%',
|
|
2929
|
+
'height:100%',
|
|
2930
|
+
'pointer-events:none',
|
|
2931
|
+
].join(';');
|
|
2932
|
+
surface.appendChild(previewCanvas);
|
|
2933
|
+
const guide = doc.createElement('div');
|
|
2934
|
+
guide.style.cssText = [
|
|
2935
|
+
'position:absolute',
|
|
2936
|
+
'inset:8px',
|
|
2937
|
+
'border:1px solid var(--fps-editor-border-soft)',
|
|
2938
|
+
'border-radius:8px',
|
|
2939
|
+
'pointer-events:none',
|
|
2940
|
+
].join(';');
|
|
2941
|
+
surface.appendChild(guide);
|
|
2942
|
+
const fields = doc.createElement('div');
|
|
2943
|
+
fields.style.cssText = [
|
|
2944
|
+
'display:flex',
|
|
2945
|
+
'flex-direction:column',
|
|
2946
|
+
'gap:6px',
|
|
2947
|
+
'min-width:0',
|
|
2948
|
+
].join(';');
|
|
2949
|
+
wrapper.appendChild(fields);
|
|
2950
|
+
const boxByLayer = new Map();
|
|
2951
|
+
const fieldInputsByLayer = new Map();
|
|
2952
|
+
const editableLayers = layers.filter(isInspectorGroundDecalLayoutLayerEditable);
|
|
2953
|
+
const scaleLockedByLayer = new Map();
|
|
2954
|
+
for (const layer of editableLayers) {
|
|
2955
|
+
scaleLockedByLayer.set(layer, isInspectorGroundDecalLayoutUniformScaleLayer(layer));
|
|
2956
|
+
}
|
|
2957
|
+
let previewRenderVersion = 0;
|
|
2958
|
+
const renderPreview = () => {
|
|
2959
|
+
const version = ++previewRenderVersion;
|
|
2960
|
+
void renderInspectorGroundDecalLayoutPreview(previewCanvas, layers, textureSize, mask, () => version === previewRenderVersion);
|
|
2961
|
+
};
|
|
2962
|
+
const updateLayerRect = (layer, rect) => {
|
|
2963
|
+
layer.rect = normalizeInspectorGroundDecalLayoutRect(rect);
|
|
2964
|
+
const layerFieldInputs = fieldInputsByLayer.get(layer);
|
|
2965
|
+
if (layerFieldInputs)
|
|
2966
|
+
updateInspectorGroundDecalLayoutFieldInputs(layerFieldInputs, layer.rect);
|
|
2967
|
+
const layerBox = boxByLayer.get(layer);
|
|
2968
|
+
if (layerBox)
|
|
2969
|
+
renderInspectorGroundDecalLayoutBox(layerBox, layer.rect);
|
|
2970
|
+
renderPreview();
|
|
2971
|
+
};
|
|
2972
|
+
const commitLayout = () => {
|
|
2973
|
+
commitInspectorGroundDecalLayoutValue(input, layers, fieldInputsByLayer, composite);
|
|
2974
|
+
};
|
|
2975
|
+
let dragActive = false;
|
|
2976
|
+
const beginDrag = (event, layer, box, mode) => {
|
|
2977
|
+
if (!editable)
|
|
2978
|
+
return;
|
|
2979
|
+
if ('button' in event && event.button !== 0)
|
|
2980
|
+
return;
|
|
2981
|
+
event.preventDefault();
|
|
2982
|
+
event.stopPropagation();
|
|
2983
|
+
if (dragActive)
|
|
2984
|
+
return;
|
|
2985
|
+
dragActive = true;
|
|
2986
|
+
const bounds = surface.getBoundingClientRect();
|
|
2987
|
+
const startX = event.clientX;
|
|
2988
|
+
const startY = event.clientY;
|
|
2989
|
+
const start = layer.rect;
|
|
2990
|
+
const captureTarget = event.currentTarget instanceof HTMLElement ? event.currentTarget : box;
|
|
2991
|
+
const pointerId = 'pointerId' in event ? event.pointerId : null;
|
|
2992
|
+
if (pointerId !== null) {
|
|
2993
|
+
try {
|
|
2994
|
+
captureTarget.setPointerCapture(pointerId);
|
|
2995
|
+
}
|
|
2996
|
+
catch {
|
|
2997
|
+
// Capture can fail if the browser has already ended the pointer stream.
|
|
2998
|
+
}
|
|
2999
|
+
}
|
|
3000
|
+
surface.style.cursor = mode === 'move' ? 'grabbing' : 'nwse-resize';
|
|
3001
|
+
box.style.cursor = surface.style.cursor;
|
|
3002
|
+
const move = (moveEvent) => {
|
|
3003
|
+
moveEvent.preventDefault();
|
|
3004
|
+
const dx = bounds.width > 0 ? (moveEvent.clientX - startX) / bounds.width : 0;
|
|
3005
|
+
const dy = bounds.height > 0 ? (moveEvent.clientY - startY) / bounds.height : 0;
|
|
3006
|
+
const nextRect = mode === 'move'
|
|
3007
|
+
? normalizeInspectorGroundDecalLayoutRect({
|
|
3008
|
+
...start,
|
|
3009
|
+
x: start.x + dx,
|
|
3010
|
+
z: start.z - dy,
|
|
3011
|
+
})
|
|
3012
|
+
: resizeInspectorGroundDecalLayoutRect(layer, start, dx, dy, scaleLockedByLayer.get(layer) === true);
|
|
3013
|
+
updateLayerRect(layer, nextRect);
|
|
3014
|
+
};
|
|
3015
|
+
const end = () => {
|
|
3016
|
+
doc.defaultView?.removeEventListener('pointermove', move);
|
|
3017
|
+
doc.defaultView?.removeEventListener('mousemove', move);
|
|
3018
|
+
doc.defaultView?.removeEventListener('pointerup', end);
|
|
3019
|
+
doc.defaultView?.removeEventListener('mouseup', end);
|
|
3020
|
+
if (pointerId !== null) {
|
|
3021
|
+
try {
|
|
3022
|
+
captureTarget.releasePointerCapture(pointerId);
|
|
3023
|
+
}
|
|
3024
|
+
catch {
|
|
3025
|
+
// Ignore capture release errors after pointer cancellation.
|
|
3026
|
+
}
|
|
3027
|
+
}
|
|
3028
|
+
dragActive = false;
|
|
3029
|
+
surface.style.cursor = editable ? 'grab' : 'not-allowed';
|
|
3030
|
+
box.style.cursor = editable ? 'grab' : 'not-allowed';
|
|
3031
|
+
commitLayout();
|
|
3032
|
+
};
|
|
3033
|
+
doc.defaultView?.addEventListener('pointermove', move);
|
|
3034
|
+
doc.defaultView?.addEventListener('pointerup', end, { once: true });
|
|
3035
|
+
doc.defaultView?.addEventListener('mousemove', move);
|
|
3036
|
+
doc.defaultView?.addEventListener('mouseup', end, { once: true });
|
|
3037
|
+
};
|
|
3038
|
+
for (const layer of editableLayers) {
|
|
3039
|
+
const box = createInspectorGroundDecalLayoutBox(doc, layer, editable);
|
|
3040
|
+
surface.appendChild(box);
|
|
3041
|
+
boxByLayer.set(layer, box);
|
|
3042
|
+
const handle = box.querySelector('[data-editor-inspector-ground-decal-layout-resize]');
|
|
3043
|
+
box.addEventListener('pointerdown', (event) => {
|
|
3044
|
+
const target = event.target instanceof HTMLElement ? event.target : null;
|
|
3045
|
+
if (target?.closest('[data-editor-inspector-ground-decal-layout-resize]'))
|
|
3046
|
+
return;
|
|
3047
|
+
beginDrag(event, layer, box, 'move');
|
|
3048
|
+
});
|
|
3049
|
+
box.addEventListener('mousedown', (event) => {
|
|
3050
|
+
const target = event.target instanceof HTMLElement ? event.target : null;
|
|
3051
|
+
if (target?.closest('[data-editor-inspector-ground-decal-layout-resize]'))
|
|
3052
|
+
return;
|
|
3053
|
+
beginDrag(event, layer, box, 'move');
|
|
3054
|
+
});
|
|
3055
|
+
handle?.addEventListener('pointerdown', (event) => beginDrag(event, layer, box, 'resize'));
|
|
3056
|
+
handle?.addEventListener('mousedown', (event) => beginDrag(event, layer, box, 'resize'));
|
|
3057
|
+
let layerInputs;
|
|
3058
|
+
layerInputs = createInspectorGroundDecalLayoutLayerFields(doc, fields, layer, editable, (changedKey) => {
|
|
3059
|
+
const next = readRectFromLayoutFieldInputs(layerInputs, layer.rect, layer, changedKey, scaleLockedByLayer.get(layer) === true);
|
|
3060
|
+
if (!next)
|
|
3061
|
+
return;
|
|
3062
|
+
updateLayerRect(layer, next);
|
|
3063
|
+
}, (changedKey) => {
|
|
3064
|
+
const next = readRectFromLayoutFieldInputs(layerInputs, layer.rect, layer, changedKey, scaleLockedByLayer.get(layer) === true);
|
|
3065
|
+
if (!next)
|
|
3066
|
+
return;
|
|
3067
|
+
updateLayerRect(layer, next);
|
|
3068
|
+
commitLayout();
|
|
3069
|
+
}, isInspectorGroundDecalLayoutUniformScaleLayer(layer) ? {
|
|
3070
|
+
locked: scaleLockedByLayer.get(layer) === true,
|
|
3071
|
+
onChange: (locked) => {
|
|
3072
|
+
scaleLockedByLayer.set(layer, locked);
|
|
3073
|
+
if (!locked)
|
|
3074
|
+
return;
|
|
3075
|
+
updateLayerRect(layer, normalizeInspectorGroundDecalLayoutUniformRect(layer, layer.rect, 'width'));
|
|
3076
|
+
commitLayout();
|
|
3077
|
+
},
|
|
3078
|
+
} : undefined);
|
|
3079
|
+
fieldInputsByLayer.set(layer, layerInputs);
|
|
3080
|
+
renderInspectorGroundDecalLayoutBox(box, layer.rect);
|
|
3081
|
+
}
|
|
3082
|
+
renderPreview();
|
|
3083
|
+
return wrapper;
|
|
3084
|
+
}
|
|
3085
|
+
function readInspectorGroundDecalLayoutLayers(property) {
|
|
3086
|
+
if (isInspectorGroundDecalLayoutCompositeValue(property.value)) {
|
|
3087
|
+
const layers = property.value.layers
|
|
3088
|
+
.map((layer, fallbackIndex) => readInspectorGroundDecalLayoutLayer(layer, fallbackIndex))
|
|
3089
|
+
.filter((layer) => !!layer);
|
|
3090
|
+
if (layers.length > 0)
|
|
3091
|
+
return layers;
|
|
3092
|
+
}
|
|
3093
|
+
const label = readInspectorControlOptionString(property, 'layerLabel') || property.label || 'Ground decal layer';
|
|
3094
|
+
return [{
|
|
3095
|
+
index: 0,
|
|
3096
|
+
id: 'layer_0',
|
|
3097
|
+
role: 'layer',
|
|
3098
|
+
kind: 'unknown',
|
|
3099
|
+
label,
|
|
3100
|
+
rect: readInspectorGroundDecalLayoutRect(property.value),
|
|
3101
|
+
enabled: true,
|
|
3102
|
+
editable: true,
|
|
3103
|
+
zOrder: 0,
|
|
3104
|
+
}];
|
|
3105
|
+
}
|
|
3106
|
+
function isInspectorGroundDecalLayoutCompositeValue(value) {
|
|
3107
|
+
return isRecord(value) && Array.isArray(value.layers);
|
|
3108
|
+
}
|
|
3109
|
+
function readInspectorGroundDecalLayoutLayer(value, fallbackIndex) {
|
|
3110
|
+
if (!isRecord(value))
|
|
3111
|
+
return null;
|
|
3112
|
+
const index = Number.isInteger(value.index) && typeof value.index === 'number' && value.index >= 0
|
|
3113
|
+
? value.index
|
|
3114
|
+
: fallbackIndex;
|
|
3115
|
+
const label = readStringField(value, 'label') || `Layer ${index + 1}`;
|
|
3116
|
+
return {
|
|
3117
|
+
index,
|
|
3118
|
+
id: readStringField(value, 'id') || `layer_${index}`,
|
|
3119
|
+
role: readStringField(value, 'role') || 'layer',
|
|
3120
|
+
kind: readStringField(value, 'kind') || 'unknown',
|
|
3121
|
+
label,
|
|
3122
|
+
rect: readInspectorGroundDecalLayoutRect(value.rect),
|
|
3123
|
+
enabled: readOptionalBooleanField(value, 'enabled') ?? true,
|
|
3124
|
+
editable: readOptionalBooleanField(value, 'editable') ?? true,
|
|
3125
|
+
zOrder: readOptionalNumberField(value, 'zOrder') ?? index,
|
|
3126
|
+
opacity: readOptionalNumberField(value, 'opacity') ?? undefined,
|
|
3127
|
+
scaleMode: readInspectorGroundDecalLayoutScaleMode(value.scaleMode),
|
|
3128
|
+
aspectRatio: readInspectorGroundDecalLayoutAspectRatio(value.aspectRatio),
|
|
3129
|
+
text: readStringField(value, 'text') || undefined,
|
|
3130
|
+
preview: readInspectorGroundDecalLayoutPreview(value.preview),
|
|
3131
|
+
};
|
|
3132
|
+
}
|
|
3133
|
+
function readInspectorGroundDecalLayoutScaleMode(value) {
|
|
3134
|
+
return value === 'uniform' || value === 'free' ? value : undefined;
|
|
3135
|
+
}
|
|
3136
|
+
function readInspectorGroundDecalLayoutAspectRatio(value) {
|
|
3137
|
+
return typeof value === 'number' && Number.isFinite(value) && value > 0 ? value : undefined;
|
|
3138
|
+
}
|
|
3139
|
+
function readInspectorGroundDecalLayoutPreview(value) {
|
|
3140
|
+
if (!isRecord(value))
|
|
3141
|
+
return undefined;
|
|
3142
|
+
if (value.kind === 'image') {
|
|
3143
|
+
const url = readStringField(value, 'url');
|
|
3144
|
+
if (!url)
|
|
3145
|
+
return undefined;
|
|
3146
|
+
return {
|
|
3147
|
+
kind: 'image',
|
|
3148
|
+
url,
|
|
3149
|
+
alt: readStringField(value, 'alt') || undefined,
|
|
3150
|
+
tint: readInspectorGroundDecalLayoutColor(value.tint),
|
|
3151
|
+
};
|
|
3152
|
+
}
|
|
3153
|
+
if (value.kind === 'text') {
|
|
3154
|
+
return {
|
|
3155
|
+
kind: 'text',
|
|
3156
|
+
text: readStringField(value, 'text'),
|
|
3157
|
+
fontFamily: readStringField(value, 'fontFamily') || undefined,
|
|
3158
|
+
fontSize: readOptionalNumberField(value, 'fontSize') ?? undefined,
|
|
3159
|
+
fontWeight: readStringField(value, 'fontWeight') || undefined,
|
|
3160
|
+
color: readInspectorGroundDecalLayoutColor(value.color),
|
|
3161
|
+
strokeColor: readInspectorGroundDecalLayoutColor(value.strokeColor),
|
|
3162
|
+
strokeWidth: readOptionalNumberField(value, 'strokeWidth') ?? undefined,
|
|
3163
|
+
align: readInspectorGroundDecalLayoutTextAlign(value.align),
|
|
3164
|
+
baseline: readInspectorGroundDecalLayoutTextBaseline(value.baseline),
|
|
3165
|
+
};
|
|
3166
|
+
}
|
|
3167
|
+
if (value.kind === 'color') {
|
|
3168
|
+
const color = readInspectorGroundDecalLayoutColor(value.color);
|
|
3169
|
+
return color ? { kind: 'color', color } : undefined;
|
|
3170
|
+
}
|
|
3171
|
+
if (value.kind === 'progress') {
|
|
3172
|
+
const color = readInspectorGroundDecalLayoutColor(value.color);
|
|
3173
|
+
if (!color)
|
|
3174
|
+
return undefined;
|
|
3175
|
+
return {
|
|
3176
|
+
kind: 'progress',
|
|
3177
|
+
value: readOptionalNumberField(value, 'value') ?? 0,
|
|
3178
|
+
direction: readInspectorGroundDecalLayoutProgressDirection(value.direction),
|
|
3179
|
+
color,
|
|
3180
|
+
};
|
|
3181
|
+
}
|
|
3182
|
+
return undefined;
|
|
3183
|
+
}
|
|
3184
|
+
function readInspectorGroundDecalLayoutTextureSize(value) {
|
|
3185
|
+
if (!isRecord(value) || !isRecord(value.textureSize))
|
|
3186
|
+
return { width: 512, height: 512 };
|
|
3187
|
+
const width = readOptionalNumberField(value.textureSize, 'width') ?? 512;
|
|
3188
|
+
const height = readOptionalNumberField(value.textureSize, 'height') ?? 512;
|
|
3189
|
+
return {
|
|
3190
|
+
width: Math.max(1, Math.round(width)),
|
|
3191
|
+
height: Math.max(1, Math.round(height)),
|
|
3192
|
+
};
|
|
3193
|
+
}
|
|
3194
|
+
function readInspectorGroundDecalLayoutMask(value) {
|
|
3195
|
+
if (!isRecord(value) || !isRecord(value.mask))
|
|
3196
|
+
return undefined;
|
|
3197
|
+
return {
|
|
3198
|
+
enabled: readOptionalBooleanField(value.mask, 'enabled') ?? true,
|
|
3199
|
+
source: readStringField(value.mask, 'source') || undefined,
|
|
3200
|
+
cornerRadius: readOptionalNumberField(value.mask, 'cornerRadius') ?? undefined,
|
|
3201
|
+
padding: readOptionalNumberField(value.mask, 'padding') ?? undefined,
|
|
3202
|
+
};
|
|
3203
|
+
}
|
|
3204
|
+
function readInspectorGroundDecalLayoutColor(value) {
|
|
3205
|
+
if (!isRecord(value))
|
|
3206
|
+
return undefined;
|
|
3207
|
+
const r = readOptionalNumberField(value, 'r');
|
|
3208
|
+
const g = readOptionalNumberField(value, 'g');
|
|
3209
|
+
const b = readOptionalNumberField(value, 'b');
|
|
3210
|
+
if (r === null || g === null || b === null)
|
|
3211
|
+
return undefined;
|
|
3212
|
+
const a = readOptionalNumberField(value, 'a');
|
|
3213
|
+
return {
|
|
3214
|
+
r,
|
|
3215
|
+
g,
|
|
3216
|
+
b,
|
|
3217
|
+
...(a === null ? {} : { a }),
|
|
3218
|
+
};
|
|
3219
|
+
}
|
|
3220
|
+
function readOptionalNumberField(record, key) {
|
|
3221
|
+
const value = record[key];
|
|
3222
|
+
return typeof value === 'number' && Number.isFinite(value) ? value : null;
|
|
3223
|
+
}
|
|
3224
|
+
function readOptionalBooleanField(record, key) {
|
|
3225
|
+
const value = record[key];
|
|
3226
|
+
return typeof value === 'boolean' ? value : null;
|
|
3227
|
+
}
|
|
3228
|
+
function readInspectorGroundDecalLayoutTextAlign(value) {
|
|
3229
|
+
return value === 'left' || value === 'center' || value === 'right' ? value : undefined;
|
|
3230
|
+
}
|
|
3231
|
+
function readInspectorGroundDecalLayoutTextBaseline(value) {
|
|
3232
|
+
return value === 'top' || value === 'middle' || value === 'bottom' ? value : undefined;
|
|
3233
|
+
}
|
|
3234
|
+
function readInspectorGroundDecalLayoutProgressDirection(value) {
|
|
3235
|
+
return value === 'leftToRight' || value === 'rightToLeft' || value === 'bottomToTop' || value === 'topToBottom'
|
|
3236
|
+
? value
|
|
3237
|
+
: undefined;
|
|
3238
|
+
}
|
|
3239
|
+
function isInspectorGroundDecalLayoutLayerEditable(layer) {
|
|
3240
|
+
return layer.enabled && layer.editable;
|
|
3241
|
+
}
|
|
3242
|
+
function isInspectorGroundDecalLayoutUniformScaleLayer(layer) {
|
|
3243
|
+
return layer.scaleMode === 'uniform';
|
|
3244
|
+
}
|
|
3245
|
+
function createInspectorGroundDecalLayoutBox(doc, layer, editable) {
|
|
3246
|
+
const box = doc.createElement('div');
|
|
3247
|
+
box.dataset.editorInspectorGroundDecalLayoutBox = 'true';
|
|
3248
|
+
box.dataset.editorInspectorGroundDecalLayoutLayer = layer.id;
|
|
3249
|
+
box.title = layer.label;
|
|
3250
|
+
box.style.cssText = [
|
|
3251
|
+
'position:absolute',
|
|
3252
|
+
'box-sizing:border-box',
|
|
3253
|
+
'border:2px solid var(--fps-editor-accent-strong)',
|
|
3254
|
+
'background:rgba(65,145,255,0.08)',
|
|
3255
|
+
'box-shadow:inset 0 0 0 1px rgba(0,0,0,0.32)',
|
|
3256
|
+
'border-radius:3px',
|
|
3257
|
+
'overflow:visible',
|
|
3258
|
+
editable ? 'cursor:grab' : 'cursor:not-allowed',
|
|
3259
|
+
'touch-action:none',
|
|
3260
|
+
].join(';');
|
|
3261
|
+
const handle = doc.createElement('button');
|
|
3262
|
+
handle.type = 'button';
|
|
3263
|
+
handle.dataset.editorInspectorGroundDecalLayoutResize = 'true';
|
|
3264
|
+
handle.disabled = !editable;
|
|
3265
|
+
handle.title = `Resize ${layer.label}`;
|
|
3266
|
+
handle.style.cssText = [
|
|
3267
|
+
'position:absolute',
|
|
3268
|
+
'right:0',
|
|
3269
|
+
'bottom:0',
|
|
3270
|
+
'width:10px',
|
|
3271
|
+
'height:10px',
|
|
3272
|
+
'box-sizing:border-box',
|
|
3273
|
+
'padding:0',
|
|
3274
|
+
'border:1px solid var(--fps-editor-accent-strong)',
|
|
3275
|
+
'border-radius:3px',
|
|
3276
|
+
'background:var(--fps-editor-panel)',
|
|
3277
|
+
editable ? 'cursor:nwse-resize' : 'cursor:not-allowed',
|
|
3278
|
+
'touch-action:none',
|
|
3279
|
+
].join(';');
|
|
3280
|
+
box.appendChild(handle);
|
|
3281
|
+
return box;
|
|
3282
|
+
}
|
|
3283
|
+
function createInspectorGroundDecalLayoutLayerFields(doc, parent, layer, editable, onInput, onCommit, scaleLock) {
|
|
3284
|
+
const row = doc.createElement('div');
|
|
3285
|
+
row.style.cssText = [
|
|
3286
|
+
'display:grid',
|
|
3287
|
+
'grid-template-columns:minmax(52px,0.9fr) repeat(4,minmax(0,1fr))',
|
|
3288
|
+
'gap:6px',
|
|
3289
|
+
'align-items:end',
|
|
3290
|
+
'min-width:0',
|
|
3291
|
+
].join(';');
|
|
3292
|
+
parent.appendChild(row);
|
|
3293
|
+
const name = doc.createElement('div');
|
|
3294
|
+
name.title = layer.label;
|
|
3295
|
+
name.style.cssText = [
|
|
3296
|
+
'min-width:0',
|
|
3297
|
+
'display:flex',
|
|
3298
|
+
'align-items:center',
|
|
3299
|
+
'gap:4px',
|
|
3300
|
+
'color:var(--fps-editor-muted)',
|
|
3301
|
+
'font-size:10px',
|
|
3302
|
+
'font-weight:900',
|
|
3303
|
+
'line-height:24px',
|
|
3304
|
+
].join(';');
|
|
3305
|
+
const nameText = doc.createElement('span');
|
|
3306
|
+
nameText.textContent = layer.label;
|
|
3307
|
+
nameText.style.cssText = 'min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap';
|
|
3308
|
+
name.appendChild(nameText);
|
|
3309
|
+
if (scaleLock) {
|
|
3310
|
+
let locked = scaleLock.locked;
|
|
3311
|
+
const lockButton = createInspectorVectorHeaderButton(doc, '', locked);
|
|
3312
|
+
lockButton.style.width = '20px';
|
|
3313
|
+
lockButton.style.height = '20px';
|
|
3314
|
+
lockButton.style.flex = '0 0 auto';
|
|
3315
|
+
lockButton.disabled = !editable;
|
|
3316
|
+
lockButton.appendChild(createLocalEditorIcon(doc, 'lock', { size: 11, strokeWidth: 2.2 }));
|
|
3317
|
+
const applyLockState = () => {
|
|
3318
|
+
updateInspectorGroundDecalLayoutScaleLockButton(lockButton, layer, locked);
|
|
3319
|
+
};
|
|
3320
|
+
lockButton.addEventListener('click', (event) => {
|
|
3321
|
+
event.preventDefault();
|
|
3322
|
+
event.stopPropagation();
|
|
3323
|
+
if (!editable)
|
|
3324
|
+
return;
|
|
3325
|
+
locked = !locked;
|
|
3326
|
+
applyLockState();
|
|
3327
|
+
scaleLock.onChange(locked);
|
|
3328
|
+
});
|
|
3329
|
+
applyLockState();
|
|
3330
|
+
name.appendChild(lockButton);
|
|
3331
|
+
}
|
|
3332
|
+
row.appendChild(name);
|
|
3333
|
+
const inputs = new Map();
|
|
3334
|
+
for (const key of ['x', 'z', 'width', 'depth']) {
|
|
3335
|
+
const fieldLabel = key === 'width' ? 'W' : key === 'depth' ? 'D' : key.toUpperCase();
|
|
3336
|
+
const accessibleLabel = `${layer.label} ${key === 'width' ? 'width' : key === 'depth' ? 'depth' : key}`;
|
|
3337
|
+
const field = doc.createElement('label');
|
|
3338
|
+
field.style.cssText = [
|
|
3339
|
+
'display:flex',
|
|
3340
|
+
'flex-direction:column',
|
|
3341
|
+
'gap:3px',
|
|
3342
|
+
'min-width:0',
|
|
3343
|
+
'color:var(--fps-editor-muted)',
|
|
3344
|
+
'font-size:10px',
|
|
3345
|
+
'font-weight:900',
|
|
3346
|
+
'text-transform:uppercase',
|
|
3347
|
+
'letter-spacing:0',
|
|
3348
|
+
].join(';');
|
|
3349
|
+
field.textContent = fieldLabel;
|
|
3350
|
+
const fieldInput = doc.createElement('input');
|
|
3351
|
+
fieldInput.type = 'text';
|
|
3352
|
+
fieldInput.inputMode = 'decimal';
|
|
3353
|
+
fieldInput.setAttribute('aria-label', accessibleLabel);
|
|
3354
|
+
fieldInput.title = accessibleLabel;
|
|
3355
|
+
fieldInput.disabled = !editable;
|
|
3356
|
+
fieldInput.value = formatLocalEditorBrowserInspectorNumberValue(layer.rect[key]);
|
|
3357
|
+
fieldInput.style.cssText = createInspectorInputStyle();
|
|
3358
|
+
fieldInput.addEventListener('input', () => onInput(key));
|
|
3359
|
+
fieldInput.addEventListener('change', () => onCommit(key));
|
|
3360
|
+
fieldInput.addEventListener('keydown', (event) => {
|
|
3361
|
+
if (event.key !== 'Enter' || event.isComposing)
|
|
3362
|
+
return;
|
|
3363
|
+
event.preventDefault();
|
|
3364
|
+
event.stopPropagation();
|
|
3365
|
+
onCommit(key);
|
|
3366
|
+
});
|
|
3367
|
+
field.appendChild(fieldInput);
|
|
3368
|
+
row.appendChild(field);
|
|
3369
|
+
inputs.set(key, fieldInput);
|
|
3370
|
+
}
|
|
3371
|
+
return inputs;
|
|
3372
|
+
}
|
|
3373
|
+
function updateInspectorGroundDecalLayoutScaleLockButton(button, layer, locked) {
|
|
3374
|
+
const title = locked ? `Unlock ${layer.label} size ratio` : `Lock ${layer.label} size ratio`;
|
|
3375
|
+
button.title = title;
|
|
3376
|
+
button.dataset.editorTooltip = title;
|
|
3377
|
+
button.dataset.editorInspectorVectorLockState = locked ? 'locked' : 'unlocked';
|
|
3378
|
+
button.setAttribute('aria-pressed', locked ? 'true' : 'false');
|
|
3379
|
+
button.style.borderColor = locked ? 'var(--fps-editor-accent)' : 'var(--fps-editor-border)';
|
|
3380
|
+
button.style.background = locked ? 'var(--fps-editor-accent-soft)' : 'var(--fps-editor-button-bg)';
|
|
3381
|
+
button.style.color = locked ? 'var(--fps-editor-accent)' : 'var(--fps-editor-text)';
|
|
3382
|
+
}
|
|
3383
|
+
async function renderInspectorGroundDecalLayoutPreview(canvas, layers, textureSize, mask, shouldContinue) {
|
|
3384
|
+
if (canvas.width !== textureSize.width)
|
|
3385
|
+
canvas.width = textureSize.width;
|
|
3386
|
+
if (canvas.height !== textureSize.height)
|
|
3387
|
+
canvas.height = textureSize.height;
|
|
3388
|
+
const imageByLayer = new Map();
|
|
3389
|
+
await Promise.all(layers.map(async (layer) => {
|
|
3390
|
+
if (layer.preview?.kind !== 'image')
|
|
3391
|
+
return;
|
|
3392
|
+
imageByLayer.set(layer, await loadInspectorGroundDecalLayoutImage(canvas.ownerDocument, layer.preview.url));
|
|
3393
|
+
}));
|
|
3394
|
+
if (!shouldContinue())
|
|
3395
|
+
return;
|
|
3396
|
+
const ctx = canvas.getContext('2d');
|
|
3397
|
+
if (!ctx)
|
|
3398
|
+
return;
|
|
3399
|
+
ctx.clearRect(0, 0, textureSize.width, textureSize.height);
|
|
3400
|
+
ctx.save();
|
|
3401
|
+
applyInspectorGroundDecalLayoutMask(ctx, textureSize, mask);
|
|
3402
|
+
const sortedLayers = [...layers]
|
|
3403
|
+
.filter(layer => layer.enabled)
|
|
3404
|
+
.sort((left, right) => left.zOrder - right.zOrder || left.index - right.index);
|
|
3405
|
+
for (const layer of sortedLayers) {
|
|
3406
|
+
drawInspectorGroundDecalLayoutLayer(ctx, layer, textureSize, imageByLayer.get(layer) ?? null);
|
|
3407
|
+
}
|
|
3408
|
+
ctx.restore();
|
|
3409
|
+
}
|
|
3410
|
+
function applyInspectorGroundDecalLayoutMask(ctx, textureSize, mask) {
|
|
3411
|
+
if (mask?.enabled === false)
|
|
3412
|
+
return;
|
|
3413
|
+
const minSize = Math.min(textureSize.width, textureSize.height);
|
|
3414
|
+
const padding = Math.max(0, minSize * (mask?.padding ?? 0));
|
|
3415
|
+
const radius = Math.max(0, minSize * (mask?.cornerRadius ?? 0.18));
|
|
3416
|
+
roundedInspectorGroundDecalLayoutRectPath(ctx, padding, padding, textureSize.width - padding * 2, textureSize.height - padding * 2, radius);
|
|
3417
|
+
ctx.clip();
|
|
3418
|
+
}
|
|
3419
|
+
function drawInspectorGroundDecalLayoutLayer(ctx, layer, textureSize, image) {
|
|
3420
|
+
const preview = layer.preview;
|
|
3421
|
+
if (!preview)
|
|
3422
|
+
return;
|
|
3423
|
+
const rect = resolveInspectorGroundDecalLayoutPixelRect(layer.rect, textureSize);
|
|
3424
|
+
ctx.save();
|
|
3425
|
+
ctx.globalAlpha = layer.opacity ?? 1;
|
|
3426
|
+
if (preview.kind === 'color') {
|
|
3427
|
+
ctx.fillStyle = inspectorGroundDecalLayoutColorToCss(preview.color);
|
|
3428
|
+
ctx.fillRect(rect.x, rect.y, rect.width, rect.height);
|
|
3429
|
+
}
|
|
3430
|
+
else if (preview.kind === 'progress') {
|
|
3431
|
+
drawInspectorGroundDecalLayoutProgress(ctx, preview, rect);
|
|
3432
|
+
}
|
|
3433
|
+
else if (preview.kind === 'image') {
|
|
3434
|
+
if (image) {
|
|
3435
|
+
drawInspectorGroundDecalLayoutImage(ctx, image, rect, preview.tint);
|
|
3436
|
+
}
|
|
3437
|
+
else {
|
|
3438
|
+
drawInspectorGroundDecalLayoutMissingTexture(ctx, rect);
|
|
3439
|
+
}
|
|
3440
|
+
}
|
|
3441
|
+
else if (preview.kind === 'text') {
|
|
3442
|
+
drawInspectorGroundDecalLayoutText(ctx, preview, rect);
|
|
3443
|
+
}
|
|
3444
|
+
ctx.restore();
|
|
3445
|
+
}
|
|
3446
|
+
function drawInspectorGroundDecalLayoutImage(ctx, image, rect, tint) {
|
|
3447
|
+
if (!tint) {
|
|
3448
|
+
ctx.drawImage(image, rect.x, rect.y, rect.width, rect.height);
|
|
3449
|
+
return;
|
|
3450
|
+
}
|
|
3451
|
+
const inheritedAlpha = ctx.globalAlpha;
|
|
3452
|
+
const tintAlpha = Math.max(0, Math.min(1, tint.a ?? 1));
|
|
3453
|
+
const totalAlpha = inheritedAlpha * tintAlpha;
|
|
3454
|
+
if (isInspectorGroundDecalLayoutWhiteTint(tint)) {
|
|
3455
|
+
ctx.globalAlpha = totalAlpha;
|
|
3456
|
+
ctx.drawImage(image, rect.x, rect.y, rect.width, rect.height);
|
|
3457
|
+
ctx.globalAlpha = inheritedAlpha;
|
|
3458
|
+
return;
|
|
3459
|
+
}
|
|
3460
|
+
const ownerDocument = ctx.canvas.ownerDocument;
|
|
3461
|
+
const canvas = ownerDocument.createElement('canvas');
|
|
3462
|
+
canvas.width = Math.max(1, Math.round(rect.width));
|
|
3463
|
+
canvas.height = Math.max(1, Math.round(rect.height));
|
|
3464
|
+
const tintContext = canvas.getContext('2d');
|
|
3465
|
+
if (!tintContext) {
|
|
3466
|
+
ctx.globalAlpha = totalAlpha;
|
|
3467
|
+
ctx.drawImage(image, rect.x, rect.y, rect.width, rect.height);
|
|
3468
|
+
ctx.globalAlpha = inheritedAlpha;
|
|
3469
|
+
return;
|
|
3470
|
+
}
|
|
3471
|
+
try {
|
|
3472
|
+
tintContext.drawImage(image, 0, 0, canvas.width, canvas.height);
|
|
3473
|
+
const imageData = tintContext.getImageData(0, 0, canvas.width, canvas.height);
|
|
3474
|
+
const data = imageData.data;
|
|
3475
|
+
const r = Math.max(0, Math.min(1, tint.r));
|
|
3476
|
+
const g = Math.max(0, Math.min(1, tint.g));
|
|
3477
|
+
const b = Math.max(0, Math.min(1, tint.b));
|
|
3478
|
+
for (let index = 0; index < data.length; index += 4) {
|
|
3479
|
+
data[index] = Math.round(data[index] * r);
|
|
3480
|
+
data[index + 1] = Math.round(data[index + 1] * g);
|
|
3481
|
+
data[index + 2] = Math.round(data[index + 2] * b);
|
|
3482
|
+
data[index + 3] = Math.round(data[index + 3] * totalAlpha);
|
|
3483
|
+
}
|
|
3484
|
+
tintContext.putImageData(imageData, 0, 0);
|
|
3485
|
+
ctx.globalAlpha = 1;
|
|
3486
|
+
ctx.drawImage(canvas, rect.x, rect.y, rect.width, rect.height);
|
|
3487
|
+
ctx.globalAlpha = inheritedAlpha;
|
|
3488
|
+
}
|
|
3489
|
+
catch {
|
|
3490
|
+
ctx.globalAlpha = totalAlpha;
|
|
3491
|
+
ctx.drawImage(image, rect.x, rect.y, rect.width, rect.height);
|
|
3492
|
+
ctx.globalAlpha = inheritedAlpha;
|
|
3493
|
+
}
|
|
3494
|
+
}
|
|
3495
|
+
function isInspectorGroundDecalLayoutWhiteTint(tint) {
|
|
3496
|
+
return tint.r >= 0.999 && tint.g >= 0.999 && tint.b >= 0.999;
|
|
3497
|
+
}
|
|
3498
|
+
function drawInspectorGroundDecalLayoutProgress(ctx, preview, rect) {
|
|
3499
|
+
const value = Math.max(0, Math.min(1, preview.value));
|
|
3500
|
+
const direction = preview.direction ?? 'bottomToTop';
|
|
3501
|
+
ctx.fillStyle = inspectorGroundDecalLayoutColorToCss(preview.color);
|
|
3502
|
+
if (direction === 'leftToRight') {
|
|
3503
|
+
ctx.fillRect(rect.x, rect.y, rect.width * value, rect.height);
|
|
3504
|
+
}
|
|
3505
|
+
else if (direction === 'rightToLeft') {
|
|
3506
|
+
const width = rect.width * value;
|
|
3507
|
+
ctx.fillRect(rect.x + rect.width - width, rect.y, width, rect.height);
|
|
3508
|
+
}
|
|
3509
|
+
else if (direction === 'topToBottom') {
|
|
3510
|
+
ctx.fillRect(rect.x, rect.y, rect.width, rect.height * value);
|
|
3511
|
+
}
|
|
3512
|
+
else {
|
|
3513
|
+
const height = rect.height * value;
|
|
3514
|
+
ctx.fillRect(rect.x, rect.y + rect.height - height, rect.width, height);
|
|
3515
|
+
}
|
|
3516
|
+
}
|
|
3517
|
+
function drawInspectorGroundDecalLayoutText(ctx, preview, rect) {
|
|
3518
|
+
const fontSize = preview.fontSize ?? Math.floor(rect.height * 0.72);
|
|
3519
|
+
const fontWeight = preview.fontWeight ?? '800';
|
|
3520
|
+
const fontFamily = preview.fontFamily ?? 'system-ui, Arial, sans-serif';
|
|
3521
|
+
ctx.font = `${fontWeight} ${fontSize}px ${fontFamily}`;
|
|
3522
|
+
ctx.textAlign = preview.align ?? 'center';
|
|
3523
|
+
ctx.textBaseline = preview.baseline ?? 'middle';
|
|
3524
|
+
const x = resolveInspectorGroundDecalLayoutTextX(rect, ctx.textAlign);
|
|
3525
|
+
const y = resolveInspectorGroundDecalLayoutTextY(rect, ctx.textBaseline);
|
|
3526
|
+
if (preview.strokeColor && preview.strokeWidth) {
|
|
3527
|
+
ctx.lineWidth = preview.strokeWidth;
|
|
3528
|
+
ctx.strokeStyle = inspectorGroundDecalLayoutColorToCss(preview.strokeColor);
|
|
3529
|
+
ctx.strokeText(preview.text, x, y);
|
|
3530
|
+
}
|
|
3531
|
+
ctx.fillStyle = inspectorGroundDecalLayoutColorToCss(preview.color ?? { r: 1, g: 1, b: 1, a: 1 });
|
|
3532
|
+
ctx.fillText(preview.text, x, y);
|
|
3533
|
+
}
|
|
3534
|
+
function resolveInspectorGroundDecalLayoutPixelRect(rect, textureSize) {
|
|
3535
|
+
const width = rect.width * textureSize.width;
|
|
3536
|
+
const height = rect.depth * textureSize.height;
|
|
3537
|
+
const centerX = textureSize.width * (0.5 + rect.x);
|
|
3538
|
+
const centerY = textureSize.height * (0.5 - rect.z);
|
|
3539
|
+
return {
|
|
3540
|
+
x: centerX - width / 2,
|
|
3541
|
+
y: centerY - height / 2,
|
|
3542
|
+
width,
|
|
3543
|
+
height,
|
|
3544
|
+
};
|
|
3545
|
+
}
|
|
3546
|
+
function resolveInspectorGroundDecalLayoutTextX(rect, align) {
|
|
3547
|
+
if (align === 'left' || align === 'start')
|
|
3548
|
+
return rect.x;
|
|
3549
|
+
if (align === 'right' || align === 'end')
|
|
3550
|
+
return rect.x + rect.width;
|
|
3551
|
+
return rect.x + rect.width / 2;
|
|
3552
|
+
}
|
|
3553
|
+
function resolveInspectorGroundDecalLayoutTextY(rect, baseline) {
|
|
3554
|
+
if (baseline === 'top' || baseline === 'hanging')
|
|
3555
|
+
return rect.y;
|
|
3556
|
+
if (baseline === 'bottom' || baseline === 'alphabetic' || baseline === 'ideographic')
|
|
3557
|
+
return rect.y + rect.height;
|
|
3558
|
+
return rect.y + rect.height / 2;
|
|
3559
|
+
}
|
|
3560
|
+
function drawInspectorGroundDecalLayoutMissingTexture(ctx, rect) {
|
|
3561
|
+
ctx.fillStyle = 'rgba(255,0,80,0.55)';
|
|
3562
|
+
ctx.fillRect(rect.x, rect.y, rect.width, rect.height);
|
|
3563
|
+
ctx.strokeStyle = 'rgba(255,255,255,0.9)';
|
|
3564
|
+
ctx.lineWidth = 4;
|
|
3565
|
+
ctx.strokeRect(rect.x, rect.y, rect.width, rect.height);
|
|
3566
|
+
}
|
|
3567
|
+
function roundedInspectorGroundDecalLayoutRectPath(ctx, x, y, width, height, radius) {
|
|
3568
|
+
const r = Math.max(0, Math.min(radius, width / 2, height / 2));
|
|
3569
|
+
ctx.beginPath();
|
|
3570
|
+
ctx.moveTo(x + r, y);
|
|
3571
|
+
ctx.lineTo(x + width - r, y);
|
|
3572
|
+
ctx.quadraticCurveTo(x + width, y, x + width, y + r);
|
|
3573
|
+
ctx.lineTo(x + width, y + height - r);
|
|
3574
|
+
ctx.quadraticCurveTo(x + width, y + height, x + width - r, y + height);
|
|
3575
|
+
ctx.lineTo(x + r, y + height);
|
|
3576
|
+
ctx.quadraticCurveTo(x, y + height, x, y + height - r);
|
|
3577
|
+
ctx.lineTo(x, y + r);
|
|
3578
|
+
ctx.quadraticCurveTo(x, y, x + r, y);
|
|
3579
|
+
ctx.closePath();
|
|
3580
|
+
}
|
|
3581
|
+
function inspectorGroundDecalLayoutColorToCss(color) {
|
|
3582
|
+
const r = Math.round(Math.max(0, Math.min(1, color.r)) * 255);
|
|
3583
|
+
const g = Math.round(Math.max(0, Math.min(1, color.g)) * 255);
|
|
3584
|
+
const b = Math.round(Math.max(0, Math.min(1, color.b)) * 255);
|
|
3585
|
+
const a = Math.max(0, Math.min(1, color.a ?? 1));
|
|
3586
|
+
return `rgba(${r}, ${g}, ${b}, ${a})`;
|
|
3587
|
+
}
|
|
3588
|
+
function loadInspectorGroundDecalLayoutImage(doc, url) {
|
|
3589
|
+
const cached = inspectorGroundDecalLayoutImageCache.get(url);
|
|
3590
|
+
if (cached)
|
|
3591
|
+
return cached;
|
|
3592
|
+
const promise = new Promise((resolve) => {
|
|
3593
|
+
const image = doc.createElement('img');
|
|
3594
|
+
image.onload = () => resolve(image);
|
|
3595
|
+
image.onerror = () => resolve(null);
|
|
3596
|
+
image.src = url;
|
|
3597
|
+
});
|
|
3598
|
+
inspectorGroundDecalLayoutImageCache.set(url, promise);
|
|
3599
|
+
return promise;
|
|
3600
|
+
}
|
|
3601
|
+
function readInspectorGroundDecalLayoutRect(value) {
|
|
3602
|
+
if (!isRecord(value))
|
|
3603
|
+
return { x: 0, z: 0, width: 0.25, depth: 0.25 };
|
|
3604
|
+
return normalizeInspectorGroundDecalLayoutRect({
|
|
3605
|
+
x: readFiniteRecordNumber(value, 'x', 0),
|
|
3606
|
+
z: readFiniteRecordNumber(value, 'z', 0),
|
|
3607
|
+
width: readFiniteRecordNumber(value, 'width', 0.25),
|
|
3608
|
+
depth: readFiniteRecordNumber(value, 'depth', 0.25),
|
|
3609
|
+
});
|
|
3610
|
+
}
|
|
3611
|
+
function readFiniteRecordNumber(record, key, fallback) {
|
|
3612
|
+
const value = record[key];
|
|
3613
|
+
return typeof value === 'number' && Number.isFinite(value) ? value : fallback;
|
|
3614
|
+
}
|
|
3615
|
+
function normalizeInspectorGroundDecalLayoutRect(rect) {
|
|
3616
|
+
return {
|
|
3617
|
+
x: clampNumber(roundInspectorGroundDecalLayoutValue(rect.x), -1, 1),
|
|
3618
|
+
z: clampNumber(roundInspectorGroundDecalLayoutValue(rect.z), -1, 1),
|
|
3619
|
+
width: clampNumber(roundInspectorGroundDecalLayoutValue(rect.width), INSPECTOR_GROUND_DECAL_LAYOUT_MIN_SIZE, INSPECTOR_GROUND_DECAL_LAYOUT_MAX_SIZE),
|
|
3620
|
+
depth: clampNumber(roundInspectorGroundDecalLayoutValue(rect.depth), INSPECTOR_GROUND_DECAL_LAYOUT_MIN_SIZE, INSPECTOR_GROUND_DECAL_LAYOUT_MAX_SIZE),
|
|
3621
|
+
};
|
|
3622
|
+
}
|
|
3623
|
+
function resizeInspectorGroundDecalLayoutRect(layer, start, dx, dy, locked) {
|
|
3624
|
+
if (!locked) {
|
|
3625
|
+
return normalizeInspectorGroundDecalLayoutRect({
|
|
3626
|
+
...start,
|
|
3627
|
+
width: start.width + dx * 2,
|
|
3628
|
+
depth: start.depth + dy * 2,
|
|
3629
|
+
});
|
|
3630
|
+
}
|
|
3631
|
+
const source = Math.abs(dx) >= Math.abs(dy) ? 'width' : 'depth';
|
|
3632
|
+
return normalizeInspectorGroundDecalLayoutUniformRect(layer, {
|
|
3633
|
+
...start,
|
|
3634
|
+
width: start.width + dx * 2,
|
|
3635
|
+
depth: start.depth + dy * 2,
|
|
3636
|
+
}, source);
|
|
3637
|
+
}
|
|
3638
|
+
function normalizeInspectorGroundDecalLayoutUniformRect(layer, rect, source) {
|
|
3639
|
+
const aspectRatio = resolveInspectorGroundDecalLayoutAspectRatio(layer);
|
|
3640
|
+
const normalizedPosition = normalizeInspectorGroundDecalLayoutRect(rect);
|
|
3641
|
+
const candidateDepth = source === 'width' ? rect.width / aspectRatio : rect.depth;
|
|
3642
|
+
const minDepth = Math.max(INSPECTOR_GROUND_DECAL_LAYOUT_MIN_SIZE, INSPECTOR_GROUND_DECAL_LAYOUT_MIN_SIZE / aspectRatio);
|
|
3643
|
+
const maxDepth = Math.min(INSPECTOR_GROUND_DECAL_LAYOUT_MAX_SIZE, INSPECTOR_GROUND_DECAL_LAYOUT_MAX_SIZE / aspectRatio);
|
|
3644
|
+
const depth = clampNumber(candidateDepth, minDepth, maxDepth);
|
|
3645
|
+
return {
|
|
3646
|
+
...normalizedPosition,
|
|
3647
|
+
width: roundInspectorGroundDecalLayoutValue(depth * aspectRatio),
|
|
3648
|
+
depth: roundInspectorGroundDecalLayoutValue(depth),
|
|
3649
|
+
};
|
|
3650
|
+
}
|
|
3651
|
+
function resolveInspectorGroundDecalLayoutAspectRatio(layer) {
|
|
3652
|
+
if (typeof layer.aspectRatio === 'number' && Number.isFinite(layer.aspectRatio) && layer.aspectRatio > 0) {
|
|
3653
|
+
return layer.aspectRatio;
|
|
3654
|
+
}
|
|
3655
|
+
if (layer.rect.depth > 0)
|
|
3656
|
+
return Math.max(0.001, layer.rect.width / layer.rect.depth);
|
|
3657
|
+
return 1;
|
|
3658
|
+
}
|
|
3659
|
+
function roundInspectorGroundDecalLayoutValue(value) {
|
|
3660
|
+
return Math.round(value * 1000) / 1000;
|
|
3661
|
+
}
|
|
3662
|
+
function renderInspectorGroundDecalLayoutBox(box, rect) {
|
|
3663
|
+
const left = (0.5 + rect.x - rect.width / 2) * 100;
|
|
3664
|
+
const top = (0.5 - rect.z - rect.depth / 2) * 100;
|
|
3665
|
+
box.style.left = `${left}%`;
|
|
3666
|
+
box.style.top = `${top}%`;
|
|
3667
|
+
box.style.width = `${rect.width * 100}%`;
|
|
3668
|
+
box.style.height = `${rect.depth * 100}%`;
|
|
3669
|
+
}
|
|
3670
|
+
function updateInspectorGroundDecalLayoutFieldInputs(inputs, rect) {
|
|
3671
|
+
for (const key of ['x', 'z', 'width', 'depth']) {
|
|
3672
|
+
const input = inputs.get(key);
|
|
3673
|
+
if (input)
|
|
3674
|
+
input.value = formatLocalEditorBrowserInspectorNumberValue(rect[key]);
|
|
3675
|
+
}
|
|
3676
|
+
}
|
|
3677
|
+
function readRectFromLayoutFieldInputs(inputs, fallback, layer, changedKey, locked) {
|
|
3678
|
+
const next = { ...fallback };
|
|
3679
|
+
for (const key of ['x', 'z', 'width', 'depth']) {
|
|
3680
|
+
const input = inputs.get(key);
|
|
3681
|
+
if (!input)
|
|
3682
|
+
continue;
|
|
3683
|
+
const value = parseLocalEditorBrowserInspectorNumberValue(input.value, 'final');
|
|
3684
|
+
if (value == null)
|
|
3685
|
+
return null;
|
|
3686
|
+
next[key] = value;
|
|
3687
|
+
}
|
|
3688
|
+
if (locked && (changedKey === 'width' || changedKey === 'depth')) {
|
|
3689
|
+
return normalizeInspectorGroundDecalLayoutUniformRect(layer, next, changedKey);
|
|
3690
|
+
}
|
|
3691
|
+
return normalizeInspectorGroundDecalLayoutRect(next);
|
|
3692
|
+
}
|
|
3693
|
+
function serializeInspectorGroundDecalLayoutValue(layers, composite) {
|
|
3694
|
+
if (!composite && layers.length === 1)
|
|
3695
|
+
return JSON.stringify(normalizeInspectorGroundDecalLayoutRect(layers[0].rect));
|
|
3696
|
+
return JSON.stringify({
|
|
3697
|
+
layers: layers.map(layer => ({
|
|
3698
|
+
index: layer.index,
|
|
3699
|
+
id: layer.id,
|
|
3700
|
+
role: layer.role,
|
|
3701
|
+
kind: layer.kind,
|
|
3702
|
+
label: layer.label,
|
|
3703
|
+
rect: normalizeInspectorGroundDecalLayoutRect(layer.rect),
|
|
3704
|
+
})),
|
|
3705
|
+
});
|
|
3706
|
+
}
|
|
3707
|
+
function commitInspectorGroundDecalLayoutValue(input, layers, fieldInputsByLayer, composite) {
|
|
3708
|
+
for (const layer of layers) {
|
|
3709
|
+
layer.rect = normalizeInspectorGroundDecalLayoutRect(layer.rect);
|
|
3710
|
+
const fieldInputs = fieldInputsByLayer.get(layer);
|
|
3711
|
+
if (fieldInputs)
|
|
3712
|
+
updateInspectorGroundDecalLayoutFieldInputs(fieldInputs, layer.rect);
|
|
3713
|
+
}
|
|
3714
|
+
const nextValue = serializeInspectorGroundDecalLayoutValue(layers, composite);
|
|
3715
|
+
if (input.value === nextValue)
|
|
3716
|
+
return;
|
|
3717
|
+
input.value = nextValue;
|
|
3718
|
+
input.dispatchEvent(new Event('change', { bubbles: true }));
|
|
3719
|
+
}
|
|
2280
3720
|
function createInspectorAssetPickerCardControl(doc, _target, property, bindInput) {
|
|
2281
3721
|
const wrapper = doc.createElement('div');
|
|
2282
3722
|
wrapper.dataset.editorInspectorAssetPickerControl = 'true';
|
|
@@ -2464,6 +3904,8 @@ export function applyLocalEditorBrowserInspectorControlBinding(element, target,
|
|
|
2464
3904
|
element.dataset.serializedCommitMode = property.commitMode;
|
|
2465
3905
|
element.dataset.serializedPersistence = property.persistence;
|
|
2466
3906
|
element.dataset.serializedEffect = getInspectorPropertyEffect(property);
|
|
3907
|
+
if (readInspectorControlOptionBoolean(property, 'forceVectorLock'))
|
|
3908
|
+
element.dataset.serializedForceVectorLock = 'true';
|
|
2467
3909
|
if (property.disabledReason)
|
|
2468
3910
|
element.dataset.serializedDisabledReason = property.disabledReason;
|
|
2469
3911
|
if (options?.source)
|