diff --git a/src/components/HelpModal.jsx b/src/components/HelpModal.jsx index 2b6f705..90b6fdf 100644 --- a/src/components/HelpModal.jsx +++ b/src/components/HelpModal.jsx @@ -37,7 +37,8 @@ const HelpModal = () => (

Drag mouse to move

Scroll mouse wheel to zoom

Click middle mouse button to current hovering color

-

Hold shift for placing while moving mouse

+

Hold left shift for placing while moving mouse

+

Hold right shift for placing while moving mouse according to historical view

Pinch to zoom (on touch devices)

Pan to move (on touch devices)

Click or tap to place a pixel

diff --git a/src/controls/PixelPainterControls.js b/src/controls/PixelPainterControls.js index 50cd1e7..4c1efee 100644 --- a/src/controls/PixelPainterControls.js +++ b/src/controls/PixelPainterControls.js @@ -61,8 +61,13 @@ class PixelPlainterControls { this.isMultiTab = false; // on touch: timeout to detect long-press this.tapTimeout = null; - // if we are shift-hold-painting - this.holdPainting = false; + /* + * if we are shift-hold-painting + * 0: no + * 1: left shift + * 2: right shift + */ + this.holdPainting = 0; // if we are waiting before placeing pixel via holdPainting again this.coolDownDelta = false; @@ -162,14 +167,20 @@ class PixelPlainterControls { return null; } - static placePixel(store, renderer, cell) { + /* + * place pixel + * either with given colorIndex or with selected color if none is given + */ + static placePixel(store, renderer, cell, colorIndex = null) { const state = store.getState(); const { autoZoomIn } = state.gui; const { scale, isHistoricalView, - selectedColor, } = state.canvas; + const selectedColor = (colorIndex === null) + ? state.canvas.selectedColor + : colorIndex; if (isHistoricalView) return; @@ -393,16 +404,42 @@ class PixelPlainterControls { || y < -maxCoords || y >= maxCoords ) { if (hover) { - store.dispatch(unsetHover(screenCoor)); + store.dispatch(unsetHover()); } return; } if (!hover || hover[0] !== x || hover[1] !== y) { store.dispatch(setHover(screenCoor)); - } - if (this.holdPainting && !this.coolDownDelta) { - PixelPlainterControls.placePixel(store, this.renderer, screenCoor); + /* shift placing */ + if (!this.coolDownDelta) { + switch (this.holdPainting) { + case 1: { + /* left shift: from selected color */ + PixelPlainterControls.placePixel( + store, + this.renderer, + screenCoor, + ); + break; + } + case 2: { + /* right shift: from historical view */ + const colorIndex = this.renderer + .getColorIndexOfPixel(x, y, true); + if (colorIndex !== null) { + PixelPlainterControls.placePixel( + store, + this.renderer, + screenCoor, + colorIndex, + ); + } + break; + } + default: + } + } } } } @@ -412,7 +449,7 @@ class PixelPlainterControls { viewport.style.cursor = 'auto'; store.dispatch(unsetHover()); store.dispatch(onViewFinishChange()); - this.holdPainting = false; + this.holdPainting = 0; this.clearTabTimeout(); } @@ -448,7 +485,7 @@ class PixelPlainterControls { switch (event.key) { case 'Shift': case 'CapsLock': - this.holdPainting = false; + this.holdPainting = 0; break; default: } @@ -496,19 +533,28 @@ class PixelPlainterControls { if (event.key === 'Control') { // ctrl const clrIndex = this.renderer.getColorIndexOfPixel(...hover); - if (clrIndex !== null) { - store.dispatch(selectColor(clrIndex)); - } + store.dispatch(selectColor(clrIndex)); return; } if (event.location === KeyboardEvent.DOM_KEY_LOCATION_LEFT) { // left shift - this.holdPainting = true; + this.holdPainting = 1; PixelPlainterControls.placePixel(store, this.renderer, hover); return; } if (event.location === KeyboardEvent.DOM_KEY_LOCATION_RIGHT) { // right shift + this.holdPainting = 2; + const colorIndex = this.renderer + .getColorIndexOfPixel(...hover, true); + if (colorIndex !== null) { + PixelPlainterControls.placePixel( + store, + this.renderer, + hover, + colorIndex, + ); + } } } break; diff --git a/src/ui/ChunkLoader2D.js b/src/ui/ChunkLoader2D.js index d0cabfe..9964a90 100644 --- a/src/ui/ChunkLoader2D.js +++ b/src/ui/ChunkLoader2D.js @@ -77,6 +77,46 @@ class ChunkLoader { ); } + /* + * Get color of pixel in current historical view + * @param x, y world coordiantes of pixel + * @return ColorIndex or null if chunks not loaded or historical view not set + */ + getHistoricalIndexOfPixel( + x: number, + y: number, + ) { + const state: State = this.store.getState(); + const { canvasSize, historicalDate, historicalTime } = state.canvas; + if (!historicalDate) { + return null; + } + const [cx, cy] = getChunkOfPixel(canvasSize, x, y); + const px = getCellInsideChunk(canvasSize, [x, y]); + const curTime = Date.now(); + + if (!historicalTime || historicalTime !== '0000') { + // eslint-disable-next-line max-len + const incrementialChunkKey = `${historicalDate}${historicalTime}:${cx}:${cy}`; + const incrementialChunk = this.chunks.get(incrementialChunkKey); + if (incrementialChunk) { + const incrementialColor = incrementialChunk.getColorIndex(px); + incrementialChunk.timestamp = curTime; + if (incrementialColor !== null) { + return incrementialColor; + } + } + } + + const chunkKey = `${historicalDate}:${cx}:${cy}`; + const chunk = this.chunks.get(chunkKey); + if (!chunk) { + return null; + } + chunk.timestamp = curTime; + return chunk.getColorIndex(px); + } + /* * preLoad chunks by generating them out of * available lower zoomlevel chunks diff --git a/src/ui/Renderer2D.js b/src/ui/Renderer2D.js index 5213e17..96d277a 100644 --- a/src/ui/Renderer2D.js +++ b/src/ui/Renderer2D.js @@ -146,8 +146,10 @@ class Renderer { } } - getColorIndexOfPixel(cx, cy) { - return this.chunkLoader.getColorIndexOfPixel(cx, cy); + getColorIndexOfPixel(cx, cy, historical: boolean = false) { + return (historical) + ? this.chunkLoader.getHistoricalIndexOfPixel(cx, cy) + : this.chunkLoader.getColorIndexOfPixel(cx, cy); } updateScale(