stricter thresholds for clicks and taps to avoid accidental pixel

placing
This commit is contained in:
HF 2024-01-24 13:13:31 +01:00
parent ce09198374
commit 641c996c0c
3 changed files with 71 additions and 33 deletions

View File

@ -16,6 +16,7 @@ import {
screenToWorld, screenToWorld,
getChunkOfPixel, getChunkOfPixel,
getOffsetOfPixel, getOffsetOfPixel,
getTapOrClickCenter,
} from '../core/utils'; } from '../core/utils';
import { import {
HOLD_PAINT, HOLD_PAINT,
@ -28,8 +29,9 @@ class PixelPainterControls {
// //
clickTapStartView = [0, 0]; clickTapStartView = [0, 0];
clickTapStartTime = 0; clickTapStartTime = 0;
clickTapStartCoords = [0, 0];
tapStartDist = 50; tapStartDist = 50;
// screen coords of where a tap/click started
clickTapStartCoords = [0, 0];
// stored speed for acceleration // stored speed for acceleration
speedScalar = 0; speedScalar = 0;
// on mouse: true as long as left mouse button is pressed // on mouse: true as long as left mouse button is pressed
@ -118,7 +120,7 @@ class PixelPainterControls {
].map(Math.abs); ].map(Math.abs);
// thresholds for single click / holding // thresholds for single click / holding
if (clickTapStartTime > Date.now() - 250 if (clickTapStartTime > Date.now() - 250
&& coordsDiff[0] < 3 && coordsDiff[1] < 3 && coordsDiff[0] < 6 && coordsDiff[1] < 6
) { ) {
PixelPainterControls.placePixel( PixelPainterControls.placePixel(
store, store,
@ -231,7 +233,7 @@ class PixelPainterControls {
this.clearTabTimeout(); this.clearTabTimeout();
this.isTapPainting = false; this.isTapPainting = false;
this.clickTapStartTime = Date.now(); this.clickTapStartTime = Date.now();
this.clickTapStartCoords = PixelPainterControls.getTouchCenter(event); this.clickTapStartCoords = getTapOrClickCenter(event);
this.clickTapStartView = this.renderer.view; this.clickTapStartView = this.renderer.view;
if (event.touches.length > 1) { if (event.touches.length > 1) {
@ -263,26 +265,25 @@ class PixelPainterControls {
onTouchEnd(event) { onTouchEnd(event) {
event.preventDefault(); event.preventDefault();
if (event.touches.length) { if (event.touches.length) {
// still other touches left
return; return;
} }
const { store, renderer } = this; const { store, renderer } = this;
if (!this.wasEverMultiTap) { if (!this.wasEverMultiTap) {
const { pageX, pageY } = event.changedTouches[0]; const [clientX, clientY] = getTapOrClickCenter(event);
const { clickTapStartCoords, clickTapStartTime } = this; const { clickTapStartCoords, clickTapStartTime } = this;
const coordsDiff = [ const coordsDiff = [
clickTapStartCoords[0] - pageX, clickTapStartCoords[0] - clientX,
clickTapStartCoords[1] - pageY, clickTapStartCoords[1] - clientY,
].map(Math.abs); ].map(Math.abs);
// thresholds for single click / holding // thresholds for single click / holding
if (clickTapStartTime > Date.now() - 580 if (clickTapStartTime > Date.now() - 580
&& coordsDiff[0] < 3 && coordsDiff[1] < 3 && coordsDiff[0] < 6 && coordsDiff[1] < 6
) { ) {
PixelPainterControls.placePixel( PixelPainterControls.placePixel(
store, store,
this.renderer, this.renderer,
this.screenToWorld([pageX, pageY]), this.screenToWorld([clientX, clientY]),
); );
setTimeout(() => { setTimeout(() => {
store.dispatch(unsetHover()); store.dispatch(unsetHover());
@ -300,7 +301,7 @@ class PixelPainterControls {
const multiTouch = (event.touches.length > 1); const multiTouch = (event.touches.length > 1);
const state = this.store.getState(); const state = this.store.getState();
const [clientX, clientY] = PixelPainterControls.getTouchCenter(event); const [clientX, clientY] = getTapOrClickCenter(event);
if (this.isMultiTap !== multiTouch) { if (this.isMultiTap !== multiTouch) {
this.wasEverMultiTap = true; this.wasEverMultiTap = true;
// if one finger got lifted or added, reset clickTabStart // if one finger got lifted or added, reset clickTabStart
@ -328,7 +329,7 @@ class PixelPainterControls {
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 > 5 || deltaY > 5) {
this.clearTabTimeout(); this.clearTabTimeout();
} }
const { viewscale: scale } = this.renderer; const { viewscale: scale } = this.renderer;

View File

@ -636,6 +636,30 @@ export function getDateKeyOfTs(ts) {
return `${year}${month}${day}`; return `${year}${month}${day}`;
} }
/*
* get screen coords of touch / mouse event
* @param event MouseEvent or TouchEvent
* @return [x, y] in screen coordinates
*/
export function getTapOrClickCenter(event) {
if (event instanceof TouchEvent) {
const touches = (event.touches.length)
? event.touches : event.changedTouches;
let x = 0;
let y = 0;
for (const { pageX, pageY } of touches) {
x += pageX;
y += pageY;
}
const { length } = touches;
return [x / length, y / length];
}
return [
event.clientX,
event.clientY,
];
}
/* /*
* check if parent window exists and * check if parent window exists and
* is accessible * is accessible

View File

@ -28,6 +28,7 @@ import ChunkLoader from './ChunkLoader3D';
import { import {
getChunkOfPixel, getChunkOfPixel,
getOffsetOfPixel, getOffsetOfPixel,
getTapOrClickCenter,
} from '../core/utils'; } from '../core/utils';
import { import {
THREE_TILE_SIZE, THREE_TILE_SIZE,
@ -62,12 +63,15 @@ class Renderer3D extends Renderer {
threeRenderer; threeRenderer;
// temp variables for mouse events // temp variables for mouse events
mouse = new Vector2(); mouse = new Vector2();
mouseMoveStart;
raycaster = new Raycaster(); raycaster = new Raycaster();
pressTime; pressTime;
pressCdTime; pressCdTime;
multitap = 0; multitap = 0;
lastIntersect = 0; lastIntersect = 0;
// on touch: true if current tab was ever more than one figher at any time
wasEverMultiTap = false;
// screen coords of where a tap/click started
clickTapStartCoords = [0, 0];
constructor(store) { constructor(store) {
super(store); super(store);
@ -188,7 +192,6 @@ class Renderer3D extends Renderer {
); );
this.controls = controls; this.controls = controls;
this.onDocumentMouseMove = this.onDocumentMouseMove.bind(this); this.onDocumentMouseMove = this.onDocumentMouseMove.bind(this);
this.onDocumentTouchMove = this.onDocumentTouchMove.bind(this); this.onDocumentTouchMove = this.onDocumentTouchMove.bind(this);
// eslint-disable-next-line max-len // eslint-disable-next-line max-len
@ -516,13 +519,16 @@ class Renderer3D extends Renderer {
); );
} }
onDocumentMouseDownOrTouchStart() { onDocumentMouseDownOrTouchStart(event) {
this.pressTime = Date.now(); this.pressTime = Date.now();
const state = this.store.getState(); this.clickTapStartCoords = getTapOrClickCenter(event);
this.mouseMoveStart = state.canvas.hover; this.wasEverMultiTap = (event.touches?.length > 1);
} }
onDocumentTouchMove() { onDocumentTouchMove(event) {
if (event.touches?.length > 1) {
this.wasEverMultiTap = true;
}
this.updateRollOverMesh(0, 0); this.updateRollOverMesh(0, 0);
} }
@ -581,7 +587,7 @@ class Renderer3D extends Renderer {
); );
} }
multiTapEnd() { multiTapEnd(event) {
const { const {
store, store,
multitap, multitap,
@ -589,12 +595,16 @@ class Renderer3D extends Renderer {
this.multitap = 0; this.multitap = 0;
const state = store.getState(); const state = store.getState();
if (!this.mouseMoveStart || !state.canvas.hover) { if (!state.canvas.hover || this.wasEverMultiTap) {
return; return;
} }
const [px, py, pz] = this.mouseMoveStart; const [clientX, clientY] = getTapOrClickCenter(event);
const [qx, qy, qz] = state.canvas.hover; const { clickTapStartCoords } = this;
if (px !== qx || py !== qy || pz !== qz) { const coordsDiff = [
clickTapStartCoords[0] - clientX,
clickTapStartCoords[1] - clientY,
].map(Math.abs);
if (coordsDiff[0] > 5 || coordsDiff[1] > 5) {
return; return;
} }
@ -605,7 +615,7 @@ class Renderer3D extends Renderer {
if (this.rollOverMesh.position.y < 0) { if (this.rollOverMesh.position.y < 0) {
return; return;
} }
this.placeVoxel(px, py, pz); this.placeVoxel(...state.canvas.hover);
break; break;
} }
case 2: { case 2: {
@ -644,6 +654,9 @@ class Renderer3D extends Renderer {
onDocumentTouchEnd(event) { onDocumentTouchEnd(event) {
event.preventDefault(); event.preventDefault();
if (event.touches.length) {
return;
}
const curTime = Date.now(); const curTime = Date.now();
if (curTime - this.pressTime > 600) { if (curTime - this.pressTime > 600) {
@ -654,7 +667,7 @@ class Renderer3D extends Renderer {
// we should reset on every tap // we should reset on every tap
// but we don't need that right now... // but we don't need that right now...
if (this.multitap === 0) { if (this.multitap === 0) {
setTimeout(this.multiTapEnd, 500); setTimeout(() => this.multiTapEnd(event), 500);
} }
this.multitap += 1; this.multitap += 1;
} }
@ -680,21 +693,21 @@ class Renderer3D extends Renderer {
return; return;
} }
if (!this.mouseMoveStart || !state.canvas.hover) { if (!state.canvas.hover) {
return; return;
} }
const [px, py, pz] = this.mouseMoveStart; const [clientX, clientY] = getTapOrClickCenter(event);
const [qx, qy, qz] = state.canvas.hover; const { clickTapStartCoords } = this;
if (px !== qx || py !== qy || pz !== qz) { const coordsDiff = [
clickTapStartCoords[0] - clientX,
clickTapStartCoords[1] - clientY,
].map(Math.abs);
if (coordsDiff[0] > 5 || coordsDiff[1] > 5) {
return; return;
} }
event.preventDefault(); event.preventDefault();
const { const { button } = event;
clientX,
clientY,
button,
} = event;
const { const {
innerWidth, innerWidth,
innerHeight, innerHeight,