Skip to content

Commit 12f91e6

Browse files
committed
Implement lens composition
1 parent 14389b9 commit 12f91e6

File tree

2 files changed

+95
-5
lines changed

2 files changed

+95
-5
lines changed

druid/src/lens.rs

Lines changed: 93 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,44 @@ pub trait Lens<T: ?Sized, U: ?Sized> {
5757
fn with_mut<V, F: FnOnce(&mut U) -> V>(&self, data: &mut T, f: F) -> V;
5858
}
5959

60+
/// Helpers for manipulating `Lens`es
61+
pub trait LensExt<A: ?Sized, B: ?Sized>: Lens<A, B> {
62+
/// Copy the targeted value out of `data`
63+
fn get(&self, data: &A) -> B
64+
where
65+
B: Clone,
66+
{
67+
self.with(data, |x| x.clone())
68+
}
69+
70+
/// Set the targeted value in `data` to `value`
71+
fn put(&self, data: &mut A, value: B)
72+
where
73+
B: Sized,
74+
{
75+
self.with_mut(data, |x| *x = value);
76+
}
77+
78+
/// Compose a `Lens<A, B>` with a `Lens<B, C>` to produce a `Lens<A, C>`
79+
///
80+
/// ```
81+
/// # use druid::*;
82+
/// struct Foo { x: (u32, bool) }
83+
/// let lens = lens!(Foo, x).then(lens!((u32, bool), 1));
84+
/// assert_eq!(lens.get(&Foo { x: (0, true) }), true);
85+
/// ```
86+
fn then<Other, C>(self, other: Other) -> Then<Self, Other, B>
87+
where
88+
Other: Lens<B, C> + Sized,
89+
C: ?Sized,
90+
Self: Sized,
91+
{
92+
Then::new(self, other)
93+
}
94+
}
95+
96+
impl<A: ?Sized, B: ?Sized, T: Lens<A, B>> LensExt<A, B> for T {}
97+
6098
// A case can be made this should be in the `widget` module.
6199

62100
/// A wrapper for its widget subtree to have access to a part
@@ -147,7 +185,7 @@ where
147185
/// See also the `lens` macro.
148186
///
149187
/// ```
150-
/// let lens = druid::Field::new(|x: &Vec<u32>| &x[42], |x| &mut x[42]);
188+
/// let lens = druid::lens::Field::new(|x: &Vec<u32>| &x[42], |x| &mut x[42]);
151189
/// ```
152190
pub struct Field<Get, GetMut> {
153191
get: Get,
@@ -194,9 +232,61 @@ where
194232
#[macro_export]
195233
macro_rules! lens {
196234
($ty:ty, [$index:expr]) => {
197-
$crate::Field::new::<$ty, _>(|x| &x[$index], |x| &mut x[$index])
235+
$crate::lens::Field::new::<$ty, _>(|x| &x[$index], |x| &mut x[$index])
198236
};
199237
($ty:ty, $field:tt) => {
200-
$crate::Field::new::<$ty, _>(|x| &x.$field, |x| &mut x.$field)
238+
$crate::lens::Field::new::<$ty, _>(|x| &x.$field, |x| &mut x.$field)
201239
};
202240
}
241+
242+
/// `Lens` composed of two lenses joined together
243+
#[derive(Debug, Copy)]
244+
pub struct Then<T, U, B: ?Sized> {
245+
left: T,
246+
right: U,
247+
_marker: PhantomData<B>,
248+
}
249+
250+
impl<T, U, B: ?Sized> Then<T, U, B> {
251+
/// Compose two lenses
252+
///
253+
/// See also `LensExt::then`.
254+
pub fn new<A: ?Sized, C: ?Sized>(left: T, right: U) -> Self
255+
where
256+
T: Lens<A, B>,
257+
U: Lens<B, C>,
258+
{
259+
Self {
260+
left,
261+
right,
262+
_marker: PhantomData,
263+
}
264+
}
265+
}
266+
267+
impl<T, U, A, B, C> Lens<A, C> for Then<T, U, B>
268+
where
269+
A: ?Sized,
270+
B: ?Sized,
271+
C: ?Sized,
272+
T: Lens<A, B>,
273+
U: Lens<B, C>,
274+
{
275+
fn with<V, F: FnOnce(&C) -> V>(&self, data: &A, f: F) -> V {
276+
self.left.with(data, |b| self.right.with(b, f))
277+
}
278+
279+
fn with_mut<V, F: FnOnce(&mut C) -> V>(&self, data: &mut A, f: F) -> V {
280+
self.left.with_mut(data, |b| self.right.with_mut(b, f))
281+
}
282+
}
283+
284+
impl<T: Clone, U: Clone, B> Clone for Then<T, U, B> {
285+
fn clone(&self) -> Self {
286+
Self {
287+
left: self.left.clone(),
288+
right: self.right.clone(),
289+
_marker: PhantomData,
290+
}
291+
}
292+
}

druid/src/lib.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ mod command;
2626
mod data;
2727
mod env;
2828
mod event;
29-
mod lens;
29+
pub mod lens;
3030
mod localization;
3131
mod menu;
3232
mod mouse;
@@ -57,7 +57,7 @@ pub use command::{sys as commands, Command, Selector};
5757
pub use data::Data;
5858
pub use env::{Env, Key, Value};
5959
pub use event::{Event, WheelEvent};
60-
pub use lens::{Field, Lens, LensWrap};
60+
pub use lens::{Lens, LensExt, LensWrap};
6161
pub use localization::LocalizedString;
6262
pub use menu::{sys as platform_menus, ContextMenu, MenuDesc, MenuItem};
6363
pub use mouse::MouseEvent;

0 commit comments

Comments
 (0)