summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPolesznyák Márk László <116908301+pml68@users.noreply.github.com>2024-09-22 21:32:38 +0200
committerpml68 <contact@pml68.me>2024-09-22 23:55:11 +0200
commit0e1e21ad18dcf48e64422f7f45af44ac5ea4a7be (patch)
tree2fe99dcb6380deb067e3bc3005fb5275f33b48d5
parentfeat: add usable state logic (diff)
parentfeat: add "Copy to clipboard" button for code view (diff)
downloadiced-builder-0e1e21ad18dcf48e64422f7f45af44ac5ea4a7be.tar.gz
Merge pull request #1 from pml68/feat/codegen
Codegen done
-rw-r--r--Cargo.lock58
-rw-r--r--Cargo.toml4
-rw-r--r--fonts/icons.ttfbin0 -> 6352 bytes
-rw-r--r--src/codegen/mod.rs160
-rw-r--r--src/main.rs125
-rw-r--r--src/types/mod.rs29
-rw-r--r--src/types/rendered_element.rs47
7 files changed, 352 insertions, 71 deletions
diff --git a/Cargo.lock b/Cargo.lock
index c877aa9..b519b92 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -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"
diff --git a/Cargo.toml b/Cargo.toml
index 50b4017..301ecfe 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -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
new file mode 100644
index 0000000..393c692
--- /dev/null
+++ b/fonts/icons.ttf
Binary files differ
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;
+ }
+}