track untrackeds

This commit is contained in:
Lillian Vixe 2024-06-14 09:26:40 -07:00
parent 25f5ea3971
commit 7d2ff0408b
4 changed files with 622 additions and 0 deletions

16
assets/test.vvs Normal file
View file

@ -0,0 +1,16 @@
version 1
torso|head|waist|frontleftpaw|frontrightpaw
head|leftear|rightear
waist|tail|backleftpaw|backrightpaw
||
element|torso
position|0|0|0
scale|0.25|0.25|0.25
rotation|0|0|0|1
element|head
position|0|0|0
scale|1|1|1
rotation|0|0|0|1
||
head|grid|test.vvg

View file

@ -0,0 +1,594 @@
use std::{fs, io, path::Path};
use bevy::{
asset::{AssetServer, Assets},
ecs::{
entity::Entity,
event::EventWriter,
system::{Commands, ResMut},
},
math::{Quat, Vec3},
render::color::Color,
utils::hashbrown::hash_map::Entry,
};
use bevy_egui::{
egui::{
self,
ecolor::{hsv_from_rgb, rgb_from_hsv},
epaint::Hsva,
},
EguiContexts,
};
use crate::{
vvedit::s_editor_ui::{GridEditData, Selection, StructureEditData},
vvlib::{
s_identifier_validation::{self, is_valid},
s_oct_asset::OctTreeAsset,
s_structure_asset::{StructureAsset, StructureUpdateMessage, StructureUpdateType},
},
};
use super::{
s_editor_ui::{EditWindowUIState, PopupWindowMode},
s_string_tree::{StringTree, StringTreeElement},
};
pub fn color_from(egui_color: Hsva) -> Color {
let rgb = rgb_from_hsv((egui_color.h, egui_color.s, egui_color.v));
return Color::rgb_from_array(rgb);
}
pub fn egui_color_from(color: Color) -> Hsva {
let x = hsv_from_rgb([color.r(), color.g(), color.b()]);
return Hsva::new(x.0, x.1, x.2, 1.);
}
#[test]
pub fn round_trip_color() {
use rand::Rng;
let mut rng = rand::thread_rng();
let min = 0.;
let max = 1.;
for _ in 0..10000 {
let r = rng.gen_range(min..max);
let g = rng.gen_range(min..max);
let b = rng.gen_range(min..max);
let mut color = Color::rgb(r, g, b);
let mut egui_color = egui_color_from(color);
color = color_from(egui_color);
egui_color = egui_color_from(color);
color = color_from(egui_color);
egui_color = egui_color_from(color);
color = color_from(egui_color);
egui_color = egui_color_from(color);
color = color_from(egui_color);
egui_color = egui_color_from(color);
color = color_from(egui_color);
egui_color = egui_color_from(color);
color = color_from(egui_color);
egui_color = egui_color_from(color);
color = color_from(egui_color);
egui_color = egui_color_from(color);
color = color_from(egui_color);
assert_eq!((color.r() * 255.).floor(), (r * 255.).floor());
assert_eq!((color.g() * 255.).floor(), (g * 255.).floor());
assert_eq!((color.b() * 255.).floor(), (b * 255.).floor());
}
}
pub fn show_popup(
state: &mut EditWindowUIState,
contexts: &mut EguiContexts,
asset_server: &mut ResMut<AssetServer>,
structure_assets: &mut ResMut<Assets<StructureAsset>>,
event_writer: &mut EventWriter<StructureUpdateMessage>,
mut commands: &mut Commands,
) {
enum PopupStateChange {
None,
RemoveStructureElement(String, String, bool), //structure subject, name of element, keep children(reparenting upward) or drop
RenameComplete,
Reparent(String, String, String), //structure subject, name of element to reparent, new parent
Addition(String, String, String), //structure subject, new element name, new parent
Cancel,
}
let mut state_change = PopupStateChange::None;
//let mut added_valid_name = false;
match &mut state.popup {
PopupWindowMode::None => return,
PopupWindowMode::EditStructureElement(structure_selection, name, rename) => {
//formatting comment
let response = egui::Window::new("edit structure element ".to_owned() + &name.clone())
.id(state.egui.popup_identifier.unwrap());
response.show(contexts.ctx_mut(), |ui| {
//formatting comment
let cur = name.clone();
ui.label("what do you want to do with ".to_owned() + &name + "?");
ui.horizontal(|ui| {
//.
ui.label("rename element: ");
let mut rename_string = rename.clone();
ui.text_edit_singleline(&mut rename_string);
if s_identifier_validation::is_valid(rename_string.clone()) {
*rename = rename_string;
}
if ui.button("Confirm and change name").clicked() {
//.
state_change = PopupStateChange::RenameComplete;
}
});
ui.horizontal(|ui| {
if ui.button("remove and reparent to parent").clicked() {
state_change = PopupStateChange::RemoveStructureElement(
structure_selection.clone(),
name.clone(),
true,
);
}
if ui.button("remove and drop children").clicked() {
state_change = PopupStateChange::RemoveStructureElement(
structure_selection.clone(),
name.clone(),
false,
);
}
});
enum Tasks {
MoveElement,
RemoveElement,
DeleteElementWithChildren,
DeleteElementAndReparent,
}
ui.collapsing("Reparent to child of", |ui| {
ui.vertical(|ui| {
let mut selected_structure = false;
let mut selection = None;
if structure_selection != "" {
for structure in &state.structures {
if structure.path == structure_selection.clone() {
let asset = structure_assets.get_mut(structure.id.clone());
if asset.is_some() {
selection = Some(&mut asset.unwrap().layout);
selected_structure = true;
}
break;
}
}
}
if selected_structure {
let selection = selection.unwrap();
for each in selection.list_of_elements() {
if each != cur
&& Some(&each) != selection.parent_of(&cur.clone())
&& !selection.is_upstream_of(&cur, &each)
{
if ui.button(&each).clicked() {
state_change = PopupStateChange::Reparent(
structure_selection.clone(),
cur.clone(),
each,
);
}
}
}
if selection.parent_of(&cur).is_some() {
if ui.button("None").clicked() {
state_change = PopupStateChange::Reparent(
structure_selection.clone(),
cur.clone(),
"".into(),
);
}
}
}
});
});
// todo: rename action. edit subject action (mesh, light), remove action (with, without reparenting)
if ui.button("Cancel").clicked() {
state_change = PopupStateChange::Cancel;
}
});
}
PopupWindowMode::CreateStructureElement(structure_selection, name, parent) => {
let response = egui::Window::new("new structure element ".to_owned() + &name.clone())
.id(state.egui.popup_identifier.unwrap());
response.show(contexts.ctx_mut(), |ui| {
let mut selected_structure = false;
let mut selection = None;
if structure_selection != "" {
for structure in &state.structures {
if structure.path == structure_selection.clone() {
let asset = structure_assets.get_mut(structure.id.clone());
if asset.is_some() {
selection = Some(&mut asset.unwrap().layout);
selected_structure = true;
}
break;
}
}
}
if selected_structure {
let selection = selection.unwrap();
ui.label("please name the new structure node.");
let mut newname = name.clone();
ui.text_edit_singleline(&mut newname);
if is_valid(newname.clone()) && !selection.is_present(&newname) {
*name = newname;
}
if ui.button("Confirm and add.").clicked() {
//added_valid_name = true;
state_change = PopupStateChange::Addition(
structure_selection.clone(),
name.clone(),
parent.clone(),
);
}
ui.collapsing(
"parent status: ".to_owned() + parent.clone().as_str(),
|ui| {
ui.vertical(|ui| {
ui.radio_value(parent, "".into(), "Root level - no parent");
for each in selection.list_of_elements() {
ui.radio_value(parent, each.clone(), each.clone());
}
});
},
);
}
});
}
PopupWindowMode::LoadFile(current_selection, counter, files) => {
// immediate mode means we run every frame
if *counter == i32::MIN {
// do nothing. disk inaccessible
} else if (*counter > (30 * 64)) | files.is_empty() {
//rescan disk
let x = fs::read_dir("assets");
if let Ok(x) = x {
let x: Result<Vec<String>, io::Error> = x
.into_iter()
.map(
//
|x| {
x.map(
//
|entry| entry.path().to_string_lossy().into_owned(),
)
},
)
.collect();
if let Ok(x) = x {
*files = x;
*counter = 0;
} else {
*counter = i32::MIN;
}
} else {
*counter = i32::MIN;
}
} else {
*counter += 1;
}
let response =
egui::Window::new("load file".to_owned()).id(state.egui.popup_identifier.unwrap());
response.show(contexts.ctx_mut(), |ui| {
let mut selection_updated = current_selection.clone();
ui.text_edit_singleline(&mut selection_updated);
*current_selection = selection_updated.clone();
*current_selection = current_selection.replace("assets/", "");
#[derive(PartialEq)]
enum Extension {
Invalid,
Structure,
Grid,
}
let mut extension = Extension::Invalid;
if current_selection.ends_with(".vvg") {
extension = Extension::Grid;
}
if current_selection.ends_with(".vvs") {
extension = Extension::Structure;
}
if Path::new(("assets/".to_owned() + current_selection.as_str()).as_str()).exists()
& (current_selection.as_str() != "")
& !(extension == Extension::Invalid)
{
if ui.button("Load file").clicked() {
*current_selection = current_selection.replace("assets/", "");
let path_pre = current_selection.clone();
match extension {
Extension::Invalid => {}
Extension::Structure => {
let handle = asset_server.load::<StructureAsset>(path_pre.clone());
let data = StructureEditData {
path: path_pre,
id: handle,
has_changed_since_last_save: false,
edit_entity: Entity::PLACEHOLDER, //BAD BAD BAD
};
let mut loaded_already = false;
for each in &state.structures {
if each.id == data.id {
loaded_already = true;
}
}
if !loaded_already {
state.structures.push(data);
}
}
Extension::Grid => {
let handle = asset_server.load::<OctTreeAsset>(path_pre.clone());
let data = GridEditData {
path: path_pre,
id: handle,
has_changed_since_last_save: false,
};
let mut loaded_already = false;
for each in &state.grids {
if each.id == data.id {
loaded_already = true;
}
}
if !loaded_already {
state.grids.push(data);
}
}
}
}
ui.label("");
} else {
ui.label("✘ not a valid file to load");
}
if ui.button("close").clicked() {
state_change = PopupStateChange::Cancel;
}
ui.separator();
for each in files {
if ui.button(each.as_str()).clicked() {
*current_selection = each.clone();
*current_selection = current_selection.replace("assets/", "");
}
}
});
}
}
match state_change {
PopupStateChange::None => {} // nothing to do if state does not change
PopupStateChange::RemoveStructureElement(subject, element, keep_children) => {
let mut selected_structure = false;
let mut selection = None;
if subject != "" {
for structure in &mut state.structures {
if structure.path == subject {
let asset = structure_assets.get_mut(structure.id.clone());
if asset.is_some() {
structure.has_changed_since_last_save = true;
selection = Some(&mut asset.unwrap().layout);
selected_structure = true;
event_writer.send(StructureUpdateMessage {
update_type: StructureUpdateType::Rebuild,
entity: structure.edit_entity,
});
}
break;
}
}
}
if selected_structure {
let selection = selection.unwrap();
if keep_children {
selection.remove_element_preserve_branch(&element);
} else {
selection.remove_branch(&element);
}
state.popup = PopupWindowMode::None;
}
}
PopupStateChange::Cancel => {
state.popup = PopupWindowMode::None;
}
PopupStateChange::RenameComplete => {
if let PopupWindowMode::EditStructureElement(subject, old, new) = &state.popup {
let mut selected_structure = false;
let mut selection = None;
if subject != "" {
for structure in &mut state.structures {
if &structure.path == subject {
let asset = structure_assets.get_mut(structure.id.clone());
if asset.is_some() {
selection = Some(&mut asset.unwrap().layout);
selected_structure = true;
event_writer.send(StructureUpdateMessage {
update_type: StructureUpdateType::Rebuild,
entity: structure.edit_entity,
});
}
if selected_structure {
let selection = selection.unwrap();
if selection.rename(old, new) {
structure.has_changed_since_last_save = true;
}
if let Selection::Structure(structure_name) =
state.selection.clone()
{
if let Entry::Occupied(element_select) = state
.struct_element_selections
.clone()
.entry(structure_name.clone())
{
let element_select = element_select.get();
if element_select == old {
state
.struct_element_selections
.insert(structure_name, new.clone());
}
}
}
}
state.popup = PopupWindowMode::None;
break;
}
}
}
}
}
PopupStateChange::Reparent(subject, cur, next) => {
if subject != "" {
for structure in &mut state.structures {
if structure.path == subject {
let asset = structure_assets.get_mut(structure.id.clone());
if asset.is_some() {
let selection = &mut asset.unwrap().layout;
if next != "" {
if selection.move_branch(&cur, &next) {
event_writer.send(StructureUpdateMessage {
update_type: StructureUpdateType::Rebuild,
entity: structure.edit_entity,
});
structure.has_changed_since_last_save = true;
}
} else {
if selection.deparent(&cur) {
event_writer.send(StructureUpdateMessage {
update_type: StructureUpdateType::Rebuild,
entity: structure.edit_entity,
});
structure.has_changed_since_last_save = true;
}
}
}
break;
}
}
}
state.popup = PopupWindowMode::None;
}
PopupStateChange::Addition(subject, name, parent) => {
if subject != "" {
for structure in &mut state.structures {
if structure.path == subject {
let asset = structure_assets.get_mut(structure.id.clone());
if asset.is_some() {
let selection = &mut asset.unwrap().layout;
let par = if parent == "" { None } else { Some(&parent) };
if selection.add(&name, par) {
structure.has_changed_since_last_save = true;
event_writer.send(StructureUpdateMessage {
update_type: StructureUpdateType::Rebuild,
entity: structure.edit_entity,
});
}
state.popup = PopupWindowMode::None;
return;
}
}
}
}
}
}
}
pub fn show_stringtree_selector(ui: &mut egui::Ui, tree: &StringTree, selection: &mut String) {
for each in &tree.root {
match each {
super::s_string_tree::StringTreeElement::None => {}
super::s_string_tree::StringTreeElement::Element(name, children) => {
ui.horizontal(|ui| {
ui.label(">".to_owned());
//ui.label(name.clone());
ui.radio_value(selection, name.clone(), name.clone());
});
for each in children.as_ref() {
show_child(ui, each, 1, selection);
}
}
}
}
}
pub fn show_child(
ui: &mut egui::Ui,
subject: &StringTreeElement,
depth: usize,
selection: &mut String,
) {
match subject {
StringTreeElement::None => {}
StringTreeElement::Element(name, children) => {
let mut prec = String::from("");
for i in 0..depth {
if i == 0 {
prec = prec + "+";
} else {
prec = prec + "+";
}
}
prec = prec + ">";
ui.horizontal(|ui| {
ui.label(prec);
ui.radio_value(selection, name.clone(), name.clone());
});
for each in children.as_ref() {
show_child(ui, each, depth + 1, selection);
}
}
}
}
pub fn editable_vec3(ui: &mut egui::Ui, value: &mut Vec3, should_reshape: &mut bool) {
let copy = value.clone();
ui.vertical(|ui| {
ui.horizontal(|ui| {
ui.label("X:");
ui.add(egui::Slider::new(&mut value.x, -100f32..=100f32));
});
ui.horizontal(|ui| {
ui.label("Y:");
ui.add(egui::Slider::new(&mut value.y, -100f32..=100f32));
});
ui.horizontal(|ui| {
ui.label("Z:");
ui.add(egui::Slider::new(&mut value.z, -100f32..=100f32));
});
});
if copy != *value {
*should_reshape = true;
println!("code reached.");
}
}
pub fn editable_quat(ui: &mut egui::Ui, value: &mut Quat, should_reshape: &mut bool) {
let copy = value.clone();
let (mut x, mut y, mut z) = value.to_euler(bevy::math::EulerRot::XYZ);
x = x.to_degrees();
y = y.to_degrees();
z = z.to_degrees();
ui.vertical(|ui| {
ui.horizontal(|ui| {
ui.label("X:");
ui.add(egui::Slider::new(&mut x, -180f32..=180f32));
});
ui.horizontal(|ui| {
ui.label("Y:");
ui.add(egui::Slider::new(&mut y, -180f32..=180f32));
});
ui.horizontal(|ui| {
ui.label("Z:");
ui.add(egui::Slider::new(&mut z, -180f32..=180f32));
});
});
*value = Quat::from_euler(
bevy::math::EulerRot::XYZ,
x.to_radians(),
y.to_radians(),
z.to_radians(),
);
if copy != *value {
*should_reshape = true;
println!("code reached.");
}
}
//pub fn set_visibility

View file

@ -0,0 +1,8 @@
what its like to load a file
default structure is empty
no meshes loaded
load a structure: structure is updated. load all meshes in the structure.
load a mesh: add a mesh node in the structure at root with name of mesh
~~BONUS: want a discovery-list-display of all known assets - scan assets folder~~
~~BONUS: Keep track of if structure or meshes have changed, when they changed offer to save if loading a new structure or closing program (meshes will stay loaded unchanged until close, loading a new structure erases current structure (temp?))~~

View file

@ -0,0 +1,4 @@
pub fn is_valid<S: Into<String>>(text: S) -> bool {
let text: String = text.into();
!(text.contains("|") & text.contains(char::is_whitespace) & text.contains(","))
}