summaryrefslogtreecommitdiff
path: root/src
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 /src
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
Diffstat (limited to 'src')
-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
4 files changed, 293 insertions, 68 deletions
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;
+ }
+}