egui_xml_macros/
lib.rs

1//! `load_layout!` macro for loading layout from XML for egui
2//!
3//! The `load_layout!` macro allows for loading layout configurations from XML for use with the egui GUI framework. It takes an XML representation of the layout structure and generates Rust code to construct the layout within an egui UI.
4//!
5//! # Example
6//!
7//! ```rust
8//! use eframe::egui;
9//! use egui::{Rounding, Ui};
10//! use egui_xml::load_layout;
11//!
12//! struct MyApp;
13//!
14//! fn color_background(ui: &mut Ui, color: egui::Color32) {
15//!     ui.painter()
16//!         .rect_filled(ui.available_rect_before_wrap(), Rounding::same(5.0), color);
17//! }
18//!
19//! impl eframe::App for MyApp {
20//!     fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
21//!         egui::CentralPanel::default().show(ctx, |ui| {
22//!             load_layout!(
23//!                 <Strip direction="west">
24//!                     <Panel size="relative" value="0.3">
25//!                         color_background(ui, egui::Color32::from_rgb(0, 0, 255));
26//!                     </Panel>
27//!                     <Panel size="remainder">
28//!                         <Strip direction="north">
29//!                             <Panel size="relative" value="0.3">
30//!                                 color_background(ui, egui::Color32::from_rgb(0, 255, 255));
31//!                             </Panel>
32//!                             <Panel size="remainder">
33//!                                 color_background(ui, egui::Color32::from_rgb(255, 0, 255));
34//!                             </Panel>
35//!                         </Strip>
36//!                     </Panel>
37//!                 </Strip>
38//!             );
39//!         });
40//!     }
41//! }
42//! ```
43
44extern crate proc_macro;
45
46use std::{cell::RefCell, rc::Rc};
47
48use egui_xml_parser::{Node, XMLForm};
49use layout::strip::expand_strip;
50use proc_macro::TokenStream;
51
52use quote::{quote, TokenStreamExt};
53use syn::{parse_macro_input, LitStr};
54
55mod layout;
56
57struct XMLContext;
58
59fn expand_nodes(
60    children: &[Rc<RefCell<Node>>],
61    ctx: &XMLContext,
62) -> Result<proc_macro2::TokenStream, String> {
63    let mut expanded = quote! {};
64
65    for node in children.iter() {
66        let node_expanded = expand_node(node, ctx)?;
67        expanded.append_all(node_expanded);
68    }
69
70    Ok(expanded)
71}
72
73fn expand_node(
74    node: &Rc<RefCell<Node>>,
75    ctx: &XMLContext,
76) -> Result<proc_macro2::TokenStream, String> {
77    match &*node.borrow() {
78        egui_xml_parser::Node::Panel { children, .. } => expand_nodes(children, ctx),
79        egui_xml_parser::Node::Rust { code, .. } => Ok(code.parse().unwrap()),
80        egui_xml_parser::Node::Border { .. } => Ok(quote! {}),
81        egui_xml_parser::Node::Grid { .. } => Ok(quote! {}),
82        egui_xml_parser::Node::Default { children, .. } => expand_nodes(children, ctx),
83        egui_xml_parser::Node::Strip { .. } => expand_strip(node, ctx),
84    }
85}
86
87/// Macro for loading layout from XML.
88///
89/// This macro parses an XML layout representation and generates Rust code to construct the layout within an egui UI.
90///
91/// # Example
92///
93/// ```rust
94/// load_layout!(
95///     <Strip direction="west">
96///         <Panel size="relative" value="0.3">
97///             color_background(ui, egui::Color32::from_rgb(0, 0, 255));
98///         </Panel>
99///         <Panel size="remainder">
100///             <Strip direction="north">
101///                 <Panel size="relative" value="0.3">
102///                     color_background(ui, egui::Color32::from_rgb(0, 255, 255));
103///                 </Panel>
104///                 <Panel size="remainder">
105///                     color_background(ui, egui::Color32::from_rgb(255, 0, 255));
106///                 </Panel>
107///             </Strip>
108///         </Panel>
109///     </Strip>
110/// );
111/// ```
112#[proc_macro]
113pub fn load_layout(input: TokenStream) -> TokenStream {
114    let xml = input.to_string();
115
116    let form: XMLForm = match xml.try_into() {
117        Ok(form) => form,
118        Err(_) => panic!("Failed to load XML"),
119    };
120
121    let ctx = XMLContext;
122
123    let expanded = match expand_node(&form.root, &ctx) {
124        Ok(expanded) => expanded,
125        Err(e) => panic!("{}", e),
126    };
127
128    expanded.into()
129}
130
131/// Macro for loading layout from a file.
132///
133/// This macro reads the content of the specified file and passes it to the `load_layout` macro for parsing and code generation.
134///
135/// # Example
136///
137/// ```rust
138/// load_layout_file!("layout.xml");
139/// ```
140#[proc_macro]
141pub fn load_layout_file(input: TokenStream) -> TokenStream {
142    // Parse the input tokens into a syntax tree
143    let input = parse_macro_input!(input as LitStr);
144    let file_path = input.value();
145
146    let file_content =
147        std::fs::read_to_string(&file_path).expect(&format!("unable to find {}", file_path));
148
149    load_layout(file_content.parse().unwrap())
150}