Merge pull request #915 from carb/master

Add optional PanAndZoomCanvasAction for trackpad support
This commit is contained in:
Dylan Vorster
2022-02-06 22:16:42 -07:00
committed by GitHub
5 changed files with 128 additions and 0 deletions

View File

@ -14,6 +14,7 @@ import demo_listeners from './demo-listeners';
import demo_zoom from './demo-zoom-to-fit';
import demo_zoom_nodes from './demo-zoom-to-fit-nodes';
import demo_canvas_drag from './demo-canvas-drag';
import demo_pan_and_zoom from './demo-pan-and-zoom';
import demo_dynamic_ports from './demo-dynamic-ports';
import demo_labels from './demo-labelled-links';
@ -26,5 +27,6 @@ export const EventsAndListeners = demo_listeners;
export const ZoomToFit = demo_zoom;
export const ZoomToFitSelectNodes = demo_zoom_nodes;
export const CanvasDrag = demo_canvas_drag;
export const CanvasPanAndZoom = demo_pan_and_zoom;
export const DynamicPorts = demo_dynamic_ports;
export const LinksWithLabels = demo_labels;

View File

@ -0,0 +1,57 @@
import * as React from 'react';
import createEngine, { DiagramModel, DefaultNodeModel } from '@projectstorm/react-diagrams';
import { DemoButton, DemoWorkspaceWidget } from '../helpers/DemoWorkspaceWidget';
import { CanvasWidget } from '@projectstorm/react-canvas-core';
import { DemoCanvasWidget } from '../helpers/DemoCanvasWidget';
/**
* Tests the pan and zoom action, which is intended as a trackpad/mobile
* alternative to the standard ZoomCanvasAction
*/
class CanvasPanAndZoomToggle extends React.Component<any, any> {
render() {
const { engine } = this.props;
return (
<DemoCanvasWidget>
<CanvasWidget engine={engine} />
</DemoCanvasWidget>
);
}
}
export default () => {
/**
* 1) setup the diagram engine
* PandAndZoomCanvasAction and ZoomCanvasAction are mutually exclusive
* If both are enabled, ZoomCanvasAction will override.
*/
var engine = createEngine({
registerDefaultPanAndZoomCanvasAction: true,
registerDefaultZoomCanvasAction: false
});
//2) setup the diagram model
var model = new DiagramModel();
//3-A) create a default node
var node1 = new DefaultNodeModel('Node 1', 'rgb(0,192,255)');
var port1 = node1.addOutPort('Out');
node1.setPosition(100, 100);
//3-B) create another default node
var node2 = new DefaultNodeModel('Node 2', 'rgb(192,255,0)');
var port2 = node2.addInPort('In');
node2.setPosition(400, 100);
//3-C) link the 2 nodes together
var link1 = port1.link(port2);
//4) add the models to the root graph
model.addAll(node1, node2, link1);
//5) load model into engine
engine.setModel(model);
//6) render the diagram!
return <CanvasPanAndZoomToggle engine={engine} model={model} />;
};

View File

@ -8,6 +8,7 @@ import { MouseEvent } from 'react';
import { BaseModel } from './core-models/BaseModel';
import { Point } from '@projectstorm/geometry';
import { ActionEventBus } from './core-actions/ActionEventBus';
import { PanAndZoomCanvasAction } from './actions/PanAndZoomCanvasAction';
import { ZoomCanvasAction } from './actions/ZoomCanvasAction';
import { DeleteItemsAction } from './actions/DeleteItemsAction';
import { StateMachine } from './core-state/StateMachine';
@ -25,6 +26,7 @@ export interface CanvasEngineListener extends BaseListener {
*/
export interface CanvasEngineOptions {
registerDefaultDeleteItemsAction?: boolean;
registerDefaultPanAndZoomCanvasAction?: boolean;
registerDefaultZoomCanvasAction?: boolean;
/**
* Defines the debounce wait time in milliseconds if > 0
@ -62,6 +64,8 @@ export class CanvasEngine<
};
if (this.options.registerDefaultZoomCanvasAction === true) {
this.eventBus.registerAction(new ZoomCanvasAction());
} else if (this.options.registerDefaultPanAndZoomCanvasAction === true) {
this.eventBus.registerAction(new PanAndZoomCanvasAction());
}
if (this.options.registerDefaultDeleteItemsAction === true) {
this.eventBus.registerAction(new DeleteItemsAction());

View File

@ -0,0 +1,64 @@
import { WheelEvent } from 'react';
import { Action, ActionEvent, InputType } from '../core-actions/Action';
export interface PanAndZoomCanvasActionOptions {
inverseZoom?: boolean;
}
export class PanAndZoomCanvasAction extends Action {
constructor(options: PanAndZoomCanvasActionOptions = {}) {
super({
type: InputType.MOUSE_WHEEL,
fire: (actionEvent: ActionEvent<WheelEvent>) => {
const { event } = actionEvent;
// we can block layer rendering because we are only targeting the transforms
for (let layer of this.engine.getModel().getLayers()) {
layer.allowRepaint(false);
}
const model = this.engine.getModel();
event.stopPropagation();
if (event.ctrlKey) {
// Pinch and zoom gesture
const oldZoomFactor = this.engine.getModel().getZoomLevel() / 100;
let scrollDelta = options.inverseZoom ? event.deltaY : -event.deltaY;
scrollDelta /= 3;
if (model.getZoomLevel() + scrollDelta > 10) {
model.setZoomLevel(model.getZoomLevel() + scrollDelta);
}
const zoomFactor = model.getZoomLevel() / 100;
const boundingRect = event.currentTarget.getBoundingClientRect();
const clientWidth = boundingRect.width;
const clientHeight = boundingRect.height;
// compute difference between rect before and after scroll
const widthDiff = clientWidth * zoomFactor - clientWidth * oldZoomFactor;
const heightDiff = clientHeight * zoomFactor - clientHeight * oldZoomFactor;
// compute mouse coords relative to canvas
const clientX = event.clientX - boundingRect.left;
const clientY = event.clientY - boundingRect.top;
// compute width and height increment factor
const xFactor = (clientX - model.getOffsetX()) / oldZoomFactor / clientWidth;
const yFactor = (clientY - model.getOffsetY()) / oldZoomFactor / clientHeight;
model.setOffset(model.getOffsetX() - widthDiff * xFactor, model.getOffsetY() - heightDiff * yFactor);
} else {
// Pan gesture
let yDelta = options.inverseZoom ? -event.deltaY : event.deltaY;
let xDelta = options.inverseZoom ? -event.deltaX : event.deltaX;
model.setOffset(model.getOffsetX() - xDelta, model.getOffsetY() - yDelta);
}
this.engine.repaintCanvas();
// re-enable rendering
for (let layer of this.engine.getModel().getLayers()) {
layer.allowRepaint(true);
}
}
});
}
}

View File

@ -41,3 +41,4 @@ export * from './states/MoveItemsState';
export * from './actions/DeleteItemsAction';
export * from './actions/ZoomCanvasAction';
export * from './actions/PanAndZoomCanvasAction';