context menu custom attribs
This commit is contained in:
@@ -9,6 +9,15 @@
|
|||||||
align_self="baseline"
|
align_self="baseline"
|
||||||
align_items="baseline" />
|
align_items="baseline" />
|
||||||
|
|
||||||
|
<blueprint name="my_context_menu">
|
||||||
|
<context_menu>
|
||||||
|
<cell translation="TESTBED.HELLO_WORLD" action="first" _custom1="foo" />
|
||||||
|
<cell text="Second button" action="second" _custom1="bar" />
|
||||||
|
<cell text="Third button" action="third" />
|
||||||
|
<cell text="Foobar test test test" action="foobar" />
|
||||||
|
</context_menu>
|
||||||
|
</blueprint>
|
||||||
|
|
||||||
<elements>
|
<elements>
|
||||||
<rectangle position="absolute" width="100%" height="100%" color="#1e3a3eee" />
|
<rectangle position="absolute" width="100%" height="100%" color="#1e3a3eee" />
|
||||||
<div
|
<div
|
||||||
|
|||||||
@@ -8,9 +8,9 @@ use glam::Vec2;
|
|||||||
use wgui::{
|
use wgui::{
|
||||||
assets::AssetPath,
|
assets::AssetPath,
|
||||||
components::{
|
components::{
|
||||||
|
Component,
|
||||||
button::{ButtonClickCallback, ComponentButton},
|
button::{ButtonClickCallback, ComponentButton},
|
||||||
checkbox::ComponentCheckbox,
|
checkbox::ComponentCheckbox,
|
||||||
Component,
|
|
||||||
},
|
},
|
||||||
drawing::Color,
|
drawing::Color,
|
||||||
event::StyleSetRequest,
|
event::StyleSetRequest,
|
||||||
@@ -79,9 +79,15 @@ fn handle_button_click(button: Rc<ComponentButton>, label: Widget, text: &'stati
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl TestbedGeneric {
|
impl TestbedGeneric {
|
||||||
pub fn new(assets: Box<assets::Asset>) -> anyhow::Result<Self> {
|
fn doc_params(globals: &WguiGlobals, extra: ParseDocumentExtra) -> ParseDocumentParams {
|
||||||
const XML_PATH: AssetPath = AssetPath::BuiltIn("gui/various_widgets.xml");
|
ParseDocumentParams {
|
||||||
|
globals: globals.clone(),
|
||||||
|
path: AssetPath::BuiltIn("gui/various_widgets.xml"),
|
||||||
|
extra,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn new(assets: Box<assets::Asset>) -> anyhow::Result<Self> {
|
||||||
let globals = WguiGlobals::new(
|
let globals = WguiGlobals::new(
|
||||||
assets,
|
assets,
|
||||||
wgui::globals::Defaults::default(),
|
wgui::globals::Defaults::default(),
|
||||||
@@ -117,11 +123,7 @@ impl TestbedGeneric {
|
|||||||
};
|
};
|
||||||
|
|
||||||
let (layout, state) = wgui::parser::new_layout_from_assets(
|
let (layout, state) = wgui::parser::new_layout_from_assets(
|
||||||
&ParseDocumentParams {
|
&TestbedGeneric::doc_params(&globals, extra),
|
||||||
globals: globals.clone(),
|
|
||||||
path: XML_PATH,
|
|
||||||
extra,
|
|
||||||
},
|
|
||||||
&LayoutParams {
|
&LayoutParams {
|
||||||
resize_to_parent: true,
|
resize_to_parent: true,
|
||||||
},
|
},
|
||||||
@@ -254,25 +256,15 @@ impl TestbedGeneric {
|
|||||||
data: &mut Data,
|
data: &mut Data,
|
||||||
position: Vec2,
|
position: Vec2,
|
||||||
) -> anyhow::Result<()> {
|
) -> 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,
|
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(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ use crate::{
|
|||||||
components::{Component, ComponentWeak},
|
components::{Component, ComponentWeak},
|
||||||
drawing::{self},
|
drawing::{self},
|
||||||
globals::WguiGlobals,
|
globals::WguiGlobals,
|
||||||
|
i18n::Translation,
|
||||||
layout::{Layout, LayoutParams, LayoutState, Widget, WidgetID, WidgetMap, WidgetPair},
|
layout::{Layout, LayoutParams, LayoutState, Widget, WidgetID, WidgetMap, WidgetPair},
|
||||||
log::LogErr,
|
log::LogErr,
|
||||||
parser::{
|
parser::{
|
||||||
@@ -28,8 +29,10 @@ use crate::{
|
|||||||
widget_sprite::parse_widget_sprite,
|
widget_sprite::parse_widget_sprite,
|
||||||
},
|
},
|
||||||
widget::ConstructEssentials,
|
widget::ConstructEssentials,
|
||||||
|
windowing::context_menu,
|
||||||
};
|
};
|
||||||
use anyhow::Context;
|
use anyhow::Context;
|
||||||
|
use glam::Vec2;
|
||||||
use ouroboros::self_referencing;
|
use ouroboros::self_referencing;
|
||||||
use smallvec::SmallVec;
|
use smallvec::SmallVec;
|
||||||
use std::{cell::RefMut, collections::HashMap, path::Path, rc::Rc};
|
use std::{cell::RefMut, collections::HashMap, path::Path, rc::Rc};
|
||||||
@@ -256,6 +259,73 @@ impl ParserState {
|
|||||||
self.data.take_results_from(&mut data_local);
|
self.data.take_results_from(&mut data_local);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn instantiate_context_menu(
|
||||||
|
&mut self,
|
||||||
|
on_custom_attribs: Option<OnCustomAttribsFunc>,
|
||||||
|
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 <context_menu> tag, got <{tag_name}>");
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut cells = Vec::<context_menu::Cell>::new();
|
||||||
|
|
||||||
|
for child in el_context_menu.children() {
|
||||||
|
match child.tag_name().name() {
|
||||||
|
"" => {}
|
||||||
|
"cell" => {
|
||||||
|
let mut title: Option<Translation> = None;
|
||||||
|
let mut action_name: Option<Rc<str>> = None;
|
||||||
|
let mut attribs = Vec::<AttribPair>::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`
|
// convenience wrapper functions for `data`
|
||||||
@@ -549,7 +619,7 @@ fn parse_widget_other_internal(
|
|||||||
let template_node = doc
|
let template_node = doc
|
||||||
.borrow_doc()
|
.borrow_doc()
|
||||||
.get_node(template.node)
|
.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)?;
|
parse_children(&template_file, ctx, template_node, parent_id)?;
|
||||||
|
|
||||||
@@ -691,7 +761,12 @@ pub fn replace_vars(input: &str, vars: &HashMap<Rc<str>, Rc<str>>) -> Rc<str> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[allow(clippy::manual_strip)]
|
#[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<str>, Rc<str>>,
|
||||||
|
ctx: &ParserContext,
|
||||||
|
key: &str,
|
||||||
|
value: &str,
|
||||||
|
) -> AttribPair {
|
||||||
if value.starts_with('~') {
|
if value.starts_with('~') {
|
||||||
let name = &value[1..];
|
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"),
|
None => AttribPair::new(key, "undefined"),
|
||||||
}
|
}
|
||||||
} else {
|
} 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 key == "macro" {
|
||||||
if let Some(macro_attrib) = ctx.get_macro_attrib(value) {
|
if let Some(macro_attrib) = ctx.get_macro_attrib(value) {
|
||||||
for (macro_key, macro_value) in ¯o_attrib.attribs {
|
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 {
|
} else {
|
||||||
log::warn!("requested macro named \"{value}\" not found!");
|
log::warn!("requested macro named \"{value}\" not found!");
|
||||||
}
|
}
|
||||||
} else {
|
} 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 struct AttribPair {
|
||||||
pub attrib: Rc<str>,
|
pub attrib: Rc<str>,
|
||||||
pub value: Rc<str>,
|
pub value: Rc<str>,
|
||||||
@@ -1157,7 +1232,7 @@ fn parse_document_root(
|
|||||||
.document
|
.document
|
||||||
.borrow_doc()
|
.borrow_doc()
|
||||||
.get_node(node_layout)
|
.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() {
|
for child_node in node_layout.children() {
|
||||||
match child_node.tag_name().name() {
|
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))?,
|
"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),
|
||||||
|
"blueprint" => parse_tag_template(file, ctx, child_node),
|
||||||
"macro" => parse_tag_macro(file, ctx, child_node),
|
"macro" => parse_tag_macro(file, ctx, child_node),
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,8 +4,7 @@ use glam::Vec2;
|
|||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
assets::AssetPath,
|
assets::AssetPath,
|
||||||
components::button::ComponentButton,
|
components::{ComponentTrait, button::ComponentButton},
|
||||||
event::CallbackDataCommon,
|
|
||||||
globals::WguiGlobals,
|
globals::WguiGlobals,
|
||||||
i18n::Translation,
|
i18n::Translation,
|
||||||
layout::Layout,
|
layout::Layout,
|
||||||
@@ -16,26 +15,23 @@ use crate::{
|
|||||||
|
|
||||||
pub struct Cell {
|
pub struct Cell {
|
||||||
pub title: Translation,
|
pub title: Translation,
|
||||||
pub action_name: Rc<str>,
|
pub action_name: Option<Rc<str>>,
|
||||||
|
pub attribs: Vec<parser::AttribPair>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct Blueprint {
|
pub struct Blueprint {
|
||||||
pub cells: Vec<Cell>,
|
pub cells: Vec<Cell>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct ContextMenuAction<'a> {
|
|
||||||
pub common: &'a mut CallbackDataCommon<'a>,
|
|
||||||
pub name: Rc<str>, // action name
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct OpenParams {
|
pub struct OpenParams {
|
||||||
pub position: Vec2,
|
pub position: Vec2,
|
||||||
pub data: Blueprint,
|
pub data: Blueprint,
|
||||||
|
pub on_custom_attribs: Option<parser::OnCustomAttribsFunc>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
enum Task {
|
enum Task {
|
||||||
ActionClicked(Rc<str>),
|
ActionClicked(Option<Rc<str>>),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
@@ -67,7 +63,7 @@ impl ContextMenu {
|
|||||||
self.window.close();
|
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();
|
let globals = layout.state.globals.clone();
|
||||||
|
|
||||||
self.window.open(&mut WguiWindowParams {
|
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 data_cell = state.parse_template(&doc_params(&globals), "Cell", layout, id_buttons, par)?;
|
||||||
|
|
||||||
let button = data_cell.fetch_component_as::<ComponentButton>("button")?;
|
let button = data_cell.fetch_component_as::<ComponentButton>("button")?;
|
||||||
|
let button_id = button.base().get_id();
|
||||||
self
|
self
|
||||||
.tasks
|
.tasks
|
||||||
.handle_button(&button, Task::ActionClicked(cell.action_name.clone()));
|
.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 {
|
if idx < params.data.cells.len() - 1 {
|
||||||
state.parse_template(
|
state.parse_template(
|
||||||
&doc_params(&globals),
|
&doc_params(&globals),
|
||||||
@@ -112,8 +118,8 @@ impl ContextMenu {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn tick(&mut self, layout: &mut Layout) -> anyhow::Result<TickResult> {
|
pub fn tick(&mut self, layout: &mut Layout) -> anyhow::Result<TickResult> {
|
||||||
if let Some(p) = self.pending_open.take() {
|
if let Some(mut p) = self.pending_open.take() {
|
||||||
self.open_process(&p, layout)?;
|
self.open_process(&mut p, layout)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut result = TickResult::default();
|
let mut result = TickResult::default();
|
||||||
@@ -121,7 +127,7 @@ impl ContextMenu {
|
|||||||
for task in self.tasks.drain() {
|
for task in self.tasks.drain() {
|
||||||
match task {
|
match task {
|
||||||
Task::ActionClicked(action_name) => {
|
Task::ActionClicked(action_name) => {
|
||||||
result.action_name = Some(action_name);
|
result.action_name = action_name;
|
||||||
self.close();
|
self.close();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user