1use std::cell::RefCell;
5use std::rc::Rc;
6
7use crabshell_reactive::{Signal, untrack};
8
9use crate::cx::{Anchor, Cx};
10use crate::input::{Handler, run_isolated};
11use crate::prop::{IntoProp, Prop};
12use crate::region::Region;
13use crate::style::Style;
14use crate::tree::{Tree, borrow};
15use crate::widgets::Widget;
16use crate::window::{Anchors, Hints, Output, Window};
17
18pub(crate) type CloseHandler = Rc<Handler<dyn Fn(&Cx)>>;
19
20pub struct FloatingWindow {
35 pub(crate) title: Option<Prop<String>>,
36 pub(crate) width: Option<Prop<u32>>,
37 pub(crate) height: Option<Prop<u32>>,
38 pub(crate) min_size: Option<Prop<(u32, u32)>>,
39 pub(crate) max_size: Option<Prop<(u32, u32)>>,
40 pub(crate) maximized: Option<Prop<bool>>,
41 pub(crate) fullscreen: Option<Prop<bool>>,
42 pub(crate) visible: Option<Prop<bool>>,
43 pub(crate) hints: Hints,
44 pub(crate) on_close: Option<CloseHandler>,
45 pub(crate) style: Option<Prop<Style>>,
46 pub(crate) child: Option<Box<dyn Widget>>,
47}
48
49impl Default for FloatingWindow {
50 fn default() -> FloatingWindow {
51 FloatingWindow::new()
52 }
53}
54
55impl FloatingWindow {
56 pub const DEFAULT_SIZE: (u32, u32) = (400, 300);
58
59 pub fn new() -> FloatingWindow {
61 FloatingWindow {
62 title: None,
63 width: None,
64 height: None,
65 min_size: None,
66 max_size: None,
67 maximized: None,
68 fullscreen: None,
69 visible: None,
70 hints: Hints::default(),
71 on_close: None,
72 style: None,
73 child: None,
74 }
75 }
76
77 pub fn title(mut self, title: impl IntoProp<String>) -> FloatingWindow {
79 self.title = Some(title.into_prop());
80 self
81 }
82
83 pub fn width(mut self, width: impl IntoProp<u32>) -> FloatingWindow {
86 self.width = Some(width.into_prop());
87 self
88 }
89
90 pub fn height(mut self, height: impl IntoProp<u32>) -> FloatingWindow {
92 self.height = Some(height.into_prop());
93 self
94 }
95
96 pub fn min_size(mut self, size: impl IntoProp<(u32, u32)>) -> FloatingWindow {
99 self.min_size = Some(size.into_prop());
100 self
101 }
102
103 pub fn max_size(mut self, size: impl IntoProp<(u32, u32)>) -> FloatingWindow {
106 self.max_size = Some(size.into_prop());
107 self
108 }
109
110 pub fn maximized(mut self, maximized: impl IntoProp<bool>) -> FloatingWindow {
112 self.maximized = Some(maximized.into_prop());
113 self
114 }
115
116 pub fn fullscreen(mut self, fullscreen: impl IntoProp<bool>) -> FloatingWindow {
118 self.fullscreen = Some(fullscreen.into_prop());
119 self
120 }
121
122 pub fn visible(mut self, visible: impl IntoProp<bool>) -> FloatingWindow {
125 self.visible = Some(visible.into_prop());
126 self
127 }
128
129 pub fn mask(mut self, mask: impl IntoProp<Option<Region>>) -> FloatingWindow {
134 self.hints.mask = Some(mask.into_prop());
135 self
136 }
137
138 pub fn blur(mut self, region: impl IntoProp<Option<Region>>) -> FloatingWindow {
141 self.hints.blur = Some(region.into_prop());
142 self
143 }
144
145 pub fn inhibit_idle(mut self, inhibit: impl IntoProp<bool>) -> FloatingWindow {
149 self.hints.inhibit_idle = Some(inhibit.into_prop());
150 self
151 }
152
153 pub fn inhibit_shortcuts(mut self, inhibit: impl IntoProp<bool>) -> FloatingWindow {
158 self.hints.inhibit_shortcuts = Some(inhibit.into_prop());
159 self
160 }
161
162 pub fn shortcuts_inhibited(mut self, signal: Signal<bool>) -> FloatingWindow {
166 self.hints.shortcuts_inhibited = Some(signal);
167 self
168 }
169
170 pub fn focus_grab(mut self, signal: Signal<bool>) -> FloatingWindow {
174 self.hints.focus_grab = Some(signal);
175 self
176 }
177
178 pub fn compositor_opacity(mut self, opacity: impl IntoProp<f32>) -> FloatingWindow {
183 self.hints.compositor_opacity = Some(opacity.into_prop());
184 self
185 }
186
187 pub fn visible_mask(mut self, region: impl IntoProp<Option<Region>>) -> FloatingWindow {
191 self.hints.visible_mask = Some(region.into_prop());
192 self
193 }
194
195 pub fn on_close(mut self, handler: impl Fn(&Cx) + 'static) -> FloatingWindow {
199 self.on_close = Some(Rc::new(Handler::new(handler)));
200 self
201 }
202
203 pub fn style(mut self, style: impl IntoProp<Style>) -> FloatingWindow {
206 self.style = Some(style.into_prop());
207 self
208 }
209
210 pub fn child(mut self, child: impl Widget) -> FloatingWindow {
212 self.child = Some(Box::new(child));
213 self
214 }
215}
216
217#[derive(Clone, Debug, PartialEq, Eq)]
219pub struct FloatingConfig {
220 pub title: String,
222 pub width: u32,
224 pub height: u32,
226 pub min_size: (u32, u32),
228 pub max_size: (u32, u32),
230 pub maximized: bool,
232 pub fullscreen: bool,
234 pub shown: bool,
236}
237
238#[derive(Clone, Copy, Debug, PartialEq, Eq)]
242pub enum WindowRequest {
243 Move,
245 Resize(Anchors),
247 Minimize,
249}
250
251pub(crate) struct FloatingState {
253 title: String,
254 width: u32,
255 height: u32,
256 min_size: (u32, u32),
257 max_size: (u32, u32),
258 maximized: bool,
259 fullscreen: bool,
260 visible: bool,
261 dismissed: bool,
263 on_close: Signal<Option<CloseHandler>>,
266 pub(crate) requests: Vec<WindowRequest>,
267}
268
269impl Window {
270 pub fn floating(cx: &Cx, mut window: FloatingWindow) -> Window {
273 let child = untrack(|| window.child.take().map(|child| child.into_element()));
274 let path: Rc<str> = "FloatingWindow".into();
275 let cx = cx
276 .child()
277 .named(path.to_string())
278 .at(Rc::new(Anchor::default()));
279 let owner = cx.owner();
280 let on_close = cx.signal(window.on_close.take());
281 let tree = Rc::new_cyclic(|this| {
282 RefCell::new(Tree::new(
283 this.clone(),
284 "FloatingWindow",
285 Output::new(""),
286 cx,
287 path,
288 ))
289 });
290 borrow(&tree).mount_floating(window, child, on_close);
291 Window { tree, owner }
292 }
293
294 pub fn floating_config(&self) -> Option<FloatingConfig> {
297 let tree = self.tree.borrow();
298 let floating = tree.floating.as_ref()?;
299 let (width, height) = FloatingWindow::DEFAULT_SIZE;
300 Some(FloatingConfig {
301 title: floating.title.clone(),
302 width: if floating.width == 0 {
303 width
304 } else {
305 floating.width
306 },
307 height: if floating.height == 0 {
308 height
309 } else {
310 floating.height
311 },
312 min_size: floating.min_size,
313 max_size: floating.max_size,
314 maximized: floating.maximized,
315 fullscreen: floating.fullscreen,
316 shown: floating.visible && !floating.dismissed,
317 })
318 }
319
320 pub fn request_close(&self) {
323 let call = {
324 let mut tree = borrow(&self.tree);
325 let root = tree.root;
326 let Some(floating) = tree.floating.as_mut() else {
327 return;
328 };
329 let handler = untrack(|| floating.on_close.try_with(Option::clone)).flatten();
330 let Some(handler) = handler else {
331 floating.dismissed = true;
332 tree.surface.changed = true;
333 return;
334 };
335 let Some(node) = tree.node(root) else {
336 return;
337 };
338 (node.cx.clone(), Rc::clone(&node.path), handler)
339 };
340 let (cx, path, handler) = call;
341 run_isolated(&path, "on_close", handler, |f| f(&cx));
342 }
343
344 pub fn take_requests(&self) -> Vec<WindowRequest> {
346 let mut tree = borrow(&self.tree);
347 tree
348 .floating
349 .as_mut()
350 .map(|floating| std::mem::take(&mut floating.requests))
351 .unwrap_or_default()
352 }
353}
354
355impl Cx {
356 pub fn start_move(&self) {
360 self.request(WindowRequest::Move);
361 }
362
363 pub fn start_resize(&self, edges: impl Into<Anchors>) {
367 self.request(WindowRequest::Resize(edges.into()));
368 }
369
370 pub fn minimize(&self) {
372 self.request(WindowRequest::Minimize);
373 }
374
375 fn request(&self, request: WindowRequest) {
376 let Some((tree, _)) = self.anchor.as_ref().and_then(|anchor| anchor.get()) else {
377 return tracing::warn!("{request:?} needs a widget's context in a floating window");
378 };
379 let mut tree = borrow(&tree);
380 match tree.floating.as_mut() {
381 Some(floating) => floating.requests.push(request),
382 None => tracing::warn!("{request:?} works only in a floating window"),
383 }
384 }
385}
386
387impl Tree {
388 fn mount_floating(
390 &mut self,
391 window: FloatingWindow,
392 child: Option<crate::widgets::Element>,
393 on_close: Signal<Option<CloseHandler>>,
394 ) {
395 let (width, height) = FloatingWindow::DEFAULT_SIZE;
396 self.floating = Some(FloatingState {
397 title: String::new(),
398 width,
399 height,
400 min_size: (0, 0),
401 max_size: (0, 0),
402 maximized: false,
403 fullscreen: false,
404 visible: true,
405 dismissed: false,
406 on_close,
407 requests: Vec::new(),
408 });
409 let root = self.root;
410 let FloatingWindow {
411 title,
412 width,
413 height,
414 min_size,
415 max_size,
416 maximized,
417 fullscreen,
418 visible,
419 hints,
420 on_close: _,
421 style,
422 child: _,
423 } = window;
424 self.bind_opt(root, title, "title", |tree, _, value| {
425 tree.set_floating(|f| &mut f.title, value);
426 });
427 self.bind_opt(root, width, "width", |tree, _, value| {
428 tree.set_floating(|f| &mut f.width, value);
429 });
430 self.bind_opt(root, height, "height", |tree, _, value| {
431 tree.set_floating(|f| &mut f.height, value);
432 });
433 self.bind_opt(root, min_size, "min_size", |tree, _, value| {
434 tree.set_floating(|f| &mut f.min_size, value);
435 });
436 self.bind_opt(root, max_size, "max_size", |tree, _, value| {
437 tree.set_floating(|f| &mut f.max_size, value);
438 });
439 self.bind_opt(root, maximized, "maximized", |tree, _, value| {
440 tree.set_floating(|f| &mut f.maximized, value);
441 });
442 self.bind_opt(root, fullscreen, "fullscreen", |tree, _, value| {
443 tree.set_floating(|f| &mut f.fullscreen, value);
444 });
445 self.bind_opt(root, visible, "visible", |tree, _, value| {
446 if !value {
448 tree.set_floating(|f| &mut f.dismissed, false);
449 }
450 tree.set_floating(|f| &mut f.visible, value);
451 });
452 self.bind_compositor_hints(hints);
453 self.mount_root(style, child);
454 self.surface.changed = true;
455 }
456
457 fn set_floating<T: PartialEq>(
459 &mut self,
460 field: impl FnOnce(&mut FloatingState) -> &mut T,
461 value: T,
462 ) {
463 let Some(floating) = self.floating.as_mut() else {
464 return;
465 };
466 let slot = field(floating);
467 if *slot != value {
468 *slot = value;
469 self.surface.changed = true;
470 }
471 }
472}