2022-04-07 21:51:09 +02:00
|
|
|
import {Model} from "./Model";
|
|
|
|
|
2022-04-08 12:56:50 +02:00
|
|
|
|
2022-04-07 21:51:09 +02:00
|
|
|
/**
|
|
|
|
* Paints prime numbers on a grid according to some `Model`.
|
|
|
|
*
|
|
|
|
* A `Painter` keeps track of the HTML side and determines which part of the `Model` is currently visible to the user.
|
|
|
|
*/
|
|
|
|
export class Painter {
|
|
|
|
/**
|
|
|
|
* The canvas to draw on.
|
|
|
|
*
|
|
|
|
* @private
|
|
|
|
*/
|
|
|
|
private readonly canvas: HTMLCanvasElement;
|
|
|
|
/**
|
|
|
|
* The context of `canvas` to draw on.
|
|
|
|
*
|
|
|
|
* @private
|
|
|
|
*/
|
|
|
|
private readonly ctx: CanvasRenderingContext2D;
|
|
|
|
/**
|
|
|
|
* `true` if and only if this painter is currently in a draw loop.
|
|
|
|
*
|
|
|
|
* @see #startDrawLoop
|
|
|
|
* @private
|
|
|
|
*/
|
|
|
|
private isDrawing: boolean = false;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* The model that determines where primes are painted.
|
|
|
|
*/
|
|
|
|
public model: Model | undefined;
|
2022-04-08 12:56:50 +02:00
|
|
|
|
2022-04-07 21:51:09 +02:00
|
|
|
/**
|
|
|
|
* The scale to draw prime numbers at.
|
|
|
|
*/
|
2022-04-08 12:56:50 +02:00
|
|
|
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;
|
2022-04-17 17:44:15 +02:00
|
|
|
const max = this.model?.maxX != null ? Math.max(0, this.model.maxX - this.width) : null;
|
2022-04-08 12:56:50 +02:00
|
|
|
|
|
|
|
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;
|
2022-04-17 17:44:15 +02:00
|
|
|
const max = this.model?.maxY != null ? Math.max(0, this.model.maxY - this.height) : null;
|
2022-04-08 12:56:50 +02:00
|
|
|
|
|
|
|
this._scrollY = min != null && scrollY < min ? min : (max != null && scrollY > max ? max : scrollY);
|
|
|
|
}
|
2022-04-07 21:51:09 +02:00
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Constructs a new `Painter`.
|
|
|
|
*
|
|
|
|
* @param canvas the canvas to be used by this painter
|
|
|
|
*/
|
|
|
|
constructor(canvas: HTMLCanvasElement) {
|
|
|
|
this.canvas = canvas;
|
|
|
|
this.ctx = canvas.getContext("2d")!;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* The width of this painter in terms of grid squares.
|
|
|
|
*
|
|
|
|
* @private
|
|
|
|
*/
|
|
|
|
private get width(): number {
|
2022-04-08 12:56:50 +02:00
|
|
|
return Math.floor(this.ctx.canvas.clientWidth / this.scale) - 1;
|
2022-04-07 21:51:09 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* The height of this painter in terms of grid squares.
|
|
|
|
*
|
|
|
|
* @private
|
|
|
|
*/
|
|
|
|
private get height(): number {
|
2022-04-08 12:56:50 +02:00
|
|
|
return Math.floor(this.ctx.canvas.clientHeight / this.scale) - 1;
|
2022-04-07 21:51:09 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Starts repeatedly invoking `draw` onto the `canvas`.
|
|
|
|
*/
|
|
|
|
public startDrawLoop(): void {
|
|
|
|
if (this.isDrawing) return;
|
|
|
|
|
|
|
|
const cb = () => {
|
|
|
|
if (!this.isDrawing) return;
|
|
|
|
|
|
|
|
this.draw();
|
|
|
|
window.requestAnimationFrame(cb);
|
|
|
|
};
|
|
|
|
|
|
|
|
this.isDrawing = true;
|
|
|
|
window.requestAnimationFrame(cb);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Stops the draw loop started by `startDrawLoop`.
|
|
|
|
*/
|
|
|
|
public stopDrawLoop(): void {
|
|
|
|
this.isDrawing = false;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Draws on the canvas once.
|
|
|
|
*/
|
|
|
|
draw(): void {
|
|
|
|
this.clearCanvas();
|
2022-04-08 12:56:50 +02:00
|
|
|
|
|
|
|
this.ctx.save();
|
|
|
|
this.ctx.scale(this.scale, this.scale);
|
|
|
|
this.ctx.translate(0.2, 0.2);
|
2022-04-07 21:51:09 +02:00
|
|
|
this.drawPrimes();
|
2022-04-17 17:44:15 +02:00
|
|
|
this.drawBoundaries();
|
2022-04-08 12:56:50 +02:00
|
|
|
this.ctx.restore();
|
2022-04-07 21:51:09 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Clears the entire canvas.
|
|
|
|
*
|
|
|
|
* @private
|
|
|
|
*/
|
|
|
|
private clearCanvas(): void {
|
|
|
|
this.ctx.save();
|
|
|
|
this.ctx.fillStyle = "#ffffff";
|
|
|
|
this.ctx.fillRect(0, 0, this.canvas.clientWidth, this.canvas.clientHeight);
|
|
|
|
this.ctx.restore();
|
|
|
|
}
|
|
|
|
|
2022-04-17 17:44:15 +02:00
|
|
|
/**
|
|
|
|
* Draws the boundaries of the `Model`.
|
|
|
|
*
|
|
|
|
* @private
|
|
|
|
*/
|
2022-04-08 12:56:50 +02:00
|
|
|
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();
|
|
|
|
}
|
|
|
|
|
2022-04-07 21:51:09 +02:00
|
|
|
/**
|
|
|
|
* Draws prime numbers on the canvas.
|
|
|
|
*
|
|
|
|
* @private
|
|
|
|
*/
|
|
|
|
private drawPrimes(): void {
|
|
|
|
this.ctx.save();
|
|
|
|
this.ctx.fillStyle = "#0033cc"; // TODO: Get color programmatically
|
2022-04-08 12:56:50 +02:00
|
|
|
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);
|
2022-04-07 21:51:09 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
this.ctx.restore();
|
|
|
|
}
|
|
|
|
}
|