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 im::Vector support to the List widget. #940

Merged
merged 6 commits into from
May 15, 2020
Merged
Show file tree
Hide file tree
Changes from 5 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
4 changes: 3 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,8 @@ While some features like the clipboard, menus or file dialogs are not yet availa
- `UpdateCtx::request_timer` and `UpdateCtx::request_anim_frame`. ([#898] by [@finnerale])
- `UpdateCtx::size` and `LifeCycleCtx::size`. ([#917] by [@jneem])
- `WidgetExt::debug_widget_id`, for displaying widget ids on hover. ([#876] by [@cmyr])
- `im` feature, with `Data` support for the [`im` crate](https://docs.rs/im/) collections. ([#924])
- `im` feature, with `Data` support for the [`im` crate](https://docs.rs/im/) collections. ([#924] by [@cmyr])
- `im::Vector` support for the `List` widget. ([#940] by [@xStrom])

### Changed

Expand Down Expand Up @@ -171,6 +172,7 @@ While some features like the clipboard, menus or file dialogs are not yet availa
[#924]: https://github.com/xi-editor/druid/pull/924
[#925]: https://github.com/xi-editor/druid/pull/925
[#928]: https://github.com/xi-editor/druid/pull/928
[#940]: https://github.com/xi-editor/druid/pull/940

## [0.5.0] - 2020-04-01

Expand Down
197 changes: 105 additions & 92 deletions druid/examples/list.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,109 +14,122 @@

//! Demos basic list widget and list manipulations.

use std::sync::Arc;

use druid::lens::{self, LensExt};
use druid::widget::{Button, CrossAxisAlignment, Flex, Label, List, Scroll};
use druid::{
AppLauncher, Color, Data, Lens, LocalizedString, UnitPoint, Widget, WidgetExt, WindowDesc,
};

#[derive(Clone, Data, Lens)]
struct AppData {
left: Arc<Vec<u32>>,
right: Arc<Vec<u32>>,
#[cfg(not(feature = "im"))]
pub fn main() {
eprintln!("This examples requires the \"im\" feature to be enabled:");
eprintln!("cargo run --example list --features im");
}

#[cfg(feature = "im")]
pub fn main() {
let main_window = WindowDesc::new(ui_builder)
.title(LocalizedString::new("list-demo-window-title").with_placeholder("List Demo"));
// Set our initial data
let data = AppData {
left: Arc::new(vec![1, 2]),
right: Arc::new(vec![1, 2, 3]),
};
AppLauncher::with_window(main_window)
.use_simple_logger()
.launch(data)
.expect("launch failed");
example::main()
}

fn ui_builder() -> impl Widget<AppData> {
let mut root = Flex::column();
#[cfg(feature = "im")]
mod example {
use druid::im::{vector, Vector};
use druid::lens::{self, LensExt};
use druid::widget::{Button, CrossAxisAlignment, Flex, Label, List, Scroll};
use druid::{
AppLauncher, Color, Data, Lens, LocalizedString, UnitPoint, Widget, WidgetExt, WindowDesc,
};

#[derive(Clone, Data, Lens)]
struct AppData {
left: Vector<u32>,
right: Vector<u32>,
}

pub fn main() {
let main_window = WindowDesc::new(ui_builder)
.title(LocalizedString::new("list-demo-window-title").with_placeholder("List Demo"));
// Set our initial data
let data = AppData {
left: vector![1, 2],
right: vector![1, 2, 3],
};
AppLauncher::with_window(main_window)
.use_simple_logger()
.launch(data)
.expect("launch failed");
}

fn ui_builder() -> impl Widget<AppData> {
let mut root = Flex::column();

// Build a button to add children to both lists
root.add_child(
Button::new("Add")
.on_click(|_, data: &mut AppData, _| {
// Add child to left list
let value = data.left.len() + 1;
Arc::make_mut(&mut data.left).push(value as u32);
// Build a button to add children to both lists
root.add_child(
Button::new("Add")
.on_click(|_, data: &mut AppData, _| {
// Add child to left list
let value = data.left.len() + 1;
data.left.push_back(value as u32);

// Add child to right list
let value = data.right.len() + 1;
Arc::make_mut(&mut data.right).push(value as u32);
})
.fix_height(30.0)
.expand_width(),
);
// Add child to right list
let value = data.right.len() + 1;
data.right.push_back(value as u32);
})
.fix_height(30.0)
.expand_width(),
);

let mut lists = Flex::row().cross_axis_alignment(CrossAxisAlignment::Start);
let mut lists = Flex::row().cross_axis_alignment(CrossAxisAlignment::Start);

// Build a simple list
lists.add_flex_child(
Scroll::new(List::new(|| {
Label::new(|item: &u32, _env: &_| format!("List item #{}", item))
.align_vertical(UnitPoint::LEFT)
.padding(10.0)
.expand()
.height(50.0)
.background(Color::rgb(0.5, 0.5, 0.5))
}))
.vertical()
.lens(AppData::left),
1.0,
);
// Build a simple list
lists.add_flex_child(
Scroll::new(List::new(|| {
Label::new(|item: &u32, _env: &_| format!("List item #{}", item))
.align_vertical(UnitPoint::LEFT)
.padding(10.0)
.expand()
.height(50.0)
.background(Color::rgb(0.5, 0.5, 0.5))
}))
.vertical()
.lens(AppData::left),
1.0,
);

// Build a list with shared data
lists.add_flex_child(
Scroll::new(List::new(|| {
Flex::row()
.with_child(
Label::new(|(_, item): &(Arc<Vec<u32>>, u32), _env: &_| {
format!("List item #{}", item)
})
.align_vertical(UnitPoint::LEFT),
)
.with_flex_spacer(1.0)
.with_child(
Button::new("Delete")
.on_click(|_ctx, (shared, item): &mut (Arc<Vec<u32>>, u32), _env| {
// We have access to both child's data and shared data.
// Remove element from right list.
Arc::make_mut(shared).retain(|v| v != item);
// Build a list with shared data
lists.add_flex_child(
Scroll::new(List::new(|| {
Flex::row()
.with_child(
Label::new(|(_, item): &(Vector<u32>, u32), _env: &_| {
format!("List item #{}", item)
})
.fix_size(80.0, 20.0)
.align_vertical(UnitPoint::CENTER),
)
.padding(10.0)
.background(Color::rgb(0.5, 0.0, 0.5))
.fix_height(50.0)
}))
.vertical()
.lens(lens::Id.map(
// Expose shared data with children data
|d: &AppData| (d.right.clone(), d.right.clone()),
|d: &mut AppData, x: (Arc<Vec<u32>>, Arc<Vec<u32>>)| {
// If shared data was changed reflect the changes in our AppData
d.right = x.0
},
)),
1.0,
);
.align_vertical(UnitPoint::LEFT),
)
.with_flex_spacer(1.0)
.with_child(
Button::new("Delete")
.on_click(|_ctx, (shared, item): &mut (Vector<u32>, u32), _env| {
// We have access to both child's data and shared data.
// Remove element from right list.
shared.retain(|v| v != item);
})
.fix_size(80.0, 20.0)
.align_vertical(UnitPoint::CENTER),
)
.padding(10.0)
.background(Color::rgb(0.5, 0.0, 0.5))
.fix_height(50.0)
}))
.vertical()
.lens(lens::Id.map(
// Expose shared data with children data
|d: &AppData| (d.right.clone(), d.right.clone()),
|d: &mut AppData, x: (Vector<u32>, Vector<u32>)| {
// If shared data was changed reflect the changes in our AppData
d.right = x.0
},
)),
1.0,
);

root.add_flex_child(lists, 1.0);
root.add_flex_child(lists, 1.0);

// Mark the widget as needing its layout rects painted
root.debug_paint_layout()
// Mark the widget as needing its layout rects painted
root.debug_paint_layout()
}
}
13 changes: 0 additions & 13 deletions druid/src/data.rs
Original file line number Diff line number Diff line change
Expand Up @@ -190,12 +190,6 @@ impl<T: Data, U: Data> Data for Result<T, U> {
}
}

impl<D: Data> Data for Vec<D> {
fn same(&self, other: &Self) -> bool {
self.iter().zip(other.iter()).all(|(a, b)| a.same(b))
}
}

impl Data for () {
fn same(&self, _other: &Self) -> bool {
true
Expand Down Expand Up @@ -457,13 +451,6 @@ impl_data_for_array! { 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 }
mod test {
use super::Data;

#[test]
fn vec_data() {
let input = vec![1u8, 0, 0, 1, 0];
assert!(input.same(&vec![1u8, 0, 0, 1, 0]));
assert!(!input.same(&vec![1u8, 1, 0, 1, 0]));
}

#[test]
fn array_data() {
let input = [1u8, 0, 0, 1, 0];
Expand Down
53 changes: 51 additions & 2 deletions druid/src/widget/list.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@
use std::cmp::Ordering;
use std::sync::Arc;

#[cfg(feature = "im")]
use crate::im::Vector;

use crate::kurbo::{Point, Rect, Size};

use crate::{
Expand Down Expand Up @@ -71,6 +74,53 @@ pub trait ListIter<T>: Data {
fn data_len(&self) -> usize;
}

#[cfg(feature = "im")]
impl<T: Data> ListIter<T> for Vector<T> {
fn for_each(&self, mut cb: impl FnMut(&T, usize)) {
for (i, item) in self.iter().enumerate() {
cb(item, i);
}
}

fn for_each_mut(&mut self, mut cb: impl FnMut(&mut T, usize)) {
for (i, item) in self.iter_mut().enumerate() {
cb(item, i);
}
}

fn data_len(&self) -> usize {
self.len()
}
}

#[cfg(feature = "im")]
impl<T1: Data, T: Data> ListIter<(T1, T)> for (T1, Vector<T>) {
Copy link
Collaborator

@luleyleo luleyleo May 15, 2020

Choose a reason for hiding this comment

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

Maybe call them Shared and T instead? Or at least S and T.
I still do not understand why people like useless names for generic parameters,
this is really not something where we should let mathematicians inspire us. 😉

Copy link
Member Author

Choose a reason for hiding this comment

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

I just copied that from the Arc<Vec> implementation, but yeah it took me a while to grasp what the T1 even is, because I hadn't used List before. I changed it to S and added a comment.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Thanks! At first I got it the wrong way, thought T1 would be the iterable due to the 1.

fn for_each(&self, mut cb: impl FnMut(&(T1, T), usize)) {
for (i, item) in self.1.iter().enumerate() {
let d = (self.0.to_owned(), item.to_owned());
cb(&d, i);
}
}

fn for_each_mut(&mut self, mut cb: impl FnMut(&mut (T1, T), usize)) {
for (i, item) in self.1.iter_mut().enumerate() {
let mut d = (self.0.clone(), item.clone());
cb(&mut d, i);

if !self.0.same(&d.0) {
self.0 = d.0;
}
if !item.same(&d.1) {
*item = d.1;
}
}
}

fn data_len(&self) -> usize {
self.1.len()
}
}

impl<T: Data> ListIter<T> for Arc<Vec<T>> {
fn for_each(&self, mut cb: impl FnMut(&T, usize)) {
for (i, item) in self.iter().enumerate() {
Expand Down Expand Up @@ -111,7 +161,6 @@ impl<T1: Data, T: Data> ListIter<(T1, T)> for (T1, Arc<Vec<T>>) {
}

fn for_each_mut(&mut self, mut cb: impl FnMut(&mut (T1, T), usize)) {
let shared = self.0.to_owned();
let mut new_data = Vec::with_capacity(self.1.len());
let mut any_shared_changed = false;
let mut any_el_changed = false;
Expand All @@ -120,7 +169,7 @@ impl<T1: Data, T: Data> ListIter<(T1, T)> for (T1, Arc<Vec<T>>) {
let mut d = (self.0.clone(), item.to_owned());
cb(&mut d, i);

if !any_shared_changed && !shared.same(&d.0) {
if !any_shared_changed && !self.0.same(&d.0) {
any_shared_changed = true;
}
if any_shared_changed {
Expand Down