use std::cell::RefCell;
use std::rc::Rc;
use crabshell_reactive::{Signal, untrack};
use crate::cx::{Anchor, Cx};
use crate::input::{Handler, run_isolated};
use crate::prop::{IntoProp, Prop};
use crate::region::Region;
use crate::style::Style;
use crate::tree::{Tree, borrow};
use crate::widgets::Widget;
use crate::window::{Anchors, Hints, Output, Window};
pub(crate) type CloseHandler = Rc<Handler<dyn Fn(&Cx)>>;
pub struct FloatingWindow {
pub(crate) title: Option<Prop<String>>,
pub(crate) width: Option<Prop<u32>>,
pub(crate) height: Option<Prop<u32>>,
pub(crate) min_size: Option<Prop<(u32, u32)>>,
pub(crate) max_size: Option<Prop<(u32, u32)>>,
pub(crate) maximized: Option<Prop<bool>>,
pub(crate) fullscreen: Option<Prop<bool>>,
pub(crate) visible: Option<Prop<bool>>,
pub(crate) hints: Hints,
pub(crate) on_close: Option<CloseHandler>,
pub(crate) style: Option<Prop<Style>>,
pub(crate) child: Option<Box<dyn Widget>>,
}
impl Default for FloatingWindow {
fn default() -> FloatingWindow {
FloatingWindow::new()
}
}
impl FloatingWindow {
pub const DEFAULT_SIZE: (u32, u32) = (400, 300);
pub fn new() -> FloatingWindow {
FloatingWindow {
title: None,
width: None,
height: None,
min_size: None,
max_size: None,
maximized: None,
fullscreen: None,
visible: None,
hints: Hints::default(),
on_close: None,
style: None,
child: None,
}
}
pub fn title(mut self, title: impl IntoProp<String>) -> FloatingWindow {
self.title = Some(title.into_prop());
self
}
pub fn width(mut self, width: impl IntoProp<u32>) -> FloatingWindow {
self.width = Some(width.into_prop());
self
}
pub fn height(mut self, height: impl IntoProp<u32>) -> FloatingWindow {
self.height = Some(height.into_prop());
self
}
pub fn min_size(mut self, size: impl IntoProp<(u32, u32)>) -> FloatingWindow {
self.min_size = Some(size.into_prop());
self
}
pub fn max_size(mut self, size: impl IntoProp<(u32, u32)>) -> FloatingWindow {
self.max_size = Some(size.into_prop());
self
}
pub fn maximized(mut self, maximized: impl IntoProp<bool>) -> FloatingWindow {
self.maximized = Some(maximized.into_prop());
self
}
pub fn fullscreen(mut self, fullscreen: impl IntoProp<bool>) -> FloatingWindow {
self.fullscreen = Some(fullscreen.into_prop());
self
}
pub fn visible(mut self, visible: impl IntoProp<bool>) -> FloatingWindow {
self.visible = Some(visible.into_prop());
self
}
pub fn blur(mut self, region: impl IntoProp<Option<Region>>) -> FloatingWindow {
self.hints.blur = Some(region.into_prop());
self
}
pub fn inhibit_idle(mut self, inhibit: impl IntoProp<bool>) -> FloatingWindow {
self.hints.inhibit_idle = Some(inhibit.into_prop());
self
}
pub fn inhibit_shortcuts(mut self, inhibit: impl IntoProp<bool>) -> FloatingWindow {
self.hints.inhibit_shortcuts = Some(inhibit.into_prop());
self
}
pub fn shortcuts_inhibited(mut self, signal: Signal<bool>) -> FloatingWindow {
self.hints.shortcuts_inhibited = Some(signal);
self
}
pub fn focus_grab(mut self, signal: Signal<bool>) -> FloatingWindow {
self.hints.focus_grab = Some(signal);
self
}
pub fn on_close(mut self, handler: impl Fn(&Cx) + 'static) -> FloatingWindow {
self.on_close = Some(Rc::new(Handler::new(handler)));
self
}
pub fn style(mut self, style: impl IntoProp<Style>) -> FloatingWindow {
self.style = Some(style.into_prop());
self
}
pub fn child(mut self, child: impl Widget) -> FloatingWindow {
self.child = Some(Box::new(child));
self
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct FloatingConfig {
pub title: String,
pub width: u32,
pub height: u32,
pub min_size: (u32, u32),
pub max_size: (u32, u32),
pub maximized: bool,
pub fullscreen: bool,
pub shown: bool,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum WindowRequest {
Move,
Resize(Anchors),
Minimize,
}
pub(crate) struct FloatingState {
title: String,
width: u32,
height: u32,
min_size: (u32, u32),
max_size: (u32, u32),
maximized: bool,
fullscreen: bool,
visible: bool,
dismissed: bool,
on_close: Signal<Option<CloseHandler>>,
pub(crate) requests: Vec<WindowRequest>,
}
impl Window {
pub fn floating(cx: &Cx, mut window: FloatingWindow) -> Window {
let child = untrack(|| window.child.take().map(|child| child.into_element()));
let path: Rc<str> = "FloatingWindow".into();
let cx = cx
.child()
.named(path.to_string())
.at(Rc::new(Anchor::default()));
let owner = cx.owner();
let on_close = cx.signal(window.on_close.take());
let tree = Rc::new_cyclic(|this| {
RefCell::new(Tree::new(
this.clone(),
"FloatingWindow",
Output::new(""),
cx,
path,
))
});
borrow(&tree).mount_floating(window, child, on_close);
Window { tree, owner }
}
pub fn floating_config(&self) -> Option<FloatingConfig> {
let tree = self.tree.borrow();
let floating = tree.floating.as_ref()?;
let (width, height) = FloatingWindow::DEFAULT_SIZE;
Some(FloatingConfig {
title: floating.title.clone(),
width: if floating.width == 0 {
width
} else {
floating.width
},
height: if floating.height == 0 {
height
} else {
floating.height
},
min_size: floating.min_size,
max_size: floating.max_size,
maximized: floating.maximized,
fullscreen: floating.fullscreen,
shown: floating.visible && !floating.dismissed,
})
}
pub fn request_close(&self) {
let call = {
let mut tree = borrow(&self.tree);
let root = tree.root;
let Some(floating) = tree.floating.as_mut() else {
return;
};
let handler = untrack(|| floating.on_close.try_with(Option::clone)).flatten();
let Some(handler) = handler else {
floating.dismissed = true;
tree.surface.changed = true;
return;
};
let Some(node) = tree.node(root) else {
return;
};
(node.cx.clone(), Rc::clone(&node.path), handler)
};
let (cx, path, handler) = call;
run_isolated(&path, "on_close", handler, |f| f(&cx));
}
pub fn take_requests(&self) -> Vec<WindowRequest> {
let mut tree = borrow(&self.tree);
tree
.floating
.as_mut()
.map(|floating| std::mem::take(&mut floating.requests))
.unwrap_or_default()
}
}
impl Cx {
pub fn start_move(&self) {
self.request(WindowRequest::Move);
}
pub fn start_resize(&self, edges: impl Into<Anchors>) {
self.request(WindowRequest::Resize(edges.into()));
}
pub fn minimize(&self) {
self.request(WindowRequest::Minimize);
}
fn request(&self, request: WindowRequest) {
let Some((tree, _)) = self.anchor.as_ref().and_then(|anchor| anchor.get()) else {
return tracing::warn!("{request:?} needs a widget's context in a floating window");
};
let mut tree = borrow(&tree);
match tree.floating.as_mut() {
Some(floating) => floating.requests.push(request),
None => tracing::warn!("{request:?} works only in a floating window"),
}
}
}
impl Tree {
fn mount_floating(
&mut self,
window: FloatingWindow,
child: Option<crate::widgets::Element>,
on_close: Signal<Option<CloseHandler>>,
) {
let (width, height) = FloatingWindow::DEFAULT_SIZE;
self.floating = Some(FloatingState {
title: String::new(),
width,
height,
min_size: (0, 0),
max_size: (0, 0),
maximized: false,
fullscreen: false,
visible: true,
dismissed: false,
on_close,
requests: Vec::new(),
});
let root = self.root;
let FloatingWindow {
title,
width,
height,
min_size,
max_size,
maximized,
fullscreen,
visible,
hints,
on_close: _,
style,
child: _,
} = window;
self.bind_opt(root, title, "title", |tree, _, value| {
tree.set_floating(|f| &mut f.title, value);
});
self.bind_opt(root, width, "width", |tree, _, value| {
tree.set_floating(|f| &mut f.width, value);
});
self.bind_opt(root, height, "height", |tree, _, value| {
tree.set_floating(|f| &mut f.height, value);
});
self.bind_opt(root, min_size, "min_size", |tree, _, value| {
tree.set_floating(|f| &mut f.min_size, value);
});
self.bind_opt(root, max_size, "max_size", |tree, _, value| {
tree.set_floating(|f| &mut f.max_size, value);
});
self.bind_opt(root, maximized, "maximized", |tree, _, value| {
tree.set_floating(|f| &mut f.maximized, value);
});
self.bind_opt(root, fullscreen, "fullscreen", |tree, _, value| {
tree.set_floating(|f| &mut f.fullscreen, value);
});
self.bind_opt(root, visible, "visible", |tree, _, value| {
if !value {
tree.set_floating(|f| &mut f.dismissed, false);
}
tree.set_floating(|f| &mut f.visible, value);
});
self.bind_compositor_hints(hints);
self.mount_root(style, child);
self.surface.changed = true;
}
fn set_floating<T: PartialEq>(
&mut self,
field: impl FnOnce(&mut FloatingState) -> &mut T,
value: T,
) {
let Some(floating) = self.floating.as_mut() else {
return;
};
let slot = field(floating);
if *slot != value {
*slot = value;
self.surface.changed = true;
}
}
}