Files
feuerwerk/src/editor/graph/graphView.ts
T

78 lines
3.2 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import type { Container, FederatedPointerEvent, FederatedWheelEvent, Graphics } from 'pixi.js';
import { clamp, drawGrid, zoomFactor } from '../../ui';
import type { Point } from '../framing';
/**
* What every surface that shows nodes shares with the graph editor: how far it zooms, how the grid
* is drawn and how the wheel zooms about the pointer. The graph editor and a plugin's node board
* look and move the same because they both come through here; the Tile tab's board borrows the
* panning and zooming.
*/
/** Spacing of the background grid – and, with snapping on, of the positions a drag can leave. */
export const GRID_STEP = 24;
export const ZOOM_LIMITS = { min: 0.25, max: 2.5 };
/** Breathing room around the framed nodes, so they do not touch the edge. */
export const FIT_MARGIN = 24;
/** Below this on-screen spacing the grid is noise rather than orientation, so it is dropped. */
const MIN_GRID_STEP = 8;
export interface GraphView {
/** Where the graph origin sits on screen. */
pan: Point;
/** Screen pixels per graph unit. */
zoom: number;
}
/**
* The view after zooming by `factor` about a screen point, which keeps the graph point under it.
* `limits` are the graph's own unless a surface with other content says otherwise – the tile
* board shows 48 px tiles, which one wants to come closer to than a node graph.
*/
export function zoomedAbout(
view: GraphView,
at: Point,
factor: number,
limits: { min: number; max: number } = ZOOM_LIMITS,
): GraphView {
const zoom = clamp(view.zoom * factor, limits.min, limits.max);
const graphX = (at.x - view.pan.x) / view.zoom;
const graphY = (at.y - view.pan.y) / view.zoom;
return { pan: { x: at.x - graphX * zoom, y: at.y - graphY * zoom }, zoom };
}
/**
* Drag with any button to pan, wheel to zoom about the pointer – all a board does with the pointer
* when its content takes none. `set` receives each new view and which gesture made it.
*/
export function panAndZoom(
surface: Container,
get: () => GraphView,
set: (view: GraphView, gesture: 'pan' | 'zoom') => void,
limits: { min: number; max: number } = ZOOM_LIMITS,
): void {
let panning: { start: Point; pan: Point } | null = null;
surface.on('pointerdown', (e: FederatedPointerEvent) => {
panning = { start: { x: e.global.x, y: e.global.y }, pan: { ...get().pan } };
});
surface.on('globalpointermove', (e: FederatedPointerEvent) => {
if (!panning) return;
const { start, pan } = panning;
set({ ...get(), pan: { x: pan.x + e.global.x - start.x, y: pan.y + e.global.y - start.y } }, 'pan');
});
surface.on('pointerup', () => (panning = null));
surface.on('pointerupoutside', () => (panning = null));
surface.on('wheel', (e: FederatedWheelEvent) => {
set(zoomedAbout(get(), surface.toLocal(e.global), zoomFactor(e.deltaY), limits), 'zoom');
e.preventDefault();
});
}
export function drawGraphGrid(g: Graphics, view: GraphView, w: number, h: number): void {
g.clear();
const step = GRID_STEP * view.zoom;
if (step < MIN_GRID_STEP) return;
// `pan` is where the graph origin sits on screen, but no axis lines: it marks nothing.
drawGrid(g, { originX: view.pan.x, originY: view.pan.y, step, w, h, alpha: 0.04 });
}