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::appearance::iced_theme_from_str; #[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) => iced_theme_from_str(theme), None => iced::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 { use tokio::fs; let path = if let Some(p) = path { let parent = p.parent(); if parent.is_some_and(|parent| !parent.exists()) { fs::create_dir_all(parent.unwrap()).await?; } 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)?; 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 app_code = format!( r#"// Automatically generated by iced Builder use iced::{{widget::{{{imports}}},Element}}; 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.to_string().replace(" ", "") ); 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 } }