From 76d7e6c00ec11a72adfcb10fbfad6d92a873b253 Mon Sep 17 00:00:00 2001
From: sanine <sanine.not@pm.me>
Date: Sun, 29 May 2022 20:09:42 -0500
Subject: add centered zooming

---
 src/Canvas.js | 60 ++++++++++++++++++++++++++++++++++++++++++-----------------
 src/main.js   |  6 +++---
 2 files changed, 46 insertions(+), 20 deletions(-)

(limited to 'src')

diff --git a/src/Canvas.js b/src/Canvas.js
index bd39f47..7f6404b 100644
--- a/src/Canvas.js
+++ b/src/Canvas.js
@@ -12,7 +12,10 @@ class Canvas {
 		this.zoom = 1;
 		const ZOOM_SPEED = 1.2;
 		
-		this.mousePos = { x: 0, y: 0 };
+		this.mouse = {
+			screenPos: { x: 0, y: 0 },
+			drawingPos: { x: 0, y: 0 },
+		};
 		this.pan = { x: 0, y: 0 };
 		this.panning = false;
 
@@ -24,14 +27,11 @@ class Canvas {
 
 		/* 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;
+			this.mouse.screenPos.x = e.offsetX;
+			this.mouse.screenPos.y = e.offsetY;
 
+			const [xd, yd] = this.screenToDrawingPos(e.offsetX, e.offsetY);
+			
 			/* compute movement */
 			const dx = e.movementX / (this.zoom * this.scale);
 			const dy = e.movementY / (this.zoom * this.scale);
@@ -43,14 +43,15 @@ class Canvas {
 				this.setTransform();
 			}
 
-			this.mousePos.x = x;
-			this.mousePos.y = y;
+			this.mouse.drawingPos.x = xd;
+			this.mouse.drawingPos.y = yd;
 
 			if (this.onMouseMove) this.onMouseMove(this.mousePos);
 		});
 
 		/* clicking */
 		this.element.addEventListener('mousedown', e => {
+			e.preventDefault();
 			if (e.button === 1) this.panning = true;
 		});
 		this.element.addEventListener('mouseup', e => {
@@ -65,9 +66,19 @@ class Canvas {
 		/* 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;
+			const alpha = (1/delta) - 1;
+
+			/* zoom in */
 			this.zoom *= delta;
-			this.zoom = clamp(this.zoom, 1, Infinity);
+			this.zoom = clamp(this.zoom, 1, 1000);
+			this.setTransform();
+
+			/* pan to keep mouse in the same place */
+			const [mouseX, mouseY] = this.screenToDrawingPos(this.mouse.screenPos.x, this.mouse.screenPos.y);
+			this.pan.x += mouseX - this.mouse.drawingPos.x;
+			this.pan.y += mouseY - this.mouse.drawingPos.y;
 			this.setTransform();
 			this.draw();
 		});
@@ -80,14 +91,10 @@ class Canvas {
 
 	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;
+		const xMax = this.pixelsToUnits(this.element.width) - 1;
+		const yMax = this.pixelsToUnits(this.element.height) - 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);
@@ -105,6 +112,25 @@ class Canvas {
 		this.draw();
 	}
 
+	pixelsToUnits(value) {
+		return value / this.zoom / this.scale;
+	}
+
+	unitsToPixels(value) {
+		return value * this.zoom * this.scale;
+	}
+
+	screenToDrawingPos(xs, ys) {
+		const matrix = this.context.getTransform();
+		matrix.invertSelf();
+
+		/* compute drawing-space position */
+		const x = matrix.a*xs + matrix.b*ys + matrix.e;
+		const y = matrix.c*xs + matrix.d*ys + matrix.f;
+
+		return [ x, y ];
+	}
+
 	draw() {
 		this.context.clearRect(0, 0, 1, 1);
 		if (this.onDraw) this.onDraw(this.context);
diff --git a/src/main.js b/src/main.js
index 5c9b085..d618d05 100644
--- a/src/main.js
+++ b/src/main.js
@@ -5,7 +5,7 @@ const $ = id => document.getElementById(id)
 window.onload = () => {
 	const canvas = new Canvas('root');
 
-	const pos = canvas.mousePos;
+	const pos = canvas.mouse.drawingPos;
 	canvas.onMouseMove = () => canvas.draw();
 	
 	canvas.onDraw = ct => {
@@ -19,9 +19,9 @@ window.onload = () => {
 		ct.fillRect(2/3, 2/3, 1/3, 1/3);
 
 		ct.strokeStyle = '#fff';
-		ct.lineWidth = 1/(canvas.zoom * canvas.scale);
+		ct.lineWidth = canvas.pixelsToUnits(1);
 		ct.beginPath();
-		ct.arc(pos.x, pos.y, 30 /(canvas.zoom * canvas.scale), 0, 2*Math.PI);
+		ct.arc(pos.x, pos.y, canvas.pixelsToUnits(30), 0, 2*Math.PI);
 		ct.closePath();
 		ct.stroke();
 	};
-- 
cgit v1.2.1