summaryrefslogtreecommitdiff
path: root/iced_builder
diff options
context:
space:
mode:
Diffstat (limited to 'iced_builder')
-rw-r--r--iced_builder/Cargo.toml64
-rw-r--r--iced_builder/assets/config.toml1
-rw-r--r--iced_builder/assets/themes/Rose Pine.toml33
-rw-r--r--iced_builder/assets/windows/iced_builder.manifest8
-rw-r--r--iced_builder/assets/windows/iced_builder.rc3
-rw-r--r--iced_builder/build.rs12
-rw-r--r--iced_builder/fonts/icons.toml6
-rw-r--r--iced_builder/fonts/icons.ttfbin6348 -> 0 bytes
-rw-r--r--iced_builder/rustfmt.toml4
-rw-r--r--iced_builder/src/config.rs121
-rw-r--r--iced_builder/src/dialogs.rs30
-rw-r--r--iced_builder/src/environment.rs43
-rw-r--r--iced_builder/src/error.rs55
-rw-r--r--iced_builder/src/icon.rs23
-rw-r--r--iced_builder/src/main.rs382
-rw-r--r--iced_builder/src/panes.rs4
-rw-r--r--iced_builder/src/panes/code_view.rs50
-rw-r--r--iced_builder/src/panes/designer_view.rs37
-rw-r--r--iced_builder/src/panes/element_list.rs49
-rw-r--r--iced_builder/src/panes/style.rs40
-rw-r--r--iced_builder/src/theme.rs381
-rw-r--r--iced_builder/src/types.rs48
-rw-r--r--iced_builder/src/types/element_name.rs85
-rw-r--r--iced_builder/src/types/project.rs165
-rwxr-xr-xiced_builder/src/types/rendered_element.rs468
-rw-r--r--iced_builder/src/widget.rs21
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
deleted file mode 100644
index 7af6b0e..0000000
--- a/iced_builder/fonts/icons.ttf
+++ /dev/null
Binary files differ
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()
-}