task(C3fin): selection handles and snap marks in pixi renderer
This commit is contained in:
@@ -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<string, string> = {
|
||||
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 {
|
||||
|
||||
@@ -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[];
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user