Track tab items by location instead of path
This commit is contained in:
parent
e45172f8af
commit
17774af1e4
3 changed files with 88 additions and 74 deletions
16
src/app.rs
16
src/app.rs
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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(),
|
||||||
|
|
|
||||||
142
src/tab.rs
142
src/tab.rs
|
|
@ -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,
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue