Skip to content

Support calc bevy::ui::Val #5893

@zyxkad

Description

@zyxkad

What problem does this solve or what need does it fill?

In css, we have calc() expression that can auto calculate and update when value is changed (e.g. window resize event)

What solution would you like?

The define of bevy::ui::Val:

/// An enum that describes possible types of value in flexbox layout options
#[derive(Clone, PartialEq, Debug, Default, Serialize, Deserialize, Reflect)]
#[reflect_value(PartialEq, Serialize, Deserialize)]
pub enum Val {
    /// No value defined
    #[default]
    Undefined,
    /// Automatically determine this value
    Auto,
    /// Set this value in pixels
    Px(f32),
    /// Set this value in percent
    Percent(f32),
    /// A calc expression
    Calc(CalcVal),
}

Define of CalcVal:

#[derive(Clone, PartialEq, Debug, Serialize, Deserialize, Reflect)]
// Use `Box` to avoid infinity type size, so we cannot impl `Copy`
pub enum CalcVal {
    /// a + b
    Add(Box<Val>, Box<Val>),
    /// a - b
    Sub(Box<Val>, Box<Val>),
    /// a * b
    Mul(Box<Val>, f32),
    /// a / b
    Div(Box<Val>, f32),
    /// -a
    Neg(Box<Val>),
}

impl Add<f32> for Val {
    type Output = Val;

    fn add(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::Calc(_) => panic!(),
        }
    }
}

impl Add<Val> for &Val {
    type Output = Val;

    fn add(self, rhs: Val) -> Self::Output {
        if let Val::Undefined = rhs {
            return Val::Undefined;
        }
        match self {
            Val::Undefined => Val::Undefined,
            Val::Auto => match rhs {
                Val::Auto => Val::Auto,
                _ => Val::Calc(CalcVal::Add(Box::new(self.clone()), Box::new(rhs))),
            },
            Val::Px(value) => match rhs {
                Val::Px(rhs) => Val::Px(value + rhs),
                _ => Val::Calc(CalcVal::Add(Box::new(self.clone()), Box::new(rhs))),
            },
            Val::Percent(value) => match rhs {
                Val::Percent(rhs) => Val::Percent(value + rhs),
                _ => Val::Calc(CalcVal::Add(Box::new(self.clone()), Box::new(rhs))),
            },
            Val::Calc(value) => match value {
                _ => Val::Calc(CalcVal::Add(Box::new(Val::Calc(value.clone())), Box::new(rhs))),
            }
        }
    }
}

impl AddAssign<f32> for Val {
    fn add_assign(&mut self, rhs: f32) {
        match self {
            Val::Undefined | Val::Auto => {}
            Val::Px(value) | Val::Percent(value) => *value += rhs,
            _ => panic!(),
        }
    }
}

impl Sub<Val> for &Val {
    type Output = Val;

    fn sub(self, rhs: Val) -> Self::Output {
        if let Val::Undefined = rhs {
            return Val::Undefined;
        }
        match self {
            Val::Undefined => Val::Undefined,
            Val::Auto => match rhs {
                Val::Auto => Val::Auto,
                _ => Val::Calc(CalcVal::Sub(Box::new(self.clone()), Box::new(rhs))),
            },
            Val::Px(value) => match rhs {
                Val::Px(rhs) => Val::Px(value - rhs),
                _ => Val::Calc(CalcVal::Sub(Box::new(self.clone()), Box::new(rhs))),
            },
            Val::Percent(value) => match rhs {
                Val::Percent(rhs) => Val::Percent(value - rhs),
                _ => Val::Calc(CalcVal::Sub(Box::new(self.clone()), Box::new(rhs))),
            },
            Val::Calc(value) => match value {
                _ => Val::Calc(CalcVal::Sub(Box::new(Val::Calc(value.clone())), Box::new(rhs))),
            },
        }
    }
}

impl Mul<f32> for &Val {
    type Output = Val;

    fn mul(self, rhs: f32) -> Self::Output {
        Val::Calc(CalcVal::Mul(Box::new(self.clone()), rhs))
    }
}

impl Div<f32> for &Val {
    type Output = Val;

    fn div(self, rhs: f32) -> Self::Output {
        Val::Calc(CalcVal::Div(Box::new(self.clone()), rhs))
    }
}

impl Neg for &Val {
    type Output = Val;

    fn neg(self) -> Self::Output {
        match self {
            Val::Auto | Val::Undefined => self.clone(),
            Val::Px(value) => Val::Px(-value),
            Val::Percent(value) => Val::Percent(-value),
            Val::Calc(value) => match value.clone() {
                CalcVal::Sub(a, b) => Val::Calc(CalcVal::Sub(b.clone(), a.clone())),
                CalcVal::Neg(a) => *a,
                _ => Val::Calc(CalcVal::Neg(Box::new(self.clone()))),
            }
        }
    }
}

What alternative(s) have you considered?

Other solutions to solve and/or work around the problem presented.

Additional context

Any other information you would like to add such as related previous work,
screenshots, benchmarks, etc.

Metadata

Metadata

Assignees

No one assigned

    Labels

    A-UIGraphical user interfaces, styles, layouts, and widgetsC-UsabilityA targeted quality-of-life change that makes Bevy easier to use

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions