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::{Anchors, Window};
#[derive(Default)]
pub struct PopupWindow {
style: Option<Prop<Style>>,
child: Option<Box<dyn Widget>>,
edges: Option<Prop<Anchors>>,
gravity: Option<Prop<Anchors>>,
adjustment: Option<Prop<Adjust>>,
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct Adjust(u8);
impl Adjust {
pub const NONE: Adjust = Adjust(0);
pub const SLIDE_X: Adjust = Adjust(1);
pub const SLIDE_Y: Adjust = Adjust(2);
pub const FLIP_X: Adjust = Adjust(4);
pub const FLIP_Y: Adjust = Adjust(8);
pub const SLIDE: Adjust = Adjust(1 | 2);
pub const FLIP: Adjust = Adjust(4 | 8);
pub const ALL: Adjust = Adjust(15);
pub const fn contains(self, other: Adjust) -> bool {
self.0 & other.0 == other.0
}
}
impl std::ops::BitOr for Adjust {
type Output = Adjust;
fn bitor(self, other: Adjust) -> Adjust {
Adjust(self.0 | other.0)
}
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct Placement {
pub edges: Option<Anchors>,
pub gravity: Option<Anchors>,
pub adjustment: Option<Adjust>,
}
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
}
pub fn edges(mut self, edges: impl IntoProp<Anchors>) -> PopupWindow {
self.edges = Some(edges.into_prop());
self
}
pub fn gravity(mut self, gravity: impl IntoProp<Anchors>) -> PopupWindow {
self.gravity = Some(gravity.into_prop());
self
}
pub fn adjustment(mut self, adjustment: impl IntoProp<Adjust>) -> PopupWindow {
self.adjustment = Some(adjustment.into_prop());
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,
(popup.edges, popup.gravity, popup.adjustment),
)
})
}));
let (style, child, placement) = 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,
placement: Placement::default(),
});
RefCell::new(tree)
});
{
let mut tree = borrow(&popup);
tree.mount_root(style, child);
let (edges, gravity, adjustment) = placement;
let root = tree.root;
tree.bind_opt(root, edges, "edges", |tree, _, value| {
if let Some(popup) = tree.popup.as_mut() {
popup.placement.edges = Some(value);
}
});
tree.bind_opt(root, gravity, "gravity", |tree, _, value| {
if let Some(popup) = tree.popup.as_mut() {
popup.placement.gravity = Some(value);
}
});
tree.bind_opt(root, adjustment, "adjustment", |tree, _, value| {
if let Some(popup) = tree.popup.as_mut() {
popup.placement.adjustment = Some(value);
}
});
}
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 placement(&self) -> Placement {
let tree = self.tree.borrow();
tree
.popup
.as_ref()
.map(|popup| popup.placement)
.unwrap_or_default()
}
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())
}
}