From 0b6659729b9ad6957acee389114730c26490f3a5 Mon Sep 17 00:00:00 2001 From: "Florine W. Dekker" Date: Fri, 8 Apr 2022 12:56:50 +0200 Subject: [PATCH] Add mouse-based grid navigation --- src/main/index.html | 9 ---- src/main/js/Main.ts | 41 +++++++++++++++--- src/main/js/Model.ts | 38 +++++++++++------ src/main/js/Painter.ts | 97 +++++++++++++++++++++++++++++++++++++----- 4 files changed, 148 insertions(+), 37 deletions(-) diff --git a/src/main/index.html b/src/main/index.html index a5aed6e..3ee4da3 100644 --- a/src/main/index.html +++ b/src/main/index.html @@ -38,15 +38,6 @@
- - - - - - - - -
diff --git a/src/main/js/Main.ts b/src/main/js/Main.ts index 40ca8ca..8031147 100644 --- a/src/main/js/Main.ts +++ b/src/main/js/Main.ts @@ -39,12 +39,41 @@ doAfterLoad(async () => { const gridCols = $("#gridCols"); gridCols.addEventListener("change", () => model.cols = parseInt(gridCols.value)); - const gridScale = $("#gridScale"); - gridScale.addEventListener("change", () => painter.scale = parseInt(gridScale.value)); - const scrollX = $("#scrollX"); - scrollX.addEventListener("change", () => model.scrollX = parseInt(scrollX.value)); + let dragStart: [number, number] | null = null; + canvas.addEventListener("mousedown", (event: MouseEvent) => { + dragStart = [event.clientX, event.clientY]; + }); + document.addEventListener("mouseup", () => { + dragStart = null; + }); + document.addEventListener("mousemove", (event: MouseEvent) => { + if (dragStart === null) return; - const scrollY = $("#scrollY"); - scrollY.addEventListener("change", () => model.scrollY = parseInt(scrollY.value)); + const moved = [event.clientX - dragStart[0], event.clientY - dragStart[1]]; + if (Math.abs(moved[0]) > painter.scale) { + const squaresToMove = Math.sign(moved[0]) * Math.floor(Math.abs(moved[0]) / painter.scale); + painter.scrollX -= squaresToMove; + dragStart[0] += squaresToMove * painter.scale; + } + + if (Math.abs(moved[1]) > painter.scale) { + const squaresToMove = Math.sign(moved[1]) * Math.floor(Math.abs(moved[1]) / painter.scale); + painter.scrollY -= squaresToMove; + dragStart[1] += squaresToMove * painter.scale; + } + }); + canvas.addEventListener("wheel", (event: WheelEvent) => { + event.preventDefault(); + + if (event.ctrlKey) { + painter.scale -= Math.sign(event.deltaY); + } else { + if (event.shiftKey) { + painter.scrollX += Math.sign(event.deltaY); + } else { + painter.scrollY += Math.sign(event.deltaY); + } + } + }); }); diff --git a/src/main/js/Model.ts b/src/main/js/Model.ts index f6650d5..fc972ba 100644 --- a/src/main/js/Model.ts +++ b/src/main/js/Model.ts @@ -5,6 +5,12 @@ import {CachedPrimeMath, PrimeMath} from "./PrimeMath"; * Represents a particular way in which prime numbers can be arranged and drawn. */ export interface Model { + get minX(): number | null; + get maxX(): number | null; + get minY(): number | null; + get maxY(): number | null; + + /** * Returns `true` if and only if the square at coordinates (`x`, `y`) is a prime number. * @@ -17,20 +23,28 @@ export interface Model { /** * A `Model` for a simple grid of primes. - * - * Supports scrolling by setting the `scrollX` and `scrollY` values. */ export class GridModel implements Model { + /** + * The instance to calculate primes with. + * + * @private + */ private readonly primeMath: PrimeMath = CachedPrimeMath.getInstance(); - /** - * The vertical position of the viewport. - */ - public scrollX: number = 0; - /** - * The horizontal position of the viewport. - */ - public scrollY: number = 0; + get minX(): number | null { + return 0; + } + get maxX(): number | null { + return this.cols; + } + get minY(): number | null { + return 0; + } + get maxY(): number | null { + return null; + } + /** * The number of columns to draw primes in. */ @@ -38,9 +52,9 @@ export class GridModel implements Model { isPrime(x: number, y: number): boolean | null { - if (this.scrollX + x >= this.cols) return null; + if (x < 0 || x >= this.cols || y < 0) return null; - const n = (y + this.scrollY) * this.cols + this.scrollX + x + 1; + const n = y * this.cols + x + 1; return this.primeMath.isPrime(n); } } diff --git a/src/main/js/Painter.ts b/src/main/js/Painter.ts index 90b6c85..66092ce 100644 --- a/src/main/js/Painter.ts +++ b/src/main/js/Painter.ts @@ -1,5 +1,6 @@ import {Model} from "./Model"; + /** * Paints prime numbers on a grid according to some `Model`. * @@ -30,10 +31,51 @@ export class Painter { * The model that determines where primes are painted. */ public model: Model | undefined; + /** * The scale to draw prime numbers at. */ - public scale: number = 15; + private _scale: number = 15; + + public get scale(): number { + return this._scale; + } + + public set scale(scale: number) { + this._scale = Math.max(scale, 1); + } + + /** + * The vertical position of the viewport. + */ + private _scrollX: number = 0; + + public get scrollX(): number { + return this._scrollX; + } + + public set scrollX(scrollX: number) { + const min = this.model?.minX; + const max = this.model?.maxX; + + this._scrollX = min != null && scrollX < min ? min : (max != null && scrollX > max ? max : scrollX); + } + + /** + * The horizontal position of the viewport. + */ + private _scrollY: number = 0; + + public get scrollY(): number { + return this._scrollY; + } + + public set scrollY(scrollY: number) { + const min = this.model?.minY; + const max = this.model?.maxY; + + this._scrollY = min != null && scrollY < min ? min : (max != null && scrollY > max ? max : scrollY); + } /** @@ -53,7 +95,7 @@ export class Painter { * @private */ private get width(): number { - return this.ctx.canvas.clientWidth / this.scale; + return Math.floor(this.ctx.canvas.clientWidth / this.scale) - 1; } /** @@ -62,7 +104,7 @@ export class Painter { * @private */ private get height(): number { - return this.ctx.canvas.clientHeight / this.scale; + return Math.floor(this.ctx.canvas.clientHeight / this.scale) - 1; } @@ -96,7 +138,13 @@ export class Painter { */ draw(): void { this.clearCanvas(); + + this.ctx.save(); + this.ctx.scale(this.scale, this.scale); + this.ctx.translate(0.2, 0.2); + this.drawBoundaries(); this.drawPrimes(); + this.ctx.restore(); } /** @@ -111,21 +159,50 @@ export class Painter { this.ctx.restore(); } + private drawBoundaries(): void { + this.ctx.save(); + this.ctx.beginPath(); + this.ctx.strokeStyle = "#000000"; + this.ctx.lineWidth = 0.2; + + const scrolledMinX = (this.model?.minX ?? 0) - this.scrollX; + const scrolledMaxX = this.model?.maxX == null ? this.width : this.model.maxX - this.scrollX; + const scrolledMinY = (this.model?.minY ?? 0) - this.scrollY; + const scrolledMaxY = this.model?.maxY == null ? this.height : this.model.maxY - this.scrollY; + + if (this.model?.minX != null) { + this.ctx.moveTo(scrolledMinX, scrolledMinY); + this.ctx.lineTo(scrolledMinX, scrolledMaxY); + } + if (this.model?.maxX != null) { + this.ctx.moveTo(scrolledMaxX, scrolledMinY); + this.ctx.lineTo(scrolledMaxX, scrolledMaxY); + } + if (this.model?.minY != null) { + this.ctx.moveTo(scrolledMinX, scrolledMinY); + this.ctx.lineTo(scrolledMaxX, scrolledMinY); + } + if (this.model?.maxY != null) { + this.ctx.moveTo(scrolledMinX, scrolledMaxY); + this.ctx.lineTo(scrolledMaxX, scrolledMaxY); + } + + this.ctx.stroke(); + this.ctx.restore(); + } + /** * Draws prime numbers on the canvas. * * @private */ private drawPrimes(): void { - const width = this.width; - const height = this.height; - this.ctx.save(); this.ctx.fillStyle = "#0033cc"; // TODO: Get color programmatically - for (let x = 0; x < width; x++) { - for (let y = 0; y < height; y++) { - if (this.model?.isPrime(x, y)) { - this.ctx.fillRect(x * this.scale, y * this.scale, this.scale, this.scale); + for (let x = 0; x < this.width; x++) { + for (let y = 0; y < this.height; y++) { + if (this.model?.isPrime(this.scrollX + x, this.scrollY + y)) { + this.ctx.fillRect(x, y, 1, 1); } } }