diff --git a/src/App.tsx b/src/App.tsx index e26c735..2194942 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1720,6 +1720,8 @@ export default function App() { const setElementWeight = useStore((s) => s.setElementWeight); const setElementFill = useStore((s) => s.setElementFill); const setElementFillColor = useStore((s) => s.setElementFillColor); + const setElementForeground = useStore((s) => s.setElementForeground); + const setElementBackground = useStore((s) => s.setElementBackground); const resizeElement = useStore((s) => s.resizeElement); const updateWall = useStore((s) => s.updateWall); const setWallThickness = useStore((s) => s.setWallThickness); @@ -2026,6 +2028,24 @@ export default function App() { if (selection?.kind === "drawing2d") setElementFillColor(selection.id, color); }, + onSetSelectionForeground: (color) => { + // Vordergrund-Override: Wand, Decke oder Drawing2D. + if ( + selection?.kind === "wall" || + selection?.kind === "ceiling" || + selection?.kind === "drawing2d" + ) + setElementForeground(selection.kind, selection.id, color); + }, + onSetSelectionBackground: (color) => { + // Hintergrund-Override: Wand, Decke oder Drawing2D. + if ( + selection?.kind === "wall" || + selection?.kind === "ceiling" || + selection?.kind === "drawing2d" + ) + setElementBackground(selection.kind, selection.id, color); + }, onResizeSelection: (w, h, anchor) => { if (selection?.kind === "wall" || selection?.kind === "drawing2d") resizeElement(selection.kind, selection.id, w, h, anchor); diff --git a/src/i18n/de.ts b/src/i18n/de.ts index 2ac1f9f..c22a0cb 100644 --- a/src/i18n/de.ts +++ b/src/i18n/de.ts @@ -215,6 +215,9 @@ export const de = { "attr.fill": "Füllung", "attr.fillColor": "Füllfarbe", "attr.clearFill": "Füllfarbe entfernen", + "attr.foreground": "Vordergrund", + "attr.background": "Hintergrund", + "attr.bySystem": "Nach System", "attr.hatch": "Schraffur", "attr.none": "keine", "attr.inheritedFromLayer": "Wände erben die Strichstärke aus der Ebene", diff --git a/src/i18n/en.ts b/src/i18n/en.ts index 3560e7e..5f7bd8c 100644 --- a/src/i18n/en.ts +++ b/src/i18n/en.ts @@ -214,6 +214,9 @@ export const en: Record = { "attr.fill": "Fill", "attr.fillColor": "Fill color", "attr.clearFill": "Remove fill color", + "attr.foreground": "Foreground", + "attr.background": "Background", + "attr.bySystem": "By system", "attr.hatch": "Hatch", "attr.none": "none", "attr.inheritedFromLayer": "Walls inherit line weight from the layer", diff --git a/src/panels/AttributesPanel.tsx b/src/panels/AttributesPanel.tsx index 31b64f3..546e107 100644 --- a/src/panels/AttributesPanel.tsx +++ b/src/panels/AttributesPanel.tsx @@ -37,8 +37,51 @@ export function AttributesPanel() { // Strichstärke wirkt laut Kontrakt nur auf Drawing2D; Wände erben sie aus der // Ebene und sind daher hier nicht editierbar. const weightEditable = isDrawing; - // Füllung nur bei geschlossener 2D-Form sinnvoll. + // Füllschraffur nur bei geschlossener 2D-Form sinnvoll. const fillEditable = isDrawing && sel.closed === true; + // Vordergrund/Hintergrund (Muster-/Füllfarbe) tragen Wand, Decke und + // geschlossene 2D-Formen als Override; `undefined` = „Nach System". + const pocheEditable = + sel.kind === "wall" || + sel.kind === "ceiling" || + (isDrawing && sel.closed === true); + + // Ein Override-Farbfeld mit „Nach System"-Zustand: ist der Wert `undefined`, + // zeigt es „Nach System" (leerer Swatch) statt einer Farbe und erbt; ein + // Klick auf × stellt zurück auf „Nach System" (schreibt `null` → Feld weg). + const overrideColor = ( + value: string | undefined, + onSet: (color: string | null) => void, + ) => ( + + + + ); // Effektiver Linienstil rein informativ (kein Setter im Kontrakt → kein Fake). // Wir leiten ihn aus dem Modell-Element ab, wenn explizit gesetzt; sonst „—". @@ -107,41 +150,17 @@ export function AttributesPanel() { - {/* ── Füllung (nur geschlossene 2D-Form) ────────────────────────── */} + {/* ── Füllung (Vordergrund/Hintergrund + Schraffur) ─────────────── */} + {/* Vordergrund = Muster-/Schraffurfarbe, Hintergrund = Füllfarbe/Poché. + Beide sind Overrides mit Default „Nach System" (erben vom Bauteil); + `background` ist der Nachfolger des alten reinen `fillColor`-Feldes. */}
{t("attr.fill")}
- {/* Vollton-Füllfarbe (getrennt von der Strichfarbe). „keine" = transparent. */} - {t("attr.fillColor")} - - - + {t("attr.foreground")} + {overrideColor(sel.foreground, host.onSetSelectionForeground)} + + {t("attr.background")} + {overrideColor(sel.background, host.onSetSelectionBackground)} {t("attr.hatch")} diff --git a/src/panels/host.ts b/src/panels/host.ts index ba015bf..93c7d7b 100644 --- a/src/panels/host.ts +++ b/src/panels/host.ts @@ -144,6 +144,16 @@ export interface PanelHostValue { * `null` entfernt die Füllfarbe (Fläche wieder transparent). */ onSetSelectionFillColor: (color: string | null) => void; + /** + * Setzt/entfernt den Vordergrund-Override (Muster-/Schraffurfarbe) der + * Selektion — Wand, Decke oder geschlossene Drawing2D. `null` = „Nach System". + */ + onSetSelectionForeground: (color: string | null) => void; + /** + * Setzt/entfernt den Hintergrund-Override (Füllfarbe/Poché) der Selektion — + * Wand, Decke oder geschlossene Drawing2D. `null` = „Nach System". + */ + onSetSelectionBackground: (color: string | null) => void; /** * Skaliert die Selektion auf Zielbreite×-höhe (Meter) um einen Anker * (fx/fy ∈ [0,1] relativ zur bbox; 0,0 = oben-links … 1,1 = unten-rechts). diff --git a/src/state/projectSlice.ts b/src/state/projectSlice.ts index 7e551dd..c677d84 100644 --- a/src/state/projectSlice.ts +++ b/src/state/projectSlice.ts @@ -134,6 +134,24 @@ export interface ProjectSlice { * `null` entfernt das Feld (Fläche wieder transparent). */ setElementFillColor: (id: string, fillColor: string | null) => void; + /** + * Setzt/entfernt den Vordergrund-Override (Muster-/Schraffurfarbe) — Wand, + * Decke oder Drawing2D. `null` = „Nach System" (Feld entfernen → erben). + */ + setElementForeground: ( + kind: "wall" | "ceiling" | "drawing2d", + id: string, + value: string | null, + ) => void; + /** + * Setzt/entfernt den Hintergrund-Override (Füllfarbe/Poché) — Wand, Decke + * oder Drawing2D. `null` = „Nach System" (Feld entfernen → erben). + */ + setElementBackground: ( + kind: "wall" | "ceiling" | "drawing2d", + id: string, + value: string | null, + ) => void; /** * Skaliert die Geometrie eines Elements auf Zielbreite×-höhe (Meter) um einen * Ankerpunkt (fx/fy ∈ [0,1] relativ zur bbox; 0,0 = oben-links). @@ -723,6 +741,13 @@ export function createProjectSlice( }), })), + // Vordergrund-/Hintergrund-Override (Wand/Decke/Drawing2D); `null` löscht + // das Feld (→ „Nach System", erbt wieder vom Bauteil/System). + setElementForeground: (kind, id, value) => + setProject((p) => setOverrideField(p, kind, id, "foreground", value)), + setElementBackground: (kind, id, value) => + setProject((p) => setOverrideField(p, kind, id, "background", value)), + // ── Größe (Resize um Anker) ──────────────────────────────────────────── resizeElement: (kind, id, w, h, anchor) => setProject((p) => resizeElement(p, kind, id, w, h, anchor)), @@ -879,6 +904,32 @@ function mapStair( } /** Immutabler Map über eine Decke (per ID); No-op ohne Treffer. */ +/** + * Setzt/entfernt einen optionalen Farb-Override (`foreground`/`background`) an + * einem Element (Wand, Decke oder Drawing2D). `value === null` entfernt das Feld + * (→ „Nach System", erbt wieder). Immutable; unbekannte IDs sind ein No-op. + */ +function setOverrideField( + project: Project, + kind: "wall" | "ceiling" | "drawing2d", + id: string, + field: "foreground" | "background", + value: string | null, +): Project { + const apply = (item: T): T => { + if (item.id !== id) return item; + if (value === null) { + const { [field]: _omit, ...rest } = item as Record; + return rest as unknown as T; + } + return { ...item, [field]: value }; + }; + if (kind === "wall") return { ...project, walls: project.walls.map(apply) }; + if (kind === "ceiling") + return { ...project, ceilings: (project.ceilings ?? []).map(apply) }; + return { ...project, drawings2d: project.drawings2d.map(apply) }; +} + function mapCeiling( project: Project, ceilingId: string, diff --git a/src/state/selectionInfo.ts b/src/state/selectionInfo.ts index 7819493..498f4ca 100644 --- a/src/state/selectionInfo.ts +++ b/src/state/selectionInfo.ts @@ -218,6 +218,17 @@ export interface Selection { * keine (transparent). Bei Wänden/Türen `undefined` (nicht anwendbar). */ fillColor?: string | null; + /** + * Vordergrund-Override (Muster-/Schraffurfarbe) des Elements; `undefined` = + * „Nach System" (erbt vom Bauteil/System). Gesetzt bei Wand, Decke und + * geschlossener Drawing2D. + */ + foreground?: string; + /** + * Hintergrund-Override (Füllfarbe/Poché) des Elements; `undefined` = „Nach + * System". Nachfolger von `fillColor`; hat im Plan Vorrang. + */ + background?: string; /** Ob die Form geschlossen ist (rect/circle/closed polyline) — nur Drawing2D. */ closed?: boolean; /** Achsparallele Bounding-Box in Modell-Metern. */ @@ -342,6 +353,8 @@ function wallSelection(project: Project, wall: Wall): Selection { weightMm, fillHatchId: undefined, fillColor: undefined, + foreground: wall.foreground, + background: wall.background, closed: undefined, bbox: { minX, minY, maxX, maxY }, wall: wallInfo, @@ -386,6 +399,8 @@ function ceilingSelection(project: Project, ceiling: Ceiling): Selection { weightMm, fillHatchId: undefined, fillColor: undefined, + foreground: ceiling.foreground, + background: ceiling.background, closed: true, bbox: { minX: box.minX, minY: box.minY, maxX: box.maxX, maxY: box.maxY }, ceiling: ceilingInfo, @@ -542,6 +557,8 @@ function drawingSelection(project: Project, d: Drawing2D): Selection { weightMm, fillHatchId: closed ? d.hatchId ?? null : null, fillColor: closed ? d.fillColor ?? null : null, + foreground: closed ? d.foreground : undefined, + background: closed ? d.background : undefined, closed, bbox: geomBBox(d.geom), };