import { Container, type FederatedPointerEvent } from 'pixi.js'; import { ANNOTATION_KINDS, annotationsOf, type Graph } from '../../core/graph'; import { AnnotationView } from './AnnotationView'; /** The groups and comments of a graph, behind its edges: a group is a backdrop, and a comment must never cover a node. */ export class AnnotationLayer extends Container { private readonly byId = new Map(); constructor(private readonly onPress: (view: AnnotationView, e: FederatedPointerEvent) => void) { super(); } get views(): ReadonlyMap { return this.byId; } clearViews(): void { for (const view of this.byId.values()) view.destroy({ children: true }); this.byId.clear(); } /** Views are reused by id, and a box that is gone takes its view with it. */ sync(graph: Graph): void { const seen = new Set(); for (const kind of ANNOTATION_KINDS) { for (const annotation of annotationsOf(graph, kind)) { seen.add(annotation.id); let view = this.byId.get(annotation.id); if (!view || view.kind !== kind) { view?.destroy({ children: true }); view = new AnnotationView(annotation, kind); const pressed = view; view.on('pointerdown', (e: FederatedPointerEvent) => this.onPress(pressed, e)); this.byId.set(annotation.id, view); this.addChild(view); } view.sync(annotation); } } for (const [id, view] of this.byId) { if (!seen.has(id)) { view.destroy({ children: true }); this.byId.delete(id); } } } redrawViews(selected: ReadonlySet): void { for (const view of this.byId.values()) { view.selected = selected.has(view.annotation.id); view.redraw(); } } }