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}