diff --git a/README.md b/README.md index 7c232da..b659405 100644 --- a/README.md +++ b/README.md @@ -2,29 +2,43 @@ 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] Pick default color for shapes -- [x] Undo/Redo history(ctrl + Z/ctrl + shift + Z) -- [ ] Select item by clicking/double clicking on the relevant shape/image +- [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 ## 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 diff --git a/src/create.rs b/src/create.rs index 8de5de7..94cc043 100644 --- a/src/create.rs +++ b/src/create.rs @@ -5,9 +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, mouse: Res>, wnds: Res, @@ -61,9 +59,6 @@ pub fn create_sys( ms.add_child(*e); } ms.insert(s); - - undo.push(UndoItem::CreateShape { entity: ms.id() }); - *shape = None; } } @@ -81,7 +76,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, **default_color)); + *shape = Some(create_new_shape(&mut coms, mouse_pos, state.create_shape, p_size.0)); } } } @@ -280,10 +275,10 @@ fn update_main_shape_creation( } } -fn create_new_shape(coms: &mut Commands, pos: Vec2, create_shape: CreateShape, p_size: f32, color: Color) -> ShapeData { +fn create_new_shape(coms: &mut Commands, pos: Vec2, create_shape: CreateShape, p_size: f32) -> ShapeData { // Shape draw mode... let draw_mode = DrawMode::Outlined { - fill_mode: FillMode::color(color), + fill_mode: FillMode::color(Color::rgba(0.0, 0.5, 0.5, 0.4)), outline_mode: StrokeMode::new(Color::rgba(0.0, 0.5, 0.5, 0.6), 3.0), }; diff --git a/src/export.rs b/src/export.rs index 4c67259..12393dc 100644 --- a/src/export.rs +++ b/src/export.rs @@ -42,7 +42,7 @@ pub struct ImportItem { pub data: ExportData, } -pub fn json_save_sys( +pub fn toml_save_sys( mut imp_exp: ResMut, shapes: Query<&ShapeData>, images: Query<(&ImageData, &Transform)>, @@ -107,6 +107,7 @@ 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/lib.rs b/src/lib.rs index 430cfc1..2ae3790 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,5 +1,3 @@ -#![feature(let_chains)] - use bevy::prelude::*; use bevy_egui::egui; @@ -9,15 +7,10 @@ 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::*; -#[derive(Debug, Clone, Deref, DerefMut)] -pub struct DefaultColor(pub Color); - #[derive(Debug, Clone)] pub struct SnapGrid { pub width: f32, @@ -48,14 +41,14 @@ impl Default for SnapGrid { #[derive(Clone, Debug)] pub struct PointSize(pub f32); -#[derive(Component, Debug, Default, Clone)] +#[derive(Component, Debug)] pub struct ImageData { pub name: String, pub note: String, pub path: String, } -#[derive(Component, Debug, Clone)] +#[derive(Component, Debug)] pub struct ShapeData { pub name: String, pub shape: CreateShape, @@ -64,18 +57,6 @@ 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; @@ -136,4 +117,4 @@ impl Default for ButtonsColors { clicked: egui::Rgba::from_rgb(1.0, 1.0, 1.0) } } -} +} \ No newline at end of file diff --git a/src/main.rs b/src/main.rs index 503a6de..1afeead 100644 --- a/src/main.rs +++ b/src/main.rs @@ -28,8 +28,6 @@ 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))) - .insert_resource(undo::UndoStack::default()) ; app @@ -67,13 +65,12 @@ 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::json_save_sys.with_run_criteria(|ie: Res| { + .add_system(export::toml_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| { if ie.import { ShouldRun::Yes} else { ShouldRun::No } })) - .add_system(undo::undo_redo_sys) ; app.run(); @@ -95,35 +92,11 @@ 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 d4173bc..553de11 100644 --- a/src/modify.rs +++ b/src/modify.rs @@ -16,8 +16,6 @@ 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>, @@ -40,8 +38,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; } @@ -51,7 +49,6 @@ 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; } @@ -68,7 +65,6 @@ 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; } @@ -77,28 +73,6 @@ 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::MoveItem { shape: shape_data, from: *held_from, to: snap.snap_to_grid(mouse_pos) }); - } - else { - undo.push(UndoItem::MoveDot { - shape_data, - dot: sd.edges.iter().position(|e| *e == dot).unwrap_or(0), - 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::MoveItem { 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 bbac018..662ed02 100644 --- a/src/ui.rs +++ b/src/ui.rs @@ -9,11 +9,9 @@ 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, - mut default_color: ResMut, colors: Res, ) { egui::Window::new("buttons_float") @@ -44,13 +42,11 @@ 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); - let id = coms.spawn_bundle(SpriteBundle { + coms.spawn_bundle(SpriteBundle { texture: image, ..Default::default() }) - .insert(ImageData { name, note: String::new(), path }).id(); - - undo.push(UndoItem::CreateImage { entity: id }); + .insert(ImageData { name, note: String::new(), path }); } } @@ -80,14 +76,6 @@ 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); - } - }); } }); } @@ -209,10 +197,8 @@ pub fn items_tree_sys( } pub fn inspector_sys( - mut coms: Commands, // For deletion! - mut changes: Local<(f32, String)>, - mut undo: ResMut, mut selected: ResMut, // Which item is currently selected + mut coms: Commands, // For deletion! mut egui_ctx: ResMut, mut shapes: Query<&mut ShapeData>, mut transforms: Query<&mut Transform>, @@ -221,7 +207,6 @@ 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; @@ -234,119 +219,40 @@ pub fn inspector_sys( .open(&mut open) .resizable(false) .show(egui_ctx.ctx_mut(), |ui| { - 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) { + if let Ok(mut sd) = shapes.get_mut(e) { ui.horizontal(|hui| { - 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() }); - } - + hui.label("Name:"); + hui.text_edit_singleline(&mut sd.name); }); - 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 }); - } - }); - if image.is_ok() { + ui.separator(); + if let Ok(mut t) = transforms.get_mut(sd.main_shape) { ui.horizontal(|hui| { - 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; - } - if stopped_edit(&xs) { - undo.push(UndoItem::ReScale { entity: e, from: Vec2::new(changes.0, t.scale.y), to: t.scale.xy() }); - } - - 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(&ys) { - undo.push(UndoItem::ReScale { entity: e, from: Vec2::new(t.scale.x, changes.0), to: t.scale.xy() }); - } + 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.separator(); - if let Ok(ref mut sd) = shape { + ui.separator(); for (i, edge) in sd.edges.iter().enumerate() { ui.horizontal(|hui| { hui.label(format!("Edge {}", i)); let gt = global_transforms.get(*edge).unwrap(); let mut gt_x = gt.translation().x; let mut gt_y = gt.translation().y; - let rx = hui.add(egui::DragValue::new(&mut gt_x)); - let ry = hui.add(egui::DragValue::new(&mut gt_y)); + let c1 = hui.add(egui::DragValue::new(&mut gt_x)).changed(); + let c2 = hui.add(egui::DragValue::new(&mut gt_y)).changed(); - 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: 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: i, from: Vec2::new(gt_x, changes.0), to: Vec2::new(gt_x, gt_y) }); - } - - if rx.changed() || ry.changed() { + if c1 || c2 { 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()); @@ -371,11 +277,9 @@ pub fn inspector_sys( }); } ui.separator(); - } - if let Ok(mut v) = visible.get_mut(e) { - ui.checkbox(&mut v.is_visible, "Visible"); - } - if shape.is_ok() { + if let Ok(mut v) = visible.get_mut(e) { + ui.checkbox(&mut v.is_visible, "Visible"); + } if let Ok(mut dm) = draw_modes.get_mut(e) { ui.separator(); if let DrawMode::Outlined { fill_mode: f, outline_mode: _ } = &mut *dm { @@ -389,54 +293,55 @@ pub fn inspector_sys( } } ui.separator(); - } + ui.label("Notes"); + ui.text_edit_multiline(&mut sd.note); - 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() { - if let Ok(mut sd) = shape { - 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 }); + ui.separator(); + if ui.button("Delete").clicked() { + coms.entity(e).despawn_recursive(); + **selected = None; } - 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, old_entity: e }); + } + 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)); + }); + } + + ui.separator(); + if ui.button("Delete").clicked() { + coms.entity(e).despawn_recursive(); + **selected = None; } - coms.entity(e).despawn_recursive(); - **selected = None; } }); if !open { **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 deleted file mode 100644 index b2764b8..0000000 --- a/src/undo.rs +++ /dev/null @@ -1,314 +0,0 @@ -use bevy::math::Vec3Swizzles; -use bevy::prelude::*; -use bevy_prototype_lyon::prelude::*; - -use crate::{ShapeData, ImageData, CreateShape, PointSize}; - -#[derive(Debug, Clone)] -pub enum UndoItem { - Base, - CreateShape { entity: Entity }, - 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: usize, from: Vec2, to: Vec2 }, - Rotate { entity: Entity, from: f32, to: f32 }, - NameChange { entity: Entity, from: 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, old_entity: Entity }, -} -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>, - 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 } => { - 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 { - transform, - edges, - name, - note, - shape, - dm, - old_entity, - } => { - 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) { - t.translation = from.extend(t.translation.z); - } - let t = *from; - *from = *to; - *to = t; - }, - UndoItem::MoveDot { shape_data, dot, from, to } => { - 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) { - 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) { - 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); - } - 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::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_recursive(); - *item = UndoItem::DeleteImage { id: nid, transform, handle: handle.clone(), old_entity: *entity }; - } - }, - UndoItem::DeleteImage { transform, handle, id, old_entity } => { - 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(); - - 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) { - std::mem::swap(&mut sd.note, from); - } - else if let Ok((mut id, _)) = images.get_mut(*entity) { - std::mem::swap(&mut id.note, from); - } - }, - } - } - else { - 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