summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorpml68 <contact@pml68.me>2024-10-20 11:51:54 +0200
committerpml68 <contact@pml68.me>2024-10-21 11:57:56 +0200
commit7d241eb768a3c56d98f5d41183866fffb9ff167a (patch)
treea47571c3ea86983050d268fade3c3101da4f7ed7
parentfeat: update to iced 0.13.1, basic project state file, prepare for drag&drop (diff)
downloadiced-builder-7d241eb768a3c56d98f5d41183866fffb9ff167a.tar.gz
feat: implement d&d for new elements
`ActionKind` enum to help generalize d&d actions removed `codegen` module small refactors
Diffstat (limited to '')
-rw-r--r--Cargo.lock2
-rw-r--r--iced_builder/Cargo.toml1
-rw-r--r--iced_builder/src/codegen/mod.rs146
-rw-r--r--iced_builder/src/lib.rs17
-rw-r--r--iced_builder/src/main.rs68
-rw-r--r--iced_builder/src/types/element_name.rs90
-rw-r--r--iced_builder/src/types/mod.rs44
-rw-r--r--iced_builder/src/types/project.rs137
-rw-r--r--iced_builder/src/types/project/mod.rs59
-rw-r--r--iced_builder/src/types/rendered_element.rs206
10 files changed, 467 insertions, 303 deletions
diff --git a/Cargo.lock b/Cargo.lock
index a341d35..d65a742 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -1588,6 +1588,7 @@ dependencies = [
"iced",
"iced_aw",
"iced_drop",
+ "indexmap",
"rfd",
"rust-format",
"serde",
@@ -1838,6 +1839,7 @@ checksum = "707907fe3c25f5424cce2cb7e1cbcafee6bdbe735ca90ef77c29e84591e5b9da"
dependencies = [
"equivalent",
"hashbrown 0.15.0",
+ "serde",
]
[[package]]
diff --git a/iced_builder/Cargo.toml b/iced_builder/Cargo.toml
index a7b1e7b..d788bc2 100644
--- a/iced_builder/Cargo.toml
+++ b/iced_builder/Cargo.toml
@@ -18,6 +18,7 @@ tokio = { version = "1.40.0", features = ["fs"] }
rfd = "0.15.0"
rust-format = "0.3.4"
unique_id = "0.1.5"
+indexmap = { version = "2.6.0", features = ["serde"] }
[[bin]]
name = "iced-builder"
diff --git a/iced_builder/src/codegen/mod.rs b/iced_builder/src/codegen/mod.rs
deleted file mode 100644
index 38e8ec3..0000000
--- a/iced_builder/src/codegen/mod.rs
+++ /dev/null
@@ -1,146 +0,0 @@
-use rust_format::{Config, Edition, Formatter, RustFmt};
-
-use crate::{
- types::{
- project::Project,
- rendered_element::{container, row, svg, text, RenderedElement},
- ElementName,
- },
- Error,
-};
-
-impl RenderedElement {
- fn props_codegen(&self) -> String {
- let mut props_string = String::new();
-
- for (k, v) in self.props.clone() {
- if let Some(v) = v {
- props_string = format!("{props_string}.{k}({v})");
- }
- }
-
- props_string
- }
-
- fn codegen(&self) -> (String, String) {
- let mut imports = String::new();
- let mut view = String::new();
- let props = self.props_codegen();
-
- 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}){props}");
- }
- ElementName::Row => {
- imports = format!("{imports}row,");
- view = format!("{view}\nrow![{elements}]{props}");
- }
- ElementName::Column => {
- imports = format!("{imports}column,");
- view = format!("{view}\ncolumn![{elements}]{props}");
- }
- ElementName::Text(string) => {
- imports = format!("{imports}text,");
- view = format!(
- "{view}\ntext(\"{}\"){props}",
- if *string == String::new() {
- "New Text"
- } else {
- string
- }
- );
- }
- ElementName::Button(string) => {
- imports = format!("{imports}button,");
- view = format!(
- "{view}\nbutton(\"{}\"){props}",
- if *string == String::new() {
- "New Button"
- } else {
- string
- }
- );
- }
- ElementName::Image(path) => {
- imports = format!("{imports}image,");
- view = format!("{view}\nimage(\"{path}\"){props}");
- }
- ElementName::SVG(path) => {
- imports = format!("{imports}svg,");
- view = format!("{view}\nsvg(\"{path}\"){props}");
- }
- }
-
- (imports, view)
- }
-
- pub fn test() -> RenderedElement {
- let mut text1 = text("wow");
- text1.option("height", "120.5");
- text1.option("width", "230");
-
- let element = container(Some(row(Some(vec![
- text1,
- text("heh"),
- svg("/mnt/drive_d/git/obs-website/src/lib/assets/bars-solid.svg"),
- ]))));
-
- element
- }
-}
-
-impl Project {
- pub fn app_code(self) -> Result<String, Error> {
- match &self.content {
- Some(el) => {
- let (imports, view) = el.codegen();
- let mut app_code = format!("use iced::{{widget::{{{imports}}},Element}};");
-
- app_code = format!(
- r#"// Automatically generated by iced Builder
- {app_code}
-
- fn main() -> iced::Result {{
- iced::run("{}", State::update, State::view)
- }}
-
- #[derive(Default)]
- struct State;
-
- #[derive(Debug, Clone)]
- enum Message {{}}
-
- impl State {{
- fn update(&mut self, _message: Message) {{}}
-
- fn view(&self) -> Element<Message> {{
- {view}.into()
- }}
- }}"#,
- match &self.title {
- Some(t) => t,
- None => "New app",
- }
- );
- let config = 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/lib.rs b/iced_builder/src/lib.rs
index 971e0e3..6de9ba8 100644
--- a/iced_builder/src/lib.rs
+++ b/iced_builder/src/lib.rs
@@ -1,16 +1,18 @@
-pub mod codegen;
pub mod types;
use std::path::PathBuf;
use iced::widget::{pane_grid, text_editor};
-use types::{project::Project, rendered_element::RenderedElement, DesignerPage};
+use types::{
+ element_name::ElementName, project::Project, rendered_element::RenderedElement, DesignerPage,
+};
#[derive(Debug, Clone)]
pub enum Error {
IOError(std::io::ErrorKind),
SerdeError(String),
FormatError(String),
+ NonExistentElement,
DialogClosed,
String(String),
}
@@ -24,6 +26,9 @@ impl std::fmt::Display for Error {
Self::IOError(kind) => {
write!(f, "{}", kind)
}
+ Self::NonExistentElement => {
+ write!(f, "The element tree contains no matching element.")
+ }
Self::DialogClosed => {
write!(
f,
@@ -64,9 +69,9 @@ pub enum Message {
CopyCode,
SwitchPage(DesignerPage),
EditorAction(text_editor::Action),
- DropNewElement(types::ElementName, iced::Point, iced::Rectangle),
+ DropNewElement(ElementName, iced::Point, iced::Rectangle),
HandleNew(
- types::ElementName,
+ ElementName,
Vec<(iced::advanced::widget::Id, iced::Rectangle)>,
),
MoveElement(RenderedElement, iced::Point, iced::Rectangle),
@@ -74,8 +79,8 @@ pub enum Message {
RenderedElement,
Vec<(iced::advanced::widget::Id, iced::Rectangle)>,
),
- Resized(pane_grid::ResizeEvent),
- Clicked(pane_grid::Pane),
+ PaneResized(pane_grid::ResizeEvent),
+ PaneClicked(pane_grid::Pane),
PaneDragged(pane_grid::DragEvent),
NewFile,
OpenFile,
diff --git a/iced_builder/src/main.rs b/iced_builder/src/main.rs
index aeb5ea6..8efad3e 100644
--- a/iced_builder/src/main.rs
+++ b/iced_builder/src/main.rs
@@ -1,15 +1,18 @@
use std::path::PathBuf;
use iced::{
+ advanced::widget::Id,
clipboard, highlighter, keyboard,
widget::{
button, column, container,
pane_grid::{self, Pane, PaneGrid},
- row, text, text_editor, tooltip, Column, Space,
+ row, text, text_editor, themer, tooltip, Column, Space,
},
Alignment, Element, Font, Length, Settings, Task, Theme,
};
-use iced_builder::types::{project::Project, DesignerPage, ElementName};
+use iced_builder::types::{
+ element_name::ElementName, project::Project, rendered_element::ActionKind, DesignerPage,
+};
use iced_builder::Message;
use iced_drop::droppable;
@@ -37,7 +40,7 @@ struct App {
editor_content: text_editor::Content,
}
-#[derive(Clone, Debug)]
+#[derive(Clone, Copy, Debug)]
enum Panes {
Designer,
ElementList,
@@ -106,12 +109,6 @@ impl App {
self.editor_content.perform(action);
}
}
- Message::Resized(pane_grid::ResizeEvent { split, ratio }) => {
- self.pane_state.resize(split, ratio);
- }
- Message::Clicked(pane) => {
- self.focus = Some(pane);
- }
Message::DropNewElement(name, point, _) => {
return iced_drop::zones_on_point(
move |zones| Message::HandleNew(name.clone(), zones),
@@ -122,7 +119,16 @@ impl App {
.into()
}
Message::HandleNew(name, zones) => {
- println!("\n\n{:?}\n{name}\n{:?}", zones, self.title());
+ //println!("\n\n{:?}\n{name}\n{:?}", zones, self.title());
+ let ids: Vec<Id> = zones.into_iter().map(|z| z.0).collect();
+ if ids.len() > 0 {
+ let action = ActionKind::new(ids, &mut self.project.content.clone(), None);
+ let result = name.handle_action(self.project.content.as_mut(), action);
+ if let Ok(Some(ref element)) = result {
+ self.project.content = Some(element.clone());
+ }
+ println!("{:?}", result);
+ }
let code = self
.project
.clone()
@@ -140,13 +146,30 @@ impl App {
.into()
}
Message::HandleMove(element, zones) => {
- println!(
- "\n\n{:?}\n{element:0.4}",
- zones
- .into_iter()
- .map(|c| c.0)
- .collect::<Vec<iced::advanced::widget::Id>>()
- );
+ let ids: Vec<Id> = zones.into_iter().map(|z| z.0).collect();
+ if ids.len() > 0 {
+ println!(
+ "{:?}",
+ ActionKind::new(
+ ids,
+ &mut self.project.content.clone(),
+ Some(element.get_id())
+ )
+ );
+ }
+ //println!(
+ // "\n\n{:?}\n{element:0.4}",
+ // zones
+ // .into_iter()
+ // .map(|c| c.0)
+ // .collect::<Vec<iced::advanced::widget::Id>>()
+ //);
+ }
+ 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);
@@ -207,6 +230,7 @@ impl App {
keyboard::on_key_press(|key, modifiers| match key.as_ref() {
keyboard::Key::Character("o") if modifiers.command() => Some(Message::OpenFile),
keyboard::Key::Character("s") if modifiers.command() => Some(Message::SaveFile),
+ keyboard::Key::Character("n") if modifiers.command() => Some(Message::NewFile),
_ => None,
})
}
@@ -225,7 +249,7 @@ impl App {
Some(tree) => tree.as_element(),
None => text("Open a project or begin creating one").into(),
};
- let content = container(el_tree)
+ let content = container(themer(self.project.get_theme(), el_tree))
.id(iced::widget::container::Id::new("drop_zone"))
.height(Length::Fill)
.width(Length::Fill);
@@ -315,8 +339,8 @@ impl App {
.width(Length::Fill)
.height(Length::Fill)
.spacing(10)
- .on_resize(10, Message::Resized)
- .on_click(Message::Clicked)
+ .on_resize(10, Message::PaneResized)
+ .on_click(Message::PaneClicked)
.on_drag(Message::PaneDragged);
let content = Column::new()
@@ -348,8 +372,8 @@ fn items_list_view<'a>(items: Vec<ElementName>) -> Element<'a, Message> {
}
mod style {
- use iced::widget::{container::Style as CStyle, text::Style as TStyle};
- use iced::{color, Border, Theme};
+ use iced::widget::container::Style as CStyle;
+ use iced::{Border, Theme};
pub fn title_bar(theme: &Theme) -> CStyle {
let palette = theme.extended_palette();
diff --git a/iced_builder/src/types/element_name.rs b/iced_builder/src/types/element_name.rs
new file mode 100644
index 0000000..ca0668c
--- /dev/null
+++ b/iced_builder/src/types/element_name.rs
@@ -0,0 +1,90 @@
+use serde::{Deserialize, Serialize};
+
+use crate::Error;
+
+use super::rendered_element::{
+ self, button, column, container, image, row, svg, text, ActionKind, RenderedElement,
+};
+
+#[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: [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: ActionKind,
+ ) -> 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 {
+ ActionKind::Stop => Ok(None),
+ ActionKind::AddNew => Ok(Some(element)),
+ ActionKind::PushFront(id) => {
+ element_tree
+ .ok_or(Error::String(
+ "The action was of kind `PushFront`, but no element tree was provided."
+ .to_owned(),
+ ))?
+ .find_by_id(id)
+ .ok_or(Error::NonExistentElement)?
+ .push_front(&element);
+ Ok(None)
+ }
+ ActionKind::InsertAfter(parent_id, child_id) => {
+ element_tree
+ .ok_or(Error::String(
+ "The action was of kind `InsertAfter`, but no element tree was provided."
+ .to_owned(),
+ ))?
+ .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/mod.rs b/iced_builder/src/types/mod.rs
index 2d6cd8f..a48a2d8 100644
--- a/iced_builder/src/types/mod.rs
+++ b/iced_builder/src/types/mod.rs
@@ -1,49 +1,7 @@
+pub mod element_name;
pub mod project;
pub mod rendered_element;
-use serde::{Deserialize, Serialize};
-
-#[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: [Self; 7] = [
- Self::Text(String::new()),
- Self::Button(String::new()),
- Self::SVG(String::new()),
- Self::Image(String::new()),
- Self::Container,
- Self::Row,
- Self::Column,
- ];
-}
-
-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",
- }
- )
- }
-}
-
#[derive(Debug, Clone)]
pub enum DesignerPage {
Designer,
diff --git a/iced_builder/src/types/project.rs b/iced_builder/src/types/project.rs
new file mode 100644
index 0000000..0e0442a
--- /dev/null
+++ b/iced_builder/src/types/project.rs
@@ -0,0 +1,137 @@
+use rust_format::{Config, Edition, Formatter, RustFmt};
+use std::path::{Path, PathBuf};
+
+use iced::Theme;
+use serde::{Deserialize, Serialize};
+
+use crate::Error;
+
+use super::rendered_element::RenderedElement;
+
+#[derive(Debug, Clone, Serialize, Deserialize)]
+pub struct Project {
+ pub title: Option<String>,
+ pub theme: Option<String>,
+ pub content: Option<RenderedElement>,
+}
+
+impl Project {
+ pub fn new() -> Self {
+ Self {
+ title: None,
+ theme: None,
+ content: None,
+ }
+ }
+
+ pub fn get_theme(&self) -> Theme {
+ match &self.theme {
+ Some(theme) => match theme.as_str() {
+ "Light" => Theme::Light,
+ "Dark" => Theme::Dark,
+ "Dracula" => Theme::Dracula,
+ "Nord" => Theme::Nord,
+ "Solarized Light" => Theme::SolarizedLight,
+ "Solarized Dark" => Theme::SolarizedDark,
+ "Gruvbox Light" => Theme::GruvboxLight,
+ "Gruvbox Dark" => Theme::GruvboxDark,
+ "Catppuccin Latte" => Theme::CatppuccinLatte,
+ "Catppuccin Frappé" => Theme::CatppuccinFrappe,
+ "Catppuccin Macchiato" => Theme::CatppuccinMacchiato,
+ "Catppuccin Mocha" => Theme::CatppuccinMocha,
+ "Tokyo Night" => Theme::TokyoNight,
+ "Tokyo Night Storm" => Theme::TokyoNightStorm,
+ "Tokyo Night Light" => Theme::TokyoNightLight,
+ "Kanagawa Wave" => Theme::KanagawaWave,
+ "Kanagawa Dragon" => Theme::KanagawaDragon,
+ "Kanagawa Lotus" => Theme::KanagawaLotus,
+ "Moonfly" => Theme::Moonfly,
+ "Nightfly" => Theme::Nightfly,
+ "Oxocarbon" => Theme::Oxocarbon,
+ "Ferra" => Theme::Ferra,
+ _ => Theme::Dark,
+ },
+ None => Theme::Dark,
+ }
+ }
+
+ pub async fn from_file() -> 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();
+
+ let contents = tokio::fs::read_to_string(&path).await?;
+ let element: Self = serde_json::from_str(&contents)?;
+
+ Ok((path, element))
+ }
+
+ 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.clone())?;
+ tokio::fs::write(&path, contents).await?;
+
+ Ok(path)
+ }
+
+ pub fn app_code(self) -> Result<String, Error> {
+ match &self.content {
+ Some(el) => {
+ let (imports, view) = el.codegen();
+ let mut app_code = format!("use iced::{{widget::{{{imports}}},Element}};");
+
+ app_code = format!(
+ r#"// Automatically generated by iced Builder
+ {app_code}
+
+ fn main() -> iced::Result {{
+ iced::run("{}", State::update, State::view)
+ }}
+
+ #[derive(Default)]
+ struct State;
+
+ #[derive(Debug, Clone)]
+ enum Message {{}}
+
+ impl State {{
+ fn update(&mut self, _message: Message) {{}}
+
+ fn view(&self) -> Element<Message> {{
+ {view}.into()
+ }}
+ }}"#,
+ match &self.title {
+ Some(t) => t,
+ None => "New app",
+ }
+ );
+ let config = 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/project/mod.rs b/iced_builder/src/types/project/mod.rs
deleted file mode 100644
index 557fa92..0000000
--- a/iced_builder/src/types/project/mod.rs
+++ /dev/null
@@ -1,59 +0,0 @@
-use std::path::{Path, PathBuf};
-
-use serde::{Deserialize, Serialize};
-
-use crate::Error;
-
-use super::rendered_element::RenderedElement;
-
-#[derive(Debug, Clone, Serialize, Deserialize)]
-pub struct Project {
- pub title: Option<String>,
- pub content: Option<RenderedElement>,
-}
-
-impl Project {
- pub fn new() -> Self {
- Self {
- title: None,
- content: None,
- }
- }
-
- pub async fn from_file() -> 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();
-
- let contents = tokio::fs::read_to_string(&path).await?;
- let element: Self = serde_json::from_str(&contents)?;
-
- Ok((path, element))
- }
-
- 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.clone())?;
- tokio::fs::write(&path, contents).await?;
-
- Ok(path)
- }
-}
diff --git a/iced_builder/src/types/rendered_element.rs b/iced_builder/src/types/rendered_element.rs
index 967352b..827e8c2 100644
--- a/iced_builder/src/types/rendered_element.rs
+++ b/iced_builder/src/types/rendered_element.rs
@@ -1,4 +1,4 @@
-use std::collections::HashMap;
+use indexmap::IndexMap;
use iced::advanced::widget::Id;
use iced::{widget, Element, Length};
@@ -7,14 +7,14 @@ use unique_id::{string::StringGenerator, Generator};
use crate::Message;
-use super::ElementName;
+use super::element_name::ElementName;
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct RenderedElement {
- pub id: String,
+ id: String,
pub child_elements: Option<Vec<RenderedElement>>,
pub name: ElementName,
- pub props: HashMap<String, Option<String>>,
+ pub options: IndexMap<String, Option<String>>,
}
impl RenderedElement {
@@ -24,22 +24,26 @@ impl RenderedElement {
id: gen.next_id(),
child_elements: None,
name,
- props: HashMap::new(),
+ options: IndexMap::new(),
}
}
- fn from_vec(name: ElementName, child_elements: Vec<RenderedElement>) -> Self {
+ fn with(name: ElementName, child_elements: Vec<RenderedElement>) -> Self {
let gen = StringGenerator::default();
Self {
id: gen.next_id(),
child_elements: Some(child_elements),
name,
- props: HashMap::new(),
+ options: IndexMap::new(),
}
}
+ pub fn get_id(&self) -> Id {
+ Id::new(self.id.clone())
+ }
+
pub fn find_by_id(&mut self, id: Id) -> Option<&mut Self> {
- if Id::new(self.id.clone()) == id.clone() {
+ if self.get_id() == id.clone() {
println!("");
return Some(self);
} else if let Some(child_elements) = self.child_elements.as_mut() {
@@ -59,12 +63,17 @@ impl RenderedElement {
if child_element == self {
return Some(self);
} else if self.child_elements.is_some() {
- if self.child_elements.clone()?.contains(child_element) {
+ if self
+ .child_elements
+ .clone()
+ .unwrap_or(vec![])
+ .contains(child_element)
+ {
return Some(self);
} else {
if let Some(child_elements) = self.child_elements.as_mut() {
for element in child_elements {
- let element: Option<&mut Self> = element.find_parent(child_element);
+ let element = element.find_parent(child_element);
if element.is_some() {
return element;
}
@@ -77,6 +86,10 @@ impl RenderedElement {
}
}
+ pub fn is_parent(&self) -> bool {
+ self.child_elements.is_some()
+ }
+
pub fn remove(&mut self, element: &RenderedElement) {
let parent = self.find_parent(element);
if let Some(child_elements) = parent.unwrap().child_elements.as_mut() {
@@ -86,39 +99,39 @@ impl RenderedElement {
}
}
- pub fn push(&mut self, element: RenderedElement) {
+ pub fn push_front(&mut self, element: &RenderedElement) {
if let Some(child_elements) = self.child_elements.as_mut() {
- child_elements.push(element);
+ child_elements.insert(0, element.clone());
}
}
- pub fn insert_after(&mut self, id: Id, element: RenderedElement) {
+ pub fn insert_after(&mut self, id: Id, element: &RenderedElement) {
if let Some(child_elements) = self.child_elements.as_mut() {
if let Some(index) = child_elements
.iter()
.position(|x| Id::new(x.id.clone()) == id)
{
- child_elements.insert(index, element);
+ child_elements.insert(index + 1, element.clone());
} else {
- child_elements.push(element);
+ child_elements.push(element.clone());
}
}
}
fn preset_options(mut self, options: Vec<&str>) -> Self {
for opt in options {
- self.props.insert(opt.to_owned(), None);
+ self.options.insert(opt.to_owned(), None);
}
self
}
- pub fn option(&mut self, option: &'static str, value: &'static str) {
- self.props
+ pub fn option<'a>(&mut self, option: &'a str, value: &'a str) {
+ self.options
.entry(option.to_owned())
.and_modify(|opt| *opt = Some(value.to_owned()));
}
- pub fn as_element(self) -> Element<'static, Message> {
+ pub fn as_element<'a>(self) -> Element<'a, Message> {
let mut children = widget::column![];
if let Some(els) = self.child_elements.clone() {
@@ -131,12 +144,103 @@ impl RenderedElement {
widget::column![widget::text(self.name.clone().to_string()), children]
.width(Length::Fill),
)
+ .padding(10)
.style(widget::container::bordered_box),
)
- .id(Id::new(self.id.clone()))
+ .id(self.get_id())
+ .drag_hide(true)
.on_drop(move |point, rect| Message::MoveElement(self.clone(), point, rect))
.into()
}
+
+ fn props_codegen(&self) -> String {
+ let mut props_string = String::new();
+
+ for (k, v) in self.options.clone() {
+ if let Some(v) = v {
+ props_string = format!("{props_string}.{k}({v})");
+ }
+ }
+
+ props_string
+ }
+
+ pub fn codegen(&self) -> (String, String) {
+ let mut imports = String::new();
+ let mut view = String::new();
+ let props = self.props_codegen();
+
+ 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}){props}");
+ }
+ ElementName::Row => {
+ imports = format!("{imports}row,");
+ view = format!("{view}\nrow![{elements}]{props}");
+ }
+ ElementName::Column => {
+ imports = format!("{imports}column,");
+ view = format!("{view}\ncolumn![{elements}]{props}");
+ }
+ ElementName::Text(string) => {
+ imports = format!("{imports}text,");
+ view = format!(
+ "{view}\ntext(\"{}\"){props}",
+ if *string == String::new() {
+ "New Text"
+ } else {
+ string
+ }
+ );
+ }
+ ElementName::Button(string) => {
+ imports = format!("{imports}button,");
+ view = format!(
+ "{view}\nbutton(\"{}\"){props}",
+ if *string == String::new() {
+ "New Button"
+ } else {
+ string
+ }
+ );
+ }
+ ElementName::Image(path) => {
+ imports = format!("{imports}image,");
+ view = format!("{view}\nimage(\"{path}\"){props}");
+ }
+ ElementName::SVG(path) => {
+ imports = format!("{imports}svg,");
+ view = format!("{view}\nsvg(\"{path}\"){props}");
+ }
+ }
+
+ (imports, view)
+ }
+
+ pub fn test() -> RenderedElement {
+ let mut text1 = text("wow");
+ text1.option("height", "120.5");
+ text1.option("width", "230");
+
+ let element = container(Some(row(Some(vec![
+ text1,
+ text("heh"),
+ svg("/mnt/drive_d/git/obs-website/src/lib/assets/bars-solid.svg"),
+ ]))));
+
+ element
+ }
}
impl std::fmt::Display for RenderedElement {
@@ -146,7 +250,7 @@ impl std::fmt::Display for RenderedElement {
f.write_fmt(format_args!("{:?}\n", self.name))?;
f.pad("")?;
f.write_str("Options: (")?;
- for (k, v) in &self.props {
+ for (k, v) in &self.options {
if let Some(value) = v {
has_props = true;
f.write_fmt(format_args!(
@@ -181,6 +285,54 @@ impl std::fmt::Display for RenderedElement {
}
}
+#[derive(Debug, Clone)]
+pub enum ActionKind {
+ AddNew,
+ PushFront(Id),
+ InsertAfter(Id, Id),
+ Stop,
+}
+
+impl ActionKind {
+ pub fn new(
+ ids: Vec<Id>,
+ element_tree: &mut 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 {
+ let id: Id = match source_id {
+ Some(id) if ids.contains(&id) => {
+ let element_id = ids[ids.iter().position(|x| *x == id).unwrap()].clone();
+ if ids.len() > 2 && ids[ids.clone().len() - 1] == element_id {
+ return Self::Stop;
+ }
+ element_id
+ }
+ _ => ids.last().cloned().unwrap(),
+ };
+ let element = element_tree.as_mut().unwrap().find_by_id(id.clone());
+
+ match element.unwrap().is_parent() {
+ true => action = Self::PushFront(id),
+ false => {
+ if ids.len() > 2 {
+ action = Self::InsertAfter(
+ ids[ids.clone().len() - 2].clone(),
+ ids[ids.clone().len() - 1].clone(),
+ );
+ }
+ }
+ }
+ }
+ action
+ }
+}
+
pub fn text(text: &str) -> RenderedElement {
RenderedElement::new(ElementName::Text(text.to_owned())).preset_options(vec![
"size",
@@ -204,21 +356,21 @@ pub fn image(path: &str) -> RenderedElement {
pub fn container(content: Option<RenderedElement>) -> RenderedElement {
match content {
- Some(el) => RenderedElement::from_vec(ElementName::Container, vec![el]),
- None => RenderedElement::from_vec(ElementName::Container, vec![]),
+ Some(el) => RenderedElement::with(ElementName::Container, vec![el]),
+ None => RenderedElement::with(ElementName::Container, vec![]),
}
}
pub fn row(child_elements: Option<Vec<RenderedElement>>) -> RenderedElement {
match child_elements {
- Some(els) => RenderedElement::from_vec(ElementName::Row, els),
- None => RenderedElement::from_vec(ElementName::Row, vec![]),
+ Some(els) => RenderedElement::with(ElementName::Row, els),
+ None => RenderedElement::with(ElementName::Row, vec![]),
}
}
pub fn column(child_elements: Option<Vec<RenderedElement>>) -> RenderedElement {
match child_elements {
- Some(els) => RenderedElement::from_vec(ElementName::Column, els),
- None => RenderedElement::from_vec(ElementName::Column, vec![]),
+ Some(els) => RenderedElement::with(ElementName::Column, els),
+ None => RenderedElement::with(ElementName::Column, vec![]),
}
}