go through store with moveUVW and make movement controls work,
experiment with touch controls
This commit is contained in:
parent
236e83694b
commit
a14b16247a
|
@ -4,7 +4,10 @@
|
||||||
|
|
||||||
import { persistStore } from 'redux-persist';
|
import { persistStore } from 'redux-persist';
|
||||||
|
|
||||||
import createKeyPressHandler from './controls/keypress';
|
import {
|
||||||
|
createKeyDownHandler,
|
||||||
|
createKeyUpHandler,
|
||||||
|
} from './controls/keypress';
|
||||||
import {
|
import {
|
||||||
initTimer,
|
initTimer,
|
||||||
urlChange,
|
urlChange,
|
||||||
|
@ -57,8 +60,10 @@ persistStore(store, {}, () => {
|
||||||
window.name = 'main';
|
window.name = 'main';
|
||||||
renderApp(document.getElementById('app'), store);
|
renderApp(document.getElementById('app'), store);
|
||||||
|
|
||||||
const onKeyPress = createKeyPressHandler(store);
|
const onKeyDown = createKeyDownHandler(store);
|
||||||
document.addEventListener('keydown', onKeyPress, false);
|
const onKeyUp = createKeyUpHandler(store);
|
||||||
|
document.addEventListener('keydown', onKeyDown, false);
|
||||||
|
document.addEventListener('keyup', onKeyUp, false);
|
||||||
|
|
||||||
// garbage collection
|
// garbage collection
|
||||||
setInterval(() => {
|
setInterval(() => {
|
||||||
|
|
|
@ -3,29 +3,33 @@
|
||||||
* Menu for WASD keys for mobile users
|
* Menu for WASD keys for mobile users
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React from 'react';
|
import React, {
|
||||||
import { useSelector, shallowEqual } from 'react-redux';
|
useCallback,
|
||||||
|
} from 'react';
|
||||||
|
import {
|
||||||
|
useSelector,
|
||||||
|
shallowEqual,
|
||||||
|
useDispatch,
|
||||||
|
} from 'react-redux';
|
||||||
|
|
||||||
import { getRenderer } from '../../ui/rendererFactory';
|
import {
|
||||||
|
setMoveU,
|
||||||
|
setMoveV,
|
||||||
|
setMoveW,
|
||||||
|
} from '../../store/actions';
|
||||||
|
|
||||||
const btnStyle = {
|
const btnStyle = {
|
||||||
fontSize: 34,
|
fontSize: 34,
|
||||||
};
|
};
|
||||||
|
|
||||||
function cancelMovement() {
|
|
||||||
const renderer = getRenderer();
|
|
||||||
renderer.controls.moveU = 0;
|
|
||||||
renderer.controls.moveV = 0;
|
|
||||||
renderer.controls.moveW = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
const MovementControls = () => {
|
const MovementControls = () => {
|
||||||
const [pencilEnabled, is3D] = useSelector((state) => [
|
const [holdPaint, is3D] = useSelector((state) => [
|
||||||
state.gui.pencilEnabled,
|
state.gui.holdPaint,
|
||||||
state.canvas.is3D,
|
state.canvas.is3D,
|
||||||
], shallowEqual);
|
], shallowEqual);
|
||||||
|
const dispatch = useDispatch();
|
||||||
|
|
||||||
if (!pencilEnabled && !is3D) {
|
if (!holdPaint && !is3D) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -37,25 +41,13 @@ const MovementControls = () => {
|
||||||
tabIndex={0}
|
tabIndex={0}
|
||||||
style={{
|
style={{
|
||||||
...btnStyle,
|
...btnStyle,
|
||||||
// left: 46,
|
|
||||||
left: 57,
|
left: 57,
|
||||||
// bottom: 128,
|
|
||||||
bottom: 139,
|
bottom: 139,
|
||||||
}}
|
}}
|
||||||
onMouseDown={() => {
|
onMouseDown={() => dispatch(setMoveV(-1))}
|
||||||
getRenderer().controls.moveV = -1;
|
onMouseUp={() => dispatch(setMoveV(0))}
|
||||||
}}
|
onTouchStart={() => dispatch(setMoveV(-1))}
|
||||||
onMouseUp={() => {
|
onTouchEnd={() => dispatch(setMoveV(0))}
|
||||||
getRenderer().controls.moveV = 0;
|
|
||||||
}}
|
|
||||||
onTouchStart={() => {
|
|
||||||
getRenderer().controls.moveV = -1;
|
|
||||||
}}
|
|
||||||
onTouchEnd={() => {
|
|
||||||
getRenderer().controls.moveV = 0;
|
|
||||||
}}
|
|
||||||
onTouchCancel={cancelMovement}
|
|
||||||
onMouseLeave={cancelMovement}
|
|
||||||
>
|
>
|
||||||
↑
|
↑
|
||||||
</div>
|
</div>
|
||||||
|
@ -65,24 +57,13 @@ const MovementControls = () => {
|
||||||
tabIndex={0}
|
tabIndex={0}
|
||||||
style={{
|
style={{
|
||||||
...btnStyle,
|
...btnStyle,
|
||||||
// left: 46,
|
|
||||||
left: 57,
|
left: 57,
|
||||||
bottom: 98,
|
bottom: 98,
|
||||||
}}
|
}}
|
||||||
onMouseDown={() => {
|
onMouseDown={() => dispatch(setMoveV(1))}
|
||||||
getRenderer().controls.moveV = 1;
|
onMouseUp={() => dispatch(setMoveV(0))}
|
||||||
}}
|
onTouchStart={(event) => dispatch(setMoveV(1))}
|
||||||
onMouseUp={() => {
|
onTouchEnd={(event) => dispatch(setMoveV(0))}
|
||||||
getRenderer().controls.moveV = 0;
|
|
||||||
}}
|
|
||||||
onTouchStart={() => {
|
|
||||||
getRenderer().controls.moveV = 1;
|
|
||||||
}}
|
|
||||||
onTouchEnd={() => {
|
|
||||||
getRenderer().controls.moveV = 0;
|
|
||||||
}}
|
|
||||||
onTouchCancel={cancelMovement}
|
|
||||||
onMouseLeave={cancelMovement}
|
|
||||||
>
|
>
|
||||||
↓
|
↓
|
||||||
</div>
|
</div>
|
||||||
|
@ -95,20 +76,10 @@ const MovementControls = () => {
|
||||||
left: 16,
|
left: 16,
|
||||||
bottom: 98,
|
bottom: 98,
|
||||||
}}
|
}}
|
||||||
onMouseDown={() => {
|
onMouseDown={() => dispatch(setMoveU(-1))}
|
||||||
getRenderer().controls.moveU = -1;
|
onMouseUp={() => dispatch(setMoveU(0))}
|
||||||
}}
|
onTouchStart={() => dispatch(setMoveU(-1))}
|
||||||
onMouseUp={() => {
|
onTouchEnd={() => dispatch(setMoveU(0))}
|
||||||
getRenderer().controls.moveU = 0;
|
|
||||||
}}
|
|
||||||
onTouchStart={() => {
|
|
||||||
getRenderer().controls.moveU = -1;
|
|
||||||
}}
|
|
||||||
onTouchEnd={() => {
|
|
||||||
getRenderer().controls.moveU = 0;
|
|
||||||
}}
|
|
||||||
onTouchCancel={cancelMovement}
|
|
||||||
onMouseLeave={cancelMovement}
|
|
||||||
>
|
>
|
||||||
←
|
←
|
||||||
</div>
|
</div>
|
||||||
|
@ -118,24 +89,13 @@ const MovementControls = () => {
|
||||||
tabIndex={0}
|
tabIndex={0}
|
||||||
style={{
|
style={{
|
||||||
...btnStyle,
|
...btnStyle,
|
||||||
// left: 76,
|
|
||||||
left: 98,
|
left: 98,
|
||||||
bottom: 98,
|
bottom: 98,
|
||||||
}}
|
}}
|
||||||
onMouseDown={() => {
|
onMouseDown={() => dispatch(setMoveU(1))}
|
||||||
getRenderer().controls.moveU = 1;
|
onMouseUp={() => dispatch(setMoveU(0))}
|
||||||
}}
|
onTouchStart={() => dispatch(setMoveU(1))}
|
||||||
onMouseUp={() => {
|
onTouchEnd={() => dispatch(setMoveU(0))}
|
||||||
getRenderer().controls.moveU = 0;
|
|
||||||
}}
|
|
||||||
onTouchStart={() => {
|
|
||||||
getRenderer().controls.moveU = 1;
|
|
||||||
}}
|
|
||||||
onTouchEnd={() => {
|
|
||||||
getRenderer().controls.moveU = 0;
|
|
||||||
}}
|
|
||||||
onTouchCancel={cancelMovement}
|
|
||||||
onMouseLeave={cancelMovement}
|
|
||||||
>
|
>
|
||||||
→
|
→
|
||||||
</div>
|
</div>
|
||||||
|
@ -145,24 +105,13 @@ const MovementControls = () => {
|
||||||
tabIndex={0}
|
tabIndex={0}
|
||||||
style={{
|
style={{
|
||||||
...btnStyle,
|
...btnStyle,
|
||||||
// left: 76,
|
|
||||||
left: 16,
|
left: 16,
|
||||||
bottom: 139,
|
bottom: 139,
|
||||||
}}
|
}}
|
||||||
onMouseDown={() => {
|
onMouseDown={() => dispatch(setMoveW(-1))}
|
||||||
getRenderer().controls.moveW = -1;
|
onMouseUp={() => dispatch(setMoveW(0))}
|
||||||
}}
|
onTouchStart={() => dispatch(setMoveW(-1))}
|
||||||
onMouseUp={() => {
|
onTouchEnd={() => dispatch(setMoveW(0))}
|
||||||
getRenderer().controls.moveW = 0;
|
|
||||||
}}
|
|
||||||
onTouchStart={() => {
|
|
||||||
getRenderer().controls.moveW = -1;
|
|
||||||
}}
|
|
||||||
onTouchEnd={() => {
|
|
||||||
getRenderer().controls.moveW = 0;
|
|
||||||
}}
|
|
||||||
onTouchCancel={cancelMovement}
|
|
||||||
onMouseLeave={cancelMovement}
|
|
||||||
>
|
>
|
||||||
↖
|
↖
|
||||||
</div>
|
</div>
|
||||||
|
@ -172,24 +121,13 @@ const MovementControls = () => {
|
||||||
tabIndex={0}
|
tabIndex={0}
|
||||||
style={{
|
style={{
|
||||||
...btnStyle,
|
...btnStyle,
|
||||||
// left: 76,
|
|
||||||
left: 98,
|
left: 98,
|
||||||
bottom: 139,
|
bottom: 139,
|
||||||
}}
|
}}
|
||||||
onMouseDown={() => {
|
onMouseDown={() => dispatch(setMoveW(1))}
|
||||||
getRenderer().controls.moveW = 1;
|
onMouseUp={() => dispatch(setMoveW(0))}
|
||||||
}}
|
onTouchStart={() => dispatch(setMoveW(1))}
|
||||||
onMouseUp={() => {
|
onTouchEnd={() => dispatch(setMoveW(0))}
|
||||||
getRenderer().controls.moveW = 0;
|
|
||||||
}}
|
|
||||||
onTouchStart={() => {
|
|
||||||
getRenderer().controls.moveW = 1;
|
|
||||||
}}
|
|
||||||
onTouchEnd={() => {
|
|
||||||
getRenderer().controls.moveW = 0;
|
|
||||||
}}
|
|
||||||
onTouchCancel={cancelMovement}
|
|
||||||
onMouseLeave={cancelMovement}
|
|
||||||
>
|
>
|
||||||
↘
|
↘
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -7,22 +7,29 @@ import { useSelector, useDispatch } from 'react-redux';
|
||||||
import { TbPencil, TbPencilMinus } from 'react-icons/tb';
|
import { TbPencil, TbPencilMinus } from 'react-icons/tb';
|
||||||
import { t } from 'ttag';
|
import { t } from 'ttag';
|
||||||
|
|
||||||
import { togglePencil } from '../../store/actions';
|
import { HOLD_PAINT } from '../../core/constants';
|
||||||
|
import { selectHoldPaint } from '../../store/actions';
|
||||||
|
|
||||||
const PencilButton = () => {
|
const PencilButton = () => {
|
||||||
const pencilEnabled = useSelector((state) => state.gui.pencilEnabled);
|
const holdPaint = useSelector((state) => state.gui.holdPaint);
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
id="pencilbutton"
|
id="pencilbutton"
|
||||||
className={`actionbuttons${pencilEnabled ? ' pressed' : ''}`}
|
className={
|
||||||
|
`actionbuttons${(holdPaint === HOLD_PAINT.PENCIL) ? ' pressed' : ''}`
|
||||||
|
}
|
||||||
role="button"
|
role="button"
|
||||||
title={(pencilEnabled) ? t`Disable Pencil` : t`Enable Pencil`}
|
title={(holdPaint === HOLD_PAINT.PENCIL)
|
||||||
|
? t`Disable Pencil`
|
||||||
|
: t`Enable Pencil`}
|
||||||
tabIndex={-1}
|
tabIndex={-1}
|
||||||
onClick={() => dispatch(togglePencil())}
|
onClick={() => dispatch(selectHoldPaint(
|
||||||
|
(holdPaint === HOLD_PAINT.PENCIL) ? HOLD_PAINT.OFF : HOLD_PAINT.PENCIL,
|
||||||
|
))}
|
||||||
>
|
>
|
||||||
{pencilEnabled ? <TbPencilMinus /> : <TbPencil />}
|
{(holdPaint === HOLD_PAINT.PENCIL) ? <TbPencilMinus /> : <TbPencil />}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -17,6 +17,9 @@ import {
|
||||||
getChunkOfPixel,
|
getChunkOfPixel,
|
||||||
getOffsetOfPixel,
|
getOffsetOfPixel,
|
||||||
} from '../core/utils';
|
} from '../core/utils';
|
||||||
|
import {
|
||||||
|
HOLD_PAINT,
|
||||||
|
} from '../core/constants';
|
||||||
|
|
||||||
class PixelPainterControls {
|
class PixelPainterControls {
|
||||||
store;
|
store;
|
||||||
|
@ -27,28 +30,19 @@ class PixelPainterControls {
|
||||||
clickTapStartTime = 0;
|
clickTapStartTime = 0;
|
||||||
clickTapStartCoords = [0, 0];
|
clickTapStartCoords = [0, 0];
|
||||||
tapStartDist = 50;
|
tapStartDist = 50;
|
||||||
//
|
|
||||||
// on mouse: true as long as left mouse button is pressed
|
// on mouse: true as long as left mouse button is pressed
|
||||||
// on touch: set to true when one finger touches the screen
|
|
||||||
// set to false when second finger touches or touch ends
|
|
||||||
isClicking = false;
|
isClicking = false;
|
||||||
// on touch: true if more than one finger on screen
|
// on touch: true if more than one finger on screen
|
||||||
isMultiTab = false;
|
isMultiTap = false;
|
||||||
|
// on touch: true if current tab was ever more than one figher at any time
|
||||||
|
wasEverMultiTap = false;
|
||||||
|
// on touch: when painting with holdPaint is active
|
||||||
|
isTapPainting = false;
|
||||||
// on touch: timeout to detect long-press
|
// on touch: timeout to detect long-press
|
||||||
tapTimeout = null;
|
tapTimeout = null;
|
||||||
/*
|
// time of last tick
|
||||||
* if we are shift-hold-painting
|
|
||||||
* 0: no
|
|
||||||
* 1: left shift
|
|
||||||
* 2: right shift
|
|
||||||
*/
|
|
||||||
holdPainting = 0;
|
|
||||||
// if we are moving
|
|
||||||
moveU = 0;
|
|
||||||
moveV = 0;
|
|
||||||
moveW = 0;
|
|
||||||
prevTime = Date.now();
|
prevTime = Date.now();
|
||||||
// if we are waiting before placing pixel via holdPainting again
|
// if we are waiting before placing pixel via holdPaint again
|
||||||
coolDownDelta = false;
|
coolDownDelta = false;
|
||||||
|
|
||||||
constructor(renderer, viewport, store) {
|
constructor(renderer, viewport, store) {
|
||||||
|
@ -57,8 +51,6 @@ class PixelPainterControls {
|
||||||
this.viewport = viewport;
|
this.viewport = viewport;
|
||||||
|
|
||||||
this.onMouseDown = this.onMouseDown.bind(this);
|
this.onMouseDown = this.onMouseDown.bind(this);
|
||||||
this.onKeyDown = this.onKeyDown.bind(this);
|
|
||||||
this.onKeyUp = this.onKeyUp.bind(this);
|
|
||||||
this.onAuxClick = this.onAuxClick.bind(this);
|
this.onAuxClick = this.onAuxClick.bind(this);
|
||||||
this.onMouseOut = this.onMouseOut.bind(this);
|
this.onMouseOut = this.onMouseOut.bind(this);
|
||||||
this.onMouseMove = this.onMouseMove.bind(this);
|
this.onMouseMove = this.onMouseMove.bind(this);
|
||||||
|
@ -68,8 +60,6 @@ class PixelPainterControls {
|
||||||
this.onTouchEnd = this.onTouchEnd.bind(this);
|
this.onTouchEnd = this.onTouchEnd.bind(this);
|
||||||
this.onTouchMove = this.onTouchMove.bind(this);
|
this.onTouchMove = this.onTouchMove.bind(this);
|
||||||
|
|
||||||
document.addEventListener('keydown', this.onKeyDown, false);
|
|
||||||
document.addEventListener('keyup', this.onKeyUp, false);
|
|
||||||
viewport.addEventListener('auxclick', this.onAuxClick, false);
|
viewport.addEventListener('auxclick', this.onAuxClick, false);
|
||||||
viewport.addEventListener('mousedown', this.onMouseDown, false);
|
viewport.addEventListener('mousedown', this.onMouseDown, false);
|
||||||
viewport.addEventListener('mousemove', this.onMouseMove, false);
|
viewport.addEventListener('mousemove', this.onMouseMove, false);
|
||||||
|
@ -82,10 +72,8 @@ class PixelPainterControls {
|
||||||
viewport.addEventListener('touchcancel', this.onMouseOut, false);
|
viewport.addEventListener('touchcancel', this.onMouseOut, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
dispose() {
|
// eslint-disable-next-line class-methods-use-this
|
||||||
document.removeEventListener('keydown', this.onKeyDown, false);
|
dispose() {}
|
||||||
document.removeEventListener('keyup', this.onKeyUp, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
gotCoolDownDelta(delta) {
|
gotCoolDownDelta(delta) {
|
||||||
this.coolDownDelta = true;
|
this.coolDownDelta = true;
|
||||||
|
@ -129,16 +117,10 @@ class PixelPainterControls {
|
||||||
// thresholds for single click / holding
|
// thresholds for single click / holding
|
||||||
if (clickTapStartTime > Date.now() - 250
|
if (clickTapStartTime > Date.now() - 250
|
||||||
&& coordsDiff[0] < 2 && coordsDiff[1] < 2) {
|
&& coordsDiff[0] < 2 && coordsDiff[1] < 2) {
|
||||||
const cell = screenToWorld(
|
|
||||||
renderer.view,
|
|
||||||
renderer.viewscale,
|
|
||||||
this.viewport,
|
|
||||||
[clientX, clientY],
|
|
||||||
);
|
|
||||||
PixelPainterControls.placePixel(
|
PixelPainterControls.placePixel(
|
||||||
store,
|
store,
|
||||||
renderer,
|
renderer,
|
||||||
cell,
|
this.screenToWorld([clientX, clientY]),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
this.viewport.style.cursor = 'auto';
|
this.viewport.style.cursor = 'auto';
|
||||||
|
@ -147,22 +129,14 @@ class PixelPainterControls {
|
||||||
}
|
}
|
||||||
|
|
||||||
static getTouchCenter(event) {
|
static getTouchCenter(event) {
|
||||||
switch (event.touches.length) {
|
let x = 0;
|
||||||
case 1: {
|
let y = 0;
|
||||||
const { pageX, pageY } = event.touches[0];
|
for (const { pageX, pageY } of event.touches) {
|
||||||
return [pageX, pageY];
|
x += pageX;
|
||||||
}
|
y += pageY;
|
||||||
case 2: {
|
|
||||||
const pageX = Math.floor(0.5
|
|
||||||
* (event.touches[0].pageX + event.touches[1].pageX));
|
|
||||||
const pageY = Math.floor(0.5
|
|
||||||
* (event.touches[0].pageY + event.touches[1].pageY));
|
|
||||||
return [pageX, pageY];
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
return null;
|
const { length } = event.touches;
|
||||||
|
return [x / length, y / length];
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -171,22 +145,25 @@ class PixelPainterControls {
|
||||||
*/
|
*/
|
||||||
static placePixel(store, renderer, cell, colorIndex = null) {
|
static placePixel(store, renderer, cell, colorIndex = null) {
|
||||||
const state = store.getState();
|
const state = store.getState();
|
||||||
const { autoZoomIn } = state.gui;
|
if (state.canvas.isHistoricalView) {
|
||||||
const { clrIgnore, isHistoricalView } = state.canvas;
|
return;
|
||||||
|
}
|
||||||
|
const selectedColor = colorIndex
|
||||||
|
?? PixelPainterControls.getWantedColor(state, renderer, cell);
|
||||||
|
if (selectedColor === null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
const { viewscale: scale } = renderer;
|
const { viewscale: scale } = renderer;
|
||||||
const selectedColor = (colorIndex === null)
|
|
||||||
? state.canvas.selectedColor
|
|
||||||
: colorIndex;
|
|
||||||
|
|
||||||
if (isHistoricalView) return;
|
if (state.gui.autoZoomIn && scale < 8) {
|
||||||
|
|
||||||
if (autoZoomIn && scale < 8) {
|
|
||||||
renderer.updateView([cell[0], cell[1], 12]);
|
renderer.updateView([cell[0], cell[1], 12]);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// allow placing of pixel just on low zoomlevels
|
// allow placing of pixel just on low zoomlevels
|
||||||
if (scale < 3) return;
|
if (scale < 3) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const curColor = renderer.getColorIndexOfPixel(...cell);
|
const curColor = renderer.getColorIndexOfPixel(...cell);
|
||||||
if (selectedColor === curColor) {
|
if (selectedColor === curColor) {
|
||||||
|
@ -194,7 +171,7 @@ class PixelPainterControls {
|
||||||
}
|
}
|
||||||
|
|
||||||
// placing unset pixel
|
// placing unset pixel
|
||||||
if (selectedColor < clrIgnore) {
|
if (selectedColor < state.canvas.clrIgnore) {
|
||||||
const { palette } = state.canvas;
|
const { palette } = state.canvas;
|
||||||
const { rgb } = palette;
|
const { rgb } = palette;
|
||||||
let clrOffset = selectedColor * 3;
|
let clrOffset = selectedColor * 3;
|
||||||
|
@ -249,35 +226,48 @@ class PixelPainterControls {
|
||||||
document.activeElement.blur();
|
document.activeElement.blur();
|
||||||
|
|
||||||
this.renderer.cancelStoreViewInState();
|
this.renderer.cancelStoreViewInState();
|
||||||
|
this.clearTabTimeout();
|
||||||
|
this.isTapPainting = false;
|
||||||
this.clickTapStartTime = Date.now();
|
this.clickTapStartTime = Date.now();
|
||||||
this.clickTapStartCoords = PixelPainterControls.getTouchCenter(event);
|
this.clickTapStartCoords = PixelPainterControls.getTouchCenter(event);
|
||||||
this.clickTapStartView = this.renderer.view;
|
this.clickTapStartView = this.renderer.view;
|
||||||
|
|
||||||
if (event.touches.length > 1) {
|
if (event.touches.length > 1) {
|
||||||
this.tapStartDist = PixelPainterControls.getMultiTouchDistance(event);
|
this.tapStartDist = PixelPainterControls.getMultiTouchDistance(event);
|
||||||
this.isMultiTab = true;
|
this.isMultiTap = true;
|
||||||
this.clearTabTimeout();
|
this.wasEverMultiTap = true;
|
||||||
} else {
|
} else {
|
||||||
this.isClicking = true;
|
this.isMultiTap = false;
|
||||||
this.isMultiTab = false;
|
this.wasEverMultiTap = false;
|
||||||
this.tapTimeout = setTimeout(() => {
|
const state = this.store.getState();
|
||||||
// check for longer tap to select taped color
|
if (state.gui.holdPaint) {
|
||||||
PixelPainterControls.selectColor(
|
this.tapTimeout = setTimeout(() => {
|
||||||
this.store,
|
this.isTapPainting = true;
|
||||||
this.viewport,
|
PixelPainterControls.placePixel(
|
||||||
this.renderer,
|
this.store,
|
||||||
this.clickTapStartCoords,
|
this.renderer,
|
||||||
);
|
this.screenToWorld(this.clickTapStartCoords),
|
||||||
}, 600);
|
);
|
||||||
|
}, 200);
|
||||||
|
} else {
|
||||||
|
this.tapTimeout = setTimeout(() => {
|
||||||
|
// check for longer tap to select taped color
|
||||||
|
this.selectColorFromScreen(this.clickTapStartCoords);
|
||||||
|
}, 600);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onTouchEnd(event) {
|
onTouchEnd(event) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
|
if (event.touches.length) {
|
||||||
|
// still other touches left
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const { store, renderer } = this;
|
const { store, renderer } = this;
|
||||||
if (event.touches.length === 0 && this.isClicking) {
|
if (!this.wasEverMultiTap) {
|
||||||
const { pageX, pageY } = event.changedTouches[0];
|
const { pageX, pageY } = event.changedTouches[0];
|
||||||
const { clickTapStartCoords, clickTapStartTime } = this;
|
const { clickTapStartCoords, clickTapStartTime } = this;
|
||||||
const coordsDiff = [
|
const coordsDiff = [
|
||||||
|
@ -286,17 +276,12 @@ class PixelPainterControls {
|
||||||
];
|
];
|
||||||
// thresholds for single click / holding
|
// thresholds for single click / holding
|
||||||
if (clickTapStartTime > Date.now() - 580
|
if (clickTapStartTime > Date.now() - 580
|
||||||
&& coordsDiff[0] < 2 && coordsDiff[1] < 2) {
|
&& coordsDiff[0] < 2 && coordsDiff[1] < 2
|
||||||
const cell = screenToWorld(
|
) {
|
||||||
renderer.view,
|
|
||||||
renderer.viewscale,
|
|
||||||
this.viewport,
|
|
||||||
[pageX, pageY],
|
|
||||||
);
|
|
||||||
PixelPainterControls.placePixel(
|
PixelPainterControls.placePixel(
|
||||||
store,
|
store,
|
||||||
this.renderer,
|
this.renderer,
|
||||||
cell,
|
this.screenToWorld([pageX, pageY]),
|
||||||
);
|
);
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
store.dispatch(unsetHover());
|
store.dispatch(unsetHover());
|
||||||
|
@ -312,19 +297,34 @@ class PixelPainterControls {
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
|
|
||||||
const multiTouch = (event.touches.length > 1);
|
const multiTouch = (event.touches.length > 1);
|
||||||
|
const state = this.store.getState();
|
||||||
|
|
||||||
const [clientX, clientY] = PixelPainterControls.getTouchCenter(event);
|
const [clientX, clientY] = PixelPainterControls.getTouchCenter(event);
|
||||||
if (this.isMultiTab !== multiTouch) {
|
if (this.isMultiTap !== multiTouch) {
|
||||||
|
this.wasEverMultiTap = true;
|
||||||
// if one finger got lifted or added, reset clickTabStart
|
// if one finger got lifted or added, reset clickTabStart
|
||||||
this.isMultiTab = multiTouch;
|
this.isMultiTap = multiTouch;
|
||||||
this.clickTapStartCoords = [clientX, clientY];
|
this.clickTapStartCoords = [clientX, clientY];
|
||||||
this.clickTapStartView = this.renderer.view;
|
this.clickTapStartView = this.renderer.view;
|
||||||
this.tapStartDist = PixelPainterControls.getMultiTouchDistance(event);
|
this.tapStartDist = PixelPainterControls.getMultiTouchDistance(event);
|
||||||
} else {
|
return;
|
||||||
// pan
|
}
|
||||||
const { clickTapStartView, clickTapStartCoords } = this;
|
const { clickTapStartView, clickTapStartCoords } = this;
|
||||||
|
// pinch
|
||||||
|
if (multiTouch) {
|
||||||
|
this.clearTabTimeout();
|
||||||
|
const a = event.touches[0];
|
||||||
|
const b = event.touches[1];
|
||||||
|
const dist = Math.sqrt(
|
||||||
|
(b.pageX - a.pageX) ** 2 + (b.pageY - a.pageY) ** 2,
|
||||||
|
);
|
||||||
|
const pinchScale = dist / this.tapStartDist;
|
||||||
|
const [x, y] = this.renderer.view;
|
||||||
|
this.renderer.updateView([x, y, clickTapStartView[2] * pinchScale]);
|
||||||
|
}
|
||||||
|
// pan
|
||||||
|
if (!state.gui.holdPaint || multiTouch) {
|
||||||
const [lastPosX, lastPosY] = clickTapStartView;
|
const [lastPosX, lastPosY] = clickTapStartView;
|
||||||
|
|
||||||
const deltaX = clientX - clickTapStartCoords[0];
|
const deltaX = clientX - clickTapStartCoords[0];
|
||||||
const deltaY = clientY - clickTapStartCoords[1];
|
const deltaY = clientY - clickTapStartCoords[1];
|
||||||
if (deltaX > 2 || deltaY > 2) {
|
if (deltaX > 2 || deltaY > 2) {
|
||||||
|
@ -335,26 +335,24 @@ class PixelPainterControls {
|
||||||
lastPosX - (deltaX / scale),
|
lastPosX - (deltaX / scale),
|
||||||
lastPosY - (deltaY / scale),
|
lastPosY - (deltaY / scale),
|
||||||
]);
|
]);
|
||||||
|
} else if (!this.wasEverMultiTap && !this.coolDownDelta) {
|
||||||
// pinch
|
// hold paint
|
||||||
if (multiTouch) {
|
if (this.isTapPainting) {
|
||||||
this.clearTabTimeout();
|
PixelPainterControls.placePixel(
|
||||||
|
this.store,
|
||||||
const a = event.touches[0];
|
this.renderer,
|
||||||
const b = event.touches[1];
|
this.screenToWorld([clientX, clientY]),
|
||||||
const { tapStartDist } = this;
|
|
||||||
const dist = Math.sqrt(
|
|
||||||
(b.pageX - a.pageX) ** 2 + (b.pageY - a.pageY) ** 2,
|
|
||||||
);
|
);
|
||||||
const pinchScale = dist / tapStartDist;
|
} else {
|
||||||
const [x, y] = this.renderer.view;
|
// while we are waiting for isTapPainting to trigger track coordinates
|
||||||
this.renderer.updateView([x, y, clickTapStartView[2] * pinchScale]);
|
this.clickTapStartCoords = [clientX, clientY];
|
||||||
|
this.clickTapStartView = this.renderer.view;
|
||||||
|
this.tapStartDist = PixelPainterControls.getMultiTouchDistance(event);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
clearTabTimeout() {
|
clearTabTimeout() {
|
||||||
this.isClicking = false;
|
|
||||||
if (this.tapTimeout) {
|
if (this.tapTimeout) {
|
||||||
clearTimeout(this.tapTimeout);
|
clearTimeout(this.tapTimeout);
|
||||||
this.tapTimeout = null;
|
this.tapTimeout = null;
|
||||||
|
@ -376,6 +374,22 @@ class PixelPainterControls {
|
||||||
this.renderer.storeViewInState();
|
this.renderer.storeViewInState();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
holdPaintStarted(immediate) {
|
||||||
|
// if hold painting is started by keyboard,
|
||||||
|
// we immeidately have to place, and not just when mousemove starts
|
||||||
|
if (!immediate) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const { hover } = this.store.getState().canvas;
|
||||||
|
if (hover) {
|
||||||
|
PixelPainterControls.placePixel(
|
||||||
|
this.store,
|
||||||
|
this.renderer,
|
||||||
|
hover,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
onWheel(event) {
|
onWheel(event) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
document.activeElement.blur();
|
document.activeElement.blur();
|
||||||
|
@ -392,6 +406,54 @@ class PixelPainterControls {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static getWantedColor(state, renderer, cell) {
|
||||||
|
if (state.gui.holdPaint === HOLD_PAINT.HISTORY) {
|
||||||
|
return renderer.getColorIndexOfPixel(...cell, true);
|
||||||
|
}
|
||||||
|
return state.canvas.selectedColor;
|
||||||
|
}
|
||||||
|
|
||||||
|
screenToWorld(screenCoor) {
|
||||||
|
return screenToWorld(
|
||||||
|
this.renderer.view,
|
||||||
|
this.renderer.viewscale,
|
||||||
|
this.viewport,
|
||||||
|
screenCoor,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* set hover from screen coordinates
|
||||||
|
* @param [x, y] screen coordinates
|
||||||
|
* @return null if hover didn't changed,
|
||||||
|
* hover if it changed
|
||||||
|
*/
|
||||||
|
setHoverFromScrrenCoor(screenCoor) {
|
||||||
|
const { store } = this;
|
||||||
|
const state = store.getState();
|
||||||
|
const { hover: prevHover } = state.canvas;
|
||||||
|
const hover = this.screenToWorld(screenCoor);
|
||||||
|
const [x, y] = hover;
|
||||||
|
|
||||||
|
/* out of bounds check */
|
||||||
|
const { canvasSize } = state.canvas;
|
||||||
|
const maxCoords = canvasSize / 2;
|
||||||
|
if (x < -maxCoords || x >= maxCoords
|
||||||
|
|| y < -maxCoords || y >= maxCoords
|
||||||
|
) {
|
||||||
|
if (prevHover) {
|
||||||
|
store.dispatch(unsetHover());
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!prevHover || prevHover[0] !== x || prevHover[1] !== y) {
|
||||||
|
store.dispatch(setHover(hover));
|
||||||
|
return hover;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
onMouseMove(event) {
|
onMouseMove(event) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
|
|
||||||
|
@ -413,61 +475,18 @@ class PixelPainterControls {
|
||||||
lastPosY - (deltaY / viewscale),
|
lastPosY - (deltaY / viewscale),
|
||||||
]);
|
]);
|
||||||
} else {
|
} else {
|
||||||
const { store } = this;
|
const hover = this.setHoverFromScrrenCoor([clientX, clientY]);
|
||||||
const state = store.getState();
|
if (!hover) {
|
||||||
const { hover } = state.canvas;
|
|
||||||
const { view } = renderer;
|
|
||||||
const screenCoor = screenToWorld(
|
|
||||||
view,
|
|
||||||
viewscale,
|
|
||||||
this.viewport,
|
|
||||||
[clientX, clientY],
|
|
||||||
);
|
|
||||||
const [x, y] = screenCoor;
|
|
||||||
|
|
||||||
/* out of bounds check */
|
|
||||||
const { canvasSize } = state.canvas;
|
|
||||||
const maxCoords = canvasSize / 2;
|
|
||||||
if (x < -maxCoords || x >= maxCoords
|
|
||||||
|| y < -maxCoords || y >= maxCoords
|
|
||||||
) {
|
|
||||||
if (hover) {
|
|
||||||
store.dispatch(unsetHover());
|
|
||||||
}
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
const state = this.store.getState();
|
||||||
if (!hover || hover[0] !== x || hover[1] !== y) {
|
if (!this.coolDownDelta && state.gui.holdPaint) {
|
||||||
store.dispatch(setHover(screenCoor));
|
/* hold paint */
|
||||||
/* shift placing */
|
PixelPainterControls.placePixel(
|
||||||
if (!this.coolDownDelta) {
|
this.store,
|
||||||
switch (this.holdPainting) {
|
this.renderer,
|
||||||
case 1: {
|
hover,
|
||||||
/* left shift: from selected color */
|
);
|
||||||
PixelPainterControls.placePixel(
|
|
||||||
store,
|
|
||||||
this.renderer,
|
|
||||||
screenCoor,
|
|
||||||
);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case 2: {
|
|
||||||
/* right shift: from historical view */
|
|
||||||
const colorIndex = this.renderer
|
|
||||||
.getColorIndexOfPixel(x, y, true);
|
|
||||||
if (colorIndex !== null) {
|
|
||||||
PixelPainterControls.placePixel(
|
|
||||||
store,
|
|
||||||
this.renderer,
|
|
||||||
screenCoor,
|
|
||||||
colorIndex,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -476,20 +495,15 @@ class PixelPainterControls {
|
||||||
const { store, viewport } = this;
|
const { store, viewport } = this;
|
||||||
viewport.style.cursor = 'auto';
|
viewport.style.cursor = 'auto';
|
||||||
store.dispatch(unsetHover());
|
store.dispatch(unsetHover());
|
||||||
this.holdPainting = 0;
|
|
||||||
this.clearTabTimeout();
|
this.clearTabTimeout();
|
||||||
}
|
}
|
||||||
|
|
||||||
static selectColor(store, viewport, renderer, center) {
|
selectColorFromScreen(center) {
|
||||||
if (renderer.viewscale < 3) {
|
const { renderer, store } = this;
|
||||||
|
if (this.renderer.viewscale < 3) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const coords = screenToWorld(
|
const coords = this.screenToWorld(center);
|
||||||
renderer.view,
|
|
||||||
renderer.viewscale,
|
|
||||||
viewport,
|
|
||||||
center,
|
|
||||||
);
|
|
||||||
const clrIndex = renderer.getColorIndexOfPixel(...coords);
|
const clrIndex = renderer.getColorIndexOfPixel(...coords);
|
||||||
if (clrIndex !== null) {
|
if (clrIndex !== null) {
|
||||||
store.dispatch(selectColor(clrIndex));
|
store.dispatch(selectColor(clrIndex));
|
||||||
|
@ -503,146 +517,12 @@ class PixelPainterControls {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
|
this.selectColorFromScreen([clientX, clientY]);
|
||||||
PixelPainterControls.selectColor(
|
|
||||||
this.store,
|
|
||||||
this.viewport,
|
|
||||||
this.renderer,
|
|
||||||
[clientX, clientY],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
onKeyUp(event) {
|
|
||||||
/*
|
|
||||||
* key locations
|
|
||||||
*/
|
|
||||||
switch (event.code) {
|
|
||||||
case 'ArrowUp':
|
|
||||||
case 'KeyW':
|
|
||||||
this.moveV = 0;
|
|
||||||
return;
|
|
||||||
case 'ArrowLeft':
|
|
||||||
case 'KeyA':
|
|
||||||
this.moveU = 0;
|
|
||||||
return;
|
|
||||||
case 'ArrowDown':
|
|
||||||
case 'KeyS':
|
|
||||||
this.moveV = 0;
|
|
||||||
return;
|
|
||||||
case 'ArrowRight':
|
|
||||||
case 'KeyD':
|
|
||||||
this.moveU = 0;
|
|
||||||
return;
|
|
||||||
case 'KeyE':
|
|
||||||
this.moveW = 0;
|
|
||||||
return;
|
|
||||||
case 'KeyQ':
|
|
||||||
this.moveW = 0;
|
|
||||||
return;
|
|
||||||
default:
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* key char
|
|
||||||
*/
|
|
||||||
switch (event.key) {
|
|
||||||
case 'Shift':
|
|
||||||
case 'CapsLock':
|
|
||||||
this.holdPainting = 0;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
onKeyDown(event) {
|
|
||||||
// ignore key presses if modal is open or chat is used
|
|
||||||
if (event.target.nodeName === 'INPUT'
|
|
||||||
|| event.target.nodeName === 'TEXTAREA'
|
|
||||||
) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* key location
|
|
||||||
*/
|
|
||||||
switch (event.code) {
|
|
||||||
case 'ArrowUp':
|
|
||||||
case 'KeyW':
|
|
||||||
this.moveV = -1;
|
|
||||||
return;
|
|
||||||
case 'ArrowLeft':
|
|
||||||
case 'KeyA':
|
|
||||||
this.moveU = -1;
|
|
||||||
return;
|
|
||||||
case 'ArrowDown':
|
|
||||||
case 'KeyS':
|
|
||||||
this.moveV = 1;
|
|
||||||
return;
|
|
||||||
case 'ArrowRight':
|
|
||||||
case 'KeyD':
|
|
||||||
this.moveU = 1;
|
|
||||||
return;
|
|
||||||
case 'KeyE':
|
|
||||||
this.moveW = 1;
|
|
||||||
return;
|
|
||||||
case 'KeyQ':
|
|
||||||
this.moveW = -1;
|
|
||||||
return;
|
|
||||||
default:
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* key char
|
|
||||||
*/
|
|
||||||
switch (event.key) {
|
|
||||||
case '+':
|
|
||||||
this.zoom(1);
|
|
||||||
return;
|
|
||||||
case '-':
|
|
||||||
this.zoom(-1);
|
|
||||||
return;
|
|
||||||
case 'Control':
|
|
||||||
case 'Shift': {
|
|
||||||
const { store } = this;
|
|
||||||
const state = store.getState();
|
|
||||||
const { hover } = state.canvas;
|
|
||||||
if (hover) {
|
|
||||||
if (event.key === 'Control') {
|
|
||||||
// ctrl
|
|
||||||
const clrIndex = this.renderer.getColorIndexOfPixel(...hover);
|
|
||||||
store.dispatch(selectColor(clrIndex));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (event.location === KeyboardEvent.DOM_KEY_LOCATION_LEFT) {
|
|
||||||
// left shift
|
|
||||||
this.holdPainting = 1;
|
|
||||||
PixelPainterControls.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) {
|
|
||||||
PixelPainterControls.placePixel(
|
|
||||||
store,
|
|
||||||
this.renderer,
|
|
||||||
hover,
|
|
||||||
colorIndex,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
update() {
|
update() {
|
||||||
let time = Date.now();
|
let time = Date.now();
|
||||||
const { moveU, moveV, moveW } = this;
|
const { moveU, moveV, moveW } = this.store.getState().gui;
|
||||||
|
|
||||||
if (!(moveU || moveV || moveW)) {
|
if (!(moveU || moveV || moveW)) {
|
||||||
this.prevTime = time;
|
this.prevTime = time;
|
||||||
|
|
|
@ -440,7 +440,7 @@ class VoxelPainterControls {
|
||||||
return;
|
return;
|
||||||
case 'KeyQ':
|
case 'KeyQ':
|
||||||
this.moveW = -1;
|
this.moveW = -1;
|
||||||
return;
|
|
||||||
default:
|
default:
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -468,7 +468,7 @@ class VoxelPainterControls {
|
||||||
return;
|
return;
|
||||||
case 'KeyQ':
|
case 'KeyQ':
|
||||||
this.moveW = 0;
|
this.moveW = 0;
|
||||||
return;
|
|
||||||
default:
|
default:
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,14 +10,72 @@ import {
|
||||||
togglePixelNotify,
|
togglePixelNotify,
|
||||||
toggleMute,
|
toggleMute,
|
||||||
selectCanvas,
|
selectCanvas,
|
||||||
|
selectHoverColor,
|
||||||
|
selectHoldPaint,
|
||||||
|
setMoveU,
|
||||||
|
setMoveV,
|
||||||
|
setMoveW,
|
||||||
} from '../store/actions';
|
} from '../store/actions';
|
||||||
|
import {
|
||||||
|
HOLD_PAINT,
|
||||||
|
} from '../core/constants';
|
||||||
import {
|
import {
|
||||||
notify,
|
notify,
|
||||||
} from '../store/actions/thunks';
|
} from '../store/actions/thunks';
|
||||||
|
|
||||||
const usedKeys = ['g', 'h', 'x', 'm', 'r', 'p'];
|
const charKeys = ['g', 'h', 'x', 'm', 'r', 'p', '+', '-'];
|
||||||
|
|
||||||
function createKeyPressHandler(store) {
|
export function createKeyUpHandler(store) {
|
||||||
|
return (event) => {
|
||||||
|
/*
|
||||||
|
* key locations
|
||||||
|
*/
|
||||||
|
switch (event.code) {
|
||||||
|
case 'ArrowUp':
|
||||||
|
case 'KeyW':
|
||||||
|
store.dispatch(setMoveV(0));
|
||||||
|
return;
|
||||||
|
case 'ArrowLeft':
|
||||||
|
case 'KeyA':
|
||||||
|
store.dispatch(setMoveU(0));
|
||||||
|
return;
|
||||||
|
case 'ArrowDown':
|
||||||
|
case 'KeyS':
|
||||||
|
store.dispatch(setMoveV(0));
|
||||||
|
return;
|
||||||
|
case 'ArrowRight':
|
||||||
|
case 'KeyD':
|
||||||
|
store.dispatch(setMoveU(0));
|
||||||
|
return;
|
||||||
|
case 'KeyE':
|
||||||
|
store.dispatch(setMoveW(0));
|
||||||
|
return;
|
||||||
|
case 'KeyQ':
|
||||||
|
store.dispatch(setMoveW(0));
|
||||||
|
return;
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* key char
|
||||||
|
*/
|
||||||
|
switch (event.key) {
|
||||||
|
case '+':
|
||||||
|
store.dispatch(setMoveW(0));
|
||||||
|
return;
|
||||||
|
case '-':
|
||||||
|
store.dispatch(setMoveW(0));
|
||||||
|
return;
|
||||||
|
case 'Shift':
|
||||||
|
case 'CapsLock':
|
||||||
|
store.dispatch(selectHoldPaint(HOLD_PAINT.OFF));
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createKeyDownHandler(store) {
|
||||||
return (event) => {
|
return (event) => {
|
||||||
// ignore key presses if modal is open or chat is used
|
// ignore key presses if modal is open or chat is used
|
||||||
if (event.target.nodeName === 'INPUT'
|
if (event.target.nodeName === 'INPUT'
|
||||||
|
@ -44,12 +102,70 @@ function createKeyPressHandler(store) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* key locations
|
||||||
|
*/
|
||||||
|
switch (event.code) {
|
||||||
|
case 'ArrowUp':
|
||||||
|
case 'KeyW':
|
||||||
|
store.dispatch(setMoveV(-1));
|
||||||
|
return;
|
||||||
|
case 'ArrowLeft':
|
||||||
|
case 'KeyA':
|
||||||
|
store.dispatch(setMoveU(-1));
|
||||||
|
return;
|
||||||
|
case 'ArrowDown':
|
||||||
|
case 'KeyS':
|
||||||
|
store.dispatch(setMoveV(1));
|
||||||
|
return;
|
||||||
|
case 'ArrowRight':
|
||||||
|
case 'KeyD':
|
||||||
|
store.dispatch(setMoveU(1));
|
||||||
|
return;
|
||||||
|
case 'KeyE':
|
||||||
|
store.dispatch(setMoveW(1));
|
||||||
|
return;
|
||||||
|
case 'KeyQ':
|
||||||
|
store.dispatch(setMoveW(-1));
|
||||||
|
return;
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* key char
|
||||||
|
*/
|
||||||
|
switch (event.key) {
|
||||||
|
case '+':
|
||||||
|
store.dispatch(setMoveW(1));
|
||||||
|
return;
|
||||||
|
case '-':
|
||||||
|
store.dispatch(setMoveW(-1));
|
||||||
|
return;
|
||||||
|
case 'Control':
|
||||||
|
store.dispatch(selectHoverColor(-1));
|
||||||
|
return;
|
||||||
|
case 'Shift': {
|
||||||
|
if (event.location === KeyboardEvent.DOM_KEY_LOCATION_LEFT) {
|
||||||
|
// left shift
|
||||||
|
store.dispatch(selectHoldPaint(HOLD_PAINT.PENCIL), true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (event.location === KeyboardEvent.DOM_KEY_LOCATION_RIGHT) {
|
||||||
|
// right shift
|
||||||
|
store.dispatch(selectHoldPaint(HOLD_PAINT.HISTORY), true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* if char of key isn't used by a keybind,
|
* if char of key isn't used by a keybind,
|
||||||
* we check if the key location is where a
|
* we check if the key location is where a
|
||||||
* key that is used would be on QWERTY
|
* key that is used would be on QWERTY
|
||||||
*/
|
*/
|
||||||
if (!usedKeys.includes(key)) {
|
if (!charKeys.includes(key)) {
|
||||||
key = event.code;
|
key = event.code;
|
||||||
if (!key.startsWith('Key')) {
|
if (!key.startsWith('Key')) {
|
||||||
return;
|
return;
|
||||||
|
@ -98,5 +214,3 @@ function createKeyPressHandler(store) {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export default createKeyPressHandler;
|
|
||||||
|
|
|
@ -54,3 +54,10 @@ export const VIEW_UPDATE_DELAY = 1000;
|
||||||
export const MAX_LOADED_CHUNKS = 2000;
|
export const MAX_LOADED_CHUNKS = 2000;
|
||||||
export const MAX_CHUNK_AGE = 300000;
|
export const MAX_CHUNK_AGE = 300000;
|
||||||
export const GC_INTERVAL = 300000;
|
export const GC_INTERVAL = 300000;
|
||||||
|
|
||||||
|
export const HOLD_PAINT = {
|
||||||
|
OFF: 0,
|
||||||
|
PENCIL: 1,
|
||||||
|
HISTORY: 2,
|
||||||
|
OVERLAY: 3,
|
||||||
|
};
|
||||||
|
|
|
@ -93,9 +93,17 @@ export function toggleOpenPalette() {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function togglePencil() {
|
export function selectHoldPaint(value, immediate) {
|
||||||
return {
|
return {
|
||||||
type: 's/TGL_PENCIL',
|
type: 's/SELECT_HOLD_PAINT',
|
||||||
|
value,
|
||||||
|
immediate,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function selectHoverColor() {
|
||||||
|
return {
|
||||||
|
type: 'SELECT_HOVER_COLOR',
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -174,6 +182,34 @@ export function setScale(scale, zoompoint) {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function setMoveU(value) {
|
||||||
|
return {
|
||||||
|
type: 's/SET_MOVE_U',
|
||||||
|
value,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function setMoveV(value) {
|
||||||
|
return {
|
||||||
|
type: 's/SET_MOVE_V',
|
||||||
|
value,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function setMoveW(value) {
|
||||||
|
return {
|
||||||
|
type: 's/SET_MOVE_W',
|
||||||
|
value,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function cancelMove(value) {
|
||||||
|
return {
|
||||||
|
type: 's/CANCEL_MOVE',
|
||||||
|
value,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
export function requestBigChunk(center) {
|
export function requestBigChunk(center) {
|
||||||
return {
|
return {
|
||||||
type: 'REQ_BIG_CHUNK',
|
type: 'REQ_BIG_CHUNK',
|
||||||
|
|
|
@ -10,6 +10,9 @@ import {
|
||||||
getRenderer,
|
getRenderer,
|
||||||
initRenderer,
|
initRenderer,
|
||||||
} from '../../ui/rendererFactory';
|
} from '../../ui/rendererFactory';
|
||||||
|
import {
|
||||||
|
selectColor,
|
||||||
|
} from '../actions';
|
||||||
|
|
||||||
export default (store) => (next) => (action) => {
|
export default (store) => (next) => (action) => {
|
||||||
const { type } = action;
|
const { type } = action;
|
||||||
|
@ -28,7 +31,14 @@ export default (store) => (next) => (action) => {
|
||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
case 'SELECT_HOVER_COLOR': {
|
||||||
|
const renderer = getRenderer();
|
||||||
|
const clr = renderer.getPointedColor();
|
||||||
|
if (clr !== null) {
|
||||||
|
store.dispatch(selectColor(clr));
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
// nothing
|
// nothing
|
||||||
}
|
}
|
||||||
|
@ -112,7 +122,26 @@ export default (store) => (next) => (action) => {
|
||||||
|
|
||||||
case 's/TGL_HISTORICAL_VIEW': {
|
case 's/TGL_HISTORICAL_VIEW': {
|
||||||
const renderer = getRenderer();
|
const renderer = getRenderer();
|
||||||
renderer.updateView(state.view);
|
renderer.updateView(state.canvas.view);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case 's/SELECT_HOLD_PAINT': {
|
||||||
|
if (action.value) {
|
||||||
|
const renderer = getRenderer();
|
||||||
|
renderer.controls?.holdPaintStarted?.(action.immediate);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case 's/SET_MOVE_U':
|
||||||
|
case 's/SET_MOVE_V':
|
||||||
|
case 's/SET_MOVE_W':
|
||||||
|
case 's/CANCEL_MOVE': {
|
||||||
|
if (!action.value) {
|
||||||
|
const renderer = getRenderer();
|
||||||
|
renderer.storeViewInState();
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
import { HOLD_PAINT } from '../../core/constants';
|
||||||
|
|
||||||
const initialState = {
|
const initialState = {
|
||||||
showGrid: false,
|
showGrid: false,
|
||||||
showPixelNotify: false,
|
showPixelNotify: false,
|
||||||
|
@ -6,7 +8,6 @@ const initialState = {
|
||||||
isLightGrid: false,
|
isLightGrid: false,
|
||||||
compactPalette: false,
|
compactPalette: false,
|
||||||
paletteOpen: true,
|
paletteOpen: true,
|
||||||
pencilEnabled: false,
|
|
||||||
mute: false,
|
mute: false,
|
||||||
chatNotify: true,
|
chatNotify: true,
|
||||||
// top-left button menu
|
// top-left button menu
|
||||||
|
@ -15,6 +16,11 @@ const initialState = {
|
||||||
onlineCanvas: false,
|
onlineCanvas: false,
|
||||||
// selected theme
|
// selected theme
|
||||||
style: 'default',
|
style: 'default',
|
||||||
|
// properties that aren't saved
|
||||||
|
holdPaint: HOLD_PAINT.OFF,
|
||||||
|
moveU: 0,
|
||||||
|
moveV: 0,
|
||||||
|
moveW: 0,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
@ -79,13 +85,6 @@ export default function gui(
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
case 's/TGL_PENCIL': {
|
|
||||||
return {
|
|
||||||
...state,
|
|
||||||
pencilEnabled: !state.pencilEnabled,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
case 's/TGL_OPEN_MENU': {
|
case 's/TGL_OPEN_MENU': {
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
|
@ -93,6 +92,25 @@ export default function gui(
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case 's/TGL_MUTE':
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
mute: !state.mute,
|
||||||
|
};
|
||||||
|
|
||||||
|
case 's/TGL_CHAT_NOTIFY':
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
chatNotify: !state.chatNotify,
|
||||||
|
};
|
||||||
|
|
||||||
|
case 's/SELECT_HOLD_PAINT': {
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
holdPaint: action.value,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
case 's/SELECT_STYLE': {
|
case 's/SELECT_STYLE': {
|
||||||
const { style } = action;
|
const { style } = action;
|
||||||
return {
|
return {
|
||||||
|
@ -117,22 +135,43 @@ export default function gui(
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
case 's/TGL_MUTE':
|
case 's/SET_MOVE_U': {
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
mute: !state.mute,
|
moveU: action.value,
|
||||||
};
|
};
|
||||||
|
}
|
||||||
|
|
||||||
case 's/TGL_CHAT_NOTIFY':
|
case 's/SET_MOVE_V': {
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
chatNotify: !state.chatNotify,
|
moveV: action.value,
|
||||||
};
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
case 's/SET_MOVE_W': {
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
moveW: action.value,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
case 's/CANCEL_MOVE': {
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
moveU: 0,
|
||||||
|
moveV: 0,
|
||||||
|
moveW: 0,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
case 'persist/REHYDRATE':
|
case 'persist/REHYDRATE':
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
pencilEnabled: false,
|
holdPaint: HOLD_PAINT.OFF,
|
||||||
|
moveU: 0,
|
||||||
|
moveV: 0,
|
||||||
|
moveW: 0,
|
||||||
};
|
};
|
||||||
|
|
||||||
default:
|
default:
|
||||||
|
|
|
@ -62,7 +62,7 @@ class ChunkLoader2D extends ChunkLoader {
|
||||||
const key = `${this.canvasMaxTiledZoom}:${cx}:${cy}`;
|
const key = `${this.canvasMaxTiledZoom}:${cx}:${cy}`;
|
||||||
const chunk = this.cget(key);
|
const chunk = this.cget(key);
|
||||||
if (!chunk) {
|
if (!chunk) {
|
||||||
return 0;
|
return null;
|
||||||
}
|
}
|
||||||
return chunk.getColorIndex(
|
return chunk.getColorIndex(
|
||||||
getCellInsideChunk(canvasSize, [x, y]),
|
getCellInsideChunk(canvasSize, [x, y]),
|
||||||
|
|
|
@ -95,6 +95,10 @@ class Renderer {
|
||||||
|
|
||||||
updateCanvasData() {}
|
updateCanvasData() {}
|
||||||
|
|
||||||
|
getPointedColor() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
isChunkInView() {
|
isChunkInView() {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
|
@ -166,6 +166,12 @@ class Renderer2D extends Renderer {
|
||||||
return this.chunkLoader.getColorIndexOfPixel(cx, cy);
|
return this.chunkLoader.getColorIndexOfPixel(cx, cy);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getPointedColor() {
|
||||||
|
return this.getColorIndexOfPixel(
|
||||||
|
...this.store.getState().canvas.hover,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
updateView(view, origin) {
|
updateView(view, origin) {
|
||||||
let [x, y, scale] = view;
|
let [x, y, scale] = view;
|
||||||
const state = this.store.getState();
|
const state = this.store.getState();
|
||||||
|
|
|
@ -35,7 +35,7 @@ import {
|
||||||
import {
|
import {
|
||||||
setHover,
|
setHover,
|
||||||
unsetHover,
|
unsetHover,
|
||||||
selectColor,
|
selectHoverColor,
|
||||||
} from '../store/actions';
|
} from '../store/actions';
|
||||||
import pixelTransferController from './PixelTransferController';
|
import pixelTransferController from './PixelTransferController';
|
||||||
|
|
||||||
|
@ -526,6 +526,40 @@ class Renderer3D extends Renderer {
|
||||||
this.updateRollOverMesh(0, 0);
|
this.updateRollOverMesh(0, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getPointedColor() {
|
||||||
|
const {
|
||||||
|
objects,
|
||||||
|
raycaster,
|
||||||
|
mouse,
|
||||||
|
camera,
|
||||||
|
} = this;
|
||||||
|
raycaster.setFromCamera(mouse, camera);
|
||||||
|
|
||||||
|
const intersects = raycaster.intersectObjects(objects);
|
||||||
|
if (intersects.length <= 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const intersect = intersects[0];
|
||||||
|
const target = intersect.point.clone()
|
||||||
|
.add(intersect.face.normal.multiplyScalar(-0.5))
|
||||||
|
.floor()
|
||||||
|
.addScalar(0.5)
|
||||||
|
.floor();
|
||||||
|
if (target.y < 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (target.clone().sub(camera.position).length() < 120) {
|
||||||
|
const cell = target.toArray();
|
||||||
|
if (this.chunkLoader) {
|
||||||
|
const clr = this.chunkLoader.getVoxel(...cell);
|
||||||
|
if (clr) {
|
||||||
|
return clr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
placeVoxel(x, y, z, color = null) {
|
placeVoxel(x, y, z, color = null) {
|
||||||
const {
|
const {
|
||||||
store,
|
store,
|
||||||
|
@ -666,11 +700,8 @@ class Renderer3D extends Renderer {
|
||||||
innerHeight,
|
innerHeight,
|
||||||
} = window;
|
} = window;
|
||||||
const {
|
const {
|
||||||
camera,
|
|
||||||
objects,
|
|
||||||
raycaster,
|
|
||||||
mouse,
|
|
||||||
store,
|
store,
|
||||||
|
mouse,
|
||||||
} = this;
|
} = this;
|
||||||
|
|
||||||
mouse.set(
|
mouse.set(
|
||||||
|
@ -678,6 +709,18 @@ class Renderer3D extends Renderer {
|
||||||
-(clientY / innerHeight) * 2 + 1,
|
-(clientY / innerHeight) * 2 + 1,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (button === 1) {
|
||||||
|
// middle mouse button
|
||||||
|
store.dispatch(selectHoverColor());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const {
|
||||||
|
camera,
|
||||||
|
objects,
|
||||||
|
raycaster,
|
||||||
|
} = this;
|
||||||
|
|
||||||
raycaster.setFromCamera(mouse, camera);
|
raycaster.setFromCamera(mouse, camera);
|
||||||
|
|
||||||
const intersects = raycaster.intersectObjects(objects);
|
const intersects = raycaster.intersectObjects(objects);
|
||||||
|
@ -695,25 +738,6 @@ class Renderer3D extends Renderer {
|
||||||
const [x, y, z] = target.toArray();
|
const [x, y, z] = target.toArray();
|
||||||
this.placeVoxel(x, y, z);
|
this.placeVoxel(x, y, z);
|
||||||
}
|
}
|
||||||
} else if (button === 1) {
|
|
||||||
// middle mouse button
|
|
||||||
const target = intersect.point.clone()
|
|
||||||
.add(intersect.face.normal.multiplyScalar(-0.5))
|
|
||||||
.floor()
|
|
||||||
.addScalar(0.5)
|
|
||||||
.floor();
|
|
||||||
if (target.y < 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (target.clone().sub(camera.position).length() < 120) {
|
|
||||||
const cell = target.toArray();
|
|
||||||
if (this.chunkLoader) {
|
|
||||||
const clr = this.chunkLoader.getVoxel(...cell);
|
|
||||||
if (clr) {
|
|
||||||
store.dispatch(selectColor(clr));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if (button === 2) {
|
} else if (button === 2) {
|
||||||
// right mouse button
|
// right mouse button
|
||||||
const target = intersect.point.clone()
|
const target = intersect.point.clone()
|
||||||
|
|
Loading…
Reference in New Issue
Block a user