Track tab items by location instead of path

This commit is contained in:
Jeremy Soller 2024-09-13 09:35:37 -06:00
parent e45172f8af
commit 17774af1e4
No known key found for this signature in database
GPG key ID: D02FD439211AF56F
3 changed files with 88 additions and 74 deletions

View file

@ -591,7 +591,7 @@ impl App {
if let Some(ref items) = tab.items_opt() { if let Some(ref items) = tab.items_opt() {
for item in items.iter() { for item in items.iter() {
if item.selected { if item.selected {
if let Some(path) = &item.path_opt { if let Some(Location::Path(path)) = &item.location_opt {
paths.push(path.clone()); paths.push(path.clone());
} }
} }
@ -952,7 +952,7 @@ impl App {
let parent = path.parent().unwrap_or(path); let parent = path.parent().unwrap_or(path);
for item in Location::Path(parent.to_owned()).scan(IconSizes::default()) { for item in Location::Path(parent.to_owned()).scan(IconSizes::default()) {
if item.path_opt.as_deref() == Some(path) { if item.path_opt() == Some(path) {
children.push(item.property_view(IconSizes::default())); children.push(item.property_view(IconSizes::default()));
} }
} }
@ -977,7 +977,7 @@ impl App {
let parent = path.parent().unwrap_or(path); let parent = path.parent().unwrap_or(path);
for item in Location::Path(parent.to_owned()).scan(IconSizes::default()) { for item in Location::Path(parent.to_owned()).scan(IconSizes::default()) {
if item.path_opt.as_deref() == Some(path) { if item.path_opt() == Some(path) {
children.push(item.property_view(IconSizes::default())); children.push(item.property_view(IconSizes::default()));
} }
} }
@ -1772,9 +1772,7 @@ impl Application for App {
//TODO: this could be further optimized by looking at what exactly changed //TODO: this could be further optimized by looking at what exactly changed
if let Some(items) = &mut tab.items_opt { if let Some(items) = &mut tab.items_opt {
for item in items.iter_mut() { for item in items.iter_mut() {
if item.path_opt.as_ref() if item.path_opt() == Some(event_path) {
== Some(event_path)
{
//TODO: reload more, like mime types? //TODO: reload more, like mime types?
match fs::metadata(&event_path) { match fs::metadata(&event_path) {
Ok(new_metadata) => match &mut item Ok(new_metadata) => match &mut item
@ -1836,7 +1834,7 @@ impl Application for App {
if let Some(items) = tab.items_opt() { if let Some(items) = tab.items_opt() {
for item in items.iter() { for item in items.iter() {
if item.selected { if item.selected {
if let Some(path) = &item.path_opt { if let Some(Location::Path(path)) = &item.location_opt {
paths.push(path.clone()); paths.push(path.clone());
} }
} }
@ -2036,7 +2034,7 @@ impl Application for App {
let mut selected = Vec::new(); let mut selected = Vec::new();
for item in items.iter() { for item in items.iter() {
if item.selected { if item.selected {
if let Some(path) = &item.path_opt { if let Some(Location::Path(path)) = &item.location_opt {
selected.push(path.clone()); selected.push(path.clone());
} }
} }
@ -3748,7 +3746,7 @@ pub(crate) mod test_utils {
name == item.name name == item.name
&& is_dir == item.metadata.is_dir() && is_dir == item.metadata.is_dir()
&& path == item.path_opt.as_ref().expect("item should have path") && path == item.path_opt().expect("item should have path")
&& is_hidden == item.hidden && is_hidden == item.hidden
} }

View file

@ -999,7 +999,7 @@ impl Application for App {
//TODO: this could be further optimized by looking at what exactly changed //TODO: this could be further optimized by looking at what exactly changed
if let Some(items) = &mut self.tab.items_opt { if let Some(items) = &mut self.tab.items_opt {
for item in items.iter_mut() { for item in items.iter_mut() {
if item.path_opt.as_ref() == Some(event_path) { if item.path_opt() == Some(event_path) {
//TODO: reload more, like mime types? //TODO: reload more, like mime types?
match fs::metadata(&event_path) { match fs::metadata(&event_path) {
Ok(new_metadata) => { Ok(new_metadata) => {
@ -1049,7 +1049,7 @@ impl Application for App {
if let Some(items) = self.tab.items_opt() { if let Some(items) = self.tab.items_opt() {
for item in items.iter() { for item in items.iter() {
if item.selected { if item.selected {
if let Some(path) = &item.path_opt { if let Some(Location::Path(path)) = &item.location_opt {
paths.push(path.clone()); paths.push(path.clone());
let _ = update_recently_used( let _ = update_recently_used(
&path.clone(), &path.clone(),

View file

@ -377,7 +377,7 @@ pub fn item_from_entry(
display_name, display_name,
metadata: ItemMetadata::Path { metadata, children }, metadata: ItemMetadata::Path { metadata, children },
hidden, hidden,
path_opt: Some(path), location_opt: Some(Location::Path(path)),
mime, mime,
icon_handle_grid, icon_handle_grid,
icon_handle_list, icon_handle_list,
@ -621,7 +621,7 @@ pub fn scan_trash(sizes: IconSizes) -> Vec<Item> {
display_name, display_name,
metadata: ItemMetadata::Trash { metadata, entry }, metadata: ItemMetadata::Trash { metadata, entry },
hidden: false, hidden: false,
path_opt: None, location_opt: None,
mime, mime,
icon_handle_grid, icon_handle_grid,
icon_handle_list, icon_handle_list,
@ -751,6 +751,14 @@ impl std::fmt::Display for Location {
} }
impl Location { impl Location {
pub fn path_opt(&self) -> Option<&PathBuf> {
match self {
Self::Path(path) => Some(&path),
Self::Search(path, _) => Some(&path),
_ => None,
}
}
pub fn scan(&self, sizes: IconSizes) -> Vec<Item> { pub fn scan(&self, sizes: IconSizes) -> Vec<Item> {
match self { match self {
Self::Path(path) => scan_path(path, sizes), Self::Path(path) => scan_path(path, sizes),
@ -874,7 +882,7 @@ pub struct Item {
pub display_name: String, pub display_name: String,
pub metadata: ItemMetadata, pub metadata: ItemMetadata,
pub hidden: bool, pub hidden: bool,
pub path_opt: Option<PathBuf>, pub location_opt: Option<Location>,
pub mime: Mime, pub mime: Mime,
pub icon_handle_grid: widget::icon::Handle, pub icon_handle_grid: widget::icon::Handle,
pub icon_handle_list: widget::icon::Handle, pub icon_handle_list: widget::icon::Handle,
@ -894,6 +902,10 @@ impl Item {
name.replace(".", ".\u{200B}").replace("_", "_\u{200B}") name.replace(".", ".\u{200B}").replace("_", "_\u{200B}")
} }
pub fn path_opt(&self) -> Option<&PathBuf> {
self.location_opt.as_ref()?.path_opt()
}
fn preview(&self, sizes: IconSizes) -> Element<'static, app::Message> { fn preview(&self, sizes: IconSizes) -> Element<'static, app::Message> {
// This loads the image only if thumbnailing worked // This loads the image only if thumbnailing worked
let icon = widget::icon::icon(self.icon_handle_grid.clone()) let icon = widget::icon::icon(self.icon_handle_grid.clone())
@ -907,7 +919,7 @@ impl Item {
{ {
ItemThumbnail::NotImage => icon, ItemThumbnail::NotImage => icon,
ItemThumbnail::Rgba(_) => { ItemThumbnail::Rgba(_) => {
if let Some(path) = &self.path_opt { if let Some(Location::Path(path)) = &self.location_opt {
widget::image::viewer(widget::image::Handle::from_path(path)) widget::image::viewer(widget::image::Handle::from_path(path))
.min_scale(1.0) .min_scale(1.0)
.into() .into()
@ -916,7 +928,7 @@ impl Item {
} }
} }
ItemThumbnail::Svg => { ItemThumbnail::Svg => {
if let Some(path) = &self.path_opt { if let Some(Location::Path(path)) = &self.location_opt {
widget::Svg::from_path(path).into() widget::Svg::from_path(path).into()
} else { } else {
icon icon
@ -944,7 +956,7 @@ impl Item {
column = column.push(widget::text(format!("Type: {}", self.mime))); column = column.push(widget::text(format!("Type: {}", self.mime)));
if let Some(path) = &self.path_opt { if let Some(Location::Path(path)) = &self.location_opt {
for app in self.open_with.iter() { for app in self.open_with.iter() {
column = column.push( column = column.push(
widget::button( widget::button(
@ -1309,10 +1321,11 @@ impl Tab {
} }
pub fn select_path(&mut self, path: PathBuf) { pub fn select_path(&mut self, path: PathBuf) {
let location = Location::Path(path);
*self.cached_selected.borrow_mut() = None; *self.cached_selected.borrow_mut() = None;
if let Some(ref mut items) = self.items_opt { if let Some(ref mut items) = self.items_opt {
for item in items.iter_mut() { for item in items.iter_mut() {
item.selected = item.path_opt.as_ref() == Some(&path); item.selected = item.location_opt.as_ref() == Some(&location);
} }
} }
} }
@ -1566,7 +1579,7 @@ impl Tab {
.as_ref() .as_ref()
.and_then(|items| click_i_opt.and_then(|click_i| items.get(click_i))) .and_then(|items| click_i_opt.and_then(|click_i| items.get(click_i)))
{ {
if let Some(path) = &clicked_item.path_opt { if let Some(Location::Path(path)) = &clicked_item.location_opt {
if clicked_item.metadata.is_dir() { if clicked_item.metadata.is_dir() {
cd = Some(Location::Path(path.clone())); cd = Some(Location::Path(path.clone()));
} else { } else {
@ -1964,7 +1977,7 @@ impl Tab {
if let Some(ref mut items) = self.items_opt { if let Some(ref mut items) = self.items_opt {
for item in items.iter() { for item in items.iter() {
if item.selected { if item.selected {
if let Some(path) = &item.path_opt { if let Some(Location::Path(path)) = &item.location_opt {
if path.is_dir() { if path.is_dir() {
//TODO: allow opening multiple tabs? //TODO: allow opening multiple tabs?
cd = Some(Location::Path(path.clone())); cd = Some(Location::Path(path.clone()));
@ -2004,7 +2017,7 @@ impl Tab {
if let Some(clicked_item) = if let Some(clicked_item) =
self.items_opt.as_ref().and_then(|items| items.get(click_i)) self.items_opt.as_ref().and_then(|items| items.get(click_i))
{ {
if let Some(path) = &clicked_item.path_opt { if let Some(Location::Path(path)) = &clicked_item.location_opt {
if clicked_item.metadata.is_dir() { if clicked_item.metadata.is_dir() {
//cd = Some(Location::Path(path.clone())); //cd = Some(Location::Path(path.clone()));
commands.push(Command::OpenInNewTab(path.clone())) commands.push(Command::OpenInNewTab(path.clone()))
@ -2035,8 +2048,9 @@ impl Tab {
} }
Message::Thumbnail(path, thumbnail) => { Message::Thumbnail(path, thumbnail) => {
if let Some(ref mut items) = self.items_opt { if let Some(ref mut items) = self.items_opt {
let location = Location::Path(path);
for item in items.iter_mut() { for item in items.iter_mut() {
if item.path_opt.as_ref() == Some(&path) { if item.location_opt.as_ref() == Some(&location) {
if let ItemThumbnail::Rgba(rgba) = &thumbnail { if let ItemThumbnail::Rgba(rgba) = &thumbnail {
//TODO: pass handles already generated to avoid blocking main thread //TODO: pass handles already generated to avoid blocking main thread
let handle = widget::icon::from_raster_pixels( let handle = widget::icon::from_raster_pixels(
@ -2765,9 +2779,10 @@ impl Tab {
} }
} }
let column: Element<Message> = if item.metadata.is_dir() && item.path_opt.is_some() let column: Element<Message> = if item.metadata.is_dir()
&& item.location_opt.is_some()
{ {
let tab_location = Location::Path(item.path_opt.clone().unwrap()); let tab_location = item.location_opt.clone().unwrap();
let tab_location_enter = tab_location.clone(); let tab_location_enter = tab_location.clone();
let tab_location_leave = tab_location.clone(); let tab_location_leave = tab_location.clone();
let is_dnd_hovered = let is_dnd_hovered =
@ -3126,58 +3141,59 @@ impl Tab {
}; };
let button_row = button(row.into()); let button_row = button(row.into());
let button_row: Element<_> = if item.metadata.is_dir() && item.path_opt.is_some() { let button_row: Element<_> =
let tab_location = Location::Path(item.path_opt.clone().unwrap()); if item.metadata.is_dir() && item.location_opt.is_some() {
let tab_location_enter = tab_location.clone(); let tab_location = item.location_opt.clone().unwrap();
let tab_location_leave = tab_location.clone(); let tab_location_enter = tab_location.clone();
let is_dnd_hovered = let tab_location_leave = tab_location.clone();
self.dnd_hovered.as_ref().map(|(l, _)| l) == Some(&tab_location); let is_dnd_hovered =
cosmic::widget::container( self.dnd_hovered.as_ref().map(|(l, _)| l) == Some(&tab_location);
DndDestination::for_data(button_row, move |data, action| { cosmic::widget::container(
if let Some(mut data) = data { DndDestination::for_data(button_row, move |data, action| {
if action == DndAction::Copy { if let Some(mut data) = data {
Message::Drop(Some((tab_location.clone(), data))) if action == DndAction::Copy {
} else if action == DndAction::Move { Message::Drop(Some((tab_location.clone(), data)))
data.kind = ClipboardKind::Cut; } else if action == DndAction::Move {
Message::Drop(Some((tab_location.clone(), data))) data.kind = ClipboardKind::Cut;
Message::Drop(Some((tab_location.clone(), data)))
} else {
log::warn!("unsupported action: {:?}", action);
Message::Drop(None)
}
} else { } else {
log::warn!("unsupported action: {:?}", action); log::warn!("No data for drop.");
Message::Drop(None) Message::Drop(None)
} }
} else { })
log::warn!("No data for drop."); .on_enter(move |_, _, _| Message::DndEnter(tab_location_enter.clone()))
Message::Drop(None) .on_leave(move || Message::DndLeave(tab_location_leave.clone())),
} )
}) // todo refactor into the dnd destination wrapper
.on_enter(move |_, _, _| Message::DndEnter(tab_location_enter.clone())) .style(if is_dnd_hovered {
.on_leave(move || Message::DndLeave(tab_location_leave.clone())), theme::Container::custom(|t| {
) let mut a = cosmic::iced_style::container::StyleSheet::appearance(
// todo refactor into the dnd destination wrapper t,
.style(if is_dnd_hovered { &theme::Container::default(),
theme::Container::custom(|t| { );
let mut a = cosmic::iced_style::container::StyleSheet::appearance( let t = t.cosmic();
t, // todo use theme drop target color
&theme::Container::default(), let mut bg = t.accent_color();
); bg.alpha = 0.2;
let t = t.cosmic(); a.background = Some(Color::from(bg).into());
// todo use theme drop target color a.border = Border {
let mut bg = t.accent_color(); color: t.accent_color().into(),
bg.alpha = 0.2; width: 1.0,
a.background = Some(Color::from(bg).into()); radius: t.radius_s().into(),
a.border = Border { };
color: t.accent_color().into(), a
width: 1.0, })
radius: t.radius_s().into(), } else {
}; theme::Container::default()
a
}) })
.into()
} else { } else {
theme::Container::default() button_row.into()
}) };
.into()
} else {
button_row.into()
};
if item.selected || !drag_items.is_empty() { if item.selected || !drag_items.is_empty() {
let dnd_row = if !item.selected { let dnd_row = if !item.selected {
@ -3306,8 +3322,8 @@ impl Tab {
items items
.iter() .iter()
.filter(|item| item.selected) .filter(|item| item.selected)
.filter_map(|item| item.path_opt.clone()) .filter_map(|item| item.path_opt().map(|x| x.clone()))
.collect::<Vec<_>>() .collect::<Vec<PathBuf>>()
}) })
.unwrap_or_default(); .unwrap_or_default();
let item_view = DndSource::<_, cosmic::app::Message<app::Message>, ClipboardCopy>::with_id( let item_view = DndSource::<_, cosmic::app::Message<app::Message>, ClipboardCopy>::with_id(
@ -3485,7 +3501,7 @@ impl Tab {
} }
} }
if let Some(path) = item.path_opt.clone() { if let Some(Location::Path(path)) = item.location_opt.clone() {
subscriptions.push(subscription::channel( subscriptions.push(subscription::channel(
path.clone(), path.clone(),
1, 1,