diff --git a/frontend/src/render/pixi/PixiRenderer.ts b/frontend/src/render/pixi/PixiRenderer.ts index e0e2e35..308a384 100644 --- a/frontend/src/render/pixi/PixiRenderer.ts +++ b/frontend/src/render/pixi/PixiRenderer.ts @@ -217,12 +217,113 @@ export class PixiRenderer implements RenderAdapter { } } - setSelection(_sel: SelectionState): void { - // Selektionshandles in C3 + setSelection(sel: SelectionState): void { + if (!this.overlayLayer) return; + // Bestehende Selektions-Grafik entfernen: + for (const child of [...this.overlayLayer.children]) { + if ((child as Container).label === '__selection__') { + try { + child.destroy({ children: true }); + } catch { + // teardown-safe + } + } + } + if (!sel.selectedIds.length || !sel.elements || !this.pixiModule) return; + + const GraphicsCtor = this.pixiModule.Graphics; + const g = new GraphicsCtor(); + const HANDLE = 4; // halbe Kantenlänge der Eck-Handles (in Weltkoordinaten) + + for (const el of sel.elements) { + if (!sel.selectedIds.includes(el.id)) continue; + // Legacy-Konvention getElementBBox: x/y = Zentrum für rects, sonst BBox + let minX: number, minY: number, maxX: number, maxY: number; + if (el.type === 'rect' || el.type === 'stage') { + minX = el.x - el.width / 2; + minY = el.y - el.height / 2; + maxX = el.x + el.width / 2; + maxY = el.y + el.height / 2; + } else if ( + el.properties && + el.properties.x1 !== undefined && el.properties.y1 !== undefined && + el.properties.x2 !== undefined && el.properties.y2 !== undefined + ) { + minX = Math.min(el.properties.x1 as number, el.properties.x2 as number); + minY = Math.min(el.properties.y1 as number, el.properties.y2 as number); + maxX = Math.max(el.properties.x1 as number, el.properties.x2 as number); + maxY = Math.max(el.properties.y1 as number, el.properties.y2 as number); + } else { + minX = el.x - el.width / 2; + minY = el.y - el.height / 2; + maxX = el.x + el.width / 2; + maxY = el.y + el.height / 2; + } + + // Umrandung (gestrichelt wird via Linien-Segmenten simuliert): + const dashLen = 6; + const drawDashed = (ax: number, ay: number, bx: number, by: number) => { + const len = Math.hypot(bx - ax, by - ay); + const n = Math.max(1, Math.floor(len / dashLen)); + for (let i = 0; i <= n; i += 2) { + const t0 = i / n; + const t1 = Math.min(1, (i + 1) / n); + g.moveTo(ax + (bx - ax) * t0, ay + (by - ay) * t0) + .lineTo(ax + (bx - ax) * t1, ay + (by - ay) * t1) + .stroke({ color: '#50fa7b', width: 1 / this.viewport.scale }); + } + }; + drawDashed(minX, minY, maxX, minY); + drawDashed(maxX, minY, maxX, maxY); + drawDashed(maxX, maxY, minX, maxY); + drawDashed(minX, maxY, minX, minY); + + // 4 Eck-Handles (gefüllte Quadrate): + const corners = [ + { x: minX, y: minY }, { x: maxX, y: minY }, + { x: maxX, y: maxY }, { x: minX, y: maxY }, + ]; + for (const c of corners) { + g.rect(c.x - HANDLE, c.y - HANDLE, HANDLE * 2, HANDLE * 2) + .fill({ color: '#50fa7b' }) + .stroke({ color: '#1e1e2e', width: 1 / this.viewport.scale }); + } + } + g.label = '__selection__'; + this.overlayLayer.addChild(g); } - setSnapPoints(_pts: Array<{ x: number; y: number; type: string }>): void { - // Snap-Markierungen in C3 + setSnapPoints(pts: Array<{ x: number; y: number; type: string }>): void { + if (!this.overlayLayer || !this.pixiModule || pts.length === 0) return; + const GraphicsCtor = this.pixiModule.Graphics; + const g = new GraphicsCtor(); + g.label = '__snaps__'; + // Bestehende Snap-Marks entfernen: + for (const child of [...this.overlayLayer.children]) { + if ((child as Container).label === '__snaps__') { + try { + child.destroy({ children: true }); + } catch { + // teardown-safe + } + } + } + // Snap-Punkt farbig nach Typ (Legacy-Ähnlich): + const colorByType: Record = { + endpoint: '#ff79c6', + midpoint: '#8be9fd', + center: '#f1fa8c', + intersection: '#ffb86c', + grid: '#6272a4', + }; + const scaleComp = 1 / this.viewport.scale; // konstante Bildschirmgröße + for (const pt of pts) { + const color = colorByType[pt.type] ?? '#bd93f9'; + g.circle(pt.x, pt.y, 4 * scaleComp) + .fill({ color }) + .stroke({ color: '#1e1e2e', width: 1 * scaleComp }); + } + this.overlayLayer.addChild(g); } overlay(draw: (g: OverlayPainter) => void): void { diff --git a/frontend/src/render/types.ts b/frontend/src/render/types.ts index 905dbd5..cc29508 100644 --- a/frontend/src/render/types.ts +++ b/frontend/src/render/types.ts @@ -64,6 +64,8 @@ export interface OverlayPainter { /** Selektionszustand für die Anzeige von Handles. */ export interface SelectionState { selectedIds: string[]; + /** Task C3-Finish: Elemente für Handle-Zeichnung (Renderer braucht BBox). */ + elements?: CADElement[]; } /**