diff --git a/CHANGELOG.md b/CHANGELOG.md index 5880011cb..9465d3b2d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -27,7 +27,8 @@ Please only add new entries below the [Unreleased](#unreleased---releasedate) he ### Features -- **macros**: Added the `part_reader!` macro to generate a partial reader from a reference of a reader. (#pr @M-Adoo) +- **macros**: Added the `part_reader!` macro to generate a partial reader from a reference of a reader. (#688 @M-Adoo) +- **macros**: The `simple_declare` now supports the `stateless` meta attribute, `#[simple_declare(stateless)]`. (#688 @M-Adoo) ## [0.4.0-alpha.22] - 2025-01-08 @@ -100,7 +101,6 @@ Please only add new entries below the [Unreleased](#unreleased---releasedate) he - **core**: Added the `named_svgs` module to enable sharing SVGs using string keys, replacing the need for `IconTheme`. (#658 @M-Adoo) - **core**: The `keyframes!` macro has been introduced to manage the intermediate steps of animation states. (#653 @M-Adoo) - **core**: Added `QueryId` as a replacement for `TypeId` to facilitate querying types by Provider across different binaries. (#656 @M-Adoo) -- **core**: Added `OverrideClass` to override a single class within a subtree. (#657 @M-Adoo) - **widgets**: Added `LinearProgress` and `SpinnerProgress` widgets along with their respective material themes. (#630 @wjian23 @M-Adoo) - **painter**: SVG now supports switching the default color, allowing for icon color changes. (#661 @M-Adoo) @@ -285,7 +285,12 @@ Please only add new entries below the [Unreleased](#unreleased---releasedate) he - **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! { + let state = Stateful::value(0132); + providers!{ + providers: smallvec![ + Provider::new(state.clone_writer()), + Provider::value_of_state(state) + ], @SizedBox { size: Size::new(1.,1.), on_tap: |e| { @@ -302,9 +307,8 @@ Please only add new entries below the [Unreleased](#unreleased---releasedate) he } } } - }); + } ``` - - **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) diff --git a/core/src/animation/stagger.rs b/core/src/animation/stagger.rs index 17d3cdb28..990a8d9fe 100644 --- a/core/src/animation/stagger.rs +++ b/core/src/animation/stagger.rs @@ -262,12 +262,7 @@ mod tests { let c_stagger = stagger.clone_writer(); let w = fn_widget! { let mut mock_box = @MockBox { size: Size::new(100., 100.) }; - $stagger.write().push_state( - mock_box - .get_opacity_widget() - .map_writer(|w| PartData::from_ref_mut(&mut w.opacity)), - 0., - ); + $stagger.write().push_state(part_writer!(&mut mock_box.opacity),0.); stagger.run(); mock_box diff --git a/core/src/builtin_widgets.rs b/core/src/builtin_widgets.rs index c847f829e..8ef34bb49 100644 --- a/core/src/builtin_widgets.rs +++ b/core/src/builtin_widgets.rs @@ -65,8 +65,6 @@ mod mix_builtin; pub use mix_builtin::*; pub mod container; pub use container::*; -mod provider; -pub use provider::*; mod class; pub use class::*; mod constrained_box; @@ -82,6 +80,8 @@ mod text; pub use text::*; mod tooltips; pub use tooltips::*; +mod providers; +pub use providers::*; use crate::prelude::*; @@ -409,7 +409,11 @@ impl FatObj { /// doesn't exist, a new one will be created. pub fn get_text_style_widget(&mut self) -> &State { self.text_style.get_or_insert_with(|| { - State::value(TextStyleWidget { text_style: BuildCtx::get().text_style().clone() }) + State::value(TextStyleWidget { + text_style: Provider::of::(BuildCtx::get()) + .unwrap() + .clone(), + }) }) } diff --git a/core/src/builtin_widgets/class.rs b/core/src/builtin_widgets/class.rs index 3fd019791..12d312c8a 100644 --- a/core/src/builtin_widgets/class.rs +++ b/core/src/builtin_widgets/class.rs @@ -31,22 +31,19 @@ //! App::run(w).with_app_theme(theme); //! ``` -use std::{ - cell::{RefCell, UnsafeCell}, - hash::{Hash, Hasher}, -}; +use std::hash::Hash; -use ribir_algo::Sc; +use data_widget::AnonymousAttacher; +use pipe::PipeNode; use smallvec::{SmallVec, smallvec}; -use widget_id::RenderQueryable; -use crate::{ - data_widget::AnonymousAttacher, - pipe::{DynInfo, DynWidgetsInfo, GenRange}, - prelude::*, - render_helper::{PureRender, RenderProxy}, - window::WindowId, -}; +use crate::{pipe::GenRange, prelude::*, window::WindowId}; + +/// A collection of class implementations that are part of the `Theme`. +#[derive(Default, Clone)] +pub struct Classes { + pub(crate) store: ahash::HashMap, +} /// The macro is used to create a class implementation by accepting declarations /// of the built-in widget fields. @@ -85,14 +82,6 @@ macro_rules! multi_class { /// A empty class implementation that returns the input widget as is. pub fn empty_cls(w: Widget) -> Widget { w } -/// A collection comprises the implementations of the `ClassName`, offering the -/// implementation of `Class` within its descendants. - -#[derive(Default, Clone)] -pub struct Classes { - pub(crate) store: ahash::HashMap, -} - /// This type is utilized to define a constant variable as the name of a /// `Class`. It can also override its implementation across the `Theme` and /// `Classes`. @@ -112,9 +101,12 @@ pub struct ClassName(&'static str); /// A function that transforms a `Widget` into another `Widget` as a class /// implementation. /// -/// The function accepts a `Widget` as input and returns a -/// `Widget` as output, ensuring that the input widget is retained in the -/// returned widget. Otherwise, switching the class to another class will fail. +/// The function accepts a `Widget` as input and returns a `Widget` as output, +/// ensuring that the input widget is retained in the returned widget. +/// Otherwise, switching the class to another class will fail. +// Note: The provider of `Class` can be tricky, so changing the definition of +// ClassImpl without being careful may result in compatibility issues with the +// previous version's binary. See `[`ClassName::type_info`]`. pub type ClassImpl = fn(Widget) -> Widget; /// This widget is used to apply class to its child widget by the `ClassName`. @@ -123,16 +115,6 @@ pub struct Class { pub class: Option, } -/// This widget overrides the class implementation of a `ClassName`, offering a -/// lighter alternative to `Classes` when you only need to override a single -/// class. -#[simple_declare] -#[derive(Eq)] -pub struct OverrideClass { - pub name: ClassName, - pub class_impl: ClassImpl, -} - /// This macro is utilized to define class names; ensure that your name is /// unique within the application. #[macro_export] @@ -151,6 +133,22 @@ macro_rules! class_names { impl ClassName { pub const fn new(name: &'static str) -> Self { ClassName(name) } + + fn type_info(&self) -> TypeInfo { + const LAYOUT: std::alloc::Layout = std::alloc::Layout::new::(); + // Tricky: We disregard the package version since the type remains stable. + // Instead, we include the class name in the type information, allowing each + // unique class name to serve as a distinct provider. + TypeInfo { name: std::any::type_name::(), pkg_version: self.0, layout: &LAYOUT } + } + + fn from_info(info: &TypeInfo) -> Option { + if info.name == std::any::type_name::() { + Some(Self(info.pkg_version)) + } else { + None + } + } } impl Classes { @@ -163,38 +161,59 @@ impl Classes { pub fn insert(&mut self, cls: ClassName, f: ClassImpl) -> Option { self.store.insert(cls, f) } + + pub(crate) fn reader_into_provider + Query>(this: R) -> Provider { + Provider::Setup(Box::new(ClassesReaderSetup(this))) + } + + fn remove_intersects_class(&self, map: &mut ProviderCtx) -> Vec<(TypeInfo, Box)> { + map.remove_key_value_if(|info| { + ClassName::from_info(info).is_some_and(|name| self.store.contains_key(&name)) + }) + } } -impl Declare for Class { - type Builder = FatObj<()>; - #[inline] - fn declarer() -> Self::Builder { FatObj::new(()) } +struct ClassesReaderSetup(T); + +struct ClassesRestore { + overrides: Vec<(TypeInfo, Box)>, + classes: Box, } -impl ComposeChild<'static> for Classes { - type Child = GenWidget; - fn compose_child(this: impl StateWriter, child: Self::Child) -> Widget<'static> { - Provider::new(Box::new(this.clone_writer())) - .with_child(fn_widget! { pipe!($this;).map(move |_| child.gen_widget())}) - .into_widget() +impl ProviderSetup for Classes { + fn setup(self: Box, map: &mut ProviderCtx) -> Box { + let overrides = self.remove_intersects_class(map); + let classes = Box::new(Setup::new(*self)).setup(map); + Box::new(ClassesRestore { overrides, classes }) } } -impl<'c> ComposeChild<'c> for OverrideClass { - /// Since the class is lazily applied, there is no need for the child of - /// `OverrideClass` to be a `FnWidget`. - type Child = Widget<'c>; - fn compose_child(this: impl StateWriter, child: Self::Child) -> Widget<'c> { - let cls_override = this.try_into_value().unwrap_or_else(|_| { - panic!("Attempting to use `OverrideClass` as a reader or writer is not allowed.") - }); +impl + Query> ProviderSetup for ClassesReaderSetup { + fn setup(self: Box, map: &mut ProviderCtx) -> Box { + let classes = self.0; + let overrides = classes.read().remove_intersects_class(map); + let classes = Box::new(Setup::from_state(classes)).setup(map); + Box::new(ClassesRestore { overrides, classes }) + } +} - Provider::new(Box::new(Queryable(cls_override))) - .with_child(fn_widget! { child }) - .into_widget() +impl ProviderRestore for ClassesRestore { + fn restore(self: Box, map: &mut ProviderCtx) -> Box { + let setup = self.classes.restore(map); + for (info, provider) in self.overrides { + let _old = map.set_raw_provider(info, provider); + debug_assert!(_old.is_none()); + } + setup } } +impl Declare for Class { + type Builder = FatObj<()>; + #[inline] + fn declarer() -> Self::Builder { FatObj::new(()) } +} + impl<'c> ComposeChild<'c> for Class { type Child = Widget<'c>; @@ -203,27 +222,27 @@ impl<'c> ComposeChild<'c> for Class { Ok(c) => c.apply_style(child), Err(this) => { let this2 = this.clone_watcher(); - let cls_child = ClassNode::dummy(BuildCtx::get().tree().dummy_id()); + let cls_child = ClassNode::empty_node(); // Reapply the class when it is updated. let cls_child2 = cls_child.clone(); let child = child.on_build(move |orig_id| { - let tree = BuildCtx::get_mut().tree_mut(); - let mut orig_child = ClassNode::from_id(orig_id, tree); + let orig_child = ClassNode::empty_node(); + orig_child.init_for_single(orig_id); let orig_child2 = orig_child.clone(); - let wnd_id = tree.window().id(); + let wnd_id = BuildCtx::get().window().id(); let u = this2 .raw_modifies() .filter(|s| s.contains(ModifyScope::FRAMEWORK)) .sample(AppCtx::frame_ticks().clone()) - .subscribe(move |_| cls_child2.update(&orig_child2, &this2.read(), wnd_id)) + .subscribe(move |_| class_update(&cls_child2, &orig_child2, &this2.read(), wnd_id)) .unsubscribe_when_dropped(); - orig_child.attach_subscription(u); + orig_child.combine_subscription_guard(u); }); this .read() .apply_style(child) - .on_build(move |child_id| cls_child.init_from_id(child_id)) + .on_build(move |child_id| cls_child.init_for_single(child_id)) } }; FnWidget::new(f).into_widget() @@ -231,249 +250,157 @@ impl<'c> ComposeChild<'c> for Class { } impl Class { - pub fn apply_style<'a>(&self, w: Widget<'a>) -> Widget<'a> { + /// Creating a provider for a class, which can either provide the + /// implementation of a class or be used to override the implementation of a + /// class. + /// + /// ## Example + /// + /// ``` + /// use ribir_core::prelude::*; + /// + /// class_names!(RED_BOX); + /// let w = providers! { + /// providers: [ + /// Class::provider(RED_BOX, style_class!{ + /// background: Color::RED, + /// clamp: BoxClamp::fixed_size(Size::splat(48.)) + /// }), + /// ], + /// @Void { class: RED_BOX } + /// }; + /// ``` + pub fn provider(name: ClassName, cls_impl: ClassImpl) -> Provider { + Provider::custom(name.type_info(), Box::new(Queryable(cls_impl))) + } + + fn apply_style<'a>(&self, w: Widget<'a>) -> Widget<'a> { if let Some(cls_impl) = self.class_impl() { cls_impl(w) } else { w } } fn class_impl(&self) -> Option { let cls = self.class?; - let override_cls_id = QueryId::of::(); - let classes_id = QueryId::of::(); - - let (id, handle) = BuildCtx::get().all_providers().find_map(|p| { - p.query_match(&[override_cls_id, classes_id], &|id, h| { - if id == &override_cls_id { - h.downcast_ref::() - .is_some_and(|c| c.name == cls) - } else { - h.downcast_ref::() - .is_some_and(|c| c.store.contains_key(&cls)) - } - }) - })?; - - if id == override_cls_id { - handle - .into_ref::() - .map(|cls| cls.class_impl) - } else { - let classes = handle.into_ref::()?; - classes.store.get(&cls).cloned() - } + let ctx = BuildCtx::get(); + let override_cls = ctx + .as_ref() + .get_raw_provider(&cls.type_info()) + .and_then(|q| q.query(&QueryId::of::())) + .and_then(QueryHandle::into_ref::) + .map(|i| *i); + + override_cls.or_else(|| { + Provider::of::(ctx)? + .store + .get(&cls) + .copied() + }) } } -#[derive(Clone)] -struct ClassNode(Sc>); - -struct InnerClassNode { - render: Box, - id_info: DynInfo, -} +type ClassNode = PipeNode; impl ClassNode { - fn dummy(id: WidgetId) -> Self { - let inner = InnerClassNode { - render: Box::new(PureRender(Void)), - id_info: Sc::new(RefCell::new(DynWidgetsInfo { - multi_pos: 0, - gen_range: GenRange::Single(id), - })), - }; - Self(Sc::new(UnsafeCell::new(inner))) - } - - fn from_id(id: WidgetId, tree: &mut WidgetTree) -> Self { - let mut orig = None; - id.wrap_node(tree, |node| { - let c = ClassNode(Sc::new(UnsafeCell::new(InnerClassNode { - render: node, - id_info: Sc::new(RefCell::new(DynWidgetsInfo { - multi_pos: 0, - gen_range: GenRange::Single(id), - })), - }))); - orig = Some(c.clone()); - Box::new(c) - }); - - orig.unwrap() - } - - fn init_from_id(&self, id: WidgetId) { - let inner = self.inner(); - inner.id_info.borrow_mut().gen_range = GenRange::Single(id); - id.wrap_node(BuildCtx::get_mut().tree_mut(), |node| { - inner.render = node; - - Box::new(self.clone()) - }); - } - - fn update(&self, orig: &ClassNode, class: &Class, wnd_id: WindowId) { - let wnd = - AppCtx::get_window(wnd_id).expect("This handle is not valid because the window is closed"); - let child_id = self.id(); - let orig_id = orig.id(); - let InnerClassNode { render: child, id_info } = self.inner(); - let _guard = BuildCtx::init_for(child_id, wnd.tree); - let ctx = BuildCtx::get_mut(); - let n_orig = ctx.alloc(Box::new(orig.clone())); - let cls_holder = child_id.place_holder(ctx.tree_mut()); - - // Extract the child from this node, retaining only the external information - // linked from the parent to create a clean context for applying the class. - let child_node = self.take_inner(); - let mut new_id = ctx.build(class.apply_style(Widget::from_id(n_orig))); - - let tree = ctx.tree_mut(); - // Place the inner child node within the old ID for disposal, then utilize the - // class node to wrap the new child in the new ID. - // This action should be taken before modifying the `orig_id`, as the `orig_id` - // may be the same as the `child_id`. - let class_node = std::mem::replace(child_id.get_node_mut(tree).unwrap(), child_node); - - // Retain the original widget ID. - let [new, old] = tree.get_many_mut(&[n_orig, orig_id]); - // The "new" node is transferred to the "old" node, and the tracking ID within - // it needs to be updated. - new.update_track_id(orig_id); - std::mem::swap(new, old); - if new_id == n_orig { - // If applying the class does not generate additional widgets, the original - // widget ID will include all new elements after the swap. - new_id = orig_id; - } else { - n_orig.insert_after(orig_id, tree); - } - tree.remove_subtree(n_orig); - - if child_id != new_id { - // update the DynamicWidgetId out of the class node when id changed. - class_node.update_track_id(new_id); - } - - new_id.wrap_node(tree, |node| { - *child = node; - class_node - }); - - if new_id != child_id { - // If a pipe widget generates a widget with a class, we place the pipe node - // outside of the class node. However, since its widget ID is altered, we must - // notify the pipe node accordingly. - let old_rg = child_id..=orig_id; - let new_rg = new_id..=orig_id; - new_id - .query_all_iter::(tree) - .rev() - .for_each(|info| { - info - .borrow_mut() - .single_range_replace(&old_rg, &new_rg) - }); - cls_holder.replace(new_id, tree); - } - - if orig_id != child_id { - child_id.dispose_subtree(tree); - } - - let mut stack: SmallVec<[WidgetId; 1]> = smallvec![new_id]; - while let Some(w) = stack.pop() { - // Skip the original child subtree as it does not consist of new widgets. - if w != child_id { - w.on_mounted_subtree(tree); - stack.extend(w.children(tree).rev()); - } - } - - id_info.borrow_mut().gen_range = GenRange::Single(new_id); - let marker = tree.dirty_marker(); - marker.mark(new_id); - if new_id != orig_id && new_id.ancestor_of(orig_id, tree) { - marker.mark(orig_id); - } - } - - #[allow(clippy::mut_from_ref)] - fn inner(&self) -> &mut InnerClassNode { unsafe { &mut *self.0.get() } } - - fn take_inner(&self) -> Box { - std::mem::replace(&mut self.inner().render, Box::new(PureRender(Void))) - } - - fn attach_subscription(&mut self, guard: impl Any) { - let inner = &mut self.inner().render; + fn combine_subscription_guard(&self, guard: impl Any) { + let inner = self.host_render(); let child = unsafe { Box::from_raw(inner.as_mut()) }; let child = Box::new(AnonymousAttacher::new(child, Box::new(guard))); let tmp = std::mem::replace(inner, child); std::mem::forget(tmp); } - - fn id(&self) -> WidgetId { self.inner().id_info.borrow().host_id() } -} - -impl RenderProxy for ClassNode { - fn proxy(&self) -> impl Deref { self.inner().render.as_ref() } } -impl Query for ClassNode { - fn query_all<'q>( - &'q self, query_id: &QueryId, out: &mut smallvec::SmallVec<[QueryHandle<'q>; 1]>, - ) { - let inner = self.inner(); - inner.render.query_all(query_id, out); - if query_id == &QueryId::of::() { - out.push(QueryHandle::new(&inner.id_info)); - } +fn class_update(node: &ClassNode, orig: &ClassNode, class: &Class, wnd_id: WindowId) { + let wnd = + AppCtx::get_window(wnd_id).expect("This handle is not valid because the window is closed"); + + let child_id = node.dyn_info().host_id(); + let orig_id = orig.dyn_info().host_id(); + let n_orig = wnd.tree_mut().alloc_node(Box::new(orig.clone())); + let cls_holder = child_id.place_holder(wnd.tree_mut()); + + // Extract the child from this node, retaining only the external information + // linked from the parent to create a clean context for applying the class. + let child_node = node.take_data(); + + let _guard = BuildCtx::init_for(child_id, wnd.tree); + let ctx = BuildCtx::get_mut(); + let mut new_id = ctx.build(class.apply_style(Widget::from_id(n_orig))); + + let tree = ctx.tree_mut(); + // Place the inner child node within the old ID for disposal, then utilize the + // class node to wrap the new child in the new ID. + // This action should be taken before modifying the `orig_id`, as the `orig_id` + // may be the same as the `child_id`. + let class_node = std::mem::replace(child_id.get_node_mut(tree).unwrap(), child_node); + + // Retain the original widget ID. + let [new, old] = tree.get_many_mut(&[n_orig, orig_id]); + // The "new" node is transferred to the "old" node, and the tracking ID within + // it needs to be updated. + new.update_track_id(orig_id); + std::mem::swap(new, old); + if new_id == n_orig { + // If applying the class does not generate additional widgets, the original + // widget ID will include all new elements after the swap. + new_id = orig_id; + } else { + n_orig.insert_after(orig_id, tree); } + tree.remove_subtree(n_orig); - fn query_all_write<'q>(&'q self, query_id: &QueryId, out: &mut SmallVec<[QueryHandle<'q>; 1]>) { - self.inner().render.query_all_write(query_id, out) + if child_id != new_id { + // update the DynamicWidgetId out of the class node when id changed. + class_node.update_track_id(new_id); } - fn query(&self, query_id: &QueryId) -> Option { - let inner = self.inner(); - if query_id == &QueryId::of::() { - Some(QueryHandle::new(&inner.id_info)) - } else { - inner.render.query(query_id) - } + new_id.wrap_node(tree, |render| { + node.replace_data(render); + class_node + }); + + if new_id != child_id { + // If a pipe widget generates a widget with a class, we place the pipe node + // outside of the class node. However, since its widget ID is altered, we must + // notify the pipe node accordingly. + let old_rg = child_id..=orig_id; + let new_rg = new_id..=orig_id; + new_id + .query_all_iter::(tree) + .for_each(|node| { + node + .dyn_info_mut() + .single_range_replace(&old_rg, &new_rg) + }); + cls_holder.replace(new_id, tree); } - fn query_match( - &self, ids: &[QueryId], filter: &dyn Fn(&QueryId, &QueryHandle) -> bool, - ) -> Option<(QueryId, QueryHandle)> { - let inner = self.inner(); - inner.render.query_match(ids, filter).or_else(|| { - let dyn_info_id = QueryId::of::(); - (ids.contains(&dyn_info_id)) - .then(|| { - let h = QueryHandle::new(&inner.id_info); - filter(&dyn_info_id, &h).then_some((dyn_info_id, h)) - }) - .flatten() - }) + if orig_id != child_id { + child_id.dispose_subtree(tree); } - fn query_write(&self, type_id: &QueryId) -> Option { - self.inner().render.query_write(type_id) + let mut stack: SmallVec<[WidgetId; 1]> = smallvec![new_id]; + while let Some(w) = stack.pop() { + // Skip the original child subtree as it does not consist of new widgets. + if w != child_id { + w.on_mounted_subtree(tree); + stack.extend(w.children(tree).rev()); + } } -} -impl PartialEq for OverrideClass { - fn eq(&self, other: &Self) -> bool { self.name == other.name } + node.dyn_info_mut().gen_range = GenRange::Single(new_id); + let marker = tree.dirty_marker(); + marker.mark(new_id); + if new_id != orig_id && new_id.ancestor_of(orig_id, tree) { + marker.mark(orig_id); + } } -impl Hash for OverrideClass { - fn hash(&self, state: &mut H) { self.name.hash(state); } -} #[cfg(test)] mod tests { use super::*; use crate::{reset_test_env, test_helper::*}; class_names!(MARGIN, BOX_200, CLAMP_50, EMPTY); + use smallvec::smallvec; fn initd_classes() -> Classes { let mut classes = Classes::default(); @@ -493,6 +420,10 @@ mod tests { classes } + impl Classes { + fn into_provider(self) -> Provider { Provider::Setup(Box::new(Setup::new(self))) } + } + #[test] fn switch_class() { reset_test_env!(); @@ -500,12 +431,13 @@ mod tests { let (cls, w_cls) = split_value(MARGIN); let mut wnd = TestWindow::new(fn_widget! { let cls = cls.clone_watcher(); - initd_classes().with_child(fn_widget! { + @Providers { + providers: smallvec![initd_classes().into_provider()], @Container { size: Size::new(100., 100.), class: pipe!(*$cls), } - }) + } }); wnd.draw_frame(); @@ -542,12 +474,13 @@ mod tests { } .into_widget() }); - classes.with_child(fn_widget! { + @Providers { + providers: smallvec![classes.into_provider()], @Container { size: Size::new(100., 100.), class: pipe!(*$cls), } - }) + } }); wnd.draw_frame(); @@ -560,9 +493,7 @@ mod tests { reset_test_env!(); class_names!(MULTI); - let (cls, w_cls) = split_value(MARGIN); - let mut wnd = TestWindow::new(fn_widget! { let cls = cls.clone_watcher(); let mut classes = initd_classes(); @@ -576,12 +507,13 @@ mod tests { } .into_widget() }); - classes.with_child(fn_widget! { + @Providers { + providers: smallvec![classes.into_provider()], @Container { size: Size::new(100., 100.), class: pipe!(*$cls), } - }) + } }); wnd.draw_frame(); @@ -603,19 +535,20 @@ mod tests { let trigger = Stateful::new(true); let mut classes = Classes::default(); classes.insert(PROVIDER_CLS, |w| { - Provider::new(Box::new(Queryable(0))) + Providers::new([Provider::new(0i32)]) .with_child(fn_widget! { w }) - .into_widget() }); - classes.with_child(fn_widget! { + + @Providers { + providers: smallvec![classes.into_provider()], @Container { size: Size::new(100., 100.), class: pipe!($trigger; PROVIDER_CLS), on_performed_layout: |e| { - panic!("{}", *e.query::().unwrap()); + panic!("{}", *Provider::of::(e).unwrap()); } } - }) + } }); wnd.draw_frame(); } @@ -627,12 +560,13 @@ mod tests { let (cls, w_cls) = split_value(EMPTY); let mut wnd = TestWindow::new(fn_widget! { let cls = cls.clone_watcher(); - initd_classes().with_child(fn_widget! { + @Providers { + providers: smallvec![initd_classes().into_provider()], @Container { size: Size::new(100., 100.), class: pipe!(*$cls), } - }) + } }); wnd.draw_frame(); @@ -650,12 +584,13 @@ mod tests { let (cls, w_cls) = split_value(EMPTY); let mut wnd = TestWindow::new(fn_widget! { let cls = cls.clone_watcher(); - initd_classes().with_child(fn_widget! { + @Providers { + providers: smallvec![initd_classes().into_provider()], @Container { size: Size::new(100., 100.), class: pipe!(*$cls), } - }) + } }); wnd.draw_frame(); @@ -671,18 +606,18 @@ mod tests { reset_test_env!(); let mut wnd = TestWindow::new(fn_widget! { - initd_classes().with_child(fn_widget! { - @OverrideClass { - name: MARGIN, - class_impl: style_class! { + @Providers { + providers: smallvec![ + initd_classes().into_provider(), + Class::provider(MARGIN, style_class!{ clamp: BoxClamp::fixed_size(Size::new(66., 66.)) - } as ClassImpl, - @Container { - size: Size::new(100., 100.), - class: MARGIN, - } + }) + ], + @Container { + size: Size::new(100., 100.), + class: MARGIN, } - }) + } }); wnd.draw_frame(); @@ -699,13 +634,18 @@ mod tests { let mut wnd = TestWindow::new(fn_widget! { let w_trigger = w_trigger.clone_watcher(); let cls = cls.clone_watcher(); - initd_classes().with_child(fn_widget! { - let w = pipe!(*$w_trigger).map(|_|{ - @Container {size: Size::new(100., 100.) } - }); - let w = FatObj::new(w); - @ $w { class: pipe!(*$cls) } - }) + @Providers { + providers: smallvec![initd_classes().into_provider()], + @ { + let w = pipe!(*$w_trigger).map(|_|{ + @Container {size: Size::new(100., 100.) } + }); + @Class { + class: pipe!(*$cls), + @ { w } + } + } + } }); wnd.draw_frame(); @@ -742,12 +682,13 @@ mod tests { let mut wnd = TestWindow::new(fn_widget! { let cls = cls.clone_watcher(); - classes.clone().with_child(fn_widget! { + @Providers { + providers: smallvec![classes.clone().into_provider()], @Container { size: Size::new(100., 100.), class: pipe!(*$cls), } - }) + } }); wnd.draw_frame(); diff --git a/core/src/builtin_widgets/ignore_pointer.rs b/core/src/builtin_widgets/ignore_pointer.rs index 501fee488..341b72d67 100644 --- a/core/src/builtin_widgets/ignore_pointer.rs +++ b/core/src/builtin_widgets/ignore_pointer.rs @@ -14,7 +14,7 @@ impl WrapRender for IgnorePointer { host.perform_layout(clamp, ctx) } - fn hit_test(&self, host: &dyn Render, ctx: &HitTestCtx, pos: Point) -> HitTest { + fn hit_test(&self, host: &dyn Render, ctx: &mut HitTestCtx, pos: Point) -> HitTest { if self.ignore { HitTest { hit: false, can_hit_child: false } } else { host.hit_test(ctx, pos) } } } diff --git a/core/src/builtin_widgets/painting_style.rs b/core/src/builtin_widgets/painting_style.rs index ef0d9f502..eb85df776 100644 --- a/core/src/builtin_widgets/painting_style.rs +++ b/core/src/builtin_widgets/painting_style.rs @@ -1,4 +1,4 @@ -use crate::{prelude::*, wrap_render::*}; +use crate::prelude::*; /// A widget that sets the strategies for painting shapes and paths . It's can /// be inherited by its descendants. @@ -18,36 +18,16 @@ impl<'c> ComposeChild<'c> for PaintingStyleWidget { fn compose_child(this: impl StateWriter, child: Self::Child) -> Widget<'c> { // We need to provide the text style for the children to access. - match this.try_into_value() { - Ok(this) => { - let style = this.painting_style.clone(); - WrapRender::combine_child(State::value(this), child).attach_data(Box::new(Queryable(style))) - } + let provider = match this.try_into_value() { + Ok(this) => Provider::new(this.painting_style), Err(this) => { let style = this.map_reader(|w| PartData::from_ref(&w.painting_style)); - WrapRender::combine_child(this, child).attach_data(Box::new(style)) + Provider::value_of_state(style) } - } - } -} - -impl WrapRender for PaintingStyleWidget { - fn perform_layout(&self, clamp: BoxClamp, host: &dyn Render, ctx: &mut LayoutCtx) -> Size { - let old = ctx.set_painting_style(self.painting_style.clone()); - let size = host.perform_layout(clamp, ctx); - ctx.set_painting_style(old); - size - } - - fn paint(&self, host: &dyn Render, ctx: &mut PaintingCtx) { - match &self.painting_style { - PaintingStyle::Fill => ctx.painter().set_style(PathStyle::Fill), - PaintingStyle::Stroke(stroke_options) => ctx - .painter() - .set_strokes(stroke_options.clone()) - .set_style(PathStyle::Stroke), }; - host.paint(ctx) + Providers::new([provider]) + .with_child(child) + .into_widget() } } diff --git a/core/src/builtin_widgets/provider.rs b/core/src/builtin_widgets/provider.rs deleted file mode 100644 index a597656d5..000000000 --- a/core/src/builtin_widgets/provider.rs +++ /dev/null @@ -1,319 +0,0 @@ -use smallvec::SmallVec; - -use crate::prelude::*; - -/// This widget enables its descendants to access the data it provides, -/// streamlining data sharing throughout the widget tree. -/// -/// Descendants have the ability to inquire about the type of data provided by -/// their ancestors. If the ancestor is a writer, descendants can also access -/// the write reference (`WriteRef`) for that data. -/// -/// Its child must be a function widget, which enforces its subtree to utilize -/// the build context it provides for construction. -/// -/// Data querying occurs from the bottom to the top of the widget tree. In cases -/// where there are two providers of the same type in one path, the closer -/// provider will be queried. -/// -/// The system theme should serve as a global provider by default. -/// -/// You can utilize the provider with `BuildCtx`, the event object, `LayoutCtx`, -/// and `PaintCtx`. -/// -/// ## Example -/// -/// Any type can be wrapped with `Queryable` for providing data. -/// -/// ```rust -/// use ribir::prelude::*; -/// -/// Provider::new(Box::new(Queryable(1i32))) -/// // Provider only accepts function widgets as its child. -/// .with_child(fn_widget! { -/// let value = Provider::of::(BuildCtx::get()).unwrap(); -/// assert_eq!(*value, 1); -/// -/// let value = Provider::write_of::(BuildCtx::get()); -/// // We not share a writer. -/// assert!(value.is_none()); -/// @Text { text: "Good!" } -/// }); -/// ``` -/// -/// You can provide a state reader or writer without the `Queryable` wrapper. If -/// you provide a writer, you can access its write reference to modify it. -/// -/// ```rust -/// use ribir::prelude::*; -/// -/// Provider::new(Box::new(Stateful::new(0i32))).with_child(fn_widget! { -/// // we can query the type of the data. -/// let ctx = BuildCtx::get(); -/// { -/// let cnt = Provider::of::>(ctx).unwrap(); -/// assert_eq!(*cnt.read(), 0); -/// } -/// -/// // the write ref of the value -/// { -/// let mut cnt: WriteRef = Provider::write_of::(ctx).unwrap(); -/// assert_eq!(*cnt, 0); -/// *cnt = 1; -/// } -/// -/// // The value type of the state. -/// let cnt = Provider::of::(ctx).unwrap(); -/// assert_eq!(*cnt, 1); -/// @Text { text: "Good!" } -/// }); -/// ``` -#[simple_declare] -pub struct Provider { - pub provider: Box, -} - -/// Macro use to create a `Provider` that provides many data. -/// -/// ``` -/// use ribir::prelude::*; -/// -/// providers![State::value(0), Queryable("Hello!")].with_child(fn_widget! { -/// let hi = Provider::of::<&'static str>(BuildCtx::get()).unwrap(); -/// @Text { text: *hi } -/// }); -/// ``` -#[macro_export] -macro_rules! providers { - ($($q: expr),*) => { - Provider::new(Box::new([$(Box::new($q) as Box),*])) - }; -} - -impl Provider { - /// Create a Provider - #[inline] - pub fn new(provider: Box) -> Self { Provider { provider } } - - /// Query a reference of type `T` if it was provided by the ancestors. - #[inline] - pub fn of(ctx: &impl ProviderCtx) -> Option> { ctx.of() } - - /// Query a write reference of type `T` if the ancestor provided a writer of - /// type `T`. - #[inline] - pub fn write_of(ctx: &impl ProviderCtx) -> Option> { ctx.write_of() } -} - -impl<'c> ComposeChild<'c> for Provider { - type Child = FnWidget<'c>; - fn compose_child(this: impl StateWriter, child: Self::Child) -> Widget<'c> { - let provider = this - .try_into_value() - .unwrap_or_else(|_| { - panic!( - "Provider should not be treated as a shared object to be held onto; instead, utilize \ - `Provider::xxx_of` to access its content." - ) - }) - .provider; - - Widget::from_fn(move |ctx| ctx.build_with_provider(child.into_widget(), provider)) - } -} - -/// The context allows `Provider` to access shared data. It is implemented for -/// `BuildCtx` and other widget contexts such as `LayoutCtx`, `PaintCtx`, and -/// event objects. -pub trait ProviderCtx { - fn all_of(&self) -> impl Iterator> { - self.all_providers().flat_map(|p| { - let mut out = SmallVec::new(); - p.query_all(&QueryId::of::(), &mut out); - out.into_iter().filter_map(QueryHandle::into_ref) - }) - } - - fn all_write_of(&self) -> impl Iterator> { - self.all_providers().flat_map(|p| { - let mut out = SmallVec::new(); - p.query_all_write(&QueryId::of::(), &mut out); - out.into_iter().filter_map(QueryHandle::into_mut) - }) - } - - fn of(&self) -> Option> { self.all_of().next() } - - fn write_of(&self) -> Option> { self.all_write_of().next() } - - fn all_providers(&self) -> impl Iterator; -} - -impl ProviderCtx for BuildCtx { - fn all_providers(&self) -> impl Iterator { - self - .current_providers - .iter() - .rev() - .map(|q| &**q) - .chain(self.providers.iter().rev().filter_map(|id| { - let r = id.assert_get(self.tree()); - r.queryable().then(|| r.as_query()) - })) - } -} - -impl> ProviderCtx for T { - fn all_providers(&self) -> impl Iterator { - self.id().ancestors(self.tree()).filter_map(|id| { - let r = id.assert_get(self.tree()); - r.queryable().then(|| r.as_query()) - }) - } -} - -impl Query for [Box; M] { - fn query_all<'q>(&'q self, query_id: &QueryId, out: &mut SmallVec<[QueryHandle<'q>; 1]>) { - self - .iter() - .for_each(|q| q.query_all(query_id, out)) - } - - fn query_all_write<'q>(&'q self, query_id: &QueryId, out: &mut SmallVec<[QueryHandle<'q>; 1]>) { - self - .iter() - .for_each(|q| q.query_all_write(query_id, out)) - } - - fn query(&self, query_id: &QueryId) -> Option { - self.iter().find_map(|q| q.query(query_id)) - } - - fn query_write(&self, query_id: &QueryId) -> Option { - self.iter().find_map(|q| q.query_write(query_id)) - } - - fn query_match( - &self, ids: &[QueryId], filter: &dyn Fn(&QueryId, &QueryHandle) -> bool, - ) -> Option<(QueryId, QueryHandle)> { - self - .iter() - .find_map(|q| q.query_match(ids, filter)) - } - - fn queryable(&self) -> bool { true } -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::{reset_test_env, test_helper::*}; - - #[test] - fn direct_pass() { - reset_test_env!(); - let (value, w_value) = split_value(0); - - let w = fn_widget! { - let w_value = w_value.clone_writer(); - Provider::new(Box::new(Queryable(1i32))).with_child(fn_widget! { - let v = Provider::of::(BuildCtx::get()).unwrap(); - *w_value.write() = *v; - Void - }) - }; - - let mut wnd = TestWindow::new(w); - wnd.draw_frame(); - assert_eq!(*value.read(), 1); - } - - #[test] - fn indirect_pass() { - reset_test_env!(); - - let (value, w_value) = split_value(0); - let w = fn_widget! { - let w_value = w_value.clone_writer(); - Provider::new(Box::new(Queryable(1i32))).with_child(fn_widget! { - @MockBox { - size: Size::new(1.,1.), - @ { - let v = Provider::of::(BuildCtx::get()).unwrap(); - *$w_value.write() = *v; - Void - } - } - }) - }; - - let mut wnd = TestWindow::new(w); - wnd.draw_frame(); - - assert_eq!(*value.read(), 1); - } - - #[test] - fn with_multi_providers() { - reset_test_env!(); - - let (value1, w_value1) = split_value(0); - let (value2, w_value2) = split_value(0); - let w = fn_widget! { - let w_value1 = w_value1.clone_writer(); - let w_value2 = w_value2.clone_writer(); - @MockMulti { - @ { - Provider::new(Box::new(Queryable(1i32))).with_child(fn_widget! { - let v = Provider::of::(BuildCtx::get()).unwrap(); - *$w_value1.write() = *v; - @ Void{} - }) - } - - @ { - Provider::new(Box::new(Queryable(2i32))).with_child(fn_widget! { - let v = Provider::of::(BuildCtx::get()).unwrap(); - *$w_value2.write() = *v; - @ Void{} - }) - } - } - }; - - let mut wnd = TestWindow::new(w); - wnd.draw_frame(); - - assert_eq!(*value1.read(), 1); - assert_eq!(*value2.read(), 2); - } - - #[test] - fn provider_for_pipe() { - reset_test_env!(); - let (value, w_value) = split_value(0); - let (trigger, w_trigger) = split_value(true); - - let w = fn_widget! { - let trigger = trigger.clone_watcher(); - Provider::new(Box::new(w_value.clone_writer())) - .with_child(fn_widget! { - // We do not allow the use of the build context in the pipe at the moment. - let value = Provider::of::>(BuildCtx::get()) - .unwrap().clone_writer(); - pipe!(*$trigger).map(move |_| { - *value.write() += 1; - Void - }) - }) - }; - - let mut wnd = TestWindow::new(w); - wnd.draw_frame(); - assert_eq!(*value.read(), 1); - - *w_trigger.write() = false; - wnd.draw_frame(); - assert_eq!(*value.read(), 2); - } -} diff --git a/core/src/builtin_widgets/providers.rs b/core/src/builtin_widgets/providers.rs new file mode 100644 index 000000000..24b24bbea --- /dev/null +++ b/core/src/builtin_widgets/providers.rs @@ -0,0 +1,717 @@ +//! Providers serve as a mechanism in Ribir to distribute data to descendants. +//! +//! The data is set up in the context for the descendants' scope and removed +//! when out of scope. Descendants can access the data using [`Provider::of`] +//! with contexts such as `BuildCtx`, `LayoutCtx`, `PaintingCtx`, and event +//! objects. +//! +//! ``` +//! use ribir::prelude::*; +//! +//! providers! { +//! providers: [Provider::new(1i32)], +//! @{ +//! // Providers accessible via `BuildCtx`. +//! let value = Provider::of::(BuildCtx::get()).unwrap(); +//! assert_eq!(*value, 1); +//! @Text { +//! text: "Good!", +//! on_tap: move |e| { +//! // Accessing providers through event objects. +//! let value = Provider::write_of::(e).unwrap(); +//! assert_eq!(*value, 1); +//! } +//! } +//! } +//! }; +//! ``` +//! +//! ## Provider Based on Type +//! +//! Providers are based on their type, allowing only one provider of the same +//! type to remain in the context at a time. When a new provider of the same +//! type is provided, it overwrites the previous one. Thus, the closest provider +//! of the same type to its usage will be accessed. +//! +//! ``` +//! use ribir::prelude::*; +//! use smallvec::smallvec; +//! +//! providers! { +//! providers: smallvec![Provider::new(1i32), Provider::new(Color::RED)], +//! @Providers { +//! providers: [Provider::new(2i32)], +//! @ { +//! let value = Provider::of::(BuildCtx::get()).unwrap(); +//! assert_eq!(*value, 2); +//! let value = Provider::of::(BuildCtx::get()).unwrap(); +//! assert_eq!(*value, Color::RED); +//! @Text { text: "`i32` overwritten with 2, color remains unchanged" } +//! } +//! } +//! }; +//! ``` +//! +//! State can be provided as either its value or itself. When providing a writer +//! as its value, you can access its write reference using +//! [`Provider::write_of`] to modify the state. +//! +//! ``` +//! use ribir::prelude::*; +//! use smallvec::smallvec; +//! +//! let state = Stateful::new(1i32); +//! +//! providers! { +//! providers: smallvec![ +//! // Providing a `Stateful` +//! Provider::new(state.clone_writer()), +//! // Providing an `i32` +//! Provider::value_of_state(state) +//! ], +//! @ { +//! let ctx = BuildCtx::get(); +//! // Accessing the state value +//! let value = *Provider::of::(ctx).unwrap(); +//! assert_eq!(value, 1); +//! // Accessing the state itself +//! { +//! let _state = Provider::of::>(ctx).unwrap(); +//! } +//! // Accessing the write reference of the state to modify the value +//! let mut value = Provider::write_of::(ctx).unwrap(); +//! *value = 2; +//! @Text { text: "Both state and its value provided!" } +//! } +//! }; +//! ``` +//! +//! ## Scope of Providers in the Build Process +//! +//! Providers are visible to their descendants. However, in the build process, +//! the scope starts when the `Providers` are created and ends when they are +//! fully composed with their children. Thus, it is recommended to declare +//! providers together with their children. +//! +//! ``` +//! use ribir::prelude::*; +//! +//! let _bad_example = fn_widget! { +//! let providers = Providers::new([Provider::new(1i32)]); +//! // Providers are already accessible here +//! let v = *Provider::of::(BuildCtx::get()).unwrap(); +//! assert_eq!(v, 1); +//! +//! @Row { +//! @Text { text: "I can access providers in the build process" } +//! @ $providers { +//! @Text { text: "We only want providers accessible in this subtree" } +//! } +//! } +//! }; +//! ``` +//! +//! The correct approach is as follows: +//! +//! ``` +//! use ribir::prelude::*; +//! +//! let _good_example = fn_widget! { +//! @Row { +//! @Text { text: "I can't see the `i32` provider" } +//! @Providers { +//! providers: [Provider::new(1i32)], +//! @Text { text: "I can see the `i32` provider" } +//! } +//! } +//! }; +//! ``` +use std::cell::RefCell; + +use ribir_painter::Color; +use smallvec::SmallVec; +use widget_id::RenderQueryable; + +use crate::prelude::*; + +/// The widget that provides data to its descendants. See the +/// [module-level](self) documentation for more details. +pub struct Providers { + providers: RefCell>, +} + +/// Macro used to generate a function widget using `BuildVariants` as the root +/// widget. +#[macro_export] +macro_rules! providers { + ($($t: tt)*) => { fn_widget! { @Providers { $($t)* } } }; +} + +/// The type use to store the data you want to share. +pub enum Provider { + /// The value of the provider has not been setup yet. + Setup(Box), + /// The provider has already been setup to the context, and wait for restore. + Restore(Box), +} + +/// This trait is used to set up the providers in the context. In most cases, +/// you don't need to worry about it unless you want to customize the setup +/// process. +pub trait ProviderSetup { + fn setup(self: Box, ctx: &mut ProviderCtx) -> Box; +} + +/// This trait is used to retrieve the providers from the context. In most +/// cases, you don't need to worry about it unless you want to customize the +/// retrieval process. +pub trait ProviderRestore { + fn restore(self: Box, ctx: &mut ProviderCtx) -> Box; +} + +/// A type for providing the container color of the widget. +#[derive(Copy, Clone)] +#[repr(transparent)] +pub struct ContainerColor(pub Color); + +/// The context used to store the providers. +#[derive(Default)] +pub struct ProviderCtx { + data: ahash::AHashMap>, + /// The stack is used to temporarily store the providers that are set up and + /// will be popped and restored when the scope is exited. + setup_providers: Vec<(WidgetId, *const Providers)>, +} + +impl Provider { + /// Establish a provider for `T`. + pub fn new(value: T) -> Provider { Provider::Setup(Box::new(Setup::new(value))) } + + /// Establish a provider for the `Value` of a state. If you use a writer to + /// create this provider, a write reference of the value can be accessed + /// through [`Provider::write_of`]. + pub fn value_of_state(value: impl StateReader + Query) -> Provider { + Provider::Setup(Box::new(Setup::from_state(value))) + } + + /// Access the provider of `P` within the context. + pub fn of(ctx: &impl AsRef) -> Option> { + ctx.as_ref().get_provider::

() + } + + /// Access the write reference of `P` within the context. + pub fn write_of(ctx: &impl AsRef) -> Option> { + ctx.as_ref().get_provider_write::

() + } + + pub(crate) fn custom(info: TypeInfo, value: Box) -> Self { + Provider::Setup(Box::new(Setup { info, value })) + } + + /// Setup the provider to the context. + pub fn setup(&mut self, ctx: &mut ProviderCtx) { + let Provider::Setup(setup) = self else { + panic!("Provider already setup"); + }; + // Safety: We will have two references to the setup, but we will + // forget one of them after the setup is completed. + let setup = unsafe { Box::from_raw(&mut **setup) }; + let restore = setup.setup(ctx); + let f = std::mem::replace(self, Provider::Restore(restore)); + std::mem::forget(f); + } + + /// Restore the provider from the context. + pub fn restore(&mut self, map: &mut ProviderCtx) { + let Provider::Restore(restore) = self else { + panic!("Provider restore not match."); + }; + // Safety: We will have two references to the restore, but we will forget + // one of them after the restore is completed. + let restore = unsafe { Box::from_raw(&mut **restore) }; + let setup = restore.restore(map); + let f = std::mem::replace(self, Provider::Setup(setup)); + std::mem::forget(f); + } + + fn info() -> TypeInfo { TypeInfoOf::::type_info() } +} + +pub struct ProvidersDeclarer { + providers: Option>, +} + +impl Declare for Providers { + type Builder = ProvidersDeclarer; + + fn declarer() -> Self::Builder { ProvidersDeclarer { providers: None } } +} + +impl ProvidersDeclarer { + pub fn providers(mut self, variants: impl Into>) -> Self { + assert!(self.providers.is_none(), "Providers already initialized"); + self.providers = Some(variants.into()); + self + } +} + +impl ObjDeclarer for ProvidersDeclarer { + type Target = Providers; + + #[track_caller] + fn finish(self) -> Self::Target { + let Some(mut providers) = self.providers else { + panic!("Providers not initialized"); + }; + let map = BuildCtx::get_mut().as_mut(); + + for p in providers.iter_mut() { + p.setup(map); + } + Providers { providers: RefCell::new(providers) } + } +} + +impl Providers { + pub fn new(providers: impl Into>) -> Self { + Providers::declarer() + .providers(providers) + .finish() + } + + pub(crate) fn setup_providers(&self, map: &mut ProviderCtx) { + for p in self.providers.borrow_mut().iter_mut() { + p.setup(map); + } + } + + pub(crate) fn restore_providers(&self, map: &mut ProviderCtx) { + for p in self.providers.borrow_mut().iter_mut() { + p.restore(map); + } + } +} + +impl ContainerColor { + pub fn provider(color: Color) -> Provider { Provider::new(ContainerColor(color)) } +} + +impl ProviderCtx { + pub(crate) fn collect_from(id: WidgetId, tree: &WidgetTree) -> ProviderCtx { + let ancestors = id + .ancestors(tree) + .filter(|id| id.queryable(tree)) + .collect::>(); + + let mut ctx = ProviderCtx::default(); + let mut providers = SmallVec::new(); + for p in ancestors.iter().rev() { + ctx.push_providers_for(*p, tree, &mut providers); + } + + ctx + } + + /// Push the providers to the stack, the caller should guarantee that the + /// providers is available before popping it. + pub(crate) fn push_providers(&mut self, id: WidgetId, providers: *const Providers) { + unsafe { &*providers }.setup_providers(self); + self.setup_providers.push((id, providers)); + } + + /// Pop the providers from the stack and restore it. + pub(crate) fn pop_providers(&mut self) -> Option<(WidgetId, *const Providers)> { + self.setup_providers.pop().inspect(|(_, p)| { + unsafe { &**p }.restore_providers(self); + }) + } + + /// Pop the providers for the specified widget from the stack and restore it. + /// + /// Only if the `w` is the last widget in the stack, it will be invoked. + pub(crate) fn pop_providers_for(&mut self, w: WidgetId) { + while self + .setup_providers + .last() + .is_some_and(|(id, _)| id == &w) + { + self.pop_providers(); + } + } + + pub(crate) fn push_providers_for<'t>( + &mut self, w: WidgetId, tree: &'t WidgetTree, buffer: &mut SmallVec<[QueryHandle<'t>; 1]>, + ) { + w.assert_get(tree) + .query_all(&QueryId::of::(), buffer); + + for providers in buffer + .drain(..) + .rev() + .filter_map(QueryHandle::into_ref::) + { + self.push_providers(w, &*providers); + } + } + + pub(crate) fn remove_raw_provider(&mut self, info: &TypeInfo) -> Option> { + self.data.remove(info) + } + + pub(crate) fn set_raw_provider( + &mut self, info: TypeInfo, p: Box, + ) -> Option> { + self.data.insert(info, p) + } + + pub(crate) fn get_raw_provider(&self, info: &TypeInfo) -> Option<&dyn Query> { + self.data.get(info).map(|q| &**q) + } + + pub(crate) fn get_provider(&self) -> Option> { + let info = Provider::info::(); + self + .data + .get(&info) + .and_then(|q| q.query(&QueryId::of::())) + .and_then(QueryHandle::into_ref) + } + + pub(crate) fn get_provider_write(&self) -> Option> { + let info = Provider::info::(); + self + .data + .get(&info) + .and_then(|q| q.query_write(&QueryId::of::())) + .and_then(QueryHandle::into_mut) + } + + pub(crate) fn remove_key_value_if( + &mut self, f: impl Fn(&TypeInfo) -> bool, + ) -> Vec<(TypeInfo, Box)> { + let mut out = Vec::new(); + let keys = self.data.keys().cloned().collect::>(); + for k in keys { + if f(&k) { + if let Some(v) = self.data.remove(&k) { + out.push((k, v)); + } + } + } + out + } +} + +impl Providers { + pub fn with_child<'w, const M: usize>(self, child: impl IntoWidget<'w, M>) -> Widget<'w> { + let child = child.into_widget(); + self.restore_providers(BuildCtx::get_mut().as_mut()); + Widget::from_fn(move |ctx| { + self.setup_providers(ctx.as_mut()); + let id = ctx.build(child); + self.restore_providers(ctx.as_mut()); + id.wrap_node(ctx.tree_mut(), |render| { + Box::new(ProvidersRender { providers: Queryable(self), render }) + }); + id + }) + } +} + +impl Drop for Providers { + fn drop(&mut self) { + let need_restore = self + .providers + .borrow() + .iter() + .any(|p| matches!(p, Provider::Restore(_))); + + assert!( + !need_restore, + "You have created a `Providers` object but did not use it to wrap a child. This may result \ + in the providers context being in an incorrect state." + ); + } +} + +impl Drop for ProviderCtx { + fn drop(&mut self) { + while self.pop_providers().is_some() {} + + assert!( + self.data.is_empty(), + "Some providers may not be restored if you create an independent `Providers` instead of \ + composing it with a child." + ); + } +} + +struct ProvidersRender { + providers: Queryable, + render: Box, +} + +impl Query for ProvidersRender { + fn query_all<'q>(&'q self, query_id: &QueryId, out: &mut SmallVec<[QueryHandle<'q>; 1]>) { + self.render.query_all(query_id, out); + if let Some(h) = self.providers.query(query_id) { + out.push(h) + } + } + + fn query_all_write<'q>(&'q self, query_id: &QueryId, out: &mut SmallVec<[QueryHandle<'q>; 1]>) { + self.render.query_all_write(query_id, out); + if let Some(h) = self.providers.query_write(query_id) { + out.push(h) + } + } + + fn query(&self, query_id: &QueryId) -> Option { + self + .providers + .query(query_id) + .or_else(|| self.render.query(query_id)) + } + + fn query_write(&self, query_id: &QueryId) -> Option { + self + .providers + .query_write(query_id) + .or_else(|| self.render.query_write(query_id)) + } + + fn queryable(&self) -> bool { true } +} + +impl Render for ProvidersRender { + fn perform_layout(&self, clamp: BoxClamp, ctx: &mut LayoutCtx) -> Size { + let Self { render, providers: Queryable(p) } = self; + p.setup_providers(ctx.as_mut()); + let size = render.perform_layout(clamp, ctx); + p.restore_providers(ctx.as_mut()); + size + } + + fn paint(&self, ctx: &mut PaintingCtx) { + let Self { render, providers: Queryable(p) } = self; + let id = ctx.id(); + // The providers will be popped in the `PaintingCtx::finish` method, once the + // painting of the entire subtree is completed. + ctx.as_mut().push_providers(id, p); + + render.paint(ctx); + } + + fn hit_test(&self, ctx: &mut HitTestCtx, pos: Point) -> HitTest { + let Self { render, providers: Queryable(p) } = self; + let id = ctx.id(); + // The providers will be popped in the `HitTestCtx::finish` method, once the + // hit test of the entire subtree is completed. + ctx.as_mut().push_providers(id, p); + render.hit_test(ctx, pos) + } + + fn only_sized_by_parent(&self) -> bool { self.render.only_sized_by_parent() } + + fn get_transform(&self) -> Option { self.render.get_transform() } +} + +pub struct Setup { + info: TypeInfo, + value: Box, +} + +struct Restore { + info: TypeInfo, + value: Option>, +} + +impl ProviderSetup for Setup { + fn setup(self: Box, map: &mut ProviderCtx) -> Box { + let Setup { info, value } = *self; + let old = map.set_raw_provider(info, value); + + Box::new(Restore { info, value: old }) + } +} + +impl ProviderRestore for Restore { + fn restore(self: Box, map: &mut ProviderCtx) -> Box { + let Restore { info, value } = *self; + let v = if let Some(v) = value { + map.set_raw_provider(info, v) + } else { + map.remove_raw_provider(&info) + }; + let Some(v) = v else { + panic!("Provider restore not matched"); + }; + Box::new(Setup { info, value: v }) + } +} + +impl Setup { + pub(crate) fn new(value: T) -> Self { + Setup { info: Provider::info::(), value: Box::new(Queryable(value)) } + } + + pub(crate) fn from_state(value: impl StateReader + Query) -> Self { + Setup { info: Provider::info::(), value: Box::new(value) } + } +} + +#[cfg(test)] +mod tests { + + use smallvec::smallvec; + + use crate::{prelude::*, reset_test_env, test_helper::*}; + + #[test] + fn smoke() { + reset_test_env!(); + + let mut wnd = TestWindow::new(mock_multi! { + @Providers { + providers: smallvec![Provider::new(Color::RED)], + @ { + assert_eq!(BuildCtx::color(), Color::RED); + @MockMulti { + @fn_widget!{ + assert_eq!(BuildCtx::color(), Color::RED); + Void + } + } + } + } + @ { + let color = BuildCtx::color(); + assert_eq!(color, Palette::of(BuildCtx::get()).primary()); + Void + } + }); + wnd.draw_frame(); + } + + #[test] + fn embedded() { + reset_test_env!(); + + let mut wnd = TestWindow::new(providers! { + providers: smallvec![Provider::new(Color::RED)], + @Providers { + providers: smallvec![ContainerColor::provider(Color::GREEN)], + @ { + let container_color = BuildCtx::container_color(); + assert_eq!(container_color, Color::GREEN); + let color = BuildCtx::color(); + assert_eq!(color, Color::RED); + Void + } + } + }); + wnd.draw_frame(); + } + + #[test] + fn direct_pass() { + reset_test_env!(); + + let (value, w_value) = split_value(0); + + let mut wnd = TestWindow::new(providers! { + providers: smallvec![Provider::new(1i32)], + @{ + let v = Provider::of::(BuildCtx::get()).unwrap(); + *w_value.write() = *v; + Void + } + }); + wnd.draw_frame(); + assert_eq!(*value.read(), 1); + } + + #[test] + fn indirect_pass() { + reset_test_env!(); + + let (value, w_value) = split_value(0); + let w = providers! { + providers: smallvec![Provider::new(1i32)], + @MockBox { + size: Size::new(1.,1.), + @ { + let v = Provider::of::(BuildCtx::get()).unwrap(); + *$w_value.write() = *v; + Void + } + } + }; + + let mut wnd = TestWindow::new(w); + wnd.draw_frame(); + + assert_eq!(*value.read(), 1); + } + + #[test] + fn with_multi_providers() { + reset_test_env!(); + + let (value1, w_value1) = split_value(0); + let (value2, w_value2) = split_value(0); + let w = mock_multi! { + @Providers { + providers: smallvec![Provider::new(1i32)], + @ { + let v = Provider::of::(BuildCtx::get()).unwrap(); + *$w_value1.write() = *v; + Void + } + } + + @Providers { + providers: smallvec![Provider::new(2i32)], + @ { + let v = Provider::of::(BuildCtx::get()).unwrap(); + *$w_value2.write() = *v; + Void + } + } + }; + + let mut wnd = TestWindow::new(w); + wnd.draw_frame(); + + assert_eq!(*value1.read(), 1); + assert_eq!(*value2.read(), 2); + } + + #[test] + fn provider_for_pipe() { + reset_test_env!(); + let (value, w_value) = split_value(0); + let (trigger, w_trigger) = split_value(true); + + let w = providers! { + providers: smallvec![Provider::new(w_value.clone_writer())], + @ { + // We do not allow the use of the build context in the pipe at the moment. + let value = Provider::of::>(BuildCtx::get()) + .unwrap().clone_writer(); + pipe!(*$trigger).map(move |_| { + *value.write() += 1; + Void + }) + } + }; + + let mut wnd = TestWindow::new(w); + wnd.draw_frame(); + assert_eq!(*value.read(), 1); + + *w_trigger.write() = false; + wnd.draw_frame(); + assert_eq!(*value.read(), 2); + } +} diff --git a/core/src/builtin_widgets/scrollable.rs b/core/src/builtin_widgets/scrollable.rs index 926e79d1c..ea6bd440b 100644 --- a/core/src/builtin_widgets/scrollable.rs +++ b/core/src/builtin_widgets/scrollable.rs @@ -34,7 +34,7 @@ impl<'c> ComposeChild<'c> for ScrollableWidget { fn compose_child(this: impl StateWriter, child: Self::Child) -> Widget<'c> { fn_widget! { let mut view = @UnconstrainedBox { - dir: pipe!{ + dir: distinct_pipe!{ let this = $this; match this.scrollable { Scrollable::X => UnconstrainedDir::X, @@ -43,11 +43,12 @@ impl<'c> ComposeChild<'c> for ScrollableWidget { } }, clamp_dim: ClampDim::MAX_SIZE, + on_wheel: move |e| $this.write().scroll(-e.delta_x, -e.delta_y), }; let child = FatObj::new(child); let mut child = @ $child { - anchor: pipe!{ + anchor: distinct_pipe!{ let this = $this; let pos = this.get_scroll_pos(); Anchor::left_top(-pos.x, -pos.y) @@ -61,12 +62,7 @@ impl<'c> ComposeChild<'c> for ScrollableWidget { .distinct_until_changed() .subscribe(move |v| $this.write().set_page(v)); - @Clip { - @ $view { - on_wheel: move |e| $this.write().scroll(-e.delta_x, -e.delta_y), - @ { child } - } - } + @Clip { @ $view { @ { child } } } } .into_widget() } diff --git a/core/src/builtin_widgets/text.rs b/core/src/builtin_widgets/text.rs index 4fdb3b3d9..5735e83b1 100644 --- a/core/src/builtin_widgets/text.rs +++ b/core/src/builtin_widgets/text.rs @@ -18,12 +18,12 @@ pub struct Text { impl Render for Text { fn perform_layout(&self, clamp: BoxClamp, ctx: &mut LayoutCtx) -> Size { - let style = ctx.text_style(); + let style = Provider::of::(ctx).unwrap(); let info = AppCtx::typography_store() .borrow_mut() .typography( self.text.substr(..), - style, + &style, clamp.max, self.text_align, GlyphBaseline::Middle, @@ -49,11 +49,19 @@ impl Render for Text { return; }; + let style = Provider::of::(ctx).map(|p| p.clone()); + let painter = ctx.painter(); + if let Some(PaintingStyle::Stroke(options)) = style { + painter + .set_style(PathStyle::Stroke) + .set_strokes(options); + } else { + painter.set_style(PathStyle::Fill); + } + let visual_glyphs = self.glyphs().unwrap(); let font_db = AppCtx::font_db().clone(); - ctx - .painter() - .draw_glyphs_in_rect(&visual_glyphs, box_rect, &font_db.borrow()); + painter.draw_glyphs_in_rect(&visual_glyphs, box_rect, &font_db.borrow()); } } @@ -104,7 +112,7 @@ mod tests { widget_test_suit!( text_clip, WidgetTester::new(fn_widget! { - @ MockBox { + @MockBox { size: Size::new(50., 45.), @Text { text: "hello world,\rnice to meet you.", diff --git a/core/src/builtin_widgets/text_style.rs b/core/src/builtin_widgets/text_style.rs index 6549df2f8..938107b52 100644 --- a/core/src/builtin_widgets/text_style.rs +++ b/core/src/builtin_widgets/text_style.rs @@ -1,4 +1,4 @@ -use crate::{prelude::*, wrap_render::WrapRender}; +use crate::prelude::*; /// This widget establishes the text style for painting the text within its /// descendants. @@ -17,28 +17,16 @@ impl<'c> ComposeChild<'c> for TextStyleWidget { fn compose_child(this: impl StateWriter, child: Self::Child) -> Widget<'c> { // We need to provide the text style for the children to access. - let (child, provider): (_, Box) = match this.try_into_value() { - Ok(this) => { - let style = this.text_style.clone(); - (WrapRender::combine_child(State::value(this), child), Box::new(Queryable(style))) - } + let provider = match this.try_into_value() { + Ok(this) => Provider::new(this.text_style), Err(this) => { let style = this.map_reader(|w| PartData::from_ref(&w.text_style)); - (WrapRender::combine_child(this, child), Box::new(style)) + Provider::value_of_state(style) } }; - Provider::new(provider) - .with_child(fn_widget! { child }) + Providers::new([provider]) + .with_child(child) .into_widget() } } - -impl WrapRender for TextStyleWidget { - fn perform_layout(&self, clamp: BoxClamp, host: &dyn Render, ctx: &mut LayoutCtx) -> Size { - let old = ctx.set_text_style(self.text_style.clone()); - let size = host.perform_layout(clamp, ctx); - ctx.set_text_style(old); - size - } -} diff --git a/core/src/builtin_widgets/theme.rs b/core/src/builtin_widgets/theme.rs index cb689b2bd..4a261c451 100644 --- a/core/src/builtin_widgets/theme.rs +++ b/core/src/builtin_widgets/theme.rs @@ -4,7 +4,7 @@ use std::collections::HashMap; pub use ribir_algo::{CowArc, Resource}; -use smallvec::SmallVec; +use smallvec::{SmallVec, smallvec}; use crate::prelude::*; @@ -28,13 +28,17 @@ pub enum Brightness { Light, } -/// A `Theme` widget is used to share design configurations among its -/// descendants. +/// The `Theme` widget serves to distribute design settings to its +/// subsequent elements. /// -/// This includes palettes, font styles, animation transitions, and icons. An -/// app theme is always present, but you can also use a different -/// theme for parts of your sub-tree. You can customize parts of the theme using -/// `Palette`, `TypographyTheme`, and `IconTheme`. +/// Access it through `Theme::of`, and utilize `Theme::write_of` to obtain +/// a writable reference to the theme for modifications. +/// +/// Certain components of the theme that are frequently used, such as `Palette`, +/// `TextStyle`, `IconFont`, `Color`, and `ContainerColor`, are also provide +/// as read-only providers when the `Theme` widget compose. If you want to +/// customize specific aspects of the theme, utilize `Providers` to overwrite +/// elements like `Palette`, `TextStyle`, and more. /// /// # Examples /// @@ -61,13 +65,13 @@ pub enum Brightness { /// App::run(w); /// ``` /// -/// You can provide a theme for a widget: +/// You can use an other theme for a widget. /// /// ``` /// use ribir::prelude::*; /// +/// // Feel free to use a different theme here. /// let w = Theme::default().with_child(fn_widget! { -/// // Feel free to use a different theme here. /// Void /// }); /// ``` @@ -85,7 +89,9 @@ pub struct Theme { pub transitions_theme: TransitionTheme, pub compose_decorators: ComposeDecorators, pub custom_styles: CustomStyles, + // The theme requires font bytes. pub font_bytes: Vec>, + // The theme requires font files. pub font_files: Vec, /// This font is used for icons to display text as icons through font /// ligatures. It is crucial to ensure that this font is included in either @@ -97,32 +103,75 @@ pub struct Theme { /// size, which is not ideal for web platforms. Therefore, this configuration /// allows the application developer to supply the font file. Certainly, the /// icon also works with `SVG` and [`named_svgs`](super::named_svgs). - pub icon_font: FontFace, + pub icon_font: IconFont, } +#[derive(Clone, Debug, Default)] +pub struct IconFont(pub FontFace); + impl Theme { - /// Retrieve the nearest `Theme` from the context among its ancestors - pub fn of(ctx: &impl ProviderCtx) -> QueryRef { - // At least one application theme exists - Provider::of(ctx).unwrap() - } + pub fn of(ctx: &impl AsRef) -> QueryRef { Provider::of(ctx).unwrap() } - /// Retrieve the nearest `Theme` from the context among its ancestors and - /// return a write reference to the theme. - pub fn write_of(ctx: &impl ProviderCtx) -> WriteRef { - // At least one application theme exists + pub fn write_of(ctx: &impl AsRef) -> WriteRef { Provider::write_of(ctx).unwrap() } + pub(crate) fn preprocess_before_compose( + this: impl StateWriter, child: GenWidget, + ) -> (SmallVec<[Provider; 1]>, Widget<'static>) { + fn load_fonts(theme: &impl StateWriter) { + // Loading fonts does not require regenerating the `Theme` subtree, as this + // method has already been called within a regenerated subtree. + let mut t = theme.write(); + t.load_fonts(); + t.forget_modifies(); + } + + load_fonts(&this); + let container_color = this.map_reader(|t| { + // Safety Note: In this instance, a copied value of the palette is utilized, + // which is not the correct method of using `PartData`. However, in this case, + // it is only a read-only value, and once added to the providers, neither the + // state reader nor its read reference can be accessed by anyone. Therefore, it + // is considered safe. + unsafe { PartData::from_ptr(ContainerColor(t.palette.secondary_container())) } + }); + + let providers = smallvec![ + // The theme provider is designated as writable state, + // while other components of the theme provider are treated as read-only state. + Provider::value_of_state(this.clone_writer()), + Provider::value_of_state(part_reader!(&this.palette.primary)), + Provider::value_of_state(container_color), + Provider::value_of_state(part_reader!(&this.typography_theme.body_medium.text)), + Provider::value_of_state(part_reader!(&this.palette)), + Provider::value_of_state(part_reader!(&this.typography_theme)), + Provider::value_of_state(part_reader!(&this.icon_theme)), + Classes::reader_into_provider(part_reader!(&this.classes)), + Provider::value_of_state(part_reader!(&this.transitions_theme)), + Provider::value_of_state(part_reader!(&this.compose_decorators)), + Provider::value_of_state(part_reader!(&this.custom_styles)), + Provider::value_of_state(part_reader!(&this.icon_font)) + ]; + let child = pipe!($this;) + .map(move |_| { + load_fonts(&this); + child.gen_widget() + }) + .into_widget(); + (providers, child) + } + /// Loads the fonts specified in the theme configuration. fn load_fonts(&mut self) { let mut font_db = AppCtx::font_db().borrow_mut(); let Theme { font_bytes, font_files, .. } = self; + font_bytes - .iter() + .drain(..) .for_each(|data| font_db.load_from_bytes(data.clone())); - font_files.iter().for_each(|path| { + font_files.drain(..).for_each(|path| { let _ = font_db.load_font_file(path); }); } @@ -135,100 +184,9 @@ impl ComposeChild<'static> for Theme { fn compose_child(this: impl StateWriter, child: Self::Child) -> Widget<'static> { use crate::prelude::*; - this.silent().load_fonts(); - let w = this.clone_watcher(); - let theme = ThemeQuerier(this.clone_writer()); - - Provider::new(Box::new(theme)) - .with_child(fn_widget! { - pipe!($w;).map(move |_| child.gen_widget()) - }) - .into_widget() - } -} - -struct ThemeQuerier>(T); - -impl> Query for ThemeQuerier { - fn query_all<'q>(&'q self, query_id: &QueryId, out: &mut SmallVec<[QueryHandle<'q>; 1]>) { - // The value of the writer and the writer itself cannot be queried - // at the same time. - if let Some(h) = self.query(query_id) { - out.push(h) - } - } - - fn query_all_write<'q>(&'q self, query_id: &QueryId, out: &mut SmallVec<[QueryHandle<'q>; 1]>) { - if let Some(h) = self.query_write(query_id) { - out.push(h) - } - } - - fn query(&self, query_id: &QueryId) -> Option { - ReadRef::filter_map(self.0.read(), |v: &Theme| { - let w: Option<&dyn QueryAny> = if &QueryId::of::() == query_id { - Some(v) - } else if &QueryId::of::() == query_id { - Some(&v.palette) - } else if &QueryId::of::() == query_id { - Some(&v.typography_theme) - } else if &QueryId::of::() == query_id { - Some(&v.typography_theme.body_medium.text) - } else if &QueryId::of::() == query_id { - Some(&v.classes) - } else if &QueryId::of::() == query_id { - Some(&v.icon_theme) - } else if &QueryId::of::() == query_id { - Some(&v.transitions_theme) - } else if &QueryId::of::() == query_id { - Some(&v.compose_decorators) - } else if &QueryId::of::() == query_id { - Some(&v.custom_styles) - } else { - None - }; - w.map(PartData::from_ref) - }) - .ok() - .map(QueryHandle::from_read_ref) - } - - fn query_write(&self, query_id: &QueryId) -> Option { - WriteRef::filter_map(self.0.write(), |v: &mut Theme| { - let w: Option<&mut dyn QueryAny> = if &QueryId::of::() == query_id { - Some(v) - } else if &QueryId::of::() == query_id { - Some(&mut v.palette) - } else if &QueryId::of::() == query_id { - Some(&mut v.typography_theme) - } else if &QueryId::of::() == query_id { - Some(&mut v.icon_theme) - } else if &QueryId::of::() == query_id { - Some(&mut v.transitions_theme) - } else if &QueryId::of::() == query_id { - Some(&mut v.compose_decorators) - } else if &QueryId::of::() == query_id { - Some(&mut v.custom_styles) - } else { - None - }; - w.map(PartData::from_ref_mut) - }) - .ok() - .map(QueryHandle::from_write_ref) - } - - fn query_match( - &self, ids: &[QueryId], filter: &dyn Fn(&QueryId, &QueryHandle) -> bool, - ) -> Option<(QueryId, QueryHandle)> { - ids.iter().find_map(|id| { - self - .query(id) - .filter(|h| filter(id, h)) - .map(|h| (*id, h)) - }) + let (providers, child) = Theme::preprocess_before_compose(this, child); + Providers::new(providers).with_child(child) } - fn queryable(&self) -> bool { true } } impl Default for Theme { @@ -312,16 +270,22 @@ mod tests { theme.with_child(fn_widget! { $writer.write().push(Palette::of(BuildCtx::get()).brightness); let writer = writer.clone_writer(); - Palette { brightness: Brightness::Dark, ..Default::default() } - .with_child(fn_widget!{ + let palette = Palette { brightness: Brightness::Dark, ..Default::default() }; + @Providers { + providers: [Provider::new(palette)], + @ { $writer.write().push(Palette::of(BuildCtx::get()).brightness); let writer = writer.clone_writer(); - Palette { brightness: Brightness::Light, ..Default::default() } - .with_child(fn_widget!{ + let palette = Palette { brightness: Brightness::Light, ..Default::default() }; + @Providers { + providers: [Provider::new(palette)], + @ { $writer.write().push(Palette::of(BuildCtx::get()).brightness); Void - }) - }) + } + } + } + } }) }; diff --git a/core/src/builtin_widgets/theme/custom_styles.rs b/core/src/builtin_widgets/theme/custom_styles.rs index 5c973ee5e..ac7c2d87b 100644 --- a/core/src/builtin_widgets/theme/custom_styles.rs +++ b/core/src/builtin_widgets/theme/custom_styles.rs @@ -16,14 +16,13 @@ macro_rules! fill_custom_style { } pub trait CustomStyle: Sized + Clone + 'static { - fn default_style(ctx: &impl ProviderCtx) -> Self; + fn default_style(ctx: &impl AsRef) -> Self; #[inline] - fn of(ctx: &impl ProviderCtx) -> Self { + fn of(ctx: &impl AsRef) -> Self { let tid = TypeId::of::(); - ctx - .all_of::() - .find_map(|t| { + Provider::of::(ctx) + .and_then(|t| { t.themes .get(&tid) .and_then(|c| c.downcast_ref::()) diff --git a/core/src/builtin_widgets/theme/icon_theme.rs b/core/src/builtin_widgets/theme/icon_theme.rs index bfd4803cb..4a7b3ad39 100644 --- a/core/src/builtin_widgets/theme/icon_theme.rs +++ b/core/src/builtin_widgets/theme/icon_theme.rs @@ -80,7 +80,7 @@ impl IconTheme { /// Retrieve the nearest `IconTheme` from the context among its ancestors #[inline] - pub fn of(ctx: &impl ProviderCtx) -> QueryRef { + pub fn of(ctx: &impl AsRef) -> QueryRef { // At least one application theme exists Provider::of::(ctx).unwrap() } @@ -88,7 +88,7 @@ impl IconTheme { /// Retrieve the nearest `IconTheme` from the context among its ancestors and /// return a write reference to the theme. #[inline] - pub fn write_of(ctx: &impl ProviderCtx) -> WriteRef { + pub fn write_of(ctx: &impl AsRef) -> WriteRef { // At least one application theme exists Provider::write_of::(ctx).unwrap() } @@ -103,7 +103,7 @@ impl IconTheme { } impl IconSize { - pub fn of(ctx: &impl ProviderCtx) -> QueryRef { + pub fn of(ctx: &impl AsRef) -> QueryRef { QueryRef::map(IconTheme::of(ctx), |i| &i.icon_size) } } @@ -113,17 +113,15 @@ impl NamedSvg { /// get the svg icon of the ident from the context if it have otherwise return /// a default icon. - pub fn of_or_miss(self, ctx: &impl ProviderCtx) -> Resource { + pub fn of_or_miss(self, ctx: &impl AsRef) -> Resource { NamedSvg::of(self, ctx) .or_else(|| MISS_ICON.of(ctx)) .expect("Neither Icon({:?}) nor 'MISS_ICON' are initialized in all `IconTheme` instances.") } /// get the svg icon of the ident from the context if it have. - pub fn of(self, ctx: &impl ProviderCtx) -> Option> { - ctx - .all_of::() - .find_map(|t| t.svgs.get(&self).cloned()) + pub fn of(self, ctx: &impl AsRef) -> Option> { + IconTheme::of(ctx).svgs.get(&self).cloned() } } diff --git a/core/src/builtin_widgets/theme/palette.rs b/core/src/builtin_widgets/theme/palette.rs index 02c5e24d2..bc1ebcbe7 100644 --- a/core/src/builtin_widgets/theme/palette.rs +++ b/core/src/builtin_widgets/theme/palette.rs @@ -1,23 +1,29 @@ use super::*; -/// The palette enables you to modify the color of your application to suit -/// your brand. `Palette` provide base colors with different light tone. +/// The palette allows you to customize the colors of your application to align +/// with your brand. /// -/// Note: `Palette` mainly learn from Material design color system -/// Reference https://m3.material.io/styles/color/ +/// `Palette` provides base colors with different light tones. It is a part of +/// the theme, but you can also use it independently for a subtree. /// -/// # Examples +/// Please be aware that `Palette` is primarily based on the Material Design +/// color system. For more information, you can refer to the Material Design +/// color system at https://m3.material.io/styles/color/. /// +/// # Examples /// /// ```rust /// use ribir_core::prelude::*; /// -/// let _w = Palette::default().with_child(fn_widget! { -/// // Every widget created in this scope can access the `Palette`. -/// let _primary = Palette::of(BuildCtx::get()).primary(); -/// Void -/// }); -/// ``` +/// let _w = providers! { +/// providers: [Provider::new(Palette::default())], +/// @ { +/// // Every widget created in this scope can access the `Palette`. +/// let _primary = Palette::of(BuildCtx::get()).primary(); +/// Void +/// } +/// }; +/// ```` #[derive(Clone, Debug)] pub struct Palette { // Accent colors: primary, secondary, and tertiary @@ -107,21 +113,10 @@ pub struct LightnessCfg { pub shadow: LightnessTone, } -impl ComposeChild<'static> for Palette { - type Child = GenWidget; - fn compose_child(this: impl StateWriter, child: Self::Child) -> Widget<'static> { - Provider::new(Box::new(this.clone_writer())) - .with_child(fn_widget! { - pipe!($this;).map(move |_| child.gen_widget()) - }) - .into_widget() - } -} - impl Palette { /// Retrieve the nearest `Palette` from the context among its ancestors #[inline] - pub fn of(ctx: &impl ProviderCtx) -> QueryRef { + pub fn of(ctx: &impl AsRef) -> QueryRef { // At least one application theme exists Provider::of::(ctx).unwrap() } @@ -129,7 +124,7 @@ impl Palette { /// Retrieve the nearest `Palette` from the context among its ancestors and /// return a write reference to the theme. #[inline] - pub fn write_of(ctx: &impl ProviderCtx) -> WriteRef { + pub fn write_of(ctx: &impl AsRef) -> WriteRef { // At least one application theme exists Provider::write_of::(ctx).unwrap() } diff --git a/core/src/builtin_widgets/theme/transition_theme.rs b/core/src/builtin_widgets/theme/transition_theme.rs index dfacb50cd..7eceabd0a 100644 --- a/core/src/builtin_widgets/theme/transition_theme.rs +++ b/core/src/builtin_widgets/theme/transition_theme.rs @@ -51,7 +51,7 @@ impl TransitionTheme { /// Retrieve the nearest `TransitionTheme` from the context among its /// ancestors #[inline] - pub fn of(ctx: &impl ProviderCtx) -> QueryRef { + pub fn of(ctx: &impl AsRef) -> QueryRef { // At least one application theme exists Provider::of::(ctx).unwrap() } @@ -59,7 +59,7 @@ impl TransitionTheme { /// Retrieve the nearest `TransitionTheme` from the context among its /// ancestors and return a write reference to the theme. #[inline] - pub fn write_of(ctx: &impl ProviderCtx) -> WriteRef { + pub fn write_of(ctx: &impl AsRef) -> WriteRef { // At least one application theme exists Provider::write_of::(ctx).unwrap() } @@ -70,7 +70,7 @@ impl TransitionIdent { /// get transition of the transition identify from the context if it have or /// return linear transition. - pub fn of(self, ctx: &impl ProviderCtx) -> Box { + pub fn of(self, ctx: &impl AsRef) -> Box { let panic_msg = format!( "Neither `Transition({:?})` nor `transitions::LINEAR` are initialized in all \ `TransitionTheme` instances.", @@ -82,10 +82,9 @@ impl TransitionIdent { .expect(&panic_msg) } - pub fn find(self, ctx: &impl ProviderCtx) -> Option> { - ctx - .all_of::() - .find_map(|t| t.transitions.get(&self).map(|t| t.box_clone())) + pub fn find(self, ctx: &impl AsRef) -> Option> { + Provider::of::(ctx) + .and_then(|t| t.transitions.get(&self).map(|t| t.box_clone())) } } diff --git a/core/src/builtin_widgets/theme/typography_theme.rs b/core/src/builtin_widgets/theme/typography_theme.rs index 2bf254551..610274b4b 100644 --- a/core/src/builtin_widgets/theme/typography_theme.rs +++ b/core/src/builtin_widgets/theme/typography_theme.rs @@ -56,26 +56,15 @@ bitflags! { impl TypographyTheme { /// Retrieve the nearest `TypographyTheme` from the context among its /// ancestors - pub fn of(ctx: &impl ProviderCtx) -> QueryRef { + pub fn of(ctx: &impl AsRef) -> QueryRef { // At least one application theme exists Provider::of(ctx).unwrap() } /// Retrieve the nearest `TypographyTheme` from the context among its /// ancestors and return a write reference to the theme. - pub fn write_of(ctx: &impl ProviderCtx) -> WriteRef { + pub fn write_of(ctx: &impl AsRef) -> WriteRef { // At least one application theme exists Provider::write_of(ctx).unwrap() } } - -impl ComposeChild<'static> for TypographyTheme { - type Child = GenWidget; - fn compose_child(this: impl StateWriter, child: Self::Child) -> Widget<'static> { - Provider::new(Box::new(this.clone_writer())) - .with_child(fn_widget! { - pipe!($this;).map(move |_| child.gen_widget()) - }) - .into_widget() - } -} diff --git a/core/src/builtin_widgets/transform_widget.rs b/core/src/builtin_widgets/transform_widget.rs index 7a7c4f2ae..73d6f7f44 100644 --- a/core/src/builtin_widgets/transform_widget.rs +++ b/core/src/builtin_widgets/transform_widget.rs @@ -24,7 +24,7 @@ impl WrapRender for TransformWidget { host.paint(ctx) } - fn hit_test(&self, host: &dyn Render, ctx: &HitTestCtx, pos: Point) -> HitTest { + fn hit_test(&self, host: &dyn Render, ctx: &mut HitTestCtx, pos: Point) -> HitTest { if let Some(t) = self.transform.inverse() { let lt = ctx.box_pos().unwrap(); let pos = (pos - lt).to_point(); diff --git a/core/src/builtin_widgets/unconstrained_box.rs b/core/src/builtin_widgets/unconstrained_box.rs index f5c3658d2..57573edd6 100644 --- a/core/src/builtin_widgets/unconstrained_box.rs +++ b/core/src/builtin_widgets/unconstrained_box.rs @@ -14,7 +14,7 @@ pub struct UnconstrainedBox { /// Enum to describe which axis will imposes no constraints on its child, use by /// `UnConstrainedBox`. -#[derive(Default, Clone, Copy)] +#[derive(Default, Clone, Copy, Eq, PartialEq)] pub enum UnconstrainedDir { X, Y, diff --git a/core/src/builtin_widgets/visibility.rs b/core/src/builtin_widgets/visibility.rs index 837c37af6..738909cc8 100644 --- a/core/src/builtin_widgets/visibility.rs +++ b/core/src/builtin_widgets/visibility.rs @@ -47,7 +47,7 @@ impl WrapRender for VisibilityRender { } } - fn hit_test(&self, host: &dyn Render, ctx: &HitTestCtx, pos: Point) -> HitTest { + fn hit_test(&self, host: &dyn Render, ctx: &mut HitTestCtx, pos: Point) -> HitTest { if self.display { host.hit_test(ctx, pos) } else { diff --git a/core/src/context/build_ctx.rs b/core/src/context/build_ctx.rs index dd7a830d2..a28084305 100644 --- a/core/src/context/build_ctx.rs +++ b/core/src/context/build_ctx.rs @@ -1,50 +1,32 @@ #![allow(static_mut_refs)] use std::ptr::NonNull; -use smallvec::SmallVec; -use widget_id::{RenderQueryable, new_node}; - use crate::{local_sender::LocalSender, prelude::*}; /// A context provide during build the widget tree. pub struct BuildCtx { - /// Widgets from the root widget to the current widget provide data that the - /// descendants can access. - pub(crate) providers: SmallVec<[WidgetId; 1]>, - /// Providers are available for current building - pub(crate) current_providers: SmallVec<[Box; 1]>, - pub(crate) tree: NonNull, - // Todo: Since `Theme`, `Palette`, `TypographyTheme` and `TextStyle` are frequently queried - // during the building process, layout and paint. we should cache the closest one. - /// The stack is utilized to temporarily store the children's relationships + tree: NonNull, + provider_ctx: ProviderCtx, + /// The stack is used to temporarily store the children's relationships /// during the build process. The widget's lifetime remains valid throughout /// this process; hence, we use 'static to avoid introducing a lifetime for /// the BuildCtx. - pub(crate) children: Vec<(WidgetId, Widget<'static>)>, + children: Vec<(WidgetId, Widget<'static>)>, } impl BuildCtx { /// Return the window of this context is created from. pub fn window(&self) -> Sc { self.tree().window() } - pub fn text_style(&self) -> QueryRef { Provider::of::(self).unwrap() } - - /// This method returns the color of the current build process, with the - /// primary color of the palette serving as the default. - /// - /// This color is used for interactions between the user and the widget theme. - /// For instance, the `Button` widget does not have a `color` property, but - /// its class utilizes the color returned by this method to style the button. - /// Therefore, users can modify this color to change the button's color - /// without having to override its class. - pub fn variant_color(&self) -> Color { - // todo: We have not yet enabled the ability to change the variant color. This - // method is provided for compatibility purposes now. - Palette::of(self).primary() - } + /// Return the `Color` provide in the current build context. + pub fn color() -> Color { *Provider::of::(BuildCtx::get()).unwrap() } - /// The container color of the variant color. - pub fn variant_container_color(&self) -> Color { Palette::of(self).secondary_container() } + /// Return the `ContainerColor` provide in the current build context. + pub fn container_color() -> Color { + Provider::of::(BuildCtx::get()) + .map(|v| v.0) + .unwrap() + } pub(crate) fn tree(&self) -> &WidgetTree { // Safety: Please refer to the comments in `WidgetTree::tree_mut` for more @@ -61,9 +43,7 @@ impl BuildCtx { unsafe { tree.as_mut() } } - pub(crate) fn alloc(&mut self, node: Box) -> WidgetId { - new_node(&mut self.tree_mut().arena, node) - } + pub(crate) fn tree_ptr(&self) -> *mut WidgetTree { self.tree.as_ptr() } pub(crate) fn build(&mut self, widget: Widget<'_>) -> WidgetId { let size = self.children.len(); @@ -81,18 +61,6 @@ impl BuildCtx { root } - pub(crate) fn build_with_provider( - &mut self, widget: Widget<'_>, provider: Box, - ) -> WidgetId { - // We push the provider into the build context to ensure that the widget build - // logic can access this provider. - self.current_providers.push(provider); - let id = self.build(widget.into_widget()); - let provider = self.current_providers.pop().unwrap(); - // Attach the provider to the widget so its descendants can access it. - id.attach_data(provider, self.tree_mut()); - id - } pub(crate) fn build_parent(&mut self, parent: Widget<'_>, children: Vec>) -> WidgetId { let root = self.build(parent); let p = root.single_leaf(self.tree_mut()); @@ -150,13 +118,9 @@ impl BuildCtx { pub(crate) fn set_for(startup: WidgetId, tree: NonNull) { let t = unsafe { tree.as_ref() }; - let mut providers: SmallVec<[WidgetId; 1]> = startup - .ancestors(t) - .filter(|id| id.queryable(t)) - .collect(); - providers.reverse(); - let ctx = - BuildCtx { tree, providers, current_providers: <_>::default(), children: <_>::default() }; + let provider_ctx = ProviderCtx::collect_from(startup, t); + let ctx = BuildCtx { tree, children: <_>::default(), provider_ctx }; + BuildCtx::set(ctx); } @@ -168,6 +132,10 @@ impl BuildCtx { } unsafe { CTX = None } } + + pub(crate) fn empty(tree: NonNull) -> Self { + Self { tree, children: <_>::default(), provider_ctx: <_>::default() } + } } pub(crate) struct BuildCtxInitdGuard; @@ -175,3 +143,11 @@ pub(crate) struct BuildCtxInitdGuard; impl Drop for BuildCtxInitdGuard { fn drop(&mut self) { BuildCtx::clear() } } + +impl AsRef for BuildCtx { + fn as_ref(&self) -> &ProviderCtx { &self.provider_ctx } +} + +impl AsMut for BuildCtx { + fn as_mut(&mut self) -> &mut ProviderCtx { &mut self.provider_ctx } +} diff --git a/core/src/context/layout_ctx.rs b/core/src/context/layout_ctx.rs index 066a1158b..7d076cc8a 100644 --- a/core/src/context/layout_ctx.rs +++ b/core/src/context/layout_ctx.rs @@ -1,8 +1,8 @@ use ribir_geom::{Point, Size}; -use ribir_painter::{PaintingStyle, TextStyle}; use super::{WidgetCtx, WidgetCtxImpl}; use crate::{ + prelude::ProviderCtx, widget::{BoxClamp, WidgetTree}, widget_tree::WidgetId, window::DelayEvent, @@ -14,12 +14,11 @@ use crate::{ /// `LayoutCtx`. `LayoutCtx` provide method to perform child layout and also /// provides methods to update descendants position. pub struct LayoutCtx<'a> { - pub(crate) id: WidgetId, + id: WidgetId, /// The widget tree of the window, not borrow it from `wnd` is because a /// `LayoutCtx` always in a mutable borrow. - pub(crate) tree: &'a mut WidgetTree, - painting_style: PaintingStyle, - text_style: TextStyle, + tree: &'a mut WidgetTree, + provider_ctx: ProviderCtx, } impl<'a> WidgetCtxImpl for LayoutCtx<'a> { @@ -32,17 +31,13 @@ impl<'a> WidgetCtxImpl for LayoutCtx<'a> { impl<'a> LayoutCtx<'a> { pub(crate) fn new(id: WidgetId, tree: &'a mut WidgetTree) -> Self { - let painting_style = if let Some(style) = id.query_ancestors_ref::(tree) { - style.clone() + let provider_ctx = if let Some(p) = id.parent(tree) { + ProviderCtx::collect_from(p, tree) } else { - PaintingStyle::Fill + ProviderCtx::default() }; - let text_style = id - .query_ancestors_ref::(tree) - .unwrap() - .clone(); - Self { id, tree, painting_style, text_style } + Self { id, tree, provider_ctx } } /// Perform layout of the widget of the context and return its size. @@ -68,7 +63,7 @@ impl<'a> LayoutCtx<'a> { /// Perform layout of the `child` and return its size. pub fn perform_child_layout(&mut self, child: WidgetId, clamp: BoxClamp) -> Size { let info = self.tree.store.layout_info(child); - info + let size = info .filter(|info| info.clamp == clamp) .and_then(|info| info.size) .unwrap_or_else(|| { @@ -81,7 +76,9 @@ impl<'a> LayoutCtx<'a> { self.id = id; size - }) + }); + self.provider_ctx.pop_providers_for(child); + size } /// Adjust the position of the widget where it should be placed relative to @@ -149,18 +146,12 @@ impl<'a> LayoutCtx<'a> { assert_eq!(child.parent(self.tree), Some(self.id)); self.tree.store.force_layout(child).is_some() } +} - pub fn set_painting_style(&mut self, style: PaintingStyle) -> PaintingStyle { - std::mem::replace(&mut self.painting_style, style) - } - - pub fn painting_style(&self) -> &PaintingStyle { &self.painting_style } - - pub fn set_text_style(&mut self, style: TextStyle) -> TextStyle { - std::mem::replace(&mut self.text_style, style) - } - - pub fn text_style(&self) -> &TextStyle { &self.text_style } +impl<'w> AsRef for LayoutCtx<'w> { + fn as_ref(&self) -> &ProviderCtx { &self.provider_ctx } +} - pub fn text_style_mut(&mut self) -> &mut TextStyle { &mut self.text_style } +impl<'w> AsMut for LayoutCtx<'w> { + fn as_mut(&mut self) -> &mut ProviderCtx { &mut self.provider_ctx } } diff --git a/core/src/context/painting_ctx.rs b/core/src/context/painting_ctx.rs index 53b88aabb..9ac24a8db 100644 --- a/core/src/context/painting_ctx.rs +++ b/core/src/context/painting_ctx.rs @@ -1,13 +1,14 @@ use super::WidgetCtxImpl; use crate::{ - prelude::{Painter, WidgetId}, + prelude::{Painter, ProviderCtx, WidgetId}, widget::WidgetTree, }; pub struct PaintingCtx<'a> { - pub(crate) id: WidgetId, - pub(crate) tree: &'a WidgetTree, - pub(crate) painter: &'a mut Painter, + id: WidgetId, + tree: &'a WidgetTree, + painter: &'a mut Painter, + provider_ctx: ProviderCtx, } impl<'a> WidgetCtxImpl for PaintingCtx<'a> { @@ -20,9 +21,31 @@ impl<'a> WidgetCtxImpl for PaintingCtx<'a> { impl<'a> PaintingCtx<'a> { pub(crate) fn new(id: WidgetId, tree: &'a WidgetTree, painter: &'a mut Painter) -> Self { - Self { id, tree, painter } + let provider_ctx = if let Some(p) = id.parent(tree) { + ProviderCtx::collect_from(p, tree) + } else { + ProviderCtx::default() + }; + + Self { id, tree, painter, provider_ctx } } + + /// Called by the framework when the painting widget is finished. + pub(crate) fn finish(&mut self) { self.provider_ctx.pop_providers_for(self.id); } + + pub(crate) fn switch_to(&mut self, id: WidgetId) { self.id = id; } + /// Return the 2d painter to draw 2d things. #[inline] pub fn painter(&mut self) -> &mut Painter { self.painter } } + +impl<'w> AsRef for PaintingCtx<'w> { + #[inline] + fn as_ref(&self) -> &ProviderCtx { &self.provider_ctx } +} + +impl<'w> AsMut for PaintingCtx<'w> { + #[inline] + fn as_mut(&mut self) -> &mut ProviderCtx { &mut self.provider_ctx } +} diff --git a/core/src/context/widget_ctx.rs b/core/src/context/widget_ctx.rs index 18cd7efdf..822de9f81 100644 --- a/core/src/context/widget_ctx.rs +++ b/core/src/context/widget_ctx.rs @@ -4,6 +4,7 @@ use ribir_algo::Sc; use ribir_geom::{Point, Rect, Size}; use crate::{ + prelude::ProviderCtx, query::QueryRef, state::WriteRef, widget::{BoxClamp, WidgetTree}, @@ -96,6 +97,13 @@ pub(crate) trait WidgetCtxImpl { fn id(&self) -> WidgetId; fn tree(&self) -> &WidgetTree; + + fn split_tree(&mut self) -> (&mut Self, &WidgetTree) { + // Safety: The widget tree remains read-only throughout the entire hit testing + // process. + let tree = unsafe { &*(self.tree() as *const WidgetTree) }; + (self, tree) + } } impl WidgetCtx for T { @@ -202,38 +210,44 @@ impl WidgetCtx for T { fn window(&self) -> Sc { self.tree().window() } } -macro_rules! define_widget_context { - ( - $(#[$outer:meta])* - $name: ident $(, $extra_name: ident: $extra_ty: ty)* - ) => { - $(#[$outer])* - pub struct $name { - pub(crate) id: WidgetId, - pub(crate) tree: NonNull, - $(pub(crate) $extra_name: $extra_ty,)* - } +pub struct HitTestCtx { + id: WidgetId, + tree: NonNull, + provider_ctx: ProviderCtx, +} - impl WidgetCtxImpl for $name { - #[inline] - fn id(&self) -> WidgetId { self.id } +impl WidgetCtxImpl for HitTestCtx { + #[inline] + fn id(&self) -> WidgetId { self.id } - fn tree(&self) -> &WidgetTree { - unsafe {self.tree.as_ref()} - } - } - }; + fn tree(&self) -> &WidgetTree { unsafe { self.tree.as_ref() } } } -pub(crate) use define_widget_context; - -define_widget_context!(HitTestCtx); impl HitTestCtx { + pub(crate) fn new(tree: NonNull) -> Self { + let id = unsafe { tree.as_ref() }.root(); + let provider_ctx = ProviderCtx::default(); + Self { id, tree, provider_ctx } + } + pub fn box_hit_test(&self, pos: Point) -> bool { self .box_rect() .is_some_and(|rect| rect.contains(pos)) } + + pub(crate) fn set_id(&mut self, id: WidgetId) { self.id = id; } + + /// Method to be called when the hit test of the subtree is finished. + pub(crate) fn finish(&mut self) { self.provider_ctx.pop_providers_for(self.id); } +} + +impl AsRef for HitTestCtx { + fn as_ref(&self) -> &ProviderCtx { &self.provider_ctx } +} + +impl AsMut for HitTestCtx { + fn as_mut(&mut self) -> &mut ProviderCtx { &mut self.provider_ctx } } #[cfg(test)] @@ -241,8 +255,6 @@ mod tests { use super::*; use crate::{prelude::*, reset_test_env, test_helper::*}; - define_widget_context!(TestCtx); - #[test] fn map_self_eq_self() { reset_test_env!(); @@ -261,7 +273,9 @@ mod tests { let pos = Point::zero(); let child = root.single_child(tree).unwrap(); - let w_ctx = TestCtx { id: child, tree: wnd.tree }; + let mut w_ctx = HitTestCtx::new(wnd.tree); + w_ctx.set_id(child); + assert_eq!(w_ctx.map_from(pos, child), pos); assert_eq!(w_ctx.map_to(pos, child), pos); } @@ -286,7 +300,7 @@ mod tests { let root = wnd.tree().root(); let child = get_single_child_by_depth(root, wnd.tree(), 2); - let w_ctx = TestCtx { id: root, tree: wnd.tree }; + let w_ctx = HitTestCtx::new(wnd.tree); let from_pos = Point::new(30., 30.); assert_eq!(w_ctx.map_from(from_pos, child), Point::new(45., 45.)); let to_pos = Point::new(50., 50.); diff --git a/core/src/data_widget.rs b/core/src/data_widget.rs index 8eab2b480..3c034fd7c 100644 --- a/core/src/data_widget.rs +++ b/core/src/data_widget.rs @@ -65,15 +65,6 @@ impl Query for DataAttacher { } fn queryable(&self) -> bool { true } - - fn query_match( - &self, ids: &[QueryId], filter: &dyn Fn(&QueryId, &QueryHandle) -> bool, - ) -> Option<(QueryId, QueryHandle)> { - self - .render - .query_match(ids, filter) - .or_else(|| self.data.query_match(ids, filter)) - } } impl Query for AnonymousAttacher { @@ -94,12 +85,6 @@ impl Query for AnonymousAttacher { } fn queryable(&self) -> bool { self.render.queryable() } - - fn query_match( - &self, ids: &[QueryId], filter: &dyn Fn(&QueryId, &QueryHandle) -> bool, - ) -> Option<(QueryId, QueryHandle)> { - self.render.query_match(ids, filter) - } } impl RenderProxy for AnonymousAttacher { diff --git a/core/src/events.rs b/core/src/events.rs index 7565a8d25..29354cbc0 100644 --- a/core/src/events.rs +++ b/core/src/events.rs @@ -3,7 +3,9 @@ use std::ptr::NonNull; use self::dispatcher::DispatchInfo; use crate::{ builtin_widgets::MixFlags, - context::{WidgetCtx, WidgetCtxImpl, define_widget_context}, + context::{WidgetCtx, WidgetCtxImpl}, + prelude::ProviderCtx, + query::QueryHandle, widget_tree::{WidgetId, WidgetTree}, }; @@ -17,6 +19,7 @@ pub use keyboard::*; mod character; pub use character::*; mod wheel; +use smallvec::SmallVec; pub use wheel::*; mod ime_pre_edit; pub use ime_pre_edit::*; @@ -26,12 +29,16 @@ pub use lifecycle::*; pub(crate) mod focus_mgr; mod listener_impl_helper; -define_widget_context!( - CommonEvent, +pub struct CommonEvent { + pub(crate) id: WidgetId, + // The framework guarantees the validity of this pointer at all times; therefore, refrain from + // using a reference to prevent introducing lifetimes in `CommonEvent` and maintain cleaner code. + tree: NonNull, + provider_ctx: ProviderCtx, target: WidgetId, propagation: bool, - prevent_default: bool -); + prevent_default: bool, +} pub type FocusEvent = CommonEvent; pub type FocusBubbleEvent = CommonEvent; @@ -121,6 +128,7 @@ pub enum Event { PointerUpCapture(PointerEvent), PointerMove(PointerEvent), PointerMoveCapture(PointerEvent), + PointerCancelCapture(PointerEvent), PointerCancel(PointerEvent), PointerEnter(PointerEvent), PointerLeave(PointerEvent), @@ -189,6 +197,7 @@ impl std::ops::Deref for Event { | Event::PointerUpCapture(e) | Event::PointerMove(e) | Event::PointerMoveCapture(e) + | Event::PointerCancelCapture(e) | Event::PointerCancel(e) | Event::PointerEnter(e) | Event::PointerLeave(e) @@ -221,6 +230,7 @@ impl std::ops::DerefMut for Event { | Event::PointerMove(e) | Event::PointerMoveCapture(e) | Event::PointerCancel(e) + | Event::PointerCancelCapture(e) | Event::PointerEnter(e) | Event::PointerLeave(e) | Event::Tap(e) @@ -244,6 +254,7 @@ impl Event { | Event::PointerMove(_) | Event::PointerMoveCapture(_) | Event::PointerCancel(_) + | Event::PointerCancelCapture(_) | Event::PointerEnter(_) | Event::PointerLeave(_) | Event::Tap(_) @@ -283,12 +294,45 @@ impl CommonEvent { /// it because in most case the event create in a environment that the /// `Dispatcher` already borrowed. pub(crate) fn new(target: WidgetId, tree: NonNull) -> Self { - Self { target, tree, id: target, propagation: true, prevent_default: false } + Self { + target, + id: target, + propagation: true, + prevent_default: false, + provider_ctx: ProviderCtx::collect_from(target, unsafe { tree.as_ref() }), + tree, + } } - pub(crate) fn set_current_target(&mut self, id: WidgetId) { self.id = id; } + pub(crate) fn bubble_to_parent(&mut self, id: WidgetId) { + self.id = id; + self.provider_ctx.pop_providers_for(id); + } + + pub(crate) fn capture_to_child(&mut self, id: WidgetId, buffer: &mut SmallVec<[QueryHandle; 1]>) { + let tree = unsafe { self.tree.as_ref() }; + self + .provider_ctx + .push_providers_for(id, tree, buffer); + self.id = id; + } fn pick_info(&self, f: impl FnOnce(&DispatchInfo) -> R) -> R { f(&self.window().dispatcher.borrow().info) } } + +impl WidgetCtxImpl for CommonEvent { + #[inline] + fn id(&self) -> WidgetId { self.id } + + fn tree(&self) -> &WidgetTree { unsafe { self.tree.as_ref() } } +} + +impl AsRef for CommonEvent { + fn as_ref(&self) -> &ProviderCtx { &self.provider_ctx } +} + +impl AsMut for CommonEvent { + fn as_mut(&mut self) -> &mut ProviderCtx { &mut self.provider_ctx } +} diff --git a/core/src/events/dispatcher.rs b/core/src/events/dispatcher.rs index 03d1c44b6..ced32b68b 100644 --- a/core/src/events/dispatcher.rs +++ b/core/src/events/dispatcher.rs @@ -93,13 +93,10 @@ impl Dispatcher { state: ElementState, ) { let wnd = self.window(); - if let Some(focus_id) = wnd.focusing() { - let event = KeyboardEvent::new(&wnd, focus_id, physical_key, key, is_repeat, location); - match state { - ElementState::Pressed => wnd.add_delay_event(DelayEvent::KeyDown(event)), - ElementState::Released => wnd.add_delay_event(DelayEvent::KeyUp(event)), - }; - } else if key == VirtualKey::Named(NamedKey::Tab) { + if let Some(id) = wnd.focusing() { + let e = DelayEvent::KeyBoard { key, state, physical_key, is_repeat, location, id }; + wnd.add_delay_event(e); + } else if key == VirtualKey::Named(NamedKey::Tab) && state == ElementState::Pressed { wnd.add_delay_event(DelayEvent::TabFocusMove); } } @@ -163,8 +160,9 @@ impl Dispatcher { .window() .add_delay_event(DelayEvent::GrabPointerMove(grab_pointer)); } else { - self.pointer_enter_leave_dispatch(); - if let Some(hit) = self.hit_widget() { + let new_hit = self.hit_widget(); + self.pointer_enter_leave_dispatch(new_hit); + if let Some(hit) = new_hit { self .window() .add_delay_event(DelayEvent::PointerMove(hit)); @@ -175,7 +173,7 @@ impl Dispatcher { pub fn on_cursor_left(&mut self) { if self.grab_mouse_wid.borrow().is_none() { self.info.cursor_pos = Point::new(-1., -1.); - self.pointer_enter_leave_dispatch(); + self.pointer_enter_leave_dispatch(self.hit_widget()); } } @@ -189,7 +187,23 @@ impl Dispatcher { self.info.mouse_button.1 |= button.into(); // only the first button press emit event. if self.info.mouse_button.1 == button.into() { - self.bubble_pointer_down(); + let hit = self.hit_widget(); + let wnd = self.window(); + let tree = wnd.tree(); + + let nearest_focus = hit.and_then(|wid| { + wid.ancestors(tree).find(|id| { + id.query_all_iter::(tree) + .any(|m| m.contain_flag(MixFlags::Focus)) + }) + }); + if let Some(focus_id) = nearest_focus { + wnd.focus_mgr.borrow_mut().focus(focus_id, tree); + } else { + wnd.focus_mgr.borrow_mut().blur(tree); + } + + self.cursor_press_down(hit); } } ElementState::Released => { @@ -222,28 +236,7 @@ impl Dispatcher { } } - fn bubble_pointer_down(&mut self) { - let hit = self.hit_widget(); - let wnd = self.window(); - let tree = wnd.tree(); - - let nearest_focus = hit.and_then(|wid| { - wid.ancestors(tree).find(|id| { - id.query_all_iter::(tree) - .any(|m| m.contain_flag(MixFlags::Focus)) - }) - }); - if let Some(focus_id) = nearest_focus { - wnd.focus_mgr.borrow_mut().focus(focus_id, tree); - } else { - wnd.focus_mgr.borrow_mut().blur(tree); - } - - self.cursor_press_down(hit); - } - - fn pointer_enter_leave_dispatch(&mut self) { - let new_hit = self.hit_widget(); + fn pointer_enter_leave_dispatch(&mut self, new_hit: Option) { let wnd = self.window(); let tree = wnd.tree(); @@ -267,52 +260,56 @@ impl Dispatcher { } fn hit_widget(&self) -> Option { - let mut hit_target = None; - let wnd = self.window(); - let tree = wnd.tree(); - - let mut w = Some(tree.root()); - let mut pos = self.info.cursor_pos; - while let Some(id) = w { - let r = id.assert_get(tree); - let ctx = HitTestCtx { id, tree: wnd.tree }; - let HitTest { hit, can_hit_child } = r.hit_test(&ctx, pos); + fn deepest_test(ctx: &mut HitTestCtx, pos: &mut Point) -> Option { + // Safety: The widget tree remains read-only throughout the entire hit testing + // process. + let tree = unsafe { &*(ctx.tree() as *const WidgetTree) }; + let mut hit_target = None; + loop { + let id = ctx.id(); + let r = id.assert_get(tree); + let HitTest { hit, can_hit_child } = r.hit_test(ctx, *pos); + + if hit { + hit_target = Some(id); + } - pos = tree.map_from_parent(id, pos); + if hit || can_hit_child { + if let Some(c) = id.last_child(tree) { + *pos = ctx.map_from_parent(*pos); + ctx.set_id(c); + continue; + } + } - if hit { - hit_target = w; + break; } - w = id - .last_child(tree) - .filter(|_| can_hit_child) - .or_else(|| { - if hit { - return None; - } - - pos = tree.map_to_parent(id, pos); + hit_target + } - let mut node = w; - while let Some(p) = node { - node = p.previous_sibling(tree); - if node.is_some() { - break; - } else { - node = p.parent(tree); - - if let Some(id) = node { - pos = tree.map_to_parent(id, pos); - if node == hit_target { - node = None; - } - } - } - } - node - }); + let mut ctx = HitTestCtx::new(self.window().tree); + let mut pos = self.info.cursor_pos; + let mut hit_target = deepest_test(&mut ctx, &mut pos); + + let (ctx, tree) = ctx.split_tree(); + while hit_target.is_some() && Some(ctx.id()) != hit_target { + ctx.finish(); + let id = ctx.id(); + if let Some(sibling) = id.previous_sibling(tree) { + ctx.set_id(sibling); + if let Some(hit) = deepest_test(ctx, &mut pos) { + hit_target = Some(hit); + } + } else if let Some(p) = id.parent(tree) { + ctx.finish(); + ctx.set_id(p); + pos = ctx.map_to_parent(pos); + } else { + break; + } } + hit_target } } diff --git a/core/src/events/ime_pre_edit.rs b/core/src/events/ime_pre_edit.rs index 45109c394..e1dba1a26 100644 --- a/core/src/events/ime_pre_edit.rs +++ b/core/src/events/ime_pre_edit.rs @@ -1,6 +1,6 @@ use crate::{impl_common_event_deref, prelude::*}; -#[derive(Debug)] +#[derive(Debug, Clone)] pub enum ImePreEdit { /// Notifies when the IME PreEdit begin a new round. /// diff --git a/core/src/events/listener_impl_helper.rs b/core/src/events/listener_impl_helper.rs index 9a468d102..5ea39418f 100644 --- a/core/src/events/listener_impl_helper.rs +++ b/core/src/events/listener_impl_helper.rs @@ -22,5 +22,13 @@ macro_rules! impl_common_event_deref { #[inline] fn borrow_mut(&mut self) -> &mut CommonEvent { &mut self.common } } + + impl AsRef<$crate::prelude::ProviderCtx> for $event_name { + fn as_ref(&self) -> &$crate::prelude::ProviderCtx { self.common.as_ref() } + } + + impl AsMut<$crate::prelude::ProviderCtx> for $event_name { + fn as_mut(&mut self) -> &mut $crate::prelude::ProviderCtx { self.common.as_mut() } + } }; } diff --git a/core/src/overlay.rs b/core/src/overlay.rs index b8917dd68..2441da564 100644 --- a/core/src/overlay.rs +++ b/core/src/overlay.rs @@ -182,15 +182,12 @@ impl Overlay { showing_overlays.remove(self); if let Some(wid) = track_id.and_then(|track_id| track_id.get()) { - AppCtx::frame_ticks() - .clone() - .take(1) - .subscribe(move |_| { - let tree = wnd.tree_mut(); - let root = tree.root(); - wid.dispose_subtree(tree); - tree.dirty_marker().mark(root); - }); + AppCtx::once_next_frame(move |_| { + let tree = wnd.tree_mut(); + let root = tree.root(); + wid.dispose_subtree(tree); + tree.dirty_marker().mark(root); + }); } } } diff --git a/core/src/pipe.rs b/core/src/pipe.rs index 64e95b18e..3b3957096 100644 --- a/core/src/pipe.rs +++ b/core/src/pipe.rs @@ -1,5 +1,5 @@ use std::{ - cell::{Cell, RefCell, UnsafeCell}, + cell::{Cell, UnsafeCell}, convert::Infallible, ops::RangeInclusive, ptr::NonNull, @@ -48,13 +48,14 @@ pub trait Pipe: 'static { /// /// - *scope*: specifies the scope of the modifications to be emitted by the /// stream. - /// - *updater*: If this is a pipe widget, an updater needs to be provided. + /// - *init*: If this is a pipe widget, an `PipeWidgetBuildInit` needs to be + /// provided to init the build context. fn unzip( - self, scope: ModifyScope, updater: Option, + self, scope: ModifyScope, init: Option, ) -> (Self::Value, ValueStream); fn box_unzip( - self: Box, scope: ModifyScope, priority: Option, + self: Box, scope: ModifyScope, priority: Option, ) -> (Self::Value, ValueStream); } @@ -97,31 +98,29 @@ pub(crate) trait InnerPipe: Pipe + Sized { Self::Value: IntoWidget<'static, M>, { let f = move || { - let ctx = BuildCtx::get(); - let info = DynWidgetsInfo::new(GenRange::Single(ctx.tree().root())); + let pipe_node = PipeNode::empty_node(); - let updater = PipeWidgetUpdater::new_with_tree(info.clone(), BuildCtx::get().tree.as_ptr()); - let (w, modifies) = self.unzip(ModifyScope::FRAMEWORK, Some(updater)); + let tree_ptr = BuildCtx::get().tree_ptr(); + let init = PipeWidgetBuildInit::new_with_tree(pipe_node.clone(), tree_ptr); + let (w, modifies) = self.unzip(ModifyScope::FRAMEWORK, Some(init)); + w.into_widget().on_build(move |w| { + pipe_node.init_for_single(w); - w.into_widget().on_build(|w| { - info - .borrow_mut() - .set_gen_range(GenRange::Single(w)); - - let pipe_node = PipeNode::share_capture(w, info); let c_pipe_node = pipe_node.clone(); let u = modifies.subscribe(move |(_, w)| { - let info = pipe_node.dyn_info(); - let old = info.borrow().host_id(); - + let old = pipe_node.dyn_info().host_id(); + let old_node = pipe_node.take_data(); + let without_ctx = BuildCtx::try_get().is_none(); + if without_ctx { + BuildCtx::set_for(old, unsafe { NonNull::new_unchecked(tree_ptr) }); + } let ctx = BuildCtx::get_mut(); - let old_node = pipe_node.remove_old_data(); let new = ctx.build(w.into_widget()); let tree = ctx.tree_mut(); pipe_node.transplant_to_new(old_node, new, tree); - query_outside_infos(new, &info, tree) - .for_each(|info| info.borrow_mut().single_replace(old, new)); + query_outside_infos(new, &pipe_node, tree) + .for_each(|node| node.dyn_info_mut().single_replace(old, new)); update_key_status_single(old, new, tree); old.insert_after(new, tree); @@ -129,10 +128,12 @@ pub(crate) trait InnerPipe: Pipe + Sized { new.on_mounted_subtree(tree); tree.dirty_marker().mark(new); - // The context initialized by `PipeWidgetUpdater` must be cleared. - BuildCtx::clear(); + + if without_ctx { + BuildCtx::clear(); + } }); - c_pipe_node.own_subscription(u); + c_pipe_node.attach_subscription(u); }) }; f.into_widget() @@ -143,37 +144,38 @@ pub(crate) trait InnerPipe: Pipe + Sized { Self::Value: IntoIterator, ::Item: IntoWidget<'static, M>, { - let info = DynWidgetsInfo::new(GenRange::Multi(vec![])); - - let mut updater = PipeWidgetUpdater::new(info.clone()); - let (m, modifies) = self.unzip(ModifyScope::FRAMEWORK, Some(updater.clone())); + let node = PipeNode::empty_node(); + let mut init = PipeWidgetBuildInit::new(node.clone()); + let (m, modifies) = self.unzip(ModifyScope::FRAMEWORK, Some(init.clone())); let mut iter = m.into_iter(); let first = iter .next() .map_or_else(|| Void.into_widget(), IntoWidget::into_widget); - let info2 = info.clone(); + let pipe_node = node.clone(); let first = first.into_widget().on_build(move |id| { - match &mut info2.borrow_mut().gen_range { - GenRange::Multi(m) => m.push(id), - _ => unreachable!(), - }; + pipe_node.init(id, GenRange::Multi(vec![id])); + let tree_ptr = BuildCtx::get().tree_ptr(); + init.set_tree(tree_ptr); - updater.set_tree(BuildCtx::get().tree.as_ptr()); - let pipe_node = PipeNode::share_capture(id, info2); let c_pipe_node = pipe_node.clone(); let u = modifies.subscribe(move |(_, m)| { - let info = pipe_node.dyn_info(); - let old = match &info.borrow().gen_range { + let old = match &pipe_node.dyn_info().gen_range { GenRange::Multi(m) => m.clone(), _ => unreachable!(), }; - let ctx = BuildCtx::get_mut(); + let old_node = pipe_node.take_data(); + let without_ctx = BuildCtx::try_get().is_none(); + if without_ctx { + BuildCtx::set_for(pipe_node.dyn_info().host_id(), unsafe { + NonNull::new_unchecked(tree_ptr) + }); + } - let old_node = pipe_node.remove_old_data(); + let ctx = BuildCtx::get_mut(); let mut new = vec![]; for (idx, w) in m.into_iter().enumerate() { let id = ctx.build(w.into_widget()); @@ -187,8 +189,8 @@ pub(crate) trait InnerPipe: Pipe + Sized { let tree = ctx.tree_mut(); pipe_node.transplant_to_new(old_node, new[0], tree); - query_outside_infos(new[0], &info, tree) - .for_each(|info| info.borrow_mut().multi_replace(&old, &new)); + query_outside_infos(new[0], &pipe_node, tree) + .for_each(|node| node.dyn_info_mut().multi_replace(&old, &new)); update_key_state_multi(old.iter().copied(), new.iter().copied(), tree); @@ -202,18 +204,19 @@ pub(crate) trait InnerPipe: Pipe + Sized { tree.dirty_marker().mark(*w); }); - // The context initialized by `PipeWidgetUpdater` must be cleared. - BuildCtx::clear(); + if without_ctx { + BuildCtx::clear(); + } }); - c_pipe_node.own_subscription(u); + c_pipe_node.attach_subscription(u); }); let mut widgets = vec![first]; for (idx, w) in iter.enumerate() { - let info = info.clone(); + let pipe_node = node.clone(); let w = w.into_widget().on_build(move |id| { - match &mut info.borrow_mut().gen_range { + match &mut pipe_node.dyn_info_mut().gen_range { GenRange::Multi(m) => m.push(id), _ => unreachable!(), }; @@ -223,7 +226,7 @@ pub(crate) trait InnerPipe: Pipe + Sized { // 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. - id.attach_data(Box::new(Queryable(info.clone())), tree); + id.attach_data(Box::new(pipe_node), tree); } }); @@ -239,37 +242,38 @@ pub(crate) trait InnerPipe: Pipe + Sized { Self::Value: IntoWidget<'static, M>, { let f = move || { - let ctx = BuildCtx::get(); - let root = ctx.tree().root(); - let info = DynWidgetsInfo::new(GenRange::ParentOnly(root..=root)); + let node = PipeNode::empty_node(); + let init = PipeWidgetBuildInit::new_with_tree(node.clone(), BuildCtx::get().tree_ptr()); + let (w, modifies) = self.unzip(ModifyScope::FRAMEWORK, Some(init)); - let updater = PipeWidgetUpdater::new_with_tree(info.clone(), ctx.tree.as_ptr()); - let (w, modifies) = self.unzip(ModifyScope::FRAMEWORK, Some(updater)); - - w.into_widget().on_build(|p| { - let pipe_node = PipeNode::share_capture(p, info.clone()); - let tree = BuildCtx::get_mut().tree_mut(); + w.into_widget().on_build(move |p| { + let tree: &mut WidgetTree = BuildCtx::get_mut().tree_mut(); let leaf = p.single_leaf(tree); - info - .borrow_mut() - .set_gen_range(GenRange::ParentOnly(p..=leaf)); + node.init(p, GenRange::ParentOnly(p..=leaf)); // 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. - if leaf.contain_type::(tree) { - leaf.attach_data(Box::new(Queryable(info)), tree); + if leaf.contain_type::(tree) { + leaf.attach_data(Box::new(node.clone()), tree); }; - let c_pipe_node = pipe_node.clone(); + let pipe_node = node.clone(); let u = modifies.subscribe(move |(_, w)| { - let info = pipe_node.dyn_info(); - let (top, bottom) = match &info.borrow().gen_range { + let (top, bottom) = match &pipe_node.dyn_info().gen_range { GenRange::ParentOnly(p) => p.clone().into_inner(), _ => unreachable!(), }; - let old_node = pipe_node.remove_old_data(); + let old_node = pipe_node.take_data(); + + let without_ctx = BuildCtx::try_get().is_none(); + if without_ctx { + BuildCtx::set_for(pipe_node.dyn_info().host_id(), unsafe { + NonNull::new_unchecked(tree) + }); + } + let p = BuildCtx::get_mut().build(w.into_widget()); let tree = BuildCtx::get_mut().tree_mut(); pipe_node.transplant_to_new(old_node, p, tree); @@ -280,9 +284,9 @@ pub(crate) trait InnerPipe: Pipe + Sized { new_rg.end().append(c, tree); } - query_outside_infos(p, &info, tree).for_each(|info| { - info - .borrow_mut() + query_outside_infos(p, &pipe_node, tree).for_each(|node| { + node + .dyn_info_mut() .single_range_replace(&(top..=bottom), &new_rg); }); @@ -304,11 +308,13 @@ pub(crate) trait InnerPipe: Pipe + Sized { } tree.dirty_marker().mark(p); - // The context initialized by `PipeWidgetUpdater` must be cleared. - BuildCtx::clear(); + + if without_ctx { + BuildCtx::clear(); + } }); - c_pipe_node.own_subscription(u); + node.attach_subscription(u); }) }; f.into_widget() @@ -332,20 +338,19 @@ impl Pipe for ModifiesPipe { #[inline] fn unzip( - self, scope: ModifyScope, updater: Option, + self, scope: ModifyScope, init: Option, ) -> (Self::Value, ValueStream) { let stream = self .0 .filter(move |s| s.contains(scope)) .map(|s| (s, s)); - let stream = if let Some(updater) = updater { - let build_init = updater.build_init_fn(); - stream + let stream = if let Some(init) = init { + let source = stream .sample(AppCtx::frame_ticks().clone()) - .priority(updater) - .tap(move |_| build_init()) - .box_it() + .priority(init.clone()); + + PipeWidgetContextOp { source, init }.box_it() } else { stream.box_it() }; @@ -355,7 +360,7 @@ impl Pipe for ModifiesPipe { #[inline] fn box_unzip( - self: Box, scope: ModifyScope, updater: Option, + self: Box, scope: ModifyScope, updater: Option, ) -> (Self::Value, ValueStream) { (*self).unzip(scope, updater) } @@ -366,16 +371,16 @@ impl Pipe for Box> { #[inline] fn unzip( - self, scope: ModifyScope, updater: Option, + self, scope: ModifyScope, init: Option, ) -> (Self::Value, ValueStream) { - self.box_unzip(scope, updater) + self.box_unzip(scope, init) } #[inline] fn box_unzip( - self: Box, scope: ModifyScope, updater: Option, + self: Box, scope: ModifyScope, init: Option, ) -> (Self::Value, ValueStream) { - (*self).box_unzip(scope, updater) + (*self).box_unzip(scope, init) } } @@ -388,18 +393,18 @@ where type Value = V; fn unzip( - self, scope: ModifyScope, updater: Option, + self, scope: ModifyScope, init: Option, ) -> (Self::Value, ValueStream) { let Self { source, mut f, .. } = self; - let (v, stream) = source.unzip(scope, updater); + let (v, stream) = source.unzip(scope, init); (f(v), stream.map(move |(s, v)| (s, f(v))).box_it()) } #[inline] fn box_unzip( - self: Box, scope: ModifyScope, updater: Option, + self: Box, scope: ModifyScope, init: Option, ) -> (Self::Value, ValueStream) { - (*self).unzip(scope, updater) + (*self).unzip(scope, init) } } @@ -412,18 +417,18 @@ where type Value = V; fn unzip( - self, scope: ModifyScope, updater: Option, + self, scope: ModifyScope, init: Option, ) -> (Self::Value, ValueStream) { let Self { source, f, .. } = self; - let (v, stream) = source.unzip(scope, updater); + let (v, stream) = source.unzip(scope, init); (v, f(stream)) } #[inline] fn box_unzip( - self: Box, scope: ModifyScope, updater: Option, + self: Box, scope: ModifyScope, init: Option, ) -> (Self::Value, ValueStream) { - (*self).unzip(scope, updater) + (*self).unzip(scope, init) } } @@ -435,14 +440,14 @@ impl Pipe for ValuePipe { #[inline] fn unzip( - self, _: ModifyScope, _: Option, + self, _: ModifyScope, _: Option, ) -> (Self::Value, ValueStream) { (self.0, observable::empty().box_it()) } #[inline] fn box_unzip( - self: Box, scope: ModifyScope, priority: Option, + self: Box, scope: ModifyScope, priority: Option, ) -> (Self::Value, ValueStream) { self.unzip(scope, priority) } @@ -606,11 +611,11 @@ fn update_key_states( /// the pipe to the first node, and user can attach `key` or `listener` to the /// widget after `Pipe::build_widget` call. #[derive(Clone)] -struct PipeNode(Sc>); +pub(crate) struct PipeNode(Sc>); struct InnerPipeNode { data: Box, - dyn_info: DynInfo, + dyn_info: DynWidgetsInfo, } #[derive(Debug)] @@ -619,8 +624,6 @@ pub(crate) struct DynWidgetsInfo { pub(crate) gen_range: GenRange, } -pub(crate) type DynInfo = Sc>; - #[derive(Debug)] pub enum GenRange { Single(WidgetId), @@ -629,12 +632,10 @@ pub enum GenRange { } impl DynWidgetsInfo { - pub(crate) fn new(range: GenRange) -> DynInfo { - Sc::new(RefCell::new(DynWidgetsInfo { gen_range: range, multi_pos: 0 })) + pub(crate) fn new(range: GenRange) -> DynWidgetsInfo { + DynWidgetsInfo { gen_range: range, multi_pos: 0 } } - fn set_gen_range(&mut self, range: GenRange) { self.gen_range = range; } - pub(crate) fn single_replace(&mut self, old: WidgetId, new: WidgetId) { match &mut self.gen_range { GenRange::Single(id) => { @@ -711,32 +712,42 @@ impl DynWidgetsInfo { } impl PipeNode { - fn share_capture(id: WidgetId, dyn_info: DynInfo) -> Self { - let mut pipe_node = None; + pub(crate) fn empty_node() -> Self { + let gen_range = GenRange::Single(BuildCtx::get().tree().dummy_id()); + let dyn_info = DynWidgetsInfo::new(gen_range); + let inner = InnerPipeNode { data: Box::new(PureRender(Void)), dyn_info }; + Self(Sc::new(UnsafeCell::new(inner))) + } + pub(crate) fn init_for_single(&self, w: WidgetId) { self.init(w, GenRange::Single(w)); } + + pub(crate) fn init(&self, id: WidgetId, range: GenRange) { + self.dyn_info_mut().gen_range = range; + let node = self.clone(); id.wrap_node(BuildCtx::get_mut().tree_mut(), |r| { - let inner_node = InnerPipeNode { data: r, dyn_info }; - let p = Self(Sc::new(UnsafeCell::new(inner_node))); - pipe_node = Some(p.clone()); - Box::new(p) + node.as_mut().data = r; + Box::new(node) }); - - // Safety: init before. - unsafe { pipe_node.unwrap_unchecked() } } - fn dyn_info(&self) -> DynInfo { self.as_ref().dyn_info.clone() } + pub(crate) fn dyn_info(&self) -> &DynWidgetsInfo { &self.as_ref().dyn_info } + + pub(crate) fn dyn_info_mut(&self) -> &mut DynWidgetsInfo { &mut self.as_mut().dyn_info } // Remove the old widget so that the new widget build logic cannot access it // anymore. - fn remove_old_data(&self) -> Box { - std::mem::replace(&mut self.as_mut().data, Box::new(PureRender(Void))) + pub(crate) fn take_data(&self) -> Box { + self.replace_data(Box::new(PureRender(Void))) + } + + pub(crate) fn replace_data(&self, new: Box) -> Box { + std::mem::replace(&mut self.as_mut().data, new) } fn transplant_to_new( &self, old_node: Box, new_id: WidgetId, tree: &mut WidgetTree, ) { - let old = self.as_ref().dyn_info.borrow().host_id(); + let old = self.dyn_info().host_id(); let [old, new] = tree.get_many_mut(&[old, new_id]); std::mem::swap(old, new); @@ -758,12 +769,14 @@ impl PipeNode { unsafe { &mut *self.0.get() } } + pub(crate) fn host_render(&self) -> &mut Box { &mut self.as_mut().data } + /// Attach a subscription to host widget of the `PipeNode`, and the /// subscription will be unsubscribed when the `PipeNode` dropped. - fn own_subscription(self, u: impl Subscription + 'static) { + fn attach_subscription(self, u: impl Subscription + 'static) { let tree = BuildCtx::get_mut().tree_mut(); let node = self.as_mut(); - let id = node.dyn_info.borrow().host_id(); + let id = node.dyn_info.host_id(); // if the subscription is closed, we can cancel and unwrap the `PipeNode` // immediately. if u.is_closed() { @@ -776,33 +789,33 @@ impl PipeNode { } fn set_pos_of_multi(w: WidgetId, pos: usize, tree: &WidgetTree) -> bool { - w.query_all_iter::(tree) - .inspect(|info| info.borrow_mut().set_pos_of_multi(pos)) + w.query_all_iter::(tree) + .inspect(|node| node.dyn_info_mut().set_pos_of_multi(pos)) .count() > 0 } fn query_outside_infos<'l>( - id: WidgetId, to: &'l DynInfo, tree: &'l WidgetTree, -) -> impl Iterator> { + id: WidgetId, to: &'l PipeNode, tree: &'l WidgetTree, +) -> impl Iterator> { let mut hit = false; - id.query_all_iter::(tree) + id.query_all_iter::(tree) .rev() .take_while(move |info| { if hit { false } else { - hit = Sc::ptr_eq(info, to); + hit = Sc::ptr_eq(&info.0, &to.0); true } }) } -fn pipe_priority_value(info: &DynInfo, tree: &WidgetTree) -> i64 { - let id = info.borrow().host_id(); +fn pipe_priority_value(node: &PipeNode, tree: &WidgetTree) -> i64 { + let id = node.dyn_info().host_id(); let depth = id.ancestors(tree).count() as i64; - let embed = query_outside_infos(id, info, tree).count() as i64; + let embed = query_outside_infos(id, node, tree).count() as i64; - let pos = info.borrow_mut().pos_of_multi() as i64; + let pos = node.dyn_info().pos_of_multi() as i64; depth << 60 | pos << 40 | embed } @@ -810,8 +823,8 @@ impl Query for PipeNode { fn query_all<'q>(&'q self, query_id: &QueryId, out: &mut SmallVec<[QueryHandle<'q>; 1]>) { let p = self.as_ref(); p.data.query_all(query_id, out); - if query_id == &QueryId::of::() { - out.push(QueryHandle::new(&p.dyn_info)) + if query_id == &QueryId::of::() { + out.push(QueryHandle::new(self)) } } @@ -822,8 +835,8 @@ impl Query for PipeNode { fn query(&self, query_id: &QueryId) -> Option { let p = self.as_ref(); - if query_id == &QueryId::of::() { - Some(QueryHandle::new(&p.dyn_info)) + if query_id == &QueryId::of::() { + Some(QueryHandle::new(self)) } else { p.data.query(query_id) } @@ -833,21 +846,6 @@ impl Query for PipeNode { self.as_ref().data.query_write(query_id) } - fn query_match( - &self, ids: &[QueryId], filter: &dyn Fn(&QueryId, &QueryHandle) -> bool, - ) -> Option<(QueryId, QueryHandle)> { - let p = self.as_ref(); - p.data.query_match(ids, filter).or_else(|| { - let dyn_info_id = QueryId::of::(); - (ids.contains(&dyn_info_id)) - .then(|| { - let h = QueryHandle::new(&p.dyn_info); - filter(&dyn_info_id, &h).then_some((dyn_info_id, h)) - }) - .flatten() - }) - } - fn queryable(&self) -> bool { true } } @@ -863,7 +861,7 @@ impl Render for PipeNode { false } - fn hit_test(&self, ctx: &HitTestCtx, pos: Point) -> HitTest { + fn hit_test(&self, ctx: &mut HitTestCtx, pos: Point) -> HitTest { self.as_ref().data.hit_test(ctx, pos) } @@ -871,21 +869,19 @@ impl Render for PipeNode { } #[derive(Clone)] -pub struct PipeWidgetUpdater { - dyn_info: DynInfo, +pub struct PipeWidgetBuildInit { + node: PipeNode, tree: Sc>, } -impl PipeWidgetUpdater { - fn new_with_tree(dyn_info: DynInfo, tree: *mut WidgetTree) -> Self { - let mut p = Self::new(dyn_info); +impl PipeWidgetBuildInit { + fn new_with_tree(node: PipeNode, tree: *mut WidgetTree) -> Self { + let mut p = Self::new(node); p.set_tree(tree); p } - fn new(dyn_info: DynInfo) -> Self { - Self { dyn_info, tree: Sc::new(Cell::new(std::ptr::null_mut())) } - } + fn new(node: PipeNode) -> Self { Self { node, tree: Sc::new(Cell::new(std::ptr::null_mut())) } } fn set_tree(&mut self, tree: *mut WidgetTree) { assert!(self.tree.get().is_null()); @@ -896,23 +892,13 @@ impl PipeWidgetUpdater { let tree = self.tree.get(); (!tree.is_null()).then(|| unsafe { &*tree }) } - - fn build_init_fn(&self) -> impl Fn() { - let this = self.clone(); - - move || { - let wid = this.dyn_info.borrow().host_id(); - let tree = NonNull::new(this.tree.get()).expect("Tree must not be null."); - BuildCtx::set_for(wid, tree) - } - } } -impl Priority for PipeWidgetUpdater { +impl Priority for PipeWidgetBuildInit { fn priority(&mut self) -> i64 { self .tree() - .map_or(-1, |tree| pipe_priority_value(&self.dyn_info, tree)) + .map_or(-1, |tree| pipe_priority_value(&self.node, tree)) } fn queue(&mut self) -> Option<&PriorityTaskQueue> { @@ -926,6 +912,61 @@ impl Priority for PipeWidgetUpdater { } } +struct PipeWidgetContextOp { + source: S, + init: PipeWidgetBuildInit, +} + +struct PipeWidgetContextObserver { + observer: O, + init: PipeWidgetBuildInit, +} + +impl Observable for PipeWidgetContextOp +where + O: Observer + 'static, + S: Observable> + 'static, +{ + type Unsub = S::Unsub; + + fn actual_subscribe(self, observer: O) -> Self::Unsub { + let Self { source, init } = self; + source.actual_subscribe(PipeWidgetContextObserver { observer, init }) + } +} + +impl ObservableExt for PipeWidgetContextOp where + S: ObservableExt +{ +} + +impl Observer for PipeWidgetContextObserver +where + O: Observer, +{ + fn next(&mut self, value: Item) { + let Self { observer, init } = self; + let wid = init.node.dyn_info().host_id(); + let tree = NonNull::new(init.tree.get()).expect("Tree must not be null."); + + // Initialize the build context + let old = init.node.take_data(); + BuildCtx::set_for(wid, tree); + init.node.replace_data(old); + + observer.next(value); + + // Clear the build context immediately after the downstream process. + BuildCtx::clear(); + } + + fn error(self, err: Err) { self.observer.error(err); } + + fn complete(self) { self.observer.complete(); } + + fn is_finished(&self) -> bool { self.observer.is_finished() } +} + #[cfg(test)] mod tests { use std::{cell::Cell, rc::Rc}; diff --git a/core/src/query.rs b/core/src/query.rs index fb91e6425..a687815df 100644 --- a/core/src/query.rs +++ b/core/src/query.rs @@ -24,16 +24,7 @@ pub trait Query: Any { /// Queries the reference of the writer that matches the provided type id. fn query_write(&self, id: &QueryId) -> Option; - /// Query multiple types sequentially from inside to outside and return the - /// first one that matches the `filter` function. - /// - /// It's not equivalent to query the id one by one, it query all types level - /// by level. - fn query_match( - &self, ids: &[QueryId], filter: &dyn Fn(&QueryId, &QueryHandle) -> bool, - ) -> Option<(QueryId, QueryHandle)>; - - /// Hint this is a non-queryable type. + /// Hint if this is a queryable type. fn queryable(&self) -> bool { true } } @@ -123,6 +114,7 @@ impl<'a> QueryHandle<'a> { } } +#[allow(clippy::borrowed_box)] // The state reference must be a trait boxed. fn downcast_from_state_ref(owned: &Box) -> Option<&T> { owned .q_downcast_ref::>() @@ -218,17 +210,6 @@ impl Query for Queryable { fn query_write(&self, _: &QueryId) -> Option { None } - fn query_match( - &self, ids: &[QueryId], filter: &dyn Fn(&QueryId, &QueryHandle) -> bool, - ) -> Option<(QueryId, QueryHandle)> { - ids.iter().find_map(|id| { - self - .query(id) - .filter(|h| filter(id, h)) - .map(|h| (*id, h)) - }) - } - fn queryable(&self) -> bool { true } } @@ -270,16 +251,6 @@ where } } - fn query_match( - &self, ids: &[QueryId], filter: &dyn Fn(&QueryId, &QueryHandle) -> bool, - ) -> Option<(QueryId, QueryHandle)> { - ids.iter().find_map(|id| { - self - .query(id) - .filter(|h| filter(id, h)) - .map(|h| (*id, h)) - }) - } fn queryable(&self) -> bool { true } } @@ -307,17 +278,6 @@ macro_rules! impl_query_for_reader { fn query_write(&self, _: &QueryId) -> Option { None } - fn query_match( - &self, ids: &[QueryId], filter: &dyn Fn(&QueryId, &QueryHandle) -> bool, - ) -> Option<(QueryId, QueryHandle)> { - ids.iter().find_map(|id| { - self - .query(id) - .filter(|h| filter(id, h)) - .map(move |q| (*id, q)) - }) - } - fn queryable(&self) -> bool { true } }; } @@ -354,21 +314,21 @@ pub struct QueryId { info: fn() -> TypeInfo, } -#[derive(Debug, PartialEq, Eq)] -struct TypeInfo { - name: &'static str, - pkg_version: &'static str, - layout: &'static std::alloc::Layout, +#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)] +pub(crate) struct TypeInfo { + pub(crate) name: &'static str, + pub(crate) pkg_version: &'static str, + pub(crate) layout: &'static std::alloc::Layout, } -struct TypeInfoOf { +pub(crate) struct TypeInfoOf { _phantom: std::marker::PhantomData, } impl TypeInfoOf { const LAYOUT: std::alloc::Layout = std::alloc::Layout::new::(); - fn type_info() -> TypeInfo { + pub(crate) fn type_info() -> TypeInfo { TypeInfo { name: std::any::type_name::(), pkg_version: env!("CARGO_PKG_VERSION"), @@ -430,8 +390,7 @@ impl dyn QueryAny { #[cfg(test)] mod tests { use super::*; - use crate::{reset_test_env, state::State}; - use crate::prelude::PartData; + use crate::{prelude::PartData, reset_test_env, state::State}; #[test] fn query_ref() { diff --git a/core/src/render_helper.rs b/core/src/render_helper.rs index ff0ddb06c..93363c51f 100644 --- a/core/src/render_helper.rs +++ b/core/src/render_helper.rs @@ -20,12 +20,6 @@ impl Query for PureRender { fn query_write(&self, _: &QueryId) -> Option { None } - fn query_match( - &self, _: &[QueryId], _: &dyn Fn(&QueryId, &QueryHandle) -> bool, - ) -> Option<(QueryId, QueryHandle)> { - None - } - fn queryable(&self) -> bool { false } } @@ -49,7 +43,9 @@ where fn only_sized_by_parent(&self) -> bool { self.proxy().only_sized_by_parent() } #[inline] - fn hit_test(&self, ctx: &HitTestCtx, pos: Point) -> HitTest { self.proxy().hit_test(ctx, pos) } + fn hit_test(&self, ctx: &mut HitTestCtx, pos: Point) -> HitTest { + self.proxy().hit_test(ctx, pos) + } #[inline] fn get_transform(&self) -> Option { self.proxy().get_transform() } @@ -69,7 +65,7 @@ impl RenderProxy for Sc { impl Render for Resource { fn perform_layout(&self, clamp: BoxClamp, ctx: &mut LayoutCtx) -> Size { - let line_width = ctx.painting_style().line_width(); + let line_width = Provider::of::(ctx).and_then(|p| p.line_width()); let size = self .bounds(line_width) .max() @@ -82,7 +78,13 @@ impl Render for Resource { fn only_sized_by_parent(&self) -> bool { true } fn paint(&self, ctx: &mut PaintingCtx) { + let style = Provider::of::(ctx).map(|p| p.clone()); + let painter = ctx.painter(); let path = PaintPath::Share(self.clone()); - ctx.painter().draw_path(path); + if let Some(PaintingStyle::Stroke(options)) = style { + painter.set_strokes(options).stroke_path(path); + } else { + painter.fill_path(path); + } } } diff --git a/core/src/widget.rs b/core/src/widget.rs index 712f235df..d5f47b783 100644 --- a/core/src/widget.rs +++ b/core/src/widget.rs @@ -45,7 +45,7 @@ pub trait Render: 'static { /// Verify if the provided position is within this widget and return whether /// its child can be hit if the widget itself is not hit. - fn hit_test(&self, ctx: &HitTestCtx, pos: Point) -> HitTest { + fn hit_test(&self, ctx: &mut HitTestCtx, pos: Point) -> HitTest { let hit = ctx.box_hit_test(pos); // If the widget is not solely sized by the parent, indicating it is not a // fixed-size container, we permit the child to receive hits even if it @@ -196,7 +196,7 @@ impl<'w> Widget<'w> { } pub(crate) fn from_render(r: Box) -> Widget<'static> { - Widget::from_fn(|_| BuildCtx::get_mut().alloc(r)) + Widget::from_fn(|_| BuildCtx::get_mut().tree_mut().alloc_node(r)) } /// Attach anonymous data to a widget and user can't query it. diff --git a/core/src/widget_children/compose_child_impl.rs b/core/src/widget_children/compose_child_impl.rs index 191e132b5..722894f15 100644 --- a/core/src/widget_children/compose_child_impl.rs +++ b/core/src/widget_children/compose_child_impl.rs @@ -239,9 +239,8 @@ where let f = move || { let tid = TypeId::of::(); let ctx = BuildCtx::get(); - let decor = ctx - .all_of::() - .find_map(|t| QueryRef::filter_map(t, |t| t.styles.get(&tid)).ok()); + let decor = Provider::of::(BuildCtx::get()) + .and_then(|t| QueryRef::filter_map(t, |t| t.styles.get(&tid)).ok()); if let Some(style) = decor { style(Box::new(self), host, ctx) diff --git a/core/src/widget_tree.rs b/core/src/widget_tree.rs index 39b93c857..f9a014ca9 100644 --- a/core/src/widget_tree.rs +++ b/core/src/widget_tree.rs @@ -30,26 +30,21 @@ type TreeArena = Arena>; impl WidgetTree { pub fn init(&mut self, wnd: &Window, content: GenWidget) -> WidgetId { self.root.0.remove_subtree(&mut self.arena); - let _guard = BuildCtx::init(BuildCtx { - tree: wnd.tree, - providers: <_>::default(), - current_providers: <_>::default(), - children: <_>::default(), - }); + let _guard = BuildCtx::init(BuildCtx::empty(wnd.tree)); let theme = AppCtx::app_theme().clone_writer(); - let overlays = Queryable(ShowingOverlays::default()); - let root = Provider::new(Box::new(overlays)) - .with_child(fn_widget! { - theme.with_child(fn_widget!{ - let overlays = Provider::of::(BuildCtx::get()).unwrap(); - overlays.rebuild(); - @Root { - @{ content.gen_widget() } - } - }) - }) - .into_widget(); + let child = move || { + let overlays = Provider::of::(BuildCtx::get()).unwrap(); + overlays.rebuild(); + Root + .with_child(content.gen_widget()) + .into_widget() + }; + + let (mut providers, child) = Theme::preprocess_before_compose(theme, child.into()); + providers.push(Provider::new(ShowingOverlays::default())); + + let root = Providers::new(providers).with_child(child); let root = BuildCtx::get_mut().build(root); self.root = root; @@ -69,8 +64,7 @@ impl WidgetTree { let wnd = self.window(); let mut painter = wnd.painter.borrow_mut(); let tree = wnd.tree(); - let mut ctx = PaintingCtx::new(self.root(), tree, &mut painter); - self.root().paint_subtree(&mut ctx); + self.root().paint_subtree(tree, &mut painter); } /// Do the work of computing the layout for all node which need, Return if any @@ -98,6 +92,10 @@ impl WidgetTree { } } + pub(crate) fn alloc_node(&mut self, node: Box) -> WidgetId { + new_node(&mut self.arena, node) + } + pub(crate) fn layout_info(&self, id: WidgetId) -> Option<&LayoutInfo> { self.store.layout_info(id) } @@ -260,8 +258,6 @@ impl Render for Root { clamp.max } - - fn paint(&self, _: &mut PaintingCtx) {} } #[cfg(test)] diff --git a/core/src/widget_tree/widget_id.rs b/core/src/widget_tree/widget_id.rs index dbdd9de91..25ec426d1 100644 --- a/core/src/widget_tree/widget_id.rs +++ b/core/src/widget_tree/widget_id.rs @@ -23,13 +23,10 @@ pub(crate) enum PlaceHolder { pub trait RenderQueryable: Render + Query { fn as_render(&self) -> &dyn Render; - fn as_query(&self) -> &dyn Query; } impl RenderQueryable for T { fn as_render(&self) -> &dyn Render { self } - - fn as_query(&self) -> &dyn Query { self } } /// You can get TrackId by builtin method of track_id(). @@ -279,44 +276,42 @@ impl WidgetId { self.wrap_node(tree, |render| Box::new(AnonymousAttacher::new(render, Box::new(data)))); } - pub(crate) fn paint_subtree(self, ctx: &mut PaintingCtx) { - let mut w = Some(self); - while let Some(id) = w { - ctx.id = id; - ctx.painter.save(); - let wnd = ctx.window(); - let tree = wnd.tree(); - - let mut need_paint = false; - if ctx.painter.alpha() != 0. { + pub(crate) fn paint_subtree(self, tree: &WidgetTree, painter: &mut Painter) { + let mut ctx = PaintingCtx::new(self, tree, painter); + let mut painting = vec![]; + loop { + let id = ctx.id(); + if ctx.painter().alpha() != 0. { if let Some(layout_box) = ctx.box_rect() { + painting.push(id); let render = id.assert_get(tree); ctx - .painter + .painter() + .save() .translate(layout_box.min_x(), layout_box.min_y()); - render.paint(ctx); - need_paint = true; + render.paint(&mut ctx); + + if let Some(c) = id.first_child(tree) { + ctx.switch_to(c); + continue; + } + }; + } + + while let Some(painting) = painting.pop() { + ctx.painter().restore(); + ctx.switch_to(painting); + ctx.finish(); + + if let Some(sibling) = painting.next_sibling(tree) { + ctx.switch_to(sibling); + break; } } - w = id - .first_child(tree) - .filter(|_| need_paint) - .or_else(|| { - let mut node = w; - while let Some(p) = node { - // self node sub-tree paint finished, goto sibling - ctx.painter.restore(); - node = p.next_sibling(tree); - if node.is_some() { - break; - } else if p != self { - // if there is no more sibling, back to parent to find sibling. - node = p.parent(tree); - } - } - node - }); + if painting.is_empty() { + break; + } } } } @@ -367,12 +362,6 @@ impl WidgetId { .and_then(QueryHandle::into_ref) } - pub(crate) fn query_ancestors_ref(self, tree: &WidgetTree) -> Option> { - self - .ancestors(tree) - .find_map(|id| id.query_ref::(tree)) - } - /// return if this object contain type `T` pub(crate) fn contain_type(self, tree: &WidgetTree) -> bool { self diff --git a/core/src/window.rs b/core/src/window.rs index d74c99b57..a5db7e0ac 100644 --- a/core/src/window.rs +++ b/core/src/window.rs @@ -7,6 +7,7 @@ use std::{ use futures::{Future, task::LocalSpawnExt}; use ribir_algo::Sc; +use smallvec::SmallVec; use widget_id::TrackId; use winit::event::{DeviceId, ElementState, MouseButton, WindowEvent}; pub use winit::window::CursorIcon; @@ -386,8 +387,8 @@ impl Window { let offset = tree.map_to_global(Point::zero(), *p); painter.translate(offset.x, offset.y); } - let mut ctx = PaintingCtx::new(wid, tree, &mut painter); - wid.paint_subtree(&mut ctx); + + wid.paint_subtree(tree, &mut painter); } }); } @@ -455,32 +456,39 @@ impl Window { self.emit(id, &mut e); } DelayEvent::FocusIn { bottom, up } => { - let mut e = Event::FocusInCapture(FocusEvent::new(bottom, self.tree)); - self.top_down_emit(&mut e, bottom, up); - let mut e = Event::FocusIn(FocusEvent::new(bottom, self.tree)); - self.bottom_up_emit(&mut e, bottom, up); + let top = up.unwrap_or_else(|| self.tree().root()); + self.top_down_emit(&mut Event::FocusInCapture(FocusEvent::new(top, self.tree)), bottom); + self.bottom_up_emit(&mut Event::FocusIn(FocusEvent::new(bottom, self.tree)), up); } DelayEvent::Blur(id) => { let mut e = Event::Blur(FocusEvent::new(id, self.tree)); self.emit(id, &mut e); } DelayEvent::FocusOut { bottom, up } => { - let mut e = Event::FocusOutCapture(FocusEvent::new(bottom, self.tree)); - self.top_down_emit(&mut e, bottom, up); - let mut e = Event::FocusOut(FocusEvent::new(bottom, self.tree)); - self.bottom_up_emit(&mut e, bottom, up); + let top = up.unwrap_or_else(|| self.tree().root()); + self.top_down_emit(&mut Event::FocusOutCapture(FocusEvent::new(top, self.tree)), bottom); + self.bottom_up_emit(&mut Event::FocusOut(FocusEvent::new(bottom, self.tree)), up); } - DelayEvent::KeyDown(event) => { - let id = event.id(); - - let mut e = Event::KeyDownCapture(event); - self.top_down_emit(&mut e, id, None); - let Event::KeyDownCapture(e) = e else { unreachable!() }; - let mut e = Event::KeyDown(e); - self.bottom_up_emit(&mut e, id, None); - let Event::KeyDown(e) = e else { unreachable!() }; - if !e.is_prevent_default() && *e.key() == VirtualKey::Named(NamedKey::Tab) { - self.add_delay_event(DelayEvent::TabFocusMove); + DelayEvent::KeyBoard { id, physical_key, key, is_repeat, location, state } => { + let root = self.tree().root(); + let event = + KeyboardEvent::new(self, root, physical_key, key.clone(), is_repeat, location); + let mut event = match state { + ElementState::Pressed => Event::KeyDownCapture(event), + ElementState::Released => Event::KeyUpCapture(event), + }; + self.top_down_emit(&mut event, id); + drop(event); + let event = KeyboardEvent::new(self, id, physical_key, key, is_repeat, location); + let mut event = match state { + ElementState::Pressed => Event::KeyDown(event), + ElementState::Released => Event::KeyUp(event), + }; + self.bottom_up_emit(&mut event, None); + if let Event::KeyDown(e) = event { + if !e.is_prevent_default() && *e.key() == VirtualKey::Named(NamedKey::Tab) { + self.add_delay_event(DelayEvent::TabFocusMove); + } } } DelayEvent::TabFocusMove => { @@ -499,74 +507,64 @@ impl Window { focus_mgr.focus_next_widget(self.tree()); } } - DelayEvent::KeyUp(event) => { - let id = event.id(); - let mut e = Event::KeyUpCapture(event); - self.top_down_emit(&mut e, id, None); - let Event::KeyUpCapture(e) = e else { unreachable!() }; - let mut e = Event::KeyUp(e); - self.bottom_up_emit(&mut e, id, None); - } + DelayEvent::Chars { id, chars } => { - let mut e = Event::CharsCapture(CharsEvent::new(chars, id, self)); - self.top_down_emit(&mut e, id, None); - let Event::CharsCapture(e) = e else { unreachable!() }; - let mut e = Event::Chars(e); - self.bottom_up_emit(&mut e, id, None); + let event = CharsEvent::new(chars.clone(), self.tree().root(), self); + self.top_down_emit(&mut Event::CharsCapture(event), id); + self.bottom_up_emit(&mut Event::Chars(CharsEvent::new(chars, id, self)), None); } DelayEvent::Wheel { id, delta_x, delta_y } => { - let mut e = Event::WheelCapture(WheelEvent::new(delta_x, delta_y, id, self)); - self.top_down_emit(&mut e, id, None); - let mut e = Event::Wheel(WheelEvent::new(delta_x, delta_y, id, self)); - self.bottom_up_emit(&mut e, id, None); + let event = WheelEvent::new(delta_x, delta_y, self.tree().root(), self); + self.top_down_emit(&mut Event::WheelCapture(event), id); + self.bottom_up_emit(&mut Event::Wheel(WheelEvent::new(delta_x, delta_y, id, self)), None); } DelayEvent::PointerDown(id) => { - let mut e = Event::PointerDownCapture(PointerEvent::from_mouse(id, self)); - self.top_down_emit(&mut e, id, None); - let mut e = Event::PointerDown(PointerEvent::from_mouse(id, self)); - self.bottom_up_emit(&mut e, id, None); + let root = self.tree().root(); + let event = PointerEvent::from_mouse(root, self); + self.top_down_emit(&mut Event::PointerDownCapture(event), id); + self.bottom_up_emit(&mut Event::PointerDown(PointerEvent::from_mouse(id, self)), None); self .focus_mgr .borrow_mut() .refresh_focus(self.tree()); } DelayEvent::PointerMove(id) => { - let mut e = Event::PointerMoveCapture(PointerEvent::from_mouse(id, self)); - self.top_down_emit(&mut e, id, None); - let mut e = Event::PointerMove(PointerEvent::from_mouse(id, self)); - self.bottom_up_emit(&mut e, id, None); + let event = PointerEvent::from_mouse(self.tree().root(), self); + self.top_down_emit(&mut Event::PointerMoveCapture(event), id); + self.bottom_up_emit(&mut Event::PointerMove(PointerEvent::from_mouse(id, self)), None); } DelayEvent::PointerUp(id) => { - let mut e = Event::PointerUpCapture(PointerEvent::from_mouse(id, self)); - self.top_down_emit(&mut e, id, None); - let mut e = Event::PointerUp(PointerEvent::from_mouse(id, self)); - self.bottom_up_emit(&mut e, id, None); + let event = PointerEvent::from_mouse(self.tree().root(), self); + self.top_down_emit(&mut Event::PointerUpCapture(event), id); + let event = PointerEvent::from_mouse(id, self); + self.bottom_up_emit(&mut Event::PointerUp(event), None); } DelayEvent::_PointerCancel(id) => { - let mut e = Event::PointerCancel(PointerEvent::from_mouse(id, self)); - self.bottom_up_emit(&mut e, id, None); + let event = PointerEvent::from_mouse(self.tree().root(), self); + self.top_down_emit(&mut Event::PointerCancelCapture(event), id); + let event = PointerEvent::from_mouse(id, self); + self.bottom_up_emit(&mut Event::PointerCancel(event), None); } DelayEvent::PointerEnter { bottom, up } => { - let mut e = Event::PointerEnter(PointerEvent::from_mouse(bottom, self)); - self.top_down_emit(&mut e, bottom, up); + let top = up.unwrap_or_else(|| self.tree().root()); + self.top_down_emit(&mut Event::PointerEnter(PointerEvent::from_mouse(top, self)), bottom); } DelayEvent::PointerLeave { bottom, up } => { - let mut e = Event::PointerLeave(PointerEvent::from_mouse(bottom, self)); - self.bottom_up_emit(&mut e, bottom, up); + self.bottom_up_emit(&mut Event::PointerLeave(PointerEvent::from_mouse(bottom, self)), up); } DelayEvent::Tap(wid) => { - let mut e = Event::TapCapture(PointerEvent::from_mouse(wid, self)); - self.top_down_emit(&mut e, wid, None); - let mut e = Event::Tap(PointerEvent::from_mouse(wid, self)); - self.bottom_up_emit(&mut e, wid, None); + let event = PointerEvent::from_mouse(self.tree().root(), self); + self.top_down_emit(&mut Event::TapCapture(event), wid); + let event = PointerEvent::from_mouse(wid, self); + self.bottom_up_emit(&mut Event::Tap(event), None); } DelayEvent::ImePreEdit { wid, pre_edit } => { - let mut e = Event::ImePreEditCapture(ImePreEditEvent::new(pre_edit, wid, self)); - self.top_down_emit(&mut e, wid, None); - let Event::ImePreEditCapture(e) = e else { unreachable!() }; - self.bottom_up_emit(&mut Event::ImePreEdit(e), wid, None); + let root = self.tree().root(); + let ime_event = ImePreEditEvent::new(pre_edit.clone(), root, self); + self.top_down_emit(&mut Event::ImePreEditCapture(ime_event), wid); + let ime_event = ImePreEditEvent::new(pre_edit, wid, self); + self.bottom_up_emit(&mut Event::ImePreEdit(ime_event), None); } - DelayEvent::GrabPointerDown(wid) => { let mut e = Event::PointerDown(PointerEvent::from_mouse(wid, self)); self.emit(wid, &mut e); @@ -592,19 +590,20 @@ impl Window { }) } - fn top_down_emit(&self, e: &mut Event, bottom: WidgetId, up: Option) { + fn top_down_emit(&self, e: &mut Event, bottom: WidgetId) { let tree = self.tree(); let path = bottom .ancestors(tree) - .take_while(|id| Some(*id) != up) + .take_while(|id| id != &e.target()) .collect::>(); + let mut buffer = SmallVec::new(); path.iter().rev().all(|id| { + e.capture_to_child(*id, &mut buffer); id.query_all_iter::(tree) .rev() .all(|m| { if m.contain_flag(e.flags()) { - e.set_current_target(*id); m.dispatch(e); } e.is_propagation() @@ -612,19 +611,19 @@ impl Window { }); } - fn bottom_up_emit(&self, e: &mut Event, bottom: WidgetId, up: Option) { + fn bottom_up_emit(&self, e: &mut Event, up: Option) { if !e.is_propagation() { return; } let tree = self.tree(); - bottom + e.target() .ancestors(tree) .take_while(|id| Some(*id) != up) .all(|id| { + e.bubble_to_parent(id); id.query_all_iter::(tree).all(|m| { if m.contain_flag(e.flags()) { - e.set_current_target(id); m.dispatch(e); } e.is_propagation() @@ -796,25 +795,56 @@ impl Drop for Window { pub(crate) enum DelayEvent { Mounted(WidgetId), PerformedLayout(WidgetId), - Disposed { parent: Option, id: WidgetId }, + Disposed { + parent: Option, + id: WidgetId, + }, RemoveSubtree(WidgetId), Focus(WidgetId), Blur(WidgetId), - FocusIn { bottom: WidgetId, up: Option }, - FocusOut { bottom: WidgetId, up: Option }, - KeyDown(KeyboardEvent), - KeyUp(KeyboardEvent), + FocusIn { + bottom: WidgetId, + up: Option, + }, + FocusOut { + bottom: WidgetId, + up: Option, + }, + KeyBoard { + id: WidgetId, + physical_key: PhysicalKey, + key: VirtualKey, + is_repeat: bool, + location: KeyLocation, + state: ElementState, + }, TabFocusMove, - Chars { id: WidgetId, chars: String }, - Wheel { id: WidgetId, delta_x: f32, delta_y: f32 }, + Chars { + id: WidgetId, + chars: String, + }, + Wheel { + id: WidgetId, + delta_x: f32, + delta_y: f32, + }, PointerDown(WidgetId), PointerMove(WidgetId), PointerUp(WidgetId), _PointerCancel(WidgetId), - PointerEnter { bottom: WidgetId, up: Option }, - PointerLeave { bottom: WidgetId, up: Option }, + PointerEnter { + bottom: WidgetId, + up: Option, + }, + PointerLeave { + bottom: WidgetId, + up: Option, + }, Tap(WidgetId), - ImePreEdit { wid: WidgetId, pre_edit: ImePreEdit }, + ImePreEdit { + wid: WidgetId, + pre_edit: ImePreEdit, + }, GrabPointerDown(WidgetId), GrabPointerMove(WidgetId), GrabPointerUp(WidgetId), diff --git a/core/src/wrap_render.rs b/core/src/wrap_render.rs index 19448e953..1db91bbc8 100644 --- a/core/src/wrap_render.rs +++ b/core/src/wrap_render.rs @@ -23,7 +23,7 @@ pub trait WrapRender { host.only_sized_by_parent() } - fn hit_test(&self, host: &dyn Render, ctx: &HitTestCtx, pos: Point) -> HitTest { + fn hit_test(&self, host: &dyn Render, ctx: &mut HitTestCtx, pos: Point) -> HitTest { host.hit_test(ctx, pos) } @@ -75,12 +75,6 @@ impl Query for RenderPair { self.host.query_write(query_id) } - fn query_match( - &self, ids: &[QueryId], filter: &dyn Fn(&QueryId, &QueryHandle) -> bool, - ) -> Option<(QueryId, QueryHandle)> { - self.host.query_match(ids, filter) - } - fn queryable(&self) -> bool { self.host.queryable() } } @@ -99,7 +93,7 @@ impl Render for RenderPair { .only_sized_by_parent(self.host.as_render()) } - fn hit_test(&self, ctx: &HitTestCtx, pos: Point) -> HitTest { + fn hit_test(&self, ctx: &mut HitTestCtx, pos: Point) -> HitTest { self .wrapper .hit_test(self.host.as_render(), ctx, pos) @@ -123,7 +117,7 @@ where self.read().only_sized_by_parent(host) } - fn hit_test(&self, host: &dyn Render, ctx: &HitTestCtx, pos: Point) -> HitTest { + fn hit_test(&self, host: &dyn Render, ctx: &mut HitTestCtx, pos: Point) -> HitTest { self.read().hit_test(host, ctx, pos) } diff --git a/macros/src/declare_derive.rs b/macros/src/declare_derive.rs index d3887f50a..1419cbb9c 100644 --- a/macros/src/declare_derive.rs +++ b/macros/src/declare_derive.rs @@ -52,6 +52,7 @@ pub(crate) fn declare_derive(input: &mut syn::DeriveInput) -> syn::Result; + #[track_caller] fn finish(mut fat_ಠ_ಠ: FatObj) -> FatObj { #(#field_values)* let this_ಠ_ಠ = State::value(#host { diff --git a/macros/src/distinct_pipe_macro.rs b/macros/src/distinct_pipe_macro.rs index 571f67145..e6bd8388b 100644 --- a/macros/src/distinct_pipe_macro.rs +++ b/macros/src/distinct_pipe_macro.rs @@ -15,7 +15,7 @@ pub fn gen_code(input: TokenStream, refs_ctx: Option<&mut DollarRefsCtx>) -> Tok #map_handler ) // Since the pipe has an initial value, we skip the initial notification. - .value_chain(|s| s.distinct_until_key_changed(|v: &(_, _)| v.1).skip(1).box_it()) + .value_chain(|s| s.skip(1).distinct_until_key_changed(|v: &(_, _)| v.1).box_it()) } }); result_to_token_stream(res) diff --git a/macros/src/simple_declare_attr.rs b/macros/src/simple_declare_attr.rs index 257d63047..d7df0c6c6 100644 --- a/macros/src/simple_declare_attr.rs +++ b/macros/src/simple_declare_attr.rs @@ -50,6 +50,7 @@ pub(crate) fn simple_declarer_attr( impl #g_impl ObjDeclarer for #name #g_ty #g_where { type Target = #ident #g_ty; + #[track_caller] fn finish(self) -> Self::Target { #ident {#(#init_pairs),*} } @@ -60,6 +61,7 @@ pub(crate) fn simple_declarer_attr( impl #g_impl ObjDeclarer for #name #g_ty #g_where { type Target = State<#ident #g_ty>; + #[track_caller] fn finish(mut self) -> Self::Target { State::value(#ident {#(#init_pairs),*}) } diff --git a/macros/src/variable_names.rs b/macros/src/variable_names.rs index 7efc85d50..c5910ad6a 100644 --- a/macros/src/variable_names.rs +++ b/macros/src/variable_names.rs @@ -120,7 +120,7 @@ pub static BUILTIN_INFOS: phf::Map<&'static str, BuiltinMember> = phf_map! { "foreground" => builtin_member! { "Foreground", Field, "foreground"}, // PaintingStyleWidget "painting_style" => builtin_member! { "PaintingStyleWidget", Field, "painting_style" }, - // TextStyleWidget + // TextStyle "text_style" => builtin_member! { "TextStyleWidget", Field, "text_style" }, "font_size" => builtin_member! { "TextStyleWidget", Method, "text_style" }, "font_face" => builtin_member! { "TextStyleWidget", Method, "text_style" }, diff --git a/tests/rdl_macro_test.rs b/tests/rdl_macro_test.rs index 3d79c3450..650f23351 100644 --- a/tests/rdl_macro_test.rs +++ b/tests/rdl_macro_test.rs @@ -188,17 +188,6 @@ widget_layout_test!( LayoutCase::default().with_size(Size::new(4., 2.)) ); -widget_layout_test!( - capture_closure_used_ctx, - WidgetTester::new(fn_widget! { - let size_box = @SizedBox { size: ZERO_SIZE }; - @ $size_box { - on_mounted: move |e| $size_box.write().size = IconSize::of(&e).tiny - } - }), - LayoutCase::default().with_size(Size::new(18., 18.)) -); - #[test] fn pipe_single_parent() { reset_test_env!(); diff --git a/themes/material/src/classes/buttons_cls.rs b/themes/material/src/classes/buttons_cls.rs index 4ec81bfba..b6194bbfd 100644 --- a/themes/material/src/classes/buttons_cls.rs +++ b/themes/material/src/classes/buttons_cls.rs @@ -28,7 +28,7 @@ named_style_class!(common_label_only => { fn text_button_init(classes: &mut Classes) { fn interactive(w: Widget) -> Widget { FatObj::new(base_interactive(w, md::RADIUS_20)) - .foreground(BuildCtx::get().variant_color()) + .foreground(BuildCtx::color()) .clamp(BTN_40_CLAMP) .into_widget() } @@ -50,7 +50,7 @@ fn text_button_init(classes: &mut Classes) { fn filled_button_init(classes: &mut Classes) { fn filled_interactive(w: Widget) -> Widget { - let color = BuildCtx::get().variant_color(); + let color = BuildCtx::color(); let w = FatObj::new(w) .background(color) .border_radius(md::RADIUS_20) @@ -82,7 +82,7 @@ fn button_init(classes: &mut Classes) { .into_widget(); FatObj::new(base_interactive(w, md::RADIUS_20)) - .foreground(BuildCtx::get().variant_color()) + .foreground(BuildCtx::color()) .into_widget() } @@ -96,7 +96,7 @@ fn button_init(classes: &mut Classes) { } fn fab_common_interactive(w: Widget, radius: Radius, btn_clamp: BoxClamp) -> Widget { - let color = BuildCtx::get().variant_color(); + let color = BuildCtx::color(); let p = Palette::of(BuildCtx::get()); let w = FatObj::new(w) diff --git a/themes/material/src/classes/checkbox_cls.rs b/themes/material/src/classes/checkbox_cls.rs index f420d7298..38bb6187d 100644 --- a/themes/material/src/classes/checkbox_cls.rs +++ b/themes/material/src/classes/checkbox_cls.rs @@ -39,7 +39,7 @@ pub(super) fn init(classes: &mut Classes) { }; fn check_icon_with_ripple<'w>(icon: Widget<'w>, ripple: Widget<'w>) -> Widget<'w> { - let ripple_color = BuildCtx::get().variant_color(); + let ripple_color = BuildCtx::color(); let icon = container! { size: md::SIZE_18, background: ripple_color, @@ -50,7 +50,6 @@ pub(super) fn init(classes: &mut Classes) { } classes.insert(CHECKBOX_CHECKED, |w| { - let ctx = BuildCtx::get(); let icon = rdl! { let mut builder = Path::builder(); builder @@ -72,7 +71,7 @@ pub(super) fn init(classes: &mut Classes) { }; @FatObj { on_mounted: move |_| enter.run(), - foreground: Palette::of(ctx).on_of(&ctx.variant_color()), + foreground: Palette::of(BuildCtx::get()).on_of(&BuildCtx::color()), painting_style: PaintingStyle::Stroke(StrokeOptions { width: 2., ..Default::default() @@ -85,13 +84,12 @@ pub(super) fn init(classes: &mut Classes) { check_icon_with_ripple(icon, w) }); classes.insert(CHECKBOX_INDETERMINATE, |w| { - let ctx = BuildCtx::get(); let icon = rdl! { let icon = @Container{ size: Size::new(12., 2.), h_align: HAlign::Center, v_align: VAlign::Center, - background: Palette::of(ctx).on_of(&ctx.variant_color()), + background: Palette::of(BuildCtx::get()).on_of(&BuildCtx::color()), }; let enter = @Animate { state: part_writer!(&mut icon.size), @@ -107,7 +105,7 @@ pub(super) fn init(classes: &mut Classes) { let foreground = Palette::of(BuildCtx::get()).on_surface_variant(); let icon = container! { size: md::SIZE_18, - border: md::border_on_surface_variant_2(), + border: md::border_2_surface_color(), border_radius: md::RADIUS_2, clamp: BoxClamp::fixed_size(md::SIZE_18), } diff --git a/themes/material/src/classes/progress_cls.rs b/themes/material/src/classes/progress_cls.rs index e5f34338e..c627cd145 100644 --- a/themes/material/src/classes/progress_cls.rs +++ b/themes/material/src/classes/progress_cls.rs @@ -28,7 +28,7 @@ fn lerp_angle(from: &Angle, to: &Angle, rate: f32) -> Angle { } pub(super) fn init(classes: &mut Classes) { classes.insert(MD_BASE_LINEAR_INDICATOR, style_class! { - background: BuildCtx::get().variant_color(), + background: BuildCtx::color(), border_radius: md::RADIUS_2, }); classes.insert(MD_BASE_SPINNER, style_class! { @@ -41,14 +41,14 @@ pub(super) fn init(classes: &mut Classes) { }); classes.insert(MD_BASE_SPINNER_INDICATOR, style_class! { class: MD_BASE_SPINNER, - foreground: BuildCtx::get().variant_color(), + foreground: BuildCtx::color(), }); classes.insert(MD_BASE_SPINNER_TRACK, style_class! { class: MD_BASE_SPINNER, - foreground: BuildCtx::get().variant_container_color(), + foreground: BuildCtx::container_color(), }); classes.insert(LINEAR_INDETERMINATE_TRACK, style_class! { - background: BuildCtx::get().variant_container_color(), + background: BuildCtx::container_color(), border_radius: md::RADIUS_2, margin: md::EDGES_LEFT_4, }); diff --git a/themes/material/src/classes/radio_cls.rs b/themes/material/src/classes/radio_cls.rs index 69bfdddd5..a7ac99a79 100644 --- a/themes/material/src/classes/radio_cls.rs +++ b/themes/material/src/classes/radio_cls.rs @@ -33,7 +33,7 @@ pub(super) fn init(classes: &mut Classes) { } classes.insert(RADIO_SELECTED, |ripple| { - let color = BuildCtx::get().variant_color(); + let color = BuildCtx::color(); let icon = rdl! { let w = @Container { size: md::SIZE_10, @@ -52,7 +52,7 @@ pub(super) fn init(classes: &mut Classes) { }; @Container { size: md::SIZE_20, - border: md::border_variant_color_2(), + border: md::border_2(), border_radius: md::RADIUS_10, on_mounted: move |_| scale_in.run(), @ { w } @@ -65,7 +65,7 @@ pub(super) fn init(classes: &mut Classes) { let foreground = Palette::of(BuildCtx::get()).on_surface_variant(); let icon = container! { size: md::SIZE_20, - border: md::border_on_surface_variant_2(), + border: md::border_2_surface_color(), border_radius: md::RADIUS_10, }; icon_with_ripple(icon.into_widget(), ripple, foreground) diff --git a/themes/material/src/classes/scrollbar_cls.rs b/themes/material/src/classes/scrollbar_cls.rs index 5efa47b8f..6cdb0e6bb 100644 --- a/themes/material/src/classes/scrollbar_cls.rs +++ b/themes/material/src/classes/scrollbar_cls.rs @@ -15,42 +15,39 @@ pub(super) fn init(classes: &mut Classes) { classes.insert(SCROLL_CLIENT_AREA, empty_cls); classes.insert(H_SCROLL_THUMB, style_class! { - background: BuildCtx::get().variant_color(), + background: BuildCtx::color(), border_radius: md::RADIUS_4, margin: EdgeInsets::vertical(1.), clamp: BoxClamp::min_width(THUMB_MIN_SIZE).with_fixed_height(md::THICKNESS_8) }); classes.insert(V_SCROLL_THUMB, style_class! { - background: BuildCtx::get().variant_color(), + background: BuildCtx::color(), border_radius: md::RADIUS_4, margin: EdgeInsets::horizontal(1.), clamp: BoxClamp::min_height(THUMB_MIN_SIZE).with_fixed_width(md::THICKNESS_8) }); - classes - .insert(H_SCROLL_TRACK, multi_class![base_track, style_class! { v_align: VAlign::Bottom }]); - classes.insert(V_SCROLL_TRACK, multi_class![base_track, style_class! { h_align: HAlign::Right }]); + classes.insert(H_SCROLL_TRACK, |w| style_track(w, true)); + classes.insert(V_SCROLL_TRACK, |w| style_track(w, false)); } -fn base_track(w: Widget) -> Widget { - fn_widget! { - let scroll = &*Provider::of::>(BuildCtx::get()).unwrap(); - let mut w = FatObj::new(w).opacity(0.); - - // Show the scrollbar when scrolling. - let mut fade: Option> = None; - let u = watch!(($scroll).get_scroll_pos()) - .distinct_until_changed() - .subscribe(move |_| { - $w.write().opacity = 1.; - if let Some(f) = fade.take() { - f.unsubscribe(); - } - let u = observable::timer((), Duration::from_secs(3), AppCtx::scheduler()) - .filter(move |_| !$w.is_hover()) - .subscribe(move |_| $w.write().opacity = 0.); - fade = Some(u); - }); +fn style_track(w: Widget, is_hor: bool) -> Widget { + rdl! { + let scroll = Provider::of::>(BuildCtx::get()).unwrap(); + let mut w = FatObj::new(w); + if is_hor { + w = w.v_align(VAlign::Bottom); + } else { + w = w.h_align(HAlign::Right); + } + let mut w = @ $w { + opacity: 0., + visible: false, + background: { + let color = BuildCtx::container_color(); + pipe!(if $w.is_hover() { color } else { color.with_alpha(0.)}) + }, + }; let trans = EasingTransition { easing: md::easing::STANDARD, @@ -58,18 +55,37 @@ fn base_track(w: Widget) -> Widget { }; // Smoothly fade in and out the scrollbar. part_writer!(&mut w.opacity).transition(trans.clone()); - - let mut w = @ $w { - background: { - let color = BuildCtx::get().variant_container_color(); - pipe!(if $w.is_hover() { color } else { color.with_alpha(0.)}) - }, - on_disposed: move |_| u.unsubscribe(), - }; // Smoothly display the background. part_writer!(&mut w.background).transition(trans); - w + // Show the scrollbar when scrolling. + let mut fade: Option> = None; + let auto_hide = move |_| { + $w.write().opacity = 1.; + $w.write().visible = true; + if let Some(f) = fade.take() { + f.unsubscribe(); + } + let u = observable::timer((), Duration::from_secs(3), AppCtx::scheduler()) + .filter(move |_| !$w.is_hover()) + .subscribe(move |_| { + $w.write().opacity = 0.; + $w.write().visible = false; + }); + fade = Some(u); + }; + + let u = if is_hor { + watch!(($scroll).get_scroll_pos().x) + .distinct_until_changed() + .subscribe(auto_hide) + } else { + watch!(($scroll).get_scroll_pos().y) + .distinct_until_changed() + .subscribe(auto_hide) + }; + + @ $w { on_disposed: move |_| u.unsubscribe() } } .into_widget() } diff --git a/themes/material/src/classes/slider_cls.rs b/themes/material/src/classes/slider_cls.rs index 76ce6658d..c82bb37f6 100644 --- a/themes/material/src/classes/slider_cls.rs +++ b/themes/material/src/classes/slider_cls.rs @@ -27,20 +27,20 @@ pub(super) fn init(classes: &mut Classes) { clamp: BoxClamp::fixed_height(INDICATOR_HEIGHT) }); classes.insert(SLIDER_ACTIVE_TRACK, style_class! { - background: BuildCtx::get().variant_color(), + background: BuildCtx::color(), border_radius: RADIUS_L8_R2, clamp: BoxClamp::fixed_height(TRACK_HEIGHT), }); classes.insert(SLIDER_INACTIVE_TRACK, style_class! { border_radius: RADIUS_L2_R8, - background: BuildCtx::get().variant_container_color(), + background: BuildCtx::container_color(), clamp: BoxClamp::fixed_height(TRACK_HEIGHT), }); classes.insert(SLIDER_INDICATOR, style_class! { v_align: VAlign::Center, - background: BuildCtx::get().variant_color(), + background: BuildCtx::color(), border_radius: md::RADIUS_2, margin: EdgeInsets::horizontal(6.), clamp: BoxClamp::fixed_size(Size::new(md::THICKNESS_4, INDICATOR_HEIGHT)), @@ -48,33 +48,27 @@ pub(super) fn init(classes: &mut Classes) { classes.insert(RANGE_SLIDER_INACTIVE_TRACK_LEFT, style_class! { border_radius: RADIUS_L8_R2, - background: BuildCtx::get().variant_container_color(), + background: BuildCtx::container_color(), clamp: BoxClamp::fixed_height(TRACK_HEIGHT), }); classes.insert(RANGE_SLIDER_INACTIVE_TRACK_RIGHT, style_class! { border_radius: RADIUS_L2_R8, - background: BuildCtx::get().variant_container_color(), + background: BuildCtx::container_color(), clamp: BoxClamp::fixed_height(TRACK_HEIGHT), }); classes.insert(RANGE_SLIDER_ACTIVE_TRACK, style_class! { border_radius: md::RADIUS_2, - background: BuildCtx::get().variant_color(), + background: BuildCtx::color(), clamp: BoxClamp::fixed_height(TRACK_HEIGHT), }); classes.insert(STOP_INDICATOR_ACTIVE, stop_indicator_class! { - background: { - let ctx = BuildCtx::get(); - Palette::of(ctx).on_of(&ctx.variant_color()) - } + background: Palette::of(BuildCtx::get()).on_of(&BuildCtx::color()) }); classes.insert(STOP_INDICATOR_INACTIVE, stop_indicator_class! { - background: { - let ctx = BuildCtx::get(); - Palette::of(ctx).on_container_of(&ctx.variant_color()) - } + background: Palette::of(BuildCtx::get()).on_container_of(&BuildCtx::color()) }); } diff --git a/themes/material/src/md.rs b/themes/material/src/md.rs index c6d2b939d..6212e1f84 100644 --- a/themes/material/src/md.rs +++ b/themes/material/src/md.rs @@ -107,11 +107,11 @@ pub const EDGES_HOR_36: EdgeInsets = EdgeInsets::horizontal(36.); pub const EDGES_HOR_48: EdgeInsets = EdgeInsets::horizontal(48.); // Borders -pub fn border_variant_color_2() -> Border { - let color = BuildCtx::get().variant_color(); +pub fn border_2() -> Border { + let color = BuildCtx::color(); Border::all(BorderSide::new(2., color.into())) } -pub fn border_on_surface_variant_2() -> Border { +pub fn border_2_surface_color() -> Border { let surface_variant = Palette::of(BuildCtx::get()).on_surface_variant(); Border::all(BorderSide::new(2., surface_variant.into())) } diff --git a/widgets/src/avatar.rs b/widgets/src/avatar.rs index 87257b074..1ff14c38b 100644 --- a/widgets/src/avatar.rs +++ b/widgets/src/avatar.rs @@ -37,7 +37,7 @@ pub struct AvatarStyle { } impl CustomStyle for AvatarStyle { - fn default_style(ctx: &impl ProviderCtx) -> Self { + fn default_style(ctx: &impl AsRef) -> Self { AvatarStyle { size: Size::splat(40.), radius: Some(20.), diff --git a/widgets/src/icon.rs b/widgets/src/icon.rs index 33a73ae83..a3df9d039 100644 --- a/widgets/src/icon.rs +++ b/widgets/src/icon.rs @@ -26,11 +26,11 @@ use ribir_core::{impl_compose_child_for_wrap_render, prelude::*, wrap_render::Wr /// theme /// .font_files /// .push("the font file path".to_string()); -/// theme.icon_font = FontFace { +/// theme.icon_font = IconFont(FontFace { /// families: Box::new([FontFamily::Name("Your icon font family name".into())]), /// // The rest of the face configuration depends on your font file /// ..<_>::default() -/// }; +/// }); /// /// // Using a named SVG as an icon /// let _icon = icon! { @ { svgs::DELETE } }; @@ -87,13 +87,14 @@ impl_compose_child_for_wrap_render!(IconText); impl WrapRender for IconText { fn perform_layout(&self, clamp: BoxClamp, host: &dyn Render, ctx: &mut LayoutCtx) -> Size { - let font_face = Theme::of(&ctx).icon_font.clone(); - let old = ctx.text_style().clone(); - let style = ctx.text_style_mut(); + let font_face = Provider::of::(&ctx).unwrap().0.clone(); + let mut style = Provider::of::(ctx).unwrap().clone(); style.font_face = font_face; style.font_size = style.line_height; + let mut style = Provider::new(style); + style.setup(ctx.as_mut()); let size = host.perform_layout(clamp, ctx); - *ctx.text_style_mut() = old; + style.restore(ctx.as_mut()); size } } @@ -105,7 +106,9 @@ struct IconRender { impl Render for IconRender { fn perform_layout(&self, clamp: BoxClamp, ctx: &mut LayoutCtx) -> Size { - let icon_size = ctx.text_style().line_height; + let icon_size = Provider::of::(ctx) + .unwrap() + .line_height; let child_size = ctx .perform_single_child_layout(BoxClamp::default()) .unwrap_or_default(); @@ -173,11 +176,11 @@ mod tests { theme .font_bytes .push(include_bytes!("../../fonts/material-search.ttf").to_vec()); - theme.icon_font = FontFace { + theme.icon_font = IconFont(FontFace { families: Box::new([FontFamily::Name("Material Symbols Rounded 48pt".into())]), weight: FontWeight::NORMAL, ..<_>::default() - }; + }); }) .with_comparison(0.002) ); diff --git a/widgets/src/input.rs b/widgets/src/input.rs index 1d01547e2..3f2a285f5 100644 --- a/widgets/src/input.rs +++ b/widgets/src/input.rs @@ -38,7 +38,7 @@ pub struct PlaceholderStyle { } impl CustomStyle for PlaceholderStyle { - fn default_style(ctx: &impl ProviderCtx) -> Self { + fn default_style(ctx: &impl AsRef) -> Self { Self { foreground: Palette::of(ctx).on_surface_variant().into(), text_style: TypographyTheme::of(ctx).body_medium.text.clone(), @@ -52,7 +52,7 @@ pub struct InputStyle { } impl CustomStyle for InputStyle { - fn default_style(_: &impl ProviderCtx) -> Self { InputStyle { size: Some(20.) } } + fn default_style(_: &impl AsRef) -> Self { InputStyle { size: Some(20.) } } } #[derive(Clone, PartialEq)] @@ -62,7 +62,7 @@ pub struct TextAreaStyle { } impl CustomStyle for TextAreaStyle { - fn default_style(_: &impl ProviderCtx) -> Self { + fn default_style(_: &impl AsRef) -> Self { TextAreaStyle { rows: Some(2.), cols: Some(20.) } } } diff --git a/widgets/src/input/selected_text.rs b/widgets/src/input/selected_text.rs index 5a814b802..603fb5902 100644 --- a/widgets/src/input/selected_text.rs +++ b/widgets/src/input/selected_text.rs @@ -12,7 +12,7 @@ pub struct SelectedHighLightStyle { pub brush: Brush, } impl CustomStyle for SelectedHighLightStyle { - fn default_style(_: &impl ProviderCtx) -> Self { + fn default_style(_: &impl AsRef) -> Self { SelectedHighLightStyle { brush: Color::from_rgb(181, 215, 254).into() } } } diff --git a/widgets/src/lists.rs b/widgets/src/lists.rs index 9a3dbfb4e..95bd573b1 100644 --- a/widgets/src/lists.rs +++ b/widgets/src/lists.rs @@ -338,7 +338,7 @@ pub struct ListItemStyle { } impl CustomStyle for ListItemStyle { - fn default_style(ctx: &impl ProviderCtx) -> Self { + fn default_style(ctx: &impl AsRef) -> Self { let typography = TypographyTheme::of(ctx); let palette = Palette::of(ctx); ListItemStyle { diff --git a/widgets/src/path.rs b/widgets/src/path.rs index 6137b121f..9bfd9bb4d 100644 --- a/widgets/src/path.rs +++ b/widgets/src/path.rs @@ -16,10 +16,16 @@ impl Render for PathPaintKit { fn paint(&self, ctx: &mut PaintingCtx) { let path = PaintPath::Share(self.path.clone()); - ctx.painter().draw_path(path); + let style = Provider::of::(ctx).map(|p| p.clone()); + let painter = ctx.painter(); + if let Some(PaintingStyle::Stroke(options)) = style { + painter.set_strokes(options).stroke_path(path); + } else { + painter.fill_path(path); + } } - fn hit_test(&self, _ctx: &HitTestCtx, _: Point) -> HitTest { + fn hit_test(&self, _ctx: &mut HitTestCtx, _: Point) -> HitTest { HitTest { hit: false, can_hit_child: false } } } diff --git a/widgets/src/progress.rs b/widgets/src/progress.rs index 11156eb30..431232b76 100644 --- a/widgets/src/progress.rs +++ b/widgets/src/progress.rs @@ -92,7 +92,8 @@ impl Compose for SpinnerProgress { }; // It is essential to ensure that the spinner is accessible by the class, // as the class may need to perform animations on the spinner. - Provider::new(Box::new(spinner.clone_writer())).with_child(fn_widget!{ + @Providers { + providers: [Provider::new(spinner.clone_writer())], @ $spinner { class: distinct_pipe! { if $this.value.is_some() { @@ -102,7 +103,7 @@ impl Compose for SpinnerProgress { } } } - }) + } } .into_widget() } @@ -132,9 +133,20 @@ impl Render for SpinnerArc { let end = end - Angle::pi() / 2.; let center = Point::new(size.width / 2., size.height / 2.); let radius = center.x.min(center.y); + + let style = Provider::of::(ctx).map(|p| p.clone()); let painter = ctx.painter(); - match painter.style() { - PathStyle::Fill => { + match style { + Some(PaintingStyle::Stroke(strokes)) => { + let radius = radius - strokes.width / 2.0; + painter + .set_strokes(strokes) + .begin_path(arc_start_at(start, center, radius)) + .arc_to(center, radius, start, end) + .end_path(false) + .stroke(); + } + _ => { painter .begin_path(center) .line_to(arc_start_at(start, center, radius)) @@ -143,15 +155,6 @@ impl Render for SpinnerArc { .end_path(true) .fill(); } - PathStyle::Stroke => { - let line_width = painter.line_width(); - let radius = radius - line_width / 2.0; - painter - .begin_path(arc_start_at(start, center, radius)) - .arc_to(center, radius, start, end) - .end_path(false) - .stroke(); - } } } } diff --git a/widgets/src/scrollbar.rs b/widgets/src/scrollbar.rs index edcb92f30..d14c64a7e 100644 --- a/widgets/src/scrollbar.rs +++ b/widgets/src/scrollbar.rs @@ -59,84 +59,87 @@ impl<'c> ComposeChild<'c> for Scrollbar { type Child = Widget<'c>; fn compose_child(this: impl StateWriter, child: Self::Child) -> Widget<'c> { let scroll = this.read().scroll.clone_writer(); - let w = fn_widget! { - let h_scrollbar = distinct_pipe!($scroll.is_x_scrollable()) - .map(move |need_bar| need_bar.then(||{ - let mut h_track = @Stack { - class: H_SCROLL_TRACK, - clamp: BoxClamp::EXPAND_X, - on_wheel: move |e| $scroll.write().scroll(-e.delta_x, -e.delta_y), - }; - - @ $h_track { - on_tap: move |e| if e.is_primary { - let rate = e.position().x / $h_track.layout_width(); - let mut scroll = Provider::write_of::(e).unwrap(); - let x = rate * scroll.max_scrollable().x; - let scroll_pos = Point::new(x, scroll.get_scroll_pos().y); - scroll.jump_to(scroll_pos); - }, - @Container { - class: H_SCROLL_THUMB, - size: distinct_pipe!{ - let width = h_thumb_rate(&$scroll) * $h_track.layout_width(); - Size::new(width, 0.) - }, - anchor: distinct_pipe!{ - let pos = $scroll.get_x_scroll_rate() * $h_track.layout_width(); - Anchor::left(pos) + // Here we provide the `ScrollableWidget`, which allows the theme to access + // scroll states or enables descendants to trigger scrolling to a different + // position. + providers! { + providers: smallvec::smallvec![ + Provider::new(scroll.clone_writer()), + Provider::value_of_state(scroll.clone_writer()) + ], + @ { + let h_scrollbar = distinct_pipe!($scroll.is_x_scrollable()) + .map(move |need_bar| need_bar.then(||{ + let mut h_track = @Stack { + class: H_SCROLL_TRACK, + clamp: BoxClamp::EXPAND_X, + on_wheel: move |e| $scroll.write().scroll(-e.delta_x, -e.delta_y), + }; + + @ $h_track { + on_tap: move |e| if e.is_primary { + let rate = e.position().x / $h_track.layout_width(); + let mut scroll = $scroll.write(); + let x = rate * scroll.max_scrollable().x; + let scroll_pos = Point::new(x, scroll.get_scroll_pos().y); + scroll.jump_to(scroll_pos); }, + @Container { + class: H_SCROLL_THUMB, + size: distinct_pipe!{ + let width = h_thumb_rate(&$scroll) * $h_track.layout_width(); + Size::new(width, 0.) + }, + anchor: distinct_pipe!{ + let pos = $scroll.get_x_scroll_rate() * $h_track.layout_width(); + Anchor::left(pos) + }, + } } - } - })); - - let v_scrollbar = distinct_pipe!($scroll.is_y_scrollable()) - .map(move |need_bar| need_bar.then(|| { - let mut v_track = @Stack { - class: V_SCROLL_TRACK, - clamp: BoxClamp::EXPAND_Y, - on_wheel: move |e| $scroll.write().scroll(-e.delta_x, -e.delta_y), - }; - - @ $v_track { - on_tap: move |e| if e.is_primary { - let rate = e.position().y / $v_track.layout_height(); - let mut scroll = Provider::write_of::(e).unwrap(); - let y = rate * scroll.max_scrollable().y; - let scroll_pos = Point::new(scroll.get_scroll_pos().x, y); - scroll.jump_to(scroll_pos); - }, - @Container { - class: V_SCROLL_THUMB, - size: distinct_pipe!{ - let height = v_thumb_rate(&$scroll) * $v_track.layout_height(); - Size::new(0., height) - }, - anchor: distinct_pipe!{ - let pos = $scroll.get_y_scroll_rate() * $v_track.layout_height(); - Anchor::top(pos) + })); + + let v_scrollbar = distinct_pipe!($scroll.is_y_scrollable()) + .map(move |need_bar| need_bar.then(|| { + let mut v_track = @Stack { + class: V_SCROLL_TRACK, + clamp: BoxClamp::EXPAND_Y, + on_wheel: move |e| $scroll.write().scroll(-e.delta_x, -e.delta_y), + }; + + @ $v_track { + on_tap: move |e| if e.is_primary { + let rate = e.position().y / $v_track.layout_height(); + let mut scroll = $scroll.write(); + let y = rate * scroll.max_scrollable().y; + let scroll_pos = Point::new(scroll.get_scroll_pos().x, y); + scroll.jump_to(scroll_pos); }, + @Container { + class: V_SCROLL_THUMB, + size: distinct_pipe!{ + let height = v_thumb_rate(&$scroll) * $v_track.layout_height(); + Size::new(0., height) + }, + anchor: distinct_pipe!{ + let pos = $scroll.get_y_scroll_rate() * $v_track.layout_height(); + Anchor::top(pos) + }, + } } - } - })); + })); - let scroll = FatObj::new(scroll); - @Stack { - @ $scroll { - class: SCROLL_CLIENT_AREA, - @{ child } + let scroll = FatObj::new(scroll); + @Stack { + @ $scroll { + class: SCROLL_CLIENT_AREA, + @{ child } + } + @ { h_scrollbar } + @ { v_scrollbar } } - @ { h_scrollbar } - @ { v_scrollbar } } - }; - - // Here we provide the `ScrollableWidget`, which allows the theme to access - // scroll states or enables descendants to trigger scrolling to a different - // position. - Provider::new(Box::new(this.read().scroll.clone_writer())) - .with_child(w) - .into_widget() + } + .into_widget() } } diff --git a/widgets/src/tabs.rs b/widgets/src/tabs.rs index 4b25fce63..d85c21828 100644 --- a/widgets/src/tabs.rs +++ b/widgets/src/tabs.rs @@ -77,7 +77,7 @@ pub struct TabsStyle { } impl CustomStyle for TabsStyle { - fn default_style(ctx: &impl ProviderCtx) -> Self { + fn default_style(ctx: &impl AsRef) -> Self { let palette = Palette::of(ctx); TabsStyle { extent_with_both: 64., diff --git a/widgets/src/text_field.rs b/widgets/src/text_field.rs index 183cf3677..5a8059d23 100644 --- a/widgets/src/text_field.rs +++ b/widgets/src/text_field.rs @@ -176,7 +176,7 @@ pub enum TextFieldState { } impl CustomStyle for TextFieldThemeSuit { - fn default_style(ctx: &impl ProviderCtx) -> Self { + fn default_style(ctx: &impl AsRef) -> Self { Self::from_theme(&Palette::of(ctx), &TypographyTheme::of(ctx)) } }