wgui: parser: template injection support

This commit is contained in:
Aleksander
2025-06-18 21:30:32 +02:00
parent b9e462f88b
commit 09ea4f3096
4 changed files with 134 additions and 57 deletions

View File

@@ -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;

View File

@@ -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(),

View File

@@ -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>

View File

@@ -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()
}, },
)?; )?;