use crate::{UiRect, Val};
use bevy_asset::Handle;
use bevy_ecs::{prelude::Component, reflect::ReflectComponent};
use bevy_math::{Rect, Vec2};
use bevy_reflect::prelude::*;
use bevy_render::{color::Color, texture::Image};
use bevy_transform::prelude::GlobalTransform;
use serde::{Deserialize, Serialize};
use smallvec::SmallVec;
use std::num::{NonZeroI16, NonZeroU16};
use thiserror::Error;
/// Describes the size of a UI node
#[derive(Component, Debug, Copy, Clone, Reflect)]
#[reflect(Component, Default)]
pub struct Node {
/// The size of the node as width and height in logical pixels
/// automatically calculated by [`super::layout::ui_layout_system`]
pub(crate) calculated_size: Vec2,
}
impl Node {
/// The calculated node size as width and height in logical pixels
/// automatically calculated by [`super::layout::ui_layout_system`]
pub const fn size(&self) -> Vec2 {
self.calculated_size
}
/// Returns the size of the node in physical pixels based on the given scale factor and `UiScale`.
#[inline]
pub fn physical_size(&self, scale_factor: f64, ui_scale: f64) -> Vec2 {
Vec2::new(
(self.calculated_size.x as f64 * scale_factor * ui_scale) as f32,
(self.calculated_size.y as f64 * scale_factor * ui_scale) as f32,
)
}
/// Returns the logical pixel coordinates of the UI node, based on its [`GlobalTransform`].
#[inline]
pub fn logical_rect(&self, transform: &GlobalTransform) -> Rect {
Rect::from_center_size(transform.translation().truncate(), self.size())
}
/// Returns the physical pixel coordinates of the UI node, based on its [`GlobalTransform`] and the scale factor.
#[inline]
pub fn physical_rect(
&self,
transform: &GlobalTransform,
scale_factor: f64,
ui_scale: f64,
) -> Rect {
let rect = self.logical_rect(transform);
Rect {
min: Vec2::new(
(rect.min.x as f64 * scale_factor * ui_scale) as f32,
(rect.min.y as f64 * scale_factor * ui_scale) as f32,
),
max: Vec2::new(
(rect.max.x as f64 * scale_factor * ui_scale) as f32,
(rect.max.y as f64 * scale_factor * ui_scale) as f32,
),
}
}
}
impl Node {
pub const DEFAULT: Self = Self {
calculated_size: Vec2::ZERO,
};
}
impl Default for Node {
fn default() -> Self {
Self::DEFAULT
}
}
/// Describes the style of a UI container node
///
/// Node's can be laid out using either Flexbox or CSS Grid Layout.
/// See below for general learning resources and for documentation on the individual style properties.
///
/// ### Flexbox
///
/// - [MDN: Basic Concepts of Flexbox](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Flexible_Box_Layout/Basic_Concepts_of_Flexbox)
/// - [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.
/// - [Flexbox Froggy](https://flexboxfroggy.com/). An interactive tutorial/game that teaches the essential parts of Flexbox in a fun engaging way.
///
/// ### CSS Grid
///
/// - [MDN: Basic Concepts of Grid Layout](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Grid_Layout/Basic_Concepts_of_Grid_Layout)
/// - [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.
/// - [CSS Grid Garden](https://cssgridgarden.com/). An interactive tutorial/game that teaches the essential parts of CSS Grid in a fun engaging way.
#[derive(Component, Clone, PartialEq, Debug, Reflect)]
#[reflect(Component, Default, PartialEq)]
pub struct Style {
/// Which layout algorithm to use when laying out this node's contents:
/// - [`Display::Flex`]: Use the Flexbox layout algorithm
/// - [`Display::Grid`]: Use the CSS Grid layout algorithm
/// - [`Display::None`]: Hide this node and perform layout as if it does not exist.
///
///
pub display: Display,
/// Whether a node should be laid out in-flow with, or independently of it's siblings:
/// - [`PositionType::Relative`]: Layout this node in-flow with other nodes using the usual (flexbox/grid) layout algorithm.
/// - [`PositionType::Absolute`]: Layout this node on top and independently of other nodes.
///
///
pub position_type: PositionType,
/// Whether overflowing content should be displayed or clipped.
///
///
pub overflow: Overflow,
/// Defines the text direction. For example English is written LTR (left-to-right) while Arabic is written RTL (right-to-left).
///
/// Note: the corresponding CSS property also affects box layout order, but this isn't yet implemented in bevy.
///
pub direction: Direction,
/// The horizontal position of the left edge of the node.
/// - For relatively positioned nodes, this is relative to the node's position as computed during regular layout.
/// - For absolutely positioned nodes, this is relative to the *parent* node's bounding box.
///
///
pub left: Val,
/// The horizontal position of the right edge of the node.
/// - For relatively positioned nodes, this is relative to the node's position as computed during regular layout.
/// - For absolutely positioned nodes, this is relative to the *parent* node's bounding box.
///
///
pub right: Val,
/// The vertical position of the top edge of the node.
/// - For relatively positioned nodes, this is relative to the node's position as computed during regular layout.
/// - For absolutely positioned nodes, this is relative to the *parent* node's bounding box.
///
///
pub top: Val,
/// The vertical position of the bottom edge of the node.
/// - For relatively positioned nodes, this is relative to the node's position as computed during regular layout.
/// - For absolutely positioned nodes, this is relative to the *parent* node's bounding box.
///
///
pub bottom: Val,
/// The ideal width of the node. `width` is used when it is within the bounds defined by `min_width` and `max_width`.
///
///
pub width: Val,
/// The ideal height of the node. `height` is used when it is within the bounds defined by `min_height` and `max_height`.
///
///
pub height: Val,
/// The minimum width of the node. `min_width` is used if it is greater than either `width` and/or `max_width`.
///
///
pub min_width: Val,
/// The minimum height of the node. `min_height` is used if it is greater than either `height` and/or `max_height`.
///
///
pub min_height: Val,
/// The maximum width of the node. `max_width` is used if it is within the bounds defined by `min_width` and `width`.
///
///
pub max_width: Val,
/// The maximum height of the node. `max_height` is used if it is within the bounds defined by `min_height` and `height`.
///
///
pub max_height: Val,
/// The aspect ratio of the node (defined as `width / height`)
///
///
pub aspect_ratio: Option,
/// - For Flexbox containers, sets default cross-axis alignment of the child items.
/// - For CSS Grid containers, controls block (vertical) axis alignment of children of this grid container within their grid areas.
///
/// This value is overridden if [`AlignSelf`] on the child node is set.
///
///
pub align_items: AlignItems,
/// - For Flexbox containers, this property has no effect. See `justify_content` for main-axis alignment of flex items.
/// - For CSS Grid containers, sets default inline (horizontal) axis alignment of child items within their grid areas.
///
/// This value is overridden if [`JustifySelf`] on the child node is set.
///
///
pub justify_items: JustifyItems,
/// - For Flexbox items, controls cross-axis alignment of the item.
/// - For CSS Grid items, controls block (vertical) axis alignment of a grid item within it's grid area.
///
/// If set to `Auto`, alignment is inherited from the value of [`AlignItems`] set on the parent node.
///
///
pub align_self: AlignSelf,
/// - For Flexbox items, this property has no effect. See `justify_content` for main-axis alignment of flex items.
/// - For CSS Grid items, controls inline (horizontal) axis alignment of a grid item within it's grid area.
///
/// If set to `Auto`, alignment is inherited from the value of [`JustifyItems`] set on the parent node.
///
///
pub justify_self: JustifySelf,
/// - For Flexbox containers, controls alignment of lines if flex_wrap is set to [`FlexWrap::Wrap`] and there are multiple lines of items.
/// - For CSS Grid containers, controls alignment of grid rows.
///
///
pub align_content: AlignContent,
/// - For Flexbox containers, controls alignment of items in the main axis.
/// - For CSS Grid containers, controls alignment of grid columns.
///
///
pub justify_content: JustifyContent,
/// The amount of space around a node outside its border.
///
/// If a percentage value is used, the percentage is calculated based on the width of the parent node.
///
/// # Example
/// ```
/// # use bevy_ui::{Style, UiRect, Val};
/// let style = Style {
/// margin: UiRect {
/// left: Val::Percent(10.),
/// right: Val::Percent(10.),
/// top: Val::Percent(15.),
/// bottom: Val::Percent(15.)
/// },
/// ..Default::default()
/// };
/// ```
/// 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.
///
///
pub margin: UiRect,
/// The amount of space between the edges of a node and its contents.
///
/// If a percentage value is used, the percentage is calculated based on the width of the parent node.
///
/// # Example
/// ```
/// # use bevy_ui::{Style, UiRect, Val};
/// let style = Style {
/// padding: UiRect {
/// left: Val::Percent(1.),
/// right: Val::Percent(2.),
/// top: Val::Percent(3.),
/// bottom: Val::Percent(4.)
/// },
/// ..Default::default()
/// };
/// ```
/// 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.
///
///
pub padding: UiRect,
/// The amount of space between the margins of a node and its padding.
///
/// If a percentage value is used, the percentage is calculated based on the width of the parent node.
///
/// 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.
///
///
pub border: UiRect,
/// Whether a Flexbox container should be a row or a column. This property has no effect of Grid nodes.
///
///
pub flex_direction: FlexDirection,
/// Whether a Flexbox container should wrap it's contents onto multiple line wrap if they overflow. This property has no effect of Grid nodes.
///
///
pub flex_wrap: FlexWrap,
/// Defines how much a flexbox item should grow if there's space available. Defaults to 0 (don't grow at all).
///
///
pub flex_grow: f32,
/// Defines how much a flexbox item should shrink if there's not enough space available. Defaults to 1.
///
///
pub flex_shrink: f32,
/// The initial length of a flexbox in the main axis, before flex growing/shrinking properties are applied.
///
/// `flex_basis` overrides `size` on the main axis if both are set, but it obeys the bounds defined by `min_size` and `max_size`.
///
///
pub flex_basis: Val,
/// The size of the gutters between items in a vertical flexbox layout or between rows in a grid layout
///
/// Note: Values of `Val::Auto` are not valid and are treated as zero.
///
///
pub row_gap: Val,
/// The size of the gutters between items in a horizontal flexbox layout or between column in a grid layout
///
/// Note: Values of `Val::Auto` are not valid and are treated as zero.
///
///
pub column_gap: Val,
/// Controls whether automatically placed grid items are placed row-wise or column-wise. And whether the sparse or dense packing algorithm is used.
/// Only affect Grid layouts
///
///
pub grid_auto_flow: GridAutoFlow,
/// Defines the number of rows a grid has and the sizes of those rows. If grid items are given explicit placements then more rows may
/// be implicitly generated by items that are placed out of bounds. The sizes of those rows are controlled by `grid_auto_rows` property.
///
///
pub grid_template_rows: Vec,
/// Defines the number of columns a grid has and the sizes of those columns. If grid items are given explicit placements then more columns may
/// be implicitly generated by items that are placed out of bounds. The sizes of those columns are controlled by `grid_auto_columns` property.
///
///
pub grid_template_columns: Vec,
/// Defines the size of implicitly created rows. Rows are created implicitly when grid items are given explicit placements that are out of bounds
/// of the rows explicitly created using `grid_template_rows`.
///
///
pub grid_auto_rows: Vec,
/// Defines the size of implicitly created columns. Columns are created implicitly when grid items are given explicit placements that are out of bounds
/// of the columns explicitly created using `grid_template_columns`.
///
///
pub grid_auto_columns: Vec,
/// The row in which a grid item starts and how many rows it spans.
///
///
pub grid_row: GridPlacement,
/// The column in which a grid item starts and how many columns it spans.
///
///
pub grid_column: GridPlacement,
}
impl Style {
pub const DEFAULT: Self = Self {
display: Display::DEFAULT,
position_type: PositionType::DEFAULT,
left: Val::Auto,
right: Val::Auto,
top: Val::Auto,
bottom: Val::Auto,
direction: Direction::DEFAULT,
flex_direction: FlexDirection::DEFAULT,
flex_wrap: FlexWrap::DEFAULT,
align_items: AlignItems::DEFAULT,
justify_items: JustifyItems::DEFAULT,
align_self: AlignSelf::DEFAULT,
justify_self: JustifySelf::DEFAULT,
align_content: AlignContent::DEFAULT,
justify_content: JustifyContent::DEFAULT,
margin: UiRect::DEFAULT,
padding: UiRect::DEFAULT,
border: UiRect::DEFAULT,
flex_grow: 0.0,
flex_shrink: 1.0,
flex_basis: Val::Auto,
width: Val::Auto,
height: Val::Auto,
min_width: Val::Auto,
min_height: Val::Auto,
max_width: Val::Auto,
max_height: Val::Auto,
aspect_ratio: None,
overflow: Overflow::DEFAULT,
row_gap: Val::ZERO,
column_gap: Val::ZERO,
grid_auto_flow: GridAutoFlow::DEFAULT,
grid_template_rows: Vec::new(),
grid_template_columns: Vec::new(),
grid_auto_rows: Vec::new(),
grid_auto_columns: Vec::new(),
grid_column: GridPlacement::DEFAULT,
grid_row: GridPlacement::DEFAULT,
};
}
impl Default for Style {
fn default() -> Self {
Self::DEFAULT
}
}
/// How items are aligned according to the cross axis
#[derive(Copy, Clone, PartialEq, Eq, Debug, Serialize, Deserialize, Reflect)]
#[reflect(PartialEq, Serialize, Deserialize)]
pub enum AlignItems {
/// The items are packed in their default position as if no alignment was applied
Default,
/// Items are packed towards the start of the axis.
Start,
/// Items are packed towards the end of the axis.
End,
/// Items are packed towards the start of the axis, unless the flex direction is reversed;
/// then they are packed towards the end of the axis.
FlexStart,
/// Items are packed towards the end of the axis, unless the flex direction is reversed;
/// then they are packed towards the start of the axis.
FlexEnd,
/// Items are aligned at the center.
Center,
/// Items are aligned at the baseline.
Baseline,
/// Items are stretched across the whole cross axis.
Stretch,
}
impl AlignItems {
pub const DEFAULT: Self = Self::Default;
}
impl Default for AlignItems {
fn default() -> Self {
Self::DEFAULT
}
}
/// How items are aligned according to the main axis
#[derive(Copy, Clone, PartialEq, Eq, Debug, Serialize, Deserialize, Reflect)]
#[reflect(PartialEq, Serialize, Deserialize)]
pub enum JustifyItems {
/// The items are packed in their default position as if no alignment was applied
Default,
/// Items are packed towards the start of the axis.
Start,
/// Items are packed towards the end of the axis.
End,
/// Items are aligned at the center.
Center,
/// Items are aligned at the baseline.
Baseline,
/// Items are stretched across the whole main axis.
Stretch,
}
impl JustifyItems {
pub const DEFAULT: Self = Self::Default;
}
impl Default for JustifyItems {
fn default() -> Self {
Self::DEFAULT
}
}
/// How this item is aligned according to the cross axis.
/// Overrides [`AlignItems`].
#[derive(Copy, Clone, PartialEq, Eq, Debug, Serialize, Deserialize, Reflect)]
#[reflect(PartialEq, Serialize, Deserialize)]
pub enum AlignSelf {
/// Use the parent node's [`AlignItems`] value to determine how this item should be aligned.
Auto,
/// This item will be aligned with the start of the axis.
Start,
/// This item will be aligned with the end of the axis.
End,
/// This item will be aligned with the start of the axis, unless the flex direction is reversed;
/// then it will be aligned with the end of the axis.
FlexStart,
/// This item will be aligned with the end of the axis, unless the flex direction is reversed;
/// then it will be aligned with the start of the axis.
FlexEnd,
/// This item will be aligned at the center.
Center,
/// This item will be aligned at the baseline.
Baseline,
/// This item will be stretched across the whole cross axis.
Stretch,
}
impl AlignSelf {
pub const DEFAULT: Self = Self::Auto;
}
impl Default for AlignSelf {
fn default() -> Self {
Self::DEFAULT
}
}
/// How this item is aligned according to the main axis.
/// Overrides [`JustifyItems`].
#[derive(Copy, Clone, PartialEq, Eq, Debug, Serialize, Deserialize, Reflect)]
#[reflect(PartialEq, Serialize, Deserialize)]
pub enum JustifySelf {
/// Use the parent node's [`JustifyItems`] value to determine how this item should be aligned.
Auto,
/// This item will be aligned with the start of the axis.
Start,
/// This item will be aligned with the end of the axis.
End,
/// This item will be aligned at the center.
Center,
/// This item will be aligned at the baseline.
Baseline,
/// This item will be stretched across the whole main axis.
Stretch,
}
impl JustifySelf {
pub const DEFAULT: Self = Self::Auto;
}
impl Default for JustifySelf {
fn default() -> Self {
Self::DEFAULT
}
}
/// Defines how each line is aligned within the flexbox.
///
/// It only applies if [`FlexWrap::Wrap`] is present and if there are multiple lines of items.
#[derive(Copy, Clone, PartialEq, Eq, Debug, Serialize, Deserialize, Reflect)]
#[reflect(PartialEq, Serialize, Deserialize)]
pub enum AlignContent {
/// The items are packed in their default position as if no alignment was applied
Default,
/// Each line moves towards the start of the cross axis.
Start,
/// Each line moves towards the end of the cross axis.
End,
/// Each line moves towards the start of the cross axis, unless the flex direction is reversed; then the line moves towards the end of the cross axis.
FlexStart,
/// Each line moves towards the end of the cross axis, unless the flex direction is reversed; then the line moves towards the start of the cross axis.
FlexEnd,
/// Each line moves towards the center of the cross axis.
Center,
/// Each line will stretch to fill the remaining space.
Stretch,
/// Each line fills the space it needs, putting the remaining space, if any
/// inbetween the lines.
SpaceBetween,
/// The gap between the first and last items is exactly THE SAME as the gap between items.
/// The gaps are distributed evenly.
SpaceEvenly,
/// Each line fills the space it needs, putting the remaining space, if any
/// around the lines.
SpaceAround,
}
impl AlignContent {
pub const DEFAULT: Self = Self::Default;
}
impl Default for AlignContent {
fn default() -> Self {
Self::DEFAULT
}
}
/// Defines how items are aligned according to the main axis
#[derive(Copy, Clone, PartialEq, Eq, Debug, Serialize, Deserialize, Reflect)]
#[reflect(PartialEq, Serialize, Deserialize)]
pub enum JustifyContent {
/// The items are packed in their default position as if no alignment was applied
Default,
/// Items are packed toward the start of the axis.
Start,
/// Items are packed toward the end of the axis.
End,
/// Pushed towards the start, unless the flex direction is reversed; then pushed towards the end.
FlexStart,
/// Pushed towards the end, unless the flex direction is reversed; then pushed towards the start.
FlexEnd,
/// Centered along the main axis.
Center,
/// Remaining space is distributed between the items.
SpaceBetween,
/// Remaining space is distributed around the items.
SpaceAround,
/// Like [`JustifyContent::SpaceAround`] but with even spacing between items.
SpaceEvenly,
}
impl JustifyContent {
pub const DEFAULT: Self = Self::Default;
}
impl Default for JustifyContent {
fn default() -> Self {
Self::DEFAULT
}
}
/// Defines the text direction
///
/// For example English is written LTR (left-to-right) while Arabic is written RTL (right-to-left).
#[derive(Copy, Clone, PartialEq, Eq, Debug, Serialize, Deserialize, Reflect)]
#[reflect(PartialEq, Serialize, Deserialize)]
pub enum Direction {
/// Inherit from parent node.
Inherit,
/// Text is written left to right.
LeftToRight,
/// Text is written right to left.
RightToLeft,
}
impl Direction {
pub const DEFAULT: Self = Self::Inherit;
}
impl Default for Direction {
fn default() -> Self {
Self::DEFAULT
}
}
/// Whether to use a Flexbox layout model.
///
/// Part of the [`Style`] component.
#[derive(Copy, Clone, PartialEq, Eq, Debug, Serialize, Deserialize, Reflect)]
#[reflect(PartialEq, Serialize, Deserialize)]
pub enum Display {
/// Use Flexbox layout model to determine the position of this [`Node`].
Flex,
/// Use CSS Grid layout model to determine the position of this [`Node`].
Grid,
/// Use no layout, don't render this node and its children.
///
/// If you want to hide a node and its children,
/// but keep its layout in place, set its [`Visibility`](bevy_render::view::Visibility) component instead.
None,
}
impl Display {
pub const DEFAULT: Self = Self::Flex;
}
impl Default for Display {
fn default() -> Self {
Self::DEFAULT
}
}
/// Defines how flexbox items are ordered within a flexbox
#[derive(Copy, Clone, PartialEq, Eq, Debug, Serialize, Deserialize, Reflect)]
#[reflect(PartialEq, Serialize, Deserialize)]
pub enum FlexDirection {
/// Same way as text direction along the main axis.
Row,
/// Flex from top to bottom.
Column,
/// Opposite way as text direction along the main axis.
RowReverse,
/// Flex from bottom to top.
ColumnReverse,
}
impl FlexDirection {
pub const DEFAULT: Self = Self::Row;
}
impl Default for FlexDirection {
fn default() -> Self {
Self::DEFAULT
}
}
/// Whether to show or hide overflowing items
#[derive(Copy, Clone, PartialEq, Eq, Debug, Reflect, Serialize, Deserialize)]
#[reflect(PartialEq, Serialize, Deserialize)]
pub struct Overflow {
/// Whether to show or clip overflowing items on the x axis
pub x: OverflowAxis,
/// Whether to show or clip overflowing items on the y axis
pub y: OverflowAxis,
}
impl Overflow {
pub const DEFAULT: Self = Self {
x: OverflowAxis::DEFAULT,
y: OverflowAxis::DEFAULT,
};
/// Show overflowing items on both axes
pub const fn visible() -> Self {
Self {
x: OverflowAxis::Visible,
y: OverflowAxis::Visible,
}
}
/// Clip overflowing items on both axes
pub const fn clip() -> Self {
Self {
x: OverflowAxis::Clip,
y: OverflowAxis::Clip,
}
}
/// Clip overflowing items on the x axis
pub const fn clip_x() -> Self {
Self {
x: OverflowAxis::Clip,
y: OverflowAxis::Visible,
}
}
/// Clip overflowing items on the y axis
pub const fn clip_y() -> Self {
Self {
x: OverflowAxis::Visible,
y: OverflowAxis::Clip,
}
}
/// Overflow is visible on both axes
pub const fn is_visible(&self) -> bool {
self.x.is_visible() && self.y.is_visible()
}
}
impl Default for Overflow {
fn default() -> Self {
Self::DEFAULT
}
}
/// Whether to show or hide overflowing items
#[derive(Copy, Clone, PartialEq, Eq, Debug, Reflect, Serialize, Deserialize)]
#[reflect(PartialEq, Serialize, Deserialize)]
pub enum OverflowAxis {
/// Show overflowing items.
Visible,
/// Hide overflowing items.
Clip,
}
impl OverflowAxis {
pub const DEFAULT: Self = Self::Visible;
/// Overflow is visible on this axis
pub const fn is_visible(&self) -> bool {
matches!(self, Self::Visible)
}
}
impl Default for OverflowAxis {
fn default() -> Self {
Self::DEFAULT
}
}
/// The strategy used to position this node
#[derive(Copy, Clone, PartialEq, Eq, Debug, Serialize, Deserialize, Reflect)]
#[reflect(PartialEq, Serialize, Deserialize)]
pub enum PositionType {
/// Relative to all other nodes with the [`PositionType::Relative`] value.
Relative,
/// Independent of all other nodes.
///
/// As usual, the `Style.position` field of this node is specified relative to its parent node.
Absolute,
}
impl PositionType {
pub const DEFAULT: Self = Self::Relative;
}
impl Default for PositionType {
fn default() -> Self {
Self::DEFAULT
}
}
/// Defines if flexbox items appear on a single line or on multiple lines
#[derive(Copy, Clone, PartialEq, Eq, Debug, Serialize, Deserialize, Reflect)]
#[reflect(PartialEq, Serialize, Deserialize)]
pub enum FlexWrap {
/// Single line, will overflow if needed.
NoWrap,
/// Multiple lines, if needed.
Wrap,
/// Same as [`FlexWrap::Wrap`] but new lines will appear before the previous one.
WrapReverse,
}
impl FlexWrap {
pub const DEFAULT: Self = Self::NoWrap;
}
impl Default for FlexWrap {
fn default() -> Self {
Self::DEFAULT
}
}
/// Controls whether grid items are placed row-wise or column-wise. And whether the sparse or dense packing algorithm is used.
///
/// The "dense" packing algorithm attempts to fill in holes earlier in the grid, if smaller items come up later. This may cause items to appear out-of-order, when doing so would fill in holes left by larger items.
///
/// Defaults to [`GridAutoFlow::Row`]
///
///
#[derive(Copy, Clone, PartialEq, Eq, Debug, Serialize, Deserialize, Reflect)]
#[reflect(PartialEq, Serialize, Deserialize)]
pub enum GridAutoFlow {
/// Items are placed by filling each row in turn, adding new rows as necessary
Row,
/// Items are placed by filling each column in turn, adding new columns as necessary.
Column,
/// Combines `Row` with the dense packing algorithm.
RowDense,
/// Combines `Column` with the dense packing algorithm.
ColumnDense,
}
impl GridAutoFlow {
pub const DEFAULT: Self = Self::Row;
}
impl Default for GridAutoFlow {
fn default() -> Self {
Self::DEFAULT
}
}
#[derive(Copy, Clone, PartialEq, Debug, Serialize, Deserialize, Reflect)]
#[reflect_value(PartialEq, Serialize, Deserialize)]
pub enum MinTrackSizingFunction {
/// Track minimum size should be a fixed pixel value
Px(f32),
/// Track minimum size should be a percentage value
Percent(f32),
/// Track minimum size should be content sized under a min-content constraint
MinContent,
/// Track minimum size should be content sized under a max-content constraint
MaxContent,
/// Track minimum size should be automatically sized
Auto,
}
#[derive(Copy, Clone, PartialEq, Debug, Serialize, Deserialize, Reflect)]
#[reflect_value(PartialEq, Serialize, Deserialize)]
pub enum MaxTrackSizingFunction {
/// Track maximum size should be a fixed pixel value
Px(f32),
/// Track maximum size should be a percentage value
Percent(f32),
/// Track maximum size should be content sized under a min-content constraint
MinContent,
/// Track maximum size should be content sized under a max-content constraint
MaxContent,
/// Track maximum size should be sized according to the fit-content formula with a fixed pixel limit
FitContentPx(f32),
/// Track maximum size should be sized according to the fit-content formula with a percentage limit
FitContentPercent(f32),
/// Track maximum size should be automatically sized
Auto,
/// The dimension as a fraction of the total available grid space (`fr` units in CSS)
/// Specified value is the numerator of the fraction. Denominator is the sum of all fractions specified in that grid dimension
/// Spec:
Fraction(f32),
}
/// A [`GridTrack`] is a Row or Column of a CSS Grid. This struct specifies what size the track should be.
/// See below for the different "track sizing functions" you can specify.
#[derive(Copy, Clone, PartialEq, Debug, Serialize, Deserialize, Reflect)]
#[reflect(PartialEq, Serialize, Deserialize)]
pub struct GridTrack {
pub(crate) min_sizing_function: MinTrackSizingFunction,
pub(crate) max_sizing_function: MaxTrackSizingFunction,
}
impl GridTrack {
pub const DEFAULT: Self = Self {
min_sizing_function: MinTrackSizingFunction::Auto,
max_sizing_function: MaxTrackSizingFunction::Auto,
};
/// Create a grid track with a fixed pixel size
pub fn px>(value: f32) -> T {
Self {
min_sizing_function: MinTrackSizingFunction::Px(value),
max_sizing_function: MaxTrackSizingFunction::Px(value),
}
.into()
}
/// Create a grid track with a percentage size
pub fn percent>(value: f32) -> T {
Self {
min_sizing_function: MinTrackSizingFunction::Percent(value),
max_sizing_function: MaxTrackSizingFunction::Percent(value),
}
.into()
}
/// Create a grid track with an `fr` size.
/// Note that this will give the track a content-based minimum size.
/// Usually you are best off using `GridTrack::flex` instead which uses a zero minimum size
pub fn fr>(value: f32) -> T {
Self {
min_sizing_function: MinTrackSizingFunction::Auto,
max_sizing_function: MaxTrackSizingFunction::Fraction(value),
}
.into()
}
/// Create a grid track with an `minmax(0, Nfr)` size.
pub fn flex>(value: f32) -> T {
Self {
min_sizing_function: MinTrackSizingFunction::Px(0.0),
max_sizing_function: MaxTrackSizingFunction::Fraction(value),
}
.into()
}
/// Create a grid track which is automatically sized to fit it's contents, and then
pub fn auto>() -> T {
Self {
min_sizing_function: MinTrackSizingFunction::Auto,
max_sizing_function: MaxTrackSizingFunction::Auto,
}
.into()
}
/// Create a grid track which is automatically sized to fit it's contents when sized at their "min-content" sizes
pub fn min_content>() -> T {
Self {
min_sizing_function: MinTrackSizingFunction::MinContent,
max_sizing_function: MaxTrackSizingFunction::MinContent,
}
.into()
}
/// Create a grid track which is automatically sized to fit it's contents when sized at their "max-content" sizes
pub fn max_content>() -> T {
Self {
min_sizing_function: MinTrackSizingFunction::MaxContent,
max_sizing_function: MaxTrackSizingFunction::MaxContent,
}
.into()
}
/// Create a fit-content() grid track with fixed pixel limit
///
///
pub fn fit_content_px>(limit: f32) -> T {
Self {
min_sizing_function: MinTrackSizingFunction::Auto,
max_sizing_function: MaxTrackSizingFunction::FitContentPx(limit),
}
.into()
}
/// Create a fit-content() grid track with percentage limit
///
///
pub fn fit_content_percent>(limit: f32) -> T {
Self {
min_sizing_function: MinTrackSizingFunction::Auto,
max_sizing_function: MaxTrackSizingFunction::FitContentPercent(limit),
}
.into()
}
/// Create a minmax() grid track
///
///
pub fn minmax>(min: MinTrackSizingFunction, max: MaxTrackSizingFunction) -> T {
Self {
min_sizing_function: min,
max_sizing_function: max,
}
.into()
}
}
impl Default for GridTrack {
fn default() -> Self {
Self::DEFAULT
}
}
#[derive(Copy, Clone, PartialEq, Debug, Serialize, Deserialize, Reflect)]
#[reflect(PartialEq, Serialize, Deserialize)]
/// How many times to repeat a repeated grid track
///
///
pub enum GridTrackRepetition {
/// Repeat the track fixed number of times
Count(u16),
/// Repeat the track to fill available space
///
///
AutoFill,
/// Repeat the track to fill available space but collapse any tracks that do not end up with
/// an item placed in them.
///
///
AutoFit,
}
impl From for GridTrackRepetition {
fn from(count: u16) -> Self {
Self::Count(count)
}
}
impl From for GridTrackRepetition {
fn from(count: i32) -> Self {
Self::Count(count as u16)
}
}
impl From for GridTrackRepetition {
fn from(count: usize) -> Self {
Self::Count(count as u16)
}
}
/// Represents a *possibly* repeated [`GridTrack`].
///
/// The repetition parameter can either be:
/// - The integer `1`, in which case the track is non-repeated.
/// - a `u16` count to repeat the track N times
/// - A `GridTrackRepetition::AutoFit` or `GridTrackRepetition::AutoFill`
///
/// Note: that in the common case you want a non-repeating track (repetition count 1), you may use the constructor methods on [`GridTrack`]
/// to create a `RepeatedGridTrack`. i.e. `GridTrack::px(10.0)` is equivalent to `RepeatedGridTrack::px(1, 10.0)`.
///
/// You may only use one auto-repetition per track list. And if your track list contains an auto repetition
/// then all track (in and outside of the repetition) must be fixed size (px or percent). Integer repetitions are just shorthand for writing out
/// N tracks longhand and are not subject to the same limitations.
#[derive(Clone, PartialEq, Debug, Serialize, Deserialize, Reflect)]
#[reflect(PartialEq, Serialize, Deserialize)]
pub struct RepeatedGridTrack {
pub(crate) repetition: GridTrackRepetition,
pub(crate) tracks: SmallVec<[GridTrack; 1]>,
}
impl RepeatedGridTrack {
/// Create a repeating set of grid tracks with a fixed pixel size
pub fn px>(repetition: impl Into, value: f32) -> T {
Self {
repetition: repetition.into(),
tracks: SmallVec::from_buf([GridTrack::px(value)]),
}
.into()
}
/// Create a repeating set of grid tracks with a percentage size
pub fn percent>(repetition: impl Into, value: f32) -> T {
Self {
repetition: repetition.into(),
tracks: SmallVec::from_buf([GridTrack::percent(value)]),
}
.into()
}
/// Create a repeating set of grid tracks with automatic size
pub fn auto>(repetition: u16) -> T {
Self {
repetition: GridTrackRepetition::Count(repetition),
tracks: SmallVec::from_buf([GridTrack::auto()]),
}
.into()
}
/// Create a repeating set of grid tracks with an `fr` size.
/// Note that this will give the track a content-based minimum size.
/// Usually you are best off using `GridTrack::flex` instead which uses a zero minimum size
pub fn fr>(repetition: u16, value: f32) -> T {
Self {
repetition: GridTrackRepetition::Count(repetition),
tracks: SmallVec::from_buf([GridTrack::fr(value)]),
}
.into()
}
/// Create a repeating set of grid tracks with an `minmax(0, Nfr)` size.
pub fn flex>(repetition: u16, value: f32) -> T {
Self {
repetition: GridTrackRepetition::Count(repetition),
tracks: SmallVec::from_buf([GridTrack::flex(value)]),
}
.into()
}
/// Create a repeating set of grid tracks with min-content size
pub fn min_content>(repetition: u16) -> T {
Self {
repetition: GridTrackRepetition::Count(repetition),
tracks: SmallVec::from_buf([GridTrack::min_content()]),
}
.into()
}
/// Create a repeating set of grid tracks with max-content size
pub fn max_content>(repetition: u16) -> T {
Self {
repetition: GridTrackRepetition::Count(repetition),
tracks: SmallVec::from_buf([GridTrack::max_content()]),
}
.into()
}
/// Create a repeating set of fit-content() grid tracks with fixed pixel limit
pub fn fit_content_px>(repetition: u16, limit: f32) -> T {
Self {
repetition: GridTrackRepetition::Count(repetition),
tracks: SmallVec::from_buf([GridTrack::fit_content_px(limit)]),
}
.into()
}
/// Create a repeating set of fit-content() grid tracks with percentage limit
pub fn fit_content_percent>(repetition: u16, limit: f32) -> T {
Self {
repetition: GridTrackRepetition::Count(repetition),
tracks: SmallVec::from_buf([GridTrack::fit_content_percent(limit)]),
}
.into()
}
/// Create a repeating set of minmax() grid track
pub fn minmax>(
repetition: impl Into,
min: MinTrackSizingFunction,
max: MaxTrackSizingFunction,
) -> T {
Self {
repetition: repetition.into(),
tracks: SmallVec::from_buf([GridTrack::minmax(min, max)]),
}
.into()
}
/// Create a repetition of a set of tracks
pub fn repeat_many>(
repetition: impl Into,
tracks: impl Into>,
) -> T {
Self {
repetition: repetition.into(),
tracks: SmallVec::from_vec(tracks.into()),
}
.into()
}
}
impl From for RepeatedGridTrack {
fn from(track: GridTrack) -> Self {
Self {
repetition: GridTrackRepetition::Count(1),
tracks: SmallVec::from_buf([track]),
}
}
}
impl From for Vec {
fn from(track: GridTrack) -> Self {
vec![GridTrack {
min_sizing_function: track.min_sizing_function,
max_sizing_function: track.max_sizing_function,
}]
}
}
impl From for Vec {
fn from(track: GridTrack) -> Self {
vec![RepeatedGridTrack {
repetition: GridTrackRepetition::Count(1),
tracks: SmallVec::from_buf([track]),
}]
}
}
impl From for Vec {
fn from(track: RepeatedGridTrack) -> Self {
vec![track]
}
}
#[derive(Copy, Clone, PartialEq, Eq, Debug, Serialize, Deserialize, Reflect)]
#[reflect(PartialEq, Serialize, Deserialize)]
/// Represents the position of a grid item in a single axis.
///
/// There are 3 fields which may be set:
/// - `start`: which grid line the item should start at
/// - `end`: which grid line the item should end at
/// - `span`: how many tracks the item should span
///
/// The default `span` is 1. If neither `start` or `end` is set then the item will be placed automatically.
///
/// Generally, at most two fields should be set. If all three fields are specified then `span` will be ignored. If `end` specifies an earlier
/// grid line than `start` then `end` will be ignored and the item will have a span of 1.
///
///
pub struct GridPlacement {
/// The grid line at which the item should start. Lines are 1-indexed. Negative indexes count backwards from the end of the grid. Zero is not a valid index.
pub(crate) start: Option,
/// How many grid tracks the item should span. Defaults to 1.
pub(crate) span: Option,
/// The grid line at which the item should end. Lines are 1-indexed. Negative indexes count backwards from the end of the grid. Zero is not a valid index.
pub(crate) end: Option,
}
impl GridPlacement {
pub const DEFAULT: Self = Self {
start: None,
span: Some(unsafe { NonZeroU16::new_unchecked(1) }),
end: None,
};
/// Place the grid item automatically (letting the `span` default to `1`).
pub fn auto() -> Self {
Self::DEFAULT
}
/// Place the grid item automatically, specifying how many tracks it should `span`.
///
/// # Panics
///
/// Panics if `span` is `0`
pub fn span(span: u16) -> Self {
Self {
start: None,
end: None,
span: try_into_grid_span(span).expect("Invalid span value of 0."),
}
}
/// Place the grid item specifying the `start` grid line (letting the `span` default to `1`).
///
/// # Panics
///
/// Panics if `start` is `0`
pub fn start(start: i16) -> Self {
Self {
start: try_into_grid_index(start).expect("Invalid start value of 0."),
..Self::DEFAULT
}
}
/// Place the grid item specifying the `end` grid line (letting the `span` default to `1`).
///
/// # Panics
///
/// Panics if `end` is `0`
pub fn end(end: i16) -> Self {
Self {
end: try_into_grid_index(end).expect("Invalid end value of 0."),
..Self::DEFAULT
}
}
/// Place the grid item specifying the `start` grid line and how many tracks it should `span`.
///
/// # Panics
///
/// Panics if `start` or `span` is `0`
pub fn start_span(start: i16, span: u16) -> Self {
Self {
start: try_into_grid_index(start).expect("Invalid start value of 0."),
end: None,
span: try_into_grid_span(span).expect("Invalid span value of 0."),
}
}
/// Place the grid item specifying `start` and `end` grid lines (`span` will be inferred)
///
/// # Panics
///
/// Panics if `start` or `end` is `0`
pub fn start_end(start: i16, end: i16) -> Self {
Self {
start: try_into_grid_index(start).expect("Invalid start value of 0."),
end: try_into_grid_index(end).expect("Invalid end value of 0."),
span: None,
}
}
/// Place the grid item specifying the `end` grid line and how many tracks it should `span`.
///
/// # Panics
///
/// Panics if `end` or `span` is `0`
pub fn end_span(end: i16, span: u16) -> Self {
Self {
start: None,
end: try_into_grid_index(end).expect("Invalid end value of 0."),
span: try_into_grid_span(span).expect("Invalid span value of 0."),
}
}
/// Mutate the item, setting the `start` grid line
///
/// # Panics
///
/// Panics if `start` is `0`
pub fn set_start(mut self, start: i16) -> Self {
self.start = try_into_grid_index(start).expect("Invalid start value of 0.");
self
}
/// Mutate the item, setting the `end` grid line
///
/// # Panics
///
/// Panics if `end` is `0`
pub fn set_end(mut self, end: i16) -> Self {
self.end = try_into_grid_index(end).expect("Invalid end value of 0.");
self
}
/// Mutate the item, setting the number of tracks the item should `span`
///
/// # Panics
///
/// Panics if `span` is `0`
pub fn set_span(mut self, span: u16) -> Self {
self.span = try_into_grid_span(span).expect("Invalid span value of 0.");
self
}
/// Returns the grid line at which the item should start, or `None` if not set.
pub fn get_start(self) -> Option {
self.start.map(NonZeroI16::get)
}
/// Returns the grid line at which the item should end, or `None` if not set.
pub fn get_end(self) -> Option {
self.end.map(NonZeroI16::get)
}
/// Returns span for this grid item, or `None` if not set.
pub fn get_span(self) -> Option {
self.span.map(NonZeroU16::get)
}
}
impl Default for GridPlacement {
fn default() -> Self {
Self::DEFAULT
}
}
/// Convert an `i16` to `NonZeroI16`, fails on `0` and returns the `InvalidZeroIndex` error.
fn try_into_grid_index(index: i16) -> Result