Skip to content

Commit bacc693

Browse files
Implement FromStr for Val (#16926)
# Objective This PR implements `FromStr` for `Val`, so developers can parse values like `10px` and `50%` ## Testing Added tests for this. I think they cover pretty much everything, and it's a fairly simple unit test. ## Limitations Currently the following float values are not parsed: - `inf`, `-inf`, `+infinity`, `NaN` - `2.5E10`, `2.5e10`, `2.5E-10` For my use case this is perfectly fine but other developers might want to support these values
1 parent 5b899dc commit bacc693

File tree

1 file changed

+128
-0
lines changed

1 file changed

+128
-0
lines changed

crates/bevy_ui/src/geometry.rs

Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,16 @@ use bevy_reflect::{ReflectDeserialize, ReflectSerialize};
1010
///
1111
/// This enum allows specifying values for various [`Node`](crate::Node) properties in different units,
1212
/// 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 pixels
16+
/// * `%`: percentage
17+
/// * `vw`: percentage of the viewport width
18+
/// * `vh`: percentage of the viewport height
19+
/// * `vmin`: percentage of the viewport's smaller dimension
20+
/// * `vmax`: percentage of the viewport's larger dimension
21+
///
22+
/// Additionally, `auto` will be parsed as [`Val::Auto`].
1323
#[derive(Copy, Clone, Debug, Reflect)]
1424
#[reflect(Default, PartialEq, Debug)]
1525
#[cfg_attr(
@@ -45,6 +55,70 @@ pub enum Val {
4555
VMax(f32),
4656
}
4757

58+
#[derive(Debug, Error, PartialEq, Eq)]
59+
pub enum ValParseError {
60+
UnitMissing,
61+
ValueMissing,
62+
InvalidValue,
63+
InvalidUnit,
64+
}
65+
66+
impl core::fmt::Display for ValParseError {
67+
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
68+
match self {
69+
ValParseError::UnitMissing => write!(f, "unit missing"),
70+
ValParseError::ValueMissing => write!(f, "value missing"),
71+
ValParseError::InvalidValue => write!(f, "invalid value"),
72+
ValParseError::InvalidUnit => write!(f, "invalid unit"),
73+
}
74+
}
75+
}
76+
77+
impl core::str::FromStr for Val {
78+
type Err = ValParseError;
79+
80+
fn from_str(s: &str) -> Result<Self, Self::Err> {
81+
let s = s.trim();
82+
83+
if s.eq_ignore_ascii_case("auto") {
84+
return Ok(Val::Auto);
85+
}
86+
87+
let Some(end_of_number) = s
88+
.bytes()
89+
.position(|c| !(c.is_ascii_digit() || c == b'.' || c == b'-' || c == b'+'))
90+
else {
91+
return Err(ValParseError::UnitMissing);
92+
};
93+
94+
if end_of_number == 0 {
95+
return Err(ValParseError::ValueMissing);
96+
}
97+
98+
let (value, unit) = s.split_at(end_of_number);
99+
100+
let value: f32 = value.parse().map_err(|_| ValParseError::InvalidValue)?;
101+
102+
let unit = unit.trim();
103+
104+
if unit.eq_ignore_ascii_case("px") {
105+
Ok(Val::Px(value))
106+
} else if unit.eq_ignore_ascii_case("%") {
107+
Ok(Val::Percent(value))
108+
} else if unit.eq_ignore_ascii_case("vw") {
109+
Ok(Val::Vw(value))
110+
} else if unit.eq_ignore_ascii_case("vh") {
111+
Ok(Val::Vh(value))
112+
} else if unit.eq_ignore_ascii_case("vmin") {
113+
Ok(Val::VMin(value))
114+
} else if unit.eq_ignore_ascii_case("vmax") {
115+
Ok(Val::VMax(value))
116+
} else {
117+
Err(ValParseError::InvalidUnit)
118+
}
119+
}
120+
}
121+
48122
impl PartialEq for Val {
49123
fn eq(&self, other: &Self) -> bool {
50124
let same_unit = matches!(
@@ -689,6 +763,60 @@ mod tests {
689763
);
690764
}
691765

766+
#[test]
767+
fn val_str_parse() {
768+
assert_eq!("auto".parse::<Val>(), Ok(Val::Auto));
769+
assert_eq!("Auto".parse::<Val>(), Ok(Val::Auto));
770+
assert_eq!("AUTO".parse::<Val>(), Ok(Val::Auto));
771+
772+
assert_eq!("3px".parse::<Val>(), Ok(Val::Px(3.)));
773+
assert_eq!("3 px".parse::<Val>(), Ok(Val::Px(3.)));
774+
assert_eq!("3.5px".parse::<Val>(), Ok(Val::Px(3.5)));
775+
assert_eq!("-3px".parse::<Val>(), Ok(Val::Px(-3.)));
776+
assert_eq!("3.5 PX".parse::<Val>(), Ok(Val::Px(3.5)));
777+
778+
assert_eq!("3%".parse::<Val>(), Ok(Val::Percent(3.)));
779+
assert_eq!("3 %".parse::<Val>(), Ok(Val::Percent(3.)));
780+
assert_eq!("3.5%".parse::<Val>(), Ok(Val::Percent(3.5)));
781+
assert_eq!("-3%".parse::<Val>(), Ok(Val::Percent(-3.)));
782+
783+
assert_eq!("3vw".parse::<Val>(), Ok(Val::Vw(3.)));
784+
assert_eq!("3 vw".parse::<Val>(), Ok(Val::Vw(3.)));
785+
assert_eq!("3.5vw".parse::<Val>(), Ok(Val::Vw(3.5)));
786+
assert_eq!("-3vw".parse::<Val>(), Ok(Val::Vw(-3.)));
787+
assert_eq!("3.5 VW".parse::<Val>(), Ok(Val::Vw(3.5)));
788+
789+
assert_eq!("3vh".parse::<Val>(), Ok(Val::Vh(3.)));
790+
assert_eq!("3 vh".parse::<Val>(), Ok(Val::Vh(3.)));
791+
assert_eq!("3.5vh".parse::<Val>(), Ok(Val::Vh(3.5)));
792+
assert_eq!("-3vh".parse::<Val>(), Ok(Val::Vh(-3.)));
793+
assert_eq!("3.5 VH".parse::<Val>(), Ok(Val::Vh(3.5)));
794+
795+
assert_eq!("3vmin".parse::<Val>(), Ok(Val::VMin(3.)));
796+
assert_eq!("3 vmin".parse::<Val>(), Ok(Val::VMin(3.)));
797+
assert_eq!("3.5vmin".parse::<Val>(), Ok(Val::VMin(3.5)));
798+
assert_eq!("-3vmin".parse::<Val>(), Ok(Val::VMin(-3.)));
799+
assert_eq!("3.5 VMIN".parse::<Val>(), Ok(Val::VMin(3.5)));
800+
801+
assert_eq!("3vmax".parse::<Val>(), Ok(Val::VMax(3.)));
802+
assert_eq!("3 vmax".parse::<Val>(), Ok(Val::VMax(3.)));
803+
assert_eq!("3.5vmax".parse::<Val>(), Ok(Val::VMax(3.5)));
804+
assert_eq!("-3vmax".parse::<Val>(), Ok(Val::VMax(-3.)));
805+
assert_eq!("3.5 VMAX".parse::<Val>(), Ok(Val::VMax(3.5)));
806+
807+
assert_eq!("".parse::<Val>(), Err(ValParseError::UnitMissing));
808+
assert_eq!(
809+
"hello world".parse::<Val>(),
810+
Err(ValParseError::ValueMissing)
811+
);
812+
assert_eq!("3".parse::<Val>(), Err(ValParseError::UnitMissing));
813+
assert_eq!("3.5".parse::<Val>(), Err(ValParseError::UnitMissing));
814+
assert_eq!("3pxx".parse::<Val>(), Err(ValParseError::InvalidUnit));
815+
assert_eq!("3.5pxx".parse::<Val>(), Err(ValParseError::InvalidUnit));
816+
assert_eq!("3-3px".parse::<Val>(), Err(ValParseError::InvalidValue));
817+
assert_eq!("3.5-3px".parse::<Val>(), Err(ValParseError::InvalidValue));
818+
}
819+
692820
#[test]
693821
fn default_val_equals_const_default_val() {
694822
assert_eq!(Val::default(), Val::DEFAULT);

0 commit comments

Comments
 (0)