From 2fc20d74866fed7af56194922c0c7d2158ff90b4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Fita?= <4925040+michalfita@users.noreply.github.com> Date: Mon, 12 Aug 2024 19:35:18 +0100 Subject: [PATCH 1/4] style: promote idiomatic functional Rust --- src/menu.rs | 95 +++++++++++++++++++++++++++-------------------------- 1 file changed, 48 insertions(+), 47 deletions(-) diff --git a/src/menu.rs b/src/menu.rs index b0aa3fc7..661967fc 100644 --- a/src/menu.rs +++ b/src/menu.rs @@ -25,6 +25,7 @@ macro_rules! menu_button { vec![$(Element::from($x)),+] ) .align_items(Alignment::Center) + .spacing(8) ) .height(Length::Fixed(32.0)) .padding([4, 16]) @@ -38,12 +39,9 @@ pub fn context_menu<'a>( key_binds: &HashMap, ) -> Element<'a, tab::Message> { let find_key = |action: &Action| -> String { - for (key_bind, key_action) in key_binds.iter() { - if action == key_action { - return key_bind.to_string(); - } - } - String::new() + key_binds.iter() + .find(|&(_, key_action)| action == key_action) + .map_or_else(|| String::new(), |(key_bind, _)| key_bind.to_string()) }; let menu_item = |label, action| { @@ -77,20 +75,16 @@ pub fn context_menu<'a>( .into() }; - let mut selected_dir = 0; - let mut selected = 0; - tab.items_opt().map(|items| { - for item in items.iter() { - if item.selected { - selected += 1; - if item.metadata.is_dir() { - selected_dir += 1; - } - } + let (selected, selected_dir) = tab.items_opt().into_iter().fold((0, 0), + |selections, items| { + let selected_iter = items.into_iter().filter(|i| i.selected); + let selected_count = selected_iter.clone().count(); + let selected_dirs_count = selected_iter.filter(|i| i.metadata.is_dir()).count(); + (selections.0 + selected_count, selections.1 + selected_dirs_count) } - }); + ); - let mut children: Vec> = Vec::new(); + let mut children: Vec> = Vec::with_capacity(16); match tab.location { Location::Path(_) | Location::Search(_, _) => { if selected > 0 { @@ -110,47 +104,54 @@ pub fn context_menu<'a>( children .push(menu_item(fl!("open-in-new-window"), Action::OpenInNewWindow).into()); } - children.push(container(horizontal_rule(1)).padding([0, 8]).into()); - children.push(menu_item(fl!("rename"), Action::Rename).into()); - children.push(menu_item(fl!("cut"), Action::Cut).into()); - children.push(menu_item(fl!("copy"), Action::Copy).into()); + children.extend(vec![ + container(horizontal_rule(1)).padding([0, 8]).into(), + menu_item(fl!("rename"), Action::Rename).into(), + menu_item(fl!("cut"), Action::Cut).into(), + menu_item(fl!("copy"), Action::Copy).into(), //TODO: Print? - children.push(container(horizontal_rule(1)).padding([0, 8]).into()); - children.push(menu_item(fl!("show-details"), Action::Properties).into()); - children.push(container(horizontal_rule(1)).padding([0, 8]).into()); - children.push(menu_item(fl!("add-to-sidebar"), Action::AddToSidebar).into()); - children.push(container(horizontal_rule(1)).padding([0, 8]).into()); - children.push(menu_item(fl!("move-to-trash"), Action::MoveToTrash).into()); + container(horizontal_rule(1)).padding([0, 8]).into(), + menu_item(fl!("show-details"), Action::Properties).into(), + container(horizontal_rule(1)).padding([0, 8]).into(), + menu_item(fl!("add-to-sidebar"), Action::AddToSidebar).into(), + container(horizontal_rule(1)).padding([0, 8]).into(), + menu_item(fl!("move-to-trash"), Action::MoveToTrash).into(), + ]); } else { //TODO: need better designs for menu with no selection //TODO: have things like properties but they apply to the folder? - children.push(menu_item(fl!("new-file"), Action::NewFile).into()); - children.push(menu_item(fl!("new-folder"), Action::NewFolder).into()); - children.push(menu_item(fl!("open-in-terminal"), Action::OpenTerminal).into()); - children.push(container(horizontal_rule(1)).padding([0, 8]).into()); - children.push(menu_item(fl!("select-all"), Action::SelectAll).into()); - children.push(menu_item(fl!("paste"), Action::Paste).into()); - children.push(container(horizontal_rule(1)).padding([0, 8]).into()); + children.extend(vec![ + menu_item(fl!("new-file"), Action::NewFile).into(), + menu_item(fl!("new-folder"), Action::NewFolder).into(), + menu_item(fl!("open-in-terminal"), Action::OpenTerminal).into(), + container(horizontal_rule(1)).padding([0, 8]).into(), + menu_item(fl!("select-all"), Action::SelectAll).into(), + menu_item(fl!("paste"), Action::Paste).into(), + container(horizontal_rule(1)).padding([0, 8]).into(), // TODO: Nested menu - children.push(sort_item(fl!("sort-by-name"), HeadingOptions::Name)); - children.push(sort_item(fl!("sort-by-modified"), HeadingOptions::Modified)); - children.push(sort_item(fl!("sort-by-size"), HeadingOptions::Size)); + sort_item(fl!("sort-by-name"), HeadingOptions::Name), + sort_item(fl!("sort-by-modified"), HeadingOptions::Modified), + sort_item(fl!("sort-by-size"), HeadingOptions::Size), + ]); } } Location::Trash => { children.push(menu_item(fl!("select-all"), Action::SelectAll).into()); if selected > 0 { - children.push(container(horizontal_rule(1)).padding([0, 8]).into()); - children.push(menu_item(fl!("show-details"), Action::Properties).into()); - children.push(container(horizontal_rule(1)).padding([0, 8]).into()); - children - .push(menu_item(fl!("restore-from-trash"), Action::RestoreFromTrash).into()); + children.extend(vec![ + container(horizontal_rule(1)).padding([0, 8]).into(), + menu_item(fl!("show-details"), Action::Properties).into(), + container(horizontal_rule(1)).padding([0, 8]).into(), + menu_item(fl!("restore-from-trash"), Action::RestoreFromTrash).into(), + ]); } - children.push(container(horizontal_rule(1)).padding([0, 8]).into()); + children.extend(vec![ + container(horizontal_rule(1)).padding([0, 8]).into(), // TODO: Nested menu - children.push(sort_item(fl!("sort-by-name"), HeadingOptions::Name)); - children.push(sort_item(fl!("sort-by-modified"), HeadingOptions::Modified)); - children.push(sort_item(fl!("sort-by-size"), HeadingOptions::Size)); + sort_item(fl!("sort-by-name"), HeadingOptions::Name), + sort_item(fl!("sort-by-modified"), HeadingOptions::Modified), + sort_item(fl!("sort-by-size"), HeadingOptions::Size), + ]); } } From da8e9088837d6575ca9247102eddfb30467fa7b2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Fita?= <4925040+michalfita@users.noreply.github.com> Date: Wed, 14 Aug 2024 16:27:10 +0100 Subject: [PATCH 2/4] style: introduce semantic of selection counter --- src/menu.rs | 56 ++++++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 45 insertions(+), 11 deletions(-) diff --git a/src/menu.rs b/src/menu.rs index 661967fc..251d6c60 100644 --- a/src/menu.rs +++ b/src/menu.rs @@ -75,31 +75,65 @@ pub fn context_menu<'a>( .into() }; - let (selected, selected_dir) = tab.items_opt().into_iter().fold((0, 0), - |selections, items| { + struct SelectionCounter { + total_count: usize, + dirs_count: usize, + } + + impl SelectionCounter { + fn new() -> Self { + Self { + total_count: 0, + dirs_count: 0, + } + } + + fn any(&self) -> bool { + self.total_count > 0 + } + + fn exactly_one(&self) -> bool { + self.total_count == 1 + } + + fn exactly_one_dir(&self) -> bool { + self.dirs_count == 1 + } + + fn no_dirs(&self) -> bool { + self.dirs_count == 0 + } + + fn only_directories(&self) -> bool { + self.total_count == self.dirs_count + } + } + + let selected = tab.items_opt().into_iter().fold(SelectionCounter::new(), + |mut selections, items| { let selected_iter = items.into_iter().filter(|i| i.selected); - let selected_count = selected_iter.clone().count(); - let selected_dirs_count = selected_iter.filter(|i| i.metadata.is_dir()).count(); - (selections.0 + selected_count, selections.1 + selected_dirs_count) + selections.total_count += selected_iter.clone().count(); + selections.dirs_count += selected_iter.filter(|i| i.metadata.is_dir()).count(); + selections } ); let mut children: Vec> = Vec::with_capacity(16); match tab.location { Location::Path(_) | Location::Search(_, _) => { - if selected > 0 { - if selected_dir == 1 && selected == 1 || selected_dir == 0 { + if selected.any() { + if selected.exactly_one_dir() && selected.exactly_one() || selected.no_dirs() { children.push(menu_item(fl!("open"), Action::Open).into()); } - if selected == 1 { + if selected.exactly_one() { children.push(menu_item(fl!("open-with"), Action::OpenWith).into()); - if selected_dir == 1 { + if selected.exactly_one_dir() { children .push(menu_item(fl!("open-in-terminal"), Action::OpenTerminal).into()); } } // All selected items are directories - if selected == selected_dir { + if selected.only_directories() { children.push(menu_item(fl!("open-in-new-tab"), Action::OpenInNewTab).into()); children .push(menu_item(fl!("open-in-new-window"), Action::OpenInNewWindow).into()); @@ -137,7 +171,7 @@ pub fn context_menu<'a>( } Location::Trash => { children.push(menu_item(fl!("select-all"), Action::SelectAll).into()); - if selected > 0 { + if selected.any() { children.extend(vec![ container(horizontal_rule(1)).padding([0, 8]).into(), menu_item(fl!("show-details"), Action::Properties).into(), From 285655addff09ca7b4e95433b1e30ce6bafc9901 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Fita?= <4925040+michalfita@users.noreply.github.com> Date: Fri, 16 Aug 2024 11:21:26 +0100 Subject: [PATCH 3/4] =?UTF-8?q?style:=20get=20rid=20of=20supposed=20O(n?= =?UTF-8?q?=C2=B2)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/menu.rs | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/src/menu.rs b/src/menu.rs index 251d6c60..4cb1a112 100644 --- a/src/menu.rs +++ b/src/menu.rs @@ -109,14 +109,15 @@ pub fn context_menu<'a>( } } - let selected = tab.items_opt().into_iter().fold(SelectionCounter::new(), - |mut selections, items| { - let selected_iter = items.into_iter().filter(|i| i.selected); - selections.total_count += selected_iter.clone().count(); - selections.dirs_count += selected_iter.filter(|i| i.metadata.is_dir()).count(); - selections - } - ); + let selected = tab.items_opt().map_or(SelectionCounter::new(), |items| { + items.into_iter() + .filter(|i| i.selected) + .fold(SelectionCounter::new(), |mut counter, selection| { + counter.total_count += 1; + selection.metadata.is_dir().then(|| counter.dirs_count += 1); + counter + }) + }); let mut children: Vec> = Vec::with_capacity(16); match tab.location { From f546f9009d25611812f341d37f85c97bfeda4f62 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Fita?= <4925040+michalfita@users.noreply.github.com> Date: Fri, 16 Aug 2024 12:23:15 +0100 Subject: [PATCH 4/4] style: move selection count where it belongs --- src/menu.rs | 44 +------------------------------------------- src/tab.rs | 52 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 53 insertions(+), 43 deletions(-) diff --git a/src/menu.rs b/src/menu.rs index 4cb1a112..5f31abad 100644 --- a/src/menu.rs +++ b/src/menu.rs @@ -75,49 +75,7 @@ pub fn context_menu<'a>( .into() }; - struct SelectionCounter { - total_count: usize, - dirs_count: usize, - } - - impl SelectionCounter { - fn new() -> Self { - Self { - total_count: 0, - dirs_count: 0, - } - } - - fn any(&self) -> bool { - self.total_count > 0 - } - - fn exactly_one(&self) -> bool { - self.total_count == 1 - } - - fn exactly_one_dir(&self) -> bool { - self.dirs_count == 1 - } - - fn no_dirs(&self) -> bool { - self.dirs_count == 0 - } - - fn only_directories(&self) -> bool { - self.total_count == self.dirs_count - } - } - - let selected = tab.items_opt().map_or(SelectionCounter::new(), |items| { - items.into_iter() - .filter(|i| i.selected) - .fold(SelectionCounter::new(), |mut counter, selection| { - counter.total_count += 1; - selection.metadata.is_dir().then(|| counter.dirs_count += 1); - counter - }) - }); + let selected = tab.selection_counts(); let mut children: Vec> = Vec::with_capacity(16); match tab.location { diff --git a/src/tab.rs b/src/tab.rs index f80056cc..977242e4 100644 --- a/src/tab.rs +++ b/src/tab.rs @@ -912,6 +912,46 @@ impl HeadingOptions { } } +/// Helper type to deal with conditions based on items selection count +pub struct SelectionCounter { + total_count: usize, + dirs_count: usize, +} + +impl SelectionCounter { + fn new() -> Self { + Self { + total_count: 0, + dirs_count: 0, + } + } + + /// Whether any items are selected + pub fn any(&self) -> bool { + self.total_count > 0 + } + + /// Whether exactly one item is selected + pub fn exactly_one(&self) -> bool { + self.total_count == 1 + } + + /// Whether exactly one directory is selected + pub fn exactly_one_dir(&self) -> bool { + self.dirs_count == 1 + } + + /// Whether selection has no directories + pub fn no_dirs(&self) -> bool { + self.dirs_count == 0 + } + + /// Whether selection has only directories + pub fn only_directories(&self) -> bool { + self.total_count == self.dirs_count + } +} + // TODO when creating items, pass > to each item // as a drag data, so that when dnd is initiated, they are all included #[derive(Clone)] @@ -1216,6 +1256,18 @@ impl Tab { last } + pub fn selection_counts(&self) -> SelectionCounter { + self.items_opt.as_ref().map_or(SelectionCounter::new(), |items| { + items.into_iter() + .filter(|i| i.selected) + .fold(SelectionCounter::new(), |mut counter, selection| { + counter.total_count += 1; + selection.metadata.is_dir().then(|| counter.dirs_count += 1); + counter + }) + }) + } + pub fn change_location(&mut self, location: &Location, history_i_opt: Option) { self.location = location.clone(); self.items_opt = None;