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 const fn content_inset(&self) -> BorderRect {224BorderRect {225left: self.border.left + self.padding.left,226right: self.border.right + self.padding.right,227top: self.border.top + self.padding.top,228bottom: self.border.bottom + self.padding.bottom,229}230}231232/// Returns the inverse of the scale factor for this node.233/// To convert from physical coordinates to logical coordinates multiply by this value.234#[inline]235pub const fn inverse_scale_factor(&self) -> f32 {236self.inverse_scale_factor237}238239// Returns true if `point` within the node.240//241// Matches the sdf function in `ui.wgsl` that is used by the UI renderer to draw rounded rectangles.242pub fn contains_point(&self, transform: UiGlobalTransform, point: Vec2) -> bool {243let Some(local_point) = transform244.try_inverse()245.map(|transform| transform.transform_point2(point))246else {247return false;248};249let [top, bottom] = if local_point.x < 0. {250[self.border_radius.top_left, self.border_radius.bottom_left]251} else {252[253self.border_radius.top_right,254self.border_radius.bottom_right,255]256};257let r = if local_point.y < 0. { top } else { bottom };258let corner_to_point = local_point.abs() - 0.5 * self.size;259let q = corner_to_point + r;260let l = q.max(Vec2::ZERO).length();261let m = q.max_element().min(0.);262l + m - r < 0.263}264265/// Transform a point to normalized node space with the center of the node at the origin and the corners at [+/-0.5, +/-0.5]266pub fn normalize_point(&self, transform: UiGlobalTransform, point: Vec2) -> Option<Vec2> {267self.size268.cmpgt(Vec2::ZERO)269.all()270.then(|| transform.try_inverse())271.flatten()272.map(|transform| transform.transform_point2(point) / self.size)273}274275/// Resolve the node's clipping rect in local space276pub fn resolve_clip_rect(277&self,278overflow: Overflow,279overflow_clip_margin: OverflowClipMargin,280) -> Rect {281let mut clip_rect = Rect::from_center_size(Vec2::ZERO, self.size);282283let clip_inset = match overflow_clip_margin.visual_box {284OverflowClipBox::BorderBox => BorderRect::ZERO,285OverflowClipBox::ContentBox => self.content_inset(),286OverflowClipBox::PaddingBox => self.border(),287};288289clip_rect.min.x += clip_inset.left;290clip_rect.min.y += clip_inset.top;291clip_rect.max.x -= clip_inset.right;292clip_rect.max.y -= clip_inset.bottom;293294if overflow.x == OverflowAxis::Visible {295clip_rect.min.x = -f32::INFINITY;296clip_rect.max.x = f32::INFINITY;297}298if overflow.y == OverflowAxis::Visible {299clip_rect.min.y = -f32::INFINITY;300clip_rect.max.y = f32::INFINITY;301}302303clip_rect304}305}306307impl ComputedNode {308pub const DEFAULT: Self = Self {309stack_index: 0,310size: Vec2::ZERO,311content_size: Vec2::ZERO,312scrollbar_size: Vec2::ZERO,313scroll_position: Vec2::ZERO,314outline_width: 0.,315outline_offset: 0.,316unrounded_size: Vec2::ZERO,317border_radius: ResolvedBorderRadius::ZERO,318border: BorderRect::ZERO,319padding: BorderRect::ZERO,320inverse_scale_factor: 1.,321};322}323324impl Default for ComputedNode {325fn default() -> Self {326Self::DEFAULT327}328}329330/// The scroll position of the node. Values are in logical pixels, increasing from top-left to bottom-right.331///332/// 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.333/// This might seem backwards, however what's really happening is that334/// the scroll position is moving the visible "window" in the local coordinate system of the scrolled content -335/// moving the window down causes the content to move up.336///337/// Updating the values of `ScrollPosition` will reposition the children of the node by the offset amount in logical pixels.338/// `ScrollPosition` may be updated by the layout system when a layout change makes a previously valid `ScrollPosition` invalid.339/// Changing this does nothing on a `Node` without setting at least one `OverflowAxis` to `OverflowAxis::Scroll`.340#[derive(Component, Debug, Clone, Default, Deref, DerefMut, Reflect)]341#[reflect(Component, Default, Clone)]342pub struct ScrollPosition(pub Vec2);343344impl ScrollPosition {345pub const DEFAULT: Self = Self(Vec2::ZERO);346}347348impl From<Vec2> for ScrollPosition {349fn from(value: Vec2) -> Self {350Self(value)351}352}353354/// The base component for UI entities. It describes UI layout and style properties.355///356/// When defining new types of UI entities, require [`Node`] to make them behave like UI nodes.357///358/// Nodes can be laid out using either Flexbox or CSS Grid Layout.359///360/// See below for general learning resources and for documentation on the individual style properties.361///362/// ### Flexbox363///364/// - [MDN: Basic Concepts of Flexbox](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Flexible_Box_Layout/Basic_Concepts_of_Flexbox)365/// - [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.366/// - [Flexbox Froggy](https://flexboxfroggy.com/). An interactive tutorial/game that teaches the essential parts of Flexbox in a fun engaging way.367///368/// ### CSS Grid369///370/// - [MDN: Basic Concepts of Grid Layout](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Grid_Layout/Basic_Concepts_of_Grid_Layout)371/// - [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.372/// - [CSS Grid Garden](https://cssgridgarden.com/). An interactive tutorial/game that teaches the essential parts of CSS Grid in a fun engaging way.373///374/// # See also375///376/// - [`RelativeCursorPosition`](crate::RelativeCursorPosition) to obtain the cursor position relative to this node377/// - [`Interaction`](crate::Interaction) to obtain the interaction state of this node378379#[derive(Component, Clone, PartialEq, Debug, Reflect)]380#[require(381ComputedNode,382ComputedUiTargetCamera,383ComputedUiRenderTargetInfo,384UiTransform,385BackgroundColor,386BorderColor,387BorderRadius,388FocusPolicy,389ScrollPosition,390Visibility,391ZIndex392)]393#[reflect(Component, Default, PartialEq, Debug, Clone)]394#[cfg_attr(395feature = "serialize",396derive(serde::Serialize, serde::Deserialize),397reflect(Serialize, Deserialize)398)]399pub struct Node {400/// Which layout algorithm to use when laying out this node's contents:401/// - [`Display::Flex`]: Use the Flexbox layout algorithm402/// - [`Display::Grid`]: Use the CSS Grid layout algorithm403/// - [`Display::None`]: Hide this node and perform layout as if it does not exist.404///405/// <https://developer.mozilla.org/en-US/docs/Web/CSS/display>406pub display: Display,407408/// Which part of a Node's box length styles like width and height control409/// - [`BoxSizing::BorderBox`]: They refer to the "border box" size (size including padding and border)410/// - [`BoxSizing::ContentBox`]: They refer to the "content box" size (size excluding padding and border)411///412/// `BoxSizing::BorderBox` is generally considered more intuitive and is the default in Bevy even though it is not on the web.413///414/// See: <https://developer.mozilla.org/en-US/docs/Web/CSS/box-sizing>415pub box_sizing: BoxSizing,416417/// Whether a node should be laid out in-flow with, or independently of its siblings:418/// - [`PositionType::Relative`]: Layout this node in-flow with other nodes using the usual (flexbox/grid) layout algorithm.419/// - [`PositionType::Absolute`]: Layout this node on top and independently of other nodes.420///421/// <https://developer.mozilla.org/en-US/docs/Web/CSS/position>422pub position_type: PositionType,423424/// Whether overflowing content should be displayed or clipped.425///426/// <https://developer.mozilla.org/en-US/docs/Web/CSS/overflow>427pub overflow: Overflow,428429/// How much space in logical pixels should be reserved for scrollbars when overflow is set to scroll or auto on an axis.430pub scrollbar_width: f32,431432/// How the bounds of clipped content should be determined433///434/// <https://developer.mozilla.org/en-US/docs/Web/CSS/overflow-clip-margin>435pub overflow_clip_margin: OverflowClipMargin,436437/// The horizontal position of the left edge of the node.438/// - For relatively positioned nodes, this is relative to the node's position as computed during regular layout.439/// - For absolutely positioned nodes, this is relative to the *parent* node's bounding box.440///441/// <https://developer.mozilla.org/en-US/docs/Web/CSS/left>442pub left: Val,443444/// The horizontal position of the right edge of the node.445/// - For relatively positioned nodes, this is relative to the node's position as computed during regular layout.446/// - For absolutely positioned nodes, this is relative to the *parent* node's bounding box.447///448/// <https://developer.mozilla.org/en-US/docs/Web/CSS/right>449pub right: Val,450451/// The vertical position of the top edge of the node.452/// - For relatively positioned nodes, this is relative to the node's position as computed during regular layout.453/// - For absolutely positioned nodes, this is relative to the *parent* node's bounding box.454///455/// <https://developer.mozilla.org/en-US/docs/Web/CSS/top>456pub top: Val,457458/// The vertical position of the bottom edge of the node.459/// - For relatively positioned nodes, this is relative to the node's position as computed during regular layout.460/// - For absolutely positioned nodes, this is relative to the *parent* node's bounding box.461///462/// <https://developer.mozilla.org/en-US/docs/Web/CSS/bottom>463pub bottom: Val,464465/// The ideal width of the node. `width` is used when it is within the bounds defined by `min_width` and `max_width`.466///467/// <https://developer.mozilla.org/en-US/docs/Web/CSS/width>468pub width: Val,469470/// The ideal height of the node. `height` is used when it is within the bounds defined by `min_height` and `max_height`.471///472/// <https://developer.mozilla.org/en-US/docs/Web/CSS/height>473pub height: Val,474475/// The minimum width of the node. `min_width` is used if it is greater than `width` and/or `max_width`.476///477/// <https://developer.mozilla.org/en-US/docs/Web/CSS/min-width>478pub min_width: Val,479480/// The minimum height of the node. `min_height` is used if it is greater than `height` and/or `max_height`.481///482/// <https://developer.mozilla.org/en-US/docs/Web/CSS/min-height>483pub min_height: Val,484485/// The maximum width of the node. `max_width` is used if it is within the bounds defined by `min_width` and `width`.486///487/// <https://developer.mozilla.org/en-US/docs/Web/CSS/max-width>488pub max_width: Val,489490/// The maximum height of the node. `max_height` is used if it is within the bounds defined by `min_height` and `height`.491///492/// <https://developer.mozilla.org/en-US/docs/Web/CSS/max-height>493pub max_height: Val,494495/// The aspect ratio of the node (defined as `width / height`)496///497/// <https://developer.mozilla.org/en-US/docs/Web/CSS/aspect-ratio>498pub aspect_ratio: Option<f32>,499500/// Used to control how each individual item is aligned by default within the space they're given.501/// - For Flexbox containers, sets default cross axis alignment of the child items.502/// - For CSS Grid containers, controls block (vertical) axis alignment of children of this grid container within their grid areas.503///504/// This value is overridden if [`AlignSelf`] on the child node is set.505///506/// <https://developer.mozilla.org/en-US/docs/Web/CSS/align-items>507pub align_items: AlignItems,508509/// Used to control how each individual item is aligned by default within the space they're given.510/// - For Flexbox containers, this property has no effect. See `justify_content` for main axis alignment of flex items.511/// - For CSS Grid containers, sets default inline (horizontal) axis alignment of child items within their grid areas.512///513/// This value is overridden if [`JustifySelf`] on the child node is set.514///515/// <https://developer.mozilla.org/en-US/docs/Web/CSS/justify-items>516pub justify_items: JustifyItems,517518/// Used to control how the specified item is aligned within the space it's given.519/// - For Flexbox items, controls cross axis alignment of the item.520/// - For CSS Grid items, controls block (vertical) axis alignment of a grid item within its grid area.521///522/// If set to `Auto`, alignment is inherited from the value of [`AlignItems`] set on the parent node.523///524/// <https://developer.mozilla.org/en-US/docs/Web/CSS/align-self>525pub align_self: AlignSelf,526527/// Used to control how the specified item is aligned within the space it's given.528/// - For Flexbox items, this property has no effect. See `justify_content` for main axis alignment of flex items.529/// - For CSS Grid items, controls inline (horizontal) axis alignment of a grid item within its grid area.530///531/// If set to `Auto`, alignment is inherited from the value of [`JustifyItems`] set on the parent node.532///533/// <https://developer.mozilla.org/en-US/docs/Web/CSS/justify-self>534pub justify_self: JustifySelf,535536/// Used to control how items are distributed.537/// - For Flexbox containers, controls alignment of lines if `flex_wrap` is set to [`FlexWrap::Wrap`] and there are multiple lines of items.538/// - For CSS Grid containers, controls alignment of grid rows.539///540/// <https://developer.mozilla.org/en-US/docs/Web/CSS/align-content>541pub align_content: AlignContent,542543/// Used to control how items are distributed.544/// - For Flexbox containers, controls alignment of items in the main axis.545/// - For CSS Grid containers, controls alignment of grid columns.546///547/// <https://developer.mozilla.org/en-US/docs/Web/CSS/justify-content>548pub justify_content: JustifyContent,549550/// The amount of space around a node outside its border.551///552/// If a percentage value is used, the percentage is calculated based on the width of the parent node.553///554/// # Example555/// ```556/// # use bevy_ui::{Node, UiRect, Val};557/// let node = Node {558/// margin: UiRect {559/// left: Val::Percent(10.),560/// right: Val::Percent(10.),561/// top: Val::Percent(15.),562/// bottom: Val::Percent(15.)563/// },564/// ..Default::default()565/// };566/// ```567/// 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.568///569/// <https://developer.mozilla.org/en-US/docs/Web/CSS/margin>570pub margin: UiRect,571572/// The amount of space between the edges of a node and its contents.573///574/// If a percentage value is used, the percentage is calculated based on the width of the parent node.575///576/// # Example577/// ```578/// # use bevy_ui::{Node, UiRect, Val};579/// let node = Node {580/// padding: UiRect {581/// left: Val::Percent(1.),582/// right: Val::Percent(2.),583/// top: Val::Percent(3.),584/// bottom: Val::Percent(4.)585/// },586/// ..Default::default()587/// };588/// ```589/// 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.590///591/// <https://developer.mozilla.org/en-US/docs/Web/CSS/padding>592pub padding: UiRect,593594/// The amount of space between the margins of a node and its padding.595///596/// If a percentage value is used, the percentage is calculated based on the width of the parent node.597///598/// 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.599///600/// <https://developer.mozilla.org/en-US/docs/Web/CSS/border-width>601pub border: UiRect,602603/// Whether a Flexbox container should be a row or a column. This property has no effect on Grid nodes.604///605/// <https://developer.mozilla.org/en-US/docs/Web/CSS/flex-direction>606pub flex_direction: FlexDirection,607608/// Whether a Flexbox container should wrap its contents onto multiple lines if they overflow. This property has no effect on Grid nodes.609///610/// <https://developer.mozilla.org/en-US/docs/Web/CSS/flex-wrap>611pub flex_wrap: FlexWrap,612613/// Defines how much a flexbox item should grow if there's space available. Defaults to 0 (don't grow at all).614///615/// <https://developer.mozilla.org/en-US/docs/Web/CSS/flex-grow>616pub flex_grow: f32,617618/// Defines how much a flexbox item should shrink if there's not enough space available. Defaults to 1.619///620/// <https://developer.mozilla.org/en-US/docs/Web/CSS/flex-shrink>621pub flex_shrink: f32,622623/// The initial length of a flexbox in the main axis, before flex growing/shrinking properties are applied.624///625/// `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`.626///627/// <https://developer.mozilla.org/en-US/docs/Web/CSS/flex-basis>628pub flex_basis: Val,629630/// The size of the gutters between items in a vertical flexbox layout or between rows in a grid layout.631///632/// Note: Values of `Val::Auto` are not valid and are treated as zero.633///634/// <https://developer.mozilla.org/en-US/docs/Web/CSS/row-gap>635pub row_gap: Val,636637/// The size of the gutters between items in a horizontal flexbox layout or between column in a grid layout.638///639/// Note: Values of `Val::Auto` are not valid and are treated as zero.640///641/// <https://developer.mozilla.org/en-US/docs/Web/CSS/column-gap>642pub column_gap: Val,643644/// 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.645/// Only affects Grid layouts.646///647/// <https://developer.mozilla.org/en-US/docs/Web/CSS/grid-auto-flow>648pub grid_auto_flow: GridAutoFlow,649650/// Defines the number of rows a grid has and the sizes of those rows. If grid items are given explicit placements then more rows may651/// be implicitly generated by items that are placed out of bounds. The sizes of those rows are controlled by `grid_auto_rows` property.652///653/// <https://developer.mozilla.org/en-US/docs/Web/CSS/grid-template-rows>654pub grid_template_rows: Vec<RepeatedGridTrack>,655656/// Defines the number of columns a grid has and the sizes of those columns. If grid items are given explicit placements then more columns may657/// be implicitly generated by items that are placed out of bounds. The sizes of those columns are controlled by `grid_auto_columns` property.658///659/// <https://developer.mozilla.org/en-US/docs/Web/CSS/grid-template-columns>660pub grid_template_columns: Vec<RepeatedGridTrack>,661662/// Defines the size of implicitly created rows. Rows are created implicitly when grid items are given explicit placements that are out of bounds663/// of the rows explicitly created using `grid_template_rows`.664///665/// <https://developer.mozilla.org/en-US/docs/Web/CSS/grid-auto-rows>666pub grid_auto_rows: Vec<GridTrack>,667/// Defines the size of implicitly created columns. Columns are created implicitly when grid items are given explicit placements that are out of bounds668/// of the columns explicitly created using `grid_template_columns`.669///670/// <https://developer.mozilla.org/en-US/docs/Web/CSS/grid-auto-columns>671pub grid_auto_columns: Vec<GridTrack>,672673/// The row in which a grid item starts and how many rows it spans.674///675/// <https://developer.mozilla.org/en-US/docs/Web/CSS/grid-row>676pub grid_row: GridPlacement,677678/// The column in which a grid item starts and how many columns it spans.679///680/// <https://developer.mozilla.org/en-US/docs/Web/CSS/grid-column>681pub grid_column: GridPlacement,682}683684impl Node {685pub const DEFAULT: Self = Self {686display: Display::DEFAULT,687box_sizing: BoxSizing::DEFAULT,688position_type: PositionType::DEFAULT,689left: Val::Auto,690right: Val::Auto,691top: Val::Auto,692bottom: Val::Auto,693flex_direction: FlexDirection::DEFAULT,694flex_wrap: FlexWrap::DEFAULT,695align_items: AlignItems::DEFAULT,696justify_items: JustifyItems::DEFAULT,697align_self: AlignSelf::DEFAULT,698justify_self: JustifySelf::DEFAULT,699align_content: AlignContent::DEFAULT,700justify_content: JustifyContent::DEFAULT,701margin: UiRect::DEFAULT,702padding: UiRect::DEFAULT,703border: UiRect::DEFAULT,704flex_grow: 0.0,705flex_shrink: 1.0,706flex_basis: Val::Auto,707width: Val::Auto,708height: Val::Auto,709min_width: Val::Auto,710min_height: Val::Auto,711max_width: Val::Auto,712max_height: Val::Auto,713aspect_ratio: None,714overflow: Overflow::DEFAULT,715overflow_clip_margin: OverflowClipMargin::DEFAULT,716scrollbar_width: 0.,717row_gap: Val::ZERO,718column_gap: Val::ZERO,719grid_auto_flow: GridAutoFlow::DEFAULT,720grid_template_rows: Vec::new(),721grid_template_columns: Vec::new(),722grid_auto_rows: Vec::new(),723grid_auto_columns: Vec::new(),724grid_column: GridPlacement::DEFAULT,725grid_row: GridPlacement::DEFAULT,726};727}728729impl Default for Node {730fn default() -> Self {731Self::DEFAULT732}733}734735/// Used to control how each individual item is aligned by default within the space they're given.736/// - For Flexbox containers, sets default cross axis alignment of the child items.737/// - For CSS Grid containers, controls block (vertical) axis alignment of children of this grid container within their grid areas.738///739/// <https://developer.mozilla.org/en-US/docs/Web/CSS/align-items>740#[derive(Copy, Clone, PartialEq, Eq, Debug, Reflect)]741#[reflect(Default, PartialEq, Clone)]742#[cfg_attr(743feature = "serialize",744derive(serde::Serialize, serde::Deserialize),745reflect(Serialize, Deserialize)746)]747pub enum AlignItems {748/// The items are packed in their default position as if no alignment was applied.749Default,750/// The items are packed towards the start of the axis.751Start,752/// The items are packed towards the end of the axis.753End,754/// The items are packed towards the start of the axis, unless the flex direction is reversed;755/// then they are packed towards the end of the axis.756FlexStart,757/// The items are packed towards the end of the axis, unless the flex direction is reversed;758/// then they are packed towards the start of the axis.759FlexEnd,760/// The items are packed along the center of the axis.761Center,762/// The items are packed such that their baselines align.763Baseline,764/// The items are stretched to fill the space they're given.765Stretch,766}767768impl AlignItems {769pub const DEFAULT: Self = Self::Default;770}771772impl Default for AlignItems {773fn default() -> Self {774Self::DEFAULT775}776}777778/// Used to control how each individual item is aligned by default within the space they're given.779/// - For Flexbox containers, this property has no effect. See `justify_content` for main axis alignment of flex items.780/// - For CSS Grid containers, sets default inline (horizontal) axis alignment of child items within their grid areas.781///782/// <https://developer.mozilla.org/en-US/docs/Web/CSS/justify-items>783#[derive(Copy, Clone, PartialEq, Eq, Debug, Reflect)]784#[reflect(Default, PartialEq, Clone)]785#[cfg_attr(786feature = "serialize",787derive(serde::Serialize, serde::Deserialize),788reflect(Serialize, Deserialize)789)]790pub enum JustifyItems {791/// The items are packed in their default position as if no alignment was applied.792Default,793/// The items are packed towards the start of the axis.794Start,795/// The items are packed towards the end of the axis.796End,797/// The items are packed along the center of the axis798Center,799/// The items are packed such that their baselines align.800Baseline,801/// The items are stretched to fill the space they're given.802Stretch,803}804805impl JustifyItems {806pub const DEFAULT: Self = Self::Default;807}808809impl Default for JustifyItems {810fn default() -> Self {811Self::DEFAULT812}813}814815/// Used to control how the specified item is aligned within the space it's given.816/// - For Flexbox items, controls cross axis alignment of the item.817/// - For CSS Grid items, controls block (vertical) axis alignment of a grid item within its grid area.818///819/// <https://developer.mozilla.org/en-US/docs/Web/CSS/align-self>820#[derive(Copy, Clone, PartialEq, Eq, Debug, Reflect)]821#[reflect(Default, PartialEq, Clone)]822#[cfg_attr(823feature = "serialize",824derive(serde::Serialize, serde::Deserialize),825reflect(Serialize, Deserialize)826)]827pub enum AlignSelf {828/// Use the parent node's [`AlignItems`] value to determine how this item should be aligned.829Auto,830/// This item will be aligned with the start of the axis.831Start,832/// This item will be aligned with the end of the axis.833End,834/// This item will be aligned with the start of the axis, unless the flex direction is reversed;835/// then it will be aligned with the end of the axis.836FlexStart,837/// This item will be aligned with the end of the axis, unless the flex direction is reversed;838/// then it will be aligned with the start of the axis.839FlexEnd,840/// This item will be aligned along the center of the axis.841Center,842/// This item will be aligned at the baseline.843Baseline,844/// This item will be stretched to fill the container.845Stretch,846}847848impl AlignSelf {849pub const DEFAULT: Self = Self::Auto;850}851852impl Default for AlignSelf {853fn default() -> Self {854Self::DEFAULT855}856}857858/// Used to control how the specified item is aligned within the space it's given.859/// - For Flexbox items, this property has no effect. See `justify_content` for main axis alignment of flex items.860/// - For CSS Grid items, controls inline (horizontal) axis alignment of a grid item within its grid area.861///862/// <https://developer.mozilla.org/en-US/docs/Web/CSS/justify-self>863#[derive(Copy, Clone, PartialEq, Eq, Debug, Reflect)]864#[reflect(Default, PartialEq, Clone)]865#[cfg_attr(866feature = "serialize",867derive(serde::Serialize, serde::Deserialize),868reflect(Serialize, Deserialize)869)]870pub enum JustifySelf {871/// Use the parent node's [`JustifyItems`] value to determine how this item should be aligned.872Auto,873/// This item will be aligned with the start of the axis.874Start,875/// This item will be aligned with the end of the axis.876End,877/// This item will be aligned along the center of the axis.878Center,879/// This item will be aligned at the baseline.880Baseline,881/// This item will be stretched to fill the space it's given.882Stretch,883}884885impl JustifySelf {886pub const DEFAULT: Self = Self::Auto;887}888889impl Default for JustifySelf {890fn default() -> Self {891Self::DEFAULT892}893}894895/// Used to control how items are distributed.896/// - For Flexbox containers, controls alignment of lines if `flex_wrap` is set to [`FlexWrap::Wrap`] and there are multiple lines of items.897/// - For CSS Grid containers, controls alignment of grid rows.898///899/// <https://developer.mozilla.org/en-US/docs/Web/CSS/align-content>900#[derive(Copy, Clone, PartialEq, Eq, Debug, Reflect)]901#[reflect(Default, PartialEq, Clone)]902#[cfg_attr(903feature = "serialize",904derive(serde::Serialize, serde::Deserialize),905reflect(Serialize, Deserialize)906)]907pub enum AlignContent {908/// The items are packed in their default position as if no alignment was applied.909Default,910/// The items are packed towards the start of the axis.911Start,912/// The items are packed towards the end of the axis.913End,914/// The items are packed towards the start of the axis, unless the flex direction is reversed;915/// then the items are packed towards the end of the axis.916FlexStart,917/// The items are packed towards the end of the axis, unless the flex direction is reversed;918/// then the items are packed towards the start of the axis.919FlexEnd,920/// The items are packed along the center of the axis.921Center,922/// The items are stretched to fill the container along the axis.923Stretch,924/// The items are distributed such that the gap between any two items is equal.925SpaceBetween,926/// The items are distributed such that the gap between and around any two items is equal.927SpaceEvenly,928/// The items are distributed such that the gap between and around any two items is equal, with half-size gaps on either end.929SpaceAround,930}931932impl AlignContent {933pub const DEFAULT: Self = Self::Default;934}935936impl Default for AlignContent {937fn default() -> Self {938Self::DEFAULT939}940}941942/// Used to control how items are distributed.943/// - For Flexbox containers, controls alignment of items in the main axis.944/// - For CSS Grid containers, controls alignment of grid columns.945///946/// <https://developer.mozilla.org/en-US/docs/Web/CSS/justify-content>947#[derive(Copy, Clone, PartialEq, Eq, Debug, Reflect)]948#[reflect(Default, PartialEq, Clone)]949#[cfg_attr(950feature = "serialize",951derive(serde::Serialize, serde::Deserialize),952reflect(Serialize, Deserialize)953)]954pub enum JustifyContent {955/// The items are packed in their default position as if no alignment was applied.956Default,957/// The items are packed towards the start of the axis.958Start,959/// The items are packed towards the end of the axis.960End,961/// The items are packed towards the start of the axis, unless the flex direction is reversed;962/// then the items are packed towards the end of the axis.963FlexStart,964/// The items are packed towards the end of the axis, unless the flex direction is reversed;965/// then the items are packed towards the start of the axis.966FlexEnd,967/// The items are packed along the center of the axis.968Center,969/// The items are stretched to fill the container along the axis.970Stretch,971/// The items are distributed such that the gap between any two items is equal.972SpaceBetween,973/// The items are distributed such that the gap between and around any two items is equal.974SpaceEvenly,975/// The items are distributed such that the gap between and around any two items is equal, with half-size gaps on either end.976SpaceAround,977}978979impl JustifyContent {980pub const DEFAULT: Self = Self::Default;981}982983impl Default for JustifyContent {984fn default() -> Self {985Self::DEFAULT986}987}988989/// Defines the layout model used by this node.990///991/// Part of the [`Node`] component.992#[derive(Copy, Clone, PartialEq, Eq, Debug, Reflect)]993#[reflect(Default, PartialEq, Clone)]994#[cfg_attr(995feature = "serialize",996derive(serde::Serialize, serde::Deserialize),997reflect(Serialize, Deserialize)998)]999pub enum Display {1000/// Use Flexbox layout model to determine the position of this [`Node`]'s children.1001Flex,1002/// Use CSS Grid layout model to determine the position of this [`Node`]'s children.1003Grid,1004/// Use CSS Block layout model to determine the position of this [`Node`]'s children.1005Block,1006/// Use no layout, don't render this node and its children.1007///1008/// If you want to hide a node and its children,1009/// but keep its layout in place, set its [`Visibility`] component instead.1010None,1011}10121013impl Display {1014pub const DEFAULT: Self = Self::Flex;1015}10161017impl Default for Display {1018fn default() -> Self {1019Self::DEFAULT1020}1021}10221023/// Which part of a Node's box length styles like width and height control1024///1025/// See: <https://developer.mozilla.org/en-US/docs/Web/CSS/box-sizing>1026#[derive(Copy, Clone, PartialEq, Eq, Debug, Reflect)]1027#[reflect(Default, PartialEq, Clone)]1028#[cfg_attr(1029feature = "serialize",1030derive(serde::Serialize, serde::Deserialize),1031reflect(Serialize, Deserialize)1032)]1033pub enum BoxSizing {1034/// Length styles like width and height refer to the "border box" size (size including padding and border)1035BorderBox,1036/// Length styles like width and height refer to the "content box" size (size excluding padding and border)1037ContentBox,1038}10391040impl BoxSizing {1041pub const DEFAULT: Self = Self::BorderBox;1042}10431044impl Default for BoxSizing {1045fn default() -> Self {1046Self::DEFAULT1047}1048}10491050/// Defines how flexbox items are ordered within a flexbox1051#[derive(Copy, Clone, PartialEq, Eq, Debug, Reflect)]1052#[reflect(Default, PartialEq, Clone)]1053#[cfg_attr(1054feature = "serialize",1055derive(serde::Serialize, serde::Deserialize),1056reflect(Serialize, Deserialize)1057)]1058pub enum FlexDirection {1059/// Same way as text direction along the main axis.1060Row,1061/// Flex from top to bottom.1062Column,1063/// Opposite way as text direction along the main axis.1064RowReverse,1065/// Flex from bottom to top.1066ColumnReverse,1067}10681069impl FlexDirection {1070pub const DEFAULT: Self = Self::Row;1071}10721073impl Default for FlexDirection {1074fn default() -> Self {1075Self::DEFAULT1076}1077}10781079/// Whether to show or hide overflowing items1080#[derive(Copy, Clone, PartialEq, Eq, Debug, Reflect)]1081#[reflect(Default, PartialEq, Clone)]1082#[cfg_attr(1083feature = "serialize",1084derive(serde::Serialize, serde::Deserialize),1085reflect(Serialize, Deserialize)1086)]1087pub struct Overflow {1088/// Whether to show or clip overflowing items on the x axis1089pub x: OverflowAxis,1090/// Whether to show or clip overflowing items on the y axis1091pub y: OverflowAxis,1092}10931094impl Overflow {1095pub const DEFAULT: Self = Self {1096x: OverflowAxis::DEFAULT,1097y: OverflowAxis::DEFAULT,1098};10991100/// Show overflowing items on both axes1101pub const fn visible() -> Self {1102Self {1103x: OverflowAxis::Visible,1104y: OverflowAxis::Visible,1105}1106}11071108/// Clip overflowing items on both axes1109pub const fn clip() -> Self {1110Self {1111x: OverflowAxis::Clip,1112y: OverflowAxis::Clip,1113}1114}11151116/// Clip overflowing items on the x axis1117pub const fn clip_x() -> Self {1118Self {1119x: OverflowAxis::Clip,1120y: OverflowAxis::Visible,1121}1122}11231124/// Clip overflowing items on the y axis1125pub const fn clip_y() -> Self {1126Self {1127x: OverflowAxis::Visible,1128y: OverflowAxis::Clip,1129}1130}11311132/// Hide overflowing items on both axes by influencing layout and then clipping1133pub const fn hidden() -> Self {1134Self {1135x: OverflowAxis::Hidden,1136y: OverflowAxis::Hidden,1137}1138}11391140/// Hide overflowing items on the x axis by influencing layout and then clipping1141pub const fn hidden_x() -> Self {1142Self {1143x: OverflowAxis::Hidden,1144y: OverflowAxis::Visible,1145}1146}11471148/// Hide overflowing items on the y axis by influencing layout and then clipping1149pub const fn hidden_y() -> Self {1150Self {1151x: OverflowAxis::Visible,1152y: OverflowAxis::Hidden,1153}1154}11551156/// Overflow is visible on both axes1157pub const fn is_visible(&self) -> bool {1158self.x.is_visible() && self.y.is_visible()1159}11601161pub const fn scroll() -> Self {1162Self {1163x: OverflowAxis::Scroll,1164y: OverflowAxis::Scroll,1165}1166}11671168/// Scroll overflowing items on the x axis1169pub const fn scroll_x() -> Self {1170Self {1171x: OverflowAxis::Scroll,1172y: OverflowAxis::Visible,1173}1174}11751176/// Scroll overflowing items on the y axis1177pub const fn scroll_y() -> Self {1178Self {1179x: OverflowAxis::Visible,1180y: OverflowAxis::Scroll,1181}1182}1183}11841185impl Default for Overflow {1186fn default() -> Self {1187Self::DEFAULT1188}1189}11901191/// Whether to show or hide overflowing items1192#[derive(Copy, Clone, PartialEq, Eq, Debug, Reflect)]1193#[reflect(Default, PartialEq, Clone)]1194#[cfg_attr(1195feature = "serialize",1196derive(serde::Serialize, serde::Deserialize),1197reflect(Serialize, Deserialize)1198)]1199pub enum OverflowAxis {1200/// Show overflowing items.1201Visible,1202/// Hide overflowing items by clipping.1203Clip,1204/// Hide overflowing items by influencing layout and then clipping.1205Hidden,1206/// Scroll overflowing items.1207Scroll,1208}12091210impl OverflowAxis {1211pub const DEFAULT: Self = Self::Visible;12121213/// Overflow is visible on this axis1214pub const fn is_visible(&self) -> bool {1215matches!(self, Self::Visible)1216}1217}12181219impl Default for OverflowAxis {1220fn default() -> Self {1221Self::DEFAULT1222}1223}12241225/// The bounds of the visible area when a UI node is clipped.1226#[derive(Default, Copy, Clone, PartialEq, Debug, Reflect)]1227#[reflect(Default, PartialEq, Clone)]1228#[cfg_attr(1229feature = "serialize",1230derive(serde::Serialize, serde::Deserialize),1231reflect(Serialize, Deserialize)1232)]1233pub struct OverflowClipMargin {1234/// Visible unclipped area1235pub visual_box: OverflowClipBox,1236/// Width of the margin on each edge of the visual box in logical pixels.1237/// The width of the margin will be zero if a negative value is set.1238pub margin: f32,1239}12401241impl OverflowClipMargin {1242pub const DEFAULT: Self = Self {1243visual_box: OverflowClipBox::PaddingBox,1244margin: 0.,1245};12461247/// Clip any content that overflows outside the content box1248pub const fn content_box() -> Self {1249Self {1250visual_box: OverflowClipBox::ContentBox,1251..Self::DEFAULT1252}1253}12541255/// Clip any content that overflows outside the padding box1256pub const fn padding_box() -> Self {1257Self {1258visual_box: OverflowClipBox::PaddingBox,1259..Self::DEFAULT1260}1261}12621263/// Clip any content that overflows outside the border box1264pub const fn border_box() -> Self {1265Self {1266visual_box: OverflowClipBox::BorderBox,1267..Self::DEFAULT1268}1269}12701271/// Add a margin on each edge of the visual box in logical pixels.1272/// The width of the margin will be zero if a negative value is set.1273pub const fn with_margin(mut self, margin: f32) -> Self {1274self.margin = margin;1275self1276}1277}12781279/// Used to determine the bounds of the visible area when a UI node is clipped.1280#[derive(Default, Copy, Clone, PartialEq, Eq, Debug, Reflect)]1281#[reflect(Default, PartialEq, Clone)]1282#[cfg_attr(1283feature = "serialize",1284derive(serde::Serialize, serde::Deserialize),1285reflect(Serialize, Deserialize)1286)]1287pub enum OverflowClipBox {1288/// Clip any content that overflows outside the content box1289ContentBox,1290/// Clip any content that overflows outside the padding box1291#[default]1292PaddingBox,1293/// Clip any content that overflows outside the border box1294BorderBox,1295}12961297/// The strategy used to position this node1298#[derive(Copy, Clone, PartialEq, Eq, Debug, Reflect)]1299#[reflect(Default, PartialEq, Clone)]1300#[cfg_attr(1301feature = "serialize",1302derive(serde::Serialize, serde::Deserialize),1303reflect(Serialize, Deserialize)1304)]1305pub enum PositionType {1306/// Relative to all other nodes with the [`PositionType::Relative`] value.1307Relative,1308/// Independent of all other nodes, but relative to its parent node.1309Absolute,1310}13111312impl PositionType {1313pub const DEFAULT: Self = Self::Relative;1314}13151316impl Default for PositionType {1317fn default() -> Self {1318Self::DEFAULT1319}1320}13211322/// Defines if flexbox items appear on a single line or on multiple lines1323#[derive(Copy, Clone, PartialEq, Eq, Debug, Reflect)]1324#[reflect(Default, PartialEq, Clone)]1325#[cfg_attr(1326feature = "serialize",1327derive(serde::Serialize, serde::Deserialize),1328reflect(Serialize, Deserialize)1329)]1330pub enum FlexWrap {1331/// Single line, will overflow if needed.1332NoWrap,1333/// Multiple lines, if needed.1334Wrap,1335/// Same as [`FlexWrap::Wrap`] but new lines will appear before the previous one.1336WrapReverse,1337}13381339impl FlexWrap {1340pub const DEFAULT: Self = Self::NoWrap;1341}13421343impl Default for FlexWrap {1344fn default() -> Self {1345Self::DEFAULT1346}1347}13481349/// Controls whether grid items are placed row-wise or column-wise as well as whether the sparse or dense packing algorithm is used.1350///1351/// The "dense" packing algorithm attempts to fill in holes earlier in the grid, if smaller items come up later.1352/// This may cause items to appear out-of-order when doing so would fill in holes left by larger items.1353///1354/// Defaults to [`GridAutoFlow::Row`].1355///1356/// <https://developer.mozilla.org/en-US/docs/Web/CSS/grid-auto-flow>1357#[derive(Copy, Clone, PartialEq, Eq, Debug, Reflect)]1358#[reflect(Default, PartialEq, Clone)]1359#[cfg_attr(1360feature = "serialize",1361derive(serde::Serialize, serde::Deserialize),1362reflect(Serialize, Deserialize)1363)]1364pub enum GridAutoFlow {1365/// Items are placed by filling each row in turn, adding new rows as necessary.1366Row,1367/// Items are placed by filling each column in turn, adding new columns as necessary.1368Column,1369/// Combines `Row` with the dense packing algorithm.1370RowDense,1371/// Combines `Column` with the dense packing algorithm.1372ColumnDense,1373}13741375impl GridAutoFlow {1376pub const DEFAULT: Self = Self::Row;1377}13781379impl Default for GridAutoFlow {1380fn default() -> Self {1381Self::DEFAULT1382}1383}13841385#[derive(Default, Copy, Clone, PartialEq, Debug, Reflect)]1386#[reflect(Default, PartialEq, Clone)]1387#[cfg_attr(1388feature = "serialize",1389derive(serde::Serialize, serde::Deserialize),1390reflect(Serialize, Deserialize)1391)]1392pub enum MinTrackSizingFunction {1393/// Track minimum size should be a fixed pixel value1394Px(f32),1395/// Track minimum size should be a percentage value1396Percent(f32),1397/// Track minimum size should be content sized under a min-content constraint1398MinContent,1399/// Track minimum size should be content sized under a max-content constraint1400MaxContent,1401/// Track minimum size should be automatically sized1402#[default]1403Auto,1404/// Track minimum size should be a percent of the viewport's smaller dimension.1405VMin(f32),1406/// Track minimum size should be a percent of the viewport's larger dimension.1407VMax(f32),1408/// Track minimum size should be a percent of the viewport's height dimension.1409Vh(f32),1410/// Track minimum size should be a percent of the viewport's width dimension.1411Vw(f32),1412}14131414#[derive(Default, Copy, Clone, PartialEq, Debug, Reflect)]1415#[reflect(Default, PartialEq, Clone)]1416#[cfg_attr(1417feature = "serialize",1418derive(serde::Serialize, serde::Deserialize),1419reflect(Serialize, Deserialize)1420)]1421pub enum MaxTrackSizingFunction {1422/// Track maximum size should be a fixed pixel value1423Px(f32),1424/// Track maximum size should be a percentage value1425Percent(f32),1426/// Track maximum size should be content sized under a min-content constraint1427MinContent,1428/// Track maximum size should be content sized under a max-content constraint1429MaxContent,1430/// Track maximum size should be sized according to the fit-content formula with a fixed pixel limit1431FitContentPx(f32),1432/// Track maximum size should be sized according to the fit-content formula with a percentage limit1433FitContentPercent(f32),1434/// Track maximum size should be automatically sized1435#[default]1436Auto,1437/// The dimension as a fraction of the total available grid space (`fr` units in CSS)1438/// Specified value is the numerator of the fraction. Denominator is the sum of all fractions specified in that grid dimension.1439///1440/// Spec: <https://www.w3.org/TR/css3-grid-layout/#fr-unit>1441Fraction(f32),1442/// Track maximum size should be a percent of the viewport's smaller dimension.1443VMin(f32),1444/// Track maximum size should be a percent of the viewport's smaller dimension.1445VMax(f32),1446/// Track maximum size should be a percent of the viewport's height dimension.1447Vh(f32),1448/// Track maximum size should be a percent of the viewport's width dimension.1449Vw(f32),1450}14511452/// A [`GridTrack`] is a Row or Column of a CSS Grid. This struct specifies what size the track should be.1453/// See below for the different "track sizing functions" you can specify.1454#[derive(Copy, Clone, PartialEq, Debug, Reflect)]1455#[reflect(Default, PartialEq, Clone)]1456#[cfg_attr(1457feature = "serialize",1458derive(serde::Serialize, serde::Deserialize),1459reflect(Serialize, Deserialize)1460)]1461pub struct GridTrack {1462pub(crate) min_sizing_function: MinTrackSizingFunction,1463pub(crate) max_sizing_function: MaxTrackSizingFunction,1464}14651466impl GridTrack {1467pub const DEFAULT: Self = Self {1468min_sizing_function: MinTrackSizingFunction::Auto,1469max_sizing_function: MaxTrackSizingFunction::Auto,1470};14711472/// Create a grid track with a fixed pixel size1473pub fn px<T: From<Self>>(value: f32) -> T {1474Self {1475min_sizing_function: MinTrackSizingFunction::Px(value),1476max_sizing_function: MaxTrackSizingFunction::Px(value),1477}1478.into()1479}14801481/// Create a grid track with a percentage size1482pub fn percent<T: From<Self>>(value: f32) -> T {1483Self {1484min_sizing_function: MinTrackSizingFunction::Percent(value),1485max_sizing_function: MaxTrackSizingFunction::Percent(value),1486}1487.into()1488}14891490/// Create a grid track with an `fr` size.1491/// Note that this will give the track a content-based minimum size.1492/// Usually you are best off using `GridTrack::flex` instead which uses a zero minimum size.1493pub fn fr<T: From<Self>>(value: f32) -> T {1494Self {1495min_sizing_function: MinTrackSizingFunction::Auto,1496max_sizing_function: MaxTrackSizingFunction::Fraction(value),1497}1498.into()1499}15001501/// Create a grid track with a `minmax(0, Nfr)` size.1502pub fn flex<T: From<Self>>(value: f32) -> T {1503Self {1504min_sizing_function: MinTrackSizingFunction::Px(0.0),1505max_sizing_function: MaxTrackSizingFunction::Fraction(value),1506}1507.into()1508}15091510/// Create a grid track which is automatically sized to fit its contents.1511pub fn auto<T: From<Self>>() -> T {1512Self {1513min_sizing_function: MinTrackSizingFunction::Auto,1514max_sizing_function: MaxTrackSizingFunction::Auto,1515}1516.into()1517}15181519/// Create a grid track which is automatically sized to fit its contents when sized at their "min-content" sizes1520pub fn min_content<T: From<Self>>() -> T {1521Self {1522min_sizing_function: MinTrackSizingFunction::MinContent,1523max_sizing_function: MaxTrackSizingFunction::MinContent,1524}1525.into()1526}15271528/// Create a grid track which is automatically sized to fit its contents when sized at their "max-content" sizes1529pub fn max_content<T: From<Self>>() -> T {1530Self {1531min_sizing_function: MinTrackSizingFunction::MaxContent,1532max_sizing_function: MaxTrackSizingFunction::MaxContent,1533}1534.into()1535}15361537/// Create a `fit-content()` grid track with fixed pixel limit.1538///1539/// <https://developer.mozilla.org/en-US/docs/Web/CSS/fit-content_function>1540pub fn fit_content_px<T: From<Self>>(limit: f32) -> T {1541Self {1542min_sizing_function: MinTrackSizingFunction::Auto,1543max_sizing_function: MaxTrackSizingFunction::FitContentPx(limit),1544}1545.into()1546}15471548/// Create a `fit-content()` grid track with percentage limit.1549///1550/// <https://developer.mozilla.org/en-US/docs/Web/CSS/fit-content_function>1551pub fn fit_content_percent<T: From<Self>>(limit: f32) -> T {1552Self {1553min_sizing_function: MinTrackSizingFunction::Auto,1554max_sizing_function: MaxTrackSizingFunction::FitContentPercent(limit),1555}1556.into()1557}15581559/// Create a `minmax()` grid track.1560///1561/// <https://developer.mozilla.org/en-US/docs/Web/CSS/minmax>1562pub fn minmax<T: From<Self>>(min: MinTrackSizingFunction, max: MaxTrackSizingFunction) -> T {1563Self {1564min_sizing_function: min,1565max_sizing_function: max,1566}1567.into()1568}15691570/// Create a grid track with a percentage of the viewport's smaller dimension1571pub fn vmin<T: From<Self>>(value: f32) -> T {1572Self {1573min_sizing_function: MinTrackSizingFunction::VMin(value),1574max_sizing_function: MaxTrackSizingFunction::VMin(value),1575}1576.into()1577}15781579/// Create a grid track with a percentage of the viewport's larger dimension1580pub fn vmax<T: From<Self>>(value: f32) -> T {1581Self {1582min_sizing_function: MinTrackSizingFunction::VMax(value),1583max_sizing_function: MaxTrackSizingFunction::VMax(value),1584}1585.into()1586}15871588/// Create a grid track with a percentage of the viewport's height dimension1589pub fn vh<T: From<Self>>(value: f32) -> T {1590Self {1591min_sizing_function: MinTrackSizingFunction::Vh(value),1592max_sizing_function: MaxTrackSizingFunction::Vh(value),1593}1594.into()1595}15961597/// Create a grid track with a percentage of the viewport's width dimension1598pub fn vw<T: From<Self>>(value: f32) -> T {1599Self {1600min_sizing_function: MinTrackSizingFunction::Vw(value),1601max_sizing_function: MaxTrackSizingFunction::Vw(value),1602}1603.into()1604}1605}16061607impl Default for GridTrack {1608fn default() -> Self {1609Self::DEFAULT1610}1611}16121613#[derive(Copy, Clone, PartialEq, Debug, Reflect, From)]1614#[reflect(Default, PartialEq, Clone)]1615#[cfg_attr(1616feature = "serialize",1617derive(serde::Serialize, serde::Deserialize),1618reflect(Serialize, Deserialize)1619)]1620/// How many times to repeat a repeated grid track1621///1622/// <https://developer.mozilla.org/en-US/docs/Web/CSS/repeat>1623pub enum GridTrackRepetition {1624/// Repeat the track fixed number of times1625Count(u16),1626/// Repeat the track to fill available space1627///1628/// <https://developer.mozilla.org/en-US/docs/Web/CSS/repeat#auto-fill>1629AutoFill,1630/// Repeat the track to fill available space but collapse any tracks that do not end up with1631/// an item placed in them.1632///1633/// <https://developer.mozilla.org/en-US/docs/Web/CSS/repeat#auto-fit>1634AutoFit,1635}16361637impl Default for GridTrackRepetition {1638fn default() -> Self {1639Self::Count(1)1640}1641}16421643impl From<i32> for GridTrackRepetition {1644fn from(count: i32) -> Self {1645Self::Count(count as u16)1646}1647}16481649impl From<usize> for GridTrackRepetition {1650fn from(count: usize) -> Self {1651Self::Count(count as u16)1652}1653}16541655/// Represents a *possibly* repeated [`GridTrack`].1656///1657/// The repetition parameter can either be:1658/// - The integer `1`, in which case the track is non-repeated.1659/// - a `u16` count to repeat the track N times.1660/// - A `GridTrackRepetition::AutoFit` or `GridTrackRepetition::AutoFill`.1661///1662/// Note: that in the common case you want a non-repeating track (repetition count 1), you may use the constructor methods on [`GridTrack`]1663/// to create a `RepeatedGridTrack`. i.e. `GridTrack::px(10.0)` is equivalent to `RepeatedGridTrack::px(1, 10.0)`.1664///1665/// You may only use one auto-repetition per track list. And if your track list contains an auto repetition1666/// then all tracks (in and outside of the repetition) must be fixed size (px or percent). Integer repetitions are just shorthand for writing out1667/// N tracks longhand and are not subject to the same limitations.1668#[derive(Clone, PartialEq, Debug, Reflect)]1669#[reflect(Default, PartialEq, Clone)]1670#[cfg_attr(1671feature = "serialize",1672derive(serde::Serialize, serde::Deserialize),1673reflect(Serialize, Deserialize)1674)]1675pub struct RepeatedGridTrack {1676pub(crate) repetition: GridTrackRepetition,1677pub(crate) tracks: SmallVec<[GridTrack; 1]>,1678}16791680impl RepeatedGridTrack {1681/// Create a repeating set of grid tracks with a fixed pixel size1682pub fn px<T: From<Self>>(repetition: impl Into<GridTrackRepetition>, value: f32) -> T {1683Self {1684repetition: repetition.into(),1685tracks: SmallVec::from_buf([GridTrack::px(value)]),1686}1687.into()1688}16891690/// Create a repeating set of grid tracks with a percentage size1691pub fn percent<T: From<Self>>(repetition: impl Into<GridTrackRepetition>, value: f32) -> T {1692Self {1693repetition: repetition.into(),1694tracks: SmallVec::from_buf([GridTrack::percent(value)]),1695}1696.into()1697}16981699/// Create a repeating set of grid tracks with automatic size1700pub fn auto<T: From<Self>>(repetition: u16) -> T {1701Self {1702repetition: GridTrackRepetition::Count(repetition),1703tracks: SmallVec::from_buf([GridTrack::auto()]),1704}1705.into()1706}17071708/// Create a repeating set of grid tracks with an `fr` size.1709/// Note that this will give the track a content-based minimum size.1710/// Usually you are best off using `GridTrack::flex` instead which uses a zero minimum size.1711pub fn fr<T: From<Self>>(repetition: u16, value: f32) -> T {1712Self {1713repetition: GridTrackRepetition::Count(repetition),1714tracks: SmallVec::from_buf([GridTrack::fr(value)]),1715}1716.into()1717}17181719/// Create a repeating set of grid tracks with a `minmax(0, Nfr)` size.1720pub fn flex<T: From<Self>>(repetition: u16, value: f32) -> T {1721Self {1722repetition: GridTrackRepetition::Count(repetition),1723tracks: SmallVec::from_buf([GridTrack::flex(value)]),1724}1725.into()1726}17271728/// Create a repeating set of grid tracks with min-content size1729pub fn min_content<T: From<Self>>(repetition: u16) -> T {1730Self {1731repetition: GridTrackRepetition::Count(repetition),1732tracks: SmallVec::from_buf([GridTrack::min_content()]),1733}1734.into()1735}17361737/// Create a repeating set of grid tracks with max-content size1738pub fn max_content<T: From<Self>>(repetition: u16) -> T {1739Self {1740repetition: GridTrackRepetition::Count(repetition),1741tracks: SmallVec::from_buf([GridTrack::max_content()]),1742}1743.into()1744}17451746/// Create a repeating set of `fit-content()` grid tracks with fixed pixel limit1747pub fn fit_content_px<T: From<Self>>(repetition: u16, limit: f32) -> T {1748Self {1749repetition: GridTrackRepetition::Count(repetition),1750tracks: SmallVec::from_buf([GridTrack::fit_content_px(limit)]),1751}1752.into()1753}17541755/// Create a repeating set of `fit-content()` grid tracks with percentage limit1756pub fn fit_content_percent<T: From<Self>>(repetition: u16, limit: f32) -> T {1757Self {1758repetition: GridTrackRepetition::Count(repetition),1759tracks: SmallVec::from_buf([GridTrack::fit_content_percent(limit)]),1760}1761.into()1762}17631764/// Create a repeating set of `minmax()` grid track1765pub fn minmax<T: From<Self>>(1766repetition: impl Into<GridTrackRepetition>,1767min: MinTrackSizingFunction,1768max: MaxTrackSizingFunction,1769) -> T {1770Self {1771repetition: repetition.into(),1772tracks: SmallVec::from_buf([GridTrack::minmax(min, max)]),1773}1774.into()1775}17761777/// Create a repeating set of grid tracks with the percentage size of the viewport's smaller dimension1778pub fn vmin<T: From<Self>>(repetition: impl Into<GridTrackRepetition>, value: f32) -> T {1779Self {1780repetition: repetition.into(),1781tracks: SmallVec::from_buf([GridTrack::vmin(value)]),1782}1783.into()1784}17851786/// Create a repeating set of grid tracks with the percentage size of the viewport's larger dimension1787pub fn vmax<T: From<Self>>(repetition: impl Into<GridTrackRepetition>, value: f32) -> T {1788Self {1789repetition: repetition.into(),1790tracks: SmallVec::from_buf([GridTrack::vmax(value)]),1791}1792.into()1793}17941795/// Create a repeating set of grid tracks with the percentage size of the viewport's height dimension1796pub fn vh<T: From<Self>>(repetition: impl Into<GridTrackRepetition>, value: f32) -> T {1797Self {1798repetition: repetition.into(),1799tracks: SmallVec::from_buf([GridTrack::vh(value)]),1800}1801.into()1802}18031804/// Create a repeating set of grid tracks with the percentage size of the viewport's width dimension1805pub fn vw<T: From<Self>>(repetition: impl Into<GridTrackRepetition>, value: f32) -> T {1806Self {1807repetition: repetition.into(),1808tracks: SmallVec::from_buf([GridTrack::vw(value)]),1809}1810.into()1811}18121813/// Create a repetition of a set of tracks1814pub fn repeat_many<T: From<Self>>(1815repetition: impl Into<GridTrackRepetition>,1816tracks: impl Into<Vec<GridTrack>>,1817) -> T {1818Self {1819repetition: repetition.into(),1820tracks: SmallVec::from_vec(tracks.into()),1821}1822.into()1823}1824}18251826impl Default for RepeatedGridTrack {1827fn default() -> Self {1828Self {1829repetition: Default::default(),1830tracks: SmallVec::from_buf([GridTrack::default()]),1831}1832}1833}18341835impl From<GridTrack> for RepeatedGridTrack {1836fn from(track: GridTrack) -> Self {1837Self {1838repetition: GridTrackRepetition::Count(1),1839tracks: SmallVec::from_buf([track]),1840}1841}1842}18431844impl From<GridTrack> for Vec<GridTrack> {1845fn from(track: GridTrack) -> Self {1846vec![track]1847}1848}18491850impl From<GridTrack> for Vec<RepeatedGridTrack> {1851fn from(track: GridTrack) -> Self {1852vec![RepeatedGridTrack {1853repetition: GridTrackRepetition::Count(1),1854tracks: SmallVec::from_buf([track]),1855}]1856}1857}18581859impl From<RepeatedGridTrack> for Vec<RepeatedGridTrack> {1860fn from(track: RepeatedGridTrack) -> Self {1861vec![track]1862}1863}18641865#[derive(Copy, Clone, PartialEq, Eq, Debug, Reflect)]1866#[reflect(Default, PartialEq, Clone)]1867#[cfg_attr(1868feature = "serialize",1869derive(serde::Serialize, serde::Deserialize),1870reflect(Serialize, Deserialize)1871)]1872/// Represents the position of a grid item in a single axis.1873///1874/// There are 3 fields which may be set:1875/// - `start`: which grid line the item should start at1876/// - `end`: which grid line the item should end at1877/// - `span`: how many tracks the item should span1878///1879/// The default `span` is 1. If neither `start` or `end` is set then the item will be placed automatically.1880///1881/// Generally, at most two fields should be set. If all three fields are specified then `span` will be ignored. If `end` specifies an earlier1882/// grid line than `start` then `end` will be ignored and the item will have a span of 1.1883///1884/// <https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Grid_Layout/Line-based_Placement_with_CSS_Grid>1885pub struct GridPlacement {1886/// The grid line at which the item should start.1887/// Lines are 1-indexed.1888/// Negative indexes count backwards from the end of the grid.1889/// Zero is not a valid index.1890pub(crate) start: Option<NonZero<i16>>,1891/// How many grid tracks the item should span.1892/// Defaults to 1.1893pub(crate) span: Option<NonZero<u16>>,1894/// The grid line at which the item should end.1895/// Lines are 1-indexed.1896/// Negative indexes count backwards from the end of the grid.1897/// Zero is not a valid index.1898pub(crate) end: Option<NonZero<i16>>,1899}19001901impl GridPlacement {1902pub const DEFAULT: Self = Self {1903start: None,1904span: NonZero::<u16>::new(1),1905end: None,1906};19071908/// Place the grid item automatically (letting the `span` default to `1`).1909pub fn auto() -> Self {1910Self::DEFAULT1911}19121913/// Place the grid item automatically, specifying how many tracks it should `span`.1914///1915/// # Panics1916///1917/// Panics if `span` is `0`.1918pub fn span(span: u16) -> Self {1919Self {1920start: None,1921end: None,1922span: try_into_grid_span(span).expect("Invalid span value of 0."),1923}1924}19251926/// Place the grid item specifying the `start` grid line (letting the `span` default to `1`).1927///1928/// # Panics1929///1930/// Panics if `start` is `0`.1931pub fn start(start: i16) -> Self {1932Self {1933start: try_into_grid_index(start).expect("Invalid start value of 0."),1934..Self::DEFAULT1935}1936}19371938/// Place the grid item specifying the `end` grid line (letting the `span` default to `1`).1939///1940/// # Panics1941///1942/// Panics if `end` is `0`.1943pub fn end(end: i16) -> Self {1944Self {1945end: try_into_grid_index(end).expect("Invalid end value of 0."),1946..Self::DEFAULT1947}1948}19491950/// Place the grid item specifying the `start` grid line and how many tracks it should `span`.1951///1952/// # Panics1953///1954/// Panics if `start` or `span` is `0`.1955pub fn start_span(start: i16, span: u16) -> Self {1956Self {1957start: try_into_grid_index(start).expect("Invalid start value of 0."),1958end: None,1959span: try_into_grid_span(span).expect("Invalid span value of 0."),1960}1961}19621963/// Place the grid item specifying `start` and `end` grid lines (`span` will be inferred)1964///1965/// # Panics1966///1967/// Panics if `start` or `end` is `0`.1968pub fn start_end(start: i16, end: i16) -> Self {1969Self {1970start: try_into_grid_index(start).expect("Invalid start value of 0."),1971end: try_into_grid_index(end).expect("Invalid end value of 0."),1972span: None,1973}1974}19751976/// Place the grid item specifying the `end` grid line and how many tracks it should `span`.1977///1978/// # Panics1979///1980/// Panics if `end` or `span` is `0`.1981pub fn end_span(end: i16, span: u16) -> Self {1982Self {1983start: None,1984end: try_into_grid_index(end).expect("Invalid end value of 0."),1985span: try_into_grid_span(span).expect("Invalid span value of 0."),1986}1987}19881989/// Mutate the item, setting the `start` grid line1990///1991/// # Panics1992///1993/// Panics if `start` is `0`.1994pub fn set_start(mut self, start: i16) -> Self {1995self.start = try_into_grid_index(start).expect("Invalid start value of 0.");1996self1997}19981999/// Mutate the item, setting the `end` grid line2000///2001/// # Panics2002///2003/// Panics if `end` is `0`.2004pub fn set_end(mut self, end: i16) -> Self {2005self.end = try_into_grid_index(end).expect("Invalid end value of 0.");2006self2007}20082009/// Mutate the item, setting the number of tracks the item should `span`2010///2011/// # Panics2012///2013/// Panics if `span` is `0`.2014pub fn set_span(mut self, span: u16) -> Self {2015self.span = try_into_grid_span(span).expect("Invalid span value of 0.");2016self2017}20182019/// Returns the grid line at which the item should start, or `None` if not set.2020pub fn get_start(self) -> Option<i16> {2021self.start.map(NonZero::<i16>::get)2022}20232024/// Returns the grid line at which the item should end, or `None` if not set.2025pub fn get_end(self) -> Option<i16> {2026self.end.map(NonZero::<i16>::get)2027}20282029/// Returns span for this grid item, or `None` if not set.2030pub fn get_span(self) -> Option<u16> {2031self.span.map(NonZero::<u16>::get)2032}2033}20342035impl Default for GridPlacement {2036fn default() -> Self {2037Self::DEFAULT2038}2039}20402041/// Convert an `i16` to `NonZero<i16>`, fails on `0` and returns the `InvalidZeroIndex` error.2042fn try_into_grid_index(index: i16) -> Result<Option<NonZero<i16>>, GridPlacementError> {2043Ok(Some(2044NonZero::<i16>::new(index).ok_or(GridPlacementError::InvalidZeroIndex)?,2045))2046}20472048/// Convert a `u16` to `NonZero<u16>`, fails on `0` and returns the `InvalidZeroSpan` error.2049fn try_into_grid_span(span: u16) -> Result<Option<NonZero<u16>>, GridPlacementError> {2050Ok(Some(2051NonZero::<u16>::new(span).ok_or(GridPlacementError::InvalidZeroSpan)?,2052))2053}20542055/// Errors that occur when setting constraints for a `GridPlacement`2056#[derive(Debug, Eq, PartialEq, Clone, Copy, Error)]2057pub enum GridPlacementError {2058#[error("Zero is not a valid grid position")]2059InvalidZeroIndex,2060#[error("Spans cannot be zero length")]2061InvalidZeroSpan,2062}20632064/// The background color of the node2065///2066/// This serves as the "fill" color.2067#[derive(Component, Copy, Clone, Debug, PartialEq, Reflect)]2068#[reflect(Component, Default, Debug, PartialEq, Clone)]2069#[cfg_attr(2070feature = "serialize",2071derive(serde::Serialize, serde::Deserialize),2072reflect(Serialize, Deserialize)2073)]2074pub struct BackgroundColor(pub Color);20752076impl BackgroundColor {2077/// Background color is transparent by default.2078pub const DEFAULT: Self = Self(Color::NONE);2079}20802081impl Default for BackgroundColor {2082fn default() -> Self {2083Self::DEFAULT2084}2085}20862087impl<T: Into<Color>> From<T> for BackgroundColor {2088fn from(color: T) -> Self {2089Self(color.into())2090}2091}20922093/// The border color of the UI node.2094#[derive(Component, Copy, Clone, Debug, PartialEq, Reflect)]2095#[reflect(Component, Default, Debug, PartialEq, Clone)]2096#[cfg_attr(2097feature = "serialize",2098derive(serde::Serialize, serde::Deserialize),2099reflect(Serialize, Deserialize)2100)]2101pub struct BorderColor {2102pub top: Color,2103pub right: Color,2104pub bottom: Color,2105pub left: Color,2106}21072108impl<T: Into<Color>> From<T> for BorderColor {2109fn from(color: T) -> Self {2110Self::all(color.into())2111}2112}21132114impl BorderColor {2115/// Border color is transparent by default.2116pub const DEFAULT: Self = BorderColor {2117top: Color::NONE,2118right: Color::NONE,2119bottom: Color::NONE,2120left: Color::NONE,2121};21222123/// Helper to create a `BorderColor` struct with all borders set to the given color2124#[inline]2125pub fn all(color: impl Into<Color>) -> Self {2126let color = color.into();2127Self {2128top: color,2129bottom: color,2130left: color,2131right: color,2132}2133}21342135/// Helper to set all border colors to a given color.2136pub fn set_all(&mut self, color: impl Into<Color>) -> &mut Self {2137let color: Color = color.into();2138self.top = color;2139self.bottom = color;2140self.left = color;2141self.right = color;2142self2143}21442145/// Check if all contained border colors are transparent2146pub fn is_fully_transparent(&self) -> bool {2147self.top.is_fully_transparent()2148&& self.bottom.is_fully_transparent()2149&& self.left.is_fully_transparent()2150&& self.right.is_fully_transparent()2151}2152}21532154impl Default for BorderColor {2155fn default() -> Self {2156Self::DEFAULT2157}2158}21592160#[derive(Component, Copy, Clone, Default, Debug, PartialEq, Reflect)]2161#[reflect(Component, Default, Debug, PartialEq, Clone)]2162#[cfg_attr(2163feature = "serialize",2164derive(serde::Serialize, serde::Deserialize),2165reflect(Serialize, Deserialize)2166)]2167/// The [`Outline`] component adds an outline outside the edge of a UI node.2168/// Outlines do not take up space in the layout.2169///2170/// To add an [`Outline`] to a ui node you can spawn a `(Node, Outline)` tuple bundle:2171/// ```2172/// # use bevy_ecs::prelude::*;2173/// # use bevy_ui::prelude::*;2174/// # use bevy_color::palettes::basic::{RED, BLUE};2175/// fn setup_ui(mut commands: Commands) {2176/// commands.spawn((2177/// Node {2178/// width: Val::Px(100.),2179/// height: Val::Px(100.),2180/// ..Default::default()2181/// },2182/// BackgroundColor(BLUE.into()),2183/// Outline::new(Val::Px(10.), Val::ZERO, RED.into())2184/// ));2185/// }2186/// ```2187///2188/// [`Outline`] components can also be added later to existing UI nodes:2189/// ```2190/// # use bevy_ecs::prelude::*;2191/// # use bevy_ui::prelude::*;2192/// # use bevy_color::Color;2193/// fn outline_hovered_button_system(2194/// mut commands: Commands,2195/// mut node_query: Query<(Entity, &Interaction, Option<&mut Outline>), Changed<Interaction>>,2196/// ) {2197/// for (entity, interaction, mut maybe_outline) in node_query.iter_mut() {2198/// let outline_color =2199/// if matches!(*interaction, Interaction::Hovered) {2200/// Color::WHITE2201/// } else {2202/// Color::NONE2203/// };2204/// if let Some(mut outline) = maybe_outline {2205/// outline.color = outline_color;2206/// } else {2207/// commands.entity(entity).insert(Outline::new(Val::Px(10.), Val::ZERO, outline_color));2208/// }2209/// }2210/// }2211/// ```2212/// Inserting and removing an [`Outline`] component repeatedly will result in table moves, so it is generally preferable to2213/// set `Outline::color` to [`Color::NONE`] to hide an outline.2214pub struct Outline {2215/// The width of the outline.2216///2217/// Percentage `Val` values are resolved based on the width of the outlined [`Node`].2218pub width: Val,2219/// The amount of space between a node's outline the edge of the node.2220///2221/// Percentage `Val` values are resolved based on the width of the outlined [`Node`].2222pub offset: Val,2223/// The color of the outline.2224///2225/// If you are frequently toggling outlines for a UI node on and off it is recommended to set [`Color::NONE`] to hide the outline.2226/// This avoids the table moves that would occur from the repeated insertion and removal of the `Outline` component.2227pub color: Color,2228}22292230impl Outline {2231/// Create a new outline2232pub const fn new(width: Val, offset: Val, color: Color) -> Self {2233Self {2234width,2235offset,2236color,2237}2238}2239}22402241/// The calculated clip of the node2242#[derive(Component, Default, Copy, Clone, Debug, Reflect)]2243#[reflect(Component, Default, Debug, Clone)]2244pub struct CalculatedClip {2245/// The rect of the clip2246pub clip: Rect,2247}22482249/// UI node entities with this component will ignore any clipping rect they inherit,2250/// the node will not be clipped regardless of its ancestors' `Overflow` setting.2251#[derive(Component)]2252pub struct OverrideClip;22532254#[expect(2255rustdoc::redundant_explicit_links,2256reason = "To go around the `<code>` limitations, we put the link twice so we're \2257sure it's recognized as a markdown link."2258)]2259/// Indicates that this [`Node`] entity's front-to-back ordering is not controlled solely2260/// by its location in the UI hierarchy. A node with a higher z-index will appear on top2261/// of sibling nodes with a lower z-index.2262///2263/// UI nodes that have the same z-index will appear according to the order in which they2264/// appear in the UI hierarchy. In such a case, the last node to be added to its parent2265/// will appear in front of its siblings.2266///2267/// Nodes without this component will be treated as if they had a value of2268/// <code>[ZIndex][ZIndex]\(0\)</code>.2269///2270/// Use [`GlobalZIndex`] if you need to order separate UI hierarchies or nodes that are2271/// not siblings in a given UI hierarchy.2272#[derive(Component, Copy, Clone, Debug, Default, PartialEq, Eq, Reflect)]2273#[reflect(Component, Default, Debug, PartialEq, Clone)]2274pub struct ZIndex(pub i32);22752276/// `GlobalZIndex` allows a [`Node`] entity anywhere in the UI hierarchy to escape the implicit draw ordering of the UI's layout tree and2277/// be rendered above or below other UI nodes.2278/// Nodes with a `GlobalZIndex` of greater than 0 will be drawn on top of nodes without a `GlobalZIndex` or nodes with a lower `GlobalZIndex`.2279/// Nodes with a `GlobalZIndex` of less than 0 will be drawn below nodes without a `GlobalZIndex` or nodes with a greater `GlobalZIndex`.2280///2281/// If two Nodes have the same `GlobalZIndex`, the node with the greater [`ZIndex`] will be drawn on top.2282#[derive(Component, Copy, Clone, Debug, Default, PartialEq, Eq, Reflect)]2283#[reflect(Component, Default, Debug, PartialEq, Clone)]2284pub struct GlobalZIndex(pub i32);22852286/// Used to add rounded corners to a UI node. You can set a UI node to have uniformly2287/// rounded corners or specify different radii for each corner. If a given radius exceeds half2288/// the length of the smallest dimension between the node's height or width, the radius will2289/// calculated as half the smallest dimension.2290///2291/// Elliptical nodes are not supported yet. Percentage values are based on the node's smallest2292/// dimension, either width or height.2293///2294/// # Example2295/// ```rust2296/// # use bevy_ecs::prelude::*;2297/// # use bevy_ui::prelude::*;2298/// # use bevy_color::palettes::basic::{BLUE};2299/// fn setup_ui(mut commands: Commands) {2300/// commands.spawn((2301/// Node {2302/// width: Val::Px(100.),2303/// height: Val::Px(100.),2304/// border: UiRect::all(Val::Px(2.)),2305/// ..Default::default()2306/// },2307/// BackgroundColor(BLUE.into()),2308/// BorderRadius::new(2309/// // top left2310/// Val::Px(10.),2311/// // top right2312/// Val::Px(20.),2313/// // bottom right2314/// Val::Px(30.),2315/// // bottom left2316/// Val::Px(40.),2317/// ),2318/// ));2319/// }2320/// ```2321///2322/// <https://developer.mozilla.org/en-US/docs/Web/CSS/border-radius>2323#[derive(Component, Copy, Clone, Debug, PartialEq, Reflect)]2324#[reflect(Component, PartialEq, Default, Debug, Clone)]2325#[cfg_attr(2326feature = "serialize",2327derive(serde::Serialize, serde::Deserialize),2328reflect(Serialize, Deserialize)2329)]2330pub struct BorderRadius {2331pub top_left: Val,2332pub top_right: Val,2333pub bottom_right: Val,2334pub bottom_left: Val,2335}23362337impl Default for BorderRadius {2338fn default() -> Self {2339Self::DEFAULT2340}2341}23422343impl BorderRadius {2344pub const DEFAULT: Self = Self::ZERO;23452346/// Zero curvature. All the corners will be right-angled.2347pub const ZERO: Self = Self::all(Val::Px(0.));23482349/// Maximum curvature. The UI Node will take a capsule shape or circular if width and height are equal.2350pub const MAX: Self = Self::all(Val::Px(f32::MAX));23512352#[inline]2353/// Set all four corners to the same curvature.2354pub const fn all(radius: Val) -> Self {2355Self {2356top_left: radius,2357top_right: radius,2358bottom_left: radius,2359bottom_right: radius,2360}2361}23622363#[inline]2364pub const fn new(top_left: Val, top_right: Val, bottom_right: Val, bottom_left: Val) -> Self {2365Self {2366top_left,2367top_right,2368bottom_right,2369bottom_left,2370}2371}23722373#[inline]2374/// Sets the radii to logical pixel values.2375pub const fn px(top_left: f32, top_right: f32, bottom_right: f32, bottom_left: f32) -> Self {2376Self {2377top_left: Val::Px(top_left),2378top_right: Val::Px(top_right),2379bottom_right: Val::Px(bottom_right),2380bottom_left: Val::Px(bottom_left),2381}2382}23832384#[inline]2385/// Sets the radii to percentage values.2386pub const fn percent(2387top_left: f32,2388top_right: f32,2389bottom_right: f32,2390bottom_left: f32,2391) -> Self {2392Self {2393top_left: Val::Percent(top_left),2394top_right: Val::Percent(top_right),2395bottom_right: Val::Percent(bottom_right),2396bottom_left: Val::Percent(bottom_left),2397}2398}23992400#[inline]2401/// Sets the radius for the top left corner.2402/// Remaining corners will be right-angled.2403pub const fn top_left(radius: Val) -> Self {2404Self {2405top_left: radius,2406..Self::DEFAULT2407}2408}24092410#[inline]2411/// Sets the radius for the top right corner.2412/// Remaining corners will be right-angled.2413pub const fn top_right(radius: Val) -> Self {2414Self {2415top_right: radius,2416..Self::DEFAULT2417}2418}24192420#[inline]2421/// Sets the radius for the bottom right corner.2422/// Remaining corners will be right-angled.2423pub const fn bottom_right(radius: Val) -> Self {2424Self {2425bottom_right: radius,2426..Self::DEFAULT2427}2428}24292430#[inline]2431/// Sets the radius for the bottom left corner.2432/// Remaining corners will be right-angled.2433pub const fn bottom_left(radius: Val) -> Self {2434Self {2435bottom_left: radius,2436..Self::DEFAULT2437}2438}24392440#[inline]2441/// Sets the radii for the top left and bottom left corners.2442/// Remaining corners will be right-angled.2443pub const fn left(radius: Val) -> Self {2444Self {2445top_left: radius,2446bottom_left: radius,2447..Self::DEFAULT2448}2449}24502451#[inline]2452/// Sets the radii for the top right and bottom right corners.2453/// Remaining corners will be right-angled.2454pub const fn right(radius: Val) -> Self {2455Self {2456top_right: radius,2457bottom_right: radius,2458..Self::DEFAULT2459}2460}24612462#[inline]2463/// Sets the radii for the top left and top right corners.2464/// Remaining corners will be right-angled.2465pub const fn top(radius: Val) -> Self {2466Self {2467top_left: radius,2468top_right: radius,2469..Self::DEFAULT2470}2471}24722473#[inline]2474/// Sets the radii for the bottom left and bottom right corners.2475/// Remaining corners will be right-angled.2476pub const fn bottom(radius: Val) -> Self {2477Self {2478bottom_left: radius,2479bottom_right: radius,2480..Self::DEFAULT2481}2482}24832484/// Returns the [`BorderRadius`] with its `top_left` field set to the given value.2485#[inline]2486pub const fn with_top_left(mut self, radius: Val) -> Self {2487self.top_left = radius;2488self2489}24902491/// Returns the [`BorderRadius`] with its `top_right` field set to the given value.2492#[inline]2493pub const fn with_top_right(mut self, radius: Val) -> Self {2494self.top_right = radius;2495self2496}24972498/// Returns the [`BorderRadius`] with its `bottom_right` field set to the given value.2499#[inline]2500pub const fn with_bottom_right(mut self, radius: Val) -> Self {2501self.bottom_right = radius;2502self2503}25042505/// Returns the [`BorderRadius`] with its `bottom_left` field set to the given value.2506#[inline]2507pub const fn with_bottom_left(mut self, radius: Val) -> Self {2508self.bottom_left = radius;2509self2510}25112512/// Returns the [`BorderRadius`] with its `top_left` and `bottom_left` fields set to the given value.2513#[inline]2514pub const fn with_left(mut self, radius: Val) -> Self {2515self.top_left = radius;2516self.bottom_left = radius;2517self2518}25192520/// Returns the [`BorderRadius`] with its `top_right` and `bottom_right` fields set to the given value.2521#[inline]2522pub const fn with_right(mut self, radius: Val) -> Self {2523self.top_right = radius;2524self.bottom_right = radius;2525self2526}25272528/// Returns the [`BorderRadius`] with its `top_left` and `top_right` fields set to the given value.2529#[inline]2530pub const fn with_top(mut self, radius: Val) -> Self {2531self.top_left = radius;2532self.top_right = radius;2533self2534}25352536/// Returns the [`BorderRadius`] with its `bottom_left` and `bottom_right` fields set to the given value.2537#[inline]2538pub const fn with_bottom(mut self, radius: Val) -> Self {2539self.bottom_left = radius;2540self.bottom_right = radius;2541self2542}25432544/// Resolve the border radius for a single corner from the given context values.2545/// Returns the radius of the corner in physical pixels.2546pub const fn resolve_single_corner(2547radius: Val,2548scale_factor: f32,2549min_length: f32,2550viewport_size: Vec2,2551) -> f32 {2552if let Ok(radius) = radius.resolve(scale_factor, min_length, viewport_size) {2553radius.clamp(0., 0.5 * min_length)2554} else {25550.2556}2557}25582559/// Resolve the border radii for the corners from the given context values.2560/// Returns the radii of the each corner in physical pixels.2561pub const fn resolve(2562&self,2563scale_factor: f32,2564node_size: Vec2,2565viewport_size: Vec2,2566) -> ResolvedBorderRadius {2567let length = node_size.x.min(node_size.y);2568ResolvedBorderRadius {2569top_left: Self::resolve_single_corner(2570self.top_left,2571scale_factor,2572length,2573viewport_size,2574),2575top_right: Self::resolve_single_corner(2576self.top_right,2577scale_factor,2578length,2579viewport_size,2580),2581bottom_left: Self::resolve_single_corner(2582self.bottom_left,2583scale_factor,2584length,2585viewport_size,2586),2587bottom_right: Self::resolve_single_corner(2588self.bottom_right,2589scale_factor,2590length,2591viewport_size,2592),2593}2594}2595}25962597/// Represents the resolved border radius values for a UI node.2598///2599/// The values are in physical pixels.2600#[derive(Copy, Clone, Debug, Default, PartialEq, Reflect)]2601#[reflect(Clone, PartialEq, Default)]2602pub struct ResolvedBorderRadius {2603pub top_left: f32,2604pub top_right: f32,2605pub bottom_right: f32,2606pub bottom_left: f32,2607}26082609impl ResolvedBorderRadius {2610pub const ZERO: Self = Self {2611top_left: 0.,2612top_right: 0.,2613bottom_right: 0.,2614bottom_left: 0.,2615};2616}26172618impl From<ResolvedBorderRadius> for [f32; 4] {2619fn from(radius: ResolvedBorderRadius) -> Self {2620[2621radius.top_left,2622radius.top_right,2623radius.bottom_right,2624radius.bottom_left,2625]2626}2627}26282629#[derive(Component, Clone, Debug, Default, PartialEq, Reflect, Deref, DerefMut)]2630#[reflect(Component, PartialEq, Default, Clone)]2631#[cfg_attr(2632feature = "serialize",2633derive(serde::Serialize, serde::Deserialize),2634reflect(Serialize, Deserialize)2635)]2636/// List of shadows to draw for a [`Node`].2637///2638/// Draw order is determined implicitly from the vector of [`ShadowStyle`]s, back-to-front.2639pub struct BoxShadow(pub Vec<ShadowStyle>);26402641impl BoxShadow {2642/// A single drop shadow2643pub fn new(2644color: Color,2645x_offset: Val,2646y_offset: Val,2647spread_radius: Val,2648blur_radius: Val,2649) -> Self {2650Self(vec![ShadowStyle {2651color,2652x_offset,2653y_offset,2654spread_radius,2655blur_radius,2656}])2657}2658}26592660impl From<ShadowStyle> for BoxShadow {2661fn from(value: ShadowStyle) -> Self {2662Self(vec![value])2663}2664}26652666#[derive(Copy, Clone, Debug, PartialEq, Reflect)]2667#[reflect(PartialEq, Default, Clone)]2668#[cfg_attr(2669feature = "serialize",2670derive(serde::Serialize, serde::Deserialize),2671reflect(Serialize, Deserialize)2672)]2673pub struct ShadowStyle {2674/// The shadow's color2675pub color: Color,2676/// Horizontal offset2677pub x_offset: Val,2678/// Vertical offset2679pub y_offset: Val,2680/// How much the shadow should spread outward.2681///2682/// Negative values will make the shadow shrink inwards.2683/// Percentage values are based on the width of the UI node.2684pub spread_radius: Val,2685/// Blurriness of the shadow2686pub blur_radius: Val,2687}26882689impl Default for ShadowStyle {2690fn default() -> Self {2691Self {2692color: Color::BLACK,2693x_offset: Val::Percent(20.),2694y_offset: Val::Percent(20.),2695spread_radius: Val::ZERO,2696blur_radius: Val::Percent(10.),2697}2698}2699}27002701#[derive(Component, Copy, Clone, Debug, PartialEq, Reflect)]2702#[reflect(Component, Debug, PartialEq, Default, Clone)]2703#[cfg_attr(2704feature = "serialize",2705derive(serde::Serialize, serde::Deserialize),2706reflect(Serialize, Deserialize)2707)]2708/// This component can be added to any UI node to modify its layout behavior.2709pub struct LayoutConfig {2710/// If set to true the coordinates for this node and its descendents will be rounded to the nearest physical pixel.2711/// This can help prevent visual artifacts like blurry images or semi-transparent edges that can occur with sub-pixel positioning.2712///2713/// Defaults to true.2714pub use_rounding: bool,2715}27162717impl Default for LayoutConfig {2718fn default() -> Self {2719Self { use_rounding: true }2720}2721}27222723/// Indicates that this root [`Node`] entity should be rendered to a specific camera.2724///2725/// UI then will be laid out respecting the camera's viewport and scale factor, and2726/// rendered to this camera's [`bevy_camera::RenderTarget`].2727///2728/// Setting this component on a non-root node will have no effect. It will be overridden2729/// by the root node's component.2730///2731/// Root node's without an explicit [`UiTargetCamera`] will be rendered to the default UI camera,2732/// which is either a single camera with the [`IsDefaultUiCamera`] marker component or the highest2733/// order camera targeting the primary window.2734#[derive(Component, Clone, Debug, Reflect, Eq, PartialEq)]2735#[reflect(Component, Debug, PartialEq, Clone)]2736pub struct UiTargetCamera(pub Entity);27372738impl UiTargetCamera {2739pub fn entity(&self) -> Entity {2740self.02741}2742}27432744/// Marker used to identify default cameras, they will have priority over the [`PrimaryWindow`] camera.2745///2746/// This is useful if the [`PrimaryWindow`] has two cameras, one of them used2747/// just for debug purposes and the user wants a way to choose the default [`Camera`]2748/// without having to add a [`UiTargetCamera`] to the root node.2749///2750/// Another use is when the user wants the Ui to be in another window by default,2751/// all that is needed is to place this component on the camera2752///2753/// ```2754/// # use bevy_ui::prelude::*;2755/// # use bevy_ecs::prelude::Commands;2756/// # use bevy_camera::{Camera, Camera2d, RenderTarget};2757/// # use bevy_window::{Window, WindowRef};2758///2759/// fn spawn_camera(mut commands: Commands) {2760/// let another_window = commands.spawn(Window {2761/// title: String::from("Another window"),2762/// ..Default::default()2763/// }).id();2764/// commands.spawn((2765/// Camera2d,2766/// Camera {2767/// target: RenderTarget::Window(WindowRef::Entity(another_window)),2768/// ..Default::default()2769/// },2770/// // We add the Marker here so all Ui will spawn in2771/// // another window if no UiTargetCamera is specified2772/// IsDefaultUiCamera2773/// ));2774/// }2775/// ```2776#[derive(Component, Default)]2777pub struct IsDefaultUiCamera;27782779#[derive(SystemParam)]2780pub struct DefaultUiCamera<'w, 's> {2781cameras: Query<'w, 's, (Entity, &'static Camera)>,2782default_cameras: Query<'w, 's, Entity, (With<Camera>, With<IsDefaultUiCamera>)>,2783primary_window: Query<'w, 's, Entity, With<PrimaryWindow>>,2784}27852786impl<'w, 's> DefaultUiCamera<'w, 's> {2787pub fn get(&self) -> Option<Entity> {2788self.default_cameras.single().ok().or_else(|| {2789// If there isn't a single camera and the query isn't empty, there is two or more cameras queried.2790if !self.default_cameras.is_empty() {2791once!(warn!("Two or more Entities with IsDefaultUiCamera found when only one Camera with this marker is allowed."));2792}2793self.cameras2794.iter()2795.filter(|(_, c)| match c.target {2796RenderTarget::Window(WindowRef::Primary) => true,2797RenderTarget::Window(WindowRef::Entity(w)) => {2798self.primary_window.get(w).is_ok()2799}2800_ => false,2801})2802.max_by_key(|(e, c)| (c.order, *e))2803.map(|(e, _)| e)2804})2805}2806}28072808/// Derived information about the camera target for this UI node.2809///2810/// Updated in [`UiSystems::Prepare`](crate::UiSystems::Prepare) by [`propagate_ui_target_cameras`](crate::update::propagate_ui_target_cameras)2811#[derive(Component, Clone, Copy, Debug, Reflect, PartialEq)]2812#[reflect(Component, Default, PartialEq, Clone)]2813pub struct ComputedUiTargetCamera {2814pub(crate) camera: Entity,2815}28162817impl Default for ComputedUiTargetCamera {2818fn default() -> Self {2819Self {2820camera: Entity::PLACEHOLDER,2821}2822}2823}28242825impl ComputedUiTargetCamera {2826/// Returns the id of the target camera for this UI node.2827pub fn get(&self) -> Option<Entity> {2828Some(self.camera).filter(|&entity| entity != Entity::PLACEHOLDER)2829}2830}28312832/// Derived information about the render target for this UI node.2833#[derive(Component, Clone, Copy, Debug, Reflect, PartialEq)]2834#[reflect(Component, Default, PartialEq, Clone)]2835pub struct ComputedUiRenderTargetInfo {2836/// The scale factor of the target camera's render target.2837pub(crate) scale_factor: f32,2838/// The size of the target camera's viewport in physical pixels.2839pub(crate) physical_size: UVec2,2840}28412842impl Default for ComputedUiRenderTargetInfo {2843fn default() -> Self {2844Self {2845scale_factor: 1.,2846physical_size: UVec2::ZERO,2847}2848}2849}28502851impl ComputedUiRenderTargetInfo {2852pub const fn scale_factor(&self) -> f32 {2853self.scale_factor2854}28552856/// Returns the size of the target camera's viewport in physical pixels.2857pub const fn physical_size(&self) -> UVec2 {2858self.physical_size2859}28602861/// Returns the size of the target camera's viewport in logical pixels.2862pub fn logical_size(&self) -> Vec2 {2863self.physical_size.as_vec2() / self.scale_factor2864}2865}28662867#[cfg(test)]2868mod tests {2869use crate::GridPlacement;28702871#[test]2872fn invalid_grid_placement_values() {2873assert!(std::panic::catch_unwind(|| GridPlacement::span(0)).is_err());2874assert!(std::panic::catch_unwind(|| GridPlacement::start(0)).is_err());2875assert!(std::panic::catch_unwind(|| GridPlacement::end(0)).is_err());2876assert!(std::panic::catch_unwind(|| GridPlacement::start_end(0, 1)).is_err());2877assert!(std::panic::catch_unwind(|| GridPlacement::start_end(-1, 0)).is_err());2878assert!(std::panic::catch_unwind(|| GridPlacement::start_span(1, 0)).is_err());2879assert!(std::panic::catch_unwind(|| GridPlacement::start_span(0, 1)).is_err());2880assert!(std::panic::catch_unwind(|| GridPlacement::end_span(0, 1)).is_err());2881assert!(std::panic::catch_unwind(|| GridPlacement::end_span(1, 0)).is_err());2882assert!(std::panic::catch_unwind(|| GridPlacement::default().set_start(0)).is_err());2883assert!(std::panic::catch_unwind(|| GridPlacement::default().set_end(0)).is_err());2884assert!(std::panic::catch_unwind(|| GridPlacement::default().set_span(0)).is_err());2885}28862887#[test]2888fn grid_placement_accessors() {2889assert_eq!(GridPlacement::start(5).get_start(), Some(5));2890assert_eq!(GridPlacement::end(-4).get_end(), Some(-4));2891assert_eq!(GridPlacement::span(2).get_span(), Some(2));2892assert_eq!(GridPlacement::start_end(11, 21).get_span(), None);2893assert_eq!(GridPlacement::start_span(3, 5).get_end(), None);2894assert_eq!(GridPlacement::end_span(-4, 12).get_start(), None);2895}2896}289728982899