diff --git a/CHANGELOG.md b/CHANGELOG.md index 9a1404ba5..79dd25138 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -29,18 +29,19 @@ 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. (#pr @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. (#pr @M-Adoo) +- **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`. (#pr @M-Adoo) +- **core**: Removed `PartData`. (#690 @M-Adoo) ## [0.4.0-alpha.22] - 2025-01-08 diff --git a/core/src/builtin_widgets.rs b/core/src/builtin_widgets.rs index abc20a0fe..e2487c0bb 100644 --- a/core/src/builtin_widgets.rs +++ b/core/src/builtin_widgets.rs @@ -986,19 +986,23 @@ impl<'a> FatObj> { )* }; } - let mut host = self.host; - if let Some(painting_style) = self.painting_style { - self - .providers - .get_or_insert_default() - .push(PaintingStyleWidget::into_provider(painting_style)); - } - if let Some(text_style) = self.text_style { - self - .providers - .get_or_insert_default() - .push(TextStyleWidget::into_provider(text_style)); + 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] diff --git a/core/src/builtin_widgets/providers.rs b/core/src/builtin_widgets/providers.rs index abb918628..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; @@ -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 { @@ -422,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 @@ -779,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 } } @@ -790,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 } }); @@ -807,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/theme.rs b/core/src/builtin_widgets/theme.rs index 66c9cc0b8..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() } 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/examples/wordle_game/src/ui.rs b/examples/wordle_game/src/ui.rs index ae2dd31e1..09e9deceb 100644 --- a/examples/wordle_game/src/ui.rs +++ b/examples/wordle_game/src/ui.rs @@ -15,54 +15,70 @@ 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 color = Stateful::new(Color::TRANSPARENT); + let color2 = color.clone_writer(); + + 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(); + watch! { $this.key_hint(key).map_or( + base, + |s| match s { + CharHint::Correct => success, + CharHint::WrongPosition => warning, + CharHint::Wrong => error, + }) + } + .subscribe(move |c| *color2.write() = c); + + filled_button! { + providers: [Provider::value_of_writer(color, None)], + on_tap: move |_| $this.write().guessing.enter_char(key), + @ { 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 +117,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/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/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 c8f9e4ce1..22ce8b61a 100644 --- a/themes/material/src/classes/scrollbar_cls.rs +++ b/themes/material/src/classes/scrollbar_cls.rs @@ -31,6 +31,8 @@ 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 mut w = FatObj::new(w); @@ -42,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 { 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 }