use std::path::PathBuf; use iced::theme::{Base, Mode, Theme}; use iced::{Task, window}; use rust_format::{Formatter, PostProcess, PrettyPlease}; use serde::{Deserialize, Serialize}; use smol::fs; 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, mode: Mode) -> Theme { self.theme .as_ref() .and_then(|theme| iced_theme_from_str(theme)) .unwrap_or_else(|| Theme::default(mode)) } pub async fn from_path(path: PathBuf) -> Result<(PathBuf, Self), Error> { let contents = fs::read_to_string(&path).await?; let project: Self = serde_json::from_str(&contents)?; Ok((path, project)) } pub fn from_file() -> Task> { window::latest() .and_then(|id| { window::run(id, |window| { rfd::AsyncFileDialog::new() .set_parent(window) .set_title("Open a JSON file...") .add_filter("*.json, *.JSON", &["json", "JSON"]) .pick_file() }) }) .then(Task::future) .map(|picked_file| picked_file.ok_or(Error::DialogClosed)) .and_then(|picked_file| { Task::future(Self::from_path(picked_file.path().to_owned())) }) } pub fn write_to_file( &self, path: Option, ) -> Task> { let path = if let Some(p) = path { Task::done(Ok(p)) } else { window::latest() .and_then(|id| { window::run(id, |window| { rfd::AsyncFileDialog::new() .set_parent(window) .set_title("Save to JSON file...") .add_filter("*.json, *.JSON", &["json", "JSON"]) .save_file() }) }) .then(Task::future) .map(|picked_file| picked_file.ok_or(Error::DialogClosed)) .and_then(|picked_file| { Task::done(Ok(picked_file.path().to_owned())) }) }; let contents = serde_json::to_string(self).map_err(Error::from); path.and_then(move |path| { Task::done(contents.clone().map(|contents| { let path = path.clone(); async move { fs::write(path.clone(), contents) .await .map(|_| path) .map_err(Error::from) } })) }) .and_then(Task::future) .and_then(move |path| Task::done(Ok(path))) } pub fn app_code(&mut self, theme_mode: Mode) -> 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(theme_mode); let app_code = format!( r#"// Automatically generated by iced Builder use iced::{{widget::{{{imports}}},Element}}; _blank_!(); fn main() -> iced::Result {{ iced::application(State::default, State::update, State::view).title("{title}").theme(State::theme).run() }} _blank_!(); #[derive(Default)] struct State; _blank_!(); #[derive(Debug, Clone)] enum Message {{}} _blank_!(); impl State {{ fn update(&mut self, _message: Message) {{ // Insert your desired update logic here }} _blank_!(); fn theme(&self) -> iced::Theme {{ iced::Theme::{theme} }} _blank_!(); fn view(&self) -> Element {{ {view}.into() }} }}"#, title = match self.title { Some(ref t) => t, None => "New app", }, theme = theme.to_string().replace(" ", "") ); let config = rust_format::Config::new_str() .post_proc(PostProcess::ReplaceMarkers); Ok(PrettyPlease::from_config(config).format_str(&app_code)?) } None => Err("No element tree present".into()), }; codegen.finish(); result } }