diff --git a/uidev/assets/gui/various_widgets.xml b/uidev/assets/gui/various_widgets.xml
index 87e9293..1f86bba 100644
--- a/uidev/assets/gui/various_widgets.xml
+++ b/uidev/assets/gui/various_widgets.xml
@@ -23,6 +23,13 @@
+
+
+
+
+
+
+
diff --git a/uidev/src/testbed/testbed_generic.rs b/uidev/src/testbed/testbed_generic.rs
index cd2d338..9280e6c 100644
--- a/uidev/src/testbed/testbed_generic.rs
+++ b/uidev/src/testbed/testbed_generic.rs
@@ -8,12 +8,13 @@ use wgui::{
button::{ButtonClickCallback, ComponentButton},
checkbox::ComponentCheckbox,
},
+ drawing::Color,
event::EventListenerCollection,
globals::WguiGlobals,
i18n::Translation,
layout::{Layout, Widget},
- parser::{ParseDocumentParams, ParserState},
- widget::label::WidgetLabel,
+ parser::{ParseDocumentExtra, ParseDocumentParams, ParserState},
+ widget::{label::WidgetLabel, rectangle::WidgetRectangle},
};
pub struct TestbedGeneric {
@@ -58,12 +59,27 @@ impl TestbedGeneric {
let globals = WguiGlobals::new(Box::new(assets::Asset {}))?;
+ let extra = ParseDocumentExtra {
+ on_custom_attrib: Some(Box::new(move |par| {
+ if par.attrib == "my_custom" {
+ let mut rect = par.get_widget_as::().unwrap();
+ rect.params.color = match par.value {
+ "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),
+ _ => Color::new(1.0, 1.0, 1.0, 1.0),
+ }
+ }
+ })),
+ dev_mode: false,
+ };
+
let (layout, state) = wgui::parser::new_layout_from_assets(
listeners,
&ParseDocumentParams {
globals,
path: XML_PATH,
- extra: Default::default(),
+ extra,
},
)?;
diff --git a/wgui/src/components/button.rs b/wgui/src/components/button.rs
index 0bfbc62..12ef640 100644
--- a/wgui/src/components/button.rs
+++ b/wgui/src/components/button.rs
@@ -246,7 +246,7 @@ pub fn construct(
listeners: &mut EventListenerCollection,
parent: WidgetID,
params: Params,
-) -> anyhow::Result> {
+) -> anyhow::Result<(WidgetID, Rc)> {
let mut style = params.style;
// force-override style
@@ -256,7 +256,7 @@ pub fn construct(
let globals = layout.state.globals.clone();
- let (id_rect, _) = layout.add_child(
+ let (id_root, _) = layout.add_child(
parent,
WidgetRectangle::create(WidgetRectangleParams {
color: params.color,
@@ -268,6 +268,7 @@ pub fn construct(
}),
style,
)?;
+ let id_rect = id_root;
let light_text = (params.color.r + params.color.g + params.color.b) < 1.5;
@@ -316,5 +317,5 @@ pub fn construct(
let button = Rc::new(ComponentButton { base, data, state });
layout.defer_component_init(Component(button.clone()));
- Ok(button)
+ Ok((id_root, button))
}
diff --git a/wgui/src/components/checkbox.rs b/wgui/src/components/checkbox.rs
index 0439d1f..b0b087f 100644
--- a/wgui/src/components/checkbox.rs
+++ b/wgui/src/components/checkbox.rs
@@ -264,7 +264,7 @@ pub fn construct(
listeners: &mut EventListenerCollection,
parent: WidgetID,
params: Params,
-) -> anyhow::Result> {
+) -> anyhow::Result<(WidgetID, Rc)> {
let mut style = params.style;
// force-override style
@@ -282,7 +282,7 @@ pub fn construct(
let globals = layout.state.globals.clone();
- let (id_container, _) = layout.add_child(
+ let (id_root, _) = layout.add_child(
parent,
WidgetRectangle::create(WidgetRectangleParams {
color: Color::new(1.0, 1.0, 1.0, 0.0),
@@ -292,6 +292,7 @@ pub fn construct(
}),
style,
)?;
+ let id_container = id_root;
let box_size = taffy::Size {
width: length(params.box_size),
@@ -375,5 +376,5 @@ pub fn construct(
let checkbox = Rc::new(ComponentCheckbox { base, data, state });
layout.defer_component_init(Component(checkbox.clone()));
- Ok(checkbox)
+ Ok((id_root, checkbox))
}
diff --git a/wgui/src/components/slider.rs b/wgui/src/components/slider.rs
index 12e911c..4aaf441 100644
--- a/wgui/src/components/slider.rs
+++ b/wgui/src/components/slider.rs
@@ -322,13 +322,14 @@ pub fn construct(
listeners: &mut EventListenerCollection,
parent: WidgetID,
params: Params,
-) -> anyhow::Result> {
+) -> anyhow::Result<(WidgetID, Rc)> {
let mut style = params.style;
style.position = taffy::Position::Relative;
style.min_size = style.size;
style.max_size = style.size;
- let (body_id, slider_body_node) = layout.add_child(parent, WidgetDiv::create(), style)?;
+ let (root_id, slider_body_node) = layout.add_child(parent, WidgetDiv::create(), style)?;
+ let body_id = root_id;
let (_background_id, _) = layout.add_child(
body_id,
@@ -432,5 +433,5 @@ pub fn construct(
let slider = Rc::new(ComponentSlider { base, data, state });
layout.defer_component_init(Component(slider.clone()));
- Ok(slider)
+ Ok((root_id, slider))
}
diff --git a/wgui/src/parser/component_button.rs b/wgui/src/parser/component_button.rs
index c38929a..9de06ff 100644
--- a/wgui/src/parser/component_button.rs
+++ b/wgui/src/parser/component_button.rs
@@ -15,7 +15,7 @@ pub fn parse_component_button<'a, U1, U2>(
ctx: &mut ParserContext,
node: roxmltree::Node<'a, 'a>,
parent_id: WidgetID,
-) -> anyhow::Result<()> {
+) -> anyhow::Result {
let mut color = Color::new(1.0, 1.0, 1.0, 1.0);
let mut border_color: Option = None;
let mut round = WLength::Units(4.0);
@@ -55,7 +55,7 @@ pub fn parse_component_button<'a, U1, U2>(
));
}
- let component = button::construct(
+ let (new_id, component) = button::construct(
ctx.layout,
ctx.listeners,
parent_id,
@@ -71,5 +71,5 @@ pub fn parse_component_button<'a, U1, U2>(
process_component(file, ctx, node, Component(component));
- Ok(())
+ Ok(new_id)
}
diff --git a/wgui/src/parser/component_checkbox.rs b/wgui/src/parser/component_checkbox.rs
index 1039c9c..86e1317 100644
--- a/wgui/src/parser/component_checkbox.rs
+++ b/wgui/src/parser/component_checkbox.rs
@@ -13,7 +13,7 @@ pub fn parse_component_checkbox<'a, U1, U2>(
ctx: &mut ParserContext,
node: roxmltree::Node<'a, 'a>,
parent_id: WidgetID,
-) -> anyhow::Result<()> {
+) -> anyhow::Result {
let mut box_size = 24.0;
let mut translation = Translation::default();
let mut checked = 0;
@@ -39,7 +39,7 @@ pub fn parse_component_checkbox<'a, U1, U2>(
}
}
- let component = checkbox::construct(
+ let (new_id, component) = checkbox::construct(
ctx.layout,
ctx.listeners,
parent_id,
@@ -53,5 +53,5 @@ pub fn parse_component_checkbox<'a, U1, U2>(
process_component(file, ctx, node, Component(component));
- Ok(())
+ Ok(new_id)
}
diff --git a/wgui/src/parser/component_slider.rs b/wgui/src/parser/component_slider.rs
index de96de1..9826e00 100644
--- a/wgui/src/parser/component_slider.rs
+++ b/wgui/src/parser/component_slider.rs
@@ -11,7 +11,7 @@ pub fn parse_component_slider<'a, U1, U2>(
ctx: &mut ParserContext,
node: roxmltree::Node<'a, 'a>,
parent_id: WidgetID,
-) -> anyhow::Result<()> {
+) -> anyhow::Result {
let mut min_value = 0.0;
let mut max_value = 1.0;
let mut initial_value = 0.5;
@@ -34,7 +34,7 @@ pub fn parse_component_slider<'a, U1, U2>(
}
}
- let component = slider::construct(
+ let (new_id, component) = slider::construct(
ctx.layout,
ctx.listeners,
parent_id,
@@ -50,5 +50,5 @@ pub fn parse_component_slider<'a, U1, U2>(
process_component(file, ctx, node, Component(component));
- Ok(())
+ Ok(new_id)
}
diff --git a/wgui/src/parser/mod.rs b/wgui/src/parser/mod.rs
index 3547e09..3eb2d14 100644
--- a/wgui/src/parser/mod.rs
+++ b/wgui/src/parser/mod.rs
@@ -13,7 +13,7 @@ use crate::{
drawing::{self},
event::EventListenerCollection,
globals::WguiGlobals,
- layout::{Layout, LayoutState, Widget, WidgetID},
+ layout::{Layout, LayoutState, Widget, WidgetID, WidgetMap},
parser::{
component_button::parse_component_button, component_checkbox::parse_component_checkbox,
component_slider::parse_component_slider, widget_div::parse_widget_div,
@@ -23,6 +23,7 @@ use crate::{
};
use ouroboros::self_referencing;
use std::{
+ cell::RefMut,
collections::HashMap,
path::{Path, PathBuf},
rc::Rc,
@@ -619,59 +620,96 @@ fn parse_widget_universal<'a, U1, U2>(
}
}
-fn parse_children<'a, U1, U2>(
+fn parse_child<'a, U1, U2>(
file: &ParserFile,
ctx: &mut ParserContext,
- node: roxmltree::Node<'a, 'a>,
+ parent_node: roxmltree::Node<'a, 'a>,
+ child_node: roxmltree::Node<'a, 'a>,
parent_id: WidgetID,
) -> anyhow::Result<()> {
- for child_node in node.children() {
- match node.attribute("ignore_in_mode") {
- Some("dev") => {
- if !ctx.doc_params.extra.dev_mode {
- continue;
- }
+ match parent_node.attribute("ignore_in_mode") {
+ Some("dev") => {
+ if !ctx.doc_params.extra.dev_mode {
+ return Ok(()); // do not parse
}
- Some("live") => {
- if ctx.doc_params.extra.dev_mode {
- continue;
- }
- }
- Some(s) => print_invalid_attrib("ignore_in_mode", s),
- _ => {}
}
+ Some("live") => {
+ if ctx.doc_params.extra.dev_mode {
+ return Ok(()); // do not parse
+ }
+ }
+ Some(s) => print_invalid_attrib("ignore_in_mode", s),
+ _ => {}
+ }
- match child_node.tag_name().name() {
- "include" => {
- parse_tag_include(file, ctx, child_node, parent_id)?;
- }
- "div" => {
- parse_widget_div(file, ctx, child_node, parent_id)?;
- }
- "rectangle" => {
- parse_widget_rectangle(file, ctx, child_node, parent_id)?;
- }
- "label" => {
- parse_widget_label(file, ctx, child_node, parent_id)?;
- }
- "sprite" => {
- parse_widget_sprite(file, ctx, child_node, parent_id)?;
- }
- "button" => {
- parse_component_button(file, ctx, child_node, parent_id)?;
- }
- "slider" => {
- parse_component_slider(file, ctx, child_node, parent_id)?;
- }
- "check_box" => {
- parse_component_checkbox(file, ctx, child_node, parent_id)?;
- }
- "" => { /* ignore */ }
- other_tag_name => {
- parse_widget_other(other_tag_name, file, ctx, child_node, parent_id)?;
+ let mut new_widget_id: Option = None;
+
+ match child_node.tag_name().name() {
+ "include" => {
+ parse_tag_include(file, ctx, child_node, parent_id)?;
+ }
+ "div" => {
+ new_widget_id = Some(parse_widget_div(file, ctx, child_node, parent_id)?);
+ }
+ "rectangle" => {
+ new_widget_id = Some(parse_widget_rectangle(file, ctx, child_node, parent_id)?);
+ }
+ "label" => {
+ new_widget_id = Some(parse_widget_label(file, ctx, child_node, parent_id)?);
+ }
+ "sprite" => {
+ new_widget_id = Some(parse_widget_sprite(file, ctx, child_node, parent_id)?);
+ }
+ "button" => {
+ new_widget_id = Some(parse_component_button(file, ctx, child_node, parent_id)?);
+ }
+ "slider" => {
+ new_widget_id = Some(parse_component_slider(file, ctx, child_node, parent_id)?);
+ }
+ "check_box" => {
+ new_widget_id = Some(parse_component_checkbox(file, ctx, child_node, parent_id)?);
+ }
+ "" => { /* ignore */ }
+ other_tag_name => {
+ parse_widget_other(other_tag_name, file, ctx, child_node, parent_id)?;
+ }
+ }
+
+ // check for custom attributes (if the callback is set)
+ if let Some(widget_id) = new_widget_id {
+ if let Some(on_custom_attrib) = &ctx.doc_params.extra.on_custom_attrib {
+ for attrib in child_node.attributes() {
+ let attr_name = attrib.name();
+ if !attr_name.starts_with('_') || attr_name.is_empty() {
+ continue;
+ }
+
+ let attr_without_prefix = &attr_name[1..]; // safe
+
+ on_custom_attrib(CustomAttribInfo {
+ widgets: &ctx.layout.state.widgets,
+ parent_id,
+ widget_id,
+ attrib: attr_without_prefix,
+ value: attrib.value(),
+ });
}
}
}
+
+ Ok(())
+}
+
+fn parse_children<'a, U1, U2>(
+ file: &ParserFile,
+ ctx: &mut ParserContext,
+ parent_node: roxmltree::Node<'a, 'a>,
+ parent_id: WidgetID,
+) -> anyhow::Result<()> {
+ for child_node in parent_node.children() {
+ parse_child(file, ctx, parent_node, child_node, parent_id)?;
+ }
+
Ok(())
}
@@ -693,11 +731,30 @@ fn create_default_context<'a, U1, U2>(
}
}
-pub struct UnusedAttribInfo {}
+pub struct CustomAttribInfo<'a> {
+ pub parent_id: WidgetID,
+ pub widget_id: WidgetID,
+ pub widgets: &'a WidgetMap,
+ pub attrib: &'a str, // without _ at the beginning
+ pub value: &'a str,
+}
+
+// helper functions
+impl CustomAttribInfo<'_> {
+ pub fn get_widget(&self) -> Option<&Widget> {
+ self.widgets.get(self.widget_id)
+ }
+
+ pub fn get_widget_as(&self) -> Option> {
+ Some(self.widgets.get(self.widget_id)?.get_as_mut::())
+ }
+}
+
+pub type OnCustomAttribFunc = Box;
#[derive(Default)]
pub struct ParseDocumentExtra {
- //pub on_unused_attrib: Option>,
+ pub on_custom_attrib: Option, // all attributes with '_' character prepended
pub dev_mode: bool,
}
diff --git a/wgui/src/parser/widget_div.rs b/wgui/src/parser/widget_div.rs
index bc74cc2..3939820 100644
--- a/wgui/src/parser/widget_div.rs
+++ b/wgui/src/parser/widget_div.rs
@@ -12,7 +12,7 @@ pub fn parse_widget_div<'a, U1, U2>(
ctx: &mut ParserContext,
node: roxmltree::Node<'a, 'a>,
parent_id: WidgetID,
-) -> anyhow::Result<()> {
+) -> anyhow::Result {
let attribs: Vec<_> = iter_attribs(file, ctx, &node, false).collect();
let style = parse_style(&attribs);
@@ -23,5 +23,5 @@ pub fn parse_widget_div<'a, U1, U2>(
parse_widget_universal(file, ctx, node, new_id);
parse_children(file, ctx, node, new_id)?;
- Ok(())
+ Ok(new_id)
}
diff --git a/wgui/src/parser/widget_label.rs b/wgui/src/parser/widget_label.rs
index c8e4bf4..1a1eb30 100644
--- a/wgui/src/parser/widget_label.rs
+++ b/wgui/src/parser/widget_label.rs
@@ -13,7 +13,7 @@ pub fn parse_widget_label<'a, U1, U2>(
ctx: &mut ParserContext,
node: roxmltree::Node<'a, 'a>,
parent_id: WidgetID,
-) -> anyhow::Result<()> {
+) -> anyhow::Result {
let mut params = WidgetLabelParams::default();
let attribs: Vec<_> = iter_attribs(file, ctx, &node, false).collect();
@@ -41,5 +41,5 @@ pub fn parse_widget_label<'a, U1, U2>(
parse_widget_universal(file, ctx, node, new_id);
parse_children(file, ctx, node, new_id)?;
- Ok(())
+ Ok(new_id)
}
diff --git a/wgui/src/parser/widget_rectangle.rs b/wgui/src/parser/widget_rectangle.rs
index 2433f20..51f5bdb 100644
--- a/wgui/src/parser/widget_rectangle.rs
+++ b/wgui/src/parser/widget_rectangle.rs
@@ -14,7 +14,7 @@ pub fn parse_widget_rectangle<'a, U1, U2>(
ctx: &mut ParserContext,
node: roxmltree::Node<'a, 'a>,
parent_id: WidgetID,
-) -> anyhow::Result<()> {
+) -> anyhow::Result {
let mut params = WidgetRectangleParams::default();
let attribs: Vec<_> = iter_attribs(file, ctx, &node, false).collect();
let style = parse_style(&attribs);
@@ -62,5 +62,5 @@ pub fn parse_widget_rectangle<'a, U1, U2>(
parse_widget_universal(file, ctx, node, new_id);
parse_children(file, ctx, node, new_id)?;
- Ok(())
+ Ok(new_id)
}
diff --git a/wgui/src/parser/widget_sprite.rs b/wgui/src/parser/widget_sprite.rs
index 4ec28aa..cb18c91 100644
--- a/wgui/src/parser/widget_sprite.rs
+++ b/wgui/src/parser/widget_sprite.rs
@@ -15,7 +15,7 @@ pub fn parse_widget_sprite<'a, U1, U2>(
ctx: &mut ParserContext,
node: roxmltree::Node<'a, 'a>,
parent_id: WidgetID,
-) -> anyhow::Result<()> {
+) -> anyhow::Result {
let mut params = WidgetSpriteParams::default();
let attribs: Vec<_> = iter_attribs(file, ctx, &node, false).collect();
let style = parse_style(&attribs);
@@ -62,5 +62,5 @@ pub fn parse_widget_sprite<'a, U1, U2>(
parse_widget_universal(file, ctx, node, new_id);
parse_children(file, ctx, node, new_id)?;
- Ok(())
+ Ok(new_id)
}