/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ /** * Cap-surface appearance controls shown inside the expanded Section panel. * * Layout principle (tight compact panel, ≤ 260 px wide): * [Display] Surfaces ⬛ Lines ⬛ * ─────────────────────────────────────────────── * [Hatch] * [Colours] Fill ▣ Hatch ▣ * [Shape] Spacing __px Angle __° Width __px * * Surfaces and Lines toggle independently so users can get a clean * "architectural drawing" look (outlines only), a pure hatched fill, or * the combination. All style inputs are hidden when Surfaces is off. */ import { useCallback, useId } from 'react'; import { useViewerStore } from '@/store'; import type { SectionCapHatchId } from '@/store/types'; const PATTERN_LABELS: Record = { solid: 'Solid fill', diagonal: 'Diagonal', crossHatch: 'Cross-hatch', horizontal: 'Horizontal', vertical: 'Vertical', concrete: 'Concrete', brick: 'Brick', insulation: 'Insulation', }; const PATTERN_IDS: SectionCapHatchId[] = [ 'diagonal', 'crossHatch', 'horizontal', 'vertical', 'concrete', 'brick', 'insulation', 'solid', ]; function rgbaToHex(c: [number, number, number, number]): string { const to2 = (v: number) => Math.round(Math.max(0, Math.min(1, v)) * 255).toString(16).padStart(2, '0'); return `#${to2(c[0])}${to2(c[1])}${to2(c[2])}`; } function hexToRgba(hex: string, alpha: number): [number, number, number, number] { const m = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex); if (!m) return [1, 1, 1, alpha]; return [ parseInt(m[1], 16) / 255, parseInt(m[2], 16) / 255, parseInt(m[3], 16) / 255, alpha, ]; } interface DisplayToggleProps { active: boolean; label: string; onToggle: () => void; } function DisplayToggle({ active, label, onToggle }: DisplayToggleProps): React.JSX.Element { return ( ); } export function SectionCapControls(): React.JSX.Element { const sectionPlane = useViewerStore((s) => s.sectionPlane); const setShowCap = useViewerStore((s) => s.setSectionShowCap); const setShowOutlines = useViewerStore((s) => s.setSectionShowOutlines); const setCapStyle = useViewerStore((s) => s.setSectionCapStyle); const onPattern = useCallback((e: React.ChangeEvent) => { setCapStyle({ pattern: e.target.value as SectionCapHatchId }); }, [setCapStyle]); const onFillColor = useCallback((e: React.ChangeEvent) => { setCapStyle({ fillColor: hexToRgba(e.target.value, sectionPlane.capStyle.fillColor[3]) }); }, [setCapStyle, sectionPlane.capStyle.fillColor]); const onStrokeColor = useCallback((e: React.ChangeEvent) => { setCapStyle({ strokeColor: hexToRgba(e.target.value, sectionPlane.capStyle.strokeColor[3]) }); }, [setCapStyle, sectionPlane.capStyle.strokeColor]); const onSpacing = useCallback((e: React.ChangeEvent) => { const v = Number(e.target.value); if (Number.isFinite(v)) setCapStyle({ spacingPx: Math.max(2, v) }); }, [setCapStyle]); const onAngle = useCallback((e: React.ChangeEvent) => { const deg = Number(e.target.value); if (Number.isFinite(deg)) setCapStyle({ angleRad: (deg * Math.PI) / 180 }); }, [setCapStyle]); const onWidth = useCallback((e: React.ChangeEvent) => { const v = Number(e.target.value); if (Number.isFinite(v)) setCapStyle({ widthPx: Math.max(1, v) }); }, [setCapStyle]); const onToggleCap = useCallback(() => setShowCap(!sectionPlane.showCap), [setShowCap, sectionPlane.showCap]); const onToggleOutlines = useCallback(() => setShowOutlines(!sectionPlane.showOutlines), [setShowOutlines, sectionPlane.showOutlines]); const angleDeg = Math.round((sectionPlane.capStyle.angleRad * 180) / Math.PI); // Stable ids for label/control association. Multiple instances of the // panel (rare, but possible during HMR) each get their own id namespace. const baseId = useId(); const patternId = `${baseId}-pattern`; const fillId = `${baseId}-fill`; const strokeId = `${baseId}-stroke`; const spacingId = `${baseId}-spacing`; const angleId = `${baseId}-angle`; const widthId = `${baseId}-width`; const hatchInputsDisabled = !sectionPlane.showCap; return (
{/* Display toggles — surfaces and lines independently. */}
Display
{/* Hatch style — disabled visually when surfaces are off. */}
); } export default SectionCapControls;