Files
wayvr/dash-frontend/src/util/popup_manager.rs
2026-01-07 02:23:39 +09:00

187 lines
4.3 KiB
Rust

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<Weak<RefCell<MountedPopupState>>>,
}
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<MountedPopup>,
}
#[derive(Clone)]
pub struct PopupHandle {
state: Rc<RefCell<MountedPopupState>>,
}
impl PopupHandle {
pub fn close(&self) {
self.state.borrow_mut().mounted_popup = None; // Drop will be called
}
}
pub struct PopupManager {
state: Rc<RefCell<State>>,
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<dyn Fn(PopupContentFuncData) -> 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::<WidgetLabel>(&layout.state, "popup_title")?;
label_title.set_text_simple(&mut globals.get(), params.title);
}
let but_back = state.fetch_component_as::<ComponentButton>("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(())
}
}