bevyengine / bevy

A refreshingly simple data-driven game engine built in Rust
https://bevyengine.org
Apache License 2.0
35.7k stars 3.53k forks source link

Support calc `bevy::ui::Val` #5893

Open zyxkad opened 2 years ago

zyxkad commented 2 years ago

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.

afonsolage commented 2 years ago

Reading calc definition, it seems a bit more complex, since it supports:

Also I think this should be upstreammed to Taffy, since all layout calculation/resolution is done there.

zyxkad commented 2 years ago

Reading calc definition, it seems a bit more complex, since it supports:

  • Math expression;
  • Add, Sub, Mul and Div operations;
  • Nested calc calls;
  • Parenthesis resolution;

Also I think this should be upstreammed to Taffy, since all layout calculation/resolution is done there.

Math expression only include Add, Sub, Mul and Div operations for calc.
Parenthesis resolution is only for parsing text.

And I will open a issue on taffy, thanks.

TimJentzsch commented 2 years ago

Could you provide a use case that you would need this for?

zyxkad commented 2 years ago

Could you provide a use case that you would need this for?

If I want to give border to some Bundle, I need to use calc