diff --git a/CHANGELOG.md b/CHANGELOG.md index 1a274d1a..e9c786bf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -27,7 +27,7 @@ Please only add new entries below the [Unreleased](#unreleased---releasedate) he ### Features -- **core**: Support for modifying the theme at runtime. (#pr @M-Adoo) +- **core**: Support for modifying the theme at runtime. (#618 @M-Adoo) The code: @@ -53,7 +53,7 @@ Please only add new entries below the [Unreleased](#unreleased---releasedate) he App::run(w); ``` -- **core**: Added `Provider` widget to share data between sub-tree. (#pr @M-Adoo) +- **core**: Added `Provider` widget to share data between sub-tree. (#618 @M-Adoo) ```rust Provider::new(Box::new(State::value(0i32))).with_child(fn_widget! { @SizedBox { @@ -75,12 +75,14 @@ Please only add new entries below the [Unreleased](#unreleased---releasedate) he }); ``` -- **core**: Added `WidgetCtx::query`, `WidgetCtx::query_write`, `WidgetCtx::query_of_widget` and `WidgetCtx::query_write_of_widget`. (#pr @M-Adoo) +- **core**: Added `Overlay::of` to allow querying the overlay in event callbacks. (#618 @M-Adoo) +- **core**: Added `WidgetCtx::query`, `WidgetCtx::query_write`, `WidgetCtx::query_of_widget` and `WidgetCtx::query_write_of_widget`. (#618 @M-Adoo) ### Breaking +- **core**: Removed `Overlay::new_with_handle` and `OverlayCloseHandle`. (#618 @M-Adoo) - **core**: `GenWidget::gen_widget` no longer requires a `&mut BuildCtx` parameter. (#616 @M-Adoo) -- **core**: Removed `FullTheme` and `InheritTheme`, now only using `Theme`. Any part of the theme, such as `Palette`, can be directly used to overwrite its corresponding theme component. (#pr @M-Adoo) +- **core**: Removed `FullTheme` and `InheritTheme`, now only using `Theme`. Any part of the theme, such as `Palette`, can be directly used to overwrite its corresponding theme component. (#618 @M-Adoo) ## [0.4.0-alpha.5] - 2024-08-14 @@ -115,7 +117,7 @@ Please only add new entries below the [Unreleased](#unreleased---releasedate) he ### Breaking -- Removed `WidgetCtx::query_widget_type` and `WidgetCtx::query_type` (#pr @M-Adoo) +- Removed `WidgetCtx::query_widget_type` and `WidgetCtx::query_type` (#618 @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) diff --git a/core/src/builtin_widgets/global_anchor.rs b/core/src/builtin_widgets/global_anchor.rs index d551baa1..3c723e2e 100644 --- a/core/src/builtin_widgets/global_anchor.rs +++ b/core/src/builtin_widgets/global_anchor.rs @@ -86,6 +86,7 @@ impl FatObj { /// Anchor the widget's horizontal position by placing its left edge right to /// the left edge of the specified widget (`wid`) with the given relative /// pixel value (`relative`). + // Todo: Should we control the subscription in the inner part? pub fn left_align_to( &mut self, wid: &LazyWidgetId, offset: f32, ctx: &BuildCtx, ) -> impl Subscription { diff --git a/core/src/builtin_widgets/theme.rs b/core/src/builtin_widgets/theme.rs index 92eb32e3..427fef76 100644 --- a/core/src/builtin_widgets/theme.rs +++ b/core/src/builtin_widgets/theme.rs @@ -39,7 +39,7 @@ pub enum Brightness { /// /// Every descendant widget of the theme can query it or its parts. /// -/// ``` +/// ```no_run /// use ribir::prelude::*; /// /// let w = fn_widget! { diff --git a/core/src/context/widget_ctx.rs b/core/src/context/widget_ctx.rs index 872427b1..b07e608a 100644 --- a/core/src/context/widget_ctx.rs +++ b/core/src/context/widget_ctx.rs @@ -16,6 +16,10 @@ pub trait WidgetCtx { fn widget_id(&self) -> WidgetId; /// Return parent of widget of this context. fn parent(&self) -> Option; + // Determine if the current widget in the context is an ancestor of `w`. + fn ancestor_of(&self, w: WidgetId) -> bool; + // Determine if the current widget in the context is an successor of `w`. + fn successor_of(&self, w: WidgetId) -> bool; /// Return parent of widget `w`. fn widget_parent(&self, w: WidgetId) -> Option; /// Return the single child of `widget`. @@ -98,6 +102,12 @@ impl WidgetCtx for T { #[inline] fn parent(&self) -> Option { self.id().parent(self.tree()) } + #[inline] + fn ancestor_of(&self, w: WidgetId) -> bool { self.id().ancestor_of(w, self.tree()) } + + #[inline] + fn successor_of(&self, w: WidgetId) -> bool { w.ancestor_of(self.id(), self.tree()) } + #[inline] fn widget_parent(&self, w: WidgetId) -> Option { w.parent(self.tree()) } diff --git a/core/src/lib.rs b/core/src/lib.rs index ee6d1f93..38455206 100644 --- a/core/src/lib.rs +++ b/core/src/lib.rs @@ -46,7 +46,7 @@ pub mod prelude { #[doc(no_inline)] pub use crate::events::*; #[doc(no_inline)] - pub use crate::overlay::{Overlay, OverlayCloseHandle}; + pub use crate::overlay::Overlay; #[doc(no_inline)] pub use crate::pipe::{BoxPipe, FinalChain, MapPipe, ModifiesPipe, Pipe}; #[doc(no_inline)] diff --git a/core/src/overlay.rs b/core/src/overlay.rs index bbf3cffb..4da6cf3e 100644 --- a/core/src/overlay.rs +++ b/core/src/overlay.rs @@ -1,132 +1,106 @@ -use std::{cell::RefCell, mem::replace, rc::Rc}; +use std::{ + cell::RefCell, + rc::{Rc, Weak}, +}; use ribir_algo::Sc; use crate::prelude::*; +/// Overlay let independent the widget "float" visual elements on top of +/// other widgets by inserting them into the root stack of the widget stack. +/// +/// ### Example +/// +/// ```no_run +/// use ribir::prelude::*; +/// +/// let w = fn_widget! { +/// let overlay = Overlay::new(fn_widget! { +/// @Text { +/// on_tap: move |e| Overlay::of(&**e).unwrap().close(), +/// h_align: HAlign::Center, +/// v_align: VAlign::Center, +/// text: "Click me to close overlay!" +/// } +/// }); +/// @FilledButton{ +/// on_tap: move |e| overlay.show(e.window()), +/// @{ Label::new("Click me to show overlay") } +/// } +/// }; +/// App::run(w); +/// ``` #[derive(Clone)] -pub struct OverlayStyle { - pub close_policy: ClosePolicy, - pub mask_brush: Option, -} +pub struct Overlay(Sc>); bitflags! { #[derive(Clone, Copy)] - pub struct ClosePolicy: u8 { + pub struct AutoClosePolicy: u8 { const NONE = 0b0000; const ESC = 0b0001; const TAP_OUTSIDE = 0b0010; } } -impl CustomStyle for OverlayStyle { - fn default_style(_: &impl ProviderCtx) -> 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()), - } - } +struct InnerOverlay { + gen: GenWidget, + auto_close_policy: AutoClosePolicy, + mask: Option, + showing: Option, } -/// A handle to close the overlay -#[derive(Clone)] -pub struct OverlayCloseHandle(OverlayState); -impl OverlayCloseHandle { - pub fn close(&self) { self.0.close() } +struct ShowingInfo { + id: WidgetId, + wnd: Weak, + generator: GenWidget, } -type Builder = Box Widget<'static>>; -struct OverlayData { - builder: Builder, - style: Option, - state: OverlayState, -} - -// Todo: -// 1. The overlay should be regenerated if the window theme changes. -// 2. We don't need to use `new_with_handle`; we can utilize the provider to -// query the overlay to which the current widget belongs. - -#[derive(Clone)] -pub struct Overlay(Sc>); - 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::run(w); - /// ``` + /// Create overlay from a function widget that may call many times. pub fn new(gen: impl Into) -> Self { let gen = gen.into(); - Self::inner_new(Box::new(move |_| gen.gen_widget())) + Self(Sc::new(RefCell::new(InnerOverlay { + gen, + auto_close_policy: AutoClosePolicy::ESC | AutoClosePolicy::TAP_OUTSIDE, + mask: None, + showing: None, + }))) } - /// 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") } - /// } - /// }.into_widget() - /// } - /// ); - /// @FilledButton { - /// on_tap: move |e| overlay.show(e.window()), - /// @{ Label::new("Click to show overlay") } - /// } - /// }; - /// - /// App::run(w).with_size(Size::new(200., 200.)); - /// ``` - pub fn new_with_handle( - builder: impl FnMut(OverlayCloseHandle) -> Widget<'static> + 'static, - ) -> Self { - Self::inner_new(Box::new(builder)) + /// Return the overlay that the `ctx` belongs to if it is within an overlay. + pub fn of(ctx: &impl WidgetCtx) -> Option { + let wnd = ctx.window(); + let tree = wnd.tree(); + let overlays = tree + .root() + .query_ref::(tree) + .unwrap(); + + overlays.showing_of(ctx) + } + + /// Set the auto close policy of the overlay. + pub fn set_auto_close_policy(&self, policy: AutoClosePolicy) { + self.0.borrow_mut().auto_close_policy = policy } - /// 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.borrow_mut().style = Some(style); } + /// Get the auto close policy of the overlay. + pub fn auto_close_policy(&self) -> AutoClosePolicy { self.0.borrow().auto_close_policy } - /// the Overlay widget will be show at the top level of all widget. - /// if the overlay is showing, nothing will happen. + /// Set the mask for the background of the overlay being used. + pub fn set_mask(&self, mask: Brush) { self.0.borrow_mut().mask = Some(mask); } + + /// Get the mask of the the background of the overlay used. + pub fn mask(&self) -> Option { self.0.borrow().mask.clone() } + + /// Show the overlay. pub fn show(&self, wnd: Rc) { - if self.is_show() { + if self.is_showing() { return; } - let mut inner = self.0.borrow_mut(); - let handle = inner.state.close_handle(); - let w = (inner.builder)(handle); - let style = inner.style.clone(); - inner.state.show(w, style, wnd); + let gen = self.0.borrow().gen.clone(); + self.inner_show(gen, wnd); } /// User can make transform before the widget show at the top level of all @@ -150,16 +124,15 @@ impl Overlay { /// 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() + /// overlay.show_map(move |w| { + /// let wid = wid.clone(); + /// fn_widget! { + /// let mut w = @$w {}; + /// w.left_align_to(&wid, 0., ctx!()); + /// w + /// }.into_widget() + /// }, + /// e.window() /// ); /// }, /// @{ Label::new("Click to show overlay") } @@ -167,152 +140,141 @@ impl Overlay { /// }; /// App::run(w); /// ``` - pub fn show_map(&self, f: F, wnd: Rc) + pub fn show_map(&self, mut f: F, wnd: Rc) where - F: FnOnce(Widget<'static>, OverlayCloseHandle) -> O + 'static, - O: IntoWidget<'static, FN> + 'static, + F: FnMut(Widget<'static>) -> Widget + 'static, { - if self.is_show() { + if self.is_showing() { return; } - - let mut inner = self.0.borrow_mut(); - let close_handle = inner.state.close_handle(); - let overlay = (inner.builder)(close_handle.clone()); - let overlay = f(overlay, close_handle); - let style = inner.style.clone(); - inner - .state - .show(overlay.into_widget(), style, wnd); + let gen = self.0.borrow().gen.clone(); + let gen = move |_: &mut BuildCtx| f(gen.gen_widget()); + self.inner_show(gen.into(), 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() { + if self.is_showing() { return; } self.show_map( - move |w, _| { + move |w| { fn_widget! { @$w { anchor: Anchor::from_point(pos) } } + .into_widget() }, wnd, ); } - /// return whether the overlay is show. - pub fn is_show(&self) -> bool { self.0.borrow().state.is_show() } + /// return whether the overlay is showing. + pub fn is_showing(&self) -> bool { self.0.borrow().showing.is_some() } - /// remove the showing overlay. - pub fn close(&self) { self.0.borrow().state.close() } + /// Close the overlay; all widgets within the overlay will be removed. + pub fn close(&self) { + let showing = self.0.borrow_mut().showing.take(); + if let Some(showing) = showing { + let ShowingInfo { id, wnd, .. } = showing; + if let Some(wnd) = wnd.upgrade() { + let ctx = BuildCtx::create(wnd.tree().root(), wnd.tree); + let showing_overlays = Provider::of::(&*ctx).unwrap(); + showing_overlays.remove(self); - fn inner_new(builder: Builder) -> Self { - Self(Sc::new(RefCell::new(OverlayData { - builder, - style: None, - state: OverlayState::default(), - }))) - } -} - -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 tree = wnd.tree_mut(); let root = tree.root(); - wid.dispose_subtree(tree); + id.dispose_subtree(tree); tree.mark_dirty(root); - }); + } } } - fn is_show(&self) -> bool { !matches!(*self.0.borrow(), OverlayInnerState::Hided) } - - fn show(&self, w: Widget<'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 mut build_ctx = BuildCtx::create(wnd.tree().root(), wnd.tree); - let style = style.unwrap_or_else(|| OverlayStyle::of(build_ctx.deref())); - let wid = this - .wrap_style(w, style) - .into_widget() - .build(&mut build_ctx); - *this.0.borrow_mut() = OverlayInnerState::Showing(wid, wnd.clone()); - let tree = wnd.tree_mut(); - tree.root().append(wid, tree); - wid.on_mounted_subtree(tree); - tree.mark_dirty(wid); - }); - } - - 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; - let this2 = this.clone(); + fn inner_show(&self, content: GenWidget, wnd: Rc) { + let background = self.mask(); + let gen = fn_widget! { @Container { size: Size::new(f32::INFINITY, f32::INFINITY), - background: mask_brush.unwrap_or_else(|| Color::from_u32(0).into()), + background: background.clone(), on_tap: move |e| { - if close_policy.contains(ClosePolicy::TAP_OUTSIDE) - && e.target() == e.current_target() { - this.close(); + if e.target() == e.current_target() { + if let Some(overlay) = Overlay::of(&**e) + .filter(|o| o.auto_close_policy().contains(AutoClosePolicy::TAP_OUTSIDE)) + { + overlay.close(); + } } }, on_key_down: move |e| { - if close_policy.contains(ClosePolicy::ESC) - && *e.key() == VirtualKey::Named(NamedKey::Escape) { - this2.close(); + if *e.key() == VirtualKey::Named(NamedKey::Escape) { + if let Some(overlay) = Overlay::of(&**e) + .filter(|o| o.auto_close_policy().contains(AutoClosePolicy::ESC)) + { + overlay.close(); + } } }, - @ { w } + @ { content.gen_widget() } } - } + }; + + let mut ctx = BuildCtx::create(wnd.tree().root(), wnd.tree); + let id = gen(&mut ctx).build(&mut ctx); + self.0.borrow_mut().showing = + Some(ShowingInfo { id, generator: gen.into(), wnd: Rc::downgrade(&wnd) }); + + let showing_overlays = Provider::of::(&*ctx).unwrap(); + showing_overlays.add(self.clone()); + + let tree = wnd.tree_mut(); + tree.root().append(id, tree); + id.on_mounted_subtree(tree); + tree.mark_dirty(id); } - fn close_handle(&self) -> OverlayCloseHandle { OverlayCloseHandle(self.clone()) } + fn showing_root(&self) -> Option { self.0.borrow().showing.as_ref().map(|s| s.id) } } -pub(crate) struct OverlayRoot {} +pub(crate) struct ShowingOverlays(RefCell>); + +impl ShowingOverlays { + pub(crate) fn rebuild(&self, ctx: &mut BuildCtx) { + for o in self.0.borrow().iter() { + let mut o = o.0.borrow_mut(); + let ShowingInfo { id, generator, .. } = o.showing.as_mut().unwrap(); -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(); + id.dispose_subtree(ctx.tree_mut()); + *id = generator.gen_widget().build(ctx); + let tree = ctx.tree_mut(); + tree.root().append(*id, tree); + id.on_mounted_subtree(tree); } - size } - fn paint(&self, _: &mut PaintingCtx) {} + fn add(&self, overlay: Overlay) { + assert!(overlay.showing_root().is_some()); + self.0.borrow_mut().push(overlay) + } + + fn remove(&self, overlay: &Overlay) { + assert!(overlay.showing_root().is_none()); + self + .0 + .borrow_mut() + .retain(|o| !Sc::ptr_eq(&o.0, &overlay.0)) + } + + fn showing_of(&self, ctx: &impl WidgetCtx) -> Option { + self.0.borrow().iter().find_map(|o| { + o.showing_root() + .map_or(false, |w| ctx.successor_of(w)) + .then(|| o.clone()) + }) + } +} + +impl Default for ShowingOverlays { + fn default() -> Self { Self(RefCell::new(vec![])) } } #[cfg(test)] @@ -356,13 +318,11 @@ mod tests { let root = wnd.tree().root(); assert_eq!(wnd.tree().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 + // Root -> BoxDecoration-> Container -> Anchor assert_layout_result_by_path!(wnd, {path = [1, 0, 0, 0], x == 50., y == 30.,}); overlay.close(); diff --git a/core/src/widget.rs b/core/src/widget.rs index 42b15e67..08b516dd 100644 --- a/core/src/widget.rs +++ b/core/src/widget.rs @@ -96,6 +96,7 @@ impl<'w> LazyNode<'w> { /// A boxed function widget that can be called multiple times to regenerate /// widget. +#[derive(Clone)] pub struct GenWidget(InnerGenWidget); type InnerGenWidget = Sc Widget<'static>>>>; diff --git a/core/src/widget_tree.rs b/core/src/widget_tree.rs index 8f6cfcd3..6ffb4f23 100644 --- a/core/src/widget_tree.rs +++ b/core/src/widget_tree.rs @@ -14,7 +14,7 @@ mod layout_info; pub use layout_info::*; use self::widget::widget_id::new_node; -use crate::{overlay::OverlayRoot, prelude::*, render_helper::PureRender}; +use crate::{overlay::ShowingOverlays, prelude::*, render_helper::PureRender}; pub(crate) type DirtySet = Rc>>; @@ -36,16 +36,15 @@ impl WidgetTree { ctx.pre_alloc = Some(root_id); let theme = AppCtx::app_theme().clone_writer(); - let overlays = Queryable(Overlays::default()); + let overlays = Queryable(ShowingOverlays::default()); let id = Provider::new(Box::new(overlays)) .with_child(fn_widget! { theme.with_child(fn_widget!{ + let ctx = unsafe { &*(ctx!() as *mut BuildCtx) }; + let overlays = Provider::of::(ctx).unwrap(); + overlays.rebuild(ctx!()); @Root { @{ content.gen_widget() } - @{ - let overlays = Provider::of::(ctx!()).unwrap(); - overlays.rebuild() - } } }) }) @@ -93,6 +92,7 @@ impl WidgetTree { } } + // todo: split layout and paint dirty. pub(crate) fn mark_dirty(&self, id: WidgetId) { self.dirty_set.borrow_mut().insert(id); } pub(crate) fn is_dirty(&self) -> bool { !self.dirty_set.borrow().is_empty() } @@ -226,7 +226,7 @@ impl Default for WidgetTree { let mut arena = TreeArena::new(); Self { - root: new_node(&mut arena, Box::new(PureRender(OverlayRoot {}))), + root: new_node(&mut arena, Box::new(PureRender(Void))), wnd: Weak::new(), arena, store: LayoutStore::default(), @@ -254,19 +254,6 @@ impl Render for Root { fn paint(&self, _: &mut PaintingCtx) {} } -pub(crate) struct Overlays(Stateful>); - -impl Overlays { - fn rebuild(&self) -> Vec> { - // todo: drop the old overlay subtree, create a new one for new theme - vec![] - } -} - -impl Default for Overlays { - fn default() -> Self { Self(Stateful::new(vec![])) } -} - #[cfg(test)] mod tests { use super::*; diff --git a/ribir/src/app.rs b/ribir/src/app.rs index e5cb97bf..462f71a7 100644 --- a/ribir/src/app.rs +++ b/ribir/src/app.rs @@ -145,6 +145,10 @@ impl App { } } wnd.emit_events(); + + if wnd.need_draw() { + request_redraw(&wnd) + } } Event::AboutToWait => { let run_count = AppCtx::run_until_stalled();