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 wgui::{
components::{
Component,
button::{ButtonClickCallback, ComponentButton},
checkbox::ComponentCheckbox,
Component,
},
drawing::Color,
event::EventListenerCollection,
@@ -66,17 +66,17 @@ impl TestbedGeneric {
let extra = ParseDocumentExtra {
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;
};
let Some(mult_value) = par.get_value("mult") else {
let Some(mult_value) = par.get_value("_mult") else {
return;
};
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),
"green" => Color::new(0.0, 1.0, 0.0, 1.0),
"blue" => Color::new(0.0, 0.0, 1.0, 1.0),

View File

@@ -1,11 +1,12 @@
use crate::{
components::{Component, button},
components::{button, Component},
drawing::Color,
i18n::Translation,
layout::WidgetID,
parser::{
ParserContext, ParserFile, iter_attribs, parse_children, process_component,
parse_children, process_component,
style::{parse_color_opt, parse_round, parse_style, parse_text_style},
AttribPair, ParserContext, ParserFile,
},
widget::util::WLength,
};
@@ -15,6 +16,7 @@ pub fn parse_component_button<'a, U1, U2>(
ctx: &mut ParserContext<U1, U2>,
node: roxmltree::Node<'a, 'a>,
parent_id: WidgetID,
attribs: &[AttribPair],
) -> anyhow::Result<WidgetID> {
let mut 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 translation: Option<Translation> = None;
let attribs: Vec<_> = iter_attribs(file, ctx, &node, false).collect();
let text_style = parse_text_style(&attribs);
let style = parse_style(&attribs);
let text_style = parse_text_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() {
"text" => {
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)?;
Ok(new_id)

View File

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

View File

@@ -1,32 +1,31 @@
use crate::{
components::{Component, slider},
components::{slider, Component},
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>(
file: &'a ParserFile,
ctx: &mut ParserContext<U1, U2>,
node: roxmltree::Node<'a, 'a>,
parent_id: WidgetID,
attribs: &[AttribPair],
) -> anyhow::Result<WidgetID> {
let mut min_value = 0.0;
let mut max_value = 1.0;
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 {
match key.as_ref() {
for pair in attribs {
let (key, value) = (pair.attrib.as_ref(), pair.value.as_ref());
match key {
"min_value" => {
parse_check_f32(value.as_ref(), &mut min_value);
parse_check_f32(value, &mut min_value);
}
"max_value" => {
parse_check_f32(value.as_ref(), &mut max_value);
parse_check_f32(value, &mut max_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)
}

View File

@@ -441,7 +441,7 @@ fn print_invalid_value(value: &str) {
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 {
print_invalid_value(value);
return None;
@@ -534,15 +534,16 @@ fn parse_widget_other<'a, U1, U2>(
xml_tag_name: &str,
file: &'a ParserFile,
ctx: &mut ParserContext<U1, U2>,
node: roxmltree::Node<'a, 'a>,
parent_id: WidgetID,
attribs: &[AttribPair],
) -> anyhow::Result<()> {
let Some(template) = ctx.get_template(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, 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)
}
@@ -550,17 +551,15 @@ fn parse_widget_other<'a, U1, U2>(
fn parse_tag_include<'a, U1, U2>(
file: &ParserFile,
ctx: &mut ParserContext<U1, U2>,
node: roxmltree::Node<'a, 'a>,
parent_id: WidgetID,
attribs: &[AttribPair],
) -> anyhow::Result<()> {
for attrib in node.attributes() {
let (key, value) = (attrib.name(), attrib.value());
for pair in attribs {
#[allow(clippy::single_match)]
match key {
match pair.attrib.as_ref() {
"src" => {
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_file, node_layout) = get_doc_from_path(ctx, &new_path)?;
@@ -569,7 +568,7 @@ fn parse_tag_include<'a, U1, U2>(
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>,
key: &str,
value: &str,
) -> (Rc<str>, Rc<str>) {
) -> AttribPair {
if value.starts_with('~') {
let name = &value[1..];
(
Rc::from(key),
match ctx.get_var(name) {
Some(name) => name,
None => Rc::from("undefined"),
},
)
match ctx.get_var(name) {
Some(name) => AttribPair::new(key, name.clone()),
None => AttribPair::new(key, "undefined"),
}
} 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,
ctx: &'a ParserContext<U1, U2>,
node: &'a roxmltree::Node<'a, 'a>,
is_tag_macro: bool,
) -> impl Iterator<Item = (/*key*/ Rc<str>, /*value*/ Rc<str>)> + 'a {
let mut res = Vec::<(Rc<str>, Rc<str>)>::new();
) -> Vec<AttribPair> {
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();
return raw_attribs(node);
}
let mut res = vec![];
for attrib in node.attributes() {
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>) {
@@ -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<'_, '_>) {
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 {
match key.as_ref() {
for pair in attribs {
match pair.attrib.as_ref() {
"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<'_, '_>) {
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();
for (key, value) in attribs {
match key.as_ref() {
for pair in attribs {
match pair.attrib.as_ref() {
"name" => {
macro_name = Some(value);
macro_name = Some(pair.value);
}
_ => {
if macro_attribs.insert(key.clone(), value).is_some() {
log::warn!("macro attrib \"{key}\" already defined!");
if macro_attribs.insert(pair.attrib.clone(), pair.value).is_some() {
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>(
file: &'a ParserFile,
ctx: &mut ParserContext<U1, U2>,
node: roxmltree::Node<'a, 'a>,
component: Component,
widget_id: WidgetID,
attribs: &[AttribPair],
) {
let attribs: Vec<_> = iter_attribs(file, ctx, &node, false).collect();
let mut component_id: Option<Rc<str>> = None;
for (key, value) in attribs {
for pair in attribs {
#[allow(clippy::single_match)]
match key.as_ref() {
match pair.attrib.as_ref() {
"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);
}
fn parse_widget_universal<'a, U1, U2>(
file: &'a ParserFile,
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 {
fn parse_widget_universal<'a, U1, U2>(ctx: &mut ParserContext<U1, U2>, widget_id: WidgetID, attribs: &[AttribPair]) {
for pair in attribs {
#[allow(clippy::single_match)]
match key.as_ref() {
match pair.attrib.as_ref() {
"id" => {
// 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;
match child_node.tag_name().name() {
"include" => {
parse_tag_include(file, ctx, child_node, parent_id)?;
parse_tag_include(file, ctx, parent_id, &attribs)?;
}
"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" => {
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" => {
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" => {
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" => {
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" => {
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" => {
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 */ }
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
&& 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() {
let attr_name = attrib.name();
if !attr_name.starts_with('_') || attr_name.is_empty() {
for pair in attribs {
if !pair.attrib.starts_with('_') || pair.attrib.is_empty() {
continue;
}
let attr_without_prefix = &attr_name[1..]; // safe
pairs.push(CustomAttribPair {
attrib: attr_without_prefix,
value: attrib.value(),
});
pairs.push(pair.clone());
}
if !pairs.is_empty() {
@@ -921,16 +906,30 @@ fn create_default_context<'a, U1, U2>(
}
}
pub struct CustomAttribPair<'a> {
pub attrib: &'a str, // without _ at the beginning
pub value: &'a str,
#[derive(Clone)]
pub struct AttribPair {
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 parent_id: WidgetID,
pub widget_id: WidgetID,
pub widgets: &'a WidgetMap,
pub pairs: &'a [CustomAttribPair<'a>],
pub pairs: &'a [AttribPair],
}
// helper functions
@@ -943,11 +942,11 @@ impl CustomAttribsInfo<'_> {
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
for pair in self.pairs {
if pair.attrib == attrib_name {
return Some(pair.value);
if *pair.attrib == *attrib_name {
return Some(pair.value.clone());
}
}
@@ -958,35 +957,23 @@ impl CustomAttribsInfo<'_> {
CustomAttribsInfoOwned {
parent_id: self.parent_id,
widget_id: self.widget_id,
pairs: self
.pairs
.iter()
.map(|p| CustomAttribPairOwned {
attrib: p.attrib.to_string(),
value: p.value.to_string(),
})
.collect(),
pairs: self.pairs.iter().cloned().collect(),
}
}
}
pub struct CustomAttribPairOwned {
pub attrib: String, // without _ at the beginning
pub value: String,
}
pub struct CustomAttribsInfoOwned {
pub parent_id: WidgetID,
pub widget_id: WidgetID,
pub pairs: Vec<CustomAttribPairOwned>,
pub pairs: Vec<AttribPair>,
}
impl CustomAttribsInfoOwned {
pub fn get_value(&self, attrib_name: &str) -> Option<&str> {
// O(n) search, these pairs won't be problematically big anyways
for pair in &self.pairs {
if pair.attrib == attrib_name {
return Some(pair.value.as_str());
if pair.attrib.as_ref() == attrib_name {
return Some(pair.value.as_ref());
}
}
@@ -1091,7 +1078,7 @@ fn parse_document_root<U1, U2>(
#[allow(clippy::single_match)]
match child_node.tag_name().name() {
/* 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),
"template" => parse_tag_template(file, ctx, child_node),
"macro" => parse_tag_macro(file, ctx, child_node),

View File

@@ -1,15 +1,13 @@
use std::rc::Rc;
use taffy::{
AlignContent, AlignItems, AlignSelf, BoxSizing, Display, FlexDirection, FlexWrap, JustifyContent,
JustifySelf, Overflow,
AlignContent, AlignItems, AlignSelf, BoxSizing, Display, FlexDirection, FlexWrap, JustifyContent, JustifySelf,
Overflow,
};
use crate::{
drawing,
parser::{
is_percent, parse_color_hex, parse_f32, parse_percent, parse_size_unit, parse_val,
print_invalid_attrib, print_invalid_value,
is_percent, parse_color_hex, parse_f32, parse_percent, parse_size_unit, parse_val, print_invalid_attrib,
print_invalid_value, AttribPair,
},
renderer_vk::text::{FontWeight, HorizontalAlign, TextStyle},
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();
for (key, value) in attribs {
match key.as_ref() {
for pair in attribs {
let (key, value) = (pair.attrib.as_ref(), pair.value.as_ref());
match key {
"color" => {
if let Some(color) = parse_color_hex(value) {
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::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();
for (key, value) in attribs {
for pair in attribs {
let (key, value) = (pair.attrib.as_ref(), pair.value.as_ref());
match key.as_ref() {
"display" => match value.as_ref() {
"flex" => style.display = Display::Flex,

View File

@@ -1,9 +1,6 @@
use crate::{
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},
widget::div::WidgetDiv,
};
@@ -12,15 +9,13 @@ pub fn parse_widget_div<'a, U1, U2>(
ctx: &mut ParserContext<U1, U2>,
node: roxmltree::Node<'a, 'a>,
parent_id: WidgetID,
attribs: &[AttribPair],
) -> 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
.layout
.add_child(parent_id, WidgetDiv::create(), style)?;
let (new_id, _) = ctx.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)?;
Ok(new_id)

View File

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

View File

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

View File

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

View File

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

View File

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