From 327b7d314809c805c113f1a8369aa0ac64a121ce Mon Sep 17 00:00:00 2001 From: Karim Date: Fri, 31 Jul 2026 17:00:57 +0200 Subject: [PATCH] =?UTF-8?q?Zeichenwerkzeug-Zifferntasten:=201=20(Text)=20u?= =?UTF-8?q?nd=203=20(Kreis)=20fehlten=20in=20der=20Shortcut-Map,=20obwohl?= =?UTF-8?q?=20beide=20Befehle=20l=C3=A4ngst=20fertig=20implementiert=20war?= =?UTF-8?q?en.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Zusätzlich musste man nach jedem gesetzten Punkt Esc drücken, bevor eine Zifferntaste das nächste Werkzeug wählte — der Fokus blieb im Befehlsfeld hängen (dort wird nach Abschluss nicht mehr geblurred) und die globale Kürzel-Prüfung ignorierte jede Eingabe im Feld pauschal. Jetzt gibt die Engine den Fokus frei, sobald ein Befehl fertig ist (Commit/Abbruch), und eine Zifferntaste wählt direkt das nächste Werkzeug (Vectorworks-Stil) — aber nur, wenn das Feld leer ist UND der aktuelle Schritt weder Tab-Felder noch Freitext erwartet (Textlabel-Eingabe wie „Raum 101" bleibt geschützt). --- src/App.tsx | 48 ++++++++++++++++++++++++++++++++---------- src/commands/engine.ts | 11 ++++++++++ src/ui/CommandLine.tsx | 8 +++++++ 3 files changed, 56 insertions(+), 11 deletions(-) diff --git a/src/App.tsx b/src/App.tsx index 432fed6..13a97a4 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -871,6 +871,19 @@ export default function App() { void engineTick; const engineView = engine.view(); const commandActive = engineView.active; + // Fokus wieder freigeben, sobald ein Befehl fertig ist (Commit ODER Abbruch): + // beim Start wird das Befehlsfeld fokussiert (onSelectTool), bliebe es das + // auch danach, würde die nächste Zifferntaste im (inaktiven) Feld landen + // statt beim globalen Werkzeug-Kürzel anzukommen — Esc wäre nötig. Mit dem + // Blur bei aktiv→inaktiv wählt man wie in Vectorworks sofort das nächste + // Werkzeug per Zifferntaste, ohne dazwischen Esc zu drücken. + const prevCommandActiveRef = useRef(commandActive); + useEffect(() => { + if (prevCommandActiveRef.current && !commandActive) { + commandLineRef.current?.blur(); + } + prevCommandActiveRef.current = commandActive; + }, [commandActive]); // 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(""); @@ -1037,12 +1050,19 @@ export default function App() { return () => window.removeEventListener("keydown", onKey); }, [undo, redo]); - // Nummern-Shortcuts für Zeichenwerkzeuge (Vectorworks-Stil): 2 Linie, 4 - // Rechteck, 5 Polylinie, 6 Wand, 7 Decke, 8 Fenster, 9 Türe, 0 Raum. Nur auf - // Geschoss-Tabs, nicht beim Tippen in Feldern und nicht mit Modifikator - // (Ctrl/Cmd/Alt bleiben frei). 1 (Text) und 3 (Kreis) sind bewusst noch - // unbelegt — dafür fehlt bislang ein Werkzeug. Der Handler wird einmal - // registriert und liest die aktuelle Auswahl-Funktion/Ebenen-Art über ein Ref. + // Nummern-Shortcuts für Zeichenwerkzeuge (Vectorworks-Stil): 1 Text, 2 Linie, + // 3 Kreis, 4 Rechteck, 5 Polylinie, 6 Wand, 7 Decke, 8 Fenster, 9 Türe, 0 Raum. + // Nur auf Geschoss-Tabs, nicht mit Modifikator (Ctrl/Cmd/Alt bleiben frei). + // Direkt nach dem Setzen eines Punkts (z. B. „Startpunkt angeben") steht der + // Fokus schon im Befehlsfeld (onSelectTool fokussiert es) — trotzdem soll die + // nächste Zifferntaste OHNE Esc gleich das nächste Werkzeug wählen, wie in + // Vectorworks. Das ist nur sicher, wenn dort gerade nichts Sinnvolles getippt + // werden könnte: das Feld ist noch leer UND der Schritt hat weder Tab-Felder + // (Länge/Winkel/Radius, hasFields()) noch erwartet er Freitext (Text-Label, + // acceptsFreeText()) — sonst wäre die Ziffer selbst Nutzinhalt (z. B. „Raum + // 101" oder eine getippte Koordinate) und darf nicht als Werkzeugwechsel + // verschluckt werden. Der Handler wird einmal registriert und liest die + // aktuelle Auswahl-Funktion/Ebenen-Art über ein Ref. const toolShortcutRef = useRef({ onSelectTool, floor: activeLevel.kind === "floor", @@ -1053,7 +1073,9 @@ export default function App() { }; useEffect(() => { const MAP: Record = { + "1": "text", "2": "line", + "3": "circle", "4": "rect", "5": "polyline", "6": "wall", @@ -1065,11 +1087,15 @@ export default function App() { const onKey = (e: KeyboardEvent) => { if (e.ctrlKey || e.metaKey || e.altKey) return; const el = e.target as HTMLElement | null; - if ( - el && - (el.tagName === "INPUT" || el.tagName === "TEXTAREA" || el.isContentEditable) - ) { - return; + const isInput = + !!el && (el.tagName === "INPUT" || el.tagName === "TEXTAREA" || el.isContentEditable); + if (isInput) { + const isCmdLineInput = el.classList.contains("cmdline-input"); + const empty = isCmdLineInput && (el as HTMLInputElement).value === ""; + const eng = engineRef.current; + const cmdLineFreeToSwitch = + empty && !eng?.hasFields() && !eng?.acceptsFreeText(); + if (!cmdLineFreeToSwitch) return; } const id = MAP[e.key]; if (!id) return; diff --git a/src/commands/engine.ts b/src/commands/engine.ts index b24633f..8a00bfb 100644 --- a/src/commands/engine.ts +++ b/src/commands/engine.ts @@ -168,6 +168,17 @@ export class CommandEngine { return this.currentFields().length > 0; } + /** + * Erwartet der aktuelle Schritt freien Text (z. B. das Textwerkzeug-Label)? + * Dort ist JEDE Taste — auch Ziffern — literaler Inhalt, im Unterschied zu + * einem reinen Punkt-Pick-Schritt (nur „point"/„number", keine Felder), wo + * Ziffern ansonsten ungenutzt sind. + */ + acceptsFreeText(): boolean { + if (!this.command || !this.state) return false; + return this.command.accepts(this.state).includes("text"); + } + /** * Setzt den Feld-Zustand zurück, wenn der Schritt wechselt — Signatur = * phase + Feld-IDs + lastPoint. Der lastPoint-Teil sorgt dafür, dass ein diff --git a/src/ui/CommandLine.tsx b/src/ui/CommandLine.tsx index 8ae19c8..1991d20 100644 --- a/src/ui/CommandLine.tsx +++ b/src/ui/CommandLine.tsx @@ -66,6 +66,13 @@ export interface CommandLineHandle { * Zeichnen, ohne dass das Feld fokussiert war): fokussiert + setzt den Text. */ beginTyping: (seed: string) => void; + /** + * Gibt den Eingabefokus frei (z. B. wenn ein Befehl fertig ist), damit die + * nächste Zifferntaste sofort wieder das nächste Werkzeug wählt statt im + * Eingabefeld zu landen (Vectorworks-Verhalten: kein Esc nötig zwischen zwei + * Werkzeugen). + */ + blur: () => void; } export const CommandLine = forwardRef( @@ -87,6 +94,7 @@ export const CommandLine = forwardRef( setText(seed); inputRef.current?.focus(); }, + blur: () => inputRef.current?.blur(), })); // Autocomplete nur im Ruhezustand (Befehlsname tippen), nicht in einem Schritt.