use crate::{1ui_transform::{UiGlobalTransform, UiTransform},2FocusPolicy, UiRect, Val,3};4use bevy_camera::{visibility::Visibility, Camera, RenderTarget};5use bevy_color::{Alpha, Color};6use bevy_derive::{Deref, DerefMut};7use bevy_ecs::{prelude::*, system::SystemParam};8use bevy_math::{vec4, Rect, UVec2, Vec2, Vec4Swizzles};9use bevy_reflect::prelude::*;10use bevy_sprite::BorderRect;11use bevy_utils::once;12use bevy_window::{PrimaryWindow, WindowRef};13use core::{f32, num::NonZero};14use derive_more::derive::From;15use smallvec::SmallVec;16use thiserror::Error;17use tracing::warn;1819/// Provides the computed size and layout properties of the node.20///21/// Fields in this struct are public but should not be modified under most circumstances.22/// For example, in a scrollbar you may want to derive the handle's size from the proportion of23/// scrollable content in-view. You can directly modify `ComputedNode` after layout to set the24/// handle size without any delays.25#[derive(Component, Debug, Copy, Clone, PartialEq, Reflect)]26#[reflect(Component, Default, Debug, Clone)]27pub struct ComputedNode {28/// The order of the node in the UI layout.29/// Nodes with a higher stack index are drawn on top of and receive interactions before nodes with lower stack indices.30///31/// Automatically calculated in [`UiSystems::Stack`](`super::UiSystems::Stack`).32pub stack_index: u32,33/// The size of the node as width and height in physical pixels.34///35/// Automatically calculated by [`ui_layout_system`](`super::layout::ui_layout_system`).36pub size: Vec2,37/// Size of this node's content.38///39/// Automatically calculated by [`ui_layout_system`](`super::layout::ui_layout_system`).40pub content_size: Vec2,41/// Space allocated for scrollbars.42///43/// Automatically calculated by [`ui_layout_system`](`super::layout::ui_layout_system`).44pub scrollbar_size: Vec2,45/// Resolved offset of scrolled content46///47/// Automatically calculated by [`ui_layout_system`](`super::layout::ui_layout_system`).48pub scroll_position: Vec2,49/// The width of this node's outline.50/// If this value is `Auto`, negative or `0.` then no outline will be rendered.51/// Outline updates bypass change detection.52///53/// Automatically calculated by [`ui_layout_system`](`super::layout::ui_layout_system`).54pub outline_width: f32,55/// The amount of space between the outline and the edge of the node.56/// Outline updates bypass change detection.57///58/// Automatically calculated by [`ui_layout_system`](`super::layout::ui_layout_system`).59pub outline_offset: f32,60/// The unrounded size of the node as width and height in physical pixels.61///62/// Automatically calculated by [`ui_layout_system`](`super::layout::ui_layout_system`).63pub unrounded_size: Vec2,64/// Resolved border values in physical pixels.65/// Border updates bypass change detection.66///67/// Automatically calculated by [`ui_layout_system`](`super::layout::ui_layout_system`).68pub border: BorderRect,69/// Resolved border radius values in physical pixels.70/// Border radius updates bypass change detection.71///72/// Automatically calculated by [`ui_layout_system`](`super::layout::ui_layout_system`).73pub border_radius: ResolvedBorderRadius,74/// Resolved padding values in physical pixels.75/// Padding updates bypass change detection.76///77/// Automatically calculated by [`ui_layout_system`](`super::layout::ui_layout_system`).78pub padding: BorderRect,79/// Inverse scale factor for this Node.80/// Multiply physical coordinates by the inverse scale factor to give logical coordinates.81///82/// Automatically calculated by [`ui_layout_system`](`super::layout::ui_layout_system`).83pub inverse_scale_factor: f32,84}8586impl ComputedNode {87/// The calculated node size as width and height in physical pixels.88///89/// Automatically calculated by [`ui_layout_system`](`super::layout::ui_layout_system`).90#[inline]91pub const fn size(&self) -> Vec2 {92self.size93}9495/// The calculated node content size as width and height in physical pixels.96///97/// Automatically calculated by [`ui_layout_system`](`super::layout::ui_layout_system`).98#[inline]99pub const fn content_size(&self) -> Vec2 {100self.content_size101}102103/// Check if the node is empty.104/// A node is considered empty if it has a zero or negative extent along either of its axes.105#[inline]106pub const fn is_empty(&self) -> bool {107self.size.x <= 0. || self.size.y <= 0.108}109110/// The order of the node in the UI layout.111/// Nodes with a higher stack index are drawn on top of and receive interactions before nodes with lower stack indices.112///113/// Automatically calculated in [`UiSystems::Stack`](super::UiSystems::Stack).114pub const fn stack_index(&self) -> u32 {115self.stack_index116}117118/// The calculated node size as width and height in physical pixels before rounding.119///120/// Automatically calculated by [`ui_layout_system`](`super::layout::ui_layout_system`).121#[inline]122pub const fn unrounded_size(&self) -> Vec2 {123self.unrounded_size124}125126/// Returns the thickness of the UI node's outline in physical pixels.127/// If this value is negative or `0.` then no outline will be rendered.128///129/// Automatically calculated by [`ui_layout_system`](`super::layout::ui_layout_system`).130#[inline]131pub const fn outline_width(&self) -> f32 {132self.outline_width133}134135/// Returns the amount of space between the outline and the edge of the node in physical pixels.136///137/// Automatically calculated by [`ui_layout_system`](`super::layout::ui_layout_system`).138#[inline]139pub const fn outline_offset(&self) -> f32 {140self.outline_offset141}142143/// Returns the size of the node when including its outline.144///145/// Automatically calculated by [`ui_layout_system`](`super::layout::ui_layout_system`).146#[inline]147pub const fn outlined_node_size(&self) -> Vec2 {148let offset = 2. * (self.outline_offset + self.outline_width);149Vec2::new(self.size.x + offset, self.size.y + offset)150}151152/// Returns the border radius for each corner of the outline153/// An outline's border radius is derived from the node's border-radius154/// so that the outline wraps the border equally at all points.155///156/// Automatically calculated by [`ui_layout_system`](`super::layout::ui_layout_system`).157#[inline]158pub const fn outline_radius(&self) -> ResolvedBorderRadius {159let outer_distance = self.outline_width + self.outline_offset;160const fn compute_radius(radius: f32, outer_distance: f32) -> f32 {161if radius > 0. {162radius + outer_distance163} else {1640.165}166}167ResolvedBorderRadius {168top_left: compute_radius(self.border_radius.top_left, outer_distance),169top_right: compute_radius(self.border_radius.top_right, outer_distance),170bottom_right: compute_radius(self.border_radius.bottom_right, outer_distance),171bottom_left: compute_radius(self.border_radius.bottom_left, outer_distance),172}173}174175/// Returns the thickness of the node's border on each edge in physical pixels.176///177/// Automatically calculated by [`ui_layout_system`](`super::layout::ui_layout_system`).178#[inline]179pub const fn border(&self) -> BorderRect {180self.border181}182183/// Returns the border radius for each of the node's corners in physical pixels.184///185/// Automatically calculated by [`ui_layout_system`](`super::layout::ui_layout_system`).186#[inline]187pub const fn border_radius(&self) -> ResolvedBorderRadius {188self.border_radius189}190191/// Returns the inner border radius for each of the node's corners in physical pixels.192pub fn inner_radius(&self) -> ResolvedBorderRadius {193fn clamp_corner(r: f32, size: Vec2, offset: Vec2) -> f32 {194let s = 0.5 * size + offset;195let sm = s.x.min(s.y);196r.min(sm)197}198let b = vec4(199self.border.left,200self.border.top,201self.border.right,202self.border.bottom,203);204let s = self.size() - b.xy() - b.zw();205ResolvedBorderRadius {206top_left: clamp_corner(self.border_radius.top_left, s, b.xy()),207top_right: clamp_corner(self.border_radius.top_right, s, b.zy()),208bottom_right: clamp_corner(self.border_radius.bottom_left, s, b.xw()),209bottom_left: clamp_corner(self.border_radius.bottom_right, s, b.zw()),210}211}212213/// Returns the thickness of the node's padding on each edge in physical pixels.214///215/// Automatically calculated by [`ui_layout_system`](`super::layout::ui_layout_system`).216#[inline]217pub const fn padding(&self) -> BorderRect {218self.padding219}220221/// Returns the combined inset on each edge including both padding and border thickness in physical pixels.222#[inline]223pub fn content_inset(&self) -> BorderRect {224self.border + self.padding225}226227/// Returns the inverse of the scale factor for this node.228/// To convert from physical coordinates to logical coordinates multiply by this value.229#[inline]230pub const fn inverse_scale_factor(&self) -> f32 {231self.inverse_scale_factor232}233234// Returns true if `point` within the node.235//236// Matches the sdf function in `ui.wgsl` that is used by the UI renderer to draw rounded rectangles.237pub fn contains_point(&self, transform: UiGlobalTransform, point: Vec2) -> bool {238let Some(local_point) = transform239.try_inverse()240.map(|transform| transform.transform_point2(point))241else {242return false;243};244let [top, bottom] = if local_point.x < 0. {245[self.border_radius.top_left, self.border_radius.bottom_left]246} else {247[248self.border_radius.top_right,249self.border_radius.bottom_right,250]251};252let r = if local_point.y < 0. { top } else { bottom };253let corner_to_point = local_point.abs() - 0.5 * self.size;254let q = corner_to_point + r;255let l = q.max(Vec2::ZERO).length();256let m = q.max_element().min(0.);257l + m - r < 0.258}259260/// Transform a point to normalized node space with the center of the node at the origin and the corners at [+/-0.5, +/-0.5]261pub fn normalize_point(&self, transform: UiGlobalTransform, point: Vec2) -> Option<Vec2> {262self.size263.cmpgt(Vec2::ZERO)264.all()265.then(|| transform.try_inverse())266.flatten()267.map(|transform| transform.transform_point2(point) / self.size)268}269270/// Resolve the node's clipping rect in local space271pub fn resolve_clip_rect(272&self,273overflow: Overflow,274overflow_clip_margin: OverflowClipMargin,275) -> Rect {276let mut clip_rect = Rect::from_center_size(Vec2::ZERO, self.size);277278let clip_inset = match overflow_clip_margin.visual_box {279OverflowClipBox::BorderBox => BorderRect::ZERO,280OverflowClipBox::ContentBox => self.content_inset(),281OverflowClipBox::PaddingBox => self.border(),282};283284clip_rect.min.x += clip_inset.left;285clip_rect.min.y += clip_inset.top;286clip_rect.max.x -= clip_inset.right;287clip_rect.max.y -= clip_inset.bottom;288289if overflow.x == OverflowAxis::Visible {290clip_rect.min.x = -f32::INFINITY;291clip_rect.max.x = f32::INFINITY;292}293if overflow.y == OverflowAxis::Visible {294clip_rect.min.y = -f32::INFINITY;295clip_rect.max.y = f32::INFINITY;296}297298clip_rect299}300}301302impl ComputedNode {303pub const DEFAULT: Self = Self {304stack_index: 0,305size: Vec2::ZERO,306content_size: Vec2::ZERO,307scrollbar_size: Vec2::ZERO,308scroll_position: Vec2::ZERO,309outline_width: 0.,310outline_offset: 0.,311unrounded_size: Vec2::ZERO,312border_radius: ResolvedBorderRadius::ZERO,313border: BorderRect::ZERO,314padding: BorderRect::ZERO,315inverse_scale_factor: 1.,316};317}318319impl Default for ComputedNode {320fn default() -> Self {321Self::DEFAULT322}323}324325/// The scroll position of the node. Values are in logical pixels, increasing from top-left to bottom-right.326///327/// 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.328/// This might seem backwards, however what's really happening is that329/// the scroll position is moving the visible "window" in the local coordinate system of the scrolled content -330/// moving the window down causes the content to move up.331///332/// Updating the values of `ScrollPosition` will reposition the children of the node by the offset amount in logical pixels.333/// `ScrollPosition` may be updated by the layout system when a layout change makes a previously valid `ScrollPosition` invalid.334/// Changing this does nothing on a `Node` without setting at least one `OverflowAxis` to `OverflowAxis::Scroll`.335#[derive(Component, Debug, Clone, Default, Deref, DerefMut, Reflect)]336#[reflect(Component, Default, Clone)]337pub struct ScrollPosition(pub Vec2);338339impl ScrollPosition {340pub const DEFAULT: Self = Self(Vec2::ZERO);341}342343impl From<Vec2> for ScrollPosition {344fn from(value: Vec2) -> Self {345Self(value)346}347}348349/// The base component for UI entities. It describes UI layout and style properties.350///351/// When defining new types of UI entities, require [`Node`] to make them behave like UI nodes.352///353/// Nodes can be laid out using either Flexbox or CSS Grid Layout.354///355/// See below for general learning resources and for documentation on the individual style properties.356///357/// ### Flexbox358///359/// - [MDN: Basic Concepts of Flexbox](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Flexible_Box_Layout/Basic_Concepts_of_Flexbox)360/// - [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.361/// - [Flexbox Froggy](https://flexboxfroggy.com/). An interactive tutorial/game that teaches the essential parts of Flexbox in a fun engaging way.362///363/// ### CSS Grid364///365/// - [MDN: Basic Concepts of Grid Layout](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Grid_Layout/Basic_Concepts_of_Grid_Layout)366/// - [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.367/// - [CSS Grid Garden](https://cssgridgarden.com/). An interactive tutorial/game that teaches the essential parts of CSS Grid in a fun engaging way.368///369/// # See also370///371/// - [`RelativeCursorPosition`](crate::RelativeCursorPosition) to obtain the cursor position relative to this node372/// - [`Interaction`](crate::Interaction) to obtain the interaction state of this node373374#[derive(Component, Clone, PartialEq, Debug, Reflect)]375#[require(376ComputedNode,377ComputedUiTargetCamera,378ComputedUiRenderTargetInfo,379UiTransform,380BackgroundColor,381BorderColor,382BorderRadius,383FocusPolicy,384ScrollPosition,385Visibility,386ZIndex387)]388#[reflect(Component, Default, PartialEq, Debug, Clone)]389#[cfg_attr(390feature = "serialize",391derive(serde::Serialize, serde::Deserialize),392reflect(Serialize, Deserialize)393)]394pub struct Node {395/// Which layout algorithm to use when laying out this node's contents:396/// - [`Display::Flex`]: Use the Flexbox layout algorithm397/// - [`Display::Grid`]: Use the CSS Grid layout algorithm398/// - [`Display::None`]: Hide this node and perform layout as if it does not exist.399///400/// <https://developer.mozilla.org/en-US/docs/Web/CSS/display>401pub display: Display,402403/// Which part of a Node's box length styles like width and height control404/// - [`BoxSizing::BorderBox`]: They refer to the "border box" size (size including padding and border)405/// - [`BoxSizing::ContentBox`]: They refer to the "content box" size (size excluding padding and border)406///407/// `BoxSizing::BorderBox` is generally considered more intuitive and is the default in Bevy even though it is not on the web.408///409/// See: <https://developer.mozilla.org/en-US/docs/Web/CSS/box-sizing>410pub box_sizing: BoxSizing,411412/// Whether a node should be laid out in-flow with, or independently of its siblings:413/// - [`PositionType::Relative`]: Layout this node in-flow with other nodes using the usual (flexbox/grid) layout algorithm.414/// - [`PositionType::Absolute`]: Layout this node on top and independently of other nodes.415///416/// <https://developer.mozilla.org/en-US/docs/Web/CSS/position>417pub position_type: PositionType,418419/// Whether overflowing content should be displayed or clipped.420///421/// <https://developer.mozilla.org/en-US/docs/Web/CSS/overflow>422pub overflow: Overflow,423424/// How much space in logical pixels should be reserved for scrollbars when overflow is set to scroll or auto on an axis.425pub scrollbar_width: f32,426427/// How the bounds of clipped content should be determined428///429/// <https://developer.mozilla.org/en-US/docs/Web/CSS/overflow-clip-margin>430pub overflow_clip_margin: OverflowClipMargin,431432/// The horizontal position of the left edge of the node.433/// - For relatively positioned nodes, this is relative to the node's position as computed during regular layout.434/// - For absolutely positioned nodes, this is relative to the *parent* node's bounding box.435///436/// <https://developer.mozilla.org/en-US/docs/Web/CSS/left>437pub left: Val,438439/// The horizontal position of the right edge of the node.440/// - For relatively positioned nodes, this is relative to the node's position as computed during regular layout.441/// - For absolutely positioned nodes, this is relative to the *parent* node's bounding box.442///443/// <https://developer.mozilla.org/en-US/docs/Web/CSS/right>444pub right: Val,445446/// The vertical position of the top edge of the node.447/// - For relatively positioned nodes, this is relative to the node's position as computed during regular layout.448/// - For absolutely positioned nodes, this is relative to the *parent* node's bounding box.449///450/// <https://developer.mozilla.org/en-US/docs/Web/CSS/top>451pub top: Val,452453/// The vertical position of the bottom edge of the node.454/// - For relatively positioned nodes, this is relative to the node's position as computed during regular layout.455/// - For absolutely positioned nodes, this is relative to the *parent* node's bounding box.456///457/// <https://developer.mozilla.org/en-US/docs/Web/CSS/bottom>458pub bottom: Val,459460/// The ideal width of the node. `width` is used when it is within the bounds defined by `min_width` and `max_width`.461///462/// <https://developer.mozilla.org/en-US/docs/Web/CSS/width>463pub width: Val,464465/// The ideal height of the node. `height` is used when it is within the bounds defined by `min_height` and `max_height`.466///467/// <https://developer.mozilla.org/en-US/docs/Web/CSS/height>468pub height: Val,469470/// The minimum width of the node. `min_width` is used if it is greater than `width` and/or `max_width`.471///472/// <https://developer.mozilla.org/en-US/docs/Web/CSS/min-width>473pub min_width: Val,474475/// The minimum height of the node. `min_height` is used if it is greater than `height` and/or `max_height`.476///477/// <https://developer.mozilla.org/en-US/docs/Web/CSS/min-height>478pub min_height: Val,479480/// The maximum width of the node. `max_width` is used if it is within the bounds defined by `min_width` and `width`.481///482/// <https://developer.mozilla.org/en-US/docs/Web/CSS/max-width>483pub max_width: Val,484485/// The maximum height of the node. `max_height` is used if it is within the bounds defined by `min_height` and `height`.486///487/// <https://developer.mozilla.org/en-US/docs/Web/CSS/max-height>488pub max_height: Val,489490/// The aspect ratio of the node (defined as `width / height`)491///492/// <https://developer.mozilla.org/en-US/docs/Web/CSS/aspect-ratio>493pub aspect_ratio: Option<f32>,494495/// Used to control how each individual item is aligned by default within the space they're given.496/// - For Flexbox containers, sets default cross axis alignment of the child items.497/// - For CSS Grid containers, controls block (vertical) axis alignment of children of this grid container within their grid areas.498///499/// This value is overridden if [`AlignSelf`] on the child node is set.500///501/// <https://developer.mozilla.org/en-US/docs/Web/CSS/align-items>502pub align_items: AlignItems,503504/// Used to control how each individual item is aligned by default within the space they're given.505/// - For Flexbox containers, this property has no effect. See `justify_content` for main axis alignment of flex items.506/// - For CSS Grid containers, sets default inline (horizontal) axis alignment of child items within their grid areas.507///508/// This value is overridden if [`JustifySelf`] on the child node is set.509///510/// <https://developer.mozilla.org/en-US/docs/Web/CSS/justify-items>511pub justify_items: JustifyItems,512513/// Used to control how the specified item is aligned within the space it's given.514/// - For Flexbox items, controls cross axis alignment of the item.515/// - For CSS Grid items, controls block (vertical) axis alignment of a grid item within its grid area.516///517/// If set to `Auto`, alignment is inherited from the value of [`AlignItems`] set on the parent node.518///519/// <https://developer.mozilla.org/en-US/docs/Web/CSS/align-self>520pub align_self: AlignSelf,521522/// Used to control how the specified item is aligned within the space it's given.523/// - For Flexbox items, this property has no effect. See `justify_content` for main axis alignment of flex items.524/// - For CSS Grid items, controls inline (horizontal) axis alignment of a grid item within its grid area.525///526/// If set to `Auto`, alignment is inherited from the value of [`JustifyItems`] set on the parent node.527///528/// <https://developer.mozilla.org/en-US/docs/Web/CSS/justify-self>529pub justify_self: JustifySelf,530531/// Used to control how items are distributed.532/// - For Flexbox containers, controls alignment of lines if `flex_wrap` is set to [`FlexWrap::Wrap`] and there are multiple lines of items.533/// - For CSS Grid containers, controls alignment of grid rows.534///535/// <https://developer.mozilla.org/en-US/docs/Web/CSS/align-content>536pub align_content: AlignContent,537538/// Used to control how items are distributed.539/// - For Flexbox containers, controls alignment of items in the main axis.540/// - For CSS Grid containers, controls alignment of grid columns.541///542/// <https://developer.mozilla.org/en-US/docs/Web/CSS/justify-content>543pub justify_content: JustifyContent,544545/// The amount of space around a node outside its border.546///547/// If a percentage value is used, the percentage is calculated based on the width of the parent node.548///549/// # Example550/// ```551/// # use bevy_ui::{Node, UiRect, Val};552/// let node = Node {553/// margin: UiRect {554/// left: Val::Percent(10.),555/// right: Val::Percent(10.),556/// top: Val::Percent(15.),557/// bottom: Val::Percent(15.)558/// },559/// ..Default::default()560/// };561/// ```562/// 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.563///564/// <https://developer.mozilla.org/en-US/docs/Web/CSS/margin>565pub margin: UiRect,566567/// The amount of space between the edges of a node and its contents.568///569/// If a percentage value is used, the percentage is calculated based on the width of the parent node.570///571/// # Example572/// ```573/// # use bevy_ui::{Node, UiRect, Val};574/// let node = Node {575/// padding: UiRect {576/// left: Val::Percent(1.),577/// right: Val::Percent(2.),578/// top: Val::Percent(3.),579/// bottom: Val::Percent(4.)580/// },581/// ..Default::default()582/// };583/// ```584/// 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.585///586/// <https://developer.mozilla.org/en-US/docs/Web/CSS/padding>587pub padding: UiRect,588589/// The amount of space between the margins of a node and its padding.590///591/// If a percentage value is used, the percentage is calculated based on the width of the parent node.592///593/// 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.594///595/// <https://developer.mozilla.org/en-US/docs/Web/CSS/border-width>596pub border: UiRect,597598/// Whether a Flexbox container should be a row or a column. This property has no effect on Grid nodes.599///600/// <https://developer.mozilla.org/en-US/docs/Web/CSS/flex-direction>601pub flex_direction: FlexDirection,602603/// Whether a Flexbox container should wrap its contents onto multiple lines if they overflow. This property has no effect on Grid nodes.604///605/// <https://developer.mozilla.org/en-US/docs/Web/CSS/flex-wrap>606pub flex_wrap: FlexWrap,607608/// Defines how much a flexbox item should grow if there's space available. Defaults to 0 (don't grow at all).609///610/// <https://developer.mozilla.org/en-US/docs/Web/CSS/flex-grow>611pub flex_grow: f32,612613/// Defines how much a flexbox item should shrink if there's not enough space available. Defaults to 1.614///615/// <https://developer.mozilla.org/en-US/docs/Web/CSS/flex-shrink>616pub flex_shrink: f32,617618/// The initial length of a flexbox in the main axis, before flex growing/shrinking properties are applied.619///620/// `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`.621///622/// <https://developer.mozilla.org/en-US/docs/Web/CSS/flex-basis>623pub flex_basis: Val,624625/// The size of the gutters between items in a vertical flexbox layout or between rows in a grid layout.626///627/// Note: Values of `Val::Auto` are not valid and are treated as zero.628///629/// <https://developer.mozilla.org/en-US/docs/Web/CSS/row-gap>630pub row_gap: Val,631632/// The size of the gutters between items in a horizontal flexbox layout or between column in a grid layout.633///634/// Note: Values of `Val::Auto` are not valid and are treated as zero.635///636/// <https://developer.mozilla.org/en-US/docs/Web/CSS/column-gap>637pub column_gap: Val,638639/// 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.640/// Only affects Grid layouts.641///642/// <https://developer.mozilla.org/en-US/docs/Web/CSS/grid-auto-flow>643pub grid_auto_flow: GridAutoFlow,644645/// Defines the number of rows a grid has and the sizes of those rows. If grid items are given explicit placements then more rows may646/// be implicitly generated by items that are placed out of bounds. The sizes of those rows are controlled by `grid_auto_rows` property.647///648/// <https://developer.mozilla.org/en-US/docs/Web/CSS/grid-template-rows>649pub grid_template_rows: Vec<RepeatedGridTrack>,650651/// Defines the number of columns a grid has and the sizes of those columns. If grid items are given explicit placements then more columns may652/// be implicitly generated by items that are placed out of bounds. The sizes of those columns are controlled by `grid_auto_columns` property.653///654/// <https://developer.mozilla.org/en-US/docs/Web/CSS/grid-template-columns>655pub grid_template_columns: Vec<RepeatedGridTrack>,656657/// Defines the size of implicitly created rows. Rows are created implicitly when grid items are given explicit placements that are out of bounds658/// of the rows explicitly created using `grid_template_rows`.659///660/// <https://developer.mozilla.org/en-US/docs/Web/CSS/grid-auto-rows>661pub grid_auto_rows: Vec<GridTrack>,662/// Defines the size of implicitly created columns. Columns are created implicitly when grid items are given explicit placements that are out of bounds663/// of the columns explicitly created using `grid_template_columns`.664///665/// <https://developer.mozilla.org/en-US/docs/Web/CSS/grid-auto-columns>666pub grid_auto_columns: Vec<GridTrack>,667668/// The row in which a grid item starts and how many rows it spans.669///670/// <https://developer.mozilla.org/en-US/docs/Web/CSS/grid-row>671pub grid_row: GridPlacement,672673/// The column in which a grid item starts and how many columns it spans.674///675/// <https://developer.mozilla.org/en-US/docs/Web/CSS/grid-column>676pub grid_column: GridPlacement,677}678679impl Node {680pub const DEFAULT: Self = Self {681display: Display::DEFAULT,682box_sizing: BoxSizing::DEFAULT,683position_type: PositionType::DEFAULT,684left: Val::Auto,685right: Val::Auto,686top: Val::Auto,687bottom: Val::Auto,688flex_direction: FlexDirection::DEFAULT,689flex_wrap: FlexWrap::DEFAULT,690align_items: AlignItems::DEFAULT,691justify_items: JustifyItems::DEFAULT,692align_self: AlignSelf::DEFAULT,693justify_self: JustifySelf::DEFAULT,694align_content: AlignContent::DEFAULT,695justify_content: JustifyContent::DEFAULT,696margin: UiRect::DEFAULT,697padding: UiRect::DEFAULT,698border: UiRect::DEFAULT,699flex_grow: 0.0,700flex_shrink: 1.0,701flex_basis: Val::Auto,702width: Val::Auto,703height: Val::Auto,704min_width: Val::Auto,705min_height: Val::Auto,706max_width: Val::Auto,707max_height: Val::Auto,708aspect_ratio: None,709overflow: Overflow::DEFAULT,710overflow_clip_margin: OverflowClipMargin::DEFAULT,711scrollbar_width: 0.,712row_gap: Val::ZERO,713column_gap: Val::ZERO,714grid_auto_flow: GridAutoFlow::DEFAULT,715grid_template_rows: Vec::new(),716grid_template_columns: Vec::new(),717grid_auto_rows: Vec::new(),718grid_auto_columns: Vec::new(),719grid_column: GridPlacement::DEFAULT,720grid_row: GridPlacement::DEFAULT,721};722}723724impl Default for Node {725fn default() -> Self {726Self::DEFAULT727}728}729730/// Used to control how each individual item is aligned by default within the space they're given.731/// - For Flexbox containers, sets default cross axis alignment of the child items.732/// - For CSS Grid containers, controls block (vertical) axis alignment of children of this grid container within their grid areas.733///734/// <https://developer.mozilla.org/en-US/docs/Web/CSS/align-items>735#[derive(Copy, Clone, PartialEq, Eq, Debug, Reflect)]736#[reflect(Default, PartialEq, Clone)]737#[cfg_attr(738feature = "serialize",739derive(serde::Serialize, serde::Deserialize),740reflect(Serialize, Deserialize)741)]742pub enum AlignItems {743/// The items are packed in their default position as if no alignment was applied.744Default,745/// The items are packed towards the start of the axis.746Start,747/// The items are packed towards the end of the axis.748End,749/// The items are packed towards the start of the axis, unless the flex direction is reversed;750/// then they are packed towards the end of the axis.751FlexStart,752/// The items are packed towards the end of the axis, unless the flex direction is reversed;753/// then they are packed towards the start of the axis.754FlexEnd,755/// The items are packed along the center of the axis.756Center,757/// The items are packed such that their baselines align.758Baseline,759/// The items are stretched to fill the space they're given.760Stretch,761}762763impl AlignItems {764pub const DEFAULT: Self = Self::Default;765}766767impl Default for AlignItems {768fn default() -> Self {769Self::DEFAULT770}771}772773/// Used to control how each individual item is aligned by default within the space they're given.774/// - For Flexbox containers, this property has no effect. See `justify_content` for main axis alignment of flex items.775/// - For CSS Grid containers, sets default inline (horizontal) axis alignment of child items within their grid areas.776///777/// <https://developer.mozilla.org/en-US/docs/Web/CSS/justify-items>778#[derive(Copy, Clone, PartialEq, Eq, Debug, Reflect)]779#[reflect(Default, PartialEq, Clone)]780#[cfg_attr(781feature = "serialize",782derive(serde::Serialize, serde::Deserialize),783reflect(Serialize, Deserialize)784)]785pub enum JustifyItems {786/// The items are packed in their default position as if no alignment was applied.787Default,788/// The items are packed towards the start of the axis.789Start,790/// The items are packed towards the end of the axis.791End,792/// The items are packed along the center of the axis793Center,794/// The items are packed such that their baselines align.795Baseline,796/// The items are stretched to fill the space they're given.797Stretch,798}799800impl JustifyItems {801pub const DEFAULT: Self = Self::Default;802}803804impl Default for JustifyItems {805fn default() -> Self {806Self::DEFAULT807}808}809810/// Used to control how the specified item is aligned within the space it's given.811/// - For Flexbox items, controls cross axis alignment of the item.812/// - For CSS Grid items, controls block (vertical) axis alignment of a grid item within its grid area.813///814/// <https://developer.mozilla.org/en-US/docs/Web/CSS/align-self>815#[derive(Copy, Clone, PartialEq, Eq, Debug, Reflect)]816#[reflect(Default, PartialEq, Clone)]817#[cfg_attr(818feature = "serialize",819derive(serde::Serialize, serde::Deserialize),820reflect(Serialize, Deserialize)821)]822pub enum AlignSelf {823/// Use the parent node's [`AlignItems`] value to determine how this item should be aligned.824Auto,825/// This item will be aligned with the start of the axis.826Start,827/// This item will be aligned with the end of the axis.828End,829/// This item will be aligned with the start of the axis, unless the flex direction is reversed;830/// then it will be aligned with the end of the axis.831FlexStart,832/// This item will be aligned with the end of the axis, unless the flex direction is reversed;833/// then it will be aligned with the start of the axis.834FlexEnd,835/// This item will be aligned along the center of the axis.836Center,837/// This item will be aligned at the baseline.838Baseline,839/// This item will be stretched to fill the container.840Stretch,841}842843impl AlignSelf {844pub const DEFAULT: Self = Self::Auto;845}846847impl Default for AlignSelf {848fn default() -> Self {849Self::DEFAULT850}851}852853/// Used to control how the specified item is aligned within the space it's given.854/// - For children of flex nodes, this property has no effect. See `justify_content` for main axis alignment of flex items.855/// - For CSS Grid items, controls inline (horizontal) axis alignment of a grid item within its grid area.856///857/// <https://developer.mozilla.org/en-US/docs/Web/CSS/justify-self>858#[derive(Copy, Clone, PartialEq, Eq, Debug, Reflect)]859#[reflect(Default, PartialEq, Clone)]860#[cfg_attr(861feature = "serialize",862derive(serde::Serialize, serde::Deserialize),863reflect(Serialize, Deserialize)864)]865pub enum JustifySelf {866/// Use the parent node's [`JustifyItems`] value to determine how this item should be aligned.867Auto,868/// This item will be aligned with the start of the axis.869Start,870/// This item will be aligned with the end of the axis.871End,872/// This item will be aligned along the center of the axis.873Center,874/// This item will be aligned at the baseline.875Baseline,876/// This item will be stretched to fill the space it's given.877Stretch,878}879880impl JustifySelf {881pub const DEFAULT: Self = Self::Auto;882}883884impl Default for JustifySelf {885fn default() -> Self {886Self::DEFAULT887}888}889890/// Used to control how items are distributed.891/// - For Flexbox containers, controls alignment of lines if `flex_wrap` is set to [`FlexWrap::Wrap`] and there are multiple lines of items.892/// - For CSS Grid containers, controls alignment of grid rows.893///894/// <https://developer.mozilla.org/en-US/docs/Web/CSS/align-content>895#[derive(Copy, Clone, PartialEq, Eq, Debug, Reflect)]896#[reflect(Default, PartialEq, Clone)]897#[cfg_attr(898feature = "serialize",899derive(serde::Serialize, serde::Deserialize),900reflect(Serialize, Deserialize)901)]902pub enum AlignContent {903/// The items are packed in their default position as if no alignment was applied.904Default,905/// The items are packed towards the start of the axis.906Start,907/// The items are packed towards the end of the axis.908End,909/// The items are packed towards the start of the axis, unless the flex direction is reversed;910/// then the items are packed towards the end of the axis.911FlexStart,912/// The items are packed towards the end of the axis, unless the flex direction is reversed;913/// then the items are packed towards the start of the axis.914FlexEnd,915/// The items are packed along the center of the axis.916Center,917/// The items are stretched to fill the container along the axis.918Stretch,919/// The items are distributed such that the gap between any two items is equal.920SpaceBetween,921/// The items are distributed such that the gap between and around any two items is equal.922SpaceEvenly,923/// The items are distributed such that the gap between and around any two items is equal, with half-size gaps on either end.924SpaceAround,925}926927impl AlignContent {928pub const DEFAULT: Self = Self::Default;929}930931impl Default for AlignContent {932fn default() -> Self {933Self::DEFAULT934}935}936937/// Used to control how items are distributed.938/// - For Flexbox containers, controls alignment of items in the main axis.939/// - For CSS Grid containers, controls alignment of grid columns.940///941/// <https://developer.mozilla.org/en-US/docs/Web/CSS/justify-content>942#[derive(Copy, Clone, PartialEq, Eq, Debug, Reflect)]943#[reflect(Default, PartialEq, Clone)]944#[cfg_attr(945feature = "serialize",946derive(serde::Serialize, serde::Deserialize),947reflect(Serialize, Deserialize)948)]949pub enum JustifyContent {950/// The items are packed in their default position as if no alignment was applied.951Default,952/// The items are packed towards the start of the axis.953Start,954/// The items are packed towards the end of the axis.955End,956/// The items are packed towards the start of the axis, unless the flex direction is reversed;957/// then the items are packed towards the end of the axis.958FlexStart,959/// The items are packed towards the end of the axis, unless the flex direction is reversed;960/// then the items are packed towards the start of the axis.961FlexEnd,962/// The items are packed along the center of the axis.963Center,964/// The items are stretched to fill the container along the axis.965Stretch,966/// The items are distributed such that the gap between any two items is equal.967SpaceBetween,968/// The items are distributed such that the gap between and around any two items is equal.969SpaceEvenly,970/// The items are distributed such that the gap between and around any two items is equal, with half-size gaps on either end.971SpaceAround,972}973974impl JustifyContent {975pub const DEFAULT: Self = Self::Default;976}977978impl Default for JustifyContent {979fn default() -> Self {980Self::DEFAULT981}982}983984/// Defines the layout model used by this node.985///986/// Part of the [`Node`] component.987#[derive(Copy, Clone, PartialEq, Eq, Debug, Reflect)]988#[reflect(Default, PartialEq, Clone)]989#[cfg_attr(990feature = "serialize",991derive(serde::Serialize, serde::Deserialize),992reflect(Serialize, Deserialize)993)]994pub enum Display {995/// Use Flexbox layout model to determine the position of this [`Node`]'s children.996Flex,997/// Use CSS Grid layout model to determine the position of this [`Node`]'s children.998Grid,999/// Use CSS Block layout model to determine the position of this [`Node`]'s children.1000Block,1001/// Use no layout, don't render this node and its children.1002///1003/// If you want to hide a node and its children,1004/// but keep its layout in place, set its [`Visibility`] component instead.1005None,1006}10071008impl Display {1009pub const DEFAULT: Self = Self::Flex;1010}10111012impl Default for Display {1013fn default() -> Self {1014Self::DEFAULT1015}1016}10171018/// Which part of a Node's box length styles like width and height control1019///1020/// See: <https://developer.mozilla.org/en-US/docs/Web/CSS/box-sizing>1021#[derive(Copy, Clone, PartialEq, Eq, Debug, Reflect)]1022#[reflect(Default, PartialEq, Clone)]1023#[cfg_attr(1024feature = "serialize",1025derive(serde::Serialize, serde::Deserialize),1026reflect(Serialize, Deserialize)1027)]1028pub enum BoxSizing {1029/// Length styles like width and height refer to the "border box" size (size including padding and border)1030BorderBox,1031/// Length styles like width and height refer to the "content box" size (size excluding padding and border)1032ContentBox,1033}10341035impl BoxSizing {1036pub const DEFAULT: Self = Self::BorderBox;1037}10381039impl Default for BoxSizing {1040fn default() -> Self {1041Self::DEFAULT1042}1043}10441045/// Defines how flexbox items are ordered within a flexbox1046#[derive(Copy, Clone, PartialEq, Eq, Debug, Reflect)]1047#[reflect(Default, PartialEq, Clone)]1048#[cfg_attr(1049feature = "serialize",1050derive(serde::Serialize, serde::Deserialize),1051reflect(Serialize, Deserialize)1052)]1053pub enum FlexDirection {1054/// Same way as text direction along the main axis.1055Row,1056/// Flex from top to bottom.1057Column,1058/// Opposite way as text direction along the main axis.1059RowReverse,1060/// Flex from bottom to top.1061ColumnReverse,1062}10631064impl FlexDirection {1065pub const DEFAULT: Self = Self::Row;1066}10671068impl Default for FlexDirection {1069fn default() -> Self {1070Self::DEFAULT1071}1072}10731074/// Whether to show or hide overflowing items1075#[derive(Copy, Clone, PartialEq, Eq, Debug, Reflect)]1076#[reflect(Default, PartialEq, Clone)]1077#[cfg_attr(1078feature = "serialize",1079derive(serde::Serialize, serde::Deserialize),1080reflect(Serialize, Deserialize)1081)]1082pub struct Overflow {1083/// Whether to show or clip overflowing items on the x axis1084pub x: OverflowAxis,1085/// Whether to show or clip overflowing items on the y axis1086pub y: OverflowAxis,1087}10881089impl Overflow {1090pub const DEFAULT: Self = Self {1091x: OverflowAxis::DEFAULT,1092y: OverflowAxis::DEFAULT,1093};10941095/// Show overflowing items on both axes1096pub const fn visible() -> Self {1097Self {1098x: OverflowAxis::Visible,1099y: OverflowAxis::Visible,1100}1101}11021103/// Clip overflowing items on both axes1104pub const fn clip() -> Self {1105Self {1106x: OverflowAxis::Clip,1107y: OverflowAxis::Clip,1108}1109}11101111/// Clip overflowing items on the x axis1112pub const fn clip_x() -> Self {1113Self {1114x: OverflowAxis::Clip,1115y: OverflowAxis::Visible,1116}1117}11181119/// Clip overflowing items on the y axis1120pub const fn clip_y() -> Self {1121Self {1122x: OverflowAxis::Visible,1123y: OverflowAxis::Clip,1124}1125}11261127/// Hide overflowing items on both axes by influencing layout and then clipping1128pub const fn hidden() -> Self {1129Self {1130x: OverflowAxis::Hidden,1131y: OverflowAxis::Hidden,1132}1133}11341135/// Hide overflowing items on the x axis by influencing layout and then clipping1136pub const fn hidden_x() -> Self {1137Self {1138x: OverflowAxis::Hidden,1139y: OverflowAxis::Visible,1140}1141}11421143/// Hide overflowing items on the y axis by influencing layout and then clipping1144pub const fn hidden_y() -> Self {1145Self {1146x: OverflowAxis::Visible,1147y: OverflowAxis::Hidden,1148}1149}11501151/// Overflow is visible on both axes1152pub const fn is_visible(&self) -> bool {1153self.x.is_visible() && self.y.is_visible()1154}11551156pub const fn scroll() -> Self {1157Self {1158x: OverflowAxis::Scroll,1159y: OverflowAxis::Scroll,1160}1161}11621163/// Scroll overflowing items on the x axis1164pub const fn scroll_x() -> Self {1165Self {1166x: OverflowAxis::Scroll,1167y: OverflowAxis::Visible,1168}1169}11701171/// Scroll overflowing items on the y axis1172pub const fn scroll_y() -> Self {1173Self {1174x: OverflowAxis::Visible,1175y: OverflowAxis::Scroll,1176}1177}1178}11791180impl Default for Overflow {1181fn default() -> Self {1182Self::DEFAULT1183}1184}11851186/// Whether to show or hide overflowing items1187#[derive(Copy, Clone, PartialEq, Eq, Debug, Reflect)]1188#[reflect(Default, PartialEq, Clone)]1189#[cfg_attr(1190feature = "serialize",1191derive(serde::Serialize, serde::Deserialize),1192reflect(Serialize, Deserialize)1193)]1194pub enum OverflowAxis {1195/// Show overflowing items.1196Visible,1197/// Hide overflowing items by clipping.1198Clip,1199/// Hide overflowing items by influencing layout and then clipping.1200Hidden,1201/// Scroll overflowing items.1202Scroll,1203}12041205impl OverflowAxis {1206pub const DEFAULT: Self = Self::Visible;12071208/// Overflow is visible on this axis1209pub const fn is_visible(&self) -> bool {1210matches!(self, Self::Visible)1211}1212}12131214impl Default for OverflowAxis {1215fn default() -> Self {1216Self::DEFAULT1217}1218}12191220/// The bounds of the visible area when a UI node is clipped.1221#[derive(Default, Copy, Clone, PartialEq, Debug, Reflect)]1222#[reflect(Default, PartialEq, Clone)]1223#[cfg_attr(1224feature = "serialize",1225derive(serde::Serialize, serde::Deserialize),1226reflect(Serialize, Deserialize)1227)]1228pub struct OverflowClipMargin {1229/// Visible unclipped area1230pub visual_box: OverflowClipBox,1231/// Width of the margin on each edge of the visual box in logical pixels.1232/// The width of the margin will be zero if a negative value is set.1233pub margin: f32,1234}12351236impl OverflowClipMargin {1237pub const DEFAULT: Self = Self {1238visual_box: OverflowClipBox::PaddingBox,1239margin: 0.,1240};12411242/// Clip any content that overflows outside the content box1243pub const fn content_box() -> Self {1244Self {1245visual_box: OverflowClipBox::ContentBox,1246..Self::DEFAULT1247}1248}12491250/// Clip any content that overflows outside the padding box1251pub const fn padding_box() -> Self {1252Self {1253visual_box: OverflowClipBox::PaddingBox,1254..Self::DEFAULT1255}1256}12571258/// Clip any content that overflows outside the border box1259pub const fn border_box() -> Self {1260Self {1261visual_box: OverflowClipBox::BorderBox,1262..Self::DEFAULT1263}1264}12651266/// Add a margin on each edge of the visual box in logical pixels.1267/// The width of the margin will be zero if a negative value is set.1268pub const fn with_margin(mut self, margin: f32) -> Self {1269self.margin = margin;1270self1271}1272}12731274/// Used to determine the bounds of the visible area when a UI node is clipped.1275#[derive(Default, Copy, Clone, PartialEq, Eq, Debug, Reflect)]1276#[reflect(Default, PartialEq, Clone)]1277#[cfg_attr(1278feature = "serialize",1279derive(serde::Serialize, serde::Deserialize),1280reflect(Serialize, Deserialize)1281)]1282pub enum OverflowClipBox {1283/// Clip any content that overflows outside the content box1284ContentBox,1285/// Clip any content that overflows outside the padding box1286#[default]1287PaddingBox,1288/// Clip any content that overflows outside the border box1289BorderBox,1290}12911292/// The strategy used to position this node1293#[derive(Copy, Clone, PartialEq, Eq, Debug, Reflect)]1294#[reflect(Default, PartialEq, Clone)]1295#[cfg_attr(1296feature = "serialize",1297derive(serde::Serialize, serde::Deserialize),1298reflect(Serialize, Deserialize)1299)]1300pub enum PositionType {1301/// Relative to all other nodes with the [`PositionType::Relative`] value.1302Relative,1303/// Independent of all other nodes, but relative to its parent node.1304Absolute,1305}13061307impl PositionType {1308pub const DEFAULT: Self = Self::Relative;1309}13101311impl Default for PositionType {1312fn default() -> Self {1313Self::DEFAULT1314}1315}13161317/// Defines if flexbox items appear on a single line or on multiple lines1318#[derive(Copy, Clone, PartialEq, Eq, Debug, Reflect)]1319#[reflect(Default, PartialEq, Clone)]1320#[cfg_attr(1321feature = "serialize",1322derive(serde::Serialize, serde::Deserialize),1323reflect(Serialize, Deserialize)1324)]1325pub enum FlexWrap {1326/// Single line, will overflow if needed.1327NoWrap,1328/// Multiple lines, if needed.1329Wrap,1330/// Same as [`FlexWrap::Wrap`] but new lines will appear before the previous one.1331WrapReverse,1332}13331334impl FlexWrap {1335pub const DEFAULT: Self = Self::NoWrap;1336}13371338impl Default for FlexWrap {1339fn default() -> Self {1340Self::DEFAULT1341}1342}13431344/// Controls whether grid items are placed row-wise or column-wise as well as whether the sparse or dense packing algorithm is used.1345///1346/// The "dense" packing algorithm attempts to fill in holes earlier in the grid, if smaller items come up later.1347/// This may cause items to appear out-of-order when doing so would fill in holes left by larger items.1348///1349/// Defaults to [`GridAutoFlow::Row`].1350///1351/// <https://developer.mozilla.org/en-US/docs/Web/CSS/grid-auto-flow>1352#[derive(Copy, Clone, PartialEq, Eq, Debug, Reflect)]1353#[reflect(Default, PartialEq, Clone)]1354#[cfg_attr(1355feature = "serialize",1356derive(serde::Serialize, serde::Deserialize),1357reflect(Serialize, Deserialize)1358)]1359pub enum GridAutoFlow {1360/// Items are placed by filling each row in turn, adding new rows as necessary.1361Row,1362/// Items are placed by filling each column in turn, adding new columns as necessary.1363Column,1364/// Combines `Row` with the dense packing algorithm.1365RowDense,1366/// Combines `Column` with the dense packing algorithm.1367ColumnDense,1368}13691370impl GridAutoFlow {1371pub const DEFAULT: Self = Self::Row;1372}13731374impl Default for GridAutoFlow {1375fn default() -> Self {1376Self::DEFAULT1377}1378}13791380#[derive(Default, Copy, Clone, PartialEq, Debug, Reflect)]1381#[reflect(Default, PartialEq, Clone)]1382#[cfg_attr(1383feature = "serialize",1384derive(serde::Serialize, serde::Deserialize),1385reflect(Serialize, Deserialize)1386)]1387pub enum MinTrackSizingFunction {1388/// Track minimum size should be a fixed pixel value1389Px(f32),1390/// Track minimum size should be a percentage value1391Percent(f32),1392/// Track minimum size should be content sized under a min-content constraint1393MinContent,1394/// Track minimum size should be content sized under a max-content constraint1395MaxContent,1396/// Track minimum size should be automatically sized1397#[default]1398Auto,1399/// Track minimum size should be a percent of the viewport's smaller dimension.1400VMin(f32),1401/// Track minimum size should be a percent of the viewport's larger dimension.1402VMax(f32),1403/// Track minimum size should be a percent of the viewport's height dimension.1404Vh(f32),1405/// Track minimum size should be a percent of the viewport's width dimension.1406Vw(f32),1407}14081409#[derive(Default, Copy, Clone, PartialEq, Debug, Reflect)]1410#[reflect(Default, PartialEq, Clone)]1411#[cfg_attr(1412feature = "serialize",1413derive(serde::Serialize, serde::Deserialize),1414reflect(Serialize, Deserialize)1415)]1416pub enum MaxTrackSizingFunction {1417/// Track maximum size should be a fixed pixel value1418Px(f32),1419/// Track maximum size should be a percentage value1420Percent(f32),1421/// Track maximum size should be content sized under a min-content constraint1422MinContent,1423/// Track maximum size should be content sized under a max-content constraint1424MaxContent,1425/// Track maximum size should be sized according to the fit-content formula with a fixed pixel limit1426FitContentPx(f32),1427/// Track maximum size should be sized according to the fit-content formula with a percentage limit1428FitContentPercent(f32),1429/// Track maximum size should be automatically sized1430#[default]1431Auto,1432/// The dimension as a fraction of the total available grid space (`fr` units in CSS)1433/// Specified value is the numerator of the fraction. Denominator is the sum of all fractions specified in that grid dimension.1434///1435/// Spec: <https://www.w3.org/TR/css3-grid-layout/#fr-unit>1436Fraction(f32),1437/// Track maximum size should be a percent of the viewport's smaller dimension.1438VMin(f32),1439/// Track maximum size should be a percent of the viewport's smaller dimension.1440VMax(f32),1441/// Track maximum size should be a percent of the viewport's height dimension.1442Vh(f32),1443/// Track maximum size should be a percent of the viewport's width dimension.1444Vw(f32),1445}14461447/// A [`GridTrack`] is a Row or Column of a CSS Grid. This struct specifies what size the track should be.1448/// See below for the different "track sizing functions" you can specify.1449#[derive(Copy, Clone, PartialEq, Debug, Reflect)]1450#[reflect(Default, PartialEq, Clone)]1451#[cfg_attr(1452feature = "serialize",1453derive(serde::Serialize, serde::Deserialize),1454reflect(Serialize, Deserialize)1455)]1456pub struct GridTrack {1457pub(crate) min_sizing_function: MinTrackSizingFunction,1458pub(crate) max_sizing_function: MaxTrackSizingFunction,1459}14601461impl GridTrack {1462pub const DEFAULT: Self = Self {1463min_sizing_function: MinTrackSizingFunction::Auto,1464max_sizing_function: MaxTrackSizingFunction::Auto,1465};14661467/// Create a grid track with a fixed pixel size1468pub fn px<T: From<Self>>(value: f32) -> T {1469Self {1470min_sizing_function: MinTrackSizingFunction::Px(value),1471max_sizing_function: MaxTrackSizingFunction::Px(value),1472}1473.into()1474}14751476/// Create a grid track with a percentage size1477pub fn percent<T: From<Self>>(value: f32) -> T {1478Self {1479min_sizing_function: MinTrackSizingFunction::Percent(value),1480max_sizing_function: MaxTrackSizingFunction::Percent(value),1481}1482.into()1483}14841485/// Create a grid track with an `fr` size.1486/// Note that this will give the track a content-based minimum size.1487/// Usually you are best off using `GridTrack::flex` instead which uses a zero minimum size.1488pub fn fr<T: From<Self>>(value: f32) -> T {1489Self {1490min_sizing_function: MinTrackSizingFunction::Auto,1491max_sizing_function: MaxTrackSizingFunction::Fraction(value),1492}1493.into()1494}14951496/// Create a grid track with a `minmax(0, Nfr)` size.1497pub fn flex<T: From<Self>>(value: f32) -> T {1498Self {1499min_sizing_function: MinTrackSizingFunction::Px(0.0),1500max_sizing_function: MaxTrackSizingFunction::Fraction(value),1501}1502.into()1503}15041505/// Create a grid track which is automatically sized to fit its contents.1506pub fn auto<T: From<Self>>() -> T {1507Self {1508min_sizing_function: MinTrackSizingFunction::Auto,1509max_sizing_function: MaxTrackSizingFunction::Auto,1510}1511.into()1512}15131514/// Create a grid track which is automatically sized to fit its contents when sized at their "min-content" sizes1515pub fn min_content<T: From<Self>>() -> T {1516Self {1517min_sizing_function: MinTrackSizingFunction::MinContent,1518max_sizing_function: MaxTrackSizingFunction::MinContent,1519}1520.into()1521}15221523/// Create a grid track which is automatically sized to fit its contents when sized at their "max-content" sizes1524pub fn max_content<T: From<Self>>() -> T {1525Self {1526min_sizing_function: MinTrackSizingFunction::MaxContent,1527max_sizing_function: MaxTrackSizingFunction::MaxContent,1528}1529.into()1530}15311532/// Create a `fit-content()` grid track with fixed pixel limit.1533///1534/// <https://developer.mozilla.org/en-US/docs/Web/CSS/fit-content_function>1535pub fn fit_content_px<T: From<Self>>(limit: f32) -> T {1536Self {1537min_sizing_function: MinTrackSizingFunction::Auto,1538max_sizing_function: MaxTrackSizingFunction::FitContentPx(limit),1539}1540.into()1541}15421543/// Create a `fit-content()` grid track with percentage limit.1544///1545/// <https://developer.mozilla.org/en-US/docs/Web/CSS/fit-content_function>1546pub fn fit_content_percent<T: From<Self>>(limit: f32) -> T {1547Self {1548min_sizing_function: MinTrackSizingFunction::Auto,1549max_sizing_function: MaxTrackSizingFunction::FitContentPercent(limit),1550}1551.into()1552}15531554/// Create a `minmax()` grid track.1555///1556/// <https://developer.mozilla.org/en-US/docs/Web/CSS/minmax>1557pub fn minmax<T: From<Self>>(min: MinTrackSizingFunction, max: MaxTrackSizingFunction) -> T {1558Self {1559min_sizing_function: min,1560max_sizing_function: max,1561}1562.into()1563}15641565/// Create a grid track with a percentage of the viewport's smaller dimension1566pub fn vmin<T: From<Self>>(value: f32) -> T {1567Self {1568min_sizing_function: MinTrackSizingFunction::VMin(value),1569max_sizing_function: MaxTrackSizingFunction::VMin(value),1570}1571.into()1572}15731574/// Create a grid track with a percentage of the viewport's larger dimension1575pub fn vmax<T: From<Self>>(value: f32) -> T {1576Self {1577min_sizing_function: MinTrackSizingFunction::VMax(value),1578max_sizing_function: MaxTrackSizingFunction::VMax(value),1579}1580.into()1581}15821583/// Create a grid track with a percentage of the viewport's height dimension1584pub fn vh<T: From<Self>>(value: f32) -> T {1585Self {1586min_sizing_function: MinTrackSizingFunction::Vh(value),1587max_sizing_function: MaxTrackSizingFunction::Vh(value),1588}1589.into()1590}15911592/// Create a grid track with a percentage of the viewport's width dimension1593pub fn vw<T: From<Self>>(value: f32) -> T {1594Self {1595min_sizing_function: MinTrackSizingFunction::Vw(value),1596max_sizing_function: MaxTrackSizingFunction::Vw(value),1597}1598.into()1599}1600}16011602impl Default for GridTrack {1603fn default() -> Self {1604Self::DEFAULT1605}1606}16071608#[derive(Copy, Clone, PartialEq, Debug, Reflect, From)]1609#[reflect(Default, PartialEq, Clone)]1610#[cfg_attr(1611feature = "serialize",1612derive(serde::Serialize, serde::Deserialize),1613reflect(Serialize, Deserialize)1614)]1615/// How many times to repeat a repeated grid track1616///1617/// <https://developer.mozilla.org/en-US/docs/Web/CSS/repeat>1618pub enum GridTrackRepetition {1619/// Repeat the track fixed number of times1620Count(u16),1621/// Repeat the track to fill available space1622///1623/// <https://developer.mozilla.org/en-US/docs/Web/CSS/repeat#auto-fill>1624AutoFill,1625/// Repeat the track to fill available space but collapse any tracks that do not end up with1626/// an item placed in them.1627///1628/// <https://developer.mozilla.org/en-US/docs/Web/CSS/repeat#auto-fit>1629AutoFit,1630}16311632impl Default for GridTrackRepetition {1633fn default() -> Self {1634Self::Count(1)1635}1636}16371638impl From<i32> for GridTrackRepetition {1639fn from(count: i32) -> Self {1640Self::Count(count as u16)1641}1642}16431644impl From<usize> for GridTrackRepetition {1645fn from(count: usize) -> Self {1646Self::Count(count as u16)1647}1648}16491650/// Represents a *possibly* repeated [`GridTrack`].1651///1652/// The repetition parameter can either be:1653/// - The integer `1`, in which case the track is non-repeated.1654/// - a `u16` count to repeat the track N times.1655/// - A `GridTrackRepetition::AutoFit` or `GridTrackRepetition::AutoFill`.1656///1657/// Note: that in the common case you want a non-repeating track (repetition count 1), you may use the constructor methods on [`GridTrack`]1658/// to create a `RepeatedGridTrack`. i.e. `GridTrack::px(10.0)` is equivalent to `RepeatedGridTrack::px(1, 10.0)`.1659///1660/// You may only use one auto-repetition per track list. And if your track list contains an auto repetition1661/// then all tracks (in and outside of the repetition) must be fixed size (px or percent). Integer repetitions are just shorthand for writing out1662/// N tracks longhand and are not subject to the same limitations.1663#[derive(Clone, PartialEq, Debug, Reflect)]1664#[reflect(Default, PartialEq, Clone)]1665#[cfg_attr(1666feature = "serialize",1667derive(serde::Serialize, serde::Deserialize),1668reflect(Serialize, Deserialize)1669)]1670pub struct RepeatedGridTrack {1671pub(crate) repetition: GridTrackRepetition,1672pub(crate) tracks: SmallVec<[GridTrack; 1]>,1673}16741675impl RepeatedGridTrack {1676/// Create a repeating set of grid tracks with a fixed pixel size1677pub fn px<T: From<Self>>(repetition: impl Into<GridTrackRepetition>, value: f32) -> T {1678Self {1679repetition: repetition.into(),1680tracks: SmallVec::from_buf([GridTrack::px(value)]),1681}1682.into()1683}16841685/// Create a repeating set of grid tracks with a percentage size1686pub fn percent<T: From<Self>>(repetition: impl Into<GridTrackRepetition>, value: f32) -> T {1687Self {1688repetition: repetition.into(),1689tracks: SmallVec::from_buf([GridTrack::percent(value)]),1690}1691.into()1692}16931694/// Create a repeating set of grid tracks with automatic size1695pub fn auto<T: From<Self>>(repetition: u16) -> T {1696Self {1697repetition: GridTrackRepetition::Count(repetition),1698tracks: SmallVec::from_buf([GridTrack::auto()]),1699}1700.into()1701}17021703/// Create a repeating set of grid tracks with an `fr` size.1704/// Note that this will give the track a content-based minimum size.1705/// Usually you are best off using `GridTrack::flex` instead which uses a zero minimum size.1706pub fn fr<T: From<Self>>(repetition: u16, value: f32) -> T {1707Self {1708repetition: GridTrackRepetition::Count(repetition),1709tracks: SmallVec::from_buf([GridTrack::fr(value)]),1710}1711.into()1712}17131714/// Create a repeating set of grid tracks with a `minmax(0, Nfr)` size.1715pub fn flex<T: From<Self>>(repetition: u16, value: f32) -> T {1716Self {1717repetition: GridTrackRepetition::Count(repetition),1718tracks: SmallVec::from_buf([GridTrack::flex(value)]),1719}1720.into()1721}17221723/// Create a repeating set of grid tracks with min-content size1724pub fn min_content<T: From<Self>>(repetition: u16) -> T {1725Self {1726repetition: GridTrackRepetition::Count(repetition),1727tracks: SmallVec::from_buf([GridTrack::min_content()]),1728}1729.into()1730}17311732/// Create a repeating set of grid tracks with max-content size1733pub fn max_content<T: From<Self>>(repetition: u16) -> T {1734Self {1735repetition: GridTrackRepetition::Count(repetition),1736tracks: SmallVec::from_buf([GridTrack::max_content()]),1737}1738.into()1739}17401741/// Create a repeating set of `fit-content()` grid tracks with fixed pixel limit1742pub fn fit_content_px<T: From<Self>>(repetition: u16, limit: f32) -> T {1743Self {1744repetition: GridTrackRepetition::Count(repetition),1745tracks: SmallVec::from_buf([GridTrack::fit_content_px(limit)]),1746}1747.into()1748}17491750/// Create a repeating set of `fit-content()` grid tracks with percentage limit1751pub fn fit_content_percent<T: From<Self>>(repetition: u16, limit: f32) -> T {1752Self {1753repetition: GridTrackRepetition::Count(repetition),1754tracks: SmallVec::from_buf([GridTrack::fit_content_percent(limit)]),1755}1756.into()1757}17581759/// Create a repeating set of `minmax()` grid track1760pub fn minmax<T: From<Self>>(1761repetition: impl Into<GridTrackRepetition>,1762min: MinTrackSizingFunction,1763max: MaxTrackSizingFunction,1764) -> T {1765Self {1766repetition: repetition.into(),1767tracks: SmallVec::from_buf([GridTrack::minmax(min, max)]),1768}1769.into()1770}17711772/// Create a repeating set of grid tracks with the percentage size of the viewport's smaller dimension1773pub fn vmin<T: From<Self>>(repetition: impl Into<GridTrackRepetition>, value: f32) -> T {1774Self {1775repetition: repetition.into(),1776tracks: SmallVec::from_buf([GridTrack::vmin(value)]),1777}1778.into()1779}17801781/// Create a repeating set of grid tracks with the percentage size of the viewport's larger dimension1782pub fn vmax<T: From<Self>>(repetition: impl Into<GridTrackRepetition>, value: f32) -> T {1783Self {1784repetition: repetition.into(),1785tracks: SmallVec::from_buf([GridTrack::vmax(value)]),1786}1787.into()1788}17891790/// Create a repeating set of grid tracks with the percentage size of the viewport's height dimension1791pub fn vh<T: From<Self>>(repetition: impl Into<GridTrackRepetition>, value: f32) -> T {1792Self {1793repetition: repetition.into(),1794tracks: SmallVec::from_buf([GridTrack::vh(value)]),1795}1796.into()1797}17981799/// Create a repeating set of grid tracks with the percentage size of the viewport's width dimension1800pub fn vw<T: From<Self>>(repetition: impl Into<GridTrackRepetition>, value: f32) -> T {1801Self {1802repetition: repetition.into(),1803tracks: SmallVec::from_buf([GridTrack::vw(value)]),1804}1805.into()1806}18071808/// Create a repetition of a set of tracks1809pub fn repeat_many<T: From<Self>>(1810repetition: impl Into<GridTrackRepetition>,1811tracks: impl Into<Vec<GridTrack>>,1812) -> T {1813Self {1814repetition: repetition.into(),1815tracks: SmallVec::from_vec(tracks.into()),1816}1817.into()1818}1819}18201821impl Default for RepeatedGridTrack {1822fn default() -> Self {1823Self {1824repetition: Default::default(),1825tracks: SmallVec::from_buf([GridTrack::default()]),1826}1827}1828}18291830impl From<GridTrack> for RepeatedGridTrack {1831fn from(track: GridTrack) -> Self {1832Self {1833repetition: GridTrackRepetition::Count(1),1834tracks: SmallVec::from_buf([track]),1835}1836}1837}18381839impl From<GridTrack> for Vec<GridTrack> {1840fn from(track: GridTrack) -> Self {1841vec![track]1842}1843}18441845impl From<GridTrack> for Vec<RepeatedGridTrack> {1846fn from(track: GridTrack) -> Self {1847vec![RepeatedGridTrack {1848repetition: GridTrackRepetition::Count(1),1849tracks: SmallVec::from_buf([track]),1850}]1851}1852}18531854impl From<RepeatedGridTrack> for Vec<RepeatedGridTrack> {1855fn from(track: RepeatedGridTrack) -> Self {1856vec![track]1857}1858}18591860#[derive(Copy, Clone, PartialEq, Eq, Debug, Reflect)]1861#[reflect(Default, PartialEq, Clone)]1862#[cfg_attr(1863feature = "serialize",1864derive(serde::Serialize, serde::Deserialize),1865reflect(Serialize, Deserialize)1866)]1867/// Represents the position of a grid item in a single axis.1868///1869/// There are 3 fields which may be set:1870/// - `start`: which grid line the item should start at1871/// - `end`: which grid line the item should end at1872/// - `span`: how many tracks the item should span1873///1874/// The default `span` is 1. If neither `start` or `end` is set then the item will be placed automatically.1875///1876/// Generally, at most two fields should be set. If all three fields are specified then `span` will be ignored. If `end` specifies an earlier1877/// grid line than `start` then `end` will be ignored and the item will have a span of 1.1878///1879/// <https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Grid_Layout/Line-based_Placement_with_CSS_Grid>1880pub struct GridPlacement {1881/// The grid line at which the item should start.1882/// Lines are 1-indexed.1883/// Negative indexes count backwards from the end of the grid.1884/// Zero is not a valid index.1885pub(crate) start: Option<NonZero<i16>>,1886/// How many grid tracks the item should span.1887/// Defaults to 1.1888pub(crate) span: Option<NonZero<u16>>,1889/// The grid line at which the item should end.1890/// Lines are 1-indexed.1891/// Negative indexes count backwards from the end of the grid.1892/// Zero is not a valid index.1893pub(crate) end: Option<NonZero<i16>>,1894}18951896impl GridPlacement {1897pub const DEFAULT: Self = Self {1898start: None,1899span: NonZero::<u16>::new(1),1900end: None,1901};19021903/// Place the grid item automatically (letting the `span` default to `1`).1904pub fn auto() -> Self {1905Self::DEFAULT1906}19071908/// Place the grid item automatically, specifying how many tracks it should `span`.1909///1910/// # Panics1911///1912/// Panics if `span` is `0`.1913pub fn span(span: u16) -> Self {1914Self {1915start: None,1916end: None,1917span: try_into_grid_span(span).expect("Invalid span value of 0."),1918}1919}19201921/// Place the grid item specifying the `start` grid line (letting the `span` default to `1`).1922///1923/// # Panics1924///1925/// Panics if `start` is `0`.1926pub fn start(start: i16) -> Self {1927Self {1928start: try_into_grid_index(start).expect("Invalid start value of 0."),1929..Self::DEFAULT1930}1931}19321933/// Place the grid item specifying the `end` grid line (letting the `span` default to `1`).1934///1935/// # Panics1936///1937/// Panics if `end` is `0`.1938pub fn end(end: i16) -> Self {1939Self {1940end: try_into_grid_index(end).expect("Invalid end value of 0."),1941..Self::DEFAULT1942}1943}19441945/// Place the grid item specifying the `start` grid line and how many tracks it should `span`.1946///1947/// # Panics1948///1949/// Panics if `start` or `span` is `0`.1950pub fn start_span(start: i16, span: u16) -> Self {1951Self {1952start: try_into_grid_index(start).expect("Invalid start value of 0."),1953end: None,1954span: try_into_grid_span(span).expect("Invalid span value of 0."),1955}1956}19571958/// Place the grid item specifying `start` and `end` grid lines (`span` will be inferred)1959///1960/// # Panics1961///1962/// Panics if `start` or `end` is `0`.1963pub fn start_end(start: i16, end: i16) -> Self {1964Self {1965start: try_into_grid_index(start).expect("Invalid start value of 0."),1966end: try_into_grid_index(end).expect("Invalid end value of 0."),1967span: None,1968}1969}19701971/// Place the grid item specifying the `end` grid line and how many tracks it should `span`.1972///1973/// # Panics1974///1975/// Panics if `end` or `span` is `0`.1976pub fn end_span(end: i16, span: u16) -> Self {1977Self {1978start: None,1979end: try_into_grid_index(end).expect("Invalid end value of 0."),1980span: try_into_grid_span(span).expect("Invalid span value of 0."),1981}1982}19831984/// Mutate the item, setting the `start` grid line1985///1986/// # Panics1987///1988/// Panics if `start` is `0`.1989pub fn set_start(mut self, start: i16) -> Self {1990self.start = try_into_grid_index(start).expect("Invalid start value of 0.");1991self1992}19931994/// Mutate the item, setting the `end` grid line1995///1996/// # Panics1997///1998/// Panics if `end` is `0`.1999pub fn set_end(mut self, end: i16) -> Self {2000self.end = try_into_grid_index(end).expect("Invalid end value of 0.");2001self2002}20032004/// Mutate the item, setting the number of tracks the item should `span`2005///2006/// # Panics2007///2008/// Panics if `span` is `0`.2009pub fn set_span(mut self, span: u16) -> Self {2010self.span = try_into_grid_span(span).expect("Invalid span value of 0.");2011self2012}20132014/// Returns the grid line at which the item should start, or `None` if not set.2015pub fn get_start(self) -> Option<i16> {2016self.start.map(NonZero::<i16>::get)2017}20182019/// Returns the grid line at which the item should end, or `None` if not set.2020pub fn get_end(self) -> Option<i16> {2021self.end.map(NonZero::<i16>::get)2022}20232024/// Returns span for this grid item, or `None` if not set.2025pub fn get_span(self) -> Option<u16> {2026self.span.map(NonZero::<u16>::get)2027}2028}20292030impl Default for GridPlacement {2031fn default() -> Self {2032Self::DEFAULT2033}2034}20352036/// Convert an `i16` to `NonZero<i16>`, fails on `0` and returns the `InvalidZeroIndex` error.2037fn try_into_grid_index(index: i16) -> Result<Option<NonZero<i16>>, GridPlacementError> {2038Ok(Some(2039NonZero::<i16>::new(index).ok_or(GridPlacementError::InvalidZeroIndex)?,2040))2041}20422043/// Convert a `u16` to `NonZero<u16>`, fails on `0` and returns the `InvalidZeroSpan` error.2044fn try_into_grid_span(span: u16) -> Result<Option<NonZero<u16>>, GridPlacementError> {2045Ok(Some(2046NonZero::<u16>::new(span).ok_or(GridPlacementError::InvalidZeroSpan)?,2047))2048}20492050/// Errors that occur when setting constraints for a `GridPlacement`2051#[derive(Debug, Eq, PartialEq, Clone, Copy, Error)]2052pub enum GridPlacementError {2053#[error("Zero is not a valid grid position")]2054InvalidZeroIndex,2055#[error("Spans cannot be zero length")]2056InvalidZeroSpan,2057}20582059/// The background color of the node2060///2061/// This serves as the "fill" color.2062#[derive(Component, Copy, Clone, Debug, PartialEq, Reflect)]2063#[reflect(Component, Default, Debug, PartialEq, Clone)]2064#[cfg_attr(2065feature = "serialize",2066derive(serde::Serialize, serde::Deserialize),2067reflect(Serialize, Deserialize)2068)]2069pub struct BackgroundColor(pub Color);20702071impl BackgroundColor {2072/// Background color is transparent by default.2073pub const DEFAULT: Self = Self(Color::NONE);2074}20752076impl Default for BackgroundColor {2077fn default() -> Self {2078Self::DEFAULT2079}2080}20812082impl<T: Into<Color>> From<T> for BackgroundColor {2083fn from(color: T) -> Self {2084Self(color.into())2085}2086}20872088/// The border color of the UI node.2089#[derive(Component, Copy, Clone, Debug, PartialEq, Reflect)]2090#[reflect(Component, Default, Debug, PartialEq, Clone)]2091#[cfg_attr(2092feature = "serialize",2093derive(serde::Serialize, serde::Deserialize),2094reflect(Serialize, Deserialize)2095)]2096pub struct BorderColor {2097pub top: Color,2098pub right: Color,2099pub bottom: Color,2100pub left: Color,2101}21022103impl<T: Into<Color>> From<T> for BorderColor {2104fn from(color: T) -> Self {2105Self::all(color.into())2106}2107}21082109impl BorderColor {2110/// Border color is transparent by default.2111pub const DEFAULT: Self = BorderColor {2112top: Color::NONE,2113right: Color::NONE,2114bottom: Color::NONE,2115left: Color::NONE,2116};21172118/// Helper to create a `BorderColor` struct with all borders set to the given color2119#[inline]2120pub fn all(color: impl Into<Color>) -> Self {2121let color = color.into();2122Self {2123top: color,2124bottom: color,2125left: color,2126right: color,2127}2128}21292130/// Helper to set all border colors to a given color.2131pub fn set_all(&mut self, color: impl Into<Color>) -> &mut Self {2132let color: Color = color.into();2133self.top = color;2134self.bottom = color;2135self.left = color;2136self.right = color;2137self2138}21392140/// Check if all contained border colors are transparent2141pub fn is_fully_transparent(&self) -> bool {2142self.top.is_fully_transparent()2143&& self.bottom.is_fully_transparent()2144&& self.left.is_fully_transparent()2145&& self.right.is_fully_transparent()2146}2147}21482149impl Default for BorderColor {2150fn default() -> Self {2151Self::DEFAULT2152}2153}21542155#[derive(Component, Copy, Clone, Default, Debug, PartialEq, Reflect)]2156#[reflect(Component, Default, Debug, PartialEq, Clone)]2157#[cfg_attr(2158feature = "serialize",2159derive(serde::Serialize, serde::Deserialize),2160reflect(Serialize, Deserialize)2161)]2162/// The [`Outline`] component adds an outline outside the edge of a UI node.2163/// Outlines do not take up space in the layout.2164///2165/// To add an [`Outline`] to a ui node you can spawn a `(Node, Outline)` tuple bundle:2166/// ```2167/// # use bevy_ecs::prelude::*;2168/// # use bevy_ui::prelude::*;2169/// # use bevy_color::palettes::basic::{RED, BLUE};2170/// fn setup_ui(mut commands: Commands) {2171/// commands.spawn((2172/// Node {2173/// width: Val::Px(100.),2174/// height: Val::Px(100.),2175/// ..Default::default()2176/// },2177/// BackgroundColor(BLUE.into()),2178/// Outline::new(Val::Px(10.), Val::ZERO, RED.into())2179/// ));2180/// }2181/// ```2182///2183/// [`Outline`] components can also be added later to existing UI nodes:2184/// ```2185/// # use bevy_ecs::prelude::*;2186/// # use bevy_ui::prelude::*;2187/// # use bevy_color::Color;2188/// fn outline_hovered_button_system(2189/// mut commands: Commands,2190/// mut node_query: Query<(Entity, &Interaction, Option<&mut Outline>), Changed<Interaction>>,2191/// ) {2192/// for (entity, interaction, mut maybe_outline) in node_query.iter_mut() {2193/// let outline_color =2194/// if matches!(*interaction, Interaction::Hovered) {2195/// Color::WHITE2196/// } else {2197/// Color::NONE2198/// };2199/// if let Some(mut outline) = maybe_outline {2200/// outline.color = outline_color;2201/// } else {2202/// commands.entity(entity).insert(Outline::new(Val::Px(10.), Val::ZERO, outline_color));2203/// }2204/// }2205/// }2206/// ```2207/// Inserting and removing an [`Outline`] component repeatedly will result in table moves, so it is generally preferable to2208/// set `Outline::color` to [`Color::NONE`] to hide an outline.2209pub struct Outline {2210/// The width of the outline.2211///2212/// Percentage `Val` values are resolved based on the width of the outlined [`Node`].2213pub width: Val,2214/// The amount of space between a node's outline the edge of the node.2215///2216/// Percentage `Val` values are resolved based on the width of the outlined [`Node`].2217pub offset: Val,2218/// The color of the outline.2219///2220/// If you are frequently toggling outlines for a UI node on and off it is recommended to set [`Color::NONE`] to hide the outline.2221/// This avoids the table moves that would occur from the repeated insertion and removal of the `Outline` component.2222pub color: Color,2223}22242225impl Outline {2226/// Create a new outline2227pub const fn new(width: Val, offset: Val, color: Color) -> Self {2228Self {2229width,2230offset,2231color,2232}2233}2234}22352236/// The calculated clip of the node2237#[derive(Component, Default, Copy, Clone, Debug, Reflect)]2238#[reflect(Component, Default, Debug, Clone)]2239pub struct CalculatedClip {2240/// The rect of the clip2241pub clip: Rect,2242}22432244/// UI node entities with this component will ignore any clipping rect they inherit,2245/// the node will not be clipped regardless of its ancestors' `Overflow` setting.2246#[derive(Component)]2247pub struct OverrideClip;22482249#[expect(2250rustdoc::redundant_explicit_links,2251reason = "To go around the `<code>` limitations, we put the link twice so we're \2252sure it's recognized as a markdown link."2253)]2254/// Indicates that this [`Node`] entity's front-to-back ordering is not controlled solely2255/// by its location in the UI hierarchy. A node with a higher z-index will appear on top2256/// of sibling nodes with a lower z-index.2257///2258/// UI nodes that have the same z-index will appear according to the order in which they2259/// appear in the UI hierarchy. In such a case, the last node to be added to its parent2260/// will appear in front of its siblings.2261///2262/// Nodes without this component will be treated as if they had a value of2263/// <code>[ZIndex][ZIndex]\(0\)</code>.2264///2265/// Use [`GlobalZIndex`] if you need to order separate UI hierarchies or nodes that are2266/// not siblings in a given UI hierarchy.2267#[derive(Component, Copy, Clone, Debug, Default, PartialEq, Eq, Reflect)]2268#[reflect(Component, Default, Debug, PartialEq, Clone)]2269pub struct ZIndex(pub i32);22702271/// `GlobalZIndex` allows a [`Node`] entity anywhere in the UI hierarchy to escape the implicit draw ordering of the UI's layout tree and2272/// be rendered above or below other UI nodes.2273/// Nodes with a `GlobalZIndex` of greater than 0 will be drawn on top of nodes without a `GlobalZIndex` or nodes with a lower `GlobalZIndex`.2274/// Nodes with a `GlobalZIndex` of less than 0 will be drawn below nodes without a `GlobalZIndex` or nodes with a greater `GlobalZIndex`.2275///2276/// If two Nodes have the same `GlobalZIndex`, the node with the greater [`ZIndex`] will be drawn on top.2277#[derive(Component, Copy, Clone, Debug, Default, PartialEq, Eq, Reflect)]2278#[reflect(Component, Default, Debug, PartialEq, Clone)]2279pub struct GlobalZIndex(pub i32);22802281/// Used to add rounded corners to a UI node. You can set a UI node to have uniformly2282/// rounded corners or specify different radii for each corner. If a given radius exceeds half2283/// the length of the smallest dimension between the node's height or width, the radius will2284/// calculated as half the smallest dimension.2285///2286/// Elliptical nodes are not supported yet. Percentage values are based on the node's smallest2287/// dimension, either width or height.2288///2289/// # Example2290/// ```rust2291/// # use bevy_ecs::prelude::*;2292/// # use bevy_ui::prelude::*;2293/// # use bevy_color::palettes::basic::{BLUE};2294/// fn setup_ui(mut commands: Commands) {2295/// commands.spawn((2296/// Node {2297/// width: Val::Px(100.),2298/// height: Val::Px(100.),2299/// border: UiRect::all(Val::Px(2.)),2300/// ..Default::default()2301/// },2302/// BackgroundColor(BLUE.into()),2303/// BorderRadius::new(2304/// // top left2305/// Val::Px(10.),2306/// // top right2307/// Val::Px(20.),2308/// // bottom right2309/// Val::Px(30.),2310/// // bottom left2311/// Val::Px(40.),2312/// ),2313/// ));2314/// }2315/// ```2316///2317/// <https://developer.mozilla.org/en-US/docs/Web/CSS/border-radius>2318#[derive(Component, Copy, Clone, Debug, PartialEq, Reflect)]2319#[reflect(Component, PartialEq, Default, Debug, Clone)]2320#[cfg_attr(2321feature = "serialize",2322derive(serde::Serialize, serde::Deserialize),2323reflect(Serialize, Deserialize)2324)]2325pub struct BorderRadius {2326pub top_left: Val,2327pub top_right: Val,2328pub bottom_right: Val,2329pub bottom_left: Val,2330}23312332impl Default for BorderRadius {2333fn default() -> Self {2334Self::DEFAULT2335}2336}23372338impl BorderRadius {2339pub const DEFAULT: Self = Self::ZERO;23402341/// Zero curvature. All the corners will be right-angled.2342pub const ZERO: Self = Self::all(Val::Px(0.));23432344/// Maximum curvature. The UI Node will take a capsule shape or circular if width and height are equal.2345pub const MAX: Self = Self::all(Val::Px(f32::MAX));23462347#[inline]2348/// Set all four corners to the same curvature.2349pub const fn all(radius: Val) -> Self {2350Self {2351top_left: radius,2352top_right: radius,2353bottom_left: radius,2354bottom_right: radius,2355}2356}23572358#[inline]2359pub const fn new(top_left: Val, top_right: Val, bottom_right: Val, bottom_left: Val) -> Self {2360Self {2361top_left,2362top_right,2363bottom_right,2364bottom_left,2365}2366}23672368#[inline]2369/// Sets the radii to logical pixel values.2370pub const fn px(top_left: f32, top_right: f32, bottom_right: f32, bottom_left: f32) -> Self {2371Self {2372top_left: Val::Px(top_left),2373top_right: Val::Px(top_right),2374bottom_right: Val::Px(bottom_right),2375bottom_left: Val::Px(bottom_left),2376}2377}23782379#[inline]2380/// Sets the radii to percentage values.2381pub const fn percent(2382top_left: f32,2383top_right: f32,2384bottom_right: f32,2385bottom_left: f32,2386) -> Self {2387Self {2388top_left: Val::Percent(top_left),2389top_right: Val::Percent(top_right),2390bottom_right: Val::Percent(bottom_right),2391bottom_left: Val::Percent(bottom_left),2392}2393}23942395#[inline]2396/// Sets the radius for the top left corner.2397/// Remaining corners will be right-angled.2398pub const fn top_left(radius: Val) -> Self {2399Self {2400top_left: radius,2401..Self::DEFAULT2402}2403}24042405#[inline]2406/// Sets the radius for the top right corner.2407/// Remaining corners will be right-angled.2408pub const fn top_right(radius: Val) -> Self {2409Self {2410top_right: radius,2411..Self::DEFAULT2412}2413}24142415#[inline]2416/// Sets the radius for the bottom right corner.2417/// Remaining corners will be right-angled.2418pub const fn bottom_right(radius: Val) -> Self {2419Self {2420bottom_right: radius,2421..Self::DEFAULT2422}2423}24242425#[inline]2426/// Sets the radius for the bottom left corner.2427/// Remaining corners will be right-angled.2428pub const fn bottom_left(radius: Val) -> Self {2429Self {2430bottom_left: radius,2431..Self::DEFAULT2432}2433}24342435#[inline]2436/// Sets the radii for the top left and bottom left corners.2437/// Remaining corners will be right-angled.2438pub const fn left(radius: Val) -> Self {2439Self {2440top_left: radius,2441bottom_left: radius,2442..Self::DEFAULT2443}2444}24452446#[inline]2447/// Sets the radii for the top right and bottom right corners.2448/// Remaining corners will be right-angled.2449pub const fn right(radius: Val) -> Self {2450Self {2451top_right: radius,2452bottom_right: radius,2453..Self::DEFAULT2454}2455}24562457#[inline]2458/// Sets the radii for the top left and top right corners.2459/// Remaining corners will be right-angled.2460pub const fn top(radius: Val) -> Self {2461Self {2462top_left: radius,2463top_right: radius,2464..Self::DEFAULT2465}2466}24672468#[inline]2469/// Sets the radii for the bottom left and bottom right corners.2470/// Remaining corners will be right-angled.2471pub const fn bottom(radius: Val) -> Self {2472Self {2473bottom_left: radius,2474bottom_right: radius,2475..Self::DEFAULT2476}2477}24782479/// Returns the [`BorderRadius`] with its `top_left` field set to the given value.2480#[inline]2481pub const fn with_top_left(mut self, radius: Val) -> Self {2482self.top_left = radius;2483self2484}24852486/// Returns the [`BorderRadius`] with its `top_right` field set to the given value.2487#[inline]2488pub const fn with_top_right(mut self, radius: Val) -> Self {2489self.top_right = radius;2490self2491}24922493/// Returns the [`BorderRadius`] with its `bottom_right` field set to the given value.2494#[inline]2495pub const fn with_bottom_right(mut self, radius: Val) -> Self {2496self.bottom_right = radius;2497self2498}24992500/// Returns the [`BorderRadius`] with its `bottom_left` field set to the given value.2501#[inline]2502pub const fn with_bottom_left(mut self, radius: Val) -> Self {2503self.bottom_left = radius;2504self2505}25062507/// Returns the [`BorderRadius`] with its `top_left` and `bottom_left` fields set to the given value.2508#[inline]2509pub const fn with_left(mut self, radius: Val) -> Self {2510self.top_left = radius;2511self.bottom_left = radius;2512self2513}25142515/// Returns the [`BorderRadius`] with its `top_right` and `bottom_right` fields set to the given value.2516#[inline]2517pub const fn with_right(mut self, radius: Val) -> Self {2518self.top_right = radius;2519self.bottom_right = radius;2520self2521}25222523/// Returns the [`BorderRadius`] with its `top_left` and `top_right` fields set to the given value.2524#[inline]2525pub const fn with_top(mut self, radius: Val) -> Self {2526self.top_left = radius;2527self.top_right = radius;2528self2529}25302531/// Returns the [`BorderRadius`] with its `bottom_left` and `bottom_right` fields set to the given value.2532#[inline]2533pub const fn with_bottom(mut self, radius: Val) -> Self {2534self.bottom_left = radius;2535self.bottom_right = radius;2536self2537}25382539/// Resolve the border radius for a single corner from the given context values.2540/// Returns the radius of the corner in physical pixels.2541pub const fn resolve_single_corner(2542radius: Val,2543scale_factor: f32,2544min_length: f32,2545viewport_size: Vec2,2546) -> f32 {2547if let Ok(radius) = radius.resolve(scale_factor, min_length, viewport_size) {2548radius.clamp(0., 0.5 * min_length)2549} else {25500.2551}2552}25532554/// Resolve the border radii for the corners from the given context values.2555/// Returns the radii of the each corner in physical pixels.2556pub const fn resolve(2557&self,2558scale_factor: f32,2559node_size: Vec2,2560viewport_size: Vec2,2561) -> ResolvedBorderRadius {2562let length = node_size.x.min(node_size.y);2563ResolvedBorderRadius {2564top_left: Self::resolve_single_corner(2565self.top_left,2566scale_factor,2567length,2568viewport_size,2569),2570top_right: Self::resolve_single_corner(2571self.top_right,2572scale_factor,2573length,2574viewport_size,2575),2576bottom_left: Self::resolve_single_corner(2577self.bottom_left,2578scale_factor,2579length,2580viewport_size,2581),2582bottom_right: Self::resolve_single_corner(2583self.bottom_right,2584scale_factor,2585length,2586viewport_size,2587),2588}2589}2590}25912592/// Represents the resolved border radius values for a UI node.2593///2594/// The values are in physical pixels.2595#[derive(Copy, Clone, Debug, Default, PartialEq, Reflect)]2596#[reflect(Clone, PartialEq, Default)]2597pub struct ResolvedBorderRadius {2598pub top_left: f32,2599pub top_right: f32,2600pub bottom_right: f32,2601pub bottom_left: f32,2602}26032604impl ResolvedBorderRadius {2605pub const ZERO: Self = Self {2606top_left: 0.,2607top_right: 0.,2608bottom_right: 0.,2609bottom_left: 0.,2610};2611}26122613impl From<ResolvedBorderRadius> for [f32; 4] {2614fn from(radius: ResolvedBorderRadius) -> Self {2615[2616radius.top_left,2617radius.top_right,2618radius.bottom_right,2619radius.bottom_left,2620]2621}2622}26232624#[derive(Component, Clone, Debug, Default, PartialEq, Reflect, Deref, DerefMut)]2625#[reflect(Component, PartialEq, Default, Clone)]2626#[cfg_attr(2627feature = "serialize",2628derive(serde::Serialize, serde::Deserialize),2629reflect(Serialize, Deserialize)2630)]2631/// List of shadows to draw for a [`Node`].2632///2633/// Draw order is determined implicitly from the vector of [`ShadowStyle`]s, back-to-front.2634pub struct BoxShadow(pub Vec<ShadowStyle>);26352636impl BoxShadow {2637/// A single drop shadow2638pub fn new(2639color: Color,2640x_offset: Val,2641y_offset: Val,2642spread_radius: Val,2643blur_radius: Val,2644) -> Self {2645Self(vec![ShadowStyle {2646color,2647x_offset,2648y_offset,2649spread_radius,2650blur_radius,2651}])2652}2653}26542655impl From<ShadowStyle> for BoxShadow {2656fn from(value: ShadowStyle) -> Self {2657Self(vec![value])2658}2659}26602661#[derive(Copy, Clone, Debug, PartialEq, Reflect)]2662#[reflect(PartialEq, Default, Clone)]2663#[cfg_attr(2664feature = "serialize",2665derive(serde::Serialize, serde::Deserialize),2666reflect(Serialize, Deserialize)2667)]2668pub struct ShadowStyle {2669/// The shadow's color2670pub color: Color,2671/// Horizontal offset2672pub x_offset: Val,2673/// Vertical offset2674pub y_offset: Val,2675/// How much the shadow should spread outward.2676///2677/// Negative values will make the shadow shrink inwards.2678/// Percentage values are based on the width of the UI node.2679pub spread_radius: Val,2680/// Blurriness of the shadow2681pub blur_radius: Val,2682}26832684impl Default for ShadowStyle {2685fn default() -> Self {2686Self {2687color: Color::BLACK,2688x_offset: Val::Percent(20.),2689y_offset: Val::Percent(20.),2690spread_radius: Val::ZERO,2691blur_radius: Val::Percent(10.),2692}2693}2694}26952696#[derive(Component, Copy, Clone, Debug, PartialEq, Reflect)]2697#[reflect(Component, Debug, PartialEq, Default, Clone)]2698#[cfg_attr(2699feature = "serialize",2700derive(serde::Serialize, serde::Deserialize),2701reflect(Serialize, Deserialize)2702)]2703/// This component can be added to any UI node to modify its layout behavior.2704pub struct LayoutConfig {2705/// If set to true the coordinates for this node and its descendents will be rounded to the nearest physical pixel.2706/// This can help prevent visual artifacts like blurry images or semi-transparent edges that can occur with sub-pixel positioning.2707///2708/// Defaults to true.2709pub use_rounding: bool,2710}27112712impl Default for LayoutConfig {2713fn default() -> Self {2714Self { use_rounding: true }2715}2716}27172718/// Indicates that this root [`Node`] entity should be rendered to a specific camera.2719///2720/// UI then will be laid out respecting the camera's viewport and scale factor, and2721/// rendered to this camera's [`bevy_camera::RenderTarget`].2722///2723/// Setting this component on a non-root node will have no effect. It will be overridden2724/// by the root node's component.2725///2726/// Root node's without an explicit [`UiTargetCamera`] will be rendered to the default UI camera,2727/// which is either a single camera with the [`IsDefaultUiCamera`] marker component or the highest2728/// order camera targeting the primary window.2729#[derive(Component, Clone, Debug, Reflect, Eq, PartialEq)]2730#[reflect(Component, Debug, PartialEq, Clone)]2731pub struct UiTargetCamera(pub Entity);27322733impl UiTargetCamera {2734pub fn entity(&self) -> Entity {2735self.02736}2737}27382739/// Marker used to identify default cameras, they will have priority over the [`PrimaryWindow`] camera.2740///2741/// This is useful if the [`PrimaryWindow`] has two cameras, one of them used2742/// just for debug purposes and the user wants a way to choose the default [`Camera`]2743/// without having to add a [`UiTargetCamera`] to the root node.2744///2745/// Another use is when the user wants the Ui to be in another window by default,2746/// all that is needed is to place this component on the camera2747///2748/// ```2749/// # use bevy_ui::prelude::*;2750/// # use bevy_ecs::prelude::Commands;2751/// # use bevy_camera::{Camera, Camera2d, RenderTarget};2752/// # use bevy_window::{Window, WindowRef};2753///2754/// fn spawn_camera(mut commands: Commands) {2755/// let another_window = commands.spawn(Window {2756/// title: String::from("Another window"),2757/// ..Default::default()2758/// }).id();2759/// commands.spawn((2760/// Camera2d,2761/// Camera {2762/// target: RenderTarget::Window(WindowRef::Entity(another_window)),2763/// ..Default::default()2764/// },2765/// // We add the Marker here so all Ui will spawn in2766/// // another window if no UiTargetCamera is specified2767/// IsDefaultUiCamera2768/// ));2769/// }2770/// ```2771#[derive(Component, Default)]2772pub struct IsDefaultUiCamera;27732774#[derive(SystemParam)]2775pub struct DefaultUiCamera<'w, 's> {2776cameras: Query<'w, 's, (Entity, &'static Camera)>,2777default_cameras: Query<'w, 's, Entity, (With<Camera>, With<IsDefaultUiCamera>)>,2778primary_window: Query<'w, 's, Entity, With<PrimaryWindow>>,2779}27802781impl<'w, 's> DefaultUiCamera<'w, 's> {2782pub fn get(&self) -> Option<Entity> {2783self.default_cameras.single().ok().or_else(|| {2784// If there isn't a single camera and the query isn't empty, there is two or more cameras queried.2785if !self.default_cameras.is_empty() {2786once!(warn!("Two or more Entities with IsDefaultUiCamera found when only one Camera with this marker is allowed."));2787}2788self.cameras2789.iter()2790.filter(|(_, c)| match c.target {2791RenderTarget::Window(WindowRef::Primary) => true,2792RenderTarget::Window(WindowRef::Entity(w)) => {2793self.primary_window.get(w).is_ok()2794}2795_ => false,2796})2797.max_by_key(|(e, c)| (c.order, *e))2798.map(|(e, _)| e)2799})2800}2801}28022803/// Derived information about the camera target for this UI node.2804///2805/// Updated in [`UiSystems::Prepare`](crate::UiSystems::Prepare) by [`propagate_ui_target_cameras`](crate::update::propagate_ui_target_cameras)2806#[derive(Component, Clone, Copy, Debug, Reflect, PartialEq)]2807#[reflect(Component, Default, PartialEq, Clone)]2808pub struct ComputedUiTargetCamera {2809pub(crate) camera: Entity,2810}28112812impl Default for ComputedUiTargetCamera {2813fn default() -> Self {2814Self {2815camera: Entity::PLACEHOLDER,2816}2817}2818}28192820impl ComputedUiTargetCamera {2821/// Returns the id of the target camera for this UI node.2822pub fn get(&self) -> Option<Entity> {2823Some(self.camera).filter(|&entity| entity != Entity::PLACEHOLDER)2824}2825}28262827/// Derived information about the render target for this UI node.2828#[derive(Component, Clone, Copy, Debug, Reflect, PartialEq)]2829#[reflect(Component, Default, PartialEq, Clone)]2830pub struct ComputedUiRenderTargetInfo {2831/// The scale factor of the target camera's render target.2832pub(crate) scale_factor: f32,2833/// The size of the target camera's viewport in physical pixels.2834pub(crate) physical_size: UVec2,2835}28362837impl Default for ComputedUiRenderTargetInfo {2838fn default() -> Self {2839Self {2840scale_factor: 1.,2841physical_size: UVec2::ZERO,2842}2843}2844}28452846impl ComputedUiRenderTargetInfo {2847pub const fn scale_factor(&self) -> f32 {2848self.scale_factor2849}28502851/// Returns the size of the target camera's viewport in physical pixels.2852pub const fn physical_size(&self) -> UVec2 {2853self.physical_size2854}28552856/// Returns the size of the target camera's viewport in logical pixels.2857pub fn logical_size(&self) -> Vec2 {2858self.physical_size.as_vec2() / self.scale_factor2859}2860}28612862#[cfg(test)]2863mod tests {2864use crate::GridPlacement;28652866#[test]2867fn invalid_grid_placement_values() {2868assert!(std::panic::catch_unwind(|| GridPlacement::span(0)).is_err());2869assert!(std::panic::catch_unwind(|| GridPlacement::start(0)).is_err());2870assert!(std::panic::catch_unwind(|| GridPlacement::end(0)).is_err());2871assert!(std::panic::catch_unwind(|| GridPlacement::start_end(0, 1)).is_err());2872assert!(std::panic::catch_unwind(|| GridPlacement::start_end(-1, 0)).is_err());2873assert!(std::panic::catch_unwind(|| GridPlacement::start_span(1, 0)).is_err());2874assert!(std::panic::catch_unwind(|| GridPlacement::start_span(0, 1)).is_err());2875assert!(std::panic::catch_unwind(|| GridPlacement::end_span(0, 1)).is_err());2876assert!(std::panic::catch_unwind(|| GridPlacement::end_span(1, 0)).is_err());2877assert!(std::panic::catch_unwind(|| GridPlacement::default().set_start(0)).is_err());2878assert!(std::panic::catch_unwind(|| GridPlacement::default().set_end(0)).is_err());2879assert!(std::panic::catch_unwind(|| GridPlacement::default().set_span(0)).is_err());2880}28812882#[test]2883fn grid_placement_accessors() {2884assert_eq!(GridPlacement::start(5).get_start(), Some(5));2885assert_eq!(GridPlacement::end(-4).get_end(), Some(-4));2886assert_eq!(GridPlacement::span(2).get_span(), Some(2));2887assert_eq!(GridPlacement::start_end(11, 21).get_span(), None);2888assert_eq!(GridPlacement::start_span(3, 5).get_end(), None);2889assert_eq!(GridPlacement::end_span(-4, 12).get_start(), None);2890}2891}289228932894