use std::{ptr::NonNull, rc::Rc};
use smallvec::SmallVec;
use widget_id::{new_node, RenderQueryable};
use crate::{pipe::DynInfo, prelude::*, render_helper::PureRender, window::WindowId};
pub struct BuildCtx {
pub(crate) providers: SmallVec<[WidgetId; 1]>,
pub(crate) current_providers: SmallVec<[Box<dyn Query>; 1]>,
pub(crate) pre_alloc: Option<WidgetId>,
pub(crate) tree: NonNull<WidgetTree>,
}
#[derive(Debug, Clone)]
pub struct BuildCtxHandle {
startup: StartUpWidget,
wnd_id: WindowId,
}
#[derive(Debug, Clone)]
enum StartUpWidget {
Id(WidgetId),
PipeNode(DynInfo),
}
impl BuildCtx {
pub fn window(&self) -> Rc<Window> { self.tree().window() }
pub fn handle(&self) -> BuildCtxHandle {
let id = *self.providers.last().unwrap();
let startup = if let Some(info) = self
.current_providers
.iter()
.find_map(|p| p.query(TypeId::of::<DynInfo>()))
.and_then(|h| h.into_ref())
.or_else(|| id.query_ref::<DynInfo>(self.tree()))
{
StartUpWidget::PipeNode(info.clone())
} else {
StartUpWidget::Id(id)
};
BuildCtxHandle { wnd_id: self.window().id(), startup }
}
#[inline]
pub(crate) fn create(startup: WidgetId, tree: NonNull<WidgetTree>) -> BuildCtxGuard {
BuildCtxGuard::new(startup, tree)
}
pub(crate) fn tree(&self) -> &WidgetTree {
unsafe { self.tree.as_ref() }
}
pub(crate) fn tree_mut(&mut self) -> &mut WidgetTree {
let mut tree = self.tree;
unsafe { tree.as_mut() }
}
pub(crate) fn alloc(&mut self, node: Box<dyn RenderQueryable>) -> WidgetId {
if let Some(id) = self.pre_alloc.take() {
*id.get_node_mut(self.tree_mut()).unwrap() = node;
id
} else {
new_node(&mut self.tree_mut().arena, node)
}
}
pub(crate) fn pre_alloc(&mut self) -> WidgetId {
if let Some(id) = self.pre_alloc {
id
} else {
let id = new_node(&mut self.tree_mut().arena, Box::new(PureRender(Void)));
self.pre_alloc = Some(id);
id
}
}
pub(crate) fn consume_root_with_provider<'w>(
&mut self, w: Widget<'w>, provider: Box<dyn Query>,
) -> (Widget<'w>, Box<dyn Query>) {
self.current_providers.push(provider);
let w = w.consume_root(self);
let provider = self.current_providers.pop().unwrap();
(w, provider)
}
}
impl BuildCtxHandle {
pub fn with_ctx<R>(&self, f: impl FnOnce(&mut BuildCtx) -> R) -> Option<R> {
AppCtx::get_window(self.wnd_id).map(|wnd: Rc<Window>| {
let id = match &self.startup {
StartUpWidget::Id(id) => *id,
StartUpWidget::PipeNode(p) => p.borrow().host_id(),
};
let mut ctx = BuildCtx::create(id, wnd.tree);
f(&mut ctx)
})
}
}
static mut CURRENT_CTX: Option<BuildCtx> = None;
enum CtxRestore {
None,
Info { providers_len: usize, current_providers_len: usize },
}
pub(crate) struct BuildCtxGuard {
ctx: &'static mut BuildCtx,
restore: CtxRestore,
}
impl BuildCtxGuard {
pub(crate) fn new(startup: WidgetId, tree: NonNull<WidgetTree>) -> Self {
let t = unsafe { tree.as_ref() };
let providers_list = startup.ancestors(t).filter(|id| id.queryable(t));
if let Some(ctx) = unsafe { CURRENT_CTX.as_mut() } {
let last = ctx.providers.last().copied();
let providers: SmallVec<[WidgetId; 1]> = providers_list
.take_while(|id| Some(*id) != last)
.collect();
let providers_len = ctx.providers.len();
let current_providers_len = ctx.current_providers.len();
ctx
.providers
.extend(providers.iter().rev().copied());
BuildCtxGuard { ctx, restore: CtxRestore::Info { providers_len, current_providers_len } }
} else {
let mut providers: SmallVec<[WidgetId; 1]> = providers_list.collect();
providers.reverse();
unsafe {
CURRENT_CTX =
Some(BuildCtx { tree, providers, pre_alloc: None, current_providers: <_>::default() });
BuildCtxGuard { ctx: CURRENT_CTX.as_mut().unwrap(), restore: CtxRestore::None }
}
}
}
}
impl std::ops::Deref for BuildCtxGuard {
type Target = BuildCtx;
fn deref(&self) -> &Self::Target { &*self.ctx }
}
impl std::ops::DerefMut for BuildCtxGuard {
fn deref_mut(&mut self) -> &mut Self::Target { self.ctx }
}
impl Drop for BuildCtxGuard {
fn drop(&mut self) {
match self.restore {
CtxRestore::None => unsafe { CURRENT_CTX = None },
CtxRestore::Info { providers_len, current_providers_len } => {
assert!(self.ctx.providers.len() >= providers_len);
assert!(self.ctx.current_providers.len() >= current_providers_len);
self.ctx.providers.drain(providers_len..);
self
.ctx
.current_providers
.drain(current_providers_len..);
}
}
}
}