From 718c37c3725fc6f3a26ca1ec004a805b5d77e1b1 Mon Sep 17 00:00:00 2001 From: Paul Miller Date: Sun, 14 Jun 2020 08:57:45 -0400 Subject: [PATCH 1/4] listiter for hashmap, with example --- druid/examples/list_hashmap.rs | 169 +++++++++++++++++++++++++++++++++ druid/src/widget/list.rs | 86 +++++++++++++++++ 2 files changed, 255 insertions(+) create mode 100644 druid/examples/list_hashmap.rs diff --git a/druid/examples/list_hashmap.rs b/druid/examples/list_hashmap.rs new file mode 100644 index 0000000000..e9b96b464b --- /dev/null +++ b/druid/examples/list_hashmap.rs @@ -0,0 +1,169 @@ +// Copyright 2020 The xi-editor Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Demos basic list widget and list manipulations using im's HashMap. + +#[cfg(not(feature = "im"))] +pub fn main() { + eprintln!("This examples requires the \"im\" feature to be enabled:"); + eprintln!("cargo run --example list_hashmap --features im"); +} + +#[cfg(feature = "im")] +pub fn main() { + example::main() +} + +#[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_map: im::HashMap, + right_map: im::HashMap, + } + + 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 mut map = im::HashMap::new(); + map.insert("One".to_string(), 1); + map.insert("Two".to_string(), 2); + map.insert("Three".to_string(), 3); + let data = AppData { + left_map: map.clone(), + right_map: map, + }; + AppLauncher::with_window(main_window) + .use_simple_logger() + .launch(data) + .expect("launch failed"); + } + + fn ui_builder() -> impl Widget { + 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, _| { + // Inserting into our HashMaps by finding the highest key and going + 1 + // Add child to left list + let mut left_max = data + .left_map + .iter() + .max_by_key(|(_, value)| value.clone()) + .unwrap(); + let value = left_max.1 + 1; + data.left_map + .insert(format!("{}", value).to_string(), value as u32); + + // Add child to left list + let mut right_max = data + .right_map + .iter() + .max_by_key(|(_, value)| value.clone()) + .unwrap(); + let value = right_max.1 + 1; + data.right_map + .insert(format!("{}", value).to_string(), value as u32); + }) + .fix_height(30.0) + .expand_width(), + ); + + 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_key, item_value): &(String, u32), _env: &_| { + format!("List item key:{}, value:{}", item_key, item_value) + }) + .align_vertical(UnitPoint::LEFT) + .padding(10.0) + .expand() + .height(50.0) + .background(Color::rgb(0.5, 0.5, 0.5)) + })) + .vertical() + .lens(AppData::left_map), + 1.0, + ); + + // Build a list with shared data + lists.add_flex_child( + Scroll::new(List::new(|| { + Flex::row() + .with_child( + Label::new( + |(_, item_key, item_value): &( + im::HashMap, + String, + u32, + ), + _env: &_| { + format!("List item key:{}, value:{}", item_key, item_value) + }, + ) + .align_vertical(UnitPoint::LEFT), + ) + .with_flex_spacer(1.0) + .with_child( + Button::new("Delete") + .on_click( + |_ctx, + (shared, item_key, item_value): &mut ( + im::HashMap, + String, + u32, + ), + _env| { + // We have access to both child's data and shared data. + // Remove element from right list. + shared.remove(item_key).expect("That item wasn't found"); + }, + ) + .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_map.clone(), d.right_map.clone()), + |d: &mut AppData, x: (im::HashMap, im::HashMap)| { + // If shared data was changed reflect the changes in our AppData + d.right_map = x.0 + }, + )), + 1.0, + ); + + root.add_flex_child(lists, 1.0); + + // Mark the widget as needing its layout rects painted + root.debug_paint_layout() + } +} diff --git a/druid/src/widget/list.rs b/druid/src/widget/list.rs index 2b9d35eaf6..233dc318d9 100644 --- a/druid/src/widget/list.rs +++ b/druid/src/widget/list.rs @@ -122,6 +122,92 @@ impl ListIter<(S, T)> for (S, Vector) { } } +// K == hashmap key type +#[cfg(feature = "im")] +impl ListIter<(K, T)> for im::HashMap { + fn for_each(&self, mut cb: impl FnMut(&(K, T), usize)) { + for (i, (key, value)) in self.iter().enumerate() { + cb(&(key.to_owned(), value.to_owned()), i); + } + } + + fn for_each_mut(&mut self, mut cb: impl FnMut(&mut (K, T), usize)) { + for (i, (key, value)) in self.iter_mut().enumerate() { + cb(&mut (key.to_owned(), value.to_owned()), i); + } + } + + fn data_len(&self) -> usize { + self.len() + } +} + +// K == hashmap key type +// S == shared data type +#[cfg(feature = "im")] +impl ListIter<(S, K, T)> + for (S, im::HashMap) +{ + fn for_each(&self, mut cb: impl FnMut(&(S, K, T), usize)) { + let (shared, map) = self; + + for (i, (key, value)) in map.iter().enumerate() { + cb(&(shared.to_owned(), key.to_owned(), value.to_owned()), i); + } + } + + fn for_each_mut(&mut self, mut cb: impl FnMut(&mut (S, K, T), usize)) { + let (shared, map) = self; + for (i, (key, value)) in map.iter_mut().enumerate() { + let mut d = (shared.clone(), key.clone(), value.clone()); + cb(&mut d, i); + + if !shared.same(&d.0) { + *shared = d.0; + } + if !value.same(&d.2) { + *value = d.2 + } + } + } + + fn data_len(&self) -> usize { + self.1.len() + } +} + +// // K == hashmap key type +// // S == shared data type +// #[cfg(feature = "im")] +// impl ListIter<(S, T)> +// for (S, im::HashMap) +// { +// fn for_each(&self, mut cb: impl FnMut(&(S, K, T), usize)) { +// for (i, item) in self.1.iter().enumerate() { +// let d = (self.0.to_owned(), item.0.to_owned(), item.1.to_owned()); +// cb(&d, i); +// } +// } + +// fn for_each_mut(&mut self, mut cb: impl FnMut(&mut (S, K, T), usize)) { +// for (i, item) in self.1.iter_mut().enumerate() { +// let mut d = (self.0.clone(), item.0.clone(), item.1.clone()); +// cb(&mut d, i); + +// if !self.0.same(&d.0) { +// self.0 = d.0; +// } +// if !item.1.same(&d.2) { +// *item.1 = d.2; +// } +// } +// } + +// fn data_len(&self) -> usize { +// self.1.len() +// } +// } + impl ListIter for Arc> { fn for_each(&self, mut cb: impl FnMut(&T, usize)) { for (i, item) in self.iter().enumerate() { From 4b5d1207d1a84f4de04c8d1ba7e4af76e742245c Mon Sep 17 00:00:00 2001 From: Paul Miller Date: Sun, 14 Jun 2020 09:07:48 -0400 Subject: [PATCH 2/4] cleanup and changelog --- CHANGELOG.md | 3 +++ druid/examples/list_hashmap.rs | 7 +++---- druid/src/widget/list.rs | 32 -------------------------------- 3 files changed, 6 insertions(+), 36 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 37e311ffc5..7c56e5979d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,8 @@ You can find its changes [documented below](#060---2020-06-01). ### Added +- `im::HashMap` support for the `List` widget. ([#1033] by [@futurepaul]) + ### Changed - `Image` and `ImageData` exported by default. ([#1011] by [@covercash2]) @@ -322,6 +324,7 @@ Last release without a changelog :( [#1008]: https://github.com/xi-editor/druid/pull/1008 [#1011]: https://github.com/xi-editor/druid/pull/1011 [#1013]: https://github.com/xi-editor/druid/pull/1013 +[#1033]: https://github.com/xi-editor/druid/pull/1033 [Unreleased]: https://github.com/xi-editor/druid/compare/v0.6.0...master [0.6.0]: https://github.com/xi-editor/druid/compare/v0.5.0...v0.6.0 diff --git a/druid/examples/list_hashmap.rs b/druid/examples/list_hashmap.rs index e9b96b464b..a2d0c62b7d 100644 --- a/druid/examples/list_hashmap.rs +++ b/druid/examples/list_hashmap.rs @@ -27,7 +27,6 @@ pub fn main() { #[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::{ @@ -67,7 +66,7 @@ mod example { .on_click(|_, data: &mut AppData, _| { // Inserting into our HashMaps by finding the highest key and going + 1 // Add child to left list - let mut left_max = data + let left_max = data .left_map .iter() .max_by_key(|(_, value)| value.clone()) @@ -77,7 +76,7 @@ mod example { .insert(format!("{}", value).to_string(), value as u32); // Add child to left list - let mut right_max = data + let right_max = data .right_map .iter() .max_by_key(|(_, value)| value.clone()) @@ -131,7 +130,7 @@ mod example { Button::new("Delete") .on_click( |_ctx, - (shared, item_key, item_value): &mut ( + (shared, item_key, _item_value): &mut ( im::HashMap, String, u32, diff --git a/druid/src/widget/list.rs b/druid/src/widget/list.rs index 233dc318d9..c4329a7aeb 100644 --- a/druid/src/widget/list.rs +++ b/druid/src/widget/list.rs @@ -176,38 +176,6 @@ impl ListIter<(S, K, } } -// // K == hashmap key type -// // S == shared data type -// #[cfg(feature = "im")] -// impl ListIter<(S, T)> -// for (S, im::HashMap) -// { -// fn for_each(&self, mut cb: impl FnMut(&(S, K, T), usize)) { -// for (i, item) in self.1.iter().enumerate() { -// let d = (self.0.to_owned(), item.0.to_owned(), item.1.to_owned()); -// cb(&d, i); -// } -// } - -// fn for_each_mut(&mut self, mut cb: impl FnMut(&mut (S, K, T), usize)) { -// for (i, item) in self.1.iter_mut().enumerate() { -// let mut d = (self.0.clone(), item.0.clone(), item.1.clone()); -// cb(&mut d, i); - -// if !self.0.same(&d.0) { -// self.0 = d.0; -// } -// if !item.1.same(&d.2) { -// *item.1 = d.2; -// } -// } -// } - -// fn data_len(&self) -> usize { -// self.1.len() -// } -// } - impl ListIter for Arc> { fn for_each(&self, mut cb: impl FnMut(&T, usize)) { for (i, item) in self.iter().enumerate() { From 454c10950b941d848c785c1ba72bfe5b8d76581b Mon Sep 17 00:00:00 2001 From: Paul Miller Date: Sun, 14 Jun 2020 09:27:30 -0400 Subject: [PATCH 3/4] please clippy --- druid/examples/list_hashmap.rs | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/druid/examples/list_hashmap.rs b/druid/examples/list_hashmap.rs index a2d0c62b7d..f45f18d6ae 100644 --- a/druid/examples/list_hashmap.rs +++ b/druid/examples/list_hashmap.rs @@ -69,21 +69,19 @@ mod example { let left_max = data .left_map .iter() - .max_by_key(|(_, value)| value.clone()) + .max_by_key(|(_, value)| *value) .unwrap(); let value = left_max.1 + 1; - data.left_map - .insert(format!("{}", value).to_string(), value as u32); + data.left_map.insert(format!("{}", value), value as u32); // Add child to left list let right_max = data .right_map .iter() - .max_by_key(|(_, value)| value.clone()) + .max_by_key(|(_, value)| *value) .unwrap(); let value = right_max.1 + 1; - data.right_map - .insert(format!("{}", value).to_string(), value as u32); + data.right_map.insert(format!("{}", value), value as u32); }) .fix_height(30.0) .expand_width(), From 791059b149654240fb600d0d0c4ce1a7be782f8d Mon Sep 17 00:00:00 2001 From: Paul Miller Date: Sun, 14 Jun 2020 12:34:04 -0400 Subject: [PATCH 4/4] add impl_example --- druid/examples/web/src/lib.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/druid/examples/web/src/lib.rs b/druid/examples/web/src/lib.rs index 0f4e666514..ce4d0ce825 100644 --- a/druid/examples/web/src/lib.rs +++ b/druid/examples/web/src/lib.rs @@ -67,6 +67,7 @@ impl_example!(invalidation); impl_example!(layout); impl_example!(lens); impl_example!(list); +impl_example!(list_hashmap); impl_example!(multiwin); impl_example!(open_save); impl_example!(panels.unwrap());