diff --git a/src/App.tsx b/src/App.tsx
index 6af317f..05c6ed8 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -2715,9 +2715,6 @@ export default function App() {
referenceLines={referenceLines}
onReferenceLines={setReferenceLines}
referenceLinesEnabled={planActive}
- lineMode={lineMode}
- onLineMode={setLineMode}
- lineModeEnabled={planActive}
combos={{
// Ebenen-Kombinationen: Speichern = Snapshot des Layer-Baums →
// localStorage; Laden = Map lesen → Sichtbarkeits-Flags anwenden.
@@ -2959,6 +2956,11 @@ export default function App() {
rendererMode={rendererMode}
webGpuAvailable={webGpuAvailable}
onRendererModeChange={setRendererMode}
+ lineMode={lineMode}
+ onLineModeChange={setLineMode}
+ lineModeEnabled={planActive}
+ snap={snap}
+ onSnapChange={setSnap}
/>
{/* Schwebendes Ressourcen-Fenster ÜBER allem (hoher z-index, abgedunkelter
diff --git a/src/panels/ToolsPanel.tsx b/src/panels/ToolsPanel.tsx
index a644021..a21a197 100644
--- a/src/panels/ToolsPanel.tsx
+++ b/src/panels/ToolsPanel.tsx
@@ -119,7 +119,6 @@ function ToolIcon({ id }: { id: ToolId }) {
export function ToolsPanel() {
const host = usePanelHost();
- const snap = host.snap;
return (
@@ -190,91 +189,6 @@ export function ToolsPanel() {
)}
{host.activeTool === "ceiling" &&
}
-
- {/* Fang-Abschnitt. Master-Checkbox steuert die übrigen Fang-Optionen. */}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
);
}
diff --git a/src/styles.css b/src/styles.css
index 916d786..85dd895 100644
--- a/src/styles.css
+++ b/src/styles.css
@@ -803,6 +803,97 @@ body {
cursor: default;
}
+/* Generische Segment-Pille für die Statusleiste (Linien-Darstellung, Fang) —
+ gleiche Masse/Optik wie .sb-renderer-toggle, aber nicht renderer-spezifisch. */
+.sb-seg {
+ display: inline-flex;
+ align-items: stretch;
+ height: 16px;
+ border: 1px solid var(--border);
+ border-radius: 999px;
+ overflow: hidden;
+ background: var(--input);
+}
+.sb-seg.disabled {
+ opacity: 0.4;
+}
+.sb-seg-btn {
+ display: inline-flex;
+ align-items: center;
+ border: none;
+ border-left: 1px solid var(--border);
+ background: transparent;
+ color: var(--muted);
+ font-family: inherit;
+ font-size: 10px;
+ font-weight: 600;
+ letter-spacing: 0.02em;
+ padding: 0 8px;
+ cursor: pointer;
+ transition: background 0.14s, color 0.14s;
+}
+.sb-seg-btn:first-child {
+ border-left: none;
+}
+.sb-seg-btn:hover:not(:disabled) {
+ background: var(--accent-dim);
+ color: var(--accent);
+}
+.sb-seg-btn.active {
+ background: var(--accent);
+ color: #fff;
+}
+.sb-seg-btn:disabled {
+ opacity: 0.4;
+ cursor: default;
+}
+/* Caret-Segment (öffnet das Fang-Popover) — schmaler als die Text-Segmente. */
+.sb-seg-caret {
+ padding: 0 6px;
+ font-size: 9px;
+ line-height: 1;
+}
+
+/* Fang-Kontrolle: Wrapper ist relativ positioniert, damit das Popover NACH
+ OBEN öffnen kann (die Leiste sitzt ganz unten im App-Shell). */
+.sb-snap-wrap {
+ position: relative;
+ display: inline-flex;
+}
+.sb-snap-popover {
+ position: absolute;
+ right: 0;
+ bottom: 100%;
+ margin: 0 0 6px;
+ padding: 6px 0;
+ min-width: 170px;
+ background: var(--panel, #1c2128);
+ border: 1px solid var(--border);
+ border-radius: 8px;
+ box-shadow: 0 8px 24px rgba(0, 0, 0, 0.45);
+ z-index: 200;
+}
+.sb-snap-row {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ padding: 3px 12px;
+ font-size: 12px;
+ cursor: pointer;
+ user-select: none;
+ white-space: nowrap;
+}
+.sb-snap-row input[type="checkbox"] {
+ accent-color: var(--accent);
+}
+.sb-snap-row-num {
+ justify-content: space-between;
+ cursor: default;
+}
+.sb-snap-row-num input[type="number"] {
+ width: 64px;
+}
+
/* ── Command-Line (Rhino-artig) — über der Statusleiste ──────────────────────
Dunkle, ruhige Leiste mit Prompt links, klickbaren Inline-Optionen und einer
monospace-Texteingabe rechts; das Autocomplete-Dropdown öffnet nach oben. */
diff --git a/src/ui/StatusBar.tsx b/src/ui/StatusBar.tsx
index b247960..8f418d9 100644
--- a/src/ui/StatusBar.tsx
+++ b/src/ui/StatusBar.tsx
@@ -8,7 +8,9 @@
// Rein gesteuert (controlled): alle Werte kommen aus App (Modell + View-State),
// keine eigene Logik. Bezeichner englisch, UI-Text/Kommentare deutsch.
+import { useEffect, useRef, useState } from "react";
import type { Vec2 } from "../model/types";
+import type { SnapSettings } from "../tools/types";
import { t } from "../i18n";
/** Plan-Renderer-Wahl, gespiegelt aus dem `?engine`-URL-Param (siehe PlanView). */
@@ -41,6 +43,199 @@ export interface StatusBarProps {
* Live-Remount der Renderer-Hooks).
*/
onRendererModeChange: (mode: RendererMode) => void;
+ /** Linien-Darstellung: „display" (Haarlinien) oder „print" (echte mm-Stärken). */
+ lineMode: "display" | "print";
+ /** Linien-Darstellung wechseln. */
+ onLineModeChange: (mode: "display" | "print") => void;
+ /** Nur im Grundriss sinnvoll — deaktiviert beide Segmente wenn `false`. */
+ lineModeEnabled: boolean;
+ /** Fang-Einstellungen (Werkzeug-übergreifend). */
+ snap: SnapSettings;
+ /** Fang-Einstellungen ändern. */
+ onSnapChange: (s: SnapSettings) => void;
+}
+
+/**
+ * Zweisegmentige Umschalt-Pille Display/Print für die Statusleiste — optisch
+ * an `.sb-renderer-toggle` angelehnt (gleiche Höhe/Radius), eigene Klasse
+ * `.sb-seg` weil sie generisch (nicht renderer-spezifisch) bleibt.
+ */
+function LineModeToggle({
+ lineMode,
+ onLineModeChange,
+ lineModeEnabled,
+}: {
+ lineMode: "display" | "print";
+ onLineModeChange: (m: "display" | "print") => void;
+ lineModeEnabled: boolean;
+}) {
+ return (
+
+ {t("topbar.lineModeGroup")}
+
+
+
+
+
+ );
+}
+
+/**
+ * Fang-Kontrolle für die Statusleiste: eine Pille schaltet `snap.enabled`
+ * direkt um (Klick = an/aus, aktive Einfärbung wie der Renderer-Umschalter);
+ * ein kleiner Caret daneben öffnet ein NACH OBEN öffnendes Popover (Leiste
+ * sitzt unten) mit den Fang-Unteroptionen — 1:1 aus dem früheren
+ * ToolsPanel-„Fang-Abschnitt" übernommen. Schliesst bei Klick ausserhalb.
+ */
+function SnapControl({
+ snap,
+ onSnapChange,
+}: {
+ snap: SnapSettings;
+ onSnapChange: (s: SnapSettings) => void;
+}) {
+ const [open, setOpen] = useState(false);
+ const wrapRef = useRef(null);
+
+ useEffect(() => {
+ if (!open) return;
+ const onDown = (e: MouseEvent) => {
+ if (wrapRef.current && !wrapRef.current.contains(e.target as Node)) {
+ setOpen(false);
+ }
+ };
+ document.addEventListener("mousedown", onDown);
+ return () => document.removeEventListener("mousedown", onDown);
+ }, [open]);
+
+ return (
+
+
+
+
+
+ {open && (
+
+
+
+
+
+
+
+
+
+
+ )}
+
+ );
}
/** Ein Wert-Feld im rechten Cluster: kleine Beschriftung + Wert. */
@@ -72,6 +267,11 @@ export function StatusBar({
rendererMode,
webGpuAvailable,
onRendererModeChange,
+ lineMode,
+ onLineModeChange,
+ lineModeEnabled,
+ snap,
+ onSnapChange,
}: StatusBarProps) {
const coords = cursor
? `${fmt(cursor.x)}, ${fmt(cursor.y)}`
@@ -82,8 +282,17 @@ export function StatusBar({
{/* Links: Werkzeug-Hinweis. */}
{hint}
- {/* Rechts: abgeleiteter Wert-Cluster. */}
+ {/* Rechts: abgeleiteter Wert-Cluster. Interaktive Kontrollen (Linien-
+ Darstellung, Fang) zuerst, danach die reinen Anzeigewerte. */}
+
+
+
+
{t("status.renderer")}
diff --git a/src/ui/TopBar.tsx b/src/ui/TopBar.tsx
index 1c7644c..9535316 100644
--- a/src/ui/TopBar.tsx
+++ b/src/ui/TopBar.tsx
@@ -141,12 +141,6 @@ export interface TopBarProps {
/** Referenzlinien nur im Grundriss sinnvoll (sonst deaktiviert). */
referenceLinesEnabled: boolean;
- // Linien-Darstellung: Display (Haarlinien) ↔ Print (echte mm-Strichstärken).
- lineMode: "display" | "print";
- onLineMode: (m: "display" | "print") => void;
- /** Nur im Grundriss sinnvoll. */
- lineModeEnabled: boolean;
-
// Sichtbarkeits-Kombinationen (Ebenen-/Zeichnungskombinationen).
/** Handler-Satz für die beiden Kombinations-Dropdowns. */
combos: ComboMenuHandlers;
@@ -855,9 +849,6 @@ export function TopBar({
referenceLines,
onReferenceLines,
referenceLinesEnabled,
- lineMode,
- onLineMode,
- lineModeEnabled,
combos,
resourcesOpen,
onToggleResources,
@@ -1202,30 +1193,6 @@ export function TopBar({
- {/* Linien-Darstellung: Display (alle Haarlinien) ↔ Print (echte mm-
- Strichstärken). Eine Einfach-Auswahl → als Dropdown (Trigger-Pill +
- Häkchen-Liste, wie Detailgrad/Massstab), nicht mehr als Segment-Pillen.
- Optionen tragen ein Icon plus Klartext. Nur im Grundriss sinnvoll. */}
-
-
-
-
{/* Rechte Gruppe: Layout-Menü · Ressourcen · Projektname. */}
{layoutMenu}