use crate::UiRect; 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, DEFAULT_IMAGE_HANDLE}, }; use bevy_transform::prelude::GlobalTransform; use serde::{Deserialize, Serialize}; use smallvec::SmallVec; use std::ops::{Div, DivAssign, Mul, MulAssign}; 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 } } /// Represents the possible value types for layout properties. /// /// This enum allows specifying values for various [`Style`] properties in different units, /// such as logical pixels, percentages, or automatically determined values. #[derive(Copy, Clone, PartialEq, Debug, Serialize, Deserialize, Reflect)] #[reflect(PartialEq, Serialize, Deserialize)] pub enum Val { /// Automatically determine the value based on the context and other [`Style`] properties. Auto, /// Set this value in logical pixels. Px(f32), /// Set the value as a percentage of its parent node's length along a specific axis. /// /// If the UI node has no parent, the percentage is calculated based on the window's length /// along the corresponding axis. /// /// The chosen axis depends on the `Style` field set: /// * For `flex_basis`, the percentage is relative to the main-axis length determined by the `flex_direction`. /// * For `gap`, `min_size`, `size`, and `max_size`: /// - `width` is relative to the parent's width. /// - `height` is relative to the parent's height. /// * For `margin`, `padding`, and `border` values: the percentage is relative to the parent node's width. /// * For positions, `left` and `right` are relative to the parent's width, while `bottom` and `top` are relative to the parent's height. Percent(f32), /// Set this value in percent of the viewport width Vw(f32), /// Set this value in percent of the viewport height Vh(f32), /// Set this value in percent of the viewport's smaller dimension. VMin(f32), /// Set this value in percent of the viewport's larger dimension. VMax(f32), } impl Val { pub const DEFAULT: Self = Self::Auto; } impl Default for Val { fn default() -> Self { Self::DEFAULT } } impl Mul for Val { type Output = Val; fn mul(self, rhs: f32) -> Self::Output { match self { Val::Auto => Val::Auto, Val::Px(value) => Val::Px(value * rhs), Val::Percent(value) => Val::Percent(value * rhs), Val::Vw(value) => Val::Vw(value * rhs), Val::Vh(value) => Val::Vh(value * rhs), Val::VMin(value) => Val::VMin(value * rhs), Val::VMax(value) => Val::VMax(value * rhs), } } } impl MulAssign for Val { fn mul_assign(&mut self, rhs: f32) { match self { Val::Auto => {} Val::Px(value) | Val::Percent(value) | Val::Vw(value) | Val::Vh(value) | Val::VMin(value) | Val::VMax(value) => *value *= rhs, } } } impl Div for Val { type Output = Val; fn div(self, rhs: f32) -> Self::Output { match self { Val::Auto => Val::Auto, Val::Px(value) => Val::Px(value / rhs), Val::Percent(value) => Val::Percent(value / rhs), Val::Vw(value) => Val::Vw(value / rhs), Val::Vh(value) => Val::Vh(value / rhs), Val::VMin(value) => Val::VMin(value / rhs), Val::VMax(value) => Val::VMax(value / rhs), } } } impl DivAssign for Val { fn div_assign(&mut self, rhs: f32) { match self { Val::Auto => {} Val::Px(value) | Val::Percent(value) | Val::Vw(value) | Val::Vh(value) | Val::VMin(value) | Val::VMax(value) => *value /= rhs, } } } #[derive(Debug, Eq, PartialEq, Clone, Copy, Error)] pub enum ValArithmeticError { #[error("the variants of the Vals don't match")] NonIdenticalVariants, #[error("the given variant of Val is not evaluateable (non-numeric)")] NonEvaluateable, } impl Val { /// Tries to add the values of two [`Val`]s. /// Returns [`ValArithmeticError::NonIdenticalVariants`] if two [`Val`]s are of different variants. /// When adding non-numeric [`Val`]s, it returns the value unchanged. pub fn try_add(&self, rhs: Val) -> Result { match (self, rhs) { (Val::Auto, Val::Auto) => Ok(*self), (Val::Px(value), Val::Px(rhs_value)) => Ok(Val::Px(value + rhs_value)), (Val::Percent(value), Val::Percent(rhs_value)) => Ok(Val::Percent(value + rhs_value)), _ => Err(ValArithmeticError::NonIdenticalVariants), } } /// Adds `rhs` to `self` and assigns the result to `self` (see [`Val::try_add`]) pub fn try_add_assign(&mut self, rhs: Val) -> Result<(), ValArithmeticError> { *self = self.try_add(rhs)?; Ok(()) } /// Tries to subtract the values of two [`Val`]s. /// Returns [`ValArithmeticError::NonIdenticalVariants`] if two [`Val`]s are of different variants. /// When adding non-numeric [`Val`]s, it returns the value unchanged. pub fn try_sub(&self, rhs: Val) -> Result { match (self, rhs) { (Val::Auto, Val::Auto) => Ok(*self), (Val::Px(value), Val::Px(rhs_value)) => Ok(Val::Px(value - rhs_value)), (Val::Percent(value), Val::Percent(rhs_value)) => Ok(Val::Percent(value - rhs_value)), _ => Err(ValArithmeticError::NonIdenticalVariants), } } /// Subtracts `rhs` from `self` and assigns the result to `self` (see [`Val::try_sub`]) pub fn try_sub_assign(&mut self, rhs: Val) -> Result<(), ValArithmeticError> { *self = self.try_sub(rhs)?; Ok(()) } /// A convenience function for simple evaluation of [`Val::Percent`] variant into a concrete [`Val::Px`] value. /// Returns a [`ValArithmeticError::NonEvaluateable`] if the [`Val`] is impossible to evaluate into [`Val::Px`]. /// Otherwise it returns an [`f32`] containing the evaluated value in pixels. /// /// **Note:** If a [`Val::Px`] is evaluated, it's inner value returned unchanged. pub fn evaluate(&self, size: f32) -> Result { match self { Val::Percent(value) => Ok(size * value / 100.0), Val::Px(value) => Ok(*value), _ => Err(ValArithmeticError::NonEvaluateable), } } /// Similar to [`Val::try_add`], but performs [`Val::evaluate`] on both values before adding. /// Returns an [`f32`] value in pixels. pub fn try_add_with_size(&self, rhs: Val, size: f32) -> Result { let lhs = self.evaluate(size)?; let rhs = rhs.evaluate(size)?; Ok(lhs + rhs) } /// Similar to [`Val::try_add_assign`], but performs [`Val::evaluate`] on both values before adding. /// The value gets converted to [`Val::Px`]. pub fn try_add_assign_with_size( &mut self, rhs: Val, size: f32, ) -> Result<(), ValArithmeticError> { *self = Val::Px(self.evaluate(size)? + rhs.evaluate(size)?); Ok(()) } /// Similar to [`Val::try_sub`], but performs [`Val::evaluate`] on both values before subtracting. /// Returns an [`f32`] value in pixels. pub fn try_sub_with_size(&self, rhs: Val, size: f32) -> Result { let lhs = self.evaluate(size)?; let rhs = rhs.evaluate(size)?; Ok(lhs - rhs) } /// Similar to [`Val::try_sub_assign`], but performs [`Val::evaluate`] on both values before adding. /// The value gets converted to [`Val::Px`]. pub fn try_sub_assign_with_size( &mut self, rhs: Val, size: f32, ) -> Result<(), ValArithmeticError> { *self = Val::Px(self.try_add_with_size(rhs, size)?); Ok(()) } } /// 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 Grid Layout](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Grid_Layout/Basic_Concepts_of_Grid_Layout) /// - [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 Flebox in a fun engaging way. /// /// ### CSS Grid /// /// - [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 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 [`JustifySelf`] 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 [`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::Px(0.0), column_gap: Val::Px(0.0), 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 cross 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 cross 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 cross axis. /// Overrides [`AlignItems`]. #[derive(Copy, Clone, PartialEq, Eq, Debug, Serialize, Deserialize, Reflect)] #[reflect(PartialEq, Serialize, Deserialize)] pub enum JustifySelf { /// 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 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 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 node 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(1), end: None, }; /// Place the grid item automatically (letting the `span` default to `1`). pub fn auto() -> Self { Self { start: None, end: None, span: Some(1), } } /// Place the grid item automatically, specifying how many tracks it should `span`. pub fn span(span: u16) -> Self { Self { start: None, end: None, span: Some(span), } } /// Place the grid item specifying the `start` grid line (letting the `span` default to `1`). pub fn start(start: i16) -> Self { Self { start: Some(start), end: None, span: Some(1), } } /// Place the grid item specifying the `end` grid line (letting the `span` default to `1`). pub fn end(end: i16) -> Self { Self { start: None, end: Some(end), span: Some(1), } } /// Place the grid item specifying the `start` grid line and how many tracks it should `span`. pub fn start_span(start: i16, span: u16) -> Self { Self { start: Some(start), end: None, span: Some(span), } } /// Place the grid item specifying `start` and `end` grid lines (`span` will be inferred) pub fn start_end(start: i16, end: i16) -> Self { Self { start: Some(start), end: Some(end), span: None, } } /// Place the grid item specifying the `end` grid line and how many tracks it should `span`. pub fn end_span(end: i16, span: u16) -> Self { Self { start: None, end: Some(end), span: Some(span), } } /// Mutate the item, setting the `start` grid line pub fn set_start(mut self, start: i16) -> Self { self.start = Some(start); self } /// Mutate the item, setting the `end` grid line pub fn set_end(mut self, end: i16) -> Self { self.end = Some(end); self } /// Mutate the item, setting the number of tracks the item should `span` pub fn set_span(mut self, span: u16) -> Self { self.span = Some(span); self } } impl Default for GridPlacement { fn default() -> Self { Self::DEFAULT } } /// The background color of the node /// /// This serves as the "fill" color. /// When combined with [`UiImage`], tints the provided texture. #[derive(Component, Copy, Clone, Debug, Reflect)] #[reflect(Component, Default)] pub struct BackgroundColor(pub Color); impl BackgroundColor { pub const DEFAULT: Self = Self(Color::WHITE); } impl Default for BackgroundColor { fn default() -> Self { Self::DEFAULT } } impl From for BackgroundColor { fn from(color: Color) -> Self { Self(color) } } /// The atlas sprite to be used in a UI Texture Atlas Node #[derive(Component, Clone, Debug, Reflect, Default)] #[reflect(Component, Default)] pub struct UiTextureAtlasImage { /// Texture index in the TextureAtlas pub index: usize, /// Whether to flip the sprite in the X axis pub flip_x: bool, /// Whether to flip the sprite in the Y axis pub flip_y: bool, } /// The border color of the UI node. #[derive(Component, Copy, Clone, Debug, Reflect)] #[reflect(Component, Default)] pub struct BorderColor(pub Color); impl From for BorderColor { fn from(color: Color) -> Self { Self(color) } } impl BorderColor { pub const DEFAULT: Self = BorderColor(Color::WHITE); } impl Default for BorderColor { fn default() -> Self { Self::DEFAULT } } /// The 2D texture displayed for this UI node #[derive(Component, Clone, Debug, Reflect)] #[reflect(Component, Default)] pub struct UiImage { /// Handle to the texture pub texture: Handle, /// Whether the image should be flipped along its x-axis pub flip_x: bool, /// Whether the image should be flipped along its y-axis pub flip_y: bool, } impl Default for UiImage { fn default() -> UiImage { UiImage { texture: DEFAULT_IMAGE_HANDLE.typed(), flip_x: false, flip_y: false, } } } impl UiImage { pub fn new(texture: Handle) -> Self { Self { texture, ..Default::default() } } /// flip the image along its x-axis #[must_use] pub const fn with_flip_x(mut self) -> Self { self.flip_x = true; self } /// flip the image along its y-axis #[must_use] pub const fn with_flip_y(mut self) -> Self { self.flip_y = true; self } } impl From> for UiImage { fn from(texture: Handle) -> Self { Self::new(texture) } } /// The calculated clip of the node #[derive(Component, Default, Copy, Clone, Debug, Reflect)] #[reflect(Component)] pub struct CalculatedClip { /// The rect of the clip pub clip: Rect, } /// Indicates that this [`Node`] entity's front-to-back ordering is not controlled solely /// by its location in the UI hierarchy. A node with a higher z-index will appear on top /// of other nodes with a lower z-index. /// /// UI nodes that have the same z-index will appear according to the order in which they /// appear in the UI hierarchy. In such a case, the last node to be added to its parent /// will appear in front of this parent's other children. /// /// Internally, nodes with a global z-index share the stacking context of root UI nodes /// (nodes that have no parent). Because of this, there is no difference between using /// [`ZIndex::Local(n)`] and [`ZIndex::Global(n)`] for root nodes. /// /// Nodes without this component will be treated as if they had a value of [`ZIndex::Local(0)`]. #[derive(Component, Copy, Clone, Debug, Reflect)] #[reflect(Component)] pub enum ZIndex { /// Indicates the order in which this node should be rendered relative to its siblings. Local(i32), /// Indicates the order in which this node should be rendered relative to root nodes and /// all other nodes that have a global z-index. Global(i32), } impl Default for ZIndex { fn default() -> Self { Self::Local(0) } } #[cfg(test)] mod tests { use crate::ValArithmeticError; use super::Val; #[test] fn val_try_add() { let auto_sum = Val::Auto.try_add(Val::Auto).unwrap(); let px_sum = Val::Px(20.).try_add(Val::Px(22.)).unwrap(); let percent_sum = Val::Percent(50.).try_add(Val::Percent(50.)).unwrap(); assert_eq!(auto_sum, Val::Auto); assert_eq!(px_sum, Val::Px(42.)); assert_eq!(percent_sum, Val::Percent(100.)); } #[test] fn val_try_add_to_self() { let mut val = Val::Px(5.); val.try_add_assign(Val::Px(3.)).unwrap(); assert_eq!(val, Val::Px(8.)); } #[test] fn val_try_sub() { let auto_sum = Val::Auto.try_sub(Val::Auto).unwrap(); let px_sum = Val::Px(72.).try_sub(Val::Px(30.)).unwrap(); let percent_sum = Val::Percent(100.).try_sub(Val::Percent(50.)).unwrap(); assert_eq!(auto_sum, Val::Auto); assert_eq!(px_sum, Val::Px(42.)); assert_eq!(percent_sum, Val::Percent(50.)); } #[test] fn different_variant_val_try_add() { let different_variant_sum_1 = Val::Px(50.).try_add(Val::Percent(50.)); let different_variant_sum_2 = Val::Percent(50.).try_add(Val::Auto); assert_eq!( different_variant_sum_1, Err(ValArithmeticError::NonIdenticalVariants) ); assert_eq!( different_variant_sum_2, Err(ValArithmeticError::NonIdenticalVariants) ); } #[test] fn different_variant_val_try_sub() { let different_variant_diff_1 = Val::Px(50.).try_sub(Val::Percent(50.)); let different_variant_diff_2 = Val::Percent(50.).try_sub(Val::Auto); assert_eq!( different_variant_diff_1, Err(ValArithmeticError::NonIdenticalVariants) ); assert_eq!( different_variant_diff_2, Err(ValArithmeticError::NonIdenticalVariants) ); } #[test] fn val_evaluate() { let size = 250.; let result = Val::Percent(80.).evaluate(size).unwrap(); assert_eq!(result, size * 0.8); } #[test] fn val_evaluate_px() { let size = 250.; let result = Val::Px(10.).evaluate(size).unwrap(); assert_eq!(result, 10.); } #[test] fn val_invalid_evaluation() { let size = 250.; let evaluate_auto = Val::Auto.evaluate(size); assert_eq!(evaluate_auto, Err(ValArithmeticError::NonEvaluateable)); } #[test] fn val_try_add_with_size() { let size = 250.; let px_sum = Val::Px(21.).try_add_with_size(Val::Px(21.), size).unwrap(); let percent_sum = Val::Percent(20.) .try_add_with_size(Val::Percent(30.), size) .unwrap(); let mixed_sum = Val::Px(20.) .try_add_with_size(Val::Percent(30.), size) .unwrap(); assert_eq!(px_sum, 42.); assert_eq!(percent_sum, 0.5 * size); assert_eq!(mixed_sum, 20. + 0.3 * size); } #[test] fn val_try_sub_with_size() { let size = 250.; let px_sum = Val::Px(60.).try_sub_with_size(Val::Px(18.), size).unwrap(); let percent_sum = Val::Percent(80.) .try_sub_with_size(Val::Percent(30.), size) .unwrap(); let mixed_sum = Val::Percent(50.) .try_sub_with_size(Val::Px(30.), size) .unwrap(); assert_eq!(px_sum, 42.); assert_eq!(percent_sum, 0.5 * size); assert_eq!(mixed_sum, 0.5 * size - 30.); } #[test] fn val_try_add_non_numeric_with_size() { let size = 250.; let percent_sum = Val::Auto.try_add_with_size(Val::Auto, size); assert_eq!(percent_sum, Err(ValArithmeticError::NonEvaluateable)); } #[test] fn val_arithmetic_error_messages() { assert_eq!( format!("{}", ValArithmeticError::NonIdenticalVariants), "the variants of the Vals don't match" ); assert_eq!( format!("{}", ValArithmeticError::NonEvaluateable), "the given variant of Val is not evaluateable (non-numeric)" ); } #[test] fn default_val_equals_const_default_val() { assert_eq!(Val::default(), Val::DEFAULT); } }