summaryrefslogtreecommitdiff
path: root/iced_builder
diff options
context:
space:
mode:
Diffstat (limited to 'iced_builder')
-rw-r--r--iced_builder/Cargo.toml12
-rw-r--r--iced_builder/assets/config.toml1
-rw-r--r--iced_builder/assets/themes/Rose Pine.toml5
-rw-r--r--iced_builder/src/config.rs123
-rw-r--r--iced_builder/src/dialogs.rs9
-rw-r--r--iced_builder/src/environment.rs51
-rw-r--r--iced_builder/src/error.rs19
-rw-r--r--iced_builder/src/lib.rs3
-rw-r--r--iced_builder/src/main.rs71
-rw-r--r--iced_builder/src/panes/code_view.rs6
-rw-r--r--iced_builder/src/panes/element_list.rs8
-rw-r--r--iced_builder/src/theme.rs124
-rw-r--r--iced_builder/src/types/project.rs53
-rwxr-xr-xiced_builder/src/types/rendered_element.rs20
14 files changed, 432 insertions, 73 deletions
diff --git a/iced_builder/Cargo.toml b/iced_builder/Cargo.toml
index c2437ec..638006b 100644
--- a/iced_builder/Cargo.toml
+++ b/iced_builder/Cargo.toml
@@ -13,13 +13,17 @@ iced = { version = "0.13.1", features = [ "image","svg","canvas","qr_code","adva
# 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"] }
+serde = { version = "1.0.217", features = ["derive"] }
+serde_json = "1.0.134"
+toml = "0.8.19"
+tokio = { version = "1.42", features = ["fs"] }
+tokio-stream = { version = "0.1", features = ["fs"] }
rfd = { version = "0.15.1", default-features = false, features = ["async-std", "gtk3"] }
rust-format = "0.3.4"
uuid = { version = "1.11.0", features = ["v4", "serde"] }
-thiserror = "2.0.6"
+thiserror = "2.0.9"
+xdg = "2.5.2"
+dirs-next = "2.0.0"
[build-dependencies]
iced_fontello = "0.13.1"
diff --git a/iced_builder/assets/config.toml b/iced_builder/assets/config.toml
new file mode 100644
index 0000000..8ef1bb3
--- /dev/null
+++ b/iced_builder/assets/config.toml
@@ -0,0 +1 @@
+theme = "Rose Pine"
diff --git a/iced_builder/assets/themes/Rose Pine.toml b/iced_builder/assets/themes/Rose Pine.toml
new file mode 100644
index 0000000..a4eeeeb
--- /dev/null
+++ b/iced_builder/assets/themes/Rose Pine.toml
@@ -0,0 +1,5 @@
+background = "#26233a"
+text = "#e0def4"
+primary = "#9ccfd8"
+success = "#f6c177"
+danger = "#eb6f92"
diff --git a/iced_builder/src/config.rs b/iced_builder/src/config.rs
new file mode 100644
index 0000000..75aee1d
--- /dev/null
+++ b/iced_builder/src/config.rs
@@ -0,0 +1,123 @@
+use std::path::PathBuf;
+
+use serde::Deserialize;
+use tokio_stream::wrappers::ReadDirStream;
+use tokio_stream::StreamExt;
+
+use crate::theme::{theme_from_str, theme_index, Theme, ThemePalette};
+use crate::{environment, Error, Result};
+
+#[derive(Debug, Default)]
+pub struct Config {
+ pub theme: Theme,
+ pub last_project: Option<PathBuf>,
+}
+
+impl Config {
+ pub fn selected_theme(&self) -> iced::Theme {
+ self.theme.selected.clone()
+ }
+
+ pub fn config_dir() -> PathBuf {
+ let dir = environment::config_dir();
+
+ if !dir.exists() {
+ std::fs::create_dir_all(dir.as_path())
+ .expect("expected permissions to create config folder");
+ }
+ println!("{}", dir.to_string_lossy());
+ dir
+ }
+
+ pub fn themes_dir() -> PathBuf {
+ let dir = Self::config_dir().join("themes");
+
+ if !dir.exists() {
+ std::fs::create_dir_all(dir.as_path())
+ .expect("expected permissions to create themes folder");
+ }
+ println!("{}", dir.to_string_lossy());
+ dir
+ }
+
+ pub fn config_file_path() -> PathBuf {
+ Self::config_dir().join(environment::CONFIG_FILE_NAME)
+ }
+
+ pub async fn load() -> Result<Self> {
+ use tokio::fs;
+
+ #[derive(Deserialize)]
+ pub struct Configuration {
+ pub theme: String,
+ pub last_project: Option<PathBuf>,
+ }
+
+ let path = Self::config_file_path();
+ if !path.try_exists()? {
+ return Err(Error::ConfigMissing);
+ }
+
+ let content = fs::read_to_string(path).await?;
+
+ let Configuration {
+ theme,
+ last_project,
+ } = toml::from_str(content.as_ref())?;
+
+ let theme = Self::load_theme(theme).await.unwrap_or_default();
+
+ Ok(Self {
+ theme,
+ last_project,
+ })
+ }
+
+ pub async fn load_theme(theme_name: String) -> Result<Theme> {
+ use tokio::fs;
+
+ let read_entry = |entry: fs::DirEntry| async move {
+ let content = fs::read_to_string(entry.path()).await.ok()?;
+
+ let palette: ThemePalette =
+ toml::from_str(content.as_ref()).ok()?;
+ let name = entry.path().file_stem()?.to_string_lossy().to_string();
+
+ Some(iced::Theme::custom(name, palette.into()))
+ };
+
+ let mut all = iced::Theme::ALL.to_owned();
+ let mut selected = iced::Theme::default();
+
+ if theme_index(theme_name.clone(), iced::Theme::ALL).is_some() {
+ selected = theme_from_str(None, &theme_name);
+ }
+
+ let mut stream =
+ ReadDirStream::new(fs::read_dir(Self::themes_dir()).await?);
+ while let Some(entry) = stream.next().await {
+ let Ok(entry) = entry else {
+ continue;
+ };
+
+ let Some(file_name) = entry.file_name().to_str().map(String::from)
+ else {
+ continue;
+ };
+
+ if let Some(file_name) = file_name.strip_suffix(".toml") {
+ if let Some(theme) = read_entry(entry).await {
+ if file_name == theme_name {
+ selected = theme.clone();
+ }
+ all.push(theme);
+ }
+ }
+ }
+
+ Ok(Theme {
+ selected,
+ all: all.into(),
+ })
+ }
+}
diff --git a/iced_builder/src/dialogs.rs b/iced_builder/src/dialogs.rs
index 047ffd2..c9a5ba2 100644
--- a/iced_builder/src/dialogs.rs
+++ b/iced_builder/src/dialogs.rs
@@ -9,6 +9,15 @@ pub fn error_dialog(description: impl Into<String>) {
.show();
}
+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>,
) -> MessageDialogResult {
diff --git a/iced_builder/src/environment.rs b/iced_builder/src/environment.rs
new file mode 100644
index 0000000..52e7ca5
--- /dev/null
+++ b/iced_builder/src/environment.rs
@@ -0,0 +1,51 @@
+use std::env;
+use std::path::PathBuf;
+
+pub const CONFIG_FILE_NAME: &str = "config.toml";
+
+pub fn config_dir() -> PathBuf {
+ portable_dir().unwrap_or_else(platform_specific_config_dir)
+}
+
+pub fn data_dir() -> PathBuf {
+ portable_dir().unwrap_or_else(|| {
+ dirs_next::data_dir()
+ .expect("expected valid data dir")
+ .join("iced-builder")
+ })
+}
+
+fn portable_dir() -> Option<PathBuf> {
+ let exe = env::current_exe().ok()?;
+ let dir = exe.parent()?;
+
+ dir.join(CONFIG_FILE_NAME)
+ .is_file()
+ .then(|| dir.to_path_buf())
+}
+
+fn platform_specific_config_dir() -> PathBuf {
+ #[cfg(target_os = "macos")]
+ {
+ xdg_config_dir().unwrap_or_else(|| {
+ dirs_next::config_dir()
+ .expect("expected valid config dir")
+ .join("iced-builder")
+ })
+ }
+ #[cfg(not(target_os = "macos"))]
+ {
+ dirs_next::config_dir()
+ .expect("expected valid config dir")
+ .join("iced-builder")
+ }
+}
+
+#[cfg(target_os = "macos")]
+fn xdg_config_dir() -> Option<PathBuf> {
+ let config_dir = xdg::BaseDirectories::with_prefix("iced-builder")
+ .ok()
+ .and_then(|xdg| xdg.find_config_file(CONFIG_FILE_NAME))?;
+
+ config_dir.parent().map(|p| p.to_path_buf())
+}
diff --git a/iced_builder/src/error.rs b/iced_builder/src/error.rs
index 8876016..9cbb6ee 100644
--- a/iced_builder/src/error.rs
+++ b/iced_builder/src/error.rs
@@ -7,12 +7,17 @@ use thiserror::Error;
#[error(transparent)]
pub enum Error {
IOError(Arc<io::Error>),
- SerdeError(Arc<serde_json::Error>),
+ #[error("config does not exist")]
+ ConfigMissing,
+ #[error("JSON parsing error: {0}")]
+ SerdeJSONError(Arc<serde_json::Error>),
+ #[error("TOML parsing error: {0}")]
+ SerdeTOMLError(#[from] toml::de::Error),
FormatError(Arc<rust_format::Error>),
- #[error("The element tree contains no matching element")]
+ #[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}")]
@@ -27,7 +32,7 @@ impl From<io::Error> for Error {
impl From<serde_json::Error> for Error {
fn from(value: serde_json::Error) -> Self {
- Self::SerdeError(Arc::new(value))
+ Self::SerdeJSONError(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/lib.rs b/iced_builder/src/lib.rs
index f3165f5..847e01e 100644
--- a/iced_builder/src/lib.rs
+++ b/iced_builder/src/lib.rs
@@ -1,7 +1,10 @@
+pub mod config;
pub mod dialogs;
+pub mod environment;
pub mod error;
pub mod icon;
pub mod panes;
+pub mod theme;
pub mod types;
pub mod widget;
diff --git a/iced_builder/src/main.rs b/iced_builder/src/main.rs
index a041c6f..02e1ae0 100644
--- a/iced_builder/src/main.rs
+++ b/iced_builder/src/main.rs
@@ -5,22 +5,34 @@ 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::config::Config;
+use iced_builder::dialogs::{
+ error_dialog, unsaved_changes_dialog, warning_dialog,
+};
use iced_builder::panes::{code_view, designer_view, element_list};
use iced_builder::types::{
Action, DesignerPage, ElementName, Message, Project,
};
+use iced_builder::{icon, Error};
use rfd::MessageDialogResult;
+use tokio::runtime;
+
+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,6 +40,7 @@ struct App {
is_loading: bool,
project_path: Option<PathBuf>,
project: Project,
+ config: Config,
theme: Spring<Theme>,
pane_state: pane_grid::State<Panes>,
focus: Option<Pane>,
@@ -43,7 +56,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,20 +65,48 @@ impl App {
b: Box::new(pane_grid::Configuration::Pane(Panes::ElementList)),
},
);
+
+ let config = match config_load {
+ Ok(config) => {
+ println!("{config:?}");
+ config
+ }
+ Err(_) => Config::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),
+ Message::FileOpened,
+ );
+ } else {
+ warning_dialog(format!(
+ "The file {} does not exist, or isn't a file.",
+ path.to_string_lossy().to_string()
+ ));
+ }
+ }
+
(
Self {
is_dirty: false,
is_loading: false,
project_path: None,
project: Project::new(),
- theme: Spring::new(Theme::SolarizedDark),
+ config,
+ theme: Spring::new(theme),
pane_state: state,
focus: None,
designer_page: DesignerPage::DesignerView,
element_list: ElementName::ALL,
editor_content: text_editor::Content::new(),
},
- Task::none(),
+ task,
)
}
@@ -104,7 +145,7 @@ impl App {
}
}
Message::RefreshEditorContent => {
- match self.project.clone().app_code() {
+ match self.project.clone().app_code(&self.config) {
Ok(code) => {
self.editor_content =
text_editor::Content::with_text(&code);
@@ -209,14 +250,14 @@ impl App {
self.is_loading = true;
return Task::perform(
- Project::from_path(),
+ Project::from_file(),
Message::FileOpened,
);
} else {
if let MessageDialogResult::Ok = 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(), Message::FileOpened);
}
}
}
@@ -231,7 +272,7 @@ impl App {
self.project_path = Some(path);
self.editor_content = text_editor::Content::with_text(
&project
- .app_code()
+ .app_code(&self.config)
.unwrap_or_else(|err| err.to_string()),
);
}
@@ -299,7 +340,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 +352,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/code_view.rs b/iced_builder/src/panes/code_view.rs
index fe7801c..b95b653 100644
--- a/iced_builder/src/panes/code_view.rs
+++ b/iced_builder/src/panes/code_view.rs
@@ -5,11 +5,11 @@ 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),
diff --git a/iced_builder/src/panes/element_list.rs b/iced_builder/src/panes/element_list.rs
index 74188af..8a1c6eb 100644
--- a/iced_builder/src/panes/element_list.rs
+++ b/iced_builder/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/theme.rs b/iced_builder/src/theme.rs
new file mode 100644
index 0000000..21ec6e3
--- /dev/null
+++ b/iced_builder/src/theme.rs
@@ -0,0 +1,124 @@
+use std::sync::Arc;
+
+use iced::Color;
+
+use crate::config::Config;
+
+pub fn theme_index(theme_name: String, slice: &[iced::Theme]) -> Option<usize> {
+ slice
+ .iter()
+ .position(|theme| theme.to_string() == theme_name)
+}
+
+pub fn theme_from_str(
+ config: Option<&Config>,
+ theme_name: &str,
+) -> iced::Theme {
+ match theme_name {
+ "Light" => iced::Theme::Light,
+ "Dark" => iced::Theme::Dark,
+ "Dracula" => iced::Theme::Dracula,
+ "Nord" => iced::Theme::Nord,
+ "Solarized Light" => iced::Theme::SolarizedLight,
+ "Solarized Dark" => iced::Theme::SolarizedDark,
+ "Gruvbox Light" => iced::Theme::GruvboxLight,
+ "Gruvbox Dark" => iced::Theme::GruvboxDark,
+ "Catppuccin Latte" => iced::Theme::CatppuccinLatte,
+ "Catppuccin Frappé" => iced::Theme::CatppuccinFrappe,
+ "Catppuccin Macchiato" => iced::Theme::CatppuccinMacchiato,
+ "Catppuccin Mocha" => iced::Theme::CatppuccinMocha,
+ "Tokyo Night" => iced::Theme::TokyoNight,
+ "Tokyo Night Storm" => iced::Theme::TokyoNightStorm,
+ "Tokyo Night Light" => iced::Theme::TokyoNightLight,
+ "Kanagawa Wave" => iced::Theme::KanagawaWave,
+ "Kanagawa Dragon" => iced::Theme::KanagawaDragon,
+ "Kanagawa Lotus" => iced::Theme::KanagawaLotus,
+ "Moonfly" => iced::Theme::Moonfly,
+ "Nightfly" => iced::Theme::Nightfly,
+ "Oxocarbon" => iced::Theme::Oxocarbon,
+ "Ferra" => iced::Theme::Ferra,
+ _ => {
+ if let Some(config) = config {
+ if theme_name == config.theme.selected.to_string() {
+ config.theme.selected.clone()
+ } else if let Some(index) =
+ theme_index(theme_name.into(), &config.theme.all)
+ {
+ config.theme.all[index].clone()
+ } else {
+ iced::Theme::default()
+ }
+ } else {
+ iced::Theme::default()
+ }
+ }
+ }
+}
+
+#[derive(Debug)]
+pub struct Theme {
+ pub selected: iced::Theme,
+ pub all: Arc<[iced::Theme]>,
+}
+
+impl Default for Theme {
+ fn default() -> Self {
+ Self {
+ selected: iced::Theme::default(),
+ all: iced::Theme::ALL.into(),
+ }
+ }
+}
+
+#[derive(Debug, serde::Deserialize)]
+pub struct ThemePalette {
+ #[serde(with = "color_serde")]
+ background: Color,
+ #[serde(with = "color_serde")]
+ text: Color,
+ #[serde(with = "color_serde")]
+ primary: Color,
+ #[serde(with = "color_serde")]
+ success: Color,
+ #[serde(with = "color_serde")]
+ danger: Color,
+}
+
+impl Default for ThemePalette {
+ fn default() -> Self {
+ let palette = iced::Theme::default().palette();
+ Self {
+ background: palette.background,
+ text: palette.text,
+ primary: palette.primary,
+ success: palette.success,
+ danger: palette.danger,
+ }
+ }
+}
+
+impl From<ThemePalette> for iced::theme::Palette {
+ fn from(palette: ThemePalette) -> Self {
+ iced::theme::Palette {
+ background: palette.background,
+ text: palette.text,
+ primary: palette.primary,
+ success: palette.success,
+ danger: palette.danger,
+ }
+ }
+}
+
+mod color_serde {
+ use iced::Color;
+ use serde::{Deserialize, Deserializer};
+
+ pub fn deserialize<'de, D>(deserializer: D) -> Result<Color, D::Error>
+ where
+ D: Deserializer<'de>,
+ {
+ Ok(String::deserialize(deserializer)
+ .map(|hex| Color::parse(&hex))?
+ .unwrap_or(Color::TRANSPARENT))
+ }
+}
diff --git a/iced_builder/src/types/project.rs b/iced_builder/src/types/project.rs
index f4dbcc4..6f3b7ed 100644
--- a/iced_builder/src/types/project.rs
+++ b/iced_builder/src/types/project.rs
@@ -5,6 +5,7 @@ use rust_format::{Config, Edition, Formatter, RustFmt};
use serde::{Deserialize, Serialize};
use super::rendered_element::RenderedElement;
+use crate::theme::theme_from_str;
use crate::{Error, Result};
#[derive(Debug, Clone, Serialize, Deserialize)]
@@ -14,6 +15,12 @@ pub struct Project {
pub element_tree: Option<RenderedElement>,
}
+impl Default for Project {
+ fn default() -> Self {
+ Self::new()
+ }
+}
+
impl Project {
pub fn new() -> Self {
Self {
@@ -23,38 +30,21 @@ impl Project {
}
}
- pub fn get_theme(&self) -> Theme {
+ pub fn get_theme(&self, config: &crate::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,
- },
+ Some(theme) => theme_from_str(Some(config), theme),
None => Theme::Dark,
}
}
- pub async fn from_path() -> Result<(PathBuf, Self)> {
+ pub async fn from_path(path: PathBuf) -> Result<(PathBuf, Self)> {
+ let contents = tokio::fs::read_to_string(&path).await?;
+ let element: Self = serde_json::from_str(&contents)?;
+
+ Ok((path, element))
+ }
+
+ pub async fn from_file() -> Result<(PathBuf, Self)> {
let picked_file = rfd::AsyncFileDialog::new()
.set_title("Open a JSON file...")
.add_filter("*.json, *.JSON", &["json", "JSON"])
@@ -64,10 +54,7 @@ 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).await
}
pub async fn write_to_file(self, path: Option<PathBuf>) -> Result<PathBuf> {
@@ -91,7 +78,7 @@ impl Project {
Ok(path)
}
- pub fn app_code(&self) -> Result<String> {
+ pub fn app_code(&self, config: &crate::config::Config) -> Result<String> {
match self.element_tree {
Some(ref element_tree) => {
let (imports, view) = element_tree.codegen();
@@ -127,7 +114,7 @@ impl Project {
Some(ref t) => t,
None => "New app",
},
- self.get_theme().to_string().replace(" ", "")
+ self.get_theme(config).to_string().replace(" ", "")
);
let config = Config::new_str()
.edition(Edition::Rust2021)
diff --git a/iced_builder/src/types/rendered_element.rs b/iced_builder/src/types/rendered_element.rs
index ccc8668..5270e5a 100755
--- a/iced_builder/src/types/rendered_element.rs
+++ b/iced_builder/src/types/rendered_element.rs
@@ -43,7 +43,7 @@ impl RenderedElement {
pub fn find_by_id(&mut self, id: Id) -> Option<&mut Self> {
if self.get_id() == id.clone() {
- return Some(self);
+ 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());
@@ -51,9 +51,9 @@ impl RenderedElement {
return element;
}
}
- return None;
+ None
} else {
- return None;
+ None
}
}
@@ -62,12 +62,12 @@ impl RenderedElement {
child_element: &RenderedElement,
) -> Option<&mut Self> {
if child_element == self {
- return Some(self);
+ Some(self)
} else if self.child_elements.is_some() {
if self
.child_elements
.clone()
- .unwrap_or(vec![])
+ .unwrap_or_default()
.contains(child_element)
{
return Some(self);
@@ -160,7 +160,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);
}
@@ -410,10 +410,10 @@ impl Action {
.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())
+ // Element is a parent and isn't a non-empty container
+ match (element.is_empty()
+ || !(element.name == ElementName::Container))
+ && element.is_parent()
{
true => {
action = Self::PushFront(id);