Files
Graphite/src/layout_abstract_syntax.rs
2020-11-01 01:41:17 -07:00

280 lines
10 KiB
Rust

use crate::layout_abstract_types::*;
// AST for a component with info on its definition (from the root element of the XML layout) and a vector of direct child component tags
#[derive(Debug, Clone, PartialEq)]
pub struct FlatComponent {
// The abstract definition of the root node of the component with prop definitions
pub own_info: LayoutComponentDefinition,
// Only stores tags, text elements are disposed of (they'd be meaningless in a tag list)
pub child_components: Vec<LayoutComponentTag>,
}
/// A component in its final processed form (after parsing its XML file), with information on its definition with a list of child components with their own children in their `content` attributes
impl FlatComponent {
// Construct a layout component which stores its own root-level component definition (with prop definitions, etc.) and a flat list of its direct child tags, each with an AST in their `content` attribute
pub fn new(own_info: LayoutComponentDefinition, child_components: Vec<LayoutComponentTag>) -> FlatComponent {
Self { own_info, child_components }
}
/// Print the component (for debugging)
#[allow(dead_code)]
pub fn debug_print(&self) {
println!("Flat Component: {:#?}", self.own_info);
for tag in &self.child_components {
tag.debug_print();
}
}
}
// ====================================================================================================
/// Wrapper for either a `LayoutComponentNode` enum or `LayoutComponentDefinition` struct
#[derive(Debug, Clone, PartialEq)]
pub enum LayoutComponentNodeOrDefinition {
LayoutComponentNode(LayoutComponentNode),
LayoutComponentDefinition(LayoutComponentDefinition),
}
// ====================================================================================================
/// AST of `LayoutComponentNode`s which hold either a tag or text node
pub type NodeTree = rctree::Node<LayoutComponentNode>;
/// AST similar to `NodeTree` (a tree of `LayoutComponentNode`s) but this holds the wrapped values `LayoutComponentNodeOrDefinition` (unwrap them with `LayoutSystem::node_tree_from_node_or_def_tree()`)
pub type NodeOrDefTree = rctree::Node<LayoutComponentNodeOrDefinition>;
// ====================================================================================================
/// Representation of an XML node with either another XML tag (`LayoutComponentTag`) or a text node (a vector of alternating `TemplateStringSegment::String`s and `TemplateStringSegment::Argument`s)
#[derive(Debug, Clone, PartialEq)]
pub enum LayoutComponentNode {
Tag(LayoutComponentTag),
Text(Vec<TemplateStringSegment>),
}
impl LayoutComponentNode {
/// Given a tag name in namespace:name format, construct a `LayoutComponentNode` that wraps a newly constructed `LayoutComponentTag` struct based on the provided name
pub fn new_tag(name: (String, String)) -> Self {
Self::Tag(LayoutComponentTag::new(name))
}
/// Given some text hanging out in the XML between tags, construct a `LayoutComponentNode` with that text which simply stores the provided `String`
pub fn new_text(text: Vec<TemplateStringSegment>) -> Self {
Self::Text(text)
}
/// Print the component node (for debugging)
#[allow(dead_code)]
pub fn debug_print(&self) {
match self {
LayoutComponentNode::Tag(tag) => tag.debug_print(),
LayoutComponentNode::Text(text) => println!("================> Text Node: {:#?}", text),
}
}
}
// ====================================================================================================
/// Abstract representation of a component based on the definitions of its props in the root tag of a component XML layout
#[derive(Debug, Clone, PartialEq)]
pub struct LayoutComponentDefinition {
/// Name of the component in namespace:name format
pub name: (String, String),
/// Accepted prop definitions, which are prefixed with ':'
pub prop_definitions: Vec<PropDefinition>,
}
impl LayoutComponentDefinition {
/// Construct a definition for a layout component given its name in namespace:name format with an (initially) empty set of prop definitions
pub fn new(name: (String, String)) -> Self {
let prop_definitions = vec![];
Self { name, prop_definitions }
}
/// Add a prop definition (with its name, valid types, and default value) to this component definition
pub fn add_prop_definition(&mut self, prop_definition: PropDefinition) {
self.prop_definitions.push(prop_definition);
}
}
// ====================================================================================================
/// Abstract representation of a tag inside an abstract component with attributes and descendant content
#[derive(Debug, Clone, PartialEq)]
pub struct LayoutComponentTag {
/// Namespace and name of the tag's referenced component
pub name: (String, String),
/// Layout attributes, which are used by the layout engine
pub layout: LayoutAttributes,
/// Props on this tag, which are prefixed with ':'
pub props: Vec<Prop>,
/// The special content attribute, containing the inner elements of this tag
pub content: Option<Vec<NodeTree>>,
}
impl LayoutComponentTag {
/// Construct a tag in an XML layout component based on its referenced component name (in namespace:name format) and empty defaults
pub fn new(name: (String, String)) -> Self {
Self {
name,
layout: Default::default(),
content: None,
props: Vec::new(),
}
}
/// Provide a sequence of ASTs for this component's content attribute
pub fn set_content(&mut self, content: Vec<NodeTree>) {
self.content = Some(content);
}
/// Add an XML tag attribute to this component (either a layout engine setting, a prop, or an event handler binding)
pub fn add_attribute(&mut self, attribute: Prop) {
// Prop argument (for reactive data system)
if attribute.name.len() > 1 && &attribute.name[..1] == ":" {
self.add_prop(attribute);
}
// Event handler attribute (for event system)
else if attribute.name.len() > 3 && &attribute.name[..3] == "on:" {
todo!("Event attributes not implemented yet");
}
// Layout attribute (for layout engine)
else {
self.add_layout_attribute(attribute);
}
}
/// Add an XML tag attribute to this component for a colon-prefixed prop
fn add_prop(&mut self, attribute: Prop) {
self.props.push(attribute);
}
/// Add an XML tag attribute to this component for a non-prefixed layout engine value
fn add_layout_attribute(&mut self, attribute: Prop) {
match &attribute.name[..] {
// Layout attributes, stored separately
"width" => self.layout.width = attribute.dimension(),
"height" => self.layout.height = attribute.dimension(),
"x-align" => self.layout.x_align = attribute.percent(),
"y-align" => self.layout.y_align = attribute.percent(),
"x-padding" => self.layout.padding.set_horizontal(attribute.dimension()),
"y-padding" => self.layout.padding.set_vertical(attribute.dimension()),
"padding" => self.layout.padding = attribute.box_dimensions(),
"x-gap" => self.layout.gap.set_horizontal(attribute.dimension()),
"y-gap" => self.layout.gap.set_vertical(attribute.dimension()),
"gap" => self.layout.gap = attribute.box_dimensions(),
_ => panic!("Unknown builtin attribute `{}`", attribute.name),
}
}
/// Print the layout tag (for debugging)
pub fn debug_print(&self) {
println!("Tag Node: {:#?}", self);
if let Some(ref content) = self.content {
for child in content {
for node in child.descendants() {
println!("> Descendant Node: {:#?}", node);
}
}
}
}
}
// ====================================================================================================
/// Name-value pair for a prop used in the prop-passing system, where the name is a `String` and the value sequence is a vector of `TypedValueOrVariableName`s
#[derive(Debug, Clone, PartialEq)]
pub struct Prop {
pub name: String,
pub value_sequence: Vec<TypedValueOrVariableName>,
}
impl Prop {
/// Construct a name-value pair representing an argument on a layout tag given its name and sequence of values
pub fn new(name: String, value_sequence: Vec<TypedValueOrVariableName>) -> Self {
Self { name, value_sequence }
}
/// Extract this attribute's values as typed values
fn values(self) -> Vec<TypedValue> {
self.value_sequence
.into_iter()
.map(|value| {
if let TypedValueOrVariableName::TypedValue(typed_value) = value {
typed_value
}
else {
todo!("Variable arguments are not yet supported")
}
})
.collect()
}
/// Convert 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()
}
/// Extract a percentage from this attribute's value
fn percent(self) -> f64 {
match self.dimension() {
Dimension::Percent(value) => value,
_ => panic!("Expected a percentage"),
}
}
/// Convert 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"),
}
}
}
// ====================================================================================================
/// Attributes used by the layout engine to calculate sizing and placement
#[derive(Clone, Debug, PartialEq)]
pub struct LayoutAttributes {
pub width: Dimension,
pub height: Dimension,
pub x_align: f64,
pub y_align: f64,
pub gap: BoxDimensions,
pub padding: BoxDimensions,
}
impl Default for LayoutAttributes {
/// Provide default values for dimensions, alignment, and outside spacing
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,
gap: zero_box,
padding: zero_box,
}
}
}