, label: Widget, text: &'stati
}
impl TestbedGeneric {
- pub fn new(assets: Box
) -> anyhow::Result {
- const XML_PATH: AssetPath = AssetPath::BuiltIn("gui/various_widgets.xml");
+ fn doc_params(globals: &WguiGlobals, extra: ParseDocumentExtra) -> ParseDocumentParams {
+ ParseDocumentParams {
+ globals: globals.clone(),
+ path: AssetPath::BuiltIn("gui/various_widgets.xml"),
+ extra,
+ }
+ }
+ pub fn new(assets: Box) -> anyhow::Result {
let globals = WguiGlobals::new(
assets,
wgui::globals::Defaults::default(),
@@ -117,11 +123,7 @@ impl TestbedGeneric {
};
let (layout, state) = wgui::parser::new_layout_from_assets(
- &ParseDocumentParams {
- globals: globals.clone(),
- path: XML_PATH,
- extra,
- },
+ &TestbedGeneric::doc_params(&globals, extra),
&LayoutParams {
resize_to_parent: true,
},
@@ -254,25 +256,15 @@ impl TestbedGeneric {
data: &mut Data,
position: Vec2,
) -> anyhow::Result<()> {
- data.context_menu.open(context_menu::OpenParams {
+ data.state.instantiate_context_menu(
+ Some(Rc::new(move |custom_attribs| {
+ log::info!("custom attribs {:?}", custom_attribs.pairs);
+ })),
+ "my_context_menu",
+ &mut self.layout,
+ &mut data.context_menu,
position,
- data: context_menu::Blueprint {
- cells: vec![
- context_menu::Cell {
- title: Translation::from_raw_text("Options"),
- action_name: "options".into(),
- },
- context_menu::Cell {
- title: Translation::from_raw_text("Exit software"),
- action_name: "exit".into(),
- },
- context_menu::Cell {
- title: Translation::from_raw_text("Restart software"),
- action_name: "restart".into(),
- },
- ],
- },
- });
+ )?;
Ok(())
}
diff --git a/wgui/src/parser/mod.rs b/wgui/src/parser/mod.rs
index 0e3625a0..08a56cb9 100644
--- a/wgui/src/parser/mod.rs
+++ b/wgui/src/parser/mod.rs
@@ -14,6 +14,7 @@ use crate::{
components::{Component, ComponentWeak},
drawing::{self},
globals::WguiGlobals,
+ i18n::Translation,
layout::{Layout, LayoutParams, LayoutState, Widget, WidgetID, WidgetMap, WidgetPair},
log::LogErr,
parser::{
@@ -28,8 +29,10 @@ use crate::{
widget_sprite::parse_widget_sprite,
},
widget::ConstructEssentials,
+ windowing::context_menu,
};
use anyhow::Context;
+use glam::Vec2;
use ouroboros::self_referencing;
use smallvec::SmallVec;
use std::{cell::RefMut, collections::HashMap, path::Path, rc::Rc};
@@ -256,6 +259,73 @@ impl ParserState {
self.data.take_results_from(&mut data_local);
Ok(())
}
+
+ pub fn instantiate_context_menu(
+ &mut self,
+ on_custom_attribs: Option,
+ template_name: &str,
+ layout: &mut Layout,
+ context_menu: &mut context_menu::ContextMenu,
+ position: Vec2,
+ ) -> anyhow::Result<()> {
+ let Some(template) = self.data.templates.get(template_name) else {
+ anyhow::bail!("no template named \"{template_name}\" found");
+ };
+
+ let doc = template.node_document.borrow_doc();
+ let node = doc.get_node(template.node).context("node not found")?;
+ let el_context_menu = node.first_element_child().context("child not found")?;
+ let tag_name = el_context_menu.tag_name().name();
+ if tag_name != "context_menu" {
+ anyhow::bail!("expected tag, got <{tag_name}>");
+ }
+
+ let mut cells = Vec::::new();
+
+ for child in el_context_menu.children() {
+ match child.tag_name().name() {
+ "" => {}
+ "cell" => {
+ let mut title: Option = None;
+ let mut action_name: Option> = None;
+ let mut attribs = Vec::::new();
+
+ for attrib in child.attributes() {
+ let (key, value) = (attrib.name(), attrib.value());
+ match key {
+ "text" => title = Some(Translation::from_raw_text(value)),
+ "translation" => title = Some(Translation::from_translation_key(value)),
+ "action" => action_name = Some(value.into()),
+ other => {
+ if !other.starts_with('_') {
+ anyhow::bail!("unexpected \"{other}\" attribute");
+ }
+ attribs.push(AttribPair::new(key, value));
+ }
+ }
+ }
+
+ let title = title.context("No text/translation provided")?;
+ cells.push(context_menu::Cell {
+ title,
+ action_name,
+ attribs,
+ });
+ }
+ other => {
+ anyhow::bail!("unexpected <{other}> tag");
+ }
+ }
+ }
+
+ context_menu.open(context_menu::OpenParams {
+ data: context_menu::Blueprint { cells },
+ on_custom_attribs,
+ position,
+ });
+
+ Ok(())
+ }
}
// convenience wrapper functions for `data`
@@ -549,7 +619,7 @@ fn parse_widget_other_internal(
let template_node = doc
.borrow_doc()
.get_node(template.node)
- .ok_or_else(|| anyhow::anyhow!("template node invalid"))?;
+ .context("template node invalid")?;
parse_children(&template_file, ctx, template_node, parent_id)?;
@@ -691,7 +761,12 @@ pub fn replace_vars(input: &str, vars: &HashMap, Rc>) -> Rc {
}
#[allow(clippy::manual_strip)]
-fn process_attrib<'a>(file: &'a ParserFile, ctx: &'a ParserContext, key: &str, value: &str) -> AttribPair {
+fn process_attrib(
+ template_parameters: &HashMap, Rc>,
+ ctx: &ParserContext,
+ key: &str,
+ value: &str,
+) -> AttribPair {
if value.starts_with('~') {
let name = &value[1..];
@@ -700,7 +775,7 @@ fn process_attrib<'a>(file: &'a ParserFile, ctx: &'a ParserContext, key: &str, v
None => AttribPair::new(key, "undefined"),
}
} else {
- AttribPair::new(key, replace_vars(value, &file.template_parameters))
+ AttribPair::new(key, replace_vars(value, template_parameters))
}
}
@@ -731,13 +806,13 @@ fn process_attribs<'a>(
if key == "macro" {
if let Some(macro_attrib) = ctx.get_macro_attrib(value) {
for (macro_key, macro_value) in ¯o_attrib.attribs {
- res.push(process_attrib(file, ctx, macro_key, macro_value));
+ res.push(process_attrib(&file.template_parameters, ctx, macro_key, macro_value));
}
} else {
log::warn!("requested macro named \"{value}\" not found!");
}
} else {
- res.push(process_attrib(file, ctx, key, value));
+ res.push(process_attrib(&file.template_parameters, ctx, key, value));
}
}
@@ -994,7 +1069,7 @@ fn create_default_context<'a>(
}
}
-#[derive(Clone)]
+#[derive(Debug, Clone)]
pub struct AttribPair {
pub attrib: Rc,
pub value: Rc,
@@ -1157,7 +1232,7 @@ fn parse_document_root(
.document
.borrow_doc()
.get_node(node_layout)
- .ok_or_else(|| anyhow::anyhow!("layout node not found"))?;
+ .context("layout node not found")?;
for child_node in node_layout.children() {
match child_node.tag_name().name() {
@@ -1165,6 +1240,7 @@ fn parse_document_root(
"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),
+ "blueprint" => parse_tag_template(file, ctx, child_node),
"macro" => parse_tag_macro(file, ctx, child_node),
_ => {}
}
diff --git a/wgui/src/windowing/context_menu.rs b/wgui/src/windowing/context_menu.rs
index c8b64323..0b5f6b3c 100644
--- a/wgui/src/windowing/context_menu.rs
+++ b/wgui/src/windowing/context_menu.rs
@@ -4,8 +4,7 @@ use glam::Vec2;
use crate::{
assets::AssetPath,
- components::button::ComponentButton,
- event::CallbackDataCommon,
+ components::{ComponentTrait, button::ComponentButton},
globals::WguiGlobals,
i18n::Translation,
layout::Layout,
@@ -16,26 +15,23 @@ use crate::{
pub struct Cell {
pub title: Translation,
- pub action_name: Rc,
+ pub action_name: Option>,
+ pub attribs: Vec,
}
pub struct Blueprint {
pub cells: Vec,
}
-pub struct ContextMenuAction<'a> {
- pub common: &'a mut CallbackDataCommon<'a>,
- pub name: Rc, // action name
-}
-
pub struct OpenParams {
pub position: Vec2,
pub data: Blueprint,
+ pub on_custom_attribs: Option,
}
#[derive(Clone)]
enum Task {
- ActionClicked(Rc),
+ ActionClicked(Option>),
}
#[derive(Default)]
@@ -67,7 +63,7 @@ impl ContextMenu {
self.window.close();
}
- fn open_process(&mut self, params: &OpenParams, layout: &mut Layout) -> anyhow::Result<()> {
+ fn open_process(&mut self, params: &mut OpenParams, layout: &mut Layout) -> anyhow::Result<()> {
let globals = layout.state.globals.clone();
self.window.open(&mut WguiWindowParams {
@@ -93,10 +89,20 @@ impl ContextMenu {
let data_cell = state.parse_template(&doc_params(&globals), "Cell", layout, id_buttons, par)?;
let button = data_cell.fetch_component_as::("button")?;
+ let button_id = button.base().get_id();
self
.tasks
.handle_button(&button, Task::ActionClicked(cell.action_name.clone()));
+ if let Some(c) = &mut params.on_custom_attribs {
+ (*c)(parser::CustomAttribsInfo {
+ pairs: &cell.attribs,
+ parent_id: id_buttons,
+ widget_id: button_id,
+ widgets: &layout.state.widgets,
+ });
+ }
+
if idx < params.data.cells.len() - 1 {
state.parse_template(
&doc_params(&globals),
@@ -112,8 +118,8 @@ impl ContextMenu {
}
pub fn tick(&mut self, layout: &mut Layout) -> anyhow::Result {
- if let Some(p) = self.pending_open.take() {
- self.open_process(&p, layout)?;
+ if let Some(mut p) = self.pending_open.take() {
+ self.open_process(&mut p, layout)?;
}
let mut result = TickResult::default();
@@ -121,7 +127,7 @@ impl ContextMenu {
for task in self.tasks.drain() {
match task {
Task::ActionClicked(action_name) => {
- result.action_name = Some(action_name);
+ result.action_name = action_name;
self.close();
}
}
|