diff options
| author | pml68 <contact@pml68.dev> | 2025-01-11 01:50:16 +0100 |
|---|---|---|
| committer | pml68 <contact@pml68.dev> | 2025-01-11 01:50:16 +0100 |
| commit | 61926598ce96bee00aafe5340af4a905759b122a (patch) | |
| tree | b79e13b3decc778cc7c66af7187c647ae0a21a52 /iced_builder | |
| parent | refactor: apply clippy suggestions (diff) | |
| download | iced-builder-61926598ce96bee00aafe5340af4a905759b122a.tar.gz | |
refactor: remove iced_drop & workspace
Diffstat (limited to 'iced_builder')
26 files changed, 0 insertions, 2133 deletions
diff --git a/iced_builder/Cargo.toml b/iced_builder/Cargo.toml deleted file mode 100644 index ab453a2..0000000 --- a/iced_builder/Cargo.toml +++ /dev/null @@ -1,64 +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.2.0", features = ["derive"] } -iced_drop = { path = "../iced_drop" } -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"] } -fxhash = "0.2.1" -thiserror = "2.0.9" -dirs-next = "2.0.0" - -[target.'cfg(macos)'.dependencies] -xdg = "2.5.2" - -[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/assets/config.toml b/iced_builder/assets/config.toml deleted file mode 100644 index 8ef1bb3..0000000 --- a/iced_builder/assets/config.toml +++ /dev/null @@ -1 +0,0 @@ -theme = "Rose Pine" diff --git a/iced_builder/assets/themes/Rose Pine.toml b/iced_builder/assets/themes/Rose Pine.toml deleted file mode 100644 index df7342b..0000000 --- a/iced_builder/assets/themes/Rose Pine.toml +++ /dev/null @@ -1,33 +0,0 @@ -is_dark = true - -[palette] -background = "#26233a" -text = "#e0def4" -primary = "#9ccfd8" -success = "#f6c177" -danger = "#eb6f92" - -[background] -base = { color = "#191724", text = "#e0def4" } -weak = { color = "#1f1d2e", text = "#e0def4" } -strong = { color = "#26233a", text = "#f4ebd3" } - -[primary] -base = { color = "#eb6f92", text = "#000000" } -weak = { color = "#f6c177", text = "#000000" } -strong = { color = "#ebbcba", text = "#000000" } - -[secondary] -base = { color = "#31748f", text = "#ffffff" } -weak = { color = "#9ccfd8", text = "#000000" } -strong = { color = "#c4a7e7", text = "#000000" } - -[success] -base = { color = "#9ccfd8", text = "#000000" } -weak = { color = "#6e6a86", text = "#ffffff" } -strong = { color = "#908caa", text = "#000000" } - -[danger] -base = { color = "#eb6f92", text = "#ffffff" } -weak = { color = "#f6c177", text = "#ffffff" } -strong = { color = "#524f67", text = "#ffffff" } diff --git a/iced_builder/assets/windows/iced_builder.manifest b/iced_builder/assets/windows/iced_builder.manifest deleted file mode 100644 index 82039bf..0000000 --- a/iced_builder/assets/windows/iced_builder.manifest +++ /dev/null @@ -1,8 +0,0 @@ -<?xml version='1.0' encoding='UTF-8' standalone='yes'?> -<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0" xmlns:asmv3="urn:schemas-microsoft-com:asm.v3" > - <asmv3:application> - <asmv3:windowsSettings xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings"> - <dpiAwareness>PerMonitorV2, unaware</dpiAwareness> - </asmv3:windowsSettings> - </asmv3:application> -</assembly> diff --git a/iced_builder/assets/windows/iced_builder.rc b/iced_builder/assets/windows/iced_builder.rc deleted file mode 100644 index 7255b65..0000000 --- a/iced_builder/assets/windows/iced_builder.rc +++ /dev/null @@ -1,3 +0,0 @@ -#define RT_MANIFEST 24 - -1 RT_MANIFEST "iced_builder.manifest" diff --git a/iced_builder/build.rs b/iced_builder/build.rs deleted file mode 100644 index 438ce37..0000000 --- a/iced_builder/build.rs +++ /dev/null @@ -1,12 +0,0 @@ -fn main() { - println!("cargo::rerun-if-changed=fonts/icons.toml"); - iced_fontello::build("fonts/icons.toml").expect("Build icons font"); - #[cfg(windows)] - { - embed_resource::compile( - "assets/windows/iced_builder.rc", - embed_resource::NONE, - ); - windows_exe_info::versioninfo::link_cargo_env(); - } -} diff --git a/iced_builder/fonts/icons.toml b/iced_builder/fonts/icons.toml deleted file mode 100644 index a70c0e7..0000000 --- a/iced_builder/fonts/icons.toml +++ /dev/null @@ -1,6 +0,0 @@ -module = "icon" - -[glyphs] -save = "entypo-floppy" -open = "fontawesome-folder-open-empty" -copy = "fontawesome-file-code" diff --git a/iced_builder/fonts/icons.ttf b/iced_builder/fonts/icons.ttf Binary files differdeleted file mode 100644 index 7af6b0e..0000000 --- a/iced_builder/fonts/icons.ttf +++ /dev/null diff --git a/iced_builder/rustfmt.toml b/iced_builder/rustfmt.toml deleted file mode 100644 index 197262a..0000000 --- a/iced_builder/rustfmt.toml +++ /dev/null @@ -1,4 +0,0 @@ -edition = "2021" -imports_granularity = "Module" -group_imports = "StdExternalCrate" -max_width = 80 diff --git a/iced_builder/src/config.rs b/iced_builder/src/config.rs deleted file mode 100644 index 9d29af7..0000000 --- a/iced_builder/src/config.rs +++ /dev/null @@ -1,121 +0,0 @@ -use std::path::PathBuf; - -use serde::Deserialize; -use tokio_stream::wrappers::ReadDirStream; -use tokio_stream::StreamExt; - -use crate::theme::{theme_from_str, theme_index, Appearance, Theme}; -use crate::{environment, Error}; - -#[derive(Debug, Clone, Default)] -pub struct Config { - pub theme: Appearance, - 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"); - } - 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"); - } - dir - } - - pub fn config_file_path() -> PathBuf { - Self::config_dir().join(environment::CONFIG_FILE_NAME) - } - - pub async fn load() -> Result<Self, Error> { - use tokio::fs; - - #[derive(Deserialize)] - pub struct Configuration { - #[serde(default)] - 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<Appearance, Error> { - use tokio::fs; - - let read_entry = |entry: fs::DirEntry| async move { - let content = fs::read_to_string(entry.path()).await.ok()?; - - let theme: Theme = toml::from_str(content.as_ref()).ok()?; - let name = entry.path().file_stem()?.to_string_lossy().to_string(); - - Some(theme.into_iced_theme(name)) - }; - - let mut all = iced::Theme::ALL.to_owned(); - let mut selected = iced::Theme::default(); - - if theme_index(&theme_name, 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(Appearance { - selected, - all: all.into(), - }) - } -} diff --git a/iced_builder/src/dialogs.rs b/iced_builder/src/dialogs.rs deleted file mode 100644 index 2d916b1..0000000 --- a/iced_builder/src/dialogs.rs +++ /dev/null @@ -1,30 +0,0 @@ -use rfd::{MessageButtons, MessageDialog, MessageDialogResult, MessageLevel}; - -pub fn error_dialog(description: impl Into<String>) { - let _ = MessageDialog::new() - .set_level(MessageLevel::Error) - .set_buttons(MessageButtons::Ok) - .set_title("Oops! Something went wrong.") - .set_description(description) - .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>) -> bool { - let result = MessageDialog::new() - .set_level(MessageLevel::Warning) - .set_buttons(MessageButtons::OkCancel) - .set_title("Unsaved changes") - .set_description(description) - .show(); - - matches!(result, MessageDialogResult::Ok) -} diff --git a/iced_builder/src/environment.rs b/iced_builder/src/environment.rs deleted file mode 100644 index 3ecb790..0000000 --- a/iced_builder/src/environment.rs +++ /dev/null @@ -1,43 +0,0 @@ -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) -} - -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 deleted file mode 100644 index f4011bd..0000000 --- a/iced_builder/src/error.rs +++ /dev/null @@ -1,55 +0,0 @@ -use std::io; -use std::sync::Arc; - -use thiserror::Error; - -#[derive(Debug, Clone, Error)] -#[error(transparent)] -pub enum Error { - 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" - )] - DialogClosed, - #[error("{0}")] - Other(String), -} - -impl From<io::Error> for Error { - fn from(value: io::Error) -> Self { - Self::IO(Arc::new(value)) - } -} - -impl From<serde_json::Error> for Error { - fn from(value: serde_json::Error) -> Self { - Self::SerdeJSON(Arc::new(value)) - } -} - -impl From<rust_format::Error> for Error { - fn from(value: rust_format::Error) -> Self { - Self::RustFmt(Arc::new(value)) - } -} - -impl From<&str> for Error { - fn from(value: &str) -> Self { - 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/iced_builder/src/icon.rs deleted file mode 100644 index f6760d5..0000000 --- a/iced_builder/src/icon.rs +++ /dev/null @@ -1,23 +0,0 @@ -// Generated automatically by iced_fontello at build time. -// Do not edit manually. Source: ../fonts/icons.toml -// 02c7558d187cdc056fdd0e6a638ef805fa10f5955f834575e51d75acd35bc70e -use iced::widget::{text, Text}; -use iced::Font; - -pub const FONT: &[u8] = include_bytes!("../fonts/icons.ttf"); - -pub fn copy<'a>() -> Text<'a> { - icon("\u{F1C9}") -} - -pub fn open<'a>() -> Text<'a> { - icon("\u{F115}") -} - -pub fn save<'a>() -> Text<'a> { - icon("\u{1F4BE}") -} - -fn icon<'a>(codepoint: &'a str) -> Text<'a> { - text(codepoint).font(Font::with_name("icons")) -} diff --git a/iced_builder/src/main.rs b/iced_builder/src/main.rs deleted file mode 100644 index 5b95b94..0000000 --- a/iced_builder/src/main.rs +++ /dev/null @@ -1,382 +0,0 @@ -#![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::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()?; - - rt.block_on(Config::load()) - }; - - iced::application(App::title, App::update, App::view) - .font(icon::FONT) - .theme(|state| state.theme.value().clone()) - .subscription(App::subscription) - .run_with(move || App::new(config_load))?; - - Ok(()) -} - -struct App { - is_dirty: bool, - is_loading: bool, - project_path: Option<PathBuf>, - project: Project, - config: Config, - theme: Animated<Theme>, - pane_state: pane_grid::State<Panes>, - focus: Option<Pane>, - designer_page: DesignerPage, - element_list: &'static [ElementName], - editor_content: text_editor::Content, -} - -#[derive(Clone, Copy, Debug)] -enum Panes { - Designer, - ElementList, -} - -impl App { - 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, - ratio: 0.8, - a: Box::new(pane_grid::Configuration::Pane(Panes::Designer)), - 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(), - 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, - ) - } - - fn title(&self) -> String { - let saved_state = if self.is_dirty { " *" } else { "" }; - - let project_name = match &self.project.title { - Some(n) => { - format!( - " - {}", - if n.len() > 60 { - format!("...{}", &n[n.len() - 40..]) - } else { - n.to_owned() - } - ) - } - None => String::new(), - }; - - format!("iced Builder{project_name}{saved_state}") - } - - fn update(&mut self, message: Message) -> Task<Message> { - match message { - Message::ToggleTheme(event) => { - self.theme.update(event); - } - Message::CopyCode => { - return clipboard::write(self.editor_content.text()) - } - Message::SwitchPage(page) => self.designer_page = page, - Message::EditorAction(action) => { - if let text_editor::Action::Scroll { lines: _ } = action { - self.editor_content.perform(action); - } - } - Message::RefreshEditorContent => { - match self.project.app_code(&self.config) { - Ok(code) => { - self.editor_content = - text_editor::Content::with_text(&code); - } - Err(error) => error_dialog(error.to_string()), - } - } - Message::DropNewElement(name, point, _) => { - return iced_drop::zones_on_point( - move |zones| Message::HandleNew(name.clone(), zones), - point, - None, - None, - ) - } - Message::HandleNew(name, zones) => { - let ids: Vec<Id> = zones.into_iter().map(|z| z.0).collect(); - 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()); - } - Err(error) => error_dialog(error.to_string()), - _ => {} - } - - self.is_dirty = true; - return Task::done(Message::RefreshEditorContent); - } - } - Message::MoveElement(element, point, _) => { - return iced_drop::zones_on_point( - move |zones| Message::HandleMove(element.clone(), zones), - point, - None, - None, - ) - } - Message::HandleMove(element, zones) => { - let ids: Vec<Id> = zones.into_iter().map(|z| z.0).collect(); - if !ids.is_empty() { - let eltree_clone = self.project.element_tree.clone(); - let action = Action::new( - &ids, - &eltree_clone, - Some(element.get_id()), - ); - let result = element.handle_action( - self.project.element_tree.as_mut(), - action, - ); - if let Err(error) = result { - error_dialog(error.to_string()); - } - - self.is_dirty = true; - return Task::done(Message::RefreshEditorContent); - } - } - Message::PaneResized(pane_grid::ResizeEvent { split, ratio }) => { - self.pane_state.resize(split, ratio); - } - Message::PaneClicked(pane) => { - self.focus = Some(pane); - } - Message::PaneDragged(pane_grid::DragEvent::Dropped { - pane, - target, - }) => { - self.pane_state.drop(pane, target); - } - Message::PaneDragged(_) => {} - Message::NewFile => { - if !self.is_loading { - if !self.is_dirty { - self.project = Project::new(); - self.project_path = None; - self.editor_content = text_editor::Content::new(); - } 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(); - } - } - } - Message::OpenFile => { - if !self.is_loading { - if !self.is_dirty { - self.is_loading = true; - - return Task::perform( - Project::from_file(self.config.clone()), - Message::FileOpened, - ); - } 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_file(self.config.clone()), Message::FileOpened); - } - } - } - Message::FileOpened(result) => { - self.is_loading = false; - self.is_dirty = false; - - match result { - Ok((path, project)) => { - self.project = project; - self.project_path = Some(path); - self.editor_content = text_editor::Content::with_text( - &self - .project - .app_code(&self.config) - .unwrap_or_else(|err| err.to_string()), - ); - } - Err(error) => error_dialog(error.to_string()), - } - } - Message::SaveFile => { - if !self.is_loading { - self.is_loading = true; - - return Task::perform( - self.project - .clone() - .write_to_file(self.project_path.clone()), - Message::FileSaved, - ); - } - } - Message::SaveFileAs => { - if !self.is_loading { - self.is_loading = true; - - return Task::perform( - self.project.clone().write_to_file(None), - Message::FileSaved, - ); - } - } - Message::FileSaved(result) => { - self.is_loading = false; - - match result { - Ok(path) => { - self.project_path = Some(path); - self.is_dirty = false; - } - Err(error) => error_dialog(error.to_string()), - } - } - } - - Task::none() - } - - fn subscription(&self) -> iced::Subscription<Message> { - keyboard::on_key_press(|key, modifiers| { - if modifiers.command() { - match key.as_ref() { - keyboard::Key::Character("o") => Some(Message::OpenFile), - keyboard::Key::Character("s") => { - Some(if modifiers.shift() { - Message::SaveFileAs - } else { - Message::SaveFile - }) - } - keyboard::Key::Character("n") => Some(Message::NewFile), - _ => None, - } - } else { - None - } - }) - } - - fn view(&self) -> Element<'_, Message> { - let header = row![pick_list( - self.config.theme.all.clone(), - Some(self.theme.target().clone()), - |theme| { Message::ToggleTheme(theme.into()) } - )] - .width(200); - let pane_grid = - PaneGrid::new(&self.pane_state, |id, pane, _is_maximized| { - let is_focused = Some(id) == self.focus; - match pane { - Panes::Designer => match &self.designer_page { - DesignerPage::DesignerView => designer_view::view( - &self.project.element_tree, - self.project.get_theme(&self.config), - is_focused, - ), - DesignerPage::CodeView => code_view::view( - &self.editor_content, - self.theme.target().clone(), - is_focused, - ), - }, - Panes::ElementList => { - element_list::view(self.element_list, is_focused) - } - } - }) - .width(Length::Fill) - .height(Length::Fill) - .spacing(10) - .on_resize(10, Message::PaneResized) - .on_click(Message::PaneClicked) - .on_drag(Message::PaneDragged); - - let content = Column::new() - .push(header) - .push(pane_grid) - .spacing(5) - .align_x(Alignment::Center) - .width(Length::Fill); - - Animation::new(&self.theme, container(content).height(Length::Fill)) - .on_update(Message::ToggleTheme) - .into() - } -} diff --git a/iced_builder/src/panes.rs b/iced_builder/src/panes.rs deleted file mode 100644 index 387662a..0000000 --- a/iced_builder/src/panes.rs +++ /dev/null @@ -1,4 +0,0 @@ -pub mod code_view; -pub mod designer_view; -pub mod element_list; -mod style; diff --git a/iced_builder/src/panes/code_view.rs b/iced_builder/src/panes/code_view.rs deleted file mode 100644 index f545157..0000000 --- a/iced_builder/src/panes/code_view.rs +++ /dev/null @@ -1,50 +0,0 @@ -use iced::widget::{button, pane_grid, row, text, text_editor, Space}; -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( - editor_content: &text_editor::Content, - theme: Theme, - is_focused: bool, -) -> pane_grid::Content<'_, Message> { - let title = row![ - text("Generated Code"), - Space::with_width(Length::Fill), - tip( - button(copy()).on_press(Message::CopyCode), - "Copy code to clipboard", - tip::Position::FollowCursor - ), - Space::with_width(20), - button("Switch to Designer view") - .on_press(Message::SwitchPage(DesignerPage::DesignerView)) - ] - .align_y(Alignment::Center); - let title_bar = pane_grid::TitleBar::new(title) - .padding(10) - .style(style::title_bar); - pane_grid::Content::new( - text_editor(editor_content) - .on_action(Message::EditorAction) - .highlight( - "rs", - if theme.to_string().contains("Dark") { - highlighter::Theme::SolarizedDark - } else { - highlighter::Theme::InspiredGitHub - }, - .font(Font::MONOSPACE) - ) - .height(Length::Fill) - .padding(20), - ) - .title_bar(title_bar) - .style(if is_focused { - style::pane_focused - } else { - style::pane_active - }) -} diff --git a/iced_builder/src/panes/designer_view.rs b/iced_builder/src/panes/designer_view.rs deleted file mode 100644 index 76456db..0000000 --- a/iced_builder/src/panes/designer_view.rs +++ /dev/null @@ -1,37 +0,0 @@ -use iced::widget::{button, container, pane_grid, row, text, themer, Space}; -use iced::{Alignment, Element, Length}; - -use super::style; -use crate::types::{DesignerPage, Message, RenderedElement}; - -pub fn view<'a>( - element_tree: &Option<RenderedElement>, - designer_theme: iced::Theme, - is_focused: bool, -) -> pane_grid::Content<'a, Message> { - let el_tree: Element<'a, Message> = match element_tree { - Some(tree) => tree.clone().into(), - None => text("Open a project or begin creating one").into(), - }; - let content = container(themer(designer_theme, el_tree)) - .id(iced::widget::container::Id::new("drop_zone")) - .height(Length::Fill) - .width(Length::Fill); - let title = row![ - text("Designer"), - Space::with_width(Length::Fill), - button("Switch to Code view") - .on_press(Message::SwitchPage(DesignerPage::CodeView)), - ] - .align_y(Alignment::Center); - let title_bar = pane_grid::TitleBar::new(title) - .padding(10) - .style(style::title_bar); - pane_grid::Content::new(content) - .title_bar(title_bar) - .style(if is_focused { - style::pane_focused - } else { - style::pane_active - }) -} diff --git a/iced_builder/src/panes/element_list.rs b/iced_builder/src/panes/element_list.rs deleted file mode 100644 index 8a1c6eb..0000000 --- a/iced_builder/src/panes/element_list.rs +++ /dev/null @@ -1,49 +0,0 @@ -use iced::widget::{column, container, pane_grid, text, Column}; -use iced::{Alignment, Element, Length}; -use iced_drop::droppable; - -use super::style; -use crate::types::{ElementName, Message}; - -fn items_list_view(items: &[ElementName]) -> Element<'_, Message> { - let mut column = Column::new() - .spacing(20) - .align_x(Alignment::Center) - .width(Length::Fill); - - for item in items { - column = - column.push(droppable(text(item.clone().to_string())).on_drop( - move |point, rect| { - Message::DropNewElement(item.clone(), point, rect) - }, - )); - } - - container(column) - .width(Length::Fill) - .height(Length::Fill) - .into() -} - -pub fn view( - element_list: &[ElementName], - is_focused: bool, -) -> pane_grid::Content<'_, Message> { - let items_list = items_list_view(element_list); - let content = column![items_list] - .align_x(Alignment::Center) - .height(Length::Fill) - .width(Length::Fill); - let title = text("Element List"); - let title_bar = pane_grid::TitleBar::new(title) - .padding(10) - .style(style::title_bar); - pane_grid::Content::new(content) - .title_bar(title_bar) - .style(if is_focused { - style::pane_focused - } else { - style::pane_active - }) -} diff --git a/iced_builder/src/panes/style.rs b/iced_builder/src/panes/style.rs deleted file mode 100644 index 1eefb2d..0000000 --- a/iced_builder/src/panes/style.rs +++ /dev/null @@ -1,40 +0,0 @@ -use iced::widget::container::Style; -use iced::{Border, Theme}; - -pub fn title_bar(theme: &Theme) -> Style { - let palette = theme.extended_palette(); - - Style { - text_color: Some(palette.background.strong.text), - background: Some(palette.background.strong.color.into()), - ..Default::default() - } -} - -pub fn pane_active(theme: &Theme) -> Style { - let palette = theme.extended_palette(); - - Style { - background: Some(palette.background.weak.color.into()), - border: Border { - width: 1.0, - color: palette.background.strong.color, - ..Border::default() - }, - ..Default::default() - } -} - -pub fn pane_focused(theme: &Theme) -> Style { - let palette = theme.extended_palette(); - - Style { - background: Some(palette.background.weak.color.into()), - border: Border { - width: 4.0, - color: palette.background.strong.color, - ..Border::default() - }, - ..Default::default() - } -} diff --git a/iced_builder/src/theme.rs b/iced_builder/src/theme.rs deleted file mode 100644 index 7d18aa9..0000000 --- a/iced_builder/src/theme.rs +++ /dev/null @@ -1,381 +0,0 @@ -use std::sync::Arc; - -use iced::theme::palette::Extended; -use iced::Color; - -use crate::config::Config; - -pub fn theme_index(theme_name: &str, 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, &config.theme.all) - { - config.theme.all[index].clone() - } else { - iced::Theme::default() - } - } else { - iced::Theme::default() - } - } - } -} - -fn palette_to_string(palette: &iced::theme::Palette) -> String { - format!( - r#"Palette {{ - background: color!(0x{}), - text: color!(0x{}), - primary: color!(0x{}), - success: color!(0x{}), - danger: color!(0x{}), - }}"#, - color_to_hex(palette.background), - color_to_hex(palette.text), - color_to_hex(palette.primary), - color_to_hex(palette.success), - color_to_hex(palette.danger), - ) -} - -fn extended_to_string(extended: &Extended) -> String { - format!( - r#" -Extended{{background:Background{{base:Pair{{color:color!(0x{}),text:color!(0x{}),}},weak:Pair{{color:color!(0x{}),text:color!(0x{}),}},strong:Pair{{color:color!(0x{}),text:color!(0x{}),}},}},primary:Primary{{base:Pair{{color:color!(0x{}),text:color!(0x{}),}},weak:Pair{{color:color!(0x{}),text:color!(0x{}),}},strong:Pair{{color:color!(0x{}),text:color!(0x{}),}},}},secondary:Secondary{{base:Pair{{color:color!(0x{}),text:color!(0x{}),}},weak:Pair{{color:color!(0x{}),text:color!(0x{}),}},strong:Pair{{color:color!(0x{}),text:color!(0x{}),}},}},success:Success{{base:Pair{{color:color!(0x{}),text:color!(0x{}),}},weak:Pair{{color:color!(0x{}),text:color!(0x{}),}},strong:Pair{{color:color!(0x{}),text:color!(0x{}),}},}},danger:Danger{{base:Pair{{color:color!(0x{}),text:color!(0x{}),}},weak:Pair{{color:color!(0x{}),text:color!(0x{}),}},strong:Pair{{color:color!(0x{}),text:color!(0x{}),}},}},is_dark:true,}}"#, - color_to_hex(extended.background.base.color), - color_to_hex(extended.background.base.text), - color_to_hex(extended.background.weak.color), - color_to_hex(extended.background.weak.text), - color_to_hex(extended.background.strong.color), - color_to_hex(extended.background.strong.text), - color_to_hex(extended.primary.base.color), - color_to_hex(extended.primary.base.text), - color_to_hex(extended.primary.weak.color), - color_to_hex(extended.primary.weak.text), - color_to_hex(extended.primary.strong.color), - color_to_hex(extended.primary.strong.text), - color_to_hex(extended.secondary.base.color), - color_to_hex(extended.secondary.base.text), - color_to_hex(extended.secondary.weak.color), - color_to_hex(extended.secondary.weak.text), - color_to_hex(extended.secondary.strong.color), - color_to_hex(extended.secondary.strong.text), - color_to_hex(extended.success.base.color), - color_to_hex(extended.success.base.text), - color_to_hex(extended.success.weak.color), - color_to_hex(extended.success.weak.text), - color_to_hex(extended.success.strong.color), - color_to_hex(extended.success.strong.text), - color_to_hex(extended.danger.base.color), - color_to_hex(extended.danger.base.text), - color_to_hex(extended.danger.weak.color), - color_to_hex(extended.danger.weak.text), - color_to_hex(extended.danger.strong.color), - color_to_hex(extended.danger.strong.text), - ) -} - -pub fn theme_to_string(theme: &iced::Theme) -> String { - let palette = theme.palette(); - let extended = theme.extended_palette(); - - let generated_extended = Extended::generate(palette); - - if &generated_extended == extended { - format!( - r#"custom( - "{}".to_string(), - {} - )"#, - theme, - palette_to_string(&palette) - ) - } else { - format!( - r#"custom_with_fn( - "{}".to_string(), - {}, - |_| {} - )"#, - theme, - palette_to_string(&palette), - extended_to_string(extended) - ) - } -} - -fn color_to_hex(color: Color) -> String { - use std::fmt::Write; - - let mut hex = String::with_capacity(12); - - let [r, g, b, a] = color.into_rgba8(); - - let _ = write!(&mut hex, "{:02X}", r); - let _ = write!(&mut hex, "{:02X}", g); - let _ = write!(&mut hex, "{:02X}", b); - - if a < u8::MAX { - let _ = write!(&mut hex, ", {:.2}", a as f32 / 255.0); - } - - hex -} - -#[derive(Debug, Clone)] -pub struct Appearance { - pub selected: iced::Theme, - pub all: Arc<[iced::Theme]>, -} - -impl Default for Appearance { - fn default() -> Self { - Self { - selected: iced::Theme::default(), - all: iced::Theme::ALL.into(), - } - } -} - -#[derive(Debug, Default, serde::Deserialize)] -pub struct Theme { - palette: ThemePalette, - is_dark: Option<bool>, - #[serde(flatten)] - extended: Option<ExtendedThemePalette>, -} - -#[derive(Debug, Clone, 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 Theme { - pub fn into_iced_theme(self, name: String) -> iced::Theme { - iced::Theme::custom_with_fn(name, self.palette.clone().into(), |_| { - self.into() - }) - } -} - -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, - } - } -} - -impl From<Theme> for Extended { - fn from(theme: Theme) -> Self { - let mut extended = Extended::generate(theme.palette.into()); - - if let Some(is_dark) = theme.is_dark { - extended.is_dark = is_dark; - } - - if let Some(extended_palette) = theme.extended { - if let Some(background) = extended_palette.background { - if let Some(base) = background.base { - extended.background.base = base.into(); - } - if let Some(weak) = background.weak { - extended.background.weak = weak.into(); - } - if let Some(strong) = background.strong { - extended.background.strong = strong.into(); - } - } - - // Handle primary - if let Some(primary) = extended_palette.primary { - if let Some(base) = primary.base { - extended.primary.base = base.into(); - } - if let Some(weak) = primary.weak { - extended.primary.weak = weak.into(); - } - if let Some(strong) = primary.strong { - extended.primary.strong = strong.into(); - } - } - - // Handle secondary - if let Some(secondary) = extended_palette.secondary { - if let Some(base) = secondary.base { - extended.secondary.base = base.into(); - } - if let Some(weak) = secondary.weak { - extended.secondary.weak = weak.into(); - } - if let Some(strong) = secondary.strong { - extended.secondary.strong = strong.into(); - } - } - - // Handle success - if let Some(success) = extended_palette.success { - if let Some(base) = success.base { - extended.success.base = base.into(); - } - if let Some(weak) = success.weak { - extended.success.weak = weak.into(); - } - if let Some(strong) = success.strong { - extended.success.strong = strong.into(); - } - } - - // Handle danger - if let Some(danger) = extended_palette.danger { - if let Some(base) = danger.base { - extended.danger.base = base.into(); - } - if let Some(weak) = danger.weak { - extended.danger.weak = weak.into(); - } - if let Some(strong) = danger.strong { - extended.danger.strong = strong.into(); - } - } - } - - extended - } -} - -#[derive(Debug, Default, serde::Deserialize)] -struct ExtendedThemePalette { - background: Option<ThemeBackground>, - primary: Option<ThemePrimary>, - secondary: Option<ThemeSecondary>, - success: Option<ThemeSuccess>, - danger: Option<ThemeDanger>, -} - -#[derive(Debug, Default, serde::Deserialize)] -struct ThemeBackground { - base: Option<ThemePair>, - weak: Option<ThemePair>, - strong: Option<ThemePair>, -} - -#[derive(Debug, Default, serde::Deserialize)] -struct ThemePrimary { - base: Option<ThemePair>, - weak: Option<ThemePair>, - strong: Option<ThemePair>, -} - -#[derive(Debug, Default, serde::Deserialize)] -struct ThemeSecondary { - base: Option<ThemePair>, - weak: Option<ThemePair>, - strong: Option<ThemePair>, -} - -#[derive(Debug, Default, serde::Deserialize)] -struct ThemeSuccess { - base: Option<ThemePair>, - weak: Option<ThemePair>, - strong: Option<ThemePair>, -} - -#[derive(Debug, Default, serde::Deserialize)] -struct ThemeDanger { - base: Option<ThemePair>, - weak: Option<ThemePair>, - strong: Option<ThemePair>, -} - -#[derive(Debug, Default, serde::Deserialize)] -struct ThemePair { - #[serde(with = "color_serde")] - color: Color, - #[serde(with = "color_serde")] - text: Color, -} - -impl From<ThemePair> for iced::theme::palette::Pair { - fn from(pair: ThemePair) -> Self { - Self { - color: pair.color, - text: pair.text, - } - } -} - -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.rs b/iced_builder/src/types.rs deleted file mode 100644 index ac9d039..0000000 --- a/iced_builder/src/types.rs +++ /dev/null @@ -1,48 +0,0 @@ -pub mod element_name; -pub mod project; -pub mod rendered_element; - -use std::path::PathBuf; - -pub use element_name::ElementName; -use iced::widget::{pane_grid, text_editor}; -use iced::Theme; -use iced_anim::Event; -pub use project::Project; -pub use rendered_element::*; - -use crate::Error; - -#[derive(Debug, Clone)] -pub enum Message { - ToggleTheme(Event<Theme>), - CopyCode, - SwitchPage(DesignerPage), - EditorAction(text_editor::Action), - RefreshEditorContent, - DropNewElement(ElementName, iced::Point, iced::Rectangle), - HandleNew( - ElementName, - Vec<(iced::advanced::widget::Id, iced::Rectangle)>, - ), - MoveElement(RenderedElement, iced::Point, iced::Rectangle), - HandleMove( - RenderedElement, - Vec<(iced::advanced::widget::Id, iced::Rectangle)>, - ), - PaneResized(pane_grid::ResizeEvent), - PaneClicked(pane_grid::Pane), - PaneDragged(pane_grid::DragEvent), - NewFile, - OpenFile, - FileOpened(Result<(PathBuf, Project), Error>), - SaveFile, - SaveFileAs, - FileSaved(Result<PathBuf, Error>), -} - -#[derive(Debug, Clone)] -pub enum DesignerPage { - DesignerView, - CodeView, -} diff --git a/iced_builder/src/types/element_name.rs b/iced_builder/src/types/element_name.rs deleted file mode 100644 index 2687673..0000000 --- a/iced_builder/src/types/element_name.rs +++ /dev/null @@ -1,85 +0,0 @@ -use serde::{Deserialize, Serialize}; - -use super::rendered_element::{ - button, column, container, image, row, svg, text, Action, RenderedElement, -}; -use crate::Error; - -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] -pub enum ElementName { - Text(String), - Button(String), - Svg(String), - Image(String), - Container, - Row, - Column, -} - -impl ElementName { - pub const ALL: &'static [Self; 7] = &[ - Self::Text(String::new()), - Self::Button(String::new()), - Self::Svg(String::new()), - Self::Image(String::new()), - Self::Container, - Self::Row, - Self::Column, - ]; - - pub fn handle_action( - &self, - element_tree: Option<&mut RenderedElement>, - action: Action, - ) -> Result<Option<RenderedElement>, Error> { - let element = match self { - Self::Text(_) => text(""), - Self::Button(_) => button(""), - Self::Svg(_) => svg(""), - Self::Image(_) => image(""), - Self::Container => container(None), - Self::Row => row(None), - Self::Column => column(None), - }; - match action { - 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.")? - .find_by_id(id) - .ok_or(Error::NonExistentElement)? - .push_front(&element); - Ok(None) - } - Action::InsertAfter(parent_id, child_id) => { - element_tree - .ok_or( - "the action was of kind `InsertAfter`, but no element tree was provided.", - )? - .find_by_id(parent_id) - .ok_or(Error::NonExistentElement)? - .insert_after(child_id, &element); - Ok(None) - } - } - } -} - -impl std::fmt::Display for ElementName { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!( - f, - "{}", - match self { - Self::Text(_) => "Text", - Self::Button(_) => "Button", - Self::Svg(_) => "SVG", - Self::Image(_) => "Image", - Self::Container => "Container", - Self::Row => "Row", - Self::Column => "Column", - } - ) - } -} diff --git a/iced_builder/src/types/project.rs b/iced_builder/src/types/project.rs deleted file mode 100644 index 27c576b..0000000 --- a/iced_builder/src/types/project.rs +++ /dev/null @@ -1,165 +0,0 @@ -use std::path::{Path, PathBuf}; - -extern crate fxhash; -use fxhash::FxHashMap; -use iced::Theme; -use rust_format::{Edition, Formatter, RustFmt}; -use serde::{Deserialize, Serialize}; - -use super::rendered_element::RenderedElement; -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 { - pub fn new() -> Self { - Self { - title: None, - theme: None, - element_tree: None, - theme_cache: FxHashMap::default(), - } - } - - pub fn get_theme(&self, config: &Config) -> Theme { - match &self.theme { - 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( - 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"]) - .pick_file() - .await - .ok_or(Error::DialogClosed)?; - - let path = picked_file.path().to_owned(); - - Self::from_path(path, config).await - } - - pub async fn write_to_file( - self, - path: Option<PathBuf>, - ) -> Result<PathBuf, Error> { - let path = if let Some(p) = path { - p - } else { - rfd::AsyncFileDialog::new() - .set_title("Save to JSON file...") - .add_filter("*.json, *.JSON", &["json", "JSON"]) - .save_file() - .await - .as_ref() - .map(rfd::FileHandle::path) - .map(Path::to_owned) - .ok_or(Error::DialogClosed)? - }; - - let contents = serde_json::to_string(&self)?; - tokio::fs::write(&path, contents).await?; - - Ok(path) - } - - 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 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 - use iced::{{widget::{{{imports}}},Element}}; - {theme_imports} - - fn main() -> iced::Result {{ - iced::application("{}", State::update, State::view).theme(State::theme).run() - }} - - #[derive(Default)] - struct State; - - #[derive(Debug, Clone)] - enum Message {{}} - - impl State {{ - fn update(&mut self, _message: Message) {{}} - - fn theme(&self) -> iced::Theme {{ - iced::Theme::{} - }} - - fn view(&self) -> Element<Message> {{ - {view}.into() - }} - }}"#, - match self.title { - Some(ref t) => t, - None => "New app", - }, - theme_code - ); - let config = rust_format::Config::new_str() - .edition(Edition::Rust2021) - .option("trailing_comma", "Never") - .option("imports_granularity", "Crate"); - let rustfmt = RustFmt::from_config(config); - Ok(rustfmt.format_str(app_code)?) - } - None => Err("No element tree present".into()), - } - } -} diff --git a/iced_builder/src/types/rendered_element.rs b/iced_builder/src/types/rendered_element.rs deleted file mode 100755 index b001556..0000000 --- a/iced_builder/src/types/rendered_element.rs +++ /dev/null @@ -1,468 +0,0 @@ -use std::collections::BTreeMap; - -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::Error; - -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] -pub struct RenderedElement { - #[serde(skip, default = "Uuid::new_v4")] - id: Uuid, - child_elements: Option<Vec<RenderedElement>>, - name: ElementName, - options: BTreeMap<String, Option<String>>, -} - -impl RenderedElement { - fn new(name: ElementName) -> Self { - Self { - id: Uuid::new_v4(), - child_elements: None, - name, - options: BTreeMap::new(), - } - } - - fn with(name: ElementName, child_elements: Vec<RenderedElement>) -> Self { - Self { - id: Uuid::new_v4(), - child_elements: Some(child_elements), - name, - options: BTreeMap::new(), - } - } - - pub fn get_id(&self) -> Id { - Id::new(self.id.to_string()) - } - - 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); - if element.is_some() { - return element; - } - } - None - } else { - None - } - } - - pub fn find_parent( - &mut self, - child_element: &RenderedElement, - ) -> Option<&mut Self> { - if child_element == self { - return Some(self); - } else if self.child_elements.is_some() { - if self - .child_elements - .clone() - .unwrap_or_default() - .contains(child_element) - { - return Some(self); - } - 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; - } - } - } - } - None - } - - pub fn is_parent(&self) -> bool { - self.child_elements.is_some() - } - - pub fn is_empty(&self) -> bool { - self.child_elements == Some(vec![]) - } - - pub fn remove(&mut self, element: &RenderedElement) { - let parent = self.find_parent(element).unwrap(); - if let Some(child_elements) = parent.child_elements.as_mut() { - if let Some(index) = - child_elements.iter().position(|x| x == element) - { - let _ = child_elements.remove(index); - } - } - } - - pub fn push_front(&mut self, element: &RenderedElement) { - if let Some(child_elements) = self.child_elements.as_mut() { - child_elements.insert(0, element.clone()); - } - } - - 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.insert(index + 1, element.clone()); - } else { - child_elements.push(element.clone()); - } - } - } - - pub fn handle_action( - &self, - element_tree: Option<&mut RenderedElement>, - action: Action, - ) -> Result<(), Error> { - let element_tree = element_tree.unwrap(); - - match action { - Action::Stop => Ok(()), - Action::Drop => { - element_tree.remove(self); - - Ok(()) - } - Action::AddNew => Err( - "the action was of kind `AddNew`, but invoking it on an existing element tree is not possible".into(), - ), - Action::PushFront(id) => { - element_tree.remove(self); - - let new_parent = element_tree.find_by_id(id).unwrap(); - new_parent.push_front(self); - - Ok(()) - } - Action::InsertAfter(parent_id, target_id) => { - element_tree.remove(self); - - let new_parent = element_tree.find_by_id(parent_id).unwrap(); - new_parent.insert_after(target_id, self); - - Ok(()) - } - } - } - - fn preset_options(mut self, options: &[&str]) -> Self { - for opt in options { - let _ = self.options.insert(opt.to_string(), None); - } - self - } - - pub fn option<'a>(mut self, option: &'a str, value: &'a str) -> Self { - let _ = self - .options - .entry(option.to_owned()) - .and_modify(|opt| *opt = Some(value.to_owned())); - self - } - - 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().into_element()); - } - } - iced_drop::droppable( - widget::container( - widget::column![ - widget::text(self.name.clone().to_string()), - children - ] - .width(Length::Fill) - .spacing(10), - ) - .padding(10) - .style(widget::container::bordered_box), - ) - .id(self.get_id()) - .drag_hide(true) - .on_drop(move |point, rect| { - Message::MoveElement(self.clone(), point, rect) - }) - .into() - } - - pub fn codegen(&self) -> (String, String) { - let mut imports = String::new(); - let mut view = String::new(); - let mut options = String::new(); - - for (k, v) in self.options.clone() { - if let Some(v) = v { - options = format!("{options}.{k}({v})"); - } - } - - let mut elements = String::new(); - - if let Some(els) = &self.child_elements { - for element in els { - let (c_imports, children) = element.codegen(); - imports = format!("{imports}{c_imports}"); - elements = format!("{elements}{children},"); - } - } - - match &self.name { - ElementName::Container => { - imports = format!("{imports}container,"); - view = format!("{view}\ncontainer({elements}){options}"); - } - ElementName::Row => { - imports = format!("{imports}row,"); - view = format!("{view}\nrow![{elements}]{options}"); - } - ElementName::Column => { - imports = format!("{imports}column,"); - view = format!("{view}\ncolumn![{elements}]{options}"); - } - ElementName::Text(string) => { - imports = format!("{imports}text,"); - view = format!( - "{view}\ntext(\"{}\"){options}", - if *string == String::new() { - "New Text" - } else { - string - } - ); - } - ElementName::Button(string) => { - imports = format!("{imports}button,"); - view = format!( - "{view}\nbutton(\"{}\"){options}", - if *string == String::new() { - "New Button" - } else { - string - } - ); - } - ElementName::Image(path) => { - imports = format!("{imports}image,"); - view = format!("{view}\nimage(\"{path}\"){options}"); - } - ElementName::Svg(path) => { - imports = format!("{imports}svg,"); - view = format!("{view}\nsvg(\"{path}\"){options}"); - } - } - - (imports, view) - } -} - -impl std::fmt::Display for RenderedElement { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - let mut has_options = false; - f.pad("")?; - f.write_fmt(format_args!("{:?}\n", self.name))?; - f.pad("")?; - f.write_str("Options: (")?; - for (k, v) in &self.options { - if let Some(value) = v { - has_options = true; - f.write_fmt(format_args!( - "\n{:width$.precision$}{}: {}", - "", - k, - value, - width = f.width().unwrap_or(0) + f.precision().unwrap_or(0), - precision = f.precision().unwrap_or(0) - ))?; - } - } - if has_options { - f.write_str("\n")?; - f.pad("")?; - } - f.write_str(")")?; - if let Some(els) = &self.child_elements { - f.write_str(" {\n")?; - for el in els { - f.write_fmt(format_args!( - "\n{:width$.precision$}\n", - el, - width = f.width().unwrap_or(0) + f.precision().unwrap_or(0), - precision = f.precision().unwrap_or(0) - ))?; - } - f.pad("")?; - f.write_str("}")?; - } - Ok(()) - } -} - -impl<'a> From<RenderedElement> for Element<'a, Message> { - fn from(value: RenderedElement) -> Self { - let child_elements = match value.child_elements { - Some(ref elements) => elements.clone(), - None => vec![], - }; - - let content: Element<'a, Message> = match value.name.clone() { - ElementName::Text(s) => { - if s == String::new() { - widget::text("New Text").into() - } else { - widget::text(s).into() - } - } - ElementName::Button(s) => { - if s == String::new() { - widget::button(widget::text("New Button")).into() - } else { - widget::button(widget::text(s)).into() - } - } - ElementName::Svg(p) => widget::svg(p).into(), - ElementName::Image(p) => widget::image(p).into(), - ElementName::Container => { - widget::container(if child_elements.len() == 1 { - child_elements[0].clone().into() - } else { - Element::from("") - }) - .padding(20) - .into() - } - ElementName::Row => widget::Row::from_vec( - child_elements.into_iter().map(Into::into).collect(), - ) - .padding(20) - .into(), - ElementName::Column => widget::Column::from_vec( - child_elements.into_iter().map(Into::into).collect(), - ) - .padding(20) - .into(), - }; - iced_drop::droppable(content) - .id(value.get_id()) - .drag_hide(true) - .on_drop(move |point, rect| { - Message::MoveElement(value.clone(), point, rect) - }) - .into() - } -} - -#[derive(Debug, Clone)] -pub enum Action<'a> { - AddNew, - PushFront(&'a Id), - InsertAfter(&'a Id, &'a Id), - Drop, - Stop, -} - -impl<'a> Action<'a> { - pub fn new( - ids: &'a [Id], - element_tree: &'a Option<RenderedElement>, - source_id: Option<Id>, - ) -> Self { - let mut action = Self::Stop; - if ids.len() == 1 { - if element_tree.is_none() { - action = Self::AddNew; - } else { - action = Self::Drop; - } - } else { - let id: &Id = match source_id { - Some(id) if ids.contains(&id) => { - let 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().unwrap(), - }; - 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() - { - 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 - } -} - -pub fn text(text: &str) -> RenderedElement { - RenderedElement::new(ElementName::Text(text.to_owned())).preset_options(&[ - "size", - "line_height", - "width", - "height", - ]) -} - -pub fn button(text: &str) -> RenderedElement { - RenderedElement::new(ElementName::Button(text.to_owned())) -} - -pub fn svg(path: &str) -> RenderedElement { - RenderedElement::new(ElementName::Svg(path.to_owned())) -} - -pub fn image(path: &str) -> RenderedElement { - RenderedElement::new(ElementName::Image(path.to_owned())) -} - -pub fn container(content: Option<RenderedElement>) -> RenderedElement { - match content { - Some(el) => RenderedElement::with(ElementName::Container, vec![el]), - None => RenderedElement::with(ElementName::Container, vec![]), - } -} - -pub fn row(child_elements: Option<Vec<RenderedElement>>) -> RenderedElement { - RenderedElement::with(ElementName::Row, child_elements.unwrap_or_default()) -} - -pub fn column(child_elements: Option<Vec<RenderedElement>>) -> RenderedElement { - RenderedElement::with( - ElementName::Column, - child_elements.unwrap_or_default(), - ) -} diff --git a/iced_builder/src/widget.rs b/iced_builder/src/widget.rs deleted file mode 100644 index ed2073a..0000000 --- a/iced_builder/src/widget.rs +++ /dev/null @@ -1,21 +0,0 @@ -use iced::widget::{container, text, tooltip}; -use iced::Element; - -pub mod tip { - pub use super::tooltip::Position; -} - -pub fn tip<'a, Message: 'a>( - target: impl Into<Element<'a, Message>>, - tip: &'a str, - position: tip::Position, -) -> Element<'a, Message> { - tooltip( - target, - container(text(tip).size(14)) - .padding(5) - .style(container::rounded_box), - position, - ) - .into() -} |
