From bdc3bdc9a1ebe7ecb2792539fa6829154dd30975 Mon Sep 17 00:00:00 2001 From: "A.Olokhtonov" Date: Sun, 8 Sep 2024 00:05:46 +0300 Subject: [PATCH] Undo for all available actions --- README.txt | 2 +- client/client_recv.js | 53 ++++++++++++++++++++++++++++++++++++---- client/webgl_draw.js | 28 +++++++++++---------- client/webgl_geometry.js | 24 +++++++++--------- 4 files changed, 77 insertions(+), 30 deletions(-) diff --git a/README.txt b/README.txt index 96af0e1..e24fdab 100644 --- a/README.txt +++ b/README.txt @@ -46,7 +46,7 @@ Release: - Alignment (horizontal, vertical, diagonal, etc) + Undo + Undo for eraser - - Undo for images (add, move, scale) + + Undo for images (add, move, scale) - Redo + Snapping to grid - Snapping to other points? diff --git a/client/client_recv.js b/client/client_recv.js index 16061a3..f9d3556 100644 --- a/client/client_recv.js +++ b/client/client_recv.js @@ -367,14 +367,38 @@ function handle_event(state, context, event, options = {}) { } else if (other_event.type === EVENT.UNDO) { // do not undo an undo, we are not Notepad } else if (other_event.type === EVENT.IMAGE) { - console.log('TODO: undo image'); + other_event.deleted = true; + const image = get_image(context, other_event.image_id); + if (image !== null) { + image.deleted = true; + } + need_draw = true; + break; } else if (other_event.type === EVENT.IMAGE_MOVE) { - // TODO - console.log('TODO: undo image move'); + other_event.deleted = true; + const image = get_image(context, other_event.image_id); + if (image !== null) { + image.move_head -= 2; + image.at.x = image.move_history[image.move_head - 2]; + image.at.y = image.move_history[image.move_head - 1]; + need_draw = true; + } else { + console.warning('Undo image move for a non-existent image'); + } break; } else if (other_event.type === EVENT.IMAGE_SCALE) { - // TODO - console.log('TODO: undo image scale'); + other_event.deleted = true; + const image = get_image(context, other_event.image_id); + if (image !== null) { + image.scale_head -= 4; + image.at.x = image.scale_history[image.scale_head - 4]; + image.at.y = image.scale_history[image.scale_head - 3]; + image.width = image.scale_history[image.scale_head - 2]; + image.height = image.scale_history[image.scale_head - 1]; + need_draw = true; + } else { + console.warning('Undo image scale for a non-existent image'); + } break; } else if (other_event.type === EVENT.ERASER) { const stroke = state.events[other_event.stroke_id]; @@ -433,6 +457,14 @@ function handle_event(state, context, event, options = {}) { if (image) { // if (config.debug_print) console.debug('move image', image_id, 'to', image_event.x, image_event.y); + if (image.move_head < image.move_history.length) { + image.move_history[image.move_head] = event.x; + image.move_history[image.move_head + 1] = event.y; + } else { + image.move_history.push(event.x, event.y); + } + + image.move_head += 2; image.at.x = event.x; image.at.y = event.y; need_draw = true; @@ -447,6 +479,17 @@ function handle_event(state, context, event, options = {}) { const image = get_image(context, image_id); if (image !== null) { + if (image.scale_head < image.scale_history.length) { + image.scale_history[image.scale_head] = image.at.x; + image.scale_history[image.scale_head + 1] = image.at.y; + image.scale_history[image.scale_head + 2] = image.width; + image.scale_history[image.scale_head + 3] = image.height; + } else { + image.scale_history.push(image.at.x, image.at.y, image.width, image.height); + } + + image.scale_head += 4; + scale_image(context, image, event.corner, {'x': event.x, 'y': event.y}); need_draw = true; } diff --git a/client/webgl_draw.js b/client/webgl_draw.js index b7dafd4..6352ee7 100644 --- a/client/webgl_draw.js +++ b/client/webgl_draw.js @@ -226,20 +226,22 @@ async function draw(state, context, animate, ts) { gl.vertexAttribPointer(locations['a_pos'], 2, gl.FLOAT, false, 2 * 4, 0); for (const entry of context.images) { - gl.uniform2f(locations['u_res'], context.canvas.width, context.canvas.height); - gl.uniform2f(locations['u_scale'], state.canvas.zoom, state.canvas.zoom); - gl.uniform2f(locations['u_translation'], state.canvas.offset.x, state.canvas.offset.y); - gl.uniform1i(locations['u_texture'], 0); // Only 1 active texture for each drawcall - gl.uniform1i(locations['u_solid'], 0); - - gl.bindTexture(gl.TEXTURE_2D, entry.texture); - gl.drawArrays(gl.TRIANGLES, offset, 6); - - // Highlight active image - if (entry.key === state.active_image) { - gl.uniform1i(locations['u_solid'], 1); - gl.uniform4f(locations['u_color'], 0.133 * 0.5, 0.545 * 0.5, 0.902 * 0.5, 0.5); + if (!entry.deleted) { + gl.uniform2f(locations['u_res'], context.canvas.width, context.canvas.height); + gl.uniform2f(locations['u_scale'], state.canvas.zoom, state.canvas.zoom); + gl.uniform2f(locations['u_translation'], state.canvas.offset.x, state.canvas.offset.y); + gl.uniform1i(locations['u_texture'], 0); // Only 1 active texture for each drawcall + gl.uniform1i(locations['u_solid'], 0); + + gl.bindTexture(gl.TEXTURE_2D, entry.texture); gl.drawArrays(gl.TRIANGLES, offset, 6); + + // Highlight active image + if (entry.key === state.active_image) { + gl.uniform1i(locations['u_solid'], 1); + gl.uniform4f(locations['u_color'], 0.133 * 0.5, 0.545 * 0.5, 0.902 * 0.5, 0.5); + gl.drawArrays(gl.TRIANGLES, offset, 6); + } } offset += 6; diff --git a/client/webgl_geometry.js b/client/webgl_geometry.js index e5ef986..751b7c3 100644 --- a/client/webgl_geometry.js +++ b/client/webgl_geometry.js @@ -193,6 +193,10 @@ function add_image(context, image_id, bitmap, p, width, height) { 'raw_at': {...p}, 'width': width, 'height': height, + 'move_history': [ p.x, p.y ], + 'scale_history': [ p.x, p.y, width, height ], + 'move_head': 2, + 'scale_head': 4, }; context.images.push(entry); @@ -214,10 +218,6 @@ function add_image(context, image_id, bitmap, p, width, height) { } } -function move_image(context, image, dx, dy) { - consol.error('wtf is this'); -} - function scale_image(context, image, corner, canvasp) { let new_width, new_height; @@ -250,15 +250,17 @@ function image_at(context, x, y) { // Iterate back to front to pick the image at the front first for (let i = context.images.length - 1; i >= 0; --i) { const image = context.images[i]; - const at = image.at; - const w = image.width; - const h = image.height; + if (!image.deleted) { + const at = image.at; + const w = image.width; + const h = image.height; - const in_x = (at.x <= x && x <= at.x + w) || (at.x + w <= x && x <= at.x); - const in_y = (at.y <= y && y <= at.y + h) || (at.y + h <= y && y <= at.y); + const in_x = (at.x <= x && x <= at.x + w) || (at.x + w <= x && x <= at.x); + const in_y = (at.y <= y && y <= at.y + h) || (at.y + h <= y && y <= at.y); - if (in_x && in_y) { - return image; + if (in_x && in_y) { + return image; + } } }