wgui: parser: template injection support
This commit is contained in:
@@ -65,15 +65,20 @@ fn add_child_internal(
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Layout {
|
impl Layout {
|
||||||
|
pub fn get_node(&self, widget_id: WidgetID) -> anyhow::Result<taffy::NodeId> {
|
||||||
|
let Some(node) = self.widget_node_map.get(&widget_id).cloned() else {
|
||||||
|
anyhow::bail!("invalid parent widget");
|
||||||
|
};
|
||||||
|
Ok(node)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn add_child(
|
pub fn add_child(
|
||||||
&mut self,
|
&mut self,
|
||||||
parent_widget_id: WidgetID,
|
parent_widget_id: WidgetID,
|
||||||
widget: WidgetState,
|
widget: WidgetState,
|
||||||
style: taffy::Style,
|
style: taffy::Style,
|
||||||
) -> anyhow::Result<(WidgetID, taffy::NodeId)> {
|
) -> anyhow::Result<(WidgetID, taffy::NodeId)> {
|
||||||
let Some(parent_node) = self.widget_node_map.get(&parent_widget_id).cloned() else {
|
let parent_node = self.get_node(parent_widget_id)?;
|
||||||
anyhow::bail!("invalid parent widget");
|
|
||||||
};
|
|
||||||
|
|
||||||
self.needs_redraw = true;
|
self.needs_redraw = true;
|
||||||
|
|
||||||
|
|||||||
@@ -15,7 +15,6 @@ use crate::{
|
|||||||
};
|
};
|
||||||
use ouroboros::self_referencing;
|
use ouroboros::self_referencing;
|
||||||
use std::{
|
use std::{
|
||||||
cell::RefCell,
|
|
||||||
collections::HashMap,
|
collections::HashMap,
|
||||||
path::{Path, PathBuf},
|
path::{Path, PathBuf},
|
||||||
rc::Rc,
|
rc::Rc,
|
||||||
@@ -32,20 +31,21 @@ struct XmlDocument {
|
|||||||
doc: roxmltree::Document<'this>,
|
doc: roxmltree::Document<'this>,
|
||||||
}
|
}
|
||||||
|
|
||||||
struct Template {
|
pub struct Template {
|
||||||
node_document: Rc<XmlDocument>,
|
node_document: Rc<XmlDocument>,
|
||||||
node: roxmltree::NodeId, // belongs to node_document which could be included in another file
|
node: roxmltree::NodeId, // belongs to node_document which could be included in another file
|
||||||
}
|
}
|
||||||
|
|
||||||
struct ParserFile<'a> {
|
struct ParserFile {
|
||||||
path: PathBuf,
|
path: PathBuf,
|
||||||
document: Rc<XmlDocument>,
|
document: Rc<XmlDocument>,
|
||||||
ctx: Rc<RefCell<ParserContext<'a>>>,
|
|
||||||
template_parameters: HashMap<Rc<str>, Rc<str>>,
|
template_parameters: HashMap<Rc<str>, Rc<str>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct ParserResult {
|
pub struct ParserResult {
|
||||||
ids: HashMap<Rc<str>, WidgetID>,
|
pub ids: HashMap<Rc<str>, WidgetID>,
|
||||||
|
pub templates: HashMap<Rc<str>, Rc<Template>>,
|
||||||
|
pub path: PathBuf,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ParserResult {
|
impl ParserResult {
|
||||||
@@ -55,6 +55,43 @@ impl ParserResult {
|
|||||||
None => anyhow::bail!("Widget by ID \"{}\" doesn't exist", id),
|
None => anyhow::bail!("Widget by ID \"{}\" doesn't exist", id),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn process_template(
|
||||||
|
&self,
|
||||||
|
template_name: &str,
|
||||||
|
layout: &mut Layout,
|
||||||
|
widget_id: WidgetID,
|
||||||
|
template_parameters: HashMap<Rc<str>, Rc<str>>,
|
||||||
|
) -> anyhow::Result<()> {
|
||||||
|
let Some(template) = self.templates.get(template_name) else {
|
||||||
|
anyhow::bail!("no template named \"{}\" found", template_name);
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut ctx = create_default_context(layout);
|
||||||
|
|
||||||
|
let file = ParserFile {
|
||||||
|
document: template.node_document.clone(),
|
||||||
|
path: self.path.clone(),
|
||||||
|
template_parameters: template_parameters.clone(), // FIXME: prevent copying
|
||||||
|
};
|
||||||
|
|
||||||
|
let node = template
|
||||||
|
.node_document
|
||||||
|
.borrow_doc()
|
||||||
|
.get_node(template.node)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
parse_widget_other_internal(
|
||||||
|
template.clone(),
|
||||||
|
template_parameters,
|
||||||
|
&file,
|
||||||
|
&mut ctx,
|
||||||
|
node,
|
||||||
|
widget_id,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct ParserContext<'a> {
|
struct ParserContext<'a> {
|
||||||
@@ -166,22 +203,15 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_widget_other<'a>(
|
fn parse_widget_other_internal<'a>(
|
||||||
xml_tag_name: &str,
|
template: Rc<Template>,
|
||||||
|
template_parameters: HashMap<Rc<str>, Rc<str>>,
|
||||||
file: &'a ParserFile,
|
file: &'a ParserFile,
|
||||||
ctx: &mut ParserContext,
|
ctx: &mut ParserContext,
|
||||||
node: roxmltree::Node<'a, 'a>,
|
node: roxmltree::Node<'a, 'a>,
|
||||||
parent_id: WidgetID,
|
parent_id: WidgetID,
|
||||||
) -> anyhow::Result<()> {
|
) -> anyhow::Result<()> {
|
||||||
let Some(template) = ctx.templates.get(xml_tag_name) else {
|
|
||||||
log::error!("Undefined tag named \"{}\"", xml_tag_name);
|
|
||||||
return Ok(()); // not critical
|
|
||||||
};
|
|
||||||
|
|
||||||
let template_parameters: HashMap<Rc<str>, Rc<str>> = iter_attribs(file, ctx, &node).collect();
|
|
||||||
|
|
||||||
let template_file = ParserFile {
|
let template_file = ParserFile {
|
||||||
ctx: file.ctx.clone(),
|
|
||||||
document: template.node_document.clone(),
|
document: template.node_document.clone(),
|
||||||
path: file.path.clone(),
|
path: file.path.clone(),
|
||||||
template_parameters,
|
template_parameters,
|
||||||
@@ -199,6 +229,30 @@ fn parse_widget_other<'a>(
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn parse_widget_other<'a>(
|
||||||
|
xml_tag_name: &str,
|
||||||
|
file: &'a ParserFile,
|
||||||
|
ctx: &mut ParserContext,
|
||||||
|
node: roxmltree::Node<'a, 'a>,
|
||||||
|
parent_id: WidgetID,
|
||||||
|
) -> anyhow::Result<()> {
|
||||||
|
let Some(template) = ctx.templates.get(xml_tag_name) else {
|
||||||
|
log::error!("Undefined tag named \"{}\"", xml_tag_name);
|
||||||
|
return Ok(()); // not critical
|
||||||
|
};
|
||||||
|
|
||||||
|
let template_parameters: HashMap<Rc<str>, Rc<str>> = iter_attribs(file, ctx, &node).collect();
|
||||||
|
|
||||||
|
parse_widget_other_internal(
|
||||||
|
template.clone(),
|
||||||
|
template_parameters,
|
||||||
|
file,
|
||||||
|
ctx,
|
||||||
|
node,
|
||||||
|
parent_id,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
fn parse_tag_include<'a>(
|
fn parse_tag_include<'a>(
|
||||||
file: &ParserFile,
|
file: &ParserFile,
|
||||||
ctx: &mut ParserContext,
|
ctx: &mut ParserContext,
|
||||||
@@ -214,7 +268,7 @@ fn parse_tag_include<'a>(
|
|||||||
let mut new_path = file.path.parent().unwrap_or(Path::new("/")).to_path_buf();
|
let mut new_path = file.path.parent().unwrap_or(Path::new("/")).to_path_buf();
|
||||||
new_path.push(value);
|
new_path.push(value);
|
||||||
|
|
||||||
let (new_file, node_layout) = get_doc_from_path(file.ctx.clone(), ctx, &new_path)?;
|
let (new_file, node_layout) = get_doc_from_path(ctx, &new_path)?;
|
||||||
parse_document_root(new_file, ctx, parent_id, node_layout)?;
|
parse_document_root(new_file, ctx, parent_id, node_layout)?;
|
||||||
|
|
||||||
return Ok(());
|
return Ok(());
|
||||||
@@ -421,6 +475,15 @@ fn parse_children<'a>(
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn create_default_context(layout: &mut Layout) -> ParserContext<'_> {
|
||||||
|
ParserContext {
|
||||||
|
layout,
|
||||||
|
ids: Default::default(),
|
||||||
|
var_map: Default::default(),
|
||||||
|
templates: Default::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn parse_from_assets(
|
pub fn parse_from_assets(
|
||||||
layout: &mut Layout,
|
layout: &mut Layout,
|
||||||
parent_id: WidgetID,
|
parent_id: WidgetID,
|
||||||
@@ -428,21 +491,16 @@ pub fn parse_from_assets(
|
|||||||
) -> anyhow::Result<ParserResult> {
|
) -> anyhow::Result<ParserResult> {
|
||||||
let path = PathBuf::from(path);
|
let path = PathBuf::from(path);
|
||||||
|
|
||||||
let ctx_rc = Rc::new(RefCell::new(ParserContext {
|
let mut ctx = create_default_context(layout);
|
||||||
layout,
|
|
||||||
ids: Default::default(),
|
|
||||||
var_map: Default::default(),
|
|
||||||
templates: Default::default(),
|
|
||||||
}));
|
|
||||||
|
|
||||||
let mut ctx = ctx_rc.borrow_mut();
|
let (file, node_layout) = get_doc_from_path(&mut ctx, &path)?;
|
||||||
|
|
||||||
let (file, node_layout) = get_doc_from_path(ctx_rc.clone(), &mut ctx, &path)?;
|
|
||||||
parse_document_root(file, &mut ctx, parent_id, node_layout)?;
|
parse_document_root(file, &mut ctx, parent_id, node_layout)?;
|
||||||
|
|
||||||
// move everything essential to the result
|
// move everything essential to the result
|
||||||
let result = ParserResult {
|
let result = ParserResult {
|
||||||
ids: std::mem::take(&mut ctx.ids),
|
ids: std::mem::take(&mut ctx.ids),
|
||||||
|
templates: std::mem::take(&mut ctx.templates),
|
||||||
|
path,
|
||||||
};
|
};
|
||||||
|
|
||||||
drop(ctx);
|
drop(ctx);
|
||||||
@@ -465,11 +523,10 @@ fn assets_path_to_xml(assets: &mut Box<dyn AssetProvider>, path: &Path) -> anyho
|
|||||||
Ok(String::from_utf8(data)?)
|
Ok(String::from_utf8(data)?)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_doc_from_path<'a>(
|
fn get_doc_from_path(
|
||||||
ctx_rc: Rc<RefCell<ParserContext<'a>>>,
|
|
||||||
ctx: &mut ParserContext,
|
ctx: &mut ParserContext,
|
||||||
path: &Path,
|
path: &Path,
|
||||||
) -> anyhow::Result<(ParserFile<'a>, roxmltree::NodeId)> {
|
) -> anyhow::Result<(ParserFile, roxmltree::NodeId)> {
|
||||||
let xml = assets_path_to_xml(&mut ctx.layout.assets, path)?;
|
let xml = assets_path_to_xml(&mut ctx.layout.assets, path)?;
|
||||||
let document = Rc::new(XmlDocument::new(xml, |xml| {
|
let document = Rc::new(XmlDocument::new(xml, |xml| {
|
||||||
let opt = roxmltree::ParsingOptions {
|
let opt = roxmltree::ParsingOptions {
|
||||||
@@ -483,7 +540,6 @@ fn get_doc_from_path<'a>(
|
|||||||
let tag_layout = require_tag_by_name(&root, "layout")?;
|
let tag_layout = require_tag_by_name(&root, "layout")?;
|
||||||
|
|
||||||
let file = ParserFile {
|
let file = ParserFile {
|
||||||
ctx: ctx_rc.clone(),
|
|
||||||
path: PathBuf::from(path),
|
path: PathBuf::from(path),
|
||||||
document: document.clone(),
|
document: document.clone(),
|
||||||
template_parameters: Default::default(),
|
template_parameters: Default::default(),
|
||||||
|
|||||||
@@ -1,7 +1,23 @@
|
|||||||
<layout>
|
<layout>
|
||||||
|
<!-- Template of a single keyboard key. -->
|
||||||
<template name="Key">
|
<template name="Key">
|
||||||
<rectangle color="#FF0000" width="32" height="32" margin="4">
|
<rectangle
|
||||||
|
border_color="#0044CC"
|
||||||
|
border="2"
|
||||||
|
round="8"
|
||||||
|
color="#000A1C"
|
||||||
|
color2="#000002"
|
||||||
|
gradient="vertical"
|
||||||
|
width="${width}"
|
||||||
|
height="${height}"
|
||||||
|
min_width="${width}"
|
||||||
|
min_height="${height}"
|
||||||
|
max_width="${width}"
|
||||||
|
max_height="${height}"
|
||||||
|
margin="4"
|
||||||
|
align_items="center"
|
||||||
|
justify_content="center">
|
||||||
|
<label color="#FFFFFF" text="${text}" size="24" />
|
||||||
</rectangle>
|
</rectangle>
|
||||||
</template>
|
</template>
|
||||||
</layout>
|
</layout>
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
use std::{
|
use std::{
|
||||||
collections::HashMap,
|
collections::HashMap,
|
||||||
process::Child,
|
process::Child,
|
||||||
|
rc::Rc,
|
||||||
str::FromStr,
|
str::FromStr,
|
||||||
sync::{Arc, LazyLock},
|
sync::{Arc, LazyLock},
|
||||||
};
|
};
|
||||||
@@ -125,7 +126,7 @@ where
|
|||||||
keymap = None;
|
keymap = None;
|
||||||
}
|
}
|
||||||
|
|
||||||
let (key_layout, _state) =
|
let (_gui_layout_key, gui_state_key) =
|
||||||
wgui::parser::new_layout_from_assets(Box::new(gui::asset::GuiAsset {}), "key.xml")?;
|
wgui::parser::new_layout_from_assets(Box::new(gui::asset::GuiAsset {}), "key.xml")?;
|
||||||
|
|
||||||
for row in 0..LAYOUT.key_sizes.len() {
|
for row in 0..LAYOUT.key_sizes.len() {
|
||||||
@@ -139,9 +140,13 @@ where
|
|||||||
)?;
|
)?;
|
||||||
|
|
||||||
for col in 0..LAYOUT.key_sizes[row].len() {
|
for col in 0..LAYOUT.key_sizes[row].len() {
|
||||||
let my_size = LAYOUT.key_sizes[row][col];
|
let my_size_f32 = LAYOUT.key_sizes[row][col];
|
||||||
let my_size = taffy::Size {
|
|
||||||
width: length(PIXELS_PER_UNIT * my_size),
|
let key_width = PIXELS_PER_UNIT * my_size_f32;
|
||||||
|
let key_height = PIXELS_PER_UNIT;
|
||||||
|
|
||||||
|
let taffy_size = taffy::Size {
|
||||||
|
width: length(key_width),
|
||||||
height: length(PIXELS_PER_UNIT),
|
height: length(PIXELS_PER_UNIT),
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -220,22 +225,17 @@ where
|
|||||||
if label.is_empty() {
|
if label.is_empty() {
|
||||||
label = LAYOUT.label_for_key(key);
|
label = LAYOUT.label_for_key(key);
|
||||||
}
|
}
|
||||||
let _ = panel.layout.add_child(
|
|
||||||
div,
|
// todo: make this easier to maintain somehow
|
||||||
Rectangle::create(RectangleParams {
|
let mut params = HashMap::new();
|
||||||
border_color: parse_color_hex("#dddddd").unwrap(),
|
params.insert(Rc::from("width"), Rc::from(key_width.to_string()));
|
||||||
border: 2.0,
|
params.insert(Rc::from("height"), Rc::from(key_height.to_string()));
|
||||||
round: WLength::Units(4.0),
|
|
||||||
..Default::default()
|
if let Some(first) = label.first() {
|
||||||
})
|
params.insert(Rc::from("text"), Rc::from(first.as_str()));
|
||||||
.unwrap(),
|
}
|
||||||
taffy::Style {
|
|
||||||
size: my_size,
|
gui_state_key.process_template("Key", &mut panel.layout, div, params)?;
|
||||||
min_size: my_size,
|
|
||||||
max_size: my_size,
|
|
||||||
..Default::default()
|
|
||||||
},
|
|
||||||
)?;
|
|
||||||
} else {
|
} else {
|
||||||
let _ = panel.layout.add_child(
|
let _ = panel.layout.add_child(
|
||||||
div,
|
div,
|
||||||
@@ -248,9 +248,9 @@ where
|
|||||||
})
|
})
|
||||||
.unwrap(),
|
.unwrap(),
|
||||||
taffy::Style {
|
taffy::Style {
|
||||||
size: my_size,
|
size: taffy_size,
|
||||||
min_size: my_size,
|
min_size: taffy_size,
|
||||||
max_size: my_size,
|
max_size: taffy_size,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
)?;
|
)?;
|
||||||
|
|||||||
Reference in New Issue
Block a user