use smallvec::SmallVec;
use crate::{prelude::*, window::WindowId};
#[simple_declare]
pub struct Provider {
pub provider: Box<dyn Query>,
}
#[macro_export]
macro_rules! providers {
($($q: expr),*) => {
Provider::new(Box::new([$(Box::new($q) as Box<dyn Query>),*]))
};
}
impl Provider {
#[inline]
pub fn new(provider: Box<dyn Query>) -> Self { Provider { provider } }
#[inline]
pub fn of<T: 'static>(ctx: &impl ProviderCtx) -> Option<QueryRef<T>> { ctx.provider_of() }
#[inline]
pub fn write_of<T: 'static>(ctx: &impl ProviderCtx) -> Option<WriteRef<T>> {
ctx.provider_write_of()
}
}
#[derive(Debug, Clone, Copy)]
pub struct ProviderHandle {
pub(crate) widget: WidgetId,
pub(crate) wnd_id: WindowId,
}
impl<'c> ComposeChild<'c> for Provider {
type Child = FnWidget<'c>;
fn compose_child(this: impl StateWriter<Value = Self>, child: Self::Child) -> Widget<'c> {
let provider = this
.try_into_value()
.unwrap_or_else(|_| {
panic!(
"Provider should not be treated as a shared object to be held onto; instead, utilize \
`Provider::xxx_of` to access its content."
)
})
.provider;
let ctx = BuildCtx::get_mut();
ctx.current_providers.push(provider);
child.into_widget().on_build(|id| {
let provider = ctx.current_providers.pop().unwrap();
id.attach_data(provider, ctx.tree_mut());
})
}
}
pub trait ProviderCtx {
fn all_providers<Q: 'static>(&self) -> impl Iterator<Item = QueryRef<Q>>;
fn all_write_providers<Q: 'static>(&self) -> impl Iterator<Item = WriteRef<Q>>;
fn provider_of<Q: 'static>(&self) -> Option<QueryRef<Q>> { self.all_providers().next() }
fn provider_write_of<Q: 'static>(&self) -> Option<WriteRef<Q>> {
self.all_write_providers().next()
}
}
impl BuildCtx {
fn current_providers<Q: 'static>(&self) -> impl Iterator<Item = QueryRef<Q>> {
self
.current_providers
.iter()
.rev()
.filter_map(|p| p.query(TypeId::of::<Q>()))
.filter_map(QueryHandle::into_ref)
}
fn current_write_providers<Q: 'static>(&self) -> impl Iterator<Item = WriteRef<Q>> {
self
.current_providers
.iter()
.rev()
.filter_map(|p| p.query_write(TypeId::of::<Q>()))
.filter_map(QueryHandle::into_mut)
}
}
impl ProviderCtx for BuildCtx {
fn all_providers<Q: 'static>(&self) -> impl Iterator<Item = QueryRef<Q>> {
self.current_providers::<Q>().chain(
self
.providers
.iter()
.rev()
.filter_map(|id| id.query_ref(self.tree())),
)
}
fn all_write_providers<Q: 'static>(&self) -> impl Iterator<Item = WriteRef<Q>> {
self.current_write_providers::<Q>().chain(
self
.providers
.iter()
.rev()
.filter_map(|id| id.query_write(self.tree())),
)
}
}
impl<T: Deref<Target: WidgetCtxImpl>> ProviderCtx for T {
fn all_providers<Q: 'static>(&self) -> impl Iterator<Item = QueryRef<Q>> {
let tree = self.tree();
self
.id()
.ancestors(tree)
.filter_map(|id| id.query_ref(tree))
}
fn all_write_providers<Q: 'static>(&self) -> impl Iterator<Item = WriteRef<Q>> {
let tree = self.tree();
self
.id()
.ancestors(tree)
.filter_map(|id| id.query_write(tree))
}
}
impl ProviderCtx for ProviderHandle {
fn all_providers<Q: 'static>(&self) -> impl Iterator<Item = QueryRef<Q>> {
AppCtx::get_window(self.wnd_id)
.into_iter()
.flat_map(|wnd| {
BuildCtx::try_get()
.into_iter()
.flat_map(|ctx| ctx.current_providers::<Q>())
.chain({
let tree = unsafe { wnd.tree.as_ref() };
self
.widget
.ancestors(tree)
.filter_map(|id| id.query_ref(tree))
})
})
}
fn all_write_providers<Q: 'static>(&self) -> impl Iterator<Item = WriteRef<Q>> {
AppCtx::get_window(self.wnd_id)
.into_iter()
.flat_map(|wnd| {
BuildCtx::try_get()
.into_iter()
.flat_map(|ctx| ctx.current_write_providers::<Q>())
.chain({
let tree = unsafe { wnd.tree.as_ref() };
self
.widget
.ancestors(tree)
.filter_map(|id| id.query_write(tree))
})
})
}
}
impl<const M: usize> Query for [Box<dyn Query>; M] {
fn query_all<'q>(&'q self, type_id: TypeId, out: &mut SmallVec<[QueryHandle<'q>; 1]>) {
self
.iter()
.for_each(|q| q.query_all(type_id, out))
}
fn query(&self, type_id: TypeId) -> Option<QueryHandle> {
self.iter().find_map(|q| q.query(type_id))
}
fn query_write(&self, type_id: TypeId) -> Option<QueryHandle> {
self.iter().find_map(|q| q.query_write(type_id))
}
fn queryable(&self) -> bool { true }
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{reset_test_env, test_helper::*};
#[test]
fn direct_pass() {
reset_test_env!();
let (value, w_value) = split_value(0);
let w = fn_widget! {
let w_value = w_value.clone_writer();
Provider::new(Box::new(Queryable(1i32))).with_child(fn_widget! {
let v = Provider::of::<i32>(BuildCtx::get()).unwrap();
*w_value.write() = *v;
Void
})
};
let mut wnd = TestWindow::new(w);
wnd.draw_frame();
assert_eq!(*value.read(), 1);
}
#[test]
fn indirect_pass() {
reset_test_env!();
let (value, w_value) = split_value(0);
let w = fn_widget! {
let w_value = w_value.clone_writer();
Provider::new(Box::new(Queryable(1i32))).with_child(fn_widget! {
@MockBox {
size: Size::new(1.,1.),
@ {
let v = Provider::of::<i32>(BuildCtx::get()).unwrap();
*$w_value.write() = *v;
Void
}
}
})
};
let mut wnd = TestWindow::new(w);
wnd.draw_frame();
assert_eq!(*value.read(), 1);
}
#[test]
fn provider_for_pipe() {
reset_test_env!();
let (value, w_value) = split_value(0);
let (trigger, w_trigger) = split_value(true);
let w = fn_widget! {
let trigger = trigger.clone_watcher();
Provider::new(Box::new(w_value.clone_writer()))
.with_child(fn_widget! {
let value = Provider::of::<Stateful<i32>>(BuildCtx::get())
.unwrap().clone_writer();
pipe!(*$trigger).map(move |_| {
*value.write() += 1;
Void
})
})
};
let mut wnd = TestWindow::new(w);
wnd.draw_frame();
assert_eq!(*value.read(), 1);
*w_trigger.write() = false;
wnd.draw_frame();
assert_eq!(*value.read(), 2);
}
}