1use std::cell::RefCell;
5use std::panic::{self, AssertUnwindSafe};
6use std::rc::Rc;
7
8use crabshell_reactive::untrack;
9
10use crate::cx::{Anchor, Cx};
11use crate::geometry::Rect;
12use crate::input::panic_message;
13use crate::prop::{IntoProp, Prop};
14use crate::style::Style;
15use crate::tree::{PopupState, Tree, borrow};
16use crate::widgets::Widget;
17use crate::window::Window;
18
19#[derive(Default)]
30pub struct PopupWindow {
31 style: Option<Prop<Style>>,
32 child: Option<Box<dyn Widget>>,
33}
34
35impl PopupWindow {
36 pub fn new() -> PopupWindow {
38 PopupWindow::default()
39 }
40
41 pub fn style(mut self, style: impl IntoProp<Style>) -> PopupWindow {
44 self.style = Some(style.into_prop());
45 self
46 }
47
48 pub fn child(mut self, child: impl Widget) -> PopupWindow {
51 self.child = Some(Box::new(child));
52 self
53 }
54}
55
56impl Cx {
57 pub fn open_popup(&self, build: impl FnOnce(&Cx) -> PopupWindow) {
67 let Some((anchor, (tree, node))) = self
68 .anchor
69 .clone()
70 .and_then(|anchor| anchor.get().map(|found| (anchor, found)))
71 else {
72 return tracing::warn!("open_popup needs a widget's context, such as a handler's");
73 };
74 let opener = {
75 let tree = tree
76 .try_borrow()
77 .expect("invariant: a window's tree is not borrowed while user code runs");
78 tree
79 .node(node)
80 .map(|node| node.path.clone())
81 .zip(Some(tree.output.clone()))
82 };
83 let Some((opener, output)) = opener else {
84 return;
85 };
86 let path: Rc<str> = format!("{opener}/PopupWindow").into();
87 let root = Rc::new(Anchor::default());
88 let cx = self.child().named(path.to_string()).at(Rc::clone(&root));
89 let built = panic::catch_unwind(AssertUnwindSafe(|| {
92 untrack(|| {
93 let popup = build(&cx);
94 let child = popup.child.map(|child| child.into_element());
95 (popup.style, child)
96 })
97 }));
98 let (style, child) = match built {
99 Ok(built) => built,
100 Err(payload) => {
101 tracing::error!(
102 "{path} panicked while it was built, so it doesn't open: {}",
103 panic_message(payload.as_ref())
104 );
105 cx.owner().dispose();
106 return;
107 }
108 };
109 let owner = cx.owner();
110 let alive = cx.signal(());
111 let popup = Rc::new_cyclic(|this| {
112 let mut tree = Tree::new(this.clone(), "PopupWindow", output, cx, path);
113 tree.popup = Some(PopupState {
114 opener: anchor,
115 close: false,
116 alive,
117 });
118 RefCell::new(tree)
119 });
120 borrow(&popup).mount_root(style, child);
121 borrow(&tree).popups.push(Window { tree: popup, owner });
122 }
123
124 pub fn close_popup(&self) {
129 if !self.close_popups_if_any() {
130 tracing::warn!("close_popup was called outside a popup");
131 }
132 }
133
134 pub(crate) fn close_popups_if_any(&self) -> bool {
138 let mut next = self
139 .anchor
140 .as_ref()
141 .and_then(|anchor| anchor.get())
142 .map(|(tree, _)| tree);
143 let mut closed = false;
144 while let Some(tree) = next.take() {
145 let mut tree = borrow(&tree);
146 let Some(popup) = tree.popup.as_mut() else {
147 break;
148 };
149 popup.close = true;
150 closed = true;
151 next = popup.opener.get().map(|(tree, _)| tree);
152 }
153 closed
154 }
155}
156
157impl Window {
158 pub fn take_popups(&self) -> Vec<Window> {
160 std::mem::take(&mut borrow(&self.tree).popups)
161 }
162
163 pub fn anchor(&self) -> Option<Rect> {
168 let opener = Rc::clone(&self.tree.borrow().popup.as_ref()?.opener);
169 let (tree, node) = opener.get()?;
170 let tree = tree.borrow();
171 tree.node(node)?.rect
172 }
173
174 pub fn is_popup(&self) -> bool {
176 self.tree.borrow().popup.is_some()
177 }
178
179 pub fn wants_close(&self) -> bool {
182 let tree = self.tree.borrow();
183 tree
184 .popup
185 .as_ref()
186 .is_some_and(|popup| popup.close || popup.alive.try_with(|_| ()).is_none())
187 }
188}