|
|
|
@ -1,39 +1,53 @@
@@ -1,39 +1,53 @@
|
|
|
|
|
function init_listeners(state, context) { |
|
|
|
|
window.addEventListener('keydown', (e) => keydown(e, state)); |
|
|
|
|
window.addEventListener('keyup', (e) => keyup(e, state)); |
|
|
|
|
window.addEventListener('keydown', (e) => keydown(e, state, context)); |
|
|
|
|
window.addEventListener('keyup', (e) => keyup(e, state, context)); |
|
|
|
|
|
|
|
|
|
context.canvas.addEventListener('mousedown', (e) => mousedown(e, state, context)); |
|
|
|
|
context.canvas.addEventListener('mousemove', (e) => mousemove(e, state, context)); |
|
|
|
|
context.canvas.addEventListener('mouseup', (e) => mouseup(e, state, context)); |
|
|
|
|
context.canvas.addEventListener('mouseleave', (e) => mouseup(e, state, context)); |
|
|
|
|
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)); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
function keydown(e, state) { |
|
|
|
|
if (e.code === 'Space') { |
|
|
|
|
function keydown(e, state, context) { |
|
|
|
|
if (e.code === 'Space' && !state.drawing) { |
|
|
|
|
state.spacedown = true; |
|
|
|
|
context.canvas.classList.add('movemode'); |
|
|
|
|
} else if (e.code === 'KeyD') { |
|
|
|
|
|
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
function keyup(e, state) { |
|
|
|
|
if (e.code === 'Space') { |
|
|
|
|
function keyup(e, state, context) { |
|
|
|
|
if (e.code === 'Space' && state.spacedown) { |
|
|
|
|
state.spacedown = false; |
|
|
|
|
state.moving = false; |
|
|
|
|
context.canvas.classList.remove('movemode'); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
function mousedown(e, state, context) { |
|
|
|
|
if (e.button !== 0) { |
|
|
|
|
return; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
if (state.spacedown) { |
|
|
|
|
state.moving = true; |
|
|
|
|
context.canvas.classList.add('moving'); |
|
|
|
|
return; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
const x = cursor_x = (e.clientX - state.canvas.offset.x) / state.canvas.zoom; |
|
|
|
|
const y = cursor_y = (e.clientY - state.canvas.offset.y) / state.canvas.zoom; |
|
|
|
|
|
|
|
|
|
const screenp = {'x': e.clientX, 'y': e.clientY}; |
|
|
|
|
const canvasp = screen_to_canvas(state, screenp); |
|
|
|
|
|
|
|
|
|
state.cursor = canvasp; |
|
|
|
|
clear_dynamic_stroke(state, context); |
|
|
|
|
update_dynamic_stroke(state, context, {'x': x, 'y': y}); |
|
|
|
|
update_dynamic_stroke(state, context, canvasp); |
|
|
|
|
state.drawing = true; |
|
|
|
|
|
|
|
|
|
window.requestAnimationFrame(() => draw(state, context)); |
|
|
|
@ -48,10 +62,12 @@ function mousemove(e, state, context) {
@@ -48,10 +62,12 @@ function mousemove(e, state, context) {
|
|
|
|
|
do_draw = true; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
const screenp = {'x': e.clientX, 'y': e.clientY}; |
|
|
|
|
|
|
|
|
|
if (state.drawing) { |
|
|
|
|
const x = cursor_x = (e.clientX - state.canvas.offset.x) / state.canvas.zoom; |
|
|
|
|
const y = cursor_y = (e.clientY - state.canvas.offset.y) / state.canvas.zoom; |
|
|
|
|
update_dynamic_stroke(state, context, {'x': x, 'y': y}); |
|
|
|
|
const canvasp = screen_to_canvas(state, screenp); |
|
|
|
|
state.cursor = canvasp; |
|
|
|
|
update_dynamic_stroke(state, context, canvasp); |
|
|
|
|
do_draw = true; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
@ -61,8 +77,13 @@ function mousemove(e, state, context) {
@@ -61,8 +77,13 @@ function mousemove(e, state, context) {
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
function mouseup(e, state, context) { |
|
|
|
|
if (state.spacedown) { |
|
|
|
|
if (e.button !== 0) { |
|
|
|
|
return; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
if (state.moving) { |
|
|
|
|
state.moving = false; |
|
|
|
|
context.canvas.classList.remove('moving'); |
|
|
|
|
return; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
@ -83,29 +104,214 @@ function mouseup(e, state, context) {
@@ -83,29 +104,214 @@ function mouseup(e, state, context) {
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
function wheel(e, state, context) { |
|
|
|
|
const x = Math.round((e.clientX - state.canvas.offset.x) / state.canvas.zoom); |
|
|
|
|
const y = Math.round((e.clientY - state.canvas.offset.y) / state.canvas.zoom); |
|
|
|
|
const screenp = {'x': e.clientX, 'y': e.clientY}; |
|
|
|
|
const canvasp = screen_to_canvas(state, screenp); |
|
|
|
|
|
|
|
|
|
const dz = (e.deltaY < 0 ? 0.1 : -0.1); |
|
|
|
|
const old_zoom = state.canvas.zoom; |
|
|
|
|
|
|
|
|
|
state.canvas.zoom *= (1.0 + dz); |
|
|
|
|
|
|
|
|
|
if (state.canvas.zoom > 100.0) { |
|
|
|
|
if (state.canvas.zoom > config.max_zoom) { |
|
|
|
|
state.canvas.zoom = old_zoom; |
|
|
|
|
return; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
if (state.canvas.zoom < 0.2) { |
|
|
|
|
if (state.canvas.zoom < config.min_zoom) { |
|
|
|
|
state.canvas.zoom = old_zoom; |
|
|
|
|
return; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
const zoom_offset_x = Math.round((dz * old_zoom) * x); |
|
|
|
|
const zoom_offset_y = Math.round((dz * old_zoom) * y); |
|
|
|
|
const zoom_offset_x = Math.round((dz * old_zoom) * canvasp.x); |
|
|
|
|
const zoom_offset_y = Math.round((dz * old_zoom) * canvasp.y); |
|
|
|
|
|
|
|
|
|
state.canvas.offset.x -= zoom_offset_x; |
|
|
|
|
state.canvas.offset.y -= zoom_offset_y; |
|
|
|
|
|
|
|
|
|
window.requestAnimationFrame(() => draw(state, context)); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
function touchstart(e, state) { |
|
|
|
|
e.preventDefault(); |
|
|
|
|
|
|
|
|
|
if (state.touch.drawing) { |
|
|
|
|
// Ingore subsequent touches if we are already drawing
|
|
|
|
|
return; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// 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); |
|
|
|
|
|
|
|
|
|
setTimeout(() => { |
|
|
|
|
state.touch.waiting_for_second_finger = false; |
|
|
|
|
}, config.second_finger_timeout); |
|
|
|
|
} else { |
|
|
|
|
console.error('Two touchstarts at the same time are not yet supported'); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
return; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// There are touches already
|
|
|
|
|
if (state.touch.waiting_for_second_finger) { |
|
|
|
|
if (e.changedTouches.length === 1) { |
|
|
|
|
state.touch.ids.push(e.changedTouches[0].identifier); |
|
|
|
|
|
|
|
|
|
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; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
return; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
function touchmove(e, state, context) { |
|
|
|
|
if (state.touch.ids.length === 1) { |
|
|
|
|
const touch = find_touch(e.changedTouches, state.touch.ids[0]); |
|
|
|
|
|
|
|
|
|
const screenp = {'x': window.devicePixelRatio * touch.clientX, 'y': window.devicePixelRatio * touch.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; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
if (!state.touch.drawing) { |
|
|
|
|
// Buffer this move
|
|
|
|
|
state.touch.moves += 1; |
|
|
|
|
|
|
|
|
|
if (state.touch.moves > config.buffer_first_touchmoves) { |
|
|
|
|
// Start drawing, no more waiting
|
|
|
|
|
state.touch.waiting_for_second_finger = false; |
|
|
|
|
state.touch.drawing = true; |
|
|
|
|
} else { |
|
|
|
|
state.touch.buffered.push(canvasp); |
|
|
|
|
} |
|
|
|
|
} else { |
|
|
|
|
// Handle buffered moves
|
|
|
|
|
if (state.touch.buffered.length > 0) { |
|
|
|
|
clear_dynamic_stroke(state, context); |
|
|
|
|
|
|
|
|
|
for (const p of state.touch.buffered) { |
|
|
|
|
update_dynamic_stroke(state, context, p); |
|
|
|
|
// const predraw = predraw_event(p.x, p.y);
|
|
|
|
|
// fire_event(predraw);
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
state.touch.buffered.length = 0; |
|
|
|
|
} |
|
|
|
|
// const predraw = predraw_event(x, y);
|
|
|
|
|
// fire_event(predraw);
|
|
|
|
|
|
|
|
|
|
update_dynamic_stroke(state, context, canvasp); |
|
|
|
|
|
|
|
|
|
window.requestAnimationFrame(() => draw(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 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); |
|
|
|
|
|
|
|
|
|
const zoom_offset_x = dz * new_finger_midpoint.x; |
|
|
|
|
const zoom_offset_y = dz * new_finger_midpoint.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.first_finger_position = first_finger_position; |
|
|
|
|
state.touch.second_finger_position = second_finger_position; |
|
|
|
|
|
|
|
|
|
window.requestAnimationFrame(() => draw(state, context)); |
|
|
|
|
|
|
|
|
|
return; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
function touchend(e, state, context) { |
|
|
|
|
for (const touch of e.changedTouches) { |
|
|
|
|
if (state.touch.drawing) { |
|
|
|
|
if (state.touch.ids[0] == touch.identifier) { |
|
|
|
|
// const event = stroke_event();
|
|
|
|
|
// await queue_event(event);
|
|
|
|
|
|
|
|
|
|
const stroke = { |
|
|
|
|
'color': Math.round(Math.random() * 4294967295), |
|
|
|
|
'points': process_stroke(state.current_stroke.points) |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
add_static_stroke(state, context, stroke); |
|
|
|
|
clear_dynamic_stroke(state, context); |
|
|
|
|
state.touch.drawing = false; |
|
|
|
|
|
|
|
|
|
window.requestAnimationFrame(() => draw(state, context)) |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
const index = state.touch.ids.indexOf(touch.identifier); |
|
|
|
|
|
|
|
|
|
if (index !== -1) { |
|
|
|
|
state.touch.ids.splice(index, 1); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
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; |
|
|
|
|
} |
|
|
|
|
} |