Skip to content

Commit ada1d75

Browse files
ickshonpetychedelia
authored andcommitted
Fallible arithmetic for Val and Val2 (bevyengine#24014)
# Objective Add fallible arithmetic support for `Val`s. ## Solution Implement `try_add` and `try_sub` methods for `Val` and `Val2`. The methods succeed only if the `Val`s are matching variants, and neither is `Val:Auto`. I considered allowing arithmetric with incompatible zero(s), but it feels potentially confusing as there isn't a clear and natural way to select the units for the result. Also removed the `val_arithmetic_error_messages` test, instead of updating it. I don't see any value there.
1 parent b2060f9 commit ada1d75

2 files changed

Lines changed: 142 additions & 3 deletions

File tree

crates/bevy_ui/src/geometry.rs

Lines changed: 103 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -301,6 +301,54 @@ impl Val {
301301
pub const fn vertical(self) -> UiRect {
302302
UiRect::vertical(self)
303303
}
304+
305+
/// Try to add two `Val`s.
306+
///
307+
/// Returns [`ValArithmeticError::IncompatibleUnits`] if the units differ or both are `Val::Auto`.
308+
///
309+
/// ```
310+
/// # use bevy_ui::{Val, ValArithmeticError};
311+
/// assert_eq!(Val::Px(1.).try_add(Val::Px(2.)), Ok(Val::Px(3.)));
312+
/// assert_eq!(
313+
/// Val::Px(1.).try_add(Val::Percent(2.)),
314+
/// Err(ValArithmeticError::IncompatibleUnits)
315+
/// );
316+
/// ```
317+
pub const fn try_add(self, val: Val) -> Result<Val, ValArithmeticError> {
318+
match (self, val) {
319+
(Val::Px(u), Val::Px(v)) => Ok(Val::Px(u + v)),
320+
(Val::Percent(u), Val::Percent(v)) => Ok(Val::Percent(u + v)),
321+
(Val::Vw(u), Val::Vw(v)) => Ok(Val::Vw(u + v)),
322+
(Val::Vh(u), Val::Vh(v)) => Ok(Val::Vh(u + v)),
323+
(Val::VMin(u), Val::VMin(v)) => Ok(Val::VMin(u + v)),
324+
(Val::VMax(u), Val::VMax(v)) => Ok(Val::VMax(u + v)),
325+
_ => Err(ValArithmeticError::IncompatibleUnits),
326+
}
327+
}
328+
329+
/// Try to subtract one `Val` from another.
330+
///
331+
/// Returns [`ValArithmeticError::IncompatibleUnits`] if the units differ or both are `Val::Auto`.
332+
///
333+
/// ```
334+
/// # use bevy_ui::{Val, ValArithmeticError};
335+
/// assert_eq!(Val::Px(3.).try_sub(Val::Px(2.)), Ok(Val::Px(1.)));
336+
/// assert_eq!(
337+
/// Val::Px(1.).try_sub(Val::Percent(2.)),
338+
/// Err(ValArithmeticError::IncompatibleUnits)
339+
/// );
340+
/// ```
341+
pub const fn try_sub(self, val: Val) -> Result<Val, ValArithmeticError> {
342+
match (self, val) {
343+
(Val::Px(u), Val::Px(v)) => Ok(Val::Px(u - v)),
344+
(Val::Percent(u), Val::Percent(v)) => Ok(Val::Percent(u - v)),
345+
(Val::Vw(u), Val::Vw(v)) => Ok(Val::Vw(u - v)),
346+
(Val::Vh(u), Val::Vh(v)) => Ok(Val::Vh(u - v)),
347+
(Val::VMin(u), Val::VMin(v)) => Ok(Val::VMin(u - v)),
348+
(Val::VMax(u), Val::VMax(v)) => Ok(Val::VMax(u - v)),
349+
_ => Err(ValArithmeticError::IncompatibleUnits),
350+
}
351+
}
304352
}
305353

306354
impl Default for Val {
@@ -389,6 +437,8 @@ impl Neg for Val {
389437
pub enum ValArithmeticError {
390438
#[error("the given variant of Val is not evaluable (non-numeric)")]
391439
NonEvaluable,
440+
#[error("the given variants of Val have incompatible units")]
441+
IncompatibleUnits,
392442
}
393443

394444
impl Val {
@@ -1178,10 +1228,60 @@ mod tests {
11781228
}
11791229

11801230
#[test]
1181-
fn val_arithmetic_error_messages() {
1231+
fn val_try_add_compatible_variants() {
1232+
assert_eq!(Val::Px(1.).try_add(Val::Px(2.)), Ok(Val::Px(3.)));
1233+
assert_eq!(
1234+
Val::Percent(1.).try_add(Val::Percent(2.)),
1235+
Ok(Val::Percent(3.))
1236+
);
1237+
assert_eq!(Val::Vw(1.).try_add(Val::Vw(2.)), Ok(Val::Vw(3.)));
1238+
assert_eq!(Val::Vh(1.).try_add(Val::Vh(2.)), Ok(Val::Vh(3.)));
1239+
assert_eq!(Val::VMin(1.).try_add(Val::VMin(2.)), Ok(Val::VMin(3.)));
1240+
assert_eq!(Val::VMax(1.).try_add(Val::VMax(2.)), Ok(Val::VMax(3.)));
1241+
}
1242+
1243+
#[test]
1244+
fn val_try_add_incompatible_variants() {
1245+
assert_eq!(
1246+
Val::Auto.try_add(Val::Auto),
1247+
Err(ValArithmeticError::IncompatibleUnits)
1248+
);
1249+
assert_eq!(
1250+
Val::Px(1.).try_add(Val::Percent(2.)),
1251+
Err(ValArithmeticError::IncompatibleUnits)
1252+
);
1253+
assert_eq!(
1254+
Val::Auto.try_add(Val::Px(1.)),
1255+
Err(ValArithmeticError::IncompatibleUnits)
1256+
);
1257+
}
1258+
1259+
#[test]
1260+
fn val_try_sub_compatible_variants() {
1261+
assert_eq!(Val::Px(3.).try_sub(Val::Px(2.)), Ok(Val::Px(1.)));
1262+
assert_eq!(
1263+
Val::Percent(3.).try_sub(Val::Percent(2.)),
1264+
Ok(Val::Percent(1.))
1265+
);
1266+
assert_eq!(Val::Vw(3.).try_sub(Val::Vw(2.)), Ok(Val::Vw(1.)));
1267+
assert_eq!(Val::Vh(3.).try_sub(Val::Vh(2.)), Ok(Val::Vh(1.)));
1268+
assert_eq!(Val::VMin(3.).try_sub(Val::VMin(2.)), Ok(Val::VMin(1.)));
1269+
assert_eq!(Val::VMax(3.).try_sub(Val::VMax(2.)), Ok(Val::VMax(1.)));
1270+
}
1271+
1272+
#[test]
1273+
fn val_try_sub_incompatible_variants() {
1274+
assert_eq!(
1275+
Val::Auto.try_sub(Val::Auto),
1276+
Err(ValArithmeticError::IncompatibleUnits)
1277+
);
1278+
assert_eq!(
1279+
Val::Px(1.).try_sub(Val::Percent(2.)),
1280+
Err(ValArithmeticError::IncompatibleUnits)
1281+
);
11821282
assert_eq!(
1183-
format!("{}", ValArithmeticError::NonEvaluable),
1184-
"the given variant of Val is not evaluable (non-numeric)"
1283+
Val::Auto.try_sub(Val::Px(1.)),
1284+
Err(ValArithmeticError::IncompatibleUnits)
11851285
);
11861286
}
11871287

crates/bevy_ui/src/ui_transform.rs

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
use crate::Val;
2+
use crate::ValArithmeticError;
23
use bevy_derive::Deref;
34
use bevy_ecs::component::Component;
45
use bevy_ecs::prelude::ReflectComponent;
@@ -69,6 +70,44 @@ impl Val2 {
6970
.unwrap_or(0.),
7071
)
7172
}
73+
74+
/// Try to add two `Val2`s component-wise.
75+
///
76+
/// Returns [`ValArithmeticError::IncompatibleUnits`] if either component has mismatched units.
77+
///
78+
/// ```
79+
/// # use bevy_ui::{Val, Val2, ValArithmeticError};
80+
/// assert_eq!(Val2::px(1., 2.).try_add(Val2::px(3., 4.)), Ok(Val2::px(4., 6.)));
81+
/// assert_eq!(
82+
/// Val2::new(Val::Px(1.), Val::Px(2.)).try_add(Val2::new(Val::Percent(3.), Val::Px(4.))),
83+
/// Err(ValArithmeticError::IncompatibleUnits)
84+
/// );
85+
/// ```
86+
pub fn try_add(self, other: Val2) -> Result<Self, ValArithmeticError> {
87+
let (Ok(x), Ok(y)) = (self.x.try_add(other.x), self.y.try_add(other.y)) else {
88+
return Err(ValArithmeticError::IncompatibleUnits);
89+
};
90+
Ok(Self { x, y })
91+
}
92+
93+
/// Try to subtract two `Val2`s component-wise.
94+
///
95+
/// Returns [`ValArithmeticError::IncompatibleUnits`] if either component has mismatched units.
96+
///
97+
/// ```
98+
/// # use bevy_ui::{Val, Val2, ValArithmeticError};
99+
/// assert_eq!(Val2::px(3., 4.).try_sub(Val2::px(1., 2.)), Ok(Val2::px(2., 2.)));
100+
/// assert_eq!(
101+
/// Val2::new(Val::Px(1.), Val::Px(2.)).try_sub(Val2::new(Val::Percent(3.), Val::Px(4.))),
102+
/// Err(ValArithmeticError::IncompatibleUnits)
103+
/// );
104+
/// ```
105+
pub fn try_sub(self, other: Val2) -> Result<Self, ValArithmeticError> {
106+
let (Ok(x), Ok(y)) = (self.x.try_sub(other.x), self.y.try_sub(other.y)) else {
107+
return Err(ValArithmeticError::IncompatibleUnits);
108+
};
109+
Ok(Self { x, y })
110+
}
72111
}
73112

74113
impl Default for Val2 {

0 commit comments

Comments
 (0)