/** * File to add generic rendering functions and shared utilities */ const _tool = { /** * Draws a reticle used for image generation * * @param {BoundingBox} bb The bounding box of the reticle (world space) * @param {string} tool Name of the tool to diplay * @param {{w: number, h: number}} resolution Resolution of generation to display * @param {object} style Styles to use for rendering the reticle * @param {string} [style.sizeTextStyle = "#FFF5"] Style of the text for diplaying the bounding box size. * @param {string} [style.genSizeTextStyle = "#FFF5"] Style of the text for diplaying generation size * @param {string} [style.toolTextStyle = "#FFF5"] Style of the text for the tool name * @param {number} [style.reticleWidth = 1] Width of the line of the reticle * @param {string} [style.reticleStyle] Style of the line of the reticle * * @returns A function that erases this reticle drawing */ _reticle_draw(bb, tool, resolution, style = {}) { defaultOpt(style, { sizeTextStyle: "#FFF5", genSizeTextStyle: "#FFF5", toolTextStyle: "#FFF5", reticleWidth: 1, reticleStyle: global.hasActiveInput ? "#BBF" : "#FFF", }); const bbvp = BoundingBox.fromStartEnd( viewport.canvasToView(bb.tl), viewport.canvasToView(bb.br) ); uiCtx.save(); // Draw targeting square reticle thingy cursor uiCtx.lineWidth = style.reticleWidth; uiCtx.strokeStyle = style.reticleStyle; uiCtx.strokeRect(bbvp.x, bbvp.y, bbvp.w, bbvp.h); // Origin is middle of the frame uiCtx.font = `bold 20px Open Sans`; // Draw Tool Name if (bb.h > 40) { const xshrink = Math.min( 1, (bbvp.w - 20) / uiCtx.measureText(tool).width ); uiCtx.font = `bold ${20 * xshrink}px Open Sans`; uiCtx.textAlign = "left"; uiCtx.fillStyle = style.toolTextStyle; uiCtx.fillText(tool, bbvp.x + 10, bbvp.y + 10 + 20 * xshrink, bb.w); } // Draw width and height { // Render Cursor Width uiCtx.textAlign = "center"; uiCtx.fillStyle = style.sizeTextStyle; uiCtx.translate(bbvp.x + bbvp.w / 2, bbvp.y + bbvp.h / 2); const xshrink = Math.min( 1, (bbvp.w - 30) / uiCtx.measureText(`${bb.w}px`).width ); const yshrink = Math.min( 1, (bbvp.h - 30) / uiCtx.measureText(`${bb.h}px`).width ); uiCtx.font = `bold ${20 * xshrink}px Open Sans`; uiCtx.fillText(`${bb.w}px`, 0, bbvp.h / 2 - 10 * xshrink, bb.w); // Render Generation Width uiCtx.fillStyle = style.genSizeTextStyle; uiCtx.font = `bold ${10 * xshrink}px Open Sans`; if (bb.w !== resolution.w) uiCtx.fillText(`${resolution.w}px`, 0, bbvp.h / 2 - 30 * xshrink, bb.h); // Render Cursor Height uiCtx.rotate(-Math.PI / 2); uiCtx.fillStyle = style.sizeTextStyle; uiCtx.font = `bold ${20 * yshrink}px Open Sans`; uiCtx.fillText(`${bb.h}px`, 0, bbvp.w / 2 - 10 * yshrink, bb.h); // Render Generation Height uiCtx.fillStyle = style.genSizeTextStyle; uiCtx.font = `bold ${10 * yshrink}px Open Sans`; if (bb.h !== resolution.h) uiCtx.fillText(`${resolution.h}px`, 0, bbvp.w / 2 - 30 * xshrink, bb.h); uiCtx.restore(); } return () => { uiCtx.save(); uiCtx.clearRect(bbvp.x - 64, bbvp.y - 64, bbvp.w + 128, bbvp.h + 128); uiCtx.restore(); }; }, /** * Draws a generic crosshair cursor at the specified location * * @param {number} x X world coordinate of the cursor * @param {number} y Y world coordinate of the cursor * @param {object} style Style of the lines of the cursor * @param {string} [style.width = 3] Line width of the lines of the cursor * @param {string} [style.style] Stroke style of the lines of the cursor * * @returns A function that erases this cursor drawing */ _cursor_draw(x, y, style = {}) { defaultOpt(style, { width: 3, style: global.hasActiveInput ? "#BBF5" : "#FFF5", }); const vpc = viewport.canvasToView(x, y); // Draw current cursor location uiCtx.lineWidth = style.width; uiCtx.strokeStyle = style.style; uiCtx.beginPath(); uiCtx.moveTo(vpc.x, vpc.y + 10); uiCtx.lineTo(vpc.x, vpc.y - 10); uiCtx.moveTo(vpc.x + 10, vpc.y); uiCtx.lineTo(vpc.x - 10, vpc.y); uiCtx.stroke(); return () => { uiCtx.clearRect(vpc.x - 15, vpc.y - 15, vpc.x + 30, vpc.y + 30); }; }, /** * Creates generic handlers for dealing with draggable selection areas * * @param {object} state State of the tool * @param {boolean} state.snapToGrid Whether the cursor should snap to the grid * @param {() => void} [state.redraw] Function to redraw the cursor * @returns */ _draggable_selection(state) { const selection = { _inside: false, _dirty_bb: true, _cached_bb: null, _selected: null, /** * If the cursor is cursor is currently inside the selection */ get inside() { return this._inside; }, /** * Get intermediate selection object */ get selected() { return this._selected; }, /** * If the selection exists */ get exists() { return !!this._selected; }, /** * Gets the selection bounding box */ get bb() { if (this._dirty_bb && this._selected) { this._cached_bb = BoundingBox.fromStartEnd( this._selected.start, this._selected.now ); this._dirty_bb = false; } return this._selected && this._cached_bb; }, /** * When the cursor enters the selection */ onenter: new Observer(), /** * When the cursor leaves the selection */ onleave: new Observer(), // Utility methods deselect() { if (this.inside) { this._inside = false; this.onleave.emit({evn: null}); } this._selected = null; }, // Dragging handlers /** * Drag start event handler * * @param {Point} evn Drag start event */ dragstartcb(evn) { const x = state.snapToGrid ? evn.ix + snap(evn.ix, 0, 64) : evn.ix; const y = state.snapToGrid ? evn.iy + snap(evn.iy, 0, 64) : evn.iy; this._selected = {start: {x, y}, now: {x, y}}; this._dirty_bb = true; }, /** * Drag event handler * * @param {Point} evn Drag event */ dragcb(evn) { const x = state.snapToGrid ? evn.x + snap(evn.x, 0, 64) : evn.x; const y = state.snapToGrid ? evn.y + snap(evn.y, 0, 64) : evn.y; if (x !== this._selected.now.x || y !== this._selected.now.y) { this._selected.now = {x, y}; this._dirty_bb = true; } }, /** * Drag end event handler * * @param {Point} evn Drag end event */ dragendcb(evn) { const x = state.snapToGrid ? evn.x + snap(evn.x, 0, 64) : evn.x; const y = state.snapToGrid ? evn.y + snap(evn.y, 0, 64) : evn.y; this._selected.now = {x, y}; this._dirty_bb = true; if ( this._selected.start.x === this._selected.now.x || this._selected.start.y === this._selected.now.y ) { this.deselect(); } }, /** * Mouse move event handler * * @param {Point} evn Mouse move event */ smousemovecb(evn) { if (!this._selected || !this.bb.contains(evn.x, evn.y)) { if (this.inside) { this._inside = false; this.onleave.emit({evn}); } } else { if (!this.inside) { this._inside = true; this.onenter.emit({evn}); } } }, }; return selection; }, };