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}