diff --git a/crates/bevy_ui/Cargo.toml b/crates/bevy_ui/Cargo.toml index 24c4305251d95..caf95361fdbad 100644 --- a/crates/bevy_ui/Cargo.toml +++ b/crates/bevy_ui/Cargo.toml @@ -34,3 +34,4 @@ taffy = "0.1.0" serde = { version = "1", features = ["derive"] } smallvec = { version = "1.6", features = ["union", "const_generics"] } bytemuck = { version = "1.5", features = ["derive"] } +thiserror = "1.0.37" diff --git a/crates/bevy_ui/src/geometry.rs b/crates/bevy_ui/src/geometry.rs index 13c8de3bbc7ce..5525988a9a8e1 100644 --- a/crates/bevy_ui/src/geometry.rs +++ b/crates/bevy_ui/src/geometry.rs @@ -1,7 +1,6 @@ use crate::Val; -use bevy_math::Vec2; use bevy_reflect::Reflect; -use std::ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Sub, SubAssign}; +use std::ops::{Div, DivAssign, Mul, MulAssign}; /// A type which is commonly used to define positions, margins, paddings and borders. /// @@ -356,42 +355,15 @@ impl Size { }; } -impl Add for Size { - type Output = Size; - - fn add(self, rhs: Vec2) -> Self::Output { - Self { - width: self.width + rhs.x, - height: self.height + rhs.y, - } - } -} - -impl AddAssign for Size { - fn add_assign(&mut self, rhs: Vec2) { - self.width += rhs.x; - self.height += rhs.y; - } -} - -impl Sub for Size { - type Output = Size; - - fn sub(self, rhs: Vec2) -> Self::Output { +impl From<(Val, Val)> for Size { + fn from(vals: (Val, Val)) -> Self { Self { - width: self.width - rhs.x, - height: self.height - rhs.y, + width: vals.0, + height: vals.1, } } } -impl SubAssign for Size { - fn sub_assign(&mut self, rhs: Vec2) { - self.width -= rhs.x; - self.height -= rhs.y; - } -} - impl Mul for Size { type Output = Size; @@ -433,27 +405,16 @@ mod tests { use super::*; #[test] - fn test_size_add() { - assert_eq!( - Size::new(Val::Px(10.), Val::Px(10.)) + Vec2::new(10., 10.), - Size::new(Val::Px(20.), Val::Px(20.)) - ); + fn test_size_from() { + let size: Size = (Val::Px(20.), Val::Px(30.)).into(); - let mut size = Size::new(Val::Px(10.), Val::Px(10.)); - size += Vec2::new(10., 10.); - assert_eq!(size, Size::new(Val::Px(20.), Val::Px(20.))); - } - - #[test] - fn test_size_sub() { assert_eq!( - Size::new(Val::Px(20.), Val::Px(20.)) - Vec2::new(10., 10.), - Size::new(Val::Px(10.), Val::Px(10.)) + size, + Size { + width: Val::Px(20.), + height: Val::Px(30.), + } ); - - let mut size = Size::new(Val::Px(20.), Val::Px(20.)); - size -= Vec2::new(10., 10.); - assert_eq!(size, Size::new(Val::Px(10.), Val::Px(10.))); } #[test] diff --git a/crates/bevy_ui/src/ui_node.rs b/crates/bevy_ui/src/ui_node.rs index a8d3776e01f8f..e48057cf37724 100644 --- a/crates/bevy_ui/src/ui_node.rs +++ b/crates/bevy_ui/src/ui_node.rs @@ -9,7 +9,8 @@ use bevy_render::{ texture::{Image, DEFAULT_IMAGE_HANDLE}, }; use serde::{Deserialize, Serialize}; -use std::ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Sub, SubAssign}; +use std::ops::{Div, DivAssign, Mul, MulAssign}; +use thiserror::Error; /// Describes the size of a UI node #[derive(Component, Debug, Clone, Default, Reflect)] @@ -34,91 +35,146 @@ pub enum Val { Percent(f32), } -impl Add for Val { +impl Mul for Val { type Output = Val; - fn add(self, rhs: f32) -> Self::Output { + fn mul(self, rhs: f32) -> Self::Output { match self { Val::Undefined => Val::Undefined, Val::Auto => Val::Auto, - Val::Px(value) => Val::Px(value + rhs), - Val::Percent(value) => Val::Percent(value + rhs), + Val::Px(value) => Val::Px(value * rhs), + Val::Percent(value) => Val::Percent(value * rhs), } } } -impl AddAssign for Val { - fn add_assign(&mut self, rhs: f32) { +impl MulAssign for Val { + fn mul_assign(&mut self, rhs: f32) { match self { Val::Undefined | Val::Auto => {} - Val::Px(value) | Val::Percent(value) => *value += rhs, + Val::Px(value) | Val::Percent(value) => *value *= rhs, } } } -impl Sub for Val { +impl Div for Val { type Output = Val; - fn sub(self, rhs: f32) -> Self::Output { + fn div(self, rhs: f32) -> Self::Output { match self { Val::Undefined => Val::Undefined, Val::Auto => Val::Auto, - Val::Px(value) => Val::Px(value - rhs), - Val::Percent(value) => Val::Percent(value - rhs), + Val::Px(value) => Val::Px(value / rhs), + Val::Percent(value) => Val::Percent(value / rhs), } } } -impl SubAssign for Val { - fn sub_assign(&mut self, rhs: f32) { +impl DivAssign for Val { + fn div_assign(&mut self, rhs: f32) { match self { Val::Undefined | Val::Auto => {} - Val::Px(value) | Val::Percent(value) => *value -= rhs, + Val::Px(value) | Val::Percent(value) => *value /= rhs, } } } -impl Mul for Val { - type Output = Val; +#[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, +} - fn mul(self, rhs: f32) -> Self::Output { - match self { - Val::Undefined => Val::Undefined, - Val::Auto => Val::Auto, - Val::Px(value) => Val::Px(value * rhs), - Val::Percent(value) => Val::Percent(value * rhs), +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::Undefined, Val::Undefined) | (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), } } -} -impl MulAssign for Val { - fn mul_assign(&mut self, rhs: f32) { - match self { - Val::Undefined | Val::Auto => {} - Val::Px(value) | Val::Percent(value) => *value *= rhs, + /// 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::Undefined, Val::Undefined) | (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), } } -} -impl Div for Val { - type Output = Val; + /// 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(()) + } - fn div(self, rhs: f32) -> Self::Output { + /// 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 innver value returned unchanged. + pub fn evaluate(&self, size: f32) -> Result { match self { - Val::Undefined => Val::Undefined, - Val::Auto => Val::Auto, - Val::Px(value) => Val::Px(value / rhs), - Val::Percent(value) => Val::Percent(value / rhs), + Val::Percent(value) => Ok(size * value / 100.0), + Val::Px(value) => Ok(*value), + _ => Err(ValArithmeticError::NonEvaluateable), } } -} -impl DivAssign for Val { - fn div_assign(&mut self, rhs: f32) { - match self { - Val::Undefined | Val::Auto => {} - Val::Px(value) | Val::Percent(value) => *value /= rhs, - } + /// 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(()) } } @@ -413,3 +469,168 @@ pub struct CalculatedClip { /// The rect of the clip pub clip: Rect, } + +#[cfg(test)] +mod tests { + use crate::ValArithmeticError; + + use super::Val; + + #[test] + fn val_try_add() { + let undefined_sum = Val::Undefined.try_add(Val::Undefined).unwrap(); + 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!(undefined_sum, Val::Undefined); + 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 undefined_sum = Val::Undefined.try_sub(Val::Undefined).unwrap(); + 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!(undefined_sum, Val::Undefined); + 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::Undefined.try_add(Val::Auto); + let different_variant_sum_2 = Val::Px(50.).try_add(Val::Percent(50.)); + let different_variant_sum_3 = Val::Percent(50.).try_add(Val::Undefined); + + assert_eq!( + different_variant_sum_1, + Err(ValArithmeticError::NonIdenticalVariants) + ); + assert_eq!( + different_variant_sum_2, + Err(ValArithmeticError::NonIdenticalVariants) + ); + assert_eq!( + different_variant_sum_3, + Err(ValArithmeticError::NonIdenticalVariants) + ); + } + + #[test] + fn different_variant_val_try_sub() { + let different_variant_diff_1 = Val::Undefined.try_sub(Val::Auto); + let different_variant_diff_2 = Val::Px(50.).try_sub(Val::Percent(50.)); + let different_variant_diff_3 = Val::Percent(50.).try_sub(Val::Undefined); + + assert_eq!( + different_variant_diff_1, + Err(ValArithmeticError::NonIdenticalVariants) + ); + assert_eq!( + different_variant_diff_2, + Err(ValArithmeticError::NonIdenticalVariants) + ); + assert_eq!( + different_variant_diff_3, + 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_undefined = Val::Undefined.evaluate(size); + let evaluate_auto = Val::Auto.evaluate(size); + + assert_eq!(evaluate_undefined, Err(ValArithmeticError::NonEvaluateable)); + 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 undefined_sum = Val::Undefined.try_add_with_size(Val::Undefined, size); + let percent_sum = Val::Auto.try_add_with_size(Val::Auto, size); + + assert_eq!(undefined_sum, Err(ValArithmeticError::NonEvaluateable)); + 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)" + ); + } +}