UNPKG

@gravity-ui/graph

Version:

Modern graph editor component

152 lines (151 loc) 5.75 kB
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))); } }