diff options
Diffstat (limited to 'iced_builder')
| -rw-r--r-- | iced_builder/Cargo.toml | 1 | ||||
| -rw-r--r-- | iced_builder/src/codegen/mod.rs | 146 | ||||
| -rw-r--r-- | iced_builder/src/lib.rs | 17 | ||||
| -rw-r--r-- | iced_builder/src/main.rs | 68 | ||||
| -rw-r--r-- | iced_builder/src/types/element_name.rs | 90 | ||||
| -rw-r--r-- | iced_builder/src/types/mod.rs | 44 | ||||
| -rw-r--r-- | iced_builder/src/types/project.rs | 137 | ||||
| -rw-r--r-- | iced_builder/src/types/project/mod.rs | 59 | ||||
| -rw-r--r-- | iced_builder/src/types/rendered_element.rs | 206 |
9 files changed, 465 insertions, 303 deletions
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![]), } } |
