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 {
|
setSelection(sel: SelectionState): void {
|
||||||
// Selektionshandles in C3
|
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 {
|
setSnapPoints(pts: Array<{ x: number; y: number; type: string }>): void {
|
||||||
// Snap-Markierungen in C3
|
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 {
|
overlay(draw: (g: OverlayPainter) => void): void {
|
||||||
|
|||||||
@@ -64,6 +64,8 @@ export interface OverlayPainter {
|
|||||||
/** Selektionszustand für die Anzeige von Handles. */
|
/** Selektionszustand für die Anzeige von Handles. */
|
||||||
export interface SelectionState {
|
export interface SelectionState {
|
||||||
selectedIds: string[];
|
selectedIds: string[];
|
||||||
|
/** Task C3-Finish: Elemente für Handle-Zeichnung (Renderer braucht BBox). */
|
||||||
|
elements?: CADElement[];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
Reference in New Issue
Block a user