Zeichenwerkzeug-Zifferntasten: 1 (Text) und 3 (Kreis) fehlten in der

Shortcut-Map, obwohl beide Befehle längst fertig implementiert waren.

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).
This commit is contained in:
2026-07-31 17:00:57 +02:00
parent 1ba6f7bcf4
commit 31fe93c122
3 changed files with 56 additions and 11 deletions
+37 -11
View File
@@ -871,6 +871,19 @@ export default function App() {
void engineTick; void engineTick;
const engineView = engine.view(); const engineView = engine.view();
const commandActive = engineView.active; 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 // Live-Echo des Befehlszeilen-Texts fürs Cursor-HUD: der getippte Wert
// erscheint sofort im aktiven Tab-Feld am Cursor (VW-Verhalten). // erscheint sofort im aktiven Tab-Feld am Cursor (VW-Verhalten).
const [cmdTyped, setCmdTyped] = useState(""); const [cmdTyped, setCmdTyped] = useState("");
@@ -1037,12 +1050,19 @@ export default function App() {
return () => window.removeEventListener("keydown", onKey); return () => window.removeEventListener("keydown", onKey);
}, [undo, redo]); }, [undo, redo]);
// Nummern-Shortcuts für Zeichenwerkzeuge (Vectorworks-Stil): 2 Linie, 4 // Nummern-Shortcuts für Zeichenwerkzeuge (Vectorworks-Stil): 1 Text, 2 Linie,
// Rechteck, 5 Polylinie, 6 Wand, 7 Decke, 8 Fenster, 9 Türe, 0 Raum. Nur auf // 3 Kreis, 4 Rechteck, 5 Polylinie, 6 Wand, 7 Decke, 8 Fenster, 9 Türe, 0 Raum.
// Geschoss-Tabs, nicht beim Tippen in Feldern und nicht mit Modifikator // Nur auf Geschoss-Tabs, nicht mit Modifikator (Ctrl/Cmd/Alt bleiben frei).
// (Ctrl/Cmd/Alt bleiben frei). 1 (Text) und 3 (Kreis) sind bewusst noch // Direkt nach dem Setzen eines Punkts (z. B. „Startpunkt angeben") steht der
// unbelegt — dafür fehlt bislang ein Werkzeug. Der Handler wird einmal // Fokus schon im Befehlsfeld (onSelectTool fokussiert es) — trotzdem soll die
// registriert und liest die aktuelle Auswahl-Funktion/Ebenen-Art über ein Ref. // 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({ const toolShortcutRef = useRef({
onSelectTool, onSelectTool,
floor: activeLevel.kind === "floor", floor: activeLevel.kind === "floor",
@@ -1053,7 +1073,9 @@ export default function App() {
}; };
useEffect(() => { useEffect(() => {
const MAP: Record<string, ToolId> = { const MAP: Record<string, ToolId> = {
"1": "text",
"2": "line", "2": "line",
"3": "circle",
"4": "rect", "4": "rect",
"5": "polyline", "5": "polyline",
"6": "wall", "6": "wall",
@@ -1065,11 +1087,15 @@ export default function App() {
const onKey = (e: KeyboardEvent) => { const onKey = (e: KeyboardEvent) => {
if (e.ctrlKey || e.metaKey || e.altKey) return; if (e.ctrlKey || e.metaKey || e.altKey) return;
const el = e.target as HTMLElement | null; const el = e.target as HTMLElement | null;
if ( const isInput =
el && !!el && (el.tagName === "INPUT" || el.tagName === "TEXTAREA" || el.isContentEditable);
(el.tagName === "INPUT" || el.tagName === "TEXTAREA" || el.isContentEditable) if (isInput) {
) { const isCmdLineInput = el.classList.contains("cmdline-input");
return; 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]; const id = MAP[e.key];
if (!id) return; if (!id) return;
+11
View File
@@ -168,6 +168,17 @@ export class CommandEngine {
return this.currentFields().length > 0; 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 = * Setzt den Feld-Zustand zurück, wenn der Schritt wechselt — Signatur =
* phase + Feld-IDs + lastPoint. Der lastPoint-Teil sorgt dafür, dass ein * phase + Feld-IDs + lastPoint. Der lastPoint-Teil sorgt dafür, dass ein
+8
View File
@@ -66,6 +66,13 @@ export interface CommandLineHandle {
* Zeichnen, ohne dass das Feld fokussiert war): fokussiert + setzt den Text. * Zeichnen, ohne dass das Feld fokussiert war): fokussiert + setzt den Text.
*/ */
beginTyping: (seed: string) => void; 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<CommandLineHandle, CommandLineProps>( export const CommandLine = forwardRef<CommandLineHandle, CommandLineProps>(
@@ -87,6 +94,7 @@ export const CommandLine = forwardRef<CommandLineHandle, CommandLineProps>(
setText(seed); setText(seed);
inputRef.current?.focus(); inputRef.current?.focus();
}, },
blur: () => inputRef.current?.blur(),
})); }));
// Autocomplete nur im Ruhezustand (Befehlsname tippen), nicht in einem Schritt. // Autocomplete nur im Ruhezustand (Befehlsname tippen), nicht in einem Schritt.