Browse Source

Drawing with one finger kinda works again

sdf
A.Olokhtonov 2 months ago
parent
commit
7f9d04d620
  1. 10
      client/aux.js
  2. 4
      client/index.js
  3. 289
      client/webgl_listeners.js

10
client/aux.js

@ -104,16 +104,6 @@ function event_size(event) { @@ -104,16 +104,6 @@ function event_size(event) {
return size;
}
function find_touch(touchlist, id) {
for (const touch of touchlist) {
if (touch.identifier === id) {
return touch;
}
}
return null;
}
function find_image(state, image_id) {
for (let i = state.events.length - 1; i >= 0; --i) {
const event = state.events[i];

4
client/index.js

@ -129,10 +129,8 @@ async function main() { @@ -129,10 +129,8 @@ async function main() {
'moving': false,
'erasing': false,
'waiting_for_second_finger': false,
'first_finger_position': null,
'second_finger_position': null,
'buffered': [],
'ids': [],
'events': [],
},
'moving': false,

289
client/webgl_listeners.js

@ -3,21 +3,17 @@ function init_listeners(state, context) { @@ -3,21 +3,17 @@ function init_listeners(state, context) {
window.addEventListener('keyup', (e) => keyup(e, state, context));
window.addEventListener('paste', (e) => paste(e, state, context));
context.canvas.addEventListener('pointerdown', (e) => mousedown(e, state, context));
context.canvas.addEventListener('pointermove', (e) => mousemove(e, state, context));
context.canvas.addEventListener('pointerup', (e) => mouseup(e, state, context));
context.canvas.addEventListener('pointerleave', (e) => mouseup(e, state, context));
context.canvas.addEventListener('pointerleave', (e) => mouseleave(e, state, context));
context.canvas.addEventListener('pointerdown', (e) => pointerdown(e, state, context));
context.canvas.addEventListener('pointermove', (e) => pointermove(e, state, context));
context.canvas.addEventListener('pointerup', (e) => pointerup(e, state, context));
context.canvas.addEventListener('pointercancel', (e) => pointerup(e, state, context));
//context.canvas.addEventListener('pointerleave', (e) => pointerup(e, state, context));
context.canvas.addEventListener('pointerleave', (e) => pointerleave(e, state, context));
context.canvas.addEventListener('contextmenu', cancel);
context.canvas.addEventListener('wheel', (e) => wheel(e, state, context));
context.canvas.addEventListener('touchstart', (e) => touchstart(e, state, context));
context.canvas.addEventListener('touchmove', (e) => touchmove(e, state, context));
context.canvas.addEventListener('touchend', (e) => touchend(e, state, context));
context.canvas.addEventListener('touchcancel', (e) => touchend(e, state, context));
context.canvas.addEventListener('drop', (e) => on_drop(e, state, context));
context.canvas.addEventListener('dragover', (e) => mousemove(e, state, context));
//context.canvas.addEventListener('dragover', (e) => pointermove(e, state, context));
debug_panel_init(state, context);
}
@ -182,13 +178,20 @@ function keyup(e, state, context) { @@ -182,13 +178,20 @@ function keyup(e, state, context) {
state.moving = false;
context.canvas.classList.remove('movemode');
} else if (e.code === 'ControlLeft' || e.code === 'ControlRight') {
exit_picker_mode(state);exit_picker_mode
exit_picker_mode(state);
} else if (e.code === 'KeyZ') {
state.zoomdown = false;
}
}
function mousedown(e, state, context) {
function pointerdown(e, state, context) {
// e.pointerType can have three values: "mouse", "pen", "touch"
// https://developer.mozilla.org/en-US/docs/Web/API/PointerEvent/pointerType
if (e.pointerType === "touch") {
touchstart(e, state, context);
return;
}
const screenp = {'x': window.devicePixelRatio * e.clientX, 'y': window.devicePixelRatio * e.clientY};
const canvasp = screen_to_canvas(state, screenp);
const raw_canvasp = {...canvasp};
@ -300,7 +303,12 @@ function update_color_picker_color(state, context, canvasp) { @@ -300,7 +303,12 @@ function update_color_picker_color(state, context, canvasp) {
state.color_picked = color_under_cursor;
}
function mousemove(e, state, context) {
function pointermove(e, state, context) {
if (e.pointerType === "touch") {
touchmove(e, state, context);
return;
}
e.preventDefault();
let do_draw = false;
@ -487,7 +495,12 @@ function mousemove(e, state, context) { @@ -487,7 +495,12 @@ function mousemove(e, state, context) {
return false;
}
function mouseup(e, state, context) {
function pointerup(e, state, context) {
if (e.pointerType === "touch") {
touchend(e, state, context);
return;
}
const screenp = {'x': window.devicePixelRatio * e.clientX, 'y': window.devicePixelRatio * e.clientY};
const canvasp = screen_to_canvas(state, screenp);
const raw_canvasp = {...canvasp};
@ -566,7 +579,7 @@ function mouseup(e, state, context) { @@ -566,7 +579,7 @@ function mouseup(e, state, context) {
}
}
function mouseleave(e, state, context) {
function pointerleave(e, state, context) {
if (state.moving) {
state.moving = false;
context.canvas.classList.remove('movemode');
@ -637,77 +650,51 @@ function wheel(e, state, context) { @@ -637,77 +650,51 @@ function wheel(e, state, context) {
schedule_draw(state, context);
}
function start_move(e, state, context) {
// two touch identifiers are expected to be pushed into state.touch.ids at this point
// TODO: @touch, remove preview
fire_event(state, clear_event(state)); // Tell others to hide predraws of this stroke
for (const touch of e.touches) {
const screenp = {'x': window.devicePixelRatio * touch.clientX, 'y': window.devicePixelRatio * touch.clientY};
if (touch.identifier === state.touch.ids[0]) {
state.touch.first_finger_position = screenp;
} else if (touch.identifier === state.touch.ids[1]) {
state.touch.second_finger_position = screenp;
}
}
}
function touchstart(e, state, context) {
e.preventDefault();
// First finger(s) down?
if (state.touch.ids.length === 0) {
if (e.changedTouches.length === 1) {
// We give a bit of time to add a second finger
state.touch.waiting_for_second_finger = true;
state.touch.moves = 0;
state.touch.buffered.length = 0;
state.touch.ids.push(e.changedTouches[0].identifier);
state.touch.drawing = true;
setTimeout(() => {
state.touch.waiting_for_second_finger = false;
}, config.second_finger_timeout);
} else {
state.touch.ids.push(e.changedTouches[0].identifier);
state.touch.ids.push(e.changedTouches[1].identifier);
start_move(e, state, context);
}
return;
const screenp = {'x': window.devicePixelRatio * e.clientX, 'y': window.devicePixelRatio * e.clientY};
if (state.touch.events.length === 0) {
// We give a bit of time to add a second finger
state.touch.buffered.length = 0;
state.touch.events.push({
'id': e.pointerId,
'x': screenp.x,
'y': screenp.y,
});
state.touch.drawing = true;
const canvasp = screen_to_canvas(state, screenp);
canvasp.pressure = 128; // TODO: check out touch devices' e.pressure
geometry_start_prestroke(state, state.me);
geometry_add_prepoint(state, context, state.me, canvasp, e.pointerType === "pen");
setTimeout(() => {
state.touch.waiting_for_second_finger = false;
}, config.second_finger_timeout);
} else if (state.touch.waiting_for_second_finger) {
state.touch.events.push({
'id': e.pointerId,
'x': screenp.x,
'y': screenp.y,
});
// TODO: @touch, remove preview
fire_event(state, clear_event(state)); // Tell others to hide predraws of this stroke
state.touch.drawing = false;
state.touch.moving = true;
}
// There are touches already
if (state.touch.waiting_for_second_finger) {
if (e.changedTouches.length === 1) {
state.touch.ids.push(e.changedTouches[0].identifier);
start_move(e, state, context);
}
return;
}
schedule_draw(state, context);
}
function touchmove(e, state, context) {
if (state.touch.ids.length === 1) {
const touch = find_touch(e.changedTouches, state.touch.ids[0]);
if (!touch) {
return;
}
const changed_touch = state.touch.events.find(ev => ev.id === e.pointerId);
if (!changed_touch) {
return;
}
const screenp = {'x': window.devicePixelRatio * touch.clientX, 'y': window.devicePixelRatio * touch.clientY};
if (state.touch.drawing) {
const screenp = {'x': window.devicePixelRatio * e.clientX, 'y': window.devicePixelRatio * e.clientY};
const canvasp = screen_to_canvas(state, screenp);
if (state.touch.moving) {
// Can happen if we have been panning the canvas and lifted one finger,
// but not the second one
return;
}
state.touch.moves += 1;
if (state.touch.moves > config.buffer_first_touchmoves) {
@ -716,8 +703,7 @@ function touchmove(e, state, context) { @@ -716,8 +703,7 @@ function touchmove(e, state, context) {
}
canvasp.pressure = 128; // TODO: check out touch devices' e.pressure
// TODO: fix when doing @touch
//geometry_add_point(state, context, state.me, canvasp);
geometry_add_prepoint(state, context, state.me, canvasp, false);
fire_event(state, predraw_event(canvasp.x, canvasp.y));
schedule_draw(state, context);
@ -725,55 +711,57 @@ function touchmove(e, state, context) { @@ -725,55 +711,57 @@ function touchmove(e, state, context) {
return;
}
if (state.touch.ids.length === 2) {
state.touch.moving = true;
let first_finger_position = null;
let second_finger_position = null;
// A separate loop because touches might be in different order ? (question mark)
// IMPORTANT: e.touches, not e.changedTouches!
for (const touch of e.touches) {
const screenp = {'x': window.devicePixelRatio * touch.clientX, 'y': window.devicePixelRatio * touch.clientY};
if (touch.identifier === state.touch.ids[0]) {
first_finger_position = screenp;
} else if (touch.identifier === state.touch.ids[1]) {
second_finger_position = screenp;
}
}
const old_finger_midpoint = mid_v2(state.touch.first_finger_position, state.touch.second_finger_position);
const new_finger_midpoint = mid_v2(first_finger_position, second_finger_position);
const new_finger_midpoint_canvas = mid_v2(
screen_to_canvas(state, first_finger_position),
screen_to_canvas(state, second_finger_position)
);
const old_finger_distance = dist_v2(state.touch.first_finger_position, state.touch.second_finger_position);
const new_finger_distance = dist_v2(first_finger_position, second_finger_position);
const dx = new_finger_midpoint.x - old_finger_midpoint.x;
const dy = new_finger_midpoint.y - old_finger_midpoint.y;
const old_zoom = state.canvas.zoom;
state.canvas.offset.x += dx;
state.canvas.offset.y += dy;
// console.log(new_finger_distance, state.touch.finger_distance);
const scale_by = new_finger_distance / old_finger_distance;
const dz = state.canvas.zoom * (scale_by - 1.0);
if (state.touch.moving) {
if (state.touch.events.length === 1) {
// Simplified move with one finger
state.canvas.offset.x += e.movementX;
state.canvas.offset.y += e.movementY;
const zoom_offset_x = dz * new_finger_midpoint_canvas.x;
const zoom_offset_y = dz * new_finger_midpoint_canvas.y;
if (config.min_zoom <= state.canvas.zoom * scale_by && state.canvas.zoom * scale_by <= config.max_zoom) {
state.canvas.zoom *= scale_by;
state.canvas.offset.x -= zoom_offset_x;
state.canvas.offset.y -= zoom_offset_y;
state.touch.events[0].x = e.clientX * window.devicePixelRatio;
state.touch.events[0].y = e.clientX * window.devicePixelRatio;
} else {
const old_f1 = {...state.touch.events[0]};
const old_f2 = {...state.touch.events[1]};
const old_finger_midpoint = mid_v2(old_f1, old_f2);
const old_finger_distance = dist_v2(old_f1, old_f2);
const changed_touch = state.touch.events.find(ev => ev.id === e.pointerId);
changed_touch.x = e.clientX * window.devicePixelRatio;
changed_touch.y = e.clientY * window.devicePixelRatio;
const new_f1 = state.touch.events[0];
const new_f2 = state.touch.events[1];
const new_finger_midpoint = mid_v2(new_f1, new_f2);
const new_finger_distance = dist_v2(new_f1, new_f2);
const new_finger_midpoint_canvas = mid_v2(
screen_to_canvas(state, new_f1),
screen_to_canvas(state, new_f2)
);
// Ideally, here we would solve for both finger positions
// remaining the same in canvas coordinates. However, we don't
// support canvas rotations, and solving this without rotations
// is impossible (imagine two fingers rotating around a common point).
// Instead, we treat the movement as translation + scale. The translate
// offset is based on the screen-space offset of the midpoint between
// the fingers. The scale is based on the distance between the fingers.
// QUITE POSSIBLY, THIS COULD BE IMPROVED
const dx = new_finger_midpoint.x - old_finger_midpoint.x;
const dy = new_finger_midpoint.y - old_finger_midpoint.y;
const old_zoom = state.canvas.zoom;
state.canvas.offset.x += dx;
state.canvas.offset.y += dy;
const scale_by = new_finger_distance / old_finger_distance;
const dz = state.canvas.zoom * (scale_by - 1.0);
const zc = old_finger_midpoint_canvas;
state.canvas.offset.x = zc.x - (zc.x - state.canvas.offset.x) * state.canvas.zoom / old_zoom;
state.canvas.offset.y = zc.y - (zc.y - state.canvas.offset.y) * state.canvas.zoom / old_zoom;
}
// If we are moving our canvas, we don't need to follow anymore
@ -781,9 +769,6 @@ function touchmove(e, state, context) { @@ -781,9 +769,6 @@ function touchmove(e, state, context) {
toggle_follow_player(state, state.following_player);
}
state.touch.first_finger_position = first_finger_position;
state.touch.second_finger_position = second_finger_position;
fire_event(state, movecanvas_event(state));
draw_html(state, context);
schedule_draw(state, context);
@ -793,31 +778,37 @@ function touchmove(e, state, context) { @@ -793,31 +778,37 @@ function touchmove(e, state, context) {
}
function touchend(e, state, context) {
for (const touch of e.changedTouches) {
if (state.touch.drawing) {
if (state.touch.ids[0] == touch.identifier) {
const stroke = geometry_prepare_stroke(state);
if (stroke) {
queue_event(state, stroke_event(state));
schedule_draw(state, context);
}
const changed_touch_idx = state.touch.events.findIndex(ev => ev.id === e.pointerId);
if (changed_touch_idx === -1) {
// This can happen, becase we don't keep track of touches
// after two fingers are already active
return;
}
state.touch.drawing = false;
}
}
state.touch.events.splice(changed_touch_idx, 1);
const index = state.touch.ids.indexOf(touch.identifier);
if (state.touch.drawing) {
const stroke = geometry_prepare_stroke(state);
if (index !== -1) {
state.touch.ids.splice(index, 1);
if (stroke) {
queue_event(state, stroke_event(state));
schedule_draw(state, context);
}
fire_event(state, lift_event());
state.touch.drawing = false;
return;
}
if (state.touch.ids.length === 0) {
// Only allow drawing again when ALL fingers have been lifted
state.touch.moving = false;
waiting_for_second_finger = false;
if (state.touch.moving) {
if (state.touch.events.length === 0) {
// Only allow drawing again when ALL fingers have been lifted
state.touch.moving = false;
waiting_for_second_finger = false;
}
return;
}
}

Loading…
Cancel
Save