bevy_ui_build_macros/
lib.rs

1/// Wrapper around the `bevy::ui::Val` enum
2///
3/// # Syntax
4/// * `unit!(num1 px)` ⇒ `Val::Px(num1 as f32)`
5/// * `unit!(num1 pct)` ⇒ `Val::Percent(num1 as f32)`
6/// * `unit!(auto)` ⇒ `Val::Auto`
7/// * `unit!(undefined)` ⇒ `Val::Undefined`
8#[macro_export]
9macro_rules! unit {
10    (@with_value px $value:literal) => ( bevy::ui::Val::Px($value as f32));
11    (@with_value pct $value:literal) => ( bevy::ui::Val::Percent($value as f32));
12    (auto) => ( bevy::ui::Val::Auto );
13    (undefined) => ( bevy::ui::Val::Undefined );
14    ($value:literal $val_unit:ident) => ( unit!(@with_value $val_unit $value));
15}
16
17/// Wrapper around `bevy::ui::Style`
18///
19/// ```rust,ignore
20/// style! {
21///     param1: something,
22///     param2: something_else,
23/// }
24/// // Is strictly equivalent to
25/// bevy::ui::Style {
26///     param1: something,
27///     param2: something_else,
28///     ..Default.default()
29/// }
30/// ```
31#[macro_export]
32macro_rules! style {
33    (@default ($default:expr) $($field:ident : $content:expr),*) => (
34        bevy::ui::Style { $($field : $content,)* .. $default }
35    );
36    ($($field:ident : $content:expr,)*) => (
37        style!(@default (Default::default()) $($field : $content),*)
38    );
39}
40
41/// Wrapper around `bevy::ui::Size::new`
42///
43/// # Syntax
44/// * `size!(num1 val1, num2 val2)` ⇒ `Size::new(unit!(num1 val1), unit!(num2 val2))`
45#[macro_export]
46macro_rules! size {
47    ($x:tt $($x_unit:ident)?, $y:tt $($y_unit:ident)?) => (
48        bevy::ui::Size::new(unit!($x $($x_unit)?), unit!($y $($y_unit)?))
49    );
50}
51
52/// Define a `bevy::ui::UiRect` similarly to how you would define it in CSS.
53///
54/// # Syntax
55/// ```rust,ignore
56/// // one argument
57/// rect!(num1 val1) == Rect::all(unit!(num1 val1))
58///
59/// // two arguments
60/// rect!(num1 val1, num2 val2) == Rect {
61///     left: unit!(num1 val1),
62///     right: unit!(num1 val1),
63///     top: unit!(num2 val2),
64///     bottom: unit!(num2 val2),
65/// }
66///
67/// // four arguments
68/// rect!(num1 val1, num2 val2, num3 val3, num4 val4) == Rect {
69///     left: unit!(num1 val1),
70///     top: unit!(num2 val2),
71///     right: unit!(num3 val3),
72///     bottom: unit!(num4 val4),
73/// }
74/// ```
75#[macro_export]
76macro_rules! rect {
77    ($x:tt $($x_unit:ident)?) => (
78        bevy::ui::UiRect::all(unit!($x $($x_unit)?))
79    );
80    (
81        $left:tt $($left_unit:ident)?, $top:tt $($top_unit:ident)?,
82        $right:tt $($right_unit:ident)?, $bottom:tt $($bottom_unit:ident)? $(,)?
83    ) => (
84        bevy::ui::UiRect {
85            left: unit!($left $($left_unit)?),
86            top: unit!($top $($top_unit)?),
87            right: unit!($right $($right_unit)?),
88            bottom: unit!($bottom $($bottom_unit)?),
89        }
90    );
91    ($x:tt $($x_unit:ident)?, $y:tt $($y_unit:ident)?) => (
92        bevy::ui::UiRect {
93            left: unit!($x $($x_unit)?),
94            top: unit!($y $($y_unit)?),
95            right: unit!($x $($x_unit)?),
96            bottom: unit!($y $($y_unit)?),
97        }
98    );
99}
100
101/// Define a bevy UI and spawns it using `cmd`
102///
103/// # Syntax
104/// ```rust,ignore
105/// use bevy::prelude::*;
106/// let commands: Commands;
107/// let my_id: Entity;
108/// build_ui! {
109///     // The bevy `Commands`
110///     #[cmd(commands)]
111///     // The "preset" is an identifier, see doc
112///     $entity
113///         // Style modifiers. Supposing $entity is a `NodeBundle`, does:
114///         // $entity.style = style!{ flex_whatever: Whatever }
115///         // Leads to a compilation error if $entity doesn't have a `style`
116///         // field
117///         { flex_whatever: Whatever }
118///         // Additional components and bundles. Translates to
119///         // $entity.insert_bundle(bundle1).insert_bundle(bundle2).insert(comp1).insert(comp2)
120///         // If you don't care for bundles or comp, just leave the left or
121///         // right of the ; blank
122///         [bundle1, bundl2 ;comp1, comp2]
123///         // Children entities, may have {..}, [..;..] and (..)
124///         (
125///             entity[ButtonBundle](square),
126///             id(my_id)
127///         )
128/// }
129/// ```
130///
131/// The `$entity` in the macro may be one of the following:
132/// * `id(Entity)`: inserts a pre-existing entity as child of containing entity
133/// * `$ident`: where `$ident` is the name of a local variable of type
134///   `T: ComponentBundle`. Spawn the bundle as base to insert extra components
135///   to. Useful to not repeat yourself.
136/// * `entity`: spawn an empty bundle as base to insert extra components to.
137///
138/// # Example
139///
140/// ```rust,ignore
141/// build_ui! {
142///     #[cmd(commands)]
143///     vertical{size:size!(100 pct, 100 pct)}(
144///         horizontal{justify_content: FlexStart, flex_basis: unit!(10 pct)}(
145///             tab_square[;focus], tab_square[;focus], tab_square[;focus],
146///         ),
147///         column_box(
148///             column[;red](
149///                 vertical(select_square, select_square),
150///                 horizontal{flex_wrap: Wrap}[gray](
151///                     square[;focus], square[;focus], square[;focus], square[;focus],
152///                     square[;focus], square[;focus], square[;focus], square[;focus],
153///                     square[;focus], square[;focus], square[;focus], square[;focus],
154///                 ),
155///                 horizontal{flex_wrap: Wrap}[gray](
156///                     square[;focus], square[;focus], square[;focus], square[;focus],
157///                     square[;focus], square[;focus], square[;focus], square[;focus],
158///                 ),
159///             ),
160///         ),
161///     )
162/// }
163/// // Basically becomes
164/// commands.spawn_bundle(NodeBundle {
165///     style: Style { size: size!(100 pct, 100 pct), .. vertical.style },
166///     .. vertical
167/// })
168///   .with_children(|cmds| {
169///     cmds.spawn_bundle(NodeBundle {
170///         style: Style {justify_content: FlexStart, flex_basis: unit!(10 pct), .. horizontal.style },
171///         .. horizontal
172///     })
173///       .with_children(|cmds| {
174///         cmds.spawn_bundle(tab_square).insert(focus);
175///         cmds.spawn_bundle(tab_square).insert(focus);
176///         cmds.spawn_bundle(tab_square).insert(focus);
177///       });
178///     cmds.spawn_bundle(column_box)
179///       .with_children(|cmds| {
180///         cmds.spawn_bundle(column).insert(red)
181///           .with_children(|cmds| {
182///             vertical.with_children(|cmds| {
183///               cmds.spawn_bundle(select_square);
184///               cmds.spawn_bundle(select_square);
185///             });
186///             cmds.spawn_bundle(NodeBundle {
187///                 style: Style {flex_wrap: Wrap, ..horizontal.style},
188///                 .. horizontal
189///             }).insert(gray)
190///               .with_children(|cmds| {
191///                 for _ in 0..12 {
192///                   cmds.spawn_bundle(square).insert(focus);
193///                 }
194///               });
195///             cmds.spawn_bundle(NodeBundle {
196///                 style: Style {flex_wrap: Wrap, ..horizontal.style},
197///                 .. horizontal
198///             }).insert(gray)
199///               .with_children(|cmds| {
200///                 for _ in 0..8 {
201///                   cmds.spawn_bundle(square).insert(focus);
202///                 }
203///               });
204///           });
205///       });
206///   });
207/// ```
208#[macro_export]
209macro_rules! build_ui {
210    (@preset entity) => (());
211    (@preset $anything_else:ident) => ($anything_else);
212    (@preset $node:ident {$($styles:tt)*}) => (
213        bevy::ui::node_bundles::NodeBundle {
214            style: style!(@default ($node.style.clone()) $($styles)*),
215            .. $node.clone()
216        }
217    );
218    // if-else terminal
219    (@child_list list: (if ($predicate:expr) { $( $if_true:tt )* } else { $( $if_false:tt )* } $(,)?),
220        cmds: $cmds:expr, prefix: ($( $prefix:tt )*),
221    ) => (
222        $( $prefix )*
223        if $predicate {
224            build_ui!(@child_list list: ($( $if_true )*), cmds: $cmds, prefix: (),);
225        } else {
226            build_ui!(@child_list list: ($( $if_false )*), cmds: $cmds, prefix: (),);
227        }
228    );
229    // if terminal
230    (@child_list list: (if ($predicate:expr) { $( $if_true:tt )* } $(,)?),
231        cmds: $cmds:expr, prefix: ($( $prefix:tt )*),
232    ) => (
233        $( $prefix )*
234        if $predicate {
235            build_ui!(@child_list list: ($( $if_true )*), cmds: $cmds, prefix: (),);
236        }
237    );
238    // if-else with tail
239    (@child_list list: (
240            if ($predicate:expr) { $( $if_true:tt )* } else { $( $if_false:tt )* }
241            , $( $tail:tt )+
242        ),
243        cmds: $cmds:expr, prefix: ($( $prefix:tt )*),
244    ) => (
245        build_ui! ( @child_list
246            list: ( $( $tail )+ ),
247            cmds: $cmds,
248            prefix: ($( $prefix )*
249                if $predicate {
250                    build_ui!(@child_list list: ($( $if_true )*), cmds: $cmds, prefix: (),);
251                } else {
252                    build_ui!(@child_list list: ($( $if_false )*), cmds: $cmds, prefix: (),);
253                }
254            ),
255        )
256    );
257    // if with tail
258    (@child_list list: (if ($predicate:expr) { $( $if_true:tt )* } , $( $tail:tt )+),
259        cmds: $cmds:expr, prefix: ($( $prefix:tt )*),
260    ) => (
261        build_ui! ( @child_list
262            list: ( $( $tail )+ ),
263            cmds: $cmds,
264            prefix: ($( $prefix )*
265                if $predicate {
266                    build_ui!(@child_list list: ($( $if_true )*), cmds: $cmds, prefix: (),);
267                }
268            ),
269        )
270    );
271    // just terminal
272    (@child_list list: ($preset:ident $( { $($syl:tt)* } )? $( [ $($bc:tt)* ] )? $( ( $( $c:tt )* ) )? $(,)?),
273        cmds: $cmds:expr, prefix: ($( $prefix:tt )*),
274    ) => (
275        $( $prefix )*
276        build_ui!{ #[cmd($cmds)] $preset $( { $($syl)* } )? $( [ $($bc)* ] )? $( ( $($c)* ) )? }
277    );
278    // just has a tail
279    (@child_list list: (
280            $preset:ident $( { $($syl:tt)* } )? $( [ $($bc:tt)* ] )? $( ( $( $c:tt )* ) )?
281            , $( $tail:tt )+
282        ),
283        cmds: $cmds:expr, prefix: ($( $prefix:tt )*),
284    ) => (
285        build_ui! ( @child_list
286            list: ( $( $tail )+ ),
287            cmds: $cmds,
288            prefix: ($( $prefix )*
289                build_ui!{ #[cmd($cmds)] $preset $( { $($syl)* } )? $( [ $($bc)* ] )? $( ( $($c)* ) )? };
290            ),
291        )
292    );
293    (#[cmd($cmds:expr)] id ( $id:expr )) => ({
294        use bevy::ecs::system::Insert;
295        let parent = $cmds.parent_entity();
296        let insert = bevy::hierarchy::AddChild {
297            child: $id,
298            parent,
299        };
300        $cmds.add_command(insert);
301    });
302    (#[cmd($cmds:expr)] $preset:ident
303        $( {$($styles:tt)*} )? // {..} style modifiers
304        $( [$($bundles:expr),* ; $($components:expr),*] )? // [..] components
305        $( ( $( $children_list:tt )* ) )?
306    ) => (
307        $cmds.spawn(build_ui!(@preset $preset $({$($styles)*})?).clone())
308            $($(.insert($bundles.clone()))*
309            $(.insert($components.clone()))*)?
310            $(.with_children(|cmds| {
311                build_ui!(@child_list
312                    list: ( $( $children_list )* ),
313                    cmds: cmds,
314                    prefix: (),
315                );
316            }))?
317    );
318}