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

impl ListIter for im::HashMap #1033

Closed
wants to merge 4 commits into from
Closed
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
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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])
Expand Down Expand Up @@ -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
Expand Down
166 changes: 166 additions & 0 deletions druid/examples/list_hashmap.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
// 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::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<String, u32>,
right_map: im::HashMap<String, 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 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<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, _| {
// Inserting into our HashMaps by finding the highest key and going + 1
// Add child to left list
let left_max = data
.left_map
.iter()
.max_by_key(|(_, value)| *value)
.unwrap();
let value = left_max.1 + 1;
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)
.unwrap();
let value = right_max.1 + 1;
data.right_map.insert(format!("{}", value), 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>,
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>,
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<String, u32>, im::HashMap<String, u32>)| {
// 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()
}
}
1 change: 1 addition & 0 deletions druid/examples/web/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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());
Expand Down
54 changes: 54 additions & 0 deletions druid/src/widget/list.rs
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,60 @@ impl<S: Data, T: Data> ListIter<(S, T)> for (S, Vector<T>) {
}
}

// K == hashmap key type
#[cfg(feature = "im")]
impl<K: Data + std::hash::Hash + std::cmp::Eq, T: Data> ListIter<(K, T)> for im::HashMap<K, T> {
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<S: Data, K: Data + std::hash::Hash + std::cmp::Eq, T: Data> ListIter<(S, K, T)>
for (S, im::HashMap<K, T>)
{
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()
}
}

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