From e04651da35d21c91cfce12e5f302720f05907af2 Mon Sep 17 00:00:00 2001 From: Zachry Thayer Date: Sat, 9 Aug 2025 11:28:56 -0700 Subject: [PATCH 1/4] Add omagari library --- src/complex.rs | 2 +- src/controller.rs | 54 +++------------------------------ src/effect.rs | 2 +- src/expr.rs | 2 +- src/helpers.rs | 2 +- src/lib.rs | 76 +++++++++++++++++++++++++++++++++++++++++++++++ src/main.rs | 26 ++-------------- src/modifiers.rs | 2 +- 8 files changed, 88 insertions(+), 78 deletions(-) create mode 100644 src/lib.rs diff --git a/src/complex.rs b/src/complex.rs index 1cb5599..e6470fb 100644 --- a/src/complex.rs +++ b/src/complex.rs @@ -5,7 +5,7 @@ use std::fs::File; use std::io; use std::io::Read; -use crate::controller::ExportedProject; +use omagari::controller::ExportedProject; struct PreparedEffect { name: String, diff --git a/src/controller.rs b/src/controller.rs index 58e7f43..dc66a58 100644 --- a/src/controller.rs +++ b/src/controller.rs @@ -1,33 +1,15 @@ use bevy::{platform::collections::HashMap, prelude::*}; use bevy_hanabi::prelude::*; -use ron::{de::from_str, ser::PrettyConfig}; -use serde::{Deserialize, Serialize}; +use ron::de::from_str; use std::{ cell::RefCell, fs::File, - io::{self, Read, Write}, + io::{self, Read}, rc::Rc, }; -use crate::{effect::*, AppContext}; - -#[derive(Resource, Serialize, Deserialize, Default)] -pub struct OmagariProject { - pub effects: Vec, -} - -#[derive(Resource, Serialize, Deserialize, Default)] -pub struct ExportedEffect { - pub name: String, - pub parent: Option, - pub texture_index: Option, - pub effect_asset: EffectAsset, -} - -#[derive(Resource, Serialize, Deserialize, Default)] -pub struct ExportedProject { - pub effects: Vec, -} +use crate::OmagariProject; +use crate::editor_prelude::AppContext; #[derive(Resource)] pub struct EffectResource { @@ -72,25 +54,6 @@ pub fn spawn_particle_effects( } } -pub fn export_effects_to_files(filename: &str, clone: Rc>) { - let base = filename.split('.').next().unwrap(); - let other_filename = format!("{}.hanabi.ron", base); - let mut to_export = ExportedProject::default(); - for effect in clone.borrow().effects.iter() { - to_export.effects.push(ExportedEffect { - name: effect.name().to_string(), - parent: effect.parent().clone(), - texture_index: effect.texture_index(), - effect_asset: effect.produce(), - }); - } - let ron_string = - ron::ser::to_string_pretty(&to_export, PrettyConfig::new().new_line("\n".to_string())) - .unwrap(); - let mut file = File::create(&other_filename).unwrap(); - file.write_all(ron_string.as_bytes()).unwrap(); -} - pub fn projects_list() -> Vec { let mut files = Vec::new(); let entries = std::fs::read_dir(".").unwrap(); @@ -104,15 +67,6 @@ pub fn projects_list() -> Vec { files } -pub fn load_project(filename: &str) -> Result { - let mut file = File::open(filename)?; - let mut ron_string = String::new(); - file.read_to_string(&mut ron_string)?; - let graph: OmagariProject = - from_str(&ron_string).map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?; - Ok(graph) -} - pub fn validate_project_filename(filename: &str) -> bool { regex::Regex::new(r".*\.omagari\.ron") .unwrap() diff --git a/src/effect.rs b/src/effect.rs index 4cbb48d..2928df5 100644 --- a/src/effect.rs +++ b/src/effect.rs @@ -5,11 +5,11 @@ use bevy_hanabi::prelude::*; use serde::Deserialize; use serde::Serialize; +use crate::editor_prelude::AppContext; use crate::helpers::*; use crate::modifiers::ModifierProducer; use crate::modifiers::RenderModifierProducer; use crate::modifiers::*; -use crate::AppContext; fn ui_for_modifiers_list( app: &mut AppContext, diff --git a/src/expr.rs b/src/expr.rs index f23555b..377b4b0 100644 --- a/src/expr.rs +++ b/src/expr.rs @@ -5,8 +5,8 @@ use bevy_hanabi::prelude::*; use serde::Deserialize; use serde::Serialize; +use crate::editor_prelude::AppContext; use crate::helpers::*; -use crate::AppContext; pub const ALL_ATTRS: [(Attribute, &str); 39] = [ (Attribute::ID, "ID"), diff --git a/src/helpers.rs b/src/helpers.rs index d721c8e..e43506f 100644 --- a/src/helpers.rs +++ b/src/helpers.rs @@ -1,7 +1,7 @@ use bevy::prelude::*; use bevy_egui::*; -use crate::AppContext; +use crate::editor_prelude::AppContext; pub trait UiProvider { fn draw_ui(&mut self, app: &mut AppContext, ui: &mut egui::Ui, index: u64); diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..cbfdb60 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,76 @@ +use bevy::prelude::*; +use bevy_hanabi::prelude::*; +use serde::{Deserialize, Serialize}; + +pub mod controller; +pub mod effect; +pub mod expr; +pub mod helpers; +pub mod modifiers; + +use effect::EffectEditor; +use std::io::{self, Read}; + +#[derive(Resource, Serialize, Deserialize, Default)] +pub struct OmagariProject { + pub effects: Vec, +} + +impl OmagariProject { + pub fn load>(p: P) -> Result { + let mut file = std::fs::File::open(p)?; + let mut ron_string = String::new(); + file.read_to_string(&mut ron_string)?; + let graph: OmagariProject = ron::de::from_str(&ron_string) + .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?; + Ok(graph) + } +} + +impl From for OmagariBundle { + fn from(project: OmagariProject) -> Self { + Self { + effects: project + .effects + .iter() + .map(|e| OmagariEffect { + effect: e.produce(), + }) + .collect(), + } + } +} + +pub struct OmagariEffect { + // texture_asset: String, + pub effect: EffectAsset, + // For setting parenting for hanabi + // is_child: bool, +} + +pub struct OmagariBundle { + pub effects: Vec, +} + +pub mod prelude { + pub use super::OmagariProject; +} + +pub mod editor_prelude { + pub use super::controller::*; + pub use super::effect::*; + pub use super::expr::*; + pub use super::helpers::*; + pub use super::modifiers::*; + + pub use super::OmagariProject; + + use super::expr::ExprWriterEditor; + + #[derive(Default)] + pub struct AppContext { + pub expr_clipboard: Option, + pub visible_effects: Vec, + pub filename: Option, + } +} diff --git a/src/main.rs b/src/main.rs index 021a6f1..1f9c5a4 100644 --- a/src/main.rs +++ b/src/main.rs @@ -8,25 +8,16 @@ use bevy::{ use bevy_panorbit_camera::{PanOrbitCamera, PanOrbitCameraPlugin}; use bevy_egui::{ - egui::{self, scroll_area::ScrollBarVisibility, Layout}, EguiContext, EguiContexts, EguiGlobalSettings, EguiPlugin, EguiPrimaryContextPass, PrimaryEguiContext, + egui::{self, Layout, scroll_area::ScrollBarVisibility}, }; use bevy_hanabi::prelude::*; use ron::ser::PrettyConfig; use std::fs::File; use std::io::Write; -mod controller; -mod effect; -mod expr; -mod helpers; -mod modifiers; - -use crate::controller::*; -use crate::effect::*; -use crate::expr::*; -use crate::helpers::*; +use omagari::editor_prelude::*; fn main() { App::new() @@ -78,13 +69,6 @@ fn setup( }); } -#[derive(Default)] -struct AppContext { - expr_clipboard: Option, - visible_effects: Vec, - filename: Option, -} - fn app_ui( mut commands: Commands, mut contexts: EguiContexts, @@ -128,10 +112,6 @@ fn app_ui( ); } - if ui.button("🖭 EXPORT").clicked() { - export_effects_to_files(&filename, project.clone()); - } - ui.add_space(10.0); ui.separator(); ui.add_space(10.0); @@ -158,7 +138,7 @@ fn app_ui( ui.menu_button("⮉ LOAD", |ui| { for f in projects_list() { if ui.button(f.clone()).clicked() { - if let Ok(project) = load_project(&f) { + if let Ok(project) = OmagariProject::load(&f) { commands.insert_resource(project); res.context.filename = Some(f.clone()); ui.close_menu(); diff --git a/src/modifiers.rs b/src/modifiers.rs index 8002575..f9216d8 100644 --- a/src/modifiers.rs +++ b/src/modifiers.rs @@ -4,9 +4,9 @@ use bevy_hanabi::prelude::*; use serde::Deserialize; use serde::Serialize; +use crate::editor_prelude::AppContext; use crate::expr::*; use crate::helpers::*; -use crate::AppContext; pub trait ModifierProducer where From b849e59fc726b55ef7cd888bd7d5eea163b36e18 Mon Sep 17 00:00:00 2001 From: Zachry Thayer Date: Sat, 9 Aug 2025 11:52:58 -0700 Subject: [PATCH 2/4] Add texture path --- src/lib.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index cbfdb60..1c3f8b5 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -34,6 +34,7 @@ impl From for OmagariBundle { .effects .iter() .map(|e| OmagariEffect { + texture_asset: "assets/effects/cloud2.png".to_string(), effect: e.produce(), }) .collect(), @@ -42,7 +43,7 @@ impl From for OmagariBundle { } pub struct OmagariEffect { - // texture_asset: String, + pub texture_asset: String, pub effect: EffectAsset, // For setting parenting for hanabi // is_child: bool, From 5288760d97e8582b8a8a06f64707d51dad461062 Mon Sep 17 00:00:00 2001 From: Zachry Thayer Date: Sat, 9 Aug 2025 11:59:55 -0700 Subject: [PATCH 3/4] fix texture path --- src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index 1c3f8b5..7b0b3a3 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -34,7 +34,7 @@ impl From for OmagariBundle { .effects .iter() .map(|e| OmagariEffect { - texture_asset: "assets/effects/cloud2.png".to_string(), + texture_asset: "effects/cloud2.png".to_string(), effect: e.produce(), }) .collect(), From f99040bca2e7c09ca1c27563d04d0d5151e7c76e Mon Sep 17 00:00:00 2001 From: Zachry Thayer Date: Sat, 9 Aug 2025 12:32:24 -0700 Subject: [PATCH 4/4] Add file picker for opening project file --- Cargo.toml | 1 + src/controller.rs | 20 ++------------------ src/lib.rs | 4 +++- src/main.rs | 27 +++++++++++++++------------ 4 files changed, 21 insertions(+), 31 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 6784941..785f875 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,3 +12,4 @@ rand = "0.9.1" regex = "1.11.1" ron = "0.8" serde = "1.0.219" +rfd = "0.15.4" diff --git a/src/controller.rs b/src/controller.rs index dc66a58..d8e5cfa 100644 --- a/src/controller.rs +++ b/src/controller.rs @@ -54,22 +54,6 @@ pub fn spawn_particle_effects( } } -pub fn projects_list() -> Vec { - let mut files = Vec::new(); - let entries = std::fs::read_dir(".").unwrap(); - for entry in entries { - let entry = entry.unwrap(); - let filename = entry.file_name(); - if filename.to_string_lossy().ends_with(".omagari.ron") { - files.push(filename.to_string_lossy().into_owned()); - } - } - files -} - -pub fn validate_project_filename(filename: &str) -> bool { - regex::Regex::new(r".*\.omagari\.ron") - .unwrap() - .captures(&filename) - .is_some() +pub fn validate_project_filename>(p: P) -> bool { + p.as_ref().ends_with("omagari.ron") } diff --git a/src/lib.rs b/src/lib.rs index 7b0b3a3..e310f44 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -58,6 +58,8 @@ pub mod prelude { } pub mod editor_prelude { + use std::path::PathBuf; + pub use super::controller::*; pub use super::effect::*; pub use super::expr::*; @@ -72,6 +74,6 @@ pub mod editor_prelude { pub struct AppContext { pub expr_clipboard: Option, pub visible_effects: Vec, - pub filename: Option, + pub filename: Option, } } diff --git a/src/main.rs b/src/main.rs index 1f9c5a4..c15b2fb 100644 --- a/src/main.rs +++ b/src/main.rs @@ -92,9 +92,9 @@ fn app_ui( let mut filename = res .context .filename - .as_ref() - .unwrap_or(&"".to_string()) - .clone(); + .clone() + .map(|x| x.display().to_string()) + .unwrap_or(String::new()); egui::TopBottomPanel::top("Toolbar") .resizable(false) @@ -128,7 +128,7 @@ fn app_ui( } filename_textedit.show(ui); - res.context.filename = Some(filename.clone()); + res.context.filename = Some(std::path::PathBuf::from(&filename)); if ui.button("🌌 NEW").clicked() { res.context.filename = None; @@ -136,13 +136,16 @@ fn app_ui( } ui.menu_button("⮉ LOAD", |ui| { - for f in projects_list() { - if ui.button(f.clone()).clicked() { - if let Ok(project) = OmagariProject::load(&f) { - commands.insert_resource(project); - res.context.filename = Some(f.clone()); - ui.close_menu(); - } + let files = rfd::FileDialog::new() + .add_filter("omagari", &["omagari.ron"]) + .set_directory(".") + .pick_file(); + + if let Some(path) = files { + if let Ok(project) = OmagariProject::load(&path) { + commands.insert_resource(project); + res.context.filename = Some(path.clone()); + ui.close_menu(); } } }); @@ -156,7 +159,7 @@ fn app_ui( ) .unwrap(); - if let Ok(mut file) = File::create(filename) { + if let Ok(mut file) = File::create(&filename) { file.write_all(ron_string.as_bytes()).unwrap(); } }