diff options
| author | Polesznyák Márk László <116908301+pml68@users.noreply.github.com> | 2024-09-22 21:32:38 +0200 |
|---|---|---|
| committer | pml68 <contact@pml68.me> | 2024-09-22 23:55:11 +0200 |
| commit | 0e1e21ad18dcf48e64422f7f45af44ac5ea4a7be (patch) | |
| tree | 2fe99dcb6380deb067e3bc3005fb5275f33b48d5 | |
| parent | feat: add usable state logic (diff) | |
| parent | feat: add "Copy to clipboard" button for code view (diff) | |
| download | iced-builder-0e1e21ad18dcf48e64422f7f45af44ac5ea4a7be.tar.gz | |
Merge pull request #1 from pml68/feat/codegen
Codegen done
Diffstat (limited to '')
| -rw-r--r-- | Cargo.lock | 58 | ||||
| -rw-r--r-- | Cargo.toml | 4 | ||||
| -rw-r--r-- | fonts/icons.ttf | bin | 0 -> 6352 bytes | |||
| -rw-r--r-- | src/codegen/mod.rs | 160 | ||||
| -rw-r--r-- | src/main.rs | 125 | ||||
| -rw-r--r-- | src/types/mod.rs | 29 | ||||
| -rw-r--r-- | src/types/rendered_element.rs | 47 |
7 files changed, 352 insertions, 71 deletions
@@ -159,6 +159,12 @@ dependencies = [ [[package]] name = "base64" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" + +[[package]] +name = "base64" version = "0.21.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" @@ -212,6 +218,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" [[package]] +name = "blob-uuid" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83fc15853171b33280f5614e77f5fa4debd33f51a86c44daa4ba3d759674c561" +dependencies = [ + "base64 0.13.1", + "uuid", +] + +[[package]] name = "block" version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1183,8 +1199,20 @@ name = "iced-builder" version = "0.1.0" dependencies = [ "iced", + "iced_aw", "rust-format", "tokio", + "unique_id", +] + +[[package]] +name = "iced_aw" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e68c330918a95bd73176206d65b84efe9aee6581da0e6dea0390cd146d7214c" +dependencies = [ + "cfg-if", + "iced", ] [[package]] @@ -1516,6 +1544,12 @@ dependencies = [ ] [[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + +[[package]] name = "lebe" version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -3012,9 +3046,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.39.3" +version = "1.40.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9babc99b9923bfa4804bd74722ff02c0381021eafa4db9949217e3be8e84fff5" +checksum = "e2b070231665d27ad9ec9b8df639893f46727666c6767db40317fbe920a5d998" dependencies = [ "backtrace", "pin-project-lite", @@ -3153,6 +3187,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "229730647fbc343e3a80e463c1db7f78f3855d3f3739bee0dda773c9a037c90a" [[package]] +name = "unique_id" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae605c39dfbdec433798d4a8b03ffbac711dc51cdeb1ba5c725bdcaf24e464cc" +dependencies = [ + "blob-uuid", + "lazy_static", + "uuid", +] + +[[package]] name = "usvg" version = "0.36.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -3214,6 +3259,15 @@ dependencies = [ ] [[package]] +name = "uuid" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81dfa00651efa65069b0b6b651f4aaa31ba9e3c3ce0137aaad053604ee7e0314" +dependencies = [ + "getrandom", +] + +[[package]] name = "version_check" version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -10,5 +10,7 @@ keywords = ["gui", "iced"] [dependencies] iced = { version = "0.12.1", features = [ "image","svg","canvas","qr_code","advanced","tokio","highlighter"] } -tokio = {version = "1.39.3",features = ["fs"]} +iced_aw = { version = "0.9.3", default-features = false, features = ["menu","color_picker"] } +tokio = { version = "1.40.0", features = ["fs"] } rust-format = "0.3.4" +unique_id = "0.1.5" diff --git a/fonts/icons.ttf b/fonts/icons.ttf Binary files differnew file mode 100644 index 0000000..393c692 --- /dev/null +++ b/fonts/icons.ttf diff --git a/src/codegen/mod.rs b/src/codegen/mod.rs new file mode 100644 index 0000000..2dd9cff --- /dev/null +++ b/src/codegen/mod.rs @@ -0,0 +1,160 @@ +use std::path::PathBuf; + +use rust_format::{Config, Edition, Formatter, RustFmt}; + +use crate::types::{rendered_element::RenderedElement, ElementName}; + +impl RenderedElement { + fn props_codegen(&self) -> String { + let mut props_string = String::new(); + + for (k, v) in self.props.clone() { + 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(); + + match self.name { + ElementName::Column | ElementName::Row | ElementName::Container => { + for element in &self.child_elements { + 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 = if self.child_elements.len() < 2 { + format!("{view}\ncontainer({elements}){props}") + } else { + format!("{view}\ncontainer(){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(\"{}\"){props}", path.display().to_string()); + } + ElementName::SVG(path) => { + imports = format!("{imports}svg,"); + view = format!("{view}\nsvg(\"{}\"){props}", path.display().to_string()); + } + } + + (imports, view) + } + + pub fn app_code( + &self, + title: &str, + theme: Option<iced::Theme>, + ) -> Result<String, Box<dyn std::error::Error>> { + let (imports, view) = self.codegen(); + let mut app_code = format!("use iced::{{widget::{{{imports}}},Sandbox,Settings,Element}};"); + + app_code = format!( + r#"{app_code} + + fn main() -> iced::Result {{ + App::run(Settings::default()) + }} + + struct App; + + impl Sandbox for App {{ + type Message = (); + + fn new() -> Self {{ + Self {{}} + }} + + fn title(&self) -> String {{ + "{title}".into() + }} + + fn theme(&self) -> iced::Theme {{ + iced::Theme::{} + }} + + fn update(&mut self, message: Self::Message) {{ + + }} + + fn view(&self) -> Element<Self::Message> {{ + {view}.into() + }} + }}"#, + if let Some(c) = theme { + c.to_string().replace(' ', "") + } else { + "default()".to_owned() + } + ); + 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)?) + } + + pub fn test() -> String { + let mut text1 = RenderedElement::new(ElementName::Text("wow")); + text1.set_property("height", "120.5"); + text1.set_property("width", "230"); + + let element = RenderedElement::new(ElementName::Container).push(RenderedElement::from_vec( + ElementName::Row, + vec![ + text1, + RenderedElement::new(ElementName::Text("heh")), + RenderedElement::new(ElementName::SVG(PathBuf::from( + "/mnt/drive_d/git/obs-website/src/lib/assets/bars-solid.svg", + ))), + ], + )); + + element.app_code("new app", None).unwrap() + } +} diff --git a/src/main.rs b/src/main.rs index 6ee558b..d3fafc6 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,21 +1,24 @@ +mod codegen; mod types; use iced::{ - executor, + clipboard, executor, highlighter::{self, Highlighter}, theme, widget::{ button, column, container, pane_grid::{self, Pane, PaneGrid}, - row, text, text_editor, Column, + row, text, text_editor, tooltip, Column, Space, }, Alignment, Application, Color, Command, Element, Font, Length, Settings, }; -use rust_format::{Formatter, RustFmt}; -use types::{DesignerPage, DesignerState}; +use types::{rendered_element::RenderedElement, DesignerPage, DesignerState}; fn main() -> iced::Result { - App::run(Settings::default()) + App::run(Settings { + fonts: vec![include_bytes!("../fonts/icons.ttf").as_slice().into()], + ..Settings::default() + }) } struct App { @@ -32,6 +35,7 @@ struct App { #[derive(Debug, Clone)] enum Message { ToggleTheme, + CopyCode, Resized(pane_grid::ResizeEvent), Clicked(pane_grid::Pane), } @@ -64,13 +68,13 @@ impl Application for App { focus: None, designer_state: DesignerState { designer_content: vec![], - designer_page: DesignerPage::Designer, + designer_page: DesignerPage::CodeView, }, element_list: vec!["Column", "Row", "PickList", "PaneGrid", "Button", "Text"] .into_iter() .map(|c| c.to_owned()) .collect(), - editor_content: text_editor::Content::new(), + editor_content: text_editor::Content::with_text(&RenderedElement::test()), }, Command::none(), ) @@ -98,6 +102,7 @@ impl Application for App { fn update(&mut self, message: Message) -> Command<Message> { match message { Message::ToggleTheme => self.dark_theme = !self.dark_theme, + Message::CopyCode => return clipboard::write(self.editor_content.text()), Message::Resized(pane_grid::ResizeEvent { split, ratio }) => { self.pane_state.resize(split, ratio); } @@ -117,27 +122,72 @@ impl Application for App { let pane_grid = PaneGrid::new(&self.pane_state, |id, pane, _is_maximized| { let is_focused = Some(id) == self.focus; match pane { - Panes::Designer => { - let content = column![text("Designer")] - .align_items(Alignment::Center) - .height(Length::Fill) - .width(Length::Fill); - let title = text("Designer").style(if is_focused { - PANE_ID_COLOR_FOCUSED - } else { - PANE_ID_COLOR_UNFOCUSED - }); - let title_bar = pane_grid::TitleBar::new(title) - .padding(10) - .style(style::title_bar); - pane_grid::Content::new(content) + Panes::Designer => match self.designer_state.designer_page { + DesignerPage::Designer => { + let content = column![text("Designer"),] + .align_items(Alignment::Center) + .height(Length::Fill) + .width(Length::Fill); + let title = text("Designer").style(if is_focused { + PANE_ID_COLOR_FOCUSED + } else { + PANE_ID_COLOR_UNFOCUSED + }); + 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 + }) + } + DesignerPage::CodeView => { + let title = row![ + text("Generated Code").style(if is_focused { + PANE_ID_COLOR_FOCUSED + } else { + PANE_ID_COLOR_UNFOCUSED + }), + Space::with_width(Length::Fill), + tooltip( + button( + container( + text('\u{0e801}').font(Font::with_name("editor-icons")) + ) + .width(30) + .center_x() + ) + .on_press(Message::CopyCode), + "Copy code to clipboard", + tooltip::Position::Left + ) + ]; + let title_bar = pane_grid::TitleBar::new(title) + .padding(10) + .style(style::title_bar); + pane_grid::Content::new( + text_editor(&self.editor_content) + .highlight::<Highlighter>( + highlighter::Settings { + theme: highlighter::Theme::Base16Mocha, + extension: "rs".to_string(), + }, + |highlight, _theme| highlight.to_format(), + ) + .height(Length::Fill) + .padding(20), + ) .title_bar(title_bar) .style(if is_focused { style::pane_focused } else { style::pane_active }) - } + } + }, Panes::ElementList => { let items_list = items_list_view(&self.element_list); let content = column![items_list] @@ -159,36 +209,7 @@ impl Application for App { } else { style::pane_active }) - } //Panes::CodeView => { - // let title = text("Generated Code").style(if is_focused { - // PANE_ID_COLOR_FOCUSED - // } else { - // PANE_ID_COLOR_UNFOCUSED - // }); - // let title_bar = - // pane_grid::TitleBar::new(title) - // .padding(10) - // .style(if is_focused { - // style::title_bar_focused - // } else { - // style::title_bar_active - // }); - // pane_grid::Content::new( - // text_editor(&self.editor_content).highlight::<Highlighter>( - // highlighter::Settings { - // theme: highlighter::Theme::Base16Mocha, - // extension: "rs".to_string(), - // }, - // |highlight, _theme| highlight.to_format(), - // ), - // ) - // .title_bar(title_bar) - // .style(if is_focused { - // style::pane_focused - // } else { - // style::pane_active - // }) - //} + } } }) .width(Length::Fill) diff --git a/src/types/mod.rs b/src/types/mod.rs index 69df615..7a04d79 100644 --- a/src/types/mod.rs +++ b/src/types/mod.rs @@ -1,25 +1,22 @@ -use iced::{Font, Length}; +pub mod rendered_element; + +use rendered_element::RenderedElement; +use std::path::PathBuf; pub struct DesignerState { pub designer_content: Vec<RenderedElement>, pub designer_page: DesignerPage, } -pub struct RenderedElement { - pub id: String, - pub children: Vec<RenderedElement>, - pub name: ElementName, - pub props: Vec<Prop>, -} - -pub enum ElementName {} - -pub enum Prop { - String(String, String), - Decimal(String, i32), - Float(String, f32), - Font(String, Font), - Length(String, Length), +#[derive(Debug)] +pub enum ElementName { + Text(&'static str), + Button(&'static str), + SVG(PathBuf), + Image(PathBuf), + Container, + Row, + Column, } pub enum DesignerPage { diff --git a/src/types/rendered_element.rs b/src/types/rendered_element.rs new file mode 100644 index 0000000..f05594d --- /dev/null +++ b/src/types/rendered_element.rs @@ -0,0 +1,47 @@ +use std::collections::HashMap; + +use unique_id::{string::StringGenerator, Generator}; + +use iced::advanced::widget::Id; + +use super::ElementName; + +#[derive(Debug)] +pub struct RenderedElement { + pub id: Id, + pub child_elements: Vec<RenderedElement>, + pub name: ElementName, + pub props: HashMap<&'static str, &'static str>, +} + +impl RenderedElement { + pub fn new(name: ElementName) -> Self { + let gen = StringGenerator::default(); + Self { + id: Id::new(gen.next_id()), + child_elements: vec![], + name, + props: HashMap::new(), + } + } + + pub fn from_vec(name: ElementName, child_elements: Vec<RenderedElement>) -> Self { + let gen = StringGenerator::default(); + Self { + id: Id::new(gen.next_id()), + child_elements, + name, + props: HashMap::new(), + } + } + + pub fn push(mut self, element: RenderedElement) -> Self { + self.child_elements.push(element); + self + } + + pub fn set_property(&mut self, prop: &'static str, value: &'static str) { + let prop_ref = self.props.entry(prop).or_insert(value); + *prop_ref = value; + } +} |
