Skip to content

Commit

Permalink
feat(core): 🎸 added Variant to support building widgets with variables
Browse files Browse the repository at this point in the history
  • Loading branch information
M-Adoo committed Jan 12, 2025
1 parent 9cec0da commit 568f72e
Show file tree
Hide file tree
Showing 20 changed files with 349 additions and 153 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ 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 `Variant` to support building widgets with variables across `Providers`. (#pr @M-Adoo)
- **macros**: Added the `part_reader!` macro to generate a partial reader from a reference of a reader. (#688 @M-Adoo)
- **macros**: The `simple_declare` now supports the `stateless` meta attribute, `#[simple_declare(stateless)]`. (#688 @M-Adoo)

Expand Down
28 changes: 16 additions & 12 deletions core/src/builtin_widgets.rs
Original file line number Diff line number Diff line change
Expand Up @@ -986,19 +986,23 @@ impl<'a> FatObj<Widget<'a>> {
)*
};
}
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
Expand Down
20 changes: 5 additions & 15 deletions core/src/builtin_widgets/providers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -184,11 +183,6 @@ pub trait ProviderRestore {
fn restore(self: Box<Self>, ctx: &mut ProviderCtx) -> Box<dyn ProviderSetup>;
}

/// 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 {
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -779,18 +769,18 @@ 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
}
}
}
}
@ {
let color = BuildCtx::color();
assert_eq!(color, Palette::of(BuildCtx::get()).primary());
assert_eq!(color.clone_value(), Palette::of(BuildCtx::get()).primary());
Void
}
});
Expand All @@ -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
}
}
Expand Down
12 changes: 12 additions & 0 deletions core/src/builtin_widgets/theme.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<ProviderCtx>) -> QueryRef<Self> { Provider::of(ctx).unwrap() }

Expand Down
14 changes: 14 additions & 0 deletions core/src/builtin_widgets/theme/palette.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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 {
Expand Down
2 changes: 2 additions & 0 deletions core/src/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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::*;
13 changes: 7 additions & 6 deletions core/src/context/build_ctx.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,15 @@ impl BuildCtx {
/// Return the window of this context is created from.
pub fn window(&self) -> Sc<Window> { self.tree().window() }

/// Return the `Color` provide in the current build context.
pub fn color() -> Color { *Provider::of::<Color>(BuildCtx::get()).unwrap() }
/// Return the variant of `Color` provided in the current build context.
pub fn color() -> Variant<Color> { Variant::new(BuildCtx::get()).unwrap() }

/// Return the `ContainerColor` provide in the current build context.
pub fn container_color() -> Color {
Provider::of::<ContainerColor>(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<ContainerColor, impl Fn(ContainerColor) -> Color> {
Variant::new(BuildCtx::get())
.unwrap()
.map(|c: ContainerColor| c.0)
}

pub(crate) fn tree(&self) -> &WidgetTree {
Expand Down
163 changes: 163 additions & 0 deletions core/src/context/build_variant.rs
Original file line number Diff line number Diff line change
@@ -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::<Color>::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<Color>`, this rectangle will
/// reflect changes in color.
pub enum Variant<V> {
Stateful(Stateful<V>),
Value(V),
}

/// `VariantMap` is a Variant that maps a value to another value using a
/// function.
#[derive(Clone)]
pub struct VariantMap<V: 'static, F> {
variant: Variant<V>,
map: F,
}

impl<V: Clone + 'static> Variant<V> {
/// Creates a new `Variant` from a provider context.
pub fn new(ctx: &impl AsRef<ProviderCtx>) -> Option<Self> {
if let Some(value) = Provider::state_of::<Stateful<V>>(ctx) {
Some(Variant::Stateful(value.clone_writer()))
} else {
Provider::of::<V>(ctx).map(|v| Variant::Value(v.clone()))
}
}

/// Maps a value to another value using a function.
pub fn map<F, U>(self, map: F) -> VariantMap<V, F>
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<Color> {
/// Convert a color variant to another color variant with its base lightness
/// tone.
pub fn into_base_color(
self, ctx: &impl AsRef<ProviderCtx>,
) -> VariantMap<Color, impl Fn(Color) -> 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<ProviderCtx>,
) -> VariantMap<Color, impl Fn(Color) -> 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<ProviderCtx>,
) -> VariantMap<Color, impl Fn(Color) -> 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<ProviderCtx>,
) -> VariantMap<Color, impl Fn(Color) -> Color> {
let p = Palette::of(ctx);
let lightness = p.lightness_group().on_container;
self.map(move |c| c.with_lightness(lightness))
}
}

impl<V, F> VariantMap<V, F> {
/// Maps a value to another value using a function.
pub fn map<F2, U1, U2>(self, map: F2) -> VariantMap<V, impl Fn(V) -> 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<U>(&self) -> U
where
F: Fn(V) -> U,
V: Clone,
{
(self.map)(self.variant.clone_value())
}
}

impl<V: Clone + 'static, U> DeclareFrom<Variant<V>, 0> for DeclareInit<U>
where
U: From<V> + 'static,
{
fn declare_from(value: Variant<V>) -> Self {
match value {
Variant::Stateful(value) => pipe!($value.clone()).declare_into(),
Variant::Value(value) => DeclareInit::Value(value.into()),
}
}
}

impl<V: Clone + 'static, F, U, P> DeclareFrom<VariantMap<V, F>, 0> for DeclareInit<P>
where
F: Fn(V) -> U + 'static,
P: From<U> + 'static,
{
fn declare_from(value: VariantMap<V, F>) -> 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<V: Clone + 'static> Clone for Variant<V> {
fn clone(&self) -> Self {
match self {
Variant::Stateful(value) => Variant::Stateful(value.clone_writer()),
Variant::Value(value) => Variant::Value(value.clone()),
}
}
}
Loading

0 comments on commit 568f72e

Please sign in to comment.