wgui: custom attribs
This commit is contained in:
@@ -23,6 +23,13 @@
|
||||
<check_box text="i'm tall" box_size="32" />
|
||||
<check_box text="i'm checked by default" checked="1" />
|
||||
</div>
|
||||
|
||||
<label text="custom attrib test, you should see three rectangles below, each of them in R, G and B" />
|
||||
<div flex_direction="row" gap="8">
|
||||
<rectangle _my_custom="red" width="16" height="16" />
|
||||
<rectangle _my_custom="green" width="16" height="16" />
|
||||
<rectangle _my_custom="blue" width="16" height="16" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div flex_direction="row" gap="16">
|
||||
|
||||
@@ -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::<WidgetRectangle>().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,
|
||||
},
|
||||
)?;
|
||||
|
||||
|
||||
@@ -246,7 +246,7 @@ pub fn construct<U1, U2>(
|
||||
listeners: &mut EventListenerCollection<U1, U2>,
|
||||
parent: WidgetID,
|
||||
params: Params,
|
||||
) -> anyhow::Result<Rc<ComponentButton>> {
|
||||
) -> anyhow::Result<(WidgetID, Rc<ComponentButton>)> {
|
||||
let mut style = params.style;
|
||||
|
||||
// force-override style
|
||||
@@ -256,7 +256,7 @@ pub fn construct<U1, U2>(
|
||||
|
||||
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<U1, U2>(
|
||||
}),
|
||||
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<U1, U2>(
|
||||
let button = Rc::new(ComponentButton { base, data, state });
|
||||
|
||||
layout.defer_component_init(Component(button.clone()));
|
||||
Ok(button)
|
||||
Ok((id_root, button))
|
||||
}
|
||||
|
||||
@@ -264,7 +264,7 @@ pub fn construct<U1, U2>(
|
||||
listeners: &mut EventListenerCollection<U1, U2>,
|
||||
parent: WidgetID,
|
||||
params: Params,
|
||||
) -> anyhow::Result<Rc<ComponentCheckbox>> {
|
||||
) -> anyhow::Result<(WidgetID, Rc<ComponentCheckbox>)> {
|
||||
let mut style = params.style;
|
||||
|
||||
// force-override style
|
||||
@@ -282,7 +282,7 @@ pub fn construct<U1, U2>(
|
||||
|
||||
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<U1, U2>(
|
||||
}),
|
||||
style,
|
||||
)?;
|
||||
let id_container = id_root;
|
||||
|
||||
let box_size = taffy::Size {
|
||||
width: length(params.box_size),
|
||||
@@ -375,5 +376,5 @@ pub fn construct<U1, U2>(
|
||||
let checkbox = Rc::new(ComponentCheckbox { base, data, state });
|
||||
|
||||
layout.defer_component_init(Component(checkbox.clone()));
|
||||
Ok(checkbox)
|
||||
Ok((id_root, checkbox))
|
||||
}
|
||||
|
||||
@@ -322,13 +322,14 @@ pub fn construct<U1, U2>(
|
||||
listeners: &mut EventListenerCollection<U1, U2>,
|
||||
parent: WidgetID,
|
||||
params: Params,
|
||||
) -> anyhow::Result<Rc<ComponentSlider>> {
|
||||
) -> anyhow::Result<(WidgetID, Rc<ComponentSlider>)> {
|
||||
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<U1, U2>(
|
||||
let slider = Rc::new(ComponentSlider { base, data, state });
|
||||
|
||||
layout.defer_component_init(Component(slider.clone()));
|
||||
Ok(slider)
|
||||
Ok((root_id, slider))
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@ pub fn parse_component_button<'a, U1, U2>(
|
||||
ctx: &mut ParserContext<U1, U2>,
|
||||
node: roxmltree::Node<'a, 'a>,
|
||||
parent_id: WidgetID,
|
||||
) -> anyhow::Result<()> {
|
||||
) -> anyhow::Result<WidgetID> {
|
||||
let mut color = Color::new(1.0, 1.0, 1.0, 1.0);
|
||||
let mut border_color: Option<Color> = 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)
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ pub fn parse_component_checkbox<'a, U1, U2>(
|
||||
ctx: &mut ParserContext<U1, U2>,
|
||||
node: roxmltree::Node<'a, 'a>,
|
||||
parent_id: WidgetID,
|
||||
) -> anyhow::Result<()> {
|
||||
) -> anyhow::Result<WidgetID> {
|
||||
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)
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ pub fn parse_component_slider<'a, U1, U2>(
|
||||
ctx: &mut ParserContext<U1, U2>,
|
||||
node: roxmltree::Node<'a, 'a>,
|
||||
parent_id: WidgetID,
|
||||
) -> anyhow::Result<()> {
|
||||
) -> anyhow::Result<WidgetID> {
|
||||
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)
|
||||
}
|
||||
|
||||
@@ -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<U1, U2>,
|
||||
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<WidgetID> = 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<U1, U2>,
|
||||
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<T: 'static>(&self) -> Option<RefMut<T>> {
|
||||
Some(self.widgets.get(self.widget_id)?.get_as_mut::<T>())
|
||||
}
|
||||
}
|
||||
|
||||
pub type OnCustomAttribFunc = Box<dyn Fn(CustomAttribInfo)>;
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct ParseDocumentExtra {
|
||||
//pub on_unused_attrib: Option<Box<dyn Fn(UnusedAttribInfo)>>,
|
||||
pub on_custom_attrib: Option<OnCustomAttribFunc>, // all attributes with '_' character prepended
|
||||
pub dev_mode: bool,
|
||||
}
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ pub fn parse_widget_div<'a, U1, U2>(
|
||||
ctx: &mut ParserContext<U1, U2>,
|
||||
node: roxmltree::Node<'a, 'a>,
|
||||
parent_id: WidgetID,
|
||||
) -> anyhow::Result<()> {
|
||||
) -> anyhow::Result<WidgetID> {
|
||||
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)
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ pub fn parse_widget_label<'a, U1, U2>(
|
||||
ctx: &mut ParserContext<U1, U2>,
|
||||
node: roxmltree::Node<'a, 'a>,
|
||||
parent_id: WidgetID,
|
||||
) -> anyhow::Result<()> {
|
||||
) -> anyhow::Result<WidgetID> {
|
||||
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)
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@ pub fn parse_widget_rectangle<'a, U1, U2>(
|
||||
ctx: &mut ParserContext<U1, U2>,
|
||||
node: roxmltree::Node<'a, 'a>,
|
||||
parent_id: WidgetID,
|
||||
) -> anyhow::Result<()> {
|
||||
) -> anyhow::Result<WidgetID> {
|
||||
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)
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@ pub fn parse_widget_sprite<'a, U1, U2>(
|
||||
ctx: &mut ParserContext<U1, U2>,
|
||||
node: roxmltree::Node<'a, 'a>,
|
||||
parent_id: WidgetID,
|
||||
) -> anyhow::Result<()> {
|
||||
) -> anyhow::Result<WidgetID> {
|
||||
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)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user