diff --git a/frontend/src/sections/Editor2D.svelte b/frontend/src/sections/Editor2D.svelte index 0a2a8c8..59d9f06 100644 --- a/frontend/src/sections/Editor2D.svelte +++ b/frontend/src/sections/Editor2D.svelte @@ -13,34 +13,72 @@ let offsetX: number let offsetY: number + let zoom: number = 1.0 - $: ((animation, frame, layer, img, refresh) => { - setTimeout(() => { - redraw() - }, 0) - })(animation, frame, layer, img, refresh) - - let canvas: HTMLCanvasElement - function redraw() { - if (!canvas) return - let ctx = canvas.getContext('2d') + let mouseX: number = 0 + let mouseY: number = 0 + let mousePixelX: number = 0 + let mousePixelY: number = 0 + + let rootCanvas: HTMLCanvasElement + let overlayCanvas: HTMLCanvasElement = document.createElement('canvas') + let canvas: HTMLCanvasElement = document.createElement('canvas') + + let overlayDirty: boolean = true + let canvasDirty: boolean = true + + // check resizes canvases, etc. + function check() { + let ctx = rootCanvas.getContext('2d') if (!ctx) return - let computedSize = getComputedStyle(canvas) - if (canvas.width !== parseInt(computedSize.width) || canvas.height !== parseInt(computedSize.height)) { - canvas.width = parseInt(computedSize.width) - canvas.height = parseInt(computedSize.height) + let computedSize = getComputedStyle(rootCanvas) + if (rootCanvas.width !== parseInt(computedSize.width) || rootCanvas.height !== parseInt(computedSize.height)) { + rootCanvas.width = parseInt(computedSize.width) + rootCanvas.height = parseInt(computedSize.height) + canvas.width = rootCanvas.width + canvas.height = rootCanvas.height + overlayCanvas.width = rootCanvas.width + overlayCanvas.height = rootCanvas.height + overlayDirty = true + canvasDirty = true } - if (offsetX === undefined || offsetY === undefined) { // Adjust offset to center image on first LOAD. - offsetX = canvas.width/2 - img.width/2 - offsetY = canvas.height/2 - img.height/2 + offsetX = rootCanvas.width/2 - img.width/2 + offsetY = rootCanvas.height/2 - img.height/2 } + } + + function draw() { + if (!rootCanvas) return + check() + if (canvasDirty) { + drawCanvas() + canvasDirty = false + } + if (overlayDirty) { + drawOverlay() + overlayDirty = false + } + let ctx = rootCanvas.getContext('2d') + if (!ctx) return + ctx.clearRect(0, 0, rootCanvas.width, rootCanvas.height) + ctx.drawImage(canvas, 0, 0) + ctx.drawImage(overlayCanvas, 0, 0) + } + + function drawCanvas() { + let ctx = canvas.getContext('2d') + if (!ctx) return ctx.fillStyle = '#111111' ctx.fillRect(0, 0, canvas.width, canvas.height) // Draw checkboard. + ctx.save() + ctx.imageSmoothingEnabled = false + ctx.scale(zoom, zoom) + //ctx.transform(1, 0, 0, 1, -img.width/2, -img.height/2) { ctx.beginPath() ctx.fillStyle = '#888888' @@ -68,6 +106,28 @@ // TODO: Draw the current layer of the current frame. ctx.drawImage(img, offsetX, offsetY) + ctx.restore() + } + + function drawOverlay() { + if (!overlayCanvas) return + let ctx = overlayCanvas.getContext('2d') + if (!ctx) return + ctx.clearRect(0, 0, overlayCanvas.width, overlayCanvas.height) + ctx.save() + ctx.translate(0.5, 0.5) + // Draw a cursor. + { + ctx.beginPath() + ctx.strokeStyle = '#ff0000' + ctx.lineWidth = 1 + + ctx.rect(offsetX*zoom+mousePixelX*zoom, offsetY*zoom+mousePixelY*zoom, 1*zoom, 1*zoom) + + ctx.rect(mouseX, mouseY, 1, 1) + ctx.stroke() + } + ctx.restore() } function capOffset() { @@ -88,6 +148,15 @@ let x: number = 0 let y: number = 0 + node.addEventListener('mouseenter', (e: MouseEvent) => { + // hide cursor + document.body.style.cursor = 'none' + }) + node.addEventListener('mouseleave', (e: MouseEvent) => { + // show cursor + document.body.style.cursor = 'default' + }) + node.addEventListener('mousedown', (e: MouseEvent) => { buttons.add(e.button) x = e.clientX @@ -95,6 +164,21 @@ }) node.addEventListener('wheel', (e: WheelEvent) => { + if (e.ctrlKey) { + if (e.deltaY < 0) { + zoom *= 2 + } else if (e.deltaY > 0) { + zoom /= 2 + } + if (zoom > 1) { + zoom = Math.round(zoom) + } + + capOffset() + canvasDirty = true + overlayDirty = true + return + } if (e.deltaY < 0) { if (e.shiftKey) { offsetX-- @@ -109,10 +193,22 @@ } } capOffset() - redraw() + canvasDirty = true + overlayDirty = true }) window.addEventListener('mousemove', (e: MouseEvent) => { + if (!canvas) return + // Get mouse position relative to canvas. + { + let rect = canvas.getBoundingClientRect() + mouseX = e.offsetX - rect.left + mouseY = e.offsetY - rect.top + mousePixelX = Math.floor(mouseX / zoom - offsetX) + mousePixelY = Math.floor(mouseY / zoom - offsetY) + overlayDirty = true + } + if (buttons.size === 0) return let dx = e.clientX - x let dy = e.clientY - y @@ -123,14 +219,15 @@ console.log('0') } if (buttons.has(1)) { - offsetX += dx - offsetY += dy + offsetX += dx / zoom + offsetY += dy / zoom capOffset() + canvasDirty = true + overlayDirty = true } if (buttons.has(2)) { console.log('2') } - redraw() }) window.addEventListener('mouseup', (e: MouseEvent) => { @@ -139,12 +236,20 @@ } onMount(() => { - redraw() + let frameID: number = 0 + let frameDraw = () => { + draw() + frameID = window.requestAnimationFrame(frameDraw) + } + frameID = window.requestAnimationFrame(frameDraw) + return () => { + window.cancelAnimationFrame(frameID) + } })
- e.preventDefault()}> + e.preventDefault()}>
\ No newline at end of file