Show parent folder in details, part of #541

This commit is contained in:
Jeremy Soller 2024-10-10 13:53:01 -06:00
parent a5dfffcd72
commit 2b4a14dfe2
No known key found for this signature in database
GPG key ID: D02FD439211AF56F
3 changed files with 120 additions and 69 deletions

View file

@ -314,7 +314,13 @@ pub enum Message {
TabConfig(TabConfig), TabConfig(TabConfig),
TabMessage(Option<Entity>, tab::Message), TabMessage(Option<Entity>, tab::Message),
TabNew, TabNew,
TabRescan(Entity, Location, Vec<tab::Item>, Option<PathBuf>), TabRescan(
Entity,
Location,
Option<tab::Item>,
Vec<tab::Item>,
Option<PathBuf>,
),
TabView(Option<Entity>, tab::View), TabView(Option<Entity>, tab::View),
ToggleContextPage(ContextPage), ToggleContextPage(ContextPage),
ToggleFoldersFirst, ToggleFoldersFirst,
@ -587,9 +593,13 @@ impl App {
async move { async move {
let location2 = location.clone(); let location2 = location.clone();
match tokio::task::spawn_blocking(move || location2.scan(icon_sizes)).await { match tokio::task::spawn_blocking(move || location2.scan(icon_sizes)).await {
Ok(items) => { Ok((parent_item_opt, items)) => message::app(Message::TabRescan(
message::app(Message::TabRescan(entity, location, items, selection_path)) entity,
} location,
parent_item_opt,
items,
selection_path,
)),
Err(err) => { Err(err) => {
log::warn!("failed to rescan: {}", err); log::warn!("failed to rescan: {}", err);
message::none() message::none()
@ -1148,6 +1158,12 @@ impl App {
break; 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); 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::<Tab>(entity) { match self.tab_model.data_mut::<Tab>(entity) {
Some(tab) => { Some(tab) => {
if location == tab.location { if location == tab.location {
tab.parent_item_opt = parent_item_opt;
tab.set_items(items); tab.set_items(items);
if let Some(selection_path) = selection_path { if let Some(selection_path) = selection_path {
tab.select_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)) match tokio::task::spawn_blocking(move || Location::Trash.scan(icon_sizes))
.await .await
{ {
Ok(items) => { Ok((_parent_item_opt, items)) => {
for path in &*recently_trashed { for path in &*recently_trashed {
for item in &items { for item in &items {
if let ItemMetadata::Trash { ref entry, .. } = item.metadata { if let ItemMetadata::Trash { ref entry, .. } = item.metadata {
@ -4243,8 +4260,9 @@ pub(crate) mod test_utils {
// New tab with items // New tab with items
let location = Location::Path(path.to_owned()); 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()); let mut tab = Tab::new(location, TabConfig::default());
tab.parent_item_opt = parent_item_opt;
tab.set_items(items); tab.set_items(items);
// Ensure correct number of directories as a sanity check // Ensure correct number of directories as a sanity check

View file

@ -325,7 +325,7 @@ enum Message {
SearchClear, SearchClear,
SearchInput(String), SearchInput(String),
TabMessage(tab::Message), TabMessage(tab::Message),
TabRescan(Vec<tab::Item>), TabRescan(Location, Option<tab::Item>, Vec<tab::Item>),
TabView(tab::View), TabView(tab::View),
ToggleFoldersFirst, ToggleFoldersFirst,
ZoomDefault, ZoomDefault,
@ -494,6 +494,11 @@ impl App {
break; 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; let icon_sizes = self.tab.config.icon_sizes;
Command::perform( Command::perform(
async move { async move {
match tokio::task::spawn_blocking(move || location.scan(icon_sizes)).await { let location2 = location.clone();
Ok(items) => message::app(Message::TabRescan(items)), 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) => { Err(err) => {
log::warn!("failed to rescan: {}", err); log::warn!("failed to rescan: {}", err);
message::none() message::none()
@ -1408,74 +1416,85 @@ impl Application for App {
} }
return Command::batch(commands); return Command::batch(commands);
} }
Message::TabRescan(mut items) => { Message::TabRescan(location, parent_item_opt, mut items) => {
// Filter if location == self.tab.location {
if let Some(filter_i) = self.filter_selected { // Filter
if let Some(filter) = self.filters.get(filter_i) { if let Some(filter_i) = self.filter_selected {
// Parse filters if let Some(filter) = self.filters.get(filter_i) {
let mut parsed_globs = Vec::new(); // Parse filters
let mut parsed_mimes = Vec::new(); let mut parsed_globs = Vec::new();
for pattern in filter.patterns.iter() { let mut parsed_mimes = Vec::new();
match pattern { for pattern in filter.patterns.iter() {
DialogFilterPattern::Glob(value) => { match pattern {
match glob::Pattern::new(value) { DialogFilterPattern::Glob(value) => {
Ok(glob) => parsed_globs.push(glob), match glob::Pattern::new(value) {
Err(err) => { Ok(glob) => parsed_globs.push(glob),
log::warn!("failed to parse glob {:?}: {}", value, err); Err(err) => {
log::warn!(
"failed to parse glob {:?}: {}",
value,
err
);
}
} }
} }
} DialogFilterPattern::Mime(value) => {
DialogFilterPattern::Mime(value) => { match mime_guess::Mime::from_str(value) {
match mime_guess::Mime::from_str(value) { Ok(mime) => parsed_mimes.push(mime),
Ok(mime) => parsed_mimes.push(mime), Err(err) => {
Err(err) => { log::warn!(
log::warn!("failed to parse mime {:?}: {}", value, err); "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 // Select based on filename
if let DialogKind::SaveFile { filename } = &self.flags.kind { if let DialogKind::SaveFile { filename } = &self.flags.kind {
for item in items.iter_mut() { for item in items.iter_mut() {
item.selected = &item.name == filename; 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 // Reset focus on location change
if self.search_get().is_some() { if self.search_get().is_some() {
return widget::text_input::focus(self.search_id.clone()); return widget::text_input::focus(self.search_id.clone());
} else { } else {
return widget::text_input::focus(self.filename_id.clone()); return widget::text_input::focus(self.filename_id.clone());
}
} }
} }
Message::TabView(view) => { Message::TabView(view) => {

View file

@ -909,8 +909,8 @@ impl Location {
} }
} }
pub fn scan(&self, sizes: IconSizes) -> Vec<Item> { pub fn scan(&self, sizes: IconSizes) -> (Option<Item>, Vec<Item>) {
match self { let items = match self {
Self::Desktop(path, display, desktop_config) => { Self::Desktop(path, display, desktop_config) => {
scan_desktop(path, display, *desktop_config, sizes) scan_desktop(path, display, *desktop_config, sizes)
} }
@ -922,7 +922,19 @@ impl Location {
Self::Trash => scan_trash(sizes), Self::Trash => scan_trash(sizes),
Self::Recents => scan_recents(sizes), Self::Recents => scan_recents(sizes),
Self::Network(uri, _) => scan_network(uri, 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_name: HeadingOptions,
pub sort_direction: bool, pub sort_direction: bool,
pub gallery: bool, pub gallery: bool,
pub(crate) parent_item_opt: Option<Item>,
pub(crate) items_opt: Option<Vec<Item>>, pub(crate) items_opt: Option<Vec<Item>>,
pub dnd_hovered: Option<(Location, Instant)>, pub dnd_hovered: Option<(Location, Instant)>,
scrollable_id: widget::Id, scrollable_id: widget::Id,
@ -1607,6 +1620,7 @@ impl Tab {
sort_name: HeadingOptions::Name, sort_name: HeadingOptions::Name,
sort_direction: true, sort_direction: true,
gallery: false, gallery: false,
parent_item_opt: None,
items_opt: None, items_opt: None,
scrollable_id: widget::Id::unique(), scrollable_id: widget::Id::unique(),
select_focus: None, select_focus: None,