use std::{ cell::RefCell, rc::{Rc, Weak}, }; use wgui::{ assets::AssetPath, components::button::ComponentButton, event::{EventAlterables, StyleSetRequest}, globals::WguiGlobals, i18n::Translation, layout::{Layout, LayoutTask, LayoutTasks, WidgetID}, parser::{Fetchable, ParseDocumentParams, ParserState}, taffy::Display, widget::label::WidgetLabel, }; use wlx_common::config::GeneralConfig; use crate::frontend::{FrontendTask, FrontendTasks}; pub struct PopupManagerParams { pub parent_id: WidgetID, } struct State { popup_stack: Vec>>, } pub struct MountedPopup { #[allow(dead_code)] state: ParserState, id_root: WidgetID, // decorations of a popup layout_tasks: LayoutTasks, frontend_tasks: FrontendTasks, } struct MountedPopupState { mounted_popup: Option, } #[derive(Clone)] pub struct PopupHandle { state: Rc>, } impl PopupHandle { pub fn close(&self) { self.state.borrow_mut().mounted_popup = None; // Drop will be called } } pub struct PopupManager { state: Rc>, parent_id: WidgetID, } pub struct PopupContentFuncData<'a> { pub layout: &'a mut Layout, pub config: &'a GeneralConfig, pub handle: PopupHandle, pub id_content: WidgetID, } #[derive(Clone)] pub struct MountPopupParams { pub title: Translation, pub on_content: Rc anyhow::Result<()>>, } impl Drop for MountedPopup { fn drop(&mut self) { self.layout_tasks.push(LayoutTask::RemoveWidget(self.id_root)); self.frontend_tasks.push(FrontendTask::RefreshPopupManager); } } impl State { fn refresh_stack(&mut self, alterables: &mut EventAlterables) { // show only the topmost popup self.popup_stack.retain(|weak| { let Some(popup) = weak.upgrade() else { return false; }; popup.borrow_mut().mounted_popup.is_some() }); for (idx, popup) in self.popup_stack.iter().enumerate() { let popup = popup.upgrade().unwrap(); // safe let popup = popup.borrow_mut(); let mounted_popup = popup.mounted_popup.as_ref().unwrap(); // safe; alterables.set_style( mounted_popup.id_root, StyleSetRequest::Display(if idx == self.popup_stack.len() - 1 { Display::Flex } else { Display::None }), ); } } } impl PopupManager { pub fn new(params: PopupManagerParams) -> Self { Self { parent_id: params.parent_id, state: Rc::new(RefCell::new(State { popup_stack: Vec::new(), })), } } pub fn refresh(&self, alterables: &mut EventAlterables) { let mut state = self.state.borrow_mut(); state.refresh_stack(alterables); } /// Mount a new popup on top of the existing popup stack. /// Only the topmost popup is visible. pub fn mount_popup( &mut self, globals: WguiGlobals, layout: &mut Layout, frontend_tasks: FrontendTasks, params: MountPopupParams, config: &GeneralConfig, ) -> anyhow::Result<()> { let doc_params = &ParseDocumentParams { globals: globals.clone(), path: AssetPath::BuiltIn("gui/view/popup_window.xml"), extra: Default::default(), }; let state = wgui::parser::parse_from_assets(doc_params, layout, self.parent_id)?; let id_root = state.get_widget_id("root")?; let id_content = state.get_widget_id("content")?; { let mut label_title = state.fetch_widget_as::(&layout.state, "popup_title")?; label_title.set_text_simple(&mut globals.get(), params.title); } let but_back = state.fetch_component_as::("but_back")?; let mounted_popup = MountedPopup { state, id_root, layout_tasks: layout.tasks.clone(), frontend_tasks: frontend_tasks.clone(), }; let mounted_popup_state = MountedPopupState { mounted_popup: Some(mounted_popup), }; let popup_handle = PopupHandle { state: Rc::new(RefCell::new(mounted_popup_state)), }; let mut state = self.state.borrow_mut(); state.popup_stack.push(Rc::downgrade(&popup_handle.state)); but_back.on_click({ let popup_handle = Rc::downgrade(&popup_handle.state); Box::new(move |_common, _evt| { if let Some(popup_handle) = popup_handle.upgrade() { popup_handle.borrow_mut().mounted_popup = None; // will call Drop } Ok(()) }) }); frontend_tasks.push(FrontendTask::RefreshPopupManager); // mount user-set popup content (*params.on_content)(PopupContentFuncData { layout, handle: popup_handle.clone(), id_content, config, })?; Ok(()) } }