use super::*;
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Hash)]
#[serde(crate = "crate::serde")]
pub enum Size {
Pixels(Numeric),
Percent(Numeric),
Combined(Numeric, Numeric),
}
impl Display for Size {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Size::Pixels(val) => write!(f, "{}px", val),
Size::Percent(val) => write!(f, "{}%", val),
Size::Combined(pix, per) => write!(f, "{}px + {}%", pix, per),
}
}
}
impl Neg for Size {
type Output = Size;
fn neg(self) -> Self::Output {
match self {
Size::Pixels(pix) => Size::Pixels(-pix),
Size::Percent(per) => Size::Percent(-per),
Size::Combined(pix, per) => Size::Combined(-pix, -per),
}
}
}
impl Add for Size {
type Output = Size;
fn add(self, rhs: Self) -> Self::Output {
let mut pixel_component: Numeric = Default::default();
let mut percent_component: Numeric = Default::default();
[self, rhs].iter().for_each(|size| match size {
Size::Pixels(s) => pixel_component = pixel_component + *s,
Size::Percent(s) => percent_component = percent_component + *s,
Size::Combined(s0, s1) => {
pixel_component = pixel_component + *s0;
percent_component = percent_component + *s1;
}
});
Size::Combined(pixel_component, percent_component)
}
}
impl Add<Percent> for Size {
type Output = Size;
fn add(self, rhs: Percent) -> Self::Output {
self + Size::Percent(rhs.0)
}
}
impl Sub<Percent> for Size {
type Output = Size;
fn sub(self, rhs: Percent) -> Self::Output {
self - Size::Percent(rhs.0)
}
}
impl Add<Size> for Percent {
type Output = Size;
fn add(self, rhs: Size) -> Self::Output {
Size::Percent(self.0) + rhs
}
}
impl Sub<Size> for Percent {
type Output = Size;
fn sub(self, rhs: Size) -> Self::Output {
Size::Percent(self.0) - rhs
}
}
impl Sub for Size {
type Output = Size;
fn sub(self, rhs: Self) -> Self::Output {
let mut pixel_component: Numeric = Default::default();
let mut percent_component: Numeric = Default::default();
let sizes = [(self, 1), (rhs, -1)];
for (size, multiplier) in sizes.iter() {
match size {
Size::Pixels(s) => {
pixel_component = pixel_component + *s * Numeric::from(*multiplier)
}
Size::Percent(s) => {
percent_component = percent_component + *s * Numeric::from(*multiplier)
}
Size::Combined(s0, s1) => {
pixel_component = pixel_component + *s0 * Numeric::from(*multiplier);
percent_component = percent_component + *s1 * Numeric::from(*multiplier);
}
}
}
Size::Combined(pixel_component, percent_component)
}
}
impl Size {
#[allow(non_snake_case)]
pub fn ZERO() -> Self {
Size::Pixels(Numeric::F64(0.0))
}
pub fn expect_percent(&self) -> f64 {
match &self {
Size::Percent(val) => val.to_float() / 100.0,
Size::Pixels(val) => {
log::warn!("Percentage value expected but stored value was pixel.");
val.to_float() / 100.0
}
Size::Combined(_, percent) => {
log::warn!("Percentage value expected but stored value was a combination.");
percent.to_float() / 100.0
}
}
}
pub fn expect_pixels(&self) -> Numeric {
match &self {
Size::Pixels(val) => val.clone(),
Size::Percent(val) => {
log::warn!("Pixel value expected but stored value was percentage.");
val.clone()
}
Size::Combined(pixels, _) => {
log::warn!("Pixel value expected but stored value was a combination.");
pixels.clone()
}
}
}
}
#[derive(Clone, Copy)]
pub enum Axis {
X,
Y,
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize, Default, PartialEq, Eq, Hash)]
#[serde(crate = "crate::serde")]
pub enum LayoutRole {
#[default]
Default,
Breakout,
}
impl Interpolatable for LayoutRole {}
impl HelperFunctions for LayoutRole {}
impl Size {
pub fn evaluate(&self, bounds: (f64, f64), axis: Axis) -> f64 {
let target_bound = match axis {
Axis::X => bounds.0,
Axis::Y => bounds.1,
};
match &self {
Size::Pixels(num) => num.to_float(),
Size::Percent(num) => target_bound * (num.to_float() / 100.0),
Size::Combined(pixel_component, percent_component) => {
(target_bound * (percent_component.to_float() / 100.0)) + pixel_component.to_float()
}
}
}
}
#[derive(Debug, Default, Serialize, Deserialize)]
pub struct CommonProperty {
name: String,
property_type: String,
optional: bool,
}
#[derive(Debug, Default, Clone)]
pub struct CommonProperties {
pub id: LocalProperty<Option<String>>,
pub x: LocalProperty<Option<Size>>,
pub y: LocalProperty<Option<Size>>,
pub padding_x: LocalProperty<Option<Size>>,
pub padding_y: LocalProperty<Option<Size>>,
pub width: LocalProperty<Option<Size>>,
pub height: LocalProperty<Option<Size>>,
pub anchor_x: LocalProperty<Option<Size>>,
pub anchor_y: LocalProperty<Option<Size>>,
pub scale_x: LocalProperty<Option<Size>>,
pub scale_y: LocalProperty<Option<Size>>,
pub skew_x: LocalProperty<Option<Rotation>>,
pub skew_y: LocalProperty<Option<Rotation>>,
pub rotate: LocalProperty<Option<Rotation>>,
pub transform: LocalProperty<Option<Transform2D>>,
pub opacity: LocalProperty<Option<Opacity>>,
pub layout_role: LocalProperty<Option<LayoutRole>>,
pub unclippable: LocalProperty<Option<bool>>,
pub _raycastable: LocalProperty<Option<bool>>,
pub _suspended: LocalProperty<Option<bool>>,
}
impl CommonProperties {
pub fn get_default_properties_literal() -> Vec<(String, String)> {
Self::get_property_identifiers()
.iter()
.map(|id| {
if id.0 == "transform" {
(
id.0.to_string(),
"Transform2D::default_wrapped()".to_string(),
)
} else {
(id.0.to_string(), "Default::default()".to_string())
}
})
.collect()
}
pub fn get_property_identifiers() -> Vec<(String, String)> {
COMMON_PROPERTIES_TYPE
.iter()
.map(|(c, t)| (c.to_string(), t.to_string()))
.collect()
}
pub fn get_as_common_property() -> Vec<CommonProperty> {
Self::get_property_identifiers()
.iter()
.map(|id| CommonProperty {
name: id.0.to_string(),
property_type: id.1.to_string(),
optional: (id.0 == "transform" || id.0 == "width" || id.0 == "height"),
})
.collect()
}
pub fn retrieve_property_scope(&self) -> HashMap<String, Variable> {
let CommonProperties {
id,
x,
y,
padding_x,
padding_y,
width,
height,
anchor_x,
anchor_y,
scale_x,
scale_y,
skew_x,
skew_y,
rotate,
transform,
opacity,
layout_role,
unclippable,
_raycastable,
_suspended,
} = self;
HashMap::from([
(
"id".to_string(),
Variable::new_from_typed_property(id.clone()),
),
(
"x".to_string(),
Variable::new_from_typed_property(x.clone()),
),
(
"y".to_string(),
Variable::new_from_typed_property(y.clone()),
),
(
"padding_x".to_string(),
Variable::new_from_typed_property(padding_x.clone()),
),
(
"padding_y".to_string(),
Variable::new_from_typed_property(padding_y.clone()),
),
(
"scale_x".to_string(),
Variable::new_from_typed_property(scale_x.clone()),
),
(
"scale_y".to_string(),
Variable::new_from_typed_property(scale_y.clone()),
),
(
"skew_x".to_string(),
Variable::new_from_typed_property(skew_x.clone()),
),
(
"skew_y".to_string(),
Variable::new_from_typed_property(skew_y.clone()),
),
(
"rotate".to_string(),
Variable::new_from_typed_property(rotate.clone()),
),
(
"anchor_x".to_string(),
Variable::new_from_typed_property(anchor_x.clone()),
),
(
"anchor_y".to_string(),
Variable::new_from_typed_property(anchor_y.clone()),
),
(
"transform".to_string(),
Variable::new_from_typed_property(transform.clone()),
),
(
"opacity".to_string(),
Variable::new_from_typed_property(opacity.clone()),
),
(
"layout_role".to_string(),
Variable::new_from_typed_property(layout_role.clone()),
),
(
"width".to_string(),
Variable::new_from_typed_property(width.clone()),
),
(
"height".to_string(),
Variable::new_from_typed_property(height.clone()),
),
(
"unclippable".to_string(),
Variable::new_from_typed_property(unclippable.clone()),
),
(
"_raycastable".to_string(),
Variable::new_from_typed_property(_raycastable.clone()),
),
(
"_suspended".to_string(),
Variable::new_from_typed_property(_suspended.clone()),
),
])
}
}
impl Into<Rotation> for Size {
fn into(self) -> Rotation {
if let Size::Percent(pix) = self {
Rotation::Percent(pix)
} else {
panic!("Tried to coerce a pixel value into a rotation value; try `%` or `rad` instead of `px`.")
}
}
}
impl Size {
pub fn get_pixels(&self, parent: f64) -> f64 {
match &self {
Self::Pixels(p) => p.to_float(),
Self::Percent(p) => parent * (p.to_float() / 100.0),
Self::Combined(pix, per) => (parent * (per.to_float() / 100.0)) + pix.to_float(),
}
}
}
impl Interpolatable for Size {
fn interpolate(&self, other: &Self, t: f64) -> Self {
fn components(size: &Size) -> (Numeric, Numeric) {
match size {
Size::Pixels(pixels) => (*pixels, Numeric::F64(0.0)),
Size::Percent(percent) => (Numeric::F64(0.0), *percent),
Size::Combined(pixels, percent) => (*pixels, *percent),
}
}
fn from_components(pixels: Numeric, percent: Numeric) -> Size {
let has_pixels = pixels.to_float().abs() > f64::EPSILON;
let has_percent = percent.to_float().abs() > f64::EPSILON;
match (has_pixels, has_percent) {
(true, true) => Size::Combined(pixels, percent),
(true, false) => Size::Pixels(pixels),
(false, true) => Size::Percent(percent),
(false, false) => Size::Pixels(Numeric::F64(0.0)),
}
}
let (self_pixels, self_percent) = components(self);
let (other_pixels, other_percent) = components(other);
let t = Numeric::F64(t);
from_components(
self_pixels + ((other_pixels - self_pixels) * t),
self_percent + ((other_percent - self_percent) * t),
)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn size_interpolation_blends_pixel_and_percent_components() {
let start = Size::Pixels(0.into());
let end = Size::Percent(100.into());
let midpoint = start.interpolate(&end, 0.5);
assert!((midpoint.get_pixels(200.0) - 100.0).abs() < 0.0001);
}
#[test]
fn size_interpolation_can_settle_from_percent_offset_to_pixel_base() {
let start = Size::Combined(0.into(), 100.into());
let end = Size::Pixels(0.into());
assert!((start.interpolate(&end, 0.5).get_pixels(200.0) - 100.0).abs() < 0.0001);
assert!(start.interpolate(&end, 1.0).get_pixels(200.0).abs() < 0.0001);
}
}
impl Default for Size {
fn default() -> Self {
Self::Percent(Numeric::F64(100.0))
}
}
impl Mul for Size {
type Output = Size;
fn mul(self, rhs: Self) -> Self::Output {
match self {
Size::Pixels(pix0) => {
match rhs {
Size::Pixels(pix1) => Size::Pixels(pix0 + pix1),
Size::Percent(per1) => Size::Pixels(pix0 * per1),
Size::Combined(pix1, per1) => Size::Pixels((pix0 * per1) + pix0 + pix1),
}
}
Size::Percent(per0) => match rhs {
Size::Pixels(pix1) => Size::Pixels(per0 * pix1),
Size::Percent(per1) => Size::Percent(per0 * per1),
Size::Combined(pix1, per1) => Size::Pixels((per0 * pix1) + (per0 * per1)),
},
Size::Combined(pix0, per0) => match rhs {
Size::Pixels(pix1) => Size::Pixels((pix0 * per0) + pix1),
Size::Percent(per1) => Size::Percent(pix0 * per0 * per1),
Size::Combined(pix1, per1) => Size::Pixels((pix0 * per0) + (pix1 * per1)),
},
}
}
}