From 6c7df141ea086f82ee9296bbc157299498ecbea3 Mon Sep 17 00:00:00 2001 From: Francesco Magliocca Date: Mon, 9 Nov 2020 00:15:59 +0100 Subject: [PATCH 1/2] Implement Undo/Redo --- src/lib.rs | 71 +++++++++++++++++++++++++++++++++++++++++++++++++++++ src/main.rs | 37 ++++++++++++++++++++++------ 2 files changed, 100 insertions(+), 8 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 63268b4..bbeacdf 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -36,4 +36,75 @@ impl CanvasElement { } } +use im::Vector; + +// A canvas contains all elements to be drawn +pub type Canvas = Vector; + +#[derive(Clone, druid::Data)] +pub struct History { + versions: Vector, + curr_version: usize, +} + +impl History { + pub fn new(canvas: Canvas) -> History { + History { + versions: im::vector![canvas], + curr_version: 0, + } + } + + pub fn get(&self) -> &Canvas { + self.versions.get(self.curr_version).unwrap() + } + + fn has_newer_versions(&self) -> bool { + self.curr_version + 1 < self.versions.len() + } + + fn has_older_versions(&self) -> bool { + self.curr_version > 0 + } + + pub fn undo(&mut self) { + if self.has_older_versions() { + self.curr_version = self.curr_version - 1; + } + } + + pub fn redo(&mut self) { + if self.has_newer_versions() { + self.curr_version = self.curr_version + 1; + } + } + + pub fn update(&mut self, update_fn: impl FnOnce(&Canvas) -> Canvas) { + // This is a linear history, + // so we first check if there are newer versions, if so + // this means we are in the past, so a change in the past destroys the future + // and creates a new future. If we want to keep all ramifications, we should use a tree, + // but honestly, this sounds like overkill. + if self.has_newer_versions() { + self.versions = self.versions.take(self.curr_version + 1); + } + let new_version = update_fn(self.get()); + self.versions.push_back(new_version); + self.curr_version = self.curr_version + 1; + } + + // Do inplace update, which will be irreversible + pub fn irreversible_update(&mut self, update_fn: impl FnOnce(&mut Canvas)) { + // This is a linear history, + // so we first check if there are newer versions, if so + // this means we are in the past, so a change in the past destroys the future + // and creates a new future. If we want to keep all ramifications, we should use a tree, + // but honestly, this sounds like overkill. + if self.has_newer_versions() { + self.versions = self.versions.take(self.curr_version + 1); + } + + update_fn(self.versions.back_mut().unwrap()); + } +} diff --git a/src/main.rs b/src/main.rs index 7387eec..ee66a65 100644 --- a/src/main.rs +++ b/src/main.rs @@ -14,23 +14,36 @@ // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . -use druid::im::{vector, Vector}; +use druid::im::{vector}; use druid::kurbo::BezPath; use druid::widget::prelude::*; use druid::{AppLauncher, Color, Data, Event, LocalizedString, WindowDesc}; -use stiletto::CanvasElement; +use stiletto::{CanvasElement, History, Canvas}; #[derive(Clone, Data)] struct CanvasData { current_element: Option, - elements: Vector, + //elements: Vector, + history: History, } impl CanvasData { fn is_drawing(&self) -> bool { self.current_element.is_some() } + + fn perform_undo(&mut self) { + if !self.is_drawing() { + self.history.undo(); + } + } + + fn perform_redo(&mut self) { + if !self.is_drawing() { + self.history.redo(); + } + } } struct CanvasWidget; @@ -60,7 +73,13 @@ impl Widget for CanvasWidget { Event::MouseUp(_) => { if data.is_drawing() { if let Some(current_element) = data.current_element.take() { - data.elements.push_back(current_element); + + data.history.update(move |canvas: &Canvas| -> Canvas { + let mut new_canvas = canvas.clone(); + new_canvas.push_back(current_element); + return new_canvas; + }); + } } } @@ -124,7 +143,7 @@ impl Widget for CanvasWidget { // and we only want to clear this widget's area. ctx.fill(rect, &Color::WHITE); - for element in &data.elements { + for element in data.history.get().iter() { element.draw(ctx); } if let Some(element) = &data.current_element { @@ -138,8 +157,10 @@ fn build_ui() -> impl Widget { let toolbar = Flex::row() .cross_axis_alignment(CrossAxisAlignment::Center) .with_spacer(30.0) - .with_child(Button::new("Undo")) - .with_child(Button::new("Redo")); + .with_child( + Button::new("Undo").on_click(|_ctx: &mut EventCtx, data: &mut CanvasData, _env: &Env| data.perform_undo()) + ) + .with_child(Button::new("Redo").on_click(|_ctx: &mut EventCtx, data: &mut CanvasData, _env: &Env| data.perform_redo())); Flex::column() .cross_axis_alignment(CrossAxisAlignment::Center) @@ -156,7 +177,7 @@ pub fn main() { ); let canvas_data = CanvasData { current_element: None, - elements: vector![], + history: History::new(vector![]), }; AppLauncher::with_window(window) .use_simple_logger() From eca25f765b193804c0086c0b624ec250ea1d64c8 Mon Sep 17 00:00:00 2001 From: Francesco Magliocca Date: Mon, 9 Nov 2020 10:46:50 +0100 Subject: [PATCH 2/2] Rename History to VersionedCanvas and remove useless comments --- src/lib.rs | 18 ++++++++++-------- src/main.rs | 14 +++++++------- 2 files changed, 17 insertions(+), 15 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index bbeacdf..bf2baa7 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -42,20 +42,24 @@ use im::Vector; pub type Canvas = Vector; #[derive(Clone, druid::Data)] -pub struct History { +pub struct VersionedCanvas { + // We internally guarantee that this vector + // is never empty versions: Vector, curr_version: usize, } -impl History { - pub fn new(canvas: Canvas) -> History { - History { +impl VersionedCanvas { + pub fn new(canvas: Canvas) -> VersionedCanvas { + VersionedCanvas { versions: im::vector![canvas], curr_version: 0, } } + // Get current canvas version pub fn get(&self) -> &Canvas { + let focus = self.versions.focus(); self.versions.get(self.curr_version).unwrap() } @@ -83,8 +87,7 @@ impl History { // This is a linear history, // so we first check if there are newer versions, if so // this means we are in the past, so a change in the past destroys the future - // and creates a new future. If we want to keep all ramifications, we should use a tree, - // but honestly, this sounds like overkill. + // and creates a new future. if self.has_newer_versions() { self.versions = self.versions.take(self.curr_version + 1); } @@ -98,8 +101,7 @@ impl History { // This is a linear history, // so we first check if there are newer versions, if so // this means we are in the past, so a change in the past destroys the future - // and creates a new future. If we want to keep all ramifications, we should use a tree, - // but honestly, this sounds like overkill. + // and creates a new future. if self.has_newer_versions() { self.versions = self.versions.take(self.curr_version + 1); } diff --git a/src/main.rs b/src/main.rs index ee66a65..fa001d2 100644 --- a/src/main.rs +++ b/src/main.rs @@ -19,13 +19,13 @@ use druid::kurbo::BezPath; use druid::widget::prelude::*; use druid::{AppLauncher, Color, Data, Event, LocalizedString, WindowDesc}; -use stiletto::{CanvasElement, History, Canvas}; +use stiletto::{CanvasElement, VersionedCanvas, Canvas}; #[derive(Clone, Data)] struct CanvasData { current_element: Option, //elements: Vector, - history: History, + elements: VersionedCanvas, } impl CanvasData { @@ -35,13 +35,13 @@ impl CanvasData { fn perform_undo(&mut self) { if !self.is_drawing() { - self.history.undo(); + self.elements.undo(); } } fn perform_redo(&mut self) { if !self.is_drawing() { - self.history.redo(); + self.elements.redo(); } } } @@ -74,7 +74,7 @@ impl Widget for CanvasWidget { if data.is_drawing() { if let Some(current_element) = data.current_element.take() { - data.history.update(move |canvas: &Canvas| -> Canvas { + data.elements.update(move |canvas: &Canvas| -> Canvas { let mut new_canvas = canvas.clone(); new_canvas.push_back(current_element); return new_canvas; @@ -143,7 +143,7 @@ impl Widget for CanvasWidget { // and we only want to clear this widget's area. ctx.fill(rect, &Color::WHITE); - for element in data.history.get().iter() { + for element in data.elements.get().iter() { element.draw(ctx); } if let Some(element) = &data.current_element { @@ -177,7 +177,7 @@ pub fn main() { ); let canvas_data = CanvasData { current_element: None, - history: History::new(vector![]), + elements: VersionedCanvas::new(vector![]), }; AppLauncher::with_window(window) .use_simple_logger()