use bevy_math::Vec2;1use bevy_reflect::{std_traits::ReflectDefault, Reflect};2use bevy_utils::default;3use core::ops::{Div, DivAssign, Mul, MulAssign, Neg};4use thiserror::Error;56#[cfg(feature = "serialize")]7use bevy_reflect::{ReflectDeserialize, ReflectSerialize};89/// Represents the possible value types for layout properties.10///11/// This enum allows specifying values for various [`Node`](crate::Node) properties in different units,12/// such as logical pixels, percentages, or automatically determined values.13///14/// `Val` also implements [`core::str::FromStr`] to allow parsing values from strings in the format `#.#px`. Whitespaces between the value and unit is allowed. The following units are supported:15/// * `px`: logical pixels16/// * `%`: percentage17/// * `vw`: percentage of the viewport width18/// * `vh`: percentage of the viewport height19/// * `vmin`: percentage of the viewport's smaller dimension20/// * `vmax`: percentage of the viewport's larger dimension21///22/// Additionally, `auto` will be parsed as [`Val::Auto`].23#[derive(Copy, Clone, Debug, Reflect)]24#[reflect(Default, PartialEq, Debug, Clone)]25#[cfg_attr(26feature = "serialize",27derive(serde::Serialize, serde::Deserialize),28reflect(Serialize, Deserialize)29)]30pub enum Val {31/// Automatically determine the value based on the context and other [`Node`](crate::Node) properties.32Auto,33/// Set this value in logical pixels.34Px(f32),35/// Set the value as a percentage of its parent node's length along a specific axis.36///37/// If the UI node has no parent, the percentage is calculated based on the window's length38/// along the corresponding axis.39///40/// The chosen axis depends on the [`Node`](crate::Node) field set:41/// * For `flex_basis`, the percentage is relative to the main-axis length determined by the `flex_direction`.42/// * For `gap`, `min_size`, `size`, and `max_size`:43/// - `width` is relative to the parent's width.44/// - `height` is relative to the parent's height.45/// * For `margin`, `padding`, and `border` values: the percentage is relative to the parent node's width.46/// * For positions, `left` and `right` are relative to the parent's width, while `bottom` and `top` are relative to the parent's height.47Percent(f32),48/// Set this value in percent of the viewport width49Vw(f32),50/// Set this value in percent of the viewport height51Vh(f32),52/// Set this value in percent of the viewport's smaller dimension.53VMin(f32),54/// Set this value in percent of the viewport's larger dimension.55VMax(f32),56}5758#[derive(Debug, Error, PartialEq, Eq)]59pub enum ValParseError {60UnitMissing,61ValueMissing,62InvalidValue,63InvalidUnit,64}6566impl core::fmt::Display for ValParseError {67fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {68match self {69ValParseError::UnitMissing => write!(f, "unit missing"),70ValParseError::ValueMissing => write!(f, "value missing"),71ValParseError::InvalidValue => write!(f, "invalid value"),72ValParseError::InvalidUnit => write!(f, "invalid unit"),73}74}75}7677impl core::str::FromStr for Val {78type Err = ValParseError;7980fn from_str(s: &str) -> Result<Self, Self::Err> {81let s = s.trim();8283if s.eq_ignore_ascii_case("auto") {84return Ok(Val::Auto);85}8687let Some(end_of_number) = s88.bytes()89.position(|c| !(c.is_ascii_digit() || c == b'.' || c == b'-' || c == b'+'))90else {91return Err(ValParseError::UnitMissing);92};9394if end_of_number == 0 {95return Err(ValParseError::ValueMissing);96}9798let (value, unit) = s.split_at(end_of_number);99100let value: f32 = value.parse().map_err(|_| ValParseError::InvalidValue)?;101102let unit = unit.trim();103104if unit.eq_ignore_ascii_case("px") {105Ok(Val::Px(value))106} else if unit.eq_ignore_ascii_case("%") {107Ok(Val::Percent(value))108} else if unit.eq_ignore_ascii_case("vw") {109Ok(Val::Vw(value))110} else if unit.eq_ignore_ascii_case("vh") {111Ok(Val::Vh(value))112} else if unit.eq_ignore_ascii_case("vmin") {113Ok(Val::VMin(value))114} else if unit.eq_ignore_ascii_case("vmax") {115Ok(Val::VMax(value))116} else {117Err(ValParseError::InvalidUnit)118}119}120}121122impl PartialEq for Val {123fn eq(&self, other: &Self) -> bool {124let same_unit = matches!(125(self, other),126(Self::Auto, Self::Auto)127| (Self::Px(_), Self::Px(_))128| (Self::Percent(_), Self::Percent(_))129| (Self::Vw(_), Self::Vw(_))130| (Self::Vh(_), Self::Vh(_))131| (Self::VMin(_), Self::VMin(_))132| (Self::VMax(_), Self::VMax(_))133);134135let left = match self {136Self::Auto => None,137Self::Px(v)138| Self::Percent(v)139| Self::Vw(v)140| Self::Vh(v)141| Self::VMin(v)142| Self::VMax(v) => Some(v),143};144145let right = match other {146Self::Auto => None,147Self::Px(v)148| Self::Percent(v)149| Self::Vw(v)150| Self::Vh(v)151| Self::VMin(v)152| Self::VMax(v) => Some(v),153};154155match (same_unit, left, right) {156(true, a, b) => a == b,157// All zero-value variants are considered equal.158(false, Some(&a), Some(&b)) => a == 0. && b == 0.,159_ => false,160}161}162}163164impl Val {165pub const DEFAULT: Self = Self::Auto;166pub const ZERO: Self = Self::Px(0.0);167}168169impl Default for Val {170fn default() -> Self {171Self::DEFAULT172}173}174175impl Mul<f32> for Val {176type Output = Val;177178fn mul(self, rhs: f32) -> Self::Output {179match self {180Val::Auto => Val::Auto,181Val::Px(value) => Val::Px(value * rhs),182Val::Percent(value) => Val::Percent(value * rhs),183Val::Vw(value) => Val::Vw(value * rhs),184Val::Vh(value) => Val::Vh(value * rhs),185Val::VMin(value) => Val::VMin(value * rhs),186Val::VMax(value) => Val::VMax(value * rhs),187}188}189}190191impl MulAssign<f32> for Val {192fn mul_assign(&mut self, rhs: f32) {193match self {194Val::Auto => {}195Val::Px(value)196| Val::Percent(value)197| Val::Vw(value)198| Val::Vh(value)199| Val::VMin(value)200| Val::VMax(value) => *value *= rhs,201}202}203}204205impl Div<f32> for Val {206type Output = Val;207208fn div(self, rhs: f32) -> Self::Output {209match self {210Val::Auto => Val::Auto,211Val::Px(value) => Val::Px(value / rhs),212Val::Percent(value) => Val::Percent(value / rhs),213Val::Vw(value) => Val::Vw(value / rhs),214Val::Vh(value) => Val::Vh(value / rhs),215Val::VMin(value) => Val::VMin(value / rhs),216Val::VMax(value) => Val::VMax(value / rhs),217}218}219}220221impl DivAssign<f32> for Val {222fn div_assign(&mut self, rhs: f32) {223match self {224Val::Auto => {}225Val::Px(value)226| Val::Percent(value)227| Val::Vw(value)228| Val::Vh(value)229| Val::VMin(value)230| Val::VMax(value) => *value /= rhs,231}232}233}234235impl Neg for Val {236type Output = Val;237238fn neg(self) -> Self::Output {239match self {240Val::Px(value) => Val::Px(-value),241Val::Percent(value) => Val::Percent(-value),242Val::Vw(value) => Val::Vw(-value),243Val::Vh(value) => Val::Vh(-value),244Val::VMin(value) => Val::VMin(-value),245Val::VMax(value) => Val::VMax(-value),246_ => self,247}248}249}250251#[derive(Debug, Eq, PartialEq, Clone, Copy, Error)]252pub enum ValArithmeticError {253#[error("the given variant of Val is not evaluable (non-numeric)")]254NonEvaluable,255}256257impl Val {258/// Resolves this [`Val`] to a value in physical pixels from the given `scale_factor`, `physical_base_value`,259/// and `physical_target_size` context values.260///261/// Returns a [`ValArithmeticError::NonEvaluable`] if the [`Val`] is impossible to resolve into a concrete value.262pub const fn resolve(263self,264scale_factor: f32,265physical_base_value: f32,266physical_target_size: Vec2,267) -> Result<f32, ValArithmeticError> {268match self {269Val::Percent(value) => Ok(physical_base_value * value / 100.0),270Val::Px(value) => Ok(value * scale_factor),271Val::Vw(value) => Ok(physical_target_size.x * value / 100.0),272Val::Vh(value) => Ok(physical_target_size.y * value / 100.0),273Val::VMin(value) => {274Ok(physical_target_size.x.min(physical_target_size.y) * value / 100.0)275}276Val::VMax(value) => {277Ok(physical_target_size.x.max(physical_target_size.y) * value / 100.0)278}279Val::Auto => Err(ValArithmeticError::NonEvaluable),280}281}282}283284/// All the types that should be able to be used in the [`Val`] enum should implement this trait.285///286/// Instead of just implementing `Into<Val>` a custom trait is added.287/// This is done in order to prevent having to define a default unit, which could lead to confusion especially for newcomers.288pub trait ValNum {289/// Called by the [`Val`] helper functions to convert the implementing type to an `f32` that can290/// be used by [`Val`].291fn val_num_f32(self) -> f32;292}293294macro_rules! impl_to_val_num {295($($impl_type:ty),*$(,)?) => {296$(297impl ValNum for $impl_type {298fn val_num_f32(self) -> f32 {299self as f32300}301}302)*303};304}305306impl_to_val_num!(f32, f64, i8, i16, i32, i64, u8, u16, u32, u64, usize, isize);307308/// Returns a [`Val::Auto`] where the value is automatically determined309/// based on the context and other [`Node`](crate::Node) properties.310pub const fn auto() -> Val {311Val::Auto312}313314/// Returns a [`Val::Px`] representing a value in logical pixels.315pub fn px<T: ValNum>(value: T) -> Val {316Val::Px(value.val_num_f32())317}318319/// Returns a [`Val::Percent`] representing a percentage of the parent node's length320/// along a specific axis.321///322/// If the UI node has no parent, the percentage is based on the window's length323/// along that axis.324///325/// Axis rules:326/// * For `flex_basis`, the percentage is relative to the main-axis length determined by the `flex_direction`.327/// * For `gap`, `min_size`, `size`, and `max_size`:328/// - `width` is relative to the parent's width.329/// - `height` is relative to the parent's height.330/// * For `margin`, `padding`, and `border` values: the percentage is relative to the parent's width.331/// * For positions, `left` and `right` are relative to the parent's width, while `bottom` and `top` are relative to the parent's height.332pub fn percent<T: ValNum>(value: T) -> Val {333Val::Percent(value.val_num_f32())334}335336/// Returns a [`Val::Vw`] representing a percentage of the viewport width.337pub fn vw<T: ValNum>(value: T) -> Val {338Val::Vw(value.val_num_f32())339}340341/// Returns a [`Val::Vh`] representing a percentage of the viewport height.342pub fn vh<T: ValNum>(value: T) -> Val {343Val::Vh(value.val_num_f32())344}345346/// Returns a [`Val::VMin`] representing a percentage of the viewport's smaller dimension.347pub fn vmin<T: ValNum>(value: T) -> Val {348Val::VMin(value.val_num_f32())349}350351/// Returns a [`Val::VMax`] representing a percentage of the viewport's larger dimension.352pub fn vmax<T: ValNum>(value: T) -> Val {353Val::VMax(value.val_num_f32())354}355356/// A type which is commonly used to define margins, paddings and borders.357///358/// # Examples359///360/// ## Margin361///362/// A margin is used to create space around UI elements, outside of any defined borders.363///364/// ```365/// # use bevy_ui::{UiRect, Val};366/// #367/// let margin = UiRect::all(Val::Auto); // Centers the UI element368/// ```369///370/// ## Padding371///372/// A padding is used to create space around UI elements, inside of any defined borders.373///374/// ```375/// # use bevy_ui::{UiRect, Val};376/// #377/// let padding = UiRect {378/// left: Val::Px(10.0),379/// right: Val::Px(20.0),380/// top: Val::Px(30.0),381/// bottom: Val::Px(40.0),382/// };383/// ```384///385/// ## Borders386///387/// A border is used to define the width of the border of a UI element.388///389/// ```390/// # use bevy_ui::{UiRect, Val};391/// #392/// let border = UiRect {393/// left: Val::Px(10.0),394/// right: Val::Px(20.0),395/// top: Val::Px(30.0),396/// bottom: Val::Px(40.0),397/// };398/// ```399#[derive(Copy, Clone, PartialEq, Debug, Reflect)]400#[reflect(Default, PartialEq, Debug, Clone)]401#[cfg_attr(402feature = "serialize",403derive(serde::Serialize, serde::Deserialize),404reflect(Serialize, Deserialize)405)]406pub struct UiRect {407/// The value corresponding to the left side of the UI rect.408pub left: Val,409/// The value corresponding to the right side of the UI rect.410pub right: Val,411/// The value corresponding to the top side of the UI rect.412pub top: Val,413/// The value corresponding to the bottom side of the UI rect.414pub bottom: Val,415}416417impl UiRect {418pub const DEFAULT: Self = Self::all(Val::ZERO);419pub const ZERO: Self = Self::all(Val::ZERO);420pub const AUTO: Self = Self::all(Val::Auto);421422/// Creates a new [`UiRect`] from the values specified.423///424/// # Example425///426/// ```427/// # use bevy_ui::{UiRect, Val};428/// #429/// let ui_rect = UiRect::new(430/// Val::Px(10.0),431/// Val::Px(20.0),432/// Val::Px(30.0),433/// Val::Px(40.0),434/// );435///436/// assert_eq!(ui_rect.left, Val::Px(10.0));437/// assert_eq!(ui_rect.right, Val::Px(20.0));438/// assert_eq!(ui_rect.top, Val::Px(30.0));439/// assert_eq!(ui_rect.bottom, Val::Px(40.0));440/// ```441pub const fn new(left: Val, right: Val, top: Val, bottom: Val) -> Self {442UiRect {443left,444right,445top,446bottom,447}448}449450/// Creates a new [`UiRect`] where all sides have the same value.451///452/// # Example453///454/// ```455/// # use bevy_ui::{UiRect, Val};456/// #457/// let ui_rect = UiRect::all(Val::Px(10.0));458///459/// assert_eq!(ui_rect.left, Val::Px(10.0));460/// assert_eq!(ui_rect.right, Val::Px(10.0));461/// assert_eq!(ui_rect.top, Val::Px(10.0));462/// assert_eq!(ui_rect.bottom, Val::Px(10.0));463/// ```464pub const fn all(value: Val) -> Self {465UiRect {466left: value,467right: value,468top: value,469bottom: value,470}471}472473/// Creates a new [`UiRect`] from the values specified in logical pixels.474///475/// This is a shortcut for [`UiRect::new()`], applying [`Val::Px`] to all arguments.476///477/// # Example478///479/// ```480/// # use bevy_ui::{UiRect, Val};481/// #482/// let ui_rect = UiRect::px(10., 20., 30., 40.);483/// assert_eq!(ui_rect.left, Val::Px(10.));484/// assert_eq!(ui_rect.right, Val::Px(20.));485/// assert_eq!(ui_rect.top, Val::Px(30.));486/// assert_eq!(ui_rect.bottom, Val::Px(40.));487/// ```488pub const fn px(left: f32, right: f32, top: f32, bottom: f32) -> Self {489UiRect {490left: Val::Px(left),491right: Val::Px(right),492top: Val::Px(top),493bottom: Val::Px(bottom),494}495}496497/// Creates a new [`UiRect`] from the values specified in percentages.498///499/// This is a shortcut for [`UiRect::new()`], applying [`Val::Percent`] to all arguments.500///501/// # Example502///503/// ```504/// # use bevy_ui::{UiRect, Val};505/// #506/// let ui_rect = UiRect::percent(5., 10., 2., 1.);507/// assert_eq!(ui_rect.left, Val::Percent(5.));508/// assert_eq!(ui_rect.right, Val::Percent(10.));509/// assert_eq!(ui_rect.top, Val::Percent(2.));510/// assert_eq!(ui_rect.bottom, Val::Percent(1.));511/// ```512pub const fn percent(left: f32, right: f32, top: f32, bottom: f32) -> Self {513UiRect {514left: Val::Percent(left),515right: Val::Percent(right),516top: Val::Percent(top),517bottom: Val::Percent(bottom),518}519}520521/// Creates a new [`UiRect`] where `left` and `right` take the given value,522/// and `top` and `bottom` set to zero `Val::ZERO`.523///524/// # Example525///526/// ```527/// # use bevy_ui::{UiRect, Val};528/// #529/// let ui_rect = UiRect::horizontal(Val::Px(10.0));530///531/// assert_eq!(ui_rect.left, Val::Px(10.0));532/// assert_eq!(ui_rect.right, Val::Px(10.0));533/// assert_eq!(ui_rect.top, Val::ZERO);534/// assert_eq!(ui_rect.bottom, Val::ZERO);535/// ```536pub const fn horizontal(value: Val) -> Self {537Self {538left: value,539right: value,540..Self::DEFAULT541}542}543544/// Creates a new [`UiRect`] where `top` and `bottom` take the given value,545/// and `left` and `right` are set to `Val::ZERO`.546///547/// # Example548///549/// ```550/// # use bevy_ui::{UiRect, Val};551/// #552/// let ui_rect = UiRect::vertical(Val::Px(10.0));553///554/// assert_eq!(ui_rect.left, Val::ZERO);555/// assert_eq!(ui_rect.right, Val::ZERO);556/// assert_eq!(ui_rect.top, Val::Px(10.0));557/// assert_eq!(ui_rect.bottom, Val::Px(10.0));558/// ```559pub const fn vertical(value: Val) -> Self {560Self {561top: value,562bottom: value,563..Self::DEFAULT564}565}566567/// Creates a new [`UiRect`] where both `left` and `right` take the value of `horizontal`, and both `top` and `bottom` take the value of `vertical`.568///569/// # Example570///571/// ```572/// # use bevy_ui::{UiRect, Val};573/// #574/// let ui_rect = UiRect::axes(Val::Px(10.0), Val::Percent(15.0));575///576/// assert_eq!(ui_rect.left, Val::Px(10.0));577/// assert_eq!(ui_rect.right, Val::Px(10.0));578/// assert_eq!(ui_rect.top, Val::Percent(15.0));579/// assert_eq!(ui_rect.bottom, Val::Percent(15.0));580/// ```581pub const fn axes(horizontal: Val, vertical: Val) -> Self {582Self {583left: horizontal,584right: horizontal,585top: vertical,586bottom: vertical,587}588}589590/// Creates a new [`UiRect`] where `left` takes the given value, and591/// the other fields are set to `Val::ZERO`.592///593/// # Example594///595/// ```596/// # use bevy_ui::{UiRect, Val};597/// #598/// let ui_rect = UiRect::left(Val::Px(10.0));599///600/// assert_eq!(ui_rect.left, Val::Px(10.0));601/// assert_eq!(ui_rect.right, Val::ZERO);602/// assert_eq!(ui_rect.top, Val::ZERO);603/// assert_eq!(ui_rect.bottom, Val::ZERO);604/// ```605pub const fn left(left: Val) -> Self {606Self {607left,608..Self::DEFAULT609}610}611612/// Creates a new [`UiRect`] where `right` takes the given value,613/// and the other fields are set to `Val::ZERO`.614///615/// # Example616///617/// ```618/// # use bevy_ui::{UiRect, Val};619/// #620/// let ui_rect = UiRect::right(Val::Px(10.0));621///622/// assert_eq!(ui_rect.left, Val::ZERO);623/// assert_eq!(ui_rect.right, Val::Px(10.0));624/// assert_eq!(ui_rect.top, Val::ZERO);625/// assert_eq!(ui_rect.bottom, Val::ZERO);626/// ```627pub const fn right(right: Val) -> Self {628Self {629right,630..Self::DEFAULT631}632}633634/// Creates a new [`UiRect`] where `top` takes the given value,635/// and the other fields are set to `Val::ZERO`.636///637/// # Example638///639/// ```640/// # use bevy_ui::{UiRect, Val};641/// #642/// let ui_rect = UiRect::top(Val::Px(10.0));643///644/// assert_eq!(ui_rect.left, Val::ZERO);645/// assert_eq!(ui_rect.right, Val::ZERO);646/// assert_eq!(ui_rect.top, Val::Px(10.0));647/// assert_eq!(ui_rect.bottom, Val::ZERO);648/// ```649pub const fn top(top: Val) -> Self {650Self {651top,652..Self::DEFAULT653}654}655656/// Creates a new [`UiRect`] where `bottom` takes the given value,657/// and the other fields are set to `Val::ZERO`.658///659/// # Example660///661/// ```662/// # use bevy_ui::{UiRect, Val};663/// #664/// let ui_rect = UiRect::bottom(Val::Px(10.0));665///666/// assert_eq!(ui_rect.left, Val::ZERO);667/// assert_eq!(ui_rect.right, Val::ZERO);668/// assert_eq!(ui_rect.top, Val::ZERO);669/// assert_eq!(ui_rect.bottom, Val::Px(10.0));670/// ```671pub const fn bottom(bottom: Val) -> Self {672Self {673bottom,674..Self::DEFAULT675}676}677678/// Returns the [`UiRect`] with its `left` field set to the given value.679///680/// # Example681///682/// ```683/// # use bevy_ui::{UiRect, Val};684/// #685/// let ui_rect = UiRect::all(Val::Px(20.0)).with_left(Val::Px(10.0));686/// assert_eq!(ui_rect.left, Val::Px(10.0));687/// assert_eq!(ui_rect.right, Val::Px(20.0));688/// assert_eq!(ui_rect.top, Val::Px(20.0));689/// assert_eq!(ui_rect.bottom, Val::Px(20.0));690/// ```691#[inline]692pub const fn with_left(mut self, left: Val) -> Self {693self.left = left;694self695}696697/// Returns the [`UiRect`] with its `right` field set to the given value.698///699/// # Example700///701/// ```702/// # use bevy_ui::{UiRect, Val};703/// #704/// let ui_rect = UiRect::all(Val::Px(20.0)).with_right(Val::Px(10.0));705/// assert_eq!(ui_rect.left, Val::Px(20.0));706/// assert_eq!(ui_rect.right, Val::Px(10.0));707/// assert_eq!(ui_rect.top, Val::Px(20.0));708/// assert_eq!(ui_rect.bottom, Val::Px(20.0));709/// ```710#[inline]711pub const fn with_right(mut self, right: Val) -> Self {712self.right = right;713self714}715716/// Returns the [`UiRect`] with its `top` field set to the given value.717///718/// # Example719///720/// ```721/// # use bevy_ui::{UiRect, Val};722/// #723/// let ui_rect = UiRect::all(Val::Px(20.0)).with_top(Val::Px(10.0));724/// assert_eq!(ui_rect.left, Val::Px(20.0));725/// assert_eq!(ui_rect.right, Val::Px(20.0));726/// assert_eq!(ui_rect.top, Val::Px(10.0));727/// assert_eq!(ui_rect.bottom, Val::Px(20.0));728/// ```729#[inline]730pub const fn with_top(mut self, top: Val) -> Self {731self.top = top;732self733}734735/// Returns the [`UiRect`] with its `bottom` field set to the given value.736///737/// # Example738///739/// ```740/// # use bevy_ui::{UiRect, Val};741/// #742/// let ui_rect = UiRect::all(Val::Px(20.0)).with_bottom(Val::Px(10.0));743/// assert_eq!(ui_rect.left, Val::Px(20.0));744/// assert_eq!(ui_rect.right, Val::Px(20.0));745/// assert_eq!(ui_rect.top, Val::Px(20.0));746/// assert_eq!(ui_rect.bottom, Val::Px(10.0));747/// ```748#[inline]749pub const fn with_bottom(mut self, bottom: Val) -> Self {750self.bottom = bottom;751self752}753}754755impl Default for UiRect {756fn default() -> Self {757Self::DEFAULT758}759}760761impl From<Val> for UiRect {762fn from(value: Val) -> Self {763UiRect::all(value)764}765}766767#[derive(Debug, Clone, Copy, PartialEq, Reflect)]768#[reflect(Default, Debug, PartialEq)]769#[cfg_attr(770feature = "serialize",771derive(serde::Serialize, serde::Deserialize),772reflect(Serialize, Deserialize)773)]774/// Responsive position relative to a UI node.775pub struct UiPosition {776/// Normalized anchor point777pub anchor: Vec2,778/// Responsive horizontal position relative to the anchor point779pub x: Val,780/// Responsive vertical position relative to the anchor point781pub y: Val,782}783784impl Default for UiPosition {785fn default() -> Self {786Self::CENTER787}788}789790impl UiPosition {791/// Position at the given normalized anchor point792pub const fn anchor(anchor: Vec2) -> Self {793Self {794anchor,795x: Val::ZERO,796y: Val::ZERO,797}798}799800/// Position at the top-left corner801pub const TOP_LEFT: Self = Self::anchor(Vec2::new(-0.5, -0.5));802803/// Position at the center of the left edge804pub const LEFT: Self = Self::anchor(Vec2::new(-0.5, 0.0));805806/// Position at the bottom-left corner807pub const BOTTOM_LEFT: Self = Self::anchor(Vec2::new(-0.5, 0.5));808809/// Position at the center of the top edge810pub const TOP: Self = Self::anchor(Vec2::new(0.0, -0.5));811812/// Position at the center of the element813pub const CENTER: Self = Self::anchor(Vec2::new(0.0, 0.0));814815/// Position at the center of the bottom edge816pub const BOTTOM: Self = Self::anchor(Vec2::new(0.0, 0.5));817818/// Position at the top-right corner819pub const TOP_RIGHT: Self = Self::anchor(Vec2::new(0.5, -0.5));820821/// Position at the center of the right edge822pub const RIGHT: Self = Self::anchor(Vec2::new(0.5, 0.0));823824/// Position at the bottom-right corner825pub const BOTTOM_RIGHT: Self = Self::anchor(Vec2::new(0.5, 0.5));826827/// Create a new position828pub const fn new(anchor: Vec2, x: Val, y: Val) -> Self {829Self { anchor, x, y }830}831832/// Creates a position from self with the given `x` and `y` coordinates833pub const fn at(self, x: Val, y: Val) -> Self {834Self { x, y, ..self }835}836837/// Creates a position from self with the given `x` coordinate838pub const fn at_x(self, x: Val) -> Self {839Self { x, ..self }840}841842/// Creates a position from self with the given `y` coordinate843pub const fn at_y(self, y: Val) -> Self {844Self { y, ..self }845}846847/// Creates a position in logical pixels from self with the given `x` and `y` coordinates848pub const fn at_px(self, x: f32, y: f32) -> Self {849self.at(Val::Px(x), Val::Px(y))850}851852/// Creates a percentage position from self with the given `x` and `y` coordinates853pub const fn at_percent(self, x: f32, y: f32) -> Self {854self.at(Val::Percent(x), Val::Percent(y))855}856857/// Creates a position from self with the given `anchor` point858pub const fn with_anchor(self, anchor: Vec2) -> Self {859Self { anchor, ..self }860}861862/// Position relative to the top-left corner863pub const fn top_left(x: Val, y: Val) -> Self {864Self::TOP_LEFT.at(x, y)865}866867/// Position relative to the left edge868pub const fn left(x: Val, y: Val) -> Self {869Self::LEFT.at(x, y)870}871872/// Position relative to the bottom-left corner873pub const fn bottom_left(x: Val, y: Val) -> Self {874Self::BOTTOM_LEFT.at(x, y)875}876877/// Position relative to the top edge878pub const fn top(x: Val, y: Val) -> Self {879Self::TOP.at(x, y)880}881882/// Position relative to the center883pub const fn center(x: Val, y: Val) -> Self {884Self::CENTER.at(x, y)885}886887/// Position relative to the bottom edge888pub const fn bottom(x: Val, y: Val) -> Self {889Self::BOTTOM.at(x, y)890}891892/// Position relative to the top-right corner893pub const fn top_right(x: Val, y: Val) -> Self {894Self::TOP_RIGHT.at(x, y)895}896897/// Position relative to the right edge898pub const fn right(x: Val, y: Val) -> Self {899Self::RIGHT.at(x, y)900}901902/// Position relative to the bottom-right corner903pub const fn bottom_right(x: Val, y: Val) -> Self {904Self::BOTTOM_RIGHT.at(x, y)905}906907/// Resolves the `Position` into physical coordinates.908pub fn resolve(909self,910scale_factor: f32,911physical_size: Vec2,912physical_target_size: Vec2,913) -> Vec2 {914let d = self.anchor.map(|p| if 0. < p { -1. } else { 1. });915916physical_size * self.anchor917+ d * Vec2::new(918self.x919.resolve(scale_factor, physical_size.x, physical_target_size)920.unwrap_or(0.),921self.y922.resolve(scale_factor, physical_size.y, physical_target_size)923.unwrap_or(0.),924)925}926}927928impl From<Val> for UiPosition {929fn from(x: Val) -> Self {930Self { x, ..default() }931}932}933934impl From<(Val, Val)> for UiPosition {935fn from((x, y): (Val, Val)) -> Self {936Self { x, y, ..default() }937}938}939940#[cfg(test)]941mod tests {942use crate::geometry::*;943use bevy_math::vec2;944945#[test]946fn val_evaluate() {947let size = 250.;948let viewport_size = vec2(1000., 500.);949let result = Val::Percent(80.).resolve(1., size, viewport_size).unwrap();950951assert_eq!(result, size * 0.8);952}953954#[test]955fn val_resolve_px() {956let size = 250.;957let viewport_size = vec2(1000., 500.);958let result = Val::Px(10.).resolve(1., size, viewport_size).unwrap();959960assert_eq!(result, 10.);961}962963#[test]964fn val_resolve_viewport_coords() {965let size = 250.;966let viewport_size = vec2(500., 500.);967968for value in (-10..10).map(|value| value as f32) {969// for a square viewport there should be no difference between `Vw` and `Vh` and between `Vmin` and `Vmax`.970assert_eq!(971Val::Vw(value).resolve(1., size, viewport_size),972Val::Vh(value).resolve(1., size, viewport_size)973);974assert_eq!(975Val::VMin(value).resolve(1., size, viewport_size),976Val::VMax(value).resolve(1., size, viewport_size)977);978assert_eq!(979Val::VMin(value).resolve(1., size, viewport_size),980Val::Vw(value).resolve(1., size, viewport_size)981);982}983984let viewport_size = vec2(1000., 500.);985assert_eq!(986Val::Vw(100.).resolve(1., size, viewport_size).unwrap(),9871000.988);989assert_eq!(990Val::Vh(100.).resolve(1., size, viewport_size).unwrap(),991500.992);993assert_eq!(Val::Vw(60.).resolve(1., size, viewport_size).unwrap(), 600.);994assert_eq!(Val::Vh(40.).resolve(1., size, viewport_size).unwrap(), 200.);995assert_eq!(996Val::VMin(50.).resolve(1., size, viewport_size).unwrap(),997250.998);999assert_eq!(1000Val::VMax(75.).resolve(1., size, viewport_size).unwrap(),1001750.1002);1003}10041005#[test]1006fn val_auto_is_non_evaluable() {1007let size = 250.;1008let viewport_size = vec2(1000., 500.);1009let resolve_auto = Val::Auto.resolve(1., size, viewport_size);10101011assert_eq!(resolve_auto, Err(ValArithmeticError::NonEvaluable));1012}10131014#[test]1015fn val_arithmetic_error_messages() {1016assert_eq!(1017format!("{}", ValArithmeticError::NonEvaluable),1018"the given variant of Val is not evaluable (non-numeric)"1019);1020}10211022#[test]1023fn val_str_parse() {1024assert_eq!("auto".parse::<Val>(), Ok(Val::Auto));1025assert_eq!("Auto".parse::<Val>(), Ok(Val::Auto));1026assert_eq!("AUTO".parse::<Val>(), Ok(Val::Auto));10271028assert_eq!("3px".parse::<Val>(), Ok(Val::Px(3.)));1029assert_eq!("3 px".parse::<Val>(), Ok(Val::Px(3.)));1030assert_eq!("3.5px".parse::<Val>(), Ok(Val::Px(3.5)));1031assert_eq!("-3px".parse::<Val>(), Ok(Val::Px(-3.)));1032assert_eq!("3.5 PX".parse::<Val>(), Ok(Val::Px(3.5)));10331034assert_eq!("3%".parse::<Val>(), Ok(Val::Percent(3.)));1035assert_eq!("3 %".parse::<Val>(), Ok(Val::Percent(3.)));1036assert_eq!("3.5%".parse::<Val>(), Ok(Val::Percent(3.5)));1037assert_eq!("-3%".parse::<Val>(), Ok(Val::Percent(-3.)));10381039assert_eq!("3vw".parse::<Val>(), Ok(Val::Vw(3.)));1040assert_eq!("3 vw".parse::<Val>(), Ok(Val::Vw(3.)));1041assert_eq!("3.5vw".parse::<Val>(), Ok(Val::Vw(3.5)));1042assert_eq!("-3vw".parse::<Val>(), Ok(Val::Vw(-3.)));1043assert_eq!("3.5 VW".parse::<Val>(), Ok(Val::Vw(3.5)));10441045assert_eq!("3vh".parse::<Val>(), Ok(Val::Vh(3.)));1046assert_eq!("3 vh".parse::<Val>(), Ok(Val::Vh(3.)));1047assert_eq!("3.5vh".parse::<Val>(), Ok(Val::Vh(3.5)));1048assert_eq!("-3vh".parse::<Val>(), Ok(Val::Vh(-3.)));1049assert_eq!("3.5 VH".parse::<Val>(), Ok(Val::Vh(3.5)));10501051assert_eq!("3vmin".parse::<Val>(), Ok(Val::VMin(3.)));1052assert_eq!("3 vmin".parse::<Val>(), Ok(Val::VMin(3.)));1053assert_eq!("3.5vmin".parse::<Val>(), Ok(Val::VMin(3.5)));1054assert_eq!("-3vmin".parse::<Val>(), Ok(Val::VMin(-3.)));1055assert_eq!("3.5 VMIN".parse::<Val>(), Ok(Val::VMin(3.5)));10561057assert_eq!("3vmax".parse::<Val>(), Ok(Val::VMax(3.)));1058assert_eq!("3 vmax".parse::<Val>(), Ok(Val::VMax(3.)));1059assert_eq!("3.5vmax".parse::<Val>(), Ok(Val::VMax(3.5)));1060assert_eq!("-3vmax".parse::<Val>(), Ok(Val::VMax(-3.)));1061assert_eq!("3.5 VMAX".parse::<Val>(), Ok(Val::VMax(3.5)));10621063assert_eq!("".parse::<Val>(), Err(ValParseError::UnitMissing));1064assert_eq!(1065"hello world".parse::<Val>(),1066Err(ValParseError::ValueMissing)1067);1068assert_eq!("3".parse::<Val>(), Err(ValParseError::UnitMissing));1069assert_eq!("3.5".parse::<Val>(), Err(ValParseError::UnitMissing));1070assert_eq!("3pxx".parse::<Val>(), Err(ValParseError::InvalidUnit));1071assert_eq!("3.5pxx".parse::<Val>(), Err(ValParseError::InvalidUnit));1072assert_eq!("3-3px".parse::<Val>(), Err(ValParseError::InvalidValue));1073assert_eq!("3.5-3px".parse::<Val>(), Err(ValParseError::InvalidValue));1074}10751076#[test]1077fn default_val_equals_const_default_val() {1078assert_eq!(Val::default(), Val::DEFAULT);1079}10801081#[test]1082fn uirect_default_equals_const_default() {1083assert_eq!(UiRect::default(), UiRect::all(Val::ZERO));1084assert_eq!(UiRect::default(), UiRect::DEFAULT);1085}10861087#[test]1088fn test_uirect_axes() {1089let x = Val::Px(1.);1090let y = Val::Vw(4.);1091let r = UiRect::axes(x, y);1092let h = UiRect::horizontal(x);1093let v = UiRect::vertical(y);10941095assert_eq!(r.top, v.top);1096assert_eq!(r.bottom, v.bottom);1097assert_eq!(r.left, h.left);1098assert_eq!(r.right, h.right);1099}11001101#[test]1102fn uirect_px() {1103let r = UiRect::px(3., 5., 20., 999.);1104assert_eq!(r.left, Val::Px(3.));1105assert_eq!(r.right, Val::Px(5.));1106assert_eq!(r.top, Val::Px(20.));1107assert_eq!(r.bottom, Val::Px(999.));1108}11091110#[test]1111fn uirect_percent() {1112let r = UiRect::percent(3., 5., 20., 99.);1113assert_eq!(r.left, Val::Percent(3.));1114assert_eq!(r.right, Val::Percent(5.));1115assert_eq!(r.top, Val::Percent(20.));1116assert_eq!(r.bottom, Val::Percent(99.));1117}1118}111911201121