Gruppieren + Gruppe betreten/verlassen (Vectorworks-artig)

Ctrl/Cmd+G fasst die Auswahl zu einer Gruppe zusammen (Elemente + bereits
gewählte Gruppen werden dabei verschachtelt statt vermischt); Ctrl/Cmd+
Shift+G löst eine Gruppenhülle auf (ein Level, Untergruppen bleiben
bestehen). Eine Gruppe ist reine Mitgliedschaft (IDs je Elementart) ohne
eigenes Transform-Objekt — Verschieben/Kopieren/Löschen/Feld brauchen
dadurch keine Sonderbehandlung, eine Gruppen-Auswahl ist einfach eine
grosse Mehrfachauswahl.

Klick auf ein gruppiertes Element wählt die ganze Gruppe (Einzelklick UND
Auswahlrahmen); Doppelklick betritt sie (Isolation): alles ausserhalb wird
gedimmt und lässt sich nicht mehr anwählen, ein Knopf oben links verlässt
sie wieder. Verschachtelte Gruppen: innerhalb einer betretenen Gruppe sind
nur ihre direkten Untergruppen als Gruppe anwählbar, ihre direkten
Mitglieder einzeln editierbar.
This commit is contained in:
2026-08-22 11:17:24 +02:00
parent 19beb8e632
commit 91ed2b2454
13 changed files with 940 additions and 19 deletions
+52
View File
@@ -20,6 +20,8 @@ import type { CommandLineHandle } from "../ui/CommandLine";
import type { Drawing2D, Project, Vec2 } from "../model/types";
import { differenceDrawings, intersectionDrawings, unionDrawings } from "../editors/booleanOps";
import type { BooleanResult } from "../editors/booleanOps";
import { groupSelection, mergeMembership, ungroup, EMPTY_MEMBERSHIP } from "./groupActions";
import type { GroupMembership } from "./groupActions";
/** Aktive Transformation (Bewegen/Spiegeln/Drehen) — Form aus useDrawingToolState. */
type ActiveTransform = {
@@ -570,3 +572,53 @@ export function useTransformShortcuts(
engine,
]);
}
/**
* Ctrl/Cmd+G = Auswahl gruppieren (Vectorworks-artig); Ctrl/Cmd+Shift+G =
* gewählte Gruppe(n) auflösen (EIN Level — Untergruppen bleiben bestehen).
* `applySelection` setzt danach alle betroffenen `selected…Ids`-Kanäle +
* `selectedGroupIds` auf einen Schlag (s. App.tsx, dieselbe Form wie die
* Gruppen-Erweiterung beim Klicken in `useMouseSelectionHandlers.ts`).
*/
export function useGroupShortcut(
project: Project,
setProject: (updater: (p: Project) => Project) => void,
currentSelection: GroupMembership,
selectedGroupIds: string[],
applySelection: (mem: GroupMembership, groupIds: string[]) => void,
): void {
useEffect(() => {
const onKey = (e: KeyboardEvent) => {
if (!(e.ctrlKey || e.metaKey)) return;
if (e.key.toLowerCase() !== "g") return;
if (isEditableTarget(e)) return;
e.preventDefault();
if (e.shiftKey) {
// Ungruppieren: für jede gewählte Gruppe eine Hülle auflösen; ihre
// direkten Mitglieder (+ Untergruppen) werden die neue Auswahl.
if (selectedGroupIds.length === 0) return;
let p = project;
let members: GroupMembership = { ...EMPTY_MEMBERSHIP };
const newGroupIds: string[] = [];
for (const gid of selectedGroupIds) {
const res = ungroup(p, gid);
if (!res) continue;
p = res.project;
members = mergeMembership(members, res.members);
newGroupIds.push(...res.subGroupIds);
}
setProject(() => p);
applySelection(members, newGroupIds);
return;
}
// Gruppieren: aktuelle Auswahl (+ bereits gewählte Gruppen als Untergruppen).
const res = groupSelection(project, currentSelection, selectedGroupIds);
if (!res) return;
setProject(() => res.project);
applySelection(currentSelection, [res.groupId]);
};
window.addEventListener("keydown", onKey);
return () => window.removeEventListener("keydown", onKey);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [project, currentSelection, selectedGroupIds]);
}