use bevy_math::{MismatchedUnitsError, StableInterpolate as _, TryStableInterpolate, Vec2};
use bevy_reflect::{std_traits::ReflectDefault, Reflect};
use bevy_utils::default;
use core::ops::{Div, DivAssign, Mul, MulAssign, Neg};
use thiserror::Error;
#[cfg(feature = "serialize")]
use bevy_reflect::{ReflectDeserialize, ReflectSerialize};
#[derive(Copy, Clone, Debug, Reflect)]
#[reflect(Default, PartialEq, Debug, Clone)]
#[cfg_attr(
feature = "serialize",
derive(serde::Serialize, serde::Deserialize),
reflect(Serialize, Deserialize)
)]
pub enum Val {
Auto,
Px(f32),
Percent(f32),
Vw(f32),
Vh(f32),
VMin(f32),
VMax(f32),
}
#[derive(Debug, Error, PartialEq, Eq)]
pub enum ValParseError {
UnitMissing,
ValueMissing,
InvalidValue,
InvalidUnit,
}
impl core::fmt::Display for ValParseError {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
ValParseError::UnitMissing => write!(f, "unit missing"),
ValParseError::ValueMissing => write!(f, "value missing"),
ValParseError::InvalidValue => write!(f, "invalid value"),
ValParseError::InvalidUnit => write!(f, "invalid unit"),
}
}
}
impl core::str::FromStr for Val {
type Err = ValParseError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let s = s.trim();
if s.eq_ignore_ascii_case("auto") {
return Ok(Val::Auto);
}
let Some(end_of_number) = s
.bytes()
.position(|c| !(c.is_ascii_digit() || c == b'.' || c == b'-' || c == b'+'))
else {
return Err(ValParseError::UnitMissing);
};
if end_of_number == 0 {
return Err(ValParseError::ValueMissing);
}
let (value, unit) = s.split_at(end_of_number);
let value: f32 = value.parse().map_err(|_| ValParseError::InvalidValue)?;
let unit = unit.trim();
if unit.eq_ignore_ascii_case("px") {
Ok(Val::Px(value))
} else if unit.eq_ignore_ascii_case("%") {
Ok(Val::Percent(value))
} else if unit.eq_ignore_ascii_case("vw") {
Ok(Val::Vw(value))
} else if unit.eq_ignore_ascii_case("vh") {
Ok(Val::Vh(value))
} else if unit.eq_ignore_ascii_case("vmin") {
Ok(Val::VMin(value))
} else if unit.eq_ignore_ascii_case("vmax") {
Ok(Val::VMax(value))
} else {
Err(ValParseError::InvalidUnit)
}
}
}
impl PartialEq for Val {
fn eq(&self, other: &Self) -> bool {
let same_unit = matches!(
(self, other),
(Self::Auto, Self::Auto)
| (Self::Px(_), Self::Px(_))
| (Self::Percent(_), Self::Percent(_))
| (Self::Vw(_), Self::Vw(_))
| (Self::Vh(_), Self::Vh(_))
| (Self::VMin(_), Self::VMin(_))
| (Self::VMax(_), Self::VMax(_))
);
let left = match self {
Self::Auto => None,
Self::Px(v)
| Self::Percent(v)
| Self::Vw(v)
| Self::Vh(v)
| Self::VMin(v)
| Self::VMax(v) => Some(v),
};
let right = match other {
Self::Auto => None,
Self::Px(v)
| Self::Percent(v)
| Self::Vw(v)
| Self::Vh(v)
| Self::VMin(v)
| Self::VMax(v) => Some(v),
};
match (same_unit, left, right) {
(true, a, b) => a == b,
(false, Some(&a), Some(&b)) => a == 0. && b == 0.,
_ => false,
}
}
}
impl Val {
pub const DEFAULT: Self = Self::Auto;
pub const ZERO: Self = Self::Px(0.0);
pub const fn left(self) -> UiRect {
UiRect::left(self)
}
pub const fn right(self) -> UiRect {
UiRect::right(self)
}
pub const fn top(self) -> UiRect {
UiRect::top(self)
}
pub const fn bottom(self) -> UiRect {
UiRect::bottom(self)
}
pub const fn all(self) -> UiRect {
UiRect::all(self)
}
pub const fn horizontal(self) -> UiRect {
UiRect::horizontal(self)
}
pub const fn vertical(self) -> UiRect {
UiRect::vertical(self)
}
}
impl Default for Val {
fn default() -> Self {
Self::DEFAULT
}
}
impl Mul<f32> 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<f32> 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<f32> 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<f32> 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,
}
}
}
impl Neg for Val {
type Output = Val;
fn neg(self) -> Self::Output {
match self {
Val::Px(value) => Val::Px(-value),
Val::Percent(value) => Val::Percent(-value),
Val::Vw(value) => Val::Vw(-value),
Val::Vh(value) => Val::Vh(-value),
Val::VMin(value) => Val::VMin(-value),
Val::VMax(value) => Val::VMax(-value),
_ => self,
}
}
}
#[derive(Debug, Eq, PartialEq, Clone, Copy, Error)]
pub enum ValArithmeticError {
#[error("the given variant of Val is not evaluable (non-numeric)")]
NonEvaluable,
}
impl Val {
pub const fn resolve(
self,
scale_factor: f32,
physical_base_value: f32,
physical_target_size: Vec2,
) -> Result<f32, ValArithmeticError> {
match self {
Val::Percent(value) => Ok(physical_base_value * value / 100.0),
Val::Px(value) => Ok(value * scale_factor),
Val::Vw(value) => Ok(physical_target_size.x * value / 100.0),
Val::Vh(value) => Ok(physical_target_size.y * value / 100.0),
Val::VMin(value) => {
Ok(physical_target_size.x.min(physical_target_size.y) * value / 100.0)
}
Val::VMax(value) => {
Ok(physical_target_size.x.max(physical_target_size.y) * value / 100.0)
}
Val::Auto => Err(ValArithmeticError::NonEvaluable),
}
}
}
impl TryStableInterpolate for Val {
type Error = MismatchedUnitsError;
fn try_interpolate_stable(&self, other: &Self, t: f32) -> Result<Self, Self::Error> {
match (self, other) {
(Val::Px(a), Val::Px(b)) => Ok(Val::Px(a.interpolate_stable(b, t))),
(Val::Percent(a), Val::Percent(b)) => Ok(Val::Percent(a.interpolate_stable(b, t))),
(Val::Vw(a), Val::Vw(b)) => Ok(Val::Vw(a.interpolate_stable(b, t))),
(Val::Vh(a), Val::Vh(b)) => Ok(Val::Vh(a.interpolate_stable(b, t))),
(Val::VMin(a), Val::VMin(b)) => Ok(Val::VMin(a.interpolate_stable(b, t))),
(Val::VMax(a), Val::VMax(b)) => Ok(Val::VMax(a.interpolate_stable(b, t))),
(Val::Auto, Val::Auto) => Ok(Val::Auto),
_ => Err(MismatchedUnitsError),
}
}
}
pub trait ValNum {
fn val_num_f32(self) -> f32;
}
macro_rules! impl_to_val_num {
($($impl_type:ty),*$(,)?) => {
$(
impl ValNum for $impl_type {
fn val_num_f32(self) -> f32 {
self as f32
}
}
)*
};
}
impl_to_val_num!(f32, f64, i8, i16, i32, i64, u8, u16, u32, u64, usize, isize);
pub const fn auto() -> Val {
Val::Auto
}
pub fn px<T: ValNum>(value: T) -> Val {
Val::Px(value.val_num_f32())
}
pub fn percent<T: ValNum>(value: T) -> Val {
Val::Percent(value.val_num_f32())
}
pub fn vw<T: ValNum>(value: T) -> Val {
Val::Vw(value.val_num_f32())
}
pub fn vh<T: ValNum>(value: T) -> Val {
Val::Vh(value.val_num_f32())
}
pub fn vmin<T: ValNum>(value: T) -> Val {
Val::VMin(value.val_num_f32())
}
pub fn vmax<T: ValNum>(value: T) -> Val {
Val::VMax(value.val_num_f32())
}
#[derive(Copy, Clone, PartialEq, Debug, Reflect)]
#[reflect(Default, PartialEq, Debug, Clone)]
#[cfg_attr(
feature = "serialize",
derive(serde::Serialize, serde::Deserialize),
reflect(Serialize, Deserialize)
)]
pub struct UiRect {
pub left: Val,
pub right: Val,
pub top: Val,
pub bottom: Val,
}
impl UiRect {
pub const DEFAULT: Self = Self::all(Val::ZERO);
pub const ZERO: Self = Self::all(Val::ZERO);
pub const AUTO: Self = Self::all(Val::Auto);
pub const fn new(left: Val, right: Val, top: Val, bottom: Val) -> Self {
UiRect {
left,
right,
top,
bottom,
}
}
pub const fn all(value: Val) -> Self {
UiRect {
left: value,
right: value,
top: value,
bottom: value,
}
}
pub const fn px(left: f32, right: f32, top: f32, bottom: f32) -> Self {
UiRect {
left: Val::Px(left),
right: Val::Px(right),
top: Val::Px(top),
bottom: Val::Px(bottom),
}
}
pub const fn percent(left: f32, right: f32, top: f32, bottom: f32) -> Self {
UiRect {
left: Val::Percent(left),
right: Val::Percent(right),
top: Val::Percent(top),
bottom: Val::Percent(bottom),
}
}
pub const fn horizontal(value: Val) -> Self {
Self {
left: value,
right: value,
..Self::DEFAULT
}
}
pub const fn vertical(value: Val) -> Self {
Self {
top: value,
bottom: value,
..Self::DEFAULT
}
}
pub const fn axes(horizontal: Val, vertical: Val) -> Self {
Self {
left: horizontal,
right: horizontal,
top: vertical,
bottom: vertical,
}
}
pub const fn left(left: Val) -> Self {
Self {
left,
..Self::DEFAULT
}
}
pub const fn right(right: Val) -> Self {
Self {
right,
..Self::DEFAULT
}
}
pub const fn top(top: Val) -> Self {
Self {
top,
..Self::DEFAULT
}
}
pub const fn bottom(bottom: Val) -> Self {
Self {
bottom,
..Self::DEFAULT
}
}
#[inline]
pub const fn with_left(mut self, left: Val) -> Self {
self.left = left;
self
}
#[inline]
pub const fn with_right(mut self, right: Val) -> Self {
self.right = right;
self
}
#[inline]
pub const fn with_top(mut self, top: Val) -> Self {
self.top = top;
self
}
#[inline]
pub const fn with_bottom(mut self, bottom: Val) -> Self {
self.bottom = bottom;
self
}
}
impl Default for UiRect {
fn default() -> Self {
Self::DEFAULT
}
}
impl From<Val> for UiRect {
fn from(value: Val) -> Self {
UiRect::all(value)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Reflect)]
#[reflect(Default, Debug, PartialEq)]
#[cfg_attr(
feature = "serialize",
derive(serde::Serialize, serde::Deserialize),
reflect(Serialize, Deserialize)
)]
pub struct UiPosition {
pub anchor: Vec2,
pub x: Val,
pub y: Val,
}
impl Default for UiPosition {
fn default() -> Self {
Self::CENTER
}
}
impl UiPosition {
pub const fn anchor(anchor: Vec2) -> Self {
Self {
anchor,
x: Val::ZERO,
y: Val::ZERO,
}
}
pub const TOP_LEFT: Self = Self::anchor(Vec2::new(-0.5, -0.5));
pub const LEFT: Self = Self::anchor(Vec2::new(-0.5, 0.0));
pub const BOTTOM_LEFT: Self = Self::anchor(Vec2::new(-0.5, 0.5));
pub const TOP: Self = Self::anchor(Vec2::new(0.0, -0.5));
pub const CENTER: Self = Self::anchor(Vec2::new(0.0, 0.0));
pub const BOTTOM: Self = Self::anchor(Vec2::new(0.0, 0.5));
pub const TOP_RIGHT: Self = Self::anchor(Vec2::new(0.5, -0.5));
pub const RIGHT: Self = Self::anchor(Vec2::new(0.5, 0.0));
pub const BOTTOM_RIGHT: Self = Self::anchor(Vec2::new(0.5, 0.5));
pub const fn new(anchor: Vec2, x: Val, y: Val) -> Self {
Self { anchor, x, y }
}
pub const fn at(self, x: Val, y: Val) -> Self {
Self { x, y, ..self }
}
pub const fn at_x(self, x: Val) -> Self {
Self { x, ..self }
}
pub const fn at_y(self, y: Val) -> Self {
Self { y, ..self }
}
pub const fn at_px(self, x: f32, y: f32) -> Self {
self.at(Val::Px(x), Val::Px(y))
}
pub const fn at_percent(self, x: f32, y: f32) -> Self {
self.at(Val::Percent(x), Val::Percent(y))
}
pub const fn with_anchor(self, anchor: Vec2) -> Self {
Self { anchor, ..self }
}
pub const fn top_left(x: Val, y: Val) -> Self {
Self::TOP_LEFT.at(x, y)
}
pub const fn left(x: Val, y: Val) -> Self {
Self::LEFT.at(x, y)
}
pub const fn bottom_left(x: Val, y: Val) -> Self {
Self::BOTTOM_LEFT.at(x, y)
}
pub const fn top(x: Val, y: Val) -> Self {
Self::TOP.at(x, y)
}
pub const fn center(x: Val, y: Val) -> Self {
Self::CENTER.at(x, y)
}
pub const fn bottom(x: Val, y: Val) -> Self {
Self::BOTTOM.at(x, y)
}
pub const fn top_right(x: Val, y: Val) -> Self {
Self::TOP_RIGHT.at(x, y)
}
pub const fn right(x: Val, y: Val) -> Self {
Self::RIGHT.at(x, y)
}
pub const fn bottom_right(x: Val, y: Val) -> Self {
Self::BOTTOM_RIGHT.at(x, y)
}
pub fn resolve(
self,
scale_factor: f32,
physical_size: Vec2,
physical_target_size: Vec2,
) -> Vec2 {
let d = self.anchor.map(|p| if 0. < p { -1. } else { 1. });
physical_size * self.anchor
+ d * Vec2::new(
self.x
.resolve(scale_factor, physical_size.x, physical_target_size)
.unwrap_or(0.),
self.y
.resolve(scale_factor, physical_size.y, physical_target_size)
.unwrap_or(0.),
)
}
}
impl From<Val> for UiPosition {
fn from(x: Val) -> Self {
Self { x, ..default() }
}
}
impl From<(Val, Val)> for UiPosition {
fn from((x, y): (Val, Val)) -> Self {
Self { x, y, ..default() }
}
}
#[cfg(test)]
mod tests {
use crate::geometry::*;
use bevy_math::vec2;
#[test]
fn val_evaluate() {
let size = 250.;
let viewport_size = vec2(1000., 500.);
let result = Val::Percent(80.).resolve(1., size, viewport_size).unwrap();
assert_eq!(result, size * 0.8);
}
#[test]
fn val_resolve_px() {
let size = 250.;
let viewport_size = vec2(1000., 500.);
let result = Val::Px(10.).resolve(1., size, viewport_size).unwrap();
assert_eq!(result, 10.);
}
#[test]
fn val_resolve_viewport_coords() {
let size = 250.;
let viewport_size = vec2(500., 500.);
for value in (-10..10).map(|value| value as f32) {
assert_eq!(
Val::Vw(value).resolve(1., size, viewport_size),
Val::Vh(value).resolve(1., size, viewport_size)
);
assert_eq!(
Val::VMin(value).resolve(1., size, viewport_size),
Val::VMax(value).resolve(1., size, viewport_size)
);
assert_eq!(
Val::VMin(value).resolve(1., size, viewport_size),
Val::Vw(value).resolve(1., size, viewport_size)
);
}
let viewport_size = vec2(1000., 500.);
assert_eq!(
Val::Vw(100.).resolve(1., size, viewport_size).unwrap(),
1000.
);
assert_eq!(
Val::Vh(100.).resolve(1., size, viewport_size).unwrap(),
500.
);
assert_eq!(Val::Vw(60.).resolve(1., size, viewport_size).unwrap(), 600.);
assert_eq!(Val::Vh(40.).resolve(1., size, viewport_size).unwrap(), 200.);
assert_eq!(
Val::VMin(50.).resolve(1., size, viewport_size).unwrap(),
250.
);
assert_eq!(
Val::VMax(75.).resolve(1., size, viewport_size).unwrap(),
750.
);
}
#[test]
fn val_auto_is_non_evaluable() {
let size = 250.;
let viewport_size = vec2(1000., 500.);
let resolve_auto = Val::Auto.resolve(1., size, viewport_size);
assert_eq!(resolve_auto, Err(ValArithmeticError::NonEvaluable));
}
#[test]
fn val_arithmetic_error_messages() {
assert_eq!(
format!("{}", ValArithmeticError::NonEvaluable),
"the given variant of Val is not evaluable (non-numeric)"
);
}
#[test]
fn val_str_parse() {
assert_eq!("auto".parse::<Val>(), Ok(Val::Auto));
assert_eq!("Auto".parse::<Val>(), Ok(Val::Auto));
assert_eq!("AUTO".parse::<Val>(), Ok(Val::Auto));
assert_eq!("3px".parse::<Val>(), Ok(Val::Px(3.)));
assert_eq!("3 px".parse::<Val>(), Ok(Val::Px(3.)));
assert_eq!("3.5px".parse::<Val>(), Ok(Val::Px(3.5)));
assert_eq!("-3px".parse::<Val>(), Ok(Val::Px(-3.)));
assert_eq!("3.5 PX".parse::<Val>(), Ok(Val::Px(3.5)));
assert_eq!("3%".parse::<Val>(), Ok(Val::Percent(3.)));
assert_eq!("3 %".parse::<Val>(), Ok(Val::Percent(3.)));
assert_eq!("3.5%".parse::<Val>(), Ok(Val::Percent(3.5)));
assert_eq!("-3%".parse::<Val>(), Ok(Val::Percent(-3.)));
assert_eq!("3vw".parse::<Val>(), Ok(Val::Vw(3.)));
assert_eq!("3 vw".parse::<Val>(), Ok(Val::Vw(3.)));
assert_eq!("3.5vw".parse::<Val>(), Ok(Val::Vw(3.5)));
assert_eq!("-3vw".parse::<Val>(), Ok(Val::Vw(-3.)));
assert_eq!("3.5 VW".parse::<Val>(), Ok(Val::Vw(3.5)));
assert_eq!("3vh".parse::<Val>(), Ok(Val::Vh(3.)));
assert_eq!("3 vh".parse::<Val>(), Ok(Val::Vh(3.)));
assert_eq!("3.5vh".parse::<Val>(), Ok(Val::Vh(3.5)));
assert_eq!("-3vh".parse::<Val>(), Ok(Val::Vh(-3.)));
assert_eq!("3.5 VH".parse::<Val>(), Ok(Val::Vh(3.5)));
assert_eq!("3vmin".parse::<Val>(), Ok(Val::VMin(3.)));
assert_eq!("3 vmin".parse::<Val>(), Ok(Val::VMin(3.)));
assert_eq!("3.5vmin".parse::<Val>(), Ok(Val::VMin(3.5)));
assert_eq!("-3vmin".parse::<Val>(), Ok(Val::VMin(-3.)));
assert_eq!("3.5 VMIN".parse::<Val>(), Ok(Val::VMin(3.5)));
assert_eq!("3vmax".parse::<Val>(), Ok(Val::VMax(3.)));
assert_eq!("3 vmax".parse::<Val>(), Ok(Val::VMax(3.)));
assert_eq!("3.5vmax".parse::<Val>(), Ok(Val::VMax(3.5)));
assert_eq!("-3vmax".parse::<Val>(), Ok(Val::VMax(-3.)));
assert_eq!("3.5 VMAX".parse::<Val>(), Ok(Val::VMax(3.5)));
assert_eq!("".parse::<Val>(), Err(ValParseError::UnitMissing));
assert_eq!(
"hello world".parse::<Val>(),
Err(ValParseError::ValueMissing)
);
assert_eq!("3".parse::<Val>(), Err(ValParseError::UnitMissing));
assert_eq!("3.5".parse::<Val>(), Err(ValParseError::UnitMissing));
assert_eq!("3pxx".parse::<Val>(), Err(ValParseError::InvalidUnit));
assert_eq!("3.5pxx".parse::<Val>(), Err(ValParseError::InvalidUnit));
assert_eq!("3-3px".parse::<Val>(), Err(ValParseError::InvalidValue));
assert_eq!("3.5-3px".parse::<Val>(), Err(ValParseError::InvalidValue));
}
#[test]
fn default_val_equals_const_default_val() {
assert_eq!(Val::default(), Val::DEFAULT);
}
#[test]
fn uirect_default_equals_const_default() {
assert_eq!(UiRect::default(), UiRect::all(Val::ZERO));
assert_eq!(UiRect::default(), UiRect::DEFAULT);
}
#[test]
fn test_uirect_axes() {
let x = Val::Px(1.);
let y = Val::Vw(4.);
let r = UiRect::axes(x, y);
let h = UiRect::horizontal(x);
let v = UiRect::vertical(y);
assert_eq!(r.top, v.top);
assert_eq!(r.bottom, v.bottom);
assert_eq!(r.left, h.left);
assert_eq!(r.right, h.right);
}
#[test]
fn uirect_px() {
let r = UiRect::px(3., 5., 20., 999.);
assert_eq!(r.left, Val::Px(3.));
assert_eq!(r.right, Val::Px(5.));
assert_eq!(r.top, Val::Px(20.));
assert_eq!(r.bottom, Val::Px(999.));
}
#[test]
fn uirect_percent() {
let r = UiRect::percent(3., 5., 20., 99.);
assert_eq!(r.left, Val::Percent(3.));
assert_eq!(r.right, Val::Percent(5.));
assert_eq!(r.top, Val::Percent(20.));
assert_eq!(r.bottom, Val::Percent(99.));
}
#[test]
fn val_constructor_fns_return_correct_val_variant() {
assert_eq!(auto(), Val::Auto);
assert_eq!(px(0.0), Val::Px(0.0));
assert_eq!(percent(0.0), Val::Percent(0.0));
assert_eq!(vw(0.0), Val::Vw(0.0));
assert_eq!(vh(0.0), Val::Vh(0.0));
assert_eq!(vmin(0.0), Val::VMin(0.0));
assert_eq!(vmax(0.0), Val::VMax(0.0));
}
}