Browse Source

Undo for eraser and image move

master
A.Olokhtonov 1 year ago
parent
commit
b6afe2c8e8
  1. 12
      client/cursor.js
  2. 4
      client/draw.js
  3. 15
      client/index.js
  4. 14
      client/math.js
  5. 75
      client/recv.js

12
client/cursor.js

@ -2,12 +2,14 @@ function on_down(e) { @@ -2,12 +2,14 @@ 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);
@ -15,11 +17,16 @@ function on_down(e) { @@ -15,11 +17,16 @@ function on_down(e) {
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;
}
@ -126,8 +133,11 @@ async function on_up(e) { @@ -126,8 +133,11 @@ async function on_up(e) {
storage.state.moving_image = false;
const image_id = elements.active_image.getAttribute('data-image-id');
const position = storage.images[image_id];
const event = image_move_event(image_id, position.x, position.y);
// 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;
}

4
client/draw.js

@ -43,6 +43,10 @@ function predraw_user(user_id, event) { @@ -43,6 +43,10 @@ function predraw_user(user_id, event) {
}
function redraw_region(bbox) {
if (bbox.xmin === bbox.xmax || bbox.ymin === bbox.ymax) {
return;
}
storage.ctx0.save();
storage.ctx0.clearRect(bbox.xmin, bbox.ymin, bbox.xmax - bbox.xmin, bbox.ymax - bbox.ymin);

15
client/index.js

@ -38,6 +38,9 @@ const storage = { @@ -38,6 +38,9 @@ const storage = {
'spacedown': false,
},
'moving_image_original_x': null,
'moving_image_original_y': null,
'erased': [],
'tool': 'brush',
'predraw': {},
@ -227,6 +230,18 @@ function eraser_events() { @@ -227,6 +230,18 @@ function eraser_events() {
return result;
}
// Generally doesn't return null
function find_stroke_backwards(stroke_id) {
for (let i = storage.events.length - 1; i >= 0; --i) {
const event = storage.events[i];
if (event.type === EVENT.STROKE && event.stroke_id === stroke_id) {
return event;
}
}
return null;
}
function main() {
const url = new URL(window.location.href);
const parts = url.pathname.split('/');

14
client/math.js

@ -81,6 +81,20 @@ function process_stroke(points) { @@ -81,6 +81,20 @@ function process_stroke(points) {
}
function stroke_stats(points, width) {
if (points.length === 0) {
const bbox = {
'xmin': 0,
'ymin': 0,
'xmax': 0,
'ymax': 0
};
return {
'bbox': bbox,
'length': 0,
};
}
let length = 0;
let xmin = points[0].x, ymin = points[0].y;
let xmax = xmin, ymax = ymin;

75
client/recv.js

@ -20,6 +20,12 @@ function des_u16(d) { @@ -20,6 +20,12 @@ function des_u16(d) {
return value;
}
function des_s16(d) {
const value = d.view.getInt16(d.offset, true);
d.offset += 2;
return value;
}
function des_u32(d) {
const value = d.view.getUint32(d.offset, true);
d.offset += 4;
@ -76,8 +82,8 @@ function des_event(d) { @@ -76,8 +82,8 @@ function des_event(d) {
case EVENT.IMAGE:
case EVENT.IMAGE_MOVE: {
event.image_id = des_u32(d);
event.x = des_u16(d);
event.y = des_u16(d);
event.x = des_s16(d); // stored as u16, but actually is s16
event.y = des_s16(d); // stored as u16, but actually is s16
break;
}
@ -133,16 +139,38 @@ async function handle_event(event) { @@ -133,16 +139,38 @@ async function handle_event(event) {
case EVENT.UNDO: {
for (let i = storage.events.length - 1; i >=0; --i) {
const other_event = storage.events[i];
if (other_event.type === EVENT.STROKE && other_event.user_id === event.user_id && !other_event.deleted) {
other_event.deleted = true;
const stats = stroke_stats(other_event.points, storage.cursor.width);
redraw_region(stats.bbox);
break;
} else if (other_event.type === EVENT.IMAGE && other_event.user_id === event.user_id && !other_event.deleted) {
other_event.deleted = true;
const item = document.querySelector(`img[data-image-id="${other_event.image_id}"]`);
if (item) item.remove();
break;
// Users can only undo their own, undeleted (not already undone) events
if (other_event.user_id === event.user_id && !other_event.deleted) {
if (other_event.type === EVENT.STROKE) {
other_event.deleted = true;
const stats = stroke_stats(other_event.points, storage.cursor.width);
redraw_region(stats.bbox);
break;
} else if (other_event.type === EVENT.IMAGE) {
other_event.deleted = true;
const item = document.querySelector(`img[data-image-id="${other_event.image_id}"]`);
if (item) item.remove();
break;
} else if (other_event.type === EVENT.ERASER) {
other_event.deleted = true;
const erased = find_stroke_backwards(other_event.stroke_id);
if (erased) {
erased.deleted = false;
const stats = stroke_stats(erased.points, storage.cursor.width);
redraw_region(stats.bbox);
}
break;
} else if (other_event.type === EVENT.IMAGE_MOVE) {
const item = document.querySelector(`img[data-image-id="${other_event.image_id}"]`);
const ix = storage.images[other_event.image_id].x -= other_event.x;
const iy = storage.images[other_event.image_id].y -= other_event.y;
item.style.transform = `translate(${ix}px, ${iy}px)`;
break;
}
}
}
@ -174,16 +202,27 @@ async function handle_event(event) { @@ -174,16 +202,27 @@ async function handle_event(event) {
}
case EVENT.IMAGE_MOVE: {
const image_id = event.image_id;
const item = document.querySelector(`.floating-image[data-image-id="${image_id}"]`);
item.style.transform = `translate(${event.x}px, ${event.y}px)`;
storage.images[event.image_id] = {
'x': event.x, 'y': event.y
};
// Already moved due to local prediction
if (event.user_id !== storage.me.id) {
const image_id = event.image_id;
const item = document.querySelector(`.floating-image[data-image-id="${image_id}"]`);
const ix = storage.images[event.image_id].x += event.x;
const iy = storage.images[event.image_id].y += event.y;
if (item) {
item.style.transform = `translate(${ix}px, ${iy}px)`;
}
}
break;
}
case EVENT.ERASER: {
if (event.deleted) {
break;
}
for (const other_event of storage.events) {
if (other_event.type === EVENT.STROKE && other_event.stroke_id === event.stroke_id) {
// Might already be deleted because of local prediction

Loading…
Cancel
Save