Add mouse-based grid navigation

This commit is contained in:
Florine W. Dekker 2022-04-08 12:56:50 +02:00
parent 6638faec61
commit 0b6659729b
Signed by: FWDekker
GPG Key ID: D3DCFAA8A4560BE0
4 changed files with 148 additions and 37 deletions

View File

@ -38,15 +38,6 @@
<form id="gridSettings">
<label for="gridCols">Columns</label>
<input type="number" id="gridCols" min="1" value="100" />
<label for="gridScale">Scale</label>
<input type="number" id="gridScale" min="1" value="15" />
<label for="scrollX">Scroll X</label>
<input type="number" id="scrollX" min="0" value="0" />
<label for="scrollY">Scroll Y</label>
<input type="number" id="scrollY" min="0" value="0" />
</form>
</div>
</section>

View File

@ -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);
}
}
});
});

View File

@ -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);
}
}

View File

@ -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);
}
}
}