diff --git a/canvas.ts b/canvas.ts index 161be20..84d01db 100644 --- a/canvas.ts +++ b/canvas.ts @@ -1,15 +1,16 @@ /// - import { Constants } from "./geometry/constants.ts"; import { Vector } from "./geometry/vector.ts"; import { ZoomableDoodler } from "./zoomableCanvas.ts"; export const init = (opt: IDoodlerOptions, zoomable: boolean) => { - if (window.doodler) throw 'Doodler has already been initialized in this window' + if (window.doodler) { + throw "Doodler has already been initialized in this window"; + } window.doodler = zoomable ? new ZoomableDoodler(opt) : new Doodler(opt); window.doodler.init(); -} +}; export interface IDoodlerOptions { width: number; @@ -47,14 +48,14 @@ export class Doodler { height, canvas, bg, - framerate + framerate, }: IDoodlerOptions) { if (!canvas) { - canvas = document.createElement('canvas'); + canvas = document.createElement("canvas"); document.body.append(canvas); } - this.bg = bg || 'white'; + this.bg = bg || "white"; this.framerate = framerate || 60; canvas.width = width; @@ -62,15 +63,15 @@ export class Doodler { this._canvas = canvas; - const ctx = canvas.getContext('2d'); - if (!ctx) throw 'Unable to initialize Doodler: Canvas context not found'; + const ctx = canvas.getContext("2d"); + if (!ctx) throw "Unable to initialize Doodler: Canvas context not found"; this.ctx = ctx; } init() { - this._canvas.addEventListener('mousedown', e => this.onClick(e)); - this._canvas.addEventListener('mouseup', e => this.offClick(e)); - this._canvas.addEventListener('mousemove', e => this.onDrag(e)); + this._canvas.addEventListener("mousedown", (e) => this.onClick(e)); + this._canvas.addEventListener("mouseup", (e) => this.offClick(e)); + this._canvas.addEventListener("mousemove", (e) => this.onDrag(e)); this.startDrawLoop(); } @@ -98,11 +99,11 @@ export class Doodler { } deleteLayer(layer: layer) { - this.layers = this.layers.filter(l => l !== layer); + this.layers = this.layers.filter((l) => l !== layer); } moveLayer(layer: layer, index: number) { - let temp = this.layers.filter(l => l !== layer); + let temp = this.layers.filter((l) => l !== layer); temp = [...temp.slice(0, index), layer, ...temp.slice(index)]; @@ -119,7 +120,7 @@ export class Doodler { this.ctx.stroke(); } dot(at: Vector, style?: IStyle) { - this.setStyle({ ...style, weight: 1 }) + this.setStyle({ ...style, weight: 1 }); this.ctx.beginPath(); this.ctx.arc(at.x, at.y, style?.weight || 1, 0, Constants.TWO_PI); @@ -199,29 +200,66 @@ export class Doodler { drawImage(img: HTMLImageElement, at: Vector): void; drawImage(img: HTMLImageElement, at: Vector, w: number, h: number): void; drawImage(img: HTMLImageElement, at: Vector, w?: number, h?: number) { - w && h ? this.ctx.drawImage(img, at.x, at.y, w, h) : this.ctx.drawImage(img, at.x, at.y); + w && h + ? this.ctx.drawImage(img, at.x, at.y, w, h) + : this.ctx.drawImage(img, at.x, at.y); } - drawSprite(img: HTMLImageElement, spritePos: Vector, sWidth: number, sHeight: number, at: Vector, width: number, height: number) { - this.ctx.drawImage(img, spritePos.x, spritePos.y, sWidth, sHeight, at.x, at.y, width, height); + drawSprite( + img: HTMLImageElement, + spritePos: Vector, + sWidth: number, + sHeight: number, + at: Vector, + width: number, + height: number, + ) { + this.ctx.drawImage( + img, + spritePos.x, + spritePos.y, + sWidth, + sHeight, + at.x, + at.y, + width, + height, + ); } setStyle(style?: IStyle) { const ctx = this.ctx; - ctx.fillStyle = style?.color || style?.fillColor || 'black'; - ctx.strokeStyle = style?.color || style?.strokeColor || 'black'; + ctx.fillStyle = style?.color || style?.fillColor || "black"; + ctx.strokeStyle = style?.color || style?.strokeColor || "black"; ctx.lineWidth = style?.weight || 1; } + fillText(text: string, pos: Vector, maxWidth: number, style?: IStyle) { + this.setStyle(style); + // TODO: add text alignment to style + this.ctx.fillText(text, pos.x, pos.y, maxWidth); + } + strokeText(text: string, pos: Vector, maxWidth: number, style?: IStyle) { + this.setStyle(style); + // TODO: add text alignment to style + this.ctx.strokeText(text, pos.x, pos.y, maxWidth); + } + // Interaction mouseX = 0; mouseY = 0; - - registerDraggable(point: Vector, radius: number, style?: IStyle & { shape: 'square' | 'circle' }) { - if (this.draggables.find(d => d.point === point)) return; - const id = this.addUIElement('circle', point, radius, { fillColor: '#5533ff50', strokeColor: '#5533ff50' }) + registerDraggable( + point: Vector, + radius: number, + style?: IStyle & { shape: "square" | "circle" }, + ) { + if (this.draggables.find((d) => d.point === point)) return; + const id = this.addUIElement("circle", point, radius, { + fillColor: "#5533ff50", + strokeColor: "#5533ff50", + }); this.draggables.push({ point, radius, style, id }); } unregisterDraggable(point: Vector) { @@ -230,7 +268,7 @@ export class Doodler { this.removeUIElement(d.id); } } - this.draggables = this.draggables.filter(d => d.point !== point); + this.draggables = this.draggables.filter((d) => d.point !== point); } registerClickable(p1: Vector, p2: Vector, cb: () => void) { @@ -241,21 +279,27 @@ export class Doodler { this.clickables.push({ onClick: cb, - checkBound: (p) => p.y >= top && p.x >= left && p.y <= bottom && p.x <= right - }) + checkBound: (p) => + p.y >= top && p.x >= left && p.y <= bottom && p.x <= right, + }); } unregisterClickable(cb: () => void) { - this.clickables = this.clickables.filter(c => c.onClick !== cb); + this.clickables = this.clickables.filter((c) => c.onClick !== cb); } addDragEvents({ onDragEnd, onDragStart, onDrag, - point - }: { point: Vector, onDragEnd?: () => void, onDragStart?: () => void, onDrag?: (movement: { x: number, y: number }) => void }) { - const d = this.draggables.find(d => d.point === point); + point, + }: { + point: Vector; + onDragEnd?: () => void; + onDragStart?: () => void; + onDrag?: (movement: { x: number; y: number }) => void; + }) { + const d = this.draggables.find((d) => d.point === point); if (d) { d.onDragEnd = onDragEnd; d.onDragStart = onDragStart; @@ -264,7 +308,7 @@ export class Doodler { } onClick(e: MouseEvent) { - const mouse = new Vector(this.mouseX, this.mouseY) + const mouse = new Vector(this.mouseX, this.mouseY); for (const d of this.draggables) { if (d.point.dist(mouse) <= d.radius) { d.beingDragged = true; @@ -295,8 +339,8 @@ export class Doodler { // this.mouseX = e.clientX - rect.left; // this.mouseY = e.clientY - rect.top; - for (const d of this.draggables.filter(d => d.beingDragged)) { - d.point.add(e.movementX, e.movementY) + for (const d of this.draggables.filter((d) => d.beingDragged)) { + d.point.add(e.movementX, e.movementY); d.onDrag && d.onDrag({ x: e.movementX, y: e.movementY }); } } @@ -305,28 +349,44 @@ export class Doodler { uiElements: Map = new Map(); private uiDrawing: uiDrawing = { rectangle: (...args: any[]) => { - !args[3].noFill && this.fillRect(args[0], args[1], args[2], args[3]) - !args[3].noStroke && this.drawRect(args[0], args[1], args[2], args[3]) + !args[3].noFill && this.fillRect(args[0], args[1], args[2], args[3]); + !args[3].noStroke && this.drawRect(args[0], args[1], args[2], args[3]); }, square: (...args: any[]) => { - !args[2].noFill && this.fillSquare(args[0], args[1], args[2]) - !args[2].noStroke && this.drawSquare(args[0], args[1], args[2]) + !args[2].noFill && this.fillSquare(args[0], args[1], args[2]); + !args[2].noStroke && this.drawSquare(args[0], args[1], args[2]); }, circle: (...args: any[]) => { - !args[2].noFill && this.fillCircle(args[0], args[1], args[2]) - !args[2].noStroke && this.drawCircle(args[0], args[1], args[2]) + !args[2].noFill && this.fillCircle(args[0], args[1], args[2]); + !args[2].noStroke && this.drawCircle(args[0], args[1], args[2]); }, - } + }; private drawUI() { for (const [shape, ...args] of this.uiElements.values()) { - this.uiDrawing[shape].apply(null, args as []) + this.uiDrawing[shape].apply(null, args as []); } } - addUIElement(shape: 'rectangle', at: Vector, width: number, height: number, style?: IStyle): string; - addUIElement(shape: 'square', at: Vector, size: number, style?: IStyle): string; - addUIElement(shape: 'circle', at: Vector, radius: number, style?: IStyle): string; + addUIElement( + shape: "rectangle", + at: Vector, + width: number, + height: number, + style?: IStyle, + ): string; + addUIElement( + shape: "square", + at: Vector, + size: number, + style?: IStyle, + ): string; + addUIElement( + shape: "circle", + at: Vector, + radius: number, + style?: IStyle, + ): string; addUIElement(shape: keyof uiDrawing, ...args: any[]) { const id = crypto.randomUUID(); for (const arg of args) { @@ -358,21 +418,21 @@ interface IDrawable { type Draggable = { point: Vector; radius: number; - style?: IStyle & { shape: 'square' | 'circle' }; + style?: IStyle & { shape: "square" | "circle" }; beingDragged?: boolean; id: string; onDragStart?: () => void; onDragEnd?: () => void; - onDrag?: (dragDistance: { x: number, y: number }) => void; -} + onDrag?: (dragDistance: { x: number; y: number }) => void; +}; type Clickable = { onClick: () => void; checkBound: (p: Vector) => boolean; -} +}; type uiDrawing = { circle: () => void; square: () => void; rectangle: () => void; -} +};