From 2b4a14dfe2403f1adfeb0423870216ee9a48cd06 Mon Sep 17 00:00:00 2001 From: Jeremy Soller Date: Thu, 10 Oct 2024 13:53:01 -0600 Subject: [PATCH] Show parent folder in details, part of #541 --- src/app.rs | 32 +++++++++--- src/dialog.rs | 137 ++++++++++++++++++++++++++++---------------------- src/tab.rs | 20 ++++++-- 3 files changed, 120 insertions(+), 69 deletions(-) diff --git a/src/app.rs b/src/app.rs index 9658f28..d4c1c82 100644 --- a/src/app.rs +++ b/src/app.rs @@ -314,7 +314,13 @@ pub enum Message { TabConfig(TabConfig), TabMessage(Option, tab::Message), TabNew, - TabRescan(Entity, Location, Vec, Option), + TabRescan( + Entity, + Location, + Option, + Vec, + Option, + ), TabView(Option, tab::View), ToggleContextPage(ContextPage), ToggleFoldersFirst, @@ -587,9 +593,13 @@ impl App { async move { let location2 = location.clone(); match tokio::task::spawn_blocking(move || location2.scan(icon_sizes)).await { - Ok(items) => { - message::app(Message::TabRescan(entity, location, items, selection_path)) - } + Ok((parent_item_opt, items)) => message::app(Message::TabRescan( + entity, + location, + parent_item_opt, + items, + selection_path, + )), Err(err) => { log::warn!("failed to rescan: {}", err); message::none() @@ -1148,6 +1158,12 @@ impl App { break; } } + if children.is_empty() { + if let Some(item) = &tab.parent_item_opt { + children + .push(item.preview_view(tab.config.icon_sizes, context_drawer)); + } + } } } } @@ -2609,10 +2625,11 @@ impl Application for App { }; return self.open_tab(location, true, None); } - Message::TabRescan(entity, location, items, selection_path) => { + Message::TabRescan(entity, location, parent_item_opt, items, selection_path) => { match self.tab_model.data_mut::(entity) { Some(tab) => { if location == tab.location { + tab.parent_item_opt = parent_item_opt; tab.set_items(items); if let Some(selection_path) = selection_path { tab.select_path(selection_path); @@ -2654,7 +2671,7 @@ impl Application for App { match tokio::task::spawn_blocking(move || Location::Trash.scan(icon_sizes)) .await { - Ok(items) => { + Ok((_parent_item_opt, items)) => { for path in &*recently_trashed { for item in &items { if let ItemMetadata::Trash { ref entry, .. } = item.metadata { @@ -4243,8 +4260,9 @@ pub(crate) mod test_utils { // New tab with items let location = Location::Path(path.to_owned()); - let items = location.scan(IconSizes::default()); + let (parent_item_opt, items) = location.scan(IconSizes::default()); let mut tab = Tab::new(location, TabConfig::default()); + tab.parent_item_opt = parent_item_opt; tab.set_items(items); // Ensure correct number of directories as a sanity check diff --git a/src/dialog.rs b/src/dialog.rs index d820a77..2e4c8b2 100644 --- a/src/dialog.rs +++ b/src/dialog.rs @@ -325,7 +325,7 @@ enum Message { SearchClear, SearchInput(String), TabMessage(tab::Message), - TabRescan(Vec), + TabRescan(Location, Option, Vec), TabView(tab::View), ToggleFoldersFirst, ZoomDefault, @@ -494,6 +494,11 @@ impl App { break; } } + if children.is_empty() { + if let Some(item) = &self.tab.parent_item_opt { + children.push(item.preview_view(self.tab.config.icon_sizes, true)); + } + } } } } @@ -505,8 +510,11 @@ impl App { let icon_sizes = self.tab.config.icon_sizes; Command::perform( async move { - match tokio::task::spawn_blocking(move || location.scan(icon_sizes)).await { - Ok(items) => message::app(Message::TabRescan(items)), + let location2 = location.clone(); + match tokio::task::spawn_blocking(move || location2.scan(icon_sizes)).await { + Ok((parent_item_opt, items)) => { + message::app(Message::TabRescan(location, parent_item_opt, items)) + } Err(err) => { log::warn!("failed to rescan: {}", err); message::none() @@ -1408,74 +1416,85 @@ impl Application for App { } return Command::batch(commands); } - Message::TabRescan(mut items) => { - // Filter - if let Some(filter_i) = self.filter_selected { - if let Some(filter) = self.filters.get(filter_i) { - // Parse filters - let mut parsed_globs = Vec::new(); - let mut parsed_mimes = Vec::new(); - for pattern in filter.patterns.iter() { - match pattern { - DialogFilterPattern::Glob(value) => { - match glob::Pattern::new(value) { - Ok(glob) => parsed_globs.push(glob), - Err(err) => { - log::warn!("failed to parse glob {:?}: {}", value, err); + Message::TabRescan(location, parent_item_opt, mut items) => { + if location == self.tab.location { + // Filter + if let Some(filter_i) = self.filter_selected { + if let Some(filter) = self.filters.get(filter_i) { + // Parse filters + let mut parsed_globs = Vec::new(); + let mut parsed_mimes = Vec::new(); + for pattern in filter.patterns.iter() { + match pattern { + DialogFilterPattern::Glob(value) => { + match glob::Pattern::new(value) { + Ok(glob) => parsed_globs.push(glob), + Err(err) => { + log::warn!( + "failed to parse glob {:?}: {}", + value, + err + ); + } } } - } - DialogFilterPattern::Mime(value) => { - match mime_guess::Mime::from_str(value) { - Ok(mime) => parsed_mimes.push(mime), - Err(err) => { - log::warn!("failed to parse mime {:?}: {}", value, err); + DialogFilterPattern::Mime(value) => { + match mime_guess::Mime::from_str(value) { + Ok(mime) => parsed_mimes.push(mime), + Err(err) => { + log::warn!( + "failed to parse mime {:?}: {}", + value, + err + ); + } } } } } + + items.retain(|item| { + if item.metadata.is_dir() { + // Directories are always shown + return true; + } + + // Check for mime type match (first because it is faster) + for mime in parsed_mimes.iter() { + if mime == &item.mime { + return true; + } + } + + // Check for glob match (last because it is slower) + for glob in parsed_globs.iter() { + if glob.matches(&item.name) { + return true; + } + } + + // No filters matched + false + }); } - - items.retain(|item| { - if item.metadata.is_dir() { - // Directories are always shown - return true; - } - - // Check for mime type match (first because it is faster) - for mime in parsed_mimes.iter() { - if mime == &item.mime { - return true; - } - } - - // Check for glob match (last because it is slower) - for glob in parsed_globs.iter() { - if glob.matches(&item.name) { - return true; - } - } - - // No filters matched - false - }); } - } - // Select based on filename - if let DialogKind::SaveFile { filename } = &self.flags.kind { - for item in items.iter_mut() { - item.selected = &item.name == filename; + // Select based on filename + if let DialogKind::SaveFile { filename } = &self.flags.kind { + for item in items.iter_mut() { + item.selected = &item.name == filename; + } } - } - self.tab.set_items(items); + self.tab.parent_item_opt = parent_item_opt; + self.tab.set_items(items); - // Reset focus on location change - if self.search_get().is_some() { - return widget::text_input::focus(self.search_id.clone()); - } else { - return widget::text_input::focus(self.filename_id.clone()); + // Reset focus on location change + if self.search_get().is_some() { + return widget::text_input::focus(self.search_id.clone()); + } else { + return widget::text_input::focus(self.filename_id.clone()); + } } } Message::TabView(view) => { diff --git a/src/tab.rs b/src/tab.rs index 86f09a5..32b4a42 100644 --- a/src/tab.rs +++ b/src/tab.rs @@ -909,8 +909,8 @@ impl Location { } } - pub fn scan(&self, sizes: IconSizes) -> Vec { - match self { + pub fn scan(&self, sizes: IconSizes) -> (Option, Vec) { + let items = match self { Self::Desktop(path, display, desktop_config) => { scan_desktop(path, display, *desktop_config, sizes) } @@ -922,7 +922,19 @@ impl Location { Self::Trash => scan_trash(sizes), Self::Recents => scan_recents(sizes), Self::Network(uri, _) => scan_network(uri, sizes), - } + }; + let parent_item_opt = match self.path_opt() { + Some(path) => match item_from_path(path, sizes) { + Ok(item) => Some(item), + Err(err) => { + log::warn!("failed to get item for {:?}: {}", path, err); + None + } + }, + //TODO: support other locations? + None => None, + }; + (parent_item_opt, items) } } @@ -1535,6 +1547,7 @@ pub struct Tab { pub sort_name: HeadingOptions, pub sort_direction: bool, pub gallery: bool, + pub(crate) parent_item_opt: Option, pub(crate) items_opt: Option>, pub dnd_hovered: Option<(Location, Instant)>, scrollable_id: widget::Id, @@ -1607,6 +1620,7 @@ impl Tab { sort_name: HeadingOptions::Name, sort_direction: true, gallery: false, + parent_item_opt: None, items_opt: None, scrollable_id: widget::Id::unique(), select_focus: None,