use std::cell::RefCell;
use ribir_algo::Sc;
use crate::{prelude::*, window::WindowId};
#[derive(Clone)]
pub struct Overlay(Sc<RefCell<InnerOverlay>>);
bitflags! {
#[derive(Clone, Copy)]
pub struct AutoClosePolicy: u8 {
const NONE = 0b0000;
const ESC = 0b0001;
const TAP_OUTSIDE = 0b0010;
}
}
struct InnerOverlay {
gen: GenWidget,
auto_close_policy: AutoClosePolicy,
mask: Option<Brush>,
showing: Option<ShowingInfo>,
}
struct ShowingInfo {
id: WidgetId,
wnd_id: WindowId,
generator: GenWidget,
}
impl Overlay {
pub fn new(gen: impl Into<GenWidget>) -> Self {
let gen = gen.into();
Self(Sc::new(RefCell::new(InnerOverlay {
gen,
auto_close_policy: AutoClosePolicy::ESC | AutoClosePolicy::TAP_OUTSIDE,
mask: None,
showing: None,
})))
}
pub fn of(ctx: &impl WidgetCtx) -> Option<Self> {
let wnd = ctx.window();
let tree = wnd.tree();
let overlays = tree
.root()
.query_ref::<ShowingOverlays>(tree)
.unwrap();
overlays.showing_of(ctx)
}
pub fn set_auto_close_policy(&self, policy: AutoClosePolicy) {
self.0.borrow_mut().auto_close_policy = policy
}
pub fn auto_close_policy(&self) -> AutoClosePolicy { self.0.borrow().auto_close_policy }
pub fn set_mask(&self, mask: Brush) { self.0.borrow_mut().mask = Some(mask); }
pub fn mask(&self) -> Option<Brush> { self.0.borrow().mask.clone() }
pub fn show(&self, wnd: Sc<Window>) {
if self.is_showing() {
return;
}
let gen = self.0.borrow().gen.clone();
self.inner_show(gen, wnd);
}
pub fn show_map<F>(&self, mut f: F, wnd: Sc<Window>)
where
F: FnMut(Widget) -> Widget + 'static,
{
if self.is_showing() {
return;
}
let gen = self.0.borrow().gen.clone();
let gen = move |_: &mut BuildCtx| f(gen.gen_widget());
self.inner_show(gen.into(), wnd);
}
pub fn show_at(&self, pos: Point, wnd: Sc<Window>) {
if self.is_showing() {
return;
}
self.show_map(
move |w| {
FatObj::new(w)
.anchor(Anchor::from_point(pos))
.into_widget()
},
wnd,
);
}
pub fn is_showing(&self) -> bool { self.0.borrow().showing.is_some() }
pub fn close(&self) {
let showing = self.0.borrow_mut().showing.take();
if let Some(showing) = showing {
let ShowingInfo { id, wnd_id, .. } = showing;
if let Some(wnd) = AppCtx::get_window(wnd_id) {
let ctx = BuildCtx::create(wnd.tree().root(), wnd.tree);
let showing_overlays = Provider::of::<ShowingOverlays>(&*ctx).unwrap();
showing_overlays.remove(self);
let tree = wnd.tree_mut();
let root = tree.root();
id.dispose_subtree(tree);
tree.mark_dirty(root);
}
}
}
fn inner_show(&self, content: GenWidget, wnd: Sc<Window>) {
let background = self.mask();
let gen = fn_widget! {
@Container {
size: Size::new(f32::INFINITY, f32::INFINITY),
background: background.clone(),
on_tap: move |e| {
if e.target() == e.current_target() {
if let Some(overlay) = Overlay::of(&**e)
.filter(|o| o.auto_close_policy().contains(AutoClosePolicy::TAP_OUTSIDE))
{
overlay.close();
}
}
},
on_key_down: move |e| {
if *e.key() == VirtualKey::Named(NamedKey::Escape) {
if let Some(overlay) = Overlay::of(&**e)
.filter(|o| o.auto_close_policy().contains(AutoClosePolicy::ESC))
{
overlay.close();
}
}
},
@ { content.gen_widget() }
}
};
let mut ctx = BuildCtx::create(wnd.tree().root(), wnd.tree);
let id = gen(&mut ctx).build(&mut ctx);
self.0.borrow_mut().showing = Some(ShowingInfo { id, generator: gen.into(), wnd_id: wnd.id() });
let showing_overlays = Provider::of::<ShowingOverlays>(&*ctx).unwrap();
showing_overlays.add(self.clone());
let tree = wnd.tree_mut();
tree.root().append(id, tree);
id.on_mounted_subtree(tree);
tree.mark_dirty(id);
}
fn showing_root(&self) -> Option<WidgetId> { self.0.borrow().showing.as_ref().map(|s| s.id) }
}
pub(crate) struct ShowingOverlays(RefCell<Vec<Overlay>>);
impl ShowingOverlays {
pub(crate) fn rebuild(&self, ctx: &mut BuildCtx) {
for o in self.0.borrow().iter() {
let mut o = o.0.borrow_mut();
let ShowingInfo { id, generator, .. } = o.showing.as_mut().unwrap();
id.dispose_subtree(ctx.tree_mut());
*id = generator.gen_widget().build(ctx);
let tree = ctx.tree_mut();
tree.root().append(*id, tree);
id.on_mounted_subtree(tree);
}
}
fn add(&self, overlay: Overlay) {
assert!(overlay.showing_root().is_some());
self.0.borrow_mut().push(overlay)
}
fn remove(&self, overlay: &Overlay) {
assert!(overlay.showing_root().is_none());
self
.0
.borrow_mut()
.retain(|o| !Sc::ptr_eq(&o.0, &overlay.0))
}
fn showing_of(&self, ctx: &impl WidgetCtx) -> Option<Overlay> {
self.0.borrow().iter().find_map(|o| {
o.showing_root()
.map_or(false, |w| ctx.successor_of(w))
.then(|| o.clone())
})
}
}
impl Default for ShowingOverlays {
fn default() -> Self { Self(RefCell::new(vec![])) }
}
#[cfg(test)]
mod tests {
use std::{cell::RefCell, rc::Rc};
use crate::{prelude::*, reset_test_env, test_helper::*};
#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)]
#[test]
fn overlay() {
reset_test_env!();
let size = Size::zero();
let widget = fn_widget! {
@MockBox {
size,
@MockBox { size }
}
};
let mut wnd = TestWindow::new(widget);
let w_log = Rc::new(RefCell::new(vec![]));
let r_log = w_log.clone();
let overlay = Overlay::new(fn_widget! {
@MockBox {
size,
on_mounted: {
let w_log = w_log.clone();
move |_| { w_log.borrow_mut().push("mounted");}
},
on_disposed: {
let w_log = w_log.clone();
move |_| { w_log.borrow_mut().push("disposed");}
}
}
});
wnd.draw_frame();
let root = wnd.tree().root();
assert_eq!(wnd.tree().count(root), 3);
overlay.show_at(Point::new(50., 30.), wnd.0.clone());
wnd.draw_frame();
assert_eq!(*r_log.borrow(), &["mounted"]);
assert_eq!(wnd.layout_info_by_path(&[1, 0]).unwrap().pos, Point::new(50., 30.));
overlay.close();
wnd.draw_frame();
assert_eq!(*r_log.borrow(), &["mounted", "disposed"]);
assert_eq!(wnd.tree().count(root), 3);
}
}