From 9652ae1a085b76ea7447de8946efb82a971eb127 Mon Sep 17 00:00:00 2001 From: RustyStriker Date: Thu, 8 Sep 2022 20:23:50 +0300 Subject: [PATCH] UNDO/REDO FINALLY DONE --- README.md | 2 +- src/export.rs | 3 +- src/main.rs | 26 ++++++++++- src/modify.rs | 2 +- src/ui.rs | 14 +++--- src/undo.rs | 116 +++++++++++++++++++++++++++++++++++++++++++++----- 6 files changed, 142 insertions(+), 21 deletions(-) diff --git a/README.md b/README.md index b260278..7c232da 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@ When an item is selected in the items tree window(will be written in gold), clic ## TODO - [x] Pick default color for shapes -- [ ] Undo/Redo history(only missing shape creation/deletion) +- [x] Undo/Redo history(ctrl + Z/ctrl + shift + Z) - [ ] Select item by clicking/double clicking on the relevant shape/image ## Quality of life todo diff --git a/src/export.rs b/src/export.rs index 12393dc..4c67259 100644 --- a/src/export.rs +++ b/src/export.rs @@ -42,7 +42,7 @@ pub struct ImportItem { pub data: ExportData, } -pub fn toml_save_sys( +pub fn json_save_sys( mut imp_exp: ResMut, shapes: Query<&ShapeData>, images: Query<(&ImageData, &Transform)>, @@ -107,7 +107,6 @@ pub fn import_sys( mut imp_exp: ResMut, assets: Res, p_size: Res, - ) { imp_exp.import = false; let file = rfd::FileDialog::new() diff --git a/src/main.rs b/src/main.rs index eb0c84f..503a6de 100644 --- a/src/main.rs +++ b/src/main.rs @@ -67,7 +67,7 @@ fn main() { .add_system(delete_selected_item_on_del_sys.with_run_criteria(|mut ec: ResMut| if ec.ctx_mut().wants_keyboard_input() { ShouldRun::No } else { ShouldRun::Yes } )) - .add_system(export::toml_save_sys.with_run_criteria(|ie: Res| { + .add_system(export::json_save_sys.with_run_criteria(|ie: Res| { if ie.export { ShouldRun::Yes } else { ShouldRun::No } })) .add_system(export::import_sys.with_run_criteria(|ie: Res| { @@ -95,11 +95,35 @@ fn configure_visuals(mut egui_ctx: ResMut) { fn delete_selected_item_on_del_sys( mut coms: Commands, + mut undo: ResMut, mut selected: ResMut, keyboard: Res>, + mut shapes: Query<&mut ShapeData>, + mut images: Query<&mut ImageData>, + transforms: Query<&Transform>, + image_handles: Query<&Handle>, + draw_modes: Query<&DrawMode>, ) { if keyboard.just_pressed(KeyCode::Delete) || keyboard.just_pressed(KeyCode::X) { if let Some(e) = selected.0 { + if let Ok(mut sd) = shapes.get_mut(e) { + let mut name = String::new(); + let mut note = String::new(); + std::mem::swap(&mut name, &mut sd.name); + std::mem::swap(&mut note, &mut sd.note); + let edges = sd.edges.iter().map(|e| transforms.get(*e).unwrap().translation.xy()).collect::>(); + let transform = transforms.get(e).map(|t| *t).unwrap_or_default(); + let dm = *draw_modes.get(e).unwrap(); + undo.push(UndoItem::DeleteShape { transform, edges, name, note, shape: sd.shape, dm, old_entity: e }); + } + else if let Ok(mut id) = images.get_mut(e) { + let mut nid = ImageData::default(); + std::mem::swap(&mut nid, &mut *id); + let transform = transforms.get(e).map(|t| *t).unwrap_or_default(); + let handle = image_handles.get(e).unwrap().clone(); + undo.push(UndoItem::DeleteImage { id: nid, transform: transform, handle, old_entity: e }); + } + coms.entity(e).despawn_recursive(); selected.0 = None; } diff --git a/src/modify.rs b/src/modify.rs index 5c59c1b..d4173bc 100644 --- a/src/modify.rs +++ b/src/modify.rs @@ -86,7 +86,7 @@ pub fn modify_sys( else { undo.push(UndoItem::MoveDot { shape_data, - dot, + dot: sd.edges.iter().position(|e| *e == dot).unwrap_or(0), from: *held_from, to: snap.snap_to_grid(mouse_pos), }); diff --git a/src/ui.rs b/src/ui.rs index 4271d62..bbac018 100644 --- a/src/ui.rs +++ b/src/ui.rs @@ -340,10 +340,10 @@ pub fn inspector_sys( changes.0 = gt_y; } if stopped_edit(&rx) { - undo.push(UndoItem::MoveDot { shape_data: e, dot: *edge, from: Vec2::new(changes.0, gt_y), to: Vec2::new(gt_x, gt_y) }); + undo.push(UndoItem::MoveDot { shape_data: e, dot: i, from: Vec2::new(changes.0, gt_y), to: Vec2::new(gt_x, gt_y) }); } if stopped_edit(&ry) { - undo.push(UndoItem::MoveDot { shape_data: e, dot: *edge, from: Vec2::new(gt_x, changes.0), to: Vec2::new(gt_x, gt_y) }); + undo.push(UndoItem::MoveDot { shape_data: e, dot: i, from: Vec2::new(gt_x, changes.0), to: Vec2::new(gt_x, gt_y) }); } if rx.changed() || ry.changed() { @@ -408,10 +408,14 @@ pub fn inspector_sys( ui.separator(); if ui.button("Delete").clicked() { if let Ok(mut sd) = shape { - let mut nsd = sd.shallow_copy(); - std::mem::swap(&mut nsd, &mut *sd); + let mut name = String::new(); + let mut note = String::new(); + std::mem::swap(&mut name, &mut sd.name); + std::mem::swap(&mut note, &mut sd.note); + let edges = sd.edges.iter().map(|e| transforms.get(*e).unwrap().translation.xy()).collect::>(); let transform = transforms.get(e).map(|t| *t).unwrap_or_default(); - undo.push(UndoItem::DeleteShape { sd: nsd, transform }); + let dm = *draw_modes.get(e).unwrap(); + undo.push(UndoItem::DeleteShape { transform, edges, name, note, shape: sd.shape, dm, old_entity: e }); } else if let Ok(mut id) = image { let mut nid = ImageData::default(); diff --git a/src/undo.rs b/src/undo.rs index 85a7445..b2764b8 100644 --- a/src/undo.rs +++ b/src/undo.rs @@ -1,15 +1,16 @@ use bevy::math::Vec3Swizzles; use bevy::prelude::*; +use bevy_prototype_lyon::prelude::*; -use crate::{ShapeData, ImageData}; +use crate::{ShapeData, ImageData, CreateShape, PointSize}; #[derive(Debug, Clone)] pub enum UndoItem { Base, CreateShape { entity: Entity }, - DeleteShape { sd: ShapeData, transform: Transform }, + DeleteShape { transform: Transform, edges: Vec, name: String, note: String, shape: CreateShape, dm: DrawMode, old_entity: Entity }, MoveItem { shape: Entity, from: Vec2, to: Vec2 }, - MoveDot { shape_data: Entity, dot: Entity, from: Vec2, to: Vec2 }, + MoveDot { shape_data: Entity, dot: usize, from: Vec2, to: Vec2 }, Rotate { entity: Entity, from: f32, to: f32 }, NameChange { entity: Entity, from: String }, NoteChange { entity: Entity, from: String }, @@ -74,25 +75,90 @@ pub fn undo_redo_sys( mut coms: Commands, mut stack: ResMut, keyboard: Res>, + p_size: Res, mut shapes: Query<&mut ShapeData>, mut transforms: Query<&mut Transform>, global_transforms: Query<&GlobalTransform>, mut images: Query<(&mut ImageData, &Handle)>, mut paths: Query<&mut bevy_prototype_lyon::prelude::Path>, + draw_modes: Query<&DrawMode>, ) { if keyboard.just_pressed(KeyCode::Z) && keyboard.pressed(KeyCode::LControl) { let item = if keyboard.pressed(KeyCode::LShift) { stack.unpop() } else { stack.pop() }; if let Some(item) = item { match item { UndoItem::Base => bevy::log::error!("POP/UNPOP: Got UndoItem::Base as a result!"), - UndoItem::CreateShape { entity: _ } => { - bevy::log::error!("NOT IMPLEMENTED : CreateShape"); + UndoItem::CreateShape { entity } => { + let transform = transforms.get(*entity).map(|t| *t).unwrap_or_default(); + let mut sd = shapes.get_mut(*entity).unwrap(); + let mut name = String::new(); + let mut note = String::new(); + std::mem::swap(&mut name, &mut sd.name); + std::mem::swap(&mut note, &mut sd.note); + let edges = sd.edges.iter().map(|e| transforms.get(*e).unwrap().translation.xy()).collect::>(); + let dm = *draw_modes.get(*entity).unwrap(); + let shape = sd.shape; + + coms.entity(*entity).despawn_recursive(); + *item = UndoItem::DeleteShape { transform, edges, name, note, shape, dm, old_entity: *entity }; + }, UndoItem::DeleteShape { - sd: _, - transform: _, + transform, + edges, + name, + note, + shape, + dm, + old_entity, } => { - bevy::log::error!("NOT IMPLEMENTED : DeleteShape"); + let main_shape = spawn_main_shape(&mut coms, *dm, *transform, *shape, edges.clone()); + + let dots_dm = DrawMode::Fill(FillMode::color(Color::RED)); + let dot_shape = shapes::Circle { radius: p_size.0, center: Vec2::ZERO }; + let center = coms.spawn_bundle(GeometryBuilder::build_as( + &dot_shape, + dots_dm, + Transform::from_xyz(0.0, 0.0, 0.5) + )).id(); + let edges = edges.iter().map(|v| { + coms.spawn_bundle(GeometryBuilder::build_as( + &dot_shape, + dots_dm, + Transform::from_translation(v.extend(0.5)) + )).id() + }).collect::>(); + + let mut nname = String::new(); + let mut nnote = String::new(); + std::mem::swap(&mut nname, name); + std::mem::swap(&mut nnote, note); + + let mut main_shape = coms.entity(main_shape); + + edges.iter().for_each(|e| { main_shape.add_child(*e); }); + let sd = ShapeData { name: nname, shape: *shape, main_shape: main_shape.id(), edges, center: Some(center), note: nnote }; + main_shape + .insert(sd) + .add_child(center); + + let oe = *old_entity; + *item = UndoItem::CreateShape { entity: main_shape.id() }; + + let f = |ne: &mut Entity| { if *ne == oe { *ne = main_shape.id(); }}; + for i in stack.items.iter_mut() { + match i { + UndoItem::CreateShape { entity } => f(entity), + UndoItem::MoveDot { shape_data, ..} => f(shape_data), + UndoItem::MoveItem { shape, .. } => f(shape), + UndoItem::Rotate { entity, .. } => f(entity), + UndoItem::NameChange { entity, .. } => f(entity), + UndoItem::NoteChange { entity, .. } => f(entity), + UndoItem::ChangeZ { entity, .. } => f(entity), + UndoItem::ReScale { entity, .. } => f(entity), + _ => {}, + } + } }, UndoItem::MoveItem { shape, from, to } => { if let Ok(mut t) = transforms.get_mut(*shape) { @@ -103,13 +169,14 @@ pub fn undo_redo_sys( *to = t; }, UndoItem::MoveDot { shape_data, dot, from, to } => { - let gt = global_transforms.get(*dot).unwrap(); - let sd = shapes.get(*shape_data).unwrap(); + let dot = sd.edges[*dot]; + let gt = global_transforms.get(dot).unwrap(); + let rot = gt.to_scale_rotation_translation().1; let ang = rot.to_euler(EulerRot::XYZ).2; let delta = Mat2::from_angle(-ang) * (*from - gt.translation().xy()); - if let Ok(mut t) = transforms.get_mut(*dot) { + if let Ok(mut t) = transforms.get_mut(dot) { t.translation += delta.extend(0.0); } // We need to recalculate the center, and update the points to be the new relevant point from the center @@ -217,4 +284,31 @@ pub fn undo_redo_sys( bevy::log::info!("Nothing to pop/unpop!"); } } +} + +fn spawn_main_shape(coms: &mut Commands, dm: DrawMode, trans: Transform, shape: CreateShape, edges: Vec) -> Entity { + match shape { + CreateShape::Triangle => { + coms.spawn_bundle(GeometryBuilder::build_as( + &shapes::Polygon { points: edges, closed: true }, + dm, + trans + )).id() + }, + CreateShape::Square => { + coms.spawn_bundle(GeometryBuilder::build_as( + &shapes::Rectangle { extents: (edges[0] - edges[1]).abs(), ..Default::default() }, + dm, + trans + )).id() + }, + CreateShape::Circle => { + coms.spawn_bundle(GeometryBuilder::build_as( + &shapes::Circle { radius: edges[0].length(), center: Vec2::ZERO }, + dm, + trans + )).id() + }, + CreateShape::Capsule => unimplemented!("I should prob get rid of this..."), + } } \ No newline at end of file