Store layout attributes separately

This commit is contained in:
Gabriel Majeri 2020-07-15 08:41:49 +03:00 committed by Keavon Chambers
parent fe272a6d7c
commit ed4931f638
3 changed files with 176 additions and 13 deletions

View file

@ -20,6 +20,7 @@ impl LayoutAbstractNode {
pub struct LayoutAbstractTag {
pub namespace: String,
pub name: String,
pub layout_attributes: LayoutAttributes,
pub attributes: Vec<Attribute>,
}
@ -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<TypeValue> {
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<TypeValueOrArgument>),
}
/// 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,
}
}
}

View file

@ -49,20 +49,80 @@ pub enum TypeValue {
Layout(Vec<ComponentAst>),
Integer(i64),
Decimal(f64),
AbsolutePx(f32),
Percent(f32),
PercentRemainder(f32),
Inner,
Width,
Height,
Dimension(Dimension),
TemplateString(Vec<TemplateStringSegment>),
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;
}
}

View file

@ -130,28 +130,31 @@ impl AttributeParser {
let pixels = value
.parse::<f32>()
.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::<f32>()
.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::<f32>()
.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::<TemplateStringSegment>::new();