From 194920e64c0ec6f83faa1f20408a787cd5506329 Mon Sep 17 00:00:00 2001 From: Adoo Date: Mon, 15 Jul 2024 15:50:22 +0800 Subject: [PATCH] =?UTF-8?q?refactor(core):=20=F0=9F=92=A1=20lazy=20build?= =?UTF-8?q?=20widget?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGELOG.md | 21 +- README.md | 8 +- core/src/animation/stagger.rs | 3 +- core/src/builtin_widgets.rs | 104 +-- core/src/builtin_widgets/align.rs | 20 +- core/src/builtin_widgets/anchor.rs | 8 +- core/src/builtin_widgets/box_decoration.rs | 2 +- core/src/builtin_widgets/container.rs | 2 +- core/src/builtin_widgets/cursor.rs | 7 +- core/src/builtin_widgets/fitted_box.rs | 2 +- core/src/builtin_widgets/focus_node.rs | 13 +- core/src/builtin_widgets/focus_scope.rs | 72 +- core/src/builtin_widgets/global_anchor.rs | 11 +- core/src/builtin_widgets/has_focus.rs | 7 +- core/src/builtin_widgets/keep_alive.rs | 34 +- core/src/builtin_widgets/key.rs | 15 +- core/src/builtin_widgets/layout_box.rs | 9 +- core/src/builtin_widgets/margin.rs | 2 +- core/src/builtin_widgets/mix_builtin.rs | 63 +- core/src/builtin_widgets/mouse_hover.rs | 7 +- core/src/builtin_widgets/padding.rs | 2 +- core/src/builtin_widgets/pointer_pressed.rs | 9 +- core/src/builtin_widgets/scrollable.rs | 9 +- core/src/builtin_widgets/theme.rs | 22 +- .../theme/compose_decorators.rs | 15 +- core/src/builtin_widgets/theme/icon_theme.rs | 7 +- core/src/builtin_widgets/transform_widget.rs | 2 +- core/src/builtin_widgets/unconstrained_box.rs | 2 +- core/src/builtin_widgets/visibility.rs | 7 +- core/src/context/app_ctx.rs | 17 +- core/src/context/build_ctx.rs | 56 +- core/src/context/layout_ctx.rs | 2 +- core/src/context/widget_ctx.rs | 50 +- core/src/data_widget.rs | 36 +- core/src/declare.rs | 4 +- core/src/events/dispatcher.rs | 55 +- core/src/events/focus_mgr.rs | 157 ++-- core/src/events/keyboard.rs | 3 +- core/src/overlay.rs | 47 +- core/src/pipe.rs | 753 +++++++++--------- core/src/state.rs | 66 +- core/src/state/map_state.rs | 23 +- core/src/state/prior_op.rs | 254 +++--- core/src/state/splitted_state.rs | 65 +- core/src/state/stateful.rs | 114 ++- core/src/test_helper.rs | 19 +- core/src/widget.rs | 160 ++-- core/src/widget_children.rs | 110 ++- core/src/widget_children/child_convert.rs | 55 +- .../src/widget_children/compose_child_impl.rs | 194 +++-- core/src/widget_children/multi_child_impl.rs | 108 ++- core/src/widget_children/single_child_impl.rs | 168 ++-- core/src/widget_tree.rs | 74 +- core/src/widget_tree/layout_info.rs | 29 +- core/src/widget_tree/widget_id.rs | 125 +-- core/src/window.rs | 52 +- dev-helper/src/widget_test.rs | 9 +- docs/en/get_started/quick_start.md | 16 +- docs/en/understanding_ribir/without_dsl.md | 8 +- docs/zh/get_started/quick_start.md | 16 +- docs/zh/understanding_ribir/without_dsl.md | 6 +- examples/counter/src/lib.rs | 3 +- examples/messages/src/messages.rs | 8 +- examples/storybook/src/storybook.rs | 17 +- examples/todos/src/ui.rs | 34 +- examples/wordle_game/src/ui.rs | 41 +- macros/src/child_template.rs | 25 +- macros/src/declare_obj.rs | 2 +- macros/src/fn_widget_macro.rs | 2 +- ribir/src/app.rs | 30 +- tests/benches/core_bench.rs | 21 +- tests/benches/example_bench.rs | 2 +- tests/benches/widgets_bench.rs | 4 +- tests/child_template_derive_test.rs | 18 +- tests/path_child_test.rs | 10 +- tests/rdl_macro_test.rs | 80 +- themes/material/src/lib.rs | 31 +- themes/material/src/ripple.rs | 7 +- themes/material/src/state_layer.rs | 10 +- widgets/src/avatar.rs | 9 +- widgets/src/buttons.rs | 5 +- widgets/src/buttons/button.rs | 7 +- widgets/src/buttons/fab_button.rs | 7 +- widgets/src/buttons/filled_button.rs | 7 +- widgets/src/buttons/outlined_button.rs | 7 +- widgets/src/checkbox.rs | 30 +- widgets/src/icon.rs | 15 +- widgets/src/input.rs | 28 +- widgets/src/input/caret.rs | 3 +- widgets/src/input/selected_text.rs | 3 +- widgets/src/input/text_selectable.rs | 16 +- widgets/src/layout/constrained_box.rs | 9 +- widgets/src/layout/expanded.rs | 20 +- widgets/src/layout/flex.rs | 53 +- widgets/src/layout/sized_box.rs | 13 +- widgets/src/layout/stack.rs | 3 +- widgets/src/link.rs | 7 +- widgets/src/lists.rs | 78 +- widgets/src/scrollbar.rs | 39 +- widgets/src/tabs.rs | 48 +- widgets/src/text.rs | 3 +- widgets/src/text_field.rs | 35 +- widgets/src/transform_box.rs | 3 +- 103 files changed, 2070 insertions(+), 2059 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3f286b475..cf98e7d8a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -27,28 +27,31 @@ Please only add new entries below the [Unreleased](#unreleased---releasedate) he ### Features -- **core**: Introduced `IntoWidget` and `IntoChild`. (@M-Adoo #pr) +- **core**: Introduced `IntoWidget` and `IntoChild`. (@M-Adoo #612) The `IntoWidget` trait allows for the conversion of any widget to the type `Widget`. - The `IntoChild` trait provides a way to convert a more general widget into a child of `ComposeChild`. + The `IntoChild` trait provides a way to convert a more general type into a child of `ComposeChild`. ### Fixed -**core**: The generation of a pipe widget from another pipe widget may potentially result in a crash. (#pr, @M-Adoo) +**core**: The generation of a pipe widget from another pipe widget may potentially result in a crash. (#612, @M-Adoo) ### Changed -- **core**: Simplify the implementation of parent composition with child widgets. (#pr, @M-Adoo) +- **core**: Lazy build the widget tree. (#612, @M-Adoo) +- **core**: Simplify the implementation of parent composition with child widgets. (#612, @M-Adoo) Merge `SingleWithChild`, `MultiWithChild`, and `ComposeWithChild` into a single trait called WithChild. ### Breaking -- Removed `ChildFrom` and `FromAnother` traits (#pr @M-Adoo) -- Removed `SingleParent` and `MultiParent` traits. (#pr @M-Adoo) -- Removed `PairChild` and `PairWithChild` traits. User can use a generic type instead. (#pr @M-Adoo) -- Allow only the child to be converted to a widget or a type that implements the Into trait. (#pr @M-Adoo) -- Removed the all builder traits such as WidgetBuilder and ComposeBuilder and so on. (#pr @M-Adoo) +- Removed `ChildFrom` and `FromAnother` traits (#612 @M-Adoo) +- Removed `SingleParent` and `MultiParent` traits. (#612 @M-Adoo) +- Removed `PairChild` and `PairWithChild` traits. User can use a generic type instead. (#612 @M-Adoo) +- Allow only the child to be converted to a widget or a type that implements the Into trait. (#612 @M-Adoo) +- Removed the all builder traits such as WidgetBuilder and ComposeBuilder and so on. (#612 @M-Adoo) +- All implicit child conversions have been removed, except for conversions to Widget. (#612 @M-Adoo) + ## [0.4.0-alpha.3] - 2024-06-26 diff --git a/README.md b/README.md index 9d217f07a..ecf6638e8 100644 --- a/README.md +++ b/README.md @@ -77,7 +77,7 @@ fn main() { let inc_btn = FilledButton::declarer() .on_tap(move |_| *c_cnt.write() += 1) .finish(ctx) - .with_child(Label::new("Inc"), ctx); + .with_child(Label::new("Inc")); let counter = H1::declarer() .text(pipe!($cnt.to_string())) @@ -85,9 +85,9 @@ fn main() { Row::declarer() .finish(ctx) - .with_child(inc_btn, ctx) - .with_child(counter, ctx) - .build(ctx) + .with_child(inc_btn) + .with_child(counter) + .into_widget() }; App::run(counter); diff --git a/core/src/animation/stagger.rs b/core/src/animation/stagger.rs index 9938bcfe6..66bed84f2 100644 --- a/core/src/animation/stagger.rs +++ b/core/src/animation/stagger.rs @@ -225,7 +225,7 @@ mod tests { use super::*; use crate::{reset_test_env, test_helper::*}; - fn stagger_run_and_stop() -> impl IntoWidgetStrict { + fn stagger_run_and_stop() -> Widget<'static> { fn_widget! { let stagger = Stagger::new(Duration::from_millis(100), transitions::EASE_IN.of(ctx!())); let mut mock_box = @MockBox { size: Size::new(100., 100.) }; @@ -253,6 +253,7 @@ mod tests { mock_box } + .into_widget() } widget_layout_test!(stagger_run_and_stop, width == 100., height == 100.,); diff --git a/core/src/builtin_widgets.rs b/core/src/builtin_widgets.rs index e4125f942..90fc22e13 100644 --- a/core/src/builtin_widgets.rs +++ b/core/src/builtin_widgets.rs @@ -94,7 +94,7 @@ pub struct LazyWidgetId(Sc>>); /// let w = multi.get_margin_widget().clone_writer(); /// multi /// .on_tap(move |_| w.write().margin = EdgeInsets::all(20.)) -/// .into_widget(ctx) +/// .into_widget() /// }; /// ``` pub struct FatObj { @@ -125,11 +125,34 @@ pub struct FatObj { } impl LazyWidgetId { - pub fn id(&self) -> Option { self.0.get() } + /// Creates a new `LazyWidgetId` associated with a widget. You can retrieve + /// the widget's ID after the build process using this `LazyWidgetId`. + pub fn new(widget: Widget) -> (Widget, Self) { + let lazy_id = Self(<_>::default()); + let w = lazy_id.clone().bind(widget); + (w, lazy_id) + } + + /// Bind a widget to the LazyWidgetId, and return a widget that will set the + /// id to the LazyWidgetId after build. + pub fn bind(self, widget: Widget) -> Widget { + let f = move |ctx: &BuildCtx| { + let id = widget.build(ctx); + assert!(self.id().is_none(), "The LazyWidgetID only allows binding to one widget."); + self.0.set(Some(id)); + id + }; - pub fn assert_id(&self) -> WidgetId { self.0.get().unwrap() } + InnerWidget::LazyBuild(Box::new(f)).into() + } - fn set(&self, wid: WidgetId) { self.0.set(Some(wid)); } + pub fn id(&self) -> Option { self.0.get() } + + pub fn assert_id(&self) -> WidgetId { + self.0.get().expect( + "The binding is not associated with a widget, or the bound widget has not been built yet.", + ) + } fn ref_count(&self) -> usize { self.0.ref_count() } } @@ -845,97 +868,88 @@ impl ObjDeclarer for FatObj { fn finish(self, _: &BuildCtx) -> Self::Target { self } } -impl IntoWidgetStrict for FatObj +impl<'w, T, const M: usize> IntoWidgetStrict<'w, M> for FatObj where - T: IntoWidget, + T: IntoWidget<'w, M>, { - #[inline] - fn into_widget_strict(self, ctx: &BuildCtx) -> Widget { - let mut host = self.host.into_widget(ctx); - self.host_id.set(host.id()); + fn into_widget_strict(self) -> Widget<'w> { self.map(|w| w.into_widget()).compose() } +} + +impl<'a> FatObj> { + fn compose(self) -> Widget<'a> { + let mut host = self.host; + host = self.host_id.clone().bind(host); if let Some(mix_builtin) = self.mix_builtin { - host = mix_builtin.with_child(host, ctx).into_widget(ctx) + host = mix_builtin.with_child(host).into_widget() } if let Some(request_focus) = self.request_focus { - host = request_focus - .with_child(host, ctx) - .into_widget(ctx); + host = request_focus.with_child(host).into_widget(); } if let Some(has_focus) = self.has_focus { - host = has_focus.with_child(host, ctx).into_widget(ctx); + host = has_focus.with_child(host).into_widget(); } if let Some(mouse_hover) = self.mouse_hover { - host = mouse_hover.with_child(host, ctx).into_widget(ctx); + host = mouse_hover.with_child(host).into_widget(); } if let Some(pointer_pressed) = self.pointer_pressed { - host = pointer_pressed - .with_child(host, ctx) - .into_widget(ctx); + host = pointer_pressed.with_child(host).into_widget(); } if let Some(fitted_box) = self.fitted_box { - host = fitted_box.with_child(host, ctx).into_widget(ctx); + host = fitted_box.with_child(host).into_widget(); } if let Some(box_decoration) = self.box_decoration { - host = box_decoration - .with_child(host, ctx) - .into_widget(ctx); + host = box_decoration.with_child(host).into_widget(); } if let Some(padding) = self.padding { - host = padding.with_child(host, ctx).into_widget(ctx); + host = padding.with_child(host).into_widget(); } if let Some(layout_box) = self.layout_box { - host = layout_box.with_child(host, ctx).into_widget(ctx); + host = layout_box.with_child(host).into_widget(); } if let Some(cursor) = self.cursor { - host = cursor.with_child(host, ctx).into_widget(ctx); + host = cursor.with_child(host).into_widget(); } if let Some(margin) = self.margin { - host = margin.with_child(host, ctx).into_widget(ctx); + host = margin.with_child(host).into_widget(); } if let Some(scrollable) = self.scrollable { - host = scrollable.with_child(host, ctx).into_widget(ctx); + host = scrollable.with_child(host).into_widget(); } if let Some(transform) = self.transform { - host = transform.with_child(host, ctx).into_widget(ctx); + host = transform.with_child(host).into_widget(); } if let Some(h_align) = self.h_align { - host = h_align.with_child(host, ctx).into_widget(ctx); + host = h_align.with_child(host).into_widget(); } if let Some(v_align) = self.v_align { - host = v_align.with_child(host, ctx).into_widget(ctx); + host = v_align.with_child(host).into_widget(); } if let Some(relative_anchor) = self.relative_anchor { - host = relative_anchor - .with_child(host, ctx) - .into_widget(ctx); + host = relative_anchor.with_child(host).into_widget(); } if let Some(global_anchor) = self.global_anchor { - host = global_anchor - .with_child(host, ctx) - .into_widget(ctx); + host = global_anchor.with_child(host).into_widget(); } if let Some(visibility) = self.visibility { - host = visibility.with_child(host, ctx).into_widget(ctx); + host = visibility.with_child(host).into_widget(); } if let Some(opacity) = self.opacity { - host = opacity.with_child(host, ctx).into_widget(ctx); + host = opacity.with_child(host).into_widget(); } if let Some(keep_alive) = self.keep_alive { - host = keep_alive.with_child(host, ctx).into_widget(ctx); + host = keep_alive.with_child(host).into_widget(); } if let Some(h) = self.keep_alive_unsubscribe_handle { - let arena = &mut ctx.tree.borrow_mut().arena; - host.id().attach_anonymous_data(h, arena); + host = host.attach_anonymous_data(h); } - self.id.set(host.id()); + let host = self.id.clone().bind(host); host } } impl FatObj<()> { #[inline] - #[track_caller] - pub fn with_child(self, child: C, _: &BuildCtx) -> FatObj { self.map(move |_| child) } + pub fn with_child(self, child: C) -> FatObj { self.map(move |_| child) } } impl std::ops::Deref for FatObj { diff --git a/core/src/builtin_widgets/align.rs b/core/src/builtin_widgets/align.rs index 386130704..b655ee0d8 100644 --- a/core/src/builtin_widgets/align.rs +++ b/core/src/builtin_widgets/align.rs @@ -168,7 +168,7 @@ mod tests { const CHILD_SIZE: Size = Size::new(10., 10.); const WND_SIZE: Size = Size::new(100., 100.); - fn h_align(h_align: HAlign) -> impl IntoWidgetStrict { + fn h_align(h_align: HAlign) -> impl IntoWidget<'static, FN> { fn_widget! { @HAlignWidget { h_align, @@ -176,7 +176,7 @@ mod tests { } } } - fn left_align() -> impl IntoWidgetStrict { h_align(HAlign::Left) } + fn left_align() -> impl IntoWidget<'static, FN> { h_align(HAlign::Left) } widget_layout_test!( left_align, wnd_size = WND_SIZE, @@ -184,7 +184,7 @@ mod tests { { path = [0, 0], size == CHILD_SIZE, } ); - fn h_center_align() -> impl IntoWidgetStrict { h_align(HAlign::Center) } + fn h_center_align() -> impl IntoWidget<'static, FN> { h_align(HAlign::Center) } widget_layout_test!( h_center_align, wnd_size = WND_SIZE, @@ -192,7 +192,7 @@ mod tests { { path = [0, 0], x == 45., size == CHILD_SIZE,} ); - fn right_align() -> impl IntoWidgetStrict { h_align(HAlign::Right) } + fn right_align() -> impl IntoWidget<'static, FN> { h_align(HAlign::Right) } widget_layout_test!( right_align, wnd_size = WND_SIZE, @@ -200,7 +200,7 @@ mod tests { { path = [0, 0], x == 90., size == CHILD_SIZE,} ); - fn h_stretch_algin() -> impl IntoWidgetStrict { h_align(HAlign::Stretch) } + fn h_stretch_algin() -> impl IntoWidget<'static, FN> { h_align(HAlign::Stretch) } widget_layout_test!( h_stretch_algin, wnd_size = WND_SIZE, @@ -208,7 +208,7 @@ mod tests { { path = [0, 0], x == 0., width == 100., height == 10.,} ); - fn v_align(v_align: VAlign) -> impl IntoWidgetStrict { + fn v_align(v_align: VAlign) -> impl IntoWidget<'static, FN> { fn_widget! { @VAlignWidget { v_align, @@ -217,7 +217,7 @@ mod tests { } } - fn top_align() -> impl IntoWidgetStrict { v_align(VAlign::Top) } + fn top_align() -> impl IntoWidget<'static, FN> { v_align(VAlign::Top) } widget_layout_test!( top_align, wnd_size = WND_SIZE, @@ -225,7 +225,7 @@ mod tests { { path = [0, 0], size == CHILD_SIZE,} ); - fn v_center_align() -> impl IntoWidgetStrict { v_align(VAlign::Center) } + fn v_center_align() -> impl IntoWidget<'static, FN> { v_align(VAlign::Center) } widget_layout_test!( v_center_align, wnd_size = WND_SIZE, @@ -233,7 +233,7 @@ mod tests { { path = [0, 0], y == 45., size == CHILD_SIZE,} ); - fn bottom_align() -> impl IntoWidgetStrict { v_align(VAlign::Bottom) } + fn bottom_align() -> impl IntoWidget<'static, FN> { v_align(VAlign::Bottom) } widget_layout_test!( bottom_align, wnd_size = WND_SIZE, @@ -241,7 +241,7 @@ mod tests { { path = [0, 0], y == 90., size == CHILD_SIZE,} ); - fn v_stretch_align() -> impl IntoWidgetStrict { v_align(VAlign::Stretch) } + fn v_stretch_align() -> impl IntoWidget<'static, FN> { v_align(VAlign::Stretch) } widget_layout_test!( v_stretch_align, wnd_size = WND_SIZE, diff --git a/core/src/builtin_widgets/anchor.rs b/core/src/builtin_widgets/anchor.rs index 1e0d1ba9b..4585259ff 100644 --- a/core/src/builtin_widgets/anchor.rs +++ b/core/src/builtin_widgets/anchor.rs @@ -205,7 +205,7 @@ mod test { const CHILD_SIZE: Size = Size::new(50., 50.); const WND_SIZE: Size = Size::new(100., 100.); - fn pixel_left_top() -> impl IntoWidgetStrict { + fn pixel_left_top() -> impl IntoWidget<'static, FN> { fn_widget! { @MockBox { size: CHILD_SIZE, @@ -220,7 +220,7 @@ mod test { { path = [0, 0], x == 1., } ); - fn pixel_left_bottom() -> impl IntoWidgetStrict { + fn pixel_left_bottom() -> impl IntoWidget<'static, FN> { fn_widget! { @MockBox { size: CHILD_SIZE, @@ -235,7 +235,7 @@ mod test { { path = [0, 0], x == 1., } ); - fn pixel_top_right() -> impl IntoWidgetStrict { + fn pixel_top_right() -> impl IntoWidget<'static, FN> { fn_widget! { @MockBox { size: CHILD_SIZE, @@ -250,7 +250,7 @@ mod test { { path = [0, 0], x == 49.,} ); - fn pixel_bottom_right() -> impl IntoWidgetStrict { + fn pixel_bottom_right() -> impl IntoWidget<'static, FN> { fn_widget! { @MockBox { size: CHILD_SIZE, diff --git a/core/src/builtin_widgets/box_decoration.rs b/core/src/builtin_widgets/box_decoration.rs index fa963edf7..be29ef10b 100644 --- a/core/src/builtin_widgets/box_decoration.rs +++ b/core/src/builtin_widgets/box_decoration.rs @@ -196,7 +196,7 @@ mod tests { } const SIZE: Size = Size::new(100., 100.); - fn with_border() -> impl IntoWidgetStrict { + fn with_border() -> impl IntoWidget<'static, FN> { fn_widget! { @MockBox { size: SIZE, diff --git a/core/src/builtin_widgets/container.rs b/core/src/builtin_widgets/container.rs index 16cae96aa..bb8d3a704 100644 --- a/core/src/builtin_widgets/container.rs +++ b/core/src/builtin_widgets/container.rs @@ -31,7 +31,7 @@ mod tests { use crate::test_helper::*; const SIZE: Size = Size::new(100., 100.); - fn smoke() -> impl IntoWidgetStrict { + fn smoke() -> impl IntoWidget<'static, FN> { fn_widget! { @Container { size: SIZE }} } widget_layout_test!(smoke, size == SIZE,); diff --git a/core/src/builtin_widgets/cursor.rs b/core/src/builtin_widgets/cursor.rs index ffa668d30..fab48115d 100644 --- a/core/src/builtin_widgets/cursor.rs +++ b/core/src/builtin_widgets/cursor.rs @@ -13,9 +13,9 @@ impl Declare for Cursor { fn declarer() -> Self::Builder { FatObj::new(()) } } -impl ComposeChild for Cursor { - type Child = Widget; - fn compose_child(this: impl StateWriter, child: Self::Child) -> impl IntoWidgetStrict { +impl<'c> ComposeChild<'c> for Cursor { + type Child = Widget<'c>; + fn compose_child(this: impl StateWriter, child: Self::Child) -> Widget<'c> { fn_widget! { let save_cursor: Stateful> = Stateful::new(None); @$child { @@ -40,6 +40,7 @@ impl ComposeChild for Cursor { }, } } + .into_widget() } } diff --git a/core/src/builtin_widgets/fitted_box.rs b/core/src/builtin_widgets/fitted_box.rs index 0430ea0f9..e855aedaa 100644 --- a/core/src/builtin_widgets/fitted_box.rs +++ b/core/src/builtin_widgets/fitted_box.rs @@ -189,7 +189,7 @@ mod tests { .test(); } - fn as_builtin_field() -> impl IntoWidgetStrict { + fn as_builtin_field() -> impl IntoWidget<'static, FN> { fn_widget! { @MockBox { size: Size::new(200., 200.), diff --git a/core/src/builtin_widgets/focus_node.rs b/core/src/builtin_widgets/focus_node.rs index dae91e759..f982cb254 100644 --- a/core/src/builtin_widgets/focus_node.rs +++ b/core/src/builtin_widgets/focus_node.rs @@ -11,9 +11,9 @@ impl Declare for RequestFocus { fn declarer() -> Self::Builder { FatObj::new(()) } } -impl ComposeChild for RequestFocus { - type Child = Widget; - fn compose_child(this: impl StateWriter, child: Self::Child) -> impl IntoWidgetStrict { +impl<'c> ComposeChild<'c> for RequestFocus { + type Child = Widget<'c>; + fn compose_child(this: impl StateWriter, child: Self::Child) -> Widget<'c> { fn_widget! { @$child { on_mounted: move |e| { @@ -21,9 +21,10 @@ impl ComposeChild for RequestFocus { $this.silent().handle = Some(handle); } } - .into_widget(ctx!()) - .try_unwrap_state_and_attach(this, ctx!()) + .into_widget() + .try_unwrap_state_and_attach(this) } + .into_widget() } } impl RequestFocus { @@ -67,7 +68,7 @@ mod tests { let wnd = TestWindow::new(widget); let tree = wnd.widget_tree.borrow(); let id = tree.content_root(); - let node = id.get(&tree.arena).unwrap(); + let node = id.get(&tree).unwrap(); let mut cnt = 0; node.query_all_iter::().for_each(|b| { if b.contain_flag(BuiltinFlags::Focus) { diff --git a/core/src/builtin_widgets/focus_scope.rs b/core/src/builtin_widgets/focus_scope.rs index 38a9c7339..7ca9d76b6 100644 --- a/core/src/builtin_widgets/focus_scope.rs +++ b/core/src/builtin_widgets/focus_scope.rs @@ -14,19 +14,18 @@ pub struct FocusScope { pub can_focus: bool, } -impl ComposeChild for FocusScope { - type Child = Widget; - fn compose_child( - this: impl StateWriter, child: Self::Child, - ) -> impl IntoWidgetStrict { +impl<'c> ComposeChild<'c> for FocusScope { + type Child = Widget<'c>; + fn compose_child(this: impl StateWriter, child: Self::Child) -> Widget<'c> { fn_widget! { @ $child { on_mounted: move |e| e.window().add_focus_node(e.id, false, FocusType::Scope), on_disposed: move|e| e.window().remove_focus_node(e.id, FocusType::Scope), } - .into_widget(ctx!()) - .try_unwrap_state_and_attach(this, ctx!()) + .into_widget() + .try_unwrap_state_and_attach(this) } + .into_widget() } } @@ -63,39 +62,38 @@ mod tests { let wnd = TestWindow::new(widget); let mut focus_mgr = wnd.focus_mgr.borrow_mut(); - let tree = wnd.widget_tree.borrow(); - let arena = &tree.arena; + let tree = &wnd.widget_tree.borrow(); - focus_mgr.refresh_focus(arena); + focus_mgr.refresh_focus(tree); - let id0 = tree.content_root().first_child(arena).unwrap(); - let scope = id0.next_sibling(arena).unwrap(); - let scope_id1 = scope.first_child(arena).unwrap(); - let scope_id2 = scope_id1.next_sibling(arena).unwrap(); - let scope_id3 = scope_id2.next_sibling(arena).unwrap(); - let id1 = scope.next_sibling(arena).unwrap(); + let id0 = tree.content_root().first_child(tree).unwrap(); + let scope = id0.next_sibling(tree).unwrap(); + let scope_id1 = scope.first_child(tree).unwrap(); + let scope_id2 = scope_id1.next_sibling(tree).unwrap(); + let scope_id3 = scope_id2.next_sibling(tree).unwrap(); + let id1 = scope.next_sibling(tree).unwrap(); { // next focus sequential - focus_mgr.focus_next_widget(arena); + focus_mgr.focus_next_widget(tree); assert_eq!(focus_mgr.focusing(), Some(id1)); - focus_mgr.focus_next_widget(arena); + focus_mgr.focus_next_widget(tree); assert_eq!(focus_mgr.focusing(), Some(scope_id1)); - focus_mgr.focus_next_widget(arena); + focus_mgr.focus_next_widget(tree); assert_eq!(focus_mgr.focusing(), Some(scope_id2)); - focus_mgr.focus_next_widget(arena); + focus_mgr.focus_next_widget(tree); assert_eq!(focus_mgr.focusing(), Some(scope_id3)); - focus_mgr.focus_next_widget(arena); + focus_mgr.focus_next_widget(tree); assert_eq!(focus_mgr.focusing(), Some(id0)); // previous focus sequential - focus_mgr.focus_prev_widget(arena); + focus_mgr.focus_prev_widget(tree); assert_eq!(focus_mgr.focusing(), Some(scope_id3)); - focus_mgr.focus_prev_widget(arena); + focus_mgr.focus_prev_widget(tree); assert_eq!(focus_mgr.focusing(), Some(scope_id2)); - focus_mgr.focus_prev_widget(arena); + focus_mgr.focus_prev_widget(tree); assert_eq!(focus_mgr.focusing(), Some(scope_id1)); - focus_mgr.focus_prev_widget(arena); + focus_mgr.focus_prev_widget(tree); assert_eq!(focus_mgr.focusing(), Some(id1)); } } @@ -124,30 +122,26 @@ mod tests { let wnd = TestWindow::new(widget); let mut focus_mgr = wnd.focus_mgr.borrow_mut(); - let widget_tree = wnd.widget_tree.borrow(); - focus_mgr.refresh_focus(&widget_tree.arena); + let tree = &wnd.widget_tree.borrow(); + focus_mgr.refresh_focus(tree); - let arena = &widget_tree.arena; - let id0 = widget_tree - .content_root() - .first_child(arena) - .unwrap(); - let scope = id0.next_sibling(arena).unwrap(); - let id1 = scope.next_sibling(arena).unwrap(); + let id0 = tree.content_root().first_child(tree).unwrap(); + let scope = id0.next_sibling(tree).unwrap(); + let id1 = scope.next_sibling(tree).unwrap(); { // next focus sequential - focus_mgr.focus_next_widget(arena); + focus_mgr.focus_next_widget(tree); assert_eq!(focus_mgr.focusing(), Some(id1)); - focus_mgr.focus_next_widget(arena); + focus_mgr.focus_next_widget(tree); assert_eq!(focus_mgr.focusing(), Some(scope)); - focus_mgr.focus_next_widget(arena); + focus_mgr.focus_next_widget(tree); assert_eq!(focus_mgr.focusing(), Some(id0)); // previous focus sequential - focus_mgr.focus_prev_widget(arena); + focus_mgr.focus_prev_widget(tree); assert_eq!(focus_mgr.focusing(), Some(scope)); - focus_mgr.focus_prev_widget(arena); + focus_mgr.focus_prev_widget(tree); assert_eq!(focus_mgr.focusing(), Some(id1)); } } diff --git a/core/src/builtin_widgets/global_anchor.rs b/core/src/builtin_widgets/global_anchor.rs index f8e6fcb3a..f2f35f766 100644 --- a/core/src/builtin_widgets/global_anchor.rs +++ b/core/src/builtin_widgets/global_anchor.rs @@ -13,11 +13,9 @@ impl Declare for GlobalAnchor { fn declarer() -> Self::Builder { FatObj::new(()) } } -impl ComposeChild for GlobalAnchor { - type Child = Widget; - fn compose_child( - this: impl StateWriter, child: Self::Child, - ) -> impl IntoWidgetStrict { +impl<'c> ComposeChild<'c> for GlobalAnchor { + type Child = Widget<'c>; + fn compose_child(this: impl StateWriter, child: Self::Child) -> Widget<'c> { fn_widget! { let wnd = ctx!().window(); let tick_of_layout_ready = wnd @@ -50,6 +48,7 @@ impl ComposeChild for GlobalAnchor { @ $child { on_disposed: move |_| { u.unsubscribe(); } } } + .into_widget() } } @@ -178,7 +177,7 @@ mod tests { use crate::test_helper::*; const WND_SIZE: Size = Size::new(100., 100.); - fn global_anchor() -> impl IntoWidgetStrict { + fn global_anchor() -> impl IntoWidget<'static, FN> { fn_widget! { let parent = @MockBox { anchor: Anchor::left_top(10., 10.), diff --git a/core/src/builtin_widgets/has_focus.rs b/core/src/builtin_widgets/has_focus.rs index f07f1601b..a973be626 100644 --- a/core/src/builtin_widgets/has_focus.rs +++ b/core/src/builtin_widgets/has_focus.rs @@ -14,14 +14,15 @@ impl Declare for HasFocus { fn declarer() -> Self::Builder { FatObj::new(()) } } -impl ComposeChild for HasFocus { - type Child = Widget; - fn compose_child(this: impl StateWriter, child: Self::Child) -> impl IntoWidgetStrict { +impl<'c> ComposeChild<'c> for HasFocus { + type Child = Widget<'c>; + fn compose_child(this: impl StateWriter, child: Self::Child) -> Widget<'c> { fn_widget! { @ $child { on_focus_in: move|_| $this.write().focused = true, on_focus_out: move |_| $this.write().focused = false, } } + .into_widget() } } diff --git a/core/src/builtin_widgets/keep_alive.rs b/core/src/builtin_widgets/keep_alive.rs index 255259f18..732d89730 100644 --- a/core/src/builtin_widgets/keep_alive.rs +++ b/core/src/builtin_widgets/keep_alive.rs @@ -23,22 +23,23 @@ impl Declare for KeepAlive { fn declarer() -> Self::Builder { FatObj::new(()) } } -impl ComposeChild for KeepAlive { - type Child = Widget; - #[inline] - fn compose_child( - this: impl StateWriter, child: Self::Child, - ) -> impl IntoWidgetStrict { - fn_widget! { +impl<'c> ComposeChild<'c> for KeepAlive { + type Child = Widget<'c>; + fn compose_child(this: impl StateWriter, child: Self::Child) -> Widget<'c> { + let f = move |ctx: &BuildCtx| { let modifies = this.raw_modifies(); - child.try_unwrap_state_and_attach(this, ctx!()).dirty_subscribe(modifies, ctx!()) - } + let child = child.try_unwrap_state_and_attach(this); + let c = child.build(ctx); + c.dirty_subscribe(modifies, ctx); + c + }; + + InnerWidget::LazyBuild(Box::new(f)).into() } } #[cfg(test)] mod tests { - use std::cell::Ref; use super::*; use crate::{reset_test_env, test_helper::*}; @@ -54,29 +55,24 @@ mod tests { let mut wnd = TestWindow::new(fn_widget! { pipe! { if *$remove_widget { - Void.into_widget(ctx!()) + Void.into_widget() } else { FatObj::new(Void) .keep_alive(pipe!(*$keep_alive)) - .into_widget(ctx!()) + .into_widget() } } }); - fn tree_arena(wnd: &TestWindow) -> Ref { - let tree = wnd.widget_tree.borrow(); - Ref::map(tree, |t| &t.arena) - } - let root = wnd.widget_tree.borrow().content_root(); wnd.draw_frame(); *c_remove_widget.write() = true; wnd.draw_frame(); - assert!(!root.is_dropped(&tree_arena(&wnd))); + assert!(!root.is_dropped(&wnd.widget_tree.borrow())); *c_keep_alive.write() = false; wnd.draw_frame(); - assert!(root.is_dropped(&tree_arena(&wnd))); + assert!(root.is_dropped(&wnd.widget_tree.borrow())); } } diff --git a/core/src/builtin_widgets/key.rs b/core/src/builtin_widgets/key.rs index db2ffa8cc..f8c4bc63d 100644 --- a/core/src/builtin_widgets/key.rs +++ b/core/src/builtin_widgets/key.rs @@ -121,16 +121,11 @@ where fn as_any(&self) -> &dyn Any { self } } -impl ComposeChild for KeyWidget { - type Child = Widget; - #[inline] - fn compose_child( - this: impl StateWriter, child: Self::Child, - ) -> impl IntoWidgetStrict { - fn_widget! { - let data: Box = Box::new(this); - child.attach_data(Queryable(data), ctx!()).into_widget(ctx!()) - } +impl<'c, V: 'static + Default + Clone + PartialEq> ComposeChild<'c> for KeyWidget { + type Child = Widget<'c>; + fn compose_child(this: impl StateWriter, child: Self::Child) -> Widget<'c> { + let data: Box = Box::new(this); + child.attach_data(Queryable(data)).into_widget() } } diff --git a/core/src/builtin_widgets/layout_box.rs b/core/src/builtin_widgets/layout_box.rs index d0dd1a2a9..d04627486 100644 --- a/core/src/builtin_widgets/layout_box.rs +++ b/core/src/builtin_widgets/layout_box.rs @@ -13,9 +13,9 @@ impl Declare for LayoutBox { fn declarer() -> Self::Builder { FatObj::new(()) } } -impl ComposeChild for LayoutBox { - type Child = Widget; - fn compose_child(this: impl StateWriter, child: Self::Child) -> impl IntoWidgetStrict { +impl<'c> ComposeChild<'c> for LayoutBox { + type Child = Widget<'c>; + fn compose_child(this: impl StateWriter, child: Self::Child) -> Widget<'c> { fn_widget! { @ $child { on_performed_layout: move |e| { @@ -26,6 +26,7 @@ impl ComposeChild for LayoutBox { } } } + .into_widget() } } @@ -66,7 +67,7 @@ mod tests { use super::*; use crate::test_helper::*; - fn smoke() -> impl IntoWidgetStrict { + fn smoke() -> impl IntoWidget<'static, FN> { fn_widget! { let mut first_box = @MockBox { size: Size::new(100., 200.) }; let second_box = @MockBox { size: pipe!($first_box.layout_size()) }; diff --git a/core/src/builtin_widgets/margin.rs b/core/src/builtin_widgets/margin.rs index b3d17e0ac..e09ec876d 100644 --- a/core/src/builtin_widgets/margin.rs +++ b/core/src/builtin_widgets/margin.rs @@ -114,7 +114,7 @@ mod tests { use super::*; use crate::test_helper::*; - fn smoke() -> impl IntoWidgetStrict { + fn smoke() -> impl IntoWidget<'static, FN> { fn_widget! { @MockBox { margin: EdgeInsets::symmetrical(1., 1.), diff --git a/core/src/builtin_widgets/mix_builtin.rs b/core/src/builtin_widgets/mix_builtin.rs index 1437be98e..ff5168ed9 100644 --- a/core/src/builtin_widgets/mix_builtin.rs +++ b/core/src/builtin_widgets/mix_builtin.rs @@ -346,42 +346,43 @@ fn life_fn_once_to_fn_mut( } } -impl ComposeChild for MixBuiltin { - type Child = Widget; - #[inline] - fn compose_child( - this: impl StateWriter, mut child: Self::Child, - ) -> impl IntoWidgetStrict { - move |ctx: &BuildCtx| match this.try_into_value() { - Ok(this) => { - let mut this = Some(this); - if let Some(m) = child - .id() - .assert_get(&ctx.tree.borrow().arena) - .query_ref::() - { - let this = unsafe { this.take().unwrap_unchecked() }; - if !m.contain_flag(BuiltinFlags::Focus) && this.contain_flag(BuiltinFlags::Focus) { - this.callbacks_for_focus_node(); +impl<'c> ComposeChild<'c> for MixBuiltin { + type Child = Widget<'c>; + fn compose_child(this: impl StateWriter, child: Self::Child) -> Widget<'c> { + let f = move |ctx: &BuildCtx| { + let child = child.build(ctx); + match this.try_into_value() { + Ok(this) => { + let mut this = Some(this); + if let Some(m) = child + .assert_get(&ctx.tree.borrow()) + .query_ref::() + { + let this = unsafe { this.take().unwrap_unchecked() }; + if !m.contain_flag(BuiltinFlags::Focus) && this.contain_flag(BuiltinFlags::Focus) { + this.callbacks_for_focus_node(); + } + m.merge(this); } - m.merge(this); - } - // We do not use an else branch here, due to the borrow conflict of the `ctx`. - if let Some(this) = this { - if this.contain_flag(BuiltinFlags::Focus) { - this.callbacks_for_focus_node(); + // We do not use an else branch here, due to the borrow conflict of the `ctx`. + if let Some(this) = this { + if this.contain_flag(BuiltinFlags::Focus) { + this.callbacks_for_focus_node(); + } + child.attach_data(Queryable(this), &mut ctx.tree.borrow_mut()); } - child = child.attach_data(Queryable(this), ctx); } - child - } - Err(this) => { - if this.read().contain_flag(BuiltinFlags::Focus) { - this.read().callbacks_for_focus_node(); + Err(this) => { + if this.read().contain_flag(BuiltinFlags::Focus) { + this.read().callbacks_for_focus_node(); + } + child.attach_data(this, &mut ctx.tree.borrow_mut()) } - child.attach_data(this, ctx) } - } + + child + }; + InnerWidget::LazyBuild(Box::new(f)).into() } } diff --git a/core/src/builtin_widgets/mouse_hover.rs b/core/src/builtin_widgets/mouse_hover.rs index 1433bef89..894df7390 100644 --- a/core/src/builtin_widgets/mouse_hover.rs +++ b/core/src/builtin_widgets/mouse_hover.rs @@ -15,14 +15,15 @@ impl Declare for MouseHover { fn declarer() -> Self::Builder { FatObj::new(()) } } -impl ComposeChild for MouseHover { - type Child = Widget; - fn compose_child(this: impl StateWriter, child: Self::Child) -> impl IntoWidgetStrict { +impl<'c> ComposeChild<'c> for MouseHover { + type Child = Widget<'c>; + fn compose_child(this: impl StateWriter, child: Self::Child) -> Widget<'c> { fn_widget! { @ $child { on_pointer_enter: move |_| $this.write().hover = true, on_pointer_leave: move |_| $this.write().hover = false, } } + .into_widget() } } diff --git a/core/src/builtin_widgets/padding.rs b/core/src/builtin_widgets/padding.rs index 6f6e4d2be..1040c72a6 100644 --- a/core/src/builtin_widgets/padding.rs +++ b/core/src/builtin_widgets/padding.rs @@ -69,7 +69,7 @@ mod tests { use super::*; use crate::test_helper::*; - fn smoke() -> impl IntoWidgetStrict { + fn smoke() -> impl IntoWidget<'static, FN> { fn_widget! { @MockMulti { padding: EdgeInsets::only_left(1.), diff --git a/core/src/builtin_widgets/pointer_pressed.rs b/core/src/builtin_widgets/pointer_pressed.rs index c208e6b86..9d8f73f96 100644 --- a/core/src/builtin_widgets/pointer_pressed.rs +++ b/core/src/builtin_widgets/pointer_pressed.rs @@ -19,16 +19,15 @@ impl PointerPressed { pub fn pointer_pressed(&self) -> bool { self.pointer_pressed } } -impl ComposeChild for PointerPressed { - type Child = Widget; - fn compose_child( - this: impl StateWriter, child: Self::Child, - ) -> impl IntoWidgetStrict { +impl<'c> ComposeChild<'c> for PointerPressed { + type Child = Widget<'c>; + fn compose_child(this: impl StateWriter, child: Self::Child) -> Widget<'c> { fn_widget! { @ $child { on_pointer_down: move|_| $this.write().pointer_pressed = true, on_pointer_up: move |_| $this.write().pointer_pressed = false, } } + .into_widget() } } diff --git a/core/src/builtin_widgets/scrollable.rs b/core/src/builtin_widgets/scrollable.rs index e527607b5..ed9cf9ce3 100644 --- a/core/src/builtin_widgets/scrollable.rs +++ b/core/src/builtin_widgets/scrollable.rs @@ -29,11 +29,9 @@ impl Declare for ScrollableWidget { fn declarer() -> Self::Builder { FatObj::new(()) } } -impl ComposeChild for ScrollableWidget { - type Child = Widget; - fn compose_child( - this: impl StateWriter, child: Self::Child, - ) -> impl IntoWidgetStrict { +impl<'c> ComposeChild<'c> for ScrollableWidget { + type Child = Widget<'c>; + fn compose_child(this: impl StateWriter, child: Self::Child) -> Widget<'c> { fn_widget! { let mut view = @UnconstrainedBox { dir: pipe!(match $this.get_scrollable() { @@ -62,6 +60,7 @@ impl ComposeChild for ScrollableWidget { } } } + .into_widget() } } diff --git a/core/src/builtin_widgets/theme.rs b/core/src/builtin_widgets/theme.rs index 18e91e23e..dc50d2ea8 100644 --- a/core/src/builtin_widgets/theme.rs +++ b/core/src/builtin_widgets/theme.rs @@ -71,18 +71,17 @@ pub struct ThemeWidget { pub theme: Sc, } -impl ComposeChild for ThemeWidget { +impl ComposeChild<'static> for ThemeWidget { type Child = GenWidget; - #[inline] fn compose_child( this: impl StateWriter, mut child: Self::Child, - ) -> impl IntoWidgetStrict { + ) -> Widget<'static> { use crate::prelude::*; - fn_widget! { + let f = move |ctx: &BuildCtx| { let theme = this.read().theme.clone(); AppCtx::load_font_from_theme(&theme); - let mut themes = ctx!().themes().clone(); + let mut themes = ctx.themes().clone(); themes.push(theme.clone()); // Keep the empty node, because the subtree may be hold its id. @@ -91,14 +90,17 @@ impl ComposeChild for ThemeWidget { // node, because the subtree may be hold its id. // // A `Void` is cheap for a theme. - let p = Void.into_widget(ctx!()).attach_data(Queryable(theme), ctx!()); + let p = Void.into_widget().build(ctx); + p.attach_data(Queryable(theme), &mut ctx.tree.borrow_mut()); + // shadow the context with the theme. - let ctx = BuildCtx::new_with_data(Some(p.id()), ctx!().tree, themes); - let child = child.gen_widget(&ctx); - ctx.append_child(p.id(), child); + let ctx = BuildCtx::new_with_data(Some(p), ctx.tree, themes); + let child = child.gen_widget(&ctx).build(&ctx); + p.append(child, &mut ctx.tree.borrow_mut()); p - } + }; + InnerWidget::LazyBuild(Box::new(f)).into() } } diff --git a/core/src/builtin_widgets/theme/compose_decorators.rs b/core/src/builtin_widgets/theme/compose_decorators.rs index 1e971cfa9..31669fa0e 100644 --- a/core/src/builtin_widgets/theme/compose_decorators.rs +++ b/core/src/builtin_widgets/theme/compose_decorators.rs @@ -2,7 +2,7 @@ use std::any::type_name; use crate::prelude::*; -type ComposeDecoratorFn = dyn Fn(Box, Widget, &BuildCtx) -> Widget; +type ComposeDecoratorFn = dyn for<'r> Fn(Box, Widget<'r>, &BuildCtx) -> Widget<'r>; /// Compose style is a compose child widget to decoration its child. #[derive(Default)] pub struct ComposeDecorators { @@ -14,13 +14,14 @@ pub struct ComposeDecorators { /// `Theme` by a function. The trait implementation only as a default logic if /// no overwrite function in `Theme`. pub trait ComposeDecorator: Sized { - fn compose_decorator(this: State, host: Widget) -> impl IntoWidgetStrict; + fn compose_decorator(this: State, host: Widget) -> Widget; } impl ComposeDecorators { #[inline] pub fn override_compose_decorator( - &mut self, compose_decorator: impl Fn(State, Widget, &BuildCtx) -> Widget + 'static, + &mut self, + compose_decorator: impl for<'r> Fn(State, Widget<'r>, &BuildCtx) -> Widget<'r> + 'static, ) { self.styles.insert( TypeId::of::(), @@ -51,20 +52,18 @@ mod tests { struct Size100Style; impl ComposeDecorator for Size100Style { - fn compose_decorator(_: State, host: Widget) -> impl IntoWidgetStrict { - fn_widget!(host) - } + fn compose_decorator(_: State, host: Widget) -> Widget { host } } theme .compose_decorators - .override_compose_decorator::(|_, host, ctx| { + .override_compose_decorator::(|_, host, _| { fn_widget! { @MockBox { size: Size::new(100., 100.), @ { host } } } - .into_widget(ctx) + .into_widget() }); let w = fn_widget! { diff --git a/core/src/builtin_widgets/theme/icon_theme.rs b/core/src/builtin_widgets/theme/icon_theme.rs index 6c97ca875..0ced6432d 100644 --- a/core/src/builtin_widgets/theme/icon_theme.rs +++ b/core/src/builtin_widgets/theme/icon_theme.rs @@ -58,8 +58,11 @@ pub const CUSTOM_ICON_START: NamedSvg = NamedSvg::new(65536); pub struct NamedSvg(pub usize); impl Compose for NamedSvg { - fn compose(this: impl StateWriter) -> impl IntoWidgetStrict { - fn_widget! { @ { pipe!($this.of_or_miss(ctx!())) }} + fn compose(this: impl StateWriter) -> Widget<'static> { + fn_widget! { + pipe!($this.of_or_miss(ctx!())) + } + .into_widget() } } diff --git a/core/src/builtin_widgets/transform_widget.rs b/core/src/builtin_widgets/transform_widget.rs index 5b3bd2545..92a4aa57f 100644 --- a/core/src/builtin_widgets/transform_widget.rs +++ b/core/src/builtin_widgets/transform_widget.rs @@ -45,7 +45,7 @@ mod tests { use super::*; use crate::test_helper::*; - fn smoke() -> impl IntoWidgetStrict { + fn smoke() -> impl IntoWidget<'static, FN> { fn_widget! { @TransformWidget { transform: Transform::new(2., 0., 0., 2., 0., 0.), diff --git a/core/src/builtin_widgets/unconstrained_box.rs b/core/src/builtin_widgets/unconstrained_box.rs index 7cdc56b25..fa8fd8ca3 100644 --- a/core/src/builtin_widgets/unconstrained_box.rs +++ b/core/src/builtin_widgets/unconstrained_box.rs @@ -78,7 +78,7 @@ mod tests { use super::*; use crate::test_helper::*; - fn smoke() -> impl IntoWidgetStrict { + fn smoke() -> impl IntoWidget<'static, FN> { let size = Size::new(200., 200.); fn_widget! { @MockMulti { diff --git a/core/src/builtin_widgets/visibility.rs b/core/src/builtin_widgets/visibility.rs index ee23fa130..ff24bfbc2 100644 --- a/core/src/builtin_widgets/visibility.rs +++ b/core/src/builtin_widgets/visibility.rs @@ -11,9 +11,9 @@ impl Declare for Visibility { fn declarer() -> Self::Builder { FatObj::new(()) } } -impl ComposeChild for Visibility { - type Child = Widget; - fn compose_child(this: impl StateWriter, child: Self::Child) -> impl IntoWidgetStrict { +impl<'c> ComposeChild<'c> for Visibility { + type Child = Widget<'c>; + fn compose_child(this: impl StateWriter, child: Self::Child) -> Widget<'c> { fn_widget! { @FocusScope { skip_descendants: pipe!(!$this.get_visible()), @@ -24,6 +24,7 @@ impl ComposeChild for Visibility { } } } + .into_widget() } } diff --git a/core/src/context/app_ctx.rs b/core/src/context/app_ctx.rs index bac99a01d..151f68170 100644 --- a/core/src/context/app_ctx.rs +++ b/core/src/context/app_ctx.rs @@ -1,5 +1,6 @@ use std::{ cell::RefCell, + convert::Infallible, rc::Rc, sync::{Mutex, MutexGuard, Once}, task::{Context, RawWaker, RawWakerVTable, Waker}, @@ -10,12 +11,12 @@ pub use futures::task::SpawnError; use futures::{executor::LocalPool, task::LocalSpawnExt, Future}; use pin_project_lite::pin_project; use ribir_text::{font_db::FontDB, shaper::TextShaper, TextReorder, TypographyStore}; -use rxrust::scheduler::NEW_TIMER_FN; +use rxrust::{scheduler::NEW_TIMER_FN, subject::Subject}; use crate::{ builtin_widgets::{FullTheme, InheritTheme, Theme}, clipboard::{Clipboard, MockClipboard}, - prelude::FuturesLocalScheduler, + prelude::{FuturesLocalScheduler, Instant}, timer::Timer, widget::IntoWidget, window::{ShellWindow, Window, WindowId}, @@ -45,6 +46,7 @@ pub struct AppCtx { runtime_waker: Box, scheduler: FuturesLocalScheduler, executor: RefCell, + frame_ticks: Subject<'static, Instant, Infallible>, #[cfg(feature = "tokio-async")] tokio_runtime: tokio::runtime::Runtime, @@ -67,8 +69,8 @@ impl AppCtx { #[track_caller] pub fn app_theme() -> &'static Theme { &Self::shared().app_theme } - pub fn new_window( - shell_wnd: Box, content: impl IntoWidget, + pub fn new_window<'w, const M: usize>( + shell_wnd: Box, content: impl IntoWidget<'w, M>, ) -> Rc { let wnd = Window::new(shell_wnd); let id = wnd.id(); @@ -137,6 +139,12 @@ impl AppCtx { #[track_caller] pub fn font_db() -> &'static Rc> { &Self::shared().font_db } + /// This function returns a stream of app ticks, where each frame of the app + /// will emit a tick notification. + pub fn frame_ticks() -> &'static Subject<'static, Instant, Infallible> { + &Self::shared().frame_ticks + } + /// Runs all tasks in the local(usually means on the main thread) pool and /// returns if no more progress can be made on any task. #[track_caller] @@ -274,6 +282,7 @@ impl AppCtx { scheduler, runtime_waker: Box::new(MockWaker), windows: RefCell::new(ahash::HashMap::default()), + frame_ticks: <_>::default(), #[cfg(feature = "tokio-async")] tokio_runtime: tokio::runtime::Builder::new_multi_thread() diff --git a/core/src/context/build_ctx.rs b/core/src/context/build_ctx.rs index ecab70e4c..eb89d93ee 100644 --- a/core/src/context/build_ctx.rs +++ b/core/src/context/build_ctx.rs @@ -1,16 +1,11 @@ use std::{ - cell::{OnceCell, Ref, RefCell}, + cell::{OnceCell, RefCell}, rc::Rc, }; use ribir_algo::Sc; -use widget_id::RenderQueryable; -use crate::{ - prelude::*, - widget::widget_id::new_node, - window::{DelayEvent, WindowId}, -}; +use crate::{prelude::*, window::WindowId}; /// A context provide during build the widget tree. pub struct BuildCtx<'a> { @@ -73,47 +68,6 @@ impl<'a> BuildCtx<'a> { f(AppCtx::app_theme()) } - /// Get the widget back of `id`, panic if not exist. - pub(crate) fn assert_get(&self, id: WidgetId) -> Ref { - Ref::map(self.tree.borrow(), |tree| id.assert_get(&tree.arena)) - } - - pub(crate) fn alloc_widget(&self, widget: Box) -> WidgetId { - new_node(&mut self.tree.borrow_mut().arena, widget) - } - - pub(crate) fn append_child(&self, parent: WidgetId, child: Widget) { - parent.append(child.consume(), &mut self.tree.borrow_mut().arena); - } - - /// Insert `next` after `prev` - pub(crate) fn insert_after(&self, prev: WidgetId, next: WidgetId) { - prev.insert_after(next, &mut self.tree.borrow_mut().arena); - } - - /// After insert new subtree to the widget tree, call this to watch the - /// subtree and fire mount events. - pub(crate) fn on_subtree_mounted(&self, id: WidgetId) { - id.descendants(&self.tree.borrow().arena) - .for_each(|w| self.on_widget_mounted(w)); - self.tree.borrow_mut().mark_dirty(id); - } - - /// After insert new widget to the widget tree, call this to watch the widget - /// and fire mount events. - pub(crate) fn on_widget_mounted(&self, id: WidgetId) { - self - .window() - .add_delay_event(DelayEvent::Mounted(id)); - } - - /// Dispose the whole subtree of `id`, include `id` itself. - pub(crate) fn dispose_subtree(&self, id: WidgetId) { - id.dispose_subtree(&mut self.tree.borrow_mut()); - } - - pub(crate) fn mark_dirty(&self, id: WidgetId) { self.tree.borrow_mut().mark_dirty(id); } - pub(crate) fn themes(&self) -> &Vec> { self.themes.get_or_init(|| { let mut themes = vec![]; @@ -121,9 +75,9 @@ impl<'a> BuildCtx<'a> { return themes; }; - let arena = &self.tree.borrow().arena; - p.ancestors(arena).any(|p| { - for t in p.assert_get(arena).query_all_iter::>() { + let tree = &self.tree.borrow(); + p.ancestors(tree).any(|p| { + for t in p.assert_get(tree).query_all_iter::>() { themes.push(t.clone()); if matches!(&**t, Theme::Full(_)) { break; diff --git a/core/src/context/layout_ctx.rs b/core/src/context/layout_ctx.rs index b64d3e3c6..9dd045b2a 100644 --- a/core/src/context/layout_ctx.rs +++ b/core/src/context/layout_ctx.rs @@ -79,7 +79,7 @@ impl<'a> LayoutCtx<'a> { /// information with same input. #[inline] pub fn force_child_relayout(&mut self, child: WidgetId) -> bool { - assert_eq!(child.parent(&self.tree.arena), Some(self.id)); + assert_eq!(child.parent(self.tree), Some(self.id)); self.tree.store.force_layout(child).is_some() } diff --git a/core/src/context/widget_ctx.rs b/core/src/context/widget_ctx.rs index afaad8547..57e64e1a4 100644 --- a/core/src/context/widget_ctx.rs +++ b/core/src/context/widget_ctx.rs @@ -82,22 +82,16 @@ pub(crate) trait WidgetCtxImpl { impl WidgetCtx for T { #[inline] - fn parent(&self) -> Option { self.with_tree(|tree| self.id().parent(&tree.arena)) } + fn parent(&self) -> Option { self.with_tree(|tree| self.id().parent(tree)) } #[inline] - fn widget_parent(&self, w: WidgetId) -> Option { - self.with_tree(|tree| w.parent(&tree.arena)) - } + fn widget_parent(&self, w: WidgetId) -> Option { self.with_tree(|tree| w.parent(tree)) } #[inline] - fn single_child(&self) -> Option { - self.with_tree(|tree| self.id().single_child(&tree.arena)) - } + fn single_child(&self) -> Option { self.with_tree(|tree| self.id().single_child(tree)) } #[inline] - fn first_child(&self) -> Option { - self.with_tree(|tree| self.id().first_child(&tree.arena)) - } + fn first_child(&self) -> Option { self.with_tree(|tree| self.id().first_child(tree)) } #[inline] fn box_rect(&self) -> Option { self.widget_box_rect(self.id()) } @@ -149,44 +143,28 @@ impl WidgetCtx for T { } fn map_to_global(&self, pos: Point) -> Point { - self.with_tree(|tree| { - tree - .store - .map_to_global(pos, self.id(), &tree.arena) - }) + self.with_tree(|tree| tree.store.map_to_global(pos, self.id(), tree)) } fn map_from_global(&self, pos: Point) -> Point { - self.with_tree(|tree| { - tree - .store - .map_from_global(pos, self.id(), &tree.arena) - }) + self.with_tree(|tree| tree.store.map_from_global(pos, self.id(), tree)) } fn map_to_parent(&self, pos: Point) -> Point { - self.with_tree(|tree| { - tree - .store - .map_to_parent(self.id(), pos, &tree.arena) - }) + self.with_tree(|tree| tree.store.map_to_parent(self.id(), pos, tree)) } fn map_from_parent(&self, pos: Point) -> Point { - self.with_tree(|tree| { - tree - .store - .map_from_parent(self.id(), pos, &tree.arena) - }) + self.with_tree(|tree| tree.store.map_from_parent(self.id(), pos, tree)) } fn map_to(&self, pos: Point, w: WidgetId) -> Point { let global = self.map_to_global(pos); - self.with_tree(|tree| tree.store.map_from_global(global, w, &tree.arena)) + self.with_tree(|tree| tree.store.map_from_global(global, w, tree)) } fn map_from(&self, pos: Point, w: WidgetId) -> Point { - let global = self.with_tree(|tree| tree.store.map_to_global(pos, w, &tree.arena)); + let global = self.with_tree(|tree| tree.store.map_to_global(pos, w, tree)); self.map_from_global(global) } @@ -199,7 +177,7 @@ impl WidgetCtx for T { &self, id: WidgetId, callback: impl FnOnce(&W) -> R, ) -> Option { self.with_tree(|tree| { - id.assert_get(&tree.arena) + id.assert_get(tree) .query_ref::() .map(|r| callback(&r)) }) @@ -259,7 +237,7 @@ mod tests { let tree = &wnd.widget_tree.borrow(); let root = tree.root(); let pos = Point::zero(); - let child = root.single_child(&tree.arena).unwrap(); + let child = root.single_child(tree).unwrap(); let w_ctx = TestCtx { id: child, wnd_id: wnd.id() }; assert_eq!(w_ctx.map_from(pos, child), pos); @@ -285,7 +263,7 @@ mod tests { wnd.draw_frame(); let root = wnd.widget_tree.borrow().root(); - let child = get_single_child_by_depth(root, &wnd.widget_tree.borrow().arena, 3); + let child = get_single_child_by_depth(root, &wnd.widget_tree.borrow(), 3); let w_ctx = TestCtx { id: root, wnd_id: wnd.id() }; let from_pos = Point::new(30., 30.); assert_eq!(w_ctx.map_from(from_pos, child), Point::new(45., 45.)); @@ -293,7 +271,7 @@ mod tests { assert_eq!(w_ctx.map_to(to_pos, child), Point::new(40., 40.)); } - fn get_single_child_by_depth(id: WidgetId, tree: &TreeArena, mut depth: u32) -> WidgetId { + fn get_single_child_by_depth(id: WidgetId, tree: &WidgetTree, mut depth: u32) -> WidgetId { let mut child = id; while depth > 0 { child = child.single_child(tree).unwrap(); diff --git a/core/src/data_widget.rs b/core/src/data_widget.rs index 6da88e370..ddc434e4c 100644 --- a/core/src/data_widget.rs +++ b/core/src/data_widget.rs @@ -33,33 +33,39 @@ impl AnonymousAttacher { } } -// fixme: These APIs should be private, use Provide instead. -impl Widget { +// fixme: These APIs should be removed, use Provide instead. +impl<'a> Widget<'a> { /// Attach data to a widget and user can query it. - pub fn attach_data(self, data: D, ctx: &BuildCtx) -> Widget { - let arena = &mut ctx.tree.borrow_mut().arena; - self.id().attach_data(data, arena); - - self + pub fn attach_data(self, data: D) -> Widget<'a> { + let f = move |ctx: &BuildCtx| { + let w = self.build(ctx); + w.attach_data(data, &mut ctx.tree.borrow_mut()); + w + }; + InnerWidget::LazyBuild(Box::new(f)).into() } /// Attach a state to a widget and try to unwrap it before attaching. /// /// User can query the state or its value type. pub fn try_unwrap_state_and_attach( - self, data: impl StateWriter, ctx: &BuildCtx, - ) -> Widget { + self, data: impl StateWriter + 'static, + ) -> Widget<'a> { match data.try_into_value() { - Ok(data) => self.attach_data(Queryable(data), ctx), - Err(data) => self.attach_data(data, ctx), + Ok(data) => self.attach_data(Queryable(data)), + Err(data) => self.attach_data(data), } } /// Attach anonymous data to a widget and user can't query it. - pub fn attach_anonymous_data(self, data: impl Any, ctx: &BuildCtx) -> Widget { - let arena = &mut ctx.tree.borrow_mut().arena; - self.id().attach_anonymous_data(data, arena); - self + pub fn attach_anonymous_data(self, data: impl Any) -> Widget<'a> { + let f = move |ctx: &BuildCtx| { + let w = self.build(ctx); + w.attach_anonymous_data(data, &mut ctx.tree.borrow_mut()); + w + }; + + InnerWidget::LazyBuild(Box::new(f)).into() } } diff --git a/core/src/declare.rs b/core/src/declare.rs index 3bc224258..98387004d 100644 --- a/core/src/declare.rs +++ b/core/src/declare.rs @@ -43,7 +43,7 @@ impl DeclareInit { match self { Self::Value(v) => (v, None), Self::Pipe(v) => { - let (v, pipe) = v.into_pipe().unzip(); + let (v, pipe) = v.into_pipe().unzip(ModifyScope::DATA, None); (v, Some(pipe)) } } @@ -67,7 +67,7 @@ impl> DeclareFrom for DeclareInit { impl DeclareFrom for DeclareInit where - P: Pipe + 'static, + P: Pipe, V: From + 'static, { #[inline] diff --git a/core/src/events/dispatcher.rs b/core/src/events/dispatcher.rs index 3a39a7026..6b2b861a9 100644 --- a/core/src/events/dispatcher.rs +++ b/core/src/events/dispatcher.rs @@ -123,7 +123,7 @@ impl Dispatcher { let tap_on = self .pointer_down_uid .take()? - .lowest_common_ancestor(hit, &tree.arena)?; + .lowest_common_ancestor(hit, tree)?; wnd.add_delay_event(DelayEvent::Tap(tap_on)); Some(()) }; @@ -158,22 +158,16 @@ impl Dispatcher { let tree = wnd.widget_tree.borrow(); let nearest_focus = self.pointer_down_uid.and_then(|wid| { - wid.ancestors(&tree.arena).find(|id| { - id.get(&tree.arena) + wid.ancestors(&tree).find(|id| { + id.get(&tree) .and_then(|w| w.query_ref::()) .map_or(false, |m| m.contain_flag(BuiltinFlags::Focus)) }) }); if let Some(focus_id) = nearest_focus { - let Window { focus_mgr, widget_tree, .. } = &*wnd; - focus_mgr - .borrow_mut() - .focus(focus_id, &widget_tree.borrow().arena); + wnd.focus_mgr.borrow_mut().focus(focus_id, &tree); } else { - let Window { focus_mgr, widget_tree, .. } = &*wnd; - focus_mgr - .borrow_mut() - .blur(&widget_tree.borrow().arena); + wnd.focus_mgr.borrow_mut().blur(&tree); } if let Some(hit) = hit { wnd.add_delay_event(DelayEvent::PointerDown(hit)); @@ -188,63 +182,62 @@ impl Dispatcher { let old = self .entered_widgets .iter() - .find(|wid| !(*wid).is_dropped(&tree.arena)) + .find(|wid| !(*wid).is_dropped(&tree)) .copied(); if let Some(old) = old { - let ancestor = new_hit.and_then(|w| w.lowest_common_ancestor(old, &tree.arena)); + let ancestor = new_hit.and_then(|w| w.lowest_common_ancestor(old, &tree)); wnd.add_delay_event(DelayEvent::PointerLeave { bottom: old, up: ancestor }); }; if let Some(new) = new_hit { - let ancestor = old.and_then(|o| o.lowest_common_ancestor(new, &tree.arena)); + let ancestor = old.and_then(|o| o.lowest_common_ancestor(new, &tree)); wnd.add_delay_event(DelayEvent::PointerEnter { bottom: new, up: ancestor }); } - self.entered_widgets = - new_hit.map_or(vec![], |wid| wid.ancestors(&tree.arena).collect::>()); + self.entered_widgets = new_hit.map_or(vec![], |wid| wid.ancestors(&tree).collect::>()); } fn hit_widget(&self) -> Option { let mut hit_target = None; let wnd = self.window(); - let tree = wnd.widget_tree.borrow(); - let arena = &tree.arena; + let tree = &wnd.widget_tree.borrow(); + let store = &tree.store; let mut w = Some(tree.root()); let mut pos = self.info.cursor_pos; while let Some(id) = w { - let r = id.assert_get(arena); + let r = id.assert_get(tree); let ctx = HitTestCtx { id, wnd_id: wnd.id() }; let HitTest { hit, can_hit_child } = r.hit_test(&ctx, pos); - pos = tree.store.map_from_parent(id, pos, arena); + pos = tree.store.map_from_parent(id, pos, tree); if hit { hit_target = w; } w = id - .last_child(&tree.arena) + .last_child(tree) .filter(|_| can_hit_child) .or_else(|| { if hit { return None; } - pos = store.map_to_parent(id, pos, arena); + pos = store.map_to_parent(id, pos, tree); let mut node = w; while let Some(p) = node { - node = p.previous_sibling(&tree.arena); + node = p.previous_sibling(tree); if node.is_some() { break; } else { - node = p.parent(&tree.arena); + node = p.parent(tree); if let Some(id) = node { - pos = store.map_to_parent(id, pos, arena); + pos = store.map_to_parent(id, pos, tree); if node == hit_target { node = None; } @@ -281,9 +274,9 @@ mod tests { btns: MouseButtons, } - fn record_pointer( - event_stack: Rc>>, widget: impl IntoWidgetStrict, - ) -> impl IntoWidgetStrict + IntoWidgetStrict { + fn record_pointer<'w>( + event_stack: Rc>>, widget: impl IntoWidgetStrict<'w, FN>, + ) -> impl IntoWidgetStrict<'w, FN> { let handler_ctor = move || { let stack = event_stack.clone(); @@ -421,7 +414,7 @@ mod tests { #[derive(Default)] struct EventRecord(Rc>>); impl Compose for EventRecord { - fn compose(this: impl StateWriter) -> impl IntoWidgetStrict { + fn compose(this: impl StateWriter) -> Widget<'static> { fn_widget! { @MockBox { size: INFINITY_SIZE, @@ -436,6 +429,7 @@ mod tests { } } } + .into_widget() } } @@ -461,7 +455,7 @@ mod tests { } impl Compose for EnterLeave { - fn compose(this: impl StateWriter) -> impl IntoWidgetStrict { + fn compose(this: impl StateWriter) -> Widget<'static> { fn_widget! { @MockBox { size: INFINITY_SIZE, @@ -475,6 +469,7 @@ mod tests { } } } + .into_widget() } } diff --git a/core/src/events/focus_mgr.rs b/core/src/events/focus_mgr.rs index aaebf1c71..e8fe9f1de 100644 --- a/core/src/events/focus_mgr.rs +++ b/core/src/events/focus_mgr.rs @@ -120,12 +120,12 @@ impl FocusManager { self.node_ids.insert(wid, node_id); let wnd = self.window(); - let arena = &wnd.widget_tree.borrow().arena; - let mut it = wid.ancestors(arena).skip(1); + let tree = &wnd.widget_tree.borrow(); + let mut it = wid.ancestors(tree).skip(1); let parent = it .find_map(|id| self.node_ids.get(&id)) .unwrap_or(&self.root); - self.insert_node(*parent, node_id, wid, arena); + self.insert_node(*parent, node_id, wid, tree); } if auto_focus && focus_type == FocusType::Node { @@ -150,7 +150,7 @@ impl FocusManager { } } - pub fn next_focus(&mut self, arena: &TreeArena) -> Option { + pub fn next_focus(&mut self, arena: &WidgetTree) -> Option { let request_focus = self.request_focusing.take(); let autos = self.frame_auto_focus.drain(..); let next_focus = request_focus @@ -333,16 +333,16 @@ impl FocusManager { fn ignore_scope_id(&self, wid: WidgetId) -> Option { let wnd = self.window(); - let arena = &wnd.widget_tree.borrow().arena; + let tree = &wnd.widget_tree.borrow(); let node_id = wid - .ancestors(arena) + .ancestors(tree) .find_map(|wid| self.node_ids.get(&wid).copied())?; self.scope_list(node_id).find(|id| { self .get(*id) .and_then(|n| n.wid) - .and_then(|wid| wid.get(arena)?.query_ref::()) + .and_then(|wid| wid.get(tree)?.query_ref::()) .map_or(false, |s| s.skip_descendants) }) } @@ -351,7 +351,7 @@ impl FocusManager { let wnd = self.window(); let tree = wnd.widget_tree.borrow(); scope_id - .and_then(|id| id.get(&tree.arena)) + .and_then(|id| id.get(&tree)) .and_then(|r| r.query_ref::().map(|s| s.clone())) .unwrap_or_default() } @@ -364,13 +364,13 @@ impl FocusManager { .and_then(|n| n.wid) .and_then(|wid| { let tree = wnd.widget_tree.borrow(); - let m = wid.get(&tree.arena)?.query_ref::()?; + let m = wid.get(&tree)?.query_ref::()?; Some(m.get_tab_index()) }) .unwrap_or_default() } - fn insert_node(&mut self, parent: NodeId, node_id: NodeId, wid: WidgetId, arena: &TreeArena) { + fn insert_node(&mut self, parent: NodeId, node_id: NodeId, wid: WidgetId, arena: &WidgetTree) { enum TreePosition { BeforeSibling, // the new node is the sibling before current node SubTree, // the new node is in the subtree of current node @@ -378,7 +378,7 @@ impl FocusManager { Skip, // the node is not in the parent's sub-tree } - fn locate_position(dst: &[WidgetId], base: &[WidgetId], arena: &TreeArena) -> TreePosition { + fn locate_position(dst: &[WidgetId], base: &[WidgetId], arena: &WidgetTree) -> TreePosition { assert!(dst.len() > 1); let cnt = dst .iter() @@ -408,7 +408,7 @@ impl FocusManager { } fn collect_sub_ancestors( - wid: WidgetId, pid: Option, arena: &TreeArena, + wid: WidgetId, pid: Option, arena: &WidgetTree, ) -> Vec { if wid.is_dropped(arena) { return vec![]; @@ -475,24 +475,24 @@ impl FocusManager { } impl FocusManager { - pub fn focus_next_widget(&mut self, arena: &TreeArena) { + pub fn focus_next_widget(&mut self, tree: &WidgetTree) { self.focus_move_circle(false); - self.refresh_focus(arena); + self.refresh_focus(tree); } - pub fn focus_prev_widget(&mut self, arena: &TreeArena) { + pub fn focus_prev_widget(&mut self, tree: &WidgetTree) { self.focus_move_circle(true); - self.refresh_focus(arena); + self.refresh_focus(tree); } - pub fn focus(&mut self, wid: WidgetId, arena: &TreeArena) { + pub fn focus(&mut self, wid: WidgetId, tree: &WidgetTree) { self.request_focus_to(Some(wid)); - self.refresh_focus(arena); + self.refresh_focus(tree); } - pub fn blur(&mut self, arena: &TreeArena) { + pub fn blur(&mut self, tree: &WidgetTree) { self.request_focus_to(None); - self.refresh_focus(arena); + self.refresh_focus(tree); } pub(crate) fn blur_on_dispose(&mut self) { self.change_focusing_to(None); } @@ -500,8 +500,8 @@ impl FocusManager { /// return the focusing widget. pub fn focusing(&self) -> Option { self.focusing } - pub fn refresh_focus(&mut self, arena: &TreeArena) { - let new_focus = self.next_focus(arena); + pub fn refresh_focus(&mut self, tree: &WidgetTree) { + let new_focus = self.next_focus(tree); if self.focus_widgets.first() != new_focus.as_ref() { self.change_focusing_to(new_focus); } @@ -529,22 +529,22 @@ impl FocusManager { let old = self .focus_widgets .iter() - .find(|wid| !(*wid).is_dropped(&tree.arena)) + .find(|wid| !(*wid).is_dropped(&tree)) .copied(); // bubble focus out if let Some(old) = old { - let ancestor = node.and_then(|w| w.lowest_common_ancestor(old, &tree.arena)); + let ancestor = node.and_then(|w| w.lowest_common_ancestor(old, &tree)); wnd.add_delay_event(DelayEvent::FocusOut { bottom: old, up: ancestor }); }; if let Some(new) = node { wnd.add_delay_event(DelayEvent::Focus(new)); - let ancestor = old.and_then(|o| o.lowest_common_ancestor(new, &tree.arena)); + let ancestor = old.and_then(|o| o.lowest_common_ancestor(new, &tree)); wnd.add_delay_event(DelayEvent::FocusIn { bottom: new, up: ancestor }); } - self.focus_widgets = node.map_or(vec![], |wid| wid.ancestors(&tree.arena).collect::>()); + self.focus_widgets = node.map_or(vec![], |wid| wid.ancestors(&tree).collect::>()); self.focusing = node; old } @@ -574,9 +574,9 @@ mod tests { let mut focus_mgr = wnd.focus_mgr.borrow_mut(); let tree = wnd.widget_tree.borrow(); - focus_mgr.refresh_focus(&tree.arena); + focus_mgr.refresh_focus(&tree); - let id = tree.content_root().first_child(&tree.arena); + let id = tree.content_root().first_child(&tree); assert!(id.is_some()); assert_eq!(focus_mgr.focusing(), id); } @@ -599,10 +599,10 @@ mod tests { let id = tree .content_root() - .first_child(&tree.arena) - .and_then(|p| p.next_sibling(&tree.arena)); + .first_child(&tree) + .and_then(|p| p.next_sibling(&tree)); assert!(id.is_some()); - focus_mgr.refresh_focus(&tree.arena); + focus_mgr.refresh_focus(&tree); assert_eq!(focus_mgr.focusing(), id); } @@ -624,50 +624,47 @@ mod tests { let wnd = TestWindow::new(widget); let mut focus_mgr = wnd.focus_mgr.borrow_mut(); - let widget_tree = wnd.widget_tree.borrow(); - let arena = &widget_tree.arena; - focus_mgr.refresh_focus(arena); + let tree = &wnd.widget_tree.borrow(); - let negative = widget_tree - .content_root() - .first_child(arena) - .unwrap(); - let id0 = negative.next_sibling(arena).unwrap(); - let id1 = id0.next_sibling(arena).unwrap(); - let id2 = id1.next_sibling(arena).unwrap(); - let id4 = id2.next_sibling(arena).unwrap(); - let id3 = id4.first_child(arena).unwrap(); - let id0_0 = id4.next_sibling(arena).unwrap(); + focus_mgr.refresh_focus(tree); + + let negative = tree.content_root().first_child(tree).unwrap(); + let id0 = negative.next_sibling(tree).unwrap(); + let id1 = id0.next_sibling(tree).unwrap(); + let id2 = id1.next_sibling(tree).unwrap(); + let id4 = id2.next_sibling(tree).unwrap(); + let id3 = id4.first_child(tree).unwrap(); + let id0_0 = id4.next_sibling(tree).unwrap(); { // next focus sequential - focus_mgr.focus_next_widget(arena); + focus_mgr.focus_next_widget(tree); assert_eq!(focus_mgr.focusing(), Some(id2)); - focus_mgr.focus_next_widget(arena); + focus_mgr.focus_next_widget(tree); assert_eq!(focus_mgr.focusing(), Some(id3)); - focus_mgr.focus_next_widget(arena); + focus_mgr.focus_next_widget(tree); assert_eq!(focus_mgr.focusing(), Some(id4)); - focus_mgr.focus_next_widget(arena); + focus_mgr.focus_next_widget(tree); assert_eq!(focus_mgr.focusing(), Some(id0)); - focus_mgr.focus_next_widget(arena); + focus_mgr.focus_next_widget(tree); assert_eq!(focus_mgr.focusing(), Some(id0_0)); - focus_mgr.focus_next_widget(arena); + focus_mgr.focus_next_widget(tree); assert_eq!(focus_mgr.focusing(), Some(id1)); // previous focus sequential - focus_mgr.focus_prev_widget(arena); + focus_mgr.focus_prev_widget(tree); assert_eq!(focus_mgr.focusing(), Some(id0_0)); - focus_mgr.focus_prev_widget(arena); + focus_mgr.focus_prev_widget(tree); assert_eq!(focus_mgr.focusing(), Some(id0)); - focus_mgr.focus_prev_widget(arena); + focus_mgr.focus_prev_widget(tree); assert_eq!(focus_mgr.focusing(), Some(id4)); - focus_mgr.focus_prev_widget(arena); + focus_mgr.focus_prev_widget(tree); assert_eq!(focus_mgr.focusing(), Some(id3)); - focus_mgr.focus_prev_widget(arena); + focus_mgr.focus_prev_widget(tree); assert_eq!(focus_mgr.focusing(), Some(id2)); - focus_mgr.focus_prev_widget(arena); + focus_mgr.focus_prev_widget(tree); assert_eq!(focus_mgr.focusing(), Some(id1)); } } @@ -682,7 +679,7 @@ mod tests { } impl Compose for EmbedFocus { - fn compose(this: impl StateWriter) -> impl IntoWidgetStrict { + fn compose(this: impl StateWriter) -> Widget<'static> { fn_widget! { @MockBox { size: INFINITY_SIZE, @@ -715,6 +712,7 @@ mod tests { } } } + .into_widget() } } @@ -723,17 +721,17 @@ mod tests { let mut wnd = TestWindow::new(fn_widget!(widget)); let tree = wnd.widget_tree.borrow(); let parent = tree.content_root(); - let child = parent.first_child(&tree.arena).unwrap(); + let child = parent.first_child(&tree).unwrap(); drop(tree); wnd .focus_mgr .borrow_mut() - .refresh_focus(&wnd.widget_tree.borrow().arena); + .refresh_focus(&wnd.widget_tree.borrow()); wnd .focus_mgr .borrow_mut() - .focus(child, &wnd.widget_tree.borrow().arena); + .focus(child, &wnd.widget_tree.borrow()); wnd.draw_frame(); assert_eq!(&*log.borrow(), &["focus child", "focusin child", "focusin parent"]); log.borrow_mut().clear(); @@ -741,7 +739,7 @@ mod tests { wnd .focus_mgr .borrow_mut() - .focus_next_widget(&wnd.widget_tree.borrow().arena); + .focus_next_widget(&wnd.widget_tree.borrow()); wnd.run_frame_tasks(); assert_eq!(&*log.borrow(), &["blur child", "focusout child", "focus parent",]); log.borrow_mut().clear(); @@ -749,7 +747,7 @@ mod tests { wnd .focus_mgr .borrow_mut() - .blur(&wnd.widget_tree.borrow().arena); + .blur(&wnd.widget_tree.borrow()); wnd.run_frame_tasks(); assert_eq!(&*log.borrow(), &["blur parent", "focusout parent",]); } @@ -814,14 +812,14 @@ mod tests { wnd.draw_frame(); let mut focus_mgr = wnd.focus_mgr.borrow_mut(); - let tree = wnd.widget_tree.borrow(); + let tree = &wnd.widget_tree.borrow(); - let first_box = tree.content_root().first_child(&tree.arena); - let focus_scope = first_box.unwrap().next_sibling(&tree.arena); + let first_box = tree.content_root().first_child(tree); + let focus_scope = first_box.unwrap().next_sibling(tree); focus_mgr.request_focus_to(focus_scope); - let inner_box = focus_scope.unwrap().first_child(&tree.arena); - focus_mgr.refresh_focus(&tree.arena); + let inner_box = focus_scope.unwrap().first_child(tree); + focus_mgr.refresh_focus(tree); assert_eq!(focus_mgr.focusing(), inner_box); } @@ -839,9 +837,9 @@ mod tests { auto_focus: true, on_chars: move |e| $input_writer.write().push_str(&e.chars), size: Size::new(10., 10.), - }.into_widget(ctx!()) + }.into_widget() } else { - Void.into_widget(ctx!()) + Void.into_widget() } }} } @@ -872,17 +870,18 @@ mod tests { let (active_idx, active_idx_writer) = split_value(0); let w = fn_widget! { @MockMulti{ - @ { (0..4).map(move |i| { - pipe! (*$active_idx).map(move |idx| { - @MockBox { - auto_focus: i == idx, - on_chars: move |e| if idx == 2 { $input_writer.write().push_str(&e.chars) }, - size: Size::new(10., 10.), - } - }.into_widget(ctx!())) - }).collect::>() - } + @ { + (0..4).map(move |i| { + pipe! (*$active_idx).map(move |idx| { + @MockBox { + auto_focus: i == idx, + on_chars: move |e| if idx == 2 { $input_writer.write().push_str(&e.chars) }, + size: Size::new(10., 10.), + } + }) + }).collect::>() } + } }; let mut wnd = TestWindow::new(w); wnd.draw_frame(); diff --git a/core/src/events/keyboard.rs b/core/src/events/keyboard.rs index 652d56dd3..bbb9f4374 100644 --- a/core/src/events/keyboard.rs +++ b/core/src/events/keyboard.rs @@ -56,7 +56,7 @@ mod tests { struct Keys(Rc>>); impl Compose for Keys { - fn compose(this: impl StateWriter) -> impl IntoWidgetStrict { + fn compose(this: impl StateWriter) -> Widget<'static> { fn_widget! { @MockBox { size: Size::zero(), @@ -80,6 +80,7 @@ mod tests { } } } + .into_widget() } } diff --git a/core/src/overlay.rs b/core/src/overlay.rs index c09308ca4..d4cb1fce7 100644 --- a/core/src/overlay.rs +++ b/core/src/overlay.rs @@ -33,8 +33,9 @@ impl OverlayCloseHandle { pub fn close(&self) { self.0.close() } } +type Builder = Box Widget<'static>>; struct OverlayData { - builder: Box Widget>, + builder: Builder, style: Option, state: OverlayState, } @@ -67,7 +68,7 @@ impl Overlay { /// ``` pub fn new(gen: impl Into) -> Self { let mut gen = gen.into(); - Self::new_with_handle(move |_, ctx| gen.gen_widget(ctx)) + Self::inner_new(Box::new(move |_, ctx| gen.gen_widget(ctx))) } /// Create overlay from a builder with a close_handle @@ -87,7 +88,7 @@ impl Overlay { /// on_tap: move |_| ctrl.close(), /// @{ Label::new("Click to close") } /// } - /// } + /// }.into_widget() /// } /// ); /// @FilledButton { @@ -99,13 +100,9 @@ impl Overlay { /// App::run(w).with_size(Size::new(200., 200.)); /// ``` pub fn new_with_handle( - mut builder: impl FnMut(OverlayCloseHandle, &BuildCtx) -> Widget + 'static, + mut builder: impl FnMut(OverlayCloseHandle) -> Widget<'static> + 'static, ) -> Self { - Self(Rc::new(RefCell::new(OverlayData { - builder: Box::new(move |ctrl, ctx| builder(ctrl, ctx)), - style: None, - state: OverlayState::default(), - }))) + Self::inner_new(Box::new(move |ctrl, _| builder(ctrl))) } /// Overlay will show with the given style, if the overlay have not been set @@ -164,8 +161,8 @@ impl Overlay { /// ``` pub fn show_map(&self, f: F, wnd: Rc) where - F: FnOnce(Widget, OverlayCloseHandle) -> O + 'static, - O: IntoWidget, + F: FnOnce(Widget<'static>, OverlayCloseHandle) -> O + 'static, + O: IntoWidget<'static, FN> + 'static, { if self.is_show() { return; @@ -179,7 +176,7 @@ impl Overlay { let style = inner.style.clone(); inner .state - .show(overlay.into_widget(&ctx), style, wnd); + .show(overlay.into_widget(), style, wnd); } /// Show the widget at the give position. @@ -203,6 +200,14 @@ impl Overlay { /// remove the showing overlay. pub fn close(&self) { self.0.borrow().state.close() } + + fn inner_new(builder: Builder) -> Self { + Self(Rc::new(RefCell::new(OverlayData { + builder, + style: None, + state: OverlayState::default(), + }))) + } } enum OverlayInnerState { @@ -231,7 +236,7 @@ impl OverlayState { fn is_show(&self) -> bool { !matches!(*self.0.borrow(), OverlayInnerState::Hided) } - fn show(&self, w: Widget, style: Option, wnd: Rc) { + fn show(&self, w: Widget<'static>, style: Option, wnd: Rc) { if self.is_show() { return; } @@ -245,17 +250,19 @@ impl OverlayState { }; let build_ctx = BuildCtx::new(None, &wnd.widget_tree); let style = style.unwrap_or_else(|| OverlayStyle::of(&build_ctx)); - let w = this.wrap_style(w, style).into_widget(&build_ctx); - let wid = w.id(); + let wid = this + .wrap_style(w, style) + .into_widget() + .build(&build_ctx); *this.0.borrow_mut() = OverlayInnerState::Showing(wid, wnd.clone()); - let root = wnd.widget_tree.borrow().root(); - build_ctx.append_child(root, w); - build_ctx.on_subtree_mounted(wid); - build_ctx.mark_dirty(wid); + let mut tree = wnd.widget_tree.borrow_mut(); + tree.root().append(wid, &mut tree); + wid.on_mounted_subtree(&tree); + tree.mark_dirty(wid); }); } - fn wrap_style(&self, w: Widget, style: OverlayStyle) -> impl IntoWidgetStrict { + fn wrap_style(&self, w: Widget<'static>, style: OverlayStyle) -> impl IntoWidget<'static, FN> { let this = self.clone(); fn_widget! { let OverlayStyle { close_policy, mask_brush } = style; diff --git a/core/src/pipe.rs b/core/src/pipe.rs index ed972117e..3691528c2 100644 --- a/core/src/pipe.rs +++ b/core/src/pipe.rs @@ -14,32 +14,14 @@ use crate::{ data_widget::Queryable, prelude::*, render_helper::{PureRender, RenderProxy}, - ticker::FrameMsg, + window::WindowId, }; pub type ValueStream = BoxOp<'static, (ModifyScope, V), Infallible>; /// A trait for a value that can be subscribed its continuous modifies. -pub trait Pipe { +pub trait Pipe: 'static { type Value; - /// Unzip the `Pipe` into its inner value and the changes stream of the - /// value. - fn unzip(self) -> (Self::Value, ValueStream); - - /// unzip the pipe value stream with a tick sample, and give a priority value - /// to the stream to determine the priority of the downstream to be notified. - /// This method only for build widget use. - fn tick_unzip( - self, prior_fn: impl FnMut() -> i64 + 'static, ctx: &BuildCtx, - ) -> (Self::Value, ValueStream) - where - Self: Sized; - - fn box_unzip(self: Box) -> (Self::Value, ValueStream); - - fn box_tick_unzip( - self: Box, prior_fn: Box i64>, ctx: &BuildCtx, - ) -> (Self::Value, ValueStream); /// Maps an `Pipe` to `Pipe` by applying a function to the /// continuous value @@ -58,8 +40,23 @@ pub trait Pipe { Self: Sized, F: FnOnce(ValueStream) -> ValueStream + 'static, { - FinalChain { source: self, f } + FinalChain { source: self, f, _marker: PhantomData } } + + /// Unzip the `Pipe` into its inner value and the stream of changes for that + /// value. + /// + /// - *scope*: specifies the scope of the modifications to be emitted by the + /// stream. + /// - *priority*: defines the priority of the emitted values if it is a + /// Some-value. + fn unzip( + self, scope: ModifyScope, priority: Option>, + ) -> (Self::Value, ValueStream); + + fn box_unzip( + self: Box, scope: ModifyScope, priority: Option>, + ) -> (Self::Value, ValueStream); } /// A trait object type for `Pipe`, help to store a concrete `Pipe` @@ -71,18 +68,16 @@ pub trait Pipe { /// Call `into_pipe` to convert it to a `Pipe` type. pub struct BoxPipe(Box>); -pub struct MapPipe V> { +pub struct MapPipe { source: S, f: F, + _marker: PhantomData, } -pub struct FinalChain -where - S: Pipe, - F: FnOnce(ValueStream) -> ValueStream, -{ +pub struct FinalChain { source: S, f: F, + _marker: PhantomData, } impl BoxPipe { @@ -96,201 +91,214 @@ impl BoxPipe { pub fn into_pipe(self) -> Box> { self.0 } } -pub(crate) trait InnerPipe: Pipe { - fn build_single(self, ctx: &BuildCtx) -> Widget +pub(crate) trait InnerPipe: Pipe + Sized { + fn build_single<'w, const M: usize>(self) -> Widget<'static> where - Self: Sized, - Self::Value: IntoWidget + 'static, + Self::Value: IntoWidget<'w, M>, { - let info = - Sc::new(Cell::new(SinglePipeInfo { gen_id: ctx.tree.borrow().root(), multi_pos: 0 })); - let info2 = info.clone(); - let handle = ctx.handle(); - let (w, modifies) = self.tick_unzip(move || pipe_priority_value(&info2, handle), ctx); - let w = w.into_widget(ctx); - info.set(SinglePipeInfo { gen_id: w.id(), multi_pos: 0 }); + let f = move |ctx: &BuildCtx| { + let info = + Sc::new(Cell::new(SinglePipeInfo { gen_id: ctx.tree.borrow().root(), multi_pos: 0 })); - let pipe_node = PipeNode::share_capture(w.id(), Box::new(info.clone()), ctx); - let c_pipe_node = pipe_node.clone(); + let priority = fn_priority(info.clone(), ctx.window().id()); + let (w, modifies) = self.unzip(ModifyScope::FRAMEWORK, Some(Box::new(priority))); + let w = w.into_widget().build(ctx); + info.set(SinglePipeInfo { gen_id: w, multi_pos: 0 }); - let u = modifies.subscribe(move |(_, w)| { - handle.with_ctx(|ctx| { - let id = info.host_id(); + let pipe_node = PipeNode::share_capture(w, Box::new(info.clone()), ctx); + let c_pipe_node = pipe_node.clone(); - let new_id = w.into_widget(ctx).consume(); + let handle = ctx.handle(); + let u = modifies.subscribe(move |(_, w)| { + handle.with_ctx(|ctx| { + let id = info.host_id(); - query_info_outside_until(id, &info, ctx, |info| info.single_replace(id, new_id)); - pipe_node.primary_transplant(id, new_id, ctx); + let new_id = w.into_widget().build(ctx); + let mut tree = ctx.tree.borrow_mut(); - update_key_status_single(id, new_id, ctx); + query_info_outside_until(id, &info, &tree, |info| info.single_replace(id, new_id)); - ctx.insert_after(id, new_id); - ctx.dispose_subtree(id); - ctx.on_subtree_mounted(new_id); + pipe_node.primary_transplant(id, new_id, &mut tree); - ctx.mark_dirty(new_id); + update_key_status_single(id, new_id, &tree); + id.insert_after(new_id, &mut tree); + id.dispose_subtree(&mut tree); + new_id.on_mounted_subtree(&tree); + + tree.mark_dirty(new_id); + }); }); - }); - c_pipe_node.own_subscription(u, ctx); - w + c_pipe_node.own_subscription(u, ctx); + w + }; + InnerWidget::LazyBuild(Box::new(f)).into() } - fn build_multi(self, ctx: &BuildCtx) -> SmallVec<[Widget; 1]> + fn build_multi<'w, const M: usize>(self) -> Vec> where Self::Value: IntoIterator, - ::Item: IntoWidget, - Self: Sized, + ::Item: IntoWidget<'w, M>, { - let collect_widgets = move |widgets: Self::Value, ctx: &BuildCtx| { - let mut widgets = widgets - .into_iter() - .map(|w| w.into_widget(ctx)) - .collect::>(); - if widgets.is_empty() { - widgets.push(Void.into_widget(ctx)); - } - widgets - }; - let info = Sc::new(RefCell::new(MultiPipeInfo { widgets: vec![], multi_pos: 0 })); - let info2 = info.clone(); - let handle = ctx.handle(); - let (m, modifies) = self.tick_unzip(move || pipe_priority_value(&info2, handle), ctx); - - let widgets = collect_widgets(m, ctx); - let pipe_node = PipeNode::share_capture(widgets[0].id(), Box::new(info.clone()), ctx); - let ids = widgets.iter().map(|w| w.id()).collect::>(); - - set_pos_of_multi(&ids, ctx); - - // We need to associate the parent information with the children pipe so that - // when the child pipe is regenerated, it can update the parent pipe information - // accordingly. - { - let arena = &mut ctx.tree.borrow_mut().arena; - for id in &ids[1..] { - if id.assert_get(arena).contain_type::() { - let info: Box = Box::new(info.clone()); - id.attach_data(Queryable(info), arena); - }; - } - } + let priority = MultiUpdatePriority::new(info.clone()); + let (m, modifies) = self.unzip(ModifyScope::FRAMEWORK, Some(Box::new(priority.clone()))); + let mut iter = m.into_iter(); - info.borrow_mut().widgets = ids; + let first = iter + .next() + .map_or_else(|| Void.into_widget(), IntoWidget::into_widget); - let c_pipe_node = pipe_node.clone(); - let u = modifies.subscribe(move |(_, m)| { - handle.with_ctx(|ctx| { - let old = info.borrow().widgets.clone(); + let info2 = info.clone(); + let first = move |ctx: &BuildCtx| { + let id = first.build(ctx); + info2.borrow_mut().widgets.push(id); + priority.set_wnd(ctx.window().id()); + let pipe_node = PipeNode::share_capture(id, Box::new(info2.clone()), ctx); + let c_pipe_node = pipe_node.clone(); + let handle = ctx.handle(); + let u = modifies.subscribe(move |(_, m)| { + handle.with_ctx(|ctx| { + let old = info2.borrow().widgets.clone(); + let mut new = vec![]; + for (idx, w) in m.into_iter().enumerate() { + let id = w.into_widget().build(ctx); + new.push(id); + set_pos_of_multi(id, idx, &ctx.tree.borrow()); + } + if new.is_empty() { + new.push(Void.into_widget().build(ctx)); + } - let new = collect_widgets(m, ctx) - .into_iter() - .map(Widget::consume) - .collect::>(); + let mut tree = ctx.tree.borrow_mut(); + query_info_outside_until(old[0], &info2, &tree, |info| info.multi_replace(&old, &new)); + pipe_node.primary_transplant(old[0], new[0], &mut tree); + + update_key_state_multi(old.iter().copied(), new.iter().copied(), &tree); + + new + .iter() + .rev() + .for_each(|w| old[0].insert_after(*w, &mut tree)); + old + .iter() + .for_each(|id| id.dispose_subtree(&mut tree)); + new.iter().for_each(|w| { + w.on_mounted_subtree(&tree); + tree.mark_dirty(*w) + }); + }); + }); - set_pos_of_multi(&new, ctx); - query_info_outside_until(old[0], &info, ctx, |info| info.multi_replace(&old, &new)); - pipe_node.primary_transplant(old[0], new[0], ctx); + c_pipe_node.own_subscription(u, ctx); + id + }; - update_key_state_multi(old.iter().copied(), new.iter().copied(), ctx); + let mut widgets = vec![InnerWidget::LazyBuild(Box::new(first)).into()]; + for (idx, w) in iter.enumerate() { + let info = info.clone(); + let f = move |ctx: &BuildCtx| { + let id = w.into_widget().build(ctx); + info.borrow_mut().widgets.push(id); + + let tree = &mut ctx.tree.borrow_mut(); + if set_pos_of_multi(id, idx + 1, tree) { + // We need to associate the parent information with the children pipe so that + // when the child pipe is regenerated, it can update the parent pipe information + // accordingly. + let info: Box = Box::new(info.clone()); + id.attach_data(Queryable(info), tree); + } + id + }; - new - .iter() - .rev() - .for_each(|w| ctx.insert_after(old[0], *w)); - old.iter().for_each(|id| ctx.dispose_subtree(*id)); - new.iter().for_each(|w| { - ctx.on_subtree_mounted(*w); - ctx.mark_dirty(*w) - }); - }); - }); - c_pipe_node.own_subscription(u, ctx); + widgets.push(InnerWidget::LazyBuild(Box::new(f)).into()); + } widgets } - fn into_parent_widget(self, ctx: &BuildCtx) -> Widget + fn into_parent_widget<'w, const M: usize>(self) -> Widget<'static> where Self: Sized, - Self::Value: IntoWidget, + Self::Value: IntoWidget<'w, M>, { - let root = ctx.tree.borrow().root(); - let info = Sc::new(RefCell::new(SingleParentPipeInfo { range: root..=root, multi_pos: 0 })); - let info2 = info.clone(); - let handle = ctx.handle(); - let (v, modifies) = self.tick_unzip(move || pipe_priority_value(&info2, handle), ctx); - let p = v.into_widget(ctx); + let f = move |ctx: &BuildCtx| { + let root = ctx.tree.borrow().root(); + let info = Sc::new(RefCell::new(SingleParentPipeInfo { range: root..=root, multi_pos: 0 })); - let pipe_node = PipeNode::share_capture(p.id(), Box::new(info.clone()), ctx); - let leaf = p.id().single_leaf(&ctx.tree.borrow().arena); - info.borrow_mut().range = p.id()..=leaf; + let priority = fn_priority(info.clone(), ctx.window().id()); + let (v, modifies) = self.unzip(ModifyScope::FRAMEWORK, Some(Box::new(priority))); - // We need to associate the parent information with the pipe of leaf widget, - // when the leaf pipe is regenerated, it can update the parent pipe information - // accordingly. - { - let arena = &mut ctx.tree.borrow_mut().arena; - if leaf.assert_get(arena).contain_type::() { - let info: Box = Box::new(info.clone()); - leaf.attach_data(Queryable(info), arena); - }; - } - - let c_pipe_node = pipe_node.clone(); + let p = v.into_widget().build(ctx); - let u = modifies.subscribe(move |(_, w)| { - handle.with_ctx(|ctx| { - let (top, bottom) = info.borrow().range.clone().into_inner(); + let pipe_node = PipeNode::share_capture(p, Box::new(info.clone()), ctx); + let leaf = p.single_leaf(&ctx.tree.borrow()); + info.borrow_mut().range = p..=leaf; - let p = w.into_widget(ctx).consume(); - let mut tree = ctx.tree.borrow_mut(); - let new_rg = p..=p.single_leaf(&tree.arena); - let children: SmallVec<[WidgetId; 1]> = bottom.children(&tree.arena).collect(); - for c in children { - new_rg.end().append(c, &mut tree.arena); - } - - drop(tree); + // We need to associate the parent information with the pipe of leaf widget, + // when the leaf pipe is regenerated, it can update the parent pipe information + // accordingly. + { + let tree = &mut ctx.tree.borrow_mut(); + if leaf.assert_get(tree).contain_type::() { + let info: Box = Box::new(info.clone()); + leaf.attach_data(Queryable(info), tree); + }; + } - query_info_outside_until(top, &info, ctx, |info| { - info.single_range_replace(&(top..=bottom), &new_rg); - }); - pipe_node.primary_transplant(top, p, ctx); - - update_key_status_single(top, p, ctx); - - ctx.insert_after(top, p); - ctx.dispose_subtree(top); - let mut w = p; - let tree = ctx.tree.borrow(); - loop { - ctx.on_widget_mounted(w); - if w == *new_rg.end() { - break; + let c_pipe_node = pipe_node.clone(); + let handle = ctx.handle(); + let u = modifies.subscribe(move |(_, w)| { + handle.with_ctx(|ctx| { + let (top, bottom) = info.borrow().range.clone().into_inner(); + + let p = w.into_widget().build(ctx); + let mut tree = ctx.tree.borrow_mut(); + let new_rg = p..=p.single_leaf(&tree); + let children: SmallVec<[WidgetId; 1]> = bottom.children(&tree).collect(); + for c in children { + new_rg.end().append(c, &mut tree); } - if let Some(c) = w.first_child(&tree.arena) { - w = c; - } else { - break; + + query_info_outside_until(top, &info, &tree, |info| { + info.single_range_replace(&(top..=bottom), &new_rg); + }); + pipe_node.primary_transplant(top, p, &mut tree); + + update_key_status_single(top, p, &tree); + top.insert_after(p, &mut tree); + top.dispose_subtree(&mut tree); + + let mut w = p; + loop { + w.on_widget_mounted(&tree); + if w == *new_rg.end() { + break; + } + if let Some(c) = w.first_child(&tree) { + w = c; + } else { + break; + } } - } - drop(tree); - ctx.mark_dirty(p); + tree.mark_dirty(p); + }); }); - }); - c_pipe_node.own_subscription(u, ctx); - p + c_pipe_node.own_subscription(u, ctx); + p + }; + InnerWidget::LazyBuild(Box::new(f)).into() } } -impl V + 'static> MapPipe { +impl V> MapPipe { #[inline] - pub fn new(source: S, f: F) -> Self { Self { source, f } } + pub fn new(source: S, f: F) -> Self { Self { source, f, _marker: PhantomData } } } pub struct ModifiesPipe(BoxOp<'static, ModifyScope, Infallible>); + impl ModifiesPipe { #[inline] pub fn new(modifies: BoxOp<'static, ModifyScope, Infallible>) -> Self { Self(modifies) } @@ -300,219 +308,177 @@ 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() } - - fn tick_unzip( - self, prior_fn: impl FnMut() -> i64 + 'static, ctx: &BuildCtx, + fn unzip( + self, scope: ModifyScope, priority: Option>, ) -> (Self::Value, ValueStream) { let stream = self .0 - .filter(|s| s.contains(ModifyScope::FRAMEWORK)) - .sample( - ctx - .window() - .frame_tick_stream() - .filter(|f| matches!(f, FrameMsg::NewFrame(_))), - ) - .prior_by(prior_fn, ctx.window().priority_task_queue().clone()) - .map(|s| (s, s)) - .box_it(); + .filter(move |s| s.contains(scope)) + .map(|s| (s, s)); + + let stream = if let Some(priority) = priority { + stream + .sample(AppCtx::frame_ticks().clone()) + .priority(priority) + .box_it() + } else { + stream.box_it() + }; (ModifyScope::empty(), stream) } - fn box_tick_unzip( - self: Box, prior_fn: Box i64>, ctx: &BuildCtx, + #[inline] + fn box_unzip( + self: Box, scope: ModifyScope, priority: Option>, ) -> (Self::Value, ValueStream) { - (*self).tick_unzip(prior_fn, ctx) + (*self).unzip(scope, priority) } } -impl Pipe for Box> { +impl Pipe for Box> { type Value = V; #[inline] - fn unzip(self) -> (Self::Value, ValueStream) { self.box_unzip() } - - #[inline] - fn box_unzip(self: Box) -> (Self::Value, ValueStream) { (*self).box_unzip() } - - #[inline] - fn tick_unzip( - self, prior_fn: impl FnMut() -> i64 + 'static, ctx: &BuildCtx, + fn unzip( + self, scope: ModifyScope, priority: Option>, ) -> (Self::Value, ValueStream) { - self.box_tick_unzip(Box::new(prior_fn), ctx) + self.box_unzip(scope, priority) } #[inline] - fn box_tick_unzip( - self: Box, prior_fn: Box i64>, ctx: &BuildCtx, + fn box_unzip( + self: Box, scope: ModifyScope, priority: Option>, ) -> (Self::Value, ValueStream) { - (*self).box_tick_unzip(prior_fn, ctx) + (*self).box_unzip(scope, priority) } } -impl Pipe for MapPipe +impl Pipe for MapPipe where - S::Value: 'static, - F: FnMut(S::Value) -> V + 'static, + Self: 'static, + S: Pipe, + F: FnMut(S::Value) -> V, { 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()) - } - - #[inline] - fn box_unzip(self: Box) -> (Self::Value, ValueStream) { (*self).unzip() } - - fn tick_unzip( - self, prior_fn: impl FnMut() -> i64 + 'static, ctx: &BuildCtx, + fn unzip( + self, scope: ModifyScope, priority: Option>, ) -> (Self::Value, ValueStream) { - let Self { source, mut f } = self; - let (v, stream) = source.tick_unzip(prior_fn, ctx); + let Self { source, mut f, .. } = self; + let (v, stream) = source.unzip(scope, priority); (f(v), stream.map(move |(s, v)| (s, f(v))).box_it()) } #[inline] - fn box_tick_unzip( - self: Box, prior_fn: Box i64>, ctx: &BuildCtx, + fn box_unzip( + self: Box, scope: ModifyScope, priority: Option>, ) -> (Self::Value, ValueStream) { - (*self).tick_unzip(prior_fn, ctx) + (*self).unzip(scope, priority) } } impl Pipe for FinalChain where + Self: 'static, S: Pipe, - F: FnOnce(ValueStream) -> ValueStream + 'static, + F: FnOnce(ValueStream) -> ValueStream, { type Value = V; - fn unzip(self) -> (V, ValueStream) { - let Self { source, f } = self; - let (v, stream) = source.unzip(); - (v, f(stream)) - } - - #[inline] - fn box_unzip(self: Box) -> (V, ValueStream) { (*self).unzip() } - - fn tick_unzip( - self, prior_fn: impl FnMut() -> i64 + 'static, ctx: &BuildCtx, + fn unzip( + self, scope: ModifyScope, priority: Option>, ) -> (Self::Value, ValueStream) { - let Self { source, f } = self; - let (v, stream) = source.tick_unzip(prior_fn, ctx); + let Self { source, f, .. } = self; + let (v, stream) = source.unzip(scope, priority); (v, f(stream)) } #[inline] - fn box_tick_unzip( - self: Box, prior_fn: Box i64>, ctx: &BuildCtx, + fn box_unzip( + self: Box, scope: ModifyScope, priority: Option>, ) -> (Self::Value, ValueStream) { - (*self).tick_unzip(prior_fn, ctx) + (*self).unzip(scope, priority) } } /// A pipe that never changes, help to construct a pipe from a value. struct ValuePipe(V); -impl Pipe for ValuePipe { +impl Pipe for ValuePipe { type Value = V; - fn unzip(self) -> (Self::Value, ValueStream) { - (self.0, observable::empty().box_it()) - } - #[inline] - fn box_unzip(self: Box) -> (Self::Value, ValueStream) { (*self).unzip() } - - fn tick_unzip( - self, _: impl FnMut() -> i64 + 'static, _: &BuildCtx, + fn unzip( + self, _: ModifyScope, _: Option>, ) -> (Self::Value, ValueStream) { - self.unzip() + (self.0, observable::empty().box_it()) } #[inline] - fn box_tick_unzip( - self: Box, prior_fn: Box i64>, ctx: &BuildCtx, + fn box_unzip( + self: Box, scope: ModifyScope, priority: Option>, ) -> (Self::Value, ValueStream) { - (*self).tick_unzip(prior_fn, ctx) + self.unzip(scope, priority) } } impl InnerPipe for T {} -impl IntoWidgetStrict for MapPipe +impl<'w, V, S, F, const M: usize> IntoWidgetStrict<'static, M> for MapPipe where - V: IntoWidget + 'static, - S: InnerPipe, - S::Value: 'static, - F: FnMut(S::Value) -> V + 'static, + Self: InnerPipe, + V: IntoWidget<'w, M>, { - fn into_widget_strict(self, ctx: &BuildCtx) -> Widget { self.build_single(ctx) } + fn into_widget_strict(self) -> Widget<'static> { self.build_single() } } -impl IntoWidgetStrict for FinalChain +impl<'w, V, S, F, const M: usize> IntoWidgetStrict<'static, M> for FinalChain where - V: IntoWidget + 'static, - S: InnerPipe, - F: FnOnce(ValueStream) -> ValueStream + 'static, + Self: InnerPipe, + V: IntoWidget<'w, M>, { - fn into_widget_strict(self, ctx: &BuildCtx) -> Widget { self.build_single(ctx) } + fn into_widget_strict(self) -> Widget<'static> { self.build_single() } } -impl + 'static> IntoWidgetStrict for Box> { - fn into_widget_strict(self, ctx: &BuildCtx) -> Widget { self.build_single(ctx) } +impl<'w, const M: usize, V> IntoWidgetStrict<'static, M> for Box> +where + V: IntoWidget<'w, M> + 'static, +{ + fn into_widget_strict(self) -> Widget<'static> { self.build_single() } } -impl IntoWidget for MapPipe, S, F> +impl<'w, V, S, F, const M: usize> IntoWidget<'static, M> for MapPipe, S, F> where - V: IntoWidget + 'static, - S: InnerPipe, - S::Value: 'static, - F: FnMut(S::Value) -> Option + 'static, + Self: InnerPipe>, + V: IntoWidget<'w, M>, { - fn into_widget(self, ctx: &BuildCtx) -> Widget { option_into_widget(self, ctx) } + fn into_widget(self) -> Widget<'static> { option_into_widget(self) } } -impl IntoWidget for FinalChain, S, F> +impl<'w, V, S, F, const M: usize> IntoWidget<'static, M> for FinalChain, S, F> where - V: IntoWidget + 'static, - S: InnerPipe>, - F: FnOnce(ValueStream>) -> ValueStream> + 'static, + Self: InnerPipe>, + V: IntoWidget<'w, M>, { - #[inline] - fn into_widget(self, ctx: &BuildCtx) -> Widget { option_into_widget(self, ctx) } + fn into_widget(self) -> Widget<'static> { option_into_widget(self) } } -impl + 'static> IntoWidget - for Box>> +impl<'w, const M: usize, V> IntoWidget<'static, M> for Box>> +where + V: IntoWidget<'w, M> + 'static, { - #[inline] - fn into_widget(self, ctx: &BuildCtx) -> Widget { option_into_widget(self, ctx) } + fn into_widget(self) -> Widget<'static> { option_into_widget(self) } } -fn option_into_widget( - p: impl InnerPipe + 'static>>, ctx: &BuildCtx, -) -> Widget { - p.map(|w| { - move |ctx: &BuildCtx| { - if let Some(w) = w { w.into_widget(ctx) } else { Void.into_widget(ctx) } - } - }) - .build_single(ctx) +fn option_into_widget<'w, const M: usize>( + p: impl InnerPipe + 'static>>, +) -> Widget<'static> { + p.map(|w| move |_: &BuildCtx| w.map_or_else(|| Void.into_widget(), IntoWidget::into_widget)) + .build_single() } -fn update_children_key_status(old: WidgetId, new: WidgetId, ctx: &BuildCtx) { - let tree = &ctx.tree.borrow().arena; - +fn update_children_key_status(old: WidgetId, new: WidgetId, tree: &WidgetTree) { match (old.first_child(tree), old.last_child(tree), new.first_child(tree), new.last_child(tree)) { // old or new children is empty. (None, _, _, _) | (_, _, None, _) => {} @@ -521,18 +487,18 @@ fn update_children_key_status(old: WidgetId, new: WidgetId, ctx: &BuildCtx) { } (Some(o_first), Some(o_last), Some(n_first), Some(n_last)) => { match (o_first == o_last, n_first == n_last) { - (true, true) => update_key_status_single(o_first, n_first, ctx), + (true, true) => update_key_status_single(o_first, n_first, tree), (true, false) => { - if let Some(old_key) = ctx - .assert_get(o_first) + if let Some(old_key) = o_first + .assert_get(tree) .query_ref::>() { let o_key = old_key.key(); new.children(tree).any(|n| { - if let Some(new_key) = ctx.assert_get(n).query_ref::>() { + if let Some(new_key) = n.assert_get(tree).query_ref::>() { let same_key = o_key == new_key.key(); if same_key { - update_key_states(&**old_key, o_first, &**new_key, n, ctx); + update_key_states(&**old_key, o_first, &**new_key, n, tree); } same_key } else { @@ -542,16 +508,16 @@ fn update_children_key_status(old: WidgetId, new: WidgetId, ctx: &BuildCtx) { } } (false, true) => { - if let Some(new_key) = ctx - .assert_get(n_first) + if let Some(new_key) = n_first + .assert_get(tree) .query_ref::>() { let n_key = new_key.key(); old.children(tree).any(|o| { - if let Some(old_key) = ctx.assert_get(o).query_ref::>() { + if let Some(old_key) = o.assert_get(tree).query_ref::>() { let same_key = old_key.key() == n_key; if same_key { - update_key_states(&**old_key, o, &**new_key, n_first, ctx); + update_key_states(&**old_key, o, &**new_key, n_first, tree); } same_key } else { @@ -560,38 +526,44 @@ fn update_children_key_status(old: WidgetId, new: WidgetId, ctx: &BuildCtx) { }); } } - (false, false) => update_key_state_multi(old.children(tree), new.children(tree), ctx), + (false, false) => update_key_state_multi(old.children(tree), new.children(tree), tree), } } } } -fn update_key_status_single(old: WidgetId, new: WidgetId, ctx: &BuildCtx) { - if let Some(old_key) = ctx.assert_get(old).query_ref::>() { - if let Some(new_key) = ctx.assert_get(new).query_ref::>() { +fn update_key_status_single(old: WidgetId, new: WidgetId, tree: &WidgetTree) { + if let Some(old_key) = old + .assert_get(tree) + .query_ref::>() + { + if let Some(new_key) = new + .assert_get(tree) + .query_ref::>() + { if old_key.key() == new_key.key() { - update_key_states(&**old_key, old, &**new_key, new, ctx); + update_key_states(&**old_key, old, &**new_key, new, tree); } } } } fn update_key_state_multi( - old: impl Iterator, new: impl Iterator, ctx: &BuildCtx, + old: impl Iterator, new: impl Iterator, tree: &WidgetTree, ) { let mut old_key_list = ahash::HashMap::default(); for o in old { - if let Some(old_key) = ctx.assert_get(o).query_ref::>() { + if let Some(old_key) = o.assert_get(tree).query_ref::>() { old_key_list.insert(old_key.key(), o); } } if !old_key_list.is_empty() { for n in new { - if let Some(new_key) = ctx.assert_get(n).query_ref::>() { + if let Some(new_key) = n.assert_get(tree).query_ref::>() { if let Some(o) = old_key_list.get(&new_key.key()).copied() { - if let Some(old_key) = ctx.assert_get(o).query_ref::>() { - update_key_states(&**old_key, o, &**new_key, n, ctx); + if let Some(old_key) = o.assert_get(tree).query_ref::>() { + update_key_states(&**old_key, o, &**new_key, n, tree); } } } @@ -600,11 +572,11 @@ fn update_key_state_multi( } fn update_key_states( - old_key: &dyn AnyKey, old: WidgetId, new_key: &dyn AnyKey, new: WidgetId, ctx: &BuildCtx, + old_key: &dyn AnyKey, old: WidgetId, new_key: &dyn AnyKey, new: WidgetId, tree: &WidgetTree, ) { new_key.record_prev_key_widget(old_key); old_key.record_next_key_widget(new_key); - update_children_key_status(old, new, ctx) + update_children_key_status(old, new, tree) } /// `PipeNode` just use to wrap a `Box`, and provide a choice to @@ -773,7 +745,7 @@ impl DynWidgetInfo for Sc> { impl PipeNode { fn share_capture(id: WidgetId, dyn_info: Box, ctx: &BuildCtx) -> Self { - let tree = &mut ctx.tree.borrow_mut().arena; + let tree = &mut ctx.tree.borrow_mut(); let mut pipe_node = None; id.wrap_node(tree, |r| { @@ -788,8 +760,7 @@ impl PipeNode { } // update the primary `PipeNode`. - fn primary_transplant(&self, old: WidgetId, new: WidgetId, ctx: &BuildCtx) { - let mut tree = ctx.tree.borrow_mut(); + fn primary_transplant(&self, old: WidgetId, new: WidgetId, tree: &mut WidgetTree) { let [old_node, new_node] = tree.get_many_mut(&[old, new]); std::mem::swap(&mut self.as_mut().data, new_node); std::mem::swap(old_node, new_node); @@ -811,7 +782,7 @@ impl PipeNode { fn own_subscription(self, u: impl Subscription + 'static, ctx: &BuildCtx) { let node = self.as_mut(); let id = node.dyn_info.host_id(); - let tree = &mut ctx.tree.borrow_mut().arena; + let tree = &mut ctx.tree.borrow_mut(); // if the subscription is closed, we can cancel and unwrap the `PipeNode` // immediately. if u.is_closed() { @@ -823,21 +794,20 @@ impl PipeNode { } } -fn set_pos_of_multi(widgets: &[WidgetId], ctx: &BuildCtx) { - let arena = &ctx.tree.borrow().arena; - widgets.iter().enumerate().for_each(|(pos, wid)| { - wid - .assert_get(arena) - .query_all_iter::() - .for_each(|info| info.set_pos_of_multi(pos)) - }); +fn set_pos_of_multi(w: WidgetId, pos: usize, tree: &WidgetTree) -> bool { + let mut b = false; + for info in w.assert_get(tree).query_all_iter::() { + info.set_pos_of_multi(pos); + b = true; + } + b } fn query_info_outside_until( - id: WidgetId, to: &Sc, ctx: &BuildCtx, mut cb: impl FnMut(&DynInfo), + id: WidgetId, to: &Sc, tree: &WidgetTree, mut cb: impl FnMut(&DynInfo), ) { for info in id - .assert_get(&ctx.tree.borrow().arena) + .assert_get(tree) .query_all_iter::() .rev() { @@ -853,22 +823,28 @@ fn query_info_outside_until( } } -fn pipe_priority_value(info: &Sc, handle: BuildCtxHandle) -> i64 +fn pipe_priority_value(info: &Sc, wnd: &Window) -> i64 where Sc: DynWidgetInfo, { - handle - .with_ctx(|ctx| { - let id = info.host_id(); - let depth = id.ancestors(&ctx.tree.borrow().arena).count() as i64; - let mut embed = 0; - query_info_outside_until(id, info, ctx, |_| { - embed += 1; - }); - let pos = info.pos_of_multi() as i64; - depth << 60 | pos << 40 | embed - }) - .unwrap_or(-1) + let id = info.host_id(); + let tree = wnd.widget_tree.borrow(); + let depth = id.ancestors(&tree).count() as i64; + let mut embed = 0; + query_info_outside_until(id, info, &tree, |_| { + embed += 1; + }); + let pos = info.pos_of_multi() as i64; + depth << 60 | pos << 40 | embed +} + +fn fn_priority(info: Sc, wnd_id: WindowId) -> WindowPriority i64 + 'static> +where + Sc: DynWidgetInfo + 'static, +{ + WindowPriority::new(wnd_id, move || { + AppCtx::get_window(wnd_id).map_or(-1, |wnd| pipe_priority_value(&info, &wnd)) + }) } impl Query for PipeNode { @@ -902,6 +878,44 @@ impl RenderProxy for PipeNode { fn proxy(&self) -> Self::Target<'_> { &*self.as_ref().data } } +#[derive(Clone)] +struct MultiUpdatePriority { + wnd_id: Sc>>, + multi_info: Sc>, +} + +impl MultiUpdatePriority { + fn new(multi_info: Sc>) -> Self { + Self { wnd_id: <_>::default(), multi_info } + } + + fn set_wnd(&self, wnd_id: WindowId) { + assert!(self.wnd_id.get().is_none()); + self.wnd_id.set(Some(wnd_id)); + } +} + +impl Priority for MultiUpdatePriority { + fn priority(&mut self) -> i64 { + self + .wnd_id + .get() + .and_then(|wnd_id| AppCtx::get_window(wnd_id)) + .map_or(-1, |wnd| pipe_priority_value(&self.multi_info, &wnd)) + } + + fn queue(&mut self) -> Option<&PriorityTaskQueue> { + self.wnd_id.get().and_then(|wnd_id| { + AppCtx::get_window(wnd_id).map(|wnd| { + let queue = wnd.priority_task_queue(); + // Safety: This trait is only used within this crate, and we can ensure that + // the window is valid when utilizing the `PriorityTaskQueue`. + unsafe { std::mem::transmute(queue) } + }) + }) + } +} + #[cfg(test)] mod tests { use std::{ @@ -934,7 +948,7 @@ mod tests { tree.layout(Size::zero()); let ids = tree .content_root() - .descendants(&tree.arena) + .descendants(&tree) .collect::>(); assert_eq!(ids.len(), 2); { @@ -943,7 +957,7 @@ mod tests { tree.layout(Size::zero()); let new_ids = tree .content_root() - .descendants(&tree.arena) + .descendants(&tree) .collect::>(); assert_eq!(new_ids.len(), 2); @@ -971,7 +985,7 @@ mod tests { tree.layout(Size::zero()); let ids = tree .content_root() - .descendants(&tree.arena) + .descendants(&tree) .collect::>(); assert_eq!(ids.len(), 3); { @@ -980,7 +994,7 @@ mod tests { tree.layout(Size::zero()); let new_ids = tree .content_root() - .descendants(&tree.arena) + .descendants(&tree) .collect::>(); assert_eq!(new_ids.len(), 3); @@ -1019,7 +1033,7 @@ mod tests { assert!( tree .content_root() - .assert_get(&tree.arena) + .assert_get(&tree) .contain_type::>() ); } @@ -1258,7 +1272,7 @@ mod tests { wid: Option, } - fn build(task: Writer) -> impl IntoWidget { + fn build(task: Writer) -> impl IntoWidget<'static, FN> { fn_widget! { @TaskWidget { keep_alive: pipe!($task.pin), @@ -1296,7 +1310,7 @@ mod tests { fn child_count(wnd: &Window) -> usize { let tree = wnd.widget_tree.borrow(); let root = tree.content_root(); - root.children(&tree.arena).count() + root.children(&tree).count() } let tasks = (0..3) @@ -1401,31 +1415,28 @@ mod tests { let mut wnd = TestWindow::new(w); wnd.draw_frame(); - fn tree_arena(wnd: &TestWindow) -> Ref { - let tree = wnd.widget_tree.borrow(); - Ref::map(tree, |t| &t.arena) - } + fn tree(wnd: &TestWindow) -> Ref { wnd.widget_tree.borrow() } let grandson_id = { - let arena = tree_arena(&wnd); + let tree = tree(&wnd); let root = wnd.widget_tree.borrow().content_root(); root - .first_child(&arena) + .first_child(&tree) .unwrap() - .first_child(&arena) + .first_child(&tree) .unwrap() }; wnd.draw_frame(); - assert!(!grandson_id.is_dropped(&tree_arena(&wnd))); + assert!(!grandson_id.is_dropped(&tree(&wnd))); c_child.write().take(); wnd.draw_frame(); - assert!(!grandson_id.is_dropped(&tree_arena(&wnd))); + assert!(!grandson_id.is_dropped(&tree(&wnd))); *c_child_destroy_until.write() = true; wnd.draw_frame(); - assert!(grandson_id.is_dropped(&tree_arena(&wnd))); + assert!(grandson_id.is_dropped(&tree(&wnd))); } #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] @@ -1435,7 +1446,7 @@ mod tests { let hit = State::value(-1); let v = BoxPipe::value(0); - let (v, s) = v.into_pipe().unzip(); + let (v, s) = v.into_pipe().unzip(ModifyScope::all(), None); assert_eq!(v, 0); @@ -1451,8 +1462,8 @@ mod tests { reset_test_env!(); let _ = fn_widget! { let v = Stateful::new(true); - let w = pipe!(*$v).map(move |_| Void.into_widget(ctx!())); - w.into_widget(ctx!()) + let w = pipe!(*$v).map(move |_| Void.into_widget()); + w.into_widget() }; } @@ -1730,8 +1741,8 @@ mod tests { @MockMulti { @ { pipe!($m_watcher;).map(move |_| [ - @ { Void.into_widget(ctx!()) }, - @ { pipe!($son_watcher;).map(|_| Void).into_widget(ctx!()) } + @ { Void.into_widget() }, + @ { pipe!($son_watcher;).map(|_| Void).into_widget() } ]) } } diff --git a/core/src/state.rs b/core/src/state.rs index 46722c530..67c166a3b 100644 --- a/core/src/state.rs +++ b/core/src/state.rs @@ -3,16 +3,12 @@ mod prior_op; mod splitted_state; mod stateful; mod watcher; -use std::{ - cell::{Cell, UnsafeCell}, - convert::Infallible, - mem::MaybeUninit, - ops::DerefMut, -}; +use std::{cell::UnsafeCell, convert::Infallible, mem::MaybeUninit, ops::DerefMut}; pub mod state_cell; pub use map_state::*; pub use prior_op::*; +use ribir_algo::Sc; use rxrust::ops::box_it::{BoxOp, CloneableBoxOp}; pub use splitted_state::*; pub use state_cell::{PartData, ReadRef}; @@ -122,7 +118,7 @@ pub trait StateWriter: StateWatcher { #[inline] fn split_writer(&self, mut_map: W) -> SplittedWriter where - W: Fn(&mut Self::Value) -> PartData + Clone + 'static, + W: Fn(&mut Self::Value) -> PartData + Clone, { SplittedWriter::new(self.clone_writer(), mut_map) } @@ -150,7 +146,7 @@ pub trait StateWriter: StateWatcher { pub struct WriteRef<'a, V> { value: ValueMutRef<'a, V>, - control: &'a dyn WriterControl, + info: &'a Sc, modify_scope: ModifyScope, modified: bool, } @@ -163,12 +159,6 @@ pub(crate) enum InnerState { Stateful(Stateful), } -trait WriterControl { - fn batched_modifies(&self) -> &Cell; - fn notifier(&self) -> &Notifier; - fn dyn_clone(&self) -> Box; -} - impl StateReader for State { type Value = T; type OriginReader = Self; @@ -271,7 +261,7 @@ impl<'a, V> WriteRef<'a, V> { let borrow = orig.value.borrow.clone(); let value = ValueMutRef { inner, borrow }; - WriteRef { value, modified: false, modify_scope: orig.modify_scope, control: orig.control } + WriteRef { value, modified: false, modify_scope: orig.modify_scope, info: orig.info } } pub fn map_split( @@ -280,14 +270,14 @@ impl<'a, V> WriteRef<'a, V> { where F: FnOnce(&mut V) -> (PartData, PartData), { - let WriteRef { control, modify_scope, modified, .. } = orig; + let WriteRef { info, modify_scope, modified, .. } = orig; let (a, b) = f(&mut *orig.value); let borrow = orig.value.borrow.clone(); let a = ValueMutRef { inner: a, borrow: borrow.clone() }; let b = ValueMutRef { inner: b, borrow }; ( - WriteRef { value: a, modified, modify_scope, control }, - WriteRef { value: b, modified, modify_scope, control }, + WriteRef { value: a, modified, modify_scope, info }, + WriteRef { value: b, modified, modify_scope, info }, ) } @@ -316,21 +306,21 @@ impl<'a, W> DerefMut for WriteRef<'a, W> { impl<'a, W> Drop for WriteRef<'a, W> { fn drop(&mut self) { - let Self { control, modify_scope, modified, .. } = self; + let Self { info, modify_scope, modified, .. } = self; if !*modified { return; } - let batched_modifies = control.batched_modifies(); + let batched_modifies = &info.batched_modifies; if batched_modifies.get().is_empty() && !modify_scope.is_empty() { batched_modifies.set(*modify_scope); - let control = control.dyn_clone(); + let info = info.clone(); let _ = AppCtx::spawn_local(async move { - let scope = control - .batched_modifies() + let scope = info + .batched_modifies .replace(ModifyScope::empty()); - control.notifier().next(scope); + info.notifier.next(scope); }); } else { batched_modifies.set(*modify_scope | batched_modifies.get()); @@ -338,18 +328,18 @@ impl<'a, W> Drop for WriteRef<'a, W> { } } -impl IntoWidgetStrict for State { - fn into_widget_strict(self, ctx: &BuildCtx) -> Widget { +impl IntoWidgetStrict<'static, RENDER> for State { + fn into_widget_strict(self) -> Widget<'static> { match self.0.into_inner() { - InnerState::Data(w) => w.into_inner().into_widget(ctx), - InnerState::Stateful(w) => w.into_widget(ctx), + InnerState::Data(w) => w.into_inner().into_widget(), + InnerState::Stateful(w) => w.into_widget(), } } } -impl IntoWidgetStrict for State { +impl IntoWidgetStrict<'static, COMPOSE> for State { #[inline] - fn into_widget_strict(self, ctx: &BuildCtx) -> Widget { Compose::compose(self).into_widget(ctx) } + fn into_widget_strict(self) -> Widget<'static> { Compose::compose(self) } } impl MultiChild for T @@ -368,7 +358,7 @@ where #[cfg(test)] mod tests { - use ribir_algo::Sc; + use std::cell::Cell; use super::*; #[cfg(target_arch = "wasm32")] @@ -455,9 +445,7 @@ mod tests { struct C; impl Compose for C { - fn compose(_: impl StateWriter) -> impl IntoWidgetStrict { - fn_widget! { Void } - } + fn compose(_: impl StateWriter) -> Widget<'static> { Void.into_widget() } } #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] @@ -488,12 +476,10 @@ mod tests { } struct CC; - impl ComposeChild for CC { - type Child = Option; - fn compose_child( - _: impl StateWriter, _: Self::Child, - ) -> impl IntoWidgetStrict { - fn_widget! { @{ Void } } + impl<'c> ComposeChild<'c> for CC { + type Child = Option>; + fn compose_child(_: impl StateWriter, _: Self::Child) -> Widget<'c> { + Void.into_widget() } } diff --git a/core/src/state/map_state.rs b/core/src/state/map_state.rs index 4ed2da548..aa3e60070 100644 --- a/core/src/state/map_state.rs +++ b/core/src/state/map_state.rs @@ -22,7 +22,7 @@ impl StateReader for MapReader where Self: 'static, S: StateReader, - M: Fn(&S::Value) -> PartData + Clone + 'static, + M: Fn(&S::Value) -> PartData + Clone, { type Value = V; type OriginReader = S; @@ -155,8 +155,9 @@ where impl RenderProxy for MapReader where + Self: 'static, S: StateReader, - F: Fn(&S::Value) -> PartData + Clone + 'static, + F: Fn(&S::Value) -> PartData + Clone, V: Render, { type R = V; @@ -171,8 +172,9 @@ where impl RenderProxy for MapWriterAsReader where + Self: 'static, S: StateReader, - F: Fn(&mut S::Value) -> PartData + Clone + 'static, + F: Fn(&mut S::Value) -> PartData + Clone, V: Render, { type R = V; @@ -185,18 +187,19 @@ where fn proxy(&self) -> Self::Target<'_> { self.read() } } -impl IntoWidgetStrict for MapWriter +impl<'w, S, F> IntoWidgetStrict<'w, RENDER> for MapWriter where - Self: StateReader, - ::Reader: IntoWidget, + Self: 'static, + Self: StateReader + 'w, + ::Reader: IntoWidget<'w, RENDER>, { - fn into_widget_strict(self, ctx: &BuildCtx) -> Widget { self.clone_reader().into_widget(ctx) } + fn into_widget_strict(self) -> Widget<'w> { self.clone_reader().into_widget() } } -impl IntoWidgetStrict for MapWriter +impl IntoWidgetStrict<'static, COMPOSE> for MapWriter where - Self: StateWriter, + Self: StateWriter + 'static, ::Value: Compose, { - fn into_widget_strict(self, ctx: &BuildCtx) -> Widget { Compose::compose(self).into_widget(ctx) } + fn into_widget_strict(self) -> Widget<'static> { Compose::compose(self) } } diff --git a/core/src/state/prior_op.rs b/core/src/state/prior_op.rs index 0051c707c..50de0bd3f 100644 --- a/core/src/state/prior_op.rs +++ b/core/src/state/prior_op.rs @@ -4,114 +4,161 @@ use priority_queue::PriorityQueue; use ribir_algo::Sc; use rxrust::prelude::*; +use super::AppCtx; +use crate::window::WindowId; + /// A priority queue of tasks. So that tasks with higher priority will be /// executed first. -#[derive(Clone, Default)] -pub struct PriorityTaskQueue<'a>( - Sc, i64, ahash::RandomState>>>, -); +#[derive(Default)] +pub struct PriorityTaskQueue(RefCell>); -pub struct PriorOp<'a, P, S> { - prior_fn: P, +pub struct PriorOp { source: S, - scheduler: PriorityTaskQueue<'a>, + priority: P, } -pub struct PriorityTask<'a>(Box); +pub struct PriorityTask(Box); -pub struct PriorObserver<'a, O, F> { +pub struct PriorObserver { observer: Sc>>, - prior_fn: F, - scheduler: PriorityTaskQueue<'a>, + priority: P, } -/// A trait for Observable that can be assigned a priority queue to collect its -/// value with a priority. The values will be emitted in order when the -/// `PriorityTaskQueue::run` method is called. +/// This trait defines an Observable that can be assigned a priority. The +/// `Priority` will collect the values and emitted in order by the priority. pub trait PriorityObservable: ObservableExt { - /// Specify the priority queue an Observable should use to collect its values - /// with a priority. The lower the priority value, the higher the priority. - fn prior( - self, prior: i64, scheduler: PriorityTaskQueue, - ) -> PriorOp i64 + 'static, Self> - where - Self: Sized, - { - PriorOp { prior_fn: move || prior, source: self, scheduler } + /// This method specifies a static priority to the Observable in the priority + /// queue of the window. A lower priority value indicates higher priority. + fn value_priority( + self, priority: i64, wnd_id: WindowId, + ) -> PriorOp i64>> { + self.fn_priority(move || priority, wnd_id) + } + + /// The method defines the priority for the Observable in the window's + /// priority queue. The priority value is calculated for each emitted value by + /// the function `f`, with lower values indicating higher priority. + fn fn_priority( + self, f: impl FnMut() -> i64, wnd_id: WindowId, + ) -> PriorOp i64>> { + PriorOp { source: self, priority: WindowPriority { wnd_id, priority: f } } } - /// Specify the priority queue an Observable should use to collect its values - /// and every value will be assigned a priority by the given function. The - /// lower the priority value, the higher the priority. - fn prior_by

(self, prior_fn: P, scheduler: PriorityTaskQueue) -> PriorOp - where - Self: Sized, - P: FnMut() -> i64, - { - PriorOp { prior_fn, source: self, scheduler } + fn priority(self, priority: P) -> PriorOp { + PriorOp { source: self, priority } } } +/// This trait is used to determine the priority of a task and the queue used to +/// collect these tasks. +pub trait Priority { + fn priority(&mut self) -> i64; + + fn queue(&mut self) -> Option<&PriorityTaskQueue>; +} + +pub struct WindowPriority

{ + wnd_id: WindowId, + priority: P, +} + +impl

WindowPriority

{ + pub fn new(wnd_id: WindowId, priority: P) -> Self { Self { wnd_id, priority } } +} + +impl i64> Priority for WindowPriority

{ + fn priority(&mut self) -> i64 { (self.priority)() } + + fn queue(&mut self) -> Option<&PriorityTaskQueue> { + AppCtx::get_window(self.wnd_id).map(|wnd| { + let queue = wnd.priority_task_queue(); + // Safety: This trait is only used within this module, and we can ensure that + // the window is valid when utilizing the `PriorityTaskQueue`. + unsafe { std::mem::transmute(queue) } + }) + } +} + +impl Priority for Box { + fn priority(&mut self) -> i64 { (**self).priority() } + + fn queue(&mut self) -> Option<&PriorityTaskQueue> { (**self).queue() } +} + impl PriorityObservable for T where T: ObservableExt {} -impl<'a, Item: 'a, Err: 'a, O, S, P> Observable for PriorOp<'a, P, S> +impl Observable for PriorOp where - O: Observer + 'a, - S: Observable>, - P: FnMut() -> i64, + O: Observer + 'static, + S: Observable> + 'static, + P: Priority + 'static, { type Unsub = ZipSubscription>; fn actual_subscribe(self, observer: O) -> Self::Unsub { - let Self { prior_fn, source, scheduler } = self; + let Self { source, priority } = self; let observer = Sc::new(RefCell::new(Some(observer))); let o2 = observer.clone(); - let u = source.actual_subscribe(PriorObserver { observer, prior_fn, scheduler }); + let u = source.actual_subscribe(PriorObserver { observer, priority }); ZipSubscription::new(u, PrioritySubscription(o2)) } } -impl<'a, Item: 'a, Err: 'a, S, P> ObservableExt for PriorOp<'a, P, S> +impl ObservableExt for PriorOp where S: ObservableExt, - P: FnMut() -> i64, + P: Priority, { } -impl<'a, Item: 'a, Err: 'a, O, F> Observer for PriorObserver<'a, O, F> +impl Observer for PriorObserver where - O: Observer + 'a, - F: FnMut() -> i64, + O: Observer + 'static, + P: Priority + 'static, { fn next(&mut self, value: Item) { - let priority = (self.prior_fn)(); - let observer = self.observer.clone(); - let task = PriorityTask(Box::new(move || { - if let Some(o) = observer.borrow_mut().as_mut() { - o.next(value) - } - })); - self.scheduler.add(task, priority) + let priority = self.priority.priority(); + if let Some(queue) = self.priority.queue() { + let observer = self.observer.clone(); + let task: Box = Box::new(move || { + if let Some(o) = observer.borrow_mut().as_mut() { + o.next(value) + } + }); + queue.add(PriorityTask(task), priority); + } else if let Some(o) = self.observer.borrow_mut().as_mut() { + o.next(value) + } } fn error(mut self, err: Err) { - let priority = (self.prior_fn)(); - let task = PriorityTask(Box::new(move || { - if let Some(o) = self.observer.borrow_mut().take() { - o.error(err) - } - })); - self.scheduler.add(task, priority + 1) + let priority = self.priority.priority(); + if let Some(queue) = self.priority.queue() { + let observer = self.observer.clone(); + let task: Box = Box::new(move || { + if let Some(o) = observer.borrow_mut().take() { + o.error(err) + } + }); + queue.add(PriorityTask(task), priority + 1); + } else if let Some(o) = self.observer.borrow_mut().take() { + o.error(err) + } } fn complete(mut self) { - let priority = (self.prior_fn)(); - let task = PriorityTask(Box::new(move || { - if let Some(o) = self.observer.borrow_mut().take() { - o.complete() - } - })); - self.scheduler.add(task, priority + 1) + let priority = self.priority.priority(); + if let Some(queue) = self.priority.queue() { + let observer = self.observer.clone(); + let task: Box = Box::new(move || { + if let Some(o) = observer.borrow_mut().take() { + o.complete() + } + }); + queue.add(PriorityTask(task), priority + 1); + } else if let Some(o) = self.observer.borrow_mut().take() { + o.complete() + } } fn is_finished(&self) -> bool { self.observer.borrow().is_none() } @@ -125,31 +172,31 @@ impl Subscription for PrioritySubscription { fn is_closed(&self) -> bool { self.0.borrow().is_none() } } -impl<'a> PartialEq for PriorityTask<'a> { +impl PartialEq for PriorityTask { fn eq(&self, _: &Self) -> bool { // Three isn't two task that are equal. false } } -impl<'a> Eq for PriorityTask<'a> {} +impl Eq for PriorityTask {} -impl<'a> std::hash::Hash for PriorityTask<'a> { +impl std::hash::Hash for PriorityTask { fn hash(&self, state: &mut H) { std::ptr::hash(&*self.0, state) } } -impl<'a> PriorityTaskQueue<'a> { +impl PriorityTaskQueue { pub fn is_empty(&self) -> bool { self.0.borrow().is_empty() } - pub fn pop(&self) -> Option<(PriorityTask<'a>, i64)> { self.0.borrow_mut().pop() } + pub fn pop(&self) -> Option<(PriorityTask, i64)> { self.0.borrow_mut().pop() } /// Add a task to the queue with a priority. - pub fn add(&self, task: PriorityTask<'a>, priority: i64) { + pub fn add(&self, task: PriorityTask, priority: i64) { self.0.borrow_mut().push(task, -priority); } } -impl<'a> PriorityTask<'a> { +impl PriorityTask { /// Create a new task. pub fn new(f: impl FnOnce() + 'static) -> Self { PriorityTask(Box::new(f)) } @@ -161,51 +208,66 @@ mod tests { use super::*; #[cfg(target_arch = "wasm32")] use crate::test_helper::wasm_bindgen_test; + use crate::{ + prelude::Void, + reset_test_env, + state::{StateReader, StateWriter, Stateful}, + test_helper::TestWindow, + }; #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] #[test] fn prior_smoke() { - let result = RefCell::new(Vec::new()); - let queue = PriorityTaskQueue::default(); + reset_test_env!(); + + let mut wnd = TestWindow::new(Void); + let wnd_id = wnd.id(); + + let r = Stateful::new(Vec::new()); + let result = r.clone_writer(); observable::of(2) - .prior(2, queue.clone()) - .subscribe(|v| result.borrow_mut().push(v)); + .value_priority(2, wnd_id) + .subscribe(move |v: i32| result.write().push(v)); + let result = r.clone_writer(); observable::of(1) - .prior(1, queue.clone()) - .subscribe(|v| result.borrow_mut().push(v)); + .value_priority(1, wnd_id) + .subscribe(move |v| result.write().push(v)); + let result = r.clone_writer(); observable::of(3) - .prior(3, queue.clone()) - .subscribe(|v| result.borrow_mut().push(v)); + .value_priority(3, wnd_id) + .subscribe(move |v| result.write().push(v)); - while let Some((task, _p)) = queue.pop() { - task.run() - } + wnd.draw_frame(); - assert_eq!(*result.borrow(), vec![1, 2, 3]); + assert_eq!(*r.read(), vec![1, 2, 3]); } #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] #[test] fn prior_by_smoke() { - let result = RefCell::new(Vec::new()); - let queue = PriorityTaskQueue::default(); + reset_test_env!(); + + let r = Stateful::new(Vec::new()); + let mut wnd = TestWindow::new(Void); + let wnd_id = wnd.id(); + let result = r.clone_writer(); observable::of(2) - .prior_by(|| 2, queue.clone()) - .subscribe(|v| result.borrow_mut().push(v)); + .fn_priority(|| 2, wnd_id) + .subscribe(move |v| result.write().push(v)); + let result = r.clone_writer(); observable::of(1) - .prior_by(|| 1, queue.clone()) - .subscribe(|v| result.borrow_mut().push(v)); + .fn_priority(|| 1, wnd_id) + .subscribe(move |v| result.write().push(v)); + let result = r.clone_writer(); observable::of(3) - .prior_by(|| 3, queue.clone()) - .subscribe(|v| result.borrow_mut().push(v)); + .fn_priority(|| 3, wnd_id) + .subscribe(move |v| result.write().push(v)); - while let Some((task, _p)) = queue.pop() { - task.run() - } - assert_eq!(*result.borrow(), vec![1, 2, 3]); + wnd.draw_frame(); + assert_eq!(*r.read(), vec![1, 2, 3]); } } diff --git a/core/src/state/splitted_state.rs b/core/src/state/splitted_state.rs index aaac47337..01d6b3241 100644 --- a/core/src/state/splitted_state.rs +++ b/core/src/state/splitted_state.rs @@ -1,5 +1,3 @@ -use ribir_algo::Sc; - use super::*; use crate::widget::*; @@ -7,22 +5,11 @@ use crate::widget::*; pub struct SplittedWriter { origin: O, splitter: W, - notifier: Notifier, - batched_modify: Sc>, - ref_count: Sc>, + info: Sc, } impl Drop for SplittedWriter { - fn drop(&mut self) { - if self.ref_count.get() == 1 { - let mut notifier = self.notifier.clone(); - // we use an async task to unsubscribe to wait the batched modifies to be - // notified. - let _ = AppCtx::spawn_local(async move { - notifier.unsubscribe(); - }); - } - } + fn drop(&mut self) { self.info.dec_writer() } } impl StateReader for SplittedWriter @@ -64,7 +51,7 @@ where W: Fn(&mut O::Value) -> PartData + Clone, { fn raw_modifies(&self) -> CloneableBoxOp<'static, ModifyScope, std::convert::Infallible> { - self.notifier.raw_modifies().box_it() + self.info.notifier.raw_modifies().box_it() } } @@ -87,12 +74,11 @@ where fn shallow(&self) -> WriteRef { self.split_ref(self.origin.shallow()) } fn clone_writer(&self) -> Self::Writer { + self.info.inc_writer(); SplittedWriter { origin: self.origin.clone_writer(), splitter: self.splitter.clone(), - notifier: self.notifier.clone(), - batched_modify: self.batched_modify.clone(), - ref_count: self.ref_count.clone(), + info: self.info.clone(), } } @@ -100,36 +86,13 @@ where fn origin_writer(&self) -> &Self::OriginWriter { &self.origin } } -impl WriterControl for SplittedWriter -where - Self: 'static, - O: StateWriter, - W: Fn(&mut O::Value) -> PartData + Clone, -{ - #[inline] - fn batched_modifies(&self) -> &Cell { &self.batched_modify } - - #[inline] - fn notifier(&self) -> &Notifier { &self.notifier } - - #[inline] - fn dyn_clone(&self) -> Box { Box::new(self.clone_writer()) } -} - impl SplittedWriter where - Self: 'static, O: StateWriter, W: Fn(&mut O::Value) -> PartData + Clone, { pub(super) fn new(origin: O, mut_map: W) -> Self { - Self { - origin, - splitter: mut_map, - notifier: Notifier::default(), - batched_modify: <_>::default(), - ref_count: Sc::new(Cell::new(1)), - } + Self { origin, splitter: mut_map, info: Sc::new(WriterInfo::new()) } } #[track_caller] @@ -144,22 +107,22 @@ where let value = ValueMutRef { inner: (self.splitter)(&mut orig.value), borrow: orig.value.borrow.clone() }; - WriteRef { value, modified: false, modify_scope, control: self } + WriteRef { value, modified: false, modify_scope, info: &self.info } } } -impl IntoWidgetStrict for SplittedWriter +impl<'w, S, F> IntoWidgetStrict<'w, RENDER> for SplittedWriter where - Self: StateWriter, - ::Reader: IntoWidget, + Self: StateWriter + 'w, + ::Reader: IntoWidget<'w, RENDER>, { - fn into_widget_strict(self, ctx: &BuildCtx) -> Widget { self.clone_reader().into_widget(ctx) } + fn into_widget_strict(self) -> Widget<'w> { self.clone_reader().into_widget() } } -impl IntoWidgetStrict for SplittedWriter +impl IntoWidgetStrict<'static, COMPOSE> for SplittedWriter where - Self: StateWriter, + Self: StateWriter + 'static, ::Value: Compose, { - fn into_widget_strict(self, ctx: &BuildCtx) -> Widget { Compose::compose(self).into_widget(ctx) } + fn into_widget_strict(self) -> Widget<'static> { Compose::compose(self) } } diff --git a/core/src/state/stateful.rs b/core/src/state/stateful.rs index 34eddd0f5..dda7c6ceb 100644 --- a/core/src/state/stateful.rs +++ b/core/src/state/stateful.rs @@ -3,13 +3,13 @@ use std::{cell::Cell, convert::Infallible}; use ribir_algo::Sc; use rxrust::{ops::box_it::CloneableBoxOp, prelude::*}; -use super::{state_cell::StateCell, WriterControl}; +use super::state_cell::StateCell; use crate::prelude::*; /// Stateful object use to watch the modifies of the inner data. pub struct Stateful { data: Sc>, - info: Sc, + info: Sc, } pub struct Reader(Sc>); @@ -37,12 +37,12 @@ impl Notifier { pub(crate) fn unsubscribe(&mut self) { self.0.clone().unsubscribe(); } } -struct StatefulInfo { - notifier: Notifier, +pub(crate) struct WriterInfo { + pub(crate) notifier: Notifier, /// The counter of the writer may be modified the data. - writer_count: Cell, + pub(crate) writer_count: Cell, /// The batched modifies of the `State` which will be notified. - batch_modified: Cell, + pub(crate) batched_modifies: Cell, } impl StateReader for Stateful { @@ -169,26 +169,17 @@ impl StateWriter for Writer { fn origin_writer(&self) -> &Self::OriginWriter { self } } -impl WriterControl for Sc { - #[inline] - fn batched_modifies(&self) -> &Cell { &self.batch_modified } - - #[inline] - fn notifier(&self) -> &Notifier { &self.notifier } - - #[inline] - fn dyn_clone(&self) -> Box { Box::new(self.clone()) } +impl Drop for Stateful { + fn drop(&mut self) { self.info.dec_writer(); } } -impl Drop for Stateful { +impl Drop for WriterInfo { fn drop(&mut self) { - self.dec_writer(); - // can cancel the notifier, because no one will modify the data. - if self.writer_count() == 0 { - let notifier = self.info.notifier.clone(); + if self.writer_count.get() == 0 { + let mut notifier = self.notifier.clone(); // we use an async task to unsubscribe to wait the batched modifies to be // notified. - let _ = AppCtx::spawn_local(async move { notifier.0.unsubscribe() }); + let _ = AppCtx::spawn_local(async move { notifier.unsubscribe() }); } } } @@ -200,74 +191,68 @@ impl Writer { impl Stateful { pub fn new(data: W) -> Self { - Self { data: Sc::new(StateCell::new(data)), info: Sc::new(StatefulInfo::new()) } + Self { data: Sc::new(StateCell::new(data)), info: Sc::new(WriterInfo::new()) } } fn write_ref(&self, scope: ModifyScope) -> WriteRef<'_, W> { let value = self.data.write(); - WriteRef { value, modified: false, modify_scope: scope, control: &self.info } - } - - fn writer_count(&self) -> usize { self.info.writer_count.get() } - fn inc_writer(&self) { - self - .info - .writer_count - .set(self.writer_count() + 1); - } - fn dec_writer(&self) { - self - .info - .writer_count - .set(self.writer_count() - 1); + WriteRef { value, modified: false, modify_scope: scope, info: &self.info } } fn clone(&self) -> Self { - self.inc_writer(); + self.info.inc_writer(); Self { data: self.data.clone(), info: self.info.clone() } } } -impl StatefulInfo { - fn new() -> Self { - StatefulInfo { - batch_modified: <_>::default(), +impl WriterInfo { + pub(crate) fn new() -> Self { + WriterInfo { + batched_modifies: <_>::default(), writer_count: Cell::new(1), notifier: <_>::default(), } } + + pub(crate) fn inc_writer(&self) { self.writer_count.set(self.writer_count.get() + 1); } + + pub(crate) fn dec_writer(&self) { self.writer_count.set(self.writer_count.get() - 1); } } -impl IntoWidgetStrict for Stateful { - fn into_widget_strict(self, ctx: &BuildCtx) -> Widget { +impl IntoWidgetStrict<'static, RENDER> for Stateful { + fn into_widget_strict(self) -> Widget<'static> { match self.try_into_value() { - Ok(r) => r.into_widget(ctx), + Ok(r) => r.into_widget(), Err(s) => { - let w = s.data.clone().into_widget(ctx); - w.dirty_subscribe(s.raw_modifies(), ctx) + let f = move |ctx: &BuildCtx| { + let w = s.data.clone().into_widget().build(ctx); + w.dirty_subscribe(s.raw_modifies(), ctx); + w + }; + InnerWidget::LazyBuild(Box::new(f)).into() } } } } -impl IntoWidgetStrict for Stateful { - fn into_widget_strict(self, ctx: &BuildCtx) -> Widget { Compose::compose(self).into_widget(ctx) } +impl IntoWidgetStrict<'static, COMPOSE> for Stateful { + fn into_widget_strict(self) -> Widget<'static> { Compose::compose(self) } } -impl IntoWidgetStrict for Reader { - fn into_widget_strict(self, ctx: &BuildCtx) -> Widget { +impl IntoWidgetStrict<'static, RENDER> for Reader { + fn into_widget_strict(self) -> Widget<'static> { match Sc::try_unwrap(self.0) { - Ok(r) => r.into_inner().into_widget(ctx), - Err(s) => s.into_widget(ctx), + Ok(r) => r.into_inner().into_widget(), + Err(s) => s.into_widget(), } } } -impl IntoWidgetStrict for Writer +impl<'w, W, const M: usize> IntoWidgetStrict<'w, M> for Writer where - Stateful: IntoWidget, + Stateful: IntoWidget<'w, M>, { - fn into_widget_strict(self, ctx: &BuildCtx) -> Widget { self.0.into_widget(ctx) } + fn into_widget_strict(self) -> Widget<'w> { self.0.into_widget() } } impl Notifier { @@ -405,13 +390,7 @@ mod tests { let mut wnd = TestWindow::new(fn_widget!(MockBox { size: Size::new(100., 100.) })); wnd.draw_frame(); let tree = wnd.widget_tree.borrow(); - assert_eq!( - tree - .content_root() - .descendants(&tree.arena) - .count(), - 1 - ); + assert_eq!(tree.content_root().descendants(&tree).count(), 1); } #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] @@ -482,7 +461,7 @@ mod tests { // data + 1, info + 1 let v = Stateful::new(1); // data +1 - let (_, stream) = pipe!(*$v).unzip(); + let (_, stream) = pipe!(*$v).unzip(ModifyScope::all(), None); let _ = stream.subscribe(|(_, v)| println!("{v}")); AppCtx::run_until_stalled(); @@ -516,16 +495,17 @@ mod tests { let v = Stateful::new(1); let data = v.data.clone(); - let info = v.info.clone(); + let notifier = v.info.notifier.0.clone(); { + let info = v.info.clone(); let u = watch!(*$v).subscribe(move |_| *v.write() = 2); u.unsubscribe(); + assert_eq!(info.ref_count(), 1); } AppCtx::run_until_stalled(); assert_eq!(data.ref_count(), 1); - assert!(info.notifier.0.is_closed()); - assert_eq!(info.ref_count(), 1); + assert!(notifier.is_closed()); } } diff --git a/core/src/test_helper.rs b/core/src/test_helper.rs index a0f74e06e..82d477912 100644 --- a/core/src/test_helper.rs +++ b/core/src/test_helper.rs @@ -41,13 +41,13 @@ macro_rules! reset_test_env { impl TestWindow { /// Create a 1024x1024 window for test - pub fn new(root: impl IntoWidgetStrict) -> Self { Self::new_wnd(root, None) } + pub fn new<'w, const M: usize>(root: impl IntoWidget<'w, M>) -> Self { Self::new_wnd(root, None) } - pub fn new_with_size(root: impl IntoWidgetStrict, size: Size) -> Self { + pub fn new_with_size<'w, const M: usize>(root: impl IntoWidget<'w, M>, size: Size) -> Self { Self::new_wnd(root, Some(size)) } - fn new_wnd(root: impl IntoWidgetStrict, size: Option) -> Self { + fn new_wnd<'w, const M: usize>(root: impl IntoWidget<'w, M>, size: Option) -> Self { let _ = NEW_TIMER_FN.set(Timer::new_timer_future); let wnd = AppCtx::new_window(Box::new(TestShellWindow::new(size)), root); wnd.run_frame_tasks(); @@ -62,12 +62,9 @@ impl TestWindow { let tree = self.0.widget_tree.borrow(); let mut node = tree.root(); for (level, idx) in path[..].iter().enumerate() { - node = node - .children(&tree.arena) - .nth(*idx) - .unwrap_or_else(|| { - panic!("node no exist: {:?}", &path[0..level]); - }); + node = node.children(&tree).nth(*idx).unwrap_or_else(|| { + panic!("node no exist: {:?}", &path[0..level]); + }); } tree.store.layout_info(node).cloned() } @@ -86,7 +83,7 @@ impl TestWindow { pub fn content_count(&self) -> usize { let widget_tree = self.0.widget_tree.borrow(); let root = widget_tree.root(); - let content = root.first_child(&widget_tree.arena).unwrap(); + let content = root.first_child(&widget_tree).unwrap(); widget_tree.count(content) } @@ -95,6 +92,8 @@ impl TestWindow { // Test window not have a eventloop, manually wake-up every frame. Timer::wake_timeout_futures(); self.run_frame_tasks(); + + AppCtx::frame_ticks().clone().next(Instant::now()); self.0.draw_frame(); } } diff --git a/core/src/widget.rs b/core/src/widget.rs index 10fcb004d..35ea6c567 100644 --- a/core/src/widget.rs +++ b/core/src/widget.rs @@ -1,4 +1,3 @@ -use std::convert::Infallible; #[doc(hidden)] pub use std::{ any::{Any, TypeId}, @@ -6,15 +5,14 @@ pub use std::{ ops::Deref, }; -use rxrust::ops::box_it::CloneableBoxOp; -use widget_id::RenderQueryable; +use widget_id::{new_node, RenderQueryable}; pub(crate) use crate::widget_tree::*; use crate::{context::*, prelude::*, render_helper::PureRender}; pub trait Compose: Sized { /// Describes the part of the user interface represented by this widget. /// Called by framework, should never directly call it. - fn compose(this: impl StateWriter) -> impl IntoWidgetStrict; + fn compose(this: impl StateWriter) -> Widget<'static>; } pub struct HitTest { @@ -53,14 +51,38 @@ pub trait Render: 'static { } /// The common type of all widget can convert to. -pub struct Widget { - id: WidgetId, - handle: BuildCtxHandle, +pub struct Widget<'w>(InnerWidget<'w>); +pub(crate) enum InnerWidget<'w> { + Leaf(Box), + Lazy(LazyWidget<'w>), + LazyBuild(Box WidgetId + 'w>), + SubTree { node: Box>, children: Vec> }, +} +/// This serves as a wrapper for `Box Widget<'w> + +/// 'w>`, but does not utilize the 'w in the return type to prevent the +/// `LazyWidget` from becoming **invariant**. This approach allows `Widget<'w>` +/// to remain **covariant** with the lifetime `'w`. + +/// This approach should be acceptable since `LazyWidget` is private and not +/// accessed externally. Additionally, the lifetime will shorten once we consume +/// it to obtain the `Widget<'w>`. +pub(crate) struct LazyWidget<'w>(Box Widget<'static> + 'w>); + +impl<'w> LazyWidget<'w> { + pub(crate) fn new(f: impl FnOnce(&BuildCtx) -> Widget<'w> + 'w) -> Self { + let f: Box Widget<'w> + 'w> = Box::new(f); + // Safety: the lifetime will shorten once we consume it to obtain the + // `Widget<'w>`. + let f: Box Widget<'static> + 'w> = unsafe { std::mem::transmute(f) }; + Self(f) + } + + fn consume(self, ctx: &BuildCtx) -> Widget<'w> { (self.0)(ctx) } } /// A boxed function widget that can be called multiple times to regenerate /// widget. -pub struct GenWidget(Box FnMut(&'a BuildCtx<'b>) -> Widget>); +pub struct GenWidget(Box Widget<'static>>); // The widget type marker. pub const COMPOSE: usize = 1; @@ -72,108 +94,99 @@ pub const FN: usize = 3; /// implemented by the framework. /// /// Instead, focus on implementing `Compose`, `Render`, or `ComposeChild`. -pub trait IntoWidget { - fn into_widget(self, ctx: &BuildCtx) -> Widget; +pub trait IntoWidget<'w, const M: usize>: 'w { + fn into_widget(self) -> Widget<'w>; } /// A trait used by the framework to implement `IntoWidget`. Unlike /// `IntoWidget`, this trait is not implemented for `Widget` itself. This design /// choice allows the framework to use either `IntoWidget` or `IntoWidgetStrict` /// as a generic bound, preventing implementation conflicts. -// fixme: should be pub(crate) -pub trait IntoWidgetStrict { - fn into_widget_strict(self, ctx: &BuildCtx) -> Widget; +pub(crate) trait IntoWidgetStrict<'w, const M: usize>: 'w { + fn into_widget_strict(self) -> Widget<'w>; } -impl IntoWidget for Widget { +impl<'w> IntoWidget<'w, FN> for Widget<'w> { #[inline(always)] - fn into_widget(self, _: &BuildCtx) -> Widget { self } + fn into_widget(self) -> Widget<'w> { self } } -impl> IntoWidget for T { +impl<'w, const M: usize, T: IntoWidgetStrict<'w, M>> IntoWidget<'w, M> for T { #[inline(always)] - fn into_widget(self, ctx: &BuildCtx) -> Widget { self.into_widget_strict(ctx) } + fn into_widget(self) -> Widget<'w> { self.into_widget_strict() } } -impl IntoWidgetStrict for C { +impl IntoWidgetStrict<'static, COMPOSE> for C { #[inline] - fn into_widget_strict(self, ctx: &BuildCtx) -> Widget { - Compose::compose(State::value(self)).into_widget(ctx) + fn into_widget_strict(self) -> Widget<'static> { + Compose::compose(State::value(self)).into_widget() } } -impl IntoWidgetStrict for R { - #[inline] - fn into_widget_strict(self, ctx: &BuildCtx) -> Widget { - Widget::new(Box::new(PureRender(self)), ctx) +impl IntoWidgetStrict<'static, RENDER> for R { + fn into_widget_strict(self) -> Widget<'static> { + InnerWidget::Leaf(Box::new(PureRender(self))).into() } } -impl>, C> Compose for W { - fn compose(this: impl StateWriter) -> impl IntoWidgetStrict { - fn_widget! { - ComposeChild::compose_child(this, None) - } +impl>, C> Compose for W { + fn compose(this: impl StateWriter) -> Widget<'static> { + ComposeChild::compose_child(this, None) } } -impl IntoWidgetStrict for F +impl<'w, F> IntoWidgetStrict<'w, FN> for F where - F: FnOnce(&BuildCtx) -> Widget, + F: FnOnce(&BuildCtx) -> Widget<'w> + 'w, { - #[inline] - fn into_widget_strict(self, ctx: &BuildCtx) -> Widget { self(ctx) } + fn into_widget_strict(self) -> Widget<'w> { + let lazy = LazyWidget::new(self); + InnerWidget::Lazy(lazy).into() + } } -impl IntoWidgetStrict for GenWidget { +impl IntoWidgetStrict<'static, FN> for GenWidget { #[inline] - fn into_widget_strict(mut self, ctx: &BuildCtx) -> Widget { self.gen_widget(ctx) } + fn into_widget_strict(self) -> Widget<'static> { self.0.into_widget_strict() } } -impl Widget { - /// Consume the widget, and return its id. This means this widget already be - /// append into its parent. - pub(crate) fn consume(self) -> WidgetId { - let id = self.id; - std::mem::forget(self); - id - } - - /// Subscribe the modifies `upstream` to mark the widget dirty when the - /// `upstream` emit a modify event that contains `ModifyScope::FRAMEWORK`. - pub(crate) fn dirty_subscribe( - self, upstream: CloneableBoxOp<'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) +impl<'a> Widget<'a> { + pub(crate) fn build(self, ctx: &BuildCtx) -> WidgetId { + // fixme: restore the state of `&BuildCtx`, wait for provider. + let mut subtrees = vec![]; + let root = self.inner_build(&mut subtrees, ctx); + while let Some((p, child)) = subtrees.pop() { + let c = child.inner_build(&mut subtrees, ctx); + p.append(c, &mut ctx.tree.borrow_mut()); + } + root } - pub(crate) fn id(&self) -> WidgetId { self.id } - - pub(crate) fn new(w: Box, ctx: &BuildCtx) -> Self { - Self::from_id(ctx.alloc_widget(w), ctx) + fn inner_build(self, subtrees: &mut Vec<(WidgetId, Widget<'a>)>, ctx: &BuildCtx) -> WidgetId { + match self.0 { + InnerWidget::Leaf(r) => new_node(&mut ctx.tree.borrow_mut().arena, r), + InnerWidget::LazyBuild(f) => f(ctx), + InnerWidget::Lazy(l) => l.consume(ctx).inner_build(subtrees, ctx), + InnerWidget::SubTree { node, children } => { + let p = node.inner_build(subtrees, ctx); + let leaf = p.single_leaf(&ctx.tree.borrow()); + for c in children.into_iter().rev() { + subtrees.push((leaf, c)) + } + p + } + } } - - pub(crate) fn from_id(id: WidgetId, ctx: &BuildCtx) -> Self { Self { id, handle: ctx.handle() } } } - impl GenWidget { #[inline] - pub fn new(f: impl FnMut(&BuildCtx) -> Widget + 'static) -> Self { Self(Box::new(f)) } + pub fn new(f: impl FnMut(&BuildCtx) -> Widget<'static> + 'static) -> Self { Self(Box::new(f)) } #[inline] - pub fn gen_widget(&mut self, ctx: &BuildCtx) -> Widget { (self.0)(ctx) } + pub fn gen_widget(&mut self, ctx: &BuildCtx) -> Widget<'static> { (self.0)(ctx) } } -impl Widget + 'static> From for GenWidget { +impl Widget<'static> + 'static> From for GenWidget { #[inline] fn from(f: F) -> Self { Self::new(f) } } @@ -184,11 +197,6 @@ pub(crate) fn hit_test_impl(ctx: &HitTestCtx, pos: Point) -> bool { .map_or(false, |rect| rect.contains(pos)) } -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)); - } +impl<'w> From> for Widget<'w> { + fn from(value: InnerWidget<'w>) -> Self { Widget(value) } } diff --git a/core/src/widget_children.rs b/core/src/widget_children.rs index 0d306e0e6..5a31dee3b 100644 --- a/core/src/widget_children.rs +++ b/core/src/widget_children.rs @@ -14,11 +14,11 @@ pub trait SingleChild {} pub trait MultiChild {} /// A boxed render widget that support accept one child. #[derive(SingleChild)] -pub struct BoxedSingleChild(Widget); +pub struct BoxedSingleChild(Widget<'static>); /// A boxed render widget that support accept multi children. #[derive(MultiChild)] -pub struct BoxedMultiChild(Widget); +pub struct BoxedMultiChild(Widget<'static>); /// This trait specifies the type of child a widget can have, and the target /// type represents the result of the widget composing its child. @@ -31,18 +31,16 @@ pub struct BoxedMultiChild(Widget); /// - 0 for SingleChild /// - 1 for MultiChild /// - 2..9 for ComposeChild -pub trait WithChild { - type Target; - fn with_child(self, child: C, ctx: &BuildCtx) -> Self::Target; +pub trait WithChild<'w, C, const N: usize, const M: usize> { + type Target: 'w; + fn with_child(self, child: C) -> Self::Target; } -/// Trait mark widget can have one child and also have compose logic for widget -/// and its child. -pub trait ComposeChild: Sized { - type Child; - fn compose_child( - this: impl StateWriter, child: Self::Child, - ) -> impl IntoWidgetStrict; +/// Trait for specifying the child type and implementing how to compose the +/// child. +pub trait ComposeChild<'c>: Sized { + type Child: 'c; + fn compose_child(this: impl StateWriter, child: Self::Child) -> Widget<'c>; } /// A pair of object and its child without compose, this keep the type @@ -55,28 +53,26 @@ pub struct Pair { /// A alias of `Pair`, means `Widget` is the child of the generic /// type. -pub type WidgetOf = Pair; +pub type WidgetOf<'a, W> = Pair>; -impl IntoWidgetStrict for BoxedMultiChild { +impl IntoWidgetStrict<'static, RENDER> for BoxedMultiChild { #[inline] - fn into_widget_strict(self, _: &BuildCtx) -> Widget { self.0 } + fn into_widget_strict(self) -> Widget<'static> { self.0 } } -impl IntoWidgetStrict for BoxedSingleChild { +impl IntoWidgetStrict<'static, RENDER> for BoxedSingleChild { #[inline] - fn into_widget_strict(self, _: &BuildCtx) -> Widget { self.0 } + fn into_widget_strict(self) -> Widget<'static> { self.0 } } impl BoxedSingleChild { #[inline] - pub fn new(widget: impl SingleIntoParent, ctx: &BuildCtx) -> Self { - Self(widget.into_parent(ctx)) - } + pub fn new(widget: impl SingleIntoParent) -> Self { Self(widget.into_parent()) } } impl BoxedMultiChild { #[inline] - pub fn new(widget: impl MultiIntoParent, ctx: &BuildCtx) -> Self { Self(widget.into_parent(ctx)) } + pub fn new(widget: impl MultiIntoParent) -> Self { Self(widget.into_parent()) } } impl Pair { @@ -117,19 +113,17 @@ mod tests { struct Footer; #[derive(Template)] - struct PageTml { - _header: WidgetOf>, - _content: WidgetOf>, - _footer: WidgetOf>, + struct PageTml<'w> { + _header: WidgetOf<'w, FatObj

>, + _content: WidgetOf<'w, FatObj>, + _footer: WidgetOf<'w, FatObj