use std::cell::RefCell;
use std::panic::{self, AssertUnwindSafe};
use std::rc::Rc;
use crabshell_reactive::untrack;
use crate::cx::{Anchor, Cx};
use crate::geometry::Rect;
use crate::input::panic_message;
use crate::prop::{IntoProp, Prop};
use crate::style::Style;
use crate::tree::{PopupState, Tree, borrow};
use crate::widgets::Widget;
use crate::window::Window;
#[derive(Default)]
pub struct PopupWindow {
style: Option<Prop<Style>>,
child: Option<Box<dyn Widget>>,
}
impl PopupWindow {
pub fn new() -> PopupWindow {
PopupWindow::default()
}
pub fn style(mut self, style: impl IntoProp<Style>) -> PopupWindow {
self.style = Some(style.into_prop());
self
}
pub fn child(mut self, child: impl Widget) -> PopupWindow {
self.child = Some(Box::new(child));
self
}
}
impl Cx {
pub fn open_popup(&self, build: impl FnOnce(&Cx) -> PopupWindow) {
let Some((anchor, (tree, node))) = self
.anchor
.clone()
.and_then(|anchor| anchor.get().map(|found| (anchor, found)))
else {
return tracing::warn!("open_popup needs a widget's context, such as a handler's");
};
let opener = {
let tree = tree
.try_borrow()
.expect("invariant: a window's tree is not borrowed while user code runs");
tree
.node(node)
.map(|node| node.path.clone())
.zip(Some(tree.output.clone()))
};
let Some((opener, output)) = opener else {
return;
};
let path: Rc<str> = format!("{opener}/PopupWindow").into();
let root = Rc::new(Anchor::default());
let cx = self.child().named(path.to_string()).at(Rc::clone(&root));
let built = panic::catch_unwind(AssertUnwindSafe(|| {
untrack(|| {
let popup = build(&cx);
let child = popup.child.map(|child| child.into_element());
(popup.style, child)
})
}));
let (style, child) = match built {
Ok(built) => built,
Err(payload) => {
tracing::error!(
"{path} panicked while it was built, so it doesn't open: {}",
panic_message(payload.as_ref())
);
cx.owner().dispose();
return;
}
};
let owner = cx.owner();
let alive = cx.signal(());
let popup = Rc::new_cyclic(|this| {
let mut tree = Tree::new(this.clone(), "PopupWindow", output, cx, path);
tree.popup = Some(PopupState {
opener: anchor,
close: false,
alive,
});
RefCell::new(tree)
});
borrow(&popup).mount_root(style, child);
borrow(&tree).popups.push(Window { tree: popup, owner });
}
pub fn close_popup(&self) {
if !self.close_popups_if_any() {
tracing::warn!("close_popup was called outside a popup");
}
}
pub(crate) fn close_popups_if_any(&self) -> bool {
let mut next = self
.anchor
.as_ref()
.and_then(|anchor| anchor.get())
.map(|(tree, _)| tree);
let mut closed = false;
while let Some(tree) = next.take() {
let mut tree = borrow(&tree);
let Some(popup) = tree.popup.as_mut() else {
break;
};
popup.close = true;
closed = true;
next = popup.opener.get().map(|(tree, _)| tree);
}
closed
}
}
impl Window {
pub fn take_popups(&self) -> Vec<Window> {
std::mem::take(&mut borrow(&self.tree).popups)
}
pub fn anchor(&self) -> Option<Rect> {
let opener = Rc::clone(&self.tree.borrow().popup.as_ref()?.opener);
let (tree, node) = opener.get()?;
let tree = tree.borrow();
tree.node(node)?.rect
}
pub fn is_popup(&self) -> bool {
self.tree.borrow().popup.is_some()
}
pub fn wants_close(&self) -> bool {
let tree = self.tree.borrow();
tree
.popup
.as_ref()
.is_some_and(|popup| popup.close || popup.alive.try_with(|_| ()).is_none())
}
}