Added Diagram Menu Manager.

This commit is contained in:
Michael Mainguy 2024-04-23 10:38:29 -05:00
parent e30bca5090
commit c81dd8c24a
4 changed files with 136 additions and 71 deletions

View File

@ -246,7 +246,6 @@ export class Base {
private click() { private click() {
let mesh = this.xr.pointerSelection.getMeshUnderPointer(this.xrInputSource.uniqueId); let mesh = this.xr.pointerSelection.getMeshUnderPointer(this.xrInputSource.uniqueId);
if (pointable(mesh)) { if (pointable(mesh)) {
logger.debug("click on " + mesh.id); logger.debug("click on " + mesh.id);
if (this.clickMenu && !this.clickMenu.isDisposed) { if (this.clickMenu && !this.clickMenu.isDisposed) {
@ -255,7 +254,7 @@ export class Base {
this.clickMenu = null; this.clickMenu = null;
} }
} else { } else {
this.clickMenu = new ClickMenu(mesh, this.diagramManager, this.xrInputSource.grip); this.clickMenu = this.diagramManager.diagramMenuManager.createClickMenu(mesh, this.xrInputSource.grip);
} }
} else { } else {

View File

@ -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 {DiagramEvent, DiagramEventType} from "./types/diagramEntity";
import log from "loglevel"; import log from "loglevel";
import {Controllers} from "../controllers/controllers"; import {Controllers} from "../controllers/controllers";
import {AppConfig} from "../util/appConfig"; import {AppConfig} from "../util/appConfig";
import {Toolbox} from "../toolbox/toolbox";
import {diagramEventHandler} from "./functions/diagramEventHandler"; import {diagramEventHandler} from "./functions/diagramEventHandler";
import {deepCopy} from "../util/functions/deepCopy"; import {deepCopy} from "../util/functions/deepCopy";
import {applyPhysics} from "./functions/diagramShapePhysics"; import {applyPhysics} from "./functions/diagramShapePhysics";
@ -12,10 +11,11 @@ import {toDiagramEntity} from "./functions/toDiagramEntity";
import {v4 as uuidv4} from 'uuid'; import {v4 as uuidv4} from 'uuid';
import {buildEntityActionManager} from "./functions/buildEntityActionManager"; import {buildEntityActionManager} from "./functions/buildEntityActionManager";
import {isDiagramEntity} from "./functions/isDiagramEntity"; import {isDiagramEntity} from "./functions/isDiagramEntity";
import {InputTextView} from "../information/inputTextView";
import {DefaultScene} from "../defaultScene"; 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 { export enum DiagramEventObserverMask {
ALL = -1, ALL = -1,
FROM_DB = 1, FROM_DB = 1,
@ -24,73 +24,41 @@ export enum DiagramEventObserverMask {
export class DiagramManager { export class DiagramManager {
public readonly _config: AppConfig; public readonly _config: AppConfig;
private readonly _controllers: Controllers; private readonly _controllers: Controllers;
private readonly diagramEntityActionManager: ActionManager; private readonly _diagramEntityActionManager: ActionManager;
private readonly inputTextView: InputTextView;
public readonly scaleMenu: ScaleMenu;
public readonly onDiagramEventObservable: Observable<DiagramEvent> = new Observable(); public readonly onDiagramEventObservable: Observable<DiagramEvent> = new Observable();
private readonly logger = log.getLogger('DiagramManager'); private readonly _diagramMenuManager: DiagramMenuManager;
private readonly toolbox: Toolbox;
private readonly _scene: Scene; private readonly _scene: Scene;
constructor() { constructor() {
this._scene = DefaultScene.Scene; this._scene = DefaultScene.Scene;
this._config = new AppConfig(); this._config = new AppConfig();
this._controllers = new Controllers(); this._controllers = new Controllers();
this.inputTextView = new InputTextView(this._controllers); this._diagramMenuManager = new DiagramMenuManager(this.onDiagramEventObservable, this._controllers);
this.inputTextView.onTextObservable.add((evt) => { this._diagramEntityActionManager = buildEntityActionManager(this._controllers);
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.onDiagramEventObservable.add(this.onDiagramEvent, DiagramEventObserverMask.FROM_DB, true, this); this.onDiagramEventObservable.add(this.onDiagramEvent, DiagramEventObserverMask.FROM_DB, true, this);
this.logger.debug("DiagramManager constructed"); logger.debug("DiagramManager constructed");
this._scene.onMeshRemovedObservable.add((mesh) => { this._scene.onMeshRemovedObservable.add((mesh) => {
if (isDiagramEntity(mesh)) { if (isDiagramEntity(mesh)) {
if (mesh.metadata.template != '#connection-template') { this.cleanupOrphanConnections(mesh)
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)});
}
});
}
} }
}); });
} }
public get diagramMenuManager(): DiagramMenuManager {
return this._diagramMenuManager;
}
public createClickMenu(mesh: AbstractMesh, grip: TransformNode): ClickMenu {
return this._diagramMenuManager.createClickMenu(mesh, grip);
}
private notifyAll(event: DiagramEvent) { private notifyAll(event: DiagramEvent) {
this.onDiagramEventObservable.notifyObservers(event, DiagramEventObserverMask.ALL); this.onDiagramEventObservable.notifyObservers(event, DiagramEventObserverMask.ALL);
} }
public editText(mesh: AbstractMesh) {
this.inputTextView.show(mesh);
}
public get controllers(): Controllers { public get controllers(): Controllers {
return this._controllers; return this._controllers;
} }
public get config(): AppConfig {
return this._config;
}
public createCopy(mesh: AbstractMesh, copy: boolean = false): AbstractMesh { public createCopy(mesh: AbstractMesh, copy: boolean = false): AbstractMesh {
let newMesh; let newMesh;
if (!mesh.isAnInstance) { if (!mesh.isAnInstance) {
@ -100,13 +68,13 @@ export class DiagramManager {
} }
newMesh.id = 'id' + uuidv4(); newMesh.id = 'id' + uuidv4();
newMesh.actionManager = this.diagramEntityActionManager; newMesh.actionManager = this._diagramEntityActionManager;
newMesh.position = mesh.absolutePosition.clone(); newMesh.position = mesh.absolutePosition.clone();
if (mesh.absoluteRotationQuaternion) { if (mesh.absoluteRotationQuaternion) {
newMesh.rotation = mesh.absoluteRotationQuaternion.toEulerAngles().clone(); newMesh.rotation = mesh.absoluteRotationQuaternion.toEulerAngles().clone();
} else { } else {
this.logger.error("no rotation quaternion"); logger.error("no rotation quaternion");
} }
applyScaling(mesh, newMesh, copy, this._config.current?.createSnap); applyScaling(mesh, newMesh, copy, this._config.current?.createSnap);
newMesh.material = mesh.material; newMesh.material = mesh.material;
@ -116,10 +84,24 @@ export class DiagramManager {
} }
return newMesh; 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) { private onDiagramEvent(event: DiagramEvent) {
diagramEventHandler( diagramEventHandler(
event, this._scene, this.toolbox, this._config.current.physicsEnabled, event, this._scene, this._diagramMenuManager.toolbox, this._config.current.physicsEnabled,
this.diagramEntityActionManager); this._diagramEntityActionManager);
} }
} }

View File

@ -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<DiagramEvent>;
private readonly _inputTextView: InputTextView;
private readonly _scene: Scene;
constructor(notifier: Observable<DiagramEvent>, 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);
}
}

View File

@ -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 {DiagramEvent, DiagramEventType} from "../diagram/types/diagramEntity";
import {toDiagramEntity} from "../diagram/functions/toDiagramEntity"; import {toDiagramEntity} from "../diagram/functions/toDiagramEntity";
import {DiagramEventObserverMask, DiagramManager} from "../diagram/diagramManager"; import {DiagramEventObserverMask} from "../diagram/diagramManager";
import {DiagramConnection} from "../diagram/diagramConnection"; import {DiagramConnection} from "../diagram/diagramConnection";
import {isDiagramEntity} from "../diagram/functions/isDiagramEntity"; import {isDiagramEntity} from "../diagram/functions/isDiagramEntity";
import {HtmlButton} from "babylon-html"; import {HtmlButton} from "babylon-html";
@ -9,12 +9,14 @@ import {HtmlButton} from "babylon-html";
export class ClickMenu { export class ClickMenu {
private readonly _mesh: AbstractMesh; private readonly _mesh: AbstractMesh;
private readonly transform: TransformNode; private readonly transform: TransformNode;
private readonly diagramManager: DiagramManager;
private connection: DiagramConnection = null; private connection: DiagramConnection = null;
public onClickMenuObservable: Observable<ActionEvent> = new Observable<ActionEvent>();
private _diagramEventObservable: Observable<DiagramEvent>;
constructor(mesh: AbstractMesh, diagramManager: DiagramManager, grip: TransformNode) { constructor(mesh: AbstractMesh, grip: TransformNode, diagramEventObservable: Observable<DiagramEvent>) {
this._mesh = mesh; this._mesh = mesh;
this.diagramManager = diagramManager; this._diagramEventObservable = diagramEventObservable;
//this.diagramManager = diagramManager;
const scene = mesh.getScene(); const scene = mesh.getScene();
this.transform = new TransformNode("transform", scene); this.transform = new TransformNode("transform", scene);
let x = -.54 / 2; let x = -.54 / 2;
@ -22,20 +24,16 @@ export class ClickMenu {
const removeButton: HtmlButton = this.makeNewButton("Remove", "remove", scene, x += .11); const removeButton: HtmlButton = this.makeNewButton("Remove", "remove", scene, x += .11);
removeButton.onPointerObservable.add((eventData) => { removeButton.onPointerObservable.add((eventData) => {
if (eventData.sourceEvent.type == "pointerup") { if (eventData.sourceEvent.type == "pointerup") {
const event: DiagramEvent = { this.onClickMenuObservable.notifyObservers(eventData);
type: DiagramEventType.REMOVE,
entity:
toDiagramEntity(this._mesh)
}
this.diagramManager.onDiagramEventObservable.notifyObservers(event, DiagramEventObserverMask.ALL);
this.dispose(); this.dispose();
} }
}, -1, false, this, false); }, -1, false, this, false);
const labelButton: HtmlButton = this.makeNewButton("Label", "label", scene, x += .11); const labelButton: HtmlButton = this.makeNewButton("Label", "label", scene, x += .11);
labelButton.onPointerObservable.add((eventData) => { labelButton.onPointerObservable.add((eventData) => {
if (eventData.sourceEvent.type == "pointerup") { if (eventData.sourceEvent.type == "pointerup") {
this.diagramManager.editText(this._mesh); this.onClickMenuObservable.notifyObservers(eventData);
this.dispose(); this.dispose();
} }
}, -1, false, this, false); }, -1, false, this, false);
@ -44,18 +42,22 @@ export class ClickMenu {
connectButton.onPointerObservable.add((eventData) => { connectButton.onPointerObservable.add((eventData) => {
if (eventData.sourceEvent.type == "pointerup") { if (eventData.sourceEvent.type == "pointerup") {
this.createMeshConnection(this._mesh, grip, eventData.additionalData.pickedPoint.clone()); this.createMeshConnection(this._mesh, grip, eventData.additionalData.pickedPoint.clone());
} }
}, -1, false, this, false); }, -1, false, this, false);
const closeButton: HtmlButton = this.makeNewButton("Close", "close", scene, x += .11); const closeButton: HtmlButton = this.makeNewButton("Close", "close", scene, x += .11);
closeButton.onPointerObservable.add((eventData) => { 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); }, -1, false, this, false);
const sizeButton: HtmlButton = this.makeNewButton("Size", "size", scene, x += .11); const sizeButton: HtmlButton = this.makeNewButton("Size", "size", scene, x += .11);
sizeButton.onPointerObservable.add((eventData) => { sizeButton.onPointerObservable.add((eventData) => {
if (eventData.sourceEvent.type == "pointerup") { if (eventData.sourceEvent.type == "pointerup") {
this.diagramManager.scaleMenu.show(this._mesh); this.onClickMenuObservable.notifyObservers(eventData);
} }
}, -1, false, this, false); }, -1, false, this, false);
@ -81,11 +83,14 @@ export class ClickMenu {
return this.transform.isDisposed(); return this.transform.isDisposed();
} }
public get mesh(): AbstractMesh {
return this._mesh;
}
public connect(mesh: AbstractMesh) { public connect(mesh: AbstractMesh) {
if (this.connection) { if (this.connection) {
if (mesh && isDiagramEntity(mesh)) { if (mesh && isDiagramEntity(mesh)) {
this.connection.to = mesh.id; this.connection.to = mesh.id;
this.diagramManager.onDiagramEventObservable.notifyObservers({ this._diagramEventObservable.notifyObservers({
type: DiagramEventType.ADD, type: DiagramEventType.ADD,
entity: toDiagramEntity(this.connection.mesh) entity: toDiagramEntity(this.connection.mesh)
}, DiagramEventObserverMask.ALL); }, DiagramEventObserverMask.ALL);
@ -100,7 +105,6 @@ export class ClickMenu {
} }
private dispose() { private dispose() {
this.diagramManager.scaleMenu.hide();
this.transform.dispose(false, true); this.transform.dispose(false, true);
} }
} }