Skip to content

Commit

Permalink
feat(core): 🎸 add Provider::state_of
Browse files Browse the repository at this point in the history
  • Loading branch information
M-Adoo committed Jan 11, 2025
1 parent 35178b5 commit 32ef3fd
Show file tree
Hide file tree
Showing 4 changed files with 114 additions and 28 deletions.
7 changes: 2 additions & 5 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -290,10 +290,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| {
Expand All @@ -304,7 +301,7 @@ Please only add new entries below the [Unreleased](#unreleased---releasedate) he
@Text {
text: {
// Access the provider in any descendants
let v = Provider::of::<Stateful<i32>>(ctx!());
let v = Provider::state_of::<Stateful<i32>>(BuildCtx::get());
let v = v.unwrap().clone_writer();
pipe!($v.to_string())
}
Expand Down
126 changes: 108 additions & 18 deletions core/src/builtin_widgets/providers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -199,10 +199,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::<i32>(BuildCtx::get()).unwrap(), 1);
/// Void
/// }
/// };
/// ```
pub fn new<T: 'static>(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::<i32>(ctx).unwrap(), 1);
///
/// // Obtain the reader
/// let reader = Provider::state_of::
/// <<Stateful<i32> as StateReader>::Reader>(ctx);
/// assert_eq!(*reader.unwrap().read(), 1);
/// Void
/// }
/// };
/// ```
pub fn value_of_reader<R>(value: R) -> Provider
where
R: StateReader<Reader: Query>,
Expand All @@ -214,18 +253,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::<i32>(ctx).unwrap(), 1);
///
/// // Obtain the writer reference
/// let w_value = Provider::write_of::<i32>(ctx);
/// *w_value.unwrap() = 2;
///
/// // Obtain the writer
/// let writer = Provider::state_of::<Stateful<i32>>(ctx);
/// assert_eq!(*writer.unwrap().write(), 2);
///
/// Void
/// }
/// };
/// ```
pub fn value_of_writer<V: 'static>(
value: impl StateWriter<Value = V> + Query, dirty: Option<DirtyPhase>,
) -> Provider {
Expand All @@ -247,14 +313,26 @@ impl Provider {
}
}

/// Access the provider of `P` within the context.
pub fn of<P: 'static>(ctx: &impl AsRef<ProviderCtx>) -> Option<QueryRef<P>> {
ctx.as_ref().get_provider::<P>()
/// 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<V: 'static>(ctx: &impl AsRef<ProviderCtx>) -> Option<QueryRef<V>> {
ctx.as_ref().get_provider::<V>()
}

/// 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<V: 'static>(ctx: &impl AsRef<ProviderCtx>) -> Option<WriteRef<V>> {
ctx.as_ref().get_provider_write::<V>()
}

/// Access the write reference of `P` within the context.
pub fn write_of<P: 'static>(ctx: &impl AsRef<ProviderCtx>) -> Option<WriteRef<P>> {
ctx.as_ref().get_provider_write::<P>()
/// 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<S>(ctx: &impl AsRef<ProviderCtx>) -> Option<QueryRef<S>>
where
S: StateReader<Value: Sized + 'static>,
{
ctx.as_ref().get_provider_state::<S>()
}

/// Setup the provider to the context.
Expand Down Expand Up @@ -438,6 +516,18 @@ impl ProviderCtx {
.and_then(QueryHandle::into_mut)
}

pub fn get_provider_state<S>(&self) -> Option<QueryRef<S>>
where
S: StateReader<Value: Sized + 'static>,
{
let info = Provider::info::<S::Value>();
self
.data
.get(&info)
.and_then(|q| q.query_write(&QueryId::of::<S>()))
.and_then(QueryHandle::into_ref)
}

pub(crate) fn remove_key_value_if(
&mut self, f: impl Fn(&TypeInfo) -> bool,
) -> Vec<(TypeInfo, Box<dyn Query>)> {
Expand Down
4 changes: 3 additions & 1 deletion themes/material/src/classes/scrollbar_cls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,6 @@ pub(super) fn init(classes: &mut Classes) {

fn style_track(w: Widget, is_hor: bool) -> Widget {
rdl! {
let scroll = Provider::of::<Stateful<ScrollableWidget>>(BuildCtx::get()).unwrap();
let mut w = FatObj::new(w);
if is_hor {
w = w.v_align(VAlign::Bottom);
Expand Down Expand Up @@ -75,6 +74,9 @@ fn style_track(w: Widget, is_hor: bool) -> Widget {
fade = Some(u);
};

let scroll = Provider::state_of::<Stateful<ScrollableWidget>>(BuildCtx::get())
.unwrap()
.clone_writer();
let u = if is_hor {
watch!(($scroll).get_scroll_pos().x)
.distinct_until_changed()
Expand Down
5 changes: 1 addition & 4 deletions widgets/src/scrollbar.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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(||{
Expand Down

0 comments on commit 32ef3fd

Please sign in to comment.