Add support for whitebox selection mode and hover labels in ViewportCanvas and ViewportPanel

This commit is contained in:
2026-04-04 20:12:00 +02:00
parent 7ab407608e
commit 94fe6ca280
2 changed files with 60 additions and 1 deletions

View File

@@ -4,7 +4,9 @@ import type { LoadedModelAsset } from "../assets/gltf-model-import";
import type { LoadedImageAsset } from "../assets/image-assets";
import type { ProjectAssetRecord } from "../assets/project-assets";
import type { EditorSelection } from "../core/selection";
import { getWhiteboxSelectionFeedbackLabel } from "../core/whitebox-selection-feedback";
import type { ToolMode } from "../core/tool-mode";
import { getWhiteboxSelectionModeLabel, type WhiteboxSelectionMode } from "../core/whitebox-selection-mode";
import type { Vec3 } from "../core/vector";
import type { ActiveTransformSession, TransformSessionState } from "../core/transform-session";
import type { SceneDocument } from "../document/scene-document";
@@ -32,6 +34,7 @@ interface ViewportCanvasProps {
projectAssets: Record<string, ProjectAssetRecord>;
loadedModelAssets: Record<string, LoadedModelAsset>;
loadedImageAssets: Record<string, LoadedImageAsset>;
whiteboxSelectionMode: WhiteboxSelectionMode;
whiteboxSnapEnabled: boolean;
whiteboxSnapStep: number;
selection: EditorSelection;
@@ -61,6 +64,7 @@ export function ViewportCanvas({
projectAssets,
loadedModelAssets,
loadedImageAssets,
whiteboxSelectionMode,
whiteboxSnapEnabled,
whiteboxSnapStep,
selection,
@@ -85,6 +89,7 @@ export function ViewportCanvas({
const containerRef = useRef<HTMLDivElement | null>(null);
const hostRef = useRef<ViewportHost | null>(null);
const [viewportMessage, setViewportMessage] = useState<string | null>(null);
const [hoveredWhiteboxLabel, setHoveredWhiteboxLabel] = useState<string | null>(null);
useEffect(() => {
const container = containerRef.current;
@@ -138,6 +143,10 @@ export function ViewportCanvas({
hostRef.current?.setWhiteboxSnapSettings(whiteboxSnapEnabled, whiteboxSnapStep);
}, [whiteboxSnapEnabled, whiteboxSnapStep]);
useEffect(() => {
hostRef.current?.setWhiteboxSelectionMode(whiteboxSelectionMode);
}, [whiteboxSelectionMode]);
useEffect(() => {
hostRef.current?.updateDocument(sceneDocument, selection);
}, [sceneDocument, selection]);
@@ -158,6 +167,10 @@ export function ViewportCanvas({
hostRef.current?.setBrushSelectionChangeHandler(onSelectionChange);
}, [onSelectionChange]);
useEffect(() => {
hostRef.current?.setWhiteboxHoverLabelChangeHandler(setHoveredWhiteboxLabel);
}, []);
useEffect(() => {
hostRef.current?.setCameraStateChangeHandler(onCameraStateChange);
}, [onCameraStateChange]);
@@ -213,8 +226,10 @@ export function ViewportCanvas({
const previewVisible = toolMode === "create" && toolPreview.kind === "create" && toolPreview.center !== null;
const transformPreviewVisible = transformSession.kind === "active";
const selectionModeVisible = toolMode === "select";
const selectedWhiteboxLabel = selectionModeVisible ? getWhiteboxSelectionFeedbackLabel(sceneDocument, selection) : null;
const showViewModeOverlay = layoutMode === "quad";
const showOverlay = showViewModeOverlay || previewVisible || transformPreviewVisible;
const showOverlay = showViewModeOverlay || selectionModeVisible || previewVisible || transformPreviewVisible || selectedWhiteboxLabel !== null || hoveredWhiteboxLabel !== null;
return (
<div
@@ -237,6 +252,24 @@ export function ViewportCanvas({
{!showViewModeOverlay ? null : (
<div className="viewport-canvas__overlay-badges">
<div className="viewport-canvas__overlay-badge viewport-canvas__overlay-badge--view">{getViewportViewModeLabel(viewMode)}</div>
{!selectionModeVisible ? null : (
<div
className="viewport-canvas__overlay-badge viewport-canvas__overlay-badge--selection"
data-testid={`viewport-selection-mode-${panelId}`}
>
{getWhiteboxSelectionModeLabel(whiteboxSelectionMode)}
</div>
)}
</div>
)}
{showViewModeOverlay || !selectionModeVisible ? null : (
<div className="viewport-canvas__overlay-badges">
<div
className="viewport-canvas__overlay-badge viewport-canvas__overlay-badge--selection"
data-testid={`viewport-selection-mode-${panelId}`}
>
{getWhiteboxSelectionModeLabel(whiteboxSelectionMode)}
</div>
</div>
)}
{!previewVisible ? null : (
@@ -251,6 +284,16 @@ export function ViewportCanvas({
: `${transformSession.operation}${transformSession.axisConstraint === null ? "" : ` · ${transformSession.axisConstraint.toUpperCase()}`}`}
</div>
)}
{selectedWhiteboxLabel === null ? null : (
<div className="viewport-canvas__overlay-preview" data-testid={`viewport-selected-whitebox-${panelId}`}>
Selected: {selectedWhiteboxLabel}
</div>
)}
{hoveredWhiteboxLabel === null ? null : (
<div className="viewport-canvas__overlay-preview" data-testid={`viewport-hovered-whitebox-${panelId}`}>
Hover: {hoveredWhiteboxLabel}
</div>
)}
</div>
)}

View File

@@ -16,6 +16,7 @@ import type { LoadedModelAsset } from "../assets/gltf-model-import";
import type { LoadedImageAsset } from "../assets/image-assets";
import type { ProjectAssetRecord } from "../assets/project-assets";
import type { EditorSelection } from "../core/selection";
import type { WhiteboxSelectionMode } from "../core/whitebox-selection-mode";
import type { ActiveTransformSession, TransformSessionState } from "../core/transform-session";
import type { ToolMode } from "../core/tool-mode";
import type { SceneDocument } from "../document/scene-document";
@@ -33,6 +34,7 @@ interface ViewportPanelProps {
projectAssets: Record<string, ProjectAssetRecord>;
loadedModelAssets: Record<string, LoadedModelAsset>;
loadedImageAssets: Record<string, LoadedImageAsset>;
whiteboxSelectionMode: WhiteboxSelectionMode;
whiteboxSnapEnabled: boolean;
whiteboxSnapStep: number;
selection: EditorSelection;
@@ -66,6 +68,7 @@ export function ViewportPanel({
projectAssets,
loadedModelAssets,
loadedImageAssets,
whiteboxSelectionMode,
whiteboxSnapEnabled,
whiteboxSnapStep,
selection,
@@ -102,6 +105,18 @@ export function ViewportPanel({
onFocusCapture={() => onActivatePanel(panelId)}
>
<div className="viewport-panel__header">
{layoutMode !== "quad" ? null : (
<div className="viewport-panel__meta">
<div className="viewport-panel__title-row">
<div className="viewport-panel__title">{getViewportPanelLabel(panelId)}</div>
{!isActive ? null : (
<div className="viewport-panel__active-badge" data-testid={`viewport-panel-active-badge-${panelId}`}>
Active
</div>
)}
</div>
</div>
)}
<div className="viewport-panel__controls">
<div className="viewport-panel__control-group" role="group" aria-label={`${getViewportPanelLabel(panelId)} view mode`}>
{VIEWPORT_VIEW_MODES.map((viewMode) => (
@@ -142,6 +157,7 @@ export function ViewportPanel({
projectAssets={projectAssets}
loadedModelAssets={loadedModelAssets}
loadedImageAssets={loadedImageAssets}
whiteboxSelectionMode={whiteboxSelectionMode}
whiteboxSnapEnabled={whiteboxSnapEnabled}
whiteboxSnapStep={whiteboxSnapStep}
selection={selection}