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()