diff --git a/core/src/builtin_widgets/lifecycle.rs b/core/src/builtin_widgets/lifecycle.rs index ef1dce14e..1015b3cba 100644 --- a/core/src/builtin_widgets/lifecycle.rs +++ b/core/src/builtin_widgets/lifecycle.rs @@ -9,9 +9,8 @@ define_widget_context!(LifecycleEvent); pub type LifecycleSubject = MutRefItemSubject<'static, AllLifecycle, Infallible>; -#[derive(Declare2, Default)] +#[derive(Default)] pub struct LifecycleListener { - #[declare(skip)] lifecycle: LifecycleSubject, } @@ -42,7 +41,17 @@ macro_rules! match_closure { }; } -impl LifecycleListenerDeclarer2 { +impl Declare2 for LifecycleListener { + type Builder = Self; + fn declare2_builder() -> Self::Builder { Self::default() } +} + +impl DeclareBuilder for LifecycleListener { + type Target = State; + fn build_declare(self, _: &BuildCtx) -> Self::Target { State::value(self) } +} + +impl LifecycleListener { pub fn on_mounted(mut self, handler: impl FnMut(&mut LifecycleEvent) + 'static) -> Self { let _ = self .subject() @@ -72,13 +81,7 @@ impl LifecycleListenerDeclarer2 { self } - fn subject(&mut self) -> LifecycleSubject { - self - .lifecycle - .get_or_insert_with(DeclareInit::default) - .value() - .clone() - } + fn subject(&mut self) -> LifecycleSubject { self.lifecycle.clone() } } impl_query_self_only!(LifecycleListener); diff --git a/core/src/declare.rs b/core/src/declare.rs index 587f9e62f..5d5b6284a 100644 --- a/core/src/declare.rs +++ b/core/src/declare.rs @@ -1,4 +1,4 @@ -use crate::{context::BuildCtx, prelude::Pipe, state::ModifyScope}; +use crate::{context::BuildCtx, pipe::Pipe, state::ModifyScope}; use rxrust::ops::box_it::BoxOp; use std::convert::Infallible; @@ -21,7 +21,7 @@ pub trait DeclareBuilder { /// The type use to store the init value of the field when declare a object. pub enum DeclareInit { Value(V), - Pipe(Pipe), + Pipe(Box>), } type ValueStream = BoxOp<'static, (ModifyScope, V), Infallible>; @@ -31,25 +31,11 @@ impl DeclareInit { match self { Self::Value(v) => (v, None), Self::Pipe(v) => { - let (v, pipe) = v.unzip(); + let (v, pipe) = v.box_unzip(); (v, Some(pipe)) } } } - - pub fn value(&self) -> &V { - match self { - Self::Value(v) => v, - Self::Pipe(v) => v.value(), - } - } - - pub fn value_mut(&mut self) -> &mut V { - match self { - Self::Value(v) => v, - Self::Pipe(v) => v.value_mut(), - } - } } impl Default for DeclareInit { @@ -66,9 +52,12 @@ impl> DeclareFrom for DeclareInit { fn declare_from(value: V) -> Self { Self::Value(value.into()) } } -impl + 'static> DeclareFrom, Pipe<()>> for DeclareInit { +impl DeclareFrom> for DeclareInit +where + V: From + 'static, +{ #[inline] - fn declare_from(value: Pipe) -> Self { Self::Pipe(value.map(U::from)) } + fn declare_from(value: P) -> Self { Self::Pipe(Box::new(Pipe::map(value, |v| v.into()))) } } /// struct help the generate code have better type hint. diff --git a/core/src/events/listener_impl_helper.rs b/core/src/events/listener_impl_helper.rs index 975ab1ee2..880a2c6c8 100644 --- a/core/src/events/listener_impl_helper.rs +++ b/core/src/events/listener_impl_helper.rs @@ -43,22 +43,30 @@ macro_rules! impl_listener { ($doc: literal, $name: ident, $event_ty: ident) => { paste::paste! { #[doc= $doc] - #[derive(Declare2)] - pub struct [<$name Listener>]{ - #[declare(skip)] + pub struct [<$name Listener>]{ [<$name:snake _subject>]: [<$name Subject>] } - impl [<$name ListenerDeclarer2>] { + impl [<$name Listener>] { fn subject(&mut self) -> [<$name Subject>] { self .[<$name:snake _subject>] - .get_or_insert_with(DeclareInit::default) - .value() .clone() } } + impl Declare2 for [<$name Listener>] { + type Builder = Self; + fn declare2_builder() -> Self::Builder { + Self { [<$name:snake _subject>]: Default::default()} + } + } + + impl DeclareBuilder for [<$name Listener>] { + type Target = State; + fn build_declare(self, _ctx: &BuildCtx) -> Self::Target { State::value(self) } + } + impl [<$name Listener>] { /// Convert a observable stream of this event. pub fn [<$name:snake _stream>](&self) -> [<$name Subject>] { @@ -89,7 +97,7 @@ macro_rules! impl_multi_event_listener { impl_all_event!($name, $($on_doc, $event_ty),+); impl_listener!($doc, $name, []); - impl [<$name ListenerDeclarer2>] { + impl [<$name Listener>] { $( #[doc = "Sets up a function that will be called whenever the `" $event_ty "` is delivered"] pub fn []( diff --git a/core/src/events/pointers.rs b/core/src/events/pointers.rs index 63e2e5e16..161f67aaa 100644 --- a/core/src/events/pointers.rs +++ b/core/src/events/pointers.rs @@ -161,7 +161,7 @@ fn x_times_tap_map_filter( } } -impl PointerListenerDeclarer2 { +impl PointerListener { pub fn on_double_tap(self, handler: impl FnMut(&mut PointerEvent) + 'static) -> Self { self.on_x_times_tap((2, handler)) } @@ -207,48 +207,6 @@ impl PointerListenerDeclarer2 { } } -impl PointerListener { - pub fn on_double_tap(self, handler: impl FnMut(&mut PointerEvent) + 'static) { - self.on_x_times_tap((2, handler)) - } - - pub fn on_double_tap_capture(self, handler: impl FnMut(&mut PointerEvent) + 'static) { - self.on_x_times_tap_capture((2, handler)) - } - - pub fn on_triple_tap(self, handler: impl FnMut(&mut PointerEvent) + 'static) { - self.on_x_times_tap((3, handler)) - } - - pub fn on_triple_tap_capture(self, handler: impl FnMut(&mut PointerEvent) + 'static) { - self.on_x_times_tap_capture((3, handler)) - } - - pub fn on_x_times_tap(self, (times, handler): (usize, impl FnMut(&mut PointerEvent) + 'static)) { - self.on_x_times_tap_impl(times, MULTI_TAP_DURATION, false, handler); - } - - pub fn on_x_times_tap_capture( - self, - (times, handler): (usize, impl FnMut(&mut PointerEvent) + 'static), - ) { - self.on_x_times_tap_impl(times, MULTI_TAP_DURATION, true, handler); - } - - fn on_x_times_tap_impl( - &self, - x: usize, - dur: Duration, - capture: bool, - handler: impl FnMut(&mut PointerEvent) + 'static, - ) { - self - .pointer_stream() - .filter_map(x_times_tap_map_filter(x, dur, capture)) - .subscribe(handler); - } -} - #[cfg(test)] mod tests { use super::*; diff --git a/core/src/lib.rs b/core/src/lib.rs index 2ddcfa4aa..945204bdf 100644 --- a/core/src/lib.rs +++ b/core/src/lib.rs @@ -37,7 +37,7 @@ pub mod prelude { #[doc(no_inline)] pub use crate::events::*; #[doc(no_inline)] - pub use crate::pipe::Pipe; + pub use crate::pipe::{FinalChain, MapPipe, ModifiesPipe, Pipe}; #[doc(no_inline)] pub use crate::state::*; #[doc(no_inline)] diff --git a/core/src/pipe.rs b/core/src/pipe.rs index 7e42dda2b..2afb38da2 100644 --- a/core/src/pipe.rs +++ b/core/src/pipe.rs @@ -16,98 +16,73 @@ use crate::{ prelude::*, render_helper::{RenderProxy, RenderTarget}, ticker::FrameMsg, - widget::{Render, RenderBuilder, Widget, WidgetId, WidgetTree}, + widget::{Render, RenderBuilder, Widget, WidgetId}, }; -/// A value that can be subscribed its continuous change from the observable -/// stream. -pub struct Pipe { - value: V, - observable: BoxOp<'static, (ModifyScope, V), Infallible>, -} +type ValueStream = BoxOp<'static, (ModifyScope, V), Infallible>; -macro_rules! new_frame_sampler { - ($ctx: ident) => { - $ctx - .window() - .frame_tick_stream() - .filter(|f| matches!(f, FrameMsg::NewFrame(_))) - }; -} +/// A trait for a value that can be subscribed its continuous modifies. +pub trait Pipe { + type Value; + /// Unzip the `Pipe` into its inner value and the changes stream of the + /// value. + fn unzip(self) -> (Self::Value, ValueStream); -impl Pipe { - #[inline] - pub fn new(init: V, observable: BoxOp<'static, (ModifyScope, V), Infallible>) -> Self { - Self { value: init, observable } - } + fn box_unzip(self: Box) -> (Self::Value, ValueStream); - /// map the inner observable stream to another observable that emit same type - /// value. - pub fn stream_map( - self, - f: impl FnOnce(BoxOp<'static, (ModifyScope, V), Infallible>) -> R, - ) -> Pipe + /// Maps an `Pipe` to `Pipe` by applying a function to the + /// continuous value + fn map(self, f: impl FnMut(Self::Value) -> R + 'static) -> MapPipe where - R: BoxIt>, + Self: Sized, { - let Self { value, observable } = self; - let observable = f(observable).box_it(); - Pipe { value, observable } + MapPipe { source: self, f: Box::new(f) } } +} - /// Creates a new `Pipe` which calls a closure on each element and - /// uses its return as the value. - pub fn map(self, mut f: F) -> Pipe +pub(crate) trait InnerPipe: Pipe { + /// unzip the pipe value stream with a tick sample, only for build widget use. + fn widget_unzip( + self, + tap_before_all: impl Fn(&Window) + 'static, + ctx: &BuildCtx, + ) -> (Self::Value, ValueStream); + + /// Chain more operations on the pipe value stream by applying the `f` on the + /// final value stream. This a lazy operation, it will not execute the `f` + /// until the pipe is be subscribed. + fn final_stream_chain( + self, + f: impl FnOnce(ValueStream) -> ValueStream + 'static, + ) -> FinalChain where - F: FnMut(V) -> R + 'static, - V: 'static, + Self: Sized, { - let Self { value, observable } = self; - Pipe { - value: f(value), - observable: observable.map(move |(scope, v)| (scope, f(v))).box_it(), - } + FinalChain { source: self, f: Box::new(f) } } - /// Unzip the `Pipe` into its inner value and the changes stream of the - /// value. - #[inline] - pub fn unzip(self) -> (V, BoxOp<'static, (ModifyScope, V), Infallible>) { - (self.value, self.observable) - } - - #[inline] - pub fn value(&self) -> &V { &self.value } - - #[inline] - pub fn value_mut(&mut self) -> &mut V { &mut self.value } - - fn build(self, ctx: &BuildCtx, build: impl Fn(V, &BuildCtx) -> Widget + 'static) -> Widget + fn build( + self, + ctx: &BuildCtx, + build: impl Fn(Self::Value, &BuildCtx) -> Widget + 'static, + ) -> Widget where - V: 'static, + Self: Sized, + Self::Value: 'static, { - let (w, modifies) = self.unzip(); + let id_share = Sc::new(Cell::new(ctx.tree.borrow().root())); + let id_share2 = id_share.clone(); + let (w, modifies) = self.widget_unzip( + move |wnd| wnd.mark_widgets_regenerating(id_share2.get(), None), + ctx, + ); let w = build(w, ctx); - let id_share = Sc::new(Cell::new(w.id())); + id_share.set(w.id()); let mut pipe_node = PipeNode::share_capture(w.id(), ctx); - - let id_share2 = id_share.clone(); let handle = ctx.handle(); - let wnd_id = ctx.window().id(); let u = modifies - // Collects all the subtrees need to be regenerated before executing the regeneration in the - // `subscribe` method. Because the `sampler` will delay the `subscribe` until a new frame - // start. - .filter(|(scope, _)| scope.contains(ModifyScope::FRAMEWORK)) - .tap(move |_| { - if let Some(wnd) = AppCtx::get_window(wnd_id) { - wnd.mark_widgets_regenerating(id_share2.get(), None) - } - }) - .sample(new_frame_sampler!(ctx)) - .box_it() .subscribe(move |(_, w)| { handle.with_ctx(|ctx| { let id = id_share.get(); @@ -134,167 +109,17 @@ impl Pipe { w.attach_anonymous_data(u, ctx) } -} - -crate::widget::multi_build_replace_impl! { - impl {#} for Pipe { - fn widget_build(self, ctx: &BuildCtx) -> Widget { - self.build(ctx, |w, ctx| w.widget_build(ctx)) - } - } -} - -impl WidgetBuilder for Pipe { - fn widget_build(self, ctx: &BuildCtx) -> Widget { self.build(ctx, |v, _| v) } -} - -macro_rules! pipe_option_to_widget { - ($name: ident, $ctx: ident) => { - $name - .map(|w| { - move |ctx: &BuildCtx| { - if let Some(w) = w { - w.widget_build(ctx) - } else { - Void.widget_build(ctx) - } - } - }) - .widget_build($ctx) - }; -} - -pub(crate) use pipe_option_to_widget; - -impl SingleParent for Pipe { - fn compose_child(self, child: Widget, ctx: &BuildCtx) -> Widget { - let (v, modifies) = self.unzip(); - let c = child.id(); - let p = v.compose_child(child, ctx); - let rg = half_to_close_interval(p.id()..c, &ctx.tree.borrow()); - update_pipe_parent(rg, modifies, ctx, |new_p, old_p, ctx| { - let child = old_p.single_child(&ctx.tree.borrow().arena).unwrap(); - let child = Widget::from_id(child, ctx); - new_p.compose_child(child, ctx).consume() - }); - p - } -} -impl MultiParent for Pipe { - fn compose_children(self, mut children: impl Iterator, ctx: &BuildCtx) -> Widget { - // if children is empty, we can let the pipe parent as the whole subtree. - let first_child = children.next(); - if let Some(first_child) = first_child { - let (v, modifies) = self.unzip(); - let p = v.compose_children(children, ctx); - let rg = half_to_close_interval(p.id()..first_child.id(), &ctx.tree.borrow()); - update_pipe_parent(rg, modifies, ctx, |new_p, old_p, ctx| { - let arena = &ctx.tree.borrow().arena; - let children = old_p.children(arena).map(|id| Widget::from_id(id, ctx)); - new_p.compose_children(children, ctx).consume() - }); - p - } else { - self.widget_build(ctx) - } - } -} - -impl SingleParent for Pipe> { - fn compose_child(self, child: Widget, ctx: &BuildCtx) -> Widget { - let handle = ctx.handle(); - self - .map(move |p| { - handle - .with_ctx(|ctx| { - if let Some(p) = p { - BoxedSingleChild::from_id(p.widget_build(ctx)) - } else { - BoxedSingleChild::new(Void, ctx) - } - }) - .expect("Context not available") - }) - .compose_child(child, ctx) - } -} - -fn half_to_close_interval(rg: Range, tree: &WidgetTree) -> Range { - rg.start..rg.end.parent(&tree.arena).unwrap() -} - -fn update_pipe_parent( - // The range of the pipe parent widget ids. - parent: Range, - // transplant the children of the old parent to the new widget. - modifies: BoxOp<'static, (ModifyScope, W), Infallible>, - ctx: &BuildCtx, - transplant: impl Fn(W, WidgetId, &BuildCtx) -> WidgetId + 'static, -) { - let mut pipe_node = PipeNode::share_capture(parent.start, ctx); - - let id_share = Sc::new(RefCell::new(parent.clone())); - let id_share2 = id_share.clone(); - let handle = ctx.handle(); - let wnd_id = ctx.window().id(); - let u = modifies - .filter(|(scope, _)| scope.contains(ModifyScope::FRAMEWORK)) - .tap(move |_| { - if let Some(wnd) = AppCtx::get_window(wnd_id) { - let rg = id_share2.borrow().clone(); - wnd.mark_widgets_regenerating(rg.start, Some(rg.end)) - } - }) - .sample(new_frame_sampler!(ctx)) - .box_it() - .subscribe(move |(_, w)| { - handle.with_ctx(|ctx| { - let rg = id_share.borrow().clone(); - - let wnd = ctx.window(); - // async clean the mark when all regenerating is done to avoid other pipe - // regenerate in the regenerating scope. - AppCtx::spawn_local(async move { wnd.remove_regenerating_mark(rg.start) }).unwrap(); - - if !ctx.window().is_in_another_regenerating(rg.start) { - let first_child = rg.end.first_child(&ctx.tree.borrow().arena).unwrap(); - let p = transplant(w, rg.end, ctx); - let new_rg = half_to_close_interval(p..first_child, &ctx.tree.borrow()); - pipe_node.transplant_to(rg.start, new_rg.start, ctx); - - update_key_status_single(rg.start, new_rg.start, ctx); - - ctx.insert_after(rg.start, new_rg.start); - ctx.dispose_subtree(rg.start); - new_rg - .end - .ancestors(&ctx.tree.borrow().arena) - .take_while(|w| w != &new_rg.start) - .for_each(|p| ctx.on_widget_mounted(p)); - - ctx.mark_dirty(new_rg.start); - *id_share.borrow_mut() = new_rg; - } - }); - }) - .unsubscribe_when_dropped(); - - parent - .start - .attach_anonymous_data(u, &mut ctx.tree.borrow_mut().arena); -} - -impl Pipe { - pub(crate) fn build_multi( + fn build_multi( self, vec: &mut Vec, - f: impl Fn(W::Item, &BuildCtx) -> Widget + 'static, + f: impl Fn(::Item, &BuildCtx) -> Widget + 'static, ctx: &BuildCtx, ) where - W: IntoIterator, + Self::Value: IntoIterator, + Self: Sized, { - let build_multi = move |widgets: W, ctx: &BuildCtx| { + let build_multi = move |widgets: Self::Value, ctx: &BuildCtx| { let mut widgets = widgets.into_iter().map(|w| f(w, ctx)).collect::>(); if widgets.is_empty() { widgets.push(Void.widget_build(ctx)); @@ -302,33 +127,26 @@ impl Pipe { widgets }; - let (m, modifies) = self.unzip(); + let ids_share = Sc::new(RefCell::new(vec![])); + let id_share2 = ids_share.clone(); + let (m, modifies) = self.widget_unzip( + move |wnd| { + for id in id_share2.borrow().iter() { + wnd.mark_widgets_regenerating(*id, None) + } + }, + ctx, + ); let widgets = build_multi(m, ctx); - let ids = widgets.iter().map(|w| w.id()).collect::>(); - let first = ids[0]; - let mut pipe_node = PipeNode::share_capture(first, ctx); - + let first = widgets[0].id(); + *ids_share.borrow_mut() = widgets.iter().map(|w| w.id()).collect(); vec.extend(widgets); - let ids_share = Sc::new(RefCell::new(ids)); - let id_share2 = ids_share.clone(); - let wnd_id = ctx.window().id(); + let mut pipe_node = PipeNode::share_capture(first, ctx); + let handle = ctx.handle(); let u = modifies - .filter(|(scope, _)| scope.contains(ModifyScope::FRAMEWORK)) - // Collects all the subtrees need to be regenerated before executing the regeneration in the - // `subscribe` method. Because the `sampler` will delay the `subscribe` until a new frame - // start. - .tap(move |_| { - if let Some(wnd) = AppCtx::get_window(wnd_id) { - for id in id_share2.borrow().iter() { - wnd.mark_widgets_regenerating(*id, None) - } - } - }) - .sample(new_frame_sampler!(ctx)) - .box_it() .subscribe(move |(_, m)| { handle.with_ctx(|ctx| { let mut old = ids_share.borrow_mut(); @@ -368,6 +186,363 @@ impl Pipe { first.attach_anonymous_data(u, &mut ctx.tree.borrow_mut().arena); } + + fn only_parent_build( + self, + ctx: &BuildCtx, + compose_child: impl FnOnce(Self::Value) -> (Widget, WidgetId), + transplant: impl Fn(Self::Value, WidgetId, &BuildCtx) -> WidgetId + 'static, + ) -> Widget + where + Self: Sized, + { + let root = ctx.tree.borrow().root(); + let id_share = Sc::new(RefCell::new(root..root)); + let id_share2 = id_share.clone(); + + let (v, modifies) = self.widget_unzip( + move |wnd| { + let rg = id_share2.borrow().clone(); + wnd.mark_widgets_regenerating(rg.start, Some(rg.end)) + }, + ctx, + ); + let (p, child) = compose_child(v); + let parent = half_to_close_interval(p.id()..child, ctx); + let mut pipe_node = PipeNode::share_capture(parent.start, ctx); + *id_share.borrow_mut() = parent; + + let handle = ctx.handle(); + + let u = modifies + .subscribe(move |(_, w)| { + handle.with_ctx(|ctx| { + let rg = id_share.borrow().clone(); + + let wnd = ctx.window(); + // async clean the mark when all regenerating is done to avoid other pipe + // regenerate in the regenerating scope. + AppCtx::spawn_local(async move { wnd.remove_regenerating_mark(rg.start) }).unwrap(); + + if !ctx.window().is_in_another_regenerating(rg.start) { + let first_child = rg.end.first_child(&ctx.tree.borrow().arena).unwrap(); + let p = transplant(w, rg.end, ctx); + let new_rg = half_to_close_interval(p..first_child, ctx); + pipe_node.transplant_to(rg.start, new_rg.start, ctx); + + update_key_status_single(rg.start, new_rg.start, ctx); + + ctx.insert_after(rg.start, new_rg.start); + ctx.dispose_subtree(rg.start); + new_rg + .end + .ancestors(&ctx.tree.borrow().arena) + .take_while(|w| w != &new_rg.start) + .for_each(|p| ctx.on_widget_mounted(p)); + + ctx.mark_dirty(new_rg.start); + *id_share.borrow_mut() = new_rg; + } + }); + }) + .unsubscribe_when_dropped(); + + p.id() + .attach_anonymous_data(u, &mut ctx.tree.borrow_mut().arena); + p + } +} + +pub struct MapPipe { + source: S, + f: Box V>, +} + +pub struct FinalChain> { + source: S, + f: Box) -> ValueStream>, +} + +impl MapPipe { + pub fn new(source: S, f: impl FnMut(S::Value) -> V + 'static) -> Self { + Self { source, f: Box::new(f) } + } +} + +pub struct ModifiesPipe(BoxOp<'static, ModifyScope, Infallible>); +impl ModifiesPipe { + #[inline] + pub fn new(modifies: BoxOp<'static, ModifyScope, Infallible>) -> Self { Self(modifies) } +} + +impl Pipe for ModifiesPipe { + type Value = ModifyScope; + + #[inline] + fn unzip(self) -> (Self::Value, ValueStream) { + ( + ModifyScope::empty(), + ObservableExt::map(self.0, |s| (s, s)).box_it(), + ) + } + + fn box_unzip(self: Box) -> (Self::Value, ValueStream) { (*self).unzip() } +} + +impl InnerPipe for ModifiesPipe { + fn widget_unzip( + self, + tap_before_all: impl Fn(&Window) + 'static, + ctx: &BuildCtx, + ) -> (Self::Value, ValueStream) { + let wnd_id = ctx.window().id(); + let stream = self + .0 + .filter(|s| s.contains(ModifyScope::FRAMEWORK)) + .tap(move |_| { + if let Some(wnd) = AppCtx::get_window(wnd_id) { + tap_before_all(&wnd) + } + }) + .sample( + ctx + .window() + .frame_tick_stream() + .filter(|f| matches!(f, FrameMsg::NewFrame(_))), + ) + .map(|s| (s, s)) + .box_it(); + (ModifyScope::empty(), stream) + } +} + +impl Pipe for MapPipe +where + S::Value: 'static, +{ + type Value = V; + + fn unzip(self) -> (Self::Value, ValueStream) { + let Self { source, mut f } = self; + let (v, stream) = source.unzip(); + (f(v), stream.map(move |(s, v)| (s, f(v))).box_it()) + } + + fn box_unzip(self: Box) -> (Self::Value, ValueStream) { (*self).unzip() } +} + +impl InnerPipe for MapPipe +where + S::Value: 'static, +{ + fn widget_unzip( + self, + tap_before_all: impl Fn(&Window) + 'static, + ctx: &BuildCtx, + ) -> (Self::Value, ValueStream) { + let Self { source, mut f } = self; + let (v, stream) = source.widget_unzip(tap_before_all, ctx); + (f(v), stream.map(move |(s, v)| (s, f(v))).box_it()) + } +} + +impl> Pipe for FinalChain { + type Value = V; + + fn unzip(self) -> (Self::Value, ValueStream) { + let Self { source, f } = self; + let (v, stream) = source.unzip(); + (v, f(stream)) + } + + fn box_unzip(self: Box) -> (Self::Value, ValueStream) { (*self).unzip() } +} + +impl> InnerPipe for FinalChain { + fn widget_unzip( + self, + tap_before_all: impl Fn(&Window) + 'static, + ctx: &BuildCtx, + ) -> (Self::Value, ValueStream) { + let Self { source, f } = self; + let (v, stream) = source.widget_unzip(tap_before_all, ctx); + (v, f(stream)) + } +} + +crate::widget::multi_build_replace_impl! { + impl {#} for MapPipe + where + S::Value: 'static, + { + fn widget_build(self, ctx: &BuildCtx) -> Widget { + self.build(ctx, |w, ctx| w.widget_build(ctx)) + } + } + + impl> {#} for FinalChain + where + S::Value: 'static, + { + fn widget_build(self, ctx: &BuildCtx) -> Widget { + self.build(ctx, |w, ctx| w.widget_build(ctx)) + } + } +} + +impl WidgetBuilder for MapPipe +where + S: InnerPipe, + S::Value: 'static, +{ + fn widget_build(self, ctx: &BuildCtx) -> Widget { self.build(ctx, |v, _| v) } +} + +impl> WidgetBuilder for FinalChain { + fn widget_build(self, ctx: &BuildCtx) -> Widget { self.build(ctx, |v, _| v) } +} + +macro_rules! pipe_option_to_widget { + ($name: ident, $ctx: ident) => { + $name + .map(|w| { + move |ctx: &BuildCtx| { + if let Some(w) = w { + w.widget_build(ctx) + } else { + Void.widget_build(ctx) + } + } + }) + .widget_build($ctx) + }; +} + +pub(crate) use pipe_option_to_widget; + +macro_rules! single_parent_impl { + () => { + fn compose_child(self, child: Widget, ctx: &BuildCtx) -> Widget { + self.only_parent_build( + ctx, + move |p| { + let c = child.id(); + let p = p.compose_child(child, ctx); + (p, c) + }, + |new_p, old_p, ctx| { + let child = old_p.single_child(&ctx.tree.borrow().arena).unwrap(); + let child = Widget::from_id(child, ctx); + new_p.compose_child(child, ctx).consume() + }, + ) + } + }; +} + +impl SingleParent for MapPipe +where + S: InnerPipe, + V: SingleParent + RenderBuilder + 'static, + S::Value: 'static, +{ + single_parent_impl!(); +} + +impl SingleParent for FinalChain +where + S: InnerPipe, + V: SingleParent + RenderBuilder + 'static, + S::Value: 'static, +{ + single_parent_impl!(); +} + +macro_rules! multi_parent_impl { + () => { + fn compose_children( + self, + mut children: impl Iterator, + ctx: &BuildCtx, + ) -> Widget { + // if children is empty, we can let the pipe parent as the whole subtree. + let first_child = children.next(); + if let Some(first_child) = first_child { + self.only_parent_build( + ctx, + move |p| { + let child = first_child.id(); + let p = p.compose_children(children.chain(std::iter::once(first_child)), ctx); + (p, child) + }, + move |new_p, old_p, ctx| { + let arena = &ctx.tree.borrow().arena; + let children = old_p.children(arena).map(|id| Widget::from_id(id, ctx)); + new_p.compose_children(children, ctx).consume() + }, + ) + } else { + self.widget_build(ctx) + } + } + }; +} + +impl MultiParent for MapPipe +where + S: InnerPipe, + V: MultiParent + RenderBuilder + 'static, + S::Value: 'static, +{ + multi_parent_impl!(); +} + +impl> MultiParent + for FinalChain +{ + multi_parent_impl!(); +} + +macro_rules! option_single_parent_impl { + () => { + fn compose_child(self, child: Widget, ctx: &BuildCtx) -> Widget { + let handle = ctx.handle(); + Pipe::map(self, move |p| { + handle + .with_ctx(|ctx| { + if let Some(p) = p { + BoxedSingleChild::from_id(p.widget_build(ctx)) + } else { + BoxedSingleChild::new(Void, ctx) + } + }) + .expect("Context not available") + }) + .compose_child(child, ctx) + } + }; +} + +impl SingleParent for MapPipe, S> +where + S: InnerPipe, + V: SingleParent + RenderBuilder + 'static, + S::Value: 'static, +{ + option_single_parent_impl!(); +} + +impl SingleParent for FinalChain, S> +where + S: InnerPipe>, + V: SingleParent + RenderBuilder + 'static, + S::Value: 'static, +{ + option_single_parent_impl!(); +} + +fn half_to_close_interval(rg: Range, ctx: &BuildCtx) -> Range { + rg.start..rg.end.parent(&ctx.tree.borrow().arena).unwrap() } fn update_children_key_status(old: WidgetId, new: WidgetId, ctx: &BuildCtx) { @@ -476,9 +651,10 @@ fn update_key_states( update_children_key_status(old, new, ctx) } -impl SingleChild for Pipe> {} -impl SingleChild for Pipe {} -impl MultiChild for Pipe {} +impl SingleChild for MapPipe {} +impl, V: MultiChild> MultiChild for FinalChain {} +impl SingleChild for MapPipe, S> {} +impl>, V: MultiChild> MultiChild for FinalChain, S> {} /// `PipeNode` just use to wrap a `Box`, and provide a choice to /// change the inner `Box` by `UnsafeCell` at a safe time. It's @@ -710,10 +886,12 @@ mod tests { wnd.draw_frame(); assert_eq!(*mnt_cnt2.read(), 2); - // trigger the parent update - *c_p_trigger.write() = true; - // then trigger the child update. - *c_c_trigger.write() = true; + { + // trigger the parent update + *c_p_trigger.write() = true; + // then trigger the child update. + *c_c_trigger.write() = true; + } wnd.draw_frame(); assert_eq!(*mnt_cnt2.read(), 4); diff --git a/core/src/widget.rs b/core/src/widget.rs index 91d46f51b..53cb6b8f1 100644 --- a/core/src/widget.rs +++ b/core/src/widget.rs @@ -370,11 +370,13 @@ macro_rules! _replace { macro_rules! multi_build_replace_impl { ($($rest:tt)*) => { - $crate::widget::_replace!(@replace($crate::widget::ComposeBuilder) $($rest)*); - $crate::widget::_replace!(@replace($crate::widget::RenderBuilder) $($rest)*); - $crate::widget::_replace!(@replace($crate::widget::ComposeChildBuilder) $($rest)*); - $crate::widget::_replace!(@replace($crate::widget::WidgetBuilder) $($rest)*); - } + $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 { @@ -385,9 +387,19 @@ macro_rules! multi_build_replace_impl_include_self { ({} $($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) { diff --git a/core/src/widget_children/child_convert.rs b/core/src/widget_children/child_convert.rs index 8b14986f9..fd7dd47a1 100644 --- a/core/src/widget_children/child_convert.rs +++ b/core/src/widget_children/child_convert.rs @@ -2,7 +2,7 @@ use super::Pair; use crate::{ builtin_widgets::{FatObj, Void}, context::BuildCtx, - prelude::Pipe, + pipe::{FinalChain, InnerPipe, MapPipe, Pipe}, state::{State, StateFrom}, widget::*, }; @@ -36,10 +36,23 @@ crate::widget::multi_build_replace_impl! { } crate::widget::multi_build_replace_impl_include_self! { - impl FromAnother>, &dyn {#}> for Widget { - fn from_another(value: Pipe>, ctx: &BuildCtx) -> Self { + impl FromAnother, S>, &dyn {#}> for Widget + where + S::Value: 'static, + { + #[inline] + fn from_another(value: MapPipe, S>, ctx: &BuildCtx) -> Self { crate::pipe::pipe_option_to_widget!(value, ctx) - } + } + } + + impl>> + FromAnother, S>, &dyn {#}> for Widget + { + #[inline] + fn from_another(value: FinalChain, S>, ctx: &BuildCtx) -> Self { + crate::pipe::pipe_option_to_widget!(value, ctx) + } } } diff --git a/core/src/widget_children/multi_child_impl.rs b/core/src/widget_children/multi_child_impl.rs index e24182f50..f72cd8a85 100644 --- a/core/src/widget_children/multi_child_impl.rs +++ b/core/src/widget_children/multi_child_impl.rs @@ -1,5 +1,8 @@ use super::*; -use crate::widget::WidgetBuilder; +use crate::{ + pipe::{FinalChain, InnerPipe, MapPipe}, + widget::WidgetBuilder, +}; /// Trait specify what child a multi child widget can have, and the target type /// after widget compose its child. @@ -33,11 +36,26 @@ crate::widget::multi_build_replace_impl_include_self! { vec.extend(self.into_iter().map(|w| w.widget_build(ctx))) } } +} + +crate::widget::multi_build_replace_impl_include_self! { + impl FillVec<&dyn {#}> for MapPipe + where + S: InnerPipe, + S::Value: 'static, + V: IntoIterator + 'static, + V::Item: {#}, + { + fn fill_vec(self, vec: &mut Vec, ctx: &BuildCtx) { + self.build_multi(vec, |v, ctx| v.widget_build(ctx), ctx); + } + } - impl FillVec<&dyn {#}> for Pipe + impl FillVec<&dyn {#}> for FinalChain where - D: IntoIterator + 'static, - D::Item: {#}, + S: InnerPipe, + V: IntoIterator + 'static, + V::Item: {#}, { fn fill_vec(self, vec: &mut Vec, ctx: &BuildCtx) { self.build_multi(vec, |v, ctx| v.widget_build(ctx), ctx); diff --git a/core/src/widget_children/single_child_impl.rs b/core/src/widget_children/single_child_impl.rs index 8b5c33c4f..d2df08780 100644 --- a/core/src/widget_children/single_child_impl.rs +++ b/core/src/widget_children/single_child_impl.rs @@ -1,3 +1,5 @@ +use crate::pipe::{InnerPipe, MapPipe}; + use super::*; /// Trait specify what child a widget can have, and the target type is the @@ -31,15 +33,19 @@ crate::widget::multi_build_replace_impl_include_self! { } } } +} - impl SingleWithChild>, dyn {#}> for P +crate::widget::multi_build_replace_impl_include_self! { + impl SingleWithChild, S>, dyn {#}> for P where P: SingleParent + {#}, - C: {#}, + S: InnerPipe, + V: {#} + 'static, + S::Value: 'static, { type Target = Widget; - fn with_child(self, child: Pipe>, ctx: &BuildCtx) -> Self::Target { + fn with_child(self, child: MapPipe, S>, ctx: &BuildCtx) -> Self::Target { let child = crate::pipe::pipe_option_to_widget!(child, ctx); self.with_child(child, ctx) } diff --git a/macros/src/pipe_macro.rs b/macros/src/pipe_macro.rs index ebc59462b..18cb41193 100644 --- a/macros/src/pipe_macro.rs +++ b/macros/src/pipe_macro.rs @@ -47,28 +47,23 @@ impl ToTokens for PipeMacro { if refs.used_ctx() { quote! {{ #refs - let upstream = #upstream; let _ctx_handle_ಠ_ಠ = ctx!().handle(); - let mut expr_value = move |ctx!(): &BuildCtx<'_>| { #(#expr)* }; - Pipe::new( - expr_value(ctx!()), - upstream - .filter_map(move |scope| _ctx_handle_ಠ_ಠ - .with_ctx(&mut expr_value) - .map(|v| (scope, v)) - ) - .box_it() + MapPipe::new( + ModifiesPipe::new(#upstream.box_it()), + move |_: ModifyScope| { + _ctx_handle_ಠ_ಠ + .with_ctx(|ctx!(): &BuildCtx<'_>| { #(#expr)* }) + .expect("ctx is not available") + } ) }} .to_tokens(tokens) } else { quote! {{ #refs - let upstream = #upstream; - let mut expr_value = move || { #(#expr)* }; - Pipe::new( - expr_value(), - upstream.map(move |scope| (scope, expr_value())).box_it() + MapPipe::new( + ModifiesPipe::new(#upstream.box_it()), + move |_: ModifyScope| { #(#expr)* }, ) }} .to_tokens(tokens) diff --git a/widgets/src/layout/expanded.rs b/widgets/src/layout/expanded.rs index e49fd047e..b6692cd58 100644 --- a/widgets/src/layout/expanded.rs +++ b/widgets/src/layout/expanded.rs @@ -21,8 +21,9 @@ impl ComposeChild for Expanded { min: Size::new(0., 0.), max: Size::new(f32::INFINITY, f32::INFINITY) }, - @{ child.attach_state_data(this, ctx!()) } + @{ child } } + .attach_state_data(this, ctx!()) } } }