From c81dd8c24a2739cde22fa3def640e9c196e13686 Mon Sep 17 00:00:00 2001 From: Michael Mainguy Date: Tue, 23 Apr 2024 10:38:29 -0500 Subject: [PATCH] Added Diagram Menu Manager. --- src/controllers/base.ts | 3 +- src/diagram/diagramManager.ts | 88 ++++++++++++------------------- src/diagram/diagramMenuManager.ts | 80 ++++++++++++++++++++++++++++ src/menus/clickMenu.ts | 36 +++++++------ 4 files changed, 136 insertions(+), 71 deletions(-) create mode 100644 src/diagram/diagramMenuManager.ts diff --git a/src/controllers/base.ts b/src/controllers/base.ts index 288794b..cf487d4 100644 --- a/src/controllers/base.ts +++ b/src/controllers/base.ts @@ -246,7 +246,6 @@ export class Base { private click() { let mesh = this.xr.pointerSelection.getMeshUnderPointer(this.xrInputSource.uniqueId); - if (pointable(mesh)) { logger.debug("click on " + mesh.id); if (this.clickMenu && !this.clickMenu.isDisposed) { @@ -255,7 +254,7 @@ export class Base { this.clickMenu = null; } } else { - this.clickMenu = new ClickMenu(mesh, this.diagramManager, this.xrInputSource.grip); + this.clickMenu = this.diagramManager.diagramMenuManager.createClickMenu(mesh, this.xrInputSource.grip); } } else { diff --git a/src/diagram/diagramManager.ts b/src/diagram/diagramManager.ts index d4048f8..e77cec5 100644 --- a/src/diagram/diagramManager.ts +++ b/src/diagram/diagramManager.ts @@ -1,9 +1,8 @@ -import {AbstractMesh, ActionManager, InstancedMesh, Mesh, Observable, Scene} from "@babylonjs/core"; +import {AbstractMesh, ActionManager, InstancedMesh, Mesh, Observable, Scene, TransformNode} from "@babylonjs/core"; import {DiagramEvent, DiagramEventType} from "./types/diagramEntity"; import log from "loglevel"; import {Controllers} from "../controllers/controllers"; import {AppConfig} from "../util/appConfig"; -import {Toolbox} from "../toolbox/toolbox"; import {diagramEventHandler} from "./functions/diagramEventHandler"; import {deepCopy} from "../util/functions/deepCopy"; import {applyPhysics} from "./functions/diagramShapePhysics"; @@ -12,10 +11,11 @@ import {toDiagramEntity} from "./functions/toDiagramEntity"; import {v4 as uuidv4} from 'uuid'; import {buildEntityActionManager} from "./functions/buildEntityActionManager"; import {isDiagramEntity} from "./functions/isDiagramEntity"; -import {InputTextView} from "../information/inputTextView"; import {DefaultScene} from "../defaultScene"; -import {ScaleMenu} from "../menus/scaleMenu"; +import {DiagramMenuManager} from "./diagramMenuManager"; +import {ClickMenu} from "../menus/clickMenu"; +const logger = log.getLogger('DiagramManager'); export enum DiagramEventObserverMask { ALL = -1, FROM_DB = 1, @@ -24,73 +24,41 @@ export enum DiagramEventObserverMask { export class DiagramManager { public readonly _config: AppConfig; private readonly _controllers: Controllers; - private readonly diagramEntityActionManager: ActionManager; - private readonly inputTextView: InputTextView; - public readonly scaleMenu: ScaleMenu; + private readonly _diagramEntityActionManager: ActionManager; public readonly onDiagramEventObservable: Observable = new Observable(); - private readonly logger = log.getLogger('DiagramManager'); - private readonly toolbox: Toolbox; + private readonly _diagramMenuManager: DiagramMenuManager; private readonly _scene: Scene; - constructor() { this._scene = DefaultScene.Scene; this._config = new AppConfig(); this._controllers = new Controllers(); - this.inputTextView = new InputTextView(this._controllers); - this.inputTextView.onTextObservable.add((evt) => { - const mesh = this._scene.getMeshById(evt.id); - if (mesh) { - const entity = toDiagramEntity(mesh); - entity.text = evt.text; - this.notifyAll({type: DiagramEventType.MODIFY, entity: entity}); - } else { - this.logger.error("mesh not found", evt.id); - } - }); - - this.toolbox = new Toolbox(); - this.scaleMenu = new ScaleMenu(); - this.scaleMenu.onScaleChangeObservable.add((mesh: AbstractMesh) => { - this.notifyAll({type: DiagramEventType.MODIFY, entity: toDiagramEntity(mesh)}); - const position = mesh.absolutePosition.clone(); - position.y = mesh.getBoundingInfo().boundingBox.maximumWorld.y + .1; - this.scaleMenu.changePosition(position); - }); - this.diagramEntityActionManager = buildEntityActionManager(this._controllers); + this._diagramMenuManager = new DiagramMenuManager(this.onDiagramEventObservable, this._controllers); + this._diagramEntityActionManager = buildEntityActionManager(this._controllers); this.onDiagramEventObservable.add(this.onDiagramEvent, DiagramEventObserverMask.FROM_DB, true, this); - this.logger.debug("DiagramManager constructed"); - + logger.debug("DiagramManager constructed"); this._scene.onMeshRemovedObservable.add((mesh) => { if (isDiagramEntity(mesh)) { - if (mesh.metadata.template != '#connection-template') { - this._scene.meshes.forEach((m) => { - if (m?.metadata?.to == mesh.id || m?.metadata?.from == mesh.id) { - this.logger.debug("removing connection", m.id); - this.notifyAll({type: DiagramEventType.REMOVE, entity: toDiagramEntity(m)}); - } - }); - } + this.cleanupOrphanConnections(mesh) } }); } + public get diagramMenuManager(): DiagramMenuManager { + return this._diagramMenuManager; + } + + public createClickMenu(mesh: AbstractMesh, grip: TransformNode): ClickMenu { + return this._diagramMenuManager.createClickMenu(mesh, grip); + } private notifyAll(event: DiagramEvent) { this.onDiagramEventObservable.notifyObservers(event, DiagramEventObserverMask.ALL); } - public editText(mesh: AbstractMesh) { - this.inputTextView.show(mesh); - } - public get controllers(): Controllers { return this._controllers; } - public get config(): AppConfig { - return this._config; - } - public createCopy(mesh: AbstractMesh, copy: boolean = false): AbstractMesh { let newMesh; if (!mesh.isAnInstance) { @@ -100,13 +68,13 @@ export class DiagramManager { } newMesh.id = 'id' + uuidv4(); - newMesh.actionManager = this.diagramEntityActionManager; + newMesh.actionManager = this._diagramEntityActionManager; newMesh.position = mesh.absolutePosition.clone(); if (mesh.absoluteRotationQuaternion) { newMesh.rotation = mesh.absoluteRotationQuaternion.toEulerAngles().clone(); } else { - this.logger.error("no rotation quaternion"); + logger.error("no rotation quaternion"); } applyScaling(mesh, newMesh, copy, this._config.current?.createSnap); newMesh.material = mesh.material; @@ -116,10 +84,24 @@ export class DiagramManager { } return newMesh; } + public get config(): AppConfig { + return this._config; + } + + private cleanupOrphanConnections(mesh: AbstractMesh) { + if (mesh.metadata.template != '#connection-template') { + this._scene.meshes.forEach((m) => { + if (m?.metadata?.to == mesh.id || m?.metadata?.from == mesh.id) { + logger.debug("removing connection", m.id); + this.notifyAll({type: DiagramEventType.REMOVE, entity: toDiagramEntity(m)}); + } + }); + } + } private onDiagramEvent(event: DiagramEvent) { diagramEventHandler( - event, this._scene, this.toolbox, this._config.current.physicsEnabled, - this.diagramEntityActionManager); + event, this._scene, this._diagramMenuManager.toolbox, this._config.current.physicsEnabled, + this._diagramEntityActionManager); } } \ No newline at end of file diff --git a/src/diagram/diagramMenuManager.ts b/src/diagram/diagramMenuManager.ts new file mode 100644 index 0000000..4f32c15 --- /dev/null +++ b/src/diagram/diagramMenuManager.ts @@ -0,0 +1,80 @@ +import {DiagramEvent, DiagramEventType} from "./types/diagramEntity"; +import {AbstractMesh, ActionEvent, Observable, Scene, TransformNode} from "@babylonjs/core"; +import {DiagramEventObserverMask} from "./diagramManager"; +import {InputTextView} from "../information/inputTextView"; +import {toDiagramEntity} from "./functions/toDiagramEntity"; +import {DefaultScene} from "../defaultScene"; +import {Controllers} from "../controllers/controllers"; +import log from "loglevel"; +import {Toolbox} from "../toolbox/toolbox"; +import {ScaleMenu} from "../menus/scaleMenu"; +import {ClickMenu} from "../menus/clickMenu"; + +const logger = log.getLogger('DiagramMenuManager'); + +export class DiagramMenuManager { + public readonly toolbox: Toolbox; + public readonly scaleMenu: ScaleMenu; + private readonly _notifier: Observable; + private readonly _inputTextView: InputTextView; + private readonly _scene: Scene; + + constructor(notifier: Observable, controllers: Controllers) { + this._scene = DefaultScene.Scene; + this._notifier = notifier; + this._inputTextView = new InputTextView(controllers); + this._inputTextView.onTextObservable.add((evt) => { + const mesh = this._scene.getMeshById(evt.id); + if (mesh) { + const entity = toDiagramEntity(mesh); + entity.text = evt.text; + this.notifyAll({type: DiagramEventType.MODIFY, entity: entity}); + } else { + logger.error("mesh not found", evt.id); + } + }); + this.toolbox = new Toolbox(); + this.scaleMenu = new ScaleMenu(); + this.scaleMenu.onScaleChangeObservable.add((mesh: AbstractMesh) => { + this.notifyAll({type: DiagramEventType.MODIFY, entity: toDiagramEntity(mesh)}); + const position = mesh.absolutePosition.clone(); + position.y = mesh.getBoundingInfo().boundingBox.maximumWorld.y + .1; + this.scaleMenu.changePosition(position); + }); + } + + public editText(mesh: AbstractMesh) { + this._inputTextView.show(mesh); + } + + public createClickMenu(mesh: AbstractMesh, grip: TransformNode): ClickMenu { + const clickMenu = new ClickMenu(mesh, grip, this._notifier); + clickMenu.onClickMenuObservable.add((evt: ActionEvent) => { + console.log(evt); + switch (evt.source.id) { + case "remove": + this.notifyAll({type: DiagramEventType.REMOVE, entity: toDiagramEntity(clickMenu.mesh)}); + break; + case "label": + this.editText(clickMenu.mesh); + break; + case "connect": + break; + case "size": + this.scaleMenu.show(clickMenu.mesh); + break; + case "close": + this.scaleMenu.hide(); + break; + } + console.log(evt); + + }, -1, false, this, false); + + return clickMenu; + } + + private notifyAll(event: DiagramEvent) { + this._notifier.notifyObservers(event, DiagramEventObserverMask.ALL); + } +} \ No newline at end of file diff --git a/src/menus/clickMenu.ts b/src/menus/clickMenu.ts index aa56074..6e705a4 100644 --- a/src/menus/clickMenu.ts +++ b/src/menus/clickMenu.ts @@ -1,7 +1,7 @@ -import {AbstractMesh, Scene, TransformNode, Vector3} from "@babylonjs/core"; +import {AbstractMesh, ActionEvent, Observable, Scene, TransformNode, Vector3} from "@babylonjs/core"; import {DiagramEvent, DiagramEventType} from "../diagram/types/diagramEntity"; import {toDiagramEntity} from "../diagram/functions/toDiagramEntity"; -import {DiagramEventObserverMask, DiagramManager} from "../diagram/diagramManager"; +import {DiagramEventObserverMask} from "../diagram/diagramManager"; import {DiagramConnection} from "../diagram/diagramConnection"; import {isDiagramEntity} from "../diagram/functions/isDiagramEntity"; import {HtmlButton} from "babylon-html"; @@ -9,12 +9,14 @@ import {HtmlButton} from "babylon-html"; export class ClickMenu { private readonly _mesh: AbstractMesh; private readonly transform: TransformNode; - private readonly diagramManager: DiagramManager; private connection: DiagramConnection = null; + public onClickMenuObservable: Observable = new Observable(); + private _diagramEventObservable: Observable; - constructor(mesh: AbstractMesh, diagramManager: DiagramManager, grip: TransformNode) { + constructor(mesh: AbstractMesh, grip: TransformNode, diagramEventObservable: Observable) { this._mesh = mesh; - this.diagramManager = diagramManager; + this._diagramEventObservable = diagramEventObservable; + //this.diagramManager = diagramManager; const scene = mesh.getScene(); this.transform = new TransformNode("transform", scene); let x = -.54 / 2; @@ -22,20 +24,16 @@ export class ClickMenu { const removeButton: HtmlButton = this.makeNewButton("Remove", "remove", scene, x += .11); removeButton.onPointerObservable.add((eventData) => { if (eventData.sourceEvent.type == "pointerup") { - const event: DiagramEvent = { - type: DiagramEventType.REMOVE, - entity: - toDiagramEntity(this._mesh) - } - this.diagramManager.onDiagramEventObservable.notifyObservers(event, DiagramEventObserverMask.ALL); + this.onClickMenuObservable.notifyObservers(eventData); this.dispose(); } }, -1, false, this, false); + const labelButton: HtmlButton = this.makeNewButton("Label", "label", scene, x += .11); labelButton.onPointerObservable.add((eventData) => { if (eventData.sourceEvent.type == "pointerup") { - this.diagramManager.editText(this._mesh); + this.onClickMenuObservable.notifyObservers(eventData); this.dispose(); } }, -1, false, this, false); @@ -44,18 +42,22 @@ export class ClickMenu { connectButton.onPointerObservable.add((eventData) => { if (eventData.sourceEvent.type == "pointerup") { this.createMeshConnection(this._mesh, grip, eventData.additionalData.pickedPoint.clone()); + } }, -1, false, this, false); const closeButton: HtmlButton = this.makeNewButton("Close", "close", scene, x += .11); closeButton.onPointerObservable.add((eventData) => { - eventData.sourceEvent.type == "pointerup" && this.dispose(); + if (eventData.sourceEvent.type == "pointerup") { + this.onClickMenuObservable.notifyObservers(eventData); + this.dispose(); + } }, -1, false, this, false); const sizeButton: HtmlButton = this.makeNewButton("Size", "size", scene, x += .11); sizeButton.onPointerObservable.add((eventData) => { if (eventData.sourceEvent.type == "pointerup") { - this.diagramManager.scaleMenu.show(this._mesh); + this.onClickMenuObservable.notifyObservers(eventData); } }, -1, false, this, false); @@ -81,11 +83,14 @@ export class ClickMenu { return this.transform.isDisposed(); } + public get mesh(): AbstractMesh { + return this._mesh; + } public connect(mesh: AbstractMesh) { if (this.connection) { if (mesh && isDiagramEntity(mesh)) { this.connection.to = mesh.id; - this.diagramManager.onDiagramEventObservable.notifyObservers({ + this._diagramEventObservable.notifyObservers({ type: DiagramEventType.ADD, entity: toDiagramEntity(this.connection.mesh) }, DiagramEventObserverMask.ALL); @@ -100,7 +105,6 @@ export class ClickMenu { } private dispose() { - this.diagramManager.scaleMenu.hide(); this.transform.dispose(false, true); } } \ No newline at end of file