attribs rework

This commit is contained in:
galister
2025-09-29 14:50:13 +09:00
parent 8c41eaa048
commit a78ae55bdc
12 changed files with 192 additions and 208 deletions

View File

@@ -7,9 +7,9 @@ use crate::{
use glam::Vec2; use glam::Vec2;
use wgui::{ use wgui::{
components::{ components::{
Component,
button::{ButtonClickCallback, ComponentButton}, button::{ButtonClickCallback, ComponentButton},
checkbox::ComponentCheckbox, checkbox::ComponentCheckbox,
Component,
}, },
drawing::Color, drawing::Color,
event::EventListenerCollection, event::EventListenerCollection,
@@ -66,17 +66,17 @@ impl TestbedGeneric {
let extra = ParseDocumentExtra { let extra = ParseDocumentExtra {
on_custom_attribs: Some(Box::new(move |par| { on_custom_attribs: Some(Box::new(move |par| {
let Some(my_custom_value) = par.get_value("my_custom") else { let Some(my_custom_value) = par.get_value("_my_custom") else {
return; return;
}; };
let Some(mult_value) = par.get_value("mult") else { let Some(mult_value) = par.get_value("_mult") else {
return; return;
}; };
let mult_f32 = mult_value.parse::<f32>().unwrap(); let mult_f32 = mult_value.parse::<f32>().unwrap();
let mut color = match my_custom_value { let mut color = match my_custom_value.as_ref() {
"red" => Color::new(1.0, 0.0, 0.0, 1.0), "red" => Color::new(1.0, 0.0, 0.0, 1.0),
"green" => Color::new(0.0, 1.0, 0.0, 1.0), "green" => Color::new(0.0, 1.0, 0.0, 1.0),
"blue" => Color::new(0.0, 0.0, 1.0, 1.0), "blue" => Color::new(0.0, 0.0, 1.0, 1.0),

View File

@@ -1,11 +1,12 @@
use crate::{ use crate::{
components::{Component, button}, components::{button, Component},
drawing::Color, drawing::Color,
i18n::Translation, i18n::Translation,
layout::WidgetID, layout::WidgetID,
parser::{ parser::{
ParserContext, ParserFile, iter_attribs, parse_children, process_component, parse_children, process_component,
style::{parse_color_opt, parse_round, parse_style, parse_text_style}, style::{parse_color_opt, parse_round, parse_style, parse_text_style},
AttribPair, ParserContext, ParserFile,
}, },
widget::util::WLength, widget::util::WLength,
}; };
@@ -15,6 +16,7 @@ pub fn parse_component_button<'a, U1, U2>(
ctx: &mut ParserContext<U1, U2>, ctx: &mut ParserContext<U1, U2>,
node: roxmltree::Node<'a, 'a>, node: roxmltree::Node<'a, 'a>,
parent_id: WidgetID, parent_id: WidgetID,
attribs: &[AttribPair],
) -> anyhow::Result<WidgetID> { ) -> anyhow::Result<WidgetID> {
let mut color: Option<Color> = None; let mut color: Option<Color> = None;
let mut border_color: Option<Color> = None; let mut border_color: Option<Color> = None;
@@ -23,11 +25,11 @@ pub fn parse_component_button<'a, U1, U2>(
let mut round = WLength::Units(4.0); let mut round = WLength::Units(4.0);
let mut translation: Option<Translation> = None; let mut translation: Option<Translation> = None;
let attribs: Vec<_> = iter_attribs(file, ctx, &node, false).collect(); let text_style = parse_text_style(attribs);
let text_style = parse_text_style(&attribs); let style = parse_style(attribs);
let style = parse_style(&attribs);
for (key, value) in attribs { for pair in attribs {
let (key, value) = (pair.attrib.as_ref(), pair.value.as_ref());
match key.as_ref() { match key.as_ref() {
"text" => { "text" => {
translation = Some(Translation::from_raw_text(&value)); translation = Some(Translation::from_raw_text(&value));
@@ -73,7 +75,7 @@ pub fn parse_component_button<'a, U1, U2>(
}, },
)?; )?;
process_component(file, ctx, node, Component(component), new_id); process_component(ctx, Component(component), new_id, attribs);
parse_children(file, ctx, node, new_id)?; parse_children(file, ctx, node, new_id)?;
Ok(new_id) Ok(new_id)

View File

@@ -1,38 +1,35 @@
use crate::{ use crate::{
components::{Component, checkbox}, components::{checkbox, Component},
i18n::Translation, i18n::Translation,
layout::WidgetID, layout::WidgetID,
parser::{ parser::{parse_check_f32, parse_check_i32, process_component, style::parse_style, AttribPair, ParserContext},
ParserContext, ParserFile, iter_attribs, parse_check_f32, parse_check_i32, process_component, style::parse_style,
},
}; };
pub fn parse_component_checkbox<'a, U1, U2>( pub fn parse_component_checkbox<'a, U1, U2>(
file: &'a ParserFile,
ctx: &mut ParserContext<U1, U2>, ctx: &mut ParserContext<U1, U2>,
node: roxmltree::Node<'a, 'a>,
parent_id: WidgetID, parent_id: WidgetID,
attribs: &[AttribPair],
) -> anyhow::Result<WidgetID> { ) -> anyhow::Result<WidgetID> {
let mut box_size = 24.0; let mut box_size = 24.0;
let mut translation = Translation::default(); let mut translation = Translation::default();
let mut checked = 0; let mut checked = 0;
let attribs: Vec<_> = iter_attribs(file, ctx, &node, false).collect(); let style = parse_style(attribs);
let style = parse_style(&attribs);
for (key, value) in attribs { for pair in attribs {
match key.as_ref() { let (key, value) = (pair.attrib.as_ref(), pair.value.as_ref());
match key {
"text" => { "text" => {
translation = Translation::from_raw_text(&value); translation = Translation::from_raw_text(value);
} }
"translation" => { "translation" => {
translation = Translation::from_translation_key(&value); translation = Translation::from_translation_key(value);
} }
"box_size" => { "box_size" => {
parse_check_f32(value.as_ref(), &mut box_size); parse_check_f32(value, &mut box_size);
} }
"checked" => { "checked" => {
parse_check_i32(value.as_ref(), &mut checked); parse_check_i32(value, &mut checked);
} }
_ => {} _ => {}
} }
@@ -50,7 +47,7 @@ pub fn parse_component_checkbox<'a, U1, U2>(
}, },
)?; )?;
process_component(file, ctx, node, Component(component), new_id); process_component(ctx, Component(component), new_id, attribs);
Ok(new_id) Ok(new_id)
} }

View File

@@ -1,32 +1,31 @@
use crate::{ use crate::{
components::{Component, slider}, components::{slider, Component},
layout::WidgetID, layout::WidgetID,
parser::{ParserContext, ParserFile, iter_attribs, parse_check_f32, process_component, style::parse_style}, parser::{parse_check_f32, process_component, style::parse_style, AttribPair, ParserContext},
}; };
pub fn parse_component_slider<'a, U1, U2>( pub fn parse_component_slider<'a, U1, U2>(
file: &'a ParserFile,
ctx: &mut ParserContext<U1, U2>, ctx: &mut ParserContext<U1, U2>,
node: roxmltree::Node<'a, 'a>,
parent_id: WidgetID, parent_id: WidgetID,
attribs: &[AttribPair],
) -> anyhow::Result<WidgetID> { ) -> anyhow::Result<WidgetID> {
let mut min_value = 0.0; let mut min_value = 0.0;
let mut max_value = 1.0; let mut max_value = 1.0;
let mut initial_value = 0.5; let mut initial_value = 0.5;
let attribs: Vec<_> = iter_attribs(file, ctx, &node, false).collect(); let style = parse_style(attribs);
let style = parse_style(&attribs);
for (key, value) in attribs { for pair in attribs {
match key.as_ref() { let (key, value) = (pair.attrib.as_ref(), pair.value.as_ref());
match key {
"min_value" => { "min_value" => {
parse_check_f32(value.as_ref(), &mut min_value); parse_check_f32(value, &mut min_value);
} }
"max_value" => { "max_value" => {
parse_check_f32(value.as_ref(), &mut max_value); parse_check_f32(value, &mut max_value);
} }
"value" => { "value" => {
parse_check_f32(value.as_ref(), &mut initial_value); parse_check_f32(value, &mut initial_value);
} }
_ => {} _ => {}
} }
@@ -46,7 +45,7 @@ pub fn parse_component_slider<'a, U1, U2>(
}, },
)?; )?;
process_component(file, ctx, node, Component(component), new_id); process_component(ctx, Component(component), new_id, attribs);
Ok(new_id) Ok(new_id)
} }

View File

@@ -441,7 +441,7 @@ fn print_invalid_value(value: &str) {
log::warn!("Invalid value \"{value}\""); log::warn!("Invalid value \"{value}\"");
} }
fn parse_val(value: &Rc<str>) -> Option<f32> { fn parse_val(value: &str) -> Option<f32> {
let Ok(val) = value.parse::<f32>() else { let Ok(val) = value.parse::<f32>() else {
print_invalid_value(value); print_invalid_value(value);
return None; return None;
@@ -534,15 +534,16 @@ fn parse_widget_other<'a, U1, U2>(
xml_tag_name: &str, xml_tag_name: &str,
file: &'a ParserFile, file: &'a ParserFile,
ctx: &mut ParserContext<U1, U2>, ctx: &mut ParserContext<U1, U2>,
node: roxmltree::Node<'a, 'a>,
parent_id: WidgetID, parent_id: WidgetID,
attribs: &[AttribPair],
) -> anyhow::Result<()> { ) -> anyhow::Result<()> {
let Some(template) = ctx.get_template(xml_tag_name) else { let Some(template) = ctx.get_template(xml_tag_name) else {
log::error!("Undefined tag named \"{xml_tag_name}\""); log::error!("Undefined tag named \"{xml_tag_name}\"");
return Ok(()); // not critical return Ok(()); // not critical
}; };
let template_parameters: HashMap<Rc<str>, Rc<str>> = iter_attribs(file, ctx, &node, false).collect(); let template_parameters: HashMap<Rc<str>, Rc<str>> =
attribs.iter().map(|a| (a.attrib.clone(), a.value.clone())).collect();
parse_widget_other_internal(&template, template_parameters, file, ctx, parent_id) parse_widget_other_internal(&template, template_parameters, file, ctx, parent_id)
} }
@@ -550,17 +551,15 @@ fn parse_widget_other<'a, U1, U2>(
fn parse_tag_include<'a, U1, U2>( fn parse_tag_include<'a, U1, U2>(
file: &ParserFile, file: &ParserFile,
ctx: &mut ParserContext<U1, U2>, ctx: &mut ParserContext<U1, U2>,
node: roxmltree::Node<'a, 'a>,
parent_id: WidgetID, parent_id: WidgetID,
attribs: &[AttribPair],
) -> anyhow::Result<()> { ) -> anyhow::Result<()> {
for attrib in node.attributes() { for pair in attribs {
let (key, value) = (attrib.name(), attrib.value());
#[allow(clippy::single_match)] #[allow(clippy::single_match)]
match key { match pair.attrib.as_ref() {
"src" => { "src" => {
let mut new_path = file.path.parent().unwrap_or_else(|| Path::new("/")).to_path_buf(); let mut new_path = file.path.parent().unwrap_or_else(|| Path::new("/")).to_path_buf();
new_path.push(value); new_path.push(pair.value.as_ref());
let new_path = assets::normalize_path(&new_path); let new_path = assets::normalize_path(&new_path);
let (new_file, node_layout) = get_doc_from_path(ctx, &new_path)?; let (new_file, node_layout) = get_doc_from_path(ctx, &new_path)?;
@@ -569,7 +568,7 @@ fn parse_tag_include<'a, U1, U2>(
return Ok(()); return Ok(());
} }
_ => { _ => {
print_invalid_attrib(key, value); print_invalid_attrib(pair.attrib.as_ref(), pair.value.as_ref());
} }
} }
} }
@@ -637,38 +636,39 @@ fn process_attrib<'a, U1, U2>(
ctx: &'a ParserContext<U1, U2>, ctx: &'a ParserContext<U1, U2>,
key: &str, key: &str,
value: &str, value: &str,
) -> (Rc<str>, Rc<str>) { ) -> AttribPair {
if value.starts_with('~') { if value.starts_with('~') {
let name = &value[1..]; let name = &value[1..];
( match ctx.get_var(name) {
Rc::from(key), Some(name) => AttribPair::new(key, name.clone()),
match ctx.get_var(name) { None => AttribPair::new(key, "undefined"),
Some(name) => name, }
None => Rc::from("undefined"),
},
)
} else { } else {
(Rc::from(key), replace_vars(value, &file.template_parameters)) AttribPair::new(key, replace_vars(value, &file.template_parameters))
} }
} }
fn iter_attribs<'a, U1, U2>( fn raw_attribs<'a>(node: &'a roxmltree::Node<'a, 'a>) -> Vec<AttribPair> {
let mut res = vec![];
for attrib in node.attributes() {
let (key, value) = (attrib.name(), attrib.value());
res.push(AttribPair::new(key, value));
}
return res;
}
fn process_attribs<'a, U1, U2>(
file: &'a ParserFile, file: &'a ParserFile,
ctx: &'a ParserContext<U1, U2>, ctx: &'a ParserContext<U1, U2>,
node: &'a roxmltree::Node<'a, 'a>, node: &'a roxmltree::Node<'a, 'a>,
is_tag_macro: bool, is_tag_macro: bool,
) -> impl Iterator<Item = (/*key*/ Rc<str>, /*value*/ Rc<str>)> + 'a { ) -> Vec<AttribPair> {
let mut res = Vec::<(Rc<str>, Rc<str>)>::new();
if is_tag_macro { if is_tag_macro {
// return as-is, no attrib post-processing // return as-is, no attrib post-processing
for attrib in node.attributes() { return raw_attribs(node);
let (key, value) = (attrib.name(), attrib.value());
res.push((Rc::from(key), Rc::from(value)));
}
return res.into_iter();
} }
let mut res = vec![];
for attrib in node.attributes() { for attrib in node.attributes() {
let (key, value) = (attrib.name(), attrib.value()); let (key, value) = (attrib.name(), attrib.value());
@@ -686,7 +686,7 @@ fn iter_attribs<'a, U1, U2>(
} }
} }
res.into_iter() res
} }
fn parse_tag_theme<'a, U1, U2>(ctx: &mut ParserContext<U1, U2>, node: roxmltree::Node<'a, 'a>) { fn parse_tag_theme<'a, U1, U2>(ctx: &mut ParserContext<U1, U2>, node: roxmltree::Node<'a, 'a>) {
@@ -707,15 +707,15 @@ fn parse_tag_theme<'a, U1, U2>(ctx: &mut ParserContext<U1, U2>, node: roxmltree:
fn parse_tag_template<U1, U2>(file: &ParserFile, ctx: &mut ParserContext<U1, U2>, node: roxmltree::Node<'_, '_>) { fn parse_tag_template<U1, U2>(file: &ParserFile, ctx: &mut ParserContext<U1, U2>, node: roxmltree::Node<'_, '_>) {
let mut template_name: Option<Rc<str>> = None; let mut template_name: Option<Rc<str>> = None;
let attribs: Vec<_> = iter_attribs(file, ctx, &node, false).collect(); let attribs = process_attribs(file, ctx, &node, false);
for (key, value) in attribs { for pair in attribs {
match key.as_ref() { match pair.attrib.as_ref() {
"name" => { "name" => {
template_name = Some(value); template_name = Some(pair.value);
} }
_ => { _ => {
print_invalid_attrib(&key, &value); print_invalid_attrib(pair.value.as_ref(), pair.value.as_ref());
} }
} }
} }
@@ -737,17 +737,17 @@ fn parse_tag_template<U1, U2>(file: &ParserFile, ctx: &mut ParserContext<U1, U2>
fn parse_tag_macro<U1, U2>(file: &ParserFile, ctx: &mut ParserContext<U1, U2>, node: roxmltree::Node<'_, '_>) { fn parse_tag_macro<U1, U2>(file: &ParserFile, ctx: &mut ParserContext<U1, U2>, node: roxmltree::Node<'_, '_>) {
let mut macro_name: Option<Rc<str>> = None; let mut macro_name: Option<Rc<str>> = None;
let attribs: Vec<_> = iter_attribs(file, ctx, &node, true).collect(); let attribs = process_attribs(file, ctx, &node, true);
let mut macro_attribs = HashMap::<Rc<str>, Rc<str>>::new(); let mut macro_attribs = HashMap::<Rc<str>, Rc<str>>::new();
for (key, value) in attribs { for pair in attribs {
match key.as_ref() { match pair.attrib.as_ref() {
"name" => { "name" => {
macro_name = Some(value); macro_name = Some(pair.value);
} }
_ => { _ => {
if macro_attribs.insert(key.clone(), value).is_some() { if macro_attribs.insert(pair.attrib.clone(), pair.value).is_some() {
log::warn!("macro attrib \"{key}\" already defined!"); log::warn!("macro attrib \"{}\" already defined!", pair.attrib);
} }
} }
} }
@@ -762,21 +762,18 @@ fn parse_tag_macro<U1, U2>(file: &ParserFile, ctx: &mut ParserContext<U1, U2>, n
} }
fn process_component<'a, U1, U2>( fn process_component<'a, U1, U2>(
file: &'a ParserFile,
ctx: &mut ParserContext<U1, U2>, ctx: &mut ParserContext<U1, U2>,
node: roxmltree::Node<'a, 'a>,
component: Component, component: Component,
widget_id: WidgetID, widget_id: WidgetID,
attribs: &[AttribPair],
) { ) {
let attribs: Vec<_> = iter_attribs(file, ctx, &node, false).collect();
let mut component_id: Option<Rc<str>> = None; let mut component_id: Option<Rc<str>> = None;
for (key, value) in attribs { for pair in attribs {
#[allow(clippy::single_match)] #[allow(clippy::single_match)]
match key.as_ref() { match pair.attrib.as_ref() {
"id" => { "id" => {
component_id = Some(value); component_id = Some(pair.value.clone());
} }
_ => {} _ => {}
} }
@@ -785,20 +782,13 @@ fn process_component<'a, U1, U2>(
ctx.insert_component(widget_id, component, component_id); ctx.insert_component(widget_id, component, component_id);
} }
fn parse_widget_universal<'a, U1, U2>( fn parse_widget_universal<'a, U1, U2>(ctx: &mut ParserContext<U1, U2>, widget_id: WidgetID, attribs: &[AttribPair]) {
file: &'a ParserFile, for pair in attribs {
ctx: &mut ParserContext<U1, U2>,
node: roxmltree::Node<'a, 'a>,
widget_id: WidgetID,
) {
let attribs: Vec<_> = iter_attribs(file, ctx, &node, false).collect();
for (key, value) in attribs {
#[allow(clippy::single_match)] #[allow(clippy::single_match)]
match key.as_ref() { match pair.attrib.as_ref() {
"id" => { "id" => {
// Attach a specific widget to name-ID map (just like getElementById) // Attach a specific widget to name-ID map (just like getElementById)
ctx.insert_id(&value, widget_id); ctx.insert_id(&pair.value, widget_id);
} }
_ => {} _ => {}
} }
@@ -827,36 +817,38 @@ fn parse_child<'a, U1, U2>(
_ => {} _ => {}
} }
let attribs = process_attribs(file, ctx, &child_node, false);
let mut new_widget_id: Option<WidgetID> = None; let mut new_widget_id: Option<WidgetID> = None;
match child_node.tag_name().name() { match child_node.tag_name().name() {
"include" => { "include" => {
parse_tag_include(file, ctx, child_node, parent_id)?; parse_tag_include(file, ctx, parent_id, &attribs)?;
} }
"div" => { "div" => {
new_widget_id = Some(parse_widget_div(file, ctx, child_node, parent_id)?); new_widget_id = Some(parse_widget_div(file, ctx, child_node, parent_id, &attribs)?);
} }
"rectangle" => { "rectangle" => {
new_widget_id = Some(parse_widget_rectangle(file, ctx, child_node, parent_id)?); new_widget_id = Some(parse_widget_rectangle(file, ctx, child_node, parent_id, &attribs)?);
} }
"label" => { "label" => {
new_widget_id = Some(parse_widget_label(file, ctx, child_node, parent_id)?); new_widget_id = Some(parse_widget_label(file, ctx, child_node, parent_id, &attribs)?);
} }
"sprite" => { "sprite" => {
new_widget_id = Some(parse_widget_sprite(file, ctx, child_node, parent_id)?); new_widget_id = Some(parse_widget_sprite(file, ctx, child_node, parent_id, &attribs)?);
} }
"Button" => { "Button" => {
new_widget_id = Some(parse_component_button(file, ctx, child_node, parent_id)?); new_widget_id = Some(parse_component_button(file, ctx, child_node, parent_id, &attribs)?);
} }
"Slider" => { "Slider" => {
new_widget_id = Some(parse_component_slider(file, ctx, child_node, parent_id)?); new_widget_id = Some(parse_component_slider(ctx, parent_id, &attribs)?);
} }
"CheckBox" => { "CheckBox" => {
new_widget_id = Some(parse_component_checkbox(file, ctx, child_node, parent_id)?); new_widget_id = Some(parse_component_checkbox(ctx, parent_id, &attribs)?);
} }
"" => { /* ignore */ } "" => { /* ignore */ }
other_tag_name => { other_tag_name => {
parse_widget_other(other_tag_name, file, ctx, child_node, parent_id)?; parse_widget_other(other_tag_name, file, ctx, parent_id, &attribs)?;
} }
} }
@@ -864,20 +856,13 @@ fn parse_child<'a, U1, U2>(
if let Some(widget_id) = new_widget_id if let Some(widget_id) = new_widget_id
&& let Some(on_custom_attribs) = &ctx.doc_params.extra.on_custom_attribs && let Some(on_custom_attribs) = &ctx.doc_params.extra.on_custom_attribs
{ {
let mut pairs = SmallVec::<[CustomAttribPair; 4]>::new(); let mut pairs = SmallVec::<[AttribPair; 4]>::new();
for attrib in child_node.attributes() { for pair in attribs {
let attr_name = attrib.name(); if !pair.attrib.starts_with('_') || pair.attrib.is_empty() {
if !attr_name.starts_with('_') || attr_name.is_empty() {
continue; continue;
} }
pairs.push(pair.clone());
let attr_without_prefix = &attr_name[1..]; // safe
pairs.push(CustomAttribPair {
attrib: attr_without_prefix,
value: attrib.value(),
});
} }
if !pairs.is_empty() { if !pairs.is_empty() {
@@ -921,16 +906,30 @@ fn create_default_context<'a, U1, U2>(
} }
} }
pub struct CustomAttribPair<'a> { #[derive(Clone)]
pub attrib: &'a str, // without _ at the beginning pub struct AttribPair {
pub value: &'a str, pub attrib: Rc<str>,
pub value: Rc<str>,
}
impl AttribPair {
fn new<A, V>(attrib: A, value: V) -> Self
where
A: Into<Rc<str>>,
V: Into<Rc<str>>,
{
Self {
attrib: attrib.into(),
value: value.into(),
}
}
} }
pub struct CustomAttribsInfo<'a> { pub struct CustomAttribsInfo<'a> {
pub parent_id: WidgetID, pub parent_id: WidgetID,
pub widget_id: WidgetID, pub widget_id: WidgetID,
pub widgets: &'a WidgetMap, pub widgets: &'a WidgetMap,
pub pairs: &'a [CustomAttribPair<'a>], pub pairs: &'a [AttribPair],
} }
// helper functions // helper functions
@@ -943,11 +942,11 @@ impl CustomAttribsInfo<'_> {
self.widgets.get(self.widget_id)?.get_as_mut::<T>() self.widgets.get(self.widget_id)?.get_as_mut::<T>()
} }
pub fn get_value(&self, attrib_name: &str) -> Option<&str> { pub fn get_value(&self, attrib_name: &str) -> Option<Rc<str>> {
// O(n) search, these pairs won't be problematically big anyways // O(n) search, these pairs won't be problematically big anyways
for pair in self.pairs { for pair in self.pairs {
if pair.attrib == attrib_name { if *pair.attrib == *attrib_name {
return Some(pair.value); return Some(pair.value.clone());
} }
} }
@@ -958,35 +957,23 @@ impl CustomAttribsInfo<'_> {
CustomAttribsInfoOwned { CustomAttribsInfoOwned {
parent_id: self.parent_id, parent_id: self.parent_id,
widget_id: self.widget_id, widget_id: self.widget_id,
pairs: self pairs: self.pairs.iter().cloned().collect(),
.pairs
.iter()
.map(|p| CustomAttribPairOwned {
attrib: p.attrib.to_string(),
value: p.value.to_string(),
})
.collect(),
} }
} }
} }
pub struct CustomAttribPairOwned {
pub attrib: String, // without _ at the beginning
pub value: String,
}
pub struct CustomAttribsInfoOwned { pub struct CustomAttribsInfoOwned {
pub parent_id: WidgetID, pub parent_id: WidgetID,
pub widget_id: WidgetID, pub widget_id: WidgetID,
pub pairs: Vec<CustomAttribPairOwned>, pub pairs: Vec<AttribPair>,
} }
impl CustomAttribsInfoOwned { impl CustomAttribsInfoOwned {
pub fn get_value(&self, attrib_name: &str) -> Option<&str> { pub fn get_value(&self, attrib_name: &str) -> Option<&str> {
// O(n) search, these pairs won't be problematically big anyways // O(n) search, these pairs won't be problematically big anyways
for pair in &self.pairs { for pair in &self.pairs {
if pair.attrib == attrib_name { if pair.attrib.as_ref() == attrib_name {
return Some(pair.value.as_str()); return Some(pair.value.as_ref());
} }
} }
@@ -1091,7 +1078,7 @@ fn parse_document_root<U1, U2>(
#[allow(clippy::single_match)] #[allow(clippy::single_match)]
match child_node.tag_name().name() { match child_node.tag_name().name() {
/* topmost include directly in <layout> */ /* topmost include directly in <layout> */
"include" => parse_tag_include(file, ctx, child_node, parent_id)?, "include" => parse_tag_include(file, ctx, parent_id, &raw_attribs(&child_node))?,
"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), "macro" => parse_tag_macro(file, ctx, child_node),

View File

@@ -1,15 +1,13 @@
use std::rc::Rc;
use taffy::{ use taffy::{
AlignContent, AlignItems, AlignSelf, BoxSizing, Display, FlexDirection, FlexWrap, JustifyContent, AlignContent, AlignItems, AlignSelf, BoxSizing, Display, FlexDirection, FlexWrap, JustifyContent, JustifySelf,
JustifySelf, Overflow, Overflow,
}; };
use crate::{ use crate::{
drawing, drawing,
parser::{ parser::{
is_percent, parse_color_hex, parse_f32, parse_percent, parse_size_unit, parse_val, is_percent, parse_color_hex, parse_f32, parse_percent, parse_size_unit, parse_val, print_invalid_attrib,
print_invalid_attrib, print_invalid_value, print_invalid_value, AttribPair,
}, },
renderer_vk::text::{FontWeight, HorizontalAlign, TextStyle}, renderer_vk::text::{FontWeight, HorizontalAlign, TextStyle},
widget::util::WLength, widget::util::WLength,
@@ -45,11 +43,12 @@ pub fn parse_color_opt(value: &str, color: &mut Option<drawing::Color>) {
} }
} }
pub fn parse_text_style(attribs: &[(Rc<str>, Rc<str>)]) -> TextStyle { pub fn parse_text_style(attribs: &[AttribPair]) -> TextStyle {
let mut style = TextStyle::default(); let mut style = TextStyle::default();
for (key, value) in attribs { for pair in attribs {
match key.as_ref() { let (key, value) = (pair.attrib.as_ref(), pair.value.as_ref());
match key {
"color" => { "color" => {
if let Some(color) = parse_color_hex(value) { if let Some(color) = parse_color_hex(value) {
style.color = Some(color); style.color = Some(color);
@@ -88,10 +87,11 @@ pub fn parse_text_style(attribs: &[(Rc<str>, Rc<str>)]) -> TextStyle {
#[allow(clippy::too_many_lines)] #[allow(clippy::too_many_lines)]
#[allow(clippy::cognitive_complexity)] #[allow(clippy::cognitive_complexity)]
pub fn parse_style(attribs: &[(Rc<str>, Rc<str>)]) -> taffy::Style { pub fn parse_style(attribs: &[AttribPair]) -> taffy::Style {
let mut style = taffy::Style::default(); let mut style = taffy::Style::default();
for (key, value) in attribs { for pair in attribs {
let (key, value) = (pair.attrib.as_ref(), pair.value.as_ref());
match key.as_ref() { match key.as_ref() {
"display" => match value.as_ref() { "display" => match value.as_ref() {
"flex" => style.display = Display::Flex, "flex" => style.display = Display::Flex,

View File

@@ -1,9 +1,6 @@
use crate::{ use crate::{
layout::WidgetID, layout::WidgetID,
parser::{ parser::{parse_children, parse_widget_universal, style::parse_style, AttribPair, ParserContext, ParserFile},
ParserContext, ParserFile, iter_attribs, parse_children, parse_widget_universal,
style::parse_style,
},
widget::div::WidgetDiv, widget::div::WidgetDiv,
}; };
@@ -12,15 +9,13 @@ pub fn parse_widget_div<'a, U1, U2>(
ctx: &mut ParserContext<U1, U2>, ctx: &mut ParserContext<U1, U2>,
node: roxmltree::Node<'a, 'a>, node: roxmltree::Node<'a, 'a>,
parent_id: WidgetID, parent_id: WidgetID,
attribs: &[AttribPair],
) -> anyhow::Result<WidgetID> { ) -> anyhow::Result<WidgetID> {
let attribs: Vec<_> = iter_attribs(file, ctx, &node, false).collect(); let style = parse_style(attribs);
let style = parse_style(&attribs);
let (new_id, _) = ctx let (new_id, _) = ctx.layout.add_child(parent_id, WidgetDiv::create(), style)?;
.layout
.add_child(parent_id, WidgetDiv::create(), style)?;
parse_widget_universal(file, ctx, node, new_id); parse_widget_universal(ctx, new_id, attribs);
parse_children(file, ctx, node, new_id)?; parse_children(file, ctx, node, new_id)?;
Ok(new_id) Ok(new_id)

View File

@@ -2,8 +2,9 @@ use crate::{
i18n::Translation, i18n::Translation,
layout::WidgetID, layout::WidgetID,
parser::{ parser::{
ParserContext, ParserFile, iter_attribs, parse_children, parse_widget_universal, parse_children, parse_widget_universal,
style::{parse_style, parse_text_style}, style::{parse_style, parse_text_style},
AttribPair, ParserContext, ParserFile,
}, },
widget::label::{WidgetLabel, WidgetLabelParams}, widget::label::{WidgetLabel, WidgetLabelParams},
}; };
@@ -13,23 +14,24 @@ pub fn parse_widget_label<'a, U1, U2>(
ctx: &mut ParserContext<U1, U2>, ctx: &mut ParserContext<U1, U2>,
node: roxmltree::Node<'a, 'a>, node: roxmltree::Node<'a, 'a>,
parent_id: WidgetID, parent_id: WidgetID,
attribs: &[AttribPair],
) -> anyhow::Result<WidgetID> { ) -> anyhow::Result<WidgetID> {
let mut params = WidgetLabelParams::default(); let mut params = WidgetLabelParams::default();
let attribs: Vec<_> = iter_attribs(file, ctx, &node, false).collect();
let style = parse_style(&attribs); let style = parse_style(attribs);
params.style = parse_text_style(&attribs); params.style = parse_text_style(attribs);
for (key, value) in attribs { for pair in attribs {
match &*key { let (key, value) = (pair.attrib.as_ref(), pair.value.as_ref());
match key {
"text" => { "text" => {
if !value.is_empty() { if !value.is_empty() {
params.content = Translation::from_raw_text(&value); params.content = Translation::from_raw_text(value);
} }
} }
"translation" => { "translation" => {
if !value.is_empty() { if !value.is_empty() {
params.content = Translation::from_translation_key(&value); params.content = Translation::from_translation_key(value);
} }
} }
_ => {} _ => {}
@@ -42,7 +44,7 @@ pub fn parse_widget_label<'a, U1, U2>(
.layout .layout
.add_child(parent_id, WidgetLabel::create(&mut globals.get(), params), style)?; .add_child(parent_id, WidgetLabel::create(&mut globals.get(), params), style)?;
parse_widget_universal(file, ctx, node, new_id); parse_widget_universal(ctx, new_id, attribs);
parse_children(file, ctx, node, new_id)?; parse_children(file, ctx, node, new_id)?;
Ok(new_id) Ok(new_id)

View File

@@ -2,9 +2,9 @@ use crate::{
drawing::GradientMode, drawing::GradientMode,
layout::WidgetID, layout::WidgetID,
parser::{ parser::{
ParserContext, ParserFile, iter_attribs, parse_children, parse_widget_universal, parse_children, parse_widget_universal, print_invalid_attrib,
print_invalid_attrib,
style::{parse_color, parse_round, parse_style}, style::{parse_color, parse_round, parse_style},
AttribPair, ParserContext, ParserFile,
}, },
widget::rectangle::{WidgetRectangle, WidgetRectangleParams}, widget::rectangle::{WidgetRectangle, WidgetRectangleParams},
}; };
@@ -14,42 +14,43 @@ pub fn parse_widget_rectangle<'a, U1, U2>(
ctx: &mut ParserContext<U1, U2>, ctx: &mut ParserContext<U1, U2>,
node: roxmltree::Node<'a, 'a>, node: roxmltree::Node<'a, 'a>,
parent_id: WidgetID, parent_id: WidgetID,
attribs: &[AttribPair],
) -> anyhow::Result<WidgetID> { ) -> anyhow::Result<WidgetID> {
let mut params = WidgetRectangleParams::default(); let mut params = WidgetRectangleParams::default();
let attribs: Vec<_> = iter_attribs(file, ctx, &node, false).collect();
let style = parse_style(&attribs); let style = parse_style(&attribs);
for (key, value) in attribs { for pair in attribs {
match &*key { let (key, value) = (pair.attrib.as_ref(), pair.value.as_ref());
match key {
"color" => { "color" => {
parse_color(&value, &mut params.color); parse_color(value, &mut params.color);
} }
"color2" => { "color2" => {
parse_color(&value, &mut params.color2); parse_color(value, &mut params.color2);
} }
"gradient" => { "gradient" => {
params.gradient = match &*value { params.gradient = match value {
"horizontal" => GradientMode::Horizontal, "horizontal" => GradientMode::Horizontal,
"vertical" => GradientMode::Vertical, "vertical" => GradientMode::Vertical,
"radial" => GradientMode::Radial, "radial" => GradientMode::Radial,
"none" => GradientMode::None, "none" => GradientMode::None,
_ => { _ => {
print_invalid_attrib(&key, &value); print_invalid_attrib(key, value);
GradientMode::None GradientMode::None
} }
} }
} }
"round" => { "round" => {
parse_round(&value, &mut params.round); parse_round(value, &mut params.round);
} }
"border" => { "border" => {
params.border = value.parse().unwrap_or_else(|_| { params.border = value.parse().unwrap_or_else(|_| {
print_invalid_attrib(&key, &value); print_invalid_attrib(key, value);
0.0 0.0
}); });
} }
"border_color" => { "border_color" => {
parse_color(&value, &mut params.border_color); parse_color(value, &mut params.border_color);
} }
_ => {} _ => {}
} }
@@ -59,7 +60,7 @@ pub fn parse_widget_rectangle<'a, U1, U2>(
.layout .layout
.add_child(parent_id, WidgetRectangle::create(params), style)?; .add_child(parent_id, WidgetRectangle::create(params), style)?;
parse_widget_universal(file, ctx, node, new_id); parse_widget_universal(ctx, new_id, attribs);
parse_children(file, ctx, node, new_id)?; parse_children(file, ctx, node, new_id)?;
Ok(new_id) Ok(new_id)

View File

@@ -1,6 +1,6 @@
use crate::{ use crate::{
layout::WidgetID, layout::WidgetID,
parser::{ParserContext, ParserFile, iter_attribs, parse_children, parse_widget_universal, style::parse_style}, parser::{parse_children, parse_widget_universal, style::parse_style, AttribPair, ParserContext, ParserFile},
renderer_vk::text::custom_glyph::{CustomGlyphContent, CustomGlyphData}, renderer_vk::text::custom_glyph::{CustomGlyphContent, CustomGlyphData},
widget::sprite::{WidgetSprite, WidgetSpriteParams}, widget::sprite::{WidgetSprite, WidgetSpriteParams},
}; };
@@ -12,17 +12,18 @@ pub fn parse_widget_sprite<'a, U1, U2>(
ctx: &mut ParserContext<U1, U2>, ctx: &mut ParserContext<U1, U2>,
node: roxmltree::Node<'a, 'a>, node: roxmltree::Node<'a, 'a>,
parent_id: WidgetID, parent_id: WidgetID,
attribs: &[AttribPair],
) -> anyhow::Result<WidgetID> { ) -> anyhow::Result<WidgetID> {
let mut params = WidgetSpriteParams::default(); let mut params = WidgetSpriteParams::default();
let attribs: Vec<_> = iter_attribs(file, ctx, &node, false).collect();
let style = parse_style(&attribs); let style = parse_style(&attribs);
let mut glyph = None; let mut glyph = None;
for (key, value) in attribs { for pair in attribs {
match key.as_ref() { let (key, value) = (pair.attrib.as_ref(), pair.value.as_ref());
match key {
"src" => { "src" => {
if !value.is_empty() { if !value.is_empty() {
glyph = match CustomGlyphContent::from_assets(&mut ctx.layout.state.globals.assets(), &value) { glyph = match CustomGlyphContent::from_assets(&mut ctx.layout.state.globals.assets(), value) {
Ok(glyph) => Some(glyph), Ok(glyph) => Some(glyph),
Err(e) => { Err(e) => {
log::warn!("failed to load {value}: {e}"); log::warn!("failed to load {value}: {e}");
@@ -32,15 +33,15 @@ pub fn parse_widget_sprite<'a, U1, U2>(
} }
} }
"src_ext" => { "src_ext" => {
if !value.is_empty() && std::fs::exists(value.as_ref()).unwrap_or(false) { if !value.is_empty() && std::fs::exists(value).unwrap_or(false) {
glyph = CustomGlyphContent::from_file(&value).ok(); glyph = CustomGlyphContent::from_file(value).ok();
} }
} }
"color" => { "color" => {
if let Some(color) = parse_color_hex(&value) { if let Some(color) = parse_color_hex(value) {
params.color = Some(color); params.color = Some(color);
} else { } else {
print_invalid_attrib(&key, &value); print_invalid_attrib(key, value);
} }
} }
_ => {} _ => {}
@@ -55,7 +56,7 @@ pub fn parse_widget_sprite<'a, U1, U2>(
let (new_id, _) = ctx.layout.add_child(parent_id, WidgetSprite::create(params), style)?; let (new_id, _) = ctx.layout.add_child(parent_id, WidgetSprite::create(params), style)?;
parse_widget_universal(file, ctx, node, new_id); parse_widget_universal(ctx, new_id, attribs);
parse_children(file, ctx, node, new_id)?; parse_children(file, ctx, node, new_id)?;
Ok(new_id) Ok(new_id)

View File

@@ -11,7 +11,7 @@ use wgui::{
use crate::{ use crate::{
backend::{common::OverlaySelector, overlay::OverlayID, task::TaskType, wayvr::WayVRAction}, backend::{common::OverlaySelector, overlay::OverlayID, task::TaskType, wayvr::WayVRAction},
config::{AStrSetExt, save_layout}, config::{save_layout, AStrSetExt},
state::AppState, state::AppState,
}; };
@@ -24,8 +24,8 @@ pub(super) fn setup_custom_button<S>(
_app: &AppState, _app: &AppState,
) { ) {
const EVENTS: [(&str, EventListenerKind); 2] = [ const EVENTS: [(&str, EventListenerKind); 2] = [
("press", EventListenerKind::MousePress), ("_press", EventListenerKind::MousePress),
("release", EventListenerKind::MouseRelease), ("_release", EventListenerKind::MouseRelease),
]; ];
for (name, kind) in &EVENTS { for (name, kind) in &EVENTS {

View File

@@ -15,7 +15,7 @@ use wgui::{
event::{self, EventCallback, EventListenerCollection, ListenerHandleVec}, event::{self, EventCallback, EventListenerCollection, ListenerHandleVec},
i18n::Translation, i18n::Translation,
layout::Layout, layout::Layout,
parser::{CustomAttribsInfoOwned, parse_color_hex}, parser::{parse_color_hex, CustomAttribsInfoOwned},
widget::label::WidgetLabel, widget::label::WidgetLabel,
}; };
@@ -31,14 +31,14 @@ pub(super) fn setup_custom_label<S>(
listener_handles: &mut ListenerHandleVec, listener_handles: &mut ListenerHandleVec,
app: &AppState, app: &AppState,
) { ) {
let Some(source) = attribs.get_value("source") else { let Some(source) = attribs.get_value("_source") else {
log::warn!("custom label with no source!"); log::warn!("custom label with no source!");
return; return;
}; };
let callback: EventCallback<AppState, S> = match source { let callback: EventCallback<AppState, S> = match source {
"shell" => { "shell" => {
let Some(exec) = attribs.get_value("exec") else { let Some(exec) = attribs.get_value("_exec") else {
log::warn!("label with shell source but no exec attribute!"); log::warn!("label with shell source but no exec attribute!");
return; return;
}; };
@@ -57,7 +57,7 @@ pub(super) fn setup_custom_label<S>(
}) })
} }
"fifo" => { "fifo" => {
let Some(path) = attribs.get_value("path") else { let Some(path) = attribs.get_value("_path") else {
log::warn!("label with fifo source but no path attribute!"); log::warn!("label with fifo source but no path attribute!");
return; return;
}; };
@@ -76,7 +76,7 @@ pub(super) fn setup_custom_label<S>(
} }
"battery" => { "battery" => {
let Some(device) = attribs let Some(device) = attribs
.get_value("device") .get_value("_device")
.and_then(|s| s.parse::<usize>().ok()) .and_then(|s| s.parse::<usize>().ok())
else { else {
log::warn!("label with battery source but no device attribute!"); log::warn!("label with battery source but no device attribute!");
@@ -85,19 +85,19 @@ pub(super) fn setup_custom_label<S>(
let state = BatteryLabelState { let state = BatteryLabelState {
low_color: attribs low_color: attribs
.get_value("low_color") .get_value("_low_color")
.and_then(parse_color_hex) .and_then(parse_color_hex)
.unwrap_or(BAT_LOW), .unwrap_or(BAT_LOW),
normal_color: attribs normal_color: attribs
.get_value("normal_color") .get_value("_normal_color")
.and_then(parse_color_hex) .and_then(parse_color_hex)
.unwrap_or(BAT_NORMAL), .unwrap_or(BAT_NORMAL),
charging_color: attribs charging_color: attribs
.get_value("charging_color") .get_value("_charging_color")
.and_then(parse_color_hex) .and_then(parse_color_hex)
.unwrap_or(BAT_CHARGING), .unwrap_or(BAT_CHARGING),
low_threshold: attribs low_threshold: attribs
.get_value("low_threshold") .get_value("_low_threshold")
.and_then(|s| s.parse().ok()) .and_then(|s| s.parse().ok())
.unwrap_or(BAT_LOW_THRESHOLD), .unwrap_or(BAT_LOW_THRESHOLD),
device, device,
@@ -108,7 +108,7 @@ pub(super) fn setup_custom_label<S>(
}) })
} }
"clock" => { "clock" => {
let Some(display) = attribs.get_value("display") else { let Some(display) = attribs.get_value("_display") else {
log::warn!("label with clock source but no display attribute!"); log::warn!("label with clock source but no display attribute!");
return; return;
}; };
@@ -116,7 +116,7 @@ pub(super) fn setup_custom_label<S>(
let format = match display { let format = match display {
"name" => { "name" => {
let maybe_pretty_tz = attribs let maybe_pretty_tz = attribs
.get_value("timezone") .get_value("_timezone")
.and_then(|tz| tz.parse::<usize>().ok()) .and_then(|tz| tz.parse::<usize>().ok())
.and_then(|tz_idx| app.session.config.timezones.get(tz_idx)) .and_then(|tz_idx| app.session.config.timezones.get(tz_idx))
.and_then(|tz_name| { .and_then(|tz_name| {
@@ -152,7 +152,7 @@ pub(super) fn setup_custom_label<S>(
}; };
let tz_str = attribs let tz_str = attribs
.get_value("timezone") .get_value("_timezone")
.and_then(|tz| tz.parse::<usize>().ok()) .and_then(|tz| tz.parse::<usize>().ok())
.and_then(|tz_idx| app.session.config.timezones.get(tz_idx)); .and_then(|tz_idx| app.session.config.timezones.get(tz_idx));