import paper from '@scratch/paper'; import {getRaster} from '../layer'; import {forEachLinePoint, getBrushMark} from '../bitmap'; import {createCanvas, getGuideLayer} from '../layer'; import {ART_BOARD_WIDTH, ART_BOARD_HEIGHT} from '../view'; /** * Tool for drawing lines with the bitmap brush. */ class LineTool extends paper.Tool { /** * @param {!function} onUpdateImage A callback to call when the image visibly changes */ constructor (onUpdateImage) { super(); this.onUpdateImage = onUpdateImage; // We have to set these functions instead of just declaring them because // paper.js tools hook up the listeners in the setter functions. this.onMouseMove = this.handleMouseMove; this.onMouseDown = this.handleMouseDown; this.onMouseDrag = this.handleMouseDrag; this.onMouseUp = this.handleMouseUp; this.colorState = null; this.active = false; this.startPoint = null; this.cursorPreview = null; // Raster to which to draw this.drawTarget = null; } setColor (color) { this.color = color; this.tmpCanvas = getBrushMark(this.size, this.color); } setLineSize (size) { // For performance, make sure this is an integer this.size = Math.max(1, ~~size); this.tmpCanvas = getBrushMark(this.size, this.color); } // Draw a brush mark at the given point draw (x, y) { const roundedUpRadius = Math.ceil(this.size / 2); this.drawTarget.drawImage(this.tmpCanvas, new paper.Point(~~x - roundedUpRadius, ~~y - roundedUpRadius)); } updateCursorIfNeeded () { if (!this.size) { return; } // The cursor preview was unattached from the view by an outside process, // such as changing costumes or undo. if (this.cursorPreview && !this.cursorPreview.parent) { this.cursorPreview = null; } if (!this.cursorPreview || !(this.lastSize === this.size && this.lastColor === this.color)) { if (this.cursorPreview) { this.cursorPreview.remove(); } this.tmpCanvas = getBrushMark(this.size, this.color); this.cursorPreview = new paper.Raster(this.tmpCanvas); this.cursorPreview.guide = true; this.cursorPreview.parent = getGuideLayer(); this.cursorPreview.data.isHelperItem = true; } this.lastSize = this.size; this.lastColor = this.color; } handleMouseMove (event) { this.updateCursorIfNeeded(); this.cursorPreview.position = new paper.Point(~~event.point.x, ~~event.point.y); } handleMouseDown (event) { if (event.event.button > 0) return; // only first mouse button this.active = true; if (this.cursorPreview) this.cursorPreview.remove(); const tmpCanvas = createCanvas(); this.drawTarget = new paper.Raster(tmpCanvas); this.drawTarget.parent = getGuideLayer(); this.drawTarget.guide = true; this.drawTarget.locked = true; this.drawTarget.position = getRaster().position; this.draw(event.point.x, event.point.y); this.startPoint = event.point; } handleMouseDrag (event) { if (event.event.button > 0 || !this.active) return; // only first mouse button // Clear const context = this.drawTarget.canvas.getContext('2d'); context.clearRect(0, 0, ART_BOARD_WIDTH, ART_BOARD_HEIGHT); forEachLinePoint(this.startPoint, event.point, this.draw.bind(this)); } handleMouseUp (event) { if (event.event.button > 0 || !this.active) return; // only first mouse button this.drawTarget.remove(); this.drawTarget = getRaster(); forEachLinePoint(this.startPoint, event.point, this.draw.bind(this)); this.drawTarget = null; this.onUpdateImage(); this.lastPoint = null; this.active = false; this.updateCursorIfNeeded(); this.cursorPreview.position = new paper.Point(~~event.point.x, ~~event.point.y); } deactivateTool () { this.active = false; this.tmpCanvas = null; if (this.cursorPreview) { this.cursorPreview.remove(); this.cursorPreview = null; } } } export default LineTool;