ayitinya Posted October 10, 2023 Share Posted October 10, 2023 I'm using PIXI js with vue 3 (sample code below) Because most of the graphics follow a similar pattern with different behaviours and props, we chose to go the OOP route with typescript to avoid code duplication But doing it this way, or we can't tell what is wrong leads to errors like `cannot find propagation path to disconnected target` details are provided as comments in the code before delving in, here is a summary when the DisplayObject is created as a property of a class, or overriden from a parent class event listeners do not work, the error cannot find propagation path to disconnected target is encountered changing the properties of the display object also do not cause it to rerender App.vue (It's a single page application, no router shenanigans) const main = ref<HTMLElement | null>(null) let visualisationEngine: VisualisationEngine onMounted(() => { if (main.value) { visualisationEngine = new VisualisationEngine(main.value!!, window) visualisationEngine.init() } }) VisualisationEngine.ts import { Application, Graphics } from 'pixi.js' import '@pixi/unsafe-eval' // usage here because it's an electron app import { onEscKeyDown, onPointerDown, onPointerMove, onPointerUp } from '@/visualisation_engine/eventHandlers' import { Viewport } from 'pixi-viewport' interface GlobalThis { __PIXI_APP__: Application<HTMLCanvasElement> } export class VisualisationEngine { app: Application<HTMLCanvasElement> elem: HTMLElement window: Window viewport: Viewport constructor(elem: HTMLElement, window: Window) { this.window = window this.app = new Application<HTMLCanvasElement>({ antialias: true, autoDensity: true, resizeTo: elem, hello: true }) this.viewport = new Viewport({ ... }) // this.viewport.drag().pinch().wheel().decelerate() this.elem = elem ;(globalThis as any as GlobalThis).__PIXI_APP__ = this.app //for debugging w/ pixi devtools } init() { this.render() const background = this.drawBackground() // event listeners on app.stage didn't run an so this was done as a hack this.startEventHandlers(background) } render() { this.elem.appendChild(this.app.view) this.app.stage.addChild(this.viewport) } drawBackground() { // using background as a property of the class caused eventListeners to not be fired properly, actually at all // error regarding propagation occurs const background = new Graphics() background.beginFill(0x000000) background.drawRect(0, 0, this.elem.clientWidth, this.elem.clientHeight) background.endFill() background.eventMode = 'static' // this.app.stage.addChild(background) this.viewport.addChild(background) return background } startEventHandlers(graphic: Graphics) { document.addEventListener('keydown', (event) => { if (event.key === 'Escape') { onEscKeyDown() } }) graphic .on('pointerdown', (event) => { onPointerDown(event, (elem) => this.viewport.addChild(elem)) }) .on('pointermove', (event) => { onPointerMove(event, (elem) => this.viewport.addChild(elem)) }) .on('pointerup', (event) => onPointerUp(event)) } } eventListeners.ts export const onPointerDown = (event: FederatedPointerEvent, callback: (elem: Graphics) => void) => { const mainStore = useMainStore() if (mainStore.elementToAdd !== null) { mainStore.elementToAdd.position.set(Math.round(event.globalX), Math.round(event.globalY)) const elem = mainStore.elementToAdd.draw() // /* draw draws out the graphics and returns it, this was another hack as returning the whole container caused the events launched from the graphics object to error */ if (callback) { callback(elem) } mainStore.onScreenElements.push(mainStore.elementToAdd) mainStore.elementToAdd = null return } ... mainStore.currentlySelectedElement = null } export const onPointerUp = (event: FederatedPointerEvent) => { const mainStore = useMainStore() if (useMainStore().draggingElement !== null) { useMainStore().draggingElement = null } } export const onPointerMove = (event: FederatedPointerEvent, callback: (elem: Graphics) => void) => { if (useMainStore().draggingElement !== null) { useMainStore().draggingElement!!.move(event.globalX, event.globalY) // nothing is updated on screen console.log(useMainStore().draggingElement!!.position) // but the x, y of the container changes /* trying to call element.parent yields null, and errors because it has lost reference to it's parent */ } } BaseElement.ts import { defineStore } from 'pinia' import { Container, Graphics } from 'pixi.js' import { useMainStore } from '@/store/main' import { ref } from 'vue' export abstract class BaseElement extends Container { useStore nodes: number[] abstract readonly type: string id: number = Math.floor(Math.random() * 1000000) readonly store = defineStore(`${this.constructor.name}-${this.id}`, () => { const name = ref(this.name) const value = ref<{ [key: string]: { [key: string]: string } }>({}) return { name, value } }) protected constructor(x: number = 0, y: number = 0, name: string, nodes: number[]) { super() this.nodes = nodes this.x = x this.y = y this.useStore = this.store() this.useStore.$patch({ name: name }) } abstract draw(): Graphics initializeEventListeners(graphic: Graphics) { // this function is called in the draw method and the graphic object passed as argument graphic.hitArea = graphic.getBounds() graphic.eventMode = 'static' graphic.on( 'pointerdown', () => { if (useMainStore().currentAction === 'move') { useMainStore().draggingElement = this } else { useMainStore().currentlySelectedElement = this } }, this ) graphic.on('pointerup', () => { console.log('pointer up') }) } move(x: number, y: number) { this.x = x this.y = y } } a sample element import { BaseElement } from './BaseElement' import { Graphics, Point } from 'pixi.js' export class Resistor extends BaseElement { override type = 'Resistor' constructor(x: number, y: number, nodes: [number, number], name: string = 'R', resistance: number) { super(x, y, name, nodes) } override draw() { // plan was to make all graphics properties of the parent class, but doing so just prevent all events from being triggerd const resistorGraphic = new Graphics() const body = new Graphics() resistorGraphic .lineStyle(DIMENSIONS.CONDUCTOR_WIDTH, COLORS.CONDUCTOR) .moveTo(this.x, this.y) ... body.addChild(resistorGraphic) this.addConductor().forEach((conductor) => { body.addChild(conductor) }) this.initializeEventListeners(resistorGraphic) return body } } How are the elements added to the canvas The user is required to select from several elements. On selecting, a new object is created with the selected element and added to the global pinia store instance like mainStore.setElementToAdd(new Resistor(...)) setElementToAdd(element: BaseElement) { ... state.elementToAdd = element ... } Quote Link to comment Share on other sites More sharing options...
Recommended Posts
Join the conversation
You can post now and register later. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.