use std::cell::RefCell;
use std::rc::{Rc, Weak};
use crate::anim::Easing;
use crate::base::{Rect, Size};
use crate::reactive::{animate, Signal};
use crate::render::Cell;
use super::super::overlays::{LayerHandle, Overlays};
use super::{
origin_at, solve_geometry, DrawerCloseReason, DrawerEdge, DrawerFocus, DrawerHandle, Inner,
Mount,
};
thread_local! {
static EDGE_REGISTRY: RefCell<[Option<Weak<RefCell<Inner>>>; 4]> =
const { RefCell::new([None, None, None, None]) };
}
fn registry_claim(edge: DrawerEdge, me: &Rc<RefCell<Inner>>) {
let incumbent = EDGE_REGISTRY.with(|slots| {
let mut slots = slots.borrow_mut();
let old = slots[edge.index()].take();
slots[edge.index()] = Some(Rc::downgrade(me));
old.and_then(|w| w.upgrade())
});
if let Some(other) = incumbent {
if !Rc::ptr_eq(&other, me) {
close_now(&other, DrawerCloseReason::Replaced, true);
}
}
}
fn registry_release(edge: DrawerEdge, me: &Rc<RefCell<Inner>>) {
EDGE_REGISTRY.with(|slots| {
let mut slots = slots.borrow_mut();
if let Some(w) = &slots[edge.index()] {
if w.upgrade().is_some_and(|rc| Rc::ptr_eq(&rc, me)) {
slots[edge.index()] = None;
}
}
});
}
fn registry_holds(edge: DrawerEdge, me: &Rc<RefCell<Inner>>) -> bool {
EDGE_REGISTRY.with(|slots| {
slots.borrow()[edge.index()]
.as_ref()
.and_then(|w| w.upgrade())
.is_some_and(|rc| Rc::ptr_eq(&rc, me))
})
}
fn blur_passive_drawers(me: &Rc<RefCell<Inner>>) {
let others: Vec<Rc<RefCell<Inner>>> = EDGE_REGISTRY.with(|slots| {
slots
.borrow()
.iter()
.flatten()
.filter_map(|w| w.upgrade())
.filter(|rc| !Rc::ptr_eq(rc, me))
.collect()
});
for other in others {
let tree = {
let b = other.borrow();
if b.cfg.focus != DrawerFocus::Passive {
continue;
}
b.mount.as_ref().and_then(|m| m.panel.tree())
};
if let Some(mut tree) = tree {
tree.set_focus(None);
}
}
}
pub(super) fn open_now(inner: &Rc<RefCell<Inner>>) {
enum Plan {
Nothing,
Reopen {
closing: Signal<bool>,
progress: Signal<f32>,
bound: Option<Signal<bool>>,
},
Fresh,
}
let plan = {
let mut b = inner.borrow_mut();
if b.opening {
Plan::Nothing
} else if let Some(m) = &b.mount {
let (closing, progress) = (m.closing, m.progress);
if b.desired_open {
Plan::Nothing
} else {
b.desired_open = true;
b.pending_reason = None;
Plan::Reopen {
closing,
progress,
bound: b.bound,
}
}
} else {
b.opening = true;
Plan::Fresh
}
};
match plan {
Plan::Nothing => {}
Plan::Reopen {
closing,
progress,
bound,
} => {
if let Some(sig) = bound {
sig.set_if_changed(true);
}
closing.set_if_changed(false);
progress.set(1.0);
if inner.borrow().cfg.focus == DrawerFocus::Modal {
blur_passive_drawers(inner);
}
}
Plan::Fresh => open_fresh(inner),
}
}
fn open_fresh(inner: &Rc<RefCell<Inner>>) {
let (overlays, host, build, edge, size, modal, want_scrim, outside_closes, motion, bound) = {
let b = inner.borrow();
(
b.overlays.clone(),
b.host,
b.build.clone(),
b.cfg.edge,
b.cfg.size,
b.cfg.focus == DrawerFocus::Modal,
b.cfg.scrim && b.cfg.focus == DrawerFocus::Modal,
b.cfg.close_on_outside && b.cfg.focus == DrawerFocus::Modal,
b.cfg.motion,
b.bound,
)
};
let viewport = super::super::viewport::current_viewport();
if viewport.w <= 0 || viewport.h <= 0 {
inner.borrow_mut().opening = false;
return;
}
registry_claim(edge, inner);
let geo = solve_geometry(viewport, edge, size);
let scope = host.child();
let content = (build)(scope); if !registry_holds(edge, inner) {
scope.dispose();
inner.borrow_mut().opening = false;
return;
}
let handle = DrawerHandle {
inner: inner.clone(),
};
let view = {
let b = inner.borrow();
super::super::drawer_view::panel_view(&b.cfg, handle, content)
};
let veil = veil_cell();
let scrim = want_scrim.then(|| make_scrim(&overlays, edge.scrim_z(), viewport, veil));
let panel = overlays.layer_tree(
edge.panel_z(),
Rect::new(geo.closed.x, geo.closed.y, geo.rect.w, geo.rect.h),
modal,
scope,
view,
);
if outside_closes {
let weak = Rc::downgrade(inner);
overlays.on_outside_press(&panel, move || {
if let Some(inner) = weak.upgrade() {
begin_close(&inner, DrawerCloseReason::OutsidePress);
}
});
}
let progress = scope.signal(0.0f32);
let closing = scope.signal(false);
let geometry = scope.signal(geo);
let eased = animate(scope, progress, Easing::EaseOut, motion);
let weak = Rc::downgrade(inner);
let panel_for_slide = panel.clone();
scope.effect_labeled("drawer-slide", move || {
let t = eased.get().clamp(0.0, 1.0);
let geo = geometry.get();
if !panel_for_slide.is_alive() {
return;
}
panel_for_slide.set_offset(origin_at(geo, t));
if closing.get() && t <= f32::EPSILON {
if let Some(inner) = weak.upgrade() {
finish(&inner, false);
}
}
});
let weak = Rc::downgrade(inner);
scope.on_cleanup(move || {
if let Some(inner) = weak.upgrade() {
finish(&inner, true);
}
});
{
let mut b = inner.borrow_mut();
b.mount = Some(Mount {
scope,
panel,
scrim,
progress,
closing,
geometry,
veil,
});
b.desired_open = true;
b.opening = false;
}
if modal {
blur_passive_drawers(inner);
}
if let Some(sig) = bound {
sig.set_if_changed(true);
}
progress.set(1.0); }
pub(super) fn begin_close(inner: &Rc<RefCell<Inner>>, reason: DrawerCloseReason) {
let plan = {
let mut b = inner.borrow_mut();
if !b.desired_open || b.mount.is_none() {
None
} else {
b.desired_open = false;
b.pending_reason = Some(reason);
let m = b.mount.as_ref().expect("checked above");
Some((m.progress, m.closing, b.bound))
}
};
let Some((progress, closing, bound)) = plan else {
return;
};
if let Some(sig) = bound {
sig.set_if_changed(false);
}
progress.set(0.0);
closing.set(true);
}
pub(super) fn close_now(
inner: &Rc<RefCell<Inner>>,
reason: DrawerCloseReason,
dispose_scope: bool,
) {
{
let mut b = inner.borrow_mut();
if b.mount.is_none() {
return;
}
b.pending_reason.get_or_insert(reason);
}
finish(inner, !dispose_scope);
}
fn finish(inner: &Rc<RefCell<Inner>>, from_cleanup: bool) {
let (mount, reason, bound, overlays) = {
let mut b = inner.borrow_mut();
let Some(mount) = b.mount.take() else {
return;
};
b.desired_open = false;
b.opening = false;
let reason = b.pending_reason.take().unwrap_or({
if from_cleanup {
DrawerCloseReason::HostGone
} else {
DrawerCloseReason::Api
}
});
(mount, reason, b.bound, b.overlays.clone())
};
let resting = mount.geometry.try_get_untracked().map(|g| g.rect);
mount.panel.remove();
if let Some(scrim) = mount.scrim {
scrim.remove();
}
if let Some(rect) = resting {
overlays.damage_root_under_rect(rect);
}
if !from_cleanup {
mount.scope.dispose();
}
{
let edge = inner.borrow().cfg.edge;
registry_release(edge, inner);
}
if let Some(sig) = bound {
if sig.is_alive() {
sig.set_if_changed(false);
}
}
let taken = inner.borrow_mut().on_close.take();
if let Some(mut f) = taken {
f(reason);
let mut b = inner.borrow_mut();
if b.on_close.is_none() {
b.on_close = Some(f);
}
}
}
pub(super) fn reclamp(inner: &Rc<RefCell<Inner>>, viewport: Size) {
if viewport.w <= 0 || viewport.h <= 0 {
return; }
let work = {
let b = inner.borrow();
b.mount.as_ref().map(|m| {
(
m.panel.clone(),
m.scrim.clone(),
m.geometry,
m.veil,
b.cfg.edge,
b.cfg.size,
b.overlays.clone(),
)
})
};
let Some((panel, scrim, geometry, veil, edge, size, overlays)) = work else {
return;
};
let old_rect = geometry.try_get_untracked().map(|g| g.rect);
let geo = solve_geometry(viewport, edge, size);
if geometry
.try_get_untracked()
.is_none_or(|current| current == geo)
{
return;
}
panel.with_surface(|s| s.resize(geo.rect.size(), Cell::EMPTY));
if let Some(mut tree) = panel.tree() {
tree.set_viewport(geo.rect.size());
}
panel.damage();
if let Some(old) = old_rect {
overlays.damage_root_under_rect(old);
}
if let Some(scrim) = scrim {
scrim.with_surface(|s| {
s.resize(viewport, Cell::EMPTY);
s.fill_rect(Rect::from_size(viewport), veil);
});
scrim.damage();
}
geometry.set(geo); }
fn make_scrim(overlays: &Overlays, z: i32, viewport: Size, veil: Cell) -> LayerHandle {
let layer = overlays.layer(z, Rect::from_size(viewport));
layer.with_surface(|s| s.fill_rect(Rect::from_size(viewport), veil));
layer
}
fn veil_cell() -> Cell {
let mut cell = Cell::EMPTY;
cell.bg = super::super::theme::current_theme().tokens.overlay;
cell
}