function on_touchstart(e) { e.preventDefault(); if (storage.touch.drawing) { return; } // First finger(s) down? if (storage.touch.ids.length === 0) { // We only handle 1 and 2 if (e.changedTouches.length > 2) { return; } storage.touch.ids.length = 0; for (const touch of e.changedTouches) { storage.touch.ids.push(touch.identifier); } if (e.changedTouches.length === 1) { const touch = e.changedTouches[0]; const x = Math.round((touch.clientX + storage.canvas.offset_x) / storage.canvas.zoom); const y = Math.round((touch.clientY + storage.canvas.offset_y) / storage.canvas.zoom); storage.touch.position.x = x; storage.touch.position.y = y; // We give a bit of time to add a second finger storage.touch.waiting_for_second_finger = true; storage.touch.moves = 0; storage.touch.buffered.length = 0; storage.ruler_origin.x = x; storage.ruler_origin.y = y; setTimeout(() => { storage.touch.waiting_for_second_finger = false; }, config.second_finger_timeout); } return; } // There are touches already if (storage.touch.waiting_for_second_finger) { if (e.changedTouches.length === 1) { const changed_touch = e.changedTouches[0]; storage.touch.screen_position.x = changed_touch.clientX; storage.touch.screen_position.y = changed_touch.clientY; storage.touch.ids.push(e.changedTouches[0].identifier); 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 x = touch.clientX; const y = touch.clientY; if (touch.identifier === storage.touch.ids[0]) { first_finger_position = {'x': x, 'y': y}; } if (touch.identifier === storage.touch.ids[1]) { second_finger_position = {'x': x, 'y': y}; } } storage.touch.finger_distance = dist_v2( first_finger_position, second_finger_position); // console.log(storage.touch.finger_distance); } return; } } function on_touchmove(e) { if (storage.touch.ids.length === 1 && !storage.touch.moving) { storage.touch.moves += 1; if (storage.touch.moves > config.buffer_first_touchmoves) { storage.touch.waiting_for_second_finger = false; // Immediately start drawing on move storage.touch.drawing = true; if (storage.ctx1.lineWidth !== storage.cursor.width) { storage.ctx1.lineWidth = storage.cursor.width; } } else { let drawing_touch = null; for (const touch of e.changedTouches) { if (touch.identifier === storage.touch.ids[0]) { drawing_touch = touch; break; } } if (!drawing_touch) { return; } const last_x = storage.touch.position.x; const last_y = storage.touch.position.y; const x = Math.max(Math.round((drawing_touch.clientX + storage.canvas.offset_x) / storage.canvas.zoom), 0); const y = Math.max(Math.round((drawing_touch.clientY + storage.canvas.offset_y) / storage.canvas.zoom), 0); storage.touch.buffered.push({ 'last_x': last_x, 'last_y': last_y, 'x': x, 'y': y, }); storage.touch.position.x = x; storage.touch.position.y = y; } } if (storage.touch.drawing) { let drawing_touch = null; for (const touch of e.changedTouches) { if (touch.identifier === storage.touch.ids[0]) { drawing_touch = touch; break; } } if (!drawing_touch) { return; } const last_x = storage.touch.position.x; const last_y = storage.touch.position.y; const x = storage.touch.position.x = Math.max(Math.round((drawing_touch.clientX + storage.canvas.offset_x) / storage.canvas.zoom), 0); const y = storage.touch.position.y = Math.max(Math.round((drawing_touch.clientY + storage.canvas.offset_y) / storage.canvas.zoom), 0); if (storage.tools.active === 'pencil') { if (storage.touch.buffered.length > 0) { for (const p of storage.touch.buffered) { storage.ctx1.beginPath(); storage.ctx1.moveTo(p.last_x, p.last_y); storage.ctx1.lineTo(p.x, p.y); storage.ctx1.stroke(); const predraw = predraw_event(p.x, p.y); storage.current_stroke.push(predraw); fire_event(predraw); } storage.touch.buffered.length = 0; } storage.ctx1.beginPath(); storage.ctx1.moveTo(last_x, last_y); storage.ctx1.lineTo(x, y); storage.ctx1.stroke(); const predraw = predraw_event(x, y); storage.current_stroke.push(predraw); fire_event(predraw); storage.touch.position.x = x; storage.touch.position.y = y; return; } else if (storage.tools.active === 'eraser') { const erase_step = (last_x, last_y, x, y) => { const erased = strokes_intersect_line(last_x, last_y, x, y); storage.erased.push(...erased); if (erased.length > 0) { for (const other_event of storage.events) { for (const stroke_id of erased) { if (stroke_id === other_event.stroke_id) { if (!other_event.deleted) { other_event.deleted = true; const stats = stroke_stats(other_event.points, storage.cursor.width); redraw_region(stats.bbox); } } } } } }; if (storage.touch.buffered.length > 0) { for (const p of storage.touch.buffered) { erase_step(p.last_x, p.last_y, p.x, p.y); } storage.touch.buffered.length = 0; } erase_step(last_x, last_y, x, y); } else if (storage.tools.active === 'ruler') { const old_ruler = [ {'x': storage.ruler_origin.x, 'y': storage.ruler_origin.y}, {'x': last_x, 'y': last_y} ]; const stats = stroke_stats(old_ruler, storage.cursor.width); const bbox = stats.bbox; storage.ctx1.clearRect(bbox.xmin, bbox.ymin, bbox.xmax - bbox.xmin, bbox.ymax - bbox.ymin); storage.ctx1.beginPath(); storage.ctx1.moveTo(storage.ruler_origin.x, storage.ruler_origin.y); storage.ctx1.lineTo(x, y); storage.ctx1.stroke(); } else { console.error('fuck'); } } if (storage.touch.ids.length === 2) { storage.touch.moving = true; let first_finger_position_screen = null; let second_finger_position_screen = null; let first_finger_position_canvas = null; let second_finger_position_canvas = 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 x = touch.clientX; const y = touch.clientY; const xc = Math.max(Math.round((touch.clientX + storage.canvas.offset_x) / storage.canvas.zoom), 0); const yc = Math.max(Math.round((touch.clientY + storage.canvas.offset_y) / storage.canvas.zoom), 0); if (touch.identifier === storage.touch.ids[0]) { first_finger_position_screen = {'x': x, 'y': y}; first_finger_position_canvas = {'x': xc, 'y': yc}; } if (touch.identifier === storage.touch.ids[1]) { second_finger_position_screen = {'x': x, 'y': y}; second_finger_position_canvas = {'x': xc, 'y': yc}; } } const new_finger_distance = dist_v2( first_finger_position_screen, second_finger_position_screen); const zoom_center = { 'x': (first_finger_position_canvas.x + second_finger_position_canvas.x) / 2.0, 'y': (first_finger_position_canvas.y + second_finger_position_canvas.y) / 2.0 }; for (const touch of e.changedTouches) { // The second finger to be down is considered the "main" one // Movement of the second finger is ignored if (touch.identifier === storage.touch.ids[1]) { const x = Math.round(touch.clientX); const y = Math.round(touch.clientY); const dx = x - storage.touch.screen_position.x; const dy = y - storage.touch.screen_position.y; const old_zoom = storage.canvas.zoom; const old_offset_x = storage.canvas.offset_x; const old_offset_y = storage.canvas.offset_y; storage.canvas.offset_x -= dx; storage.canvas.offset_y -= dy; // console.log(new_finger_distance, storage.touch.finger_distance); const scale_by = new_finger_distance / storage.touch.finger_distance; const dz = storage.canvas.zoom * (scale_by - 1.0); const zoom_offset_y = Math.round(dz * zoom_center.y); const zoom_offset_x = Math.round(dz * zoom_center.x); if (storage.min_zoom <= storage.canvas.zoom * scale_by && storage.canvas.zoom * scale_by <= storage.max_zoom) { storage.canvas.zoom *= scale_by; storage.canvas.offset_x += zoom_offset_x; storage.canvas.offset_y += zoom_offset_y; } storage.touch.finger_distance = new_finger_distance; if (storage.canvas.offset_x !== old_offset_x || storage.canvas.offset_y !== old_offset_y || old_zoom !== storage.canvas.zoom) { move_canvas(); } storage.touch.screen_position.x = x; storage.touch.screen_position.y = y; break; } } return; } } async function on_touchend(e) { for (const touch of e.changedTouches) { if (storage.touch.drawing) { if (storage.touch.ids[0] == touch.identifier) { storage.touch.drawing = false; if (storage.tools.active === 'pencil') { const event = stroke_event(); storage.current_stroke = []; await queue_event(event); } else if (storage.tools.active === 'eraser') { const events = eraser_events(); storage.erased = []; if (events.length > 0) { for (const event of events) { await queue_event(event); } } } else if (storage.tools.active === 'ruler') { const event = ruler_event(storage.touch.position.x, storage.touch.position.y); await queue_event(event); } else { console.error('fuck'); } } } const index = storage.touch.ids.indexOf(touch.identifier); if (index !== -1) { storage.touch.ids.splice(index, 1); } if (storage.touch.moving && storage.touch.ids.length === 0) { // Only allow drawing again when ALL fingers have been lifted storage.touch.moving = false; } } if (storage.touch.ids.length === 0) { waiting_for_second_finger = false; } }