bevy_ui/ui_node.rs
1use crate::{
2 ui_transform::{UiGlobalTransform, UiTransform},
3 FocusPolicy, UiRect, Val,
4};
5use bevy_camera::{visibility::Visibility, Camera, RenderTarget};
6use bevy_color::{Alpha, Color};
7use bevy_derive::{Deref, DerefMut};
8use bevy_ecs::{prelude::*, system::SystemParam};
9use bevy_math::{BVec2, Rect, UVec2, Vec2, Vec4, Vec4Swizzles};
10use bevy_reflect::prelude::*;
11use bevy_sprite::BorderRect;
12use bevy_utils::once;
13use bevy_window::{PrimaryWindow, WindowRef};
14use core::{f32, num::NonZero};
15use derive_more::derive::From;
16use smallvec::SmallVec;
17use thiserror::Error;
18use tracing::warn;
19
20/// Provides the computed size and layout properties of the node.
21///
22/// Fields in this struct are public but should not be modified under most circumstances.
23/// For example, in a scrollbar you may want to derive the handle's size from the proportion of
24/// scrollable content in-view. You can directly modify `ComputedNode` after layout to set the
25/// handle size without any delays.
26#[derive(Component, Debug, Copy, Clone, PartialEq, Reflect)]
27#[reflect(Component, Default, Debug, Clone)]
28pub struct ComputedNode {
29 /// The order of the node in the UI layout.
30 /// Nodes with a higher stack index are drawn on top of and receive interactions before nodes with lower stack indices.
31 ///
32 /// Automatically calculated in [`UiSystems::Stack`](`super::UiSystems::Stack`).
33 pub stack_index: u32,
34 /// The size of the node as width and height in physical pixels.
35 ///
36 /// Automatically calculated by [`ui_layout_system`](`super::layout::ui_layout_system`).
37 pub size: Vec2,
38 /// Size of this node's content.
39 ///
40 /// Automatically calculated by [`ui_layout_system`](`super::layout::ui_layout_system`).
41 pub content_size: Vec2,
42 /// Space allocated for scrollbars.
43 ///
44 /// Automatically calculated by [`ui_layout_system`](`super::layout::ui_layout_system`).
45 pub scrollbar_size: Vec2,
46 /// Resolved offset of scrolled content
47 ///
48 /// Automatically calculated by [`ui_layout_system`](`super::layout::ui_layout_system`).
49 pub scroll_position: Vec2,
50 /// The width of this node's outline.
51 /// If this value is `Auto`, negative or `0.` then no outline will be rendered.
52 /// Outline updates bypass change detection.
53 ///
54 /// Automatically calculated by [`ui_layout_system`](`super::layout::ui_layout_system`).
55 pub outline_width: f32,
56 /// The amount of space between the outline and the edge of the node.
57 /// Outline updates bypass change detection.
58 ///
59 /// Automatically calculated by [`ui_layout_system`](`super::layout::ui_layout_system`).
60 pub outline_offset: f32,
61 /// The unrounded size of the node as width and height in physical pixels.
62 ///
63 /// Automatically calculated by [`ui_layout_system`](`super::layout::ui_layout_system`).
64 pub unrounded_size: Vec2,
65 /// Resolved border values in physical pixels.
66 /// Border updates bypass change detection.
67 ///
68 /// Automatically calculated by [`ui_layout_system`](`super::layout::ui_layout_system`).
69 pub border: BorderRect,
70 /// Resolved border radius values in physical pixels.
71 /// Border radius updates bypass change detection.
72 ///
73 /// Automatically calculated by [`ui_layout_system`](`super::layout::ui_layout_system`).
74 pub border_radius: ResolvedBorderRadius,
75 /// Resolved padding values in physical pixels.
76 /// Padding updates bypass change detection.
77 ///
78 /// Automatically calculated by [`ui_layout_system`](`super::layout::ui_layout_system`).
79 pub padding: BorderRect,
80 /// Inverse scale factor for this Node.
81 /// Multiply physical coordinates by the inverse scale factor to give logical coordinates.
82 ///
83 /// Automatically calculated by [`ui_layout_system`](`super::layout::ui_layout_system`).
84 pub inverse_scale_factor: f32,
85}
86
87impl ComputedNode {
88 /// The calculated node size as width and height in physical pixels.
89 ///
90 /// Automatically calculated by [`ui_layout_system`](`super::layout::ui_layout_system`).
91 #[inline]
92 pub const fn size(&self) -> Vec2 {
93 self.size
94 }
95
96 /// The calculated node content size as width and height in physical pixels.
97 ///
98 /// Automatically calculated by [`ui_layout_system`](`super::layout::ui_layout_system`).
99 #[inline]
100 pub const fn content_size(&self) -> Vec2 {
101 self.content_size
102 }
103
104 /// Check if the node is empty.
105 /// A node is considered empty if it has a zero or negative extent along either of its axes.
106 #[inline]
107 pub const fn is_empty(&self) -> bool {
108 self.size.x <= 0. || self.size.y <= 0.
109 }
110
111 /// The order of the node in the UI layout.
112 /// Nodes with a higher stack index are drawn on top of and receive interactions before nodes with lower stack indices.
113 ///
114 /// Automatically calculated in [`UiSystems::Stack`](super::UiSystems::Stack).
115 pub const fn stack_index(&self) -> u32 {
116 self.stack_index
117 }
118
119 /// The calculated node size as width and height in physical pixels before rounding.
120 ///
121 /// Automatically calculated by [`ui_layout_system`](`super::layout::ui_layout_system`).
122 #[inline]
123 pub const fn unrounded_size(&self) -> Vec2 {
124 self.unrounded_size
125 }
126
127 /// Returns the thickness of the UI node's outline in physical pixels.
128 /// If this value is negative or `0.` then no outline will be rendered.
129 ///
130 /// Automatically calculated by [`ui_layout_system`](`super::layout::ui_layout_system`).
131 #[inline]
132 pub const fn outline_width(&self) -> f32 {
133 self.outline_width
134 }
135
136 /// Returns the amount of space between the outline and the edge of the node in physical pixels.
137 ///
138 /// Automatically calculated by [`ui_layout_system`](`super::layout::ui_layout_system`).
139 #[inline]
140 pub const fn outline_offset(&self) -> f32 {
141 self.outline_offset
142 }
143
144 /// Returns the size of the node when including its outline.
145 ///
146 /// Automatically calculated by [`ui_layout_system`](`super::layout::ui_layout_system`).
147 #[inline]
148 pub const fn outlined_node_size(&self) -> Vec2 {
149 let offset = 2. * (self.outline_offset + self.outline_width);
150 Vec2::new(self.size.x + offset, self.size.y + offset)
151 }
152
153 /// Returns the border radius for each corner of the outline
154 /// An outline's border radius is derived from the node's border-radius
155 /// so that the outline wraps the border equally at all points.
156 ///
157 /// Automatically calculated by [`ui_layout_system`](`super::layout::ui_layout_system`).
158 #[inline]
159 pub const fn outline_radius(&self) -> ResolvedBorderRadius {
160 let outer_distance = self.outline_width + self.outline_offset;
161 const fn compute_radius(radius: f32, outer_distance: f32) -> f32 {
162 if radius > 0. {
163 radius + outer_distance
164 } else {
165 0.
166 }
167 }
168 ResolvedBorderRadius {
169 top_left: compute_radius(self.border_radius.top_left, outer_distance),
170 top_right: compute_radius(self.border_radius.top_right, outer_distance),
171 bottom_right: compute_radius(self.border_radius.bottom_right, outer_distance),
172 bottom_left: compute_radius(self.border_radius.bottom_left, outer_distance),
173 }
174 }
175
176 /// Returns the thickness of the node's border on each edge in physical pixels.
177 ///
178 /// Automatically calculated by [`ui_layout_system`](`super::layout::ui_layout_system`).
179 #[inline]
180 pub const fn border(&self) -> BorderRect {
181 self.border
182 }
183
184 /// Returns the border radius for each of the node's corners in physical pixels.
185 ///
186 /// Automatically calculated by [`ui_layout_system`](`super::layout::ui_layout_system`).
187 #[inline]
188 pub const fn border_radius(&self) -> ResolvedBorderRadius {
189 self.border_radius
190 }
191
192 /// Returns the inner border radius for each of the node's corners in physical pixels.
193 pub fn inner_radius(&self) -> ResolvedBorderRadius {
194 fn clamp_corner(r: f32, size: Vec2, offset: Vec2) -> f32 {
195 let s = 0.5 * size + offset;
196 let sm = s.x.min(s.y);
197 r.min(sm)
198 }
199 let b = Vec4::from((self.border.min_inset, self.border.max_inset));
200 let s = self.size() - b.xy() - b.zw();
201 ResolvedBorderRadius {
202 top_left: clamp_corner(self.border_radius.top_left, s, b.xy()),
203 top_right: clamp_corner(self.border_radius.top_right, s, b.zy()),
204 bottom_right: clamp_corner(self.border_radius.bottom_left, s, b.xw()),
205 bottom_left: clamp_corner(self.border_radius.bottom_right, s, b.zw()),
206 }
207 }
208
209 /// Returns the thickness of the node's padding on each edge in physical pixels.
210 ///
211 /// Automatically calculated by [`ui_layout_system`](`super::layout::ui_layout_system`).
212 #[inline]
213 pub const fn padding(&self) -> BorderRect {
214 self.padding
215 }
216
217 /// Returns the combined inset on each edge including both padding and border thickness in physical pixels.
218 #[inline]
219 pub fn content_inset(&self) -> BorderRect {
220 let mut content_inset = self.border + self.padding;
221 content_inset.max_inset += self.scrollbar_size;
222 content_inset
223 }
224
225 /// Returns the inverse of the scale factor for this node.
226 /// To convert from physical coordinates to logical coordinates multiply by this value.
227 #[inline]
228 pub const fn inverse_scale_factor(&self) -> f32 {
229 self.inverse_scale_factor
230 }
231
232 // Returns true if `point` within the node.
233 //
234 // Matches the sdf function in `ui.wgsl` that is used by the UI renderer to draw rounded rectangles.
235 pub fn contains_point(&self, transform: UiGlobalTransform, point: Vec2) -> bool {
236 let Some(local_point) = transform
237 .try_inverse()
238 .map(|transform| transform.transform_point2(point))
239 else {
240 return false;
241 };
242 let [top, bottom] = if local_point.x < 0. {
243 [self.border_radius.top_left, self.border_radius.bottom_left]
244 } else {
245 [
246 self.border_radius.top_right,
247 self.border_radius.bottom_right,
248 ]
249 };
250 let r = if local_point.y < 0. { top } else { bottom };
251 let corner_to_point = local_point.abs() - 0.5 * self.size;
252 let q = corner_to_point + r;
253 let l = q.max(Vec2::ZERO).length();
254 let m = q.max_element().min(0.);
255 l + m - r < 0.
256 }
257
258 /// Transform a point to normalized node space with the center of the node at the origin and the corners at [+/-0.5, +/-0.5]
259 pub fn normalize_point(&self, transform: UiGlobalTransform, point: Vec2) -> Option<Vec2> {
260 self.size
261 .cmpgt(Vec2::ZERO)
262 .all()
263 .then(|| transform.try_inverse())
264 .flatten()
265 .map(|transform| transform.transform_point2(point) / self.size)
266 }
267
268 /// Resolve the node's clipping rect in local space
269 pub fn resolve_clip_rect(
270 &self,
271 overflow: Overflow,
272 overflow_clip_margin: OverflowClipMargin,
273 ) -> Rect {
274 let mut clip_rect = Rect::from_center_size(Vec2::ZERO, self.size);
275
276 let clip_inset = match overflow_clip_margin.visual_box {
277 OverflowClipBox::BorderBox => BorderRect::ZERO,
278 OverflowClipBox::ContentBox => self.content_inset(),
279 OverflowClipBox::PaddingBox => self.border(),
280 };
281
282 clip_rect.min += clip_inset.min_inset;
283 clip_rect.max -= clip_inset.max_inset;
284
285 if overflow.x == OverflowAxis::Visible {
286 clip_rect.min.x = -f32::INFINITY;
287 clip_rect.max.x = f32::INFINITY;
288 }
289 if overflow.y == OverflowAxis::Visible {
290 clip_rect.min.y = -f32::INFINITY;
291 clip_rect.max.y = f32::INFINITY;
292 }
293
294 clip_rect
295 }
296
297 /// Returns the node's border-box in object-centered physical coordinates.
298 /// This is the full rectangle enclosing the node.
299 #[inline]
300 pub fn border_box(&self) -> Rect {
301 Rect::from_center_size(Vec2::ZERO, self.size)
302 }
303
304 /// Returns the node's padding-box in object-centered physical coordinates.
305 /// This is the region inside the border containing the node's padding and content areas.
306 #[inline]
307 pub fn padding_box(&self) -> Rect {
308 let mut out = self.border_box();
309 out.min += self.border.min_inset;
310 out.max -= self.border.max_inset;
311 out
312 }
313
314 /// Returns the node's content-box in object-centered physical coordinates.
315 /// This is the innermost region of the node, where its content is placed.
316 #[inline]
317 pub fn content_box(&self) -> Rect {
318 let mut out = self.border_box();
319 let content_inset = self.content_inset();
320 out.min += content_inset.min_inset;
321 out.max -= content_inset.max_inset;
322 out
323 }
324
325 const fn compute_thumb(
326 gutter_min: f32,
327 content_length: f32,
328 gutter_length: f32,
329 scroll_position: f32,
330 ) -> [f32; 2] {
331 if content_length <= gutter_length {
332 return [gutter_min, gutter_min + gutter_length];
333 }
334 let thumb_len = gutter_length * gutter_length / content_length;
335 let thumb_min = gutter_min + scroll_position * gutter_length / content_length;
336 [thumb_min, thumb_min + thumb_len]
337 }
338
339 /// Compute the bounds of the horizontal scrollbar and the thumb
340 /// in object-centered coordinates.
341 pub fn horizontal_scrollbar(&self) -> Option<(Rect, [f32; 2])> {
342 if self.scrollbar_size.y <= 0. {
343 return None;
344 }
345 let content_inset = self.content_inset();
346 let half_size = 0.5 * self.size;
347 let min_x = -half_size.x + content_inset.min_inset.x;
348 let max_x = half_size.x - content_inset.max_inset.x;
349 let min_y = half_size.y - content_inset.max_inset.y;
350 let max_y = min_y + self.scrollbar_size.y;
351 let gutter = Rect {
352 min: Vec2::new(min_x, min_y),
353 max: Vec2::new(max_x, max_y),
354 };
355 Some((
356 gutter,
357 Self::compute_thumb(
358 gutter.min.x,
359 self.content_size.x,
360 gutter.size().x,
361 self.scroll_position.x,
362 ),
363 ))
364 }
365
366 /// Compute the bounds of the vertical scrollbar and the thumb
367 /// in object-centered coordinates.
368 pub fn vertical_scrollbar(&self) -> Option<(Rect, [f32; 2])> {
369 if self.scrollbar_size.x <= 0. {
370 return None;
371 }
372 let content_inset = self.content_inset();
373 let half_size = 0.5 * self.size;
374 let min_x = half_size.x - content_inset.max_inset.x;
375 let max_x = min_x + self.scrollbar_size.x;
376 let min_y = -half_size.y + content_inset.min_inset.y;
377 let max_y = half_size.y - content_inset.max_inset.y;
378 let gutter = Rect {
379 min: Vec2::new(min_x, min_y),
380 max: Vec2::new(max_x, max_y),
381 };
382 Some((
383 gutter,
384 Self::compute_thumb(
385 gutter.min.y,
386 self.content_size.y,
387 gutter.size().y,
388 self.scroll_position.y,
389 ),
390 ))
391 }
392}
393
394impl ComputedNode {
395 pub const DEFAULT: Self = Self {
396 stack_index: 0,
397 size: Vec2::ZERO,
398 content_size: Vec2::ZERO,
399 scrollbar_size: Vec2::ZERO,
400 scroll_position: Vec2::ZERO,
401 outline_width: 0.,
402 outline_offset: 0.,
403 unrounded_size: Vec2::ZERO,
404 border_radius: ResolvedBorderRadius::ZERO,
405 border: BorderRect::ZERO,
406 padding: BorderRect::ZERO,
407 inverse_scale_factor: 1.,
408 };
409}
410
411impl Default for ComputedNode {
412 fn default() -> Self {
413 Self::DEFAULT
414 }
415}
416
417/// The scroll position of the node. Values are in logical pixels, increasing from top-left to bottom-right.
418///
419/// Increasing the x-coordinate causes the scrolled content to visibly move left on the screen, while increasing the y-coordinate causes the scrolled content to move up.
420/// This might seem backwards, however what's really happening is that
421/// the scroll position is moving the visible "window" in the local coordinate system of the scrolled content -
422/// moving the window down causes the content to move up.
423///
424/// Updating the values of `ScrollPosition` will reposition the children of the node by the offset amount in logical pixels.
425/// `ScrollPosition` may be updated by the layout system when a layout change makes a previously valid `ScrollPosition` invalid.
426/// Changing this does nothing on a `Node` without setting at least one `OverflowAxis` to `OverflowAxis::Scroll`.
427#[derive(Component, Debug, Clone, Default, Deref, DerefMut, Reflect)]
428#[reflect(Component, Default, Clone)]
429pub struct ScrollPosition(pub Vec2);
430
431impl ScrollPosition {
432 pub const DEFAULT: Self = Self(Vec2::ZERO);
433}
434
435impl From<Vec2> for ScrollPosition {
436 fn from(value: Vec2) -> Self {
437 Self(value)
438 }
439}
440
441/// Controls whether a UI element ignores its parent's [`ScrollPosition`] along specific axes.
442///
443/// When an axis is set to `true`, the node will not have the parent’s scroll position applied
444/// on that axis. This can be used to keep an element visually fixed along one or both axes
445/// even when its parent UI element is scrolled.
446#[derive(Component, Debug, Clone, Default, Deref, DerefMut, Reflect)]
447#[reflect(Component, Default, Clone)]
448pub struct IgnoreScroll(pub BVec2);
449
450impl From<BVec2> for IgnoreScroll {
451 fn from(value: BVec2) -> Self {
452 Self(value)
453 }
454}
455
456/// The base component for UI entities. It describes UI layout and style properties.
457///
458/// When defining new types of UI entities, require [`Node`] to make them behave like UI nodes.
459///
460/// Nodes can be laid out using either Flexbox or CSS Grid Layout.
461///
462/// See below for general learning resources and for documentation on the individual style properties.
463///
464/// ### Flexbox
465///
466/// - [MDN: Basic Concepts of Flexbox](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Flexible_Box_Layout/Basic_Concepts_of_Flexbox)
467/// - [A Complete Guide To Flexbox](https://css-tricks.com/snippets/css/a-guide-to-flexbox/) by CSS Tricks. This is detailed guide with illustrations and comprehensive written explanation of the different Flexbox properties and how they work.
468/// - [Flexbox Froggy](https://flexboxfroggy.com/). An interactive tutorial/game that teaches the essential parts of Flexbox in a fun engaging way.
469///
470/// ### CSS Grid
471///
472/// - [MDN: Basic Concepts of Grid Layout](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Grid_Layout/Basic_Concepts_of_Grid_Layout)
473/// - [A Complete Guide To CSS Grid](https://css-tricks.com/snippets/css/complete-guide-grid/) by CSS Tricks. This is detailed guide with illustrations and comprehensive written explanation of the different CSS Grid properties and how they work.
474/// - [CSS Grid Garden](https://cssgridgarden.com/). An interactive tutorial/game that teaches the essential parts of CSS Grid in a fun engaging way.
475///
476/// # See also
477///
478/// - [`RelativeCursorPosition`](crate::RelativeCursorPosition) to obtain the cursor position relative to this node
479/// - [`Interaction`](crate::Interaction) to obtain the interaction state of this node
480
481#[derive(Component, Clone, PartialEq, Debug, Reflect)]
482#[require(
483 ComputedNode,
484 ComputedUiTargetCamera,
485 ComputedUiRenderTargetInfo,
486 UiTransform,
487 BackgroundColor,
488 BorderColor,
489 FocusPolicy,
490 ScrollPosition,
491 Visibility,
492 ZIndex
493)]
494#[reflect(Component, Default, PartialEq, Debug, Clone)]
495#[cfg_attr(
496 feature = "serialize",
497 derive(serde::Serialize, serde::Deserialize),
498 reflect(Serialize, Deserialize)
499)]
500pub struct Node {
501 /// Which layout algorithm to use when laying out this node's contents:
502 /// - [`Display::Flex`]: Use the Flexbox layout algorithm
503 /// - [`Display::Grid`]: Use the CSS Grid layout algorithm
504 /// - [`Display::None`]: Hide this node and perform layout as if it does not exist.
505 ///
506 /// <https://developer.mozilla.org/en-US/docs/Web/CSS/display>
507 pub display: Display,
508
509 /// Which part of a Node's box length styles like width and height control
510 /// - [`BoxSizing::BorderBox`]: They refer to the "border box" size (size including padding and border)
511 /// - [`BoxSizing::ContentBox`]: They refer to the "content box" size (size excluding padding and border)
512 ///
513 /// `BoxSizing::BorderBox` is generally considered more intuitive and is the default in Bevy even though it is not on the web.
514 ///
515 /// See: <https://developer.mozilla.org/en-US/docs/Web/CSS/box-sizing>
516 pub box_sizing: BoxSizing,
517
518 /// Whether a node should be laid out in-flow with, or independently of its siblings:
519 /// - [`PositionType::Relative`]: Layout this node in-flow with other nodes using the usual (flexbox/grid) layout algorithm.
520 /// - [`PositionType::Absolute`]: Layout this node on top and independently of other nodes.
521 ///
522 /// <https://developer.mozilla.org/en-US/docs/Web/CSS/position>
523 pub position_type: PositionType,
524
525 /// Whether overflowing content should be displayed or clipped.
526 ///
527 /// <https://developer.mozilla.org/en-US/docs/Web/CSS/overflow>
528 pub overflow: Overflow,
529
530 /// How much space in logical pixels should be reserved for scrollbars when overflow is set to scroll or auto on an axis.
531 pub scrollbar_width: f32,
532
533 /// How the bounds of clipped content should be determined
534 ///
535 /// <https://developer.mozilla.org/en-US/docs/Web/CSS/overflow-clip-margin>
536 pub overflow_clip_margin: OverflowClipMargin,
537
538 /// The horizontal position of the left edge of the node.
539 /// - For relatively positioned nodes, this is relative to the node's position as computed during regular layout.
540 /// - For absolutely positioned nodes, this is relative to the *parent* node's bounding box.
541 ///
542 /// <https://developer.mozilla.org/en-US/docs/Web/CSS/left>
543 pub left: Val,
544
545 /// The horizontal position of the right edge of the node.
546 /// - For relatively positioned nodes, this is relative to the node's position as computed during regular layout.
547 /// - For absolutely positioned nodes, this is relative to the *parent* node's bounding box.
548 ///
549 /// <https://developer.mozilla.org/en-US/docs/Web/CSS/right>
550 pub right: Val,
551
552 /// The vertical position of the top edge of the node.
553 /// - For relatively positioned nodes, this is relative to the node's position as computed during regular layout.
554 /// - For absolutely positioned nodes, this is relative to the *parent* node's bounding box.
555 ///
556 /// <https://developer.mozilla.org/en-US/docs/Web/CSS/top>
557 pub top: Val,
558
559 /// The vertical position of the bottom edge of the node.
560 /// - For relatively positioned nodes, this is relative to the node's position as computed during regular layout.
561 /// - For absolutely positioned nodes, this is relative to the *parent* node's bounding box.
562 ///
563 /// <https://developer.mozilla.org/en-US/docs/Web/CSS/bottom>
564 pub bottom: Val,
565
566 /// The ideal width of the node. `width` is used when it is within the bounds defined by `min_width` and `max_width`.
567 ///
568 /// <https://developer.mozilla.org/en-US/docs/Web/CSS/width>
569 pub width: Val,
570
571 /// The ideal height of the node. `height` is used when it is within the bounds defined by `min_height` and `max_height`.
572 ///
573 /// <https://developer.mozilla.org/en-US/docs/Web/CSS/height>
574 pub height: Val,
575
576 /// The minimum width of the node. `min_width` is used if it is greater than `width` and/or `max_width`.
577 ///
578 /// <https://developer.mozilla.org/en-US/docs/Web/CSS/min-width>
579 pub min_width: Val,
580
581 /// The minimum height of the node. `min_height` is used if it is greater than `height` and/or `max_height`.
582 ///
583 /// <https://developer.mozilla.org/en-US/docs/Web/CSS/min-height>
584 pub min_height: Val,
585
586 /// The maximum width of the node. `max_width` is used if it is within the bounds defined by `min_width` and `width`.
587 ///
588 /// <https://developer.mozilla.org/en-US/docs/Web/CSS/max-width>
589 pub max_width: Val,
590
591 /// The maximum height of the node. `max_height` is used if it is within the bounds defined by `min_height` and `height`.
592 ///
593 /// <https://developer.mozilla.org/en-US/docs/Web/CSS/max-height>
594 pub max_height: Val,
595
596 /// The aspect ratio of the node (defined as `width / height`)
597 ///
598 /// <https://developer.mozilla.org/en-US/docs/Web/CSS/aspect-ratio>
599 pub aspect_ratio: Option<f32>,
600
601 /// Used to control how each individual item is aligned by default within the space they're given.
602 /// - For Flexbox containers, sets default cross axis alignment of the child items.
603 /// - For CSS Grid containers, controls block (vertical) axis alignment of children of this grid container within their grid areas.
604 ///
605 /// This value is overridden if [`AlignSelf`] on the child node is set.
606 ///
607 /// <https://developer.mozilla.org/en-US/docs/Web/CSS/align-items>
608 pub align_items: AlignItems,
609
610 /// Used to control how each individual item is aligned by default within the space they're given.
611 /// - For Flexbox containers, this property has no effect. See `justify_content` for main axis alignment of flex items.
612 /// - For CSS Grid containers, sets default inline (horizontal) axis alignment of child items within their grid areas.
613 ///
614 /// This value is overridden if [`JustifySelf`] on the child node is set.
615 ///
616 /// <https://developer.mozilla.org/en-US/docs/Web/CSS/justify-items>
617 pub justify_items: JustifyItems,
618
619 /// Used to control how the specified item is aligned within the space it's given.
620 /// - For Flexbox items, controls cross axis alignment of the item.
621 /// - For CSS Grid items, controls block (vertical) axis alignment of a grid item within its grid area.
622 ///
623 /// If set to `Auto`, alignment is inherited from the value of [`AlignItems`] set on the parent node.
624 ///
625 /// <https://developer.mozilla.org/en-US/docs/Web/CSS/align-self>
626 pub align_self: AlignSelf,
627
628 /// Used to control how the specified item is aligned within the space it's given.
629 /// - For Flexbox items, this property has no effect. See `justify_content` for main axis alignment of flex items.
630 /// - For CSS Grid items, controls inline (horizontal) axis alignment of a grid item within its grid area.
631 ///
632 /// If set to `Auto`, alignment is inherited from the value of [`JustifyItems`] set on the parent node.
633 ///
634 /// <https://developer.mozilla.org/en-US/docs/Web/CSS/justify-self>
635 pub justify_self: JustifySelf,
636
637 /// Used to control how items are distributed.
638 /// - For Flexbox containers, controls alignment of lines if `flex_wrap` is set to [`FlexWrap::Wrap`] and there are multiple lines of items.
639 /// - For CSS Grid containers, controls alignment of grid rows.
640 ///
641 /// <https://developer.mozilla.org/en-US/docs/Web/CSS/align-content>
642 pub align_content: AlignContent,
643
644 /// Used to control how items are distributed.
645 /// - For Flexbox containers, controls alignment of items in the main axis.
646 /// - For CSS Grid containers, controls alignment of grid columns.
647 ///
648 /// <https://developer.mozilla.org/en-US/docs/Web/CSS/justify-content>
649 pub justify_content: JustifyContent,
650
651 /// The amount of space around a node outside its border.
652 ///
653 /// If a percentage value is used, the percentage is calculated based on the width of the parent node.
654 ///
655 /// # Example
656 /// ```
657 /// # use bevy_ui::{Node, UiRect, Val};
658 /// let node = Node {
659 /// margin: UiRect {
660 /// left: Val::Percent(10.),
661 /// right: Val::Percent(10.),
662 /// top: Val::Percent(15.),
663 /// bottom: Val::Percent(15.)
664 /// },
665 /// ..Default::default()
666 /// };
667 /// ```
668 /// A node with this style and a parent with dimensions of 100px by 300px will have calculated margins of 10px on both left and right edges, and 15px on both top and bottom edges.
669 ///
670 /// <https://developer.mozilla.org/en-US/docs/Web/CSS/margin>
671 pub margin: UiRect,
672
673 /// The amount of space between the edges of a node and its contents.
674 ///
675 /// If a percentage value is used, the percentage is calculated based on the width of the parent node.
676 ///
677 /// # Example
678 /// ```
679 /// # use bevy_ui::{Node, UiRect, Val};
680 /// let node = Node {
681 /// padding: UiRect {
682 /// left: Val::Percent(1.),
683 /// right: Val::Percent(2.),
684 /// top: Val::Percent(3.),
685 /// bottom: Val::Percent(4.)
686 /// },
687 /// ..Default::default()
688 /// };
689 /// ```
690 /// A node with this style and a parent with dimensions of 300px by 100px will have calculated padding of 3px on the left, 6px on the right, 9px on the top and 12px on the bottom.
691 ///
692 /// <https://developer.mozilla.org/en-US/docs/Web/CSS/padding>
693 pub padding: UiRect,
694
695 /// The amount of space between the margins of a node and its padding.
696 ///
697 /// If a percentage value is used, the percentage is calculated based on the width of the parent node.
698 ///
699 /// The size of the node will be expanded if there are constraints that prevent the layout algorithm from placing the border within the existing node boundary.
700 ///
701 /// <https://developer.mozilla.org/en-US/docs/Web/CSS/border-width>
702 pub border: UiRect,
703
704 /// Used to add rounded corners to a UI node. You can set a UI node to have uniformly
705 /// rounded corners or specify different radii for each corner. If a given radius exceeds half
706 /// the length of the smallest dimension between the node's height or width, the radius will
707 /// calculated as half the smallest dimension.
708 ///
709 /// Elliptical nodes are not supported yet. Percentage values are based on the node's smallest
710 /// dimension, either width or height.
711 ///
712 /// # Example
713 /// ```rust
714 /// # use bevy_ecs::prelude::*;
715 /// # use bevy_ui::prelude::*;
716 /// # use bevy_color::palettes::basic::{BLUE};
717 /// fn setup_ui(mut commands: Commands) {
718 /// commands.spawn((
719 /// Node {
720 /// width: Val::Px(100.),
721 /// height: Val::Px(100.),
722 /// border: UiRect::all(Val::Px(2.)),
723 /// border_radius: BorderRadius::new(
724 /// // top left
725 /// Val::Px(10.),
726 /// // top right
727 /// Val::Px(20.),
728 /// // bottom right
729 /// Val::Px(30.),
730 /// // bottom left
731 /// Val::Px(40.),
732 /// ),
733 /// ..Default::default()
734 /// },
735 /// BackgroundColor(BLUE.into()),
736 /// ));
737 /// }
738 /// ```
739 ///
740 /// <https://developer.mozilla.org/en-US/docs/Web/CSS/border-radius>
741 pub border_radius: BorderRadius,
742
743 /// Whether a Flexbox container should be a row or a column. This property has no effect on Grid nodes.
744 ///
745 /// <https://developer.mozilla.org/en-US/docs/Web/CSS/flex-direction>
746 pub flex_direction: FlexDirection,
747
748 /// Whether a Flexbox container should wrap its contents onto multiple lines if they overflow. This property has no effect on Grid nodes.
749 ///
750 /// <https://developer.mozilla.org/en-US/docs/Web/CSS/flex-wrap>
751 pub flex_wrap: FlexWrap,
752
753 /// Defines how much a flexbox item should grow if there's space available. Defaults to 0 (don't grow at all).
754 ///
755 /// <https://developer.mozilla.org/en-US/docs/Web/CSS/flex-grow>
756 pub flex_grow: f32,
757
758 /// Defines how much a flexbox item should shrink if there's not enough space available. Defaults to 1.
759 ///
760 /// <https://developer.mozilla.org/en-US/docs/Web/CSS/flex-shrink>
761 pub flex_shrink: f32,
762
763 /// The initial length of a flexbox in the main axis, before flex growing/shrinking properties are applied.
764 ///
765 /// `flex_basis` overrides `width` (if the main axis is horizontal) or `height` (if the main axis is vertical) when both are set, but it obeys the constraints defined by `min_width`/`min_height` and `max_width`/`max_height`.
766 ///
767 /// <https://developer.mozilla.org/en-US/docs/Web/CSS/flex-basis>
768 pub flex_basis: Val,
769
770 /// The size of the gutters between items in a vertical flexbox layout or between rows in a grid layout.
771 ///
772 /// Note: Values of `Val::Auto` are not valid and are treated as zero.
773 ///
774 /// <https://developer.mozilla.org/en-US/docs/Web/CSS/row-gap>
775 pub row_gap: Val,
776
777 /// The size of the gutters between items in a horizontal flexbox layout or between column in a grid layout.
778 ///
779 /// Note: Values of `Val::Auto` are not valid and are treated as zero.
780 ///
781 /// <https://developer.mozilla.org/en-US/docs/Web/CSS/column-gap>
782 pub column_gap: Val,
783
784 /// Controls whether automatically placed grid items are placed row-wise or column-wise as well as whether the sparse or dense packing algorithm is used.
785 /// Only affects Grid layouts.
786 ///
787 /// <https://developer.mozilla.org/en-US/docs/Web/CSS/grid-auto-flow>
788 pub grid_auto_flow: GridAutoFlow,
789
790 /// Defines the number of rows a grid has and the sizes of those rows. If grid items are given explicit placements then more rows may
791 /// be implicitly generated by items that are placed out of bounds. The sizes of those rows are controlled by `grid_auto_rows` property.
792 ///
793 /// <https://developer.mozilla.org/en-US/docs/Web/CSS/grid-template-rows>
794 pub grid_template_rows: Vec<RepeatedGridTrack>,
795
796 /// Defines the number of columns a grid has and the sizes of those columns. If grid items are given explicit placements then more columns may
797 /// be implicitly generated by items that are placed out of bounds. The sizes of those columns are controlled by `grid_auto_columns` property.
798 ///
799 /// <https://developer.mozilla.org/en-US/docs/Web/CSS/grid-template-columns>
800 pub grid_template_columns: Vec<RepeatedGridTrack>,
801
802 /// Defines the size of implicitly created rows. Rows are created implicitly when grid items are given explicit placements that are out of bounds
803 /// of the rows explicitly created using `grid_template_rows`.
804 ///
805 /// <https://developer.mozilla.org/en-US/docs/Web/CSS/grid-auto-rows>
806 pub grid_auto_rows: Vec<GridTrack>,
807 /// Defines the size of implicitly created columns. Columns are created implicitly when grid items are given explicit placements that are out of bounds
808 /// of the columns explicitly created using `grid_template_columns`.
809 ///
810 /// <https://developer.mozilla.org/en-US/docs/Web/CSS/grid-auto-columns>
811 pub grid_auto_columns: Vec<GridTrack>,
812
813 /// The row in which a grid item starts and how many rows it spans.
814 ///
815 /// <https://developer.mozilla.org/en-US/docs/Web/CSS/grid-row>
816 pub grid_row: GridPlacement,
817
818 /// The column in which a grid item starts and how many columns it spans.
819 ///
820 /// <https://developer.mozilla.org/en-US/docs/Web/CSS/grid-column>
821 pub grid_column: GridPlacement,
822}
823
824impl Node {
825 pub const DEFAULT: Self = Self {
826 display: Display::DEFAULT,
827 box_sizing: BoxSizing::DEFAULT,
828 position_type: PositionType::DEFAULT,
829 left: Val::Auto,
830 right: Val::Auto,
831 top: Val::Auto,
832 bottom: Val::Auto,
833 flex_direction: FlexDirection::DEFAULT,
834 flex_wrap: FlexWrap::DEFAULT,
835 align_items: AlignItems::DEFAULT,
836 justify_items: JustifyItems::DEFAULT,
837 align_self: AlignSelf::DEFAULT,
838 justify_self: JustifySelf::DEFAULT,
839 align_content: AlignContent::DEFAULT,
840 justify_content: JustifyContent::DEFAULT,
841 margin: UiRect::DEFAULT,
842 padding: UiRect::DEFAULT,
843 border: UiRect::DEFAULT,
844 border_radius: BorderRadius::DEFAULT,
845 flex_grow: 0.0,
846 flex_shrink: 1.0,
847 flex_basis: Val::Auto,
848 width: Val::Auto,
849 height: Val::Auto,
850 min_width: Val::Auto,
851 min_height: Val::Auto,
852 max_width: Val::Auto,
853 max_height: Val::Auto,
854 aspect_ratio: None,
855 overflow: Overflow::DEFAULT,
856 overflow_clip_margin: OverflowClipMargin::DEFAULT,
857 scrollbar_width: 0.,
858 row_gap: Val::ZERO,
859 column_gap: Val::ZERO,
860 grid_auto_flow: GridAutoFlow::DEFAULT,
861 grid_template_rows: Vec::new(),
862 grid_template_columns: Vec::new(),
863 grid_auto_rows: Vec::new(),
864 grid_auto_columns: Vec::new(),
865 grid_column: GridPlacement::DEFAULT,
866 grid_row: GridPlacement::DEFAULT,
867 };
868}
869
870impl Default for Node {
871 fn default() -> Self {
872 Self::DEFAULT
873 }
874}
875
876/// Used to control how each individual item is aligned by default within the space they're given.
877/// - For Flexbox containers, sets default cross axis alignment of the child items.
878/// - For CSS Grid containers, controls block (vertical) axis alignment of children of this grid container within their grid areas.
879///
880/// <https://developer.mozilla.org/en-US/docs/Web/CSS/align-items>
881#[derive(Copy, Clone, PartialEq, Eq, Debug, Reflect)]
882#[reflect(Default, PartialEq, Clone)]
883#[cfg_attr(
884 feature = "serialize",
885 derive(serde::Serialize, serde::Deserialize),
886 reflect(Serialize, Deserialize)
887)]
888pub enum AlignItems {
889 /// The items are packed in their default position as if no alignment was applied.
890 Default,
891 /// The items are packed towards the start of the axis.
892 Start,
893 /// The items are packed towards the end of the axis.
894 End,
895 /// The items are packed towards the start of the axis, unless the flex direction is reversed;
896 /// then they are packed towards the end of the axis.
897 FlexStart,
898 /// The items are packed towards the end of the axis, unless the flex direction is reversed;
899 /// then they are packed towards the start of the axis.
900 FlexEnd,
901 /// The items are packed along the center of the axis.
902 Center,
903 /// The items are packed such that their baselines align.
904 Baseline,
905 /// The items are stretched to fill the space they're given.
906 Stretch,
907}
908
909impl AlignItems {
910 pub const DEFAULT: Self = Self::Default;
911}
912
913impl Default for AlignItems {
914 fn default() -> Self {
915 Self::DEFAULT
916 }
917}
918
919/// Used to control how each individual item is aligned by default within the space they're given.
920/// - For Flexbox containers, this property has no effect. See `justify_content` for main axis alignment of flex items.
921/// - For CSS Grid containers, sets default inline (horizontal) axis alignment of child items within their grid areas.
922///
923/// <https://developer.mozilla.org/en-US/docs/Web/CSS/justify-items>
924#[derive(Copy, Clone, PartialEq, Eq, Debug, Reflect)]
925#[reflect(Default, PartialEq, Clone)]
926#[cfg_attr(
927 feature = "serialize",
928 derive(serde::Serialize, serde::Deserialize),
929 reflect(Serialize, Deserialize)
930)]
931pub enum JustifyItems {
932 /// The items are packed in their default position as if no alignment was applied.
933 Default,
934 /// The items are packed towards the start of the axis.
935 Start,
936 /// The items are packed towards the end of the axis.
937 End,
938 /// The items are packed along the center of the axis
939 Center,
940 /// The items are packed such that their baselines align.
941 Baseline,
942 /// The items are stretched to fill the space they're given.
943 Stretch,
944}
945
946impl JustifyItems {
947 pub const DEFAULT: Self = Self::Default;
948}
949
950impl Default for JustifyItems {
951 fn default() -> Self {
952 Self::DEFAULT
953 }
954}
955
956/// Used to control how the specified item is aligned within the space it's given.
957/// - For Flexbox items, controls cross axis alignment of the item.
958/// - For CSS Grid items, controls block (vertical) axis alignment of a grid item within its grid area.
959///
960/// <https://developer.mozilla.org/en-US/docs/Web/CSS/align-self>
961#[derive(Copy, Clone, PartialEq, Eq, Debug, Reflect)]
962#[reflect(Default, PartialEq, Clone)]
963#[cfg_attr(
964 feature = "serialize",
965 derive(serde::Serialize, serde::Deserialize),
966 reflect(Serialize, Deserialize)
967)]
968pub enum AlignSelf {
969 /// Use the parent node's [`AlignItems`] value to determine how this item should be aligned.
970 Auto,
971 /// This item will be aligned with the start of the axis.
972 Start,
973 /// This item will be aligned with the end of the axis.
974 End,
975 /// This item will be aligned with the start of the axis, unless the flex direction is reversed;
976 /// then it will be aligned with the end of the axis.
977 FlexStart,
978 /// This item will be aligned with the end of the axis, unless the flex direction is reversed;
979 /// then it will be aligned with the start of the axis.
980 FlexEnd,
981 /// This item will be aligned along the center of the axis.
982 Center,
983 /// This item will be aligned at the baseline.
984 Baseline,
985 /// This item will be stretched to fill the container.
986 Stretch,
987}
988
989impl AlignSelf {
990 pub const DEFAULT: Self = Self::Auto;
991}
992
993impl Default for AlignSelf {
994 fn default() -> Self {
995 Self::DEFAULT
996 }
997}
998
999/// Used to control how the specified item is aligned within the space it's given.
1000/// - For children of flex nodes, this property has no effect. See `justify_content` for main axis alignment of flex items.
1001/// - For CSS Grid items, controls inline (horizontal) axis alignment of a grid item within its grid area.
1002///
1003/// <https://developer.mozilla.org/en-US/docs/Web/CSS/justify-self>
1004#[derive(Copy, Clone, PartialEq, Eq, Debug, Reflect)]
1005#[reflect(Default, PartialEq, Clone)]
1006#[cfg_attr(
1007 feature = "serialize",
1008 derive(serde::Serialize, serde::Deserialize),
1009 reflect(Serialize, Deserialize)
1010)]
1011pub enum JustifySelf {
1012 /// Use the parent node's [`JustifyItems`] value to determine how this item should be aligned.
1013 Auto,
1014 /// This item will be aligned with the start of the axis.
1015 Start,
1016 /// This item will be aligned with the end of the axis.
1017 End,
1018 /// This item will be aligned along the center of the axis.
1019 Center,
1020 /// This item will be aligned at the baseline.
1021 Baseline,
1022 /// This item will be stretched to fill the space it's given.
1023 Stretch,
1024}
1025
1026impl JustifySelf {
1027 pub const DEFAULT: Self = Self::Auto;
1028}
1029
1030impl Default for JustifySelf {
1031 fn default() -> Self {
1032 Self::DEFAULT
1033 }
1034}
1035
1036/// Used to control how items are distributed.
1037/// - For Flexbox containers, controls alignment of lines if `flex_wrap` is set to [`FlexWrap::Wrap`] and there are multiple lines of items.
1038/// - For CSS Grid containers, controls alignment of grid rows.
1039///
1040/// <https://developer.mozilla.org/en-US/docs/Web/CSS/align-content>
1041#[derive(Copy, Clone, PartialEq, Eq, Debug, Reflect)]
1042#[reflect(Default, PartialEq, Clone)]
1043#[cfg_attr(
1044 feature = "serialize",
1045 derive(serde::Serialize, serde::Deserialize),
1046 reflect(Serialize, Deserialize)
1047)]
1048pub enum AlignContent {
1049 /// The items are packed in their default position as if no alignment was applied.
1050 Default,
1051 /// The items are packed towards the start of the axis.
1052 Start,
1053 /// The items are packed towards the end of the axis.
1054 End,
1055 /// The items are packed towards the start of the axis, unless the flex direction is reversed;
1056 /// then the items are packed towards the end of the axis.
1057 FlexStart,
1058 /// The items are packed towards the end of the axis, unless the flex direction is reversed;
1059 /// then the items are packed towards the start of the axis.
1060 FlexEnd,
1061 /// The items are packed along the center of the axis.
1062 Center,
1063 /// The items are stretched to fill the container along the axis.
1064 Stretch,
1065 /// The items are distributed such that the gap between any two items is equal.
1066 SpaceBetween,
1067 /// The items are distributed such that the gap between and around any two items is equal.
1068 SpaceEvenly,
1069 /// The items are distributed such that the gap between and around any two items is equal, with half-size gaps on either end.
1070 SpaceAround,
1071}
1072
1073impl AlignContent {
1074 pub const DEFAULT: Self = Self::Default;
1075}
1076
1077impl Default for AlignContent {
1078 fn default() -> Self {
1079 Self::DEFAULT
1080 }
1081}
1082
1083/// Used to control how items are distributed.
1084/// - For Flexbox containers, controls alignment of items in the main axis.
1085/// - For CSS Grid containers, controls alignment of grid columns.
1086///
1087/// <https://developer.mozilla.org/en-US/docs/Web/CSS/justify-content>
1088#[derive(Copy, Clone, PartialEq, Eq, Debug, Reflect)]
1089#[reflect(Default, PartialEq, Clone)]
1090#[cfg_attr(
1091 feature = "serialize",
1092 derive(serde::Serialize, serde::Deserialize),
1093 reflect(Serialize, Deserialize)
1094)]
1095pub enum JustifyContent {
1096 /// The items are packed in their default position as if no alignment was applied.
1097 Default,
1098 /// The items are packed towards the start of the axis.
1099 Start,
1100 /// The items are packed towards the end of the axis.
1101 End,
1102 /// The items are packed towards the start of the axis, unless the flex direction is reversed;
1103 /// then the items are packed towards the end of the axis.
1104 FlexStart,
1105 /// The items are packed towards the end of the axis, unless the flex direction is reversed;
1106 /// then the items are packed towards the start of the axis.
1107 FlexEnd,
1108 /// The items are packed along the center of the axis.
1109 Center,
1110 /// The items are stretched to fill the container along the axis.
1111 Stretch,
1112 /// The items are distributed such that the gap between any two items is equal.
1113 SpaceBetween,
1114 /// The items are distributed such that the gap between and around any two items is equal.
1115 SpaceEvenly,
1116 /// The items are distributed such that the gap between and around any two items is equal, with half-size gaps on either end.
1117 SpaceAround,
1118}
1119
1120impl JustifyContent {
1121 pub const DEFAULT: Self = Self::Default;
1122}
1123
1124impl Default for JustifyContent {
1125 fn default() -> Self {
1126 Self::DEFAULT
1127 }
1128}
1129
1130/// Defines the layout model used by this node.
1131///
1132/// Part of the [`Node`] component.
1133#[derive(Copy, Clone, PartialEq, Eq, Debug, Reflect)]
1134#[reflect(Default, PartialEq, Clone)]
1135#[cfg_attr(
1136 feature = "serialize",
1137 derive(serde::Serialize, serde::Deserialize),
1138 reflect(Serialize, Deserialize)
1139)]
1140pub enum Display {
1141 /// Use Flexbox layout model to determine the position of this [`Node`]'s children.
1142 Flex,
1143 /// Use CSS Grid layout model to determine the position of this [`Node`]'s children.
1144 Grid,
1145 /// Use CSS Block layout model to determine the position of this [`Node`]'s children.
1146 Block,
1147 /// Use no layout, don't render this node and its children.
1148 ///
1149 /// If you want to hide a node and its children,
1150 /// but keep its layout in place, set its [`Visibility`] component instead.
1151 None,
1152}
1153
1154impl Display {
1155 pub const DEFAULT: Self = Self::Flex;
1156}
1157
1158impl Default for Display {
1159 fn default() -> Self {
1160 Self::DEFAULT
1161 }
1162}
1163
1164/// Which part of a Node's box length styles like width and height control
1165///
1166/// See: <https://developer.mozilla.org/en-US/docs/Web/CSS/box-sizing>
1167#[derive(Copy, Clone, PartialEq, Eq, Debug, Reflect)]
1168#[reflect(Default, PartialEq, Clone)]
1169#[cfg_attr(
1170 feature = "serialize",
1171 derive(serde::Serialize, serde::Deserialize),
1172 reflect(Serialize, Deserialize)
1173)]
1174pub enum BoxSizing {
1175 /// Length styles like width and height refer to the "border box" size (size including padding and border)
1176 BorderBox,
1177 /// Length styles like width and height refer to the "content box" size (size excluding padding and border)
1178 ContentBox,
1179}
1180
1181impl BoxSizing {
1182 pub const DEFAULT: Self = Self::BorderBox;
1183}
1184
1185impl Default for BoxSizing {
1186 fn default() -> Self {
1187 Self::DEFAULT
1188 }
1189}
1190
1191/// Defines how flexbox items are ordered within a flexbox
1192#[derive(Copy, Clone, PartialEq, Eq, Debug, Reflect)]
1193#[reflect(Default, PartialEq, Clone)]
1194#[cfg_attr(
1195 feature = "serialize",
1196 derive(serde::Serialize, serde::Deserialize),
1197 reflect(Serialize, Deserialize)
1198)]
1199pub enum FlexDirection {
1200 /// Same way as text direction along the main axis.
1201 Row,
1202 /// Flex from top to bottom.
1203 Column,
1204 /// Opposite way as text direction along the main axis.
1205 RowReverse,
1206 /// Flex from bottom to top.
1207 ColumnReverse,
1208}
1209
1210impl FlexDirection {
1211 pub const DEFAULT: Self = Self::Row;
1212}
1213
1214impl Default for FlexDirection {
1215 fn default() -> Self {
1216 Self::DEFAULT
1217 }
1218}
1219
1220/// Whether to show or hide overflowing items
1221#[derive(Copy, Clone, PartialEq, Eq, Debug, Reflect)]
1222#[reflect(Default, PartialEq, Clone)]
1223#[cfg_attr(
1224 feature = "serialize",
1225 derive(serde::Serialize, serde::Deserialize),
1226 reflect(Serialize, Deserialize)
1227)]
1228pub struct Overflow {
1229 /// Whether to show or clip overflowing items on the x axis
1230 pub x: OverflowAxis,
1231 /// Whether to show or clip overflowing items on the y axis
1232 pub y: OverflowAxis,
1233}
1234
1235impl Overflow {
1236 pub const DEFAULT: Self = Self {
1237 x: OverflowAxis::DEFAULT,
1238 y: OverflowAxis::DEFAULT,
1239 };
1240
1241 /// Show overflowing items on both axes
1242 pub const fn visible() -> Self {
1243 Self {
1244 x: OverflowAxis::Visible,
1245 y: OverflowAxis::Visible,
1246 }
1247 }
1248
1249 /// Clip overflowing items on both axes
1250 pub const fn clip() -> Self {
1251 Self {
1252 x: OverflowAxis::Clip,
1253 y: OverflowAxis::Clip,
1254 }
1255 }
1256
1257 /// Clip overflowing items on the x axis
1258 pub const fn clip_x() -> Self {
1259 Self {
1260 x: OverflowAxis::Clip,
1261 y: OverflowAxis::Visible,
1262 }
1263 }
1264
1265 /// Clip overflowing items on the y axis
1266 pub const fn clip_y() -> Self {
1267 Self {
1268 x: OverflowAxis::Visible,
1269 y: OverflowAxis::Clip,
1270 }
1271 }
1272
1273 /// Hide overflowing items on both axes by influencing layout and then clipping
1274 pub const fn hidden() -> Self {
1275 Self {
1276 x: OverflowAxis::Hidden,
1277 y: OverflowAxis::Hidden,
1278 }
1279 }
1280
1281 /// Hide overflowing items on the x axis by influencing layout and then clipping
1282 pub const fn hidden_x() -> Self {
1283 Self {
1284 x: OverflowAxis::Hidden,
1285 y: OverflowAxis::Visible,
1286 }
1287 }
1288
1289 /// Hide overflowing items on the y axis by influencing layout and then clipping
1290 pub const fn hidden_y() -> Self {
1291 Self {
1292 x: OverflowAxis::Visible,
1293 y: OverflowAxis::Hidden,
1294 }
1295 }
1296
1297 /// Overflow is visible on both axes
1298 pub const fn is_visible(&self) -> bool {
1299 self.x.is_visible() && self.y.is_visible()
1300 }
1301
1302 pub const fn scroll() -> Self {
1303 Self {
1304 x: OverflowAxis::Scroll,
1305 y: OverflowAxis::Scroll,
1306 }
1307 }
1308
1309 /// Scroll overflowing items on the x axis
1310 pub const fn scroll_x() -> Self {
1311 Self {
1312 x: OverflowAxis::Scroll,
1313 y: OverflowAxis::Visible,
1314 }
1315 }
1316
1317 /// Scroll overflowing items on the y axis
1318 pub const fn scroll_y() -> Self {
1319 Self {
1320 x: OverflowAxis::Visible,
1321 y: OverflowAxis::Scroll,
1322 }
1323 }
1324}
1325
1326impl Default for Overflow {
1327 fn default() -> Self {
1328 Self::DEFAULT
1329 }
1330}
1331
1332/// Whether to show or hide overflowing items
1333#[derive(Copy, Clone, PartialEq, Eq, Debug, Reflect)]
1334#[reflect(Default, PartialEq, Clone)]
1335#[cfg_attr(
1336 feature = "serialize",
1337 derive(serde::Serialize, serde::Deserialize),
1338 reflect(Serialize, Deserialize)
1339)]
1340pub enum OverflowAxis {
1341 /// Show overflowing items.
1342 Visible,
1343 /// Hide overflowing items by clipping.
1344 Clip,
1345 /// Hide overflowing items by influencing layout and then clipping.
1346 Hidden,
1347 /// Scroll overflowing items.
1348 Scroll,
1349}
1350
1351impl OverflowAxis {
1352 pub const DEFAULT: Self = Self::Visible;
1353
1354 /// Overflow is visible on this axis
1355 pub const fn is_visible(&self) -> bool {
1356 matches!(self, Self::Visible)
1357 }
1358}
1359
1360impl Default for OverflowAxis {
1361 fn default() -> Self {
1362 Self::DEFAULT
1363 }
1364}
1365
1366/// The bounds of the visible area when a UI node is clipped.
1367#[derive(Default, Copy, Clone, PartialEq, Debug, Reflect)]
1368#[reflect(Default, PartialEq, Clone)]
1369#[cfg_attr(
1370 feature = "serialize",
1371 derive(serde::Serialize, serde::Deserialize),
1372 reflect(Serialize, Deserialize)
1373)]
1374pub struct OverflowClipMargin {
1375 /// Visible unclipped area
1376 pub visual_box: OverflowClipBox,
1377 /// Width of the margin on each edge of the visual box in logical pixels.
1378 /// The width of the margin will be zero if a negative value is set.
1379 pub margin: f32,
1380}
1381
1382impl OverflowClipMargin {
1383 pub const DEFAULT: Self = Self {
1384 visual_box: OverflowClipBox::PaddingBox,
1385 margin: 0.,
1386 };
1387
1388 /// Clip any content that overflows outside the content box
1389 pub const fn content_box() -> Self {
1390 Self {
1391 visual_box: OverflowClipBox::ContentBox,
1392 ..Self::DEFAULT
1393 }
1394 }
1395
1396 /// Clip any content that overflows outside the padding box
1397 pub const fn padding_box() -> Self {
1398 Self {
1399 visual_box: OverflowClipBox::PaddingBox,
1400 ..Self::DEFAULT
1401 }
1402 }
1403
1404 /// Clip any content that overflows outside the border box
1405 pub const fn border_box() -> Self {
1406 Self {
1407 visual_box: OverflowClipBox::BorderBox,
1408 ..Self::DEFAULT
1409 }
1410 }
1411
1412 /// Add a margin on each edge of the visual box in logical pixels.
1413 /// The width of the margin will be zero if a negative value is set.
1414 pub const fn with_margin(mut self, margin: f32) -> Self {
1415 self.margin = margin;
1416 self
1417 }
1418}
1419
1420/// Used to determine the bounds of the visible area when a UI node is clipped.
1421#[derive(Default, Copy, Clone, PartialEq, Eq, Debug, Reflect)]
1422#[reflect(Default, PartialEq, Clone)]
1423#[cfg_attr(
1424 feature = "serialize",
1425 derive(serde::Serialize, serde::Deserialize),
1426 reflect(Serialize, Deserialize)
1427)]
1428pub enum OverflowClipBox {
1429 /// Clip any content that overflows outside the content box
1430 ContentBox,
1431 /// Clip any content that overflows outside the padding box
1432 #[default]
1433 PaddingBox,
1434 /// Clip any content that overflows outside the border box
1435 BorderBox,
1436}
1437
1438/// The strategy used to position this node
1439#[derive(Copy, Clone, PartialEq, Eq, Debug, Reflect)]
1440#[reflect(Default, PartialEq, Clone)]
1441#[cfg_attr(
1442 feature = "serialize",
1443 derive(serde::Serialize, serde::Deserialize),
1444 reflect(Serialize, Deserialize)
1445)]
1446pub enum PositionType {
1447 /// Relative to all other nodes with the [`PositionType::Relative`] value.
1448 Relative,
1449 /// Independent of all other nodes, but relative to its parent node.
1450 Absolute,
1451}
1452
1453impl PositionType {
1454 pub const DEFAULT: Self = Self::Relative;
1455}
1456
1457impl Default for PositionType {
1458 fn default() -> Self {
1459 Self::DEFAULT
1460 }
1461}
1462
1463/// Defines if flexbox items appear on a single line or on multiple lines
1464#[derive(Copy, Clone, PartialEq, Eq, Debug, Reflect)]
1465#[reflect(Default, PartialEq, Clone)]
1466#[cfg_attr(
1467 feature = "serialize",
1468 derive(serde::Serialize, serde::Deserialize),
1469 reflect(Serialize, Deserialize)
1470)]
1471pub enum FlexWrap {
1472 /// Single line, will overflow if needed.
1473 NoWrap,
1474 /// Multiple lines, if needed.
1475 Wrap,
1476 /// Same as [`FlexWrap::Wrap`] but new lines will appear before the previous one.
1477 WrapReverse,
1478}
1479
1480impl FlexWrap {
1481 pub const DEFAULT: Self = Self::NoWrap;
1482}
1483
1484impl Default for FlexWrap {
1485 fn default() -> Self {
1486 Self::DEFAULT
1487 }
1488}
1489
1490/// Controls whether grid items are placed row-wise or column-wise as well as whether the sparse or dense packing algorithm is used.
1491///
1492/// The "dense" packing algorithm attempts to fill in holes earlier in the grid, if smaller items come up later.
1493/// This may cause items to appear out-of-order when doing so would fill in holes left by larger items.
1494///
1495/// Defaults to [`GridAutoFlow::Row`].
1496///
1497/// <https://developer.mozilla.org/en-US/docs/Web/CSS/grid-auto-flow>
1498#[derive(Copy, Clone, PartialEq, Eq, Debug, Reflect)]
1499#[reflect(Default, PartialEq, Clone)]
1500#[cfg_attr(
1501 feature = "serialize",
1502 derive(serde::Serialize, serde::Deserialize),
1503 reflect(Serialize, Deserialize)
1504)]
1505pub enum GridAutoFlow {
1506 /// Items are placed by filling each row in turn, adding new rows as necessary.
1507 Row,
1508 /// Items are placed by filling each column in turn, adding new columns as necessary.
1509 Column,
1510 /// Combines `Row` with the dense packing algorithm.
1511 RowDense,
1512 /// Combines `Column` with the dense packing algorithm.
1513 ColumnDense,
1514}
1515
1516impl GridAutoFlow {
1517 pub const DEFAULT: Self = Self::Row;
1518}
1519
1520impl Default for GridAutoFlow {
1521 fn default() -> Self {
1522 Self::DEFAULT
1523 }
1524}
1525
1526#[derive(Default, Copy, Clone, PartialEq, Debug, Reflect)]
1527#[reflect(Default, PartialEq, Clone)]
1528#[cfg_attr(
1529 feature = "serialize",
1530 derive(serde::Serialize, serde::Deserialize),
1531 reflect(Serialize, Deserialize)
1532)]
1533pub enum MinTrackSizingFunction {
1534 /// Track minimum size should be a fixed pixel value
1535 Px(f32),
1536 /// Track minimum size should be a percentage value
1537 Percent(f32),
1538 /// Track minimum size should be content sized under a min-content constraint
1539 MinContent,
1540 /// Track minimum size should be content sized under a max-content constraint
1541 MaxContent,
1542 /// Track minimum size should be automatically sized
1543 #[default]
1544 Auto,
1545 /// Track minimum size should be a percent of the viewport's smaller dimension.
1546 VMin(f32),
1547 /// Track minimum size should be a percent of the viewport's larger dimension.
1548 VMax(f32),
1549 /// Track minimum size should be a percent of the viewport's height dimension.
1550 Vh(f32),
1551 /// Track minimum size should be a percent of the viewport's width dimension.
1552 Vw(f32),
1553}
1554
1555#[derive(Default, Copy, Clone, PartialEq, Debug, Reflect)]
1556#[reflect(Default, PartialEq, Clone)]
1557#[cfg_attr(
1558 feature = "serialize",
1559 derive(serde::Serialize, serde::Deserialize),
1560 reflect(Serialize, Deserialize)
1561)]
1562pub enum MaxTrackSizingFunction {
1563 /// Track maximum size should be a fixed pixel value
1564 Px(f32),
1565 /// Track maximum size should be a percentage value
1566 Percent(f32),
1567 /// Track maximum size should be content sized under a min-content constraint
1568 MinContent,
1569 /// Track maximum size should be content sized under a max-content constraint
1570 MaxContent,
1571 /// Track maximum size should be sized according to the fit-content formula with a fixed pixel limit
1572 FitContentPx(f32),
1573 /// Track maximum size should be sized according to the fit-content formula with a percentage limit
1574 FitContentPercent(f32),
1575 /// Track maximum size should be automatically sized
1576 #[default]
1577 Auto,
1578 /// The dimension as a fraction of the total available grid space (`fr` units in CSS)
1579 /// Specified value is the numerator of the fraction. Denominator is the sum of all fractions specified in that grid dimension.
1580 ///
1581 /// Spec: <https://www.w3.org/TR/css3-grid-layout/#fr-unit>
1582 Fraction(f32),
1583 /// Track maximum size should be a percent of the viewport's smaller dimension.
1584 VMin(f32),
1585 /// Track maximum size should be a percent of the viewport's smaller dimension.
1586 VMax(f32),
1587 /// Track maximum size should be a percent of the viewport's height dimension.
1588 Vh(f32),
1589 /// Track maximum size should be a percent of the viewport's width dimension.
1590 Vw(f32),
1591}
1592
1593/// A [`GridTrack`] is a Row or Column of a CSS Grid. This struct specifies what size the track should be.
1594/// See below for the different "track sizing functions" you can specify.
1595#[derive(Copy, Clone, PartialEq, Debug, Reflect)]
1596#[reflect(Default, PartialEq, Clone)]
1597#[cfg_attr(
1598 feature = "serialize",
1599 derive(serde::Serialize, serde::Deserialize),
1600 reflect(Serialize, Deserialize)
1601)]
1602pub struct GridTrack {
1603 pub(crate) min_sizing_function: MinTrackSizingFunction,
1604 pub(crate) max_sizing_function: MaxTrackSizingFunction,
1605}
1606
1607impl GridTrack {
1608 pub const DEFAULT: Self = Self {
1609 min_sizing_function: MinTrackSizingFunction::Auto,
1610 max_sizing_function: MaxTrackSizingFunction::Auto,
1611 };
1612
1613 /// Create a grid track with a fixed pixel size
1614 pub fn px<T: From<Self>>(value: f32) -> T {
1615 Self {
1616 min_sizing_function: MinTrackSizingFunction::Px(value),
1617 max_sizing_function: MaxTrackSizingFunction::Px(value),
1618 }
1619 .into()
1620 }
1621
1622 /// Create a grid track with a percentage size
1623 pub fn percent<T: From<Self>>(value: f32) -> T {
1624 Self {
1625 min_sizing_function: MinTrackSizingFunction::Percent(value),
1626 max_sizing_function: MaxTrackSizingFunction::Percent(value),
1627 }
1628 .into()
1629 }
1630
1631 /// Create a grid track with an `fr` size.
1632 /// Note that this will give the track a content-based minimum size.
1633 /// Usually you are best off using `GridTrack::flex` instead which uses a zero minimum size.
1634 pub fn fr<T: From<Self>>(value: f32) -> T {
1635 Self {
1636 min_sizing_function: MinTrackSizingFunction::Auto,
1637 max_sizing_function: MaxTrackSizingFunction::Fraction(value),
1638 }
1639 .into()
1640 }
1641
1642 /// Create a grid track with a `minmax(0, Nfr)` size.
1643 pub fn flex<T: From<Self>>(value: f32) -> T {
1644 Self {
1645 min_sizing_function: MinTrackSizingFunction::Px(0.0),
1646 max_sizing_function: MaxTrackSizingFunction::Fraction(value),
1647 }
1648 .into()
1649 }
1650
1651 /// Create a grid track which is automatically sized to fit its contents.
1652 pub fn auto<T: From<Self>>() -> T {
1653 Self {
1654 min_sizing_function: MinTrackSizingFunction::Auto,
1655 max_sizing_function: MaxTrackSizingFunction::Auto,
1656 }
1657 .into()
1658 }
1659
1660 /// Create a grid track which is automatically sized to fit its contents when sized at their "min-content" sizes
1661 pub fn min_content<T: From<Self>>() -> T {
1662 Self {
1663 min_sizing_function: MinTrackSizingFunction::MinContent,
1664 max_sizing_function: MaxTrackSizingFunction::MinContent,
1665 }
1666 .into()
1667 }
1668
1669 /// Create a grid track which is automatically sized to fit its contents when sized at their "max-content" sizes
1670 pub fn max_content<T: From<Self>>() -> T {
1671 Self {
1672 min_sizing_function: MinTrackSizingFunction::MaxContent,
1673 max_sizing_function: MaxTrackSizingFunction::MaxContent,
1674 }
1675 .into()
1676 }
1677
1678 /// Create a `fit-content()` grid track with fixed pixel limit.
1679 ///
1680 /// <https://developer.mozilla.org/en-US/docs/Web/CSS/fit-content_function>
1681 pub fn fit_content_px<T: From<Self>>(limit: f32) -> T {
1682 Self {
1683 min_sizing_function: MinTrackSizingFunction::Auto,
1684 max_sizing_function: MaxTrackSizingFunction::FitContentPx(limit),
1685 }
1686 .into()
1687 }
1688
1689 /// Create a `fit-content()` grid track with percentage limit.
1690 ///
1691 /// <https://developer.mozilla.org/en-US/docs/Web/CSS/fit-content_function>
1692 pub fn fit_content_percent<T: From<Self>>(limit: f32) -> T {
1693 Self {
1694 min_sizing_function: MinTrackSizingFunction::Auto,
1695 max_sizing_function: MaxTrackSizingFunction::FitContentPercent(limit),
1696 }
1697 .into()
1698 }
1699
1700 /// Create a `minmax()` grid track.
1701 ///
1702 /// <https://developer.mozilla.org/en-US/docs/Web/CSS/minmax>
1703 pub fn minmax<T: From<Self>>(min: MinTrackSizingFunction, max: MaxTrackSizingFunction) -> T {
1704 Self {
1705 min_sizing_function: min,
1706 max_sizing_function: max,
1707 }
1708 .into()
1709 }
1710
1711 /// Create a grid track with a percentage of the viewport's smaller dimension
1712 pub fn vmin<T: From<Self>>(value: f32) -> T {
1713 Self {
1714 min_sizing_function: MinTrackSizingFunction::VMin(value),
1715 max_sizing_function: MaxTrackSizingFunction::VMin(value),
1716 }
1717 .into()
1718 }
1719
1720 /// Create a grid track with a percentage of the viewport's larger dimension
1721 pub fn vmax<T: From<Self>>(value: f32) -> T {
1722 Self {
1723 min_sizing_function: MinTrackSizingFunction::VMax(value),
1724 max_sizing_function: MaxTrackSizingFunction::VMax(value),
1725 }
1726 .into()
1727 }
1728
1729 /// Create a grid track with a percentage of the viewport's height dimension
1730 pub fn vh<T: From<Self>>(value: f32) -> T {
1731 Self {
1732 min_sizing_function: MinTrackSizingFunction::Vh(value),
1733 max_sizing_function: MaxTrackSizingFunction::Vh(value),
1734 }
1735 .into()
1736 }
1737
1738 /// Create a grid track with a percentage of the viewport's width dimension
1739 pub fn vw<T: From<Self>>(value: f32) -> T {
1740 Self {
1741 min_sizing_function: MinTrackSizingFunction::Vw(value),
1742 max_sizing_function: MaxTrackSizingFunction::Vw(value),
1743 }
1744 .into()
1745 }
1746}
1747
1748impl Default for GridTrack {
1749 fn default() -> Self {
1750 Self::DEFAULT
1751 }
1752}
1753
1754#[derive(Copy, Clone, PartialEq, Debug, Reflect, From)]
1755#[reflect(Default, PartialEq, Clone)]
1756#[cfg_attr(
1757 feature = "serialize",
1758 derive(serde::Serialize, serde::Deserialize),
1759 reflect(Serialize, Deserialize)
1760)]
1761/// How many times to repeat a repeated grid track
1762///
1763/// <https://developer.mozilla.org/en-US/docs/Web/CSS/repeat>
1764pub enum GridTrackRepetition {
1765 /// Repeat the track fixed number of times
1766 Count(u16),
1767 /// Repeat the track to fill available space
1768 ///
1769 /// <https://developer.mozilla.org/en-US/docs/Web/CSS/repeat#auto-fill>
1770 AutoFill,
1771 /// Repeat the track to fill available space but collapse any tracks that do not end up with
1772 /// an item placed in them.
1773 ///
1774 /// <https://developer.mozilla.org/en-US/docs/Web/CSS/repeat#auto-fit>
1775 AutoFit,
1776}
1777
1778impl Default for GridTrackRepetition {
1779 fn default() -> Self {
1780 Self::Count(1)
1781 }
1782}
1783
1784impl From<i32> for GridTrackRepetition {
1785 fn from(count: i32) -> Self {
1786 Self::Count(count as u16)
1787 }
1788}
1789
1790impl From<usize> for GridTrackRepetition {
1791 fn from(count: usize) -> Self {
1792 Self::Count(count as u16)
1793 }
1794}
1795
1796/// Represents a *possibly* repeated [`GridTrack`].
1797///
1798/// The repetition parameter can either be:
1799/// - The integer `1`, in which case the track is non-repeated.
1800/// - a `u16` count to repeat the track N times.
1801/// - A `GridTrackRepetition::AutoFit` or `GridTrackRepetition::AutoFill`.
1802///
1803/// Note: that in the common case you want a non-repeating track (repetition count 1), you may use the constructor methods on [`GridTrack`]
1804/// to create a `RepeatedGridTrack`. i.e. `GridTrack::px(10.0)` is equivalent to `RepeatedGridTrack::px(1, 10.0)`.
1805///
1806/// You may only use one auto-repetition per track list. And if your track list contains an auto repetition
1807/// then all tracks (in and outside of the repetition) must be fixed size (px or percent). Integer repetitions are just shorthand for writing out
1808/// N tracks longhand and are not subject to the same limitations.
1809#[derive(Clone, PartialEq, Debug, Reflect)]
1810#[reflect(Default, PartialEq, Clone)]
1811#[cfg_attr(
1812 feature = "serialize",
1813 derive(serde::Serialize, serde::Deserialize),
1814 reflect(Serialize, Deserialize)
1815)]
1816pub struct RepeatedGridTrack {
1817 pub(crate) repetition: GridTrackRepetition,
1818 pub(crate) tracks: SmallVec<[GridTrack; 1]>,
1819}
1820
1821impl RepeatedGridTrack {
1822 /// Create a repeating set of grid tracks with a fixed pixel size
1823 pub fn px<T: From<Self>>(repetition: impl Into<GridTrackRepetition>, value: f32) -> T {
1824 Self {
1825 repetition: repetition.into(),
1826 tracks: SmallVec::from_buf([GridTrack::px(value)]),
1827 }
1828 .into()
1829 }
1830
1831 /// Create a repeating set of grid tracks with a percentage size
1832 pub fn percent<T: From<Self>>(repetition: impl Into<GridTrackRepetition>, value: f32) -> T {
1833 Self {
1834 repetition: repetition.into(),
1835 tracks: SmallVec::from_buf([GridTrack::percent(value)]),
1836 }
1837 .into()
1838 }
1839
1840 /// Create a repeating set of grid tracks with automatic size
1841 pub fn auto<T: From<Self>>(repetition: u16) -> T {
1842 Self {
1843 repetition: GridTrackRepetition::Count(repetition),
1844 tracks: SmallVec::from_buf([GridTrack::auto()]),
1845 }
1846 .into()
1847 }
1848
1849 /// Create a repeating set of grid tracks with an `fr` size.
1850 /// Note that this will give the track a content-based minimum size.
1851 /// Usually you are best off using `GridTrack::flex` instead which uses a zero minimum size.
1852 pub fn fr<T: From<Self>>(repetition: u16, value: f32) -> T {
1853 Self {
1854 repetition: GridTrackRepetition::Count(repetition),
1855 tracks: SmallVec::from_buf([GridTrack::fr(value)]),
1856 }
1857 .into()
1858 }
1859
1860 /// Create a repeating set of grid tracks with a `minmax(0, Nfr)` size.
1861 pub fn flex<T: From<Self>>(repetition: u16, value: f32) -> T {
1862 Self {
1863 repetition: GridTrackRepetition::Count(repetition),
1864 tracks: SmallVec::from_buf([GridTrack::flex(value)]),
1865 }
1866 .into()
1867 }
1868
1869 /// Create a repeating set of grid tracks with min-content size
1870 pub fn min_content<T: From<Self>>(repetition: u16) -> T {
1871 Self {
1872 repetition: GridTrackRepetition::Count(repetition),
1873 tracks: SmallVec::from_buf([GridTrack::min_content()]),
1874 }
1875 .into()
1876 }
1877
1878 /// Create a repeating set of grid tracks with max-content size
1879 pub fn max_content<T: From<Self>>(repetition: u16) -> T {
1880 Self {
1881 repetition: GridTrackRepetition::Count(repetition),
1882 tracks: SmallVec::from_buf([GridTrack::max_content()]),
1883 }
1884 .into()
1885 }
1886
1887 /// Create a repeating set of `fit-content()` grid tracks with fixed pixel limit
1888 pub fn fit_content_px<T: From<Self>>(repetition: u16, limit: f32) -> T {
1889 Self {
1890 repetition: GridTrackRepetition::Count(repetition),
1891 tracks: SmallVec::from_buf([GridTrack::fit_content_px(limit)]),
1892 }
1893 .into()
1894 }
1895
1896 /// Create a repeating set of `fit-content()` grid tracks with percentage limit
1897 pub fn fit_content_percent<T: From<Self>>(repetition: u16, limit: f32) -> T {
1898 Self {
1899 repetition: GridTrackRepetition::Count(repetition),
1900 tracks: SmallVec::from_buf([GridTrack::fit_content_percent(limit)]),
1901 }
1902 .into()
1903 }
1904
1905 /// Create a repeating set of `minmax()` grid track
1906 pub fn minmax<T: From<Self>>(
1907 repetition: impl Into<GridTrackRepetition>,
1908 min: MinTrackSizingFunction,
1909 max: MaxTrackSizingFunction,
1910 ) -> T {
1911 Self {
1912 repetition: repetition.into(),
1913 tracks: SmallVec::from_buf([GridTrack::minmax(min, max)]),
1914 }
1915 .into()
1916 }
1917
1918 /// Create a repeating set of grid tracks with the percentage size of the viewport's smaller dimension
1919 pub fn vmin<T: From<Self>>(repetition: impl Into<GridTrackRepetition>, value: f32) -> T {
1920 Self {
1921 repetition: repetition.into(),
1922 tracks: SmallVec::from_buf([GridTrack::vmin(value)]),
1923 }
1924 .into()
1925 }
1926
1927 /// Create a repeating set of grid tracks with the percentage size of the viewport's larger dimension
1928 pub fn vmax<T: From<Self>>(repetition: impl Into<GridTrackRepetition>, value: f32) -> T {
1929 Self {
1930 repetition: repetition.into(),
1931 tracks: SmallVec::from_buf([GridTrack::vmax(value)]),
1932 }
1933 .into()
1934 }
1935
1936 /// Create a repeating set of grid tracks with the percentage size of the viewport's height dimension
1937 pub fn vh<T: From<Self>>(repetition: impl Into<GridTrackRepetition>, value: f32) -> T {
1938 Self {
1939 repetition: repetition.into(),
1940 tracks: SmallVec::from_buf([GridTrack::vh(value)]),
1941 }
1942 .into()
1943 }
1944
1945 /// Create a repeating set of grid tracks with the percentage size of the viewport's width dimension
1946 pub fn vw<T: From<Self>>(repetition: impl Into<GridTrackRepetition>, value: f32) -> T {
1947 Self {
1948 repetition: repetition.into(),
1949 tracks: SmallVec::from_buf([GridTrack::vw(value)]),
1950 }
1951 .into()
1952 }
1953
1954 /// Create a repetition of a set of tracks
1955 pub fn repeat_many<T: From<Self>>(
1956 repetition: impl Into<GridTrackRepetition>,
1957 tracks: impl Into<Vec<GridTrack>>,
1958 ) -> T {
1959 Self {
1960 repetition: repetition.into(),
1961 tracks: SmallVec::from_vec(tracks.into()),
1962 }
1963 .into()
1964 }
1965}
1966
1967impl Default for RepeatedGridTrack {
1968 fn default() -> Self {
1969 Self {
1970 repetition: Default::default(),
1971 tracks: SmallVec::from_buf([GridTrack::default()]),
1972 }
1973 }
1974}
1975
1976impl From<GridTrack> for RepeatedGridTrack {
1977 fn from(track: GridTrack) -> Self {
1978 Self {
1979 repetition: GridTrackRepetition::Count(1),
1980 tracks: SmallVec::from_buf([track]),
1981 }
1982 }
1983}
1984
1985impl From<GridTrack> for Vec<GridTrack> {
1986 fn from(track: GridTrack) -> Self {
1987 vec![track]
1988 }
1989}
1990
1991impl From<GridTrack> for Vec<RepeatedGridTrack> {
1992 fn from(track: GridTrack) -> Self {
1993 vec![RepeatedGridTrack {
1994 repetition: GridTrackRepetition::Count(1),
1995 tracks: SmallVec::from_buf([track]),
1996 }]
1997 }
1998}
1999
2000impl From<RepeatedGridTrack> for Vec<RepeatedGridTrack> {
2001 fn from(track: RepeatedGridTrack) -> Self {
2002 vec![track]
2003 }
2004}
2005
2006#[derive(Copy, Clone, PartialEq, Eq, Debug, Reflect)]
2007#[reflect(Default, PartialEq, Clone)]
2008#[cfg_attr(
2009 feature = "serialize",
2010 derive(serde::Serialize, serde::Deserialize),
2011 reflect(Serialize, Deserialize)
2012)]
2013/// Represents the position of a grid item in a single axis.
2014///
2015/// There are 3 fields which may be set:
2016/// - `start`: which grid line the item should start at
2017/// - `end`: which grid line the item should end at
2018/// - `span`: how many tracks the item should span
2019///
2020/// The default `span` is 1. If neither `start` or `end` is set then the item will be placed automatically.
2021///
2022/// Generally, at most two fields should be set. If all three fields are specified then `span` will be ignored. If `end` specifies an earlier
2023/// grid line than `start` then `end` will be ignored and the item will have a span of 1.
2024///
2025/// <https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Grid_Layout/Line-based_Placement_with_CSS_Grid>
2026pub struct GridPlacement {
2027 /// The grid line at which the item should start.
2028 /// Lines are 1-indexed.
2029 /// Negative indexes count backwards from the end of the grid.
2030 /// Zero is not a valid index.
2031 pub(crate) start: Option<NonZero<i16>>,
2032 /// How many grid tracks the item should span.
2033 /// Defaults to 1.
2034 pub(crate) span: Option<NonZero<u16>>,
2035 /// The grid line at which the item should end.
2036 /// Lines are 1-indexed.
2037 /// Negative indexes count backwards from the end of the grid.
2038 /// Zero is not a valid index.
2039 pub(crate) end: Option<NonZero<i16>>,
2040}
2041
2042impl GridPlacement {
2043 pub const DEFAULT: Self = Self {
2044 start: None,
2045 span: NonZero::<u16>::new(1),
2046 end: None,
2047 };
2048
2049 /// Place the grid item automatically (letting the `span` default to `1`).
2050 pub fn auto() -> Self {
2051 Self::DEFAULT
2052 }
2053
2054 /// Place the grid item automatically, specifying how many tracks it should `span`.
2055 ///
2056 /// # Panics
2057 ///
2058 /// Panics if `span` is `0`.
2059 pub fn span(span: u16) -> Self {
2060 Self {
2061 start: None,
2062 end: None,
2063 span: try_into_grid_span(span).expect("Invalid span value of 0."),
2064 }
2065 }
2066
2067 /// Place the grid item specifying the `start` grid line (letting the `span` default to `1`).
2068 ///
2069 /// # Panics
2070 ///
2071 /// Panics if `start` is `0`.
2072 pub fn start(start: i16) -> Self {
2073 Self {
2074 start: try_into_grid_index(start).expect("Invalid start value of 0."),
2075 ..Self::DEFAULT
2076 }
2077 }
2078
2079 /// Place the grid item specifying the `end` grid line (letting the `span` default to `1`).
2080 ///
2081 /// # Panics
2082 ///
2083 /// Panics if `end` is `0`.
2084 pub fn end(end: i16) -> Self {
2085 Self {
2086 end: try_into_grid_index(end).expect("Invalid end value of 0."),
2087 ..Self::DEFAULT
2088 }
2089 }
2090
2091 /// Place the grid item specifying the `start` grid line and how many tracks it should `span`.
2092 ///
2093 /// # Panics
2094 ///
2095 /// Panics if `start` or `span` is `0`.
2096 pub fn start_span(start: i16, span: u16) -> Self {
2097 Self {
2098 start: try_into_grid_index(start).expect("Invalid start value of 0."),
2099 end: None,
2100 span: try_into_grid_span(span).expect("Invalid span value of 0."),
2101 }
2102 }
2103
2104 /// Place the grid item specifying `start` and `end` grid lines (`span` will be inferred)
2105 ///
2106 /// # Panics
2107 ///
2108 /// Panics if `start` or `end` is `0`.
2109 pub fn start_end(start: i16, end: i16) -> Self {
2110 Self {
2111 start: try_into_grid_index(start).expect("Invalid start value of 0."),
2112 end: try_into_grid_index(end).expect("Invalid end value of 0."),
2113 span: None,
2114 }
2115 }
2116
2117 /// Place the grid item specifying the `end` grid line and how many tracks it should `span`.
2118 ///
2119 /// # Panics
2120 ///
2121 /// Panics if `end` or `span` is `0`.
2122 pub fn end_span(end: i16, span: u16) -> Self {
2123 Self {
2124 start: None,
2125 end: try_into_grid_index(end).expect("Invalid end value of 0."),
2126 span: try_into_grid_span(span).expect("Invalid span value of 0."),
2127 }
2128 }
2129
2130 /// Mutate the item, setting the `start` grid line
2131 ///
2132 /// # Panics
2133 ///
2134 /// Panics if `start` is `0`.
2135 pub fn set_start(mut self, start: i16) -> Self {
2136 self.start = try_into_grid_index(start).expect("Invalid start value of 0.");
2137 self
2138 }
2139
2140 /// Mutate the item, setting the `end` grid line
2141 ///
2142 /// # Panics
2143 ///
2144 /// Panics if `end` is `0`.
2145 pub fn set_end(mut self, end: i16) -> Self {
2146 self.end = try_into_grid_index(end).expect("Invalid end value of 0.");
2147 self
2148 }
2149
2150 /// Mutate the item, setting the number of tracks the item should `span`
2151 ///
2152 /// # Panics
2153 ///
2154 /// Panics if `span` is `0`.
2155 pub fn set_span(mut self, span: u16) -> Self {
2156 self.span = try_into_grid_span(span).expect("Invalid span value of 0.");
2157 self
2158 }
2159
2160 /// Returns the grid line at which the item should start, or `None` if not set.
2161 pub fn get_start(self) -> Option<i16> {
2162 self.start.map(NonZero::<i16>::get)
2163 }
2164
2165 /// Returns the grid line at which the item should end, or `None` if not set.
2166 pub fn get_end(self) -> Option<i16> {
2167 self.end.map(NonZero::<i16>::get)
2168 }
2169
2170 /// Returns span for this grid item, or `None` if not set.
2171 pub fn get_span(self) -> Option<u16> {
2172 self.span.map(NonZero::<u16>::get)
2173 }
2174}
2175
2176impl Default for GridPlacement {
2177 fn default() -> Self {
2178 Self::DEFAULT
2179 }
2180}
2181
2182/// Convert an `i16` to `NonZero<i16>`, fails on `0` and returns the `InvalidZeroIndex` error.
2183fn try_into_grid_index(index: i16) -> Result<Option<NonZero<i16>>, GridPlacementError> {
2184 Ok(Some(
2185 NonZero::<i16>::new(index).ok_or(GridPlacementError::InvalidZeroIndex)?,
2186 ))
2187}
2188
2189/// Convert a `u16` to `NonZero<u16>`, fails on `0` and returns the `InvalidZeroSpan` error.
2190fn try_into_grid_span(span: u16) -> Result<Option<NonZero<u16>>, GridPlacementError> {
2191 Ok(Some(
2192 NonZero::<u16>::new(span).ok_or(GridPlacementError::InvalidZeroSpan)?,
2193 ))
2194}
2195
2196/// Errors that occur when setting constraints for a `GridPlacement`
2197#[derive(Debug, Eq, PartialEq, Clone, Copy, Error)]
2198pub enum GridPlacementError {
2199 #[error("Zero is not a valid grid position")]
2200 InvalidZeroIndex,
2201 #[error("Spans cannot be zero length")]
2202 InvalidZeroSpan,
2203}
2204
2205/// The background color of the node
2206///
2207/// This serves as the "fill" color.
2208#[derive(Component, Copy, Clone, Debug, PartialEq, Reflect)]
2209#[reflect(Component, Default, Debug, PartialEq, Clone)]
2210#[cfg_attr(
2211 feature = "serialize",
2212 derive(serde::Serialize, serde::Deserialize),
2213 reflect(Serialize, Deserialize)
2214)]
2215pub struct BackgroundColor(pub Color);
2216
2217impl BackgroundColor {
2218 /// Background color is transparent by default.
2219 pub const DEFAULT: Self = Self(Color::NONE);
2220}
2221
2222impl Default for BackgroundColor {
2223 fn default() -> Self {
2224 Self::DEFAULT
2225 }
2226}
2227
2228impl<T: Into<Color>> From<T> for BackgroundColor {
2229 fn from(color: T) -> Self {
2230 Self(color.into())
2231 }
2232}
2233
2234/// The border color of the UI node.
2235#[derive(Component, Copy, Clone, Debug, PartialEq, Reflect)]
2236#[reflect(Component, Default, Debug, PartialEq, Clone)]
2237#[cfg_attr(
2238 feature = "serialize",
2239 derive(serde::Serialize, serde::Deserialize),
2240 reflect(Serialize, Deserialize)
2241)]
2242pub struct BorderColor {
2243 pub top: Color,
2244 pub right: Color,
2245 pub bottom: Color,
2246 pub left: Color,
2247}
2248
2249impl<T: Into<Color>> From<T> for BorderColor {
2250 fn from(color: T) -> Self {
2251 Self::all(color.into())
2252 }
2253}
2254
2255impl BorderColor {
2256 /// Border color is transparent by default.
2257 pub const DEFAULT: Self = BorderColor {
2258 top: Color::NONE,
2259 right: Color::NONE,
2260 bottom: Color::NONE,
2261 left: Color::NONE,
2262 };
2263
2264 /// Helper to create a `BorderColor` struct with all borders set to the given color
2265 #[inline]
2266 pub fn all(color: impl Into<Color>) -> Self {
2267 let color = color.into();
2268 Self {
2269 top: color,
2270 bottom: color,
2271 left: color,
2272 right: color,
2273 }
2274 }
2275
2276 /// Helper to set all border colors to a given color.
2277 pub fn set_all(&mut self, color: impl Into<Color>) -> &mut Self {
2278 let color: Color = color.into();
2279 self.top = color;
2280 self.bottom = color;
2281 self.left = color;
2282 self.right = color;
2283 self
2284 }
2285
2286 /// Check if all contained border colors are transparent
2287 pub fn is_fully_transparent(&self) -> bool {
2288 self.top.is_fully_transparent()
2289 && self.bottom.is_fully_transparent()
2290 && self.left.is_fully_transparent()
2291 && self.right.is_fully_transparent()
2292 }
2293}
2294
2295impl Default for BorderColor {
2296 fn default() -> Self {
2297 Self::DEFAULT
2298 }
2299}
2300
2301#[derive(Component, Copy, Clone, Default, Debug, PartialEq, Reflect)]
2302#[reflect(Component, Default, Debug, PartialEq, Clone)]
2303#[cfg_attr(
2304 feature = "serialize",
2305 derive(serde::Serialize, serde::Deserialize),
2306 reflect(Serialize, Deserialize)
2307)]
2308/// The [`Outline`] component adds an outline outside the edge of a UI node.
2309/// Outlines do not take up space in the layout.
2310///
2311/// To add an [`Outline`] to a ui node you can spawn a `(Node, Outline)` tuple bundle:
2312/// ```
2313/// # use bevy_ecs::prelude::*;
2314/// # use bevy_ui::prelude::*;
2315/// # use bevy_color::palettes::basic::{RED, BLUE};
2316/// fn setup_ui(mut commands: Commands) {
2317/// commands.spawn((
2318/// Node {
2319/// width: Val::Px(100.),
2320/// height: Val::Px(100.),
2321/// ..Default::default()
2322/// },
2323/// BackgroundColor(BLUE.into()),
2324/// Outline::new(Val::Px(10.), Val::ZERO, RED.into())
2325/// ));
2326/// }
2327/// ```
2328///
2329/// [`Outline`] components can also be added later to existing UI nodes:
2330/// ```
2331/// # use bevy_ecs::prelude::*;
2332/// # use bevy_ui::prelude::*;
2333/// # use bevy_color::Color;
2334/// fn outline_hovered_button_system(
2335/// mut commands: Commands,
2336/// mut node_query: Query<(Entity, &Interaction, Option<&mut Outline>), Changed<Interaction>>,
2337/// ) {
2338/// for (entity, interaction, mut maybe_outline) in node_query.iter_mut() {
2339/// let outline_color =
2340/// if matches!(*interaction, Interaction::Hovered) {
2341/// Color::WHITE
2342/// } else {
2343/// Color::NONE
2344/// };
2345/// if let Some(mut outline) = maybe_outline {
2346/// outline.color = outline_color;
2347/// } else {
2348/// commands.entity(entity).insert(Outline::new(Val::Px(10.), Val::ZERO, outline_color));
2349/// }
2350/// }
2351/// }
2352/// ```
2353/// Inserting and removing an [`Outline`] component repeatedly will result in table moves, so it is generally preferable to
2354/// set `Outline::color` to [`Color::NONE`] to hide an outline.
2355pub struct Outline {
2356 /// The width of the outline.
2357 ///
2358 /// Percentage `Val` values are resolved based on the width of the outlined [`Node`].
2359 pub width: Val,
2360 /// The amount of space between a node's outline the edge of the node.
2361 ///
2362 /// Percentage `Val` values are resolved based on the width of the outlined [`Node`].
2363 pub offset: Val,
2364 /// The color of the outline.
2365 ///
2366 /// If you are frequently toggling outlines for a UI node on and off it is recommended to set [`Color::NONE`] to hide the outline.
2367 /// This avoids the table moves that would occur from the repeated insertion and removal of the `Outline` component.
2368 pub color: Color,
2369}
2370
2371impl Outline {
2372 /// Create a new outline
2373 pub const fn new(width: Val, offset: Val, color: Color) -> Self {
2374 Self {
2375 width,
2376 offset,
2377 color,
2378 }
2379 }
2380}
2381
2382/// The calculated clip of the node
2383#[derive(Component, Default, Copy, Clone, Debug, Reflect)]
2384#[reflect(Component, Default, Debug, Clone)]
2385pub struct CalculatedClip {
2386 /// The rect of the clip
2387 pub clip: Rect,
2388}
2389
2390/// UI node entities with this component will ignore any clipping rect they inherit,
2391/// the node will not be clipped regardless of its ancestors' `Overflow` setting.
2392#[derive(Component)]
2393pub struct OverrideClip;
2394
2395#[expect(
2396 rustdoc::redundant_explicit_links,
2397 reason = "To go around the `<code>` limitations, we put the link twice so we're \
2398sure it's recognized as a markdown link."
2399)]
2400/// Indicates that this [`Node`] entity's front-to-back ordering is not controlled solely
2401/// by its location in the UI hierarchy. A node with a higher z-index will appear on top
2402/// of sibling nodes with a lower z-index.
2403///
2404/// UI nodes that have the same z-index will appear according to the order in which they
2405/// appear in the UI hierarchy. In such a case, the last node to be added to its parent
2406/// will appear in front of its siblings.
2407///
2408/// Nodes without this component will be treated as if they had a value of
2409/// <code>[ZIndex][ZIndex]\(0\)</code>.
2410///
2411/// Use [`GlobalZIndex`] if you need to order separate UI hierarchies or nodes that are
2412/// not siblings in a given UI hierarchy.
2413#[derive(Component, Copy, Clone, Debug, Default, PartialEq, Eq, Reflect)]
2414#[reflect(Component, Default, Debug, PartialEq, Clone)]
2415pub struct ZIndex(pub i32);
2416
2417/// `GlobalZIndex` allows a [`Node`] entity anywhere in the UI hierarchy to escape the implicit draw ordering of the UI's layout tree and
2418/// be rendered above or below other UI nodes.
2419/// Nodes with a `GlobalZIndex` of greater than 0 will be drawn on top of nodes without a `GlobalZIndex` or nodes with a lower `GlobalZIndex`.
2420/// Nodes with a `GlobalZIndex` of less than 0 will be drawn below nodes without a `GlobalZIndex` or nodes with a greater `GlobalZIndex`.
2421///
2422/// If two Nodes have the same `GlobalZIndex`, the node with the greater [`ZIndex`] will be drawn on top.
2423#[derive(Component, Copy, Clone, Debug, Default, PartialEq, Eq, Reflect)]
2424#[reflect(Component, Default, Debug, PartialEq, Clone)]
2425pub struct GlobalZIndex(pub i32);
2426
2427/// Used to add rounded corners to a UI node. You can set a UI node to have uniformly
2428/// rounded corners or specify different radii for each corner. If a given radius exceeds half
2429/// the length of the smallest dimension between the node's height or width, the radius will
2430/// calculated as half the smallest dimension.
2431///
2432/// Elliptical nodes are not supported yet. Percentage values are based on the node's smallest
2433/// dimension, either width or height.
2434///
2435/// # Example
2436/// ```rust
2437/// # use bevy_ecs::prelude::*;
2438/// # use bevy_ui::prelude::*;
2439/// # use bevy_color::palettes::basic::{BLUE};
2440/// fn setup_ui(mut commands: Commands) {
2441/// commands.spawn((
2442/// Node {
2443/// width: Val::Px(100.),
2444/// height: Val::Px(100.),
2445/// border: UiRect::all(Val::Px(2.)),
2446/// border_radius: BorderRadius::new(
2447/// // top left
2448/// Val::Px(10.),
2449/// // top right
2450/// Val::Px(20.),
2451/// // bottom right
2452/// Val::Px(30.),
2453/// // bottom left
2454/// Val::Px(40.),
2455/// ),
2456/// ..Default::default()
2457/// },
2458/// BackgroundColor(BLUE.into()),
2459/// ));
2460/// }
2461/// ```
2462///
2463/// <https://developer.mozilla.org/en-US/docs/Web/CSS/border-radius>
2464#[derive(Copy, Clone, Debug, PartialEq, Reflect)]
2465#[reflect(PartialEq, Default, Debug, Clone)]
2466#[cfg_attr(
2467 feature = "serialize",
2468 derive(serde::Serialize, serde::Deserialize),
2469 reflect(Serialize, Deserialize)
2470)]
2471pub struct BorderRadius {
2472 pub top_left: Val,
2473 pub top_right: Val,
2474 pub bottom_right: Val,
2475 pub bottom_left: Val,
2476}
2477
2478impl Default for BorderRadius {
2479 fn default() -> Self {
2480 Self::DEFAULT
2481 }
2482}
2483
2484impl BorderRadius {
2485 pub const DEFAULT: Self = Self::ZERO;
2486
2487 /// Zero curvature. All the corners will be right-angled.
2488 pub const ZERO: Self = Self::all(Val::Px(0.));
2489
2490 /// Maximum curvature. The UI Node will take a capsule shape or circular if width and height are equal.
2491 pub const MAX: Self = Self::all(Val::Px(f32::MAX));
2492
2493 #[inline]
2494 /// Set all four corners to the same curvature.
2495 pub const fn all(radius: Val) -> Self {
2496 Self {
2497 top_left: radius,
2498 top_right: radius,
2499 bottom_left: radius,
2500 bottom_right: radius,
2501 }
2502 }
2503
2504 #[inline]
2505 pub const fn new(top_left: Val, top_right: Val, bottom_right: Val, bottom_left: Val) -> Self {
2506 Self {
2507 top_left,
2508 top_right,
2509 bottom_right,
2510 bottom_left,
2511 }
2512 }
2513
2514 #[inline]
2515 /// Sets the radii to logical pixel values.
2516 pub const fn px(top_left: f32, top_right: f32, bottom_right: f32, bottom_left: f32) -> Self {
2517 Self {
2518 top_left: Val::Px(top_left),
2519 top_right: Val::Px(top_right),
2520 bottom_right: Val::Px(bottom_right),
2521 bottom_left: Val::Px(bottom_left),
2522 }
2523 }
2524
2525 #[inline]
2526 /// Sets the radii to percentage values.
2527 pub const fn percent(
2528 top_left: f32,
2529 top_right: f32,
2530 bottom_right: f32,
2531 bottom_left: f32,
2532 ) -> Self {
2533 Self {
2534 top_left: Val::Percent(top_left),
2535 top_right: Val::Percent(top_right),
2536 bottom_right: Val::Percent(bottom_right),
2537 bottom_left: Val::Percent(bottom_left),
2538 }
2539 }
2540
2541 #[inline]
2542 /// Sets the radius for the top left corner.
2543 /// Remaining corners will be right-angled.
2544 pub const fn top_left(radius: Val) -> Self {
2545 Self {
2546 top_left: radius,
2547 ..Self::DEFAULT
2548 }
2549 }
2550
2551 #[inline]
2552 /// Sets the radius for the top right corner.
2553 /// Remaining corners will be right-angled.
2554 pub const fn top_right(radius: Val) -> Self {
2555 Self {
2556 top_right: radius,
2557 ..Self::DEFAULT
2558 }
2559 }
2560
2561 #[inline]
2562 /// Sets the radius for the bottom right corner.
2563 /// Remaining corners will be right-angled.
2564 pub const fn bottom_right(radius: Val) -> Self {
2565 Self {
2566 bottom_right: radius,
2567 ..Self::DEFAULT
2568 }
2569 }
2570
2571 #[inline]
2572 /// Sets the radius for the bottom left corner.
2573 /// Remaining corners will be right-angled.
2574 pub const fn bottom_left(radius: Val) -> Self {
2575 Self {
2576 bottom_left: radius,
2577 ..Self::DEFAULT
2578 }
2579 }
2580
2581 #[inline]
2582 /// Sets the radii for the top left and bottom left corners.
2583 /// Remaining corners will be right-angled.
2584 pub const fn left(radius: Val) -> Self {
2585 Self {
2586 top_left: radius,
2587 bottom_left: radius,
2588 ..Self::DEFAULT
2589 }
2590 }
2591
2592 #[inline]
2593 /// Sets the radii for the top right and bottom right corners.
2594 /// Remaining corners will be right-angled.
2595 pub const fn right(radius: Val) -> Self {
2596 Self {
2597 top_right: radius,
2598 bottom_right: radius,
2599 ..Self::DEFAULT
2600 }
2601 }
2602
2603 #[inline]
2604 /// Sets the radii for the top left and top right corners.
2605 /// Remaining corners will be right-angled.
2606 pub const fn top(radius: Val) -> Self {
2607 Self {
2608 top_left: radius,
2609 top_right: radius,
2610 ..Self::DEFAULT
2611 }
2612 }
2613
2614 #[inline]
2615 /// Sets the radii for the bottom left and bottom right corners.
2616 /// Remaining corners will be right-angled.
2617 pub const fn bottom(radius: Val) -> Self {
2618 Self {
2619 bottom_left: radius,
2620 bottom_right: radius,
2621 ..Self::DEFAULT
2622 }
2623 }
2624
2625 /// Returns the [`BorderRadius`] with its `top_left` field set to the given value.
2626 #[inline]
2627 pub const fn with_top_left(mut self, radius: Val) -> Self {
2628 self.top_left = radius;
2629 self
2630 }
2631
2632 /// Returns the [`BorderRadius`] with its `top_right` field set to the given value.
2633 #[inline]
2634 pub const fn with_top_right(mut self, radius: Val) -> Self {
2635 self.top_right = radius;
2636 self
2637 }
2638
2639 /// Returns the [`BorderRadius`] with its `bottom_right` field set to the given value.
2640 #[inline]
2641 pub const fn with_bottom_right(mut self, radius: Val) -> Self {
2642 self.bottom_right = radius;
2643 self
2644 }
2645
2646 /// Returns the [`BorderRadius`] with its `bottom_left` field set to the given value.
2647 #[inline]
2648 pub const fn with_bottom_left(mut self, radius: Val) -> Self {
2649 self.bottom_left = radius;
2650 self
2651 }
2652
2653 /// Returns the [`BorderRadius`] with its `top_left` and `bottom_left` fields set to the given value.
2654 #[inline]
2655 pub const fn with_left(mut self, radius: Val) -> Self {
2656 self.top_left = radius;
2657 self.bottom_left = radius;
2658 self
2659 }
2660
2661 /// Returns the [`BorderRadius`] with its `top_right` and `bottom_right` fields set to the given value.
2662 #[inline]
2663 pub const fn with_right(mut self, radius: Val) -> Self {
2664 self.top_right = radius;
2665 self.bottom_right = radius;
2666 self
2667 }
2668
2669 /// Returns the [`BorderRadius`] with its `top_left` and `top_right` fields set to the given value.
2670 #[inline]
2671 pub const fn with_top(mut self, radius: Val) -> Self {
2672 self.top_left = radius;
2673 self.top_right = radius;
2674 self
2675 }
2676
2677 /// Returns the [`BorderRadius`] with its `bottom_left` and `bottom_right` fields set to the given value.
2678 #[inline]
2679 pub const fn with_bottom(mut self, radius: Val) -> Self {
2680 self.bottom_left = radius;
2681 self.bottom_right = radius;
2682 self
2683 }
2684
2685 /// Resolve the border radius for a single corner from the given context values.
2686 /// Returns the radius of the corner in physical pixels.
2687 pub const fn resolve_single_corner(
2688 radius: Val,
2689 scale_factor: f32,
2690 min_length: f32,
2691 viewport_size: Vec2,
2692 ) -> f32 {
2693 if let Ok(radius) = radius.resolve(scale_factor, min_length, viewport_size) {
2694 radius.clamp(0., 0.5 * min_length)
2695 } else {
2696 0.
2697 }
2698 }
2699
2700 /// Resolve the border radii for the corners from the given context values.
2701 /// Returns the radii of the each corner in physical pixels.
2702 pub const fn resolve(
2703 &self,
2704 scale_factor: f32,
2705 node_size: Vec2,
2706 viewport_size: Vec2,
2707 ) -> ResolvedBorderRadius {
2708 let length = node_size.x.min(node_size.y);
2709 ResolvedBorderRadius {
2710 top_left: Self::resolve_single_corner(
2711 self.top_left,
2712 scale_factor,
2713 length,
2714 viewport_size,
2715 ),
2716 top_right: Self::resolve_single_corner(
2717 self.top_right,
2718 scale_factor,
2719 length,
2720 viewport_size,
2721 ),
2722 bottom_left: Self::resolve_single_corner(
2723 self.bottom_left,
2724 scale_factor,
2725 length,
2726 viewport_size,
2727 ),
2728 bottom_right: Self::resolve_single_corner(
2729 self.bottom_right,
2730 scale_factor,
2731 length,
2732 viewport_size,
2733 ),
2734 }
2735 }
2736}
2737
2738/// Represents the resolved border radius values for a UI node.
2739///
2740/// The values are in physical pixels.
2741#[derive(Copy, Clone, Debug, Default, PartialEq, Reflect)]
2742#[reflect(Clone, PartialEq, Default)]
2743pub struct ResolvedBorderRadius {
2744 pub top_left: f32,
2745 pub top_right: f32,
2746 pub bottom_right: f32,
2747 pub bottom_left: f32,
2748}
2749
2750impl ResolvedBorderRadius {
2751 pub const ZERO: Self = Self {
2752 top_left: 0.,
2753 top_right: 0.,
2754 bottom_right: 0.,
2755 bottom_left: 0.,
2756 };
2757}
2758
2759impl From<ResolvedBorderRadius> for [f32; 4] {
2760 fn from(radius: ResolvedBorderRadius) -> Self {
2761 [
2762 radius.top_left,
2763 radius.top_right,
2764 radius.bottom_right,
2765 radius.bottom_left,
2766 ]
2767 }
2768}
2769
2770#[derive(Component, Clone, Debug, Default, PartialEq, Reflect, Deref, DerefMut)]
2771#[reflect(Component, PartialEq, Default, Clone)]
2772#[cfg_attr(
2773 feature = "serialize",
2774 derive(serde::Serialize, serde::Deserialize),
2775 reflect(Serialize, Deserialize)
2776)]
2777/// List of shadows to draw for a [`Node`].
2778///
2779/// Draw order is determined implicitly from the vector of [`ShadowStyle`]s, back-to-front.
2780pub struct BoxShadow(pub Vec<ShadowStyle>);
2781
2782impl BoxShadow {
2783 /// A single drop shadow
2784 pub fn new(
2785 color: Color,
2786 x_offset: Val,
2787 y_offset: Val,
2788 spread_radius: Val,
2789 blur_radius: Val,
2790 ) -> Self {
2791 Self(vec![ShadowStyle {
2792 color,
2793 x_offset,
2794 y_offset,
2795 spread_radius,
2796 blur_radius,
2797 }])
2798 }
2799}
2800
2801impl From<ShadowStyle> for BoxShadow {
2802 fn from(value: ShadowStyle) -> Self {
2803 Self(vec![value])
2804 }
2805}
2806
2807#[derive(Copy, Clone, Debug, PartialEq, Reflect)]
2808#[reflect(PartialEq, Default, Clone)]
2809#[cfg_attr(
2810 feature = "serialize",
2811 derive(serde::Serialize, serde::Deserialize),
2812 reflect(Serialize, Deserialize)
2813)]
2814pub struct ShadowStyle {
2815 /// The shadow's color
2816 pub color: Color,
2817 /// Horizontal offset
2818 pub x_offset: Val,
2819 /// Vertical offset
2820 pub y_offset: Val,
2821 /// How much the shadow should spread outward.
2822 ///
2823 /// Negative values will make the shadow shrink inwards.
2824 /// Percentage values are based on the width of the UI node.
2825 pub spread_radius: Val,
2826 /// Blurriness of the shadow
2827 pub blur_radius: Val,
2828}
2829
2830impl Default for ShadowStyle {
2831 fn default() -> Self {
2832 Self {
2833 color: Color::BLACK,
2834 x_offset: Val::Percent(20.),
2835 y_offset: Val::Percent(20.),
2836 spread_radius: Val::ZERO,
2837 blur_radius: Val::Percent(10.),
2838 }
2839 }
2840}
2841
2842#[derive(Component, Copy, Clone, Debug, PartialEq, Reflect)]
2843#[reflect(Component, Debug, PartialEq, Default, Clone)]
2844#[cfg_attr(
2845 feature = "serialize",
2846 derive(serde::Serialize, serde::Deserialize),
2847 reflect(Serialize, Deserialize)
2848)]
2849/// This component can be added to any UI node to modify its layout behavior.
2850pub struct LayoutConfig {
2851 /// If set to true the coordinates for this node and its descendents will be rounded to the nearest physical pixel.
2852 /// This can help prevent visual artifacts like blurry images or semi-transparent edges that can occur with sub-pixel positioning.
2853 ///
2854 /// Defaults to true.
2855 pub use_rounding: bool,
2856}
2857
2858impl Default for LayoutConfig {
2859 fn default() -> Self {
2860 Self { use_rounding: true }
2861 }
2862}
2863
2864/// Indicates that this root [`Node`] entity should be rendered to a specific camera.
2865///
2866/// UI then will be laid out respecting the camera's viewport and scale factor, and
2867/// rendered to this camera's [`bevy_camera::RenderTarget`].
2868///
2869/// Setting this component on a non-root node will have no effect. It will be overridden
2870/// by the root node's component.
2871///
2872/// Root node's without an explicit [`UiTargetCamera`] will be rendered to the default UI camera,
2873/// which is either a single camera with the [`IsDefaultUiCamera`] marker component or the highest
2874/// order camera targeting the primary window.
2875#[derive(Component, Clone, Debug, Reflect, Eq, PartialEq)]
2876#[reflect(Component, Debug, PartialEq, Clone)]
2877pub struct UiTargetCamera(pub Entity);
2878
2879impl UiTargetCamera {
2880 pub fn entity(&self) -> Entity {
2881 self.0
2882 }
2883}
2884
2885/// Marker used to identify default cameras, they will have priority over the [`PrimaryWindow`] camera.
2886///
2887/// This is useful if the [`PrimaryWindow`] has two cameras, one of them used
2888/// just for debug purposes and the user wants a way to choose the default [`Camera`]
2889/// without having to add a [`UiTargetCamera`] to the root node.
2890///
2891/// Another use is when the user wants the Ui to be in another window by default,
2892/// all that is needed is to place this component on the camera
2893///
2894/// ```
2895/// # use bevy_ui::prelude::*;
2896/// # use bevy_ecs::prelude::Commands;
2897/// # use bevy_camera::{Camera, Camera2d, RenderTarget};
2898/// # use bevy_window::{Window, WindowRef};
2899///
2900/// fn spawn_camera(mut commands: Commands) {
2901/// let another_window = commands.spawn(Window {
2902/// title: String::from("Another window"),
2903/// ..Default::default()
2904/// }).id();
2905/// commands.spawn((
2906/// Camera2d,
2907/// Camera {
2908/// ..Default::default()
2909/// },
2910/// RenderTarget::Window(WindowRef::Entity(another_window)),
2911/// // We add the Marker here so all Ui will spawn in
2912/// // another window if no UiTargetCamera is specified
2913/// IsDefaultUiCamera
2914/// ));
2915/// }
2916/// ```
2917#[derive(Component, Default)]
2918pub struct IsDefaultUiCamera;
2919
2920#[derive(SystemParam)]
2921pub struct DefaultUiCamera<'w, 's> {
2922 cameras: Query<'w, 's, (Entity, &'static Camera, &'static RenderTarget)>,
2923 default_cameras: Query<'w, 's, Entity, (With<Camera>, With<IsDefaultUiCamera>)>,
2924 primary_window: Query<'w, 's, Entity, With<PrimaryWindow>>,
2925}
2926
2927impl<'w, 's> DefaultUiCamera<'w, 's> {
2928 pub fn get(&self) -> Option<Entity> {
2929 self.default_cameras.single().ok().or_else(|| {
2930 // If there isn't a single camera and the query isn't empty, there is two or more cameras queried.
2931 if !self.default_cameras.is_empty() {
2932 once!(warn!("Two or more Entities with IsDefaultUiCamera found when only one Camera with this marker is allowed."));
2933 }
2934 self.cameras
2935 .iter()
2936 .filter(|(_, _, render_target)| match render_target {
2937 RenderTarget::Window(WindowRef::Primary) => true,
2938 RenderTarget::Window(WindowRef::Entity(w)) => {
2939 self.primary_window.get(*w).is_ok()
2940 }
2941 _ => false,
2942 })
2943 .max_by_key(|(e, c, _)| (c.order, *e))
2944 .map(|(e, _, _)| e)
2945 })
2946 }
2947}
2948
2949/// Derived information about the camera target for this UI node.
2950///
2951/// Updated in [`UiSystems::Prepare`](crate::UiSystems::Prepare) by [`propagate_ui_target_cameras`](crate::update::propagate_ui_target_cameras)
2952#[derive(Component, Clone, Copy, Debug, Reflect, PartialEq)]
2953#[reflect(Component, Default, PartialEq, Clone)]
2954pub struct ComputedUiTargetCamera {
2955 pub(crate) camera: Entity,
2956}
2957
2958impl Default for ComputedUiTargetCamera {
2959 fn default() -> Self {
2960 Self {
2961 camera: Entity::PLACEHOLDER,
2962 }
2963 }
2964}
2965
2966impl ComputedUiTargetCamera {
2967 /// Returns the id of the target camera for this UI node.
2968 pub fn get(&self) -> Option<Entity> {
2969 Some(self.camera).filter(|&entity| entity != Entity::PLACEHOLDER)
2970 }
2971}
2972
2973/// Derived information about the render target for this UI node.
2974#[derive(Component, Clone, Copy, Debug, Reflect, PartialEq)]
2975#[reflect(Component, Default, PartialEq, Clone)]
2976pub struct ComputedUiRenderTargetInfo {
2977 /// The scale factor of the target camera's render target.
2978 pub(crate) scale_factor: f32,
2979 /// The size of the target camera's viewport in physical pixels.
2980 pub(crate) physical_size: UVec2,
2981}
2982
2983impl Default for ComputedUiRenderTargetInfo {
2984 fn default() -> Self {
2985 Self {
2986 scale_factor: 1.,
2987 physical_size: UVec2::ZERO,
2988 }
2989 }
2990}
2991
2992impl ComputedUiRenderTargetInfo {
2993 pub const fn scale_factor(&self) -> f32 {
2994 self.scale_factor
2995 }
2996
2997 /// Returns the size of the target camera's viewport in physical pixels.
2998 pub const fn physical_size(&self) -> UVec2 {
2999 self.physical_size
3000 }
3001
3002 /// Returns the size of the target camera's viewport in logical pixels.
3003 pub fn logical_size(&self) -> Vec2 {
3004 self.physical_size.as_vec2() / self.scale_factor
3005 }
3006}
3007
3008#[cfg(test)]
3009mod tests {
3010 use crate::ComputedNode;
3011 use crate::GridPlacement;
3012 use bevy_math::{Rect, Vec2};
3013 use bevy_sprite::BorderRect;
3014
3015 #[test]
3016 fn invalid_grid_placement_values() {
3017 assert!(std::panic::catch_unwind(|| GridPlacement::span(0)).is_err());
3018 assert!(std::panic::catch_unwind(|| GridPlacement::start(0)).is_err());
3019 assert!(std::panic::catch_unwind(|| GridPlacement::end(0)).is_err());
3020 assert!(std::panic::catch_unwind(|| GridPlacement::start_end(0, 1)).is_err());
3021 assert!(std::panic::catch_unwind(|| GridPlacement::start_end(-1, 0)).is_err());
3022 assert!(std::panic::catch_unwind(|| GridPlacement::start_span(1, 0)).is_err());
3023 assert!(std::panic::catch_unwind(|| GridPlacement::start_span(0, 1)).is_err());
3024 assert!(std::panic::catch_unwind(|| GridPlacement::end_span(0, 1)).is_err());
3025 assert!(std::panic::catch_unwind(|| GridPlacement::end_span(1, 0)).is_err());
3026 assert!(std::panic::catch_unwind(|| GridPlacement::default().set_start(0)).is_err());
3027 assert!(std::panic::catch_unwind(|| GridPlacement::default().set_end(0)).is_err());
3028 assert!(std::panic::catch_unwind(|| GridPlacement::default().set_span(0)).is_err());
3029 }
3030
3031 #[test]
3032 fn grid_placement_accessors() {
3033 assert_eq!(GridPlacement::start(5).get_start(), Some(5));
3034 assert_eq!(GridPlacement::end(-4).get_end(), Some(-4));
3035 assert_eq!(GridPlacement::span(2).get_span(), Some(2));
3036 assert_eq!(GridPlacement::start_end(11, 21).get_span(), None);
3037 assert_eq!(GridPlacement::start_span(3, 5).get_end(), None);
3038 assert_eq!(GridPlacement::end_span(-4, 12).get_start(), None);
3039 }
3040
3041 #[test]
3042 fn computed_node_both_scrollbars() {
3043 let node = ComputedNode {
3044 size: Vec2::splat(100.),
3045 scrollbar_size: Vec2::splat(10.),
3046 content_size: Vec2::splat(100.),
3047 ..Default::default()
3048 };
3049
3050 let (gutter, thumb) = node.horizontal_scrollbar().unwrap();
3051 assert_eq!(
3052 gutter,
3053 Rect {
3054 min: Vec2::new(-50., 40.),
3055 max: Vec2::new(40., 50.)
3056 }
3057 );
3058 assert_eq!(thumb, [-50., 31.]);
3059
3060 let (gutter, thumb) = node.vertical_scrollbar().unwrap();
3061 assert_eq!(
3062 gutter,
3063 Rect {
3064 min: Vec2::new(40., -50.),
3065 max: Vec2::new(50., 40.)
3066 }
3067 );
3068 assert_eq!(thumb, [-50., 31.]);
3069 }
3070
3071 #[test]
3072 fn computed_node_single_horizontal_scrollbar() {
3073 let mut node = ComputedNode {
3074 size: Vec2::splat(100.),
3075 scrollbar_size: Vec2::new(0., 10.),
3076 content_size: Vec2::new(200., 100.),
3077 scroll_position: Vec2::new(0., 0.),
3078 ..Default::default()
3079 };
3080
3081 assert_eq!(None, node.vertical_scrollbar());
3082
3083 let (gutter, thumb) = node.horizontal_scrollbar().unwrap();
3084 assert_eq!(
3085 gutter,
3086 Rect {
3087 min: Vec2::new(-50., 40.),
3088 max: Vec2::new(50., 50.)
3089 }
3090 );
3091 assert_eq!(thumb, [-50., 0.]);
3092
3093 node.scroll_position.x += 100.;
3094 let (gutter, thumb) = node.horizontal_scrollbar().unwrap();
3095 assert_eq!(
3096 gutter,
3097 Rect {
3098 min: Vec2::new(-50., 40.),
3099 max: Vec2::new(50., 50.)
3100 }
3101 );
3102 assert_eq!(thumb, [0., 50.]);
3103 }
3104
3105 #[test]
3106 fn computed_node_single_vertical_scrollbar() {
3107 let mut node = ComputedNode {
3108 size: Vec2::splat(100.),
3109 scrollbar_size: Vec2::new(10., 0.),
3110 content_size: Vec2::new(100., 200.),
3111 scroll_position: Vec2::new(0., 0.),
3112 ..Default::default()
3113 };
3114
3115 assert_eq!(None, node.horizontal_scrollbar());
3116
3117 let (gutter, thumb) = node.vertical_scrollbar().unwrap();
3118 assert_eq!(
3119 gutter,
3120 Rect {
3121 min: Vec2::new(40., -50.),
3122 max: Vec2::new(50., 50.)
3123 }
3124 );
3125 assert_eq!(thumb, [-50., 0.]);
3126
3127 node.scroll_position.y += 100.;
3128 let (gutter, thumb) = node.vertical_scrollbar().unwrap();
3129 assert_eq!(
3130 gutter,
3131 Rect {
3132 min: Vec2::new(40., -50.),
3133 max: Vec2::new(50., 50.)
3134 }
3135 );
3136 assert_eq!(thumb, [0., 50.]);
3137 }
3138
3139 #[test]
3140 fn border_box_is_centered_rect_of_node_size() {
3141 let node = ComputedNode {
3142 size: Vec2::new(100.0, 50.0),
3143 ..Default::default()
3144 };
3145 let border_box = node.border_box();
3146
3147 assert_eq!(border_box.min, Vec2::new(-50.0, -25.0));
3148 assert_eq!(border_box.max, Vec2::new(50.0, 25.0));
3149 }
3150
3151 #[test]
3152 fn padding_box_subtracts_border_thickness() {
3153 let node = ComputedNode {
3154 size: Vec2::new(100.0, 60.0),
3155 border: BorderRect {
3156 min_inset: Vec2::new(5.0, 3.0),
3157 max_inset: Vec2::new(7.0, 9.0),
3158 },
3159 ..Default::default()
3160 };
3161 let padding_box = node.padding_box();
3162
3163 assert_eq!(padding_box.min, Vec2::new(-50.0 + 5.0, -30.0 + 3.0));
3164 assert_eq!(padding_box.max, Vec2::new(50.0 - 7.0, 30.0 - 9.0));
3165 }
3166
3167 #[test]
3168 fn content_box_uses_content_inset() {
3169 let node = ComputedNode {
3170 size: Vec2::new(80.0, 40.0),
3171 padding: BorderRect {
3172 min_inset: Vec2::new(4.0, 2.0),
3173 max_inset: Vec2::new(6.0, 8.0),
3174 },
3175 ..Default::default()
3176 };
3177 let content_box = node.content_box();
3178
3179 assert_eq!(content_box.min, Vec2::new(-40.0 + 4.0, -20.0 + 2.0));
3180 assert_eq!(content_box.max, Vec2::new(40.0 - 6.0, 20.0 - 8.0));
3181 }
3182}