summaryrefslogtreecommitdiff
path: root/src/Canvas.js
blob: bd39f475db229c38466bbb3657fcea988599f1d0 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
import { clamp } from './modules/Util.js';

class Canvas {
	constructor(rootId) {
		const root = document.getElementById(rootId);

		this.element = document.createElement('canvas');
		this.context = this.element.getContext('2d');

		/* state variables */
		this.scale = 1;
		this.zoom = 1;
		const ZOOM_SPEED = 1.2;
		
		this.mousePos = { x: 0, y: 0 };
		this.pan = { x: 0, y: 0 };
		this.panning = false;

		/* callbacks */
		this.onDraw = null;
		this.onMouseMove = null;

		/* register event listeners */

		/* mouse movement */
		this.element.addEventListener('mousemove', e => {
			const matrix = this.context.getTransform();
			matrix.invertSelf();
			const screenX = e.offsetX; const screenY = e.offsetY;

			/* compute drawing-space position */
			const x = matrix.a*screenX + matrix.b*screenY + matrix.e;
			const y = matrix.c*screenX + matrix.d*screenY + matrix.f;

			/* compute movement */
			const dx = e.movementX / (this.zoom * this.scale);
			const dy = e.movementY / (this.zoom * this.scale);

			/* pan? */
			if (this.panning) {
				this.pan.x += dx;
				this.pan.y += dy;
				this.setTransform();
			}

			this.mousePos.x = x;
			this.mousePos.y = y;

			if (this.onMouseMove) this.onMouseMove(this.mousePos);
		});

		/* clicking */
		this.element.addEventListener('mousedown', e => {
			if (e.button === 1) this.panning = true;
		});
		this.element.addEventListener('mouseup', e => {
			if (e.button === 1) this.panning = false;
		}); 

		/* mouse leave */
		this.element.addEventListener('mouseleave', e => {
			this.panning = false;
		});

		/* mouse wheel */
		this.element.addEventListener('wheel', e => {
			if (this.panning) return; // don't zoom and pan simultaneously
			const delta = e.deltaY < 0 ? ZOOM_SPEED : 1/ZOOM_SPEED;
			this.zoom *= delta;
			this.zoom = clamp(this.zoom, 1, Infinity);
			this.setTransform();
			this.draw();
		});
		
		/* finalize setup */
		this.fillWindow();
		window.addEventListener('resize', () => this.fillWindow());
		root.appendChild(this.element);
	}

	setTransform() {
		/* clamp pan */
		console.log('---');
		console.log(this.pan);
		const xMax = this.element.width / (this.zoom * this.scale) - 1;
		const yMax = this.element.height / (this.zoom * this.scale) - 1;
		this.pan.x = clamp(this.pan.x, xMax, 0);
		this.pan.y = clamp(this.pan.y, yMax, 0);
		console.log(xMax, yMax);
		console.log(this.pan);

		this.context.setTransform(1, 0, 0, 1, 0, 0);
		this.context.scale(this.zoom * this.scale, this.zoom * this.scale);
		this.context.translate(this.pan.x, this.pan.y);
	}

	fillWindow() {
		const width = window.innerWidth;
		const height = window.innerHeight;
		this.scale = Math.max(width, height);

		this.element.width = width; this.element.height = height;
		this.setTransform();

		this.draw();
	}

	draw() {
		this.context.clearRect(0, 0, 1, 1);
		if (this.onDraw) this.onDraw(this.context);
	}
}

export default Canvas;