wgui: add <macro> support, propagate variables into ParserResult

This commit is contained in:
Aleksander
2025-06-20 00:27:25 +02:00
parent b35020cd80
commit 887a2f6bde
6 changed files with 145 additions and 86 deletions

View File

@@ -20,8 +20,6 @@ use std::{
rc::Rc, rc::Rc,
}; };
type VarMap = HashMap<Rc<str>, Rc<str>>;
#[self_referencing] #[self_referencing]
struct XmlDocument { struct XmlDocument {
xml: String, xml: String,
@@ -44,6 +42,8 @@ struct ParserFile {
pub struct ParserResult { pub struct ParserResult {
pub ids: HashMap<Rc<str>, WidgetID>, pub ids: HashMap<Rc<str>, WidgetID>,
macro_attribs: HashMap<Rc<str>, MacroAttribs>,
var_map: HashMap<Rc<str>, Rc<str>>,
pub templates: HashMap<Rc<str>, Rc<Template>>, pub templates: HashMap<Rc<str>, Rc<Template>>,
pub path: PathBuf, pub path: PathBuf,
} }
@@ -67,7 +67,13 @@ impl ParserResult {
anyhow::bail!("no template named \"{}\" found", template_name); anyhow::bail!("no template named \"{}\" found", template_name);
}; };
let mut ctx = create_default_context(layout); let mut ctx = ParserContext {
layout,
ids: Default::default(),
macro_attribs: self.macro_attribs.clone(), // FIXME: prevent copying
var_map: self.var_map.clone(), // FIXME: prevent copying
templates: Default::default(),
};
let file = ParserFile { let file = ParserFile {
document: template.node_document.clone(), document: template.node_document.clone(),
@@ -75,21 +81,15 @@ impl ParserResult {
template_parameters: template_parameters.clone(), // FIXME: prevent copying template_parameters: template_parameters.clone(), // FIXME: prevent copying
}; };
let node = template
.node_document
.borrow_doc()
.get_node(template.node)
.unwrap();
parse_widget_other_internal( parse_widget_other_internal(
template.clone(), template.clone(),
template_parameters, template_parameters,
&file, &file,
&mut ctx, &mut ctx,
node,
widget_id, widget_id,
)?; )?;
// FIXME?
ctx.ids.into_iter().for_each(|(id, key)| { ctx.ids.into_iter().for_each(|(id, key)| {
self.ids.insert(id, key); self.ids.insert(id, key);
}); });
@@ -98,11 +98,17 @@ impl ParserResult {
} }
} }
#[derive(Debug, Clone)]
struct MacroAttribs {
attribs: HashMap<Rc<str>, Rc<str>>,
}
struct ParserContext<'a> { struct ParserContext<'a> {
layout: &'a mut Layout, layout: &'a mut Layout,
var_map: VarMap, var_map: HashMap<Rc<str>, Rc<str>>,
templates: HashMap<Rc<str>, Rc<Template>>, macro_attribs: HashMap<Rc<str>, MacroAttribs>,
ids: HashMap<Rc<str>, WidgetID>, ids: HashMap<Rc<str>, WidgetID>,
templates: HashMap<Rc<str>, Rc<Template>>,
} }
// Parses a color from a HTML hex string // Parses a color from a HTML hex string
@@ -207,12 +213,11 @@ where
} }
} }
fn parse_widget_other_internal<'a>( fn parse_widget_other_internal(
template: Rc<Template>, template: Rc<Template>,
template_parameters: HashMap<Rc<str>, Rc<str>>, template_parameters: HashMap<Rc<str>, Rc<str>>,
file: &'a ParserFile, file: &ParserFile,
ctx: &mut ParserContext, ctx: &mut ParserContext,
node: roxmltree::Node<'a, 'a>,
parent_id: WidgetID, parent_id: WidgetID,
) -> anyhow::Result<()> { ) -> anyhow::Result<()> {
let template_file = ParserFile { let template_file = ParserFile {
@@ -245,16 +250,10 @@ fn parse_widget_other<'a>(
return Ok(()); // not critical return Ok(()); // not critical
}; };
let template_parameters: HashMap<Rc<str>, Rc<str>> = iter_attribs(file, ctx, &node).collect(); let template_parameters: HashMap<Rc<str>, Rc<str>> =
iter_attribs(file, ctx, &node, false).collect();
parse_widget_other_internal( parse_widget_other_internal(template.clone(), template_parameters, file, ctx, parent_id)
template.clone(),
template_parameters,
file,
ctx,
node,
parent_id,
)
} }
fn parse_tag_include<'a>( fn parse_tag_include<'a>(
@@ -333,7 +332,10 @@ pub fn replace_vars(input: &str, vars: &HashMap<Rc<str>, Rc<str>>) -> Rc<str> {
match vars.get(input_var) { match vars.get(input_var) {
Some(replacement) => replacement.clone(), Some(replacement) => replacement.clone(),
None => Rc::from(""), None => {
log::warn!("failed to replace var named \"{}\" (not found)", input_var);
Rc::from("")
}
} }
}); });
@@ -341,31 +343,64 @@ pub fn replace_vars(input: &str, vars: &HashMap<Rc<str>, Rc<str>>) -> Rc<str> {
} }
#[allow(clippy::manual_strip)] #[allow(clippy::manual_strip)]
fn process_attrib<'a>(
file: &'a ParserFile,
ctx: &'a ParserContext,
key: &str,
value: &str,
) -> (Rc<str>, Rc<str>) {
if value.starts_with("~") {
let name = &value[1..];
(
Rc::from(key),
match ctx.var_map.get(name) {
Some(name) => name.clone(),
None => Rc::from("undefined"),
},
)
} else {
(
Rc::from(key),
replace_vars(value, &file.template_parameters),
)
}
}
fn iter_attribs<'a>( fn iter_attribs<'a>(
file: &'a ParserFile, file: &'a ParserFile,
ctx: &'a ParserContext, ctx: &'a ParserContext,
node: &roxmltree::Node<'a, 'a>, node: &'a roxmltree::Node<'a, 'a>,
is_tag_macro: bool,
) -> impl Iterator<Item = (/*key*/ Rc<str>, /*value*/ Rc<str>)> + 'a { ) -> impl Iterator<Item = (/*key*/ Rc<str>, /*value*/ Rc<str>)> + 'a {
node.attributes().map(|attrib| { let mut res = Vec::<(Rc<str>, Rc<str>)>::new();
if is_tag_macro {
// return as-is, no attrib post-processing
for attrib in node.attributes() {
let (key, value) = (attrib.name(), attrib.value());
res.push((Rc::from(key), Rc::from(value)));
}
return res.into_iter();
}
for attrib in node.attributes() {
let (key, value) = (attrib.name(), attrib.value()); let (key, value) = (attrib.name(), attrib.value());
if value.starts_with("~") { if key == "macro" {
let name = &value[1..]; if let Some(macro_attrib) = ctx.macro_attribs.get(value) {
for (macro_key, macro_value) in macro_attrib.attribs.iter() {
( res.push(process_attrib(file, ctx, macro_key, macro_value));
Rc::from(key), }
match ctx.var_map.get(name) { } else {
Some(name) => name.clone(), log::warn!("requested macro named \"{}\" not found!", value);
None => Rc::from("undefined"), }
},
)
} else { } else {
( res.push(process_attrib(file, ctx, key, value));
Rc::from(key),
replace_vars(value, &file.template_parameters),
)
} }
}) }
res.into_iter()
} }
fn parse_tag_theme<'a>( fn parse_tag_theme<'a>(
@@ -395,7 +430,7 @@ fn parse_tag_template(
) -> anyhow::Result<()> { ) -> anyhow::Result<()> {
let mut template_name: Option<Rc<str>> = None; let mut template_name: Option<Rc<str>> = None;
let attribs: Vec<_> = iter_attribs(file, ctx, &node).collect(); let attribs: Vec<_> = iter_attribs(file, ctx, &node, false).collect();
for (key, value) in attribs { for (key, value) in attribs {
match key.as_ref() { match key.as_ref() {
@@ -424,13 +459,51 @@ fn parse_tag_template(
Ok(()) Ok(())
} }
fn parse_tag_macro(
file: &ParserFile,
ctx: &mut ParserContext,
node: roxmltree::Node<'_, '_>,
) -> anyhow::Result<()> {
let mut macro_name: Option<Rc<str>> = None;
let attribs: Vec<_> = iter_attribs(file, ctx, &node, true).collect();
let mut macro_attribs = HashMap::<Rc<str>, Rc<str>>::new();
for (key, value) in attribs {
match key.as_ref() {
"name" => {
macro_name = Some(value);
}
_ => {
if macro_attribs.insert(key.clone(), value).is_some() {
log::warn!("macro attrib \"{}\" already defined!", key);
}
}
}
}
let Some(name) = macro_name else {
log::error!("Template name not specified, ignoring");
return Ok(());
};
ctx.macro_attribs.insert(
name.clone(),
MacroAttribs {
attribs: macro_attribs,
},
);
Ok(())
}
fn parse_universal<'a>( fn parse_universal<'a>(
file: &'a ParserFile, file: &'a ParserFile,
ctx: &mut ParserContext, ctx: &mut ParserContext,
node: roxmltree::Node<'a, 'a>, node: roxmltree::Node<'a, 'a>,
widget_id: WidgetID, widget_id: WidgetID,
) -> anyhow::Result<()> { ) -> anyhow::Result<()> {
let attribs: Vec<_> = iter_attribs(file, ctx, &node).collect(); let attribs: Vec<_> = iter_attribs(file, ctx, &node, false).collect();
for (key, value) in attribs { for (key, value) in attribs {
#[allow(clippy::single_match)] #[allow(clippy::single_match)]
@@ -485,6 +558,7 @@ fn create_default_context(layout: &mut Layout) -> ParserContext<'_> {
ids: Default::default(), ids: Default::default(),
var_map: Default::default(), var_map: Default::default(),
templates: Default::default(), templates: Default::default(),
macro_attribs: Default::default(),
} }
} }
@@ -504,6 +578,8 @@ pub fn parse_from_assets(
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), templates: std::mem::take(&mut ctx.templates),
macro_attribs: std::mem::take(&mut ctx.macro_attribs),
var_map: std::mem::take(&mut ctx.var_map),
path, path,
}; };
@@ -571,6 +647,7 @@ fn parse_document_root(
"include" => parse_tag_include(&file, ctx, child_node, parent_id)?, "include" => parse_tag_include(&file, ctx, child_node, parent_id)?,
"theme" => parse_tag_theme(ctx, child_node)?, "theme" => parse_tag_theme(ctx, child_node)?,
"template" => parse_tag_template(&file, ctx, child_node)?, "template" => parse_tag_template(&file, ctx, child_node)?,
"macro" => parse_tag_macro(&file, ctx, child_node)?,
_ => {} _ => {}
} }
} }

View File

@@ -16,7 +16,7 @@ pub fn style_from_node<'a>(
..Default::default() ..Default::default()
}; };
let attribs: Vec<_> = iter_attribs(file, ctx, &node).collect(); let attribs: Vec<_> = iter_attribs(file, ctx, &node, false).collect();
for (key, value) in attribs { for (key, value) in attribs {
match &*key { match &*key {

View File

@@ -15,7 +15,7 @@ pub fn parse_widget_label<'a>(
parent_id: WidgetID, parent_id: WidgetID,
) -> anyhow::Result<()> { ) -> anyhow::Result<()> {
let mut params = TextParams::default(); let mut params = TextParams::default();
let attribs: Vec<_> = iter_attribs(file, ctx, &node).collect(); let attribs: Vec<_> = iter_attribs(file, ctx, &node, false).collect();
for (key, value) in attribs { for (key, value) in attribs {
match &*key { match &*key {
"text" => { "text" => {

View File

@@ -16,7 +16,7 @@ pub fn parse_widget_rectangle<'a>(
parent_id: WidgetID, parent_id: WidgetID,
) -> anyhow::Result<()> { ) -> anyhow::Result<()> {
let mut params = RectangleParams::default(); let mut params = RectangleParams::default();
let attribs: Vec<_> = iter_attribs(file, ctx, &node).collect(); let attribs: Vec<_> = iter_attribs(file, ctx, &node, false).collect();
for (key, value) in attribs { for (key, value) in attribs {
match &*key { match &*key {

View File

@@ -15,7 +15,7 @@ pub fn parse_widget_sprite<'a>(
parent_id: WidgetID, parent_id: WidgetID,
) -> anyhow::Result<()> { ) -> anyhow::Result<()> {
let mut params = SpriteBoxParams::default(); let mut params = SpriteBoxParams::default();
let attribs: Vec<_> = iter_attribs(file, ctx, &node).collect(); let attribs: Vec<_> = iter_attribs(file, ctx, &node, false).collect();
let mut glyph = None; let mut glyph = None;
for (key, value) in attribs { for (key, value) in attribs {

View File

@@ -1,15 +1,20 @@
<layout> <layout>
<macro name="keycap_rect"
margin="2" width="100%" overflow="hidden" box_sizing="border_box"
border_color="#0044CC" border="2" round="8" color="#000A1C" color2="#000002" gradient="vertical"
align_items="center" justify_content="center" />
<macro name="keycap_div"
width="${width}" height="${height}" min_width="${width}" min_height="${height}" max_width="${width}" max_height="${height}"
/>
<!-- The keyboard is build from the xkb keymap. This file is for customizing the keycaps. --> <!-- The keyboard is build from the xkb keymap. This file is for customizing the keycaps. -->
<!-- Key cap with a single label. --> <!-- Key cap with a single label. -->
<!-- Used for special keys. --> <!-- Used for special keys. -->
<template name="KeySpecial"> <template name="KeySpecial">
<div width="${width}" height="${height}" min_width="${width}" min_height="${height}" max_width="${width}" max_height="${height}"> <div macro="keycap_div">
<rectangle id="${id}" <rectangle id="${id}" macro="keycap_rect">
margin="2" width="100%" overflow="hidden" box_sizing="border_box"
border_color="#0044CC" border="2" round="8" color="#000A1C" color2="#000002" gradient="vertical"
align_items="center"
justify_content="center">
<sprite color="#FFFFFF" width="32" height="32" src="keyboard/${text}.svg" /> <sprite color="#FFFFFF" width="32" height="32" src="keyboard/${text}.svg" />
</rectangle> </rectangle>
</div> </div>
@@ -18,12 +23,8 @@
<!-- Key cap with a single label. --> <!-- Key cap with a single label. -->
<!-- Used for letter keys on layouts without AltGr. --> <!-- Used for letter keys on layouts without AltGr. -->
<template name="KeyLetter"> <template name="KeyLetter">
<div width="${width}" height="${height}" min_width="${width}" min_height="${height}" max_width="${width}" max_height="${height}"> <div macro="keycap_div">
<rectangle id="${id}" <rectangle id="${id}" macro="keycap_rect">
margin="2" width="100%" overflow="hidden" box_sizing="border_box"
border_color="#0044CC" border="2" round="8" color="#000A1C" color2="#000002" gradient="vertical"
align_items="center"
justify_content="center">
<label color="#FFFFFF" text="${text}" size="24" /> <label color="#FFFFFF" text="${text}" size="24" />
</rectangle> </rectangle>
</div> </div>
@@ -32,14 +33,8 @@
<!-- Key cap with a primary label on top and an AltGr label on bottom. --> <!-- Key cap with a primary label on top and an AltGr label on bottom. -->
<!-- Used for letter keys on layouts with AltGr. --> <!-- Used for letter keys on layouts with AltGr. -->
<template name="KeyLetterAltGr"> <template name="KeyLetterAltGr">
<div width="${width}" height="${height}" min_width="${width}" min_height="${height}" max_width="${width}" max_height="${height}"> <div macro="keycap_div">
<rectangle id="${id}" <rectangle id="${id}" macro="keycap_rect" gap="4">
margin="2" width="100%" overflow="hidden" box_sizing="border_box"
border_color="#0044CC" border="2" round="8" color="#000A1C" color2="#000002" gradient="vertical"
gap="4"
flex_direction="column"
align_items="center"
justify_content="center">
<label color="#FFFFFF" text="${text}" size="24" /> <label color="#FFFFFF" text="${text}" size="24" />
<label color="#FFFFFF70" text="${text_altgr}" size="24" /> <label color="#FFFFFF70" text="${text_altgr}" size="24" />
</rectangle> </rectangle>
@@ -49,15 +44,8 @@
<!-- Key cap with a primary label on bottom and a Shift label on top. --> <!-- Key cap with a primary label on bottom and a Shift label on top. -->
<!-- Used for number & symbol keys on layouts without AltGr. --> <!-- Used for number & symbol keys on layouts without AltGr. -->
<template name="KeySymbol"> <template name="KeySymbol">
<div width="${width}" height="${height}" min_width="${width}" min_height="${height}" max_width="${width}" max_height="${height}"> <div macro="keycap_div">
<rectangle id="${id}" <rectangle id="${id}" macro="keycap_rect" gap="4">
margin="2" width="100%" overflow="hidden" box_sizing="border_box"
border_color="#0044CC" border="2" round="8" color="#000A1C" color2="#000002" gradient="vertical"
gap="4"
flex_direction="column"
align_items="center"
justify_content="center"
>
<label color="#FFFFFF70" text="${text_shift}" size="24" /> <label color="#FFFFFF70" text="${text_shift}" size="24" />
<label color="#FFFFFF" text="${text}" size="24" /> <label color="#FFFFFF" text="${text}" size="24" />
</rectangle> </rectangle>
@@ -67,14 +55,8 @@
<!-- Key cap with a primary label on bottom-left, an AltGr label on bottom-right, Shift label on top-left. --> <!-- Key cap with a primary label on bottom-left, an AltGr label on bottom-right, Shift label on top-left. -->
<!-- Used for number & symbol keys on layouts with AltGr. --> <!-- Used for number & symbol keys on layouts with AltGr. -->
<template name="KeySymbolAltGr"> <template name="KeySymbolAltGr">
<div width="${width}" height="${height}" min_width="${width}" min_height="${height}" max_width="${width}" max_height="${height}"> <div macro="keycap_div">
<rectangle id="${id}" <rectangle id="${id}" macro="keycap_rect" flex_direction="row" flex_wrap="wrap">
margin="2" width="100%" overflow="hidden" box_sizing="border_box"
border_color="#0044CC" border="2" round="8" color="#000A1C" color2="#000002" gradient="vertical"
flex_direction="row"
flex_wrap="wrap"
align_items="center"
justify_content="center">
<div width="50%" height="50%" align_items="center" justify_content="center"> <div width="50%" height="50%" align_items="center" justify_content="center">
<label color="#FFFFFF70" text="${text_shift}" size="24" /> <label color="#FFFFFF70" text="${text_shift}" size="24" />
</div> </div>