Skip to main content

revue/widget/
macros.rs

1//! Declarative UI macros
2
3/// Create a vertical stack with children
4///
5/// # Examples
6///
7/// ```ignore
8/// use revue::prelude::*;
9///
10/// let view = vstack![
11///     Text::heading("Title"),
12///     Text::new("Content"),
13/// ];
14///
15/// // With gap
16/// let view = vstack![gap: 2;
17///     Text::new("Line 1"),
18///     Text::new("Line 2"),
19/// ];
20/// ```
21#[macro_export]
22macro_rules! vstack {
23    // With gap
24    (gap: $gap:expr; $($child:expr),* $(,)?) => {{
25        $crate::widget::vstack()
26            .gap($gap)
27            $(.child($child))*
28    }};
29    // Without gap
30    ($($child:expr),* $(,)?) => {{
31        $crate::widget::vstack()
32            $(.child($child))*
33    }};
34}
35
36/// Create a horizontal stack with children
37///
38/// # Examples
39///
40/// ```ignore
41/// use revue::prelude::*;
42///
43/// let view = hstack![
44///     Text::new("Left"),
45///     Text::new("Right"),
46/// ];
47///
48/// // With gap
49/// let view = hstack![gap: 1;
50///     Text::new("A"),
51///     Text::new("B"),
52/// ];
53/// ```
54#[macro_export]
55macro_rules! hstack {
56    // With gap
57    (gap: $gap:expr; $($child:expr),* $(,)?) => {{
58        $crate::widget::hstack()
59            .gap($gap)
60            $(.child($child))*
61    }};
62    // Without gap
63    ($($child:expr),* $(,)?) => {{
64        $crate::widget::hstack()
65            $(.child($child))*
66    }};
67}
68
69/// Create a border with a child
70///
71/// # Examples
72///
73/// ```ignore
74/// use revue::prelude::*;
75///
76/// let view = bordered![
77///     Text::new("Content")
78/// ];
79///
80/// // With title
81/// let view = bordered!["My Panel";
82///     Text::new("Content")
83/// ];
84///
85/// // With type and title
86/// let view = bordered![rounded, "Card Title";
87///     Text::new("Card content")
88/// ];
89/// ```
90#[macro_export]
91macro_rules! bordered {
92    // Border type + title + child
93    ($border_type:ident, $title:expr; $child:expr) => {{
94        $crate::widget::Border::$border_type()
95            .title($title)
96            .child($child)
97    }};
98    // Title + child
99    ($title:expr; $child:expr) => {{
100        $crate::widget::Border::single().title($title).child($child)
101    }};
102    // Just child
103    ($child:expr) => {{
104        $crate::widget::Border::single().child($child)
105    }};
106}
107
108/// Create text with common styling
109///
110/// # Examples
111///
112/// ```ignore
113/// use revue::prelude::*;
114///
115/// let t = text!("Hello");
116/// let t = text!("Error!", red);
117/// let t = text!("Success", green, bold);
118/// ```
119#[macro_export]
120macro_rules! text {
121    // Text with color and modifiers
122    ($content:expr, $color:ident, bold) => {{
123        $crate::widget::Text::new($content)
124            .fg($crate::style::Color::$color)
125            .bold()
126    }};
127    ($content:expr, $color:ident, italic) => {{
128        $crate::widget::Text::new($content)
129            .fg($crate::style::Color::$color)
130            .italic()
131    }};
132    // Text with color
133    ($content:expr, red) => {{
134        $crate::widget::Text::error($content)
135    }};
136    ($content:expr, green) => {{
137        $crate::widget::Text::success($content)
138    }};
139    ($content:expr, yellow) => {{
140        $crate::widget::Text::warning($content)
141    }};
142    ($content:expr, cyan) => {{
143        $crate::widget::Text::info($content)
144    }};
145    ($content:expr, $color:ident) => {{
146        $crate::widget::Text::new($content).fg($crate::style::Color::$color)
147    }};
148    // Plain text
149    ($content:expr) => {{
150        $crate::widget::Text::new($content)
151    }};
152}
153
154/// Build a complete UI layout declaratively
155///
156/// # Examples
157///
158/// ```ignore
159/// use revue::prelude::*;
160///
161/// let ui = ui! {
162///     vstack(gap: 1) {
163///         Text::heading("Dashboard")
164///         hstack {
165///             bordered!["Stats"; Text::new("100")]
166///             bordered!["Users"; Text::new("42")]
167///         }
168///         Text::muted("Press 'q' to quit")
169///     }
170/// };
171/// ```
172#[macro_export]
173macro_rules! ui {
174    // VStack with options
175    (vstack(gap: $gap:expr) { $($child:tt)* }) => {{
176        $crate::widget::vstack()
177            .gap($gap)
178            $(.child($crate::ui!(@child $child)))*
179    }};
180    // VStack without options
181    (vstack { $($child:tt)* }) => {{
182        $crate::widget::vstack()
183            $(.child($crate::ui!(@child $child)))*
184    }};
185    // HStack with options
186    (hstack(gap: $gap:expr) { $($child:tt)* }) => {{
187        $crate::widget::hstack()
188            .gap($gap)
189            $(.child($crate::ui!(@child $child)))*
190    }};
191    // HStack without options
192    (hstack { $($child:tt)* }) => {{
193        $crate::widget::hstack()
194            $(.child($crate::ui!(@child $child)))*
195    }};
196    // Child processing - recursive ui! call
197    (@child vstack $($rest:tt)*) => {{
198        $crate::ui!(vstack $($rest)*)
199    }};
200    (@child hstack $($rest:tt)*) => {{
201        $crate::ui!(hstack $($rest)*)
202    }};
203    // Child processing - direct expression
204    (@child $expr:expr) => {{
205        $expr
206    }};
207}