@gravity-ui/graph
Version:
Modern graph editor component
152 lines (151 loc) • 5.75 kB
JavaScript
import intersects from "intersects";
import { Emitter } from "../../utils/Emitter";
import { clamp } from "../../utils/functions/clamp";
export var ECameraScaleLevel;
(function (ECameraScaleLevel) {
ECameraScaleLevel[ECameraScaleLevel["Minimalistic"] = 100] = "Minimalistic";
ECameraScaleLevel[ECameraScaleLevel["Schematic"] = 200] = "Schematic";
ECameraScaleLevel[ECameraScaleLevel["Detailed"] = 300] = "Detailed";
})(ECameraScaleLevel || (ECameraScaleLevel = {}));
export const getInitCameraState = () => {
return {
/**
* Viewport of camera in canvas space
* x,y - center of camera(may be negative)
* width, height - size of viewport, equals to canvas w/h
* */
x: 0,
y: 0,
width: 0,
height: 0,
/**
* Viewport of camera in camera space
* relativeX, relativeY - center of camera
* relativeWidth, relativeHeight - size of viewport
*
* In easy words, it's a scale-aware viewport
*/
relativeX: 0,
relativeY: 0,
relativeWidth: 0,
relativeHeight: 0,
scale: 0.5,
scaleMax: 1,
scaleMin: 0.01,
};
};
export class CameraService extends Emitter {
constructor(graph, state = getInitCameraState()) {
super();
this.graph = graph;
this.state = state;
}
resize(newState) {
const diffX = newState.width - this.state.width;
const diffY = newState.height - this.state.height;
this.set(newState);
this.move(diffX, diffY);
}
set(newState) {
this.graph.executеDefaultEventAction("camera-change", Object.assign({}, this.state, newState), () => {
this.state = Object.assign(this.state, newState);
this.updateRelative();
});
}
updateRelative() {
this.state.relativeX = this.getRelative(this.state.x) | 0;
this.state.relativeY = this.getRelative(this.state.y) | 0;
this.state.relativeWidth = this.getRelative(this.state.width) | 0;
this.state.relativeHeight = this.getRelative(this.state.height) | 0;
}
getCameraRect() {
const { x, y, width, height } = this.state;
return { x, y, width, height };
}
getCameraScale() {
return this.state.scale;
}
getCameraBlockScaleLevel(cameraScale = this.getCameraScale()) {
const scales = this.graph.graphConstants.block.SCALES;
let scaleLevel = ECameraScaleLevel.Minimalistic;
if (cameraScale >= scales[1]) {
scaleLevel = ECameraScaleLevel.Schematic;
}
if (cameraScale >= scales[2]) {
scaleLevel = ECameraScaleLevel.Detailed;
}
return scaleLevel;
}
getCameraState() {
return this.state;
}
move(dx = 0, dy = 0) {
const x = (this.state.x + dx) | 0;
const y = (this.state.y + dy) | 0;
this.set({
x,
y,
});
}
getRelative(n, scale = this.state.scale) {
return n / scale;
}
getRelativeXY(x, y) {
return [(x - this.state.x) / this.state.scale, (y - this.state.y) / this.state.scale];
}
/**
* Converts relative coordinate to absolute (screen space)
* Inverse of getRelative
*/
getAbsolute(n, scale = this.state.scale) {
return n * scale;
}
/**
* Converts relative coordinates to absolute (screen space)
* Inverse of getRelativeXY
*/
getAbsoluteXY(x, y) {
return [x * this.state.scale + this.state.x, y * this.state.scale + this.state.y];
}
/**
* Zoom to point
* */
zoom(x, y, scale) {
const normalizedScale = clamp(scale, this.state.scaleMin, this.state.scaleMax);
const dx = this.getRelative(x - this.state.x);
const dy = this.getRelative(y - this.state.y);
const dxInNextScale = this.getRelative(x - this.state.x, normalizedScale);
const dyInNextScale = this.getRelative(y - this.state.y, normalizedScale);
const nextX = this.state.x + (dxInNextScale - dx) * normalizedScale;
const nextY = this.state.y + (dyInNextScale - dy) * normalizedScale;
this.set({
scale: normalizedScale,
x: nextX,
y: nextY,
});
}
getScaleRelativeDimensionsBySide(size, axis) {
return clamp(Number(this.state[axis] / size), this.state.scaleMin, this.state.scaleMax);
}
getScaleRelativeDimensions(width, height) {
return Math.min(this.getScaleRelativeDimensionsBySide(width, "width"), this.getScaleRelativeDimensionsBySide(height, "height"));
}
getXYRelativeCenterDimensions(dimensions, scale) {
const x = 0 - dimensions.x * scale - (dimensions.width / 2) * scale + this.state.width / 2;
const y = 0 - dimensions.y * scale - (dimensions.height / 2) * scale + this.state.height / 2;
return { x, y };
}
isRectVisible(x, y, w, h) {
return intersects.boxBox(x + this.state.relativeX, y + this.state.relativeY, w, h, 0, 0, this.state.relativeWidth, this.state.relativeHeight);
}
isLineVisible(x1, y1, x2, y2) {
// because the camera coordinates are inverted
return intersects.lineBox(-x1, -y1, -x2, -y2, this.state.relativeX - this.state.relativeWidth, this.state.relativeY - this.state.relativeHeight, this.state.relativeWidth, this.state.relativeHeight);
}
applyToPoint(x, y) {
return [(this.getRelative(x) - this.state.relativeX) | 0, (this.getRelative(y) - this.state.relativeY) | 0];
}
applyToRect(x, y, w, h) {
return this.applyToPoint(x, y).concat(Math.floor(this.getRelative(w)), Math.floor(this.getRelative(h)));
}
}