diff --git a/CHANGELOG.md b/CHANGELOG.md index 4d6c7e3b2..79dd25138 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -29,9 +29,20 @@ Please only add new entries below the [Unreleased](#unreleased---releasedate) he - **core**: The `Render::dirty_phase` method has been added to allow widgets to mark only the paint phase as dirty when it is modified. (#689 @M-Adoo) - **core**: Supports `Provider` to dirty the tree if it's a state writer. (#689 @M-Adoo) +- **core**: Added the built-in field `providers` to provide data to its descendants. (#690 @M-Adoo) +- **core**: Added `Variant` to support building widgets with variables across `Providers`. (#690 @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) +### Fixed + +- **Core**: `PartData` allows the use of a reference to create a write reference, which is unsafe. Introduce `PartRef` and `PartMut` to replace it. (#690 @M-Adoo) + + +### Breading + +- **core**: Removed `PartData`. (#690 @M-Adoo) + ## [0.4.0-alpha.22] - 2025-01-08 ### Fixed @@ -289,10 +300,7 @@ Please only add new entries below the [Unreleased](#unreleased---releasedate) he ```rust let state = Stateful::value(0132); providers!{ - providers: smallvec![ - Provider::new(state.clone_writer()), - Provider::value_of_state(state) - ], + providers: [Provider::value_of_reader(state)], @SizedBox { size: Size::new(1.,1.), on_tap: |e| { @@ -303,7 +311,7 @@ Please only add new entries below the [Unreleased](#unreleased---releasedate) he @Text { text: { // Access the provider in any descendants - let v = Provider::of::>(ctx!()); + let v = Provider::state_of::>(BuildCtx::get()); let v = v.unwrap().clone_writer(); pipe!($v.to_string()) } diff --git a/core/src/builtin_widgets.rs b/core/src/builtin_widgets.rs index 8ef34bb49..e2487c0bb 100644 --- a/core/src/builtin_widgets.rs +++ b/core/src/builtin_widgets.rs @@ -138,8 +138,9 @@ pub struct FatObj { painting_style: Option>, text_style: Option>, keep_alive: Option>, - tooltips: Option>, keep_alive_unsubscribe_handle: Option>, + tooltips: Option>, + providers: Option>, } /// Create a function widget that uses an empty `FatObj` as the host object. @@ -187,6 +188,7 @@ impl FatObj { tooltips: self.tooltips, keep_alive: self.keep_alive, keep_alive_unsubscribe_handle: self.keep_alive_unsubscribe_handle, + providers: self.providers, } } @@ -770,7 +772,7 @@ impl FatObj { self.declare_builtin_init(v, Self::get_fitted_box_widget, |m, v| m.box_fit = v) } - /// Initializes the painting style of this widget. + /// Provide a painting style to this widget. pub fn painting_style(self, v: impl DeclareInto) -> Self { self.declare_builtin_init(v, Self::get_painting_style_widget, |m, v| m.painting_style = v) } @@ -923,6 +925,16 @@ impl FatObj { self } + /// Initializes the providers of the widget. + pub fn providers(mut self, providers: impl Into>) -> Self { + if let Some(vec) = self.providers.as_mut() { + vec.extend(providers.into()); + } else { + self.providers = Some(providers.into()); + } + self + } + fn declare_builtin_init( mut self, init: impl DeclareInto, get_builtin: impl FnOnce(&mut Self) -> &State, set_value: fn(&mut B, V), @@ -964,7 +976,7 @@ where } impl<'a> FatObj> { - fn compose(self) -> Widget<'a> { + fn compose(mut self) -> Widget<'a> { macro_rules! compose_builtin_widgets { ($host: ident + [$($field: ident),*]) => { $( @@ -974,19 +986,34 @@ impl<'a> FatObj> { )* }; } + macro_rules! consume_providers_widget { + ($host: ident, + [$($field: ident: $w_ty: ty),*]) => { + $( + if let Some($field) = self.$field { + self + .providers + .get_or_insert_default() + .push(<$w_ty>::into_provider($field)); + } + )* + }; + } let mut host = self.host; + consume_providers_widget!(host, + [ + painting_style: PaintingStyleWidget, + text_style: TextStyleWidget + ]); + + compose_builtin_widgets!( + host + [track_id, padding, fitted_box, foreground, box_decoration, scrollable, layout_box] + ); + if let Some(providers) = self.providers { + host = Providers::new(providers).with_child(host); + } + compose_builtin_widgets!( host + [ - track_id, - padding, - fitted_box, - foreground, - box_decoration, - painting_style, - text_style, - scrollable, - layout_box, class, constrained_box, tooltips, diff --git a/core/src/builtin_widgets/painting_style.rs b/core/src/builtin_widgets/painting_style.rs index 31477a276..9d4858b29 100644 --- a/core/src/builtin_widgets/painting_style.rs +++ b/core/src/builtin_widgets/painting_style.rs @@ -17,17 +17,18 @@ impl<'c> ComposeChild<'c> for PaintingStyleWidget { type Child = Widget<'c>; fn compose_child(this: impl StateWriter, child: Self::Child) -> Widget<'c> { - // We need to provide the text style for the children to access. - let provider = match this.try_into_value() { + Providers::new([Self::into_provider(this)]).with_child(child) + } +} + +impl PaintingStyleWidget { + pub fn into_provider(this: impl StateWriter) -> Provider { + match this.try_into_value() { Ok(this) => Provider::new(this.painting_style), Err(this) => Provider::value_of_writer( - this.map_writer(|w| PartData::from_ref_mut(&mut w.painting_style)), + this.map_writer(|w| PartMut::new(&mut w.painting_style)), Some(DirtyPhase::LayoutSubtree), ), - }; - - Providers::new([provider]) - .with_child(child) - .into_widget() + } } } diff --git a/core/src/builtin_widgets/providers.rs b/core/src/builtin_widgets/providers.rs index 664f1026b..3cfefcf1a 100644 --- a/core/src/builtin_widgets/providers.rs +++ b/core/src/builtin_widgets/providers.rs @@ -129,7 +129,6 @@ use std::{cell::RefCell, convert::Infallible}; use ops::box_it::CloneableBoxOp; -use ribir_painter::Color; use smallvec::SmallVec; use widget_id::RenderQueryable; @@ -141,7 +140,7 @@ pub struct Providers { providers: RefCell>, } -/// Macro used to generate a function widget using `BuildVariants` as the root +/// Macro used to generate a function widget using `Providers` as the root /// widget. #[macro_export] macro_rules! providers { @@ -184,11 +183,6 @@ 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 { @@ -199,10 +193,49 @@ pub struct ProviderCtx { } impl Provider { - /// Establish a provider for `T`. + /// Establish a provider for `T` using [`Provider::of`]. + /// + /// ## Example + /// + /// ``` + /// use ribir_core::prelude::*; + /// + /// let w = providers! { + /// providers: [Provider::new(1i32)], + /// @ { + /// assert_eq!(*Provider::of::(BuildCtx::get()).unwrap(), 1); + /// Void + /// } + /// }; + /// ``` pub fn new(value: T) -> Provider { Provider::Setup(Box::new(Setup::new(value))) } - /// Establish a provider for the `Value` of a reader. + /// Establish a provider for the value of a reader. It will clone the reader + /// to create the provider to prevent any writer leaks. + /// + /// Obtain the value using [`Provider::of`], and if you want to access the + /// reader, using [`Provider::state_of`]. + /// + /// ## Example + /// + /// ``` + /// use ribir_core::prelude::*; + /// + /// let w = providers! { + /// providers: [Provider::value_of_reader(Stateful::new(1i32))], + /// @ { + /// let ctx = BuildCtx::get(); + /// // Obtain the value + /// assert_eq!(*Provider::of::(ctx).unwrap(), 1); + /// + /// // Obtain the reader + /// let reader = Provider::state_of:: + /// < as StateReader>::Reader>(ctx); + /// assert_eq!(*reader.unwrap().read(), 1); + /// Void + /// } + /// }; + /// ``` pub fn value_of_reader(value: R) -> Provider where R: StateReader, @@ -214,18 +247,45 @@ impl Provider { } } - /// Establish a provider for the `Value` of a writer. If you create this - /// provider using a writer, you can access a write reference of the value - /// through [`Provider::write_of`]. + /// Establish a provider for the value of a writer. + /// + /// Obtain the value using [`Provider::of`], get the write reference of the + /// writer using [`Provider::write_of`], and if you need to access the writer, + /// use [`Provider::state_of`]. + /// + /// The `dirty` parameter is used to specify the affected dirty phase when + /// modifying the writer's value. Depending on how your descendants use it, if + /// they rely on the writer's value for painting or layout, a dirty phase + /// should be passed; otherwise, `None` can be passed. + /// + /// Generally, if your provider affects the layout, it impacts the entire + /// subtree because the entire subtree can access and use the provider. In + /// such cases, pass `Some(DirtyPhase::LayoutSubtree)`. /// - /// The `dirty` parameter is utilized to specify the dirty phase affected when - /// the value of writer is modified. It depends on how your descendants - /// utilize it; if they rely on the writer's value for painting or layout, a - /// dirty phase should be passed, otherwise, you can pass `None`. + /// ## Example /// - /// In general, if your provider affects the layout, it impacts the entire - /// subtree. This is because the entire subtree can access the provider and - /// utilize it, so you should pass `Some(DirtyPhase::LayoutSubtree)`. + /// ``` + /// use ribir_core::prelude::*; + /// + /// let w = providers! { + /// providers: [Provider::value_of_writer(Stateful::new(1i32), None)], + /// @ { + /// let ctx = BuildCtx::get(); + /// // Obtain the value + /// assert_eq!(*Provider::of::(ctx).unwrap(), 1); + /// + /// // Obtain the writer reference + /// let w_value = Provider::write_of::(ctx); + /// *w_value.unwrap() = 2; + /// + /// // Obtain the writer + /// let writer = Provider::state_of::>(ctx); + /// assert_eq!(*writer.unwrap().write(), 2); + /// + /// Void + /// } + /// }; + /// ``` pub fn value_of_writer( value: impl StateWriter + Query, dirty: Option, ) -> Provider { @@ -247,14 +307,26 @@ impl Provider { } } - /// Access the provider of `P` within the context. - pub fn of(ctx: &impl AsRef) -> Option> { - ctx.as_ref().get_provider::

() + /// Obtain the provider of `V` from the context where `V` is created using + /// [`Provider::new`], or where it is the value of a state created with + /// [`Provider::value_of_reader`] or [`Provider::value_of_writer`]. + 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::

() + /// Obtain the write reference of the writer's value from the context in + /// which the provider is created using [`Provider::value_of_writer`]. + pub fn write_of(ctx: &impl AsRef) -> Option> { + ctx.as_ref().get_provider_write::() + } + + /// Obtain the state of `S` from the context where `S` is created using + /// [`Provider::value_of_reader`] or [`Provider::value_of_writer`]. + pub fn state_of(ctx: &impl AsRef) -> Option> + where + S: StateReader, + { + ctx.as_ref().get_provider_state::() } /// Setup the provider to the context. @@ -297,9 +369,12 @@ impl Declare for Providers { } impl ProvidersDeclarer { - pub fn providers(mut self, variants: impl Into>) -> Self { - assert!(self.providers.is_none(), "Providers already initialized"); - self.providers = Some(variants.into()); + pub fn providers(mut self, providers: impl Into>) -> Self { + if let Some(vec) = self.providers.as_mut() { + vec.extend(providers.into()); + } else { + self.providers = Some(providers.into()); + } self } } @@ -341,10 +416,6 @@ impl Providers { } } -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 @@ -435,6 +506,18 @@ impl ProviderCtx { .and_then(QueryHandle::into_mut) } + pub fn get_provider_state(&self) -> Option> + where + S: StateReader, + { + let info = Provider::info::(); + self + .data + .get(&info) + .and_then(|q| q.query_write(&QueryId::of::())) + .and_then(QueryHandle::into_ref) + } + pub(crate) fn remove_key_value_if( &mut self, f: impl Fn(&TypeInfo) -> bool, ) -> Vec<(TypeInfo, Box)> { @@ -686,10 +769,10 @@ mod tests { @Providers { providers: smallvec![Provider::new(Color::RED)], @ { - assert_eq!(BuildCtx::color(), Color::RED); + assert_eq!(BuildCtx::color().clone_value(), Color::RED); @MockMulti { @fn_widget!{ - assert_eq!(BuildCtx::color(), Color::RED); + assert_eq!(BuildCtx::color().clone_value(), Color::RED); Void } } @@ -697,7 +780,7 @@ mod tests { } @ { let color = BuildCtx::color(); - assert_eq!(color, Palette::of(BuildCtx::get()).primary()); + assert_eq!(color.clone_value(), Palette::of(BuildCtx::get()).primary()); Void } }); @@ -714,9 +797,9 @@ mod tests { providers: smallvec![ContainerColor::provider(Color::GREEN)], @ { let container_color = BuildCtx::container_color(); - assert_eq!(container_color, Color::GREEN); + assert_eq!(container_color.clone_value(), Color::GREEN); let color = BuildCtx::color(); - assert_eq!(color, Color::RED); + assert_eq!(color.clone_value(), Color::RED); Void } } diff --git a/core/src/builtin_widgets/text_style.rs b/core/src/builtin_widgets/text_style.rs index a77933f02..a15d44ad9 100644 --- a/core/src/builtin_widgets/text_style.rs +++ b/core/src/builtin_widgets/text_style.rs @@ -16,17 +16,18 @@ impl<'c> ComposeChild<'c> for TextStyleWidget { type Child = Widget<'c>; fn compose_child(this: impl StateWriter, child: Self::Child) -> Widget<'c> { - // We need to provide the text style for the children to access. - let provider = match this.try_into_value() { + Providers::new([Self::into_provider(this)]).with_child(child) + } +} + +impl TextStyleWidget { + pub fn into_provider(this: impl StateWriter) -> Provider { + match this.try_into_value() { Ok(this) => Provider::new(this.text_style), Err(this) => Provider::value_of_writer( - this.map_writer(|w| PartData::from_ref_mut(&mut w.text_style)), + this.map_writer(|w| PartMut::new(&mut w.text_style)), Some(DirtyPhase::LayoutSubtree), ), - }; - - Providers::new([provider]) - .with_child(child) - .into_widget() + } } } diff --git a/core/src/builtin_widgets/theme.rs b/core/src/builtin_widgets/theme.rs index 701512ddb..a76312f28 100644 --- a/core/src/builtin_widgets/theme.rs +++ b/core/src/builtin_widgets/theme.rs @@ -106,9 +106,21 @@ pub struct Theme { pub icon_font: IconFont, } +/// A type for providing the icon font of the widget. #[derive(Clone, Debug, Default)] pub struct IconFont(pub FontFace); +/// A container color of the theme providing for the widgets that should +/// consider as their default container brush. The user can provide another +/// `ContainerColor` to customize use the widget. +#[derive(Clone)] +#[repr(transparent)] +pub struct ContainerColor(pub Color); + +impl ContainerColor { + pub fn provider(color: Color) -> Provider { Provider::new(ContainerColor(color)) } +} + impl Theme { pub fn of(ctx: &impl AsRef) -> QueryRef { Provider::of(ctx).unwrap() } @@ -130,11 +142,11 @@ impl Theme { 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, + // which is not the correct method of using `PartRef`. 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())) } + unsafe { PartRef::from_ptr(ContainerColor(t.palette.secondary_container())) } }); let providers = smallvec![ diff --git a/core/src/builtin_widgets/theme/palette.rs b/core/src/builtin_widgets/theme/palette.rs index bc1ebcbe7..81af53b9d 100644 --- a/core/src/builtin_widgets/theme/palette.rs +++ b/core/src/builtin_widgets/theme/palette.rs @@ -253,6 +253,13 @@ impl Palette { .with_lightness(self.lightness_cfg().surface_container) } + #[inline] + pub fn on_surface_container(&self) -> Color { + self + .neutral + .with_lightness(self.lightness_cfg().color_group.on_container) + } + #[inline] pub fn surface_container_high(&self) -> Color { self @@ -345,6 +352,13 @@ impl Palette { color.with_lightness(self.lightness_cfg().color_group.on_container) } + pub fn lightness_group(&self) -> &LightnessGroup { + match self.brightness { + Brightness::Dark => &self.dark.color_group, + Brightness::Light => &self.light.color_group, + } + } + #[inline] fn lightness_cfg(&self) -> &LightnessCfg { match self.brightness { diff --git a/core/src/context.rs b/core/src/context.rs index c3620895a..85fde691b 100644 --- a/core/src/context.rs +++ b/core/src/context.rs @@ -10,3 +10,5 @@ pub mod app_ctx; #[cfg(feature = "tokio-async")] pub use app_ctx::tokio_async::*; pub use app_ctx::*; +mod build_variant; +pub use build_variant::*; diff --git a/core/src/context/build_ctx.rs b/core/src/context/build_ctx.rs index a28084305..7cd29b489 100644 --- a/core/src/context/build_ctx.rs +++ b/core/src/context/build_ctx.rs @@ -18,14 +18,15 @@ impl BuildCtx { /// Return the window of this context is created from. pub fn window(&self) -> Sc { self.tree().window() } - /// Return the `Color` provide in the current build context. - pub fn color() -> Color { *Provider::of::(BuildCtx::get()).unwrap() } + /// Return the variant of `Color` provided in the current build context. + pub fn color() -> Variant { Variant::new(BuildCtx::get()).unwrap() } - /// Return the `ContainerColor` provide in the current build context. - pub fn container_color() -> Color { - Provider::of::(BuildCtx::get()) - .map(|v| v.0) + /// Return the variant of the `ContainerColor` provide in the current build + /// context and unwrap it as a `Color`. + pub fn container_color() -> VariantMap Color> { + Variant::new(BuildCtx::get()) .unwrap() + .map(|c: ContainerColor| c.0) } pub(crate) fn tree(&self) -> &WidgetTree { diff --git a/core/src/context/build_variant.rs b/core/src/context/build_variant.rs new file mode 100644 index 000000000..efb7a3348 --- /dev/null +++ b/core/src/context/build_variant.rs @@ -0,0 +1,163 @@ +use crate::prelude::*; +/// `Variant` is an enum designed to help you store a clone of a provider. It +/// serves as a shortcut for `Provider::state_of` and `Provider::of`. +/// +/// Initially, it checks for the existence of a stateful provider; if not +/// found, it proceeds to check the value provider. +/// +/// It supports conversion to `DeclareInit` for initialization of a declare +/// object, enabling the object to track changes in the provider value if it's a +/// stateful provider. +/// +/// ## Example +/// +/// ``` +/// use ribir_core::prelude::*; +/// +/// let _ = fn_widget! { +/// let color = Variant::::new(BuildCtx::get()).unwrap(); +/// @Container { +/// size: Size::new(100., 100.), +/// background: color, +/// } +/// }; +/// ``` +/// +/// Here, we create a 100x100 rectangle with a background using the `Color` +/// Provider. If an ancestor provides a `Stateful`, this rectangle will +/// reflect changes in color. +pub enum Variant { + Stateful(Stateful), + Value(V), +} + +/// `VariantMap` is a Variant that maps a value to another value using a +/// function. +#[derive(Clone)] +pub struct VariantMap { + variant: Variant, + map: F, +} + +impl Variant { + /// Creates a new `Variant` from a provider context. + pub fn new(ctx: &impl AsRef) -> Option { + if let Some(value) = Provider::state_of::>(ctx) { + Some(Variant::Stateful(value.clone_writer())) + } else { + Provider::of::(ctx).map(|v| Variant::Value(v.clone())) + } + } + + /// Maps a value to another value using a function. + pub fn map(self, map: F) -> VariantMap + where + F: Fn(V) -> U, + { + VariantMap { variant: self, map } + } + + /// Clones the value of the variant. + pub fn clone_value(&self) -> V { + match self { + Variant::Value(v) => v.clone(), + Variant::Stateful(v) => v.read().clone(), + } + } +} + +impl Variant { + /// Convert a color variant to another color variant with its base lightness + /// tone. + pub fn into_base_color( + self, ctx: &impl AsRef, + ) -> VariantMap Color> { + let p = Palette::of(ctx); + let lightness = p.lightness_group().base; + self.map(move |c| c.with_lightness(lightness)) + } + + /// Converts a color variant to another color variant with its container + /// lightness tone. + pub fn into_container_color( + self, ctx: &impl AsRef, + ) -> VariantMap Color> { + let p = Palette::of(ctx); + let lightness = p.lightness_group().container; + self.map(move |c| c.with_lightness(lightness)) + } + + /// Converts a color variant to another color variant that its lightness tone + /// is suitable display on its base color. + pub fn on_this_color( + self, ctx: &impl AsRef, + ) -> VariantMap Color> { + let p = Palette::of(ctx); + let lightness = p.lightness_group().on; + self.map(move |c| c.with_lightness(lightness)) + } + + /// Converts a color variant to another color variant that its lightness tone + /// is suitable display on its container color. + pub fn on_this_container_color( + self, ctx: &impl AsRef, + ) -> VariantMap Color> { + let p = Palette::of(ctx); + let lightness = p.lightness_group().on_container; + self.map(move |c| c.with_lightness(lightness)) + } +} + +impl VariantMap { + /// Maps a value to another value using a function. + pub fn map(self, map: F2) -> VariantMap U2> + where + F: Fn(V) -> U1, + F2: Fn(U1) -> U2, + { + VariantMap { variant: self.variant, map: move |v| map((self.map)(v)) } + } + + /// Clones the value of the variant. + pub fn clone_value(&self) -> U + where + F: Fn(V) -> U, + V: Clone, + { + (self.map)(self.variant.clone_value()) + } +} + +impl DeclareFrom, 0> for DeclareInit +where + U: From + 'static, +{ + fn declare_from(value: Variant) -> Self { + match value { + Variant::Stateful(value) => pipe!($value.clone()).declare_into(), + Variant::Value(value) => DeclareInit::Value(value.into()), + } + } +} + +impl DeclareFrom, 0> for DeclareInit

+where + F: Fn(V) -> U + 'static, + P: From + 'static, +{ + fn declare_from(value: VariantMap) -> Self { + match value.variant { + Variant::Stateful(s) => pipe!(P::from((value.map)($s.clone()))).declare_into(), + Variant::Value(v) => DeclareInit::Value((value.map)(v).into()), + } + } +} + +impl Clone for Variant { + fn clone(&self) -> Self { + match self { + Variant::Stateful(value) => Variant::Stateful(value.clone_writer()), + Variant::Value(value) => Variant::Value(value.clone()), + } + } +} diff --git a/core/src/query.rs b/core/src/query.rs index a687815df..f6483a895 100644 --- a/core/src/query.rs +++ b/core/src/query.rs @@ -390,7 +390,7 @@ impl dyn QueryAny { #[cfg(test)] mod tests { use super::*; - use crate::{prelude::PartData, reset_test_env, state::State}; + use crate::{prelude::PartMut, reset_test_env, state::State}; #[test] fn query_ref() { @@ -430,7 +430,7 @@ mod tests { } let x = State::value(X { a: 0, _b: 1 }); - let y = x.split_writer(|x| PartData::from_ref_mut(&mut x.a)); + let y = x.split_writer(|x| PartMut::new(&mut x.a)); { let h = y.query(&QueryId::of::()).unwrap(); assert!(h.downcast_ref::().is_some()); diff --git a/core/src/state.rs b/core/src/state.rs index 076d36c93..8e4c4a37f 100644 --- a/core/src/state.rs +++ b/core/src/state.rs @@ -11,7 +11,7 @@ pub use prior_op::*; use ribir_algo::Sc; use rxrust::ops::box_it::{BoxOp, CloneableBoxOp}; pub use splitted_state::*; -pub use state_cell::{PartData, ReadRef}; +pub use state_cell::*; use state_cell::{StateCell, ValueMutRef}; pub use stateful::*; pub use watcher::*; @@ -42,7 +42,7 @@ pub trait StateReader: 'static { #[inline] fn map_reader(&self, map: F) -> MapReader where - F: Fn(&Self::Value) -> PartData + Clone, + F: Fn(&Self::Value) -> PartRef + Clone, { MapReader { origin: self.clone_reader(), part_map: map } } @@ -123,7 +123,7 @@ pub trait StateWriter: StateWatcher { #[inline] fn split_writer(&self, mut_map: W) -> SplittedWriter where - W: Fn(&mut Self::Value) -> PartData + Clone, + W: Fn(&mut Self::Value) -> PartMut + Clone, { SplittedWriter::new(self.clone_writer(), mut_map) } @@ -142,7 +142,7 @@ pub trait StateWriter: StateWatcher { #[inline] fn map_writer(&self, part_map: M) -> MapWriter where - M: Fn(&mut Self::Value) -> PartData + Clone, + M: Fn(&mut Self::Value) -> PartMut + Clone, { let origin = self.clone_writer(); MapWriter { origin, part_map } @@ -270,9 +270,9 @@ impl State { impl<'a, V: ?Sized> WriteRef<'a, V> { pub fn map(mut orig: WriteRef<'a, V>, part_map: M) -> WriteRef<'a, U> where - M: Fn(&mut V) -> PartData, + M: Fn(&mut V) -> PartMut, { - let inner = part_map(&mut orig.value); + let inner = part_map(&mut orig.value).inner; let borrow = orig.value.borrow.clone(); let value = ValueMutRef { inner, borrow }; @@ -295,17 +295,18 @@ impl<'a, V: ?Sized> WriteRef<'a, V> { /// let c = Stateful::new(vec![1, 2, 3]); /// let b1: WriteRef<'_, Vec> = c.write(); /// let b2: Result, _> = - /// WriteRef::filter_map(b1, |v| v.get(1).map(PartData::from_ref)); + /// WriteRef::filter_map(b1, |v| v.get_mut(1).map(PartMut::::new)); /// assert_eq!(*b2.unwrap(), 2); /// ``` pub fn filter_map( mut orig: WriteRef<'a, V>, part_map: M, ) -> Result, Self> where - M: Fn(&mut V) -> Option>, + M: Fn(&mut V) -> Option>, { match part_map(&mut orig.value) { Some(inner) => { + let inner = inner.inner; let borrow = orig.value.borrow.clone(); let value = ValueMutRef { inner, borrow }; let WriteRef { modify_scope, info, .. } = orig; @@ -320,10 +321,11 @@ impl<'a, V: ?Sized> WriteRef<'a, V> { mut orig: WriteRef<'a, V>, f: F, ) -> (WriteRef<'a, U1>, WriteRef<'a, U2>) where - F: FnOnce(&mut V) -> (PartData, PartData), + F: FnOnce(&mut V) -> (PartMut, PartMut), { let WriteRef { info, modify_scope, modified, .. } = orig; let (a, b) = f(&mut *orig.value); + let (a, b) = (a.inner, b.inner); let borrow = orig.value.borrow.clone(); let a = ValueMutRef { inner: a, borrow: borrow.clone() }; let b = ValueMutRef { inner: b, borrow }; @@ -445,7 +447,7 @@ mod tests { reset_test_env!(); let origin = State::value(Origin { a: 0, b: 0 }); - let map_state = origin.map_writer(|v| PartData::from_ref_mut(&mut v.b)); + let map_state = origin.map_writer(|v| PartMut::new(&mut v.b)); let track_origin = Sc::new(Cell::new(0)); let track_map = Sc::new(Cell::new(0)); @@ -481,7 +483,7 @@ mod tests { reset_test_env!(); let origin = State::value(Origin { a: 0, b: 0 }); - let split = origin.split_writer(|v| PartData::from_ref_mut(&mut v.b)); + let split = origin.split_writer(|v| PartMut::new(&mut v.b)); let track_origin = Sc::new(Cell::new(0)); let track_split = Sc::new(Cell::new(0)); @@ -536,11 +538,11 @@ mod tests { let _map_writer_compose_widget = fn_widget! { Stateful::new((C, 0)) - .map_writer(|v| PartData::from_ref_mut(&mut v.0)) + .map_writer(|v| PartMut::new(&mut v.0)) }; let _split_writer_compose_widget = fn_widget! { Stateful::new((C, 0)) - .split_writer(|v| PartData::from_ref_mut(&mut v.0)) + .split_writer(|v| PartMut::new(&mut v.0)) }; } @@ -586,24 +588,24 @@ mod tests { let _map_writer_with_child = fn_widget! { let w = Stateful::new((CC, 0)) - .map_writer(|v| PartData::from_ref_mut(&mut v.0)); + .map_writer(|v| PartMut::new(&mut v.0)); @$w { @{ Void } } }; let _map_writer_without_child = fn_widget! { Stateful::new((CC, 0)) - .map_writer(|v| PartData::from_ref_mut(&mut v.0)) + .map_writer(|v| PartMut::new(&mut v.0)) }; let _split_writer_with_child = fn_widget! { let w = Stateful::new((CC, 0)) - .split_writer(|v| PartData::from_ref_mut(&mut v.0)); + .split_writer(|v| PartMut::new(&mut v.0)); @$w { @{ Void } } }; let _split_writer_without_child = fn_widget! { Stateful::new((CC, 0)) - .split_writer(|v| PartData::from_ref_mut(&mut v.0)) + .split_writer(|v| PartMut::new(&mut v.0)) }; } @@ -625,17 +627,17 @@ mod tests { }; let _map_reader_render_widget = fn_widget! { - Stateful::new((Void, 0)).map_reader(|v| PartData::from_ref(&v.0)) + Stateful::new((Void, 0)).map_reader(|v| PartRef::new(&v.0)) }; let _map_writer_render_widget = fn_widget! { Stateful::new((Void, 0)) - .map_writer(|v| PartData::from_ref_mut(&mut v.0)) + .map_writer(|v| PartMut::new(&mut v.0)) }; let _split_writer_render_widget = fn_widget! { Stateful::new((Void, 0)) - .split_writer(|v| PartData::from_ref_mut(&mut v.0)) + .split_writer(|v| PartMut::new(&mut v.0)) }; } @@ -644,11 +646,11 @@ mod tests { fn trait_object_part_data() { reset_test_env!(); let s = State::value(0); - let m = s.split_writer(|v| PartData::from_ref(v as &mut dyn Any)); + let m = s.split_writer(|v| PartMut::new(v as &mut dyn Any)); let v: ReadRef = m.read(); assert_eq!(*v.downcast_ref::().unwrap(), 0); - let s = s.map_writer(|v| PartData::from_ref(v as &mut dyn Any)); + let s = s.map_writer(|v| PartMut::new(v as &mut dyn Any)); let v: ReadRef = s.read(); assert_eq!(*v.downcast_ref::().unwrap(), 0); } diff --git a/core/src/state/map_state.rs b/core/src/state/map_state.rs index c18ed7c3f..6e11709d5 100644 --- a/core/src/state/map_state.rs +++ b/core/src/state/map_state.rs @@ -22,7 +22,7 @@ impl StateReader for MapReader where Self: 'static, S: StateReader, - M: Fn(&S::Value) -> PartData + Clone, + M: Fn(&S::Value) -> PartRef + Clone, { type Value = V; type OriginReader = S; @@ -52,7 +52,7 @@ impl StateReader for MapWriterAsReader where Self: 'static, S: StateReader, - M: Fn(&mut S::Value) -> PartData + Clone, + M: Fn(&mut S::Value) -> PartMut + Clone, { type Value = V; type OriginReader = S; @@ -84,7 +84,7 @@ impl StateReader for MapWriter where Self: 'static, S: StateWriter, - M: Fn(&mut S::Value) -> PartData + Clone, + M: Fn(&mut S::Value) -> PartMut + Clone, { type Value = V; type OriginReader = S; @@ -116,7 +116,7 @@ impl StateWatcher for MapWriter where Self: 'static, W: StateWriter, - M: Fn(&mut W::Value) -> PartData + Clone, + M: Fn(&mut W::Value) -> PartMut + Clone, { #[inline] fn raw_modifies(&self) -> CloneableBoxOp<'static, ModifyScope, Infallible> { @@ -128,7 +128,7 @@ impl StateWriter for MapWriter where Self: 'static, W: StateWriter, - M: Fn(&mut W::Value) -> PartData + Clone, + M: Fn(&mut W::Value) -> PartMut + Clone, { type Writer = MapWriter; type OriginWriter = W; @@ -165,7 +165,7 @@ impl RenderProxy for MapReader where Self: 'static, S: StateReader, - F: Fn(&S::Value) -> PartData + Clone, + F: Fn(&S::Value) -> PartRef + Clone, V: Render, { #[inline] @@ -176,7 +176,7 @@ impl RenderProxy for MapWriterAsReader where Self: 'static, S: StateReader, - F: Fn(&mut S::Value) -> PartData + Clone, + F: Fn(&mut S::Value) -> PartMut + Clone, V: Render, { fn proxy(&self) -> impl Deref { self.read() } diff --git a/core/src/state/splitted_state.rs b/core/src/state/splitted_state.rs index 09d22dd9b..e4738401e 100644 --- a/core/src/state/splitted_state.rs +++ b/core/src/state/splitted_state.rs @@ -16,7 +16,7 @@ impl StateReader for SplittedWriter where Self: 'static, O: StateWriter, - W: Fn(&mut O::Value) -> PartData + Clone, + W: Fn(&mut O::Value) -> PartMut + Clone, { type Value = V; type OriginReader = O; @@ -48,7 +48,7 @@ impl StateWatcher for SplittedWriter where Self: 'static, O: StateWriter, - W: Fn(&mut O::Value) -> PartData + Clone, + W: Fn(&mut O::Value) -> PartMut + Clone, { fn raw_modifies(&self) -> CloneableBoxOp<'static, ModifyScope, std::convert::Infallible> { self.info.notifier.raw_modifies().box_it() @@ -59,7 +59,7 @@ impl StateWriter for SplittedWriter where Self: 'static, O: StateWriter, - W: Fn(&mut O::Value) -> PartData + Clone, + W: Fn(&mut O::Value) -> PartMut + Clone, { type Writer = SplittedWriter; type OriginWriter = O; @@ -93,7 +93,7 @@ where impl SplittedWriter where O: StateWriter, - W: Fn(&mut O::Value) -> PartData + Clone, + W: Fn(&mut O::Value) -> PartMut + Clone, { pub(super) fn new(origin: O, mut_map: W) -> Self { Self { origin, splitter: mut_map, info: Sc::new(WriterInfo::new()) } @@ -108,8 +108,10 @@ where assert!(!orig.modified); orig.modify_scope.remove(ModifyScope::FRAMEWORK); orig.modified = true; - let value = - ValueMutRef { inner: (self.splitter)(&mut orig.value), borrow: orig.value.borrow.clone() }; + let value = ValueMutRef { + inner: (self.splitter)(&mut orig.value).inner, + borrow: orig.value.borrow.clone(), + }; WriteRef { value, modified: false, modify_scope, info: &self.info } } diff --git a/core/src/state/state_cell.rs b/core/src/state/state_cell.rs index 9430d29df..5c667d1fa 100644 --- a/core/src/state/state_cell.rs +++ b/core/src/state/state_cell.rs @@ -2,6 +2,7 @@ //! manage the borrow flag. use std::{ cell::{Cell, UnsafeCell}, + marker::PhantomData, ops::{Deref, DerefMut}, ptr::NonNull, }; @@ -57,7 +58,7 @@ impl StateCell { // SAFETY: `BorrowRef` ensures that there is only immutable access // to the value while borrowed. - let inner = PartData::PartRef(unsafe { NonNull::new_unchecked(self.data.get()) }); + let inner = InnerPart::Ref(unsafe { NonNull::new_unchecked(self.data.get()) }); ReadRef { inner, borrow: BorrowRef { borrow } } } @@ -84,7 +85,7 @@ impl StateCell { borrow.set(UNUSED - 1); let v_ref = BorrowRefMut { borrow }; - let inner = PartData::PartRef(unsafe { NonNull::new_unchecked(self.data.get()) }); + let inner = InnerPart::Ref(unsafe { NonNull::new_unchecked(self.data.get()) }); ValueMutRef { inner, borrow: v_ref } } @@ -93,28 +94,41 @@ impl StateCell { pub(super) fn into_inner(self) -> W { self.data.into_inner() } } -/// A partial data of a state, which should be point to the part data of the -/// state. -// todo: `PartData` should be a private type; otherwise, we can use a &T to create mutable data. +/// A partial reference value of a state, which should be point to the part data +/// of the state. #[derive(Clone)] -pub enum PartData { - PartRef(NonNull), - PartData(Box), +pub struct PartRef<'a, T: ?Sized> { + pub(crate) inner: InnerPart, + _phantom: PhantomData<&'a T>, } -impl PartData { - /// Create a `PartData` from a reference. - pub fn from_ref(v: &T) -> Self { PartData::PartRef(NonNull::from(v)) } +/// A partial mutable reference value of a state, which should be point to the +/// part data of the state. +#[derive(Clone)] +pub struct PartMut<'a, T: ?Sized> { + pub(crate) inner: InnerPart, + _phantom: PhantomData<&'a mut T>, +} + +#[derive(Clone)] +pub(crate) enum InnerPart { + Ref(NonNull), + // Box the `T` to allow it to be `?Sized`. + Ptr(Box), +} - /// Create a `PartData` from a mutable reference. - pub fn from_ref_mut(v: &mut T) -> Self { PartData::PartRef(NonNull::from(v)) } +impl<'a, T: ?Sized> PartRef<'a, T> { + /// Create a `PartRef` from a reference. + pub fn new(v: &T) -> Self { + Self { inner: InnerPart::Ref(NonNull::from(v)), _phantom: PhantomData } + } } -impl PartData { - /// Create a `PartData` from a pointer that points to the part data of the +impl<'a, T> PartRef<'a, T> { + /// Create a `PartRef` from a pointer that points to the part data of the /// original data. For example, `Option<&T>`, `Box`, `Arc`, `Rc`, etc. /// - /// The data used to create this `PartData` must point to the data in your + /// The data used to create this `PartRef` must point to the data in your /// original data. /// /// @@ -127,7 +141,7 @@ impl PartData { /// // We get the state of the second element. /// // `v.get(1)` returns an `Option<&i32>`, which is valid in the vector. /// let elem2 = vec.map_reader(|v| unsafe { - /// PartData::from_ptr(std::mem::transmute::<_, Option<&'static i32>>(v.get(1))) + /// PartRef::from_ptr(std::mem::transmute::<_, Option<&'static i32>>(v.get(1))) /// }); /// ``` /// @@ -140,14 +154,64 @@ impl PartData { /// /// let ab = Stateful::new((1, 2)); /// - /// let ab2 = ab.map_reader(|v| unsafe { PartData::from_ptr(*v) }); + /// let ab2 = ab.map_reader(|v| unsafe { PartRef::from_ptr(*v) }); + /// + /// // The `_a` may result in a dangling pointer issue since it utilizes the + /// // value of `ab2.read()`. However, `ab2` copies the value of `ab` rather + /// // than referencing it. When `ab2.read()` is dropped, `_a` still points to + /// // it, making access to `_a` dangerous. + /// let _a = ReadRef::map(ab2.read(), |v| unsafe { PartRef::from_ptr(v.0) }); + /// ``` + pub unsafe fn from_ptr(ptr_data: T) -> Self { + Self { inner: InnerPart::Ptr(Box::new(ptr_data)), _phantom: PhantomData } + } +} + +impl<'a, T: ?Sized> PartMut<'a, T> { + /// Create a `PartMut` from a mutable reference. + pub fn new(v: &mut T) -> Self { + Self { inner: InnerPart::Ref(NonNull::from(v)), _phantom: PhantomData } + } +} + +impl<'a, T> PartMut<'a, T> { + /// Create a `PartMut` from a pointer that points to the part data of the + /// original data. For example, `Option<&T>`, `Box`, `Arc`, `Rc`, etc. + /// + /// The data used to create this `PartMut` must point to the data in your + /// original data. + /// + /// + /// # Example + /// + /// ``` + /// use ribir_core::prelude::*; + /// + /// let vec = Stateful::new(vec![1, 2, 3]); + /// // We get the state of the second element. + /// // `v.get_mut(1)` returns an `Option<&mut i32>`, which is valid in the vector. + /// let elem2 = vec.map_writer(|v| unsafe { + /// PartMut::from_ptr(std::mem::transmute::<_, Option<&'static i32>>(v.get_mut(1))) + /// }); + /// ``` /// - /// // The `_a` may result in a dangling pointer issue since it utilizes the value of `ab2.read()`. However, `ab2` copies the - /// // value of `ab` rather than referencing it. - /// // When `ab2.read()` is dropped, `_a` still points to it, making access to `_a` dangerous. - /// let _a = ReadRef::map(ab2.read(), |v| unsafe { PartData::from_ptr(v.0) }); + /// # Safety + /// + /// Exercise caution when using this method, as it can lead to dangling + /// pointers in the state reference internals. /// ``` + /// use ribir_core::prelude::*; /// + /// let ab = Stateful::new((1, 2)); + /// + /// let ab2 = ab.map_writer(|v| unsafe { PartMut::from_ptr(*v) }); + /// + /// // The `_a` may result in a dangling pointer issue since it utilizes the + /// // value of `ab2.write()`. However, `ab2` copies the value of `ab` rather + /// // than referencing it. When `ab2.write()` is dropped, `_a` still points + /// // to it, making access to `_a` dangerous. + /// let _a = WriteRef::map(ab2.write(), |v| unsafe { PartMut::from_ptr(v.0) }); + /// ``` /// /// Otherwise, your modifications will not be applied to the state. /// ``` @@ -158,21 +222,23 @@ impl PartData { /// // We create a state of the second element. However, this state is a copy of /// // the vector because `v[1]` returns a copy of the value in the vector, not a /// // reference. - /// let mut elem2 = vec.map_writer(|v| unsafe { PartData::from_ptr(v[1]) }); + /// let mut elem2 = vec.map_writer(|v| unsafe { PartMut::from_ptr(v[1]) }); /// /// // This modification will not alter the `vec`. /// *elem2.write() = 20; /// ``` - pub unsafe fn from_ptr(ptr_data: T) -> Self { PartData::PartData(Box::new(ptr_data)) } + pub unsafe fn from_ptr(ptr_data: T) -> Self { + Self { inner: InnerPart::Ptr(Box::new(ptr_data)), _phantom: PhantomData } + } } pub struct ReadRef<'a, T: ?Sized> { - pub(crate) inner: PartData, + pub(crate) inner: InnerPart, pub(crate) borrow: BorrowRef<'a>, } pub(crate) struct ValueMutRef<'a, T: ?Sized> { - pub(crate) inner: PartData, + pub(crate) inner: InnerPart, pub(crate) borrow: BorrowRefMut<'a>, } @@ -184,21 +250,21 @@ pub(crate) struct BorrowRef<'b> { borrow: &'b Cell, } -impl Deref for PartData { +impl Deref for InnerPart { type Target = T; fn deref(&self) -> &Self::Target { match self { - PartData::PartRef(ptr) => unsafe { ptr.as_ref() }, - PartData::PartData(data) => data, + InnerPart::Ref(ptr) => unsafe { ptr.as_ref() }, + InnerPart::Ptr(data) => data, } } } -impl DerefMut for PartData { +impl DerefMut for InnerPart { fn deref_mut(&mut self) -> &mut Self::Target { match self { - PartData::PartRef(ptr) => unsafe { ptr.as_mut() }, - PartData::PartData(data) => data, + InnerPart::Ref(ptr) => unsafe { ptr.as_mut() }, + InnerPart::Ptr(data) => data, } } } @@ -255,8 +321,8 @@ impl BorrowRef<'_> { impl<'a, V: ?Sized> ReadRef<'a, V> { /// Make a new `ReadRef` by mapping the value of the current `ReadRef`. - pub fn map(r: ReadRef<'a, V>, f: impl FnOnce(&V) -> PartData) -> ReadRef<'a, U> { - ReadRef { inner: f(&r.inner), borrow: r.borrow } + pub fn map(r: ReadRef<'a, V>, f: impl FnOnce(&V) -> PartRef) -> ReadRef<'a, U> { + ReadRef { inner: f(&r.inner).inner, borrow: r.borrow } } /// Makes a new `ReadRef` for an optional component of the borrowed data. The @@ -274,18 +340,17 @@ impl<'a, V: ?Sized> ReadRef<'a, V> { /// /// let c = Stateful::new(vec![1, 2, 3]); /// let b1: ReadRef<'_, Vec> = c.read(); - /// let b2: Result, _> = - /// ReadRef::filter_map(b1, |v| v.get(1).map(PartData::from_ref)); + /// let b2: Result, _> = ReadRef::filter_map(b1, |v| v.get(1).map(PartRef::new)); /// assert_eq!(*b2.unwrap(), 2); /// ``` pub fn filter_map( orig: ReadRef<'a, V>, part_map: M, ) -> std::result::Result, Self> where - M: Fn(&V) -> Option>, + M: Fn(&V) -> Option>, { match part_map(&orig.inner) { - Some(inner) => Ok(ReadRef { inner, borrow: orig.borrow }), + Some(inner) => Ok(ReadRef { inner: inner.inner, borrow: orig.borrow }), None => Err(orig), } } @@ -293,29 +358,20 @@ impl<'a, V: ?Sized> ReadRef<'a, V> { /// Split the current `ReadRef` into two `ReadRef`s by mapping the value to /// two parts. pub fn map_split( - orig: ReadRef<'a, V>, f: impl FnOnce(&V) -> (PartData, PartData), + orig: ReadRef<'a, V>, f: impl FnOnce(&V) -> (PartRef, PartRef), ) -> (ReadRef<'a, U>, ReadRef<'a, W>) { let (a, b) = f(&*orig); let borrow = orig.borrow.clone(); - (ReadRef { inner: a, borrow: borrow.clone() }, ReadRef { inner: b, borrow }) + (ReadRef { inner: a.inner, borrow: borrow.clone() }, ReadRef { inner: b.inner, borrow }) } pub(crate) fn mut_as_ref_map( - orig: ReadRef<'a, V>, f: impl FnOnce(&mut V) -> PartData, + orig: ReadRef<'a, V>, f: impl FnOnce(&mut V) -> PartMut, ) -> ReadRef<'a, U> { - let ReadRef { inner: value, borrow } = orig; - let value = match value { - PartData::PartRef(mut ptr) => unsafe { - // Safety: This method is used to map a state to a part of it. Although a `&mut - // T` is passed to the closure, it is the user's responsibility to - // ensure that the closure does not modify the state. - f(ptr.as_mut()) - }, - PartData::PartData(mut data) => f(&mut data), - }; - - ReadRef { inner: value, borrow } + let ReadRef { mut inner, borrow } = orig; + let value = f(&mut inner); + ReadRef { inner: value.inner, borrow } } } @@ -351,3 +407,15 @@ impl Debug for WriteRef<'_, T> { impl Debug for QueryRef<'_, T> { fn fmt(&self, f: &mut Formatter<'_>) -> Result { Debug::fmt(&**self, f) } } + +impl<'a, T: ?Sized> From> for PartRef<'a, T> { + fn from(part: PartMut) -> Self { Self { inner: part.inner, _phantom: PhantomData } } +} + +impl<'a, T> From<&'a T> for PartRef<'a, T> { + fn from(part: &'a T) -> Self { PartRef::new(part) } +} + +impl<'a, T> From<&'a mut T> for PartMut<'a, T> { + fn from(part: &'a mut T) -> Self { PartMut::new(part) } +} diff --git a/core/src/state/stateful.rs b/core/src/state/stateful.rs index 9e63e3fb6..35b0ce158 100644 --- a/core/src/state/stateful.rs +++ b/core/src/state/stateful.rs @@ -149,6 +149,17 @@ impl Stateful { Self { data: Sc::new(StateCell::new(data)), info: Sc::new(WriterInfo::new()) } } + pub fn from_pipe(p: impl Pipe) -> (Self, BoxSubscription<'static>) + where + Self: 'static, + { + let (v, p) = p.unzip(ModifyScope::DATA, None); + let s = Stateful::new(v); + let s2 = s.clone_writer(); + let u = p.subscribe(move |(_, v)| *s2.write() = v); + (s, u) + } + fn write_ref(&self, scope: ModifyScope) -> WriteRef<'_, W> { let value = self.data.write(); WriteRef { value, modified: false, modify_scope: scope, info: &self.info } @@ -265,7 +276,7 @@ mod tests { { drop_writer_subscribe( #[allow(clippy::redundant_closure)] - Stateful::new(()).map_writer(|v| PartData::from_ref_mut(v)), + Stateful::new(()).map_writer(|v| PartMut::new(v)), drop_cnt.clone(), ); }; @@ -275,7 +286,7 @@ mod tests { { drop_writer_subscribe( #[allow(clippy::redundant_closure)] - Stateful::new(()).split_writer(|v| PartData::from_ref_mut(v)), + Stateful::new(()).split_writer(|v| PartMut::new(v)), drop_cnt.clone(), ); }; diff --git a/docs/en/get_started/quick_start.md b/docs/en/get_started/quick_start.md index 2925d7e43..d4097f05c 100644 --- a/docs/en/get_started/quick_start.md +++ b/docs/en/get_started/quick_start.md @@ -687,8 +687,8 @@ struct AppData { } let state = State::value(AppData { count: 0 }); -let map_count = state.map_writer(|d| PartData::from_ref_mut(&mut d.count)); -let split_count = state.split_writer(|d| PartData::from_ref_mut(&mut d.count)); +let map_count = state.map_writer(|d| PartMut::new(&mut d.count)); +let split_count = state.split_writer(|d| PartMut::new(&mut d.count)); watch!($state.count).subscribe(|_| println!("Parent data")); watch!(*$map_count).subscribe(|_| println!("Child(map) data")); @@ -759,7 +759,7 @@ struct AppData { } let state: State = State::value(AppData { count: 0 }); -let split_count = state.split_writer(|d| PartData::from_ref_mut(&mut d.count)); +let split_count = state.split_writer(|d| PartMut::new(&mut d.count)); // the root state's origin state is itself let _: &State = state.origin_reader(); diff --git a/docs/zh/get_started/quick_start.md b/docs/zh/get_started/quick_start.md index 90db6261d..1901b22f4 100644 --- a/docs/zh/get_started/quick_start.md +++ b/docs/zh/get_started/quick_start.md @@ -689,8 +689,8 @@ struct AppData { } let state = State::value(AppData { count: 0 }); -let map_count = state.map_writer(|d| PartData::from_ref(&mut d.count)); -let split_count = state.split_writer(|d| PartData::from_ref(&mut d.count)); +let map_count = state.map_writer(|d| PartMut::new(&mut d.count)); +let split_count = state.split_writer(|d| PartMut::new(&mut d.count)); watch!($state.count).subscribe(|_| println!("父状态数据")); watch!(*$map_count).subscribe(|_| println!("子状态(转换)数据")); @@ -762,7 +762,7 @@ struct AppData { } let state: State = State::value(AppData { count: 0 }); -let split_count = state.split_writer(|d| PartData::from_ref(&mut d.count)); +let split_count = state.split_writer(|d| PartMut::new(&mut d.count)); // 根状态的源状态是它自己 let _: &State = state.origin_reader(); diff --git a/examples/todos/src/ui.rs b/examples/todos/src/ui.rs index 02fc948aa..0fc1c699f 100644 --- a/examples/todos/src/ui.rs +++ b/examples/todos/src/ui.rs @@ -55,7 +55,7 @@ fn task_lists( let task = this.split_writer( // task will always exist, if the task is removed, // sthe widgets list will be rebuild. - move |todos| PartData::from_ref_mut(todos.get_task_mut(id).unwrap()), + move |todos| PartMut::new(todos.get_task_mut(id).unwrap()), ); let item = pipe!(*$editing == Some(id)) .value_chain(|s| s.distinct_until_changed().box_it()) @@ -141,10 +141,10 @@ where $item.write().opacity = 0.; let transform = item .get_transform_widget() - .map_writer(|w| PartData::from_ref_mut(&mut w.transform)); + .map_writer(|w| PartMut::new(&mut w.transform)); let opacity = item .get_opacity_widget() - .map_writer(|w| PartData::from_ref_mut(&mut w.opacity)); + .map_writer(|w| PartMut::new(&mut w.opacity)); let fly_in = stagger.push_state( (transform, opacity), (Transform::translation(0., 64.), 0.), diff --git a/examples/wordle_game/src/ui.rs b/examples/wordle_game/src/ui.rs index ae2dd31e1..176b0b20b 100644 --- a/examples/wordle_game/src/ui.rs +++ b/examples/wordle_game/src/ui.rs @@ -15,54 +15,69 @@ trait WordleExtraWidgets: StateWriter + Sized + 'static { fn char_key(self, key: char) -> Widget<'static> { let this = self.clone_writer(); - fn_widget! { - @FilledButton { - on_tap: move |_| $this.write().guessing.enter_char(key), - // todo: color: pipe!{ hint_color($this.key_hint(key)) }, - @ { key.to_string() } - } + + let palette = Palette::of(BuildCtx::get()); + let base = palette.base_of(&palette.surface_variant()); + let success = palette.success(); + let warning = palette.warning(); + let error = palette.error(); + let (color, u) = Stateful::from_pipe(pipe! { + $this.key_hint(key).map_or( + base, + |s| match s { + CharHint::Correct => success, + CharHint::WrongPosition => warning, + CharHint::Wrong => error, + }) + }); + + filled_button! { + providers: [Provider::value_of_writer(color, None)], + on_tap: move |_| $this.write().guessing.enter_char(key), + on_disposed: move |_| u.unsubscribe(), + @ { key.to_string() } } .into_widget() } fn keyboard(self, state_bar: impl StateWriter + 'static) -> Widget<'static> { let this: ::Writer = self.clone_writer(); - fn_widget! { - @Column { + let palette = Palette::of(BuildCtx::get()); + let gray = palette.base_of(&palette.surface_variant()); + self::column! { + item_gap: 5., + align_items: Align::Center, + justify_content: JustifyContent::Center, + @Row { item_gap: 5., align_items: Align::Center, justify_content: JustifyContent::Center, - @Row { - item_gap: 5., - align_items: Align::Center, - justify_content: JustifyContent::Center, - @ { this.clone_writer().chars_key(['Q', 'W', 'E', 'R','T', 'Y', 'U', 'I','O', 'P']) } - } - @Row { - item_gap: 5., - align_items: Align::Center, - justify_content: JustifyContent::Center, - @ { this.clone_writer().chars_key(['A', 'S', 'D', 'F', 'G', 'H', 'J', 'K', 'L' ]) } + @ { this.clone_writer().chars_key(['Q', 'W', 'E', 'R','T', 'Y', 'U', 'I','O', 'P']) } + } + @Row { + item_gap: 5., + align_items: Align::Center, + justify_content: JustifyContent::Center, + @ { this.clone_writer().chars_key(['A', 'S', 'D', 'F', 'G', 'H', 'J', 'K', 'L' ]) } + } + @Row { + item_gap: 5., + align_items: Align::Center, + justify_content: JustifyContent::Center, + @FilledButton { + providers: [Provider::new(gray)], + on_tap: move |_| $this.write().guessing.delete_back_char(), + @ { "Del" } } - @Row { - item_gap: 5., - align_items: Align::Center, - justify_content: JustifyContent::Center, - @FilledButton { - on_tap: move |_| $this.write().guessing.delete_back_char(), - // todo: color: palette.surface_variant(), - @ { "Del" } - } - @ { self.chars_key(['Z', 'X', 'C', 'V', 'B', 'N', 'M' ]) } - - @FilledButton { - on_tap: move |_| match $this.write().guess() { - Ok(status) => state_bar.write().text = status.state_message().into(), - Err(e) => state_bar.write().text = e.message().into(), - }, - // todo: color: palette.surface_variant(), - @ { "Enter" } - } + @ { self.chars_key(['Z', 'X', 'C', 'V', 'B', 'N', 'M' ]) } + + @FilledButton { + providers: [Provider::new(gray)], + on_tap: move |_| match $this.write().guess() { + Ok(status) => state_bar.write().text = status.state_message().into(), + Err(e) => state_bar.write().text = e.message().into(), + }, + @ { "Enter" } } } } @@ -101,7 +116,7 @@ impl + 'static> WordleExtraWidgets for T {} fn hint_color(hint: Option) -> Color { let palette = Palette::of(BuildCtx::get()); hint.map_or_else( - || palette.surface_variant(), + || palette.base_of(&palette.surface_variant()), |s| match s { CharHint::Correct => palette.success(), CharHint::WrongPosition => palette.warning(), diff --git a/macros/src/part_state.rs b/macros/src/part_state.rs index 90b0aa945..00a6861ef 100644 --- a/macros/src/part_state.rs +++ b/macros/src/part_state.rs @@ -20,7 +20,7 @@ pub fn gen_part_wrier(input: TokenStream, refs_ctx: &mut DollarRefsCtx) -> Token let PartState { and_token, mutability, state, dot, part_expr, tail_dot, tail_expr } = part; let tokens = quote_spanned! { state.span() => #host #dot map_writer( - |w| PartData::from_ref_mut(#and_token #mutability w #dot #part_expr #tail_dot #tail_expr) + |w| PartMut::new(#and_token #mutability w #dot #part_expr #tail_dot #tail_expr) ) }; refs_ctx.add_dollar_ref(info); @@ -38,7 +38,7 @@ pub fn gen_part_reader(input: TokenStream, refs_ctx: &mut DollarRefsCtx) -> Toke let PartState { and_token, mutability, state, dot, part_expr, tail_dot, tail_expr } = part; let tokens = quote_spanned! { state.span() => #host #dot map_reader( - |r| PartData::from_ref(#and_token #mutability r #dot #part_expr #tail_dot #tail_expr) + |r| PartRef::new(#and_token #mutability r #dot #part_expr #tail_dot #tail_expr) ) }; refs_ctx.add_dollar_ref(info); diff --git a/painter/src/color.rs b/painter/src/color.rs index 96f8ca64c..ac47f7d0d 100644 --- a/painter/src/color.rs +++ b/painter/src/color.rs @@ -49,6 +49,9 @@ pub struct LightnessTone(f32); impl LightnessTone { #[inline] pub fn new(tone: f32) -> Self { Self(tone.clamp(0., 1.0)) } + + #[inline] + pub fn value(self) -> f32 { self.0 } } impl Color { @@ -96,7 +99,6 @@ impl Color { self } - #[inline] pub fn with_lightness(self, l: LightnessTone) -> Self { let mut hct = htc::Hct::from_int([self.alpha, self.red, self.green, self.blue]); hct.set_tone((l.0 * 100.).clamp(0., 100.) as f64); @@ -104,6 +106,11 @@ impl Color { Self { red: argb[1], green: argb[2], blue: argb[3], alpha: argb[0] } } + pub fn lightness(self) -> LightnessTone { + let hct = htc::Hct::from_int([self.alpha, self.red, self.green, self.blue]); + LightnessTone::new(hct.tone() as f32 / 100.) + } + #[inline] pub fn into_components(self) -> [u8; 4] { let Self { red, green, blue, alpha } = self; diff --git a/painter/src/style.rs b/painter/src/style.rs index bd7042255..8e9f023ec 100644 --- a/painter/src/style.rs +++ b/painter/src/style.rs @@ -17,10 +17,11 @@ pub enum Brush { } impl Brush { - pub fn only_convert_color(&self, f: impl FnOnce(&Color) -> Color) -> Brush { + /// Returns the color of the brush, or `None` if the brush is not a color. + pub fn get_color(&self) -> Option { match self { - Brush::Color(color) => f(color).into(), - _ => panic!("Need Color!"), + Brush::Color(c) => Some(*c), + _ => None, } } diff --git a/test_cases/wordle_game/tests/wordle_game_with_material_by_wgpu.png b/test_cases/wordle_game/tests/wordle_game_with_material_by_wgpu.png index 706ea5c12..e41811dac 100644 Binary files a/test_cases/wordle_game/tests/wordle_game_with_material_by_wgpu.png and b/test_cases/wordle_game/tests/wordle_game_with_material_by_wgpu.png differ diff --git a/tests/rdl_macro_test.rs b/tests/rdl_macro_test.rs index 650f23351..e3fea0dee 100644 --- a/tests/rdl_macro_test.rs +++ b/tests/rdl_macro_test.rs @@ -827,10 +827,10 @@ fn fix_direct_use_map_writer_with_builtin() { fn _x(mut host: FatObj) { let _anchor = host .get_relative_anchor_widget() - .map_writer(|w| PartData::from_ref_mut(&mut w.anchor)); + .map_writer(|w| PartMut::new(&mut w.anchor)); let _anchor = host .get_relative_anchor_widget() - .map_writer(|w| PartData::from_ref_mut(&mut w.anchor)); + .map_writer(|w| PartMut::new(&mut w.anchor)); } } diff --git a/themes/material/src/classes/buttons_cls.rs b/themes/material/src/classes/buttons_cls.rs index b6194bbfd..0eea37867 100644 --- a/themes/material/src/classes/buttons_cls.rs +++ b/themes/material/src/classes/buttons_cls.rs @@ -57,9 +57,8 @@ fn filled_button_init(classes: &mut Classes) { .clamp(BTN_40_CLAMP) .into_widget(); - let on_color = Palette::of(BuildCtx::get()).on_of(&color); FatObj::new(base_interactive(w, md::RADIUS_20)) - .foreground(on_color) + .foreground(BuildCtx::color().on_this_color(BuildCtx::get())) .into_widget() } @@ -97,14 +96,17 @@ fn button_init(classes: &mut Classes) { fn fab_common_interactive(w: Widget, radius: Radius, btn_clamp: BoxClamp) -> Widget { let color = BuildCtx::color(); - let p = Palette::of(BuildCtx::get()); let w = FatObj::new(w) - .background(p.container_of(&color)) + .background( + color + .clone() + .into_container_color(BuildCtx::get()), + ) .clamp(btn_clamp) .border_radius(radius); FatObj::new(base_interactive(w.into_widget(), radius)) - .foreground(p.on_container_of(&color)) + .foreground(color.on_this_container_color(BuildCtx::get())) .into_widget() } diff --git a/themes/material/src/classes/checkbox_cls.rs b/themes/material/src/classes/checkbox_cls.rs index 38bb6187d..d5fb56a2a 100644 --- a/themes/material/src/classes/checkbox_cls.rs +++ b/themes/material/src/classes/checkbox_cls.rs @@ -19,7 +19,9 @@ pub(super) fn init(classes: &mut Classes) { .into_widget() }); - fn icon_with_ripple<'w>(icon: Widget<'w>, ripple: Widget<'w>, foreground: Color) -> Widget<'w> { + fn icon_with_ripple<'w>( + icon: Widget<'w>, ripple: Widget<'w>, foreground: Variant, + ) -> Widget<'w> { stack! { margin: md::EDGES_4, foreground, @@ -39,14 +41,13 @@ 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::color(); let icon = container! { size: md::SIZE_18, - background: ripple_color, + background: BuildCtx::color(), border_radius: md::RADIUS_2, @ { icon } }; - icon_with_ripple(icon.into_widget(), ripple, ripple_color) + icon_with_ripple(icon.into_widget(), ripple, BuildCtx::color()) } classes.insert(CHECKBOX_CHECKED, |w| { @@ -71,7 +72,7 @@ pub(super) fn init(classes: &mut Classes) { }; @FatObj { on_mounted: move |_| enter.run(), - foreground: Palette::of(BuildCtx::get()).on_of(&BuildCtx::color()), + foreground: BuildCtx::color().on_this_color(BuildCtx::get()), painting_style: PaintingStyle::Stroke(StrokeOptions { width: 2., ..Default::default() @@ -89,7 +90,7 @@ pub(super) fn init(classes: &mut Classes) { size: Size::new(12., 2.), h_align: HAlign::Center, v_align: VAlign::Center, - background: Palette::of(BuildCtx::get()).on_of(&BuildCtx::color()), + background: BuildCtx::color().on_this_color(BuildCtx::get()), }; let enter = @Animate { state: part_writer!(&mut icon.size), @@ -102,7 +103,6 @@ pub(super) fn init(classes: &mut Classes) { }); classes.insert(CHECKBOX_UNCHECKED, |w| { - let foreground = Palette::of(BuildCtx::get()).on_surface_variant(); let icon = container! { size: md::SIZE_18, border: md::border_2_surface_color(), @@ -111,6 +111,7 @@ pub(super) fn init(classes: &mut Classes) { } .into_widget(); - icon_with_ripple(icon, w, foreground) + let foreground = Palette::of(BuildCtx::get()).on_surface_variant(); + icon_with_ripple(icon, w, Variant::Value(foreground)) }); } diff --git a/themes/material/src/classes/progress_cls.rs b/themes/material/src/classes/progress_cls.rs index c627cd145..10170d9cb 100644 --- a/themes/material/src/classes/progress_cls.rs +++ b/themes/material/src/classes/progress_cls.rs @@ -15,38 +15,30 @@ fn indeterminate_trans() -> Box { .box_it() } -class_names! { - MD_BASE_LINEAR_INDICATOR, - MD_BASE_SPINNER, - MD_BASE_SPINNER_INDICATOR, - MD_BASE_SPINNER_TRACK +fn md_base_spinner(w: Widget, foreground: DeclareInit) -> Widget { + fat_obj! { + foreground, + clamp: BoxClamp::fixed_size(md::SIZE_48), + painting_style: PaintingStyle::Stroke(StrokeOptions { + width: md::THICKNESS_4, + line_cap: LineCap::Round, + ..Default::default() + }), + @ { w } + } + .into_widget() } +named_style_class! { md_base_linear_indicator => { + background: BuildCtx::color(), + border_radius: md::RADIUS_2, +}} + fn lerp_angle(from: &Angle, to: &Angle, rate: f32) -> Angle { let radians = from.radians.lerp(&to.radians, rate); Angle::radians(radians) } pub(super) fn init(classes: &mut Classes) { - classes.insert(MD_BASE_LINEAR_INDICATOR, style_class! { - background: BuildCtx::color(), - border_radius: md::RADIUS_2, - }); - classes.insert(MD_BASE_SPINNER, style_class! { - clamp: BoxClamp::fixed_size(md::SIZE_48), - painting_style: PaintingStyle::Stroke(StrokeOptions { - width: md::THICKNESS_4, - line_cap: LineCap::Round, - ..Default::default() - }), - }); - classes.insert(MD_BASE_SPINNER_INDICATOR, style_class! { - class: MD_BASE_SPINNER, - foreground: BuildCtx::color(), - }); - classes.insert(MD_BASE_SPINNER_TRACK, style_class! { - class: MD_BASE_SPINNER, - foreground: BuildCtx::container_color(), - }); classes.insert(LINEAR_INDETERMINATE_TRACK, style_class! { background: BuildCtx::container_color(), border_radius: md::RADIUS_2, @@ -64,18 +56,18 @@ pub(super) fn init(classes: &mut Classes) { // introduced for the progress. @Container { h_align: HAlign::Right, - class: MD_BASE_LINEAR_INDICATOR, + background: BuildCtx::color(), + border_radius: md::RADIUS_2, size: Size::new(md::THICKNESS_4, md::THICKNESS_4), } } .into_widget() }); classes.insert(LINEAR_DETERMINATE_INDICATOR, move |host| { - let host = FatObj::new(host); smooth_width! { transition: DETERMINATE_TRANS, init_value: 0., - @ $host { class: MD_BASE_LINEAR_INDICATOR } + @md_base_linear_indicator(host) } .into_widget() }); @@ -84,7 +76,6 @@ pub(super) fn init(classes: &mut Classes) { // thus transforming the entire progress into `Row[Indicator, Track, // Indicator, Track]`, adjusting the size of the four children to simulate // the repeated motion. - let host = FatObj::new(host); fn_widget! { let indicator1 = @Expanded { flex: 0.}; let track1 = @Expanded { flex: 0., }; @@ -114,15 +105,16 @@ pub(super) fn init(classes: &mut Classes) { @ $total_fraction { @Row { - @ $indicator1 { @ $host { class: MD_BASE_LINEAR_INDICATOR } } + @ $indicator1 { @md_base_linear_indicator(host) } @ $track1 { @FractionallyWidthBox { class: LINEAR_INDETERMINATE_TRACK } } @ $indicator2 { - @FractionallyWidthBox { - margin: EdgeInsets::only_left(4.), - class: MD_BASE_LINEAR_INDICATOR - } + @md_base_linear_indicator( + @FractionallyWidthBox { + margin: EdgeInsets::only_left(4.), + }.into_widget() + ) } } } @@ -131,7 +123,6 @@ pub(super) fn init(classes: &mut Classes) { }); classes.insert(SPINNER_DETERMINATE, move |w| { - let w = FatObj::new(w); let margin_angle: Angle = Angle::degrees(16.); fn_widget! { let indicator = Provider::of::>(BuildCtx::get()).unwrap(); @@ -158,8 +149,8 @@ pub(super) fn init(classes: &mut Classes) { transform: Transform::translation(-center.width, -center.height) .then_rotate(margin_angle / 2.) .then_translate(center.to_vector()), - @ $track { class: MD_BASE_SPINNER_TRACK } - @ $w { class: MD_BASE_SPINNER_INDICATOR } + @md_base_spinner(track.into_widget(), BuildCtx::container_color().declare_into()) + @md_base_spinner(w, BuildCtx::color().declare_into()) } } @@ -167,7 +158,6 @@ pub(super) fn init(classes: &mut Classes) { }); classes.insert(SPINNER_INDETERMINATE, move |w| { - let w = FatObj::new(w); fn_widget! { let indicator = Provider::of::>(BuildCtx::get()).unwrap(); let pi = Angle::pi(); @@ -186,8 +176,7 @@ pub(super) fn init(classes: &mut Classes) { transition: indeterminate_trans(), from: (Angle::zero(), pi * 0.1), }.run(); - - @ $w { class: MD_BASE_SPINNER_INDICATOR } + @md_base_spinner(w, BuildCtx::color().declare_into()) } .into_widget() }); diff --git a/themes/material/src/classes/radio_cls.rs b/themes/material/src/classes/radio_cls.rs index a7ac99a79..c61404b8b 100644 --- a/themes/material/src/classes/radio_cls.rs +++ b/themes/material/src/classes/radio_cls.rs @@ -18,7 +18,9 @@ pub(super) fn init(classes: &mut Classes) { .into_widget() }); - fn icon_with_ripple<'w>(icon: Widget<'w>, ripple: Widget<'w>, foreground: Color) -> Widget<'w> { + fn icon_with_ripple<'w>( + icon: Widget<'w>, ripple: Widget<'w>, foreground: DeclareInit, + ) -> Widget<'w> { stack! { margin: md::EDGES_4, foreground, @@ -33,11 +35,10 @@ pub(super) fn init(classes: &mut Classes) { } classes.insert(RADIO_SELECTED, |ripple| { - let color = BuildCtx::color(); let icon = rdl! { let w = @Container { size: md::SIZE_10, - background: color, + background: BuildCtx::color(), border_radius: md::RADIUS_5, h_align: HAlign::Center, v_align: VAlign::Center, @@ -59,7 +60,7 @@ pub(super) fn init(classes: &mut Classes) { } }; - icon_with_ripple(icon.into_widget(), ripple, color) + icon_with_ripple(icon.into_widget(), ripple, BuildCtx::color().declare_into()) }); classes.insert(RADIO_UNSELECTED, |ripple| { let foreground = Palette::of(BuildCtx::get()).on_surface_variant(); @@ -68,6 +69,6 @@ pub(super) fn init(classes: &mut Classes) { border: md::border_2_surface_color(), border_radius: md::RADIUS_10, }; - icon_with_ripple(icon.into_widget(), ripple, foreground) + icon_with_ripple(icon.into_widget(), ripple, foreground.declare_into()) }); } diff --git a/themes/material/src/classes/scrollbar_cls.rs b/themes/material/src/classes/scrollbar_cls.rs index 6cdb0e6bb..22ce8b61a 100644 --- a/themes/material/src/classes/scrollbar_cls.rs +++ b/themes/material/src/classes/scrollbar_cls.rs @@ -31,9 +31,10 @@ pub(super) fn init(classes: &mut Classes) { classes.insert(V_SCROLL_TRACK, |w| style_track(w, false)); } +fn track_color(w: Color, hovering: bool) -> Color { if hovering { w } else { w.with_alpha(0.) } } + 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); @@ -43,10 +44,10 @@ fn style_track(w: Widget, is_hor: bool) -> Widget { 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.)}) - }, + background: match Variant::::new(BuildCtx::get()).unwrap() { + Variant::Value(c) => pipe!(track_color(c.0, $w.is_hover())).declare_into(), + Variant::Stateful(c) => pipe!(track_color($c.0, $w.is_hover())).declare_into() + } }; let trans = EasingTransition { @@ -75,6 +76,9 @@ fn style_track(w: Widget, is_hor: bool) -> Widget { fade = Some(u); }; + let scroll = Provider::state_of::>(BuildCtx::get()) + .unwrap() + .clone_writer(); let u = if is_hor { watch!(($scroll).get_scroll_pos().x) .distinct_until_changed() diff --git a/themes/material/src/classes/slider_cls.rs b/themes/material/src/classes/slider_cls.rs index c82bb37f6..411c8d282 100644 --- a/themes/material/src/classes/slider_cls.rs +++ b/themes/material/src/classes/slider_cls.rs @@ -65,10 +65,10 @@ pub(super) fn init(classes: &mut Classes) { }); classes.insert(STOP_INDICATOR_ACTIVE, stop_indicator_class! { - background: Palette::of(BuildCtx::get()).on_of(&BuildCtx::color()) + background: BuildCtx::color().on_this_color(BuildCtx::get()) }); classes.insert(STOP_INDICATOR_INACTIVE, stop_indicator_class! { - background: Palette::of(BuildCtx::get()).on_container_of(&BuildCtx::color()) + background: BuildCtx::color().on_this_container_color(BuildCtx::get()) }); } diff --git a/themes/material/src/md.rs b/themes/material/src/md.rs index 6212e1f84..923d9859b 100644 --- a/themes/material/src/md.rs +++ b/themes/material/src/md.rs @@ -107,9 +107,8 @@ pub const EDGES_HOR_36: EdgeInsets = EdgeInsets::horizontal(36.); pub const EDGES_HOR_48: EdgeInsets = EdgeInsets::horizontal(48.); // Borders -pub fn border_2() -> Border { - let color = BuildCtx::color(); - Border::all(BorderSide::new(2., color.into())) +pub fn border_2() -> VariantMap Border> { + BuildCtx::color().map(|color| Border::all(BorderSide::new(2., color.into()))) } pub fn border_2_surface_color() -> Border { let surface_variant = Palette::of(BuildCtx::get()).on_surface_variant(); diff --git a/widgets/src/path.rs b/widgets/src/path.rs index 9bfd9bb4d..16eab5bf7 100644 --- a/widgets/src/path.rs +++ b/widgets/src/path.rs @@ -14,16 +14,7 @@ impl Render for PathPaintKit { #[inline] fn only_sized_by_parent(&self) -> bool { true } - fn paint(&self, ctx: &mut PaintingCtx) { - let path = PaintPath::Share(self.path.clone()); - 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 paint(&self, ctx: &mut PaintingCtx) { self.path.paint(ctx); } fn hit_test(&self, _ctx: &mut HitTestCtx, _: Point) -> HitTest { HitTest { hit: false, can_hit_child: false } diff --git a/widgets/src/scrollbar.rs b/widgets/src/scrollbar.rs index f4f8d6b17..f6812ad4d 100644 --- a/widgets/src/scrollbar.rs +++ b/widgets/src/scrollbar.rs @@ -63,10 +63,7 @@ impl<'c> ComposeChild<'c> for Scrollbar { // scroll states or enables descendants to trigger scrolling to a different // position. providers! { - providers: smallvec::smallvec![ - Provider::new(scroll.clone_writer()), - Provider::value_of_writer(scroll.clone_writer(), None), - ], + providers: [Provider::value_of_writer(scroll.clone_writer(), None)], @ { let h_scrollbar = distinct_pipe!($scroll.is_x_scrollable()) .map(move |need_bar| need_bar.then(||{ diff --git a/widgets/src/text_field.rs b/widgets/src/text_field.rs index 5a8059d23..a538f1ade 100644 --- a/widgets/src/text_field.rs +++ b/widgets/src/text_field.rs @@ -321,7 +321,7 @@ fn build_input_area( visible: pipe!(!$this.text.is_empty() || $theme.state == TextFieldState::Focused), }; input_area.get_visibility_widget() - .map_writer(|w| PartData::from_ref(&w.visible)) + .map_writer(|w| PartMut::new(&mut w.visible)) .transition(transitions::LINEAR.of(BuildCtx::get())); let mut input = @Input{ style: pipe!($theme.text.clone()) }; @@ -383,7 +383,7 @@ impl Compose for TextFieldLabel { text_style: pipe!($this.style.clone()), }; - this.map_writer(|w| PartData::from_ref(&w.style.font_size)) + this.map_writer(|w| PartMut::new(&mut w.style.font_size)) .transition(transitions::LINEAR.of(BuildCtx::get())); label @@ -402,9 +402,7 @@ fn build_content_area( padding: pipe!($theme.input_padding($this.text.is_empty())), }; - content_area - .get_padding_widget() - .map_writer(|w| PartData::from_ref(&w.padding)) + part_writer!(&mut content_area.padding) .transition(transitions::LINEAR.of(BuildCtx::get())); @ $content_area {