mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-10-01 00:48:12 +08:00
Refactor layout system and abstract data types to separate definitions and tags
This commit is contained in:
+1
-1
@@ -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.
|
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
|
## Component lifetime
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -14,7 +14,7 @@
|
|||||||
<!-- Footer -->
|
<!-- Footer -->
|
||||||
<row width="100%" height="24px">
|
<row width="100%" height="24px">
|
||||||
<text height="100%" y-align="50%" x-align="0%" x-padding="10px" :color="['mildwhite']" :size="14px">File: 1.8 MB | Memory: 137 MB | Scratch: 0.7/12.3 GB</text>
|
<text height="100%" y-align="50%" x-align="0%" x-padding="10px" :color="['mildwhite']" :size="14px">File: 1.8 MB | Memory: 137 MB | Scratch: 0.7/12.3 GB</text>
|
||||||
<text height="100%" y-align="50%" x-align="100%" x-padding="10px" :color="['mildwhite']" :size="14px">🖰 Box Select Objects | [⇧G] Move Selection | [⇧R] Rotate Selection | [⇧S] Scale Selection</text>
|
<text height="100%" y-align="50%" x-align="100%" x-padding="10px" :color="['mildwhite']" :size="14px">Box Select Objects | [⇧G] Move Selection | [⇧R] Rotate Selection | [⇧S] Scale Selection</text>
|
||||||
</row>
|
</row>
|
||||||
</col>
|
</col>
|
||||||
</window:main>
|
</window:main>
|
||||||
|
|||||||
+1
-1
@@ -85,7 +85,7 @@ impl Application {
|
|||||||
|
|
||||||
// Main window in the XML layout language
|
// Main window in the XML layout language
|
||||||
let mut main_window_layout = LayoutSystem::new();
|
let mut main_window_layout = LayoutSystem::new();
|
||||||
main_window_layout.load_layout_component("window", "main");
|
main_window_layout.add_window(("window", "main"));
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
surface,
|
surface,
|
||||||
|
|||||||
+179
-73
@@ -1,121 +1,230 @@
|
|||||||
use crate::layout_abstract_types::*;
|
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<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 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<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)]
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
pub enum LayoutAbstractNode {
|
pub enum LayoutComponentNodeOrDefinition {
|
||||||
Tag(LayoutAbstractTag),
|
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 (just a `String`)
|
||||||
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
|
pub enum LayoutComponentNode {
|
||||||
|
Tag(LayoutComponentTag),
|
||||||
Text(String),
|
Text(String),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl LayoutAbstractNode {
|
impl LayoutComponentNode {
|
||||||
pub fn new_tag(namespace: String, name: String) -> Self {
|
/// Given a tag name in "namespace:name" format, construct a `LayoutComponentNode` that wraps a newly constructed `LayoutComponentTag` struct based on the provided name
|
||||||
Self::Tag(LayoutAbstractTag::new(namespace, 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 {
|
pub fn new_text(text: String) -> Self {
|
||||||
Self::Text(text)
|
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)]
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
pub struct LayoutAbstractTag {
|
pub struct LayoutComponentDefinition {
|
||||||
pub namespace: String,
|
/// Name of the component in "namespace:name" format
|
||||||
pub name: String,
|
pub name: (String, String),
|
||||||
/// Layout attributes, which are used by the layout engine.
|
/// User-defined attribute parameters, which are prefixed with ':'
|
||||||
pub layout_attributes: LayoutAttributes,
|
pub user_attributes: Vec<VariableParameter>,
|
||||||
/// The special content attribute, representing the inner elements of this tag.
|
|
||||||
pub content: Option<AttributeValue>,
|
|
||||||
/// User-defined attributes, which are prefixed with ':'
|
|
||||||
pub user_attributes: Vec<Attribute>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl LayoutAbstractTag {
|
impl LayoutComponentDefinition {
|
||||||
pub fn new(namespace: String, name: String) -> Self {
|
/// 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<AttributeArg>,
|
||||||
|
/// The special content attribute, representing 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 {
|
Self {
|
||||||
namespace,
|
|
||||||
name,
|
name,
|
||||||
layout_attributes: Default::default(),
|
layout_arguments: Default::default(),
|
||||||
content: None,
|
content: None,
|
||||||
user_attributes: Vec::new(),
|
user_arguments: Vec::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn add_attribute(&mut self, attribute: Attribute) {
|
/// Provide a sequence of ASTs for this component's content attribute
|
||||||
// User-defined attribute
|
pub fn set_content(&mut self, content: Vec<NodeTree>) {
|
||||||
if attribute.name.chars().next().unwrap() == ':' {
|
self.content = Some(content);
|
||||||
self.user_attributes.push(attribute);
|
}
|
||||||
|
|
||||||
|
/// 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 {
|
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[..] {
|
match &attribute.name[..] {
|
||||||
// The special `content` attribute
|
|
||||||
"content" => self.content = Some(attribute.value),
|
|
||||||
// Layout attributes, stored separately
|
// Layout attributes, stored separately
|
||||||
"width" => self.layout_attributes.width = attribute.dimension(),
|
"width" => self.layout_arguments.width = attribute.dimension(),
|
||||||
"height" => self.layout_attributes.height = attribute.dimension(),
|
"height" => self.layout_arguments.height = attribute.dimension(),
|
||||||
"x-align" => self.layout_attributes.x_align = attribute.percent(),
|
"x-align" => self.layout_arguments.x_align = attribute.percent(),
|
||||||
"y-align" => self.layout_attributes.y_align = attribute.percent(),
|
"y-align" => self.layout_arguments.y_align = attribute.percent(),
|
||||||
"x-padding" => self.layout_attributes.padding.set_horizontal(attribute.dimension()),
|
"x-padding" => self.layout_arguments.padding.set_horizontal(attribute.dimension()),
|
||||||
"y-padding" => self.layout_attributes.padding.set_vertical(attribute.dimension()),
|
"y-padding" => self.layout_arguments.padding.set_vertical(attribute.dimension()),
|
||||||
"padding" => self.layout_attributes.padding = attribute.box_dimensions(),
|
"padding" => self.layout_arguments.padding = attribute.box_dimensions(),
|
||||||
"x-spacing" => self.layout_attributes.spacing.set_horizontal(attribute.dimension()),
|
"x-gap" => self.layout_arguments.gap.set_horizontal(attribute.dimension()),
|
||||||
"y-spacing" => self.layout_attributes.spacing.set_vertical(attribute.dimension()),
|
"y-gap" => self.layout_arguments.gap.set_vertical(attribute.dimension()),
|
||||||
"spacing" => self.layout_attributes.spacing = attribute.box_dimensions(),
|
"gap" => self.layout_arguments.gap = attribute.box_dimensions(),
|
||||||
_ => panic!("unknown builtin attribute {}", attribute.name),
|
_ => 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)]
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
pub struct Attribute {
|
pub struct AttributeArg {
|
||||||
pub name: String,
|
pub name: String,
|
||||||
pub value: AttributeValue,
|
pub value: Vec<TypeValueOrArgument>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Attribute {
|
impl AttributeArg {
|
||||||
pub fn new(name: String, value: AttributeValue) -> Self {
|
/// 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<TypeValueOrArgument>) -> Self {
|
||||||
Self { name, value }
|
Self { name, value }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Extracts this attribute's values as typed values.
|
/// Extract this attribute's values as typed values
|
||||||
fn values(self) -> Vec<TypeValue> {
|
fn values(self) -> Vec<TypeValue> {
|
||||||
if let AttributeValue::TypeValue(values) = self.value {
|
self.value
|
||||||
values
|
.into_iter()
|
||||||
.into_iter()
|
.map(|value| {
|
||||||
.map(|value| {
|
if let TypeValueOrArgument::TypeValue(value) = value {
|
||||||
if let TypeValueOrArgument::TypeValue(value) = value {
|
value
|
||||||
value
|
}
|
||||||
}
|
else {
|
||||||
else {
|
todo!("Variable arguments are note yet supported")
|
||||||
todo!("variable arguments are note yet supported")
|
}
|
||||||
}
|
})
|
||||||
})
|
.collect()
|
||||||
.collect()
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
todo!("variable arguments are not yet supported")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Converts this attribute's value into a single dimension.
|
/// Convert this attribute's value into a single dimension
|
||||||
fn dimension(self) -> Dimension {
|
fn dimension(self) -> Dimension {
|
||||||
let values = self.values();
|
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()
|
values[0].expect_dimension()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Extracts a percentage from this attribute's value.
|
/// Extract a percentage from this attribute's value
|
||||||
fn percent(self) -> f64 {
|
fn percent(self) -> f64 {
|
||||||
match self.dimension() {
|
match self.dimension() {
|
||||||
Dimension::Percent(value) => value,
|
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 {
|
fn box_dimensions(self) -> BoxDimensions {
|
||||||
let values = self.values();
|
let values = self.values();
|
||||||
match values.len() {
|
match values.len() {
|
||||||
@@ -135,29 +244,26 @@ impl Attribute {
|
|||||||
let left = values[3].expect_dimension();
|
let left = values[3].expect_dimension();
|
||||||
BoxDimensions::new(top, right, bottom, left)
|
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<TypeValueOrArgument>),
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Layout-specific attributes.
|
/// Attributes used by the layout engine to calculate sizing and placement
|
||||||
#[derive(Clone, Debug, PartialEq)]
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
pub struct LayoutAttributes {
|
pub struct LayoutAttributes {
|
||||||
pub width: Dimension,
|
pub width: Dimension,
|
||||||
pub height: Dimension,
|
pub height: Dimension,
|
||||||
pub x_align: f64,
|
pub x_align: f64,
|
||||||
pub y_align: f64,
|
pub y_align: f64,
|
||||||
pub spacing: BoxDimensions,
|
pub gap: BoxDimensions,
|
||||||
pub padding: BoxDimensions,
|
pub padding: BoxDimensions,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for LayoutAttributes {
|
impl Default for LayoutAttributes {
|
||||||
|
/// Provide default values for dimensions, alignment, and outside spacing
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
let zero_box = BoxDimensions::all(Dimension::AbsolutePx(0.0));
|
let zero_box = BoxDimensions::all(Dimension::AbsolutePx(0.0));
|
||||||
Self {
|
Self {
|
||||||
@@ -165,7 +271,7 @@ impl Default for LayoutAttributes {
|
|||||||
height: Dimension::Inner,
|
height: Dimension::Inner,
|
||||||
x_align: 0.0,
|
x_align: 0.0,
|
||||||
y_align: 0.0,
|
y_align: 0.0,
|
||||||
spacing: zero_box,
|
gap: zero_box,
|
||||||
padding: zero_box,
|
padding: zero_box,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,14 +1,19 @@
|
|||||||
use crate::color::Color;
|
use crate::color::Color;
|
||||||
use crate::layout_abstract_syntax::*;
|
use crate::layout_abstract_syntax::*;
|
||||||
|
|
||||||
|
/// Parameter definition for an attribute in the root tag of a component XML layout
|
||||||
#[derive(Debug, Clone, PartialEq)]
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
pub struct VariableParameter {
|
pub struct VariableParameter {
|
||||||
|
// Name of the variable binding that can be used within the component in {{template tags}}
|
||||||
pub name: String,
|
pub name: String,
|
||||||
|
// Combinations of allowed sequences of types that can be passed to instances of this component
|
||||||
pub type_sequence_options: Vec<Vec<TypeName>>,
|
pub type_sequence_options: Vec<Vec<TypeName>>,
|
||||||
|
// 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<TypeValue>,
|
pub type_sequence_default: Vec<TypeValue>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl VariableParameter {
|
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<Vec<TypeName>>, default: Vec<TypeValue>) -> Self {
|
pub fn new(name: String, valid_types: Vec<Vec<TypeName>>, default: Vec<TypeValue>) -> Self {
|
||||||
Self {
|
Self {
|
||||||
name,
|
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)]
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
pub enum TypeValueOrArgument {
|
pub enum TypeValueOrArgument {
|
||||||
TypeValue(TypeValue),
|
TypeValue(TypeValue),
|
||||||
VariableArgument(String),
|
VariableArgument(String),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ====================================================================================================
|
||||||
|
|
||||||
|
/// All possible names for types of values in the reactive data and layout system
|
||||||
#[derive(Debug, Clone, PartialEq)]
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
pub enum TypeName {
|
pub enum TypeName {
|
||||||
Layout,
|
Layout,
|
||||||
@@ -41,12 +52,12 @@ pub enum TypeName {
|
|||||||
None,
|
None,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub type ComponentAst = rctree::Node<LayoutAbstractNode>;
|
// ====================================================================================================
|
||||||
pub type Component = Vec<LayoutAbstractNode>;
|
|
||||||
|
|
||||||
|
/// Concrete values for data in the various types allowed by the reactive data and layout system
|
||||||
#[derive(Debug, Clone, PartialEq)]
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
pub enum TypeValue {
|
pub enum TypeValue {
|
||||||
Layout(Vec<ComponentAst>),
|
Layout(Vec<NodeTree>),
|
||||||
Integer(i64),
|
Integer(i64),
|
||||||
Decimal(f64),
|
Decimal(f64),
|
||||||
Dimension(Dimension),
|
Dimension(Dimension),
|
||||||
@@ -61,17 +72,22 @@ impl TypeValue {
|
|||||||
pub fn expect_dimension(&self) -> Dimension {
|
pub fn expect_dimension(&self) -> Dimension {
|
||||||
match self {
|
match self {
|
||||||
Self::Dimension(dimension) => *dimension,
|
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)]
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
pub enum TemplateStringSegment {
|
pub enum TemplateStringSegment {
|
||||||
String(String),
|
String(String),
|
||||||
Argument(TypeValueOrArgument),
|
Argument(TypeValueOrArgument),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ====================================================================================================
|
||||||
|
|
||||||
/// A dimension is a measure along an axis.
|
/// A dimension is a measure along an axis.
|
||||||
#[derive(Copy, Clone, Debug, PartialEq)]
|
#[derive(Copy, Clone, Debug, PartialEq)]
|
||||||
pub enum Dimension {
|
pub enum Dimension {
|
||||||
@@ -89,7 +105,9 @@ pub enum Dimension {
|
|||||||
Height,
|
Height,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Dimensions along a box's four sides.
|
// ====================================================================================================
|
||||||
|
|
||||||
|
/// Dimensions along the four sides of a box layout
|
||||||
#[derive(Copy, Clone, Debug, PartialEq)]
|
#[derive(Copy, Clone, Debug, PartialEq)]
|
||||||
pub struct BoxDimensions {
|
pub struct BoxDimensions {
|
||||||
pub top: Dimension,
|
pub top: Dimension,
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
use crate::color::Color;
|
use crate::color::Color;
|
||||||
use crate::color_palette::ColorPalette;
|
use crate::color_palette::ColorPalette;
|
||||||
use crate::layout_abstract_syntax::*;
|
|
||||||
use crate::layout_abstract_types::*;
|
use crate::layout_abstract_types::*;
|
||||||
use crate::layout_system::*;
|
use crate::layout_system::*;
|
||||||
|
|
||||||
@@ -18,7 +17,7 @@ impl AttributeParser {
|
|||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
let capture_attribute_declaration_parameter_regex: regex::Regex = regex::Regex::new(
|
let capture_attribute_declaration_parameter_regex: regex::Regex = regex::Regex::new(
|
||||||
// Parameter: ?: (?, ... | ...) = ?
|
// 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();
|
.unwrap();
|
||||||
|
|
||||||
@@ -31,7 +30,7 @@ impl AttributeParser {
|
|||||||
r#"^\s*(-?\d+)\s*$|"#,
|
r#"^\s*(-?\d+)\s*$|"#,
|
||||||
// Decimal: ?
|
// Decimal: ?
|
||||||
r#"^\s*(-?(?:(?:\d+\.\d*)|(?:\d*\.\d+)))\s*$|"#,
|
r#"^\s*(-?(?:(?:\d+\.\d*)|(?:\d*\.\d+)))\s*$|"#,
|
||||||
// AbsolutePx: px
|
// AbsolutePx: ?px
|
||||||
r#"^\s*(-?(?:(?:\d+(?:\.\d*)?)|(?:\d*(?:\.\d+))))([Pp][Xx])\s*$|"#,
|
r#"^\s*(-?(?:(?:\d+(?:\.\d*)?)|(?:\d*(?:\.\d+))))([Pp][Xx])\s*$|"#,
|
||||||
// Percent: ?%
|
// Percent: ?%
|
||||||
r#"^\s*(-?(?:(?:\d+(?:\.\d*)?)|(?:\d*(?:\.\d+))))(%)\s*$|"#,
|
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<TypeValueOrArgument> {
|
||||||
let attribute_types = input.split(",").map(|piece| piece.trim()).collect::<Vec<&str>>();
|
let attribute_types = input.split(",").map(|piece| piece.trim()).collect::<Vec<&str>>();
|
||||||
let list = attribute_types
|
let list = attribute_types
|
||||||
.iter()
|
.iter()
|
||||||
.map(|attribute_type| self.parse_attribute_type(attribute_type))
|
.map(|attribute_type| self.parse_attribute_argument_type(attribute_type))
|
||||||
.collect::<Vec<TypeValueOrArgument>>();
|
.collect::<Vec<TypeValueOrArgument>>();
|
||||||
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
|
// Match with the regular expression
|
||||||
let captures = self
|
let captures = self
|
||||||
.capture_attribute_type_sequences_regex
|
.capture_attribute_type_sequences_regex
|
||||||
@@ -98,17 +97,18 @@ impl AttributeParser {
|
|||||||
// Remove any whitespace in order to test if any XML syntax is present
|
// Remove any whitespace in order to test if any XML syntax is present
|
||||||
let trimmed = xml_syntax.trim();
|
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 {
|
let layout_entries = if trimmed.len() == 0 {
|
||||||
vec![]
|
vec![]
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
let unescaped = Self::unescape_xml(trimmed);
|
let unescaped = Self::unescape_xml(trimmed);
|
||||||
let component_ast = LayoutSystem::parse_xml_tree(&self, &unescaped[..], false, false).unwrap();
|
let parsed = LayoutSystem::parse_xml_node(&self, &unescaped[..], false).unwrap();
|
||||||
vec![component_ast]
|
// 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))
|
TypeValueOrArgument::TypeValue(TypeValue::Layout(layout_entries))
|
||||||
},
|
},
|
||||||
// Integer: ?
|
// 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
|
// Match with the regular expression
|
||||||
let captures = self
|
let captures = self
|
||||||
.capture_attribute_declaration_parameter_regex
|
.capture_attribute_declaration_parameter_regex
|
||||||
@@ -278,19 +278,21 @@ impl AttributeParser {
|
|||||||
// Required default value for the variable parameter if not provided
|
// Required default value for the variable parameter if not provided
|
||||||
let default_type_sequence = default_value
|
let default_type_sequence = default_value
|
||||||
.split(",")
|
.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::TypeValue(type_value) => type_value,
|
||||||
TypeValueOrArgument::VariableArgument(variable_value) => {
|
TypeValueOrArgument::VariableArgument(variable_value) => {
|
||||||
panic!(
|
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
|
variable_value, attribute_declaration
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
.collect::<Vec<TypeValue>>();
|
.collect::<Vec<TypeValue>>();
|
||||||
|
|
||||||
|
// TODO: Verify the default types match the specified allowed types
|
||||||
|
|
||||||
// Return the parameter
|
// 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
|
// Unrecognized type pattern
|
||||||
_ => panic!("Invalid attribute attribute declaration `{}` when parsing XML layout", attribute_declaration),
|
_ => panic!("Invalid attribute attribute declaration `{}` when parsing XML layout", attribute_declaration),
|
||||||
|
|||||||
+283
-249
@@ -2,279 +2,330 @@ use crate::layout_abstract_syntax::*;
|
|||||||
use crate::layout_abstract_types::*;
|
use crate::layout_abstract_types::*;
|
||||||
use crate::layout_attribute_parser::*;
|
use crate::layout_attribute_parser::*;
|
||||||
use crate::resource_cache::ResourceCache;
|
use crate::resource_cache::ResourceCache;
|
||||||
|
use crate::window_dom::*;
|
||||||
use std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
use std::fs;
|
use std::fs;
|
||||||
use std::io;
|
use std::io;
|
||||||
|
|
||||||
pub struct LayoutSystem {
|
pub struct LayoutSystem {
|
||||||
loaded_layouts: ResourceCache<Component>,
|
windows: Vec<WindowDom>,
|
||||||
|
loaded_components: ResourceCache<FlatComponent>,
|
||||||
attribute_parser: AttributeParser,
|
attribute_parser: AttributeParser,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl LayoutSystem {
|
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 {
|
pub fn new() -> LayoutSystem {
|
||||||
Self {
|
Self {
|
||||||
loaded_layouts: ResourceCache::new(),
|
windows: vec![],
|
||||||
|
loaded_components: ResourceCache::new(),
|
||||||
attribute_parser: AttributeParser::new(),
|
attribute_parser: AttributeParser::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Preload and cache a component by its namespace and name, then recursively explore and repeat for its descendants
|
/// Load and construct a new window from a layout component
|
||||||
pub fn load_layout_component(&mut self, namespace: &str, name: &str) {
|
pub fn add_window(&mut self, name: (&str, &str)) {
|
||||||
// Load and parse the XML file's AST for the visited tag
|
// Preload the component and its dependencies
|
||||||
let xml_path = Self::layout_xml_path(namespace, name);
|
self.preload_component(name)
|
||||||
let xml_parsed = Self::parse_xml_tree(&self.attribute_parser, &xml_path[..], true, true);
|
.expect(&format!("Failure loading layout component '{}'", Self::component_name(name))[..]);
|
||||||
let mut xml_ast = match xml_parsed {
|
|
||||||
Ok(result) => result,
|
|
||||||
Err(error) => panic!("Error parsing XML layout syntax: {}", error),
|
|
||||||
};
|
|
||||||
|
|
||||||
// Keep track of it being loaded to prevent duplicate work
|
// Get the now-loaded component
|
||||||
let mut already_loaded_layouts = HashSet::new();
|
let window_root_component_name = Self::component_name(name);
|
||||||
already_loaded_layouts.insert(Self::component_name(namespace, name));
|
let window_root_component = self.loaded_components.get(&window_root_component_name[..]).unwrap();
|
||||||
|
|
||||||
// Turn the entire XML AST into a component
|
// Construct the window and save it
|
||||||
let component = Self::component_ast_to_component(&mut xml_ast);
|
let new_window = WindowDom::new(window_root_component);
|
||||||
// Self::print_layout_component(&component);
|
self.windows.push(new_window);
|
||||||
|
|
||||||
// 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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Preload and cache every XML component file referenced by tags within a recursive traversal of descendants in the given component AST
|
/// Preload and cache a component by its namespace and name, then recursively explore and repeat for its descendants
|
||||||
fn explore_referenced_components(&mut self, layout_tree_root: &ComponentAst, already_loaded_layouts: &mut HashSet<String>) {
|
pub fn preload_component(&mut self, name: (&str, &str)) -> io::Result<()> {
|
||||||
for child_tag in layout_tree_root.descendants() {
|
// Load and parse the XML file's AST for the visited tag
|
||||||
match &*child_tag.borrow() {
|
let xml_path = Self::layout_xml_path(name);
|
||||||
// Tags are references to other XML layouts that should be loaded and cached
|
let mut component = Self::parse_xml_component(&self.attribute_parser, &xml_path[..], true)?;
|
||||||
LayoutAbstractNode::Tag(layout_abstract_tag) => {
|
|
||||||
// Cache key in form namespace:name
|
|
||||||
let key = Self::component_name(&layout_abstract_tag.namespace[..], &layout_abstract_tag.name[..]);
|
|
||||||
|
|
||||||
if !already_loaded_layouts.contains(&key[..]) {
|
// Keep track of it being loaded to prevent duplicate work during the recursive traversal
|
||||||
// Check if the cache has the loaded component and load it if not
|
let mut already_loaded_layouts = HashSet::new();
|
||||||
match self.loaded_layouts.get(&key[..]) {
|
already_loaded_layouts.insert(Self::component_name(name));
|
||||||
// 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
|
// Parse and cache components recursively for all tags referenced within this root component
|
||||||
let key_copy = key.clone();
|
self.explore_component(&mut component, &mut already_loaded_layouts);
|
||||||
already_loaded_layouts.insert(key);
|
|
||||||
|
|
||||||
// Turn the entire XML AST into a component
|
// Save this loaded root-level component to the cache
|
||||||
let component = Self::component_ast_to_component(&mut xml_ast);
|
let component_name = Self::component_name(name);
|
||||||
|
self.loaded_components.set(&component_name[..], component);
|
||||||
|
|
||||||
// Recursively explore the newly loaded AST's tags
|
// Success
|
||||||
self.explore_referenced_components(&xml_ast, already_loaded_layouts);
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
// Save the loaded component to the cache
|
/// Preload and cache every XML component file referenced by tags within a recursive traversal of descendants in the given flat component
|
||||||
self.loaded_layouts.set(&key_copy[..], component);
|
fn explore_component(&mut self, component: &mut FlatComponent, already_loaded_layouts: &mut HashSet<String>) {
|
||||||
},
|
// Go through each direct child in the list that makes up flat component
|
||||||
// Tag has already been loaded
|
for child_tag in &component.child_components {
|
||||||
Some(_) => {},
|
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
|
/// Preload and cache every XML component file referenced by tags within a recursive traversal of descendants in the given component tag
|
||||||
fn component_ast_to_component(tree: &mut ComponentAst) -> Component {
|
fn explore_component_tag(&mut self, tag: &LayoutComponentTag, already_loaded_layouts: &mut HashSet<String>) {
|
||||||
println!("====> Flattening the following component AST to a component\n{:#?}\n", tree);
|
// Determine the cache key of form "namespace:name"
|
||||||
let result = tree
|
let (name, namespace) = &tag.name;
|
||||||
.children()
|
let key = Self::component_name((&name[..], &namespace[..]));
|
||||||
.map(|mut child| {
|
|
||||||
// Clone the abstract syntax node for this child (excluding the tree)
|
|
||||||
let mut cloned_child = child.borrow_mut().clone();
|
|
||||||
|
|
||||||
// If this is a node, stick its descendants into a new `content` attribute
|
// Load the new component if it isn't already preloaded
|
||||||
match &mut cloned_child {
|
if !already_loaded_layouts.contains(&key[..]) && self.loaded_components.get(&key[..]).is_none() {
|
||||||
// Deeply clone the children and attach the tree to a new `content` attribute
|
// Load and parse the component XML file for the visited tag
|
||||||
LayoutAbstractNode::Tag(ref mut tag) => {
|
let xml_path = Self::layout_xml_path((&name[..], &namespace[..]));
|
||||||
let ast_vector_in_tag = child.children().map(|mut c| c.make_deep_copy()).collect::<Vec<_>>();
|
let mut component = Self::parse_xml_component(&self.attribute_parser, &xml_path[..], true).unwrap();
|
||||||
let layout_type_value = TypeValueOrArgument::TypeValue(TypeValue::Layout(ast_vector_in_tag));
|
|
||||||
let type_value_in_vec = AttributeValue::TypeValue(vec![layout_type_value]);
|
// Keep track of it being loaded to prevent duplicate work
|
||||||
let content_attribute = Attribute::new(String::from("content"), type_value_in_vec);
|
let key_copy = key.clone();
|
||||||
tag.add_attribute(content_attribute);
|
already_loaded_layouts.insert(key);
|
||||||
},
|
|
||||||
// Text nodes have no children
|
// Recursively explore the newly loaded component
|
||||||
LayoutAbstractNode::Text(_) => {},
|
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<FlatComponent> {
|
||||||
|
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<NodeTree> {
|
||||||
|
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::<Vec<_>>();
|
||||||
|
cloned_tag.set_content(children);
|
||||||
|
|
||||||
|
// Return this LayoutComponentTag within the component's root definition tag
|
||||||
|
Some(cloned_tag)
|
||||||
})
|
})
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
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
|
/// 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<ComponentAst> {
|
pub fn parse_xml_tree(attribute_parser: &AttributeParser, path_or_source: &str, is_path_not_source: bool, component_declaration: bool) -> io::Result<NodeOrDefTree> {
|
||||||
// XML component file markup source code
|
// XML component file markup source code
|
||||||
let (path, source) = if is_path_not_source {
|
let (path, source) = if is_path_not_source {
|
||||||
(path_or_source, fs::read_to_string(path_or_source)?)
|
(path_or_source, fs::read_to_string(path_or_source)?)
|
||||||
}
|
}
|
||||||
else {
|
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
|
// XML document parser that feeds token-by-token through the file
|
||||||
let parser = xmlparser::Tokenizer::from(&source[..]);
|
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
|
// Node stack used to collect descendant nodes while reading deeper into the tree until each reaches its closing tag
|
||||||
let mut stack: Vec<ComponentAst> = Vec::new();
|
let mut stack: Vec<NodeOrDefTree> = Vec::new();
|
||||||
// Opening XML tag used to collect the tag name and its various attributes
|
// Opening XML tag used to collect the tag name and its various attributes
|
||||||
let mut current_opening_tag: Option<LayoutAbstractNode> = None;
|
let mut current_opening_tag: Option<LayoutComponentNodeOrDefinition> = None;
|
||||||
// Top-level node that is popped from the stack when the closing tag is reached at the end of the XML document
|
// 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<ComponentAst> = None;
|
let mut final_result: Option<NodeOrDefTree> = None;
|
||||||
|
|
||||||
for token_result in parser {
|
for token_result in parser {
|
||||||
match token_result {
|
let token = token_result.expect(&format!("Invalid syntax when parsing XML layout in component: {}", path)[..]);
|
||||||
Ok(token) => {
|
match token {
|
||||||
match token {
|
// Beginning of an opening tag (<NAMESPACE:NAME ...)
|
||||||
// Beginning of an opening tag (<NAMESPACE:NAME ...)
|
xmlparser::Token::ElementStart { prefix, local, .. } => {
|
||||||
xmlparser::Token::ElementStart { prefix, local, .. } => {
|
// Get the supplied namespace and tag name as owned strings
|
||||||
// Get the supplied namespace and tag name as owned strings
|
let name = (String::from(prefix.as_str()), String::from(local.as_str()));
|
||||||
let namespace = String::from(prefix.as_str());
|
|
||||||
let tag_name = String::from(local.as_str());
|
|
||||||
|
|
||||||
// Construct an AST tag node with the namespace and tag name
|
// If this is the root element and we're parsing a component file, the root tag is the component definition
|
||||||
let abstract_tag_node = LayoutAbstractNode::new_tag(namespace, tag_name);
|
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
|
// Add the new attribute to the current yet-to-be-closed element
|
||||||
current_opening_tag = Some(abstract_tag_node);
|
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" ...)
|
// Add this attribute as an argument to the current tag
|
||||||
xmlparser::Token::Attribute { prefix, local, value, .. } => {
|
Some(LayoutComponentNodeOrDefinition::LayoutComponentNode(LayoutComponentNode::Tag(tag))) => {
|
||||||
// Check if the attribute has an empty prefix (thus, only a colon)
|
let parsed_attributes = attribute_parser.parse_attribute_argument_types(value);
|
||||||
let colon_prefixed = prefix.start() > 0 && (prefix.start() == prefix.end());
|
let attribute_argument = AttributeArg::new(name, parsed_attributes);
|
||||||
// Set the name to the given name, possibly with a prepended colon
|
tag.add_attribute(attribute_argument);
|
||||||
let name = if colon_prefixed {
|
},
|
||||||
let slice = local.as_str();
|
// It should be impossible to add an attribute when there is no opening tag in progress
|
||||||
let mut string = String::with_capacity(slice.len() + 1);
|
_ => unreachable!(),
|
||||||
string.push(':');
|
}
|
||||||
string.push_str(slice);
|
},
|
||||||
string
|
// Either the end of the opening tag (...>) or the end of a self-closing tag (.../>) or an entire closing tag (</NAMESPACE:NAME>)
|
||||||
}
|
xmlparser::Token::ElementEnd { end, .. } => {
|
||||||
else {
|
match end {
|
||||||
String::from(local.as_str())
|
// After adding any attributes, this element's opening tag ends (...>)
|
||||||
};
|
xmlparser::ElementEnd::Open => {
|
||||||
// Set the value to an ordinary string slice of the given value
|
// After adding any attributes, we are now a layer deeper in the stack of yet-to-be-closed descendants
|
||||||
let value = value.as_str();
|
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 (</NAMESPACE:NAME>)
|
||||||
|
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
|
// 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
|
||||||
let attribute = if stack.is_empty() && component_declaration {
|
match stack.last_mut() {
|
||||||
let parameter_declaration = attribute_parser.parse_attribute_declaration(value);
|
// If a parent node exists
|
||||||
Attribute::new(name, parameter_declaration)
|
Some(parent_node) => {
|
||||||
}
|
parent_node.append(closed_node_with_descendants);
|
||||||
// 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);
|
|
||||||
},
|
},
|
||||||
// Somehow the current opening tag is actually a text node (probably impossible)
|
// If this is the root node
|
||||||
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)
|
|
||||||
None => {
|
None => {
|
||||||
panic!("Error adding attribute to tag 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
|
||||||
// Either the end of the opening tag (...>) or the end of a self-closing tag (.../>) or an entire closing tag (</NAMESPACE:NAME>)
|
Some(_) => panic!("Encountered multiple root-level tags when parsing XML layout in component: {}", path),
|
||||||
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 (</NAMESPACE:NAME>)
|
|
||||||
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),
|
|
||||||
}
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
// 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) => {
|
// A text node in the space between sibling elements (... SOME TEXT ...)
|
||||||
panic!("Failed parsing XML syntax with error: {}", error);
|
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
|
/// 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 {
|
if namespace.len() > 0 {
|
||||||
format!("{}:{}", namespace, name)
|
format!("{}:{}", namespace, file)
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
String::from(name)
|
String::from(file)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get the XML file path given a namespace and component name
|
/// 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 {
|
if namespace.len() > 0 {
|
||||||
format!("gui/{}/{}.xml", namespace, name)
|
format!("gui/{}/{}.xml", namespace, file)
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
format!("gui/{}.xml", name)
|
format!("gui/{}.xml", file)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Print a component AST (for debugging)
|
/// Convert every element in the tree of `LayoutComponentNodeOrDefinition` wrapper enums into unwrapped `LayoutComponentNode` structs
|
||||||
fn print_layout_tree(tree_root: &ComponentAst) {
|
fn node_tree_from_node_or_def_tree(layout_component_node_or_definition: &NodeOrDefTree) -> NodeTree {
|
||||||
for node in tree_root.descendants() {
|
// Unwrap the `LayoutComponentNode` from the root element's value
|
||||||
println!("Printing Component AST:\n{:#?}\n", node);
|
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)
|
// Build a new tree of the correct type with the unwrapped data as its root value
|
||||||
fn print_layout_component(component: &Component) {
|
let mut tree_result = rctree::Node::new(cloned_node_data);
|
||||||
for node in component {
|
|
||||||
println!("Printing Component:\n{:#?}\n\n", node);
|
// 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
|
||||||
match node {
|
for tree_node in layout_component_node_or_definition.children() {
|
||||||
LayoutAbstractNode::Tag(ref tag) => match tag.content {
|
tree_result.append(Self::node_tree_from_node_or_def_tree(&tree_node));
|
||||||
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(_) => {},
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
tree_result
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ mod pipeline;
|
|||||||
mod resource_cache;
|
mod resource_cache;
|
||||||
mod shader_stage;
|
mod shader_stage;
|
||||||
mod texture;
|
mod texture;
|
||||||
|
mod window_dom;
|
||||||
mod window_events;
|
mod window_events;
|
||||||
|
|
||||||
use application::Application;
|
use application::Application;
|
||||||
|
|||||||
@@ -0,0 +1,9 @@
|
|||||||
|
use crate::layout_abstract_syntax::*;
|
||||||
|
|
||||||
|
pub struct WindowDom {}
|
||||||
|
|
||||||
|
impl WindowDom {
|
||||||
|
pub fn new(root_component: &FlatComponent) -> WindowDom {
|
||||||
|
Self {}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user