@volter/sdk 0.0.0-stage → 0.5.204
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/LICENSE +202 -0
- package/NOTICE +20 -0
- package/README.md +38 -3
- package/package.json +510 -4
- package/src/account.ts +210 -0
- package/src/chrome.ts +88 -0
- package/src/client.ts +1646 -0
- package/src/commands.ts +66 -0
- package/src/contributions.ts +619 -0
- package/src/css-numeric-style.ts +97 -0
- package/src/document-probe.ts +282 -0
- package/src/editor-view.ts +225 -0
- package/src/extension.ts +40 -0
- package/src/generations.ts +178 -0
- package/src/host.ts +1157 -0
- package/src/http-transport.browser.ts +14 -0
- package/src/http-transport.node.ts +19 -0
- package/src/index.ts +131 -0
- package/src/kit/CapabilityCoverageSection.tsx +185 -0
- package/src/kit/account-client.ts +333 -0
- package/src/kit/action-registry.ts +317 -0
- package/src/kit/active-product.ts +76 -0
- package/src/kit/active-project.ts +155 -0
- package/src/kit/adapter-editor-config.ts +25 -0
- package/src/kit/adapter-module.ts +7 -0
- package/src/kit/adapter-observation.ts +49 -0
- package/src/kit/animation/animation-clock.ts +479 -0
- package/src/kit/animation/stage-transport.ts +385 -0
- package/src/kit/api/assets.ts +365 -0
- package/src/kit/api/project-open.ts +355 -0
- package/src/kit/api/project-source.ts +180 -0
- package/src/kit/api/project-state.ts +110 -0
- package/src/kit/api/relay.ts +270 -0
- package/src/kit/api/themes.ts +45 -0
- package/src/kit/api-asset-library-wire.ts +45 -0
- package/src/kit/api-base.ts +10 -0
- package/src/kit/api-build.ts +99 -0
- package/src/kit/api-git-wire.ts +56 -0
- package/src/kit/api-logs.ts +92 -0
- package/src/kit/api-project-identity.ts +74 -0
- package/src/kit/api-settings.ts +36 -0
- package/src/kit/api-worktrees.ts +205 -0
- package/src/kit/asset-capabilities.ts +344 -0
- package/src/kit/asset-compare-core.ts +171 -0
- package/src/kit/asset-editor-context.tsx +101 -0
- package/src/kit/asset-events.ts +96 -0
- package/src/kit/asset-inspector-actions.ts +87 -0
- package/src/kit/asset-selection-viewer-registry.ts +113 -0
- package/src/kit/asset-selection.ts +146 -0
- package/src/kit/asset-thumbnails.ts +25 -0
- package/src/kit/asset-viewers.ts +115 -0
- package/src/kit/asset-workflow/asset-import-jobs.ts +106 -0
- package/src/kit/asset-workflow/asset-ledger-backend.ts +126 -0
- package/src/kit/asset-workflow/asset-ledger.ts +156 -0
- package/src/kit/asset-workflow/asset-materialization-report.ts +140 -0
- package/src/kit/asset-workflow/asset-pack-manifest.ts +320 -0
- package/src/kit/asset-workflow/asset-types.ts +142 -0
- package/src/kit/asset-workflow/audio-preview-player.ts +193 -0
- package/src/kit/asset-workflow/audio-waveform.ts +22 -0
- package/src/kit/asset-workflow/cloud-asset-client.ts +263 -0
- package/src/kit/asset-workflow/hosted-asset-materialization.ts +236 -0
- package/src/kit/asset-workflow/image-view-scale.ts +31 -0
- package/src/kit/asset-workflow/import-contract.ts +124 -0
- package/src/kit/asset-workflow/ledger-write-lock.ts +244 -0
- package/src/kit/asset-workflow/pixi-spritesheet.ts +197 -0
- package/src/kit/asset-workflow/preview-resource-lifetime.ts +44 -0
- package/src/kit/asset-workflow/project-asset-commands.ts +20 -0
- package/src/kit/asset-workflow/project-content.ts +288 -0
- package/src/kit/asset-workflow/project-source-index.ts +545 -0
- package/src/kit/asset-workflow/thumbnail-system.ts +256 -0
- package/src/kit/authoring/active-adapter.ts +200 -0
- package/src/kit/authoring/active-systems.ts +422 -0
- package/src/kit/authoring/adapter-key.ts +18 -0
- package/src/kit/authoring/authoring-asset-url.ts +27 -0
- package/src/kit/authoring/bootstrap-state.ts +49 -0
- package/src/kit/authoring/boundary-authoring-adapter.ts +189 -0
- package/src/kit/authoring/canvas-scene-guides.ts +84 -0
- package/src/kit/authoring/composite-authoring-adapter.ts +2109 -0
- package/src/kit/authoring/consumer-actions.ts +531 -0
- package/src/kit/authoring/design-time-layers.ts +852 -0
- package/src/kit/authoring/design-time-mount-registry.ts +244 -0
- package/src/kit/authoring/edit-mode-authoring.ts +637 -0
- package/src/kit/authoring/empty-project-authoring.ts +22 -0
- package/src/kit/authoring/instance-source-menu.ts +135 -0
- package/src/kit/authoring/layered-pick.ts +185 -0
- package/src/kit/authoring/mounted-root-subjects.ts +146 -0
- package/src/kit/authoring/no-authoring-adapter.ts +59 -0
- package/src/kit/authoring/object3d-document-persistence.ts +122 -0
- package/src/kit/authoring/panel-authoring.ts +121 -0
- package/src/kit/authoring/project-authoring-session.ts +105 -0
- package/src/kit/authoring/provenance.ts +99 -0
- package/src/kit/authoring/react-canvas-navigation.ts +259 -0
- package/src/kit/authoring/react-design-canvas-style.ts +20 -0
- package/src/kit/authoring/react-story-board.ts +917 -0
- package/src/kit/authoring/selection-scope.ts +195 -0
- package/src/kit/authoring/shell-document-ops.ts +169 -0
- package/src/kit/authoring/story-board-chrome-fit.ts +107 -0
- package/src/kit/authoring/story-board-presentation.ts +111 -0
- package/src/kit/authoring/three-root.ts +67 -0
- package/src/kit/authoring/viewport-tool-context.ts +73 -0
- package/src/kit/authoring/viewport-tool-owner.ts +38 -0
- package/src/kit/authoring/world-session-state.ts +101 -0
- package/src/kit/authoring-seam-evidence.ts +300 -0
- package/src/kit/availability-tick.ts +66 -0
- package/src/kit/bitmap-label.ts +120 -0
- package/src/kit/boot-routing.ts +392 -0
- package/src/kit/breakpoint-state.ts +43 -0
- package/src/kit/build-identity.ts +16 -0
- package/src/kit/bytes-codec.ts +62 -0
- package/src/kit/cancellation-reason.ts +58 -0
- package/src/kit/canvas-frames.ts +88 -0
- package/src/kit/capture-camera-pose.ts +77 -0
- package/src/kit/capture-size.ts +88 -0
- package/src/kit/chrome-registry.ts +159 -0
- package/src/kit/chrome-slot-registry.ts +91 -0
- package/src/kit/collaboration-client.ts +264 -0
- package/src/kit/collaboration-presence.ts +41 -0
- package/src/kit/command-dispatch.ts +19 -0
- package/src/kit/command-listener.ts +2182 -0
- package/src/kit/command-registry.ts +71 -0
- package/src/kit/component-board-registry.ts +205 -0
- package/src/kit/component-states-registry.ts +199 -0
- package/src/kit/components/AlignToolbar.tsx +204 -0
- package/src/kit/components/ApplicationMenus.tsx +372 -0
- package/src/kit/components/AssetEditorShell.tsx +216 -0
- package/src/kit/components/AssetInspectorToolSection.tsx +124 -0
- package/src/kit/components/BoardRulers.tsx +354 -0
- package/src/kit/components/CanvasAddNodeDialogs.tsx +529 -0
- package/src/kit/components/CanvasSceneViewport.tsx +1195 -0
- package/src/kit/components/ChromeSlot.tsx +20 -0
- package/src/kit/components/CodeView.tsx +470 -0
- package/src/kit/components/CompactInspectorShell.tsx +39 -0
- package/src/kit/components/ConsolePanel.tsx +273 -0
- package/src/kit/components/GameplaySessionTimeline.tsx +295 -0
- package/src/kit/components/InspectionProjection.tsx +932 -0
- package/src/kit/components/Inspector.tsx +270 -0
- package/src/kit/components/InspectorCanvasPreview.tsx +35 -0
- package/src/kit/components/InspectorFieldsSection.tsx +290 -0
- package/src/kit/components/InspectorStoriesSection.tsx +92 -0
- package/src/kit/components/InspectorToolSection.tsx +96 -0
- package/src/kit/components/InspectorTransformSection.tsx +245 -0
- package/src/kit/components/LightExplorerPanel.tsx +433 -0
- package/src/kit/components/MediaProperties.tsx +145 -0
- package/src/kit/components/ProjectHeader.tsx +328 -0
- package/src/kit/components/ReactCanvasControls.tsx +358 -0
- package/src/kit/components/RootSelectionOverlay.tsx +3688 -0
- package/src/kit/components/RootTextEditor.tsx +79 -0
- package/src/kit/components/SaveStatus.tsx +70 -0
- package/src/kit/components/SurfaceCrashBoundary.tsx +105 -0
- package/src/kit/components/SurfaceStateOverlay.tsx +24 -0
- package/src/kit/components/ToolContributionSurfaces.tsx +49 -0
- package/src/kit/components/ToolHost.tsx +380 -0
- package/src/kit/components/Toolbar.tsx +811 -0
- package/src/kit/components/TransientHint.tsx +44 -0
- package/src/kit/components/VersionControlSection.tsx +470 -0
- package/src/kit/components/ViewportOverlaysMenu.tsx +177 -0
- package/src/kit/components/VolterLogo.tsx +18 -0
- package/src/kit/components/WorktreeSwitcher.tsx +712 -0
- package/src/kit/components/account-documents.tsx +1162 -0
- package/src/kit/components/asset-documents.tsx +794 -0
- package/src/kit/components/asset-editor-persistence.ts +216 -0
- package/src/kit/components/asset-selection-section.tsx +545 -0
- package/src/kit/components/asset-thumbnails.tsx +307 -0
- package/src/kit/components/asset-viewers/AudioViewer.tsx +201 -0
- package/src/kit/components/asset-viewers/GenericJsonViewer.tsx +102 -0
- package/src/kit/components/asset-viewers/ImageViewer.tsx +300 -0
- package/src/kit/components/asset-viewers/JsonAssetDocument.tsx +98 -0
- package/src/kit/components/asset-viewers/OnlineAssetDetail.tsx +426 -0
- package/src/kit/components/asset-viewers/SourceAssetViewer.tsx +356 -0
- package/src/kit/components/asset-viewers/SpritesheetSpriteView.tsx +102 -0
- package/src/kit/components/asset-viewers/VideoViewer.tsx +101 -0
- package/src/kit/components/asset-viewers/shader-source.ts +144 -0
- package/src/kit/components/board-guides.ts +150 -0
- package/src/kit/components/canvas-scene-hotkeys.ts +37 -0
- package/src/kit/components/canvas-temporary-pivot.ts +34 -0
- package/src/kit/components/core-utilities.tsx +94 -0
- package/src/kit/components/inspector-preview-section.tsx +223 -0
- package/src/kit/components/inspector-revert-label.ts +20 -0
- package/src/kit/components/inspector-selection.ts +42 -0
- package/src/kit/components/inspector-stories-gating.ts +171 -0
- package/src/kit/components/inspector-transform-subject.ts +11 -0
- package/src/kit/components/inspector-transform.ts +88 -0
- package/src/kit/components/kind-documents.tsx +544 -0
- package/src/kit/components/primitives/DraftColorInput.tsx +74 -0
- package/src/kit/components/project-tool-documents.tsx +402 -0
- package/src/kit/components/scene-documents.tsx +221 -0
- package/src/kit/components/status-contributions.tsx +407 -0
- package/src/kit/components/tool-documents.tsx +302 -0
- package/src/kit/components/tool-schema-form.tsx +262 -0
- package/src/kit/components/use-after-paint.ts +41 -0
- package/src/kit/components/use-project-image-assets.ts +86 -0
- package/src/kit/components/workspace-history.ts +32 -0
- package/src/kit/components/world-documents.tsx +570 -0
- package/src/kit/components/world-overlay-gestures.ts +1939 -0
- package/src/kit/composite-screenshot.ts +2238 -0
- package/src/kit/content-entry-source-registry.ts +184 -0
- package/src/kit/contribution-surfaces.ts +48 -0
- package/src/kit/coverage/canvas-reveal.ts +192 -0
- package/src/kit/coverage/design-time-surfaces.ts +101 -0
- package/src/kit/coverage/ontology-invariants.ts +466 -0
- package/src/kit/coverage/session-vitals.ts +503 -0
- package/src/kit/crash-null-boundary.ts +36 -0
- package/src/kit/creation-site-edit.ts +1491 -0
- package/src/kit/creation-site-registry.ts +160 -0
- package/src/kit/delegate-harness-registry.ts +134 -0
- package/src/kit/document-areas.ts +70 -0
- package/src/kit/document-context-registry.ts +193 -0
- package/src/kit/document-open-registry.ts +200 -0
- package/src/kit/document-play-extension.ts +221 -0
- package/src/kit/document-preview-source.ts +20 -0
- package/src/kit/document-renderer-session.ts +138 -0
- package/src/kit/document-stage-sessions.ts +26 -0
- package/src/kit/document-viewports.ts +120 -0
- package/src/kit/editor-api.ts +46 -0
- package/src/kit/editor-chrome-capture.ts +136 -0
- package/src/kit/editor-commands.ts +176 -0
- package/src/kit/editor-console.ts +580 -0
- package/src/kit/editor-current-view.ts +56 -0
- package/src/kit/editor-document-probe.ts +1168 -0
- package/src/kit/editor-git-client.ts +115 -0
- package/src/kit/editor-hotkeys.ts +728 -0
- package/src/kit/editor-lease-view.ts +39 -0
- package/src/kit/editor-lease.ts +415 -0
- package/src/kit/editor-mode.ts +19 -0
- package/src/kit/editor-notifications.ts +140 -0
- package/src/kit/editor-presence.ts +563 -0
- package/src/kit/editor-presentation-activity.ts +58 -0
- package/src/kit/editor-presentation-notice.ts +42 -0
- package/src/kit/editor-runtime.tsx +147 -0
- package/src/kit/editor-server-response.ts +86 -0
- package/src/kit/editor-session-attribution.ts +85 -0
- package/src/kit/editor-session-mode.ts +54 -0
- package/src/kit/editor-state-facets.ts +74 -0
- package/src/kit/editor-view-presentation.ts +777 -0
- package/src/kit/environment-images.ts +58 -0
- package/src/kit/eyedropper-session.ts +60 -0
- package/src/kit/files/file-provider.ts +62 -0
- package/src/kit/files/project-files.ts +270 -0
- package/src/kit/finders/index.ts +137 -0
- package/src/kit/finders/scenes-from-entrypoint-selection.ts +387 -0
- package/src/kit/frame/frame-parts.ts +30 -0
- package/src/kit/framed-document-capture.ts +34 -0
- package/src/kit/game-globals-prelude.ts +143 -0
- package/src/kit/game-surface-defaults.ts +33 -0
- package/src/kit/gameplay-dom-recording.ts +318 -0
- package/src/kit/gameplay-export-state.ts +14 -0
- package/src/kit/gameplay-replay.ts +417 -0
- package/src/kit/gameplay-session-time.ts +9 -0
- package/src/kit/gameplay-sessions.ts +204 -0
- package/src/kit/hierarchy-component-marks.ts +298 -0
- package/src/kit/hierarchy-internals.ts +197 -0
- package/src/kit/hierarchy-kind-icon.ts +217 -0
- package/src/kit/hierarchy-menu-registry.ts +67 -0
- package/src/kit/hierarchy-node-rows.ts +307 -0
- package/src/kit/hierarchy-panel-view.ts +280 -0
- package/src/kit/hierarchy-projection.ts +76 -0
- package/src/kit/hierarchy-row-media.ts +45 -0
- package/src/kit/hierarchy-row-model.ts +308 -0
- package/src/kit/hierarchy-rows.ts +11 -0
- package/src/kit/hierarchy-walk.ts +86 -0
- package/src/kit/history/editor-session.ts +25 -0
- package/src/kit/history/history-commands.ts +147 -0
- package/src/kit/history/history-delegate.ts +187 -0
- package/src/kit/history/history-limit-notices.ts +43 -0
- package/src/kit/history/history-service.ts +1189 -0
- package/src/kit/history/persistence-coordinator.ts +35 -0
- package/src/kit/history/project-file-history.ts +386 -0
- package/src/kit/history/project-root-history-backends.ts +139 -0
- package/src/kit/history/resource-registry.ts +209 -0
- package/src/kit/history/snapshot-store.ts +103 -0
- package/src/kit/history/source-history-backend.ts +546 -0
- package/src/kit/history-types.ts +124 -0
- package/src/kit/hmr-registration-group.ts +67 -0
- package/src/kit/hmr-stable-react-context.ts +23 -0
- package/src/kit/hotkeys.ts +190 -0
- package/src/kit/inference-diagnostics.ts +69 -0
- package/src/kit/initial-project.ts +80 -0
- package/src/kit/inspection/active-subject.ts +571 -0
- package/src/kit/inspection/active-surface.ts +142 -0
- package/src/kit/inspection/compose-subject.ts +1055 -0
- package/src/kit/inspection/compose.ts +7 -0
- package/src/kit/inspection/display.ts +171 -0
- package/src/kit/inspection/document-subject.ts +109 -0
- package/src/kit/inspection/game-subject.ts +85 -0
- package/src/kit/inspection/null-subject.ts +119 -0
- package/src/kit/inspection/serialize.ts +357 -0
- package/src/kit/inspection/use-active-inspection.ts +180 -0
- package/src/kit/inspection-model.ts +542 -0
- package/src/kit/inspection-node-media.ts +58 -0
- package/src/kit/inspector-presentation.ts +203 -0
- package/src/kit/inspector-property-grouping.ts +64 -0
- package/src/kit/inspector-section-registry.ts +221 -0
- package/src/kit/instance-source-actions.ts +163 -0
- package/src/kit/js-heap.ts +71 -0
- package/src/kit/key-actions.ts +91 -0
- package/src/kit/keymap-presets.ts +428 -0
- package/src/kit/layout-policy.ts +31 -0
- package/src/kit/light-explorer-model.ts +134 -0
- package/src/kit/live-canvas-frame.ts +55 -0
- package/src/kit/live-document.ts +296 -0
- package/src/kit/live-gesture-lock.ts +50 -0
- package/src/kit/live-seam-evidence.ts +11 -0
- package/src/kit/live-session-registry.ts +220 -0
- package/src/kit/live-transition.ts +391 -0
- package/src/kit/manifest-project.ts +107 -0
- package/src/kit/module-fetch-diagnosis.ts +192 -0
- package/src/kit/mount-failure-report.ts +154 -0
- package/src/kit/native-selection-style.ts +497 -0
- package/src/kit/object3d-document-write-policy.ts +137 -0
- package/src/kit/packaged-runtime.ts +108 -0
- package/src/kit/palettes/maya.palette.json +57 -0
- package/src/kit/palettes/substance.palette.json +57 -0
- package/src/kit/performance-profiler.ts +367 -0
- package/src/kit/performance-sources.ts +69 -0
- package/src/kit/photograph-notice.ts +141 -0
- package/src/kit/play-boot-phase.ts +166 -0
- package/src/kit/play-camera-flight.ts +35 -0
- package/src/kit/png-encode.worker.ts +26 -0
- package/src/kit/presentation-surface.ts +248 -0
- package/src/kit/product-command.ts +90 -0
- package/src/kit/project-adapter.ts +1140 -0
- package/src/kit/project-asset-refresh.ts +23 -0
- package/src/kit/project-asset-roots.ts +68 -0
- package/src/kit/project-local-state.ts +151 -0
- package/src/kit/project-manager.ts +243 -0
- package/src/kit/project-module-changes.ts +201 -0
- package/src/kit/project-module-split.ts +270 -0
- package/src/kit/project-play-layers.ts +25 -0
- package/src/kit/project-provenance.ts +115 -0
- package/src/kit/project-ready.ts +42 -0
- package/src/kit/project-shape.ts +68 -0
- package/src/kit/project-tools.ts +107 -0
- package/src/kit/projection-types.ts +44 -0
- package/src/kit/readiness.ts +113 -0
- package/src/kit/renderer-resource-counts.ts +27 -0
- package/src/kit/reported-play-state.ts +90 -0
- package/src/kit/resolve-contributed-command.ts +14 -0
- package/src/kit/resolve-relative-specifier.ts +33 -0
- package/src/kit/retained-document-states.ts +91 -0
- package/src/kit/scene-document-plan.ts +320 -0
- package/src/kit/scene-live-open.ts +210 -0
- package/src/kit/scoped-game-css.ts +152 -0
- package/src/kit/served-url.ts +5 -0
- package/src/kit/session-close.ts +17 -0
- package/src/kit/session-tombstone.ts +127 -0
- package/src/kit/settings/settings-provider.ts +82 -0
- package/src/kit/settings-store.ts +348 -0
- package/src/kit/shell-document-state.ts +27 -0
- package/src/kit/shell-store-door.ts +45 -0
- package/src/kit/shell-store.ts +722 -0
- package/src/kit/source-conflict.ts +122 -0
- package/src/kit/stage-context.ts +377 -0
- package/src/kit/stage-invalidation.ts +25 -0
- package/src/kit/stage-store-registry.ts +69 -0
- package/src/kit/startup-failure.ts +80 -0
- package/src/kit/state-report-deferral.ts +73 -0
- package/src/kit/storage/host-files-storage.ts +97 -0
- package/src/kit/storage/http-storage.ts +174 -0
- package/src/kit/storage/index.ts +75 -0
- package/src/kit/storage/mem-storage.ts +158 -0
- package/src/kit/storage/path-lock.ts +44 -0
- package/src/kit/storage/paths.ts +26 -0
- package/src/kit/storage-types.ts +127 -0
- package/src/kit/stories/StoryPreviewMount.tsx +306 -0
- package/src/kit/stories/compose-project-stories.ts +255 -0
- package/src/kit/stories/prefabs-finder.ts +54 -0
- package/src/kit/stories/prefabs-from-stories.ts +182 -0
- package/src/kit/stories/project-story-regions.ts +24 -0
- package/src/kit/stories/story-capture.ts +579 -0
- package/src/kit/stories/story-declared-medium.ts +126 -0
- package/src/kit/stories/story-discovery.ts +176 -0
- package/src/kit/stories/story-dom-runtime.ts +78 -0
- package/src/kit/stories/story-grouping.ts +111 -0
- package/src/kit/stories/story-mount-turn.ts +27 -0
- package/src/kit/stories/story-presentation.ts +215 -0
- package/src/kit/stories/story-preview-component.ts +7 -0
- package/src/kit/stories/story-registry.ts +530 -0
- package/src/kit/stories-scope.ts +35 -0
- package/src/kit/story-document-openers.ts +36 -0
- package/src/kit/story-thumbnails.ts +47 -0
- package/src/kit/surface-keyboard.ts +101 -0
- package/src/kit/surface-state.ts +135 -0
- package/src/kit/system-seam-evidence.ts +72 -0
- package/src/kit/tab-census.ts +202 -0
- package/src/kit/tab-lifecycle-client.ts +227 -0
- package/src/kit/theme-library.ts +897 -0
- package/src/kit/theme-preference.ts +429 -0
- package/src/kit/three-viewport-presentation.ts +23 -0
- package/src/kit/tool-contribution-play.ts +74 -0
- package/src/kit/tool-loader.ts +1918 -0
- package/src/kit/transform-mode-request.ts +66 -0
- package/src/kit/transient-hint.ts +78 -0
- package/src/kit/transport-strip.tsx +174 -0
- package/src/kit/ui-source/adapter-region-includes.ts +238 -0
- package/src/kit/ui-source/file-region-resolver.ts +302 -0
- package/src/kit/ui-source/inspect.ts +775 -0
- package/src/kit/ui-source/source-write-backend.ts +605 -0
- package/src/kit/ui-source/tier-source-write-backend.ts +279 -0
- package/src/kit/user-local-state.ts +105 -0
- package/src/kit/viewport-activation-timings.ts +840 -0
- package/src/kit/viewport-editor-controls.ts +22 -0
- package/src/kit/viewport-presentation.ts +668 -0
- package/src/kit/viewport-surface-status.tsx +55 -0
- package/src/kit/wait-until.ts +37 -0
- package/src/kit/worker-call-metrics.ts +166 -0
- package/src/kit/workspace-areas.ts +191 -0
- package/src/kit/workspace-aux-commands.ts +11 -0
- package/src/kit/workspace-available-documents.ts +142 -0
- package/src/kit/workspace-core-utilities.ts +31 -0
- package/src/kit/workspace-document-ids.ts +59 -0
- package/src/kit/workspace-document-registry.ts +624 -0
- package/src/kit/workspace-document-restore.ts +146 -0
- package/src/kit/workspace-host-commands.ts +141 -0
- package/src/kit/workspace-persistence-gate.ts +40 -0
- package/src/kit/workspace-play-utilities.ts +44 -0
- package/src/kit/workspace-presets.ts +446 -0
- package/src/kit/workspace-regions.ts +276 -0
- package/src/kit/workspace-static-panels.ts +73 -0
- package/src/kit/workspace-status-registry.ts +121 -0
- package/src/kit/workspace-storage.ts +35 -0
- package/src/kit/workspace-style.ts +226 -0
- package/src/kit/workspace-utility-commands.ts +74 -0
- package/src/kit/workspace-utility-registry.ts +263 -0
- package/src/kit/world-adoption-event.ts +23 -0
- package/src/kit/world-adoption.ts +115 -0
- package/src/kit/world-canvas-viewport-state.ts +35 -0
- package/src/kit/world-document-routing.ts +104 -0
- package/src/kit/world-pan-state.ts +198 -0
- package/src/kit/write-pipe.ts +173 -0
- package/src/layout-arrangements.ts +5 -0
- package/src/layouts.tsx +108 -0
- package/src/looks.ts +16 -0
- package/src/project/output-roots.ts +73 -0
- package/src/project/tab-census.ts +155 -0
- package/src/project-tool-catalog.ts +104 -0
- package/src/selection.tsx +107 -0
- package/src/services.ts +18 -0
- package/src/session/build-report.ts +22 -0
- package/src/session/collaboration-types.ts +262 -0
- package/src/session/command-table.ts +327 -0
- package/src/session/discovery.ts +100 -0
- package/src/session/editor-brand.ts +48 -0
- package/src/session/editor-compatibility.ts +329 -0
- package/src/session/editor-control-lifecycle.ts +68 -0
- package/src/session/editor-control-protocol.ts +5 -0
- package/src/session/entrypoint-selection-readers.ts +66 -0
- package/src/session/entrypoint-selection-source.ts +120 -0
- package/src/session/game-css-scope.ts +30 -0
- package/src/session/hosted-attachment.ts +225 -0
- package/src/session/limited-view.ts +82 -0
- package/src/session/product-create.ts +24 -0
- package/src/session/product-locator.ts +478 -0
- package/src/session/project-module-url.ts +242 -0
- package/src/session/project-serving.ts +164 -0
- package/src/session/project-upgrade.ts +669 -0
- package/src/session/registry-format.ts +210 -0
- package/src/session/relative-path-guard.ts +56 -0
- package/src/session/scoped-game-css.ts +461 -0
- package/src/session/source-glob.ts +15 -0
- package/src/session/tool-contribution-convention.ts +123 -0
- package/src/session/workbench-locator.ts +712 -0
- package/src/session.ts +41 -0
- package/src/share.ts +160 -0
- package/src/source-analysis.ts +28 -0
- package/src/source-authoring.ts +439 -0
- package/src/tools/errors.ts +91 -0
- package/src/tools/provider-execution.ts +70 -0
- package/src/tools/registry.ts +341 -0
- package/src/tools/types.ts +159 -0
- package/src/transport.ts +100 -0
- package/src/types.ts +1693 -0
- package/src/views.ts +164 -0
- package/src/widgets/design-system.ts +93 -0
- package/src/widgets/editor-appearance.ts +151 -0
- package/src/widgets/editor-material.ts +83 -0
- package/src/widgets/icon-set-registry.ts +105 -0
- package/src/widgets/index.ts +71 -0
- package/src/widgets/inspector-widgets/AlignmentGrid.tsx +182 -0
- package/src/widgets/inspector-widgets/AssetSlotPicker.tsx +123 -0
- package/src/widgets/inspector-widgets/BorderEditor.tsx +309 -0
- package/src/widgets/inspector-widgets/ColorPicker.tsx +549 -0
- package/src/widgets/inspector-widgets/CurveEditor.tsx +359 -0
- package/src/widgets/inspector-widgets/FilterEditor.tsx +108 -0
- package/src/widgets/inspector-widgets/FontPicker.tsx +191 -0
- package/src/widgets/inspector-widgets/GradientEditor.tsx +623 -0
- package/src/widgets/inspector-widgets/ScrubbableInput.tsx +180 -0
- package/src/widgets/inspector-widgets/ShadowEditor.tsx +319 -0
- package/src/widgets/inspector-widgets/color-utils.ts +201 -0
- package/src/widgets/inspector-widgets/curve-utils.ts +212 -0
- package/src/widgets/inspector-widgets/index.ts +25 -0
- package/src/widgets/inspector-widgets/shared.tsx +140 -0
- package/src/widgets/interactive-edit-scope.ts +33 -0
- package/src/widgets/patterns/Dialog.tsx +140 -0
- package/src/widgets/patterns/Fields.tsx +44 -0
- package/src/widgets/patterns/List.tsx +25 -0
- package/src/widgets/patterns/StateSurface.tsx +40 -0
- package/src/widgets/patterns/Surfaces.tsx +122 -0
- package/src/widgets/patterns/Tabs.tsx +80 -0
- package/src/widgets/patterns/Toolbar.tsx +72 -0
- package/src/widgets/patterns/Tree.tsx +72 -0
- package/src/widgets/primitives/AnchoredMenu.tsx +260 -0
- package/src/widgets/primitives/Button.tsx +62 -0
- package/src/widgets/primitives/ColorInput.tsx +78 -0
- package/src/widgets/primitives/DraftTextInput.tsx +63 -0
- package/src/widgets/primitives/EditorIcon.tsx +157 -0
- package/src/widgets/primitives/FormControls.tsx +88 -0
- package/src/widgets/primitives/HoverPreview.tsx +96 -0
- package/src/widgets/primitives/JsonInput.tsx +113 -0
- package/src/widgets/primitives/Layout.tsx +100 -0
- package/src/widgets/primitives/Menu.tsx +161 -0
- package/src/widgets/primitives/NumberInput.tsx +169 -0
- package/src/widgets/primitives/Panel.tsx +80 -0
- package/src/widgets/primitives/SectionHeader.tsx +77 -0
- package/src/widgets/primitives/Text.tsx +54 -0
- package/src/widgets/primitives/ThemeRootPortal.tsx +52 -0
- package/src/widgets/primitives/Tooltip.tsx +204 -0
- package/src/widgets/primitives/Vec3Input.tsx +70 -0
- package/src/widgets/primitives/banner-tones.ts +32 -0
- package/src/widgets/primitives/clamp-to-viewport.ts +44 -0
- package/src/widgets/primitives/editor-icons.ts +254 -0
- package/src/widgets/primitives/panel-header-styles.ts +42 -0
- package/src/widgets/theme.ts +2841 -0
- package/src/widgets/z-index.ts +25 -0
|
@@ -0,0 +1,811 @@
|
|
|
1
|
+
import {
|
|
2
|
+
faArrowDown,
|
|
3
|
+
faArrowsToDot,
|
|
4
|
+
type faArrowsUpDownLeftRight,
|
|
5
|
+
faBullseye,
|
|
6
|
+
faCaretDown,
|
|
7
|
+
faCheck,
|
|
8
|
+
faCircleDot,
|
|
9
|
+
faCrosshairs,
|
|
10
|
+
faCube,
|
|
11
|
+
faGlobe,
|
|
12
|
+
faMagnet,
|
|
13
|
+
} from '@fortawesome/free-solid-svg-icons';
|
|
14
|
+
import type { StageTransformDoor, StageTransformKind } from '@volter/sdk/contributions';
|
|
15
|
+
import {
|
|
16
|
+
AnchoredMenu,
|
|
17
|
+
Button,
|
|
18
|
+
Checkbox,
|
|
19
|
+
EditorIcon,
|
|
20
|
+
EditorPopover,
|
|
21
|
+
EditorToolbar,
|
|
22
|
+
editorIcons,
|
|
23
|
+
FloatingToolbar,
|
|
24
|
+
IconButton,
|
|
25
|
+
Inline,
|
|
26
|
+
Keycap,
|
|
27
|
+
MenuItem,
|
|
28
|
+
SplitButtonGroup,
|
|
29
|
+
Stack,
|
|
30
|
+
Text,
|
|
31
|
+
TextInput,
|
|
32
|
+
Tooltip,
|
|
33
|
+
} from '@volter/sdk/widgets';
|
|
34
|
+
import { useEffect, useRef, useState, useSyncExternalStore } from 'react';
|
|
35
|
+
import { useEditorStore } from '../editor-runtime';
|
|
36
|
+
import type { GizmoAnchor, PivotMode } from '@volter/sdk/kit/shell-store';
|
|
37
|
+
import type { ShellStore } from '@volter/sdk/kit/shell-store';
|
|
38
|
+
import {
|
|
39
|
+
activeEditorKeymap,
|
|
40
|
+
type EditorKeyActionId,
|
|
41
|
+
shortcutFor,
|
|
42
|
+
subscribeEditorKeymap,
|
|
43
|
+
} from '@volter/sdk/kit/keymap-presets';
|
|
44
|
+
import { requestTransformMode } from '../transform-mode-request';
|
|
45
|
+
|
|
46
|
+
/** WHETHER IT IS LIT AND WHAT IT ARMS ARE THE STAGE'S ANSWER, not this
|
|
47
|
+
* button's: the gizmo arm reads `store.transformMode`, a modal door reads its
|
|
48
|
+
* own `armed()` (see `stageTransformDriver` in `stage-context.ts`). */
|
|
49
|
+
function ModeButton({
|
|
50
|
+
active,
|
|
51
|
+
onArm,
|
|
52
|
+
faIcon,
|
|
53
|
+
action,
|
|
54
|
+
label,
|
|
55
|
+
}: {
|
|
56
|
+
active: boolean;
|
|
57
|
+
onArm: () => void;
|
|
58
|
+
faIcon: typeof faArrowsUpDownLeftRight;
|
|
59
|
+
/** The LOGICAL action — its printed key comes from the active keymap. */
|
|
60
|
+
action: EditorKeyActionId;
|
|
61
|
+
label: string;
|
|
62
|
+
}) {
|
|
63
|
+
return (
|
|
64
|
+
<Tooltip text={label} hotkey={shortcutFor(action)}>
|
|
65
|
+
<IconButton
|
|
66
|
+
aria-label={label}
|
|
67
|
+
aria-pressed={active}
|
|
68
|
+
shape="segment"
|
|
69
|
+
size="comfortable"
|
|
70
|
+
onClick={onArm}
|
|
71
|
+
>
|
|
72
|
+
<EditorIcon icon={faIcon} size="md" />
|
|
73
|
+
</IconButton>
|
|
74
|
+
</Tooltip>
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function SnapButton({
|
|
79
|
+
store,
|
|
80
|
+
dimensions = '3d',
|
|
81
|
+
size = 'comfortable',
|
|
82
|
+
variant = 'ghost',
|
|
83
|
+
showValues = false,
|
|
84
|
+
}: {
|
|
85
|
+
store: ShellStore;
|
|
86
|
+
dimensions?: '2d' | '3d';
|
|
87
|
+
size?: 'compact' | 'default' | 'comfortable';
|
|
88
|
+
variant?: 'ghost' | 'secondary';
|
|
89
|
+
/** Draw the three steps themselves as the settings' trigger (Unreal's viewport row: the grid
|
|
90
|
+
* step, the angle and the scale step), each opening the same settings. */
|
|
91
|
+
showValues?: boolean;
|
|
92
|
+
}) {
|
|
93
|
+
const [popoverOpen, setPopoverOpen] = useState(false);
|
|
94
|
+
const ref = useRef<HTMLDivElement>(null);
|
|
95
|
+
|
|
96
|
+
useEffect(() => {
|
|
97
|
+
if (!popoverOpen) return;
|
|
98
|
+
const handler = (event: MouseEvent) => {
|
|
99
|
+
if (ref.current && !ref.current.contains(event.target as Node)) setPopoverOpen(false);
|
|
100
|
+
};
|
|
101
|
+
document.addEventListener('mousedown', handler);
|
|
102
|
+
return () => document.removeEventListener('mousedown', handler);
|
|
103
|
+
}, [popoverOpen]);
|
|
104
|
+
|
|
105
|
+
return (
|
|
106
|
+
<div ref={ref} className="volter-viewport-popover-anchor">
|
|
107
|
+
<SplitButtonGroup>
|
|
108
|
+
{dimensions === '2d' && (
|
|
109
|
+
// Godot's 2D toolbar has two snap toggles: smart snapping (alignment to the parent,
|
|
110
|
+
// other nodes and guides) and grid snapping (the step). This is the first.
|
|
111
|
+
<Tooltip text="Smart Snap">
|
|
112
|
+
<IconButton
|
|
113
|
+
aria-label="Toggle smart snap"
|
|
114
|
+
aria-pressed={store.smartSnap.enabled}
|
|
115
|
+
variant={variant}
|
|
116
|
+
size={size}
|
|
117
|
+
onClick={() => store.setSmartSnap({ enabled: !store.smartSnap.enabled })}
|
|
118
|
+
>
|
|
119
|
+
<EditorIcon icon={faArrowsToDot} size="md" />
|
|
120
|
+
</IconButton>
|
|
121
|
+
</Tooltip>
|
|
122
|
+
)}
|
|
123
|
+
<Tooltip
|
|
124
|
+
text={dimensions === '2d' ? 'Grid Snap' : 'Toggle Snap'}
|
|
125
|
+
hotkey={shortcutFor('viewport.toggleSnap')}
|
|
126
|
+
>
|
|
127
|
+
<IconButton
|
|
128
|
+
aria-label="Toggle snap"
|
|
129
|
+
aria-pressed={store.snapEnabled}
|
|
130
|
+
variant={variant}
|
|
131
|
+
size={size}
|
|
132
|
+
onClick={() => store.toggleSnap()}
|
|
133
|
+
>
|
|
134
|
+
<EditorIcon icon={faMagnet} size="md" />
|
|
135
|
+
</IconButton>
|
|
136
|
+
</Tooltip>
|
|
137
|
+
{showValues ? (
|
|
138
|
+
(
|
|
139
|
+
[
|
|
140
|
+
['translate', editorIcons.tool.move, `${store.snapValues.translate}`],
|
|
141
|
+
['rotate', editorIcons.tool.rotate, `${store.snapValues.rotate}°`],
|
|
142
|
+
['scale', editorIcons.tool.scale, `${store.snapValues.scale}`],
|
|
143
|
+
] as const
|
|
144
|
+
).map(([channel, icon, value]) => (
|
|
145
|
+
<Tooltip key={channel} text="Snap Settings">
|
|
146
|
+
<Button
|
|
147
|
+
aria-label={`${channel} snap step`}
|
|
148
|
+
aria-haspopup="dialog"
|
|
149
|
+
aria-expanded={popoverOpen}
|
|
150
|
+
variant={variant}
|
|
151
|
+
size={size}
|
|
152
|
+
data-snap-step={channel}
|
|
153
|
+
onClick={() => setPopoverOpen(!popoverOpen)}
|
|
154
|
+
>
|
|
155
|
+
<EditorIcon icon={icon} size="xs" />
|
|
156
|
+
{value}
|
|
157
|
+
</Button>
|
|
158
|
+
</Tooltip>
|
|
159
|
+
))
|
|
160
|
+
) : (
|
|
161
|
+
<Tooltip text="Snap Settings">
|
|
162
|
+
<IconButton
|
|
163
|
+
aria-label="Snap settings"
|
|
164
|
+
// A trigger that opens an anchored SETTINGS popover is a menu
|
|
165
|
+
// button, and `theme.css` reads that off the ARIA rather than a
|
|
166
|
+
// marker attribute — the same honest `dialog` the `Studio v`
|
|
167
|
+
// trigger declares. Blender's snap group is exactly this two-tone
|
|
168
|
+
// split: the magnet half in the pushbutton colour, this half in
|
|
169
|
+
// the menu well (measured native 2x `modeling.png` y=62, the
|
|
170
|
+
// toggle x 1411..1445 at #535353 and the dropdown x 1448..1507 at
|
|
171
|
+
// #272727, one 1 CSS px #3c3c3c divider between them).
|
|
172
|
+
aria-haspopup="dialog"
|
|
173
|
+
aria-expanded={popoverOpen}
|
|
174
|
+
variant={variant}
|
|
175
|
+
size={size}
|
|
176
|
+
onClick={() => setPopoverOpen(!popoverOpen)}
|
|
177
|
+
>
|
|
178
|
+
<EditorIcon icon={faCaretDown} size="xs" />
|
|
179
|
+
</IconButton>
|
|
180
|
+
</Tooltip>
|
|
181
|
+
)}
|
|
182
|
+
</SplitButtonGroup>
|
|
183
|
+
{popoverOpen && (
|
|
184
|
+
<EditorPopover className="volter-snap-popover">
|
|
185
|
+
<Stack gap={2}>
|
|
186
|
+
{dimensions === '2d' ? (
|
|
187
|
+
// Godot's Configure Snap: the grid's step and offset, in pixels.
|
|
188
|
+
<>
|
|
189
|
+
<Text variant="caption" tone="muted">
|
|
190
|
+
Grid Step (px)
|
|
191
|
+
</Text>
|
|
192
|
+
<TextInput
|
|
193
|
+
type="number"
|
|
194
|
+
aria-label="Grid step"
|
|
195
|
+
value={store.snap2D.step}
|
|
196
|
+
step={1}
|
|
197
|
+
min={1}
|
|
198
|
+
onChange={(event) => store.setSnap2D({ step: Number(event.target.value) || 8 })}
|
|
199
|
+
/>
|
|
200
|
+
<Text variant="caption" tone="muted">
|
|
201
|
+
Grid Offset (px)
|
|
202
|
+
</Text>
|
|
203
|
+
<Inline gap={1}>
|
|
204
|
+
<TextInput
|
|
205
|
+
type="number"
|
|
206
|
+
aria-label="Grid offset x"
|
|
207
|
+
value={store.snap2D.offsetX}
|
|
208
|
+
step={1}
|
|
209
|
+
onChange={(event) => store.setSnap2D({ offsetX: Number(event.target.value) || 0 })}
|
|
210
|
+
/>
|
|
211
|
+
<TextInput
|
|
212
|
+
type="number"
|
|
213
|
+
aria-label="Grid offset y"
|
|
214
|
+
value={store.snap2D.offsetY}
|
|
215
|
+
step={1}
|
|
216
|
+
onChange={(event) => store.setSnap2D({ offsetY: Number(event.target.value) || 0 })}
|
|
217
|
+
/>
|
|
218
|
+
</Inline>
|
|
219
|
+
<Text variant="caption" tone="muted">
|
|
220
|
+
Primary Line Every (steps)
|
|
221
|
+
</Text>
|
|
222
|
+
<TextInput
|
|
223
|
+
type="number"
|
|
224
|
+
aria-label="Primary line every"
|
|
225
|
+
value={store.snap2D.primaryEvery}
|
|
226
|
+
step={1}
|
|
227
|
+
min={1}
|
|
228
|
+
onChange={(event) => store.setSnap2D({ primaryEvery: Number(event.target.value) || 8 })}
|
|
229
|
+
/>
|
|
230
|
+
</>
|
|
231
|
+
) : (
|
|
232
|
+
<>
|
|
233
|
+
<Text variant="caption" tone="muted">
|
|
234
|
+
Translate
|
|
235
|
+
</Text>
|
|
236
|
+
<TextInput
|
|
237
|
+
type="number"
|
|
238
|
+
value={store.snapValues.translate}
|
|
239
|
+
step={0.25}
|
|
240
|
+
min={0.01}
|
|
241
|
+
onChange={(event) =>
|
|
242
|
+
store.setSnapValues({ translate: Number(event.target.value) || 1 })
|
|
243
|
+
}
|
|
244
|
+
/>
|
|
245
|
+
</>
|
|
246
|
+
)}
|
|
247
|
+
{dimensions === '2d' ? (
|
|
248
|
+
// Godot's Configure Snap for a 2D view: its own rotation offset and step, and scale step.
|
|
249
|
+
<>
|
|
250
|
+
<Text variant="caption" tone="muted">
|
|
251
|
+
Rotation Offset (deg)
|
|
252
|
+
</Text>
|
|
253
|
+
<TextInput
|
|
254
|
+
type="number"
|
|
255
|
+
aria-label="Rotation offset"
|
|
256
|
+
value={store.snap2D.rotationOffset}
|
|
257
|
+
step={1}
|
|
258
|
+
onChange={(event) => store.setSnap2D({ rotationOffset: Number(event.target.value) || 0 })}
|
|
259
|
+
/>
|
|
260
|
+
<Text variant="caption" tone="muted">
|
|
261
|
+
Rotation Step (deg)
|
|
262
|
+
</Text>
|
|
263
|
+
<TextInput
|
|
264
|
+
type="number"
|
|
265
|
+
aria-label="Rotation step"
|
|
266
|
+
value={store.snap2D.rotationStep}
|
|
267
|
+
step={1}
|
|
268
|
+
min={1}
|
|
269
|
+
onChange={(event) => store.setSnap2D({ rotationStep: Number(event.target.value) || 15 })}
|
|
270
|
+
/>
|
|
271
|
+
<Text variant="caption" tone="muted">
|
|
272
|
+
Scale Step
|
|
273
|
+
</Text>
|
|
274
|
+
<TextInput
|
|
275
|
+
type="number"
|
|
276
|
+
aria-label="Scale step"
|
|
277
|
+
value={store.snap2D.scaleStep}
|
|
278
|
+
step={0.05}
|
|
279
|
+
min={0.01}
|
|
280
|
+
onChange={(event) => store.setSnap2D({ scaleStep: Number(event.target.value) || 0.1 })}
|
|
281
|
+
/>
|
|
282
|
+
</>
|
|
283
|
+
) : (
|
|
284
|
+
<>
|
|
285
|
+
<Text variant="caption" tone="muted">
|
|
286
|
+
Rotate (deg)
|
|
287
|
+
</Text>
|
|
288
|
+
<TextInput
|
|
289
|
+
type="number"
|
|
290
|
+
value={store.snapValues.rotate}
|
|
291
|
+
step={5}
|
|
292
|
+
min={1}
|
|
293
|
+
onChange={(event) =>
|
|
294
|
+
store.setSnapValues({ rotate: Number(event.target.value) || 15 })
|
|
295
|
+
}
|
|
296
|
+
/>
|
|
297
|
+
<Text variant="caption" tone="muted">
|
|
298
|
+
Scale
|
|
299
|
+
</Text>
|
|
300
|
+
<TextInput
|
|
301
|
+
type="number"
|
|
302
|
+
value={store.snapValues.scale}
|
|
303
|
+
step={0.05}
|
|
304
|
+
min={0.01}
|
|
305
|
+
onChange={(event) =>
|
|
306
|
+
store.setSnapValues({ scale: Number(event.target.value) || 0.25 })
|
|
307
|
+
}
|
|
308
|
+
/>
|
|
309
|
+
</>
|
|
310
|
+
)}
|
|
311
|
+
{dimensions === '2d' && (
|
|
312
|
+
<>
|
|
313
|
+
{(
|
|
314
|
+
[
|
|
315
|
+
['Use Rotation Snap', store.rotationSnap, () => store.setRotationSnap(!store.rotationSnap)],
|
|
316
|
+
['Use Scale Snap', store.scaleSnap, () => store.setScaleSnap(!store.scaleSnap)],
|
|
317
|
+
['Snap Relative', store.snap2D.relative, () => store.setSnap2D({ relative: !store.snap2D.relative })],
|
|
318
|
+
['Use Pixel Snap', store.snap2D.pixel, () => store.setSnap2D({ pixel: !store.snap2D.pixel })],
|
|
319
|
+
] as const
|
|
320
|
+
).map(([label, on, toggle]) => (
|
|
321
|
+
<Inline key={label} gap={2} align="center" role="menuitemcheckbox" aria-checked={on} onClick={toggle}>
|
|
322
|
+
<Checkbox checked={on} readOnly />
|
|
323
|
+
<Text>{label}</Text>
|
|
324
|
+
</Inline>
|
|
325
|
+
))}
|
|
326
|
+
<Text variant="caption" tone="muted">
|
|
327
|
+
Smart Snapping
|
|
328
|
+
</Text>
|
|
329
|
+
{(
|
|
330
|
+
[
|
|
331
|
+
['parent', 'Snap to Parent'],
|
|
332
|
+
['others', 'Snap to Other Nodes'],
|
|
333
|
+
['sides', 'Snap to Node Sides'],
|
|
334
|
+
['center', 'Snap to Node Center'],
|
|
335
|
+
['guides', 'Snap to Guides'],
|
|
336
|
+
] as const
|
|
337
|
+
).map(([target, label]) => (
|
|
338
|
+
<Inline
|
|
339
|
+
key={target}
|
|
340
|
+
gap={2}
|
|
341
|
+
align="center"
|
|
342
|
+
role="menuitemcheckbox"
|
|
343
|
+
aria-checked={store.smartSnap[target]}
|
|
344
|
+
onClick={() => store.setSmartSnap({ [target]: !store.smartSnap[target] })}
|
|
345
|
+
>
|
|
346
|
+
<Checkbox checked={store.smartSnap[target]} readOnly />
|
|
347
|
+
<Text>{label}</Text>
|
|
348
|
+
</Inline>
|
|
349
|
+
))}
|
|
350
|
+
</>
|
|
351
|
+
)}
|
|
352
|
+
{dimensions === '3d' && (
|
|
353
|
+
<>
|
|
354
|
+
<Inline gap={2} align="center" onClick={() => store.toggleSnapToSurface()}>
|
|
355
|
+
<Checkbox checked={store.snapToSurface} readOnly />
|
|
356
|
+
<Text>Snap to Surface</Text>
|
|
357
|
+
</Inline>
|
|
358
|
+
<Text variant="caption" tone="dim">
|
|
359
|
+
Hold <Keycap>Ctrl</Keycap> while dragging to snap once ·{' '}
|
|
360
|
+
<Keycap>{shortcutFor('viewport.toggleSnap')}</Keycap> toggles
|
|
361
|
+
</Text>
|
|
362
|
+
<Text variant="caption" tone="dim">
|
|
363
|
+
Hold <Keycap>{shortcutFor('viewport.vertexSnapHold')}</Keycap> during translate
|
|
364
|
+
for vertex snap
|
|
365
|
+
</Text>
|
|
366
|
+
</>
|
|
367
|
+
)}
|
|
368
|
+
</Stack>
|
|
369
|
+
</EditorPopover>
|
|
370
|
+
)}
|
|
371
|
+
</div>
|
|
372
|
+
);
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
const pivotLabels: Record<PivotMode, string> = {
|
|
376
|
+
'active-element': 'Active Element',
|
|
377
|
+
'median-point': 'Median Point',
|
|
378
|
+
'individual-origins': 'Individual Origins',
|
|
379
|
+
};
|
|
380
|
+
|
|
381
|
+
function PivotButton({ store }: { store: ShellStore }) {
|
|
382
|
+
const modes: PivotMode[] = ['active-element', 'median-point', 'individual-origins'];
|
|
383
|
+
const label = `Pivot: ${pivotLabels[store.pivotMode]}`;
|
|
384
|
+
return (
|
|
385
|
+
<Tooltip text={label} hotkey={shortcutFor('viewport.cyclePivot')}>
|
|
386
|
+
<IconButton
|
|
387
|
+
aria-label={label}
|
|
388
|
+
aria-pressed={store.pivotMode !== 'active-element'}
|
|
389
|
+
variant="secondary"
|
|
390
|
+
onClick={() => {
|
|
391
|
+
const index = modes.indexOf(store.pivotMode);
|
|
392
|
+
store.setPivotMode(modes[(index + 1) % modes.length]!);
|
|
393
|
+
}}
|
|
394
|
+
>
|
|
395
|
+
<EditorIcon icon={faCrosshairs} size="md" />
|
|
396
|
+
</IconButton>
|
|
397
|
+
</Tooltip>
|
|
398
|
+
);
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
const anchorLabels: Record<GizmoAnchor, string> = {
|
|
402
|
+
auto: 'Auto',
|
|
403
|
+
pivot: 'Pivot',
|
|
404
|
+
center: 'Center',
|
|
405
|
+
};
|
|
406
|
+
|
|
407
|
+
/**
|
|
408
|
+
* WHERE the gizmo is drawn — the standard DCC pivot/centre affordance, cycled
|
|
409
|
+
* the way its `PivotButton` neighbour cycles its own three states.
|
|
410
|
+
*
|
|
411
|
+
* `Auto` leads because the right answer is a property of the SUBJECT: an
|
|
412
|
+
* ordinary node's pivot already sits in its content and nothing moves, while a
|
|
413
|
+
* world-anchored instanced system's sits at (0,0,0) with everything visible
|
|
414
|
+
* somewhere else (`instanced-presentation.ts`). The two explicit states are the
|
|
415
|
+
* override, in both directions.
|
|
416
|
+
*/
|
|
417
|
+
function AnchorButton({ store }: { store: ShellStore }) {
|
|
418
|
+
const modes: GizmoAnchor[] = ['auto', 'pivot', 'center'];
|
|
419
|
+
const label = `Anchor: ${anchorLabels[store.gizmoAnchor]}`;
|
|
420
|
+
return (
|
|
421
|
+
<Tooltip text={label}>
|
|
422
|
+
<IconButton
|
|
423
|
+
aria-label={label}
|
|
424
|
+
aria-pressed={store.gizmoAnchor !== 'auto'}
|
|
425
|
+
variant="secondary"
|
|
426
|
+
onClick={() => {
|
|
427
|
+
const index = modes.indexOf(store.gizmoAnchor);
|
|
428
|
+
store.setGizmoAnchor(modes[(index + 1) % modes.length]!);
|
|
429
|
+
}}
|
|
430
|
+
>
|
|
431
|
+
<EditorIcon icon={store.gizmoAnchor === 'center' ? faCircleDot : faBullseye} size="md" />
|
|
432
|
+
</IconButton>
|
|
433
|
+
</Tooltip>
|
|
434
|
+
);
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
function TransformOptionsButton({ store }: { store: ShellStore }) {
|
|
438
|
+
const [open, setOpen] = useState(false);
|
|
439
|
+
const ref = useRef<HTMLDivElement>(null);
|
|
440
|
+
useEffect(() => {
|
|
441
|
+
if (!open) return;
|
|
442
|
+
const close = (event: MouseEvent) => {
|
|
443
|
+
if (!ref.current?.contains(event.target as Node)) setOpen(false);
|
|
444
|
+
};
|
|
445
|
+
document.addEventListener('mousedown', close);
|
|
446
|
+
return () => document.removeEventListener('mousedown', close);
|
|
447
|
+
}, [open]);
|
|
448
|
+
return (
|
|
449
|
+
<div ref={ref} className="volter-viewport-popover-anchor">
|
|
450
|
+
<Tooltip text="Transform options">
|
|
451
|
+
<IconButton
|
|
452
|
+
aria-label="Transform options"
|
|
453
|
+
// Same reading as the snap group's dropdown half: an anchored
|
|
454
|
+
// settings popover is a menu button, and this is where Blender's
|
|
455
|
+
// proportional-editing dropdown sits in the row.
|
|
456
|
+
aria-haspopup="dialog"
|
|
457
|
+
aria-expanded={open}
|
|
458
|
+
variant="secondary"
|
|
459
|
+
onClick={() => setOpen((value) => !value)}
|
|
460
|
+
>
|
|
461
|
+
<EditorIcon icon={faArrowDown} size="md" />
|
|
462
|
+
</IconButton>
|
|
463
|
+
</Tooltip>
|
|
464
|
+
{open ? (
|
|
465
|
+
<EditorPopover style={{ minWidth: 240 }}>
|
|
466
|
+
<Stack gap={3}>
|
|
467
|
+
<Button
|
|
468
|
+
size="compact"
|
|
469
|
+
variant="ghost"
|
|
470
|
+
onClick={() => {
|
|
471
|
+
store.snapSelectionToFloor();
|
|
472
|
+
setOpen(false);
|
|
473
|
+
}}
|
|
474
|
+
>
|
|
475
|
+
Snap Selection to Floor <Keycap>{shortcutFor('viewport.snapToFloor')}</Keycap>
|
|
476
|
+
</Button>
|
|
477
|
+
<Inline
|
|
478
|
+
gap={2}
|
|
479
|
+
align="center"
|
|
480
|
+
onClick={() => store.togglePreserveChildrenTransform()}
|
|
481
|
+
style={{ cursor: 'pointer' }}
|
|
482
|
+
>
|
|
483
|
+
<Checkbox checked={store.preserveChildrenTransform} readOnly />
|
|
484
|
+
<Text>Preserve Children Transform</Text>
|
|
485
|
+
</Inline>
|
|
486
|
+
<Text variant="caption" tone="dim">
|
|
487
|
+
Keep source-writable children fixed in world space while their parent moves.
|
|
488
|
+
</Text>
|
|
489
|
+
</Stack>
|
|
490
|
+
</EditorPopover>
|
|
491
|
+
) : null}
|
|
492
|
+
</div>
|
|
493
|
+
);
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
/**
|
|
497
|
+
* THE TRANSFORM ORIENTATION control, transcribed from the frame rather than
|
|
498
|
+
* inferred: Blender's is a LABELLED DROPDOWN, not a cycling toggle. Measured on
|
|
499
|
+
* the native 2x `modeling.png` at row y=70 — the well runs x 1170..1323 (a
|
|
500
|
+
* 1 px #3c3c3c border at each end, #272727 fill between), i.e. 77 CSS px wide,
|
|
501
|
+
* because it carries an orientation glyph, the word `Global`, and a chevron at
|
|
502
|
+
* x 1304..1316 (13x8 device px, 6.5x4 CSS, peak 216). Ours was a 20 px
|
|
503
|
+
* icon-only button that cycled on click, which is also why the widget-colour
|
|
504
|
+
* rule painted it the #535353 pushbutton where the frame shows the menu well:
|
|
505
|
+
* the colour was agreeing with what the control DID.
|
|
506
|
+
*
|
|
507
|
+
* The STORE's vocabulary is unchanged and stays three.js's (`world`/`local`);
|
|
508
|
+
* only the printed word is the reference's, because `Global` is what this
|
|
509
|
+
* control is called in the frame this skew is transcribing.
|
|
510
|
+
*/
|
|
511
|
+
function TransformOrientationButton({ store }: { store: ShellStore }) {
|
|
512
|
+
const [open, setOpen] = useState(false);
|
|
513
|
+
const triggerRef = useRef<HTMLButtonElement>(null);
|
|
514
|
+
const label = store.transformSpace === 'world' ? 'Global' : 'Local';
|
|
515
|
+
return (
|
|
516
|
+
<div className="volter-viewport-popover-anchor">
|
|
517
|
+
<Tooltip text={`Transform orientation: ${label}`}>
|
|
518
|
+
<Button
|
|
519
|
+
ref={triggerRef}
|
|
520
|
+
type="button"
|
|
521
|
+
size="compact"
|
|
522
|
+
variant="secondary"
|
|
523
|
+
aria-label={`Transform orientation: ${label}`}
|
|
524
|
+
aria-haspopup="menu"
|
|
525
|
+
aria-expanded={open}
|
|
526
|
+
onClick={() => setOpen((value) => !value)}
|
|
527
|
+
className="volter-transform-orientation-trigger"
|
|
528
|
+
>
|
|
529
|
+
<EditorIcon icon={store.transformSpace === 'world' ? faGlobe : faCube} size="sm" />
|
|
530
|
+
<span>{label}</span>
|
|
531
|
+
<EditorIcon icon={faCaretDown} size="xs" />
|
|
532
|
+
</Button>
|
|
533
|
+
</Tooltip>
|
|
534
|
+
{open && (
|
|
535
|
+
<AnchoredMenu
|
|
536
|
+
anchorRef={triggerRef}
|
|
537
|
+
align="start"
|
|
538
|
+
clamp
|
|
539
|
+
aria-label="Transform orientation"
|
|
540
|
+
onDismiss={() => setOpen(false)}
|
|
541
|
+
>
|
|
542
|
+
{(['world', 'local'] as const).map((space) => (
|
|
543
|
+
<MenuItem
|
|
544
|
+
key={space}
|
|
545
|
+
role="menuitemradio"
|
|
546
|
+
aria-checked={store.transformSpace === space}
|
|
547
|
+
onSelect={() => {
|
|
548
|
+
store.setTransformSpace(space);
|
|
549
|
+
setOpen(false);
|
|
550
|
+
}}
|
|
551
|
+
>
|
|
552
|
+
<span className="volter-menu-check">
|
|
553
|
+
{store.transformSpace === space && <EditorIcon icon={faCheck} size="xs" />}
|
|
554
|
+
</span>
|
|
555
|
+
{space === 'world' ? 'Global' : 'Local'}
|
|
556
|
+
</MenuItem>
|
|
557
|
+
))}
|
|
558
|
+
</AnchoredMenu>
|
|
559
|
+
)}
|
|
560
|
+
</div>
|
|
561
|
+
);
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
/**
|
|
565
|
+
* THE VIEWPORT HEADER'S TRANSFORM CONTROLS — orientation, pivot, snap and the
|
|
566
|
+
* transform options, drawn in the document's header beside the host's 3D
|
|
567
|
+
* controls (`DocumentHeaderStrip`).
|
|
568
|
+
*
|
|
569
|
+
* WHY HERE AND NOT ON THE SHELF: a control is placed by the panel that owns its
|
|
570
|
+
* job in the reference. In Blender's 3D viewport these four are HEADER
|
|
571
|
+
* controls — transform orientation, transform pivot point, snapping with its
|
|
572
|
+
* dropdown, proportional editing — and the tool shelf (T) holds tools only.
|
|
573
|
+
* They rode the `ToolStrip` here until 2026-09-18, and the magnet is what
|
|
574
|
+
* showed it: a horizontal `SplitButtonGroup` inside a rail one tool wide spent
|
|
575
|
+
* two boxes and hung a half-width caret off the column.
|
|
576
|
+
*
|
|
577
|
+
* THE GROUPING IS TRANSCRIBED, measured on the native 2x `modeling.png`
|
|
578
|
+
* (row y=62, x in device px): Blender lays four separate WELLS with 12 device
|
|
579
|
+
* px (6 CSS px) of bare header between them — orientation x 1171..1322 (76 CSS
|
|
580
|
+
* px, it carries the "Global" label), pivot x 1335..1396 (31), snap
|
|
581
|
+
* x 1409..1510 (51), proportional editing x 1523..1624 (51). The last two are
|
|
582
|
+
* SPLIT: a toggle segment in the pushbutton colour (#535353) then a one-pixel
|
|
583
|
+
* #3c3c3c divider then an icon+caret dropdown in the menu-well colour
|
|
584
|
+
* (#272727) — exactly the `SplitButtonGroup` shape the magnet already had, in
|
|
585
|
+
* the row where it lays out. Each well is 40 device px tall (20 CSS px, the
|
|
586
|
+
* density's control height) inside a 54-device-px header band.
|
|
587
|
+
*
|
|
588
|
+
* Each control is `secondary`, so the estate's widget-colour rule paints it:
|
|
589
|
+
* a toggle takes the pushbutton class and a popover trigger the menu well,
|
|
590
|
+
* read off the trigger's own ARIA (`theme.css`, "A MENU BUTTON"). They drew as
|
|
591
|
+
* ghost icons floating on the header band before that — measured beside the
|
|
592
|
+
* frame at 4x, every one of Blender's carries a well.
|
|
593
|
+
*
|
|
594
|
+
* Three readings where ours differs from the frame, kept as they are because
|
|
595
|
+
* this unit moves placement and not shape: Blender's orientation is a labelled
|
|
596
|
+
* dropdown (`Global v`) where ours cycles world/local on click — so ours takes
|
|
597
|
+
* the pushbutton #535353 where the frame shows the #272727 menu well, which is
|
|
598
|
+
* the colour rule agreeing with what the control DOES; its pivot is one
|
|
599
|
+
* dropdown where we carry two cycling toggles — pivot MODE and gizmo ANCHOR,
|
|
600
|
+
* sharing one well because they are one job (where the gizmo sits), which is
|
|
601
|
+
* how Blender groups them; and our toggles are 20x20 against the frame's 31
|
|
602
|
+
* for pivot, because a cycling toggle carries no caret.
|
|
603
|
+
*/
|
|
604
|
+
export function TransformHeaderControls({ store: stage }: { store?: ShellStore } = {}) {
|
|
605
|
+
// THE STAGE'S OWN STORE, when the caller knows it. Every stage owns an
|
|
606
|
+
// `EditorShellStore` (`stage-store-registry.ts`) and these four configure
|
|
607
|
+
// the gizmo in THAT stage's viewport, so writing the shell's from over a
|
|
608
|
+
// document stage set a value nothing on screen reads — MEASURED 2026-09-19
|
|
609
|
+
// on a prefab story: with the shell at `rotate`, the story's stage kept
|
|
610
|
+
// drawing the default COMBINED gizmo, and two captures a mode apart were
|
|
611
|
+
// byte-identical. The world root's stage runs on the shell store itself, so
|
|
612
|
+
// the Scene document is unchanged either way.
|
|
613
|
+
const shell = useEditorStore();
|
|
614
|
+
const store = stage ?? shell;
|
|
615
|
+
useSyncExternalStore(store.subscribe, store.getShellSnapshot ?? store.getSnapshot);
|
|
616
|
+
useSyncExternalStore(subscribeEditorKeymap, activeEditorKeymap, activeEditorKeymap);
|
|
617
|
+
// ON THE STAGE'S BAR (Unreal's row) the snap steps are shown, as Unreal's row shows them. The
|
|
618
|
+
// host decides whether these controls ride the bar (a placed stage whose look puts them there),
|
|
619
|
+
// so where they are mounted is the answer, not the look alone.
|
|
620
|
+
const [mark, setMark] = useState<HTMLSpanElement | null>(null);
|
|
621
|
+
const onBar = mark?.closest('.volter-stage-bar') != null;
|
|
622
|
+
return (
|
|
623
|
+
<EditorToolbar
|
|
624
|
+
compact
|
|
625
|
+
label="Transform controls"
|
|
626
|
+
data-testid="transform-header-controls"
|
|
627
|
+
className="volter-transform-header-controls"
|
|
628
|
+
>
|
|
629
|
+
<span ref={setMark} hidden />
|
|
630
|
+
<TransformOrientationButton store={store} />
|
|
631
|
+
<SplitButtonGroup>
|
|
632
|
+
<PivotButton store={store} />
|
|
633
|
+
<AnchorButton store={store} />
|
|
634
|
+
</SplitButtonGroup>
|
|
635
|
+
<SnapButton store={store} size="default" variant="secondary" showValues={onBar} />
|
|
636
|
+
<TransformOptionsButton store={store} />
|
|
637
|
+
</EditorToolbar>
|
|
638
|
+
);
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
/**
|
|
642
|
+
* THE TOOL SHELF'S STRIP — tools, and nothing else (Blender's T shelf). The
|
|
643
|
+
* four header controls that used to trail it live in
|
|
644
|
+
* {@link TransformHeaderControls} now; the 2D canvas surface keeps its snap
|
|
645
|
+
* control inline because this strip lays out as a ROW over that stage (see
|
|
646
|
+
* `.volter-viewport-toolbar-left` in `workspace-dock.css`) and that surface has
|
|
647
|
+
* no Blender analogue to place it against.
|
|
648
|
+
*
|
|
649
|
+
* THESE FOUR ARE BLENDER'S OWN SECOND TOOLBAR GROUP, and the order is its
|
|
650
|
+
* order. Measured 2026-09-19 on `modeling-edit-none.png`: the Edit Mode
|
|
651
|
+
* toolbar's 21 buttons run in five groups of 2 / 4 / 2 / 1 / 12, and group 2
|
|
652
|
+
* is exactly Move, Rotate, Scale, Transform — the same four verbs this strip
|
|
653
|
+
* has always carried, under Font Awesome drawings and with the combined tool
|
|
654
|
+
* FIRST. So the answer to "do the host's navigation buttons belong at the top
|
|
655
|
+
* of a Blender toolbar at all" is yes and they were already right; what was
|
|
656
|
+
* wrong was the order (Transform leads in Blender's list only as the LAST
|
|
657
|
+
* member, the all-handles fallback after the three single-channel tools) and
|
|
658
|
+
* two of the glyphs (`editorIcons.tool`'s docblock says which and why).
|
|
659
|
+
* BLENDER'S GROUP 1 IS SELECT BOX AND THE 3D CURSOR, and as of 2026-09-21 the
|
|
660
|
+
* FIRST of the two is drawn above them while the second is still not. The
|
|
661
|
+
* earlier note here said neither was, and its reason was right at the time:
|
|
662
|
+
* neither had anything behind it once the Edit Mesh document was deleted (the
|
|
663
|
+
* 21-row table that said what each of the twenty-one does is in git,
|
|
664
|
+
* `packages/mesh/contributions/mesh-edit-document.tsx` before 2026-09-19).
|
|
665
|
+
* Select Box has something behind it NOW — `TransformMode`'s `'select'`, the
|
|
666
|
+
* state in which no transform gizmo is drawn at all, which is what Blender's
|
|
667
|
+
* viewport opens in (`gizmo-select-box.png`; the Blender stage's starting presentation states
|
|
668
|
+
* it, `interaction.bootTool`). The 3D cursor now exists — `Scene.cursor`, drawn
|
|
669
|
+
* and placed with Blender's Shift+Right-click on the Model document
|
|
670
|
+
* (`@volter/blender-engine`'s `blender-runtime-cursor.ts`) — but its toolbar
|
|
671
|
+
* tool, which places it with the LEFT button, is a transform mode this kit does
|
|
672
|
+
* not have, and a button without one would be the inert control the paragraph
|
|
673
|
+
* below removes.
|
|
674
|
+
*
|
|
675
|
+
* WHAT THEY DRIVE IS THE STAGE'S ANSWER, in two parts.
|
|
676
|
+
*
|
|
677
|
+
* `store` — WHICH STAGE'S GIZMO. Every stage owns an `EditorShellStore`
|
|
678
|
+
* (`stage-store-registry.ts`), and these four write `transformMode`, which
|
|
679
|
+
* that stage's `EditorViewport` reads. Writing the SHELL's from over a
|
|
680
|
+
* document stage set a value nothing on screen read: MEASURED 2026-09-19 on
|
|
681
|
+
* a prefab story with its subject selected, the shell store at `rotate`, the
|
|
682
|
+
* stage still drawing the default COMBINED gizmo, and two `capture-active-
|
|
683
|
+
* document` frames a mode apart BYTE-IDENTICAL. That, not a missing gizmo,
|
|
684
|
+
* is why these four were inert over a document stage. The world root's stage
|
|
685
|
+
* runs on the shell store itself, so the Scene document is unchanged.
|
|
686
|
+
*
|
|
687
|
+
* `door` — a stage that transforms MODALLY instead (a Model document's
|
|
688
|
+
* `G`/`R`/`S`, `registerStageTransform` in `@volter/sdk/contributions`,
|
|
689
|
+
* whose note carries that measurement). Then the three single-channel tools
|
|
690
|
+
* arm it and the all-handles tool is NOT DRAWN: Blender's fourth is a gizmo
|
|
691
|
+
* and a modal transform has no twin for it, so drawing it would be the inert
|
|
692
|
+
* control this seam exists to remove. `stageTransformDriver`
|
|
693
|
+
* (`stage-context.ts`) is what chooses between the two.
|
|
694
|
+
*/
|
|
695
|
+
const NO_DOOR_SUBSCRIBE = () => () => {};
|
|
696
|
+
const NO_DOOR_ARMED = () => null;
|
|
697
|
+
|
|
698
|
+
export function ToolStrip({
|
|
699
|
+
dimensions = '3d',
|
|
700
|
+
door,
|
|
701
|
+
store: stage,
|
|
702
|
+
otherToolActive = false,
|
|
703
|
+
onToolArmed,
|
|
704
|
+
viewTools,
|
|
705
|
+
trailingTools,
|
|
706
|
+
}: {
|
|
707
|
+
dimensions?: '2d' | '3d';
|
|
708
|
+
door?: StageTransformDoor;
|
|
709
|
+
store?: ShellStore;
|
|
710
|
+
/** Another tool of the same exclusive group is on (a 2D view's List Select, Pivot, Pan or
|
|
711
|
+
* Ruler, which Godot keeps in one radio group with these), so none of these is lit. */
|
|
712
|
+
otherToolActive?: boolean;
|
|
713
|
+
/** Called when one of these is armed, so the group's other tools can let go. */
|
|
714
|
+
onToolArmed?: () => void;
|
|
715
|
+
/** A view's own tools in the same group, drawn after these (a 2D view's List Select, Pivot,
|
|
716
|
+
* Pan and Ruler, in Godot's order). */
|
|
717
|
+
viewTools?: React.ReactNode;
|
|
718
|
+
/** A view's controls after snapping in the same row (a 2D view's Lock, Group and View menu, where
|
|
719
|
+
* Godot's 2D toolbar keeps them). */
|
|
720
|
+
trailingTools?: React.ReactNode;
|
|
721
|
+
} = {}) {
|
|
722
|
+
const shell = useEditorStore();
|
|
723
|
+
const store = stage ?? shell;
|
|
724
|
+
useSyncExternalStore(store.subscribe, store.getShellSnapshot ?? store.getSnapshot);
|
|
725
|
+
// Every hint below is rendered from the active keymap; a live switch must
|
|
726
|
+
// repaint the strip rather than leave it advertising the other table.
|
|
727
|
+
useSyncExternalStore(subscribeEditorKeymap, activeEditorKeymap, activeEditorKeymap);
|
|
728
|
+
// A modal transform's lit state is the DOOR's, and it changes without the
|
|
729
|
+
// shell store moving at all — the same `useSyncExternalStore` pair every
|
|
730
|
+
// out-of-tree panel uses, with the mesh document's own null-session shape
|
|
731
|
+
// for the gizmo arm (hooks cannot be conditional).
|
|
732
|
+
const armed = useSyncExternalStore(
|
|
733
|
+
door?.subscribe ?? NO_DOOR_SUBSCRIBE,
|
|
734
|
+
door?.armed ?? NO_DOOR_ARMED,
|
|
735
|
+
door?.armed ?? NO_DOOR_ARMED,
|
|
736
|
+
);
|
|
737
|
+
const single = [
|
|
738
|
+
{
|
|
739
|
+
mode: 'translate',
|
|
740
|
+
faIcon: editorIcons.tool.move,
|
|
741
|
+
action: 'transform.translate',
|
|
742
|
+
label: 'Move',
|
|
743
|
+
},
|
|
744
|
+
{
|
|
745
|
+
mode: 'rotate',
|
|
746
|
+
faIcon: editorIcons.tool.rotate,
|
|
747
|
+
action: 'transform.rotate',
|
|
748
|
+
label: 'Rotate',
|
|
749
|
+
},
|
|
750
|
+
{ mode: 'scale', faIcon: editorIcons.tool.scale, action: 'transform.scale', label: 'Scale' },
|
|
751
|
+
] as const satisfies readonly {
|
|
752
|
+
mode: StageTransformKind;
|
|
753
|
+
faIcon: typeof faArrowsUpDownLeftRight;
|
|
754
|
+
action: EditorKeyActionId;
|
|
755
|
+
label: string;
|
|
756
|
+
}[];
|
|
757
|
+
return (
|
|
758
|
+
<FloatingToolbar
|
|
759
|
+
label="Transform tools"
|
|
760
|
+
data-testid={dimensions === '2d' ? 'canvas-2d-toolstrip' : 'threejs-toolstrip'}
|
|
761
|
+
className="volter-viewport-toolbar volter-viewport-toolbar-left"
|
|
762
|
+
>
|
|
763
|
+
{/* SELECT BOX, first — Blender's own group 1 (see the docblock). It is
|
|
764
|
+
not offered over a MODAL door: `door.begin` takes a transform kind
|
|
765
|
+
and a modal transform has no "arm nothing" member, so the button
|
|
766
|
+
would have nothing to call. */}
|
|
767
|
+
{door ? null : (
|
|
768
|
+
<ModeButton
|
|
769
|
+
faIcon={editorIcons.tool.select}
|
|
770
|
+
action="transform.select"
|
|
771
|
+
label="Select"
|
|
772
|
+
// On a 2D surface Select is the handle mode, which the store names `combined`.
|
|
773
|
+
active={
|
|
774
|
+
!otherToolActive &&
|
|
775
|
+
(store.transformMode === 'select' || (dimensions === '2d' && store.transformMode === 'combined'))
|
|
776
|
+
}
|
|
777
|
+
onArm={() => {
|
|
778
|
+
onToolArmed?.();
|
|
779
|
+
requestTransformMode(store, 'select');
|
|
780
|
+
}}
|
|
781
|
+
/>
|
|
782
|
+
)}
|
|
783
|
+
{single.map((tool) => (
|
|
784
|
+
<ModeButton
|
|
785
|
+
key={tool.mode}
|
|
786
|
+
faIcon={tool.faIcon}
|
|
787
|
+
action={tool.action}
|
|
788
|
+
label={tool.label}
|
|
789
|
+
active={!otherToolActive && (door ? armed === tool.mode : store.transformMode === tool.mode)}
|
|
790
|
+
onArm={() => {
|
|
791
|
+
onToolArmed?.();
|
|
792
|
+
if (door) door.begin(tool.mode);
|
|
793
|
+
else requestTransformMode(store, tool.mode);
|
|
794
|
+
}}
|
|
795
|
+
/>
|
|
796
|
+
))}
|
|
797
|
+
{door || dimensions === '2d' ? null : (
|
|
798
|
+
<ModeButton
|
|
799
|
+
faIcon={editorIcons.tool.transform}
|
|
800
|
+
action="transform.combined"
|
|
801
|
+
label="Transform (all handles)"
|
|
802
|
+
active={store.transformMode === 'combined'}
|
|
803
|
+
onArm={() => requestTransformMode(store, 'combined')}
|
|
804
|
+
/>
|
|
805
|
+
)}
|
|
806
|
+
{viewTools}
|
|
807
|
+
{dimensions === '2d' ? <SnapButton store={store} dimensions="2d" /> : null}
|
|
808
|
+
{trailingTools}
|
|
809
|
+
</FloatingToolbar>
|
|
810
|
+
);
|
|
811
|
+
}
|