iced_markup/lib.rs
1//! # iced-markup
2//!
3//! A declarative markup DSL (Domain-Specific Language) for building
4//! [Iced](https://iced.rs) GUI applications in Rust.
5//!
6//! Instead of writing deeply nested builder chains by hand, you write a
7//! concise, JSX-inspired syntax inside the [`view!`] macro and get
8//! idiomatic Iced widget code at compile time — zero runtime overhead.
9//!
10//! ## Quick example
11//!
12//! ```no_run
13//! use iced::widget::{column, row, text, button};
14//! use iced_markup::view;
15//!
16//! #[derive(Clone)]
17//! enum Msg { Click }
18//!
19//! fn view() -> iced::Element<'static, Msg> {
20//! view! {
21//! column ![spacing: 20, padding: 40] {
22//! text("Welcome to Iced!") {},
23//! row ![spacing: 10] {
24//! button("Increment") ![on_press: Msg::Click] {},
25//! button("Decrement") ![on_press: Msg::Click] {},
26//! },
27//! }
28//! }
29//! .into()
30//! }
31//! ```
32//!
33//! ## Advanced Features
34//!
35//! `iced_markup` provides several advanced features to simplify complex UIs:
36//!
37//! ### 1. Native Control Flow
38//! Use real `if` and `for` blocks inside your markup:
39//!
40//! ```no_run
41//! # use iced_markup::view;
42//! # use iced::widget::{column, text, Column};
43//! # use iced::{Theme, Renderer};
44//! # let show_admin = true;
45//! # let items = vec!["A"];
46//! # enum Msg { Item }
47//! let _: Column<'_, Msg, Theme, Renderer> = view! {
48//! column {
49//! if show_admin {
50//! text("Admin Panel") {}
51//! },
52//! for item in items {
53//! text(item) {}
54//! }
55//! }
56//! };
57//! ```
58//!
59//! ### 2. Thematic Pipes (`|`)
60//! Apply styles or themes with a sleek syntax:
61//!
62//! ```no_run
63//! # use iced_markup::view;
64//! # use iced::widget::{text, Column, column};
65//! # use iced::{Theme, Renderer};
66//! # enum Msg { Item }
67//! let _: Column<'_, Msg, Theme, Renderer> = view! {
68//! column {
69//! text("Hello") | |_: &Theme| iced::widget::text::Style { color: Some(iced::Color::WHITE) } {}
70//! }
71//! };
72//! ```
73//!
74//! ### 3. Event Shorthands (`+`)
75//! Use `+click`, `+input`, or `+submit` for common events:
76//!
77//! ```no_run
78//! # use iced_markup::view;
79//! # use iced::widget::{button, Column, column};
80//! # use iced::{Theme, Renderer};
81//! # #[derive(Clone)] enum Msg { Save }
82//! let _: Column<'_, Msg, Theme, Renderer> = view! {
83//! column {
84//! button("Save") ![+click: Msg::Save] {}
85//! }
86//! };
87//! ```
88//!
89//! ### 4. Component Slots (`@`)
90//! Use named slots for flexible widget configuration:
91//!
92//! ```no_run
93//! # use iced_markup::view;
94//! # use iced::widget::{button, Column, column};
95//! # use iced::{Theme, Renderer};
96//! # #[derive(Clone)] enum Msg { Click }
97//! let _: Column<'_, Msg, Theme, Renderer> = view! {
98//! column {
99//! button("Submit") {
100//! @on_press: Msg::Click
101//! }
102//! }
103//! };
104//! ```
105//!
106//! ## Architecture
107//!
108//! The crate is structured as a classic compiler pipeline:
109//!
110//! 1. **[`parser`]** — Reads the token stream and produces a typed AST.
111//! 2. **[`node`]** — Defines every node type (widgets, attributes, control flow).
112//! 3. **[`codegen`]** — Implements `ToTokens` for every AST node, emitting
113//! optimized Iced Rust code.
114//!
115//! The public entry point is the [`view!`] procedural macro defined below.
116
117mod attribute;
118mod codegen;
119mod node;
120mod parser;
121
122use proc_macro::TokenStream;
123use proc_macro_error2::proc_macro_error;
124use quote::ToTokens;
125use syn::parse_macro_input;
126
127use crate::node::Markup;
128
129/// The `view!` procedural macro transforms a declarative, JSX-inspired markup syntax
130/// into idiomatic [Iced](https://iced.rs) widget code at compile time.
131///
132/// This provides a much more readable and maintainable way to define GUI layouts
133/// compared to deeply nested builder chains, while maintaining zero runtime overhead.
134///
135/// # Syntax breakdown
136///
137/// ```text
138/// view! {
139/// widget_name(constructor_args) ![attribute: value] {
140/// child_widget,
141/// }
142/// }
143/// ```
144///
145/// - **`widget_name`**: The Iced widget to create (e.g., `column`, `row`, `text`). Only widgets that support `.push()` (like `Column` and `Row`) should have children in `{}` blocks.
146/// - **`constructor_args`**: (Optional) Arguments passed to the widget's creation function.
147/// - **`attributes`**: (Optional) Modifiers applied via `![...]` (e.g., `![spacing: 10]`).
148/// - **`children`**: (Optional) Nested widgets inside `{...}` separated by commas.
149///
150/// # Example
151///
152/// ```no_run
153/// # use iced_markup::view;
154/// # use iced::widget::{column, text, button, Column};
155/// # use iced::{Theme, Renderer};
156/// # #[derive(Clone)] enum Message { Clicked }
157/// let _: Column<'_, Message, Theme, Renderer> = view! {
158/// column ![spacing: 20] {
159/// text("Hello World") {},
160/// button("Click me") ![on_press: Message::Clicked] {},
161/// }
162/// };
163/// ```
164///
165/// Expands roughly to:
166///
167/// ```no_run
168/// # use iced::widget::{column, text, button, Column};
169/// # use iced::{Theme, Renderer};
170/// # #[derive(Clone)] enum Message { Clicked }
171/// let _: Column<'_, Message, Theme, Renderer> = iced::widget::column([])
172/// .spacing(20)
173/// .push(iced::widget::text("Hello World"))
174/// .push(iced::widget::button("Click me").on_press(Message::Clicked));
175/// ```
176#[proc_macro]
177#[proc_macro_error]
178pub fn view(input: TokenStream) -> TokenStream {
179 // 1. Parse: Convert the input tokens into our AST structure.
180 let markup = parse_macro_input!(input as Markup);
181
182 // 2. Codegen: Transform the AST into valid Rust code.
183 let tokens = markup.to_token_stream();
184
185 // 3. Output: Return the generated code back to the compiler.
186 TokenStream::from(tokens)
187}