Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Ref lens and use move semantics for lens! macro index. #1171

Merged
merged 4 commits into from
Sep 3, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 5 additions & 3 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ You can find its changes [documented below](#060---2020-06-01).
- `request_update` in `EventCtx`. ([#1128] by [@raphlinus])
- `ExtEventSink`s can now be obtained from widget methods. ([#1152] by [@jneem])
- 'Scope' widget to allow encapsulation of reactive state. ([#1151] by [@rjwittams])
- `Ref` lens that applies `AsRef` and thus allow indexing arrays. ([#1171] by [@finnerale])

### Changed

Expand All @@ -27,8 +28,8 @@ You can find its changes [documented below](#060---2020-06-01).
- `Container::rounded` takes `KeyOrValue<f64>` instead of `f64`. ([#1054] by [@binomial0])
- `request_anim_frame` no longer invalidates the entire window. ([#1057] by [@jneem])
- Use new Piet text api ([#1143] by [@cmyr])
- `Env::try_get` (and related methods) return a `Result` instead of an `Option`.
([#1172] by [@cmyr])
- `Env::try_get` (and related methods) return a `Result` instead of an `Option`. ([#1172] by [@cmyr])
- `lens!` macro to use move semantics for the index. ([#1171] by [@finnerale])

### Deprecated

Expand Down Expand Up @@ -405,7 +406,8 @@ Last release without a changelog :(
[#1151]: https://github.com/linebender/druid/pull/1151
[#1152]: https://github.com/linebender/druid/pull/1152
[#1157]: https://github.com/linebender/druid/pull/1157
[#1172]: https://github.com/linebender/druid/pull/1157
[#1171]: https://github.com/linebender/druid/pull/1171
[#1172]: https://github.com/linebender/druid/pull/1172

[Unreleased]: https://github.com/linebender/druid/compare/v0.6.0...master
[0.6.0]: https://github.com/linebender/druid/compare/v0.5.0...v0.6.0
Expand Down
73 changes: 71 additions & 2 deletions druid/src/lens/lens.rs
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,53 @@ pub trait LensExt<A: ?Sized, B: ?Sized>: Lens<A, B> {
self.then(Deref)
}

/// Invoke a type's `AsRef` and `AsMut` impl.
///
/// It also allows indexing arrays with the [`index`] lens as shown in the example.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe worth explaining that this is because Index is implemented for slices, and not arrays. Otherwise it's a little mysterious (to me, anyway) why reffing allowing indexing to work

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good idea! I've added a note.

/// This is necessary, because the `Index` trait in Rust is only implemented
/// for slices (`[T]`), but not for arrays (`[T; N]`).
///
/// # Examples
///
/// Using `ref` this works:
///
/// ```
/// use druid::{widget::TextBox, Data, Lens, LensExt, Widget, WidgetExt};
///
/// #[derive(Clone, Default, Data, Lens)]
/// struct State {
/// data: [String; 2],
/// }
///
/// fn with_ref() -> impl Widget<State> {
/// TextBox::new().lens(State::data.as_ref().index(1))
/// }
/// ```
///
/// While this fails:
///
/// ```compile_fail
/// # use druid::*;
/// # #[derive(Clone, Default, Data, Lens)]
/// # struct State {
/// # data: [String; 2],
/// # }
/// fn without_ref() -> impl Widget<State> {
/// // results in: `[std::string::String; 2]` cannot be mutably indexed by `usize`
/// TextBox::new().lens(State::data.index(1))
/// }
/// ```
///
/// [`Lens`]: ./trait.Lens.html
/// [`index`]: #method.index
fn as_ref<T: ?Sized>(self) -> Then<Self, Ref, B>
where
B: AsRef<T> + AsMut<T>,
Self: Sized,
{
self.then(Ref)
}

/// Access an index in a container
///
/// ```
Expand Down Expand Up @@ -307,10 +354,10 @@ where
#[macro_export]
macro_rules! lens {
($ty:ty, [$index:expr]) => {
$crate::lens::Field::new::<$ty, _>(|x| &x[$index], |x| &mut x[$index])
$crate::lens::Field::new::<$ty, _>(move |x| &x[$index], move |x| &mut x[$index])
};
($ty:ty, $field:tt) => {
$crate::lens::Field::new::<$ty, _>(|x| &x.$field, |x| &mut x.$field)
$crate::lens::Field::new::<$ty, _>(move |x| &x.$field, move |x| &mut x.$field)
};
}

Expand Down Expand Up @@ -421,6 +468,28 @@ where
}
}

/// [`Lens`] for invoking `AsRef` and `AsMut` on a type.
///
/// [`LensExt::ref`] offers an easy way to apply this,
/// as well as more information and examples.
///
/// [`Lens`]: ../trait.Lens.html
/// [`LensExt::ref`]: ../trait.LensExt.html#method.as_ref
#[derive(Debug, Copy, Clone)]
pub struct Ref;

impl<T: ?Sized, U: ?Sized> Lens<T, U> for Ref
where
T: AsRef<U> + AsMut<U>,
{
fn with<V, F: FnOnce(&U) -> V>(&self, data: &T, f: F) -> V {
f(data.as_ref())
}
fn with_mut<V, F: FnOnce(&mut U) -> V>(&self, data: &mut T, f: F) -> V {
f(data.as_mut())
}
}

/// `Lens` for indexing containers
#[derive(Debug, Copy, Clone)]
pub struct Index<I> {
Expand Down
2 changes: 1 addition & 1 deletion druid/src/lens/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,6 @@

#[allow(clippy::module_inception)]
mod lens;
pub use lens::{Deref, Field, Id, InArc, Index, Map, Then};
pub use lens::{Deref, Field, Id, InArc, Index, Map, Ref, Then};
#[doc(hidden)]
pub use lens::{Lens, LensExt, LensWrap};