diff options
Diffstat (limited to '')
| -rw-r--r-- | assets/windows/iced_builder.manifest (renamed from iced_builder/assets/windows/iced_builder.manifest) | 0 | ||||
| -rw-r--r-- | assets/windows/iced_builder.rc (renamed from iced_builder/assets/windows/iced_builder.rc) | 0 | ||||
| -rw-r--r-- | build.rs (renamed from iced_builder/build.rs) | 0 | ||||
| -rw-r--r-- | fonts/icons.toml (renamed from iced_builder/fonts/icons.toml) | 0 | ||||
| -rw-r--r-- | fonts/icons.ttf (renamed from iced_builder/fonts/icons.ttf) | bin | 6348 -> 6348 bytes | |||
| -rw-r--r-- | iced_builder/Cargo.toml | 57 | ||||
| -rw-r--r-- | iced_builder/src/lib.rs | 9 | ||||
| -rw-r--r-- | rustfmt.toml (renamed from iced_builder/rustfmt.toml) | 0 | ||||
| -rw-r--r-- | src/dialogs.rs (renamed from iced_builder/src/dialogs.rs) | 19 | ||||
| -rw-r--r-- | src/error.rs (renamed from iced_builder/src/error.rs) | 27 | ||||
| -rw-r--r-- | src/icon.rs (renamed from iced_builder/src/icon.rs) | 0 | ||||
| -rw-r--r-- | src/main.rs (renamed from iced_builder/src/main.rs) | 124 | ||||
| -rw-r--r-- | src/panes.rs (renamed from iced_builder/src/panes.rs) | 0 | ||||
| -rw-r--r-- | src/panes/code_view.rs (renamed from iced_builder/src/panes/code_view.rs) | 9 | ||||
| -rw-r--r-- | src/panes/designer_view.rs (renamed from iced_builder/src/panes/designer_view.rs) | 0 | ||||
| -rw-r--r-- | src/panes/element_list.rs (renamed from iced_builder/src/panes/element_list.rs) | 8 | ||||
| -rw-r--r-- | src/panes/style.rs (renamed from iced_builder/src/panes/style.rs) | 0 | ||||
| -rw-r--r-- | src/types.rs (renamed from iced_builder/src/types.rs) | 10 | ||||
| -rw-r--r-- | src/types/element_name.rs (renamed from iced_builder/src/types/element_name.rs) | 19 | ||||
| -rw-r--r-- | src/types/project.rs (renamed from iced_builder/src/types/project.rs) | 109 | ||||
| -rwxr-xr-x | src/types/rendered_element.rs (renamed from iced_builder/src/types/rendered_element.rs) | 144 | ||||
| -rw-r--r-- | src/widget.rs (renamed from iced_builder/src/widget.rs) | 0 |
22 files changed, 267 insertions, 268 deletions
diff --git a/iced_builder/assets/windows/iced_builder.manifest b/assets/windows/iced_builder.manifest index 82039bf..82039bf 100644 --- a/iced_builder/assets/windows/iced_builder.manifest +++ b/assets/windows/iced_builder.manifest diff --git a/iced_builder/assets/windows/iced_builder.rc b/assets/windows/iced_builder.rc index 7255b65..7255b65 100644 --- a/iced_builder/assets/windows/iced_builder.rc +++ b/assets/windows/iced_builder.rc diff --git a/iced_builder/build.rs b/build.rs index 438ce37..438ce37 100644 --- a/iced_builder/build.rs +++ b/build.rs diff --git a/iced_builder/fonts/icons.toml b/fonts/icons.toml index a70c0e7..a70c0e7 100644 --- a/iced_builder/fonts/icons.toml +++ b/fonts/icons.toml diff --git a/iced_builder/fonts/icons.ttf b/fonts/icons.ttf Binary files differindex 7af6b0e..7af6b0e 100644 --- a/iced_builder/fonts/icons.ttf +++ b/fonts/icons.ttf diff --git a/iced_builder/Cargo.toml b/iced_builder/Cargo.toml deleted file mode 100644 index a1b41cc..0000000 --- a/iced_builder/Cargo.toml +++ /dev/null @@ -1,57 +0,0 @@ -[package] -name = "iced_builder" -description = "GUI builder for iced, built with iced." -version = "0.1.0" -edition = "2021" -authors = ["pml68 <contact@pml68.dev>"] -repository = "https://github.com/pml68/iced-builder" -license = "GPL-3.0-or-later" -keywords = ["gui", "iced"] - -[dependencies] -iced = { version = "0.13.1", features = [ "image","svg","canvas","qr_code","advanced","tokio","highlighter"] } -# iced_aw = { version = "0.11.0", default-features = false, features = ["menu","color_picker"] } -iced_anim = { version = "0.1.4", features = ["derive", "serde"] } -iced_drop = { path = "../iced_drop" } -serde = { version = "1.0.216", features = ["derive"] } -serde_json = "1.0.133" -tokio = { version = "1.42.0", features = ["fs"] } -rfd = { version = "0.15.1", default-features = false, features = ["async-std", "gtk3"] } -rust-format = "0.3.4" -blob-uuid = "0.5.0" -thiserror = "2.0.6" - -[build-dependencies] -iced_fontello = "0.13.1" - -[target.'cfg(windows)'.build-dependencies] -embed-resource = "3.0.1" -windows_exe_info = "0.4" - -[[bin]] -name = "iced-builder" -path = "src/main.rs" - -[lints.rust] -missing_debug_implementations = "deny" -# missing_docs = "deny" -unsafe_code = "deny" -unused_results = "deny" - -[lints.clippy] -type-complexity = "allow" -semicolon_if_nothing_returned = "deny" -trivially-copy-pass-by-ref = "deny" -default_trait_access = "deny" -match-wildcard-for-single-variants = "deny" -redundant-closure-for-method-calls = "deny" -filter_map_next = "deny" -manual_let_else = "deny" -unused_async = "deny" -from_over_into = "deny" -needless_borrow = "deny" -new_without_default = "deny" -useless_conversion = "deny" - -[lints.rustdoc] -broken_intra_doc_links = "forbid" diff --git a/iced_builder/src/lib.rs b/iced_builder/src/lib.rs deleted file mode 100644 index f3165f5..0000000 --- a/iced_builder/src/lib.rs +++ /dev/null @@ -1,9 +0,0 @@ -pub mod dialogs; -pub mod error; -pub mod icon; -pub mod panes; -pub mod types; -pub mod widget; - -pub use error::Error; -pub type Result<T> = core::result::Result<T, Error>; diff --git a/iced_builder/rustfmt.toml b/rustfmt.toml index 197262a..197262a 100644 --- a/iced_builder/rustfmt.toml +++ b/rustfmt.toml diff --git a/iced_builder/src/dialogs.rs b/src/dialogs.rs index 047ffd2..2d916b1 100644 --- a/iced_builder/src/dialogs.rs +++ b/src/dialogs.rs @@ -9,13 +9,22 @@ pub fn error_dialog(description: impl Into<String>) { .show(); } -pub fn unsaved_changes_dialog( - description: impl Into<String>, -) -> MessageDialogResult { - MessageDialog::new() +pub fn warning_dialog(description: impl Into<String>) { + let _ = MessageDialog::new() + .set_level(MessageLevel::Warning) + .set_buttons(MessageButtons::Ok) + .set_title("Heads up!") + .set_description(description) + .show(); +} + +pub fn unsaved_changes_dialog(description: impl Into<String>) -> bool { + let result = MessageDialog::new() .set_level(MessageLevel::Warning) .set_buttons(MessageButtons::OkCancel) .set_title("Unsaved changes") .set_description(description) - .show() + .show(); + + matches!(result, MessageDialogResult::Ok) } diff --git a/iced_builder/src/error.rs b/src/error.rs index 8876016..f4011bd 100644 --- a/iced_builder/src/error.rs +++ b/src/error.rs @@ -6,13 +6,18 @@ use thiserror::Error; #[derive(Debug, Clone, Error)] #[error(transparent)] pub enum Error { - IOError(Arc<io::Error>), - SerdeError(Arc<serde_json::Error>), - FormatError(Arc<rust_format::Error>), - #[error("The element tree contains no matching element")] + IO(Arc<io::Error>), + #[error("config does not exist")] + ConfigMissing, + #[error("JSON parsing error: {0}")] + SerdeJSON(Arc<serde_json::Error>), + #[error("TOML parsing error: {0}")] + SerdeTOML(#[from] toml::de::Error), + RustFmt(Arc<rust_format::Error>), + #[error("the element tree contains no matching element")] NonExistentElement, #[error( - "The file dialog has been closed without selecting a valid option" + "the file dialog has been closed without selecting a valid option" )] DialogClosed, #[error("{0}")] @@ -21,19 +26,19 @@ pub enum Error { impl From<io::Error> for Error { fn from(value: io::Error) -> Self { - Self::IOError(Arc::new(value)) + Self::IO(Arc::new(value)) } } impl From<serde_json::Error> for Error { fn from(value: serde_json::Error) -> Self { - Self::SerdeError(Arc::new(value)) + Self::SerdeJSON(Arc::new(value)) } } impl From<rust_format::Error> for Error { fn from(value: rust_format::Error) -> Self { - Self::FormatError(Arc::new(value)) + Self::RustFmt(Arc::new(value)) } } @@ -42,3 +47,9 @@ impl From<&str> for Error { Self::Other(value.to_owned()) } } + +impl From<String> for Error { + fn from(value: String) -> Self { + Self::Other(value) + } +} diff --git a/iced_builder/src/icon.rs b/src/icon.rs index f6760d5..f6760d5 100644 --- a/iced_builder/src/icon.rs +++ b/src/icon.rs diff --git a/iced_builder/src/main.rs b/src/main.rs index a041c6f..5b95b94 100644 --- a/iced_builder/src/main.rs +++ b/src/main.rs @@ -1,26 +1,47 @@ +#![feature(test)] +mod config; +mod dialogs; +mod environment; +mod error; +mod icon; +mod panes; +mod theme; +mod types; +mod widget; + use std::path::PathBuf; +use config::Config; +use dialogs::{error_dialog, unsaved_changes_dialog, warning_dialog}; +use error::Error; use iced::advanced::widget::Id; use iced::widget::pane_grid::{self, Pane, PaneGrid}; use iced::widget::{container, pick_list, row, text_editor, Column}; use iced::{clipboard, keyboard, Alignment, Element, Length, Task, Theme}; -use iced_anim::{Animation, Spring}; -use iced_builder::dialogs::{error_dialog, unsaved_changes_dialog}; -use iced_builder::icon; -use iced_builder::panes::{code_view, designer_view, element_list}; -use iced_builder::types::{ - Action, DesignerPage, ElementName, Message, Project, -}; -use rfd::MessageDialogResult; +use iced_anim::transition::Easing; +use iced_anim::{Animated, Animation}; +use panes::{code_view, designer_view, element_list}; +use tokio::runtime; +use types::{Action, DesignerPage, ElementName, Message, Project}; + +//pub(crate) type Result<T> = core::result::Result<T, Error>; + +fn main() -> Result<(), Box<dyn std::error::Error>> { + let config_load = { + let rt = runtime::Builder::new_current_thread() + .enable_all() + .build()?; -const THEMES: &'static [Theme] = &[Theme::SolarizedDark, Theme::SolarizedLight]; + rt.block_on(Config::load()) + }; -fn main() -> iced::Result { iced::application(App::title, App::update, App::view) .font(icon::FONT) .theme(|state| state.theme.value().clone()) .subscription(App::subscription) - .run_with(App::new) + .run_with(move || App::new(config_load))?; + + Ok(()) } struct App { @@ -28,7 +49,8 @@ struct App { is_loading: bool, project_path: Option<PathBuf>, project: Project, - theme: Spring<Theme>, + config: Config, + theme: Animated<Theme>, pane_state: pane_grid::State<Panes>, focus: Option<Pane>, designer_page: DesignerPage, @@ -43,7 +65,7 @@ enum Panes { } impl App { - fn new() -> (Self, Task<Message>) { + fn new(config_load: Result<Config, Error>) -> (Self, Task<Message>) { let state = pane_grid::State::with_configuration( pane_grid::Configuration::Split { axis: pane_grid::Axis::Vertical, @@ -52,25 +74,46 @@ impl App { b: Box::new(pane_grid::Configuration::Pane(Panes::ElementList)), }, ); + + let config = config_load.unwrap_or_default(); + let theme = config.selected_theme(); + + let mut task = Task::none(); + + if let Some(path) = config.last_project.clone() { + if path.exists() && path.is_file() { + task = Task::perform( + Project::from_path(path, config.clone()), + Message::FileOpened, + ); + } else { + warning_dialog(format!( + "The file {} does not exist, or isn't a file.", + path.to_string_lossy() + )); + } + } + ( Self { is_dirty: false, is_loading: false, project_path: None, project: Project::new(), - theme: Spring::new(Theme::SolarizedDark), + config, + theme: Animated::new(theme, Easing::EASE_IN), pane_state: state, focus: None, designer_page: DesignerPage::DesignerView, element_list: ElementName::ALL, editor_content: text_editor::Content::new(), }, - Task::none(), + task, ) } fn title(&self) -> String { - let saved_state = if !self.is_dirty { "" } else { " *" }; + let saved_state = if self.is_dirty { " *" } else { "" }; let project_name = match &self.project.title { Some(n) => { @@ -83,7 +126,7 @@ impl App { } ) } - None => "".to_owned(), + None => String::new(), }; format!("iced Builder{project_name}{saved_state}") @@ -104,7 +147,7 @@ impl App { } } Message::RefreshEditorContent => { - match self.project.clone().app_code() { + match self.project.app_code(&self.config) { Ok(code) => { self.editor_content = text_editor::Content::with_text(&code); @@ -119,23 +162,19 @@ impl App { None, None, ) - .into() } Message::HandleNew(name, zones) => { let ids: Vec<Id> = zones.into_iter().map(|z| z.0).collect(); - if ids.len() > 0 { - let action = Action::new( - ids, - &mut self.project.element_tree.clone(), - None, - ); + if !ids.is_empty() { + let eltree_clone = self.project.element_tree.clone(); + let action = Action::new(&ids, &eltree_clone, None); let result = name.handle_action( self.project.element_tree.as_mut(), action, ); match result { Ok(Some(ref element)) => { - self.project.element_tree = Some(element.clone()) + self.project.element_tree = Some(element.clone()); } Err(error) => error_dialog(error.to_string()), _ => {} @@ -152,14 +191,14 @@ impl App { None, None, ) - .into() } Message::HandleMove(element, zones) => { let ids: Vec<Id> = zones.into_iter().map(|z| z.0).collect(); - if ids.len() > 0 { + if !ids.is_empty() { + let eltree_clone = self.project.element_tree.clone(); let action = Action::new( - ids, - &mut self.project.element_tree.clone(), + &ids, + &eltree_clone, Some(element.get_id()), ); let result = element.handle_action( @@ -193,13 +232,11 @@ impl App { self.project = Project::new(); self.project_path = None; self.editor_content = text_editor::Content::new(); - } else { - if let MessageDialogResult::Ok = unsaved_changes_dialog("You have unsaved changes. Do you wish to discard these and create a new project?") { + } else if unsaved_changes_dialog("You have unsaved changes. Do you wish to discard these and create a new project?") { self.is_dirty = false; self.project = Project::new(); self.project_path = None; self.editor_content = text_editor::Content::new(); - } } } } @@ -209,15 +246,13 @@ impl App { self.is_loading = true; return Task::perform( - Project::from_path(), + Project::from_file(self.config.clone()), Message::FileOpened, ); - } else { - if let MessageDialogResult::Ok = unsaved_changes_dialog("You have unsaved changes. Do you wish to discard these and open another project?") { + } else if unsaved_changes_dialog("You have unsaved changes. Do you wish to discard these and open another project?") { self.is_dirty = false; self.is_loading = true; - return Task::perform(Project::from_path(), Message::FileOpened); - } + return Task::perform(Project::from_file(self.config.clone()), Message::FileOpened); } } } @@ -227,11 +262,12 @@ impl App { match result { Ok((path, project)) => { - self.project = project.clone(); + self.project = project; self.project_path = Some(path); self.editor_content = text_editor::Content::with_text( - &project - .app_code() + &self + .project + .app_code(&self.config) .unwrap_or_else(|err| err.to_string()), ); } @@ -299,7 +335,7 @@ impl App { fn view(&self) -> Element<'_, Message> { let header = row![pick_list( - THEMES, + self.config.theme.all.clone(), Some(self.theme.target().clone()), |theme| { Message::ToggleTheme(theme.into()) } )] @@ -311,12 +347,12 @@ impl App { Panes::Designer => match &self.designer_page { DesignerPage::DesignerView => designer_view::view( &self.project.element_tree, - self.project.get_theme(), + self.project.get_theme(&self.config), is_focused, ), DesignerPage::CodeView => code_view::view( &self.editor_content, - self.theme.value().clone(), + self.theme.target().clone(), is_focused, ), }, diff --git a/iced_builder/src/panes.rs b/src/panes.rs index 387662a..387662a 100644 --- a/iced_builder/src/panes.rs +++ b/src/panes.rs diff --git a/iced_builder/src/panes/code_view.rs b/src/panes/code_view.rs index fe7801c..f545157 100644 --- a/iced_builder/src/panes/code_view.rs +++ b/src/panes/code_view.rs @@ -1,15 +1,15 @@ use iced::widget::{button, pane_grid, row, text, text_editor, Space}; -use iced::{Alignment, Length, Theme}; +use iced::{Alignment, Font, Length, Theme}; use super::style; use crate::icon::copy; use crate::types::{DesignerPage, Message}; use crate::widget::tip; -pub fn view<'a>( - editor_content: &'a text_editor::Content, +pub fn view( + editor_content: &text_editor::Content, theme: Theme, is_focused: bool, -) -> pane_grid::Content<'a, Message> { +) -> pane_grid::Content<'_, Message> { let title = row![ text("Generated Code"), Space::with_width(Length::Fill), @@ -36,6 +36,7 @@ pub fn view<'a>( } else { highlighter::Theme::InspiredGitHub }, + .font(Font::MONOSPACE) ) .height(Length::Fill) .padding(20), diff --git a/iced_builder/src/panes/designer_view.rs b/src/panes/designer_view.rs index 76456db..76456db 100644 --- a/iced_builder/src/panes/designer_view.rs +++ b/src/panes/designer_view.rs diff --git a/iced_builder/src/panes/element_list.rs b/src/panes/element_list.rs index 74188af..8a1c6eb 100644 --- a/iced_builder/src/panes/element_list.rs +++ b/src/panes/element_list.rs @@ -5,7 +5,7 @@ use iced_drop::droppable; use super::style; use crate::types::{ElementName, Message}; -fn items_list_view<'a>(items: &'a [ElementName]) -> Element<'a, Message> { +fn items_list_view(items: &[ElementName]) -> Element<'_, Message> { let mut column = Column::new() .spacing(20) .align_x(Alignment::Center) @@ -26,10 +26,10 @@ fn items_list_view<'a>(items: &'a [ElementName]) -> Element<'a, Message> { .into() } -pub fn view<'a>( - element_list: &'a [ElementName], +pub fn view( + element_list: &[ElementName], is_focused: bool, -) -> pane_grid::Content<'a, Message> { +) -> pane_grid::Content<'_, Message> { let items_list = items_list_view(element_list); let content = column![items_list] .align_x(Alignment::Center) diff --git a/iced_builder/src/panes/style.rs b/src/panes/style.rs index 1eefb2d..1eefb2d 100644 --- a/iced_builder/src/panes/style.rs +++ b/src/panes/style.rs diff --git a/iced_builder/src/types.rs b/src/types.rs index 161b5e1..ac9d039 100644 --- a/iced_builder/src/types.rs +++ b/src/types.rs @@ -7,15 +7,15 @@ use std::path::PathBuf; pub use element_name::ElementName; use iced::widget::{pane_grid, text_editor}; use iced::Theme; -use iced_anim::SpringEvent; +use iced_anim::Event; pub use project::Project; pub use rendered_element::*; -use crate::Result; +use crate::Error; #[derive(Debug, Clone)] pub enum Message { - ToggleTheme(SpringEvent<Theme>), + ToggleTheme(Event<Theme>), CopyCode, SwitchPage(DesignerPage), EditorAction(text_editor::Action), @@ -35,10 +35,10 @@ pub enum Message { PaneDragged(pane_grid::DragEvent), NewFile, OpenFile, - FileOpened(Result<(PathBuf, Project)>), + FileOpened(Result<(PathBuf, Project), Error>), SaveFile, SaveFileAs, - FileSaved(Result<PathBuf>), + FileSaved(Result<PathBuf, Error>), } #[derive(Debug, Clone)] diff --git a/iced_builder/src/types/element_name.rs b/src/types/element_name.rs index e172227..2687673 100644 --- a/iced_builder/src/types/element_name.rs +++ b/src/types/element_name.rs @@ -3,13 +3,13 @@ use serde::{Deserialize, Serialize}; use super::rendered_element::{ button, column, container, image, row, svg, text, Action, RenderedElement, }; -use crate::{Error, Result}; +use crate::Error; #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub enum ElementName { Text(String), Button(String), - SVG(String), + Svg(String), Image(String), Container, Row, @@ -20,7 +20,7 @@ impl ElementName { pub const ALL: &'static [Self; 7] = &[ Self::Text(String::new()), Self::Button(String::new()), - Self::SVG(String::new()), + Self::Svg(String::new()), Self::Image(String::new()), Self::Container, Self::Row, @@ -31,23 +31,22 @@ impl ElementName { &self, element_tree: Option<&mut RenderedElement>, action: Action, - ) -> Result<Option<RenderedElement>> { + ) -> Result<Option<RenderedElement>, Error> { let element = match self { Self::Text(_) => text(""), Self::Button(_) => button(""), - Self::SVG(_) => svg(""), + Self::Svg(_) => svg(""), Self::Image(_) => image(""), Self::Container => container(None), Self::Row => row(None), Self::Column => column(None), }; match action { - Action::Stop => Ok(None), - Action::Drop => Ok(None), + Action::Stop | Action::Drop => Ok(None), Action::AddNew => Ok(Some(element)), Action::PushFront(id) => { element_tree - .ok_or("The action was of kind `PushFront`, but no element tree was provided.")? + .ok_or("the action was of kind `PushFront`, but no element tree was provided.")? .find_by_id(id) .ok_or(Error::NonExistentElement)? .push_front(&element); @@ -56,7 +55,7 @@ impl ElementName { Action::InsertAfter(parent_id, child_id) => { element_tree .ok_or( - "The action was of kind `InsertAfter`, but no element tree was provided.", + "the action was of kind `InsertAfter`, but no element tree was provided.", )? .find_by_id(parent_id) .ok_or(Error::NonExistentElement)? @@ -75,7 +74,7 @@ impl std::fmt::Display for ElementName { match self { Self::Text(_) => "Text", Self::Button(_) => "Button", - Self::SVG(_) => "SVG", + Self::Svg(_) => "SVG", Self::Image(_) => "Image", Self::Container => "Container", Self::Row => "Row", diff --git a/iced_builder/src/types/project.rs b/src/types/project.rs index f4dbcc4..27c576b 100644 --- a/iced_builder/src/types/project.rs +++ b/src/types/project.rs @@ -1,17 +1,29 @@ use std::path::{Path, PathBuf}; +extern crate fxhash; +use fxhash::FxHashMap; use iced::Theme; -use rust_format::{Config, Edition, Formatter, RustFmt}; +use rust_format::{Edition, Formatter, RustFmt}; use serde::{Deserialize, Serialize}; use super::rendered_element::RenderedElement; -use crate::{Error, Result}; +use crate::config::Config; +use crate::theme::{theme_from_str, theme_index, theme_to_string}; +use crate::Error; #[derive(Debug, Clone, Serialize, Deserialize)] pub struct Project { pub title: Option<String>, pub theme: Option<String>, pub element_tree: Option<RenderedElement>, + #[serde(skip)] + theme_cache: FxHashMap<String, String>, +} + +impl Default for Project { + fn default() -> Self { + Self::new() + } } impl Project { @@ -20,41 +32,43 @@ impl Project { title: None, theme: None, element_tree: None, + theme_cache: FxHashMap::default(), } } - pub fn get_theme(&self) -> Theme { + pub fn get_theme(&self, config: &Config) -> Theme { match &self.theme { - Some(theme) => match theme.as_str() { - "Light" => Theme::Light, - "Dark" => Theme::Dark, - "Dracula" => Theme::Dracula, - "Nord" => Theme::Nord, - "Solarized Light" => Theme::SolarizedLight, - "Solarized Dark" => Theme::SolarizedDark, - "Gruvbox Light" => Theme::GruvboxLight, - "Gruvbox Dark" => Theme::GruvboxDark, - "Catppuccin Latte" => Theme::CatppuccinLatte, - "Catppuccin Frappé" => Theme::CatppuccinFrappe, - "Catppuccin Macchiato" => Theme::CatppuccinMacchiato, - "Catppuccin Mocha" => Theme::CatppuccinMocha, - "Tokyo Night" => Theme::TokyoNight, - "Tokyo Night Storm" => Theme::TokyoNightStorm, - "Tokyo Night Light" => Theme::TokyoNightLight, - "Kanagawa Wave" => Theme::KanagawaWave, - "Kanagawa Dragon" => Theme::KanagawaDragon, - "Kanagawa Lotus" => Theme::KanagawaLotus, - "Moonfly" => Theme::Moonfly, - "Nightfly" => Theme::Nightfly, - "Oxocarbon" => Theme::Oxocarbon, - "Ferra" => Theme::Ferra, - _ => Theme::Dark, - }, - None => Theme::Dark, + Some(theme) => theme_from_str(Some(config), theme), + None => Theme::default(), + } + } + + fn theme_code(&mut self, theme: &Theme) -> String { + let theme_name = theme.to_string(); + if theme_index(&theme_name, Theme::ALL).is_none() { + (*self + .theme_cache + .entry(theme_name) + .or_insert(theme_to_string(theme))) + .to_string() + } else { + theme_name.replace(" ", "") } } - pub async fn from_path() -> Result<(PathBuf, Self)> { + pub async fn from_path( + path: PathBuf, + config: Config, + ) -> Result<(PathBuf, Self), Error> { + let contents = tokio::fs::read_to_string(&path).await?; + let mut project: Self = serde_json::from_str(&contents)?; + + let _ = project.theme_code(&project.get_theme(&config)); + + Ok((path, project)) + } + + pub async fn from_file(config: Config) -> Result<(PathBuf, Self), Error> { let picked_file = rfd::AsyncFileDialog::new() .set_title("Open a JSON file...") .add_filter("*.json, *.JSON", &["json", "JSON"]) @@ -64,13 +78,13 @@ impl Project { let path = picked_file.path().to_owned(); - let contents = tokio::fs::read_to_string(&path).await?; - let element: Self = serde_json::from_str(&contents)?; - - Ok((path, element)) + Self::from_path(path, config).await } - pub async fn write_to_file(self, path: Option<PathBuf>) -> Result<PathBuf> { + pub async fn write_to_file( + self, + path: Option<PathBuf>, + ) -> Result<PathBuf, Error> { let path = if let Some(p) = path { p } else { @@ -91,16 +105,25 @@ impl Project { Ok(path) } - pub fn app_code(&self) -> Result<String> { + pub fn app_code(&mut self, config: &Config) -> Result<String, Error> { match self.element_tree { Some(ref element_tree) => { let (imports, view) = element_tree.codegen(); - let mut app_code = - format!("use iced::{{widget::{{{imports}}},Element}};"); - - app_code = format!( + let theme = self.get_theme(config); + let theme_code = self.theme_code(&theme); + 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,Pair}}}}}};\n"; + } else { + theme_imports = "use iced::{{color,theme::Palette}};\n"; + } + } + + let app_code = format!( r#"// Automatically generated by iced Builder - {app_code} + use iced::{{widget::{{{imports}}},Element}}; + {theme_imports} fn main() -> iced::Result {{ iced::application("{}", State::update, State::view).theme(State::theme).run() @@ -127,9 +150,9 @@ impl Project { Some(ref t) => t, None => "New app", }, - self.get_theme().to_string().replace(" ", "") + theme_code ); - let config = Config::new_str() + let config = rust_format::Config::new_str() .edition(Edition::Rust2021) .option("trailing_comma", "Never") .option("imports_granularity", "Crate"); diff --git a/iced_builder/src/types/rendered_element.rs b/src/types/rendered_element.rs index d4d1a6c..b001556 100755 --- a/iced_builder/src/types/rendered_element.rs +++ b/src/types/rendered_element.rs @@ -1,17 +1,18 @@ use std::collections::BTreeMap; -use blob_uuid::random_blob; use iced::advanced::widget::Id; use iced::{widget, Element, Length}; use serde::{Deserialize, Serialize}; +use uuid::Uuid; use super::ElementName; use crate::types::Message; -use crate::Result; +use crate::Error; #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub struct RenderedElement { - id: String, + #[serde(skip, default = "Uuid::new_v4")] + id: Uuid, child_elements: Option<Vec<RenderedElement>>, name: ElementName, options: BTreeMap<String, Option<String>>, @@ -20,7 +21,7 @@ pub struct RenderedElement { impl RenderedElement { fn new(name: ElementName) -> Self { Self { - id: random_blob(), + id: Uuid::new_v4(), child_elements: None, name, options: BTreeMap::new(), @@ -29,7 +30,7 @@ impl RenderedElement { fn with(name: ElementName, child_elements: Vec<RenderedElement>) -> Self { Self { - id: random_blob(), + id: Uuid::new_v4(), child_elements: Some(child_elements), name, options: BTreeMap::new(), @@ -37,22 +38,22 @@ impl RenderedElement { } pub fn get_id(&self) -> Id { - Id::new(self.id.clone()) + Id::new(self.id.to_string()) } - pub fn find_by_id(&mut self, id: Id) -> Option<&mut Self> { - if self.get_id() == id.clone() { - return Some(self); + pub fn find_by_id(&mut self, id: &Id) -> Option<&mut Self> { + if &self.get_id() == id { + Some(self) } else if let Some(child_elements) = self.child_elements.as_mut() { for element in child_elements { - let element = element.find_by_id(id.clone()); + let element = element.find_by_id(id); if element.is_some() { return element; } } - return None; + None } else { - return None; + None } } @@ -66,24 +67,21 @@ impl RenderedElement { if self .child_elements .clone() - .unwrap_or(vec![]) + .unwrap_or_default() .contains(child_element) { return Some(self); - } else { - if let Some(child_elements) = self.child_elements.as_mut() { - for element in child_elements { - let element = element.find_parent(child_element); - if element.is_some() { - return element; - } + } + if let Some(child_elements) = self.child_elements.as_mut() { + for element in child_elements { + let element = element.find_parent(child_element); + if element.is_some() { + return element; } } - return None; } - } else { - return None; } + None } pub fn is_parent(&self) -> bool { @@ -111,10 +109,10 @@ impl RenderedElement { } } - pub fn insert_after(&mut self, id: Id, element: &RenderedElement) { + pub fn insert_after(&mut self, id: &Id, element: &RenderedElement) { if let Some(child_elements) = self.child_elements.as_mut() { if let Some(index) = - child_elements.iter().position(|x| x.get_id() == id) + child_elements.iter().position(|x| &x.get_id() == id) { child_elements.insert(index + 1, element.clone()); } else { @@ -127,7 +125,7 @@ impl RenderedElement { &self, element_tree: Option<&mut RenderedElement>, action: Action, - ) -> Result<()> { + ) -> Result<(), Error> { let element_tree = element_tree.unwrap(); match action { @@ -159,7 +157,7 @@ impl RenderedElement { } } - fn preset_options<'a>(mut self, options: &[&'a str]) -> Self { + fn preset_options(mut self, options: &[&str]) -> Self { for opt in options { let _ = self.options.insert(opt.to_string(), None); } @@ -174,12 +172,12 @@ impl RenderedElement { self } - pub fn as_element<'a>(self) -> Element<'a, Message> { + pub fn into_element<'a>(self) -> Element<'a, Message> { let mut children = widget::column![]; if let Some(els) = self.child_elements.clone() { for el in els { - children = children.push(el.clone().as_element()); + children = children.push(el.clone().into_element()); } } iced_drop::droppable( @@ -219,7 +217,7 @@ impl RenderedElement { for element in els { let (c_imports, children) = element.codegen(); imports = format!("{imports}{c_imports}"); - elements = format!("{elements}{},", children); + elements = format!("{elements}{children},"); } } @@ -262,7 +260,7 @@ impl RenderedElement { imports = format!("{imports}image,"); view = format!("{view}\nimage(\"{path}\"){options}"); } - ElementName::SVG(path) => { + ElementName::Svg(path) => { imports = format!("{imports}svg,"); view = format!("{view}\nsvg(\"{path}\"){options}"); } @@ -336,7 +334,7 @@ impl<'a> From<RenderedElement> for Element<'a, Message> { widget::button(widget::text(s)).into() } } - ElementName::SVG(p) => widget::svg(p).into(), + ElementName::Svg(p) => widget::svg(p).into(), ElementName::Image(p) => widget::image(p).into(), ElementName::Container => { widget::container(if child_elements.len() == 1 { @@ -347,13 +345,13 @@ impl<'a> From<RenderedElement> for Element<'a, Message> { .padding(20) .into() } - ElementName::Row => widget::Row::from_iter( - child_elements.into_iter().map(|el| el.into()), + ElementName::Row => widget::Row::from_vec( + child_elements.into_iter().map(Into::into).collect(), ) .padding(20) .into(), - ElementName::Column => widget::Column::from_iter( - child_elements.into_iter().map(|el| el.into()), + ElementName::Column => widget::Column::from_vec( + child_elements.into_iter().map(Into::into).collect(), ) .padding(20) .into(), @@ -369,18 +367,18 @@ impl<'a> From<RenderedElement> for Element<'a, Message> { } #[derive(Debug, Clone)] -pub enum Action { +pub enum Action<'a> { AddNew, - PushFront(Id), - InsertAfter(Id, Id), + PushFront(&'a Id), + InsertAfter(&'a Id, &'a Id), Drop, Stop, } -impl Action { +impl<'a> Action<'a> { pub fn new( - ids: Vec<Id>, - element_tree: &mut Option<RenderedElement>, + ids: &'a [Id], + element_tree: &'a Option<RenderedElement>, source_id: Option<Id>, ) -> Self { let mut action = Self::Stop; @@ -391,51 +389,39 @@ impl Action { action = Self::Drop; } } else { - let id: Id = match source_id { + let id: &Id = match source_id { Some(id) if ids.contains(&id) => { let element_id = - ids[ids.iter().position(|x| *x == id).unwrap()].clone(); - if ids.len() > 2 && ids[ids.clone().len() - 1] == element_id - { + &ids[ids.iter().position(|x| *x == id).unwrap()]; + if ids.len() > 2 && &ids[ids.len() - 1] == element_id { return Self::Stop; } element_id } - _ => ids.last().cloned().unwrap(), + _ => ids.last().unwrap(), }; - let element = element_tree - .as_mut() - .unwrap() - .find_by_id(id.clone()) - .unwrap(); - - // Element IS a parent but ISN'T a non-empty container - match element.is_parent() - && !(element.name == ElementName::Container - && !element.is_empty()) + let mut element_tree = element_tree.clone().unwrap(); + let element = element_tree.find_by_id(id).unwrap(); + + // Element is a parent and isn't a non-empty container + if (element.is_empty() || !(element.name == ElementName::Container)) + && element.is_parent() { - true => { - action = Self::PushFront(id); - } - false if ids.len() > 2 => { - let parent = element_tree - .as_mut() - .unwrap() - .find_by_id(ids[&ids.len() - 2].clone()) - .unwrap(); - - if parent.name == ElementName::Container - && parent.child_elements != Some(vec![]) - { - action = Self::Stop; - } else { - action = Self::InsertAfter( - ids[&ids.len() - 2].clone(), - ids[&ids.len() - 1].clone(), - ); - } + action = Self::PushFront(id); + } else if ids.len() > 2 { + let parent = + element_tree.find_by_id(&ids[ids.len() - 2]).unwrap(); + + if parent.name == ElementName::Container + && parent.child_elements != Some(vec![]) + { + action = Self::Stop; + } else { + action = Self::InsertAfter( + &ids[ids.len() - 2], + &ids[ids.len() - 1], + ); } - _ => {} } } action @@ -456,7 +442,7 @@ pub fn button(text: &str) -> RenderedElement { } pub fn svg(path: &str) -> RenderedElement { - RenderedElement::new(ElementName::SVG(path.to_owned())) + RenderedElement::new(ElementName::Svg(path.to_owned())) } pub fn image(path: &str) -> RenderedElement { diff --git a/iced_builder/src/widget.rs b/src/widget.rs index ed2073a..ed2073a 100644 --- a/iced_builder/src/widget.rs +++ b/src/widget.rs |
