|
|
|
@ -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; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|