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, Size};
12use crate::input::panic_message;
13use crate::prop::{IntoProp, Prop};
14use crate::region::Region;
15use crate::style::Style;
16use crate::tree::{PopupState, Tree, borrow};
17use crate::widgets::Widget;
18use crate::window::{Anchors, Hints, Window};
19
20#[derive(Default)]
31pub struct PopupWindow {
32 style: Option<Prop<Style>>,
33 child: Option<Box<dyn Widget>>,
34 edges: Option<Prop<Anchors>>,
35 gravity: Option<Prop<Anchors>>,
36 adjustment: Option<Prop<Adjust>>,
37 hints: Hints,
38}
39
40#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
44pub struct Adjust(u8);
45
46impl Adjust {
47 pub const NONE: Adjust = Adjust(0);
49 pub const SLIDE_X: Adjust = Adjust(1);
51 pub const SLIDE_Y: Adjust = Adjust(2);
53 pub const FLIP_X: Adjust = Adjust(4);
55 pub const FLIP_Y: Adjust = Adjust(8);
57 pub const RESIZE_X: Adjust = Adjust(16);
60 pub const RESIZE_Y: Adjust = Adjust(32);
62 pub const SLIDE: Adjust = Adjust(1 | 2);
64 pub const FLIP: Adjust = Adjust(4 | 8);
66 pub const RESIZE: Adjust = Adjust(16 | 32);
68 pub const ALL: Adjust = Adjust(63);
70
71 pub const fn contains(self, other: Adjust) -> bool {
73 self.0 & other.0 == other.0
74 }
75}
76
77impl std::ops::BitOr for Adjust {
78 type Output = Adjust;
79
80 fn bitor(self, other: Adjust) -> Adjust {
81 Adjust(self.0 | other.0)
82 }
83}
84
85#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
90pub struct Placement {
91 pub edges: Option<Anchors>,
93 pub gravity: Option<Anchors>,
95 pub adjustment: Option<Adjust>,
97}
98
99impl PopupWindow {
100 pub fn new() -> PopupWindow {
102 PopupWindow::default()
103 }
104
105 pub fn style(mut self, style: impl IntoProp<Style>) -> PopupWindow {
108 self.style = Some(style.into_prop());
109 self
110 }
111
112 pub fn child(mut self, child: impl Widget) -> PopupWindow {
115 self.child = Some(Box::new(child));
116 self
117 }
118
119 pub fn edges(mut self, edges: impl IntoProp<Anchors>) -> PopupWindow {
123 self.edges = Some(edges.into_prop());
124 self
125 }
126
127 pub fn gravity(mut self, gravity: impl IntoProp<Anchors>) -> PopupWindow {
130 self.gravity = Some(gravity.into_prop());
131 self
132 }
133
134 pub fn adjustment(mut self, adjustment: impl IntoProp<Adjust>) -> PopupWindow {
137 self.adjustment = Some(adjustment.into_prop());
138 self
139 }
140
141 pub fn mask(mut self, mask: impl IntoProp<Option<Region>>) -> PopupWindow {
148 self.hints.mask = Some(mask.into_prop());
149 self
150 }
151
152 pub fn compositor_opacity(mut self, opacity: impl IntoProp<f32>) -> PopupWindow {
157 self.hints.compositor_opacity = Some(opacity.into_prop());
158 self
159 }
160
161 pub fn visible_mask(mut self, region: impl IntoProp<Option<Region>>) -> PopupWindow {
165 self.hints.visible_mask = Some(region.into_prop());
166 self
167 }
168}
169
170impl Cx {
171 pub fn open_popup(&self, build: impl FnOnce(&Cx) -> PopupWindow) {
181 let Some((anchor, (tree, node))) = self
182 .anchor
183 .clone()
184 .and_then(|anchor| anchor.get().map(|found| (anchor, found)))
185 else {
186 return tracing::warn!("open_popup needs a widget's context, such as a handler's");
187 };
188 let opener = {
189 let tree = tree
190 .try_borrow()
191 .expect("invariant: a window's tree is not borrowed while user code runs");
192 tree
193 .node(node)
194 .map(|node| node.path.clone())
195 .zip(Some(tree.output.clone()))
196 };
197 let Some((opener, output)) = opener else {
198 return;
199 };
200 let path: Rc<str> = format!("{opener}/PopupWindow").into();
201 let root = Rc::new(Anchor::default());
202 let cx = self.child().named(path.to_string()).at(Rc::clone(&root));
203 let built = panic::catch_unwind(AssertUnwindSafe(|| {
206 untrack(|| {
207 let popup = build(&cx);
208 let child = popup.child.map(|child| child.into_element());
209 (
210 popup.style,
211 child,
212 (popup.edges, popup.gravity, popup.adjustment, popup.hints),
213 )
214 })
215 }));
216 let (style, child, placement) = match built {
217 Ok(built) => built,
218 Err(payload) => {
219 tracing::error!(
220 "{path} panicked while it was built, so it doesn't open: {}",
221 panic_message(payload.as_ref())
222 );
223 cx.owner().dispose();
224 return;
225 }
226 };
227 let owner = cx.owner();
228 let alive = cx.signal(());
229 let popup = Rc::new_cyclic(|this| {
230 let mut tree = Tree::new(this.clone(), "PopupWindow", output, cx, path);
231 tree.popup = Some(PopupState {
232 opener: anchor,
233 close: false,
234 alive,
235 placement: Placement::default(),
236 wanted: Size::default(),
237 limit: None,
238 });
239 RefCell::new(tree)
240 });
241 {
242 let mut tree = borrow(&popup);
243 tree.mount_root(style, child);
244 let (edges, gravity, adjustment, hints) = placement;
245 let root = tree.root;
246 tree.bind_opt(root, edges, "edges", |tree, _, value| {
247 if let Some(popup) = tree.popup.as_mut() {
248 popup.placement.edges = Some(value);
249 }
250 });
251 tree.bind_opt(root, gravity, "gravity", |tree, _, value| {
252 if let Some(popup) = tree.popup.as_mut() {
253 popup.placement.gravity = Some(value);
254 }
255 });
256 tree.bind_opt(root, adjustment, "adjustment", |tree, _, value| {
257 if let Some(popup) = tree.popup.as_mut() {
258 popup.placement.adjustment = Some(value);
259 }
260 });
261 tree.bind_compositor_hints(hints);
262 }
263 borrow(&tree).popups.push(Window { tree: popup, owner });
264 }
265
266 pub fn close_popup(&self) {
271 if !self.close_popups_if_any() {
272 tracing::warn!("close_popup was called outside a popup");
273 }
274 }
275
276 pub(crate) fn close_popups_if_any(&self) -> bool {
280 let mut next = self
281 .anchor
282 .as_ref()
283 .and_then(|anchor| anchor.get())
284 .map(|(tree, _)| tree);
285 let mut closed = false;
286 while let Some(tree) = next.take() {
287 let mut tree = borrow(&tree);
288 let Some(popup) = tree.popup.as_mut() else {
289 break;
290 };
291 popup.close = true;
292 closed = true;
293 next = popup.opener.get().map(|(tree, _)| tree);
294 }
295 closed
296 }
297}
298
299impl Window {
300 pub fn take_popups(&self) -> Vec<Window> {
302 std::mem::take(&mut borrow(&self.tree).popups)
303 }
304
305 pub fn anchor(&self) -> Option<Rect> {
310 let opener = Rc::clone(&self.tree.borrow().popup.as_ref()?.opener);
311 let (tree, node) = opener.get()?;
312 let tree = tree.borrow();
313 tree.node(node)?.rect
314 }
315
316 pub fn placement(&self) -> Placement {
319 let tree = self.tree.borrow();
320 tree
321 .popup
322 .as_ref()
323 .map(|popup| popup.placement)
324 .unwrap_or_default()
325 }
326
327 pub fn limit_size(&self, limit: Option<Size>) {
332 let mut tree = borrow(&self.tree);
333 let Some(popup) = tree.popup.as_mut() else {
334 return;
335 };
336 if popup.limit != limit {
337 popup.limit = limit;
338 tree.needs_layout = true;
339 }
340 }
341
342 pub fn wanted_size(&self) -> Size {
345 let tree = self.tree.borrow();
346 tree.popup.as_ref().map_or(tree.size, |popup| popup.wanted)
347 }
348
349 pub fn is_popup(&self) -> bool {
351 self.tree.borrow().popup.is_some()
352 }
353
354 pub fn wants_close(&self) -> bool {
357 let tree = self.tree.borrow();
358 tree
359 .popup
360 .as_ref()
361 .is_some_and(|popup| popup.close || popup.alive.try_with(|_| ()).is_none())
362 }
363}