pub(crate) use crate::widget_tree::*;
use crate::{
context::{build_ctx::BuildCtxHandle, *},
prelude::*,
};
use ribir_algo::{Sc, ShareResource};
use rxrust::ops::box_it::BoxOp;
#[doc(hidden)]
pub use std::{
any::{Any, TypeId},
marker::PhantomData,
ops::Deref,
};
use std::{cell::RefCell, convert::Infallible};
pub trait Compose: Sized {
fn compose(this: impl StateWriter<Value = Self>) -> impl WidgetBuilder;
}
pub struct HitTest {
pub hit: bool,
pub can_hit_child: bool,
}
pub trait Render: Query {
fn perform_layout(&self, clamp: BoxClamp, ctx: &mut LayoutCtx) -> Size;
fn paint(&self, ctx: &mut PaintingCtx);
fn only_sized_by_parent(&self) -> bool { false }
fn hit_test(&self, ctx: &HitTestCtx, pos: Point) -> HitTest {
let is_hit = hit_test_impl(ctx, pos);
HitTest { hit: is_hit, can_hit_child: is_hit }
}
fn get_transform(&self) -> Option<Transform> { None }
}
pub struct Widget {
id: WidgetId,
handle: BuildCtxHandle,
}
pub type BoxedWidget = Box<dyn for<'a, 'b> FnOnce(&'a BuildCtx<'b>) -> Widget>;
pub struct GenWidget(Box<dyn for<'a, 'b> FnMut(&'a BuildCtx<'b>) -> Widget>);
pub trait Query: Any {
fn query_inside_first(&self, type_id: TypeId, callback: &mut dyn FnMut(&dyn Any) -> bool)
-> bool;
fn query_outside_first(
&self,
type_id: TypeId,
callback: &mut dyn FnMut(&dyn Any) -> bool,
) -> bool;
}
impl<'a> dyn Render + 'a {
#[inline]
pub fn query_type_inside_first<T: Any>(&self, mut callback: impl FnMut(&T) -> bool) -> bool {
self.query_inside_first(TypeId::of::<T>(), &mut |a| {
a.downcast_ref().map_or(true, &mut callback)
})
}
#[inline]
pub fn query_type_outside_first<T: Any>(&self, mut callback: impl FnMut(&T) -> bool) -> bool {
Query::query_outside_first(self, TypeId::of::<T>(), &mut |a| {
a.downcast_ref().map_or(true, &mut callback)
})
}
pub fn query_most_inside<T: Any, R>(&self, callback: impl FnOnce(&T) -> R) -> Option<R> {
let mut callback = Some(callback);
let mut res = None;
self.query_type_inside_first(|a| {
let cb = callback.take().expect("should only call once");
res = Some(cb(a));
false
});
res
}
pub fn query_most_outside<T: Any, R>(&self, callback: impl FnOnce(&T) -> R) -> Option<R> {
let mut callback = Some(callback);
let mut res = None;
self.query_type_outside_first(|a| {
let cb = callback.take().expect("should only call once");
res = Some(cb(a));
false
});
res
}
pub fn contain_type<T: Any>(&self) -> bool {
let mut hit = false;
self.query_type_outside_first(|_: &T| {
hit = true;
false
});
hit
}
pub fn is<T: Any>(&self) -> bool { self.type_id() == TypeId::of::<T>() }
}
pub trait WidgetBuilder {
fn build(self, ctx: &BuildCtx) -> Widget;
fn box_it(self) -> BoxedWidget
where
Self: Sized + 'static,
{
Box::new(move |ctx| self.build(ctx))
}
}
pub trait ComposeBuilder {
fn build(self, ctx: &BuildCtx) -> Widget;
}
pub trait RenderBuilder {
fn build(self, ctx: &BuildCtx) -> Widget;
}
pub trait ComposeChildBuilder {
fn build(self, ctx: &BuildCtx) -> Widget;
}
pub trait SelfBuilder {
fn build(self, ctx: &BuildCtx) -> Widget;
}
impl Widget {
pub(crate) fn consume(self) -> WidgetId {
let id = self.id;
std::mem::forget(self);
id
}
pub(crate) fn dirty_subscribe(
self,
upstream: BoxOp<'static, ModifyScope, Infallible>,
ctx: &BuildCtx,
) -> Self {
let dirty_set = ctx.tree.borrow().dirty_set.clone();
let id = self.id();
let h = upstream
.filter(|b| b.contains(ModifyScope::FRAMEWORK))
.subscribe(move |_| {
dirty_set.borrow_mut().insert(id);
})
.unsubscribe_when_dropped();
self.attach_anonymous_data(h, ctx)
}
pub(crate) fn id(&self) -> WidgetId { self.id }
pub(crate) fn new(w: Box<dyn Render>, ctx: &BuildCtx) -> Self {
Self::from_id(ctx.alloc_widget(w), ctx)
}
pub(crate) fn from_id(id: WidgetId, ctx: &BuildCtx) -> Self { Self { id, handle: ctx.handle() } }
}
impl SelfBuilder for Widget {
#[inline(always)]
fn build(self, _: &BuildCtx) -> Widget { self }
}
impl<F> WidgetBuilder for F
where
F: FnOnce(&BuildCtx) -> Widget,
{
#[inline]
fn build(self, ctx: &BuildCtx) -> Widget { self(ctx) }
}
impl WidgetBuilder for GenWidget {
#[inline]
fn build(mut self, ctx: &BuildCtx) -> Widget { self.gen_widget(ctx) }
}
impl GenWidget {
#[inline]
pub fn new(f: impl FnMut(&BuildCtx) -> Widget + 'static) -> Self { Self(Box::new(f)) }
#[inline]
pub fn gen_widget(&mut self, ctx: &BuildCtx) -> Widget { (self.0)(ctx) }
}
impl<F: FnMut(&BuildCtx) -> Widget + 'static> From<F> for GenWidget {
#[inline]
fn from(f: F) -> Self { Self::new(f) }
}
macro_rules! impl_proxy_query {
($($t:tt)*) => {
#[inline]
fn query_inside_first(
&self,
type_id: TypeId,
callback: &mut dyn FnMut(&dyn Any) -> bool,
) -> bool {
self.$($t)*.query_inside_first(type_id, callback)
}
#[inline]
fn query_outside_first(
&self,
type_id: TypeId,
callback: &mut dyn FnMut(&dyn Any) -> bool,
) -> bool {
self.$($t)*.query_outside_first(type_id, callback)
}
}
}
macro_rules! impl_proxy_and_self_query {
($($t:tt)*) => {
fn query_inside_first(
&self,
type_id: TypeId,
callback: &mut dyn FnMut(&dyn Any) -> bool,
) -> bool {
if !self.$($t)*.query_inside_first(type_id, callback) {
return false
}
type_id != self.type_id() || callback(self)
}
fn query_outside_first(
&self,
type_id: TypeId,
callback: &mut dyn FnMut(&dyn Any) -> bool,
) -> bool {
if type_id == self.type_id() && !callback(self) {
return false
}
self.$($t)*.query_outside_first(type_id, callback)
}
}
}
macro_rules! impl_query_self_only {
() => {
#[inline]
fn query_inside_first(
&self,
type_id: TypeId,
callback: &mut dyn FnMut(&dyn Any) -> bool,
) -> bool {
self.query_outside_first(type_id, callback)
}
#[inline]
fn query_outside_first(
&self,
type_id: TypeId,
callback: &mut dyn FnMut(&dyn Any) -> bool,
) -> bool {
if type_id == self.type_id() {
callback(self)
} else {
true
}
}
};
}
pub(crate) use impl_proxy_and_self_query;
pub(crate) use impl_proxy_query;
pub(crate) use impl_query_self_only;
macro_rules! impl_proxy_render {
(
proxy $($mem: tt $(($($args: ident),*))?).*,
$name: ty $(,<$($($lf: lifetime)? $($p: ident)?), *>)?
$(,where $($w:tt)*)?
) => {
impl $(<$($($lf)? $($p)?),*>)? Render for $name $(where $($w)*)? {
#[inline]
fn perform_layout(&self, clamp: BoxClamp, ctx: &mut LayoutCtx) -> Size {
self.$($mem $(($($args),*))?).*.perform_layout(clamp, ctx)
}
#[inline]
fn paint(&self, ctx: &mut PaintingCtx) {
self.$($mem $(($($args),*))?).*.paint(ctx)
}
#[inline]
fn only_sized_by_parent(&self) -> bool {
self.$($mem $(($($args),*))?).*.only_sized_by_parent()
}
#[inline]
fn hit_test(&self, ctx: &HitTestCtx, pos: Point) -> HitTest {
self.$($mem $(($($args),*))?).*.hit_test(ctx, pos)
}
#[inline]
fn get_transform(&self) -> Option<Transform> {
self.$($mem $(($($args),*))?).*.get_transform()
}
}
};
}
impl<C: Compose + 'static> ComposeBuilder for C {
#[inline]
fn build(self, ctx: &BuildCtx) -> Widget { Compose::compose(State::value(self)).build(ctx) }
}
impl<R: Render + 'static> RenderBuilder for R {
#[inline]
fn build(self, ctx: &BuildCtx) -> Widget { Widget::new(Box::new(self), ctx) }
}
impl<W: ComposeChild<Child = Option<C>> + 'static, C> ComposeChildBuilder for W {
#[inline]
fn build(self, ctx: &BuildCtx) -> Widget {
ComposeChild::compose_child(State::value(self), None).build(ctx)
}
}
impl<T: Query> Query for ShareResource<T> {
impl_proxy_and_self_query!(deref());
}
impl<T: Query> Query for Sc<T> {
impl_proxy_and_self_query!(deref());
}
impl<T: Query> Query for RefCell<T> {
impl_proxy_and_self_query!(borrow());
}
impl_proxy_render!(proxy deref(), ShareResource<T>, <T>, where T: Render + 'static);
pub(crate) fn hit_test_impl(ctx: &HitTestCtx, pos: Point) -> bool {
ctx.box_rect().map_or(false, |rect| rect.contains(pos))
}
macro_rules! _replace {
(@replace($n: path) [$($e:tt)*] {#} $($rest:tt)*) => {
$crate::widget::_replace!(@replace($n) [$($e)* $n] $($rest)*);
};
(@replace($n: path) [$($e:tt)*] $first: tt $($rest:tt)*) => {
$crate::widget::_replace!(@replace($n) [$($e)* $first] $($rest)*);
};
(@replace($i: path) [$($e:tt)*]) => { $($e)* };
(@replace($n: path) $first: tt $($rest:tt)*) => {
$crate::widget::_replace!(@replace($n) [$first] $($rest)*);
};
}
macro_rules! multi_build_replace_impl {
($($rest:tt)*) => {
$crate::widget::repeat_and_replace!([
$crate::widget::ComposeBuilder,
$crate::widget::RenderBuilder,
$crate::widget::ComposeChildBuilder,
$crate::widget::WidgetBuilder
] $($rest)*);
};
}
macro_rules! multi_build_replace_impl_include_self {
($($rest:tt)*) => {
$crate::widget::multi_build_replace_impl!($($rest)*);
$crate::widget::_replace!(@replace($crate::widget::SelfBuilder) $($rest)*);
};
({} $($rest:tt)*) => {}
}
macro_rules! repeat_and_replace {
([$first: path $(,$n: path)*] $($rest:tt)*) => {
$crate::widget::_replace!(@replace($first) $($rest)*);
$crate::widget::repeat_and_replace!([$($n),*] $($rest)*);
};
([] $($rest:tt)*) => {
};
}
pub(crate) use _replace;
pub(crate) use multi_build_replace_impl;
pub(crate) use multi_build_replace_impl_include_self;
pub(crate) use repeat_and_replace;
impl Drop for Widget {
fn drop(&mut self) {
log::warn!("widget allocated but never used: {:?}", self.id);
self
.handle
.with_ctx(|ctx| ctx.tree.borrow_mut().remove_subtree(self.id));
}
}
#[cfg(test)]
mod tests {
use super::*;
macro_rules! impl_wrap_test {
($name: ident) => {
paste::paste! {
#[test]
fn [<$name:lower _support_query>]() {
let warp = $name::new(Void);
let void_tid = Void.type_id();
let w_tid = warp.type_id();
let mut hit = 0;
let mut hit_fn = |_: &dyn Any| {
hit += 1;
true
};
warp.query_inside_first(void_tid, &mut hit_fn);
warp.query_outside_first(void_tid, &mut hit_fn);
warp.query_inside_first(w_tid, &mut hit_fn);
warp.query_outside_first(w_tid, &mut hit_fn);
assert_eq!(hit, 4);
}
}
};
}
impl_wrap_test!(Sc);
impl_wrap_test!(RefCell);
impl_wrap_test!(ShareResource);
}