From a6452fcfbf003a3c2c447ab2fba6832bc9d38de1 Mon Sep 17 00:00:00 2001 From: RustyStriker Date: Mon, 15 Aug 2022 21:08:24 +0300 Subject: [PATCH] new items ui - images can now be rotated/scaled --- README.md | 4 +- src/create.rs | 26 ++-- src/lib.rs | 2 +- src/main.rs | 6 +- src/ui.rs | 319 ++++++++++++++++++++++++++++---------------------- 5 files changed, 197 insertions(+), 160 deletions(-) diff --git a/README.md b/README.md index 1115053..6de40b5 100644 --- a/README.md +++ b/README.md @@ -15,11 +15,11 @@ Eventually, this will be a level editor... But until then, I guess it's just fun - [x] Show images in tree view - [x] Show/Hide images - [x] Move/Drag images around - - [ ] Control images Z value(so we could reorder them) + - [x] Control images Z value(so we could reorder them) - [x] Delete images - [x] Name images - [ ] Duplicate images(and also shapes maybe?)? - - [ ] Scale images + - [x] Scale images - [x] Show hide shapes - [x] Control shape Z value - [x] Snap to grid diff --git a/src/create.rs b/src/create.rs index 6b90653..547fd57 100644 --- a/src/create.rs +++ b/src/create.rs @@ -9,7 +9,7 @@ pub fn create_sys( state: Res, mouse: Res>, wnds: Res, - mut first_point: Local>, + mut shape: Local>, q_cam: Query<(&Camera, &GlobalTransform), With>, mut paths: Query<&mut Path>, mut transforms: Query<&mut Transform>, @@ -20,7 +20,7 @@ pub fn create_sys( if let Some(mouse_pos) = mouse_pos { let mouse_pos = snap.snap_to_grid(mouse_pos); - if let Some(sd) = &mut *first_point { + if let Some(sd) = &mut *shape { update_main_shape_creation(&mut paths, &transforms, sd, mouse_pos); if mouse.just_released(MouseButton::Left) { @@ -50,16 +50,16 @@ pub fn create_sys( finalize_shape_redraw(&mut paths, &transforms, sd, mouse_pos - center); - let mut ms = coms.entity(sd.main_shape); - ms.add_child(sd.center.unwrap()); - for e in sd.edges.iter() { + let mut s = shape.take().unwrap(); + s.name = format!("{:?} {}", s.shape, s.main_shape.id()); + + let mut ms = coms.entity(s.main_shape); + ms.add_child(s.center.unwrap()); + for e in s.edges.iter() { ms.add_child(*e); } - - - coms.spawn() - .insert(first_point.take().unwrap()); - *first_point = None; + ms.insert(s); + *shape = None; } } else if mouse.just_released(MouseButton::Right) { @@ -69,14 +69,14 @@ pub fn create_sys( coms.entity(sd.main_shape).despawn(); sd.edges.iter().for_each(|e| coms.entity(*e).despawn()); - *first_point = None; + *shape = None; } } else if mouse.just_released(MouseButton::Left) { // We can now spawn a shape in the current mouse position... // Spawn the first point - *first_point = 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)); } } } @@ -328,5 +328,5 @@ fn create_new_shape(coms: &mut Commands, pos: Vec2, create_shape: CreateShape, p }; let edges = if center.is_none() { Vec::from([fp]) } else { Vec::new() }; - ShapeData { shape: create_shape, main_shape, edges, center, name: None } + ShapeData { shape: create_shape, main_shape, edges, center, name: "shape".to_string() } } \ No newline at end of file diff --git a/src/lib.rs b/src/lib.rs index 99aa27c..dcd8215 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -47,7 +47,7 @@ pub struct ImageSize(pub Vec2); #[derive(Component, Debug)] pub struct ShapeData { - pub name: Option, + pub name: String, pub shape: CreateShape, pub main_shape: Entity, pub edges: Vec, diff --git a/src/main.rs b/src/main.rs index 924cd35..848c8b3 100644 --- a/src/main.rs +++ b/src/main.rs @@ -24,6 +24,7 @@ fn main() { .insert_resource(UiState::default()) .insert_resource(PointSize(6.0)) .insert_resource(SnapGrid::default()) + .insert_resource(ui::SelectedItem::default()) ; app @@ -42,13 +43,14 @@ fn main() { if !ec.ctx_mut().is_pointer_over_area() && state.current_action == Action::Modify { ShouldRun::Yes } else { ShouldRun::No } )) .add_system(ui::action_bar_sys) - .add_system(ui::shape_tree_sys) + .add_system(ui::items_tree_sys) + .add_system(ui::inspector_sys) + .add_system(ui::grid_window_sys) .add_system(show_hide_points) .add_system(color_points) .add_system(drag_camera_sys) .add_system(zoom_camera_sys) .add_system(scale_points) - .add_system(ui::grid_window_sys) ; app.run(); diff --git a/src/ui.rs b/src/ui.rs index 9f8a924..a439127 100644 --- a/src/ui.rs +++ b/src/ui.rs @@ -4,6 +4,9 @@ use bevy_egui::{egui, EguiContext}; use bevy_prototype_lyon::prelude::{Path, DrawMode, FillMode, StrokeMode}; use crate::*; +#[derive(Default, Deref, DerefMut, Clone)] +pub struct SelectedItem(pub Option); + pub fn action_bar_sys( mut coms: Commands, assets: Res, @@ -103,168 +106,200 @@ pub fn grid_window_sys( }); } - -pub fn shape_tree_sys( - mut name_change: Local>, - mut coms: Commands, +/// Items tree +/// +/// Select items to edit in an inspector window/panel, +/// or just change the visibility of items +/// +/// TODO: Allow drag and drop to re-order + reparent +pub fn items_tree_sys( + mut selected: ResMut, mut egui_ctx: ResMut, - mut shapes: Query<(Entity, &mut ShapeData)>, + shapes:Query<(Entity, &ShapeData)>, + mut visible: Query<&mut Visibility, Or<(With, With)>>, + images: Query<(Entity, &ImageName), With>, +) { + egui::Window::new("Items") + .default_pos((10.0,100.0)) + .title_bar(true) + .resizable(false) + .show(egui_ctx.ctx_mut(), |ui| { + for (e, sd) in shapes.iter() { + ui.horizontal(|hui| { + let color = if let Some(se) = **selected && se == e { egui::Color32::GOLD } else { egui::Color32::WHITE }; + let label = egui::Label::new(egui::RichText::new(&sd.name).color(color)).sense(egui::Sense::click()); + if hui.add(label).clicked() { + if color == egui::Color32::GOLD { + **selected = None; + } + else { + **selected = Some(e); + } + } + + if let Ok(mut v) = visible.get_mut(e) { + let rt = egui::RichText::new("V"); + if hui.button(if v.is_visible { rt } else { rt.strikethrough() }).clicked() { + v.is_visible ^= true; + } + } + }); + } + for (e, n) in images.iter() { + ui.horizontal(|hui| { + let color = if let Some(se) = **selected && se == e { egui::Color32::GOLD } else { egui::Color32::WHITE }; + let label = egui::Label::new(egui::RichText::new(&**n).color(color)).sense(egui::Sense::click()); + if hui.add(label).clicked() { + if color == egui::Color32::GOLD { + **selected = None; + } + else { + **selected = Some(e); + } + } + + if let Ok(mut v) = visible.get_mut(e) { + let rt = egui::RichText::new("V"); + if hui.button(if v.is_visible { rt } else { rt.strikethrough() }).clicked() { + v.is_visible ^= true; + } + } + }); + } + + }); +} + +pub fn inspector_sys( + 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>, global_transforms: Query<&GlobalTransform>, mut paths: Query<&mut Path>, mut draw_modes: Query<&mut DrawMode, With>, mut visible: Query<&mut Visibility, Or<(With, With)>>, - mut images: Query<(Entity, &mut ImageName), With>, + mut images: Query<&mut ImageName, With>, ) { - if !shapes.is_empty() { - egui::Window::new("Shape Tree") - .default_pos((10.0, 100.0)) - .title_bar(true) - .resizable(false) - .show(egui_ctx.ctx_mut(), |ui| { - for (e, mut sd) in shapes.iter_mut () { - ui.push_id(e.id(), |ui| { - let mut ui_func = |ui: &mut egui::Ui| { - 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)); - }); - ui.horizontal(|hui| { - hui.label("Z:"); - hui.add(egui::DragValue::new(&mut t.translation.z).clamp_range(0..=i32::MAX)); - }); - 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()); - } - }); - } - 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 c1 = hui.add(egui::DragValue::new(&mut gt_x)).changed(); - let c2 = hui.add(egui::DragValue::new(&mut gt_y)).changed(); + if let Some(e) = **selected { + let mut open = true; - 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()); - if let Ok(mut t) = transforms.get_mut(*edge) { - 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 = 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 - modify::update_main_shape(&mut paths, &transforms, &*sd); - } - }); - } - }; - if let Some((sde, name)) = &mut *name_change && *sde == e { - // Changing the current shape's name - let te = egui::TextEdit::singleline(name).desired_width(100.0); - - if ui.add(te).lost_focus() { - let (_, name) = name_change.take().unwrap(); - sd.name = Some(name); - } - } - else { - let default_name = format!("{:?}", sd.shape); + egui::Window::new("Inspector") + .default_pos((350.0, 350.0)) + .fixed_size((150.0, f32::INFINITY)) + .title_bar(true) + .collapsible(false) + .open(&mut open) + .resizable(false) + .show(egui_ctx.ctx_mut(), |ui| { + if let Ok(mut sd) = shapes.get_mut(e) { + ui.horizontal(|hui| { + hui.label("Name:"); + hui.text_edit_singleline(&mut sd.name); + }); + ui.separator(); + if let Ok(mut t) = transforms.get_mut(sd.main_shape) { ui.horizontal(|hui| { - let collapse = hui.collapsing(sd.name.as_ref().unwrap_or(&default_name), |ui| { - ui.horizontal(|hui| { - if hui.button("E").clicked() { - *name_change = Some((e, format!("{:?}", sd.shape))); - } - if hui.button("D").clicked() { - coms.entity(sd.main_shape).despawn_recursive(); - coms.entity(e).despawn(); - } - let mut v = visible.get_mut(sd.main_shape).unwrap(); - let text = if v.is_visible { egui::RichText::new("V") } else { egui::RichText::new("V").strikethrough() }; - if hui.button(text).clicked() { - v.is_visible ^= true; - } - }); - ui_func(ui); - }); - if collapse.fully_closed() { - if hui.button("E").clicked() { - *name_change = Some((e, default_name)); - } - if hui.button("D").clicked() { - coms.entity(sd.main_shape).despawn_recursive(); - coms.entity(e).despawn(); - } - let mut v = visible.get_mut(sd.main_shape).unwrap(); - let text = if v.is_visible { egui::RichText::new("V") } else { egui::RichText::new("V").strikethrough() }; - if hui.button(text).clicked() { - v.is_visible ^= true; - } + 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()); } - let d_normal = DrawMode::Outlined { - 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), - }; - let d_open = DrawMode::Outlined { - fill_mode: FillMode::color(Color::rgba(0.0, 0.5, 0.5, 0.4)), - outline_mode: StrokeMode::new(Color::GOLD, 3.0), - }; + hui.label("Z:"); + hui.add(egui::DragValue::new(&mut t.translation.z).clamp_range(0..=i32::MAX)); + }); + } + 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 c1 = hui.add(egui::DragValue::new(&mut gt_x)).changed(); + let c2 = hui.add(egui::DragValue::new(&mut gt_y)).changed(); - let wanted_dm = if collapse.fully_open() { d_open } else { d_normal }; - if let Ok(mut dm) = draw_modes.get_mut(sd.main_shape) { - if *dm != wanted_dm { - *dm = wanted_dm + 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()); + if let Ok(mut t) = transforms.get_mut(*edge) { + 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 = 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 + modify::update_main_shape(&mut paths, &transforms, &*sd); } }); } - }); - } - for (e, mut n) in images.iter_mut() { - if let Some((ne, name)) = &mut *name_change && *ne == e { - let te = egui::TextEdit::singleline(name); + ui.separator(); + if let Ok(mut v) = visible.get_mut(e) { + ui.checkbox(&mut v.is_visible, "Visible"); + } - if ui.add(te).lost_focus() { - let (_, name) = name_change.take().unwrap(); - **n = name; + ui.separator(); + if ui.button("Delete").clicked() { + coms.entity(e).despawn_recursive(); + **selected = None; } } - else { + else if let Ok(mut name) = images.get_mut(e) { ui.horizontal(|hui| { - hui.label(&**n); - if hui.button("E").clicked() { - *name_change = Some((e, n.clone())); - } - if hui.button("D").clicked() { - coms.entity(e).despawn(); - } - let mut v = visible.get_mut(e).unwrap(); - let text = if v.is_visible { egui::RichText::new("V") } else { egui::RichText::new("V").strikethrough() }; - if hui.button(text).clicked() { - v.is_visible ^= true; - } + hui.label("Name:"); + hui.text_edit_singleline(&mut **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; + } } - } - }); + }); + if !open { + **selected = None; + } } } \ No newline at end of file