Zeichnen: getippte Werte live im Cursor-HUD + direktes Zahlen-Tippen
VW-Verhalten vervollständigt: das Cursor-Kästchen spiegelt jetzt den Feld- Status der Befehlszeile (Tab-Feld-Zyklus) — der getippte Wert erscheint SOFORT im aktiven Feld oben am Cursor UND unten im Befehl; gelockte Werte fett/dunkel, das aktive Tab-Ziel als blaue Pille mit weissem Text. Tab wechselt Länge ↔ Winkel (global, auch ohne Fokus), und eine Ziffer (oder . , -) beim Zeichnen startet die Werteingabe direkt (beginTyping fokussiert die Befehlszeile mit dem ersten Zeichen — kein Klick nötig). - PlanView: HudFieldsState + Segment-HUD (aktiv/gelockt/getippt je Feld) - CommandLine: onTextChange (Live-Echo) + Handle beginTyping(seed) - App: cmdTyped-State, hudFields an alle PlanView-Pfade, globaler Ziffern-Handler vor dem Tab-Zyklus 737/737 grün.
This commit is contained in:
+48
-3
@@ -93,6 +93,7 @@ import type {
|
|||||||
PlanSelection,
|
PlanSelection,
|
||||||
MarqueeSelection,
|
MarqueeSelection,
|
||||||
GripHandlers,
|
GripHandlers,
|
||||||
|
HudFieldsState,
|
||||||
} from "./plan/PlanView";
|
} from "./plan/PlanView";
|
||||||
import { Viewport3D } from "./viewport/Viewport3D";
|
import { Viewport3D } from "./viewport/Viewport3D";
|
||||||
import type { DisplayResolver, ViewportContextInfo } from "./viewport/Viewport3D";
|
import type { DisplayResolver, ViewportContextInfo } from "./viewport/Viewport3D";
|
||||||
@@ -1017,6 +1018,21 @@ export default function App() {
|
|||||||
void engineTick;
|
void engineTick;
|
||||||
const engineView = engine.view();
|
const engineView = engine.view();
|
||||||
const commandActive = engineView.active;
|
const commandActive = engineView.active;
|
||||||
|
// Live-Echo des Befehlszeilen-Texts fürs Cursor-HUD: der getippte Wert
|
||||||
|
// erscheint sofort im aktiven Tab-Feld am Cursor (VW-Verhalten).
|
||||||
|
const [cmdTyped, setCmdTyped] = useState("");
|
||||||
|
const hudFields =
|
||||||
|
commandActive && engineView.fields.length > 0
|
||||||
|
? {
|
||||||
|
fields: engineView.fields.map((f) => ({
|
||||||
|
id: f.id,
|
||||||
|
value: f.value,
|
||||||
|
locked: f.locked,
|
||||||
|
active: f.active,
|
||||||
|
})),
|
||||||
|
typed: cmdTyped,
|
||||||
|
}
|
||||||
|
: null;
|
||||||
|
|
||||||
// ── GUI-Werkzeug ↔ Befehls-Engine (ein Eingabe-Hub) ───────────────────────
|
// ── GUI-Werkzeug ↔ Befehls-Engine (ein Eingabe-Hub) ───────────────────────
|
||||||
// Ein Klick auf ein gekoppeltes Zeichen-Werkzeug (Wand/Linie/Rechteck/Polylinie)
|
// Ein Klick auf ein gekoppeltes Zeichen-Werkzeug (Wand/Linie/Rechteck/Polylinie)
|
||||||
@@ -1100,12 +1116,28 @@ export default function App() {
|
|||||||
// (das CommandLine-Input selbst behandelt Tab eigenständig).
|
// (das CommandLine-Input selbst behandelt Tab eigenständig).
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const onKey = (e: KeyboardEvent) => {
|
const onKey = (e: KeyboardEvent) => {
|
||||||
if (e.key !== "Tab") return;
|
|
||||||
const el = e.target as HTMLElement | null;
|
const el = e.target as HTMLElement | null;
|
||||||
|
const inInput =
|
||||||
|
!!el && (el.tagName === "INPUT" || el.tagName === "TEXTAREA" || el.isContentEditable);
|
||||||
|
// Direktes Zahlen-Tippen beim Zeichnen (VW-Verhalten): läuft ein Befehl
|
||||||
|
// mit Tab-Feldern und tippt man eine Ziffer (oder . , -), ohne dass ein
|
||||||
|
// Eingabefeld fokussiert ist, startet das die Werteingabe in der
|
||||||
|
// Befehlszeile — der Wert erscheint live im Cursor-HUD UND unten im Feld.
|
||||||
if (
|
if (
|
||||||
el &&
|
!inInput &&
|
||||||
(el.tagName === "INPUT" || el.tagName === "TEXTAREA" || el.isContentEditable)
|
!e.ctrlKey &&
|
||||||
|
!e.metaKey &&
|
||||||
|
!e.altKey &&
|
||||||
|
/^[0-9.,-]$/.test(e.key) &&
|
||||||
|
engineRef.current?.isActive() &&
|
||||||
|
engineRef.current.hasFields()
|
||||||
) {
|
) {
|
||||||
|
e.preventDefault();
|
||||||
|
commandLineRef.current?.beginTyping(e.key);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (e.key !== "Tab") return;
|
||||||
|
if (inInput) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
@@ -5024,6 +5056,7 @@ export default function App() {
|
|||||||
activeTool={activeTool}
|
activeTool={activeTool}
|
||||||
toolInputActive={toolInputActive}
|
toolInputActive={toolInputActive}
|
||||||
toolHandlers={toolHandlers}
|
toolHandlers={toolHandlers}
|
||||||
|
hudFields={hudFields}
|
||||||
grips={grips}
|
grips={grips}
|
||||||
edgeGrips={edgeGrips}
|
edgeGrips={edgeGrips}
|
||||||
selectedDrawingId={selectedDrawingId}
|
selectedDrawingId={selectedDrawingId}
|
||||||
@@ -5093,6 +5126,7 @@ export default function App() {
|
|||||||
gripEdit ? setGripEdit(null) : activeTransform ? cancelTransform() : engine.cancel()
|
gripEdit ? setGripEdit(null) : activeTransform ? cancelTransform() : engine.cancel()
|
||||||
}
|
}
|
||||||
suggest={(prefix) => engine.suggest(prefix)}
|
suggest={(prefix) => engine.suggest(prefix)}
|
||||||
|
onTextChange={setCmdTyped}
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
@@ -6158,6 +6192,7 @@ function Content({
|
|||||||
activeTool,
|
activeTool,
|
||||||
toolInputActive,
|
toolInputActive,
|
||||||
toolHandlers,
|
toolHandlers,
|
||||||
|
hudFields,
|
||||||
grips,
|
grips,
|
||||||
edgeGrips,
|
edgeGrips,
|
||||||
selectedDrawingId,
|
selectedDrawingId,
|
||||||
@@ -6246,6 +6281,7 @@ function Content({
|
|||||||
activeTool: ToolId;
|
activeTool: ToolId;
|
||||||
toolInputActive: boolean;
|
toolInputActive: boolean;
|
||||||
toolHandlers: ToolHandlers;
|
toolHandlers: ToolHandlers;
|
||||||
|
hudFields: HudFieldsState | null;
|
||||||
grips: Vec2[];
|
grips: Vec2[];
|
||||||
edgeGrips: EdgeGrip[];
|
edgeGrips: EdgeGrip[];
|
||||||
selectedDrawingId: string | null;
|
selectedDrawingId: string | null;
|
||||||
@@ -6345,6 +6381,7 @@ function Content({
|
|||||||
activeTool={activeTool}
|
activeTool={activeTool}
|
||||||
toolInputActive={toolInputActive}
|
toolInputActive={toolInputActive}
|
||||||
toolHandlers={toolHandlers}
|
toolHandlers={toolHandlers}
|
||||||
|
hudFields={hudFields}
|
||||||
grips={grips}
|
grips={grips}
|
||||||
edgeGrips={edgeGrips}
|
edgeGrips={edgeGrips}
|
||||||
selectedDrawingId={selectedDrawingId}
|
selectedDrawingId={selectedDrawingId}
|
||||||
@@ -6401,6 +6438,7 @@ function Content({
|
|||||||
activeTool={activeTool}
|
activeTool={activeTool}
|
||||||
toolInputActive={toolInputActive}
|
toolInputActive={toolInputActive}
|
||||||
toolHandlers={toolHandlers}
|
toolHandlers={toolHandlers}
|
||||||
|
hudFields={hudFields}
|
||||||
grips={grips}
|
grips={grips}
|
||||||
edgeGrips={edgeGrips}
|
edgeGrips={edgeGrips}
|
||||||
selectedDrawingId={selectedDrawingId}
|
selectedDrawingId={selectedDrawingId}
|
||||||
@@ -6497,6 +6535,7 @@ function Content({
|
|||||||
activeTool={activeTool}
|
activeTool={activeTool}
|
||||||
toolInputActive={toolInputActive}
|
toolInputActive={toolInputActive}
|
||||||
toolHandlers={toolHandlers}
|
toolHandlers={toolHandlers}
|
||||||
|
hudFields={hudFields}
|
||||||
grips={grips}
|
grips={grips}
|
||||||
edgeGrips={edgeGrips}
|
edgeGrips={edgeGrips}
|
||||||
selectedDrawingId={selectedDrawingId}
|
selectedDrawingId={selectedDrawingId}
|
||||||
@@ -6543,6 +6582,7 @@ function LevelPlanView({
|
|||||||
activeTool,
|
activeTool,
|
||||||
toolInputActive,
|
toolInputActive,
|
||||||
toolHandlers,
|
toolHandlers,
|
||||||
|
hudFields,
|
||||||
grips,
|
grips,
|
||||||
edgeGrips,
|
edgeGrips,
|
||||||
selectedDrawingId,
|
selectedDrawingId,
|
||||||
@@ -6586,6 +6626,7 @@ function LevelPlanView({
|
|||||||
activeTool: ToolId;
|
activeTool: ToolId;
|
||||||
toolInputActive: boolean;
|
toolInputActive: boolean;
|
||||||
toolHandlers: ToolHandlers;
|
toolHandlers: ToolHandlers;
|
||||||
|
hudFields: HudFieldsState | null;
|
||||||
grips: Vec2[];
|
grips: Vec2[];
|
||||||
edgeGrips: EdgeGrip[];
|
edgeGrips: EdgeGrip[];
|
||||||
selectedDrawingId: string | null;
|
selectedDrawingId: string | null;
|
||||||
@@ -6660,6 +6701,7 @@ function LevelPlanView({
|
|||||||
activeTool={activeTool}
|
activeTool={activeTool}
|
||||||
toolInputActive={toolInputActive}
|
toolInputActive={toolInputActive}
|
||||||
toolHandlers={toolHandlers}
|
toolHandlers={toolHandlers}
|
||||||
|
hudFields={hudFields}
|
||||||
grips={grips}
|
grips={grips}
|
||||||
edgeGrips={edgeGrips}
|
edgeGrips={edgeGrips}
|
||||||
hairline={hairline}
|
hairline={hairline}
|
||||||
@@ -6714,6 +6756,7 @@ function SectionPlanView({
|
|||||||
activeTool,
|
activeTool,
|
||||||
toolInputActive,
|
toolInputActive,
|
||||||
toolHandlers,
|
toolHandlers,
|
||||||
|
hudFields,
|
||||||
grips,
|
grips,
|
||||||
edgeGrips,
|
edgeGrips,
|
||||||
selectedDrawingId,
|
selectedDrawingId,
|
||||||
@@ -6749,6 +6792,7 @@ function SectionPlanView({
|
|||||||
activeTool: ToolId;
|
activeTool: ToolId;
|
||||||
toolInputActive: boolean;
|
toolInputActive: boolean;
|
||||||
toolHandlers: ToolHandlers;
|
toolHandlers: ToolHandlers;
|
||||||
|
hudFields: HudFieldsState | null;
|
||||||
grips: Vec2[];
|
grips: Vec2[];
|
||||||
edgeGrips: EdgeGrip[];
|
edgeGrips: EdgeGrip[];
|
||||||
selectedDrawingId: string | null;
|
selectedDrawingId: string | null;
|
||||||
@@ -6851,6 +6895,7 @@ function SectionPlanView({
|
|||||||
activeTool={activeTool}
|
activeTool={activeTool}
|
||||||
toolInputActive={toolInputActive}
|
toolInputActive={toolInputActive}
|
||||||
toolHandlers={toolHandlers}
|
toolHandlers={toolHandlers}
|
||||||
|
hudFields={hudFields}
|
||||||
grips={grips}
|
grips={grips}
|
||||||
edgeGrips={edgeGrips}
|
edgeGrips={edgeGrips}
|
||||||
hairline={hairline}
|
hairline={hairline}
|
||||||
|
|||||||
+90
-10
@@ -24,6 +24,16 @@ import { buildRandomHatchRuns, zigzagPoints, motifPoints, DASH_MM_TO_M } from ".
|
|||||||
import { dashHasDot } from "../ui/lineSegments";
|
import { dashHasDot } from "../ui/lineSegments";
|
||||||
import { DEFAULT_MARQUEE_COLOR, DEFAULT_SNAP_COLOR } from "../theme/accents";
|
import { DEFAULT_MARQUEE_COLOR, DEFAULT_SNAP_COLOR } from "../theme/accents";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Feld-Status der Befehlszeile fürs Cursor-HUD (Tab-Feld-Zyklus §2.7): Werte je
|
||||||
|
* Feld (gelockt oder live), aktives Tab-Ziel und der gerade getippte Text —
|
||||||
|
* das HUD spiegelt die Eingabe LIVE am Cursor (VW-Verhalten).
|
||||||
|
*/
|
||||||
|
export interface HudFieldsState {
|
||||||
|
fields: { id: string; value: number | null; locked: boolean; active: boolean }[];
|
||||||
|
typed: string;
|
||||||
|
}
|
||||||
|
|
||||||
const PX_PER_M = 90; // viewBox-Einheiten je Meter (Modell → SVG-Benutzerraum)
|
const PX_PER_M = 90; // viewBox-Einheiten je Meter (Modell → SVG-Benutzerraum)
|
||||||
const PAD = 60; // Rand in viewBox-Einheiten
|
const PAD = 60; // Rand in viewBox-Einheiten
|
||||||
|
|
||||||
@@ -349,6 +359,14 @@ export interface PlanViewProps {
|
|||||||
* und zeichnet den zurückgegebenen `draft` als Overlay (Vorschau + Snap-Marker).
|
* und zeichnet den zurückgegebenen `draft` als Overlay (Vorschau + Snap-Marker).
|
||||||
*/
|
*/
|
||||||
toolHandlers?: ToolHandlers;
|
toolHandlers?: ToolHandlers;
|
||||||
|
/**
|
||||||
|
* Feld-Status der Befehlszeile fürs Cursor-HUD (Tab-Feld-Zyklus §2.7):
|
||||||
|
* gelockte/live Werte je Feld, aktives Tab-Ziel und der gerade getippte
|
||||||
|
* Text. Das HUD spiegelt damit die Eingabe LIVE am Cursor (VW-Verhalten:
|
||||||
|
* tippen → Wert erscheint oben im Kästchen UND unten im Befehl); ohne
|
||||||
|
* Felder fällt es auf die draft.hud-Livewerte zurück.
|
||||||
|
*/
|
||||||
|
hudFields?: HudFieldsState | null;
|
||||||
/**
|
/**
|
||||||
* Editier-Griffe (Grips) des aktuell EINZELN selektierten Elements, in Modell-
|
* Editier-Griffe (Grips) des aktuell EINZELN selektierten Elements, in Modell-
|
||||||
* Metern (Wand-Enden, 2D-Vertices/Ecken). Werden als ziehbare Quadrate
|
* Metern (Wand-Enden, 2D-Vertices/Ecken). Werden als ziehbare Quadrate
|
||||||
@@ -437,6 +455,7 @@ export const PlanView = forwardRef<PlanViewHandle, PlanViewProps>(
|
|||||||
activeTool,
|
activeTool,
|
||||||
toolInputActive,
|
toolInputActive,
|
||||||
toolHandlers,
|
toolHandlers,
|
||||||
|
hudFields,
|
||||||
grips,
|
grips,
|
||||||
edgeGrips,
|
edgeGrips,
|
||||||
hairline,
|
hairline,
|
||||||
@@ -2302,6 +2321,7 @@ export const PlanView = forwardRef<PlanViewHandle, PlanViewProps>(
|
|||||||
toScreen={toScreen}
|
toScreen={toScreen}
|
||||||
vbPerPx={1 / (meetScale(view, svgRef.current) ?? 1)}
|
vbPerPx={1 / (meetScale(view, svgRef.current) ?? 1)}
|
||||||
snapColor={snapColor}
|
snapColor={snapColor}
|
||||||
|
hudFields={hudFields ?? null}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</svg>
|
</svg>
|
||||||
@@ -2324,11 +2344,16 @@ function DraftOverlay({
|
|||||||
toScreen,
|
toScreen,
|
||||||
vbPerPx,
|
vbPerPx,
|
||||||
snapColor,
|
snapColor,
|
||||||
|
hudFields = null,
|
||||||
}: {
|
}: {
|
||||||
draft: ToolDraft;
|
draft: ToolDraft;
|
||||||
toScreen: (v: Vec2) => Vec2;
|
toScreen: (v: Vec2) => Vec2;
|
||||||
vbPerPx: number;
|
vbPerPx: number;
|
||||||
snapColor: string;
|
snapColor: string;
|
||||||
|
hudFields?: {
|
||||||
|
fields: { id: string; value: number | null; locked: boolean; active: boolean }[];
|
||||||
|
typed: string;
|
||||||
|
} | null;
|
||||||
}) {
|
}) {
|
||||||
const vertSize = 4.5 * vbPerPx; // halbe Kantenlänge eines Stützpunkt-Quadrats (klein + einheitlich)
|
const vertSize = 4.5 * vbPerPx; // halbe Kantenlänge eines Stützpunkt-Quadrats (klein + einheitlich)
|
||||||
const markSize = 6.5 * vbPerPx; // halbe Kantenlänge des Snap-Markers (grösser = besser sichtbar)
|
const markSize = 6.5 * vbPerPx; // halbe Kantenlänge des Snap-Markers (grösser = besser sichtbar)
|
||||||
@@ -2386,7 +2411,7 @@ function DraftOverlay({
|
|||||||
<SnapMarker snap={draft.snap} toScreen={toScreen} size={markSize} color={snapColor} />
|
<SnapMarker snap={draft.snap} toScreen={toScreen} size={markSize} color={snapColor} />
|
||||||
)}
|
)}
|
||||||
{/* Maß-/Winkel-HUD am Cursor (VW-Stil `L: … W: …`, oder einfacher Text). */}
|
{/* Maß-/Winkel-HUD am Cursor (VW-Stil `L: … W: …`, oder einfacher Text). */}
|
||||||
{draft.hud && <DraftHud hud={draft.hud} toScreen={toScreen} vbPerPx={vbPerPx} />}
|
{draft.hud && <DraftHud hud={draft.hud} toScreen={toScreen} vbPerPx={vbPerPx} hudFields={hudFields} />}
|
||||||
</g>
|
</g>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -2402,39 +2427,94 @@ function DraftHud({
|
|||||||
hud,
|
hud,
|
||||||
toScreen,
|
toScreen,
|
||||||
vbPerPx,
|
vbPerPx,
|
||||||
|
hudFields = null,
|
||||||
}: {
|
}: {
|
||||||
hud: NonNullable<ToolDraft["hud"]>;
|
hud: NonNullable<ToolDraft["hud"]>;
|
||||||
toScreen: (v: Vec2) => Vec2;
|
toScreen: (v: Vec2) => Vec2;
|
||||||
vbPerPx: number;
|
vbPerPx: number;
|
||||||
|
hudFields?: {
|
||||||
|
fields: { id: string; value: number | null; locked: boolean; active: boolean }[];
|
||||||
|
typed: string;
|
||||||
|
} | null;
|
||||||
}) {
|
}) {
|
||||||
const text =
|
// Segmente: mit Befehlszeilen-Feldern (Tab-Zyklus) spiegelt das HUD deren
|
||||||
hud.length != null && hud.angleDeg != null
|
// Zustand — getippter Text erscheint LIVE im aktiven Feld (VW-Verhalten),
|
||||||
? `L: ${hud.length.toFixed(3)}m W: ${hud.angleDeg.toFixed(3)}°`
|
// gelockte Werte fest, ungelockte folgen der Maus (draft.hud-Livewerte als
|
||||||
: hud.text;
|
// Fallback je Feld-Id). Ohne Felder: bisherige L/W- bzw. Text-Anzeige.
|
||||||
if (!text) return null;
|
interface Seg {
|
||||||
|
text: string;
|
||||||
|
active: boolean;
|
||||||
|
locked: boolean;
|
||||||
|
}
|
||||||
|
const liveFor = (id: string): number | null =>
|
||||||
|
id === "length" ? (hud.length ?? null) : id === "angle" ? (hud.angleDeg ?? null) : null;
|
||||||
|
const segs: Seg[] = [];
|
||||||
|
if (hudFields && hudFields.fields.length > 0) {
|
||||||
|
for (const f of hudFields.fields) {
|
||||||
|
const label = f.id === "length" ? "L" : f.id === "angle" ? "W" : f.id;
|
||||||
|
const unit = f.id === "angle" ? "°" : "m";
|
||||||
|
const typedHere = f.active && hudFields.typed.trim() !== "";
|
||||||
|
const value = typedHere
|
||||||
|
? hudFields.typed
|
||||||
|
: f.value != null
|
||||||
|
? f.value.toFixed(3)
|
||||||
|
: (liveFor(f.id)?.toFixed(3) ?? "—");
|
||||||
|
segs.push({ text: `${label}: ${value}${unit}`, active: f.active, locked: f.locked });
|
||||||
|
}
|
||||||
|
} else if (hud.length != null && hud.angleDeg != null) {
|
||||||
|
segs.push({ text: `L: ${hud.length.toFixed(3)}m`, active: false, locked: false });
|
||||||
|
segs.push({ text: `W: ${hud.angleDeg.toFixed(3)}°`, active: false, locked: false });
|
||||||
|
} else if (hud.text) {
|
||||||
|
segs.push({ text: hud.text, active: false, locked: false });
|
||||||
|
}
|
||||||
|
if (segs.length === 0) return null;
|
||||||
|
|
||||||
const p = toScreen(hud.at);
|
const p = toScreen(hud.at);
|
||||||
const fontSize = 12 * vbPerPx;
|
const fontSize = 12 * vbPerPx;
|
||||||
const padX = 6 * vbPerPx;
|
const padX = 6 * vbPerPx;
|
||||||
const padY = 4 * vbPerPx;
|
const padY = 4 * vbPerPx;
|
||||||
|
const gap = 10 * vbPerPx;
|
||||||
const charW = fontSize * 0.62; // Monospace-Schätzung für die Kastenbreite
|
const charW = fontSize * 0.62; // Monospace-Schätzung für die Kastenbreite
|
||||||
const w = text.length * charW + padX * 2;
|
const widths = segs.map((s) => s.text.length * charW);
|
||||||
|
const w = widths.reduce((a, b) => a + b, 0) + gap * (segs.length - 1) + padX * 2;
|
||||||
const h = fontSize + padY * 2;
|
const h = fontSize + padY * 2;
|
||||||
const x = p.x + 14 * vbPerPx;
|
const x = p.x + 14 * vbPerPx;
|
||||||
const y = p.y + 14 * vbPerPx;
|
const y = p.y + 14 * vbPerPx;
|
||||||
|
let cx = x + padX;
|
||||||
return (
|
return (
|
||||||
<g className="plan-hud" pointerEvents="none">
|
<g className="plan-hud" pointerEvents="none">
|
||||||
<rect className="plan-hud-box" x={x} y={y} width={w} height={h} rx={2 * vbPerPx} />
|
<rect className="plan-hud-box" x={x} y={y} width={w} height={h} rx={2 * vbPerPx} />
|
||||||
|
{segs.map((s, i) => {
|
||||||
|
const sx = cx;
|
||||||
|
cx += widths[i] + gap;
|
||||||
|
return (
|
||||||
|
<g key={i}>
|
||||||
|
{s.active && (
|
||||||
|
<rect
|
||||||
|
className="plan-hud-active"
|
||||||
|
x={sx - 2 * vbPerPx}
|
||||||
|
y={y + 1.5 * vbPerPx}
|
||||||
|
width={widths[i] + 4 * vbPerPx}
|
||||||
|
height={h - 3 * vbPerPx}
|
||||||
|
rx={2 * vbPerPx}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
<text
|
<text
|
||||||
className="plan-hud-text"
|
className={
|
||||||
x={x + padX}
|
"plan-hud-text" + (s.active ? " active" : "") + (s.locked ? " locked" : "")
|
||||||
|
}
|
||||||
|
x={sx}
|
||||||
y={y + h / 2}
|
y={y + h / 2}
|
||||||
fontSize={fontSize}
|
fontSize={fontSize}
|
||||||
dominantBaseline="middle"
|
dominantBaseline="middle"
|
||||||
>
|
>
|
||||||
{text}
|
{s.text}
|
||||||
</text>
|
</text>
|
||||||
</g>
|
</g>
|
||||||
);
|
);
|
||||||
|
})}
|
||||||
|
</g>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -2041,6 +2041,18 @@ body {
|
|||||||
font-family: var(--mono, ui-monospace, monospace);
|
font-family: var(--mono, ui-monospace, monospace);
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
}
|
}
|
||||||
|
/* Aktives Tab-Feld im HUD: blaue Pille, weisser Text (VW: Eingabefeld-Optik). */
|
||||||
|
.plan-svg .plan-hud-active {
|
||||||
|
fill: #2864ff;
|
||||||
|
}
|
||||||
|
.plan-svg .plan-hud-text.active {
|
||||||
|
fill: #ffffff;
|
||||||
|
}
|
||||||
|
/* Gelockter (getippter) Wert: dunkler + fett, unterscheidet sich vom Live-Wert. */
|
||||||
|
.plan-svg .plan-hud-text.locked {
|
||||||
|
fill: #143a99;
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
/* Weicher Winkel-Snap: gestrichelte Führungslinie (dezent) + gelbliches
|
/* Weicher Winkel-Snap: gestrichelte Führungslinie (dezent) + gelbliches
|
||||||
Winkel-Badge nahe der Liniemitte. */
|
Winkel-Badge nahe der Liniemitte. */
|
||||||
|
|||||||
+22
-2
@@ -48,6 +48,12 @@ export interface CommandLineProps {
|
|||||||
onCycleField: () => void;
|
onCycleField: () => void;
|
||||||
/** Esc: laufenden Befehl abbrechen. */
|
/** Esc: laufenden Befehl abbrechen. */
|
||||||
onCancel: () => void;
|
onCancel: () => void;
|
||||||
|
/**
|
||||||
|
* Live-Echo des Eingabetexts (bei jeder Änderung, inkl. Leeren nach Submit/
|
||||||
|
* Esc) — speist das Cursor-HUD, das den getippten Wert im aktiven Feld
|
||||||
|
* spiegelt (VW-Verhalten: tippen → Wert erscheint oben UND unten).
|
||||||
|
*/
|
||||||
|
onTextChange?: (text: string) => void;
|
||||||
/** Autocomplete-Kandidaten für ein Präfix (nur im Ruhezustand sinnvoll). */
|
/** Autocomplete-Kandidaten für ein Präfix (nur im Ruhezustand sinnvoll). */
|
||||||
suggest: (prefix: string) => string[];
|
suggest: (prefix: string) => string[];
|
||||||
}
|
}
|
||||||
@@ -55,18 +61,32 @@ export interface CommandLineProps {
|
|||||||
export interface CommandLineHandle {
|
export interface CommandLineHandle {
|
||||||
/** Fokussiert (und öffnet) das Eingabefeld — vom globalen Tab-Handler genutzt. */
|
/** Fokussiert (und öffnet) das Eingabefeld — vom globalen Tab-Handler genutzt. */
|
||||||
focus: () => void;
|
focus: () => void;
|
||||||
|
/**
|
||||||
|
* Startet die Zahleneingabe mit einem ersten Zeichen (globales Tippen beim
|
||||||
|
* Zeichnen, ohne dass das Feld fokussiert war): fokussiert + setzt den Text.
|
||||||
|
*/
|
||||||
|
beginTyping: (seed: string) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const CommandLine = forwardRef<CommandLineHandle, CommandLineProps>(
|
export const CommandLine = forwardRef<CommandLineHandle, CommandLineProps>(
|
||||||
function CommandLine(
|
function CommandLine(
|
||||||
{ active, promptKey, options, fields, onSubmit, onOption, onCycleField, onCancel, suggest },
|
{ active, promptKey, options, fields, onSubmit, onOption, onCycleField, onCancel, suggest, onTextChange },
|
||||||
ref,
|
ref,
|
||||||
) {
|
) {
|
||||||
const [text, setText] = useState("");
|
const [text, setTextRaw] = useState("");
|
||||||
const inputRef = useRef<HTMLInputElement>(null);
|
const inputRef = useRef<HTMLInputElement>(null);
|
||||||
|
// Jede Textänderung (auch programmatisch/Leeren) ans Live-Echo melden.
|
||||||
|
const setText = (v: string) => {
|
||||||
|
setTextRaw(v);
|
||||||
|
onTextChange?.(v);
|
||||||
|
};
|
||||||
|
|
||||||
useImperativeHandle(ref, () => ({
|
useImperativeHandle(ref, () => ({
|
||||||
focus: () => inputRef.current?.focus(),
|
focus: () => inputRef.current?.focus(),
|
||||||
|
beginTyping: (seed: string) => {
|
||||||
|
setText(seed);
|
||||||
|
inputRef.current?.focus();
|
||||||
|
},
|
||||||
}));
|
}));
|
||||||
|
|
||||||
// Autocomplete nur im Ruhezustand (Befehlsname tippen), nicht in einem Schritt.
|
// Autocomplete nur im Ruhezustand (Befehlsname tippen), nicht in einem Schritt.
|
||||||
|
|||||||
Reference in New Issue
Block a user