summaryrefslogtreecommitdiff
path: root/iced_builder/src
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--iced_builder/src/lib.rs9
-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-xsrc/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
15 files changed, 267 insertions, 211 deletions
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/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