pub fn vmin<T>(value: T) -> Valwhere
T: ValNum,Expand description
Returns a Val::VMin representing a percentage of the viewport’s smaller dimension.
Examples found in repository?
examples/stress_tests/many_buttons.rs (line 166)
152fn setup_flex(mut commands: Commands, asset_server: Res<AssetServer>, args: Res<Args>) {
153 let images = if 0 < args.image_freq {
154 Some(vec![
155 asset_server.load("branding/icon.png"),
156 asset_server.load("textures/Game Icons/wrench.png"),
157 ])
158 } else {
159 None
160 };
161
162 let buttons_f = args.buttons as f32;
163 let border = if args.no_borders {
164 UiRect::ZERO
165 } else {
166 UiRect::all(vmin(0.05 * 90. / buttons_f))
167 };
168
169 let as_rainbow = |i: usize| Color::hsl((i as f32 / buttons_f) * 360.0, 0.9, 0.8);
170 commands
171 .spawn(Node {
172 display: if args.display_none {
173 Display::None
174 } else {
175 Display::Flex
176 },
177 flex_direction: FlexDirection::Column,
178 justify_content: JustifyContent::Center,
179 align_items: AlignItems::Center,
180 width: percent(100),
181 height: percent(100),
182 ..default()
183 })
184 .with_children(|commands| {
185 for column in 0..args.buttons {
186 commands.spawn(Node::default()).with_children(|commands| {
187 for row in 0..args.buttons {
188 let color = as_rainbow(row % column.max(1));
189 let border_color = Color::WHITE.with_alpha(0.5).into();
190 spawn_button(
191 commands,
192 color,
193 buttons_f,
194 column,
195 row,
196 args.text,
197 border,
198 border_color,
199 images.as_ref().map(|images| {
200 images[((column + row) / args.image_freq) % images.len()].clone()
201 }),
202 );
203 }
204 });
205 }
206 });
207}
208
209fn setup_grid(mut commands: Commands, asset_server: Res<AssetServer>, args: Res<Args>) {
210 let images = if 0 < args.image_freq {
211 Some(vec![
212 asset_server.load("branding/icon.png"),
213 asset_server.load("textures/Game Icons/wrench.png"),
214 ])
215 } else {
216 None
217 };
218
219 let buttons_f = args.buttons as f32;
220 let border = if args.no_borders {
221 UiRect::ZERO
222 } else {
223 UiRect::all(vmin(0.05 * 90. / buttons_f))
224 };
225
226 let as_rainbow = |i: usize| Color::hsl((i as f32 / buttons_f) * 360.0, 0.9, 0.8);
227 commands
228 .spawn(Node {
229 display: if args.display_none {
230 Display::None
231 } else {
232 Display::Grid
233 },
234 width: percent(100),
235 height: percent(100),
236 grid_template_columns: RepeatedGridTrack::flex(args.buttons as u16, 1.0),
237 grid_template_rows: RepeatedGridTrack::flex(args.buttons as u16, 1.0),
238 ..default()
239 })
240 .with_children(|commands| {
241 for column in 0..args.buttons {
242 for row in 0..args.buttons {
243 let color = as_rainbow(row % column.max(1));
244 let border_color = Color::WHITE.with_alpha(0.5).into();
245 spawn_button(
246 commands,
247 color,
248 buttons_f,
249 column,
250 row,
251 args.text,
252 border,
253 border_color,
254 images.as_ref().map(|images| {
255 images[((column + row) / args.image_freq) % images.len()].clone()
256 }),
257 );
258 }
259 }
260 });
261}
262
263fn spawn_button(
264 commands: &mut ChildSpawnerCommands,
265 background_color: Color,
266 buttons: f32,
267 column: usize,
268 row: usize,
269 spawn_text: bool,
270 border: UiRect,
271 border_color: BorderColor,
272 image: Option<Handle<Image>>,
273) {
274 let width = vw(90.0 / buttons);
275 let height = vh(90.0 / buttons);
276 let margin = UiRect::axes(width * 0.05, height * 0.05);
277 let mut builder = commands.spawn((
278 Button,
279 Node {
280 width,
281 height,
282 margin,
283 align_items: AlignItems::Center,
284 justify_content: JustifyContent::Center,
285 border,
286 ..default()
287 },
288 BackgroundColor(background_color),
289 border_color,
290 IdleColor(background_color),
291 ));
292
293 if let Some(image) = image {
294 builder.insert(ImageNode::new(image));
295 }
296
297 if spawn_text {
298 builder.with_children(|parent| {
299 // These labels are split to stress test multi-span text
300 parent
301 .spawn((
302 Text(format!("{column}, ")),
303 TextFont {
304 font_size: FONT_SIZE,
305 ..default()
306 },
307 TextColor(Color::srgb(0.5, 0.2, 0.2)),
308 ))
309 .with_child((
310 TextSpan(format!("{row}")),
311 TextFont {
312 font_size: FONT_SIZE,
313 ..default()
314 },
315 TextColor(Color::srgb(0.2, 0.2, 0.5)),
316 ));
317 });
318 }
319}
320
321fn despawn_ui(mut commands: Commands, root_node: Single<Entity, (With<Node>, Without<ChildOf>)>) {
322 commands.entity(*root_node).despawn();
323}
324
325fn setup_many_cameras(mut commands: Commands, asset_server: Res<AssetServer>, args: Res<Args>) {
326 let images = if 0 < args.image_freq {
327 Some(vec![
328 asset_server.load("branding/icon.png"),
329 asset_server.load("textures/Game Icons/wrench.png"),
330 ])
331 } else {
332 None
333 };
334
335 let buttons_f = args.buttons as f32;
336 let border = if args.no_borders {
337 UiRect::ZERO
338 } else {
339 UiRect::all(vmin(0.05 * 90. / buttons_f))
340 };
341
342 let as_rainbow = |i: usize| Color::hsl((i as f32 / buttons_f) * 360.0, 0.9, 0.8);
343 for column in 0..args.buttons {
344 for row in 0..args.buttons {
345 let color = as_rainbow(row % column.max(1));
346 let border_color = Color::WHITE.with_alpha(0.5).into();
347 let camera = commands
348 .spawn((
349 Camera2d,
350 Camera {
351 order: (column * args.buttons + row) as isize + 1,
352 ..Default::default()
353 },
354 ))
355 .id();
356 commands
357 .spawn((
358 Node {
359 display: if args.display_none {
360 Display::None
361 } else {
362 Display::Flex
363 },
364 flex_direction: FlexDirection::Column,
365 justify_content: JustifyContent::Center,
366 align_items: AlignItems::Center,
367 width: percent(100),
368 height: percent(100),
369 ..default()
370 },
371 UiTargetCamera(camera),
372 ))
373 .with_children(|commands| {
374 commands
375 .spawn(Node {
376 position_type: PositionType::Absolute,
377 top: vh(column as f32 * 100. / buttons_f),
378 left: vw(row as f32 * 100. / buttons_f),
379 ..Default::default()
380 })
381 .with_children(|commands| {
382 spawn_button(
383 commands,
384 color,
385 buttons_f,
386 column,
387 row,
388 args.text,
389 border,
390 border_color,
391 images.as_ref().map(|images| {
392 images[((column + row) / args.image_freq) % images.len()]
393 .clone()
394 }),
395 );
396 });
397 });
398 }
399 }
400}More examples
examples/ui/viewport_debug.rs (line 86)
68fn spawn_with_viewport_coords(commands: &mut Commands) {
69 commands
70 .spawn((
71 Node {
72 width: vw(100),
73 height: vh(100),
74 border: UiRect::axes(vw(5), vh(5)),
75 flex_wrap: FlexWrap::Wrap,
76 ..default()
77 },
78 BorderColor::all(PALETTE[0]),
79 Coords::Viewport,
80 ))
81 .with_children(|builder| {
82 builder.spawn((
83 Node {
84 width: vw(30),
85 height: vh(30),
86 border: UiRect::all(vmin(5)),
87 ..default()
88 },
89 BackgroundColor(PALETTE[2].into()),
90 BorderColor::all(PALETTE[9]),
91 ));
92
93 builder.spawn((
94 Node {
95 width: vw(60),
96 height: vh(30),
97 ..default()
98 },
99 BackgroundColor(PALETTE[3].into()),
100 ));
101
102 builder.spawn((
103 Node {
104 width: vw(45),
105 height: vh(30),
106 border: UiRect::left(vmax(45. / 2.)),
107 ..default()
108 },
109 BackgroundColor(PALETTE[4].into()),
110 BorderColor::all(PALETTE[8]),
111 ));
112
113 builder.spawn((
114 Node {
115 width: vw(45),
116 height: vh(30),
117 border: UiRect::right(vmax(45. / 2.)),
118 ..default()
119 },
120 BackgroundColor(PALETTE[5].into()),
121 BorderColor::all(PALETTE[8]),
122 ));
123
124 builder.spawn((
125 Node {
126 width: vw(60),
127 height: vh(30),
128 ..default()
129 },
130 BackgroundColor(PALETTE[6].into()),
131 ));
132
133 builder.spawn((
134 Node {
135 width: vw(30),
136 height: vh(30),
137 border: UiRect::all(vmin(5)),
138 ..default()
139 },
140 BackgroundColor(PALETTE[7].into()),
141 BorderColor::all(PALETTE[9]),
142 ));
143 });
144}examples/testbed/full_ui.rs (line 353)
31fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
32 // Camera
33 commands.spawn((Camera2d, IsDefaultUiCamera, BoxShadowSamples(6)));
34
35 // root node
36 commands
37 .spawn(Node {
38 width: percent(100),
39 height: percent(100),
40 justify_content: JustifyContent::SpaceBetween,
41 ..default()
42 })
43 .insert(Pickable::IGNORE)
44 .with_children(|parent| {
45 // left vertical fill (border)
46 parent
47 .spawn((
48 Node {
49 width: px(200),
50 border: UiRect::all(px(2)),
51 ..default()
52 },
53 BackgroundColor(Color::srgb(0.65, 0.65, 0.65)),
54 ))
55 .with_children(|parent| {
56 // left vertical fill (content)
57 parent
58 .spawn((
59 Node {
60 width: percent(100),
61 flex_direction: FlexDirection::Column,
62 padding: UiRect::all(px(5)),
63 row_gap: px(5),
64 ..default()
65 },
66 BackgroundColor(Color::srgb(0.15, 0.15, 0.15)),
67 Visibility::Visible,
68 ))
69 .with_children(|parent| {
70 // text
71 parent.spawn((
72 Text::new("Text Example"),
73 TextFont {
74 font: asset_server.load("fonts/FiraSans-Bold.ttf"),
75 font_size: 25.0,
76 ..default()
77 },
78 // Because this is a distinct label widget and
79 // not button/list item text, this is necessary
80 // for accessibility to treat the text accordingly.
81 Label,
82 ));
83
84 #[cfg(feature = "bevy_ui_debug")]
85 {
86 // Debug overlay text
87 parent.spawn((
88 Text::new("Press Space to toggle debug outlines."),
89 TextFont {
90 font: asset_server.load("fonts/FiraSans-Bold.ttf"),
91 ..default()
92 },
93 Label,
94 ));
95
96 parent.spawn((
97 Text::new("V: toggle UI root's visibility"),
98 TextFont {
99 font: asset_server.load("fonts/FiraSans-Bold.ttf"),
100 font_size: 12.,
101 ..default()
102 },
103 Label,
104 ));
105
106 parent.spawn((
107 Text::new("S: toggle outlines for hidden nodes"),
108 TextFont {
109 font: asset_server.load("fonts/FiraSans-Bold.ttf"),
110 font_size: 12.,
111 ..default()
112 },
113 Label,
114 ));
115 parent.spawn((
116 Text::new("C: toggle outlines for clipped nodes"),
117 TextFont {
118 font: asset_server.load("fonts/FiraSans-Bold.ttf"),
119 font_size: 12.,
120 ..default()
121 },
122 Label,
123 ));
124 }
125 #[cfg(not(feature = "bevy_ui_debug"))]
126 parent.spawn((
127 Text::new("Try enabling feature \"bevy_ui_debug\"."),
128 TextFont {
129 font: asset_server.load("fonts/FiraSans-Bold.ttf"),
130 ..default()
131 },
132 Label,
133 ));
134 });
135 });
136 // right vertical fill
137 parent
138 .spawn(Node {
139 flex_direction: FlexDirection::Column,
140 justify_content: JustifyContent::Center,
141 align_items: AlignItems::Center,
142 width: px(200),
143 ..default()
144 })
145 .with_children(|parent| {
146 // Title
147 parent.spawn((
148 Text::new("Scrolling list"),
149 TextFont {
150 font: asset_server.load("fonts/FiraSans-Bold.ttf"),
151 font_size: 21.,
152 ..default()
153 },
154 Label,
155 ));
156 // Scrolling list
157 parent
158 .spawn((
159 Node {
160 flex_direction: FlexDirection::Column,
161 align_self: AlignSelf::Stretch,
162 height: percent(50),
163 overflow: Overflow::scroll_y(),
164 ..default()
165 },
166 BackgroundColor(Color::srgb(0.10, 0.10, 0.10)),
167 ))
168 .with_children(|parent| {
169 parent
170 .spawn((
171 Node {
172 flex_direction: FlexDirection::Column,
173 ..Default::default()
174 },
175 BackgroundGradient::from(LinearGradient::to_bottom(vec![
176 ColorStop::auto(NAVY),
177 ColorStop::auto(Color::BLACK),
178 ])),
179 Pickable {
180 should_block_lower: false,
181 ..Default::default()
182 },
183 ))
184 .with_children(|parent| {
185 // List items
186 for i in 0..25 {
187 parent
188 .spawn((
189 Text(format!("Item {i}")),
190 TextFont {
191 font: asset_server
192 .load("fonts/FiraSans-Bold.ttf"),
193 ..default()
194 },
195 Label,
196 AccessibilityNode(Accessible::new(Role::ListItem)),
197 ))
198 .insert(Pickable {
199 should_block_lower: false,
200 ..default()
201 });
202 }
203 });
204 });
205 });
206
207 parent
208 .spawn(Node {
209 left: px(210),
210 bottom: px(10),
211 position_type: PositionType::Absolute,
212 ..default()
213 })
214 .with_children(|parent| {
215 parent
216 .spawn((
217 Node {
218 width: px(200),
219 height: px(200),
220 border: UiRect::all(px(20)),
221 flex_direction: FlexDirection::Column,
222 justify_content: JustifyContent::Center,
223 ..default()
224 },
225 BorderColor::all(LIME),
226 BackgroundColor(Color::srgb(0.8, 0.8, 1.)),
227 ))
228 .with_children(|parent| {
229 parent.spawn((
230 ImageNode::new(asset_server.load("branding/bevy_logo_light.png")),
231 // Uses the transform to rotate the logo image by 45 degrees
232 Node {
233 ..Default::default()
234 },
235 UiTransform {
236 rotation: Rot2::radians(0.25 * PI),
237 ..Default::default()
238 },
239 BorderRadius::all(px(10)),
240 Outline {
241 width: px(2),
242 offset: px(4),
243 color: DARK_GRAY.into(),
244 },
245 ));
246 });
247 });
248
249 let shadow_style = ShadowStyle {
250 color: Color::BLACK.with_alpha(0.5),
251 blur_radius: px(2),
252 x_offset: px(10),
253 y_offset: px(10),
254 ..default()
255 };
256
257 // render order test: reddest in the back, whitest in the front (flex center)
258 parent
259 .spawn(Node {
260 width: percent(100),
261 height: percent(100),
262 position_type: PositionType::Absolute,
263 align_items: AlignItems::Center,
264 justify_content: JustifyContent::Center,
265 ..default()
266 })
267 .insert(Pickable::IGNORE)
268 .with_children(|parent| {
269 parent
270 .spawn((
271 Node {
272 width: px(100),
273 height: px(100),
274 ..default()
275 },
276 BackgroundColor(Color::srgb(1.0, 0.0, 0.)),
277 BoxShadow::from(shadow_style),
278 ))
279 .with_children(|parent| {
280 parent.spawn((
281 Node {
282 // Take the size of the parent node.
283 width: percent(100),
284 height: percent(100),
285 position_type: PositionType::Absolute,
286 left: px(20),
287 bottom: px(20),
288 ..default()
289 },
290 BackgroundColor(Color::srgb(1.0, 0.3, 0.3)),
291 BoxShadow::from(shadow_style),
292 ));
293 parent.spawn((
294 Node {
295 width: percent(100),
296 height: percent(100),
297 position_type: PositionType::Absolute,
298 left: px(40),
299 bottom: px(40),
300 ..default()
301 },
302 BackgroundColor(Color::srgb(1.0, 0.5, 0.5)),
303 BoxShadow::from(shadow_style),
304 ));
305 parent.spawn((
306 Node {
307 width: percent(100),
308 height: percent(100),
309 position_type: PositionType::Absolute,
310 left: px(60),
311 bottom: px(60),
312 ..default()
313 },
314 BackgroundColor(Color::srgb(0.0, 0.7, 0.7)),
315 BoxShadow::from(shadow_style),
316 ));
317 // alpha test
318 parent.spawn((
319 Node {
320 width: percent(100),
321 height: percent(100),
322 position_type: PositionType::Absolute,
323 left: px(80),
324 bottom: px(80),
325 ..default()
326 },
327 BackgroundColor(Color::srgba(1.0, 0.9, 0.9, 0.4)),
328 BoxShadow::from(ShadowStyle {
329 color: Color::BLACK.with_alpha(0.3),
330 ..shadow_style
331 }),
332 ));
333 });
334 });
335 // bevy logo (flex center)
336 parent
337 .spawn(Node {
338 width: percent(100),
339 position_type: PositionType::Absolute,
340 justify_content: JustifyContent::Center,
341 align_items: AlignItems::FlexStart,
342 ..default()
343 })
344 .with_children(|parent| {
345 // bevy logo (image)
346 parent
347 .spawn((
348 ImageNode::new(asset_server.load("branding/bevy_logo_dark_big.png"))
349 .with_mode(NodeImageMode::Stretch),
350 Node {
351 width: px(500),
352 height: px(125),
353 margin: UiRect::top(vmin(5)),
354 ..default()
355 },
356 ))
357 .with_children(|parent| {
358 // alt text
359 // This UI node takes up no space in the layout and the `Text` component is used by the accessibility module
360 // and is not rendered.
361 parent.spawn((
362 Node {
363 display: Display::None,
364 ..default()
365 },
366 Text::new("Bevy logo"),
367 ));
368 });
369 });
370
371 // four bevy icons demonstrating image flipping
372 parent
373 .spawn(Node {
374 width: percent(100),
375 height: percent(100),
376 position_type: PositionType::Absolute,
377 justify_content: JustifyContent::Center,
378 align_items: AlignItems::FlexEnd,
379 column_gap: px(10),
380 padding: UiRect::all(px(10)),
381 ..default()
382 })
383 .insert(Pickable::IGNORE)
384 .with_children(|parent| {
385 for (flip_x, flip_y) in
386 [(false, false), (false, true), (true, true), (true, false)]
387 {
388 parent.spawn((
389 ImageNode {
390 image: asset_server.load("branding/icon.png"),
391 flip_x,
392 flip_y,
393 ..default()
394 },
395 Node {
396 // The height will be chosen automatically to preserve the image's aspect ratio
397 width: px(75),
398 ..default()
399 },
400 ));
401 }
402 });
403 });
404}