use std::any::Any;
use std::cell::{Cell, RefCell};
use std::rc::Rc;
use frust_core::{
AnyView, BoxConstraints, BuildCtx, ChangeFlags, ChildPod, EventCtx, EventResult, InputEvent,
LayoutCtx, PaintCtx, PaintScene, SemanticsCtx, View, Widget, any,
};
use kurbo::{Point, Size};
type Requests = Rc<Cell<u32>>;
thread_local! {
static ROW_SCOPES: RefCell<Vec<Requests>> = const { RefCell::new(Vec::new()) };
}
struct ScopeGuard;
impl Drop for ScopeGuard {
fn drop(&mut self) {
ROW_SCOPES.with(|scopes| {
scopes.borrow_mut().pop();
});
}
}
fn with_row_scope<R>(requests: &Requests, f: impl FnOnce() -> R) -> R {
ROW_SCOPES.with(|scopes| scopes.borrow_mut().push(Rc::clone(requests)));
let _guard = ScopeGuard;
f()
}
pub struct KeepAliveHandle {
requests: Requests,
held: bool,
}
impl KeepAliveHandle {
pub fn acquire() -> Option<Self> {
ROW_SCOPES
.with(|scopes| scopes.borrow().last().cloned())
.map(|requests| Self {
requests,
held: false,
})
}
pub fn set(&mut self, keep_alive: bool) {
if keep_alive == self.held {
return;
}
self.held = keep_alive;
let count = self.requests.get();
self.requests.set(if keep_alive {
count.saturating_add(1)
} else {
count.saturating_sub(1)
});
}
pub fn is_held(&self) -> bool {
self.held
}
}
impl Drop for KeepAliveHandle {
fn drop(&mut self) {
self.set(false);
}
}
pub fn keep_alive<State: 'static, V: View<State>>(child: V) -> KeepAliveView<State> {
KeepAliveView {
child: any(child),
enabled: true,
}
}
pub struct KeepAliveView<State: 'static> {
child: AnyView<State>,
enabled: bool,
}
impl<State: 'static> KeepAliveView<State> {
pub fn enabled(mut self, enabled: bool) -> Self {
self.enabled = enabled;
self
}
}
pub struct KeepAliveWidget {
child: ChildPod,
handle: Option<KeepAliveHandle>,
}
impl<State: 'static> View<State> for KeepAliveView<State> {
type Element = KeepAliveWidget;
fn build(&self, ctx: &mut BuildCtx<'_>) -> KeepAliveWidget {
let mut handle = KeepAliveHandle::acquire();
if let Some(handle) = handle.as_mut() {
handle.set(self.enabled);
}
KeepAliveWidget {
child: crate::authoring::build_child(&self.child, ctx),
handle,
}
}
fn rebuild(
&self,
prev: &Self,
element: &mut KeepAliveWidget,
ctx: &mut BuildCtx<'_>,
) -> ChangeFlags {
if let Some(handle) = element.handle.as_mut() {
handle.set(self.enabled);
}
crate::authoring::rebuild_child(&prev.child, &self.child, &mut element.child, ctx)
}
fn teardown(&self, element: &mut KeepAliveWidget, ctx: &mut BuildCtx<'_>) {
if let Some(handle) = element.handle.as_mut() {
handle.set(false);
}
crate::authoring::teardown_child(&self.child, &mut element.child, ctx);
}
}
impl Widget for KeepAliveWidget {
fn layout(&mut self, ctx: &mut LayoutCtx, bc: &BoxConstraints) -> Size {
let size = self.child.layout_child(ctx, bc);
self.child.set_origin(Point::ZERO);
size
}
fn paint(&mut self, ctx: &mut PaintCtx, scene: &mut dyn PaintScene) {
self.child.paint_child(ctx, scene);
}
fn event(&mut self, ctx: &mut EventCtx, event: &InputEvent) -> EventResult {
crate::authoring::route_event_single(&mut self.child, ctx, event)
}
fn semantics(&self, ctx: &mut SemanticsCtx) {
self.child.semantics_child(ctx);
}
crate::authoring::visit_children!(child);
}
pub(crate) struct RowScope<V> {
child: V,
}
impl<V> RowScope<V> {
pub(crate) fn new(child: V) -> Self {
Self { child }
}
}
pub(crate) struct RowScopeWidget {
child: ChildPod,
requests: Requests,
}
impl<State: 'static, V: View<State>> View<State> for RowScope<V> {
type Element = RowScopeWidget;
fn build(&self, ctx: &mut BuildCtx<'_>) -> RowScopeWidget {
let requests = Requests::default();
let child = with_row_scope(&requests, || {
crate::authoring::build_child(&self.child, ctx)
});
RowScopeWidget { child, requests }
}
fn rebuild(
&self,
prev: &Self,
element: &mut RowScopeWidget,
ctx: &mut BuildCtx<'_>,
) -> ChangeFlags {
let requests = Rc::clone(&element.requests);
with_row_scope(&requests, || {
crate::authoring::rebuild_child(&prev.child, &self.child, &mut element.child, ctx)
})
}
fn teardown(&self, element: &mut RowScopeWidget, ctx: &mut BuildCtx<'_>) {
crate::authoring::teardown_child(&self.child, &mut element.child, ctx);
}
}
impl Widget for RowScopeWidget {
fn layout(&mut self, ctx: &mut LayoutCtx, bc: &BoxConstraints) -> Size {
let size = self.child.layout_child(ctx, bc);
self.child.set_origin(Point::ZERO);
size
}
fn paint(&mut self, ctx: &mut PaintCtx, scene: &mut dyn PaintScene) {
self.child.paint_child(ctx, scene);
}
fn event(&mut self, ctx: &mut EventCtx, event: &InputEvent) -> EventResult {
crate::authoring::route_event_single(&mut self.child, ctx, event)
}
fn semantics(&self, ctx: &mut SemanticsCtx) {
self.child.semantics_child(ctx);
}
crate::authoring::visit_children!(child);
}
pub(crate) fn row_wants_keep_alive(pod: &ChildPod) -> bool {
let widget: &dyn Any = pod.widget();
let scope = match widget.downcast_ref::<Box<dyn Widget>>() {
Some(boxed) => {
let inner: &dyn Any = &**boxed;
inner.downcast_ref::<RowScopeWidget>()
}
None => widget.downcast_ref::<RowScopeWidget>(),
};
scope.is_some_and(|scope| scope.requests.get() > 0)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn a_handle_binds_to_the_innermost_row_and_counts_once() {
assert!(KeepAliveHandle::acquire().is_none(), "no row, no handle");
let outer = Requests::default();
let inner = Requests::default();
let (mut a, mut b) = with_row_scope(&outer, || {
let a = KeepAliveHandle::acquire().expect("in a row");
let b = with_row_scope(&inner, || KeepAliveHandle::acquire().expect("nested row"));
(a, b)
});
assert!(KeepAliveHandle::acquire().is_none(), "scopes closed");
a.set(true);
a.set(true);
b.set(true);
assert_eq!((outer.get(), inner.get()), (1, 1));
a.set(false);
assert_eq!(outer.get(), 0);
drop(b);
assert_eq!(inner.get(), 0, "dropping a handle withdraws its request");
assert!(!a.is_held());
}
}