From 23605ab38a9969895effa660bb49e931727b3b71 Mon Sep 17 00:00:00 2001 From: RustyStriker Date: Tue, 30 Aug 2022 18:24:50 +0300 Subject: [PATCH 01/10] im really bad at knowing when to commit readme changes --- README.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/README.md b/README.md index b659405..f04e12f 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,14 @@ Mostly a working level editor, why should you use it? because it exists and allows to make levels without tilemaps... +## Usage + +Left mouse click for mostly everything, when creating a shape you it will build the shape as you go(giving you a visual indicator). + +If you want to cancel a shape mid-build you can right click to undo the shape + +When an item is selected in the items tree window(will be written in gold), clicking `X` will delete it + ## TODO - [x] Items tree view showing all shapes with their child nodes and purposes(maybe location and such as well?) From b1d675a3a2cf5721d1a78ab758ccb849f712f4d6 Mon Sep 17 00:00:00 2001 From: RustyStriker Date: Wed, 31 Aug 2022 19:19:52 +0300 Subject: [PATCH 02/10] cargo claimed let chains are stable then nah? --- src/lib.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index 2ae3790..6c4364c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,3 +1,5 @@ +#![feature(let_chains)] + use bevy::prelude::*; use bevy_egui::egui; @@ -117,4 +119,4 @@ impl Default for ButtonsColors { clicked: egui::Rgba::from_rgb(1.0, 1.0, 1.0) } } -} \ No newline at end of file +} From a7eb757a8f5b32793e561aad7558ba21bf196b79 Mon Sep 17 00:00:00 2001 From: RustyStriker Date: Fri, 2 Sep 2022 15:27:37 +0300 Subject: [PATCH 03/10] readme todo update --- README.md | 30 ++++-------------------------- 1 file changed, 4 insertions(+), 26 deletions(-) diff --git a/README.md b/README.md index f04e12f..82ee875 100644 --- a/README.md +++ b/README.md @@ -12,41 +12,19 @@ When an item is selected in the items tree window(will be written in gold), clic ## TODO -- [x] Items tree view showing all shapes with their child nodes and purposes(maybe location and such as well?) -- [x] Change shape name in tree view -- [x] Delete shapes in tree view - I should probably make it ask for confirmation... -- [x] Highlight opened shapes in the tree(maybe allow only 1 opened shape? - I dont think egui allows me to do that) -- [x] Drag camera around -- [x] Zome in/out -- [x] Import images: - - [x] Insert images - - [x] Show images in tree view - - [x] Show/Hide images - - [x] Move/Drag images around - - [x] Control images Z value(so we could reorder them) - - [x] Delete images - - [x] Name images - - [x] Scale images -- [x] Show hide shapes -- [x] Control shape Z value -- [x] Snap to grid -- [x] Change grid size -- [x] Show/Hide grid(also make a visible grid in the first place) -- [x] Make grid fill the screen at all times -- [x] Comment/Editor note for shapes -- [x] Export -- [x] Import +- [ ] Pick default color for shapes +- [ ] Undo/Redo history +- [ ] Select item by clicking/double clicking on the relevant shape/image ## Quality of life todo - [ ] Double click on shape in tree view will center the relevant shape - [ ] Grab shapes instead of center points - [ ] Reorder items tree -- [ ] Select item by clicking/double clicking on the relevant shape/image - [ ] Allow for more types of export/import - [ ] Allow to set a relative export path for images(also allow to select when importing/save it in the saved file) +- [ ] Group shapes/images under an empty/another object with an actual tree view ## Maybe, just maybe todo -- [ ] Group shapes/images under an empty/another object with an actual tree view - [ ] Duplicate shapes/images From 0ddb8bdba4b8a0a2ac0f9cdaeda590fbcf398250 Mon Sep 17 00:00:00 2001 From: RustyStriker Date: Sat, 3 Sep 2022 13:46:35 +0300 Subject: [PATCH 04/10] default color for making shapes --- README.md | 2 +- src/create.rs | 7 ++++--- src/lib.rs | 3 +++ src/main.rs | 1 + src/ui.rs | 9 +++++++++ 5 files changed, 18 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 82ee875..b216275 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ When an item is selected in the items tree window(will be written in gold), clic ## TODO -- [ ] Pick default color for shapes +- [x] Pick default color for shapes - [ ] Undo/Redo history - [ ] Select item by clicking/double clicking on the relevant shape/image diff --git a/src/create.rs b/src/create.rs index 94cc043..a25d395 100644 --- a/src/create.rs +++ b/src/create.rs @@ -6,6 +6,7 @@ use bevy_prototype_lyon::prelude::*; pub fn create_sys( mut coms: Commands, p_size: Res, + default_color: Res, state: Res, mouse: Res>, wnds: Res, @@ -76,7 +77,7 @@ pub fn create_sys( else if mouse.just_released(MouseButton::Left) { // We can now spawn a shape in the current mouse position... // Spawn the first point - *shape = Some(create_new_shape(&mut coms, mouse_pos, state.create_shape, p_size.0)); + *shape = Some(create_new_shape(&mut coms, mouse_pos, state.create_shape, p_size.0, **default_color)); } } } @@ -275,10 +276,10 @@ fn update_main_shape_creation( } } -fn create_new_shape(coms: &mut Commands, pos: Vec2, create_shape: CreateShape, p_size: f32) -> ShapeData { +fn create_new_shape(coms: &mut Commands, pos: Vec2, create_shape: CreateShape, p_size: f32, color: Color) -> ShapeData { // Shape draw mode... let draw_mode = DrawMode::Outlined { - fill_mode: FillMode::color(Color::rgba(0.0, 0.5, 0.5, 0.4)), + fill_mode: FillMode::color(color), outline_mode: StrokeMode::new(Color::rgba(0.0, 0.5, 0.5, 0.6), 3.0), }; diff --git a/src/lib.rs b/src/lib.rs index 6c4364c..3e289bd 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -13,6 +13,9 @@ pub use modify::modify_sys; pub use create::create_sys; pub use helpers::*; +#[derive(Debug, Clone, Deref, DerefMut)] +pub struct DefaultColor(pub Color); + #[derive(Debug, Clone)] pub struct SnapGrid { pub width: f32, diff --git a/src/main.rs b/src/main.rs index 1afeead..bf01060 100644 --- a/src/main.rs +++ b/src/main.rs @@ -28,6 +28,7 @@ fn main() { .insert_resource(SnapGrid::default()) .insert_resource(ui::SelectedItem::default()) .insert_resource(ShouldImportExport::default()) + .insert_resource(DefaultColor(Color::rgba(0.0, 0.5, 0.5, 0.4))) ; app diff --git a/src/ui.rs b/src/ui.rs index 662ed02..0cea441 100644 --- a/src/ui.rs +++ b/src/ui.rs @@ -12,6 +12,7 @@ pub fn action_bar_sys( assets: Res, mut egui_ctx: ResMut, mut state: ResMut, + mut default_color: ResMut, colors: Res, ) { egui::Window::new("buttons_float") @@ -76,6 +77,14 @@ pub fn action_bar_sys( // state.create_shape = CreateShape::Capsule; // } }); + ui.horizontal(|hui| { + hui.label("Color: "); + let c = **default_color; + let mut color = [c.r(), c.g(), c.b(), c.a()]; + if hui.color_edit_button_rgba_unmultiplied(&mut color).changed() { + **default_color = Color::from(color); + } + }); } }); } From 92e3b9f620fa9b98b2016b78c928a5b2f7307662 Mon Sep 17 00:00:00 2001 From: RustyStriker Date: Mon, 5 Sep 2022 19:42:40 +0300 Subject: [PATCH 05/10] undo/redo - 50% --- README.md | 2 +- src/create.rs | 4 ++ src/lib.rs | 2 + src/main.rs | 2 + src/modify.rs | 28 +++++++++++- src/ui.rs | 44 +++++++++++++++--- src/undo.rs | 123 ++++++++++++++++++++++++++++++++++++++++++++++++++ 7 files changed, 197 insertions(+), 8 deletions(-) create mode 100644 src/undo.rs diff --git a/README.md b/README.md index b216275..505028b 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 +- [ ] Undo/Redo history(roughly 50%) - [ ] Select item by clicking/double clicking on the relevant shape/image ## Quality of life todo diff --git a/src/create.rs b/src/create.rs index a25d395..8de5de7 100644 --- a/src/create.rs +++ b/src/create.rs @@ -5,6 +5,7 @@ use bevy_prototype_lyon::prelude::*; pub fn create_sys( mut coms: Commands, + mut undo: ResMut, p_size: Res, default_color: Res, state: Res, @@ -60,6 +61,9 @@ pub fn create_sys( ms.add_child(*e); } ms.insert(s); + + undo.push(UndoItem::CreateShape { entity: ms.id() }); + *shape = None; } } diff --git a/src/lib.rs b/src/lib.rs index 3e289bd..0e55a56 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -9,6 +9,8 @@ pub mod modify; pub mod ui; pub mod infinite_grid; pub mod export; +pub mod undo; +pub use undo::UndoItem; pub use modify::modify_sys; pub use create::create_sys; pub use helpers::*; diff --git a/src/main.rs b/src/main.rs index bf01060..eb0c84f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -29,6 +29,7 @@ fn main() { .insert_resource(ui::SelectedItem::default()) .insert_resource(ShouldImportExport::default()) .insert_resource(DefaultColor(Color::rgba(0.0, 0.5, 0.5, 0.4))) + .insert_resource(undo::UndoStack::default()) ; app @@ -72,6 +73,7 @@ fn main() { .add_system(export::import_sys.with_run_criteria(|ie: Res| { if ie.import { ShouldRun::Yes} else { ShouldRun::No } })) + .add_system(undo::undo_redo_sys) ; app.run(); diff --git a/src/modify.rs b/src/modify.rs index 553de11..4c5c7d9 100644 --- a/src/modify.rs +++ b/src/modify.rs @@ -16,6 +16,8 @@ pub enum Holding { pub fn modify_sys( // Which entity the user currently drags, and which specific part of it(ShapeData entity, path entity) mut holding: Local, + mut held_from: Local, + mut undo: ResMut, mouse: Res>, wnds: Res, scale: Query<&OrthographicProjection, With>, @@ -38,8 +40,8 @@ pub fn modify_sys( for (e, sd) in shapes.iter() { if let Ok(t) = gtransforms.get(sd.center.unwrap()) { let t = t.translation().xy(); - if (mouse_pos - t).length_squared() < (p_size.0 * scale).powi(2) { + *held_from = t; *holding = Holding::Shape(e, sd.center.unwrap()); break; } @@ -49,6 +51,7 @@ pub fn modify_sys( let t = t.translation().xy(); if (mouse_pos - t).length_squared() < (p_size.0 * scale).powi(2) { + *held_from = t; *holding = Holding::Shape(e, *edge); break; } @@ -65,6 +68,7 @@ pub fn modify_sys( // Disregard rotations for now plz let diff = (mouse_pos - t.translation().xy()).abs(); if diff.x < size.x * scale.x && diff.y < size.y * scale.y { + *held_from = t.translation().xy(); *holding = Holding::Image(e, mouse_pos - t.translation().xy()); break; } @@ -73,6 +77,28 @@ pub fn modify_sys( } } else if mouse.just_released(MouseButton::Left) && *holding != Holding::None { + match *holding { + Holding::Shape(shape_data, dot) => { + if let Ok(sd) = shapes.get_component::(shape_data) { + if sd.center == Some(dot) { + undo.push(UndoItem::MoveShape { shape: shape_data, from: *held_from, to: snap.snap_to_grid(mouse_pos) }); + } + else { + undo.push(UndoItem::MoveDot { + shape_data, + dot, + from: *held_from, + to: snap.snap_to_grid(mouse_pos), + }); + } + } + }, + Holding::Image(e, off) => { + // ye i know it says MoVeShaPe but really it doesnt matter that much here! + undo.push(UndoItem::MoveShape { shape: e, from: *held_from, to: snap.snap_to_grid(mouse_pos - off) }); + }, + Holding::None => {}, + } *holding = Holding::None; // We just released our sad little dot/shape } else if let Holding::Shape(se, pe) = *holding { diff --git a/src/ui.rs b/src/ui.rs index 0cea441..263abb9 100644 --- a/src/ui.rs +++ b/src/ui.rs @@ -206,8 +206,10 @@ pub fn items_tree_sys( } pub fn inspector_sys( - mut selected: ResMut, // Which item is currently selected mut coms: Commands, // For deletion! + mut changes: Local, + mut undo: ResMut, + mut selected: ResMut, // Which item is currently selected mut egui_ctx: ResMut, mut shapes: Query<&mut ShapeData>, mut transforms: Query<&mut Transform>, @@ -231,20 +233,43 @@ pub fn inspector_sys( if let Ok(mut sd) = shapes.get_mut(e) { ui.horizontal(|hui| { hui.label("Name:"); - hui.text_edit_singleline(&mut sd.name); + let te = hui.text_edit_singleline(&mut sd.name); + if te.lost_focus() { + bevy::log::info!("stopped editing the name!"); + // TODO have name editing please + } }); ui.separator(); if let Ok(mut t) = transforms.get_mut(sd.main_shape) { ui.horizontal(|hui| { hui.label("Translation:"); - hui.add(egui::DragValue::new(&mut t.translation.x)); - hui.add(egui::DragValue::new(&mut t.translation.y)); - + let xdrag = hui.add(egui::DragValue::new(&mut t.translation.x)); + if started_edit(&xdrag) { + *changes = t.translation.x; + } + else if stopped_edit(&xdrag) { + undo.push(UndoItem::MoveShape { shape: e, from: Vec2::new(*changes, t.translation.y), to: t.translation.xy() }); + } + let ydrag = hui.add(egui::DragValue::new(&mut t.translation.y)); + if started_edit(&ydrag) { + *changes = t.translation.y; + } + else if stopped_edit(&ydrag) { + undo.push(UndoItem::MoveShape { shape: e, from: Vec2::new(t.translation.x , *changes), to: t.translation.xy() }); + } + }); ui.horizontal(|hui| { hui.label("Rotation:"); let mut rot = t.rotation.to_euler(EulerRot::XYZ).2.to_degrees(); - if hui.add(egui::DragValue::new(&mut rot).suffix("°")).changed() { + let drag = hui.add(egui::DragValue::new(&mut rot).suffix("°")); + if started_edit(&drag) { + *changes = rot; + } + if stopped_edit(&drag) { + undo.push(UndoItem::Rotate { entity: e, from: *changes, to: rot }); + } + if drag.changed() { t.rotation = Quat::from_rotation_z(rot.to_radians()); } hui.label("Z:"); @@ -353,4 +378,11 @@ pub fn inspector_sys( **selected = None; } } +} + +fn started_edit(res: &egui::Response) -> bool { + res.drag_started() || res.gained_focus() +} +fn stopped_edit(res :&egui::Response) -> bool { + res.drag_released() || res.lost_focus() } \ No newline at end of file diff --git a/src/undo.rs b/src/undo.rs new file mode 100644 index 0000000..2eac0e8 --- /dev/null +++ b/src/undo.rs @@ -0,0 +1,123 @@ +use bevy::prelude::*; + +use crate::{CreateShape, ShapeData}; + +#[derive(Debug, Clone)] +pub enum UndoItem { + Base, + CreateShape { entity: Entity }, + MoveDot { shape_data: Entity, dot: Entity, from: Vec2, to: Vec2 }, + MoveShape { shape: Entity, from: Vec2, to: Vec2 }, + Rotate { entity: Entity, from: f32, to: f32 }, + NameChange { entity: Entity, from: String }, + DeleteShape { name: String, shape: CreateShape, edges: Vec, transform: Transform, color: Color, note: String }, +} +pub struct UndoStack { + items: Vec, + current_action: usize, + last_valid: usize, +} + +impl Default for UndoStack { + fn default() -> Self { + Self { items: [UndoItem::Base].to_vec(), current_action: 0, last_valid: 0 } + } +} +impl UndoStack { + pub fn push(&mut self, item: UndoItem) { + bevy::log::info!("PUSHING: {:?}", item); + if self.current_action < self.items.len() - 1 { + // We need to do a "semi push" + self.current_action += 1; + self.items[self.current_action] = item; + } + else { + // We push a completly new item and might need more space! + self.items.push(item); + self.current_action = self.items.len() - 1; + } + self.last_valid = self.current_action; + } + /// Pop the last item in the stack + pub fn pop(&mut self) -> Option<&mut UndoItem> { + // If this happens we are clearly into invalid territory + assert!(self.current_action < self.items.len()); + if self.current_action > 0 { + let item = &mut self.items[self.current_action]; + self.current_action -= 1; + Some(item) + } + else { + None + } + } + // If exists, "unpops" the last popped item + pub fn unpop(&mut self) -> Option<&mut UndoItem> { + if self.current_action < self.last_valid { + self.current_action += 1; + let item = &mut self.items[self.current_action]; + Some(item) + } + else { + None + } + } +} + +pub fn undo_redo_sys( + mut coms: Commands, + mut stack: ResMut, + keyboard: Res>, + mut shapes: Query<&mut ShapeData>, + mut transforms: Query<&mut Transform>, +) { + 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::MoveDot { shape_data, dot, from, to } => { + bevy::log::error!("NOT IMPLEMENTED : MoveDot"); + }, + UndoItem::MoveShape { shape, from, to } => { + if let Ok(mut t) = transforms.get_mut(*shape) { + t.translation = from.extend(t.translation.z); + } + let t = *from; + *from = *to; + *to = t; + }, + UndoItem::Rotate { entity, from, to } => { + if let Ok(mut t) = transforms.get_mut(*entity) { + t.rotation = Quat::from_rotation_z(from.to_radians()); + } + let t = *from; + *from = *to; + *to = t; + }, + UndoItem::NameChange { entity, from } => { + if let Ok(mut sd) = shapes.get_mut(*entity) { + std::mem::swap(&mut sd.name, from); + } + // No need to update the item, so no need to replace + }, + UndoItem::DeleteShape { + name, + shape, + edges, + transform, + color, + note + } => { + bevy::log::error!("NOT IMPLEMENTED : DeleteShape"); + }, + } + } + else { + bevy::log::info!("Nothing to pop/unpop!"); + } + } +} \ No newline at end of file From 32c438ce165e83d201bd69cd3ec2af1f33d307f7 Mon Sep 17 00:00:00 2001 From: RustyStriker Date: Wed, 7 Sep 2022 19:27:47 +0300 Subject: [PATCH 06/10] undo/redo mostly done i think --- src/lib.rs | 16 +++- src/modify.rs | 4 +- src/ui.rs | 203 +++++++++++++++++++++++++++++--------------------- src/undo.rs | 84 +++++++++++++++++---- 4 files changed, 205 insertions(+), 102 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 0e55a56..430cfc1 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -48,14 +48,14 @@ impl Default for SnapGrid { #[derive(Clone, Debug)] pub struct PointSize(pub f32); -#[derive(Component, Debug)] +#[derive(Component, Debug, Default, Clone)] pub struct ImageData { pub name: String, pub note: String, pub path: String, } -#[derive(Component, Debug)] +#[derive(Component, Debug, Clone)] pub struct ShapeData { pub name: String, pub shape: CreateShape, @@ -64,6 +64,18 @@ pub struct ShapeData { pub center: Option, pub note: String, } +impl ShapeData { + pub fn shallow_copy(&self) -> Self { + Self { + name: Default::default(), + shape: self.shape, + main_shape: self.main_shape, + edges: Default::default(), + center: Default::default(), + note: Default::default() + } + } +} #[derive(Component)] pub struct MainCamera; diff --git a/src/modify.rs b/src/modify.rs index 4c5c7d9..5c59c1b 100644 --- a/src/modify.rs +++ b/src/modify.rs @@ -81,7 +81,7 @@ pub fn modify_sys( Holding::Shape(shape_data, dot) => { if let Ok(sd) = shapes.get_component::(shape_data) { if sd.center == Some(dot) { - undo.push(UndoItem::MoveShape { shape: shape_data, from: *held_from, to: snap.snap_to_grid(mouse_pos) }); + undo.push(UndoItem::MoveItem { shape: shape_data, from: *held_from, to: snap.snap_to_grid(mouse_pos) }); } else { undo.push(UndoItem::MoveDot { @@ -95,7 +95,7 @@ pub fn modify_sys( }, Holding::Image(e, off) => { // ye i know it says MoVeShaPe but really it doesnt matter that much here! - undo.push(UndoItem::MoveShape { shape: e, from: *held_from, to: snap.snap_to_grid(mouse_pos - off) }); + undo.push(UndoItem::MoveItem { shape: e, from: *held_from, to: snap.snap_to_grid(mouse_pos - off) }); }, Holding::None => {}, } diff --git a/src/ui.rs b/src/ui.rs index 263abb9..eafe14b 100644 --- a/src/ui.rs +++ b/src/ui.rs @@ -9,6 +9,7 @@ pub struct SelectedItem(pub Option); pub fn action_bar_sys( mut coms: Commands, + mut undo: ResMut, assets: Res, mut egui_ctx: ResMut, mut state: ResMut, @@ -43,11 +44,13 @@ pub fn action_bar_sys( let name = String::from(file.file_name().unwrap().to_str().unwrap_or("Image")); let path = file.to_str().unwrap().to_string(); let image: Handle = assets.load(file); - coms.spawn_bundle(SpriteBundle { + let id = coms.spawn_bundle(SpriteBundle { texture: image, ..Default::default() }) - .insert(ImageData { name, note: String::new(), path }); + .insert(ImageData { name, note: String::new(), path }).id(); + + undo.push(UndoItem::CreateImage { entity: id }); } } @@ -207,7 +210,7 @@ pub fn items_tree_sys( pub fn inspector_sys( mut coms: Commands, // For deletion! - mut changes: Local, + mut changes: Local<(f32, String)>, mut undo: ResMut, mut selected: ResMut, // Which item is currently selected mut egui_ctx: ResMut, @@ -218,6 +221,7 @@ pub fn inspector_sys( mut draw_modes: Query<&mut DrawMode, With>, mut visible: Query<&mut Visibility, Or<(With, With)>>, mut images: Query<&mut ImageData, With>, + image_handles: Query<&Handle, With>, ) { if let Some(e) = **selected { let mut open = true; @@ -230,53 +234,96 @@ pub fn inspector_sys( .open(&mut open) .resizable(false) .show(egui_ctx.ctx_mut(), |ui| { - if let Ok(mut sd) = shapes.get_mut(e) { + let mut shape = shapes.get_mut(e); + let mut image = images.get_mut(e); + + if shape.is_err() && image.is_err() { + bevy::log::error!("Selected item is not a shape nor an image, so... weird"); + **selected = None; + return; + } + + let name = if let Ok(ref mut sd) = shape { &mut sd.name } + else if let Ok(ref mut id) = image { &mut id.name } + else { unreachable!() }; + ui.horizontal(|hui| { + hui.label("Name:"); + let te = hui.text_edit_singleline(name); + if te.gained_focus() { + changes.1 = name.clone(); + } + if te.lost_focus() { + let mut new = String::new(); + std::mem::swap(&mut new, &mut changes.1); + undo.push(UndoItem::NameChange { entity: e, from: new }); + } + }); + ui.separator(); + if let Ok(mut t) = transforms.get_mut(e) { ui.horizontal(|hui| { - hui.label("Name:"); - let te = hui.text_edit_singleline(&mut sd.name); - if te.lost_focus() { - bevy::log::info!("stopped editing the name!"); - // TODO have name editing please + hui.label("Translation:"); + let xdrag = hui.add(egui::DragValue::new(&mut t.translation.x)); + if started_edit(&xdrag) { + changes.0 = t.translation.x; + } + else if stopped_edit(&xdrag) { + undo.push(UndoItem::MoveItem { shape: e, from: Vec2::new(changes.0, t.translation.y), to: t.translation.xy() }); + } + let ydrag = hui.add(egui::DragValue::new(&mut t.translation.y)); + if started_edit(&ydrag) { + changes.0 = t.translation.y; + } + else if stopped_edit(&ydrag) { + undo.push(UndoItem::MoveItem { shape: e, from: Vec2::new(t.translation.x , changes.0), to: t.translation.xy() }); + } + + }); + ui.horizontal(|hui| { + hui.label("Rotation:"); + let mut rot = t.rotation.to_euler(EulerRot::XYZ).2.to_degrees(); + let drag = hui.add(egui::DragValue::new(&mut rot).suffix("°")); + if started_edit(&drag) { + changes.0 = rot; + } + if stopped_edit(&drag) { + undo.push(UndoItem::Rotate { entity: e, from: changes.0, to: rot }); + } + if drag.changed() { + t.rotation = Quat::from_rotation_z(rot.to_radians()); + } + hui.label("Z:"); + let z = hui.add(egui::DragValue::new(&mut t.translation.z).clamp_range(0..=i32::MAX)); + if started_edit(&z) { + changes.0 = t.translation.z; + } + if stopped_edit(&z) { + undo.push(UndoItem::ChangeZ { entity: e, from: changes.0, to: t.translation.z }); } }); - ui.separator(); - if let Ok(mut t) = transforms.get_mut(sd.main_shape) { + if image.is_ok() { ui.horizontal(|hui| { - hui.label("Translation:"); - let xdrag = hui.add(egui::DragValue::new(&mut t.translation.x)); - if started_edit(&xdrag) { - *changes = t.translation.x; + hui.label("Scale:"); + let xs = hui.add(egui::DragValue::new(&mut t.scale.x).speed(0.01)); + if started_edit(&xs) { + changes.0 = t.scale.x; } - else if stopped_edit(&xdrag) { - undo.push(UndoItem::MoveShape { shape: e, from: Vec2::new(*changes, t.translation.y), to: t.translation.xy() }); - } - let ydrag = hui.add(egui::DragValue::new(&mut t.translation.y)); - if started_edit(&ydrag) { - *changes = t.translation.y; - } - else if stopped_edit(&ydrag) { - undo.push(UndoItem::MoveShape { shape: e, from: Vec2::new(t.translation.x , *changes), to: t.translation.xy() }); + if stopped_edit(&xs) { + undo.push(UndoItem::ReScale { entity: e, from: Vec2::new(changes.0, t.scale.y), to: t.scale.xy() }); } - }); - ui.horizontal(|hui| { - hui.label("Rotation:"); - let mut rot = t.rotation.to_euler(EulerRot::XYZ).2.to_degrees(); - let drag = hui.add(egui::DragValue::new(&mut rot).suffix("°")); - if started_edit(&drag) { - *changes = rot; + let ys = hui.add(egui::DragValue::new(&mut t.scale.y).speed(0.01)); + if started_edit(&ys) { + changes.0 = t.scale.y; } - if stopped_edit(&drag) { - undo.push(UndoItem::Rotate { entity: e, from: *changes, to: rot }); + if stopped_edit(&ys) { + undo.push(UndoItem::ReScale { entity: e, from: Vec2::new(t.scale.x, changes.0), to: t.scale.xy() }); } - if drag.changed() { - t.rotation = Quat::from_rotation_z(rot.to_radians()); - } - hui.label("Z:"); - hui.add(egui::DragValue::new(&mut t.translation.z).clamp_range(0..=i32::MAX)); + }); } - ui.separator(); + } + ui.separator(); + if let Ok(ref mut sd) = shape { for (i, edge) in sd.edges.iter().enumerate() { ui.horizontal(|hui| { hui.label(format!("Edge {}", i)); @@ -311,9 +358,11 @@ pub fn inspector_sys( }); } ui.separator(); - if let Ok(mut v) = visible.get_mut(e) { - ui.checkbox(&mut v.is_visible, "Visible"); - } + } + if let Ok(mut v) = visible.get_mut(e) { + ui.checkbox(&mut v.is_visible, "Visible"); + } + if let Ok(ref mut sd) = shape { if let Ok(mut dm) = draw_modes.get_mut(e) { ui.separator(); if let DrawMode::Outlined { fill_mode: f, outline_mode: _ } = &mut *dm { @@ -327,51 +376,39 @@ pub fn inspector_sys( } } ui.separator(); - ui.label("Notes"); - ui.text_edit_multiline(&mut sd.note); - - - ui.separator(); - if ui.button("Delete").clicked() { - coms.entity(e).despawn_recursive(); - **selected = None; - } } - else if let Ok(mut name) = images.get_mut(e) { - ui.horizontal(|hui| { - hui.label("Name:"); - hui.text_edit_singleline(&mut name.name); - }); - ui.separator(); - if let Ok(mut t) = transforms.get_mut(e) { - ui.horizontal(|hui| { - hui.label("Translation:"); - hui.add(egui::DragValue::new(&mut t.translation.x)); - hui.add(egui::DragValue::new(&mut t.translation.y)); - - }); - ui.horizontal(|hui| { - hui.label("Rotation:"); - let mut rot = t.rotation.to_euler(EulerRot::XYZ).2.to_degrees(); - if hui.add(egui::DragValue::new(&mut rot).suffix("°")).changed() { - t.rotation = Quat::from_rotation_z(rot.to_radians()); - } - hui.label("Z:"); - hui.add(egui::DragValue::new(&mut t.translation.z).clamp_range(0..=i32::MAX)); - }); - ui.horizontal(|hui| { - hui.label("Scale:"); - hui.add(egui::DragValue::new(&mut t.scale.x).speed(0.01)); - hui.add(egui::DragValue::new(&mut t.scale.y).speed(0.01)); - }); - } + let note = if let Ok(ref mut sd) = shape { &mut sd.note } + else if let Ok(ref mut id) = image { &mut id.note } + else { unreachable!() }; + ui.label("Notes"); + let ne = ui.text_edit_multiline(note); + if ne.gained_focus() { + changes.1 = note.clone(); + } + if ne.lost_focus() { + let mut new = String::new(); + std::mem::swap(&mut new, &mut changes.1); + undo.push(UndoItem::NoteChange { entity: e, from: new }); + } - ui.separator(); - if ui.button("Delete").clicked() { - coms.entity(e).despawn_recursive(); - **selected = None; + 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 transform = transforms.get(e).map(|t| *t).unwrap_or_default(); + undo.push(UndoItem::DeleteShape { sd: nsd, transform }); } + else if let Ok(mut id) = image { + 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 }); + } + coms.entity(e).despawn_recursive(); + **selected = None; } }); if !open { diff --git a/src/undo.rs b/src/undo.rs index 2eac0e8..54bb791 100644 --- a/src/undo.rs +++ b/src/undo.rs @@ -1,16 +1,22 @@ use bevy::prelude::*; -use crate::{CreateShape, ShapeData}; +use crate::ui::SelectedItem; +use crate::{CreateShape, ShapeData, ImageData}; #[derive(Debug, Clone)] pub enum UndoItem { Base, CreateShape { entity: Entity }, + DeleteShape { sd: ShapeData, transform: Transform }, + MoveItem { shape: Entity, from: Vec2, to: Vec2 }, MoveDot { shape_data: Entity, dot: Entity, from: Vec2, to: Vec2 }, - MoveShape { shape: Entity, from: Vec2, to: Vec2 }, Rotate { entity: Entity, from: f32, to: f32 }, NameChange { entity: Entity, from: String }, - DeleteShape { name: String, shape: CreateShape, edges: Vec, transform: Transform, color: Color, note: String }, + NoteChange { entity: Entity, from: String }, + ChangeZ { entity: Entity, from: f32, to: f32 }, + ReScale { entity: Entity, from: Vec2, to: Vec2 }, + CreateImage { entity: Entity }, + DeleteImage { id: ImageData, transform: Transform, handle: Handle }, } pub struct UndoStack { items: Vec, @@ -67,9 +73,11 @@ impl UndoStack { pub fn undo_redo_sys( mut coms: Commands, mut stack: ResMut, + mut inspector: ResMut, keyboard: Res>, mut shapes: Query<&mut ShapeData>, mut transforms: Query<&mut Transform>, + mut images: Query<(&mut ImageData, &Handle)>, ) { if keyboard.just_pressed(KeyCode::Z) && keyboard.pressed(KeyCode::LControl) { let item = if keyboard.pressed(KeyCode::LShift) { stack.unpop() } else { stack.pop() }; @@ -79,10 +87,13 @@ pub fn undo_redo_sys( UndoItem::CreateShape { entity } => { bevy::log::error!("NOT IMPLEMENTED : CreateShape"); }, - UndoItem::MoveDot { shape_data, dot, from, to } => { - bevy::log::error!("NOT IMPLEMENTED : MoveDot"); + UndoItem::DeleteShape { + sd, + transform, + } => { + bevy::log::error!("NOT IMPLEMENTED : DeleteShape"); }, - UndoItem::MoveShape { shape, from, to } => { + UndoItem::MoveItem { shape, from, to } => { if let Ok(mut t) = transforms.get_mut(*shape) { t.translation = from.extend(t.translation.z); } @@ -90,6 +101,9 @@ pub fn undo_redo_sys( *from = *to; *to = t; }, + UndoItem::MoveDot { shape_data, dot, from, to } => { + bevy::log::error!("NOT IMPLEMENTED : MoveDot"); + }, UndoItem::Rotate { entity, from, to } => { if let Ok(mut t) = transforms.get_mut(*entity) { t.rotation = Quat::from_rotation_z(from.to_radians()); @@ -102,17 +116,57 @@ pub fn undo_redo_sys( if let Ok(mut sd) = shapes.get_mut(*entity) { std::mem::swap(&mut sd.name, from); } + else if let Ok((mut id, _)) = images.get_mut(*entity) { + std::mem::swap(&mut id.name, from); + } // No need to update the item, so no need to replace }, - UndoItem::DeleteShape { - name, - shape, - edges, - transform, - color, - note - } => { - bevy::log::error!("NOT IMPLEMENTED : DeleteShape"); + UndoItem::ChangeZ { entity, from, to } => { + if let Ok(mut t) = transforms.get_mut(*entity) { + t.translation.z = *from; + } + let t = *from; + *from = *to; + *to = t; + }, + UndoItem::ReScale { entity, from, to } => { + if let Ok(mut t) = transforms.get_mut(*entity) { + t.scale = from.extend(1.0); + } + let t = *from; + *from = *to; + *to = t; + }, + UndoItem::CreateImage { entity } => { + if let Ok((mut id, handle)) = images.get_mut(*entity) { + let transform = transforms.get(*entity).map(|t| *t).unwrap_or(Transform::default()); + let mut nid = ImageData::default(); + std::mem::swap(&mut *id, &mut nid); + + coms.entity(*entity).despawn(); + *item = UndoItem::DeleteImage { id: nid, transform, handle: handle.clone() }; + } + }, + UndoItem::DeleteImage { transform, handle, id } => { + let mut nid = ImageData::default(); + std::mem::swap(id, &mut nid); + + let e = coms.spawn_bundle(SpriteBundle { + texture: handle.clone(), + transform: *transform, + ..Default::default() + }) + .insert(nid).id(); + + *item = UndoItem::CreateImage { entity: e }; + }, + UndoItem::NoteChange { entity, from } => { + if let Ok(mut sd) = shapes.get_mut(*entity) { + std::mem::swap(&mut sd.note, from); + } + else if let Ok((mut id, _)) = images.get_mut(*entity) { + std::mem::swap(&mut id.note, from); + } }, } } From c8c3b79ac2dd33b5dc1628614025549eb6590aff Mon Sep 17 00:00:00 2001 From: RustyStriker Date: Wed, 7 Sep 2022 23:34:17 +0300 Subject: [PATCH 07/10] undo is really almost done :D --- src/ui.rs | 21 +++++++++++++++++---- src/undo.rs | 41 ++++++++++++++++++++++++++++++++++------- 2 files changed, 51 insertions(+), 11 deletions(-) diff --git a/src/ui.rs b/src/ui.rs index eafe14b..eb3db37 100644 --- a/src/ui.rs +++ b/src/ui.rs @@ -330,10 +330,23 @@ pub fn inspector_sys( let gt = global_transforms.get(*edge).unwrap(); let mut gt_x = gt.translation().x; let mut gt_y = gt.translation().y; - let c1 = hui.add(egui::DragValue::new(&mut gt_x)).changed(); - let c2 = hui.add(egui::DragValue::new(&mut gt_y)).changed(); + let rx = hui.add(egui::DragValue::new(&mut gt_x)); + let ry = hui.add(egui::DragValue::new(&mut gt_y)); - if c1 || c2 { + if started_edit(&rx) { + changes.0 = gt_x; + } + if started_edit(&ry) { + 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) }); + } + 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) }); + } + + if rx.changed() || ry.changed() { let rot = gt.to_scale_rotation_translation().1; let ang = rot.to_euler(EulerRot::XYZ).2; let delta = Mat2::from_angle(-ang) * (Vec2::new(gt_x, gt_y) - gt.translation().xy()); @@ -362,7 +375,7 @@ pub fn inspector_sys( if let Ok(mut v) = visible.get_mut(e) { ui.checkbox(&mut v.is_visible, "Visible"); } - if let Ok(ref mut sd) = shape { + if shape.is_ok() { if let Ok(mut dm) = draw_modes.get_mut(e) { ui.separator(); if let DrawMode::Outlined { fill_mode: f, outline_mode: _ } = &mut *dm { diff --git a/src/undo.rs b/src/undo.rs index 54bb791..f299a09 100644 --- a/src/undo.rs +++ b/src/undo.rs @@ -1,7 +1,7 @@ +use bevy::math::Vec3Swizzles; use bevy::prelude::*; -use crate::ui::SelectedItem; -use crate::{CreateShape, ShapeData, ImageData}; +use crate::{ShapeData, ImageData}; #[derive(Debug, Clone)] pub enum UndoItem { @@ -73,23 +73,24 @@ impl UndoStack { pub fn undo_redo_sys( mut coms: Commands, mut stack: ResMut, - mut inspector: ResMut, keyboard: 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>, ) { 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 } => { + UndoItem::CreateShape { entity: _ } => { bevy::log::error!("NOT IMPLEMENTED : CreateShape"); }, UndoItem::DeleteShape { - sd, - transform, + sd: _, + transform: _, } => { bevy::log::error!("NOT IMPLEMENTED : DeleteShape"); }, @@ -102,7 +103,33 @@ pub fn undo_redo_sys( *to = t; }, UndoItem::MoveDot { shape_data, dot, from, to } => { - bevy::log::error!("NOT IMPLEMENTED : MoveDot"); + let gt = global_transforms.get(*dot).unwrap(); + + let sd = shapes.get(*shape_data).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) { + 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 + let center_offset = crate::modify::calc_shape_center_offset(&transforms, &*sd); + // Update each edge's offset, and then move the main shape's translation + for edge in sd.edges.iter() { + if let Ok(mut t) = transforms.get_mut(*edge) { + t.translation -= center_offset.extend(0.0); + } + } + if let Ok(mut t) = transforms.get_mut(sd.main_shape) { + t.translation += (Mat2::from_angle(ang) * center_offset).extend(0.0); + } + + // Now we need to update the shape itself + crate::modify::update_main_shape(&mut paths, &transforms, &*sd); + + let t = *from; + *from = *to; + *to = t; }, UndoItem::Rotate { entity, from, to } => { if let Ok(mut t) = transforms.get_mut(*entity) { From 0b36b71b860bcb69465ae470e0e926e85e25e091 Mon Sep 17 00:00:00 2001 From: RustyStriker Date: Wed, 7 Sep 2022 23:47:55 +0300 Subject: [PATCH 08/10] fix image deletion makes other undos impossible --- src/ui.rs | 2 +- src/undo.rs | 24 ++++++++++++++++++++---- 2 files changed, 21 insertions(+), 5 deletions(-) diff --git a/src/ui.rs b/src/ui.rs index eb3db37..4271d62 100644 --- a/src/ui.rs +++ b/src/ui.rs @@ -418,7 +418,7 @@ pub fn inspector_sys( 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 }); + undo.push(UndoItem::DeleteImage { id: nid, transform: transform, handle, old_entity: e }); } coms.entity(e).despawn_recursive(); **selected = None; diff --git a/src/undo.rs b/src/undo.rs index f299a09..85a7445 100644 --- a/src/undo.rs +++ b/src/undo.rs @@ -16,7 +16,7 @@ pub enum UndoItem { ChangeZ { entity: Entity, from: f32, to: f32 }, ReScale { entity: Entity, from: Vec2, to: Vec2 }, CreateImage { entity: Entity }, - DeleteImage { id: ImageData, transform: Transform, handle: Handle }, + DeleteImage { id: ImageData, transform: Transform, handle: Handle, old_entity: Entity }, } pub struct UndoStack { items: Vec, @@ -170,11 +170,11 @@ pub fn undo_redo_sys( let mut nid = ImageData::default(); std::mem::swap(&mut *id, &mut nid); - coms.entity(*entity).despawn(); - *item = UndoItem::DeleteImage { id: nid, transform, handle: handle.clone() }; + coms.entity(*entity).despawn_recursive(); + *item = UndoItem::DeleteImage { id: nid, transform, handle: handle.clone(), old_entity: *entity }; } }, - UndoItem::DeleteImage { transform, handle, id } => { + UndoItem::DeleteImage { transform, handle, id, old_entity } => { let mut nid = ImageData::default(); std::mem::swap(id, &mut nid); @@ -185,7 +185,23 @@ pub fn undo_redo_sys( }) .insert(nid).id(); + let oe = *old_entity; *item = UndoItem::CreateImage { entity: e }; + + // Update each item with the same entity id + let f = |ne: &mut Entity| { if *ne == oe { *ne = e; }}; + for i in stack.items.iter_mut() { + match i { + UndoItem::CreateShape { entity } => f(entity), + 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::NoteChange { entity, from } => { if let Ok(mut sd) = shapes.get_mut(*entity) { From 718a2c3f063bfe331fc15be275028d97c96c67b4 Mon Sep 17 00:00:00 2001 From: RustyStriker Date: Wed, 7 Sep 2022 23:48:41 +0300 Subject: [PATCH 09/10] README update(i alsways forget to do that before pushing) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 505028b..b260278 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(roughly 50%) +- [ ] Undo/Redo history(only missing shape creation/deletion) - [ ] Select item by clicking/double clicking on the relevant shape/image ## Quality of life todo From 9652ae1a085b76ea7447de8946efb82a971eb127 Mon Sep 17 00:00:00 2001 From: RustyStriker Date: Thu, 8 Sep 2022 20:23:50 +0300 Subject: [PATCH 10/10] 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