use std::path::{Path, PathBuf}; extern crate fxhash; use iced::Theme; use rust_format::{Edition, Formatter, RustFmt}; use serde::{Deserialize, Serialize}; use super::rendered_element::RenderedElement; use crate::Error; use crate::theme::{theme_from_str, theme_index}; #[derive(Debug, Clone, Serialize, Deserialize)] pub struct Project { pub title: Option, pub theme: Option, pub element_tree: Option, } impl Default for Project { fn default() -> Self { Self::new() } } impl Project { pub fn new() -> Self { Self { title: None, theme: None, element_tree: None, } } pub fn get_theme(&self) -> Theme { match &self.theme { Some(theme) => theme_from_str(None, theme), None => Theme::default(), } } pub async fn from_path(path: PathBuf) -> Result<(PathBuf, Self), Error> { let contents = tokio::fs::read_to_string(&path).await?; let project: Self = serde_json::from_str(&contents)?; Ok((path, project)) } pub async fn from_file() -> Result<(PathBuf, Self), Error> { let picked_file = rfd::AsyncFileDialog::new() .set_title("Open a JSON file...") .add_filter("*.json, *.JSON", &["json", "JSON"]) .pick_file() .await .ok_or(Error::DialogClosed)?; let path = picked_file.path().to_owned(); Self::from_path(path).await } pub async fn write_to_file( self, path: Option, ) -> Result { let path = if let Some(p) = path { p } else { rfd::AsyncFileDialog::new() .set_title("Save to JSON file...") .add_filter("*.json, *.JSON", &["json", "JSON"]) .save_file() .await .as_ref() .map(rfd::FileHandle::path) .map(Path::to_owned) .ok_or(Error::DialogClosed)? }; let contents = serde_json::to_string(&self)?; tokio::fs::write(&path, contents).await?; Ok(path) } pub fn app_code(&mut self) -> Result { use iced::debug; let codegen = debug::time("Code Generation"); let result = match self.element_tree { Some(ref element_tree) => { let (imports, view) = element_tree.codegen(); let theme = self.get_theme(); let theme_code = theme.to_string().replace(" ", ""); let mut theme_imports = ""; if theme_index(&theme.to_string(), Theme::ALL).is_none() { if theme_code.contains("Extended") { theme_imports = "use iced::{{color,theme::{{Palette,palette::{{Extended,Background,Primary,Secondary,Success,Danger,Warning,Pair}}}}}};\n"; } else { theme_imports = "use iced::{{color,theme::Palette}};\n"; } } let app_code = format!( r#"// Automatically generated by iced Builder use iced::{{widget::{{{imports}}},Element}}; {theme_imports} fn main() -> iced::Result {{ iced::application(State::default, State::update, State::view).title("{}").theme(State::theme).run() }} #[derive(Default)] struct State; #[derive(Debug, Clone)] enum Message {{}} impl State {{ fn update(&mut self, _message: Message) {{}} fn theme(&self) -> iced::Theme {{ iced::Theme::{} }} fn view(&self) -> Element {{ {view}.into() }} }}"#, match self.title { Some(ref t) => t, None => "New app", }, theme_code ); let config = rust_format::Config::new_str() .edition(Edition::Rust2021) .option("trailing_comma", "Never") .option("imports_granularity", "Crate"); let rustfmt = RustFmt::from_config(config); Ok(rustfmt.format_str(app_code)?) } None => Err("No element tree present".into()), }; codegen.finish(); result } }