From 4123df7072c86d6f83210b2a60923012b1b3d32b Mon Sep 17 00:00:00 2001 From: Lillian Vixe Date: Tue, 21 May 2024 14:59:13 -0700 Subject: [PATCH] basic proof of concept for saving structure stringtrees to file. complete (janky) stringtree editor proof. --- src/vvedit/s_editor_ui.rs | 28 ++++++++++-- src/vvedit/s_string_tree.rs | 46 +++++++++++++++++++ src/vvedit/s_ui_details.rs | 81 ++++++++++++++++++++++++++++------ src/vvlib/s_oct_asset.rs | 4 +- src/vvlib/s_structure_asset.rs | 71 +++++++++++++++++++++++++---- 5 files changed, 202 insertions(+), 28 deletions(-) diff --git a/src/vvedit/s_editor_ui.rs b/src/vvedit/s_editor_ui.rs index 4b78977..e3d686e 100644 --- a/src/vvedit/s_editor_ui.rs +++ b/src/vvedit/s_editor_ui.rs @@ -9,7 +9,8 @@ use bevy::{ math::Vec3, prelude::default, render::{camera::Camera, color::Color}, - transform::components::GlobalTransform, + transform::components::{GlobalTransform, Transform}, + utils::hashbrown::HashMap, window::{PrimaryWindow, Window}, }; use bevy_egui::{ @@ -20,8 +21,11 @@ use bevy_egui::{ use crate::{ vvedit::s_editor_bevy_input_shim::keybind_codes::REMOVE_VOXEL, vvlib::{ - s_inputs::InputRegister, s_intersections::octtree_ray_overlap, s_obb::OBB, + s_inputs::InputRegister, + s_intersections::octtree_ray_overlap, + s_obb::OBB, s_oct_asset::OctAssetMarker, + s_structure_asset::{self, StructureAsset}, }, }; use crate::{ @@ -69,7 +73,8 @@ impl Default for EditWindowUIState { pub enum PopupWindowMode { #[default] None, - EditStructureElement(String, String), //rename from and to, otherwise second string is unused + EditStructureElement(String, String), // rename from and to, otherwise second string is unused + CreateStructureElement(String, String), // node name, parent node } impl PopupWindowMode { pub fn is_none(&self) -> bool { @@ -205,6 +210,14 @@ pub fn startup_system_edit_ui( shared_ui_state .structure .add(&String::from("c2"), Some(&String::from("c1"))); + let asset_test = StructureAsset { + layout: shared_ui_state.structure.clone(), + default_pose_positions: HashMap::::new(), + }; + s_structure_asset::serialization::structures::write_latest_version( + &"test.vvs".to_string(), + &asset_test, + ); } fn update_ui_scale_factor( mut egui_settings: ResMut, @@ -498,8 +511,15 @@ pub fn edit_window_ui( }); ui.collapsing("Model Structure", |ui| { ui.vertical(|ui| { + if ui.button("add element").clicked() { + shared_ui_state.popup = PopupWindowMode::CreateStructureElement( + "unnamed".to_owned(), + "".to_owned(), + ); + } + ui.separator(); if shared_ui_state.selected_structure_element != "" { - if ui.button("...").clicked() { + if ui.button("Edit Structure of Node").clicked() { let selection = shared_ui_state.selected_structure_element.clone(); let mut should_close = false; if let PopupWindowMode::EditStructureElement(val, _) = diff --git a/src/vvedit/s_string_tree.rs b/src/vvedit/s_string_tree.rs index 64326ba..8cdd0b3 100644 --- a/src/vvedit/s_string_tree.rs +++ b/src/vvedit/s_string_tree.rs @@ -20,6 +20,20 @@ impl StringTreeElement { pub fn new(key: &String) -> StringTreeElement { StringTreeElement::Element(key.clone(), Box::new(Vec::new())) } + pub fn children(&self) -> Option> { + match self { + StringTreeElement::Element(_, children) => { + let mut result = Vec::new(); + for each in children.as_ref() { + if let Some(name) = each.name_of() { + result.push(name.clone()); + } + } + return Some(result); + } + _ => None, + } + } pub fn contains_down_branch(&self, search: &String) -> bool { self.down_branch(search) } @@ -38,6 +52,7 @@ impl StringTreeElement { false } } + #[derive(Default, Debug, Clone)] pub struct StringTree { pub root: Vec, @@ -54,6 +69,37 @@ impl StringTree { } false } + pub fn children_of(&self, subject: &String) -> Option> { + for each in &self.root { + let res = Self::check_children_of(each, subject); + if res.is_some() { + return res; + } + } + None + } + fn check_children_of(element: &StringTreeElement, subject: &String) -> Option> { + if let StringTreeElement::Element(name, children) = element { + if name == subject { + // return children's names + let mut result = Vec::new(); + for each in children.as_ref() { + if let StringTreeElement::Element(name, _) = each { + result.push(name.clone()); + } + } + return Some(result); + } else { + for each in children.as_ref() { + let res = Self::check_children_of(each, subject); + if res.is_some() { + return res; + } + } + } + } + None + } fn recursive_search_rename( element: &mut StringTreeElement, subject: &String, diff --git a/src/vvedit/s_ui_details.rs b/src/vvedit/s_ui_details.rs index 7b7e52e..5738613 100644 --- a/src/vvedit/s_ui_details.rs +++ b/src/vvedit/s_ui_details.rs @@ -1,3 +1,5 @@ +use std::mem; + use bevy::render::color::Color; use bevy_egui::{ egui::{ @@ -8,7 +10,7 @@ use bevy_egui::{ EguiContexts, }; -use crate::vvlib::s_identifier_validation; +use crate::vvlib::s_identifier_validation::{self, is_valid}; use super::{ s_editor_ui::{EditWindowUIState, PopupWindowMode}, @@ -60,9 +62,15 @@ pub fn round_trip_color() { } pub fn show_popup(state: &mut EditWindowUIState, contexts: &mut EguiContexts) { + enum PopupStateChange { + None, + RemoveStructureElement(bool), //keep children(reparenting upward) or drop + } + let mut state_change = PopupStateChange::None; let mut change_state = None; let mut reparent = None; let mut rename_complete = false; + let mut added_valid_name = false; match &mut state.popup { PopupWindowMode::None => return, PopupWindowMode::EditStructureElement(name, rename) => { @@ -86,6 +94,14 @@ pub fn show_popup(state: &mut EditWindowUIState, contexts: &mut EguiContexts) { rename_complete = true; } }); + ui.horizontal(|ui| { + if ui.button("remove and reparent to parent").clicked() { + state_change = PopupStateChange::RemoveStructureElement(true); + } + if ui.button("remove and drop children").clicked() { + state_change = PopupStateChange::RemoveStructureElement(false); + } + }); enum Tasks { MoveElement, RemoveElement, @@ -119,6 +135,33 @@ pub fn show_popup(state: &mut EditWindowUIState, contexts: &mut EguiContexts) { } }); } + PopupWindowMode::CreateStructureElement(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| { + ui.label("please name the new structure node."); + let mut newname = name.clone(); + ui.text_edit_singleline(&mut newname); + if is_valid(newname.clone()) && !state.structure.is_present(&newname) { + *name = newname; + } + if ui.button("Confirm and add.").clicked() { + added_valid_name = true; + } + 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 state.structure.list_of_elements() { + ui.radio_value(parent, each.clone(), each.clone()); + } + }); + }, + ); + }); + } } if change_state.is_some() { state.popup = change_state.unwrap(); @@ -140,6 +183,29 @@ pub fn show_popup(state: &mut EditWindowUIState, contexts: &mut EguiContexts) { } state.popup = PopupWindowMode::None; } + if added_valid_name { + if let PopupWindowMode::CreateStructureElement(name, parent) = &state.popup { + let par = if parent == "" { None } else { Some(parent) }; + state.structure.add(name, par); + } else { + return; + } + state.popup = PopupWindowMode::None; + } + match state_change { + PopupStateChange::None => {} // nothing to do if state does not change + PopupStateChange::RemoveStructureElement(keep_children) => { + let mut consumed = PopupWindowMode::None; + mem::swap(&mut state.popup, &mut consumed); + if let PopupWindowMode::EditStructureElement(subject, _) = consumed { + if keep_children { + state.structure.remove_element_preserve_branch(&subject); + } else { + state.structure.remove_branch(&subject); + } + } + } + } } pub fn show_stringtree_selector(ui: &mut egui::Ui, tree: &StringTree, selection: &mut String) { @@ -180,19 +246,6 @@ pub fn show_child( ui.horizontal(|ui| { ui.label(prec); ui.radio_value(selection, name.clone(), name.clone()); - /* if ui.button("...").clicked() { - let mut should_close = false; - if let PopupWindowMode::EditStructureElement(val, _) = state { - if val == name { - should_close = true; - } - } - if should_close { - *state = PopupWindowMode::None; - } else { - *state = PopupWindowMode::EditStructureElement(name.clone(), name.clone()) - } - } */ }); for each in children.as_ref() { show_child(ui, each, depth + 1, selection); diff --git a/src/vvlib/s_oct_asset.rs b/src/vvlib/s_oct_asset.rs index 52848ed..40726bd 100644 --- a/src/vvlib/s_oct_asset.rs +++ b/src/vvlib/s_oct_asset.rs @@ -80,10 +80,10 @@ pub mod serialization { } pub fn write_latest_version(path: &String, asset: &OctTreeAsset) -> bool { - write_any_version(path, asset, Default::default()) + write_by_version(path, asset, Default::default()) } - pub fn write_any_version( + pub fn write_by_version( path: &String, asset: &OctTreeAsset, version: MeshFileVersions, diff --git a/src/vvlib/s_structure_asset.rs b/src/vvlib/s_structure_asset.rs index 4a34ce4..11c48ba 100644 --- a/src/vvlib/s_structure_asset.rs +++ b/src/vvlib/s_structure_asset.rs @@ -11,8 +11,8 @@ use thiserror::Error; #[derive(Asset, TypePath)] pub struct StructureAsset { - layout: StringTree, - positions: HashMap, + pub layout: StringTree, + pub default_pose_positions: HashMap, } #[derive(Default)] @@ -31,7 +31,7 @@ pub enum StructureLoadError { pub mod serialization { - pub mod meshes { + pub mod structures { use std::path::Path; use bevy::{ @@ -46,10 +46,10 @@ pub mod serialization { } pub fn write_latest_version(path: &String, asset: &StructureAsset) -> bool { - write_any_version(path, asset, Default::default()) + write_by_version(path, asset, Default::default()) } - pub fn write_any_version( + pub fn write_by_version( path: &String, asset: &StructureAsset, version: StructureVersions, @@ -94,14 +94,69 @@ pub mod serialization { pub mod version_1 { + use crate::vvedit::s_string_tree::StringTree; + use super::super::super::super::{StructureAsset, StructureLoadError}; pub fn load( _data: std::str::Lines<'_>, ) -> Result { Err(StructureLoadError::FileDataInvalid("".to_string())) } - pub fn save(_asset: &StructureAsset) -> Option> { - None + pub fn save(asset: &StructureAsset) -> Option> { + // FILE STRUCTURE: + // FIRST, STRING TREE + // STRING TREE IS STORED AS LISTS OF STRINGS - FIRST STRING IS PARENT + // EACH LINE DEFINES ONE NODE TO ITS CHILDREN + // FIRST LINE IS ROOT LEVEL NODES AFTER THAT EACH CHILD NODE OF ROOT NODES IN ORDER + // PAUSE BETWEEN STRING TREE AND POSITIONS + // LIST OF NODE IDS BY STRING NAME, FOLLOWED BY TRANSFORM DATA, FOLLOWED BY NONSTANDARD DATA (MESH BEING RENDERED AT THIS TRANSFORM) + + let mut data: String = "".into(); + let mut is_first = true; + data += "\n"; + let mut to_process = Vec::::new(); + for each in &asset.layout.root { + if let Some(name) = each.name_of() { + // + if !is_first { + data += "|"; + } else { + is_first = false; + } + data += name.clone().as_str(); + } else { + return None; + } + if let Some(children) = each.children() { + for each in children { + to_process.push(each); + } + } + } + fn save_child(element: &String, tree: &StringTree, data: &mut String) { + if let Some(children) = tree.children_of(element) { + if children.is_empty() { + return; + } + *data += "\n"; + *data += element.as_str(); + for each in children { + *data += "|"; + *data += each.as_str(); + } + } + } + while !to_process.is_empty() { + if let Some(next) = to_process.pop() { + save_child(&next, &asset.layout, &mut data); + if let Some(children) = asset.layout.children_of(&next) { + for each in children { + to_process.push(each); + } + } + } + } + Some(data.as_bytes().to_vec()) } } } @@ -143,7 +198,7 @@ impl AssetLoader for StructureLoader { "could not read_to_end the bytes".to_string(), )); } - return serialization::meshes::load_detect_version(bytes); + return serialization::structures::load_detect_version(bytes); }) } }