use smallvec::SmallVec;
use crate::prelude::*;
#[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.of() }
#[inline]
pub fn write_of<T: 'static>(ctx: &impl ProviderCtx) -> Option<WriteRef<T>> { ctx.write_of() }
}
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_of<Q: 'static>(&self) -> impl Iterator<Item = QueryRef<Q>> {
self.all_providers().flat_map(|p| {
let mut out = SmallVec::new();
p.query_all(&QueryId::of::<Q>(), &mut out);
out.into_iter().filter_map(QueryHandle::into_ref)
})
}
fn all_write_of<Q: 'static>(&self) -> impl Iterator<Item = WriteRef<Q>> {
self.all_providers().flat_map(|p| {
let mut out = SmallVec::new();
p.query_all_write(&QueryId::of::<Q>(), &mut out);
out.into_iter().filter_map(QueryHandle::into_mut)
})
}
fn of<Q: 'static>(&self) -> Option<QueryRef<Q>> { self.all_of().next() }
fn write_of<Q: 'static>(&self) -> Option<WriteRef<Q>> { self.all_write_of().next() }
fn all_providers(&self) -> impl Iterator<Item = &dyn Query>;
}
impl ProviderCtx for BuildCtx {
fn all_providers(&self) -> impl Iterator<Item = &dyn Query> {
self
.current_providers
.iter()
.rev()
.map(|q| &**q)
.chain(self.providers.iter().rev().filter_map(|id| {
let r = id.assert_get(self.tree());
r.queryable().then(|| r.as_query())
}))
}
}
impl<T: Deref<Target: WidgetCtxImpl>> ProviderCtx for T {
fn all_providers(&self) -> impl Iterator<Item = &dyn Query> {
self.id().ancestors(self.tree()).filter_map(|id| {
let r = id.assert_get(self.tree());
r.queryable().then(|| r.as_query())
})
}
}
impl<const M: usize> Query for [Box<dyn Query>; M] {
fn query_all<'q>(&'q self, query_id: &QueryId, out: &mut SmallVec<[QueryHandle<'q>; 1]>) {
self
.iter()
.for_each(|q| q.query_all(query_id, out))
}
fn query_all_write<'q>(&'q self, query_id: &QueryId, out: &mut SmallVec<[QueryHandle<'q>; 1]>) {
self
.iter()
.for_each(|q| q.query_all_write(query_id, out))
}
fn query(&self, query_id: &QueryId) -> Option<QueryHandle> {
self.iter().find_map(|q| q.query(query_id))
}
fn query_write(&self, query_id: &QueryId) -> Option<QueryHandle> {
self.iter().find_map(|q| q.query_write(query_id))
}
fn query_match(
&self, ids: &[QueryId], filter: &dyn Fn(&QueryId, &QueryHandle) -> bool,
) -> Option<(QueryId, QueryHandle)> {
self
.iter()
.find_map(|q| q.query_match(ids, filter))
}
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);
}
}