diff --git a/uidev/src/testbed/testbed_generic.rs b/uidev/src/testbed/testbed_generic.rs index 53775b75..b3e60afc 100644 --- a/uidev/src/testbed/testbed_generic.rs +++ b/uidev/src/testbed/testbed_generic.rs @@ -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(()) } diff --git a/wgui/src/components/button.rs b/wgui/src/components/button.rs index c2719e84..ba4b0127 100644 --- a/wgui/src/components/button.rs +++ b/wgui/src/components/button.rs @@ -68,6 +68,7 @@ impl Default for Params<'_> { pub struct ButtonClickEvent { pub mouse_pos_absolute: Option, + pub boundary: Boundary, } pub type ButtonClickCallback = Box 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, }, )?; } diff --git a/wgui/src/drawing.rs b/wgui/src/drawing.rs index c5d8bd71..0a2c337b 100644 --- a/wgui/src/drawing.rs +++ b/wgui/src/drawing.rs @@ -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 } diff --git a/wgui/src/windowing/context_menu.rs b/wgui/src/windowing/context_menu.rs index 70e1d458..c8b64323 100644 --- a/wgui/src/windowing/context_menu.rs +++ b/wgui/src/windowing/context_menu.rs @@ -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, } +pub struct Blueprint { + pub cells: Vec, +} + pub struct ContextMenuAction<'a> { pub common: &'a mut CallbackDataCommon<'a>, pub name: Rc, // 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, - pub cells: Vec, + pub data: Blueprint, +} + +#[derive(Clone)] +enum Task { + ActionClicked(Rc), } #[derive(Default)] pub struct ContextMenu { window: WguiWindow, + pending_open: Option, + tasks: Tasks, } -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>, +} + 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::("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 { + 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) } }