diff --git a/gui/README.md b/gui/README.md index 6969428a..d59fb9dc 100644 --- a/gui/README.md +++ b/gui/README.md @@ -13,7 +13,7 @@ Interactivity is provided by script files which expose reactive variables. As th The layout engine does a top-down pass through the component tree in order to determine what to render. -Layout is controlled using predefined attributes, such as `width`, `height`, `x-align`, `y-align`, `spacing` or `padding`. +Layout is controlled using predefined attributes, such as `width`, `height`, `x-align`, `y-align`, `gap` or `padding`. ## Component lifetime diff --git a/gui/window/main.xml b/gui/window/main.xml index 72acc69f..65312c4f 100644 --- a/gui/window/main.xml +++ b/gui/window/main.xml @@ -14,7 +14,7 @@ File: 1.8 MB | Memory: 137 MB | Scratch: 0.7/12.3 GB - 🖰 Box Select Objects | [⇧G] Move Selection | [⇧R] Rotate Selection | [⇧S] Scale Selection + Box Select Objects | [⇧G] Move Selection | [⇧R] Rotate Selection | [⇧S] Scale Selection diff --git a/src/application.rs b/src/application.rs index aa5d0c13..fad4a597 100644 --- a/src/application.rs +++ b/src/application.rs @@ -85,7 +85,7 @@ impl Application { // Main window in the XML layout language let mut main_window_layout = LayoutSystem::new(); - main_window_layout.load_layout_component("window", "main"); + main_window_layout.add_window(("window", "main")); Self { surface, diff --git a/src/layout_abstract_syntax.rs b/src/layout_abstract_syntax.rs index a8bc16ab..87be6980 100644 --- a/src/layout_abstract_syntax.rs +++ b/src/layout_abstract_syntax.rs @@ -1,121 +1,230 @@ 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 +pub struct FlatComponent { + // The abstract definition of the root node of the component with attribute parameters + pub own_info: LayoutComponentDefinition, + // Only stores tags, text elements are disposed of (they'd be meaningless in a tag list) + pub child_components: Vec, +} + +/// 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 attribute parameters, 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) -> 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 LayoutAbstractNode { - Tag(LayoutAbstractTag), +pub enum LayoutComponentNodeOrDefinition { + LayoutComponentNode(LayoutComponentNode), + LayoutComponentDefinition(LayoutComponentDefinition), +} + +// ==================================================================================================== + +/// AST of `LayoutComponentNode`s which hold either a tag or text node +pub type NodeTree = rctree::Node; + +/// 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; + +// ==================================================================================================== + +/// Representation of an XML node with either another XML tag (`LayoutComponentTag`) or a text node (just a `String`) +#[derive(Debug, Clone, PartialEq)] +pub enum LayoutComponentNode { + Tag(LayoutComponentTag), Text(String), } -impl LayoutAbstractNode { - pub fn new_tag(namespace: String, name: String) -> Self { - Self::Tag(LayoutAbstractTag::new(namespace, name)) +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: String) -> 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 parameters defined by the root tag of a component XML layout #[derive(Debug, Clone, PartialEq)] -pub struct LayoutAbstractTag { - pub namespace: String, - pub name: String, - /// Layout attributes, which are used by the layout engine. - pub layout_attributes: LayoutAttributes, - /// The special content attribute, representing the inner elements of this tag. - pub content: Option, - /// User-defined attributes, which are prefixed with ':' - pub user_attributes: Vec, +pub struct LayoutComponentDefinition { + /// Name of the component in "namespace:name" format + pub name: (String, String), + /// User-defined attribute parameters, which are prefixed with ':' + pub user_attributes: Vec, } -impl LayoutAbstractTag { - pub fn new(namespace: String, name: String) -> Self { +impl LayoutComponentDefinition { + /// Construct a definition for a layout component given its name in "namespace:name" format with an empty set of parameters + pub fn new(name: (String, String)) -> Self { + let user_attributes = vec![]; + Self { name, user_attributes } + } + + /// Add a parameter definition (with its name, types, and default value) to this component definition + pub fn add_parameter(&mut self, parameter: VariableParameter) { + self.user_attributes.push(parameter); + } +} + +// ==================================================================================================== + +/// Abstract representation of a tag inside an abstract component with attributes and descendant content +#[derive(Debug, Clone, PartialEq)] +pub struct LayoutComponentTag { + /// Name of the tag's referenced component in "namespace:name" format + pub name: (String, String), + /// Layout engine attribute arguments, which are used by the layout engine + pub layout_arguments: LayoutAttributes, + /// User-defined attribute arguments, which are prefixed with ':' + pub user_arguments: Vec, + /// The special content attribute, representing the inner elements of this tag + pub content: Option>, +} + +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 { - namespace, name, - layout_attributes: Default::default(), + layout_arguments: Default::default(), content: None, - user_attributes: Vec::new(), + user_arguments: Vec::new(), } } - pub fn add_attribute(&mut self, attribute: Attribute) { - // User-defined attribute - if attribute.name.chars().next().unwrap() == ':' { - self.user_attributes.push(attribute); + /// Provide a sequence of ASTs for this component's content attribute + pub fn set_content(&mut self, content: Vec) { + self.content = Some(content); + } + + /// Add an XML tag attribute to this component (either a layout engine attribute, user-defined custom attribute, or event handler attribute) + pub fn add_attribute(&mut self, attribute: AttributeArg) { + // User-defined attribute (for reactive data system) + if attribute.name.len() > 1 && &attribute.name[..1] == ":" { + self.add_user_attribute(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_builtin_attribute(attribute); + self.add_layout_attribute(attribute); } } - fn add_builtin_attribute(&mut self, attribute: Attribute) { + /// Add an XML tag attribute to this component for a colon-prefixed custom user-defined variable value + fn add_user_attribute(&mut self, attribute: AttributeArg) { + self.user_arguments.push(attribute); + } + + /// Add an XML tag attribute to this component for a non-prefixed layout engine value + fn add_layout_attribute(&mut self, attribute: AttributeArg) { match &attribute.name[..] { - // The special `content` attribute - "content" => self.content = Some(attribute.value), // 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(), - _ => panic!("unknown builtin attribute {}", attribute.name), + "width" => self.layout_arguments.width = attribute.dimension(), + "height" => self.layout_arguments.height = attribute.dimension(), + "x-align" => self.layout_arguments.x_align = attribute.percent(), + "y-align" => self.layout_arguments.y_align = attribute.percent(), + "x-padding" => self.layout_arguments.padding.set_horizontal(attribute.dimension()), + "y-padding" => self.layout_arguments.padding.set_vertical(attribute.dimension()), + "padding" => self.layout_arguments.padding = attribute.box_dimensions(), + "x-gap" => self.layout_arguments.gap.set_horizontal(attribute.dimension()), + "y-gap" => self.layout_arguments.gap.set_vertical(attribute.dimension()), + "gap" => self.layout_arguments.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 an argument used in the attribute variable system, where the name is a `String` and the value sequence is a vector of `TypeValueOrArgument`s #[derive(Debug, Clone, PartialEq)] -pub struct Attribute { +pub struct AttributeArg { pub name: String, - pub value: AttributeValue, + pub value: Vec, } -impl Attribute { - pub fn new(name: String, value: AttributeValue) -> Self { +impl AttributeArg { + /// 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: Vec) -> Self { Self { name, value } } - /// Extracts this attribute's values as typed values. + /// Extract 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") - } + self.value + .into_iter() + .map(|value| { + if let TypeValueOrArgument::TypeValue(value) = value { + value + } + else { + todo!("Variable arguments are note yet supported") + } + }) + .collect() } - /// Converts this attribute's value into a single dimension. + /// 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"); + assert_eq!(values.len(), 1, "Expected a single value"); values[0].expect_dimension() } - /// Extracts a percentage from this attribute's value. + /// Extract a percentage from this attribute's value fn percent(self) -> f64 { match self.dimension() { Dimension::Percent(value) => value, - _ => panic!("expected a percentage"), + _ => panic!("Expected a percentage"), } } - /// Converts this attribute's values into box dimensions. + /// Convert this attribute's values into box dimensions fn box_dimensions(self) -> BoxDimensions { let values = self.values(); match values.len() { @@ -135,29 +244,26 @@ impl Attribute { let left = values[3].expect_dimension(); BoxDimensions::new(top, right, bottom, left) }, - _ => panic!("expected 1, 2 or 4 values"), + _ => panic!("Expected 1, 2 or 4 values"), } } } -#[derive(Debug, Clone, PartialEq)] -pub enum AttributeValue { - VariableParameter(VariableParameter), - TypeValue(Vec), -} +// ==================================================================================================== -/// Layout-specific attributes. +/// 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 spacing: BoxDimensions, + 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 { @@ -165,7 +271,7 @@ impl Default for LayoutAttributes { height: Dimension::Inner, x_align: 0.0, y_align: 0.0, - spacing: zero_box, + gap: zero_box, padding: zero_box, } } diff --git a/src/layout_abstract_types.rs b/src/layout_abstract_types.rs index cc76d17a..67a9f466 100644 --- a/src/layout_abstract_types.rs +++ b/src/layout_abstract_types.rs @@ -1,14 +1,19 @@ use crate::color::Color; use crate::layout_abstract_syntax::*; +/// Parameter definition for an attribute in the root tag of a component XML layout #[derive(Debug, Clone, PartialEq)] pub struct VariableParameter { + // Name of the variable binding that can be used within the component in {{template tags}} pub name: String, + // Combinations of allowed sequences of types that can be passed to instances of this component pub type_sequence_options: Vec>, + // A single sequence of default values that get used if an instance of this component never has the corresponding argument passed to it pub type_sequence_default: Vec, } impl VariableParameter { + /// Construct a parameter definition for a variable accepted by a component definition, with the variable name, allowed combinations of types, and the default value sequence pub fn new(name: String, valid_types: Vec>, default: Vec) -> Self { Self { name, @@ -18,12 +23,18 @@ impl VariableParameter { } } +// ==================================================================================================== + +/// Wrapper for either a `TypeValue` struct or the name of a variable argument (just a `String`) #[derive(Debug, Clone, PartialEq)] pub enum TypeValueOrArgument { TypeValue(TypeValue), VariableArgument(String), } +// ==================================================================================================== + +/// All possible names for types of values in the reactive data and layout system #[derive(Debug, Clone, PartialEq)] pub enum TypeName { Layout, @@ -41,12 +52,12 @@ pub enum TypeName { None, } -pub type ComponentAst = rctree::Node; -pub type Component = Vec; +// ==================================================================================================== +/// Concrete values for data in the various types allowed by the reactive data and layout system #[derive(Debug, Clone, PartialEq)] pub enum TypeValue { - Layout(Vec), + Layout(Vec), Integer(i64), Decimal(f64), Dimension(Dimension), @@ -61,17 +72,22 @@ impl TypeValue { pub fn expect_dimension(&self) -> Dimension { match self { Self::Dimension(dimension) => *dimension, - _ => panic!("expected a dimension"), + _ => panic!("Expected a dimension"), } } } +// ==================================================================================================== + +/// A piece of a template string, made up of many of these enums concatenated together in alternating order between `String` and `Argument`, where the latter is a value or argument variable #[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 { @@ -89,7 +105,9 @@ pub enum Dimension { Height, } -/// Dimensions along a box's four sides. +// ==================================================================================================== + +/// Dimensions along the four sides of a box layout #[derive(Copy, Clone, Debug, PartialEq)] pub struct BoxDimensions { pub top: Dimension, diff --git a/src/layout_attribute_parser.rs b/src/layout_attribute_parser.rs index 9670d42d..1ff915e7 100644 --- a/src/layout_attribute_parser.rs +++ b/src/layout_attribute_parser.rs @@ -1,6 +1,5 @@ use crate::color::Color; use crate::color_palette::ColorPalette; -use crate::layout_abstract_syntax::*; use crate::layout_abstract_types::*; use crate::layout_system::*; @@ -18,7 +17,7 @@ impl AttributeParser { pub fn new() -> Self { let capture_attribute_declaration_parameter_regex: regex::Regex = regex::Regex::new( // Parameter: ?: (?, ... | ...) = ? - r"^\s*(\w*)\s*(:)\s*(\()\s*((?:(?:\w+)(?:\s*,\s*\w+)*)(?:\s*\|\s*(?:(?:\w+)(?:\s*,\s*\w+)*))*)\s*(\))\s*(=)\s*([\s\w'\[\]@%\-.,]*?|\s*`[^`]*?`)\s*$", + r"^\s*(\w*)\s*(:)\s*(\()\s*((?:(?:\w+)(?:\s*,\s*\w+)*)(?:\s*\|\s*(?:(?:\w+)(?:\s*,\s*\w+)*))*)\s*(\))\s*(=)\s*([\s\w'\[\]@%\-.,]+|`[^`]*`|\[\[.*\]\])\s*$", ) .unwrap(); @@ -31,7 +30,7 @@ impl AttributeParser { r#"^\s*(-?\d+)\s*$|"#, // Decimal: ? r#"^\s*(-?(?:(?:\d+\.\d*)|(?:\d*\.\d+)))\s*$|"#, - // AbsolutePx: px + // AbsolutePx: ?px r#"^\s*(-?(?:(?:\d+(?:\.\d*)?)|(?:\d*(?:\.\d+))))([Pp][Xx])\s*$|"#, // Percent: ?% r#"^\s*(-?(?:(?:\d+(?:\.\d*)?)|(?:\d*(?:\.\d+))))(%)\s*$|"#, @@ -72,16 +71,16 @@ impl AttributeParser { } } - pub fn parse_attribute_types(&self, input: &str) -> AttributeValue { + pub fn parse_attribute_argument_types(&self, input: &str) -> Vec { let attribute_types = input.split(",").map(|piece| piece.trim()).collect::>(); let list = attribute_types .iter() - .map(|attribute_type| self.parse_attribute_type(attribute_type)) + .map(|attribute_type| self.parse_attribute_argument_type(attribute_type)) .collect::>(); - AttributeValue::TypeValue(list) + list } - pub fn parse_attribute_type(&self, attribute_type: &str) -> TypeValueOrArgument { + pub fn parse_attribute_argument_type(&self, attribute_type: &str) -> TypeValueOrArgument { // Match with the regular expression let captures = self .capture_attribute_type_sequences_regex @@ -98,17 +97,18 @@ impl AttributeParser { // Remove any whitespace in order to test if any XML syntax is present let trimmed = xml_syntax.trim(); - // Build either an empty vector (for empty XML input) or a vector with the one parsed AST + // Build either an empty vector (for empty XML input) or a vector with the one parsed XML fragment let layout_entries = if trimmed.len() == 0 { vec![] } else { let unescaped = Self::unescape_xml(trimmed); - let component_ast = LayoutSystem::parse_xml_tree(&self, &unescaped[..], false, false).unwrap(); - vec![component_ast] + let parsed = LayoutSystem::parse_xml_node(&self, &unescaped[..], false).unwrap(); + // Put the single parsed node in a vector (TODO: this should set any number of parsed nodes once `parse_xml_node` becomes `parse_xml_nodes`) + vec![parsed] }; - // Return the `Layout` typed value with the empty vector or vector with the parsed AST + // Return the `Layout` typed value with the empty vector or vector with the parsed XML fragment TypeValueOrArgument::TypeValue(TypeValue::Layout(layout_entries)) }, // Integer: ? @@ -224,7 +224,7 @@ impl AttributeParser { } } - pub fn parse_attribute_declaration(&self, attribute_declaration: &str) -> AttributeValue { + pub fn parse_attribute_parameter_declaration(&self, attribute_declaration: &str) -> VariableParameter { // Match with the regular expression let captures = self .capture_attribute_declaration_parameter_regex @@ -278,19 +278,21 @@ impl AttributeParser { // Required default value for the variable parameter if not provided let default_type_sequence = default_value .split(",") - .map(|individual_type| match self.parse_attribute_type(individual_type) { + .map(|individual_type| match self.parse_attribute_argument_type(individual_type) { TypeValueOrArgument::TypeValue(type_value) => type_value, TypeValueOrArgument::VariableArgument(variable_value) => { panic!( - "Found the default variable value `{:?}` in the attribute declaration `{}` which only allows typed values, when parsing XML layout", + "Found the default variable value `{:?}` in the attribute declaration `{}` (which only allows typed values) when parsing XML layout", variable_value, attribute_declaration ); }, }) .collect::>(); + // TODO: Verify the default types match the specified allowed types + // Return the parameter - AttributeValue::VariableParameter(VariableParameter::new(name, type_sequence_options, default_type_sequence)) + VariableParameter::new(name, type_sequence_options, default_type_sequence) }, // Unrecognized type pattern _ => panic!("Invalid attribute attribute declaration `{}` when parsing XML layout", attribute_declaration), diff --git a/src/layout_system.rs b/src/layout_system.rs index d14874e5..fa8a4efb 100644 --- a/src/layout_system.rs +++ b/src/layout_system.rs @@ -2,279 +2,330 @@ use crate::layout_abstract_syntax::*; use crate::layout_abstract_types::*; use crate::layout_attribute_parser::*; use crate::resource_cache::ResourceCache; +use crate::window_dom::*; use std::collections::HashSet; use std::fs; use std::io; pub struct LayoutSystem { - loaded_layouts: ResourceCache, + windows: Vec, + loaded_components: ResourceCache, attribute_parser: AttributeParser, } impl LayoutSystem { + /// Construct the `LayoutSystem` with zero windows, an empty cache of component XML layouts, and an `AttributeParser` with its regex parsers pub fn new() -> LayoutSystem { Self { - loaded_layouts: ResourceCache::new(), + windows: vec![], + loaded_components: ResourceCache::new(), attribute_parser: AttributeParser::new(), } } - /// Preload and cache a component by its namespace and name, then recursively explore and repeat for its descendants - pub fn load_layout_component(&mut self, namespace: &str, name: &str) { - // Load and parse the XML file's AST for the visited tag - let xml_path = Self::layout_xml_path(namespace, name); - let xml_parsed = Self::parse_xml_tree(&self.attribute_parser, &xml_path[..], true, true); - let mut xml_ast = match xml_parsed { - Ok(result) => result, - Err(error) => panic!("Error parsing XML layout syntax: {}", error), - }; + /// Load and construct a new window from a layout component + pub fn add_window(&mut self, name: (&str, &str)) { + // Preload the component and its dependencies + self.preload_component(name) + .expect(&format!("Failure loading layout component '{}'", Self::component_name(name))[..]); - // Keep track of it being loaded to prevent duplicate work - let mut already_loaded_layouts = HashSet::new(); - already_loaded_layouts.insert(Self::component_name(namespace, name)); + // Get the now-loaded component + let window_root_component_name = Self::component_name(name); + let window_root_component = self.loaded_components.get(&window_root_component_name[..]).unwrap(); - // Turn the entire XML AST into a component - let component = Self::component_ast_to_component(&mut xml_ast); - // Self::print_layout_component(&component); - - // Parse and cache components recursively for all tags referenced within this root component - self.explore_referenced_components(&xml_ast, &mut already_loaded_layouts); - - // Save the loaded component to the cache - let component_name = Self::component_name(namespace, name); - self.loaded_layouts.set(&component_name[..], component); + // Construct the window and save it + let new_window = WindowDom::new(window_root_component); + self.windows.push(new_window); } - /// Preload and cache every XML component file referenced by tags within a recursive traversal of descendants in the given component AST - fn explore_referenced_components(&mut self, layout_tree_root: &ComponentAst, already_loaded_layouts: &mut HashSet) { - for child_tag in layout_tree_root.descendants() { - match &*child_tag.borrow() { - // Tags are references to other XML layouts that should be loaded and cached - LayoutAbstractNode::Tag(layout_abstract_tag) => { - // Cache key in form namespace:name - let key = Self::component_name(&layout_abstract_tag.namespace[..], &layout_abstract_tag.name[..]); + /// Preload and cache a component by its namespace and name, then recursively explore and repeat for its descendants + pub fn preload_component(&mut self, name: (&str, &str)) -> io::Result<()> { + // Load and parse the XML file's AST for the visited tag + let xml_path = Self::layout_xml_path(name); + let mut component = Self::parse_xml_component(&self.attribute_parser, &xml_path[..], true)?; - if !already_loaded_layouts.contains(&key[..]) { - // Check if the cache has the loaded component and load it if not - match self.loaded_layouts.get(&key[..]) { - // Tag has not been loaded, so load it now - None => { - // Load and parse the XML file's AST for the visited tag - let xml_path = Self::layout_xml_path(&layout_abstract_tag.namespace[..], &layout_abstract_tag.name[..]); - let mut xml_ast = Self::parse_xml_tree(&self.attribute_parser, &xml_path[..], true, true).unwrap(); + // Keep track of it being loaded to prevent duplicate work during the recursive traversal + let mut already_loaded_layouts = HashSet::new(); + already_loaded_layouts.insert(Self::component_name(name)); - // Keep track of it being loaded to prevent duplicate work - let key_copy = key.clone(); - already_loaded_layouts.insert(key); + // Parse and cache components recursively for all tags referenced within this root component + self.explore_component(&mut component, &mut already_loaded_layouts); - // Turn the entire XML AST into a component - let component = Self::component_ast_to_component(&mut xml_ast); + // Save this loaded root-level component to the cache + let component_name = Self::component_name(name); + self.loaded_components.set(&component_name[..], component); - // Recursively explore the newly loaded AST's tags - self.explore_referenced_components(&xml_ast, already_loaded_layouts); + // Success + Ok(()) + } - // Save the loaded component to the cache - self.loaded_layouts.set(&key_copy[..], component); - }, - // Tag has already been loaded - Some(_) => {}, + /// Preload and cache every XML component file referenced by tags within a recursive traversal of descendants in the given flat component + fn explore_component(&mut self, component: &mut FlatComponent, already_loaded_layouts: &mut HashSet) { + // Go through each direct child in the list that makes up flat component + for child_tag in &component.child_components { + self.explore_component_tag(child_tag, already_loaded_layouts); + } + + // Go through each parameter attribute and preload any default values of layouts + for definition in &component.own_info.user_attributes { + for default in definition.type_sequence_default.iter() { + if let TypeValue::Layout(layouts) = default { + for layout in layouts { + match &*layout.borrow() { + LayoutComponentNode::Tag(tag) => self.explore_component_tag(tag, already_loaded_layouts), + LayoutComponentNode::Text(_) => {}, } } - }, - // Text nodes don't need to be loaded - LayoutAbstractNode::Text(_) => {}, - }; + } + } } } - /// Flatten a full XML component AST into a vector of the immediate children and put the descendants of those nodes into `content` attributes - fn component_ast_to_component(tree: &mut ComponentAst) -> Component { - println!("====> Flattening the following component AST to a component\n{:#?}\n", tree); - let result = tree - .children() - .map(|mut child| { - // Clone the abstract syntax node for this child (excluding the tree) - let mut cloned_child = child.borrow_mut().clone(); + /// Preload and cache every XML component file referenced by tags within a recursive traversal of descendants in the given component tag + fn explore_component_tag(&mut self, tag: &LayoutComponentTag, already_loaded_layouts: &mut HashSet) { + // Determine the cache key of form "namespace:name" + let (name, namespace) = &tag.name; + let key = Self::component_name((&name[..], &namespace[..])); - // If this is a node, stick its descendants into a new `content` attribute - match &mut cloned_child { - // Deeply clone the children and attach the tree to a new `content` attribute - LayoutAbstractNode::Tag(ref mut tag) => { - let ast_vector_in_tag = child.children().map(|mut c| c.make_deep_copy()).collect::>(); - let layout_type_value = TypeValueOrArgument::TypeValue(TypeValue::Layout(ast_vector_in_tag)); - let type_value_in_vec = AttributeValue::TypeValue(vec![layout_type_value]); - let content_attribute = Attribute::new(String::from("content"), type_value_in_vec); - tag.add_attribute(content_attribute); - }, - // Text nodes have no children - LayoutAbstractNode::Text(_) => {}, + // Load the new component if it isn't already preloaded + if !already_loaded_layouts.contains(&key[..]) && self.loaded_components.get(&key[..]).is_none() { + // Load and parse the component XML file for the visited tag + let xml_path = Self::layout_xml_path((&name[..], &namespace[..])); + let mut component = Self::parse_xml_component(&self.attribute_parser, &xml_path[..], true).unwrap(); + + // Keep track of it being loaded to prevent duplicate work + let key_copy = key.clone(); + already_loaded_layouts.insert(key); + + // Recursively explore the newly loaded component + self.explore_component(&mut component, already_loaded_layouts); + + // Save the loaded component to the cache + self.loaded_components.set(&key_copy[..], component); + } + + // Expore the Layout-type user attribute argument values + for argument in &tag.user_arguments { + for value in &argument.value { + if let TypeValueOrArgument::TypeValue(TypeValue::Layout(layouts)) = value { + for layout in layouts { + match &*layout.borrow() { + LayoutComponentNode::Tag(component_tag) => self.explore_component_tag(component_tag, already_loaded_layouts), + LayoutComponentNode::Text(_) => {}, + } + } } - cloned_child + } + } + + // Explore the tree of `content` children + if let Some(ref content) = tag.content { + for child_node in content.iter() { + for descendant in child_node.descendants() { + match &*descendant.borrow() { + LayoutComponentNode::Tag(component_tag) => self.explore_component_tag(component_tag, already_loaded_layouts), + LayoutComponentNode::Text(_) => {}, + } + } + } + } + } + + /// Parse an XML component all the way into a flat component structure + pub fn parse_xml_component(attribute_parser: &AttributeParser, path_or_source: &str, is_path_not_source: bool) -> io::Result { + println!("Parsing XML Component: {}", path_or_source); + let parsed_tree = &mut Self::parse_xml_tree(attribute_parser, path_or_source, is_path_not_source, true)?; + let flat_tree = Self::flatten_component_tree(parsed_tree); + Ok(flat_tree) + } + + /// Parse a fragment of XML layout syntax with a tree of tags (currently only supports a single root node, should eventually implement returning a vector of them) + pub fn parse_xml_node(attribute_parser: &AttributeParser, path_or_source: &str, is_path_not_source: bool) -> io::Result { + let parsed_tree = Self::parse_xml_tree(attribute_parser, path_or_source, is_path_not_source, false)?; + Ok(Self::node_tree_from_node_or_def_tree(&parsed_tree)) + } + + /// Flatten a full XML component AST into a vector of the immediate children and put the descendants of those nodes into `content` attributes + fn flatten_component_tree(tree: &mut NodeOrDefTree) -> FlatComponent { + let own_info = match &*tree.borrow() { + LayoutComponentNodeOrDefinition::LayoutComponentDefinition(definition) => definition.clone(), + LayoutComponentNodeOrDefinition::LayoutComponentNode(LayoutComponentNode::Tag(_)) => panic!("Tag node found in place of component definition"), + LayoutComponentNodeOrDefinition::LayoutComponentNode(LayoutComponentNode::Text(_)) => panic!("Text node found in place of component definition"), + }; + + // Turn all the tag nodes (but not text nodes) into a list of flat child components (with their descendant trees in their `content` attributes) + let child_components = tree + // Get the direct children from this tree node + .children() + // Clone each child abstract tag node (ignoring text nodes) with each of their descendants added to their `content` attribute variable + .filter_map(|child_node| { + // Filter out text nodes because they make no sense as child components + let mut cloned_tag = match &*child_node.borrow() { + LayoutComponentNodeOrDefinition::LayoutComponentNode(LayoutComponentNode::Tag(child_tag)) => child_tag.clone(), + LayoutComponentNodeOrDefinition::LayoutComponentNode(LayoutComponentNode::Text(_)) => return None, + LayoutComponentNodeOrDefinition::LayoutComponentDefinition(_) => panic!("Component definition found in place of tag node"), + }; + + // Clone the tree for this child as `LayoutComponentNode`s and turn its children into a vector, then set that vector as the content attribute + let node_within_root = Self::node_tree_from_node_or_def_tree(&child_node); + let children = node_within_root.children().map(|mut child| { + // Child must be detached in order to live on its own in the vector, otherwise it will be cleaned up when its (former) parent is dropped + child.detach(); + child + }).collect::>(); + cloned_tag.set_content(children); + + // Return this LayoutComponentTag within the component's root definition tag + Some(cloned_tag) }) .collect::>(); - Self::print_layout_component(&result); - result + + // Build and return the resulting flat component made from the cloned data for its `own_info` and `child_components` + FlatComponent::new(own_info, child_components) } /// Get an AST root node representing a parsed XML component file or XML source code - pub fn parse_xml_tree(attribute_parser: &AttributeParser, path_or_source: &str, is_path_not_source: bool, component_declaration: bool) -> io::Result { + pub fn parse_xml_tree(attribute_parser: &AttributeParser, path_or_source: &str, is_path_not_source: bool, component_declaration: bool) -> io::Result { // XML component file markup source code let (path, source) = if is_path_not_source { (path_or_source, fs::read_to_string(path_or_source)?) } else { - (&"[Inline Attribute XML]"[..], String::from(path_or_source)) + ("[Inline Attribute XML]", String::from(path_or_source)) }; // XML document parser that feeds token-by-token through the file let parser = xmlparser::Tokenizer::from(&source[..]); // Node stack used to collect descendant nodes while reading deeper into the tree until each reaches its closing tag - let mut stack: Vec = Vec::new(); + let mut stack: Vec = Vec::new(); // Opening XML tag used to collect the tag name and its various attributes - let mut current_opening_tag: Option = None; + let mut current_opening_tag: Option = None; // Top-level node that is popped from the stack when the closing tag is reached at the end of the XML document - let mut final_result: Option = None; + let mut final_result: Option = None; for token_result in parser { - match token_result { - Ok(token) => { - match token { - // Beginning of an opening tag ( { - // Get the supplied namespace and tag name as owned strings - let namespace = String::from(prefix.as_str()); - let tag_name = String::from(local.as_str()); + let token = token_result.expect(&format!("Invalid syntax when parsing XML layout in component: {}", path)[..]); + match token { + // Beginning of an opening tag ( { + // Get the supplied namespace and tag name as owned strings + let name = (String::from(prefix.as_str()), String::from(local.as_str())); - // Construct an AST tag node with the namespace and tag name - let abstract_tag_node = LayoutAbstractNode::new_tag(namespace, tag_name); + // If this is the root element and we're parsing a component file, the root tag is the component definition + if stack.is_empty() && component_declaration { + // Construct and store the component definition while attributes are added until its opening tag ends + let definition = LayoutComponentDefinition::new(name); + current_opening_tag = Some(LayoutComponentNodeOrDefinition::LayoutComponentDefinition(definition)); + } + // Otherwise, we're parsing a node inside the root or at the root of a fragment of XML layout syntax + else { + // Construct and store the component node while attributes are added until the opening (or self-closing) tag ends + let tag_node = LayoutComponentNode::new_tag(name); + current_opening_tag = Some(LayoutComponentNodeOrDefinition::LayoutComponentNode(tag_node)); + } + }, + // Any attributes within the current opening tag (... ATTRIBUTE="VALUE" ...) + xmlparser::Token::Attribute { prefix, local, value, .. } => { + // Check if the attribute has an empty prefix (thus, only a colon) + let colon_prefixed = prefix.start() > 0 && (prefix.start() == prefix.end()); + // Set the name to the given name, possibly with a prepended colon + let name = if colon_prefixed { + let slice = local.as_str(); + let mut string = String::with_capacity(slice.len() + 1); + string.push(':'); + string.push_str(slice); + string + } + else { + String::from(local.as_str()) + }; + // Set the value to an ordinary string slice of the given value + let value = value.as_str(); - // Store the AST node while attributes are added until the opening (or self-closing) tag ends - current_opening_tag = Some(abstract_tag_node); + // Add the new attribute to the current yet-to-be-closed element + match &mut current_opening_tag { + // Add this attribute as a parameter to the current root-level component definition tag + Some(LayoutComponentNodeOrDefinition::LayoutComponentDefinition(definition)) => { + let parsed_parameter = attribute_parser.parse_attribute_parameter_declaration(value); + definition.add_parameter(parsed_parameter); }, - // Any attributes within the current opening tag (... ATTRIBUTE="VALUE" ...) - xmlparser::Token::Attribute { prefix, local, value, .. } => { - // Check if the attribute has an empty prefix (thus, only a colon) - let colon_prefixed = prefix.start() > 0 && (prefix.start() == prefix.end()); - // Set the name to the given name, possibly with a prepended colon - let name = if colon_prefixed { - let slice = local.as_str(); - let mut string = String::with_capacity(slice.len() + 1); - string.push(':'); - string.push_str(slice); - string - } - else { - String::from(local.as_str()) - }; - // Set the value to an ordinary string slice of the given value - let value = value.as_str(); + // Add this attribute as an argument to the current tag + Some(LayoutComponentNodeOrDefinition::LayoutComponentNode(LayoutComponentNode::Tag(tag))) => { + let parsed_attributes = attribute_parser.parse_attribute_argument_types(value); + let attribute_argument = AttributeArg::new(name, parsed_attributes); + tag.add_attribute(attribute_argument); + }, + // It should be impossible to add an attribute when there is no opening tag in progress + _ => unreachable!(), + } + }, + // Either the end of the opening tag (...>) or the end of a self-closing tag (.../>) or an entire closing tag () + xmlparser::Token::ElementEnd { end, .. } => { + match end { + // After adding any attributes, this element's opening tag ends (...>) + xmlparser::ElementEnd::Open => { + // After adding any attributes, we are now a layer deeper in the stack of yet-to-be-closed descendants + let complete_opening_tag = current_opening_tag.take().unwrap(); + let tree_node = rctree::Node::new(complete_opening_tag); + stack.push(tree_node); + }, + // After adding any attributes, this element's self-closing tag ends (.../>) + xmlparser::ElementEnd::Empty => { + // Because a self-closing element does not go deeper, attach this now-complete node directly to its parent + let parent_node = stack.last_mut().expect(&format!("Invalid syntax when parsing XML layout in component: {}", path)[..]); + let complete_self_closing_tag = current_opening_tag.take().unwrap(); + let tree_node = rctree::Node::new(complete_self_closing_tag); + parent_node.append(tree_node); + }, + // After visiting any descendants inside the opening tag, finally the closing tag is reached () + xmlparser::ElementEnd::Close(..) => { + // Pop the element now that descendants have been parsed and we make our way back up the tree one level + let closed_node_with_descendants = stack + .pop() + .expect(&format!("Encountered extra closing tag when parsing XML layout in component: {}", path)[..]); - // Attributes on the root element are parameter declarations that list the names and types of permitted variables - let attribute = if stack.is_empty() && component_declaration { - let parameter_declaration = attribute_parser.parse_attribute_declaration(value); - Attribute::new(name, parameter_declaration) - } - // Attributes on elements inside the root are arguments to the layout engine (no colon prefix) or the child component (colon prefix) - else { - let parameter_types = attribute_parser.parse_attribute_types(value); - Attribute::new(name, parameter_types) - }; - - // Add the new attribute to the current yet-to-be-closed element - match &mut current_opening_tag { - // The opening tag is indeed a tag AST node - Some(LayoutAbstractNode::Tag(tag)) => { - tag.add_attribute(attribute); + // Append this now-complete node to its parent, unless there is no parent, in which case we save this root node as the final result + match stack.last_mut() { + // If a parent node exists + Some(parent_node) => { + parent_node.append(closed_node_with_descendants); }, - // Somehow the current opening tag is actually a text node (probably impossible) - Some(LayoutAbstractNode::Text(text)) => { - panic!( - "Unexpected text attribute {} attemping to be added to tag when parsing XML layout in component: {}", - text, path - ); - }, - // Somehow there is no current opening tag to add this attribute to (probably impossible) + // If this is the root node None => { - panic!("Error adding attribute to tag when parsing XML layout in component: {}", path); - }, - } - }, - // Either the end of the opening tag (...>) or the end of a self-closing tag (.../>) or an entire closing tag () - xmlparser::Token::ElementEnd { end, .. } => { - match end { - // After adding any attributes, this element's opening tag ends (...>) - xmlparser::ElementEnd::Open => { - // After adding any attributes, we are now a layer deeper in the stack of yet-to-be-closed descendants - let current_abstract_node = current_opening_tag - .take() - .expect(&format!("Invalid syntax when parsing XML layout in component {}", path)[..]); - let tree_node_with_descendants = rctree::Node::new(current_abstract_node); - stack.push(tree_node_with_descendants); - }, - // After adding any attributes, this element's self-closing tag ends (.../>) - xmlparser::ElementEnd::Empty => { - // Because a self-closing element does not go deeper, attach this now-complete node directly to its parent - let parent_node = stack.last_mut().expect(&format!("Invalid syntax when parsing XML layout in component: {}", path)[..]); - let current_abstract_node = current_opening_tag - .take() - .expect(&format!("Invalid syntax when parsing XML layout in component: {}", path)[..]); - let tree_node = rctree::Node::new(current_abstract_node); - parent_node.append(tree_node); - }, - // After visiting any descendants inside the opening tag, finally the closing tag is reached () - xmlparser::ElementEnd::Close(..) => { - // Pop the element now that descendants have been parsed and we make our way back up the tree one level - let closed_node_with_descendants = stack - .pop() - .expect(&format!("Encountered extra closing tag when parsing XML layout in component: {}", path)[..]); - - // Append this now-complete node to its parent, unless there is no parent, in which case we save this root node as the final result - match stack.last_mut() { - // If a parent node exists - Some(parent_node) => { - parent_node.append(closed_node_with_descendants); - }, - // If this is the root node - None => { - match final_result { - // Save the root element as the final result - None => final_result = Some(closed_node_with_descendants), - // There can only be one root element in the XML document, but this isn't the first one encountered - Some(_) => panic!("Encountered multiple root-level tags when parsing XML layout in component: {}", path), - } - }, + match final_result { + // Save the root element as the final result + None => final_result = Some(closed_node_with_descendants), + // There can only be one root element in the XML document, but this isn't the first one encountered + Some(_) => panic!("Encountered multiple root-level tags when parsing XML layout in component: {}", path), } }, } }, - // A text node in the space between sibling elements (... SOME TEXT ...) - xmlparser::Token::Text { text } => { - // Trim any whitespace from around the string - let text_string = String::from(text.as_str().trim()); - - // If the string isn't all whitespace, append a new text node to the parent - if !text_string.is_empty() { - // Get the tree node which contains this text - let parent_node = stack - .last_mut() - .expect(&format!("Encountered text outside the root tag when parsing XML layout in component: {}", path)[..]); - - // Construct an AST text node with the provided text - let abstract_text_node = LayoutAbstractNode::new_text(text_string); - // Put the AST text node in a new tree node - let new_tree_node = rctree::Node::new(abstract_text_node); - - // Attach the new text node on the parent in the tree which contains this text - parent_node.append(new_tree_node); - } - }, - _ => {}, } }, - Err(error) => { - panic!("Failed parsing XML syntax with error: {}", error); + // A text node in the space between sibling elements (... SOME TEXT ...) + xmlparser::Token::Text { text } => { + // Trim any whitespace from around the string + let text_string = String::from(text.as_str().trim()); + + // If the string isn't all whitespace, append a new text node to the parent + if !text_string.is_empty() { + // Get the tree node which contains this text + let parent_node = stack + .last_mut() + .expect(&format!("Encountered text outside the root tag when parsing XML layout in component: {}", path)[..]); + + // Construct a text node with the provided text + let abstract_text_node = LayoutComponentNodeOrDefinition::LayoutComponentNode(LayoutComponentNode::new_text(text_string)); + // Put the text node in a new tree node + let tree_node = rctree::Node::new(abstract_text_node); + + // Attach the new text node on the parent in the tree which contains this text + parent_node.append(tree_node); + } }, + _ => {}, } } @@ -286,60 +337,43 @@ impl LayoutSystem { } /// Get a string in `namespace:name` format (or just `name` for primitives) given a namespace and component name - fn component_name(namespace: &str, name: &str) -> String { + fn component_name(name: (&str, &str)) -> String { + let (namespace, file) = name; if namespace.len() > 0 { - format!("{}:{}", namespace, name) + format!("{}:{}", namespace, file) } else { - String::from(name) + String::from(file) } } /// Get the XML file path given a namespace and component name - fn layout_xml_path(namespace: &str, name: &str) -> String { + fn layout_xml_path(name: (&str, &str)) -> String { + let (namespace, file) = name; if namespace.len() > 0 { - format!("gui/{}/{}.xml", namespace, name) + format!("gui/{}/{}.xml", namespace, file) } else { - format!("gui/{}.xml", name) + format!("gui/{}.xml", file) } } - /// Print a component AST (for debugging) - fn print_layout_tree(tree_root: &ComponentAst) { - for node in tree_root.descendants() { - println!("Printing Component AST:\n{:#?}\n", node); - } - } + /// Convert every element in the tree of `LayoutComponentNodeOrDefinition` wrapper enums into unwrapped `LayoutComponentNode` structs + fn node_tree_from_node_or_def_tree(layout_component_node_or_definition: &NodeOrDefTree) -> NodeTree { + // Unwrap the `LayoutComponentNode` from the root element's value + let cloned_node_data = match &*layout_component_node_or_definition.borrow() { + LayoutComponentNodeOrDefinition::LayoutComponentNode(node) => node.clone(), + LayoutComponentNodeOrDefinition::LayoutComponentDefinition(_) => panic!("Found an unexpected component definition while expecting a node"), + }; - /// Print a component (for debugging) - fn print_layout_component(component: &Component) { - for node in component { - println!("Printing Component:\n{:#?}\n\n", node); - match node { - LayoutAbstractNode::Tag(ref tag) => match tag.content { - Some(ref value) => match value { - AttributeValue::TypeValue(ref type_value) => { - for type_value_or_argument in type_value { - match type_value_or_argument { - TypeValueOrArgument::TypeValue(type_value) => match type_value { - TypeValue::Layout(layout) => { - for component_ast in layout { - Self::print_layout_tree(&component_ast); - } - }, - _ => {}, - }, - TypeValueOrArgument::VariableArgument(_) => {}, - } - } - }, - AttributeValue::VariableParameter(_) => {}, - }, - None => {}, - }, - LayoutAbstractNode::Text(_) => {}, - } + // Build a new tree of the correct type with the unwrapped data as its root value + let mut tree_result = rctree::Node::new(cloned_node_data); + + // Go through all the direct children of the old tree and append the new recursively converted trees to match the shape of the old tree + for tree_node in layout_component_node_or_definition.children() { + tree_result.append(Self::node_tree_from_node_or_def_tree(&tree_node)); } + + tree_result } } diff --git a/src/main.rs b/src/main.rs index fa1c1b52..91bdf565 100644 --- a/src/main.rs +++ b/src/main.rs @@ -12,6 +12,7 @@ mod pipeline; mod resource_cache; mod shader_stage; mod texture; +mod window_dom; mod window_events; use application::Application; diff --git a/src/window_dom.rs b/src/window_dom.rs new file mode 100644 index 00000000..9a9de4cc --- /dev/null +++ b/src/window_dom.rs @@ -0,0 +1,9 @@ +use crate::layout_abstract_syntax::*; + +pub struct WindowDom {} + +impl WindowDom { + pub fn new(root_component: &FlatComponent) -> WindowDom { + Self {} + } +}