|
|
|
function on_down(e) {
|
|
|
|
const x = Math.round((e.clientX + storage.canvas.offset_x) / storage.canvas.zoom);
|
|
|
|
const y = Math.round((e.clientY + storage.canvas.offset_y) / storage.canvas.zoom);
|
|
|
|
|
|
|
|
// Scroll wheel (mouse button 3)
|
|
|
|
if (e.button === 1) {
|
|
|
|
// storage.state.moving = true;
|
|
|
|
// storage.state.mousedown = true;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Right mouse button
|
|
|
|
if (e.button === 2) {
|
|
|
|
const image_hit = image_at(x, y);
|
|
|
|
activate_image(image_hit);
|
|
|
|
e.preventDefault();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Left mouse button
|
|
|
|
if (e.button === 0) {
|
|
|
|
const image_hit = image_at(x, y);
|
|
|
|
|
|
|
|
if (elements.active_image !== null && image_hit !== null) {
|
|
|
|
const image_id = image_hit.getAttribute('data-image-id');
|
|
|
|
const image_position = storage.images[image_id];
|
|
|
|
storage.state.moving_image = true;
|
|
|
|
storage.moving_image_original_x = image_position.x;
|
|
|
|
storage.moving_image_original_y = image_position.y;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (storage.state.moving) {
|
|
|
|
storage.state.mousedown = true;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
storage.state.drawing = true;
|
|
|
|
|
|
|
|
if (storage.ctx1.lineWidth !== storage.cursor.width) {
|
|
|
|
storage.ctx1.lineWidth = storage.cursor.width;
|
|
|
|
}
|
|
|
|
|
|
|
|
storage.cursor.x = x;
|
|
|
|
storage.cursor.y = y;
|
|
|
|
|
|
|
|
if (storage.tools.active === 'pencil') {
|
|
|
|
const predraw = predraw_event(x, y);
|
|
|
|
storage.current_stroke.push(predraw);
|
|
|
|
fire_event(predraw);
|
|
|
|
} else if (storage.tools.active === 'ruler') {
|
|
|
|
storage.ruler_origin.x = x;
|
|
|
|
storage.ruler_origin.y = y;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function on_move(e) {
|
|
|
|
const last_x = storage.cursor.x;
|
|
|
|
const last_y = storage.cursor.y;
|
|
|
|
|
|
|
|
const x = storage.cursor.x = Math.max(Math.round((e.clientX + storage.canvas.offset_x) / storage.canvas.zoom), 0);
|
|
|
|
const y = storage.cursor.y = Math.max(Math.round((e.clientY + storage.canvas.offset_y) / storage.canvas.zoom), 0);
|
|
|
|
|
|
|
|
const old_offset_x = storage.canvas.offset_x;
|
|
|
|
const old_offset_y = storage.canvas.offset_y;
|
|
|
|
|
|
|
|
if (elements.active_image && storage.state.moving_image) {
|
|
|
|
const dx = Math.round(e.movementX / storage.canvas.zoom);
|
|
|
|
const dy = Math.round(e.movementY / storage.canvas.zoom);
|
|
|
|
|
|
|
|
const image_id = elements.active_image.getAttribute('data-image-id');
|
|
|
|
|
|
|
|
const ix = storage.images[image_id].x += dx;
|
|
|
|
const iy = storage.images[image_id].y += dy;
|
|
|
|
|
|
|
|
elements.active_image.style.transform = `translate(${ix}px, ${iy}px)`;
|
|
|
|
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (storage.state.drawing) {
|
|
|
|
if (storage.tools.active === 'pencil') {
|
|
|
|
const width = storage.cursor.width;
|
|
|
|
|
|
|
|
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);
|
|
|
|
} else if (storage.tools.active === 'eraser') {
|
|
|
|
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);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} 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');
|
|
|
|
}
|
|
|
|
} else if (storage.state.moving && storage.state.mousedown) {
|
|
|
|
storage.canvas.offset_x -= e.movementX;
|
|
|
|
storage.canvas.offset_y -= e.movementY;
|
|
|
|
|
|
|
|
if (storage.canvas.offset_x !== old_offset_x || storage.canvas.offset_y !== old_offset_y) {
|
|
|
|
move_canvas();
|
|
|
|
}
|
|
|
|
|
|
|
|
// if (storage.canvas.offset_x > storage.canvas.max_scroll_x) storage.canvas.offset_x = storage.canvas.max_scroll_x;
|
|
|
|
// if (storage.canvas.offset_x < 0) storage.canvas.offset_x = 0;
|
|
|
|
// if (storage.canvas.offset_y > storage.canvas.max_scroll_y) storage.canvas.offset_y = storage.canvas.max_scroll_y;
|
|
|
|
// if (storage.canvas.offset_y < 0) storage.canvas.offset_y = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
e.preventDefault();
|
|
|
|
}
|
|
|
|
|
|
|
|
async function on_up(e) {
|
|
|
|
if (storage.state.moving_image && e.button === 0) {
|
|
|
|
storage.state.moving_image = false;
|
|
|
|
const image_id = elements.active_image.getAttribute('data-image-id');
|
|
|
|
const position = storage.images[image_id];
|
|
|
|
// Store delta instead of new position for easy undo
|
|
|
|
const event = image_move_event(image_id, position.x - storage.moving_image_original_x, position.y - storage.moving_image_original_y);
|
|
|
|
await queue_event(event);
|
|
|
|
storage.moving_image_original_x = null;
|
|
|
|
storage.moving_image_original_y = null;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (storage.state.moving && (e.button === 1 || e.button === 0)) {
|
|
|
|
storage.state.mousedown = false;
|
|
|
|
if (!storage.state.spacedown) {
|
|
|
|
storage.state.moving = false;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (storage.state.drawing && e.button === 0) {
|
|
|
|
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.cursor.x, storage.cursor.y);
|
|
|
|
await queue_event(event);
|
|
|
|
} else {
|
|
|
|
console.error('fuck');
|
|
|
|
}
|
|
|
|
|
|
|
|
storage.state.drawing = false;
|
|
|
|
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function on_keydown(e) {
|
|
|
|
if (e.code === 'Space' && !storage.state.drawing) {
|
|
|
|
storage.state.moving = true;
|
|
|
|
storage.state.spacedown = true;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (e.code === 'KeyZ' && e.ctrlKey) {
|
|
|
|
undo();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function on_keyup(e) {
|
|
|
|
if (e.code === 'Space' && storage.state.moving) {
|
|
|
|
storage.state.moving = false;
|
|
|
|
storage.state.spacedown = false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function on_leave(e) {
|
|
|
|
// TODO: broken when "moving"
|
|
|
|
if (storage.state.moving) {
|
|
|
|
storage.state.moving = false;
|
|
|
|
storage.state.holding = false;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function on_resize(e) {
|
|
|
|
const width = window.innerWidth;
|
|
|
|
const height = window.innerHeight;
|
|
|
|
|
|
|
|
elements.canvas0.width = elements.canvas1.width = width;
|
|
|
|
elements.canvas0.height = elements.canvas1.height = height;
|
|
|
|
|
|
|
|
storage.ctx1.lineJoin = storage.ctx1.lineCap = storage.ctx0.lineJoin = storage.ctx0.lineCap = 'round';
|
|
|
|
storage.ctx1.lineWidth = storage.ctx0.lineWidth = storage.cursor.width;
|
|
|
|
|
|
|
|
redraw_region({'xmin': 0, 'xmax': width, 'ymin': 0, 'ymax': width});
|
|
|
|
// storage.canvas.max_scroll_x = storage.canvas.width - window.innerWidth;
|
|
|
|
// storage.canvas.max_scroll_y = storage.canvas.height - window.innerHeight;
|
|
|
|
}
|
|
|
|
|
|
|
|
async function on_drop(e) {
|
|
|
|
e.preventDefault();
|
|
|
|
const file = e.dataTransfer.files[0];
|
|
|
|
const bitmap = await createImageBitmap(file);
|
|
|
|
|
|
|
|
const x = storage.cursor.x - Math.round(bitmap.width / 2);
|
|
|
|
const y = storage.cursor.y - Math.round(bitmap.height / 2);
|
|
|
|
|
|
|
|
// storage.ctx0.drawImage(bitmap, x, y);
|
|
|
|
|
|
|
|
const form_data = new FormData();
|
|
|
|
form_data.append('file', file);
|
|
|
|
|
|
|
|
const resp = await fetch(`/api/image?deskId=${storage.desk_id}`, {
|
|
|
|
method: 'post',
|
|
|
|
body: form_data,
|
|
|
|
})
|
|
|
|
|
|
|
|
if (resp.ok) {
|
|
|
|
const image_id = await resp.text();
|
|
|
|
const event = image_event(image_id, x, y);
|
|
|
|
await queue_event(event);
|
|
|
|
}
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
function on_wheel(e) {
|
|
|
|
return;
|
|
|
|
|
|
|
|
const x = Math.round((e.clientX + storage.canvas.offset_x) / storage.canvas.zoom);
|
|
|
|
const y = Math.round((e.clientY + storage.canvas.offset_y) / storage.canvas.zoom);
|
|
|
|
|
|
|
|
const dz = (e.deltaY < 0 ? 0.1 : -0.1);
|
|
|
|
|
|
|
|
storage.canvas.zoom += dz;
|
|
|
|
|
|
|
|
if (storage.canvas.zoom > storage.max_zoom) {
|
|
|
|
storage.canvas.zoom = storage.max_zoom;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (storage.canvas.zoom < storage.min_zoom) {
|
|
|
|
storage.canvas.zoom = storage.min_zoom;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
const zoom_offset_x = Math.round(dz * x);
|
|
|
|
const zoom_offset_y = Math.round(dz * y);
|
|
|
|
|
|
|
|
storage.canvas.offset_x += zoom_offset_x;
|
|
|
|
storage.canvas.offset_y += zoom_offset_y;
|
|
|
|
|
|
|
|
move_canvas();
|
|
|
|
}
|
|
|
|
|
|
|
|
function cancel(e) {
|
|
|
|
e.preventDefault();
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
function update_brush() {
|
|
|
|
elements.brush_preview.classList.remove('dhide');
|
|
|
|
|
|
|
|
const color = elements.brush_color.value;
|
|
|
|
const width = elements.brush_width.value;
|
|
|
|
|
|
|
|
storage.cursor.color = color;
|
|
|
|
storage.cursor.width = width;
|
|
|
|
|
|
|
|
const x = Math.round(storage.cursor.x - width / 2);
|
|
|
|
const y = Math.round(storage.cursor.y - width / 2);
|
|
|
|
|
|
|
|
elements.brush_preview.style.transform = `translate(${x}px, ${y}px)`;
|
|
|
|
elements.brush_preview.style.width = width + 'px';
|
|
|
|
elements.brush_preview.style.height = width + 'px';
|
|
|
|
elements.brush_preview.style.background = color;
|
|
|
|
|
|
|
|
if (storage.timers.brush_preview) {
|
|
|
|
clearTimeout(storage.timers.brush_preview);
|
|
|
|
}
|
|
|
|
|
|
|
|
storage.timers.brush_preview = setTimeout(() => {
|
|
|
|
elements.brush_preview.classList.add('dhide');
|
|
|
|
}, 1000);
|
|
|
|
}
|