ticking context menu

This commit is contained in:
Aleksander
2026-01-06 12:43:03 +01:00
parent a196dd9b3a
commit e535c5fe94
4 changed files with 122 additions and 65 deletions

View File

@@ -196,7 +196,7 @@ impl TestbedGeneric {
button_context_menu.on_click({
let tasks = testbed.tasks.clone();
Box::new(move |_common, m| {
tasks.push(TestbedTask::ShowContextMenu(m.mouse_pos_absolute.unwrap()));
tasks.push(TestbedTask::ShowContextMenu(m.boundary.bottom_left()));
Ok(())
})
});
@@ -254,28 +254,25 @@ impl TestbedGeneric {
data: &mut Data,
position: Vec2,
) -> anyhow::Result<()> {
data.context_menu.open(&mut context_menu::OpenParams {
globals: &self.globals,
layout: &mut self.layout,
data.context_menu.open(context_menu::OpenParams {
position,
on_action: Rc::new(move |action| {
log::info!("got action: {}", action.name);
}),
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(),
},
],
})?;
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(())
}
@@ -297,6 +294,11 @@ impl Testbed for TestbedGeneric {
self.process_task(&task, &mut params, &mut data)?;
}
let res = data.context_menu.tick(&mut self.layout)?;
if let Some(action_name) = res.action_name {
log::info!("got action: {}", action_name);
}
Ok(())
}

View File

@@ -68,6 +68,7 @@ impl Default for Params<'_> {
pub struct ButtonClickEvent {
pub mouse_pos_absolute: Option<Vec2>,
pub boundary: Boundary,
}
pub type ButtonClickCallback = Box<dyn Fn(&mut CallbackDataCommon, ButtonClickEvent) -> anyhow::Result<()>>;
@@ -388,6 +389,7 @@ fn register_event_mouse_release(
common,
ButtonClickEvent {
mouse_pos_absolute: event_data.metadata.get_mouse_pos_absolute(),
boundary: event_data.widget_data.cached_absolute_boundary,
},
)?;
}

View File

@@ -54,6 +54,41 @@ impl Boundary {
}
}
pub const fn bottom_left(&self) -> Vec2 {
Vec2::new(self.pos.x, self.pos.y + self.size.y)
}
pub const fn bottom_right(&self) -> Vec2 {
Vec2::new(self.pos.x + self.size.x, self.pos.y + self.size.y)
}
pub const fn top_right(&self) -> Vec2 {
Vec2::new(self.pos.x + self.size.x, self.pos.y)
}
pub const fn center(&self) -> Vec2 {
Vec2::new(self.pos.x + self.size.x / 2.0, self.pos.y + self.size.y / 2.0)
}
pub const fn width(&self) -> f32 {
self.size.x
}
pub const fn height(&self) -> f32 {
self.size.y
}
pub const fn area(&self) -> f32 {
self.size.x * self.size.y
}
pub const fn contains_point(&self, point: Vec2) -> bool {
point.x >= self.pos.x
&& point.x <= self.pos.x + self.size.x
&& point.y >= self.pos.y
&& point.y <= self.pos.y + self.size.y
}
pub const fn top(&self) -> f32 {
self.pos.y
}

View File

@@ -10,6 +10,7 @@ use crate::{
i18n::Translation,
layout::Layout,
parser::{self, Fetchable},
task::Tasks,
windowing::window::{WguiWindow, WguiWindowParams, WguiWindowParamsExtra},
};
@@ -18,37 +19,60 @@ pub struct Cell {
pub action_name: Rc<str>,
}
pub struct Blueprint {
pub cells: Vec<Cell>,
}
pub struct ContextMenuAction<'a> {
pub common: &'a mut CallbackDataCommon<'a>,
pub name: Rc<str>, // action name
}
pub struct OpenParams<'a> {
pub struct OpenParams {
pub position: Vec2,
pub globals: &'a WguiGlobals,
pub layout: &'a mut Layout,
pub on_action: Rc<dyn Fn(ContextMenuAction)>,
pub cells: Vec<Cell>,
pub data: Blueprint,
}
#[derive(Clone)]
enum Task {
ActionClicked(Rc<str>),
}
#[derive(Default)]
pub struct ContextMenu {
window: WguiWindow,
pending_open: Option<OpenParams>,
tasks: Tasks<Task>,
}
fn doc_params<'a>(globals: WguiGlobals) -> parser::ParseDocumentParams<'a> {
fn doc_params<'a>(globals: &WguiGlobals) -> parser::ParseDocumentParams<'a> {
parser::ParseDocumentParams {
globals,
globals: globals.clone(),
path: AssetPath::WguiInternal("wgui/context_menu.xml"),
extra: Default::default(),
}
}
#[derive(Default)]
pub struct TickResult {
pub action_name: Option<Rc<str>>,
}
impl ContextMenu {
pub fn open(&mut self, params: &mut OpenParams) -> anyhow::Result<()> {
pub fn open(&mut self, params: OpenParams) {
self.pending_open = Some(params);
}
pub fn close(&self) {
self.window.close();
}
fn open_process(&mut self, params: &OpenParams, layout: &mut Layout) -> anyhow::Result<()> {
let globals = layout.state.globals.clone();
self.window.open(&mut WguiWindowParams {
globals: params.globals,
layout: params.layout,
globals: &globals,
layout,
position: params.position,
extra: WguiWindowParamsExtra {
with_decorations: false,
@@ -59,46 +83,25 @@ impl ContextMenu {
let content = self.window.get_content();
let mut state = parser::parse_from_assets(&doc_params(params.globals.clone()), params.layout, content.id)?;
let mut state = parser::parse_from_assets(&doc_params(&globals), layout, content.id)?;
let id_buttons = state.get_widget_id("buttons")?;
for (idx, cell) in params.cells.iter().enumerate() {
for (idx, cell) in params.data.cells.iter().enumerate() {
let mut par = HashMap::new();
par.insert(Rc::from("text"), cell.title.generate(&mut params.globals.i18n()));
let data_cell = state.parse_template(
&doc_params(params.globals.clone()),
"Cell",
params.layout,
id_buttons,
par,
)?;
par.insert(Rc::from("text"), cell.title.generate(&mut globals.i18n()));
let data_cell = state.parse_template(&doc_params(&globals), "Cell", layout, id_buttons, par)?;
let button = data_cell.fetch_component_as::<ComponentButton>("button")?;
button.on_click({
let on_action = params.on_action.clone();
let name = cell.action_name.clone();
let window = self.window.clone();
Box::new(move |common, _| {
(*on_action)(ContextMenuAction {
name: name.clone(),
// FIXME: why i can't just provide this as-is!?
/* common: common, */
common: &mut CallbackDataCommon {
alterables: common.alterables,
state: common.state,
},
});
window.close();
Ok(())
})
});
self
.tasks
.handle_button(&button, Task::ActionClicked(cell.action_name.clone()));
if idx < params.cells.len() - 1 {
if idx < params.data.cells.len() - 1 {
state.parse_template(
&doc_params(params.globals.clone()),
&doc_params(&globals),
"Separator",
params.layout,
layout,
id_buttons,
Default::default(),
)?;
@@ -108,7 +111,22 @@ impl ContextMenu {
Ok(())
}
pub fn close(&self) {
self.window.close();
pub fn tick(&mut self, layout: &mut Layout) -> anyhow::Result<TickResult> {
if let Some(p) = self.pending_open.take() {
self.open_process(&p, layout)?;
}
let mut result = TickResult::default();
for task in self.tasks.drain() {
match task {
Task::ActionClicked(action_name) => {
result.action_name = Some(action_name);
self.close();
}
}
}
Ok(result)
}
}