A.Olokhtonov
2 years ago
19 changed files with 928 additions and 72 deletions
Before Width: | Height: | Size: 957 B After Width: | Height: | Size: 957 B |
@ -0,0 +1,23 @@ |
|||||||
|
function tools_switch(tool) { |
||||||
|
if (storage.tools.active_element) { |
||||||
|
storage.tools.active_element.classList.remove('active'); |
||||||
|
} |
||||||
|
|
||||||
|
storage.tools.active = tool; |
||||||
|
storage.tools.active_element = document.querySelector(`.tool[data-tool="${tool}"`); |
||||||
|
storage.tools.active_element.classList.add('active'); |
||||||
|
} |
||||||
|
|
||||||
|
function tools_init() { |
||||||
|
const pencil = document.querySelector('.tool[data-tool="pencil"]'); |
||||||
|
const ruler = document.querySelector('.tool[data-tool="ruler"]'); |
||||||
|
const eraser = document.querySelector('.tool[data-tool="eraser"]'); |
||||||
|
const undo = document.querySelector('.tool[data-tool="undo"]'); |
||||||
|
|
||||||
|
pencil.addEventListener('click', () => tools_switch('pencil')); |
||||||
|
ruler.addEventListener('click', () => tools_switch('ruler')); |
||||||
|
eraser.addEventListener('click', () => tools_switch('eraser')); |
||||||
|
undo.addEventListener('click', queue_undo); |
||||||
|
|
||||||
|
tools_switch('pencil'); |
||||||
|
} |
@ -0,0 +1,359 @@ |
|||||||
|
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; |
||||||
|
} |
||||||
|
} |
Loading…
Reference in new issue