@gravity-ui/graph
Version:
Modern graph editor component
122 lines (121 loc) • 5.21 kB
JavaScript
import { EventedComponent } from "../../components/canvas/EventedComponent/EventedComponent";
import { getXY, isMetaKeyEvent, isTrackpadWheelEvent, isWindows } from "../../utils/functions";
import { clamp } from "../../utils/functions/clamp";
import { dragListener } from "../../utils/functions/dragListener";
import { EVENTS } from "../../utils/types/events";
export class Camera extends EventedComponent {
constructor(props, parent) {
super(props, parent);
this.handleClick = () => {
this.context.graph.api.unsetSelection();
};
this.handleMouseDownEvent = (event) => {
if (!this.context.graph.rootStore.settings.getConfigFlag("canDragCamera") || !(event instanceof MouseEvent)) {
return;
}
if (!isMetaKeyEvent(event)) {
dragListener(this.ownerDocument)
.on(EVENTS.DRAG_START, (event) => this.onDragStart(event))
.on(EVENTS.DRAG_UPDATE, (event) => this.onDragUpdate(event))
.on(EVENTS.DRAG_END, () => this.onDragEnd());
}
};
this.handleWheelEvent = (event) => {
if (!this.context.graph.rootStore.settings.getConfigFlag("canZoomCamera")) {
return;
}
event.stopPropagation();
event.preventDefault();
const isMoveEvent = isTrackpadWheelEvent(event) && !isMetaKeyEvent(event);
if (isMoveEvent) {
const windows = isWindows();
this.moveWithEdges(windows && event.shiftKey ? -event.deltaY : -event.deltaX, windows && event.shiftKey ? -event.deltaX : -event.deltaY);
return;
}
const xy = getXY(this.context.canvas, event);
if (!event.deltaY)
return;
/**
* Speed of wheel/trackpad pinch
*
* The zoom event from the trackpad pass the deltaY as a floating number, which can be less than +1/-1.
* If the delta is less than 1, it causes the zoom speed to slow down.
* Therefore, we have to round the value of deltaY to 1 if it is less than or equal to 1.
*/
const pinchSpeed = Math.sign(event.deltaY) * clamp(Math.abs(event.deltaY), 1, 20);
const dScale = this.context.constants.camera.STEP * this.context.constants.camera.SPEED * pinchSpeed;
const cameraScale = this.camera.getCameraScale();
// Smooth scale. The closer you get, the higher the speed
const smoothDScale = dScale * cameraScale;
this.camera.zoom(xy[0], xy[1], cameraScale - smoothDScale);
};
this.camera = this.context.camera;
this.ownerDocument = this.context.ownerDocument;
this.addWheelListener();
this.addEventListener("click", this.handleClick);
this.addEventListener("mousedown", this.handleMouseDownEvent);
}
setRoot() {
this.setContext({
root: this.props.root,
});
this.addWheelListener(this.props.root);
}
addWheelListener(root = this.props.root) {
root?.addEventListener("wheel", this.handleWheelEvent, { passive: false });
}
propsChanged(nextProps) {
if (this.props.root !== nextProps.root) {
this.props.root?.removeEventListener("wheel", this.handleWheelEvent);
this.addWheelListener(nextProps.root);
}
super.propsChanged(nextProps);
}
unmount() {
super.unmount();
this.props.root?.removeEventListener("wheel", this.handleWheelEvent);
this.removeEventListener("mousedown", this.handleMouseDownEvent);
}
onDragStart(event) {
this.lastDragEvent = event;
}
onDragUpdate(event) {
if (!this.lastDragEvent) {
return;
}
this.camera.move(event.pageX - this.lastDragEvent.pageX, event.pageY - this.lastDragEvent.pageY);
this.lastDragEvent = event;
}
onDragEnd() {
this.lastDragEvent = undefined;
}
moveWithEdges(deltaX, deltaY) {
const uR = this.context.graph.api.getUsableRect();
const cameraState = this.camera.getCameraState();
const gapX = cameraState.relativeWidth;
const gapY = cameraState.relativeHeight;
const moveToRight = deltaX > 0;
const moveToLeft = deltaX < 0;
const moveToTop = deltaY > 0;
const moveToBottop = deltaY < 0;
if (moveToRight && uR.x - gapX > cameraState.relativeX * -1) {
deltaX = 0;
} // left
if (moveToLeft && uR.x + uR.width + gapX < cameraState.relativeX * -1 + cameraState.relativeWidth) {
deltaX = 0;
} // right
if (moveToTop && uR.y - gapY > cameraState.relativeY * -1) {
deltaY = 0;
} // top
if (moveToBottop && uR.y + uR.height + gapY < cameraState.relativeY * -1 + cameraState.relativeHeight) {
deltaY = 0;
} // bottom
this.camera.move(deltaX, deltaY);
}
render() {
this.context.layer.resetTransform();
}
updateChildren() {
return this.props.children;
}
}