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
This commit is contained in:
parent
5b899dcc3a
commit
bacc693fec
@ -10,6 +10,16 @@ use bevy_reflect::{ReflectDeserialize, ReflectSerialize};
|
||||
///
|
||||
/// This enum allows specifying values for various [`Node`](crate::Node) properties in different units,
|
||||
/// such as logical pixels, percentages, or automatically determined values.
|
||||
///
|
||||
/// `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:
|
||||
/// * `px`: logical pixels
|
||||
/// * `%`: percentage
|
||||
/// * `vw`: percentage of the viewport width
|
||||
/// * `vh`: percentage of the viewport height
|
||||
/// * `vmin`: percentage of the viewport's smaller dimension
|
||||
/// * `vmax`: percentage of the viewport's larger dimension
|
||||
///
|
||||
/// Additionally, `auto` will be parsed as [`Val::Auto`].
|
||||
#[derive(Copy, Clone, Debug, Reflect)]
|
||||
#[reflect(Default, PartialEq, Debug)]
|
||||
#[cfg_attr(
|
||||
@ -45,6 +55,70 @@ pub enum Val {
|
||||
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!(
|
||||
@ -689,6 +763,60 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
#[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);
|
||||
|
Loading…
Reference in New Issue
Block a user