use std::cell::{RefCell, UnsafeCell};
use smallvec::SmallVec;
use crate::{
prelude::*,
render_helper::{PureRender, RenderProxy},
widget::widget_id::RenderQueryable,
window::WindowId,
};
#[derive(Clone)]
pub struct Reusable(Rc<RefCell<ReusableState>>);
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(ReusableState::WaitToUse { track_id })));
(this.handle_reusable_wrapper(obj.into_widget()), this)
}
pub fn is_in_used(&self) -> bool {
let inner_state = self.0.borrow();
matches!(*inner_state, ReusableState::Using { .. })
}
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 w = match &mut *inner_state {
ReusableState::WaitToUse { .. } => {
panic!("Reusable must be used before it can be retrieved")
}
ReusableState::Using { render, track_id } => {
let render = render.clone();
let track_id = track_id.clone();
Widget::from_fn(move |ctx| {
move_inner_render_to_new(render.take(), track_id.get().unwrap(), ctx.tree_mut())
})
}
ReusableState::Recycled { track_id, wnd_id } => {
let wnd_id = *wnd_id;
Widget::from_fn({
let track_id = track_id.clone();
move |ctx| {
let current_wnd_id = ctx.window().id();
assert_eq!(
wnd_id, current_wnd_id,
"Reusable widget must be used in the same window it was recycled in."
);
track_id
.get()
.expect("reusable has no be used yet")
}
})
}
ReusableState::Dropped => {
panic!("Widget in invalid state for reuse. Expected Init/Recycled")
}
};
self.handle_reusable_wrapper(w)
}
pub fn release(&mut self) {
let mut inner_state = self.0.borrow_mut();
if let ReusableState::Recycled { track_id, wnd_id } = &*inner_state {
let wid = track_id.get().unwrap();
if let Some(wnd) = AppCtx::get_window(*wnd_id) {
wid.dispose_subtree(wnd.tree_mut());
}
}
*inner_state = ReusableState::Dropped;
}
fn handle_reusable_wrapper<'a>(&self, widget: Widget<'a>) -> Widget<'a> {
let mut fat = FatObj::new(widget);
let this = self.clone();
fat.on_disposed(move |e| {
if matches!(*this.0.borrow(), ReusableState::Using { .. }) {
let (render_cell, track_id) = match &*this.0.borrow() {
ReusableState::Using { render, track_id, .. } => (render.clone(), track_id.clone()),
_ => panic!("Widget in invalid state for reuse. Expected Using"),
};
if track_id.get() != Some(e.current_target()) {
return;
}
e.prevent_default();
let wnd = e.window();
let tree = wnd.tree_mut();
let id = e.current_target();
move_inner_render_to_new(render_cell.take(), id, tree);
*this.0.borrow_mut() =
ReusableState::Recycled { track_id: track_id.clone(), wnd_id: wnd.id() };
}
});
let this = self.clone();
fat
.into_widget()
.on_build(move |id| this.on_build(id))
}
fn on_build(&self, id: WidgetId) {
let track_id = match &*self.0.borrow() {
ReusableState::Using { track_id, .. }
| ReusableState::Recycled { track_id, .. }
| ReusableState::WaitToUse { track_id } => track_id.clone(),
_ => panic!("Widget in invalid state for reuse. Expected Init/Recycled"),
};
let render = ReusableRenderWrapper::default();
id.wrap_node(BuildCtx::get_mut().tree_mut(), |render_node| {
render.set(render_node);
Box::new(render.clone())
});
*self.0.borrow_mut() = ReusableState::Using { track_id, render };
}
}
fn move_inner_render_to_new(
inner: Box<dyn RenderQueryable>, origin: WidgetId, tree: &mut WidgetTree,
) -> WidgetId {
let new_id = tree.alloc_node(inner);
origin
.children(tree)
.collect::<SmallVec<[WidgetId; 1]>>()
.into_iter()
.for_each(|child| new_id.append(child, tree));
new_id
.get_node_mut(tree)
.unwrap()
.update_track_id(new_id);
new_id
}
#[derive(Clone)]
struct ReusableRenderWrapper(Rc<UnsafeCell<Box<dyn RenderQueryable>>>);
enum ReusableState {
WaitToUse { track_id: TrackId },
Using { track_id: TrackId, render: ReusableRenderWrapper },
Recycled { track_id: TrackId, wnd_id: WindowId },
Dropped,
}
impl RenderProxy for ReusableRenderWrapper {
fn proxy(&self) -> impl Deref<Target = impl Render + ?Sized> { self.as_render_node() }
}
impl Query for ReusableRenderWrapper {
fn query_all<'q>(&'q self, query_id: &QueryId, out: &mut SmallVec<[QueryHandle<'q>; 1]>) {
self.as_render_node().query_all(query_id, out);
}
fn query_all_write<'q>(&'q self, query_id: &QueryId, out: &mut SmallVec<[QueryHandle<'q>; 1]>) {
self
.as_render_node()
.query_all_write(query_id, out);
}
fn query<'q>(&'q self, query_id: &QueryId) -> Option<QueryHandle<'q>> {
self.as_render_node().query(query_id)
}
fn query_write<'q>(&'q self, query_id: &QueryId) -> Option<QueryHandle<'q>> {
self.as_render_node().query_write(query_id)
}
fn queryable(&self) -> bool { true }
}
impl ReusableRenderWrapper {
fn as_render_node(&self) -> &dyn RenderQueryable { unsafe { &*self.0.get() }.as_ref() }
fn set(&self, new_node: Box<dyn RenderQueryable>) -> Box<dyn RenderQueryable> {
unsafe { std::mem::replace(&mut *self.0.get(), new_node) }
}
fn take(&self) -> Box<dyn RenderQueryable> {
unsafe { std::mem::replace(&mut *self.0.get(), Box::new(PureRender(Void))) }
}
}
impl Default for ReusableRenderWrapper {
fn default() -> Self { Self(Rc::new(UnsafeCell::new(Box::new(PureRender(Void))))) }
}
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 {}.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"]);
}
}