diff options
Diffstat (limited to 'iced_builder')
| -rw-r--r-- | iced_builder/Cargo.toml | 12 | ||||
| -rw-r--r-- | iced_builder/assets/config.toml | 1 | ||||
| -rw-r--r-- | iced_builder/assets/themes/Rose Pine.toml | 5 | ||||
| -rw-r--r-- | iced_builder/src/config.rs | 123 | ||||
| -rw-r--r-- | iced_builder/src/dialogs.rs | 9 | ||||
| -rw-r--r-- | iced_builder/src/environment.rs | 51 | ||||
| -rw-r--r-- | iced_builder/src/error.rs | 19 | ||||
| -rw-r--r-- | iced_builder/src/lib.rs | 3 | ||||
| -rw-r--r-- | iced_builder/src/main.rs | 71 | ||||
| -rw-r--r-- | iced_builder/src/panes/code_view.rs | 6 | ||||
| -rw-r--r-- | iced_builder/src/panes/element_list.rs | 8 | ||||
| -rw-r--r-- | iced_builder/src/theme.rs | 124 | ||||
| -rw-r--r-- | iced_builder/src/types/project.rs | 53 | ||||
| -rwxr-xr-x | iced_builder/src/types/rendered_element.rs | 20 |
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); |
