basic proof of concept for saving structure stringtrees to file. complete (janky) stringtree editor proof.
This commit is contained in:
parent
8c7fba2a04
commit
4123df7072
|
@ -9,7 +9,8 @@ use bevy::{
|
||||||
math::Vec3,
|
math::Vec3,
|
||||||
prelude::default,
|
prelude::default,
|
||||||
render::{camera::Camera, color::Color},
|
render::{camera::Camera, color::Color},
|
||||||
transform::components::GlobalTransform,
|
transform::components::{GlobalTransform, Transform},
|
||||||
|
utils::hashbrown::HashMap,
|
||||||
window::{PrimaryWindow, Window},
|
window::{PrimaryWindow, Window},
|
||||||
};
|
};
|
||||||
use bevy_egui::{
|
use bevy_egui::{
|
||||||
|
@ -20,8 +21,11 @@ use bevy_egui::{
|
||||||
use crate::{
|
use crate::{
|
||||||
vvedit::s_editor_bevy_input_shim::keybind_codes::REMOVE_VOXEL,
|
vvedit::s_editor_bevy_input_shim::keybind_codes::REMOVE_VOXEL,
|
||||||
vvlib::{
|
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_oct_asset::OctAssetMarker,
|
||||||
|
s_structure_asset::{self, StructureAsset},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
use crate::{
|
use crate::{
|
||||||
|
@ -69,7 +73,8 @@ impl Default for EditWindowUIState {
|
||||||
pub enum PopupWindowMode {
|
pub enum PopupWindowMode {
|
||||||
#[default]
|
#[default]
|
||||||
None,
|
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 {
|
impl PopupWindowMode {
|
||||||
pub fn is_none(&self) -> bool {
|
pub fn is_none(&self) -> bool {
|
||||||
|
@ -205,6 +210,14 @@ pub fn startup_system_edit_ui(
|
||||||
shared_ui_state
|
shared_ui_state
|
||||||
.structure
|
.structure
|
||||||
.add(&String::from("c2"), Some(&String::from("c1")));
|
.add(&String::from("c2"), Some(&String::from("c1")));
|
||||||
|
let asset_test = StructureAsset {
|
||||||
|
layout: shared_ui_state.structure.clone(),
|
||||||
|
default_pose_positions: HashMap::<String, Transform>::new(),
|
||||||
|
};
|
||||||
|
s_structure_asset::serialization::structures::write_latest_version(
|
||||||
|
&"test.vvs".to_string(),
|
||||||
|
&asset_test,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
fn update_ui_scale_factor(
|
fn update_ui_scale_factor(
|
||||||
mut egui_settings: ResMut<EguiSettings>,
|
mut egui_settings: ResMut<EguiSettings>,
|
||||||
|
@ -498,8 +511,15 @@ pub fn edit_window_ui(
|
||||||
});
|
});
|
||||||
ui.collapsing("Model Structure", |ui| {
|
ui.collapsing("Model Structure", |ui| {
|
||||||
ui.vertical(|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 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 selection = shared_ui_state.selected_structure_element.clone();
|
||||||
let mut should_close = false;
|
let mut should_close = false;
|
||||||
if let PopupWindowMode::EditStructureElement(val, _) =
|
if let PopupWindowMode::EditStructureElement(val, _) =
|
||||||
|
|
|
@ -20,6 +20,20 @@ impl StringTreeElement {
|
||||||
pub fn new(key: &String) -> StringTreeElement {
|
pub fn new(key: &String) -> StringTreeElement {
|
||||||
StringTreeElement::Element(key.clone(), Box::new(Vec::new()))
|
StringTreeElement::Element(key.clone(), Box::new(Vec::new()))
|
||||||
}
|
}
|
||||||
|
pub fn children(&self) -> Option<Vec<String>> {
|
||||||
|
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 {
|
pub fn contains_down_branch(&self, search: &String) -> bool {
|
||||||
self.down_branch(search)
|
self.down_branch(search)
|
||||||
}
|
}
|
||||||
|
@ -38,6 +52,7 @@ impl StringTreeElement {
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default, Debug, Clone)]
|
#[derive(Default, Debug, Clone)]
|
||||||
pub struct StringTree {
|
pub struct StringTree {
|
||||||
pub root: Vec<StringTreeElement>,
|
pub root: Vec<StringTreeElement>,
|
||||||
|
@ -54,6 +69,37 @@ impl StringTree {
|
||||||
}
|
}
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
pub fn children_of(&self, subject: &String) -> Option<Vec<String>> {
|
||||||
|
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<Vec<String>> {
|
||||||
|
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(
|
fn recursive_search_rename(
|
||||||
element: &mut StringTreeElement,
|
element: &mut StringTreeElement,
|
||||||
subject: &String,
|
subject: &String,
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
use std::mem;
|
||||||
|
|
||||||
use bevy::render::color::Color;
|
use bevy::render::color::Color;
|
||||||
use bevy_egui::{
|
use bevy_egui::{
|
||||||
egui::{
|
egui::{
|
||||||
|
@ -8,7 +10,7 @@ use bevy_egui::{
|
||||||
EguiContexts,
|
EguiContexts,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::vvlib::s_identifier_validation;
|
use crate::vvlib::s_identifier_validation::{self, is_valid};
|
||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
s_editor_ui::{EditWindowUIState, PopupWindowMode},
|
s_editor_ui::{EditWindowUIState, PopupWindowMode},
|
||||||
|
@ -60,9 +62,15 @@ pub fn round_trip_color() {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn show_popup(state: &mut EditWindowUIState, contexts: &mut EguiContexts) {
|
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 change_state = None;
|
||||||
let mut reparent = None;
|
let mut reparent = None;
|
||||||
let mut rename_complete = false;
|
let mut rename_complete = false;
|
||||||
|
let mut added_valid_name = false;
|
||||||
match &mut state.popup {
|
match &mut state.popup {
|
||||||
PopupWindowMode::None => return,
|
PopupWindowMode::None => return,
|
||||||
PopupWindowMode::EditStructureElement(name, rename) => {
|
PopupWindowMode::EditStructureElement(name, rename) => {
|
||||||
|
@ -86,6 +94,14 @@ pub fn show_popup(state: &mut EditWindowUIState, contexts: &mut EguiContexts) {
|
||||||
rename_complete = true;
|
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 {
|
enum Tasks {
|
||||||
MoveElement,
|
MoveElement,
|
||||||
RemoveElement,
|
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() {
|
if change_state.is_some() {
|
||||||
state.popup = change_state.unwrap();
|
state.popup = change_state.unwrap();
|
||||||
|
@ -140,6 +183,29 @@ pub fn show_popup(state: &mut EditWindowUIState, contexts: &mut EguiContexts) {
|
||||||
}
|
}
|
||||||
state.popup = PopupWindowMode::None;
|
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) {
|
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.horizontal(|ui| {
|
||||||
ui.label(prec);
|
ui.label(prec);
|
||||||
ui.radio_value(selection, name.clone(), name.clone());
|
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() {
|
for each in children.as_ref() {
|
||||||
show_child(ui, each, depth + 1, selection);
|
show_child(ui, each, depth + 1, selection);
|
||||||
|
|
|
@ -80,10 +80,10 @@ pub mod serialization {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn write_latest_version(path: &String, asset: &OctTreeAsset) -> bool {
|
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,
|
path: &String,
|
||||||
asset: &OctTreeAsset,
|
asset: &OctTreeAsset,
|
||||||
version: MeshFileVersions,
|
version: MeshFileVersions,
|
||||||
|
|
|
@ -11,8 +11,8 @@ use thiserror::Error;
|
||||||
|
|
||||||
#[derive(Asset, TypePath)]
|
#[derive(Asset, TypePath)]
|
||||||
pub struct StructureAsset {
|
pub struct StructureAsset {
|
||||||
layout: StringTree,
|
pub layout: StringTree,
|
||||||
positions: HashMap<String, Transform>,
|
pub default_pose_positions: HashMap<String, Transform>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
|
@ -31,7 +31,7 @@ pub enum StructureLoadError {
|
||||||
|
|
||||||
pub mod serialization {
|
pub mod serialization {
|
||||||
|
|
||||||
pub mod meshes {
|
pub mod structures {
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
|
||||||
use bevy::{
|
use bevy::{
|
||||||
|
@ -46,10 +46,10 @@ pub mod serialization {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn write_latest_version(path: &String, asset: &StructureAsset) -> bool {
|
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,
|
path: &String,
|
||||||
asset: &StructureAsset,
|
asset: &StructureAsset,
|
||||||
version: StructureVersions,
|
version: StructureVersions,
|
||||||
|
@ -94,14 +94,69 @@ pub mod serialization {
|
||||||
|
|
||||||
pub mod version_1 {
|
pub mod version_1 {
|
||||||
|
|
||||||
|
use crate::vvedit::s_string_tree::StringTree;
|
||||||
|
|
||||||
use super::super::super::super::{StructureAsset, StructureLoadError};
|
use super::super::super::super::{StructureAsset, StructureLoadError};
|
||||||
pub fn load(
|
pub fn load(
|
||||||
_data: std::str::Lines<'_>,
|
_data: std::str::Lines<'_>,
|
||||||
) -> Result<StructureAsset, StructureLoadError> {
|
) -> Result<StructureAsset, StructureLoadError> {
|
||||||
Err(StructureLoadError::FileDataInvalid("".to_string()))
|
Err(StructureLoadError::FileDataInvalid("".to_string()))
|
||||||
}
|
}
|
||||||
pub fn save(_asset: &StructureAsset) -> Option<Vec<u8>> {
|
pub fn save(asset: &StructureAsset) -> Option<Vec<u8>> {
|
||||||
None
|
// 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::<String>::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(),
|
"could not read_to_end the bytes".to_string(),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
return serialization::meshes::load_detect_version(bytes);
|
return serialization::structures::load_detect_version(bytes);
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue