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 blur(mut self, region: impl IntoProp<Option<Region>>) -> FloatingWindow {
132 self.hints.blur = Some(region.into_prop());
133 self
134 }
135
136 pub fn inhibit_idle(mut self, inhibit: impl IntoProp<bool>) -> FloatingWindow {
140 self.hints.inhibit_idle = Some(inhibit.into_prop());
141 self
142 }
143
144 pub fn inhibit_shortcuts(mut self, inhibit: impl IntoProp<bool>) -> FloatingWindow {
149 self.hints.inhibit_shortcuts = Some(inhibit.into_prop());
150 self
151 }
152
153 pub fn shortcuts_inhibited(mut self, signal: Signal<bool>) -> FloatingWindow {
157 self.hints.shortcuts_inhibited = Some(signal);
158 self
159 }
160
161 pub fn focus_grab(mut self, signal: Signal<bool>) -> FloatingWindow {
165 self.hints.focus_grab = Some(signal);
166 self
167 }
168
169 pub fn on_close(mut self, handler: impl Fn(&Cx) + 'static) -> FloatingWindow {
173 self.on_close = Some(Rc::new(Handler::new(handler)));
174 self
175 }
176
177 pub fn style(mut self, style: impl IntoProp<Style>) -> FloatingWindow {
180 self.style = Some(style.into_prop());
181 self
182 }
183
184 pub fn child(mut self, child: impl Widget) -> FloatingWindow {
186 self.child = Some(Box::new(child));
187 self
188 }
189}
190
191#[derive(Clone, Debug, PartialEq, Eq)]
193pub struct FloatingConfig {
194 pub title: String,
196 pub width: u32,
198 pub height: u32,
200 pub min_size: (u32, u32),
202 pub max_size: (u32, u32),
204 pub maximized: bool,
206 pub fullscreen: bool,
208 pub shown: bool,
210}
211
212#[derive(Clone, Copy, Debug, PartialEq, Eq)]
216pub enum WindowRequest {
217 Move,
219 Resize(Anchors),
221 Minimize,
223}
224
225pub(crate) struct FloatingState {
227 title: String,
228 width: u32,
229 height: u32,
230 min_size: (u32, u32),
231 max_size: (u32, u32),
232 maximized: bool,
233 fullscreen: bool,
234 visible: bool,
235 dismissed: bool,
237 on_close: Signal<Option<CloseHandler>>,
240 pub(crate) requests: Vec<WindowRequest>,
241}
242
243impl Window {
244 pub fn floating(cx: &Cx, mut window: FloatingWindow) -> Window {
247 let child = untrack(|| window.child.take().map(|child| child.into_element()));
248 let path: Rc<str> = "FloatingWindow".into();
249 let cx = cx
250 .child()
251 .named(path.to_string())
252 .at(Rc::new(Anchor::default()));
253 let owner = cx.owner();
254 let on_close = cx.signal(window.on_close.take());
255 let tree = Rc::new_cyclic(|this| {
256 RefCell::new(Tree::new(
257 this.clone(),
258 "FloatingWindow",
259 Output::new(""),
260 cx,
261 path,
262 ))
263 });
264 borrow(&tree).mount_floating(window, child, on_close);
265 Window { tree, owner }
266 }
267
268 pub fn floating_config(&self) -> Option<FloatingConfig> {
271 let tree = self.tree.borrow();
272 let floating = tree.floating.as_ref()?;
273 let (width, height) = FloatingWindow::DEFAULT_SIZE;
274 Some(FloatingConfig {
275 title: floating.title.clone(),
276 width: if floating.width == 0 {
277 width
278 } else {
279 floating.width
280 },
281 height: if floating.height == 0 {
282 height
283 } else {
284 floating.height
285 },
286 min_size: floating.min_size,
287 max_size: floating.max_size,
288 maximized: floating.maximized,
289 fullscreen: floating.fullscreen,
290 shown: floating.visible && !floating.dismissed,
291 })
292 }
293
294 pub fn request_close(&self) {
297 let call = {
298 let mut tree = borrow(&self.tree);
299 let root = tree.root;
300 let Some(floating) = tree.floating.as_mut() else {
301 return;
302 };
303 let handler = untrack(|| floating.on_close.try_with(Option::clone)).flatten();
304 let Some(handler) = handler else {
305 floating.dismissed = true;
306 tree.surface.changed = true;
307 return;
308 };
309 let Some(node) = tree.node(root) else {
310 return;
311 };
312 (node.cx.clone(), Rc::clone(&node.path), handler)
313 };
314 let (cx, path, handler) = call;
315 run_isolated(&path, "on_close", handler, |f| f(&cx));
316 }
317
318 pub fn take_requests(&self) -> Vec<WindowRequest> {
320 let mut tree = borrow(&self.tree);
321 tree
322 .floating
323 .as_mut()
324 .map(|floating| std::mem::take(&mut floating.requests))
325 .unwrap_or_default()
326 }
327}
328
329impl Cx {
330 pub fn start_move(&self) {
334 self.request(WindowRequest::Move);
335 }
336
337 pub fn start_resize(&self, edges: impl Into<Anchors>) {
341 self.request(WindowRequest::Resize(edges.into()));
342 }
343
344 pub fn minimize(&self) {
346 self.request(WindowRequest::Minimize);
347 }
348
349 fn request(&self, request: WindowRequest) {
350 let Some((tree, _)) = self.anchor.as_ref().and_then(|anchor| anchor.get()) else {
351 return tracing::warn!("{request:?} needs a widget's context in a floating window");
352 };
353 let mut tree = borrow(&tree);
354 match tree.floating.as_mut() {
355 Some(floating) => floating.requests.push(request),
356 None => tracing::warn!("{request:?} works only in a floating window"),
357 }
358 }
359}
360
361impl Tree {
362 fn mount_floating(
364 &mut self,
365 window: FloatingWindow,
366 child: Option<crate::widgets::Element>,
367 on_close: Signal<Option<CloseHandler>>,
368 ) {
369 let (width, height) = FloatingWindow::DEFAULT_SIZE;
370 self.floating = Some(FloatingState {
371 title: String::new(),
372 width,
373 height,
374 min_size: (0, 0),
375 max_size: (0, 0),
376 maximized: false,
377 fullscreen: false,
378 visible: true,
379 dismissed: false,
380 on_close,
381 requests: Vec::new(),
382 });
383 let root = self.root;
384 let FloatingWindow {
385 title,
386 width,
387 height,
388 min_size,
389 max_size,
390 maximized,
391 fullscreen,
392 visible,
393 hints,
394 on_close: _,
395 style,
396 child: _,
397 } = window;
398 self.bind_opt(root, title, "title", |tree, _, value| {
399 tree.set_floating(|f| &mut f.title, value);
400 });
401 self.bind_opt(root, width, "width", |tree, _, value| {
402 tree.set_floating(|f| &mut f.width, value);
403 });
404 self.bind_opt(root, height, "height", |tree, _, value| {
405 tree.set_floating(|f| &mut f.height, value);
406 });
407 self.bind_opt(root, min_size, "min_size", |tree, _, value| {
408 tree.set_floating(|f| &mut f.min_size, value);
409 });
410 self.bind_opt(root, max_size, "max_size", |tree, _, value| {
411 tree.set_floating(|f| &mut f.max_size, value);
412 });
413 self.bind_opt(root, maximized, "maximized", |tree, _, value| {
414 tree.set_floating(|f| &mut f.maximized, value);
415 });
416 self.bind_opt(root, fullscreen, "fullscreen", |tree, _, value| {
417 tree.set_floating(|f| &mut f.fullscreen, value);
418 });
419 self.bind_opt(root, visible, "visible", |tree, _, value| {
420 if !value {
422 tree.set_floating(|f| &mut f.dismissed, false);
423 }
424 tree.set_floating(|f| &mut f.visible, value);
425 });
426 self.bind_compositor_hints(hints);
427 self.mount_root(style, child);
428 self.surface.changed = true;
429 }
430
431 fn set_floating<T: PartialEq>(
433 &mut self,
434 field: impl FnOnce(&mut FloatingState) -> &mut T,
435 value: T,
436 ) {
437 let Some(floating) = self.floating.as_mut() else {
438 return;
439 };
440 let slot = field(floating);
441 if *slot != value {
442 *slot = value;
443 self.surface.changed = true;
444 }
445 }
446}