From b268fef3febb0aca9fd32ca5288c716c9bfa87d2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aliaksandr=20Tru=C5=A1?= Date: Tue, 17 Sep 2024 19:25:12 +0200 Subject: [PATCH 01/18] Update cosmic_files.ftl BE translation --- i18n/be/cosmic_files.ftl | 125 +++++++++++++++++++++++++++++++++++++-- 1 file changed, 120 insertions(+), 5 deletions(-) diff --git a/i18n/be/cosmic_files.ftl b/i18n/be/cosmic_files.ftl index 989c683..ceb1267 100644 --- a/i18n/be/cosmic_files.ftl +++ b/i18n/be/cosmic_files.ftl @@ -1,9 +1,14 @@ cosmic-files = Файлы COSMIC empty-folder = Пустая папка empty-folder-hidden = Пустая папка (з захаванымі элементамі) +no-results = Нічога не знойдзена filesystem = Файлавая сістэма home = Хатняя папка +networks = Сеткі +notification-in-progress = Ідзе аперацыя з файламі. trash = Сметніца +recents = Нядаўняе +undo = Адрабіць # List view name = Назва @@ -12,6 +17,9 @@ size = Памер # Dialogs +## Compress Dialog +create-archive = Стварыць архіў + ## Empty Trash Dialog empty-trash = Ачысціць сметніцу empty-trash-warning = Вы сапраўды хочаце назаўсёды выдаліць усе элементы з сметніцы? @@ -29,11 +37,13 @@ name-no-slashes = Назва не можа ўтрымліваць касыя р # Open/Save Dialog cancel = Скасаваць +create = Стварыць open = Адкрыць open-file = Адкрыць файл open-folder = Адкрыць папку open-in-new-tab = Адкрыць у новай укладцы open-in-new-window = Адкрыць у навым акне +open-item-location = Адкрыць месцазнаходжанне прадмета open-multiple-files = Адкрыць некалькі файлаў open-multiple-folders = Адкрыць некалькі папак save = Захаваць @@ -47,22 +57,106 @@ rename-folder = Перайменаваць папку replace = Замяніць replace-title = {$filename} ужо існуе ў гэтым месцы. replace-warning = Вы сапраўды хочыце замяніць яго на той, які вы захоўваеце? Пры замене яго змесціва будзе перапісана. +replace-warning-operation = Вы хочаце замяніць яго? Пры замене яго змесціва будзе перазапісана. +original-file = Зыходны файл +replace-with = Замяніць на +apply-to-all = Прымяніць на ўсіх +keep-both = Захаваць абодва +skip = Прапусціць -# List view -name = Назва -modified = Зменена -size = Памер +## Metadata Dialog +owner = Уладальнік +group = Група +other = Іншыя +read = Чытаць +write = Пісаць +execute = Выконваць # Context Pages ## About git-description = Git каміт {$hash} ад {$date} +## Add Network Drive +add-network-drive = Дадаць сеткавы дыск +connect = Падлучыць +connect-anonymously = Падлучыць ананімна +connecting = Падлучэнне... +domain = Дамен +enter-server-address = Увядзіце адрас серверу +network-drive-description = + Адрасы сервераў ўключаюць у сябе прэфікс пратаколу і адрас. + Прыклад: ssh://192.168.0.1, ftp://[2001:db8::1] +### Make sure to keep the comma which separates the columns +network-drive-schemes = + Даступная пратаколы,Прэфікс + AppleTalk,afp:// + File Transfer Protocol,ftp:// або ftps:// + Network File System,nfs:// + Server Message Block,smb:// + SSH File Transfer Protocol,sftp:// або ssh:// + WebDav,dav:// або davs:// +network-drive-error = Немагчыма атрымаць доступ да сеткавага дыска +password = Пароль +remember-password = Запомніць пароль +try-again = Паўтарыць спробу +username = Імя карыстальніка + ## Operations -operations = Аперацыі +edit-history = Гісторыя рэдагавання +history = Гісторыя +no-history = Няма элементаў у гісторыі pending = У чаканні failed = Няўдала complete = Завершана +compressing = Сцісканне {$items} {$items -> + [one] элементу + *[other] элементаў + } з {$from} у {$to} +compressed = Сціснута {$items} {$items -> + [one] элемент + *[other] элементаў + } з {$from} у {$to} +copy_noun = Капіяваць +creating = Стварэнне {$name} у {$parent} +created = Створана {$name} у {$parent} +copying = Капіяванне {$items} {$items -> + [one] элементу + *[other] элементаў + } з {$from} у {$to} +copied = Скапіявана {$items} {$items -> + [one] элемент + *[other] элементаў + } з {$from} у {$to} +emptying-trash = Emptying {trash} +emptied-trash = Emptied {trash} +extracting = Выманне {$items} {$items -> + [one] элементу + *[other] элементаў + } з {$from} у {$to} +extracted = Вынята {$items} {$items -> + [one] элемент + *[other] элементаў + } з {$from} у {$to} +moving = Перамяшчэнне {$items} {$items -> + [one] элементу + *[other] элементаў + } з {$from} у {$to} +moved = Перанесена {$items} {$items -> + [one] элемент + *[other] элементаў + } з {$from} у {$to} +renaming = Перайменаванне {$from} у {$to} +renamed = Перайменавана {$from} у {$to} +restoring = Аднаўленне {$items} {$items -> + [one] элементу + *[other] элементаў + } з {trash} +restored = Адноўлена {$items} {$items -> + [one] элемент + *[other] элементаў + } з {trash} +unknown-folder = невядомая папка ## Open with open-with = Адкрыць з дапамогай @@ -71,10 +165,14 @@ default-app = {$name} (па змаўчанні) ## Properties properties = Уласцівасці +## Show details +show-details = Паказаць дэталі + ## Settings settings = Налады settings-tab = Укладка settings-show-hidden = Паказаць схаваныя файлы +default-view = Выгляд па змаўчанні icon-size-list = Памер значка (спіс) icon-size-grid = Памер значка (сетка) sorting-name = Сартаваць @@ -91,6 +189,8 @@ light = Светлая # Context menu add-to-sidebar = Дадаць на бакавую панэль +compress = Сціснуць +extract-here = Выняць new-file = Новы файл new-folder = Новая папка open-in-terminal = Адкрыць у кансолі @@ -108,6 +208,7 @@ file = Файл new-tab = Новая ўкладка new-window = Новае акно rename = Перайменаваць +menu-show-details = Паказаць уласцівасці... close-tab = Закрыць укладку quit = Выйсці @@ -119,8 +220,22 @@ paste = Уставіць select-all = Вылучыць усё ## View +zoom-in = Павялічыць +default-size = Памер па змаўчанні +zoom-out = Паменшыць view = Выгляд grid-view = Рэжым сеткі list-view = Рэжым спіса +show-hidden-files = Паказваць схаваныя файлы +list-directories-first = Размяшчаць папкі перад файламі menu-settings = Налады... menu-about = Пра Файлы COSMIC... + +## Sort +sort = Сартаванне +sort-a-z = А-Я +sort-z-a = Я-А +sort-newest-first = Спачатку новыя +sort-oldest-first = Спачатку старыя +sort-smallest-to-largest = Ад меншага да найбольшага +sort-largest-to-smallest = Ад вялікага да найменшага From ca4cdb8fde6e3eb36329ee2c4fe88ccd12aa689e Mon Sep 17 00:00:00 2001 From: Jeremy Soller Date: Tue, 17 Sep 2024 11:31:15 -0600 Subject: [PATCH 02/18] Close context menu when changing location, fixes #328 --- src/tab.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/tab.rs b/src/tab.rs index 3632695..424c618 100644 --- a/src/tab.rs +++ b/src/tab.rs @@ -1551,6 +1551,7 @@ impl Tab { self.location = location.clone(); self.items_opt = None; self.select_focus = None; + self.context_menu = None; self.edit_location = None; if let Some(history_i) = history_i_opt { // Navigating in history From de08823052fe1d04f3552c86b3ff26554a6948d8 Mon Sep 17 00:00:00 2001 From: Jeremy Soller Date: Tue, 17 Sep 2024 11:37:49 -0600 Subject: [PATCH 03/18] Show toasts as overlay on empty element, fixes #363 --- src/app.rs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/app.rs b/src/app.rs index e164fd4..c632341 100644 --- a/src/app.rs +++ b/src/app.rs @@ -3182,7 +3182,7 @@ impl Application for App { space_xxs, space_s, .. } = theme::active().cosmic().spacing; - let mut tab_column = widget::column::with_capacity(1); + let mut tab_column = widget::column::with_capacity(3); if self.tab_model.iter().count() > 1 { tab_column = tab_column.push( @@ -3218,7 +3218,13 @@ impl Application for App { } } - let content: Element<_> = widget::toaster(&self.toasts, tab_column).into(); + // The toaster is added on top of an empty element to ensure that it does not override context menus + tab_column = tab_column.push(widget::toaster( + &self.toasts, + widget::horizontal_space(Length::Fill), + )); + + let content: Element<_> = tab_column.into(); // Uncomment to debug layout: //content.explain(cosmic::iced::Color::WHITE) From 0fe302a5247d88b6d17a3acd59da93440c646a4c Mon Sep 17 00:00:00 2001 From: Jeremy Soller Date: Tue, 17 Sep 2024 11:41:14 -0600 Subject: [PATCH 04/18] Default to list view --- src/app.rs | 5 ++++- src/config.rs | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/app.rs b/src/app.rs index c632341..24ceed5 100644 --- a/src/app.rs +++ b/src/app.rs @@ -487,7 +487,10 @@ impl App { let mut tab = Tab::new(location.clone(), self.config.tab); tab.mode = match self.mode { Mode::App => tab::Mode::App, - Mode::Desktop => tab::Mode::Desktop, + Mode::Desktop => { + tab.config.view = tab::View::Grid; + tab::Mode::Desktop + } }; let entity = self .tab_model diff --git a/src/config.rs b/src/config.rs index f23a4fe..e75569b 100644 --- a/src/config.rs +++ b/src/config.rs @@ -168,7 +168,7 @@ pub struct TabConfig { impl Default for TabConfig { fn default() -> Self { Self { - view: View::Grid, + view: View::List, folders_first: true, show_hidden: false, sort_name: HeadingOptions::Name, From ed2aeadc798b4a516ba9298f3ec3c1a8507f76e2 Mon Sep 17 00:00:00 2001 From: Jeremy Soller Date: Tue, 17 Sep 2024 11:55:54 -0600 Subject: [PATCH 05/18] Add location under the file name in list view search results, fixes #338 --- src/tab.rs | 57 ++++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 55 insertions(+), 2 deletions(-) diff --git a/src/tab.rs b/src/tab.rs index 424c618..f25e7e2 100644 --- a/src/tab.rs +++ b/src/tab.rs @@ -3044,7 +3044,8 @@ impl Tab { let modified_width = 200.0; let size_width = 100.0; let condensed = size.width < (name_width + modified_width + size_width); - let icon_size = if condensed { + let is_search = matches!(self.location, Location::Search(_, _)); + let icon_size = if condensed || is_search { icon_sizes.list_condensed() } else { icon_sizes.list() @@ -3141,6 +3142,32 @@ impl Tab { .height(Length::Fixed(row_height as f32)) .align_items(Alignment::Center) .spacing(space_xxs) + } else if is_search { + widget::row::with_children(vec![ + widget::icon::icon(item.icon_handle_list_condensed.clone()) + .content_fit(ContentFit::Contain) + .size(icon_size) + .into(), + widget::column::with_children(vec![ + widget::text(item.display_name.clone()).into(), + widget::text::caption(match item.path_opt() { + Some(path) => path.display().to_string(), + None => String::new(), + }) + .into(), + ]) + .width(Length::Fill) + .into(), + widget::text(modified_text.clone()) + .width(Length::Fixed(modified_width)) + .into(), + widget::text(size_text.clone()) + .width(Length::Fixed(size_width)) + .into(), + ]) + .height(Length::Fixed(row_height as f32)) + .align_items(Alignment::Center) + .spacing(space_xxs) } else { widget::row::with_children(vec![ widget::icon::icon(item.icon_handle_list.clone()) @@ -3258,6 +3285,32 @@ impl Tab { .align_items(Alignment::Center) .spacing(space_xxs) .into() + } else if is_search { + widget::row::with_children(vec![ + widget::icon::icon(item.icon_handle_list_condensed.clone()) + .content_fit(ContentFit::Contain) + .size(icon_size) + .into(), + widget::column::with_children(vec![ + widget::text(item.display_name.clone()).into(), + widget::text::caption(match item.path_opt() { + Some(path) => path.display().to_string(), + None => String::new(), + }) + .into(), + ]) + .width(Length::Fill) + .into(), + widget::text(modified_text.clone()) + .width(Length::Fixed(modified_width)) + .into(), + widget::text(size_text.clone()) + .width(Length::Fixed(size_width)) + .into(), + ]) + .align_items(Alignment::Center) + .spacing(space_xxs) + .into() } else { widget::row::with_children(vec![ widget::icon::icon(item.icon_handle_list.clone()) @@ -3300,7 +3353,7 @@ impl Tab { } //TODO: HACK If we don't reach the bottom of the view, go ahead and add a spacer to do that { - let top_deduct = (if condensed { 6 } else { 9 }) * space_xxs; + let top_deduct = (if condensed || is_search { 6 } else { 9 }) * space_xxs; self.item_view_size_opt .set(self.size_opt.get().map(|s| Size { From d0359af0b5c8fe0971adec0b0a793a7f9d5f4b3e Mon Sep 17 00:00:00 2001 From: Jeremy Soller Date: Tue, 17 Sep 2024 12:31:54 -0600 Subject: [PATCH 06/18] Add support for bzip and xz tar files, part of #122 --- Cargo.lock | 22 ++++++++++++++++++++++ Cargo.toml | 9 ++++++--- src/menu.rs | 9 +++++++++ src/operation.rs | 40 +++++++++++++++++++++++++++++++--------- 4 files changed, 68 insertions(+), 12 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index fe3771e..e58c1f6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1242,6 +1242,7 @@ dependencies = [ name = "cosmic-files" version = "0.1.0" dependencies = [ + "bzip2", "chrono", "dirs 5.0.1", "env_logger", @@ -1261,6 +1262,7 @@ dependencies = [ "image", "libc", "libcosmic", + "liblzma", "log", "mime_guess", "notify-debouncer-full", @@ -3576,6 +3578,26 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "liblzma" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7c45fc6fcf5b527d3cf89c1dee8c327943984b0dc8bfcf6e100473b00969e63" +dependencies = [ + "liblzma-sys", +] + +[[package]] +name = "liblzma-sys" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63117d31458acdb7b406f6c60090aa8e1e7cd6e283f8ee02ce585ed68c53fe39" +dependencies = [ + "cc", + "libc", + "pkg-config", +] + [[package]] name = "libm" version = "0.2.8" diff --git a/Cargo.toml b/Cargo.toml index c74db0b..89dfd41 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,7 +14,6 @@ chrono = { version = "0.4", features = ["unstable-locales"] } dirs = "5.0.1" env_logger = "0.11" freedesktop_entry_parser = "1.3" -flate2 = "1.0" fs_extra = { git = "https://github.com/pop-os/fs_extra.git" } gio = { version = "0.20", optional = true } glib = { version = "0.20", optional = true } @@ -35,7 +34,6 @@ rayon = "1" regex = "1" serde = { version = "1", features = ["serde_derive"] } shlex = { version = "1.3" } -tar = "0.4.41" tokio = { version = "1", features = ["sync"] } trash = { git = "https://github.com/jackpot51/trash-rs.git", branch = "delete-info" } url = "2.5" @@ -43,6 +41,11 @@ walkdir = "2.5.0" wayland-client = { version = "0.31.5", optional = true } xdg = { version = "2.5.2", optional = true } xdg-mime = "0.3" +# Compression +bzip2 = { version = "0.4", optional = true } #TODO: replace with pure Rust crate +flate2 = "1.0" +liblzma = { version = "0.3", optional = true } #TODO: replace with pure Rust crate +tar = "0.4.41" # Internationalization i18n-embed = { version = "0.14", features = [ "fluent-system", @@ -66,7 +69,7 @@ version = "0.2.1" features = ["serde"] [features] -default = ["desktop", "gvfs", "notify", "winit", "wgpu"] +default = ["bzip2", "desktop", "gvfs", "liblzma", "notify", "winit", "wgpu"] desktop = ["libcosmic/desktop", "dep:xdg"] gvfs = ["dep:gio", "dep:glib"] notify = ["dep:notify-rust"] diff --git a/src/menu.rs b/src/menu.rs index dd64abb..3fbd50c 100644 --- a/src/menu.rs +++ b/src/menu.rs @@ -131,9 +131,18 @@ pub fn context_menu<'a>( children.push(divider::horizontal::light().into()); let supported_archive_types = [ + "application/gzip", "application/x-compressed-tar", "application/x-tar", "application/zip", + #[cfg(feature = "bzip2")] + "application/x-bzip", + #[cfg(feature = "bzip2")] + "application/x-bzip-compressed-tar", + #[cfg(feature = "liblzma")] + "application/x-xz", + #[cfg(feature = "liblzma")] + "application/x-xz-compressed-tar", ] .iter() .filter_map(|mime_type| mime_type.parse::().ok()) diff --git a/src/operation.rs b/src/operation.rs index 15dabcc..5c6a04f 100644 --- a/src/operation.rs +++ b/src/operation.rs @@ -635,22 +635,26 @@ impl Operation { if let Some(file_stem) = path.file_stem() { let mut new_dir = to.join(file_stem); + // Make sure all extension parts are removed (file_stem may still contain them) + while new_dir.extension().is_some() { + new_dir.set_extension(""); + } if new_dir.exists() { - let mut extensionless_path = path.to_owned(); - extensionless_path.set_extension(""); if let Some(new_dir_parent) = new_dir.parent() { - new_dir = copy_unique_path(&extensionless_path, new_dir_parent); + new_dir = copy_unique_path(&new_dir, new_dir_parent); } } let mime = mime_for_path(&path); match mime.essence_str() { - "application/x-compressed-tar" => fs::File::open(path) - .map(io::BufReader::new) - .map(flate2::read::GzDecoder::new) - .map(tar::Archive::new) - .and_then(|mut archive| archive.unpack(new_dir)) - .map_err(err_str)?, + "application/gzip" | "application/x-compressed-tar" => { + fs::File::open(path) + .map(io::BufReader::new) + .map(flate2::read::GzDecoder::new) + .map(tar::Archive::new) + .and_then(|mut archive| archive.unpack(new_dir)) + .map_err(err_str)? + } "application/x-tar" => fs::File::open(path) .map(io::BufReader::new) .map(tar::Archive::new) @@ -662,6 +666,24 @@ impl Operation { .map_err(err_str)? .and_then(|mut archive| archive.extract(new_dir)) .map_err(err_str)?, + #[cfg(feature = "bzip2")] + "application/x-bzip" | "application/x-bzip-compressed-tar" => { + fs::File::open(path) + .map(io::BufReader::new) + .map(bzip2::read::BzDecoder::new) + .map(tar::Archive::new) + .and_then(|mut archive| archive.unpack(new_dir)) + .map_err(err_str)? + } + #[cfg(feature = "liblzma")] + "application/x-xz" | "application/x-xz-compressed-tar" => { + fs::File::open(path) + .map(io::BufReader::new) + .map(liblzma::read::XzDecoder::new) + .map(tar::Archive::new) + .and_then(|mut archive| archive.unpack(new_dir)) + .map_err(err_str)? + } _ => Err(format!("unsupported mime type {:?}", mime))?, } } From 136b1e6c379f76d39c0aefcb8e55a2a8d8c7ed2d Mon Sep 17 00:00:00 2001 From: Jeremy Soller Date: Tue, 17 Sep 2024 12:55:13 -0600 Subject: [PATCH 07/18] Use copy instead of move when pasting to different device, fixes #337 --- src/app.rs | 32 ++++++++++++++++++++++++++++---- 1 file changed, 28 insertions(+), 4 deletions(-) diff --git a/src/app.rs b/src/app.rs index 24ceed5..a908d61 100644 --- a/src/app.rs +++ b/src/app.rs @@ -1950,10 +1950,34 @@ impl Application for App { }); } ClipboardKind::Cut => { - self.operation(Operation::Move { - paths: contents.paths, - to, - }); + //TODO: determine ability to move on non-Unix systems + let mut can_move = true; + #[cfg(unix)] + { + use std::os::unix::fs::MetadataExt; + //TODO: better error handling, fall back to not moving? + if let Ok(to_meta) = fs::metadata(&to) { + for path in contents.paths.iter() { + if let Ok(meta) = fs::metadata(path) { + if meta.dev() != to_meta.dev() { + can_move = false; + } + } + } + } + } + + if can_move { + self.operation(Operation::Move { + paths: contents.paths, + to, + }); + } else { + self.operation(Operation::Copy { + paths: contents.paths, + to, + }); + } } } } From a40e6cdff41f39e7d2b26ec75e9332ea2db5fa07 Mon Sep 17 00:00:00 2001 From: Jeremy Soller Date: Wed, 18 Sep 2024 08:31:30 -0600 Subject: [PATCH 08/18] Update trash dependency --- Cargo.lock | 6 +++--- Cargo.toml | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e58c1f6..3797c0a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3575,7 +3575,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4979f22fdb869068da03c9f7528f8297c6fd2606bc3a4affe42e6a823fdb8da4" dependencies = [ "cfg-if", - "windows-targets 0.52.6", + "windows-targets 0.48.5", ] [[package]] @@ -5995,8 +5995,8 @@ dependencies = [ [[package]] name = "trash" -version = "5.0.0" -source = "git+https://github.com/jackpot51/trash-rs.git?branch=delete-info#e9fd256298bf9873a794dfe60a2261d1ed41674c" +version = "5.1.1" +source = "git+https://github.com/jackpot51/trash-rs.git?branch=cosmic#483f83908beef9166f30dfe7b57568ab01c4e140" dependencies = [ "chrono", "libc", diff --git a/Cargo.toml b/Cargo.toml index 89dfd41..0fd61ae 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -35,7 +35,7 @@ regex = "1" serde = { version = "1", features = ["serde_derive"] } shlex = { version = "1.3" } tokio = { version = "1", features = ["sync"] } -trash = { git = "https://github.com/jackpot51/trash-rs.git", branch = "delete-info" } +trash = { git = "https://github.com/jackpot51/trash-rs.git", branch = "cosmic" } url = "2.5" walkdir = "2.5.0" wayland-client = { version = "0.31.5", optional = true } From 8668c186072e508bb90286f3f02d70b0ed96e0dc Mon Sep 17 00:00:00 2001 From: Jeremy Soller Date: Wed, 18 Sep 2024 10:24:22 -0600 Subject: [PATCH 09/18] Only show networks in side bar if a mounter is found --- src/app.rs | 28 +++++++++++++++------------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/src/app.rs b/src/app.rs index a908d61..d5541f8 100644 --- a/src/app.rs +++ b/src/app.rs @@ -671,19 +671,21 @@ impl App { .divider_above() }); - nav_model = nav_model.insert(|b| { - b.text(fl!("networks")) - .icon(widget::icon::icon( - widget::icon::from_name("network-workgroup-symbolic") - .size(16) - .handle(), - )) - .data(Location::Network( - "network:///".to_string(), - fl!("networks"), - )) - .divider_above() - }); + if !self.mounters.is_empty() { + nav_model = nav_model.insert(|b| { + b.text(fl!("networks")) + .icon(widget::icon::icon( + widget::icon::from_name("network-workgroup-symbolic") + .size(16) + .handle(), + )) + .data(Location::Network( + "network:///".to_string(), + fl!("networks"), + )) + .divider_above() + }); + } // Collect all mounter items let mut nav_items = Vec::new(); From a6bacef6043cd0e08a5f47ac4bb19f36f7a1985a Mon Sep 17 00:00:00 2001 From: lucamosca1 Date: Fri, 20 Sep 2024 16:30:43 +0200 Subject: [PATCH 10/18] Update cosmic_files.ftl ITA --- i18n/it/cosmic_files.ftl | 1 + 1 file changed, 1 insertion(+) diff --git a/i18n/it/cosmic_files.ftl b/i18n/it/cosmic_files.ftl index e65ad10..3dbcd4f 100644 --- a/i18n/it/cosmic_files.ftl +++ b/i18n/it/cosmic_files.ftl @@ -9,6 +9,7 @@ notification-in-progress = Operazioni sui file in corso ... trash = Cestino recents = Recenti undo = Annulla ultima operazione +today = Oggi # List view name = Nome From 00e5c15b95bc75be7a164974af152bcfca8e8562 Mon Sep 17 00:00:00 2001 From: Jeremy Soller Date: Fri, 20 Sep 2024 08:50:41 -0600 Subject: [PATCH 11/18] Fix operations not showing up in history --- src/app.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/app.rs b/src/app.rs index d5541f8..34c6a0a 100644 --- a/src/app.rs +++ b/src/app.rs @@ -2002,9 +2002,8 @@ impl Application for App { .map(cosmic::app::Message::App), ); } - - self.complete_operations.insert(id, op); } + self.complete_operations.insert(id, op); } // Potentially show a notification commands.push(self.update_notification()); From 2e9bcfec53648401b4936e13357050f8d73f68c2 Mon Sep 17 00:00:00 2001 From: Jeremy Soller Date: Fri, 20 Sep 2024 09:00:59 -0600 Subject: [PATCH 12/18] Support drag into the breadcrumbs, fixes #177 --- src/tab.rs | 126 ++++++++++++++++++++++++++++------------------------- 1 file changed, 67 insertions(+), 59 deletions(-) diff --git a/src/tab.rs b/src/tab.rs index f25e7e2..4ef36f6 100644 --- a/src/tab.rs +++ b/src/tab.rs @@ -2323,6 +2323,58 @@ impl Tab { Some(items) } + fn dnd_dest<'a>( + &self, + location: &Location, + element: impl Into>, + ) -> Element<'a, Message> { + let location1 = location.clone(); + let location2 = location.clone(); + let location3 = location.clone(); + let is_dnd_hovered = self.dnd_hovered.as_ref().map(|(l, _)| l) == Some(&location); + widget::container( + DndDestination::for_data::(element, move |data, action| { + if let Some(mut data) = data { + if action == DndAction::Copy { + Message::Drop(Some((location1.clone(), data))) + } else if action == DndAction::Move { + data.kind = ClipboardKind::Cut; + Message::Drop(Some((location1.clone(), data))) + } else { + log::warn!("unsupported action: {:?}", action); + Message::Drop(None) + } + } else { + Message::Drop(None) + } + }) + .on_enter(move |_, _, _| Message::DndEnter(location2.clone())) + .on_leave(move || Message::DndLeave(location3.clone())), + ) + .style(if is_dnd_hovered { + theme::Container::custom(|t| { + let mut a = cosmic::iced_style::container::StyleSheet::appearance( + t, + &theme::Container::default(), + ); + let t = t.cosmic(); + // todo use theme drop target color + let mut bg = t.accent_color(); + bg.alpha = 0.2; + a.background = Some(Color::from(bg).into()); + a.border = Border { + color: t.accent_color().into(), + width: 1.0, + radius: t.radius_s().into(), + }; + a + }) + } else { + theme::Container::default() + }) + .into() + } + pub fn location_view(&self) -> Element { //TODO: responsiveness is done in a hacky way, potentially move this to a custom widget? fn text_width<'a>( @@ -2548,17 +2600,19 @@ impl Tab { w += name_width; } + let location = match &self.location { + Location::Path(_) => Location::Path(ancestor.to_path_buf()), + Location::Search(_, term) => { + Location::Search(ancestor.to_path_buf(), term.clone()) + } + other => other.clone(), + }; + let mut mouse_area = crate::mouse_area::MouseArea::new( widget::button(row) .padding(space_xxxs) .style(theme::Button::Link) - .on_press(Message::Location(match &self.location { - Location::Path(_) => Location::Path(ancestor.to_path_buf()), - Location::Search(_, term) => { - Location::Search(ancestor.to_path_buf(), term.clone()) - } - other => other.clone(), - })), + .on_press(Message::Location(location.clone())), ); if self.location_context_menu_index.is_some() { @@ -2578,7 +2632,7 @@ impl Tab { mouse_area }; - children.push(mouse_area.into()); + children.push(self.dnd_dest(&location, mouse_area)); if found_home || overflow { break; @@ -2811,58 +2865,12 @@ impl Tab { } } - let column: Element = if item.metadata.is_dir() - && item.location_opt.is_some() - { - let tab_location = item.location_opt.clone().unwrap(); - let tab_location_enter = tab_location.clone(); - let tab_location_leave = tab_location.clone(); - let is_dnd_hovered = - self.dnd_hovered.as_ref().map(|(l, _)| l) == Some(&tab_location); - cosmic::widget::container( - DndDestination::for_data::(column, move |data, action| { - if let Some(mut data) = data { - if action == DndAction::Copy { - Message::Drop(Some((tab_location.clone(), data))) - } else if action == DndAction::Move { - data.kind = ClipboardKind::Cut; - Message::Drop(Some((tab_location.clone(), data))) - } else { - log::warn!("unsupported action: {:?}", action); - Message::Drop(None) - } - } else { - Message::Drop(None) - } - }) - .on_enter(move |_, _, _| Message::DndEnter(tab_location_enter.clone())) - .on_leave(move || Message::DndLeave(tab_location_leave.clone())), - ) - .style(if is_dnd_hovered { - theme::Container::custom(|t| { - let mut a = cosmic::iced_style::container::StyleSheet::appearance( - t, - &theme::Container::default(), - ); - let t = t.cosmic(); - // todo use theme drop target color - let mut bg = t.accent_color(); - bg.alpha = 0.2; - a.background = Some(Color::from(bg).into()); - a.border = Border { - color: t.accent_color().into(), - width: 1.0, - radius: t.radius_s().into(), - }; - a - }) + let column: Element = + if item.metadata.is_dir() && item.location_opt.is_some() { + self.dnd_dest(&item.location_opt.clone().unwrap(), column) } else { - theme::Container::default() - }) - .into() - } else { - column.into() - }; + column.into() + }; if item.selected { dnd_items.push((i, (row, col), item)); From c202c8cffc3bd64b5cf3fdc2741f5bead0f656d0 Mon Sep 17 00:00:00 2001 From: Jeremy Soller Date: Fri, 20 Sep 2024 09:09:39 -0600 Subject: [PATCH 13/18] Clear selection when right clicking empty space, fixes #361 --- src/tab.rs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/tab.rs b/src/tab.rs index 4ef36f6..18b8bba 100644 --- a/src/tab.rs +++ b/src/tab.rs @@ -1230,6 +1230,7 @@ pub struct Tab { cached_selected: RefCell>, clicked: Option, selected_clicked: bool, + last_right_click: Option, } fn folder_name>(path: P) -> (String, bool) { @@ -1276,6 +1277,7 @@ impl Tab { clicked: None, dnd_hovered: None, selected_clicked: false, + last_right_click: None, } } @@ -1766,6 +1768,14 @@ impl Tab { Message::ContextMenu(point_opt) => { if point_opt.is_none() || !mod_shift { self.context_menu = point_opt; + //TODO: hack for clearing selecting when right clicking empty space + if self.context_menu.is_some() && self.last_right_click.take().is_none() { + if let Some(ref mut items) = self.items_opt { + for item in items.iter_mut() { + item.selected = false; + } + } + } } } Message::LocationContextMenuPoint(point_opt) => { @@ -2040,6 +2050,8 @@ impl Tab { } } } + //TODO: hack for clearing selecting when right clicking empty space + self.last_right_click = click_i_opt; } Message::MiddleClick(click_i) => { self.update(Message::Click(Some(click_i)), modifiers); From 5dff3418adfaad536d4a65976f4ca04df2284619 Mon Sep 17 00:00:00 2001 From: Jeremy Soller Date: Fri, 20 Sep 2024 09:24:03 -0600 Subject: [PATCH 14/18] Update dependencies --- Cargo.lock | 107 ++++++++++++++++++++++++++-------------------------- src/menu.rs | 2 +- src/tab.rs | 43 +++++++++++---------- 3 files changed, 77 insertions(+), 75 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 3797c0a..b4ebba4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -254,9 +254,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.88" +version = "1.0.89" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e1496f8fb1fbf272686b8d37f523dab3e4a7443300055e74cdaa449f3114356" +checksum = "86fdf8605db99b54d3cd748a44c6d04df638eb5dafb219b135d0149bd0db01f6" [[package]] name = "apply" @@ -290,9 +290,9 @@ checksum = "69f7f8c3906b62b754cd5326047894316021dcfe5a194c8ea52bdd94934a3457" [[package]] name = "arrayref" -version = "0.3.8" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d151e35f61089500b617991b791fc8bfd237ae50cd5950803758a179b41e67a" +checksum = "76a2e8124351fda1ef8aaaa3bbd7ebbcb486bbcd4225aca0aa0d84bb2db8fecb" [[package]] name = "arrayvec" @@ -519,9 +519,9 @@ dependencies = [ [[package]] name = "async-process" -version = "2.2.4" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8a07789659a4d385b79b18b9127fc27e1a59e1e89117c78c5ea3b806f016374" +checksum = "63255f1dc2381611000436537bbedfe83183faa303a5a0edaf191edef06526bb" dependencies = [ "async-channel", "async-io 2.3.4", @@ -534,7 +534,6 @@ dependencies = [ "futures-lite 2.3.0", "rustix 0.38.37", "tracing", - "windows-sys 0.59.0", ] [[package]] @@ -835,9 +834,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" -version = "1.7.1" +version = "1.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8318a53db07bb3f8dca91a600466bdb3f2eaadeedfdbcf02e1accbad9271ba50" +checksum = "428d9aa8fbc0670b7b8d6030a7fadd0f86151cae55e4dbbece15f3780a3dfaf3" [[package]] name = "bzip2" @@ -914,9 +913,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.1.18" +version = "1.1.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b62ac837cdb5cb22e10a256099b4fc502b1dfe560cb282963a974d7abd80e476" +checksum = "07b1695e2c7e8fc85310cde85aeaab7e3097f593c91d209d3f9df76c928100f0" dependencies = [ "jobserver", "libc", @@ -1213,7 +1212,7 @@ dependencies = [ [[package]] name = "cosmic-config" version = "0.1.0" -source = "git+https://github.com/pop-os/libcosmic.git#f942977703404e43ade60080f14d61e7aa078733" +source = "git+https://github.com/pop-os/libcosmic.git#a962865230f3b9ecba40c0c09e9c279e832c9f10" dependencies = [ "atomicwrites", "cosmic-config-derive", @@ -1232,7 +1231,7 @@ dependencies = [ [[package]] name = "cosmic-config-derive" version = "0.1.0" -source = "git+https://github.com/pop-os/libcosmic.git#f942977703404e43ade60080f14d61e7aa078733" +source = "git+https://github.com/pop-os/libcosmic.git#a962865230f3b9ecba40c0c09e9c279e832c9f10" dependencies = [ "quote", "syn 1.0.109", @@ -1341,7 +1340,7 @@ dependencies = [ [[package]] name = "cosmic-theme" version = "0.1.0" -source = "git+https://github.com/pop-os/libcosmic.git#f942977703404e43ade60080f14d61e7aa078733" +source = "git+https://github.com/pop-os/libcosmic.git#a962865230f3b9ecba40c0c09e9c279e832c9f10" dependencies = [ "almost", "cosmic-config", @@ -2075,7 +2074,7 @@ checksum = "b0299020c3ef3f60f526a4f64ab4a3d4ce116b1acbf24cdd22da0068e5d81dc3" dependencies = [ "fontconfig-parser", "log", - "memmap2 0.9.4", + "memmap2 0.9.5", "slotmap", "tinyvec", "ttf-parser 0.20.0", @@ -2760,9 +2759,9 @@ dependencies = [ [[package]] name = "iana-time-zone" -version = "0.1.60" +version = "0.1.61" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7ffbb5a1b541ea2561f8c41c087286cc091e21e556a4f09a8f6cbf17b69b141" +checksum = "235e081f3925a06703c2d0117ea8b91f042756fd6e7a6e5d901e8ca1a996b220" dependencies = [ "android_system_properties", "core-foundation-sys", @@ -2784,7 +2783,7 @@ dependencies = [ [[package]] name = "iced" version = "0.12.0" -source = "git+https://github.com/pop-os/libcosmic.git#f942977703404e43ade60080f14d61e7aa078733" +source = "git+https://github.com/pop-os/libcosmic.git#a962865230f3b9ecba40c0c09e9c279e832c9f10" dependencies = [ "dnd", "iced_accessibility", @@ -2803,7 +2802,7 @@ dependencies = [ [[package]] name = "iced_accessibility" version = "0.1.0" -source = "git+https://github.com/pop-os/libcosmic.git#f942977703404e43ade60080f14d61e7aa078733" +source = "git+https://github.com/pop-os/libcosmic.git#a962865230f3b9ecba40c0c09e9c279e832c9f10" dependencies = [ "accesskit", "accesskit_unix", @@ -2813,7 +2812,7 @@ dependencies = [ [[package]] name = "iced_core" version = "0.12.0" -source = "git+https://github.com/pop-os/libcosmic.git#f942977703404e43ade60080f14d61e7aa078733" +source = "git+https://github.com/pop-os/libcosmic.git#a962865230f3b9ecba40c0c09e9c279e832c9f10" dependencies = [ "bitflags 2.6.0", "dnd", @@ -2835,7 +2834,7 @@ dependencies = [ [[package]] name = "iced_futures" version = "0.12.0" -source = "git+https://github.com/pop-os/libcosmic.git#f942977703404e43ade60080f14d61e7aa078733" +source = "git+https://github.com/pop-os/libcosmic.git#a962865230f3b9ecba40c0c09e9c279e832c9f10" dependencies = [ "futures", "iced_core", @@ -2848,7 +2847,7 @@ dependencies = [ [[package]] name = "iced_graphics" version = "0.12.0" -source = "git+https://github.com/pop-os/libcosmic.git#f942977703404e43ade60080f14d61e7aa078733" +source = "git+https://github.com/pop-os/libcosmic.git#a962865230f3b9ecba40c0c09e9c279e832c9f10" dependencies = [ "bitflags 2.6.0", "bytemuck", @@ -2872,7 +2871,7 @@ dependencies = [ [[package]] name = "iced_renderer" version = "0.12.0" -source = "git+https://github.com/pop-os/libcosmic.git#f942977703404e43ade60080f14d61e7aa078733" +source = "git+https://github.com/pop-os/libcosmic.git#a962865230f3b9ecba40c0c09e9c279e832c9f10" dependencies = [ "iced_graphics", "iced_tiny_skia", @@ -2884,7 +2883,7 @@ dependencies = [ [[package]] name = "iced_runtime" version = "0.12.0" -source = "git+https://github.com/pop-os/libcosmic.git#f942977703404e43ade60080f14d61e7aa078733" +source = "git+https://github.com/pop-os/libcosmic.git#a962865230f3b9ecba40c0c09e9c279e832c9f10" dependencies = [ "dnd", "iced_accessibility", @@ -2898,7 +2897,7 @@ dependencies = [ [[package]] name = "iced_sctk" version = "0.1.0" -source = "git+https://github.com/pop-os/libcosmic.git#f942977703404e43ade60080f14d61e7aa078733" +source = "git+https://github.com/pop-os/libcosmic.git#a962865230f3b9ecba40c0c09e9c279e832c9f10" dependencies = [ "enum-repr", "float-cmp", @@ -2925,7 +2924,7 @@ dependencies = [ [[package]] name = "iced_style" version = "0.12.0" -source = "git+https://github.com/pop-os/libcosmic.git#f942977703404e43ade60080f14d61e7aa078733" +source = "git+https://github.com/pop-os/libcosmic.git#a962865230f3b9ecba40c0c09e9c279e832c9f10" dependencies = [ "iced_core", "once_cell", @@ -2935,7 +2934,7 @@ dependencies = [ [[package]] name = "iced_tiny_skia" version = "0.12.0" -source = "git+https://github.com/pop-os/libcosmic.git#f942977703404e43ade60080f14d61e7aa078733" +source = "git+https://github.com/pop-os/libcosmic.git#a962865230f3b9ecba40c0c09e9c279e832c9f10" dependencies = [ "bytemuck", "cosmic-text", @@ -2952,7 +2951,7 @@ dependencies = [ [[package]] name = "iced_wgpu" version = "0.12.0" -source = "git+https://github.com/pop-os/libcosmic.git#f942977703404e43ade60080f14d61e7aa078733" +source = "git+https://github.com/pop-os/libcosmic.git#a962865230f3b9ecba40c0c09e9c279e832c9f10" dependencies = [ "as-raw-xcb-connection", "bitflags 2.6.0", @@ -2981,7 +2980,7 @@ dependencies = [ [[package]] name = "iced_widget" version = "0.12.0" -source = "git+https://github.com/pop-os/libcosmic.git#f942977703404e43ade60080f14d61e7aa078733" +source = "git+https://github.com/pop-os/libcosmic.git#a962865230f3b9ecba40c0c09e9c279e832c9f10" dependencies = [ "dnd", "iced_accessibility", @@ -2999,7 +2998,7 @@ dependencies = [ [[package]] name = "iced_winit" version = "0.12.0" -source = "git+https://github.com/pop-os/libcosmic.git#f942977703404e43ade60080f14d61e7aa078733" +source = "git+https://github.com/pop-os/libcosmic.git#a962865230f3b9ecba40c0c09e9c279e832c9f10" dependencies = [ "dnd", "iced_accessibility", @@ -3515,7 +3514,7 @@ checksum = "d8adc4bb1803a324070e64a98ae98f38934d91957a99cfb3a43dcbc01bc56439" [[package]] name = "libcosmic" version = "0.1.0" -source = "git+https://github.com/pop-os/libcosmic.git#f942977703404e43ade60080f14d61e7aa078733" +source = "git+https://github.com/pop-os/libcosmic.git#a962865230f3b9ecba40c0c09e9c279e832c9f10" dependencies = [ "apply", "ashpd 0.9.1", @@ -3575,7 +3574,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4979f22fdb869068da03c9f7528f8297c6fd2606bc3a4affe42e6a823fdb8da4" dependencies = [ "cfg-if", - "windows-targets 0.48.5", + "windows-targets 0.52.6", ] [[package]] @@ -3804,9 +3803,9 @@ dependencies = [ [[package]] name = "memmap2" -version = "0.9.4" +version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe751422e4a8caa417e13c3ea66452215d7d63e19e604f4980461212f3ae1322" +checksum = "fd3f7eed9d3848f8b98834af67102b720745c4ec028fcd0aa0239277e7de374f" dependencies = [ "libc", ] @@ -4655,7 +4654,7 @@ version = "3.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ecf48c7ca261d60b74ab1a7b20da18bede46776b2e55535cb958eb595c5fa7b" dependencies = [ - "toml_edit 0.22.20", + "toml_edit 0.22.21", ] [[package]] @@ -5167,7 +5166,7 @@ checksum = "70b31447ca297092c5a9916fc3b955203157b37c19ca8edde4f52e9843e602c7" dependencies = [ "ab_glyph", "log", - "memmap2 0.9.4", + "memmap2 0.9.5", "smithay-client-toolkit 0.18.1", "tiny-skia", ] @@ -5353,7 +5352,7 @@ dependencies = [ "cursor-icon", "libc", "log", - "memmap2 0.9.4", + "memmap2 0.9.5", "rustix 0.38.37", "thiserror", "wayland-backend", @@ -5379,7 +5378,7 @@ dependencies = [ "cursor-icon", "libc", "log", - "memmap2 0.9.4", + "memmap2 0.9.5", "pkg-config", "rustix 0.38.37", "thiserror", @@ -5449,7 +5448,7 @@ dependencies = [ "foreign-types", "js-sys", "log", - "memmap2 0.9.4", + "memmap2 0.9.5", "objc", "raw-window-handle", "redox_syscall 0.4.1", @@ -5897,7 +5896,7 @@ dependencies = [ "serde", "serde_spanned", "toml_datetime", - "toml_edit 0.22.20", + "toml_edit 0.22.21", ] [[package]] @@ -5922,9 +5921,9 @@ dependencies = [ [[package]] name = "toml_edit" -version = "0.22.20" +version = "0.22.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "583c44c02ad26b0c3f3066fe629275e50627026c51ac2e595cca4c230ce1ce1d" +checksum = "3b072cee73c449a636ffd6f32bd8de3a9f7119139aff882f44943ce2986dc5cf" dependencies = [ "indexmap", "serde", @@ -6125,9 +6124,9 @@ checksum = "3b09c83c3c29d37506a3e260c08c03743a6bb66a9cd432c6934ab501a190571f" [[package]] name = "unicode-normalization" -version = "0.1.23" +version = "0.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a56d1686db2308d901306f92a263857ef59ea39678a5458e7cb17f01415101f5" +checksum = "5033c97c4262335cded6d6fc3e5c18ab755e1a3dc96376350f3d8e9f009ad956" dependencies = [ "tinyvec", ] @@ -6140,15 +6139,15 @@ checksum = "52ea75f83c0137a9b98608359a5f1af8144876eb67bcb1ce837368e906a9f524" [[package]] name = "unicode-script" -version = "0.5.6" +version = "0.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad8d71f5726e5f285a935e9fe8edfd53f0491eb6e9a5774097fdabee7cd8c9cd" +checksum = "9fb421b350c9aff471779e262955939f565ec18b86c15364e6bdf0d662ca7c1f" [[package]] name = "unicode-segmentation" -version = "1.11.0" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4c87d22b6e3f4a18d4d40ef354e97c90fcb14dd91d7dc0aa9d8a1172ebf7202" +checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" [[package]] name = "unicode-vo" @@ -6158,15 +6157,15 @@ checksum = "b1d386ff53b415b7fe27b50bb44679e2cc4660272694b7b6f3326d8480823a94" [[package]] name = "unicode-width" -version = "0.1.13" +version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0336d538f7abc86d282a4189614dfaa90810dfc2c6f6427eaf88e16311dd225d" +checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" [[package]] name = "unicode-xid" -version = "0.2.5" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "229730647fbc343e3a80e463c1db7f78f3855d3f3739bee0dda773c9a037c90a" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" [[package]] name = "unix_permissions_ext" @@ -7098,7 +7097,7 @@ dependencies = [ "js-sys", "libc", "log", - "memmap2 0.9.4", + "memmap2 0.9.5", "ndk", "ndk-sys", "objc2 0.4.1", @@ -7368,7 +7367,7 @@ dependencies = [ "async-fs 2.1.2", "async-io 2.3.4", "async-lock 3.4.0", - "async-process 2.2.4", + "async-process 2.3.0", "async-recursion", "async-task", "async-trait", diff --git a/src/menu.rs b/src/menu.rs index 3fbd50c..d02976b 100644 --- a/src/menu.rs +++ b/src/menu.rs @@ -22,7 +22,7 @@ use crate::{ macro_rules! menu_button { ($($x:expr),+ $(,)?) => ( - button( + button::custom( Row::with_children( vec![$(Element::from($x)),+] ) diff --git a/src/tab.rs b/src/tab.rs index 18b8bba..7f340ad 100644 --- a/src/tab.rs +++ b/src/tab.rs @@ -1003,7 +1003,7 @@ impl Item { if let Some(Location::Path(path)) = &self.location_opt { for app in self.open_with.iter() { column = column.push( - widget::button( + widget::button::custom( widget::row::with_children(vec![ widget::icon(app.icon.clone()).into(), if app.is_default { @@ -2440,7 +2440,7 @@ impl Tab { let mut w = 0.0; let mut prev_button = - widget::button(widget::icon::from_name("go-previous-symbolic").size(16)) + widget::button::custom(widget::icon::from_name("go-previous-symbolic").size(16)) .padding(space_xxs) .style(theme::Button::Icon); if self.history_i > 0 && !self.history.is_empty() { @@ -2449,9 +2449,10 @@ impl Tab { row = row.push(prev_button); w += 16.0 + 2.0 * space_xxs as f32; - let mut next_button = widget::button(widget::icon::from_name("go-next-symbolic").size(16)) - .padding(space_xxs) - .style(theme::Button::Icon); + let mut next_button = + widget::button::custom(widget::icon::from_name("go-next-symbolic").size(16)) + .padding(space_xxs) + .style(theme::Button::Icon); if self.history_i + 1 < self.history.len() { next_button = next_button.on_press(Message::GoNext); } @@ -2507,10 +2508,12 @@ impl Tab { match location { Location::Path(path) => { row = row.push( - widget::button(widget::icon::from_name("window-close-symbolic").size(16)) - .on_press(Message::EditLocation(None)) - .padding(space_xxs) - .style(theme::Button::Icon), + widget::button::custom( + widget::icon::from_name("window-close-symbolic").size(16), + ) + .on_press(Message::EditLocation(None)) + .padding(space_xxs) + .style(theme::Button::Icon), ); row = row.push( widget::text_input("", path.to_string_lossy()) @@ -2544,7 +2547,7 @@ impl Tab { } else if let Location::Path(path) = &self.location { row = row.push( crate::mouse_area::MouseArea::new( - widget::button(widget::icon::from_name("edit-symbolic").size(16)) + widget::button::custom(widget::icon::from_name("edit-symbolic").size(16)) .padding(space_xxs) .style(theme::Button::Icon) .on_press(Message::EditLocation(Some(self.location.clone()))), @@ -2554,7 +2557,7 @@ impl Tab { w += 16.0 + 2.0 * space_xxs as f32; } else if let Location::Search(_, term) = &self.location { row = row.push( - widget::button( + widget::button::custom( widget::row::with_children(vec![ widget::icon::from_name("system-search-symbolic") .size(16) @@ -2621,7 +2624,7 @@ impl Tab { }; let mut mouse_area = crate::mouse_area::MouseArea::new( - widget::button(row) + widget::button::custom(row) .padding(space_xxxs) .style(theme::Button::Link) .on_press(Message::Location(location.clone())), @@ -2654,7 +2657,7 @@ impl Tab { } Location::Trash => { children.push( - widget::button(widget::text::heading(fl!("trash"))) + widget::button::custom(widget::text::heading(fl!("trash"))) .padding(space_xxxs) .on_press(Message::Location(Location::Trash)) .style(theme::Button::Text) @@ -2663,7 +2666,7 @@ impl Tab { } Location::Recents => { children.push( - widget::button(widget::text::heading(fl!("recents"))) + widget::button::custom(widget::text::heading(fl!("recents"))) .padding(space_xxxs) .on_press(Message::Location(Location::Recents)) .style(theme::Button::Text) @@ -2672,7 +2675,7 @@ impl Tab { } Location::Network(uri, display_name) => { children.push( - widget::button(widget::text::heading(display_name)) + widget::button::custom(widget::text::heading(display_name)) .padding(space_xxxs) .on_press(Message::Location(Location::Network( uri.clone(), @@ -2843,14 +2846,14 @@ impl Tab { //TODO: one focus group per grid item (needs custom widget) let buttons = vec![ - widget::button( + widget::button::custom( widget::icon::icon(item.icon_handle_grid.clone()) .content_fit(ContentFit::Contain) .size(icon_sizes.grid()), ) .padding(space_xxxs) .style(button_style(item.selected, false, false, false)), - widget::button(widget::text::body(&item.display_name)) + widget::button::custom(widget::text::body(&item.display_name)) .id(item.button_id.clone()) .padding([0, space_xxxs]) .style(button_style( @@ -2983,7 +2986,7 @@ impl Tab { }; if *row == r && *col == c { let buttons = vec![ - widget::button( + widget::button::custom( widget::icon::icon(item.icon_handle_grid.clone()) .content_fit(ContentFit::Contain) .size(icon_sizes.grid()), @@ -2996,7 +2999,7 @@ impl Tab { false, false, )), - widget::button(widget::text(item.display_name.clone())) + widget::button::custom(widget::text(item.display_name.clone())) .id(item.button_id.clone()) .on_press(Message::Click(Some(*i))) .padding([0, space_xxxs]) @@ -3211,7 +3214,7 @@ impl Tab { let button = |row| { let mouse_area = crate::mouse_area::MouseArea::new( - widget::button(row) + widget::button::custom(row) .width(Length::Fill) .id(item.button_id.clone()) .padding([0, space_xxs]) From 1d879628f30195ca98017c1a676de9b7dc3fd78f Mon Sep 17 00:00:00 2001 From: Jeremy Soller Date: Fri, 20 Sep 2024 09:45:01 -0600 Subject: [PATCH 15/18] Fix missing thumbnails in condensed list view --- src/tab.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/tab.rs b/src/tab.rs index 7f340ad..938069d 100644 --- a/src/tab.rs +++ b/src/tab.rs @@ -2107,7 +2107,8 @@ impl Tab { rgba.as_raw().clone(), ); item.icon_handle_grid = handle.clone(); - item.icon_handle_list = handle; + item.icon_handle_list = handle.clone(); + item.icon_handle_list_condensed = handle; } item.thumbnail_opt = Some(thumbnail); break; From 55eb21911eb75d51902b281a6fe4d7a43a749dd9 Mon Sep 17 00:00:00 2001 From: Jeremy Soller Date: Fri, 20 Sep 2024 11:41:15 -0600 Subject: [PATCH 16/18] Place context drawer on the side of content, part of #109 --- Cargo.lock | 42 +++++++++++++++++++++--------------------- src/app.rs | 19 ++++++++++--------- src/dialog.rs | 1 + 3 files changed, 32 insertions(+), 30 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b4ebba4..83a6667 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1212,7 +1212,7 @@ dependencies = [ [[package]] name = "cosmic-config" version = "0.1.0" -source = "git+https://github.com/pop-os/libcosmic.git#a962865230f3b9ecba40c0c09e9c279e832c9f10" +source = "git+https://github.com/pop-os/libcosmic.git#ddb678ca6966dfdf10911feac5e5ac02d3b2b97e" dependencies = [ "atomicwrites", "cosmic-config-derive", @@ -1231,7 +1231,7 @@ dependencies = [ [[package]] name = "cosmic-config-derive" version = "0.1.0" -source = "git+https://github.com/pop-os/libcosmic.git#a962865230f3b9ecba40c0c09e9c279e832c9f10" +source = "git+https://github.com/pop-os/libcosmic.git#ddb678ca6966dfdf10911feac5e5ac02d3b2b97e" dependencies = [ "quote", "syn 1.0.109", @@ -1340,7 +1340,7 @@ dependencies = [ [[package]] name = "cosmic-theme" version = "0.1.0" -source = "git+https://github.com/pop-os/libcosmic.git#a962865230f3b9ecba40c0c09e9c279e832c9f10" +source = "git+https://github.com/pop-os/libcosmic.git#ddb678ca6966dfdf10911feac5e5ac02d3b2b97e" dependencies = [ "almost", "cosmic-config", @@ -2783,7 +2783,7 @@ dependencies = [ [[package]] name = "iced" version = "0.12.0" -source = "git+https://github.com/pop-os/libcosmic.git#a962865230f3b9ecba40c0c09e9c279e832c9f10" +source = "git+https://github.com/pop-os/libcosmic.git#ddb678ca6966dfdf10911feac5e5ac02d3b2b97e" dependencies = [ "dnd", "iced_accessibility", @@ -2802,7 +2802,7 @@ dependencies = [ [[package]] name = "iced_accessibility" version = "0.1.0" -source = "git+https://github.com/pop-os/libcosmic.git#a962865230f3b9ecba40c0c09e9c279e832c9f10" +source = "git+https://github.com/pop-os/libcosmic.git#ddb678ca6966dfdf10911feac5e5ac02d3b2b97e" dependencies = [ "accesskit", "accesskit_unix", @@ -2812,7 +2812,7 @@ dependencies = [ [[package]] name = "iced_core" version = "0.12.0" -source = "git+https://github.com/pop-os/libcosmic.git#a962865230f3b9ecba40c0c09e9c279e832c9f10" +source = "git+https://github.com/pop-os/libcosmic.git#ddb678ca6966dfdf10911feac5e5ac02d3b2b97e" dependencies = [ "bitflags 2.6.0", "dnd", @@ -2834,7 +2834,7 @@ dependencies = [ [[package]] name = "iced_futures" version = "0.12.0" -source = "git+https://github.com/pop-os/libcosmic.git#a962865230f3b9ecba40c0c09e9c279e832c9f10" +source = "git+https://github.com/pop-os/libcosmic.git#ddb678ca6966dfdf10911feac5e5ac02d3b2b97e" dependencies = [ "futures", "iced_core", @@ -2847,7 +2847,7 @@ dependencies = [ [[package]] name = "iced_graphics" version = "0.12.0" -source = "git+https://github.com/pop-os/libcosmic.git#a962865230f3b9ecba40c0c09e9c279e832c9f10" +source = "git+https://github.com/pop-os/libcosmic.git#ddb678ca6966dfdf10911feac5e5ac02d3b2b97e" dependencies = [ "bitflags 2.6.0", "bytemuck", @@ -2871,7 +2871,7 @@ dependencies = [ [[package]] name = "iced_renderer" version = "0.12.0" -source = "git+https://github.com/pop-os/libcosmic.git#a962865230f3b9ecba40c0c09e9c279e832c9f10" +source = "git+https://github.com/pop-os/libcosmic.git#ddb678ca6966dfdf10911feac5e5ac02d3b2b97e" dependencies = [ "iced_graphics", "iced_tiny_skia", @@ -2883,7 +2883,7 @@ dependencies = [ [[package]] name = "iced_runtime" version = "0.12.0" -source = "git+https://github.com/pop-os/libcosmic.git#a962865230f3b9ecba40c0c09e9c279e832c9f10" +source = "git+https://github.com/pop-os/libcosmic.git#ddb678ca6966dfdf10911feac5e5ac02d3b2b97e" dependencies = [ "dnd", "iced_accessibility", @@ -2897,7 +2897,7 @@ dependencies = [ [[package]] name = "iced_sctk" version = "0.1.0" -source = "git+https://github.com/pop-os/libcosmic.git#a962865230f3b9ecba40c0c09e9c279e832c9f10" +source = "git+https://github.com/pop-os/libcosmic.git#ddb678ca6966dfdf10911feac5e5ac02d3b2b97e" dependencies = [ "enum-repr", "float-cmp", @@ -2924,7 +2924,7 @@ dependencies = [ [[package]] name = "iced_style" version = "0.12.0" -source = "git+https://github.com/pop-os/libcosmic.git#a962865230f3b9ecba40c0c09e9c279e832c9f10" +source = "git+https://github.com/pop-os/libcosmic.git#ddb678ca6966dfdf10911feac5e5ac02d3b2b97e" dependencies = [ "iced_core", "once_cell", @@ -2934,7 +2934,7 @@ dependencies = [ [[package]] name = "iced_tiny_skia" version = "0.12.0" -source = "git+https://github.com/pop-os/libcosmic.git#a962865230f3b9ecba40c0c09e9c279e832c9f10" +source = "git+https://github.com/pop-os/libcosmic.git#ddb678ca6966dfdf10911feac5e5ac02d3b2b97e" dependencies = [ "bytemuck", "cosmic-text", @@ -2951,7 +2951,7 @@ dependencies = [ [[package]] name = "iced_wgpu" version = "0.12.0" -source = "git+https://github.com/pop-os/libcosmic.git#a962865230f3b9ecba40c0c09e9c279e832c9f10" +source = "git+https://github.com/pop-os/libcosmic.git#ddb678ca6966dfdf10911feac5e5ac02d3b2b97e" dependencies = [ "as-raw-xcb-connection", "bitflags 2.6.0", @@ -2980,7 +2980,7 @@ dependencies = [ [[package]] name = "iced_widget" version = "0.12.0" -source = "git+https://github.com/pop-os/libcosmic.git#a962865230f3b9ecba40c0c09e9c279e832c9f10" +source = "git+https://github.com/pop-os/libcosmic.git#ddb678ca6966dfdf10911feac5e5ac02d3b2b97e" dependencies = [ "dnd", "iced_accessibility", @@ -2998,7 +2998,7 @@ dependencies = [ [[package]] name = "iced_winit" version = "0.12.0" -source = "git+https://github.com/pop-os/libcosmic.git#a962865230f3b9ecba40c0c09e9c279e832c9f10" +source = "git+https://github.com/pop-os/libcosmic.git#ddb678ca6966dfdf10911feac5e5ac02d3b2b97e" dependencies = [ "dnd", "iced_accessibility", @@ -3514,7 +3514,7 @@ checksum = "d8adc4bb1803a324070e64a98ae98f38934d91957a99cfb3a43dcbc01bc56439" [[package]] name = "libcosmic" version = "0.1.0" -source = "git+https://github.com/pop-os/libcosmic.git#a962865230f3b9ecba40c0c09e9c279e832c9f10" +source = "git+https://github.com/pop-os/libcosmic.git#ddb678ca6966dfdf10911feac5e5ac02d3b2b97e" dependencies = [ "apply", "ashpd 0.9.1", @@ -4722,9 +4722,9 @@ dependencies = [ [[package]] name = "quick-xml" -version = "0.36.1" +version = "0.36.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96a05e2e8efddfa51a84ca47cec303fac86c8541b686d37cac5efc0e094417bc" +checksum = "f7649a7b4df05aed9ea7ec6f628c67c9953a43869b8bc50929569b2999d443fe" dependencies = [ "memchr", "serde", @@ -4833,7 +4833,7 @@ dependencies = [ "dirs 5.0.1", "infer", "mime_guess", - "quick-xml 0.36.1", + "quick-xml 0.36.2", "serde", "thiserror", "url", @@ -6540,7 +6540,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "597f2001b2e5fc1121e3d5b9791d3e78f05ba6bfa4641053846248e3a13661c3" dependencies = [ "proc-macro2", - "quick-xml 0.36.1", + "quick-xml 0.36.2", "quote", ] diff --git a/src/app.rs b/src/app.rs index 34c6a0a..5c068fb 100644 --- a/src/app.rs +++ b/src/app.rs @@ -1162,6 +1162,7 @@ impl Application for App { /// Creates the application, and optionally emits command on initialize. fn init(mut core: Core, flags: Self::Flags) -> (Self, Command) { + core.window.context_is_overlay = false; match flags.mode { Mode::App => {} Mode::Desktop => { @@ -1368,7 +1369,7 @@ impl Application for App { // Usually, the Escape key (for example) closes menus and panes one by one instead // of closing everything on one press if self.core.window.show_context { - self.core.window.show_context = false; + self.set_show_context(false); return Command::none(); } if self.search_active { @@ -1713,7 +1714,7 @@ impl Application for App { Ok(true) => { log::info!("connected to {:?}", uri); if matches!(self.context_page, ContextPage::NetworkDrive) { - self.core.window.show_context = false; + self.set_show_context(false); } } Ok(false) => { @@ -1882,7 +1883,7 @@ impl Application for App { } // Close Open With context view - self.core.window.show_context = false; + self.set_show_context(false); } Message::OpenInNewTab(entity_opt) => { return Command::batch(self.selected_paths(entity_opt).into_iter().filter_map( @@ -2257,7 +2258,7 @@ impl Application for App { //TODO: move to Command? if let tab::Message::ContextMenu(_point_opt) = tab_message { // Disable side context page - self.core.window.show_context = false; + self.set_show_context(false); } let tab_commands = match self.tab_model.data_mut::(entity) { @@ -2274,7 +2275,7 @@ impl Application for App { tab::Command::AddNetworkDrive => { let context_page = ContextPage::NetworkDrive; self.context_page = context_page; - self.core.window.show_context = true; + self.set_show_context(true); self.set_context_title(context_page.title()); } tab::Command::ChangeLocation(tab_title, tab_path, selection_path) => { @@ -2301,7 +2302,7 @@ impl Application for App { tab::Command::LocationProperties(index) => { self.context_page = ContextPage::Properties(Some(ContextItem::BreadCrumbs(index))); - self.core.window.show_context = true; + self.set_show_context(true); self.set_context_title(self.context_page.title()); } tab::Command::MoveToTrash(paths) => { @@ -2402,10 +2403,10 @@ impl Application for App { Message::ToggleContextPage(context_page) => { //TODO: ensure context menus are closed if self.context_page == context_page { - self.core.window.show_context = !self.core.window.show_context; + self.set_show_context(!self.core.window.show_context); } else { self.context_page = context_page; - self.core.window.show_context = true; + self.set_show_context(true); } self.set_context_title(context_page.title()); } @@ -2625,7 +2626,7 @@ impl Application for App { NavMenuAction::Properties(entity) => { self.context_page = ContextPage::Properties(Some(ContextItem::NavBar(entity))); - self.core.window.show_context = true; + self.set_show_context(true); self.set_context_title(self.context_page.title()); } diff --git a/src/dialog.rs b/src/dialog.rs index 30d203a..a98a182 100644 --- a/src/dialog.rs +++ b/src/dialog.rs @@ -574,6 +574,7 @@ impl Application for App { /// Creates the application, and optionally emits command on initialize. fn init(mut core: Core, flags: Self::Flags) -> (Self, Command) { + core.window.context_is_overlay = false; core.window.show_close = false; core.window.show_maximize = false; core.window.show_minimize = false; From f4fd98cc2337d4d9dadef35258b2286994462985 Mon Sep 17 00:00:00 2001 From: Jeremy Soller Date: Fri, 20 Sep 2024 14:13:04 -0600 Subject: [PATCH 17/18] Implement automatic preview, part of #109 --- src/app.rs | 181 ++++++++++++++++++++++++++++-------------------- src/key_bind.rs | 2 +- src/menu.rs | 8 +-- src/tab.rs | 125 +++++++++++++++------------------ 4 files changed, 167 insertions(+), 149 deletions(-) diff --git a/src/app.rs b/src/app.rs index 5c068fb..5d1049e 100644 --- a/src/app.rs +++ b/src/app.rs @@ -112,7 +112,7 @@ pub enum Action { OpenTerminal, OpenWith, Paste, - Properties, + Preview, Rename, RestoreFromTrash, SearchActivate, @@ -164,7 +164,9 @@ impl Action { Action::OpenTerminal => Message::OpenTerminal(entity_opt), Action::OpenWith => Message::ToggleContextPage(ContextPage::OpenWith), Action::Paste => Message::Paste(entity_opt), - Action::Properties => Message::ToggleContextPage(ContextPage::Properties(None)), + Action::Preview => { + Message::ToggleContextPage(ContextPage::Preview(entity_opt, PreviewKind::Selected)) + } Action::Rename => Message::Rename(entity_opt), Action::RestoreFromTrash => Message::RestoreFromTrash(entity_opt), Action::SearchActivate => Message::SearchActivate, @@ -210,18 +212,29 @@ impl MenuAction for Action { } } -#[derive(Clone, Copy, Debug, Eq, PartialEq)] -pub enum ContextItem { - NavBar(segmented_button::Entity), - TabBar(segmented_button::Entity), - BreadCrumbs(usize), +#[derive(Clone, Debug)] +pub struct PreviewItem(pub tab::Item); + +impl PartialEq for PreviewItem { + fn eq(&self, other: &Self) -> bool { + self.0.location_opt == other.0.location_opt + } +} + +impl Eq for PreviewItem {} + +#[derive(Clone, Debug, Eq, PartialEq)] +pub enum PreviewKind { + Custom(PreviewItem), + Location(Location), + Selected, } #[derive(Copy, Clone, Debug, Eq, PartialEq)] pub enum NavMenuAction { OpenInNewTab(segmented_button::Entity), OpenInNewWindow(segmented_button::Entity), - Properties(segmented_button::Entity), + Preview(segmented_button::Entity), RemoveFromSidebar(segmented_button::Entity), EmptyTrash, } @@ -279,6 +292,7 @@ pub enum Message { PendingComplete(u64), PendingError(u64, String), PendingProgress(u64, f32), + Preview(Entity, PreviewKind, time::Duration), RescanTrash, Rename(Option), ReplaceResult(ReplaceResult), @@ -317,13 +331,13 @@ pub enum Message { None, } -#[derive(Clone, Copy, Debug, Eq, PartialEq)] +#[derive(Clone, Debug, Eq, PartialEq)] pub enum ContextPage { About, EditHistory, NetworkDrive, OpenWith, - Properties(Option), + Preview(Option, PreviewKind), Settings, } @@ -334,7 +348,7 @@ impl ContextPage { Self::EditHistory => fl!("edit-history"), Self::NetworkDrive => fl!("add-network-drive"), Self::OpenWith => fl!("open-with"), - Self::Properties(..) => String::default(), + Self::Preview(..) => String::default(), Self::Settings => fl!("settings"), } } @@ -461,6 +475,7 @@ pub struct App { pending_operations: BTreeMap, complete_operations: BTreeMap, failed_operations: BTreeMap, + preview_opt: Option<(Entity, PreviewKind, time::Instant)>, search_active: bool, search_id: widget::Id, search_input: String, @@ -949,59 +964,42 @@ impl App { widget::settings::view_column(children).into() } - fn properties(&self, entity: Option) -> Element { - match entity { - None => self.tab_properties(self.tab_model.active()), - Some(ContextItem::TabBar(entity)) => self.tab_properties(entity), - Some(ContextItem::NavBar(item)) => { - let mut children = Vec::with_capacity(1); - if let Some(location) = self.nav_model.data::(item) { - if let Location::Path(path) = location { - //TODO: this should be done once, not when generating the view! - if let Ok(item) = tab::item_from_path(path, self.config.tab.icon_sizes) { - children.push(item.property_view(IconSizes::default())); + fn preview(&self, entity_opt: &Option, kind: &PreviewKind) -> Element { + let mut children = Vec::with_capacity(1); + let entity = entity_opt.unwrap_or_else(|| self.tab_model.active()); + match kind { + PreviewKind::Custom(PreviewItem(item)) => { + children.push(item.property_view(IconSizes::default())); + } + PreviewKind::Location(location) => { + if let Some(tab) = self.tab_model.data::(entity) { + if let Some(items) = tab.items_opt() { + for item in items.iter() { + if item.location_opt.as_ref() == Some(location) { + children.push(item.property_view(tab.config.icon_sizes)); + // Only show one property view to avoid issues like hangs when generating + // preview images on thousands of files + break; + } } } } - widget::settings::view_column(children).into() } - - Some(ContextItem::BreadCrumbs(index)) => { - let mut children = Vec::with_capacity(1); - if let Some(tab) = self.tab_model.active_data::() { - let path_opt = tab - .location - .path_opt() - .and_then(|path| path.ancestors().nth(index)) - .map(|path| path.to_path_buf()); - if let Some(ref path) = path_opt { - //TODO: this should be done once, not when generating the view! - if let Ok(item) = tab::item_from_path(path, self.config.tab.icon_sizes) { - children.push(item.property_view(IconSizes::default())); + PreviewKind::Selected => { + if let Some(tab) = self.tab_model.data::(entity) { + if let Some(items) = tab.items_opt() { + for item in items.iter() { + if item.selected { + children.push(item.property_view(tab.config.icon_sizes)); + // Only show one property view to avoid issues like hangs when generating + // preview images on thousands of files + break; + } } - }; - } - widget::settings::view_column(children).into() - } - } - } - - fn tab_properties(&self, entity: segmented_button::Entity) -> Element { - let mut children = Vec::new(); - - if let Some(tab) = self.tab_model.data::(entity) { - if let Some(items) = tab.items_opt() { - for item in items.iter() { - if item.selected { - children.push(item.property_view(tab.config.icon_sizes)); - // Only show one property view to avoid issues like hangs when generating - // preview images on thousands of files - break; } } } } - widget::settings::view_column(children).into() } @@ -1205,6 +1203,7 @@ impl Application for App { pending_operations: BTreeMap::new(), complete_operations: BTreeMap::new(), failed_operations: BTreeMap::new(), + preview_opt: None, search_active: false, search_id: widget::Id::unique(), search_input: String::new(), @@ -1307,10 +1306,7 @@ impl Application for App { NavMenuAction::OpenInNewWindow(id), ), cosmic::widget::menu::Item::Divider, - cosmic::widget::menu::Item::Button( - fl!("show-details"), - NavMenuAction::Properties(id), - ), + cosmic::widget::menu::Item::Button(fl!("show-details"), NavMenuAction::Preview(id)), cosmic::widget::menu::Item::Divider, if is_context_trash { cosmic::widget::menu::Item::Button( @@ -2031,6 +2027,17 @@ impl Application for App { } return self.update_notification(); } + Message::Preview(entity, kind, timeout) => { + if self + .preview_opt + .as_ref() + .is_some_and(|(e, k, i)| *e == entity && *k == kind && i.elapsed() > timeout) + { + self.context_page = ContextPage::Preview(Some(entity), kind); + self.set_show_context(true); + self.set_context_title(self.context_page.title()); + } + } Message::RescanTrash => { // Update trash icon if empty/full let maybe_entity = self.nav_model.iter().find(|&entity| { @@ -2273,10 +2280,9 @@ impl Application for App { commands.push(self.update(action.message(Some(entity)))); } tab::Command::AddNetworkDrive => { - let context_page = ContextPage::NetworkDrive; - self.context_page = context_page; + self.context_page = ContextPage::NetworkDrive; self.set_show_context(true); - self.set_context_title(context_page.title()); + self.set_context_title(self.context_page.title()); } tab::Command::ChangeLocation(tab_title, tab_path, selection_path) => { self.activate_nav_model_location(&tab_path); @@ -2299,12 +2305,6 @@ impl Application for App { message::app(Message::TabMessage(Some(entity), tab_message)) })); } - tab::Command::LocationProperties(index) => { - self.context_page = - ContextPage::Properties(Some(ContextItem::BreadCrumbs(index))); - self.set_show_context(true); - self.set_context_title(self.context_page.title()); - } tab::Command::MoveToTrash(paths) => { self.operation(Operation::Delete { paths }); } @@ -2375,6 +2375,23 @@ impl Application for App { log::error!("failed to get current executable path: {}", err); } }, + tab::Command::Preview(kind, mut timeout) => { + self.preview_opt = Some((entity, kind.clone(), Instant::now())); + if self.core.window.show_context { + // If the context window is already open, immediately show the preview + timeout = time::Duration::new(0, 0) + }; + commands.push(Command::perform( + async move { + tokio::time::sleep(timeout).await; + message::app(Message::Preview(entity, kind, timeout)) + }, + |x| x, + )); + } + tab::Command::PreviewCancel => { + self.preview_opt = None; + } } } return Command::batch(commands); @@ -2405,10 +2422,10 @@ impl Application for App { if self.context_page == context_page { self.set_show_context(!self.core.window.show_context); } else { - self.context_page = context_page; self.set_show_context(true); } - self.set_context_title(context_page.title()); + self.context_page = context_page; + self.set_context_title(self.context_page.title()); } Message::Undo(id) => { // TODO; @@ -2624,10 +2641,22 @@ impl Application for App { } } - NavMenuAction::Properties(entity) => { - self.context_page = ContextPage::Properties(Some(ContextItem::NavBar(entity))); - self.set_show_context(true); - self.set_context_title(self.context_page.title()); + NavMenuAction::Preview(entity) => { + if let Some(Location::Path(path)) = self.nav_model.data::(entity) { + match tab::item_from_path(path, IconSizes::default()) { + Ok(item) => { + self.context_page = ContextPage::Preview( + None, + PreviewKind::Custom(PreviewItem(item)), + ); + self.set_show_context(true); + self.set_context_title(self.context_page.title()); + } + Err(err) => { + log::warn!("failed to get item from path {:?}: {}", path, err); + } + } + } } NavMenuAction::RemoveFromSidebar(entity) => { @@ -2732,12 +2761,12 @@ impl Application for App { return None; } - Some(match self.context_page { + Some(match &self.context_page { ContextPage::About => self.about(), ContextPage::EditHistory => self.edit_history(), ContextPage::NetworkDrive => self.network_drive(), ContextPage::OpenWith => self.open_with(), - ContextPage::Properties(entity) => self.properties(entity), + ContextPage::Preview(entity_opt, kind) => self.preview(entity_opt, kind), ContextPage::Settings => self.settings(), }) } diff --git a/src/key_bind.rs b/src/key_bind.rs index 255fb89..ca56f0a 100644 --- a/src/key_bind.rs +++ b/src/key_bind.rs @@ -46,7 +46,7 @@ pub fn key_binds() -> HashMap { bind!([Ctrl], Key::Named(Named::Enter), OpenInNewTab); bind!([Shift], Key::Named(Named::Enter), OpenInNewWindow); bind!([Ctrl], Key::Character("v".into()), Paste); - bind!([], Key::Named(Named::Space), Properties); + bind!([], Key::Named(Named::Space), Preview); bind!([], Key::Named(Named::F2), Rename); bind!([Ctrl], Key::Character("f".into()), SearchActivate); bind!([Ctrl], Key::Character("a".into()), SelectAll); diff --git a/src/menu.rs b/src/menu.rs index d02976b..1cc8739 100644 --- a/src/menu.rs +++ b/src/menu.rs @@ -155,7 +155,7 @@ pub fn context_menu<'a>( children.push(divider::horizontal::light().into()); //TODO: Print? - children.push(menu_item(fl!("show-details"), Action::Properties).into()); + children.push(menu_item(fl!("show-details"), Action::Preview).into()); children.push(divider::horizontal::light().into()); children.push(menu_item(fl!("add-to-sidebar"), Action::AddToSidebar).into()); children.push(divider::horizontal::light().into()); @@ -231,7 +231,7 @@ pub fn context_menu<'a>( children.push(divider::horizontal::light().into()); } if selected > 0 { - children.push(menu_item(fl!("show-details"), Action::Properties).into()); + children.push(menu_item(fl!("show-details"), Action::Preview).into()); children.push(divider::horizontal::light().into()); children .push(menu_item(fl!("restore-from-trash"), Action::RestoreFromTrash).into()); @@ -371,7 +371,7 @@ pub fn menu_bar<'a>( menu::Item::Divider, menu::Item::Button(fl!("rename"), Action::Rename), menu::Item::Divider, - menu::Item::Button(fl!("menu-show-details"), Action::Properties), + menu::Item::Button(fl!("menu-show-details"), Action::Preview), menu::Item::Divider, menu::Item::Button(fl!("add-to-sidebar"), Action::AddToSidebar), menu::Item::Divider, @@ -486,7 +486,7 @@ pub fn location_context_menu<'a>(ancestor_index: usize) -> Element<'a, tab::Mess divider::horizontal::light().into(), menu_button!(text::body(fl!("show-details"))) .on_press(tab::Message::LocationMenuAction( - LocationMenuAction::Properties(ancestor_index), + LocationMenuAction::Preview(ancestor_index), )) .into(), ]; diff --git a/src/tab.rs b/src/tab.rs index 938069d..89b0896 100644 --- a/src/tab.rs +++ b/src/tab.rs @@ -55,7 +55,7 @@ use std::{ }; use crate::{ - app::{self, Action}, + app::{self, Action, PreviewItem, PreviewKind}, clipboard::{ClipboardCopy, ClipboardKind, ClipboardPaste}, config::{IconSizes, TabConfig, ICON_SCALE_MAX, ICON_SIZE_GRID}, dialog::DialogKind, @@ -687,11 +687,9 @@ fn uri_to_path(uri: String) -> Option { } pub fn scan_recents(sizes: IconSizes) -> Vec { - let mut recent_files = recently_used_xbel::parse_file(); - let mut recents = Vec::new(); - match recent_files { + match recently_used_xbel::parse_file() { Ok(recent_files) => { for bookmark in recent_files.bookmarks { let uri = bookmark.href; @@ -814,11 +812,12 @@ pub enum Command { DropFiles(PathBuf, ClipboardPaste), EmptyTrash, Iced(cosmic::Command), - LocationProperties(usize), MoveToTrash(Vec), OpenFile(PathBuf), OpenInNewTab(PathBuf), OpenInNewWindow(PathBuf), + Preview(PreviewKind, Duration), + PreviewCancel, } #[derive(Clone, Debug)] @@ -870,7 +869,7 @@ pub enum Message { pub enum LocationMenuAction { OpenInNewTab(usize), OpenInNewWindow(usize), - Properties(usize), + Preview(usize), } impl MenuAction for LocationMenuAction { @@ -1574,6 +1573,7 @@ impl Tab { let mut history_i_opt = None; let mod_ctrl = modifiers.contains(Modifiers::CTRL) && self.mode.multiple(); let mod_shift = modifiers.contains(Modifiers::SHIFT) && self.mode.multiple(); + let last_select_focus = self.select_focus; match message { Message::AddNetworkDrive => { commands.push(Command::AddNetworkDrive); @@ -1627,6 +1627,9 @@ impl Tab { } else { log::warn!("no item for click index {:?}", click_i_opt); } + + // Cancel any preview timers + commands.push(Command::PreviewCancel); } Message::Click(click_i_opt) => { self.selected_clicked = false; @@ -1742,7 +1745,7 @@ impl Tab { item.selected = true; self.select_range = Some((i, i)); } - + self.select_focus = click_i_opt; self.selected_clicked = true; } else if !dont_unset && item.selected { self.clicked = click_i_opt; @@ -1750,10 +1753,6 @@ impl Tab { } } } - if self.select_focus.take().is_some() { - // Unfocus currently focused button - commands.push(Command::Iced(widget::button::focus(widget::Id::unique()))); - } } } Message::Config(config) => { @@ -1806,8 +1805,22 @@ impl Tab { commands.push(Command::OpenInNewWindow(path)); } } - LocationMenuAction::Properties(ancestor_index) => { - commands.push(Command::LocationProperties(ancestor_index)); + LocationMenuAction::Preview(ancestor_index) => { + if let Some(path) = path_for_index(ancestor_index) { + //TODO: blocking code, run in command + match item_from_path(&path, IconSizes::default()) { + Ok(item) => { + // Show preview instantly + commands.push(Command::Preview( + PreviewKind::Custom(PreviewItem(item)), + Duration::new(0, 0), + )); + } + Err(err) => { + log::warn!("failed to get item from path {:?}: {}", path, err); + } + } + } } } } @@ -2160,9 +2173,11 @@ impl Tab { self.dnd_hovered = None; } Message::DndHover(loc) => { - if self.dnd_hovered.as_ref().is_some_and(|(l, i)| { - *l == loc && Instant::now().duration_since(*i) > HOVER_DURATION - }) { + if self + .dnd_hovered + .as_ref() + .is_some_and(|(l, i)| *l == loc && i.elapsed() > HOVER_DURATION) + { cd = Some(loc); } } @@ -2177,6 +2192,9 @@ impl Tab { |x| x, ))); } + + // Clear preview timer + commands.push(Command::PreviewCancel); } Message::DndLeave(loc) => { if Some(&loc) == self.dnd_hovered.as_ref().map(|(l, _)| l) { @@ -2226,6 +2244,26 @@ impl Tab { } } } + + // Update preview timer + //TODO: make this configurable + if last_select_focus != self.select_focus { + if let Some(index) = self.select_focus { + if let Some(ref items) = self.items_opt { + if let Some(item) = items.get(index) { + if let Some(location) = item.location_opt.clone() { + // Show preview after double click timeout + commands.push(Command::Preview( + PreviewKind::Location(location), + DOUBLE_CLICK_DURATION, + )); + } + } + } + } + } + + // Change directory if requested if let Some(location) = cd { if matches!(self.mode, Mode::Desktop) { match location { @@ -2252,6 +2290,7 @@ impl Tab { } } } + commands } @@ -3238,54 +3277,7 @@ impl Tab { let button_row = button(row.into()); let button_row: Element<_> = if item.metadata.is_dir() && item.location_opt.is_some() { - let tab_location = item.location_opt.clone().unwrap(); - let tab_location_enter = tab_location.clone(); - let tab_location_leave = tab_location.clone(); - let is_dnd_hovered = - self.dnd_hovered.as_ref().map(|(l, _)| l) == Some(&tab_location); - cosmic::widget::container( - DndDestination::for_data(button_row, move |data, action| { - if let Some(mut data) = data { - if action == DndAction::Copy { - Message::Drop(Some((tab_location.clone(), data))) - } else if action == DndAction::Move { - data.kind = ClipboardKind::Cut; - Message::Drop(Some((tab_location.clone(), data))) - } else { - log::warn!("unsupported action: {:?}", action); - Message::Drop(None) - } - } else { - log::warn!("No data for drop."); - Message::Drop(None) - } - }) - .on_enter(move |_, _, _| Message::DndEnter(tab_location_enter.clone())) - .on_leave(move || Message::DndLeave(tab_location_leave.clone())), - ) - // todo refactor into the dnd destination wrapper - .style(if is_dnd_hovered { - theme::Container::custom(|t| { - let mut a = cosmic::iced_style::container::StyleSheet::appearance( - t, - &theme::Container::default(), - ); - let t = t.cosmic(); - // todo use theme drop target color - let mut bg = t.accent_color(); - bg.alpha = 0.2; - a.background = Some(Color::from(bg).into()); - a.border = Border { - color: t.accent_color().into(), - width: 1.0, - radius: t.radius_s().into(), - }; - a - }) - } else { - theme::Container::default() - }) - .into() + self.dnd_dest(item.location_opt.as_ref().unwrap(), button_row) } else { button_row.into() }; @@ -3669,10 +3661,7 @@ impl Tab { } } - //TODO: how to properly kill this task? - loop { - tokio::time::sleep(std::time::Duration::new(1, 0)).await; - } + std::future::pending().await }, )); } From 333d4e58ca58fceb77f12de05f72267c61688f53 Mon Sep 17 00:00:00 2001 From: Jeremy Soller Date: Fri, 20 Sep 2024 14:34:44 -0600 Subject: [PATCH 18/18] Keep focused item in view when resizing, part of #109 --- src/mouse_area.rs | 18 ++++++++++++++++++ src/tab.rs | 11 +++++++++++ 2 files changed, 29 insertions(+) diff --git a/src/mouse_area.rs b/src/mouse_area.rs index 2de7aa7..b9b86c1 100644 --- a/src/mouse_area.rs +++ b/src/mouse_area.rs @@ -36,6 +36,7 @@ pub struct MouseArea<'a, Message> { on_press: Option) -> Message + 'a>>, on_drag_end: Option) -> Message + 'a>>, on_release: Option) -> Message + 'a>>, + on_resize: Option Message + 'a>>, on_right_press: Option) -> Message + 'a>>, on_right_press_no_capture: Option) -> Message + 'a>>, on_right_release: Option) -> Message + 'a>>, @@ -85,6 +86,13 @@ impl<'a, Message> MouseArea<'a, Message> { self } + /// The message to emit on resizing. + #[must_use] + pub fn on_resize(mut self, message: impl Fn(Size) -> Message + 'a) -> Self { + self.on_resize = Some(Box::new(message)); + self + } + /// The message to emit on a right button press. #[must_use] pub fn on_right_press(mut self, message: impl Fn(Option) -> Message + 'a) -> Self { @@ -182,6 +190,7 @@ struct State { drag_initiated: Option, modifiers: Modifiers, prev_click: Option<(mouse::Click, Instant)>, + size: Option, } impl State { @@ -235,6 +244,7 @@ impl<'a, Message> MouseArea<'a, Message> { on_double_click: None, on_press: None, on_release: None, + on_resize: None, on_right_press: None, on_right_press_no_capture: None, on_right_release: None, @@ -449,6 +459,14 @@ fn update( ) -> event::Status { let layout_bounds = layout.bounds(); + if let Some(message) = widget.on_resize.as_ref() { + let size = layout_bounds.size(); + if state.size != Some(size) { + state.size = Some(size); + shell.publish(message(size)); + } + } + if state.drag_initiated.is_none() && !cursor.is_over(layout_bounds) { return event::Status::Ignored; } diff --git a/src/tab.rs b/src/tab.rs index 89b0896..36742b7 100644 --- a/src/tab.rs +++ b/src/tab.rs @@ -849,6 +849,7 @@ pub enum Message { RightClick(Option), MiddleClick(usize), Scroll(Viewport), + ScrollToFocus, SelectAll, SetSort(HeadingOptions, bool), Thumbnail(PathBuf, ItemThumbnail), @@ -2096,6 +2097,14 @@ impl Tab { Message::Scroll(viewport) => { self.scroll_opt = Some(viewport.absolute_offset()); } + Message::ScrollToFocus => { + if let Some(offset) = self.select_focus_scroll() { + commands.push(Command::Iced(scrollable::scroll_to( + self.scrollable_id.clone(), + offset, + ))); + } + } Message::SelectAll => { self.select_all(); if self.select_focus.take().is_some() { @@ -3464,6 +3473,8 @@ impl Tab { let mut mouse_area = mouse_area::MouseArea::new(item_view) .on_press(move |_point_opt| Message::Click(None)) .on_release(|_| Message::ClickRelease(None)) + //TODO: better way to keep focused item in view + .on_resize(|_| Message::ScrollToFocus) .on_back_press(move |_point_opt| Message::GoPrevious) .on_forward_press(move |_point_opt| Message::GoNext) .on_scroll(respond_to_scroll_direction);