shape_maker/src/ui.rs

442 lines
20 KiB
Rust

use bevy::math::{Vec3Swizzles, Mat2};
use bevy::prelude::*;
use bevy_egui::{egui, EguiContext};
use bevy_prototype_lyon::prelude::{Path, DrawMode};
use crate::*;
#[derive(Default, Deref, DerefMut, Clone)]
pub struct SelectedItem(pub Option<Entity>);
pub fn action_bar_sys(
mut coms: Commands,
mut undo: ResMut<undo::UndoStack>,
assets: Res<AssetServer>,
mut egui_ctx: ResMut<EguiContext>,
mut state: ResMut<UiState>,
mut default_color: ResMut<DefaultColor>,
colors: Res<ButtonsColors>,
) {
egui::Window::new("buttons_float")
.default_pos((20.0, 20.0))
.title_bar(false)
.resizable(false)
.show(egui_ctx.ctx_mut(), |ui| {
ui.horizontal(|hui| {
let m = hui.button(egui::RichText::new("M").color(state.current_action_color(&colors, Action::Modify)))
.on_hover_text("Modify");
if m.clicked() {
state.current_action = Action::Modify;
}
let c = hui.button(egui::RichText::new("C").color(state.current_action_color(&colors, Action::Create)))
.on_hover_text("Create");
if c.clicked() {
state.current_action = Action::Create;
}
hui.label(" | ");
if hui.button(" I ").on_hover_text("Import Image").clicked() {
let file = rfd::FileDialog::new()
.add_filter("Pngs", &["png"])
.set_directory("./")
.set_title("Please dont try to pick a cat instead of an image(although you can pick a cat image)")
.pick_file();
if let Some(file) = file {
let name = String::from(file.file_name().unwrap().to_str().unwrap_or("Image"));
let path = file.to_str().unwrap().to_string();
let image: Handle<Image> = assets.load(file);
let id = coms.spawn_bundle(SpriteBundle {
texture: image,
..Default::default()
})
.insert(ImageData { name, note: String::new(), path }).id();
undo.push(UndoItem::CreateImage { entity: id });
}
}
hui.label(": :");
});
if state.current_action == Action::Create {
ui.horizontal(|hui| {
let tri = hui.button(egui::RichText::new("Tri").color(state.create_shape_color(&colors, CreateShape::Triangle)))
.on_hover_text("Triangle - from 3 edges");
if tri.clicked() {
state.create_shape = CreateShape::Triangle;
}
let squ = hui.button(egui::RichText::new("Squ").color(state.create_shape_color(&colors, CreateShape::Square)))
.on_hover_text("Square - from 2 opposing edges");
if squ.clicked() {
state.create_shape = CreateShape::Square;
}
let cir = hui.button(egui::RichText::new("Cir").color(state.create_shape_color(&colors, CreateShape::Circle)))
.on_hover_text("Circle - center point and radius");
if cir.clicked() {
state.create_shape = CreateShape::Circle;
}
// let cap = hui.button(egui::RichText::new("Cap").color(state.create_shape_color(&colors, CreateShape::Capsule)))
// .on_hover_text("Capsule - from 2 center points and a radius");
// if cap.clicked() {
// 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);
}
});
}
});
}
pub fn grid_window_sys(
mut grid: ResMut<SnapGrid>,
mut egui_ctx: ResMut<EguiContext>,
) {
egui::Window::new("Snap Grid")
.default_pos((500.0, 20.0))
.title_bar(true)
.resizable(false)
.show(egui_ctx.ctx_mut(), |ui| {
ui.horizontal(|hui| {
hui.checkbox(&mut grid.snap, "Snap Enabled");
hui.checkbox(&mut grid.visible, "Visible");
});
ui.label("Grid size:");
ui.horizontal(|hui| {
hui.label("Width:");
hui.add(egui::DragValue::new(&mut grid.width));
hui.label("Height:");
hui.add(egui::DragValue::new(&mut grid.height));
});
ui.horizontal(|hui| {
hui.label("Offset:");
hui.label("X:");
hui.add(egui::DragValue::new(&mut grid.offset.x));
hui.label("Y:");
hui.add(egui::DragValue::new(&mut grid.offset.y));
});
});
}
/// 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<SelectedItem>,
mut imp_exp: ResMut<ShouldImportExport>,
mut egui_ctx: ResMut<EguiContext>,
shapes:Query<(Entity, &ShapeData)>,
images: Query<(Entity, &ImageData), With<Sprite>>,
mut draw_modes: Query<&mut DrawMode, With<ShapeData>>,
mut visible: Query<&mut Visibility, Or<(With<Path>, With<Sprite>)>>,
) {
egui::Window::new("Items")
.default_pos((10.0,100.0))
.fixed_size((150.0, f32::INFINITY))
.title_bar(true)
.resizable(false)
.show(egui_ctx.ctx_mut(), |ui| {
ui.horizontal(|hui| {
if hui.button("Import").clicked() {
imp_exp.import = true;
}
if hui.button("Export").clicked() {
imp_exp.export = true;
}
});
ui.separator();
for (e, sd) in shapes.iter() {
let entity_selected = if let Some(se) = **selected && se == e { true } else { false };
ui.horizontal(|hui| {
let color = if entity_selected { 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 entity_selected {
**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;
}
}
});
if let Ok(mut dm) = draw_modes.get_mut(e) {
if let DrawMode::Outlined { fill_mode: _ , outline_mode: o } = &mut *dm {
o.color = if entity_selected { Color::GOLD } else { Color::rgba(0.0, 0.5, 0.5, 0.6) };
}
}
}
for (e, n) in images.iter() {
let entity_selected = if let Some(se) = **selected && se == e { true } else { false };
ui.horizontal(|hui| {
let color = if entity_selected { egui::Color32::GOLD } else { egui::Color32::WHITE };
let label = egui::Label::new(egui::RichText::new(&n.name).color(color)).sense(egui::Sense::click());
if hui.add(label).clicked() {
if entity_selected {
**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 coms: Commands, // For deletion!
mut changes: Local<(f32, String)>,
mut undo: ResMut<undo::UndoStack>,
mut selected: ResMut<SelectedItem>, // Which item is currently selected
mut egui_ctx: ResMut<EguiContext>,
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<Path>>,
mut visible: Query<&mut Visibility, Or<(With<Path>, With<Sprite>)>>,
mut images: Query<&mut ImageData, With<Sprite>>,
image_handles: Query<&Handle<Image>, With<ImageData>>,
) {
if let Some(e) = **selected {
let mut open = true;
egui::Window::new("Inspector")
.default_pos((20.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| {
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("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 });
}
});
if image.is_ok() {
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() });
}
});
}
}
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));
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));
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() {
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);
}
});
}
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 dm) = draw_modes.get_mut(e) {
ui.separator();
if let DrawMode::Outlined { fill_mode: f, outline_mode: _ } = &mut *dm {
ui.horizontal(|hui| {
hui.label("Color: ");
let mut color = [f.color.r(), f.color.g(), f.color.b(), f.color.a()];
if hui.color_edit_button_rgba_unmultiplied(&mut color).changed() {
f.color = Color::from(color);
}
});
}
}
ui.separator();
}
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::<Vec<Vec2>>();
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) = 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 });
}
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()
}