task(CAD-14): phase 3 - V2 select tool on v2 doc (click via bridge hitTest, shift-additive, box-select by center-in-bbox, onChange app channel via ref), bridge onChange wired

This commit is contained in:
Agent Zero
2026-09-16 22:03:15 +02:00
parent 180aa9b03a
commit 1d129042d4
4 changed files with 197 additions and 5 deletions
+6
View File
@@ -46,6 +46,9 @@ const CanvasArea: React.FC<CanvasAreaProps> = ({
/** CAD-14: Frische Prop-Referenz gegen Stale-Closure (Dispatcher/onChanged sind einmalig registriert). */
const onElementCreatedRef = useRef(onElementCreated);
onElementCreatedRef.current = onElementCreated;
/** CAD-14: Frische Prop-Referenz für Selection-Änderungen aus V2-Werkzeugen. */
const onSelectionChangeRef = useRef(onSelectionChange);
onSelectionChangeRef.current = onSelectionChange;
const spatialIndexRef = useRef<SpatialIndex | null>(null);
const layerManagerRef = useRef<LayerManager | null>(null);
const selectionEngineRef = useRef<SelectionEngine | null>(null);
@@ -170,6 +173,9 @@ const CanvasArea: React.FC<CanvasAreaProps> = ({
getIds: () => Array.from(selectionEngine.getSelectedIds()),
setIds: (ids) => selectionEngine.selectByIds(ids, false),
hitTest: (wx, wy, tol) => renderEngine.hitTest(wx, wy, tol),
// CAD-14: App-Kanal fuer V2-Werkzeuge (selectTool) — via Ref gegen
// Stale-Closure, derselbe Kanal wie die Legacy-Engine nutzt.
onChange: (ids) => onSelectionChangeRef.current?.(ids),
}),
});
@@ -1022,15 +1022,99 @@ export const mlineTool: ToolExtensionV2 = {
},
};
let boxStart: Pt | null = null;
export const selectTool: ToolExtensionV2 = {
manifest: {
id: 'select',
label: 'Auswahl',
icon: '\u2196',
ribbonTab: 'canvas',
tags: ['basic'],
description: 'Auswahl: Klick wählt, Drag = Box-Select, Shift additiv (CAD-14: direkt auf V2-Dokument)',
},
optionsSchema: [],
handlers: {
down(e, ctx) {
const c = ctx as FullToolContext;
const sel = c.selection;
const hit = sel?.hitTest(e.world.x, e.world.y, 5) ?? null;
if (hit) {
if (e.shift) {
// additiv: bestehende IDs + neue ID
const current = sel?.getIds() ?? [];
const next = current.includes(hit.id) ? current.filter((id) => id !== hit.id) : [...current, hit.id];
sel?.setIds(next);
} else {
sel?.setIds([hit.id]);
}
sel?.onChange?.(sel?.getIds());
c.setStatus(`Ausgewählt: ${hit.id}`);
boxStart = null;
} else {
if (!e.shift) sel?.setIds([]);
sel?.onChange?.(sel?.getIds() ?? []);
boxStart = e.world;
c.setStatus('Box-Select: aufziehen');
}
},
move(e, ctx) {
if (!boxStart) return;
const c = ctx as FullToolContext;
// Live-Vorschau der Box als gestricheltes Rechteck
ctx.setPreview?.({
id: '__boxselect__', type: 'rect', layerId: '',
x: (boxStart.x + e.world.x) / 2,
y: (boxStart.y + e.world.y) / 2,
width: Math.abs(e.world.x - boxStart.x),
height: Math.abs(e.world.y - boxStart.y),
properties: { stroke: '#00aaff', strokeWidth: 1, visible: true },
} as unknown as CADElement);
},
up(e, ctx) {
const c = ctx as FullToolContext;
if (!boxStart) return;
const start = boxStart;
boxStart = null;
ctx.setPreview?.(null);
// War es ein Drag (Box) oder nur ein Klick?
const dx = Math.abs(e.world.x - start.x);
const dy = Math.abs(e.world.y - start.y);
if (dx < 3 && dy < 3) return; // Klick bereits in down behandelt
const minX = Math.min(start.x, e.world.x);
const maxX = Math.max(start.x, e.world.x);
const minY = Math.min(start.y, e.world.y);
const maxY = Math.max(start.y, e.world.y);
const all = c.doc?.getAllElements() ?? [];
const hits = all.filter((el) => el.x >= minX && el.x <= maxX && el.y >= minY && el.y <= maxY).map((el) => el.id);
const sel = c.selection;
if (e.shift) {
const current = sel?.getIds() ?? [];
const merged = Array.from(new Set([...current, ...hits]));
sel?.setIds(merged);
} else {
sel?.setIds(hits);
}
sel?.onChange?.(sel?.getIds() ?? []);
c.setStatus(`Box-Select: ${hits.length} Element(e)`);
},
cancel(ctx) {
boxStart = null;
ctx.setPreview?.(null);
ctx.setStatus('Auswahl abgebrochen');
},
},
};
export const coreDrawingPlugin: PluginV2 = {
manifest: {
id: 'core-drawing',
name: 'Zeichnen (Kern)',
version: '1.5.0',
version: '1.6.0',
author: 'web-cad team',
description: 'Zeichenwerkzeuge V2: Linie, Rechteck, Kreis, Bogen, Polylinie, Polygon, RevCloud.',
description: 'Zeichenwerkzeuge V2: Linie, Rechteck, Kreis, Bogen, Polylinie, Polygon, RevCloud, Auswahl.',
category: 'tools',
enabledByDefault: true,
},
tools: [lineTool, rectTool, circleTool, arcTool, polylineTool, polygonTool, revcloudTool, ellipseTool, splineTool, pointTool, xlineTool, rayTool, mlineTool],
tools: [selectTool, lineTool, rectTool, circleTool, arcTool, polylineTool, polygonTool, revcloudTool, ellipseTool, splineTool, pointTool, xlineTool, rayTool, mlineTool],
};
+4 -2
View File
@@ -157,10 +157,12 @@ export interface ToolContext {
export interface ToolSelectionBridge {
/** Aktuell selektierte Element-IDs. */
getIds(): string[];
/** Setzt die Selektion vollständig. */
setIds(ids: string[]): void;
/** Setzt die Selektion vollständig (additive = zur bestehenden hinzufügen). */
setIds(ids: string[], additive?: boolean): void;
/** Element-Treffer an Weltposition (oder null). */
hitTest(worldX: number, worldY: number, tolerancePx: number): CADElement | null;
/** CAD-14: Wird bei jeder Selektionsänderung durch V2-Werkzeuge aufgerufen. */
onChange?(ids: string[]): void;
}
/** Erweiterter Kontext für Tools mit Dokument-/Selektionszugriff (A3). */