diff --git a/src/layout_abstract_syntax.rs b/src/layout_abstract_syntax.rs index 91e4073c..60903e03 100644 --- a/src/layout_abstract_syntax.rs +++ b/src/layout_abstract_syntax.rs @@ -20,6 +20,7 @@ impl LayoutAbstractNode { pub struct LayoutAbstractTag { pub namespace: String, pub name: String, + pub layout_attributes: LayoutAttributes, pub attributes: Vec, } @@ -28,12 +29,27 @@ impl LayoutAbstractTag { Self { namespace, name, + layout_attributes: Default::default(), attributes: Vec::new(), } } pub fn add_attribute(&mut self, attribute: Attribute) { - self.attributes.push(attribute); + match &attribute.name[..] { + // Layout attributes, stored separately + "width" => self.layout_attributes.width = attribute.dimension(), + "height" => self.layout_attributes.height = attribute.dimension(), + "x-align" => self.layout_attributes.x_align = attribute.percent(), + "y-align" => self.layout_attributes.y_align = attribute.percent(), + "x-padding" => self.layout_attributes.padding.set_horizontal(attribute.dimension()), + "y-padding" => self.layout_attributes.padding.set_vertical(attribute.dimension()), + "padding" => self.layout_attributes.padding = attribute.box_dimensions(), + "x-spacing" => self.layout_attributes.spacing.set_horizontal(attribute.dimension()), + "y-spacing" => self.layout_attributes.spacing.set_vertical(attribute.dimension()), + "spacing" => self.layout_attributes.spacing = attribute.box_dimensions(), + // Non-layout attribute + _ => self.attributes.push(attribute), + } } } @@ -47,6 +63,65 @@ impl Attribute { pub fn new(name: String, value: AttributeValue) -> Self { Self { name, value } } + + /// Extracts this attribute's values as typed values. + fn values(self) -> Vec { + if let AttributeValue::TypeValue(values) = self.value { + values + .into_iter() + .map(|value| { + if let TypeValueOrArgument::TypeValue(value) = value { + value + } + else { + todo!("variable arguments are note yet supported") + } + }) + .collect() + } + else { + todo!("variable arguments are not yet supported") + } + } + + /// Converts this attribute's value into a single dimension. + fn dimension(self) -> Dimension { + let values = self.values(); + assert_eq!(values.len(), 1, "expected a single value"); + values[0].expect_dimension() + } + + /// Extracts a percentage from this attribute's value. + fn percent(self) -> f32 { + match self.dimension() { + Dimension::Percent(value) => value, + _ => panic!("expected a percentage"), + } + } + + /// Converts this attribute's values into box dimensions. + fn box_dimensions(self) -> BoxDimensions { + let values = self.values(); + match values.len() { + 1 => { + let value = values[0].expect_dimension(); + BoxDimensions::all(value) + }, + 2 => { + let vertical = values[0].expect_dimension(); + let horizontal = values[1].expect_dimension(); + BoxDimensions::symmetric(vertical, horizontal) + }, + 4 => { + let top = values[0].expect_dimension(); + let right = values[1].expect_dimension(); + let bottom = values[2].expect_dimension(); + let left = values[3].expect_dimension(); + BoxDimensions::new(top, right, bottom, left) + }, + _ => panic!("expected 1, 2 or 4 values"), + } + } } #[derive(Debug, Clone, PartialEq)] @@ -54,3 +129,28 @@ pub enum AttributeValue { VariableParameter(VariableParameter), TypeValue(Vec), } + +/// Layout-specific attributes. +#[derive(Clone, Debug, PartialEq)] +pub struct LayoutAttributes { + pub width: Dimension, + pub height: Dimension, + pub x_align: f32, + pub y_align: f32, + pub spacing: BoxDimensions, + pub padding: BoxDimensions, +} + +impl Default for LayoutAttributes { + fn default() -> Self { + let zero_box = BoxDimensions::all(Dimension::AbsolutePx(0.0)); + Self { + width: Dimension::Inner, + height: Dimension::Inner, + x_align: 0.0, + y_align: 0.0, + spacing: zero_box, + padding: zero_box, + } + } +} diff --git a/src/layout_abstract_types.rs b/src/layout_abstract_types.rs index 4ef00cdc..6ad529ad 100644 --- a/src/layout_abstract_types.rs +++ b/src/layout_abstract_types.rs @@ -49,20 +49,80 @@ pub enum TypeValue { Layout(Vec), Integer(i64), Decimal(f64), - AbsolutePx(f32), - Percent(f32), - PercentRemainder(f32), - Inner, - Width, - Height, + Dimension(Dimension), TemplateString(Vec), Color(Color), Bool(bool), None, } +impl TypeValue { + /// Converts this to a dimension, panics if not possible. + pub fn expect_dimension(&self) -> Dimension { + match self { + Self::Dimension(dimension) => *dimension, + _ => panic!("expected a dimension"), + } + } +} + #[derive(Debug, Clone, PartialEq)] pub enum TemplateStringSegment { String(String), Argument(TypeValueOrArgument), } + +/// A dimension is a measure along an axis. +#[derive(Copy, Clone, Debug, PartialEq)] +pub enum Dimension { + /// Absolute value in pixels. + AbsolutePx(f32), + /// Percent of parent container size along the same axis. + Percent(f32), + /// Percent of free space remaining in parent container. + PercentRemainder(f32), + /// Minimum size required to fit the children. + Inner, + /// Size relative to the width of this component. + Width, + /// Size relative to the height of this component. + Height, +} + +/// Dimensions along a box's four sides. +#[derive(Copy, Clone, Debug, PartialEq)] +pub struct BoxDimensions { + pub top: Dimension, + pub right: Dimension, + pub bottom: Dimension, + pub left: Dimension, +} + +impl BoxDimensions { + /// Construct new box dimensions, with values given for each side. + pub fn new(top: Dimension, right: Dimension, bottom: Dimension, left: Dimension) -> Self { + Self { top, right, bottom, left } + } + + /// Construct new box dimensions, with same values used for top-bottom and left-right. + pub fn symmetric(vertical: Dimension, horizontal: Dimension) -> Self { + Self::new(vertical, horizontal, vertical, horizontal) + } + + /// Construct new box dimensions with the same value for all sides. + pub fn all(value: Dimension) -> Self { + Self::new(value, value, value, value) + } + + /// Sets the padding on the top and bottom sides. + pub fn set_vertical(&mut self, value: Dimension) { + self.top = value; + self.bottom = value; + } + + /// Sets the padding on the left and right sides. + pub fn set_horizontal(&mut self, value: Dimension) { + self.left = value; + self.right = value; + } +} diff --git a/src/layout_attribute_parser.rs b/src/layout_attribute_parser.rs index 45b545c6..9a4f0012 100644 --- a/src/layout_attribute_parser.rs +++ b/src/layout_attribute_parser.rs @@ -130,28 +130,31 @@ impl AttributeParser { let pixels = value .parse::() .expect(&format!("Invalid value `{}` specified in the attribute type`{}` when parsing XML layout", value, attribute_type)[..]); - TypeValueOrArgument::TypeValue(TypeValue::AbsolutePx(pixels)) + let dimension = Dimension::AbsolutePx(pixels); + TypeValueOrArgument::TypeValue(TypeValue::Dimension(dimension)) }, // Percent: ?% Some([value, "%"]) => { let percent = value .parse::() .expect(&format!("Invalid value `{}` specified in the attribute type `{}` when parsing XML layout", value, attribute_type)[..]); - TypeValueOrArgument::TypeValue(TypeValue::Percent(percent)) + let dimension = Dimension::Percent(percent); + TypeValueOrArgument::TypeValue(TypeValue::Dimension(dimension)) }, // PercentRemainder: ?@ Some([value, "@"]) => { let percent_remainder = value .parse::() .expect(&format!("Invalid value `{}` specified in the attribute type `{}` when parsing XML layout", value, attribute_type)[..]); - TypeValueOrArgument::TypeValue(TypeValue::PercentRemainder(percent_remainder)) + let dimension = Dimension::PercentRemainder(percent_remainder); + TypeValueOrArgument::TypeValue(TypeValue::Dimension(dimension)) }, // Inner: inner - Some([inner]) if inner.eq_ignore_ascii_case("inner") => TypeValueOrArgument::TypeValue(TypeValue::Inner), + Some([inner]) if inner.eq_ignore_ascii_case("inner") => TypeValueOrArgument::TypeValue(TypeValue::Dimension(Dimension::Inner)), // Width: width - Some([width]) if width.eq_ignore_ascii_case("width") => TypeValueOrArgument::TypeValue(TypeValue::Width), + Some([width]) if width.eq_ignore_ascii_case("width") => TypeValueOrArgument::TypeValue(TypeValue::Dimension(Dimension::Width)), // Height: height - Some([height]) if height.eq_ignore_ascii_case("height") => TypeValueOrArgument::TypeValue(TypeValue::Height), + Some([height]) if height.eq_ignore_ascii_case("height") => TypeValueOrArgument::TypeValue(TypeValue::Dimension(Dimension::Height)), // TemplateString: `? ... {{?}} ...` Some(["`", string, "`"]) => { let mut segments = Vec::::new();