use std::cell::RefCell;
use crate::prelude::*;
#[derive(Clone)]
pub struct Reusable(Rc<RefCell<ReusableInner>>);
impl Reusable {
pub fn new<'a, K>(w: impl IntoWidget<'a, K>) -> (Widget<'a>, Self) {
let mut obj = FatObj::new(w);
let track_id = obj.track_id();
let this =
Self(Rc::new(RefCell::new(ReusableInner { track_id, phase: ReusablePhase::Pending })));
(this.attach_reusable_host(obj.into_widget()), this)
}
pub fn is_in_used(&self) -> bool {
let inner_state = self.0.borrow();
matches!(inner_state.phase, ReusablePhase::Active { .. })
}
pub fn get_widget(&self) -> Widget<'static> {
let mut this = self.clone();
fn_widget! { this.gen_widget() }.into_widget()
}
fn gen_widget(&mut self) -> Widget<'static> {
let mut inner_state = self.0.borrow_mut();
let track_id = inner_state.track_id.clone();
let w = match &mut inner_state.phase {
ReusablePhase::Pending => {
panic!("Reusable must be used before it can be retrieved")
}
ReusablePhase::Active { host } => {
let host = host.clone();
let track_id = track_id.clone();
Widget::from_fn(move |ctx| {
host
.clone()
.rehost(track_id.get().unwrap(), ctx.tree_mut())
})
}
ReusablePhase::Cached { host, preserve } => {
let host = host.clone();
let preserve = preserve
.take()
.expect("recycled widget already taken for reuse");
inner_state.phase = ReusablePhase::Active { host };
preserve.into_widget()
}
ReusablePhase::Dropped => {
panic!("Widget in invalid state for reuse. Expected Init/Recycled")
}
};
self.attach_reusable_host(w)
}
pub fn release(&mut self) {
let mut inner_state = self.0.borrow_mut();
inner_state.phase = ReusablePhase::Dropped;
}
fn attach_reusable_host<'a>(&self, widget: Widget<'a>) -> Widget<'a> {
let mut fat = FatObj::new(widget);
let this = self.clone();
fat.on_disposing(move |e| {
if matches!(this.0.borrow().phase, ReusablePhase::Active { .. }) {
let (render, track_id) = match &*this.0.borrow() {
ReusableInner { track_id, phase: ReusablePhase::Active { host }, .. } => {
(host.clone(), track_id.clone())
}
_ => panic!("Widget in invalid state for reuse. Expected Active"),
};
if track_id.get() != Some(e.current_target()) {
return;
}
let preserve = e.preserve();
this.0.borrow_mut().phase =
ReusablePhase::Cached { host: render, preserve: Some(preserve) };
}
});
let this = self.clone();
fat
.into_widget()
.on_build(move |id| this.on_build(id))
}
fn on_build(&self, id: WidgetId) {
let host = PreserveHost::install(id, BuildCtx::get_mut().tree_mut());
let mut state = self.0.borrow_mut();
state.phase = ReusablePhase::Active { host };
}
}
struct ReusableInner {
track_id: TrackId,
phase: ReusablePhase,
}
enum ReusablePhase {
Pending,
Active { host: PreserveHost },
Cached { host: PreserveHost, preserve: Option<Preserve> },
Dropped,
}
impl Drop for Reusable {
fn drop(&mut self) {
if self.0.strong_count() == 1 {
self.release();
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::test_helper::{TestWindow, split_value};
#[test]
fn test_reusable() {
reset_test_env!();
let (info, w_info) = split_value(vec![]);
let (w_trigger, trigger) = split_value(0);
let ctrl = Rc::new(RefCell::new(None));
let ctrl2 = ctrl.clone();
let wnd = TestWindow::from_widget(fn_widget! {
let (w, reusable) = Reusable::new(@Text {
text: "Hello",
on_mounted: move |_| $write(w_info).push("Mounted"),
on_disposed: move |_| $write(w_info).push("Disposed"),
});
*ctrl2.borrow_mut() = Some(reusable.clone());
let mut w = Some(w.into_widget());
let f = GenWidget::new(move || {
if w.is_some() {
w.take().unwrap()
} else {
reusable.get_widget()
}
});
@ pipe!{
let f = f.clone();
fn_widget! {
if *$read(w_trigger) < 3 {
f.gen_widget()
} else {
Void::default().into_widget()
}
}
}
});
wnd.draw_frame();
assert_eq!(*info.read(), vec!["Mounted"]);
*trigger.write() += 1;
wnd.draw_frame();
assert_eq!(*info.read(), vec!["Mounted"]);
*trigger.write() += 1;
wnd.draw_frame();
assert_eq!(*info.read(), vec!["Mounted"]);
*trigger.write() += 1;
wnd.draw_frame();
assert_eq!(*info.read(), vec!["Mounted"]);
ctrl.borrow_mut().as_mut().unwrap().release();
wnd.draw_frame();
assert_eq!(*info.read(), vec!["Mounted", "Disposed"]);
}
}