mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-19 02:48:12 +08:00
Implement abstract syntax tree parsing of XML layout
This commit is contained in:
@@ -72,7 +72,7 @@ impl Application {
|
||||
let gui_rect_pipeline = Pipeline::new(&device, swap_chain_descriptor.format, vec![], &mut shader_cache, ("shaders/shader.vert", "shaders/shader.frag"));
|
||||
pipeline_cache.set("gui_rect", gui_rect_pipeline);
|
||||
|
||||
let gui_root_data = GuiNode::new(swap_chain_descriptor.width, swap_chain_descriptor.height, ColorPalette::get_color_srgb(ColorPalette::Accent));
|
||||
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();
|
||||
|
||||
@@ -23,7 +23,7 @@ pub enum ColorPalette {
|
||||
|
||||
impl ColorPalette {
|
||||
#[allow(dead_code)]
|
||||
pub fn get_color_srgb(self) -> Color {
|
||||
pub fn into_color_srgb(&self) -> Color {
|
||||
let grayscale = match self {
|
||||
ColorPalette::Black => 0 * 17, // #000000
|
||||
ColorPalette::NearBlack => 1 * 17, // #111111
|
||||
@@ -58,11 +58,34 @@ impl ColorPalette {
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn get_color_linear(self) -> Color {
|
||||
let standard_rgb = ColorPalette::get_color_srgb(self);
|
||||
pub fn into_color_linear(&self) -> Color {
|
||||
let standard_rgb = ColorPalette::into_color_srgb(self);
|
||||
|
||||
let linear = palette::Srgb::new(standard_rgb.r, standard_rgb.g, standard_rgb.b).into_linear();
|
||||
|
||||
Color::new(linear.red, linear.green, linear.blue, standard_rgb.a)
|
||||
}
|
||||
|
||||
pub fn lookup_palette_color(name_in_palette: &str) -> ColorPalette {
|
||||
match &name_in_palette.to_ascii_lowercase()[..] {
|
||||
"black" => ColorPalette::Black,
|
||||
"nearblack" => ColorPalette::NearBlack,
|
||||
"mildblack" => ColorPalette::MildBlack,
|
||||
"darkgray" => ColorPalette::DarkGray,
|
||||
"dimgray" => ColorPalette::DimGray,
|
||||
"dullgray" => ColorPalette::DullGray,
|
||||
"lowergray" => ColorPalette::LowerGray,
|
||||
"middlegray" => ColorPalette::MiddleGray,
|
||||
"uppergray" => ColorPalette::UpperGray,
|
||||
"palegray" => ColorPalette::PaleGray,
|
||||
"softgray" => ColorPalette::SoftGray,
|
||||
"lightgray" => ColorPalette::LightGray,
|
||||
"brightgray" => ColorPalette::BrightGray,
|
||||
"mildwhite" => ColorPalette::MildWhite,
|
||||
"nearwhite" => ColorPalette::NearWhite,
|
||||
"white" => ColorPalette::White,
|
||||
"accent" => ColorPalette::Accent,
|
||||
_ => panic!("Invalid color lookup of `{}` from the color palette", name_in_palette),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
use std::fs;
|
||||
use std::io;
|
||||
use crate::layout_parsed_node::*;
|
||||
use crate::layout_abstract_syntax::*;
|
||||
|
||||
pub struct GuiLayout {
|
||||
|
||||
@@ -10,6 +11,7 @@ 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 {}
|
||||
}
|
||||
|
||||
@@ -115,6 +117,15 @@ impl GuiLayout {
|
||||
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!();
|
||||
}
|
||||
}
|
||||
}
|
||||
150
src/layout_abstract_attributes.rs
Normal file
150
src/layout_abstract_attributes.rs
Normal file
@@ -0,0 +1,150 @@
|
||||
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),
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
use crate::layout_abstract_attributes::*;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct LayoutAbstractSyntaxNode {
|
||||
pub namespace: Option<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(),
|
||||
}
|
||||
}
|
||||
}
|
||||
64
src/layout_abstract_types.rs
Normal file
64
src/layout_abstract_types.rs
Normal file
@@ -0,0 +1,64 @@
|
||||
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,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct VariableArgument {
|
||||
pub name: String,
|
||||
}
|
||||
|
||||
// Value types
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum TypeName {
|
||||
Xml,
|
||||
Integer,
|
||||
Decimal,
|
||||
AbsolutePx,
|
||||
Percent,
|
||||
PercentRemainder,
|
||||
Inner,
|
||||
Width,
|
||||
Height,
|
||||
TemplateString,
|
||||
Color,
|
||||
Bool,
|
||||
None,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum TypeValue {
|
||||
Xml(()), // TODO
|
||||
Integer(i64),
|
||||
Decimal(f64),
|
||||
AbsolutePx(f32),
|
||||
Percent(f32),
|
||||
PercentRemainder(f32),
|
||||
Inner,
|
||||
Width,
|
||||
Height,
|
||||
TemplateString(Vec<TemplateStringSegment>),
|
||||
Color(Color),
|
||||
Bool(bool),
|
||||
None,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum TemplateStringSegment {
|
||||
String(String),
|
||||
Argument(VariableArgument),
|
||||
}
|
||||
@@ -17,17 +17,17 @@ impl LayoutParsedNode {
|
||||
#[derive(Debug)]
|
||||
pub struct LayoutParsedTag {
|
||||
pub namespace: Option<String>,
|
||||
pub tag: String,
|
||||
pub name: String,
|
||||
pub attributes: Vec<(String, String)>,
|
||||
}
|
||||
|
||||
impl LayoutParsedTag {
|
||||
pub fn new(namespace: String, tag: String) -> Self {
|
||||
pub fn new(namespace: String, name: String) -> Self {
|
||||
let namespace = if namespace.is_empty() { None } else { Some(namespace) };
|
||||
|
||||
Self {
|
||||
namespace,
|
||||
tag,
|
||||
name,
|
||||
attributes: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,6 +11,9 @@ 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;
|
||||
|
||||
use application::Application;
|
||||
use winit::event_loop::EventLoop;
|
||||
|
||||
Reference in New Issue
Block a user