From 43994c6bcaee580dbcdde8a1f5fda344ed45593e Mon Sep 17 00:00:00 2001 From: wjian23 Date: Mon, 19 Feb 2024 18:46:32 +0800 Subject: [PATCH] =?UTF-8?q?feat(core):=20=F0=9F=8E=B8=20overlay?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGELOG.md | 7 +- core/src/builtin_widgets.rs | 2 + .../src/builtin_widgets}/container.rs | 4 +- core/src/builtin_widgets/focus_node.rs | 2 +- core/src/builtin_widgets/focus_scope.rs | 4 +- core/src/builtin_widgets/global_anchor.rs | 2 +- core/src/context/build_ctx.rs | 7 +- core/src/events/focus_mgr.rs | 14 +- core/src/lib.rs | 3 + core/src/overlay.rs | 366 ++++++++++++++++++ core/src/pipe.rs | 26 +- core/src/state/stateful.rs | 2 +- core/src/test_helper.rs | 13 +- core/src/widget_tree.rs | 29 +- core/src/widget_tree/widget_id.rs | 12 +- core/src/window.rs | 2 +- docs/en/get_started/try_it.md | 2 +- tests/rdl_macro_test.rs | 5 +- widgets/src/layout.rs | 3 +- 19 files changed, 455 insertions(+), 50 deletions(-) rename {widgets/src/layout => core/src/builtin_widgets}/container.rs (93%) create mode 100644 core/src/overlay.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index ee68be789..55ef464d0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,6 +22,12 @@ Please only add new entries below the [Unreleased](#unreleased---releasedate) he ## [@Unreleased] - @ReleaseDate +### Features + +- Support the overlay (@wjian23). + + This enhancement simplifies the creation of overlay widgets. It streamlines the addition of any widget to an overlay and offers a more user-friendly API for overlay management + ## [0.2.0-alpha.4] - 2024-02-27 ### Fixed @@ -41,7 +47,6 @@ Please only add new entries below the [Unreleased](#unreleased---releasedate) he - fix broken links and format the example code (#526 @M-Adoo) - ## [0.1.0-beta.7](https://github.com/RibirX/Ribir/compare/ribir-v0.1.0-alpha.0...ribir-v0.1.0-beta.7) - 2024-02-02 🎉🎉🎉 The first version of Ribir. diff --git a/core/src/builtin_widgets.rs b/core/src/builtin_widgets.rs index b8778692c..1cc718424 100644 --- a/core/src/builtin_widgets.rs +++ b/core/src/builtin_widgets.rs @@ -63,6 +63,8 @@ pub mod global_anchor; pub use global_anchor::*; mod mix_builtin; pub use mix_builtin::*; +pub mod container; +pub use container::*; use crate::{ prelude::*, diff --git a/widgets/src/layout/container.rs b/core/src/builtin_widgets/container.rs similarity index 93% rename from widgets/src/layout/container.rs rename to core/src/builtin_widgets/container.rs index ec5d512b3..d309905dc 100644 --- a/widgets/src/layout/container.rs +++ b/core/src/builtin_widgets/container.rs @@ -1,4 +1,4 @@ -use ribir_core::prelude::*; +use crate::prelude::*; /// Widget with fixed size as a container for its child. #[derive(Declare, Query, SingleChild)] @@ -26,7 +26,7 @@ impl Render for Container { #[cfg(test)] mod tests { use super::*; - use ribir_core::test_helper::*; + use crate::test_helper::*; use ribir_dev_helper::*; use ribir_geom::Size; diff --git a/core/src/builtin_widgets/focus_node.rs b/core/src/builtin_widgets/focus_node.rs index 8aa6c52a9..d3ff173b1 100644 --- a/core/src/builtin_widgets/focus_node.rs +++ b/core/src/builtin_widgets/focus_node.rs @@ -61,7 +61,7 @@ mod tests { let wnd = TestWindow::new(widget); let tree = wnd.widget_tree.borrow(); - let id = tree.root(); + let id = tree.content_root(); let node = id.get(&tree.arena).unwrap(); let mut cnt = 0; node.query_type_inside_first(|b: &MixBuiltin| { diff --git a/core/src/builtin_widgets/focus_scope.rs b/core/src/builtin_widgets/focus_scope.rs index 060eba7da..75884ebfd 100644 --- a/core/src/builtin_widgets/focus_scope.rs +++ b/core/src/builtin_widgets/focus_scope.rs @@ -66,7 +66,7 @@ mod tests { focus_mgr.refresh_focus(arena); - let id0 = tree.root().first_child(arena).unwrap(); + 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(); @@ -126,7 +126,7 @@ mod tests { focus_mgr.refresh_focus(&widget_tree.arena); let arena = &widget_tree.arena; - let id0 = widget_tree.root().first_child(arena).unwrap(); + let id0 = widget_tree.content_root().first_child(arena).unwrap(); let scope = id0.next_sibling(arena).unwrap(); let id1 = scope.next_sibling(arena).unwrap(); diff --git a/core/src/builtin_widgets/global_anchor.rs b/core/src/builtin_widgets/global_anchor.rs index 45f86ee6c..f1163fe00 100644 --- a/core/src/builtin_widgets/global_anchor.rs +++ b/core/src/builtin_widgets/global_anchor.rs @@ -4,7 +4,7 @@ use std::rc::Rc; #[derive(Declare, Query, Default)] pub struct GlobalAnchor { #[declare(builtin, default)] - global_anchor: Anchor, + pub global_anchor: Anchor, } impl ComposeChild for GlobalAnchor { diff --git a/core/src/context/build_ctx.rs b/core/src/context/build_ctx.rs index 45e3f7c0d..a81d0267d 100644 --- a/core/src/context/build_ctx.rs +++ b/core/src/context/build_ctx.rs @@ -111,12 +111,7 @@ impl<'a> BuildCtx<'a> { /// Dispose the whole subtree of `id`, include `id` itself. pub(crate) fn dispose_subtree(&self, id: WidgetId) { - let mut tree = self.tree.borrow_mut(); - let parent = id.parent(&tree.arena); - tree.detach(id); - tree - .window() - .add_delay_event(DelayEvent::Disposed { id, parent }); + id.dispose_subtree(&mut self.tree.borrow_mut()); } pub(crate) fn mark_dirty(&self, id: WidgetId) { self.tree.borrow_mut().mark_dirty(id); } diff --git a/core/src/events/focus_mgr.rs b/core/src/events/focus_mgr.rs index 894b2cf2a..d33616636 100644 --- a/core/src/events/focus_mgr.rs +++ b/core/src/events/focus_mgr.rs @@ -529,13 +529,13 @@ impl FocusManager { // bubble focus out if let Some(old) = old { - let ancestor = node.and_then(|w| w.common_ancestors(old, &tree.arena).next()); + let ancestor = node.and_then(|w| w.lowest_common_ancestor(old, &tree.arena)); 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.common_ancestors(new, &tree.arena).next()); + let ancestor = old.and_then(|o| o.lowest_common_ancestor(new, &tree.arena)); wnd.add_delay_event(DelayEvent::FocusIn { bottom: new, up: ancestor }); } @@ -570,7 +570,7 @@ mod tests { focus_mgr.refresh_focus(&tree.arena); - let id = tree.root().first_child(&tree.arena); + let id = tree.content_root().first_child(&tree.arena); assert!(id.is_some()); assert_eq!(focus_mgr.focusing(), id); } @@ -592,7 +592,7 @@ mod tests { let tree = wnd.widget_tree.borrow(); let id = tree - .root() + .content_root() .first_child(&tree.arena) .and_then(|p| p.next_sibling(&tree.arena)); assert!(id.is_some()); @@ -623,7 +623,7 @@ mod tests { focus_mgr.refresh_focus(arena); - let negative = widget_tree.root().first_child(arena).unwrap(); + 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(); @@ -713,7 +713,7 @@ mod tests { let log = widget.log.clone(); let mut wnd = TestWindow::new(fn_widget!(widget)); let tree = wnd.widget_tree.borrow(); - let parent = tree.root(); + let parent = tree.content_root(); let child = parent.first_child(&tree.arena).unwrap(); drop(tree); @@ -813,7 +813,7 @@ mod tests { let mut focus_mgr = wnd.focus_mgr.borrow_mut(); let tree = wnd.widget_tree.borrow(); - let first_box = tree.root().first_child(&tree.arena); + let first_box = tree.content_root().first_child(&tree.arena); let focus_scope = first_box.unwrap().next_sibling(&tree.arena); focus_mgr.request_focus_to(focus_scope); diff --git a/core/src/lib.rs b/core/src/lib.rs index d4c2339c6..8e205d745 100644 --- a/core/src/lib.rs +++ b/core/src/lib.rs @@ -23,6 +23,7 @@ pub mod widget; pub mod widget_children; pub mod window; pub use rxrust; +pub mod overlay; pub mod prelude { pub use crate::animation::*; @@ -37,6 +38,8 @@ pub mod prelude { #[doc(no_inline)] pub use crate::events::*; #[doc(no_inline)] + pub use crate::overlay::{Overlay, OverlayCloseHandle}; + #[doc(no_inline)] pub use crate::pipe::{BoxPipe, FinalChain, MapPipe, ModifiesPipe, Pipe}; #[doc(no_inline)] pub use crate::state::*; diff --git a/core/src/overlay.rs b/core/src/overlay.rs new file mode 100644 index 000000000..2d6f04c4a --- /dev/null +++ b/core/src/overlay.rs @@ -0,0 +1,366 @@ +use std::mem::replace; +use std::time::Instant; +use std::{cell::RefCell, rc::Rc}; + +use ribir_geom::{Size, ZERO_SIZE}; +use ribir_macros::Query; + +use crate::prelude::*; +use crate::{ + context::{AppCtx, BuildCtx, LayoutCtx}, + widget::{BoxClamp, Render, WidgetBuilder, WidgetId}, +}; + +#[derive(Clone)] +pub struct OverlayStyle { + pub close_policy: ClosePolicy, + pub mask_brush: Option, +} + +bitflags! { + #[derive(Clone, Copy)] + pub struct ClosePolicy: u8 { + const NONE = 0b0000; + const ESC = 0b0001; + const TAP_OUTSIDE = 0b0010; + } +} + +impl CustomStyle for OverlayStyle { + fn default_style(_: &BuildCtx) -> Self { + Self { + close_policy: ClosePolicy::ESC | ClosePolicy::TAP_OUTSIDE, + mask_brush: Some(Color::from_f32_rgba(0.3, 0.3, 0.3, 0.3).into()), + } + } +} + +/// A handle to close the overlay +#[derive(Clone)] +pub struct OverlayCloseHandle(OverlayState); +impl OverlayCloseHandle { + pub fn close(&self) { self.0.close() } +} + +struct OverlayData { + builder: Box BoxedWidget>, + style: RefCell>, + state: OverlayState, +} + +#[derive(Clone)] +pub struct Overlay(Rc); + +impl Overlay { + /// Create overlay from Clone able widget. + /// + /// ### Example + /// ``` no_run + /// use ribir::prelude::*; + /// let w = fn_widget! { + /// let overlay = Overlay::new( + /// fn_widget! { + /// @Text { + /// h_align: HAlign::Center, + /// v_align: VAlign::Center, + /// text: "Hello" + /// } + /// } + /// ); + /// @FilledButton{ + /// on_tap: move |e| overlay.show(e.window()), + /// @{ Label::new("Click to show overlay") } + /// } + /// }; + /// App::new_window(w, None); + /// App::exec(); + /// ``` + pub fn new(widget: M) -> Self + where + M: WidgetBuilder + 'static + Clone, + { + Self(Rc::new(OverlayData { + builder: Box::new(move |_| widget.clone().box_it()), + style: RefCell::new(None), + state: OverlayState::default(), + })) + } + + /// Create overlay from a builder with a close_handle + /// + /// ### Example + /// popup a widget of a button which will close when clicked. + /// ``` no_run + /// use ribir::prelude::*; + /// let w = fn_widget! { + /// let overlay = Overlay::new_with_handle( + /// move |ctrl: OverlayCloseHandle| { + /// let ctrl = ctrl.clone(); + /// fn_widget! { + /// @FilledButton { + /// h_align: HAlign::Center, + /// v_align: VAlign::Center, + /// on_tap: move |_| ctrl.close(), + /// @{ Label::new("Click to close") } + /// } + /// } + /// } + /// ); + /// @FilledButton { + /// on_tap: move |e| overlay.show(e.window()), + /// @{ Label::new("Click to show overlay") } + /// } + /// }; + /// App::new_window(w, Some(Size::new(200., 200.))); + /// App::exec(); + /// ``` + pub fn new_with_handle(builder: M) -> Self + where + M: Fn(OverlayCloseHandle) -> O + 'static, + O: WidgetBuilder + 'static, + { + Self(Rc::new(OverlayData { + builder: Box::new(move |ctrl| builder(ctrl).box_it()), + style: RefCell::new(None), + state: OverlayState::default(), + })) + } + + /// Overlay will show with the given style, if the overlay have not been set + /// with style, the default style will be get from the theme. + pub fn with_style(&self, style: OverlayStyle) { *self.0.style.borrow_mut() = Some(style); } + + /// the Overlay widget will be show at the top level of all widget. + /// if the overlay is showing, nothing will happen. + pub fn show(&self, wnd: Rc) { + if self.is_show() { + return; + } + let w = (self.0.builder)(self.0.state.close_handle()); + let style = self.0.style.borrow().clone(); + self.0.state.show(w, style, wnd); + } + + /// User can make transform before the widget show at the top level of all + /// widget. if the overlay is showing, nothing will happen. + /// + /// ### Example + /// Overlay widget which auto align horizontal position to the src button even + /// when window's size changed + /// ``` no_run + /// use ribir::prelude::*; + /// let w = fn_widget! { + /// let overlay = Overlay::new( + /// fn_widget! { @Text { text: "overlay" } } + /// ); + /// let button = @FilledButton{}; + /// let wid = button.lazy_host_id(); + /// @$button { + /// h_align: HAlign::Center, + /// v_align: VAlign::Center, + /// on_tap: move |e| { + /// let wid = wid.clone(); + /// overlay.show_map( + /// move |w, _| { + /// let wid = wid.clone(); + /// fn_widget! { + /// let mut w = @$w {}; + /// w.left_align_to(&wid, 0., ctx!()); + /// w + /// } + /// }, + /// e.window() + /// ); + /// }, + /// @{ Label::new("Click to show overlay") } + /// } + /// }; + /// App::new_window(w, None); + /// App::exec(); + /// ``` + pub fn show_map(&self, f: F, wnd: Rc) + where + F: Fn(BoxedWidget, OverlayCloseHandle) -> O + 'static, + O: WidgetBuilder + 'static, + { + if self.is_show() { + return; + } + + let close_handle = self.0.state.close_handle(); + let w = f((self.0.builder)(close_handle.clone()), close_handle); + let style = self.0.style.borrow().clone(); + self.0.state.show(w, style, wnd); + } + + /// Show the widget at the give position. + /// if the overlay is showing, nothing will happen. + pub fn show_at(&self, pos: Point, wnd: Rc) { + if self.is_show() { + return; + } + self.show_map( + move |w, _| { + fn_widget! { + @$w { anchor: Anchor::from_point(pos) } + } + }, + wnd, + ); + } + + /// return whether the overlay is show. + pub fn is_show(&self) -> bool { self.0.state.is_show() } + + /// remove the showing overlay. + pub fn close(&self) { self.0.state.close() } +} + +enum OverlayInnerState { + ToShow(Instant, Rc), + Showing(WidgetId, Rc), + Hided, +} + +#[derive(Clone)] +struct OverlayState(Rc>); +impl Default for OverlayState { + fn default() -> Self { OverlayState(Rc::new(RefCell::new(OverlayInnerState::Hided))) } +} + +impl OverlayState { + fn close(&self) { + let state = replace(&mut *self.0.borrow_mut(), OverlayInnerState::Hided); + if let OverlayInnerState::Showing(wid, wnd) = state { + let _ = AppCtx::spawn_local(async move { + let root = wnd.widget_tree.borrow().root(); + wid.dispose_subtree(&mut wnd.widget_tree.borrow_mut()); + wnd.widget_tree.borrow_mut().mark_dirty(root); + }); + } + } + + fn is_show(&self) -> bool { !matches!(*self.0.borrow(), OverlayInnerState::Hided) } + + fn show(&self, w: impl WidgetBuilder + 'static, style: Option, wnd: Rc) { + if self.is_show() { + return; + } + let this = self.clone(); + let instant = Instant::now(); + *this.0.borrow_mut() = OverlayInnerState::ToShow(instant, wnd); + let _ = AppCtx::spawn_local(async move { + let wnd = match (instant, &*this.0.borrow()) { + (instant, OverlayInnerState::ToShow(crate_at, wnd)) if &instant == crate_at => wnd.clone(), + _ => return, + }; + 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).widget_build(&build_ctx); + let wid = w.id(); + *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); + }); + } + + fn wrap_style(&self, w: impl WidgetBuilder, style: OverlayStyle) -> impl WidgetBuilder { + let this = self.clone(); + fn_widget! { + let OverlayStyle { close_policy, mask_brush } = style; + let this2 = this.clone(); + @Container { + size: Size::new(f32::INFINITY, f32::INFINITY), + background: mask_brush.unwrap_or_else(|| Color::from_u32(0).into()), + on_tap: move |e| { + if close_policy.contains(ClosePolicy::TAP_OUTSIDE) + && e.target() == e.current_target() { + this.close(); + } + }, + on_key_down: move |e| { + if close_policy.contains(ClosePolicy::ESC) + && *e.key() == VirtualKey::Named(NamedKey::Escape) { + this2.close(); + } + }, + @$w {} + } + } + } + + fn close_handle(&self) -> OverlayCloseHandle { OverlayCloseHandle(self.clone()) } +} + +#[derive(Query)] +pub(crate) struct OverlayRoot {} + +impl Render for OverlayRoot { + fn perform_layout(&self, clamp: BoxClamp, ctx: &mut LayoutCtx) -> Size { + let mut size = ZERO_SIZE; + let mut layouter = ctx.first_child_layouter(); + while let Some(mut l) = layouter { + let child_size = l.perform_widget_layout(clamp); + size = size.max(child_size); + layouter = l.into_next_sibling(); + } + size + } + + fn paint(&self, _: &mut PaintingCtx) {} +} + +#[cfg(test)] +mod tests { + use std::{cell::RefCell, rc::Rc}; + + use crate::{prelude::*, reset_test_env, test_helper::*}; + + use ribir_dev_helper::assert_layout_result_by_path; + use ribir_geom::Size; + #[test] + fn overlay() { + reset_test_env!(); + let size = Size::zero(); + let widget = fn_widget! { + @MockBox { + size, + @MockBox { size } + } + }; + + let mut wnd = TestWindow::new(widget); + let w_log = Rc::new(RefCell::new(vec![])); + let r_log = w_log.clone(); + let overlay = Overlay::new(fn_widget! { + @MockBox { + size, + on_mounted: { + let w_log = w_log.clone(); + move |_| { w_log.borrow_mut().push("mounted");} + }, + on_disposed: move |_| { w_log.borrow_mut().push("disposed");} + } + }); + wnd.draw_frame(); + + let root = wnd.widget_tree.borrow().root(); + assert_eq!(wnd.widget_tree.borrow().count(root), 3); + + overlay.show(wnd.0.clone()); + overlay.close(); + overlay.show_at(Point::new(50., 30.), wnd.0.clone()); + wnd.draw_frame(); + assert_eq!(*r_log.borrow(), &["mounted"]); + // the path [1, 0, 0, 0] is from root to anchor, + // OverlayRoot -> BoxDecoration-> Container -> Anchor + assert_layout_result_by_path!(wnd, {path = [1, 0, 0, 0], x == 50., y == 30.,}); + + overlay.close(); + wnd.draw_frame(); + assert_eq!(*r_log.borrow(), &["mounted", "disposed"]); + assert_eq!(wnd.widget_tree.borrow().count(root), 3); + } +} diff --git a/core/src/pipe.rs b/core/src/pipe.rs index 17e8d06dd..1775602a9 100644 --- a/core/src/pipe.rs +++ b/core/src/pipe.rs @@ -1127,13 +1127,19 @@ mod tests { let wnd = TestWindow::new(w); let mut tree = wnd.widget_tree.borrow_mut(); tree.layout(Size::zero()); - let ids = tree.root().descendants(&tree.arena).collect::>(); + let ids = tree + .content_root() + .descendants(&tree.arena) + .collect::>(); assert_eq!(ids.len(), 2); { *c_size.write() = Size::new(1., 1.); } tree.layout(Size::zero()); - let new_ids = tree.root().descendants(&tree.arena).collect::>(); + let new_ids = tree + .content_root() + .descendants(&tree.arena) + .collect::>(); assert_eq!(new_ids.len(), 2); assert_eq!(ids[1], new_ids[1]); @@ -1157,13 +1163,19 @@ mod tests { let wnd = TestWindow::new(w); let mut tree = wnd.widget_tree.borrow_mut(); tree.layout(Size::zero()); - let ids = tree.root().descendants(&tree.arena).collect::>(); + let ids = tree + .content_root() + .descendants(&tree.arena) + .collect::>(); assert_eq!(ids.len(), 3); { *c_size.write() = Size::new(1., 1.); } tree.layout(Size::zero()); - let new_ids = tree.root().descendants(&tree.arena).collect::>(); + let new_ids = tree + .content_root() + .descendants(&tree.arena) + .collect::>(); assert_eq!(new_ids.len(), 3); assert_eq!(ids[0], new_ids[0]); @@ -1199,7 +1211,7 @@ mod tests { // the key should still in the root widget after pipe widget updated. assert!( tree - .root() + .content_root() .assert_get(&tree.arena) .contain_type::>() ); @@ -1472,7 +1484,7 @@ mod tests { fn child_count(wnd: &Window) -> usize { let tree = wnd.widget_tree.borrow(); - let root = tree.root(); + let root = tree.content_root(); root.children(&tree.arena).count() } @@ -1584,7 +1596,7 @@ mod tests { let grandson_id = { let arena = tree_arena(&wnd); - let root = wnd.widget_tree.borrow().root(); + let root = wnd.widget_tree.borrow().content_root(); root .first_child(&arena) .unwrap() diff --git a/core/src/state/stateful.rs b/core/src/state/stateful.rs index 440948bad..3a37856db 100644 --- a/core/src/state/stateful.rs +++ b/core/src/state/stateful.rs @@ -478,7 +478,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.root().descendants(&tree.arena).count(), 1); + assert_eq!(tree.content_root().descendants(&tree.arena).count(), 1); } #[test] diff --git a/core/src/test_helper.rs b/core/src/test_helper.rs index b184ed38e..60cbf8621 100644 --- a/core/src/test_helper.rs +++ b/core/src/test_helper.rs @@ -51,10 +51,9 @@ impl TestWindow { /// [0, 1, 2] the first node at the root level (must be 0), then down to its /// second child, then down to third child. pub fn layout_info_by_path(&self, path: &[usize]) -> Option { - assert_eq!(path[0], 0); let tree = self.0.widget_tree.borrow(); let mut node = tree.root(); - for (level, idx) in path[1..].iter().enumerate() { + for (level, idx) in path[..].iter().enumerate() { node = node.children(&tree.arena).nth(*idx).unwrap_or_else(|| { panic!("node no exist: {:?}", &path[0..level]); }); @@ -62,9 +61,6 @@ impl TestWindow { tree.store.layout_info(node).cloned() } - #[inline] - pub fn widget_count(&self) -> usize { self.widget_tree.borrow().count() } - pub fn take_last_frame(&mut self) -> Option { self .shell_wnd() @@ -76,6 +72,13 @@ impl TestWindow { .take() } + 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(); + widget_tree.count(content) + } + #[track_caller] pub fn draw_frame(&mut self) { // Test window not have a eventloop, manually wake-up every frame. diff --git a/core/src/widget_tree.rs b/core/src/widget_tree.rs index 02791d890..5de466ca7 100644 --- a/core/src/widget_tree.rs +++ b/core/src/widget_tree.rs @@ -10,10 +10,10 @@ pub mod widget_id; pub(crate) use widget_id::TreeArena; pub use widget_id::WidgetId; mod layout_info; -use crate::prelude::*; +use crate::{overlay::OverlayRoot, prelude::*}; pub use layout_info::*; -use self::widget_id::empty_node; +use self::widget::widget_id::new_node; pub(crate) type DirtySet = Rc>>; @@ -28,8 +28,15 @@ pub(crate) struct WidgetTree { impl WidgetTree { pub fn init(&mut self, wnd: Weak) { self.wnd = wnd; } - pub fn set_root(&mut self, root: WidgetId) { - self.root = root; + pub fn set_content(&mut self, content: WidgetId) { + // dispose the old content. + self + .root + .children(&self.arena) + .collect::>() + .into_iter() + .for_each(|id| id.dispose_subtree(self)); + self.root.append(content, &mut self.arena); self.mark_dirty(self.root); self.root.on_mounted_subtree(self); } @@ -72,7 +79,7 @@ impl WidgetTree { pub(crate) fn is_dirty(&self) -> bool { !self.dirty_set.borrow().is_empty() } - pub(crate) fn count(&self) -> usize { self.root().descendants(&self.arena).count() } + pub(crate) fn count(&self, wid: WidgetId) -> usize { wid.descendants(&self.arena).count() } pub(crate) fn window(&self) -> Rc { self @@ -206,8 +213,9 @@ impl WidgetTree { impl Default for WidgetTree { fn default() -> Self { let mut arena = TreeArena::new(); + Self { - root: empty_node(&mut arena), + root: new_node(&mut arena, Box::new(OverlayRoot {})), wnd: Weak::new(), arena, store: LayoutStore::default(), @@ -222,12 +230,17 @@ mod tests { use crate::{ reset_test_env, test_helper::{MockBox, MockMulti, TestWindow}, - widget::widget_id::empty_node, }; use super::*; use test::Bencher; + impl WidgetTree { + pub(crate) fn content_root(&self) -> WidgetId { self.root.first_child(&self.arena).unwrap() } + } + + fn empty_node(arena: &mut TreeArena) -> WidgetId { new_node(arena, Box::new(Void)) } + #[derive(Clone, Debug)] pub struct Recursive { pub width: usize, @@ -396,7 +409,7 @@ mod tests { let wnd = TestWindow::new(fn_widget!(post)); let mut tree = wnd.widget_tree.borrow_mut(); tree.layout(Size::new(512., 512.)); - assert_eq!(tree.count(), 16); + assert_eq!(tree.count(tree.content_root()), 16); let root = tree.root(); tree.mark_dirty(root); diff --git a/core/src/widget_tree/widget_id.rs b/core/src/widget_tree/widget_id.rs index 9535c0eec..e91be500b 100644 --- a/core/src/widget_tree/widget_id.rs +++ b/core/src/widget_tree/widget_id.rs @@ -4,7 +4,6 @@ use indextree::{Arena, Node, NodeId}; use super::WidgetTree; use crate::{ - builtin_widgets::Void, context::{PaintingCtx, WidgetCtx}, prelude::{AnonymousWrapper, DataWidget}, render_helper::RenderProxy, @@ -112,6 +111,15 @@ impl WidgetId { .for_each(|w| tree.window().add_delay_event(DelayEvent::Mounted(w))); } + /// Dispose the whole subtree of `id`, include `id` itself. + pub(crate) fn dispose_subtree(self, tree: &mut WidgetTree) { + let parent = self.parent(&tree.arena); + tree.detach(self); + tree + .window() + .add_delay_event(DelayEvent::Disposed { id: self, parent }); + } + pub(crate) fn insert_after(self, next: WidgetId, tree: &mut TreeArena) { self.0.insert_after(next.0, tree); } @@ -213,5 +221,3 @@ impl WidgetId { pub(crate) fn new_node(arena: &mut TreeArena, node: Box) -> WidgetId { WidgetId(arena.new_node(node)) } - -pub(crate) fn empty_node(arena: &mut TreeArena) -> WidgetId { new_node(arena, Box::new(Void)) } diff --git a/core/src/window.rs b/core/src/window.rs index 91129870d..d462fa4ec 100644 --- a/core/src/window.rs +++ b/core/src/window.rs @@ -287,7 +287,7 @@ impl Window { pub fn set_content_widget(&self, root: impl WidgetBuilder) { let build_ctx = BuildCtx::new(None, &self.widget_tree); let root = root.widget_build(&build_ctx); - self.widget_tree.borrow_mut().set_root(root.consume()) + self.widget_tree.borrow_mut().set_content(root.consume()) } #[inline] diff --git a/docs/en/get_started/try_it.md b/docs/en/get_started/try_it.md index acd55c7fe..ea3550d16 100644 --- a/docs/en/get_started/try_it.md +++ b/docs/en/get_started/try_it.md @@ -72,5 +72,5 @@ cargo run -p counter cargo run -p storybook cargo run -p messages cargo run -p todos -cargo run -p worle_game +cargo run -p wordle_game ``` \ No newline at end of file diff --git a/tests/rdl_macro_test.rs b/tests/rdl_macro_test.rs index 2fc69e497..f6b5ffcb1 100644 --- a/tests/rdl_macro_test.rs +++ b/tests/rdl_macro_test.rs @@ -691,7 +691,8 @@ fn fix_builtin_field_can_declare_as_widget() { }; let wnd = TestWindow::new(w); - assert_eq!(wnd.widget_count(), 2); + + assert_eq!(wnd.content_count(), 2); } #[test] @@ -705,7 +706,7 @@ fn fix_use_builtin_field_of_builtin_widget_gen_duplicate() { }; let wnd = TestWindow::new(w); - assert_eq!(wnd.widget_count(), 2); + assert_eq!(wnd.content_count(), 2); } #[test] diff --git a/widgets/src/layout.rs b/widgets/src/layout.rs index 9a5feca25..9a8aa5342 100644 --- a/widgets/src/layout.rs +++ b/widgets/src/layout.rs @@ -21,13 +21,11 @@ impl Direction { pub fn is_vertical(&self) -> bool { matches!(self, Direction::Vertical) } } -pub mod container; pub mod flex; mod sized_box; pub use flex::*; pub use sized_box::SizedBox; pub mod expanded; -pub use container::Container; pub use expanded::Expanded; mod stack; pub use stack::*; @@ -35,3 +33,4 @@ pub mod constrained_box; pub use constrained_box::ConstrainedBox; pub mod only_sized_by_parent; pub use only_sized_by_parent::OnlySizedByParent; +pub use ribir_core::builtin_widgets::container::Container;