linebender / druid

A data-first Rust-native UI design toolkit.
https://linebender.org/druid/
Apache License 2.0
9.57k stars 567 forks source link

Best Practices for List Views and Lenses #1175

Open eliemichel opened 4 years ago

eliemichel commented 4 years ago

Hi, I am taking inspiration from the list example to build a slightly more complicated example but I run into a few conceptual issues. I am especially interested in the last part where list has shared data to be able to remove elements (redacted a bit for clarity):

List::new(|| {
    Flex::row()
        .with_child(
            Label::new(|(_, item): &(Vector<u32>, u32), _env: &_| {
                format!("List item #{}", item)
            })
        )
        .with_child(
            Button::new("Delete")
                .on_click(|_ctx, (shared, item): &mut (Vector<u32>, u32), _env| {
                    shared.retain(|v| v != item);
                })
        )
}))
.vertical()
.lens(lens::Id.map(
    |d: &AppData| (d.right.clone(), d.right.clone()),
    |d: &mut AppData, x: (Vector<u32>, Vector<u32>)| {
        d.right = x.0
    },
)

(A) First, this list only depends on AppData.right, so I'd like it to be globally lensed to that, i.e. either add .lens(AppData.right) at the end or replace lens::Id.map with AppData.right.map. This works:

.lens(
    |d: &Vector<u32>| (d.clone(), d.clone()),
    |d: &mut Vector<u32>, x: (Vector<u32>, Vector<u32>)| {
        *d = x.0
    },
)
.lens(AppData::right) // or AppData::right.map in the lens above

But is there a way to "automate" this unit Vector<T> => (Vector<T>,Vector<T>)? If I have several dynamic list I'll rewrite it many times.

(B) Let's say now AppData.right is a Vector<Stuff>. I have an impl Wiget<Stuff> object that I want to use in lieu of the Label of the example. How can I lens the state (Vector<Stuff>, Stuff) into just Stuff more elegantly that by writing a dedicated lens from scratch?

Once solved I would add it to the list.rs example I think it'd be worth sharing!

eliemichel commented 4 years ago

May have found a way for (A):

fn dupli_lens<T: Data>() -> impl Lens<T,(T,T)> {
    lens::Id.map(
        |d: &T| (d.clone(), d.clone()),
        |d: &mut T, x: (T, T)| {
            *d = x.0
        },
    )
}

Then after .vertical() it's simply:

.lens(AppData::right.then(dupli_lens())
// or
.lens(dupli_lens())
.lens(AppData::right)

edit: And for (B):

fn second_lens<T: Data + Copy>() -> impl Lens<(Vector<T>,T),T> {
    lens::Id.map(
        |d: &(Vector<T>, T)| d.1,
        |d: &mut (Vector<T>, T), x: T| {
            d.1 = x
        },
    )
}

and so the label becomes a simpler impl Widget<u32>:

.with_child(
    Label::new(|item: &u32, _env: &_| {
        format!("List item #{}", item)
    })
    .align_vertical(UnitPoint::LEFT)
    .lens(second_lens())
)

Two questions:

  1. Is there sth in the current lens library to do these?
  2. Is there a performance issue at using a copy in second_lens()? How can I do if T is not copyable?