Layout language AST building now complete with recursive parsing referenced XML files
This commit is contained in:
parent
0c7e6bc883
commit
559833be8e
|
|
@ -1,3 +1,3 @@
|
|||
<box content="INNER_XML: (None) = none" :fill="FILL: (Color | None) = none" :round="ROUND: (AbsolutePx) = 0px" :border-thickness="BORDER_THICKNESS: (AbsolutePx) = 0px" :border-color="BORDER_COLOR: (Color | None) = none">
|
||||
<box content="INNER_XML: (None) = none" :fill="FILL: (Color | None) = none" :round="ROUND: (AbsolutePx | AbsolutePx, AbsolutePx, AbsolutePx, AbsolutePx) = 0px" :border-thickness="BORDER_THICKNESS: (AbsolutePx) = 0px" :border-color="BORDER_COLOR: (Color | None) = none">
|
||||
{{INNER_XML}}
|
||||
</box>
|
||||
|
|
@ -1,16 +1,16 @@
|
|||
<header:window-buttons :maximized="IS_MAXIMIZED: (Bool) = false">
|
||||
<box height="100%" y-align="50%" x-padding="18px">
|
||||
<icon :src="window_minimize.svg" />
|
||||
<icon :svg="`window_minimize.svg`" />
|
||||
</box>
|
||||
<box height="100%" y-align="50%" x-padding="18px">
|
||||
<if :a="{{IS_MAXIMIZED}}">
|
||||
<icon :src="window_restore_down.svg" />
|
||||
<icon :svg="`window_restore_down.svg`" />
|
||||
</if>
|
||||
<if :a="{{IS_MAXIMIZED}}" b="false">
|
||||
<icon :src="maximize.svg" />
|
||||
<icon :svg="`maximize.svg`" />
|
||||
</if>
|
||||
</box>
|
||||
<box height="100%" y-align="50%" x-padding="18px">
|
||||
<icon :src="window_close.svg" />
|
||||
<icon :svg="`window_close.svg`" />
|
||||
</box>
|
||||
</header:window-buttons>
|
||||
|
|
@ -1,3 +1,3 @@
|
|||
<icon content="SVG_DATA: (None) = none" :src="SVG_SOURCE: (TemplateString | None) = none">
|
||||
|
||||
<icon content="INNER_XML: (None) = none" :svg="SVG_SOURCE: (TemplateString) = ``" :style="SVG_STYLE: (TemplateString) = ``">
|
||||
{{INNER_XML}}
|
||||
</icon>
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
<if content="INNER_XML: (None) = none" :a="CONDITION_A: (Integer | Decimal | AbsolutePx | Percent | PercentRemainder | Inner | Width | Height | TemplateString | Color | Bool | None) = true" :b="CONDITION_B: (Integer | Decimal | AbsolutePx | Percent | PercentRemainder | Inner | Width | Height | TemplateString | Color | Bool | None) = true">
|
||||
{{RESULT}}
|
||||
</if>
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
<input:checkbox-with-dropdown content="OPTION_LIST: (None) = none" :disabled="DISABLED: (Bool) = false" :checked="CHECKED: (Bool) = false" :selected-index="SELECTED_INDEX: (Integer) = 0">
|
||||
<!-- Checkbox -->
|
||||
<col width="height" height="100%">
|
||||
<box width="100%" height="100%" x-align="50%" y-align="50%" :round="4px 0px 0px 4px" :fill="['accent']">
|
||||
<box width="100%" height="100%" x-align="50%" y-align="50%" :round="4px, 0px, 0px, 4px" :fill="['accent']">
|
||||
<input:checkbox :checked="{{CHECKED}}" :disabled="{{DISABLED}}" :inverted="true" />
|
||||
</box>
|
||||
</col>
|
||||
|
|
|
|||
|
|
@ -2,18 +2,10 @@
|
|||
<box width="14px" height="14px" :fill="{{BOX_COLOR}}" :round="2px">
|
||||
<if :a="{{CHECKED}}">
|
||||
<if :a="{{INVERTED}}" :b="false">
|
||||
<icon>
|
||||
<svg width="12.84px" height="11.59px" viewBox="0 0 12.84 11.59">
|
||||
<polygon points="5.53,11.59 0,6.67 1.66,4.8 5.13,7.88 10.82,0 12.84,1.46" fill="#ffffff" />
|
||||
</svg>
|
||||
</icon>
|
||||
<icon :svg="`checkmark_white.svg`" />
|
||||
</if>
|
||||
<if :a="{{INVERTED}}">
|
||||
<icon>
|
||||
<svg width="12.84px" height="11.59px" viewBox="0 0 12.84 11.59">
|
||||
<polygon points="5.53,11.59 0,6.67 1.66,4.8 5.13,7.88 10.82,0 12.84,1.46" fill="#4b79a7" />
|
||||
</svg>
|
||||
</icon>
|
||||
<icon :svg="`checkmark_accent.svg`" />
|
||||
</if>
|
||||
</if>
|
||||
</box>
|
||||
|
|
|
|||
|
|
@ -6,11 +6,7 @@
|
|||
</col>
|
||||
<!-- Dropdown arrow icon -->
|
||||
<col width="8px" height="100%">
|
||||
<icon width="8px" height="100%" x-align="50%" y-align="50%">
|
||||
<svg width="7" height="5" viewBox="0 0 7 5">
|
||||
<path d="M0,.5l3.5,3L7,.5v2l-3.5,3L0,2.5Z" transform="translate(0 -0.5)" fill="#ddd" />
|
||||
</svg>
|
||||
</icon>
|
||||
<icon width="8px" height="100%" x-align="50%" y-align="50%" :svg="`dropdown_arrow.svg`" />
|
||||
</col>
|
||||
</box>
|
||||
</input:dropdown>
|
||||
|
|
|
|||
|
|
@ -1,3 +1 @@
|
|||
<text content="TEXT_STRING: (None) = none" :color="COLOR: (Color | None) = ['middlegray']" :size="SIZE: (AbsolutePx) = 12px">
|
||||
|
||||
</text>
|
||||
<text content="TEXT_STRING: (None) = none" :color="COLOR: (Color | None) = ['middlegray']" :size="SIZE: (AbsolutePx) = 12px"></text>
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
<viewport:panels>
|
||||
<checkbox-with-dropdown :checked="true" :selected-index="2">
|
||||
<input:checkbox-with-dropdown :checked="true" :selected-index="2">
|
||||
Option A
|
||||
Option B
|
||||
Option C
|
||||
</checkbox-with-dropdown>
|
||||
</input:checkbox-with-dropdown>
|
||||
</viewport:panels>
|
||||
|
|
@ -69,13 +69,18 @@ impl Application {
|
|||
// Temporary setup below, TODO: move to appropriate place in architecture
|
||||
|
||||
// Data structure maintaining the user interface
|
||||
let gui_rect_pipeline = Pipeline::new(&device, swap_chain_descriptor.format, vec![], &mut shader_cache, ("shaders/shader.vert", "shaders/shader.frag"));
|
||||
let gui_rect_pipeline = Pipeline::new(
|
||||
&device, swap_chain_descriptor.format, Vec::new(), &mut shader_cache, ("shaders/shader.vert", "shaders/shader.frag")
|
||||
);
|
||||
pipeline_cache.set("gui_rect", gui_rect_pipeline);
|
||||
|
||||
// Render quad hierarchy
|
||||
let gui_root_data = GuiNode::new(swap_chain_descriptor.width, swap_chain_descriptor.height, ColorPalette::Accent.into_color_srgb());
|
||||
let gui_root = rctree::Node::new(gui_root_data);
|
||||
|
||||
GuiLayout::new();
|
||||
// Main window in the XML layout language
|
||||
let mut main_window_layout = GuiLayout::new();
|
||||
main_window_layout.load_layout("window", "main");
|
||||
|
||||
Self {
|
||||
surface,
|
||||
|
|
@ -97,7 +102,7 @@ impl Application {
|
|||
}
|
||||
|
||||
// Called every time by the event loop
|
||||
pub fn main_event_loop<T>(&mut self, event: Event<'_, T>, control_flow: &mut ControlFlow, window: &Window) {
|
||||
fn main_event_loop<T>(&mut self, event: Event<'_, T>, control_flow: &mut ControlFlow, window: &Window) {
|
||||
// Wait for the next event to cause a subsequent event loop run, instead of looping instantly as a game would need
|
||||
*control_flow = ControlFlow::Wait;
|
||||
|
||||
|
|
@ -122,12 +127,12 @@ impl Application {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn update_gui(&mut self) {
|
||||
fn update_gui(&mut self) {
|
||||
|
||||
}
|
||||
|
||||
// Render the queue of pipeline draw commands over the current window
|
||||
pub fn render(&mut self) {
|
||||
fn render(&mut self) {
|
||||
// Get a frame buffer to render on
|
||||
let frame = self.swap_chain.get_next_texture().expect("Timeout getting frame buffer texture");
|
||||
|
||||
|
|
|
|||
|
|
@ -1,60 +1,143 @@
|
|||
use std::fs;
|
||||
use std::io;
|
||||
use crate::layout_parsed_node::*;
|
||||
use std::collections::HashSet;
|
||||
use crate::layout_abstract_syntax::*;
|
||||
use crate::layout_attribute_parser::*;
|
||||
use crate::resource_cache::ResourceCache;
|
||||
|
||||
pub struct GuiLayout {
|
||||
|
||||
pub loaded_layouts: ResourceCache<rctree::Node<LayoutAbstractNode>>,
|
||||
attribute_parser: AttributeParser,
|
||||
}
|
||||
|
||||
impl GuiLayout {
|
||||
pub fn new() -> GuiLayout {
|
||||
let parsed_layout_tree = Self::parse_xml_file("gui/window/main.xml").unwrap();
|
||||
Self::interpret_abstract_syntax_tree(parsed_layout_tree);
|
||||
|
||||
Self {}
|
||||
Self {
|
||||
loaded_layouts: ResourceCache::new(),
|
||||
attribute_parser: AttributeParser::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn parse_xml_file(path: &str) -> io::Result<rctree::Node<LayoutParsedNode>> {
|
||||
|
||||
pub fn load_layout(&mut self, namespace: &str, name: &str) {
|
||||
// Load and parse the XML layout
|
||||
let xml_path = self.layout_xml_path(namespace, name);
|
||||
let window_main = self.parse_xml_file(&xml_path[..]).unwrap();
|
||||
|
||||
// Keep track of it being loaded to prevent duplicate work
|
||||
let mut already_loaded_layouts = HashSet::new();
|
||||
already_loaded_layouts.insert(format!("{}:{}", namespace, name));
|
||||
|
||||
// Load XML files recursively for all tags referenced in window:main and within those layouts
|
||||
self.explore_referenced_layouts(&window_main, &mut already_loaded_layouts);
|
||||
let tag_name = self.layout_name(namespace, name);
|
||||
self.loaded_layouts.set(&tag_name[..], window_main);
|
||||
}
|
||||
|
||||
fn explore_referenced_layouts(&mut self, layout_tree_root: &rctree::Node<LayoutAbstractNode>, already_loaded_layouts: &mut HashSet<String>) {
|
||||
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.layout_name(&layout_abstract_tag.namespace[..], &layout_abstract_tag.name[..]);
|
||||
|
||||
if !already_loaded_layouts.contains(&key[..]) {
|
||||
// Check if the cache has the loaded layout and load it if not
|
||||
match self.loaded_layouts.get(&key[..]) {
|
||||
// Tag has not been loaded, so load it now
|
||||
None => {
|
||||
// Load the layout for the visited tag
|
||||
let xml_path = self.layout_xml_path(&layout_abstract_tag.namespace[..], &layout_abstract_tag.name[..]);
|
||||
let new_loaded_layout = self.parse_xml_file(&xml_path[..]).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 layout's tags
|
||||
self.explore_referenced_layouts(&new_loaded_layout, already_loaded_layouts);
|
||||
|
||||
self.loaded_layouts.set(&key_copy[..], new_loaded_layout);
|
||||
}
|
||||
// Tag has already been loaded
|
||||
Some(_) => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
// Text nodes don't need to be loaded
|
||||
LayoutAbstractNode::Text(_) => {}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
fn layout_name(&self, namespace: &str, name: &str) -> String {
|
||||
if namespace.len() > 0 {
|
||||
format!("{}:{}", namespace, name)
|
||||
}
|
||||
else {
|
||||
String::from(name)
|
||||
}
|
||||
}
|
||||
|
||||
fn layout_xml_path(&self, namespace: &str, name: &str) -> String {
|
||||
if namespace.len() > 0 {
|
||||
format!("gui/{}/{}.xml", namespace, name)
|
||||
}
|
||||
else {
|
||||
format!("gui/{}.xml", name)
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_xml_file(&self, path: &str) -> io::Result<rctree::Node<LayoutAbstractNode>> {
|
||||
let source = fs::read_to_string(path)?;
|
||||
let parsed = xmlparser::Tokenizer::from(&source[..]);
|
||||
|
||||
let mut stack: Vec<rctree::Node<LayoutParsedNode>> = Vec::new();
|
||||
let mut current: Option<rctree::Node<LayoutParsedNode>> = None;
|
||||
let mut result: Option<rctree::Node<LayoutParsedNode>> = None;
|
||||
let mut stack: Vec<rctree::Node<LayoutAbstractNode>> = Vec::new();
|
||||
let mut current: Option<rctree::Node<LayoutAbstractNode>> = None;
|
||||
let mut result: Option<rctree::Node<LayoutAbstractNode>> = None;
|
||||
|
||||
let mut parsing_root_tag_with_declarations = true;
|
||||
|
||||
for token in parsed {
|
||||
match token.unwrap() {
|
||||
xmlparser::Token::ElementStart { prefix, local, .. } => {
|
||||
let namespace = String::from(prefix.as_str());
|
||||
let tag_name = String::from(local.as_str());
|
||||
|
||||
let new_parsed_layout_node = LayoutParsedNode::new_tag(namespace, tag_name);
|
||||
let new_parsed_layout_node = LayoutAbstractNode::new_tag(namespace, tag_name);
|
||||
|
||||
let new_node = rctree::Node::new(new_parsed_layout_node);
|
||||
current = Some(new_node);
|
||||
}
|
||||
xmlparser::Token::Attribute { prefix, local, value, .. } => {
|
||||
let colon_prefixed = prefix.start() > 0 && (prefix.start() == prefix.end());
|
||||
let key = if colon_prefixed {
|
||||
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()) };
|
||||
let value = String::from(value.as_str());
|
||||
let attribute = (key, value);
|
||||
let value = value.as_str();
|
||||
|
||||
let attribute = if parsing_root_tag_with_declarations {
|
||||
let parameter_declaration = self.attribute_parser.parse_attribute_declaration(value);
|
||||
Attribute::new(name, parameter_declaration)
|
||||
}
|
||||
else {
|
||||
let parameter_types = self.attribute_parser.parse_attribute_types(value);
|
||||
Attribute::new(name, parameter_types)
|
||||
};
|
||||
|
||||
match &mut current {
|
||||
Some(current_node) => {
|
||||
match &mut *current_node.borrow_mut() {
|
||||
LayoutParsedNode::Tag(tag) => {
|
||||
LayoutAbstractNode::Tag(tag) => {
|
||||
// Add this attribute to the current node that has not yet reached its closing angle bracket
|
||||
tag.add_attribute(attribute);
|
||||
}
|
||||
LayoutParsedNode::Text(_) => {
|
||||
panic!("Error adding attribute to tag when parsing XML layout in file: {}", path);
|
||||
LayoutAbstractNode::Text(text) => {
|
||||
panic!("Unexpected text attribute {} attemping to be added to tag when parsing XML layout in file: {}", text, path);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -70,6 +153,7 @@ impl GuiLayout {
|
|||
// After adding any attributes, we are now a layer deeper which the stack keeps track of
|
||||
let node_to_push = current.take().expect(&format!("Invalid syntax when parsing XML layout in file {}", path)[..]);
|
||||
stack.push(node_to_push);
|
||||
parsing_root_tag_with_declarations = false;
|
||||
}
|
||||
// After adding any attributes, the self-closing tag ends
|
||||
xmlparser::ElementEnd::Empty => {
|
||||
|
|
@ -99,7 +183,7 @@ impl GuiLayout {
|
|||
let text_string = String::from(text.as_str());
|
||||
|
||||
if !text_string.trim().is_empty() {
|
||||
let text_node = LayoutParsedNode::new_text(text_string);
|
||||
let text_node = LayoutAbstractNode::new_text(text_string);
|
||||
let new_node = rctree::Node::new(text_node);
|
||||
parent_node.append(new_node);
|
||||
}
|
||||
|
|
@ -113,19 +197,4 @@ impl GuiLayout {
|
|||
Some(tree) => Ok(tree)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn interpret_abstract_syntax_tree(root: rctree::Node<LayoutParsedNode>) {
|
||||
for node in root.descendants() {
|
||||
println!("{:?}", node);
|
||||
|
||||
match & *node.borrow() {
|
||||
LayoutParsedNode::Tag(tag) => {
|
||||
LayoutAbstractSyntaxNode::new(tag.namespace.clone(), tag.name.clone(), &tag.attributes);
|
||||
}
|
||||
LayoutParsedNode::Text(_) => {}
|
||||
};
|
||||
|
||||
println!();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,150 +0,0 @@
|
|||
use crate::layout_abstract_types::*;
|
||||
use crate::color_palette::ColorPalette;
|
||||
use crate::color::Color;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Attribute {
|
||||
VariableValue(VariableValue),
|
||||
TypeValue(TypeValue),
|
||||
}
|
||||
|
||||
pub fn parse_attribute(input: &str) -> Attribute {
|
||||
// Match variables and typed values that can be in an attribute
|
||||
let regex = regex::Regex::new(
|
||||
r#"(?x)
|
||||
^\s*(\w*)\s*(:)\s*(\()\s*(\w*\s*(?:\|\s*\w*\s*?)*)\s*(\))\s*(=)\s*(\w*)\s*$ | # Parameter ?: (? | ... | ?) = ?
|
||||
^\s*(\{\{)\s*(\w*)\s*(\}\})\s*$ | # Argument {{?}}
|
||||
^\s*(-?\d+)\s*$ | # Integer ?
|
||||
^\s*(-?(?:(?:\d+\.\d*)|(?:\d*\.\d+)))\s*$ | # Decimal ?
|
||||
^\s*(-?(?:(?:\d+(?:\.\d*)?)|(?:\d*(?:\.\d+))))([Pp][Xx])\s*$ | # AbsolutePx ?px
|
||||
^\s*(-?(?:(?:\d+(?:\.\d*)?)|(?:\d*(?:\.\d+))))(%)\s*$ | # Percent ?%
|
||||
^\s*(-?(?:(?:\d+(?:\.\d*)?)|(?:\d*(?:\.\d+))))(@)\s*$ | # PercentRemainder ?@
|
||||
^\s*([Ii][Nn][Nn][Ee][Rr])\s*$ | # Inner inner
|
||||
^\s*([Ww][Ii][Dd][Tt][Hh])\s*$ | # Width width
|
||||
^\s*([Hh][Ee][Ii][Gg][Hh][Tt])\s*$ | # Height height
|
||||
^\s*`(.*)`\s*$ | # TemplateString `? ... {{?}} ...`
|
||||
^\s*(\[)(.*)(\])\s*$ | # Color [?]
|
||||
^\s*([Tt][Rr][Uu][Ee]|[Ff][Aa][Ll][Ss][Ee])\s*$ | # Bool true/false
|
||||
^\s*([Nn][Oo][Nn][Ee])\s*$ # None none
|
||||
"#
|
||||
).unwrap();
|
||||
|
||||
// Match with the regular expression
|
||||
let captures = regex.captures(input).map(|captures|
|
||||
captures
|
||||
.iter()
|
||||
.skip(1)
|
||||
.flat_map(|c| c)
|
||||
.map(|c| c.as_str())
|
||||
.collect::<Vec<_>>()
|
||||
);
|
||||
|
||||
// Match against the captured values as a slice
|
||||
let slices = captures.as_ref().map(|c| c.as_slice());
|
||||
match slices {
|
||||
Some([name, ":", "(", types, ")", "=", default_value]) => {
|
||||
// TODO: Extend to support a list of N types (like (AbsolutePx) (AbsolutePx) (AbsolutePx) (AbsolutePx))
|
||||
let name = String::from(*name);
|
||||
|
||||
let split_types = types.split("|").map(|piece| piece.trim());
|
||||
let valid_types = split_types.map(|type_name|
|
||||
match &type_name.to_ascii_lowercase()[..] {
|
||||
"xml" => TypeName::Xml,
|
||||
"integer" => TypeName::Integer,
|
||||
"decimal" => TypeName::Decimal,
|
||||
"absolutepx" => TypeName::AbsolutePx,
|
||||
"percent" => TypeName::Percent,
|
||||
"percentremainder" => TypeName::PercentRemainder,
|
||||
"inner" => TypeName::Inner,
|
||||
"width" => TypeName::Width,
|
||||
"height" => TypeName::Height,
|
||||
"templatestring" => TypeName::TemplateString,
|
||||
"color" => TypeName::Color,
|
||||
"bool" => TypeName::Bool,
|
||||
"none" => TypeName::None,
|
||||
invalid => panic!("Invalid type `{}` specified in the attribute `{}` when parsing XML layout", invalid, input),
|
||||
}
|
||||
).collect::<Vec<_>>();
|
||||
|
||||
let default = match parse_attribute(default_value) {
|
||||
Attribute::TypeValue(type_value) => type_value,
|
||||
Attribute::VariableValue(variable_value) => panic!("Found the variable value `{:?}` in the attribute `{}` which only allows typed values, when parsing XML layout", variable_value, input),
|
||||
};
|
||||
|
||||
Attribute::VariableValue(VariableValue::Parameter(VariableParameter {name, valid_types, default}))
|
||||
}
|
||||
Some(["{{", name, "}}"]) => {
|
||||
let name = String::from(*name);
|
||||
Attribute::VariableValue(VariableValue::Argument(name))
|
||||
}
|
||||
Some([value]) if regex::Regex::new(r"^\s*(-?\d+)\s*$").unwrap().is_match(value) => {
|
||||
let integer = value.parse::<i64>().expect(&format!("Invalid value `{}` specified in the attribute `{}` when parsing XML layout", value, input)[..]);
|
||||
Attribute::TypeValue(TypeValue::Integer(integer))
|
||||
}
|
||||
Some([value]) if regex::Regex::new(r"^\s*(-?(?:(?:\d+\.\d*)|(?:\d*\.\d+)))\s*$").unwrap().is_match(value) => {
|
||||
let decimal = value.parse::<f64>().expect(&format!("Invalid value `{}` specified in the attribute `{}` when parsing XML layout", value, input)[..]);
|
||||
Attribute::TypeValue(TypeValue::Decimal(decimal))
|
||||
}
|
||||
Some([value, px]) if px.eq_ignore_ascii_case("px") => {
|
||||
let pixels = value.parse::<f32>().expect(&format!("Invalid value `{}` specified in the attribute `{}` when parsing XML layout", value, input)[..]);
|
||||
Attribute::TypeValue(TypeValue::AbsolutePx(pixels))
|
||||
}
|
||||
Some([value, "%"]) => {
|
||||
let percent = value.parse::<f32>().expect(&format!("Invalid value `{}` specified in the attribute `{}` when parsing XML layout", value, input)[..]);
|
||||
Attribute::TypeValue(TypeValue::Percent(percent))
|
||||
}
|
||||
Some([value, "@"]) => {
|
||||
let percent_remainder = value.parse::<f32>().expect(&format!("Invalid value `{}` specified in the attribute `{}` when parsing XML layout", value, input)[..]);
|
||||
Attribute::TypeValue(TypeValue::PercentRemainder(percent_remainder))
|
||||
}
|
||||
Some([inner]) if inner.eq_ignore_ascii_case("inner") => {
|
||||
Attribute::TypeValue(TypeValue::Inner)
|
||||
}
|
||||
Some([width]) if width.eq_ignore_ascii_case("width") => {
|
||||
Attribute::TypeValue(TypeValue::Width)
|
||||
}
|
||||
Some([height]) if height.eq_ignore_ascii_case("height") => {
|
||||
Attribute::TypeValue(TypeValue::Height)
|
||||
}
|
||||
Some(["`", string, "`"]) => {
|
||||
let mut segments = Vec::<TemplateStringSegment>::new();
|
||||
let mut is_template = false;
|
||||
|
||||
let regex = regex::Regex::new(r"\{\{|\}\}").unwrap();
|
||||
for part in regex.split(string) {
|
||||
let segment = match is_template {
|
||||
true => TemplateStringSegment::String(String::from(part)),
|
||||
false => TemplateStringSegment::Argument(VariableArgument { name: String::from(part) }),
|
||||
};
|
||||
segments.push(segment);
|
||||
is_template = !is_template;
|
||||
}
|
||||
|
||||
Attribute::TypeValue(TypeValue::TemplateString(segments))
|
||||
}
|
||||
Some(["[", color_name, "]"]) => {
|
||||
let regex = regex::Regex::new(r"\s*'(.*)'\s*").unwrap();
|
||||
let color = match regex.captures(color_name) {
|
||||
Some(captures) => {
|
||||
let palette_color = captures.get(1).expect(&format!("Invalid palette color name `{}` specified in the attribute `{}` when parsing XML layout", color_name, input)[..]).as_str();
|
||||
ColorPalette::lookup_palette_color(palette_color).into_color_srgb()
|
||||
}
|
||||
None => {
|
||||
let parsed = color_name.parse::<css_color_parser::Color>();
|
||||
let css_color = parsed.expect(&format!("Invalid CSS color name `{}` specified in the attribute `{}` when parsing XML layout", color_name, input)[..]);
|
||||
Color::new(css_color.r as f32 / 255.0, css_color.g as f32 / 255.0, css_color.b as f32 / 255.0, css_color.a as f32 / 255.0)
|
||||
}
|
||||
};
|
||||
|
||||
Attribute::TypeValue(TypeValue::Color(color))
|
||||
}
|
||||
Some([true_or_false]) if true_or_false.eq_ignore_ascii_case("true") || true_or_false.eq_ignore_ascii_case("false") => {
|
||||
let boolean = true_or_false.eq_ignore_ascii_case("true");
|
||||
Attribute::TypeValue(TypeValue::Bool(boolean))
|
||||
}
|
||||
Some([none]) if none.eq_ignore_ascii_case("none") => {
|
||||
Attribute::TypeValue(TypeValue::None)
|
||||
}
|
||||
_ => panic!("Invalid attribute value `{}` when parsing XML layout", input),
|
||||
}
|
||||
}
|
||||
|
|
@ -1,23 +1,53 @@
|
|||
use crate::layout_abstract_attributes::*;
|
||||
|
||||
use crate::layout_abstract_types::*;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct LayoutAbstractSyntaxNode {
|
||||
pub namespace: Option<String>,
|
||||
pub enum LayoutAbstractNode {
|
||||
Tag(LayoutAbstractTag),
|
||||
Text(String),
|
||||
}
|
||||
|
||||
impl LayoutAbstractNode {
|
||||
pub fn new_tag(namespace: String, name: String) -> Self {
|
||||
Self::Tag(LayoutAbstractTag::new(namespace, name))
|
||||
}
|
||||
|
||||
pub fn new_text(text: String) -> Self {
|
||||
Self::Text(text)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct LayoutAbstractTag {
|
||||
pub namespace: String,
|
||||
pub name: String,
|
||||
pub attributes: Vec<Attribute>,
|
||||
}
|
||||
|
||||
impl LayoutAbstractSyntaxNode {
|
||||
pub fn new(namespace: Option<String>, tag: String, attributes: &Vec<(String, String)>) -> Self {
|
||||
for attribute in attributes {
|
||||
let parsed = parse_attribute(&attribute.1[..]);
|
||||
println!("{} : {:?} -> {:?}", attribute.0, attribute.1, parsed);
|
||||
}
|
||||
|
||||
Self {
|
||||
namespace,
|
||||
name: tag,
|
||||
attributes: Vec::new(),
|
||||
}
|
||||
impl LayoutAbstractTag {
|
||||
pub fn new(namespace: String, name: String) -> Self {
|
||||
Self { namespace, name, attributes: Vec::new() }
|
||||
}
|
||||
}
|
||||
|
||||
pub fn add_attribute(&mut self, attribute: Attribute) {
|
||||
self.attributes.push(attribute);
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Attribute {
|
||||
pub name: String,
|
||||
pub value: AttributeValue,
|
||||
}
|
||||
|
||||
impl Attribute {
|
||||
pub fn new(name: String, value: AttributeValue) -> Self {
|
||||
Self { name, value }
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum AttributeValue {
|
||||
VariableParameter(VariableParameter),
|
||||
TypeValue(Vec<TypeValueOrArgument>),
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,19 +1,16 @@
|
|||
use crate::color::Color;
|
||||
|
||||
// Variable types
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum VariableValue {
|
||||
Parameter(VariableParameter),
|
||||
Argument(String),
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct VariableParameter {
|
||||
pub name: String,
|
||||
pub valid_types: Vec<TypeName>,
|
||||
pub default: TypeValue,
|
||||
// pub value: TypeValue,
|
||||
pub type_sequence_options: Vec<Vec<TypeName>>,
|
||||
pub type_sequence_default: Vec<TypeValue>,
|
||||
}
|
||||
|
||||
impl VariableParameter {
|
||||
pub fn new(name: String, valid_types: Vec<Vec<TypeName>>, default: Vec<TypeValue>) -> Self {
|
||||
Self { name, type_sequence_options: valid_types, type_sequence_default: default }
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
|
|
@ -21,11 +18,21 @@ pub struct VariableArgument {
|
|||
pub name: String,
|
||||
}
|
||||
|
||||
// Value types
|
||||
impl VariableArgument {
|
||||
pub fn new(name: String) -> Self {
|
||||
Self { name }
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum TypeValueOrArgument {
|
||||
TypeValue(TypeValue),
|
||||
VariableArgument(VariableArgument),
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum TypeName {
|
||||
Xml,
|
||||
// Xml, // TODO
|
||||
Integer,
|
||||
Decimal,
|
||||
AbsolutePx,
|
||||
|
|
@ -42,7 +49,7 @@ pub enum TypeName {
|
|||
|
||||
#[derive(Debug)]
|
||||
pub enum TypeValue {
|
||||
Xml(()), // TODO
|
||||
// Xml(()), // TODO
|
||||
Integer(i64),
|
||||
Decimal(f64),
|
||||
AbsolutePx(f32),
|
||||
|
|
@ -60,5 +67,5 @@ pub enum TypeValue {
|
|||
#[derive(Debug)]
|
||||
pub enum TemplateStringSegment {
|
||||
String(String),
|
||||
Argument(VariableArgument),
|
||||
Argument(TypeValueOrArgument),
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,230 @@
|
|||
use crate::layout_abstract_types::*;
|
||||
use crate::layout_abstract_syntax::*;
|
||||
use crate::color_palette::ColorPalette;
|
||||
use crate::color::Color;
|
||||
|
||||
pub struct AttributeParser {
|
||||
capture_attribute_declaration_parameter_regex: regex::Regex,
|
||||
capture_attribute_type_sequences_regex: regex::Regex,
|
||||
match_integer_regex: regex::Regex,
|
||||
match_decimal_regex: regex::Regex,
|
||||
split_by_string_templates_regex: regex::Regex,
|
||||
capture_color_name_in_palette_regex: regex::Regex,
|
||||
}
|
||||
|
||||
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*$"
|
||||
).unwrap();
|
||||
|
||||
let capture_attribute_type_sequences_regex: regex::Regex = regex::Regex::new(concat!(
|
||||
// Argument: {{?}}
|
||||
r#"^\s*(\{\{)\s*(\w*)\s*(\}\})\s*$|"#,
|
||||
// Integer: ?
|
||||
r#"^\s*(-?\d+)\s*$|"#,
|
||||
// Decimal: ?
|
||||
r#"^\s*(-?(?:(?:\d+\.\d*)|(?:\d*\.\d+)))\s*$|"#,
|
||||
// AbsolutePx: px
|
||||
r#"^\s*(-?(?:(?:\d+(?:\.\d*)?)|(?:\d*(?:\.\d+))))([Pp][Xx])\s*$|"#,
|
||||
// Percent: ?%
|
||||
r#"^\s*(-?(?:(?:\d+(?:\.\d*)?)|(?:\d*(?:\.\d+))))(%)\s*$|"#,
|
||||
// PercentRemainder: ?@
|
||||
r#"^\s*(-?(?:(?:\d+(?:\.\d*)?)|(?:\d*(?:\.\d+))))(@)\s*$|"#,
|
||||
// Inner: inner
|
||||
r#"^\s*([Ii][Nn][Nn][Ee][Rr])\s*$|"#,
|
||||
// Width: width
|
||||
r#"^\s*([Ww][Ii][Dd][Tt][Hh])\s*$|"#,
|
||||
// Height: height
|
||||
r#"^\s*([Hh][Ee][Ii][Gg][Hh][Tt])\s*$|"#,
|
||||
// TemplateString: `? ... {{?}} ...`
|
||||
r#"^\s*(`)(.*)(`)\s*$|"#,
|
||||
// Color: [?]
|
||||
r#"^\s*(\[)(.*)(\])\s*$|"#,
|
||||
// Bool: true/false
|
||||
r#"^\s*([Tt][Rr][Uu][Ee]|[Ff][Aa][Ll][Ss][Ee])\s*$|"#,
|
||||
// None: none
|
||||
r#"^\s*([Nn][Oo][Nn][Ee])\s*$"#,
|
||||
)).unwrap();
|
||||
|
||||
let match_integer_regex = regex::Regex::new(r"^\s*(-?\d+)\s*$").unwrap();
|
||||
|
||||
let match_decimal_regex = regex::Regex::new(r"^\s*(-?(?:(?:\d+\.\d*)|(?:\d*\.\d+)))\s*$").unwrap();
|
||||
|
||||
let split_by_string_templates_regex = regex::Regex::new(r"\{\{|\}\}").unwrap();
|
||||
|
||||
let capture_color_name_in_palette_regex = regex::Regex::new(r"\s*'(.*)'\s*").unwrap();
|
||||
|
||||
Self {
|
||||
capture_attribute_declaration_parameter_regex,
|
||||
capture_attribute_type_sequences_regex,
|
||||
match_integer_regex,
|
||||
match_decimal_regex,
|
||||
split_by_string_templates_regex,
|
||||
capture_color_name_in_palette_regex,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn parse_attribute_types(&self, input: &str) -> AttributeValue {
|
||||
let attribute_types = input.split(",").map(|piece| piece.trim()).collect::<Vec<&str>>();
|
||||
let list = attribute_types.iter().map(|attribute_type| self.parse_attribute_type(attribute_type)).collect::<Vec<TypeValueOrArgument>>();
|
||||
AttributeValue::TypeValue(list)
|
||||
}
|
||||
|
||||
pub fn parse_attribute_type(&self, attribute_type: &str) -> TypeValueOrArgument {
|
||||
// Match with the regular expression
|
||||
let captures = self.capture_attribute_type_sequences_regex.captures(attribute_type).map(|captures|
|
||||
captures.iter().skip(1).flat_map(|c| c).map(|c| c.as_str()).collect::<Vec<_>>()
|
||||
);
|
||||
|
||||
// Match against the captured values as a list of tokens
|
||||
let tokens = captures.as_ref().map(|c| c.as_slice());
|
||||
match tokens {
|
||||
// Argument: {{?}}
|
||||
Some(["{{", name, "}}"]) => {
|
||||
let name = String::from(*name);
|
||||
TypeValueOrArgument::VariableArgument(VariableArgument::new(name))
|
||||
}
|
||||
// Integer: ?
|
||||
Some([value]) if self.match_integer_regex.is_match(value) => {
|
||||
let integer = value.parse::<i64>().expect(&format!("Invalid value `{}` specified in the attribute type `{}` when parsing XML layout", value, attribute_type)[..]);
|
||||
TypeValueOrArgument::TypeValue(TypeValue::Integer(integer))
|
||||
}
|
||||
// Decimal: ?
|
||||
Some([value]) if self.match_decimal_regex.is_match(value) => {
|
||||
let decimal = value.parse::<f64>().expect(&format!("Invalid value `{}` specified in the attribute type `{}` when parsing XML layout", value, attribute_type)[..]);
|
||||
TypeValueOrArgument::TypeValue(TypeValue::Decimal(decimal))
|
||||
}
|
||||
// AbsolutePx: px
|
||||
Some([value, px]) if px.eq_ignore_ascii_case("px") => {
|
||||
let pixels = value.parse::<f32>().expect(&format!("Invalid value `{}` specified in the attribute type`{}` when parsing XML layout", value, attribute_type)[..]);
|
||||
TypeValueOrArgument::TypeValue(TypeValue::AbsolutePx(pixels))
|
||||
}
|
||||
// Percent: ?%
|
||||
Some([value, "%"]) => {
|
||||
let percent = value.parse::<f32>().expect(&format!("Invalid value `{}` specified in the attribute type `{}` when parsing XML layout", value, attribute_type)[..]);
|
||||
TypeValueOrArgument::TypeValue(TypeValue::Percent(percent))
|
||||
}
|
||||
// PercentRemainder: ?@
|
||||
Some([value, "@"]) => {
|
||||
let percent_remainder = value.parse::<f32>().expect(&format!("Invalid value `{}` specified in the attribute type `{}` when parsing XML layout", value, attribute_type)[..]);
|
||||
TypeValueOrArgument::TypeValue(TypeValue::PercentRemainder(percent_remainder))
|
||||
}
|
||||
// Inner: inner
|
||||
Some([inner]) if inner.eq_ignore_ascii_case("inner") => {
|
||||
TypeValueOrArgument::TypeValue(TypeValue::Inner)
|
||||
}
|
||||
// Width: width
|
||||
Some([width]) if width.eq_ignore_ascii_case("width") => {
|
||||
TypeValueOrArgument::TypeValue(TypeValue::Width)
|
||||
}
|
||||
// Height: height
|
||||
Some([height]) if height.eq_ignore_ascii_case("height") => {
|
||||
TypeValueOrArgument::TypeValue(TypeValue::Height)
|
||||
}
|
||||
// TemplateString: `? ... {{?}} ...`
|
||||
Some(["`", string, "`"]) => {
|
||||
let mut segments = Vec::<TemplateStringSegment>::new();
|
||||
let mut is_template = false;
|
||||
|
||||
for part in self.split_by_string_templates_regex.split(string) {
|
||||
let segment = match is_template {
|
||||
true => TemplateStringSegment::String(String::from(part)),
|
||||
false => TemplateStringSegment::Argument(TypeValueOrArgument::VariableArgument(VariableArgument::new(String::from(part)))),
|
||||
};
|
||||
segments.push(segment);
|
||||
is_template = !is_template;
|
||||
}
|
||||
|
||||
TypeValueOrArgument::TypeValue(TypeValue::TemplateString(segments))
|
||||
}
|
||||
// Color: [?]
|
||||
Some(["[", color_name, "]"]) => {
|
||||
let color = match self.capture_color_name_in_palette_regex.captures(color_name) {
|
||||
Some(captures) => {
|
||||
let palette_color = captures.get(1).expect(&format!("Invalid palette color name `{}` specified in the attribute type `{}` when parsing XML layout", color_name, attribute_type)[..]).as_str();
|
||||
ColorPalette::lookup_palette_color(palette_color).into_color_srgb()
|
||||
}
|
||||
None => {
|
||||
let parsed = color_name.parse::<css_color_parser::Color>();
|
||||
let css_color = parsed.expect(&format!("Invalid CSS color name `{}` specified in the attribute type `{}` when parsing XML layout", color_name, attribute_type)[..]);
|
||||
Color::new(css_color.r as f32 / 255.0, css_color.g as f32 / 255.0, css_color.b as f32 / 255.0, css_color.a as f32 / 255.0)
|
||||
}
|
||||
};
|
||||
|
||||
TypeValueOrArgument::TypeValue(TypeValue::Color(color))
|
||||
}
|
||||
// Bool: true/false
|
||||
Some([true_or_false]) if true_or_false.eq_ignore_ascii_case("true") || true_or_false.eq_ignore_ascii_case("false") => {
|
||||
let boolean = true_or_false.eq_ignore_ascii_case("true");
|
||||
TypeValueOrArgument::TypeValue(TypeValue::Bool(boolean))
|
||||
}
|
||||
// None: none
|
||||
Some([none]) if none.eq_ignore_ascii_case("none") => {
|
||||
TypeValueOrArgument::TypeValue(TypeValue::None)
|
||||
}
|
||||
// Unrecognized type pattern
|
||||
_ => panic!("Invalid attribute type `{}` when parsing XML layout", attribute_type),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn parse_attribute_declaration(&self, attribute_declaration: &str) -> AttributeValue {
|
||||
// Match with the regular expression
|
||||
let captures = self.capture_attribute_declaration_parameter_regex.captures(attribute_declaration).map(|captures|
|
||||
captures.iter().skip(1).flat_map(|c| c).map(|c| c.as_str()).collect::<Vec<_>>()
|
||||
);
|
||||
|
||||
// Match against the captured values as a list of tokens
|
||||
let tokens = captures.as_ref().map(|c| c.as_slice());
|
||||
match tokens {
|
||||
// Parameter: ?: (?, ... | ...) = ?
|
||||
Some([name, ":", "(", raw_types_list, ")", "=", default_value]) => {
|
||||
// Variable name bound in the parameter
|
||||
let name = String::from(*name);
|
||||
|
||||
// Split the type sequences up into a list of options separated by vertical bars
|
||||
let type_sequence_options = String::from(*raw_types_list).split("|").map(|group| {
|
||||
// Split each type sequence into individual types separated by commas
|
||||
group.split(",").map(|individual_type| {
|
||||
// Remove any whitespace around the type
|
||||
let individual_type = individual_type.trim();
|
||||
|
||||
// Return the case-insensitive TypeName enum for the individual type
|
||||
match &individual_type.to_ascii_lowercase()[..] {
|
||||
// "xml" => TypeName::Xml, // TODO
|
||||
"integer" => TypeName::Integer,
|
||||
"decimal" => TypeName::Decimal,
|
||||
"absolutepx" => TypeName::AbsolutePx,
|
||||
"percent" => TypeName::Percent,
|
||||
"percentremainder" => TypeName::PercentRemainder,
|
||||
"inner" => TypeName::Inner,
|
||||
"width" => TypeName::Width,
|
||||
"height" => TypeName::Height,
|
||||
"templatestring" => TypeName::TemplateString,
|
||||
"color" => TypeName::Color,
|
||||
"bool" => TypeName::Bool,
|
||||
"none" => TypeName::None,
|
||||
_ => panic!("Invalid type `{}` specified in the attribute type `{}` when parsing XML layout", individual_type, attribute_declaration),
|
||||
}
|
||||
}).collect::<Vec<TypeName>>()
|
||||
}).collect::<Vec<Vec<TypeName>>>();
|
||||
|
||||
// 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) {
|
||||
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", variable_value, attribute_declaration);
|
||||
}
|
||||
}
|
||||
).collect::<Vec<TypeValue>>();
|
||||
|
||||
// Return the parameter
|
||||
AttributeValue::VariableParameter(VariableParameter::new(name, type_sequence_options, default_type_sequence))
|
||||
}
|
||||
// Unrecognized type pattern
|
||||
_ => panic!("Invalid attribute attribute declaration `{}` when parsing XML layout", attribute_declaration),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,38 +0,0 @@
|
|||
#[derive(Debug)]
|
||||
pub enum LayoutParsedNode {
|
||||
Tag(LayoutParsedTag),
|
||||
Text(String),
|
||||
}
|
||||
|
||||
impl LayoutParsedNode {
|
||||
pub fn new_tag(namespace: String, tag: String) -> Self {
|
||||
Self::Tag(LayoutParsedTag::new(namespace, tag))
|
||||
}
|
||||
|
||||
pub fn new_text(text: String) -> Self {
|
||||
Self::Text(text)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct LayoutParsedTag {
|
||||
pub namespace: Option<String>,
|
||||
pub name: String,
|
||||
pub attributes: Vec<(String, String)>,
|
||||
}
|
||||
|
||||
impl LayoutParsedTag {
|
||||
pub fn new(namespace: String, name: String) -> Self {
|
||||
let namespace = if namespace.is_empty() { None } else { Some(namespace) };
|
||||
|
||||
Self {
|
||||
namespace,
|
||||
name,
|
||||
attributes: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn add_attribute(&mut self, attribute: (String, String)) {
|
||||
self.attributes.push(attribute);
|
||||
}
|
||||
}
|
||||
|
|
@ -10,10 +10,9 @@ mod gui_node;
|
|||
mod gui_attributes;
|
||||
mod window_events;
|
||||
mod gui_layout;
|
||||
mod layout_parsed_node;
|
||||
mod layout_abstract_types;
|
||||
mod layout_abstract_attributes;
|
||||
mod layout_abstract_syntax;
|
||||
mod layout_attribute_parser;
|
||||
|
||||
use application::Application;
|
||||
use winit::event_loop::EventLoop;
|
||||
|
|
|
|||
Loading…
Reference in New Issue