diff --git a/packages/mui/src/components/ContentEditor/ContentEditor.tsx b/packages/mui/src/components/ContentEditor/ContentEditor.tsx index 1b2b9c50..e82029e0 100644 --- a/packages/mui/src/components/ContentEditor/ContentEditor.tsx +++ b/packages/mui/src/components/ContentEditor/ContentEditor.tsx @@ -598,7 +598,9 @@ const ContentEditor = ({ setCallback(sm, "record_mouse_wheel", (args) => { if ( internalState.rec && - (internalState.act === "erase" || internalState.act === "paint") + (internalState.act === "erase" || + internalState.act === "paint" || + internalState.act === "token") ) { sm.transition("done"); const e: WheelEvent = args[0] as WheelEvent; diff --git a/packages/mui/src/utils/contentworker.ts b/packages/mui/src/utils/contentworker.ts index da141caf..cf316506 100644 --- a/packages/mui/src/utils/contentworker.ts +++ b/packages/mui/src/utils/contentworker.ts @@ -18,6 +18,7 @@ import { copyRect, zoomFromViewport, adjustImageToViewport, + adjustTokenDimensions, } from "./geometry"; import { Rect } from "@micahg/tbltp-common"; @@ -72,6 +73,11 @@ let green = "0"; let blue = "0"; let brush = MIN_BRUSH; +let _token_dw = 0; +let _token_dh = 0; +const _token_delta = MIN_BRUSH; +let vamp: ImageBitmap; + function trimPanning() { if (_img.x <= 0) _img.x = 0; if (_img.y <= 0) _img.y = 0; @@ -124,6 +130,30 @@ function renderImage( ctx.restore(); } +function renderToken(x: number, y: number) { + overlayCtx.save(); + // may be best to not translate since we're scaling + overlayCtx.translate(-vamp.width / 2, -vamp.height / 2); + overlayCtx.drawImage( + vamp, + // source (should always just be source dimensions) + 0, + 0, + vamp.width, + vamp.height, + // destination (adjust according to scale) + x, + y, + _token_dw, + _token_dh, + // vamp.width, + // vamp.height, + ); + + overlayCtx.restore(); + return; +} + function calculateViewport() { // REMEMBER THIS METHOD UPDATES THE _vp and the _img adjustImageToViewport( @@ -139,6 +169,18 @@ function calculateViewport() { return; } +function calculateToken(delta: number) { + [_token_dw, _token_dh] = adjustTokenDimensions( + delta, + vamp.width, + vamp.height, + _token_dw, + _token_dh, + overlayCtx.canvas.width, + overlayCtx.canvas.height, + ); +} + /** * Given a desired viewport, set our current viewport accordingly, set the zoom, * and then center the request viewport within our screen, extending its short @@ -400,6 +442,13 @@ function animateBrush() { requestAnimationFrame(() => animateBrush()); } +function animateToken() { + if (!recording) return; + renderImage(overlayCtx, imageCanvasses, _angle); + renderToken(startX, startY); + requestAnimationFrame(() => animateToken()); +} + function animateSelection() { if (!recording) return; if (selecting) { @@ -477,6 +526,11 @@ async function update(values: TableUpdate) { } try { + try { + vamp = await loadImage("/vneven.png", values.bearer); + } catch (err) { + console.error(err); + } const [bgImg, ovImg] = await loadAllImages(values); if (!bgImg) return; @@ -509,7 +563,7 @@ async function update(values: TableUpdate) { // eslint-disable-next-line no-restricted-globals self.onmessage = async (evt) => { - // console.log(evt.data.cmd); + console.log(evt.data.cmd); switch (evt.data.cmd) { case "init": { // ensure the background canvas is valid @@ -542,7 +596,6 @@ self.onmessage = async (evt) => { // indicate if things should be rendered on top of the overlay _things_on_top_of_overlay = !!evt.data.thingsOnTop; - break; } case "update": { @@ -635,6 +688,36 @@ self.onmessage = async (evt) => { } break; } + case "token": { + startX = evt.data.x; + startY = evt.data.y; + // here we do not turn recording on or off (thats handled by the move/record/end events elsewhere) + // also "recording" is not "painting" TODO MICAH COME BACK HERE AND CONFIRM ITS ABOUT CANVAS ANIMATION + // where we do not paint (painting is separate from drawing the selection or the translucent brush) + if (evt.data.buttons === 0) { + // here we don't draw BUT if you look at animateBrush, you'll see that we'll just repaint the + // overlay and then render the translucent brush + if (!recording) { + overlayCtx.fillStyle = GUIDE_FILL; + recording = true; + // _token_adjust = 0; + _token_dw = vamp.width; + _token_dh = vamp.height; + requestAnimationFrame(animateToken); + } + } else if (evt.data.buttons === 1) { + // here however we just update the canvas with the actual brush. It seems that the fill call + // in renderBrush will force the canvas to update so there isn't much point in using animation + // frames + if (recording) { + recording = false; + overlayCtx.fillStyle = `rgba(${red}, ${green}, ${blue}, ${opacity})`; + fullCtx.fillStyle = `rgba(${red}, ${green}, ${blue}, ${opacity})`; + } + renderBrush(evt.data.x, evt.data.y, brush); + } + break; + } case "move": case "select": case "record": { @@ -676,6 +759,7 @@ self.onmessage = async (evt) => { case "end_paint": { recording = false; panning = false; + brush = MIN_BRUSH; storeOverlay(); renderImage(overlayCtx, imageCanvasses, _angle); break; @@ -772,10 +856,12 @@ self.onmessage = async (evt) => { break; } case "brush_inc": { + calculateToken(_token_delta); brush += MIN_BRUSH; break; } case "brush_dec": { + calculateToken(-_token_delta); brush -= brush > MIN_BRUSH ? MIN_BRUSH : 0; break; } diff --git a/packages/mui/src/utils/geometry.ts b/packages/mui/src/utils/geometry.ts index c7721222..c8f99547 100644 --- a/packages/mui/src/utils/geometry.ts +++ b/packages/mui/src/utils/geometry.ts @@ -318,3 +318,38 @@ export function equalRects(r1: Rect, r2: Rect): boolean { r1.height === r2.height ); } + +/** + * @param delta the how much to add (or subtract if negative) to the dimensions + * @param ow original (and minimum) width of token + * @param oh original (and minimum) height of token + * @param cw: current width of token + * @param ch: current height of token + * @param mw: max width + * @param mh: max height + * @return array (width and height) of two numbers + */ +export function adjustTokenDimensions( + delta: number, + ow: number, + oh: number, + cw: number, + ch: number, + mw: number, + mh: number, +): number[] { + let dh, dw; + if (ow > oh) { + dw = cw + delta; + dh = Math.round((dw * oh) / ow); + } else { + dh = ch + delta; + dw = Math.round((dh * ow) / oh); + } + // don't go passed that max width and height + if (dw < mw && dh < mh && dw >= ow && dh >= oh) { + cw = dw; + ch = dh; + } + return [cw, ch]; +}