Merge branch 'pop-os:master' into master
This commit is contained in:
commit
9fed3c1011
5 changed files with 541 additions and 176 deletions
60
Cargo.lock
generated
60
Cargo.lock
generated
|
|
@ -941,9 +941,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cfg-expr"
|
name = "cfg-expr"
|
||||||
version = "0.16.0"
|
version = "0.17.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "345c78335be0624ed29012dc10c49102196c6882c12dde65d9f35b02da2aada8"
|
checksum = "d0890061c4d3223e7267f3bad2ec40b997d64faac1c2815a4a9d95018e2b9e9c"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"smallvec",
|
"smallvec",
|
||||||
"target-lexicon",
|
"target-lexicon",
|
||||||
|
|
@ -1212,7 +1212,7 @@ dependencies = [
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cosmic-config"
|
name = "cosmic-config"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://github.com/pop-os/libcosmic.git#ddb678ca6966dfdf10911feac5e5ac02d3b2b97e"
|
source = "git+https://github.com/pop-os/libcosmic.git#701638009df09a254b7d077ddc4d1076cd87a147"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"atomicwrites",
|
"atomicwrites",
|
||||||
"cosmic-config-derive",
|
"cosmic-config-derive",
|
||||||
|
|
@ -1231,7 +1231,7 @@ dependencies = [
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cosmic-config-derive"
|
name = "cosmic-config-derive"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://github.com/pop-os/libcosmic.git#ddb678ca6966dfdf10911feac5e5ac02d3b2b97e"
|
source = "git+https://github.com/pop-os/libcosmic.git#701638009df09a254b7d077ddc4d1076cd87a147"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"quote",
|
"quote",
|
||||||
"syn 1.0.109",
|
"syn 1.0.109",
|
||||||
|
|
@ -1317,7 +1317,7 @@ dependencies = [
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cosmic-text"
|
name = "cosmic-text"
|
||||||
version = "0.12.1"
|
version = "0.12.1"
|
||||||
source = "git+https://github.com/pop-os/cosmic-text.git#e8f567cf5b456dfab749a575c257acaa36f622d9"
|
source = "git+https://github.com/pop-os/cosmic-text.git#4fe90bb6126c22f589b46768d7754d65ae300c5e"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 2.6.0",
|
"bitflags 2.6.0",
|
||||||
"fontdb",
|
"fontdb",
|
||||||
|
|
@ -1340,7 +1340,7 @@ dependencies = [
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cosmic-theme"
|
name = "cosmic-theme"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://github.com/pop-os/libcosmic.git#ddb678ca6966dfdf10911feac5e5ac02d3b2b97e"
|
source = "git+https://github.com/pop-os/libcosmic.git#701638009df09a254b7d077ddc4d1076cd87a147"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"almost",
|
"almost",
|
||||||
"cosmic-config",
|
"cosmic-config",
|
||||||
|
|
@ -2783,7 +2783,7 @@ dependencies = [
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "iced"
|
name = "iced"
|
||||||
version = "0.12.0"
|
version = "0.12.0"
|
||||||
source = "git+https://github.com/pop-os/libcosmic.git#ddb678ca6966dfdf10911feac5e5ac02d3b2b97e"
|
source = "git+https://github.com/pop-os/libcosmic.git#701638009df09a254b7d077ddc4d1076cd87a147"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"dnd",
|
"dnd",
|
||||||
"iced_accessibility",
|
"iced_accessibility",
|
||||||
|
|
@ -2802,7 +2802,7 @@ dependencies = [
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "iced_accessibility"
|
name = "iced_accessibility"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://github.com/pop-os/libcosmic.git#ddb678ca6966dfdf10911feac5e5ac02d3b2b97e"
|
source = "git+https://github.com/pop-os/libcosmic.git#701638009df09a254b7d077ddc4d1076cd87a147"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"accesskit",
|
"accesskit",
|
||||||
"accesskit_unix",
|
"accesskit_unix",
|
||||||
|
|
@ -2812,7 +2812,7 @@ dependencies = [
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "iced_core"
|
name = "iced_core"
|
||||||
version = "0.12.0"
|
version = "0.12.0"
|
||||||
source = "git+https://github.com/pop-os/libcosmic.git#ddb678ca6966dfdf10911feac5e5ac02d3b2b97e"
|
source = "git+https://github.com/pop-os/libcosmic.git#701638009df09a254b7d077ddc4d1076cd87a147"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 2.6.0",
|
"bitflags 2.6.0",
|
||||||
"dnd",
|
"dnd",
|
||||||
|
|
@ -2834,7 +2834,7 @@ dependencies = [
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "iced_futures"
|
name = "iced_futures"
|
||||||
version = "0.12.0"
|
version = "0.12.0"
|
||||||
source = "git+https://github.com/pop-os/libcosmic.git#ddb678ca6966dfdf10911feac5e5ac02d3b2b97e"
|
source = "git+https://github.com/pop-os/libcosmic.git#701638009df09a254b7d077ddc4d1076cd87a147"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"futures",
|
"futures",
|
||||||
"iced_core",
|
"iced_core",
|
||||||
|
|
@ -2847,7 +2847,7 @@ dependencies = [
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "iced_graphics"
|
name = "iced_graphics"
|
||||||
version = "0.12.0"
|
version = "0.12.0"
|
||||||
source = "git+https://github.com/pop-os/libcosmic.git#ddb678ca6966dfdf10911feac5e5ac02d3b2b97e"
|
source = "git+https://github.com/pop-os/libcosmic.git#701638009df09a254b7d077ddc4d1076cd87a147"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 2.6.0",
|
"bitflags 2.6.0",
|
||||||
"bytemuck",
|
"bytemuck",
|
||||||
|
|
@ -2871,7 +2871,7 @@ dependencies = [
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "iced_renderer"
|
name = "iced_renderer"
|
||||||
version = "0.12.0"
|
version = "0.12.0"
|
||||||
source = "git+https://github.com/pop-os/libcosmic.git#ddb678ca6966dfdf10911feac5e5ac02d3b2b97e"
|
source = "git+https://github.com/pop-os/libcosmic.git#701638009df09a254b7d077ddc4d1076cd87a147"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"iced_graphics",
|
"iced_graphics",
|
||||||
"iced_tiny_skia",
|
"iced_tiny_skia",
|
||||||
|
|
@ -2883,7 +2883,7 @@ dependencies = [
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "iced_runtime"
|
name = "iced_runtime"
|
||||||
version = "0.12.0"
|
version = "0.12.0"
|
||||||
source = "git+https://github.com/pop-os/libcosmic.git#ddb678ca6966dfdf10911feac5e5ac02d3b2b97e"
|
source = "git+https://github.com/pop-os/libcosmic.git#701638009df09a254b7d077ddc4d1076cd87a147"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"dnd",
|
"dnd",
|
||||||
"iced_accessibility",
|
"iced_accessibility",
|
||||||
|
|
@ -2897,7 +2897,7 @@ dependencies = [
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "iced_sctk"
|
name = "iced_sctk"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://github.com/pop-os/libcosmic.git#ddb678ca6966dfdf10911feac5e5ac02d3b2b97e"
|
source = "git+https://github.com/pop-os/libcosmic.git#701638009df09a254b7d077ddc4d1076cd87a147"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"enum-repr",
|
"enum-repr",
|
||||||
"float-cmp",
|
"float-cmp",
|
||||||
|
|
@ -2924,7 +2924,7 @@ dependencies = [
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "iced_style"
|
name = "iced_style"
|
||||||
version = "0.12.0"
|
version = "0.12.0"
|
||||||
source = "git+https://github.com/pop-os/libcosmic.git#ddb678ca6966dfdf10911feac5e5ac02d3b2b97e"
|
source = "git+https://github.com/pop-os/libcosmic.git#701638009df09a254b7d077ddc4d1076cd87a147"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"iced_core",
|
"iced_core",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
|
|
@ -2934,7 +2934,7 @@ dependencies = [
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "iced_tiny_skia"
|
name = "iced_tiny_skia"
|
||||||
version = "0.12.0"
|
version = "0.12.0"
|
||||||
source = "git+https://github.com/pop-os/libcosmic.git#ddb678ca6966dfdf10911feac5e5ac02d3b2b97e"
|
source = "git+https://github.com/pop-os/libcosmic.git#701638009df09a254b7d077ddc4d1076cd87a147"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bytemuck",
|
"bytemuck",
|
||||||
"cosmic-text",
|
"cosmic-text",
|
||||||
|
|
@ -2951,7 +2951,7 @@ dependencies = [
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "iced_wgpu"
|
name = "iced_wgpu"
|
||||||
version = "0.12.0"
|
version = "0.12.0"
|
||||||
source = "git+https://github.com/pop-os/libcosmic.git#ddb678ca6966dfdf10911feac5e5ac02d3b2b97e"
|
source = "git+https://github.com/pop-os/libcosmic.git#701638009df09a254b7d077ddc4d1076cd87a147"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"as-raw-xcb-connection",
|
"as-raw-xcb-connection",
|
||||||
"bitflags 2.6.0",
|
"bitflags 2.6.0",
|
||||||
|
|
@ -2980,7 +2980,7 @@ dependencies = [
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "iced_widget"
|
name = "iced_widget"
|
||||||
version = "0.12.0"
|
version = "0.12.0"
|
||||||
source = "git+https://github.com/pop-os/libcosmic.git#ddb678ca6966dfdf10911feac5e5ac02d3b2b97e"
|
source = "git+https://github.com/pop-os/libcosmic.git#701638009df09a254b7d077ddc4d1076cd87a147"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"dnd",
|
"dnd",
|
||||||
"iced_accessibility",
|
"iced_accessibility",
|
||||||
|
|
@ -2998,7 +2998,7 @@ dependencies = [
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "iced_winit"
|
name = "iced_winit"
|
||||||
version = "0.12.0"
|
version = "0.12.0"
|
||||||
source = "git+https://github.com/pop-os/libcosmic.git#ddb678ca6966dfdf10911feac5e5ac02d3b2b97e"
|
source = "git+https://github.com/pop-os/libcosmic.git#701638009df09a254b7d077ddc4d1076cd87a147"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"dnd",
|
"dnd",
|
||||||
"iced_accessibility",
|
"iced_accessibility",
|
||||||
|
|
@ -3507,14 +3507,14 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "libc"
|
name = "libc"
|
||||||
version = "0.2.158"
|
version = "0.2.159"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d8adc4bb1803a324070e64a98ae98f38934d91957a99cfb3a43dcbc01bc56439"
|
checksum = "561d97a539a36e26a9a5fad1ea11a3039a67714694aaa379433e580854bc3dc5"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "libcosmic"
|
name = "libcosmic"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://github.com/pop-os/libcosmic.git#ddb678ca6966dfdf10911feac5e5ac02d3b2b97e"
|
source = "git+https://github.com/pop-os/libcosmic.git#701638009df09a254b7d077ddc4d1076cd87a147"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"apply",
|
"apply",
|
||||||
"ashpd 0.9.1",
|
"ashpd 0.9.1",
|
||||||
|
|
@ -4563,9 +4563,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pkg-config"
|
name = "pkg-config"
|
||||||
version = "0.3.30"
|
version = "0.3.31"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec"
|
checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "png"
|
name = "png"
|
||||||
|
|
@ -5591,9 +5591,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "system-deps"
|
name = "system-deps"
|
||||||
version = "7.0.2"
|
version = "7.0.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "070a0a5e7da2d24be457809c4b3baa57a835fd2829ad8b86f9a049052fe71031"
|
checksum = "66d23aaf9f331227789a99e8de4c91bf46703add012bdfd45fdecdfb2975a005"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cfg-expr",
|
"cfg-expr",
|
||||||
"heck 0.5.0",
|
"heck 0.5.0",
|
||||||
|
|
@ -5693,18 +5693,18 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "thiserror"
|
name = "thiserror"
|
||||||
version = "1.0.63"
|
version = "1.0.64"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "c0342370b38b6a11b6cc11d6a805569958d54cfa061a29969c3b5ce2ea405724"
|
checksum = "d50af8abc119fb8bb6dbabcfa89656f46f84aa0ac7688088608076ad2b459a84"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"thiserror-impl",
|
"thiserror-impl",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "thiserror-impl"
|
name = "thiserror-impl"
|
||||||
version = "1.0.63"
|
version = "1.0.64"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a4558b58466b9ad7ca0f102865eccc95938dca1a74a856f2b57b6629050da261"
|
checksum = "08904e7672f5eb876eaaf87e0ce17857500934f4981c4a0ab2b4aa98baac7fc3"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
|
|
|
||||||
36
src/app.rs
36
src/app.rs
|
|
@ -157,7 +157,7 @@ impl Action {
|
||||||
Action::MoveToTrash => Message::MoveToTrash(entity_opt),
|
Action::MoveToTrash => Message::MoveToTrash(entity_opt),
|
||||||
Action::NewFile => Message::NewItem(entity_opt, false),
|
Action::NewFile => Message::NewItem(entity_opt, false),
|
||||||
Action::NewFolder => Message::NewItem(entity_opt, true),
|
Action::NewFolder => Message::NewItem(entity_opt, true),
|
||||||
Action::Open => Message::TabMessage(entity_opt, tab::Message::Open),
|
Action::Open => Message::TabMessage(entity_opt, tab::Message::Open(None)),
|
||||||
Action::OpenInNewTab => Message::OpenInNewTab(entity_opt),
|
Action::OpenInNewTab => Message::OpenInNewTab(entity_opt),
|
||||||
Action::OpenInNewWindow => Message::OpenInNewWindow(entity_opt),
|
Action::OpenInNewWindow => Message::OpenInNewWindow(entity_opt),
|
||||||
Action::OpenItemLocation => Message::OpenItemLocation(entity_opt),
|
Action::OpenItemLocation => Message::OpenItemLocation(entity_opt),
|
||||||
|
|
@ -342,7 +342,7 @@ pub enum ContextPage {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ContextPage {
|
impl ContextPage {
|
||||||
fn title(&self) -> String {
|
pub fn title(&self) -> String {
|
||||||
match self {
|
match self {
|
||||||
Self::About => String::new(),
|
Self::About => String::new(),
|
||||||
Self::EditHistory => fl!("edit-history"),
|
Self::EditHistory => fl!("edit-history"),
|
||||||
|
|
@ -969,14 +969,14 @@ impl App {
|
||||||
let entity = entity_opt.unwrap_or_else(|| self.tab_model.active());
|
let entity = entity_opt.unwrap_or_else(|| self.tab_model.active());
|
||||||
match kind {
|
match kind {
|
||||||
PreviewKind::Custom(PreviewItem(item)) => {
|
PreviewKind::Custom(PreviewItem(item)) => {
|
||||||
children.push(item.property_view(IconSizes::default()));
|
children.push(item.preview_view(IconSizes::default()));
|
||||||
}
|
}
|
||||||
PreviewKind::Location(location) => {
|
PreviewKind::Location(location) => {
|
||||||
if let Some(tab) = self.tab_model.data::<Tab>(entity) {
|
if let Some(tab) = self.tab_model.data::<Tab>(entity) {
|
||||||
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.location_opt.as_ref() == Some(location) {
|
if item.location_opt.as_ref() == Some(location) {
|
||||||
children.push(item.property_view(tab.config.icon_sizes));
|
children.push(item.preview_view(tab.config.icon_sizes));
|
||||||
// Only show one property view to avoid issues like hangs when generating
|
// Only show one property view to avoid issues like hangs when generating
|
||||||
// preview images on thousands of files
|
// preview images on thousands of files
|
||||||
break;
|
break;
|
||||||
|
|
@ -990,7 +990,7 @@ impl 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 {
|
||||||
children.push(item.property_view(tab.config.icon_sizes));
|
children.push(item.preview_view(tab.config.icon_sizes));
|
||||||
// Only show one property view to avoid issues like hangs when generating
|
// Only show one property view to avoid issues like hangs when generating
|
||||||
// preview images on thousands of files
|
// preview images on thousands of files
|
||||||
break;
|
break;
|
||||||
|
|
@ -1360,6 +1360,14 @@ impl Application for App {
|
||||||
return Command::none();
|
return Command::none();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Close gallery mode if open
|
||||||
|
if let Some(tab) = self.tab_model.data_mut::<Tab>(entity) {
|
||||||
|
if tab.gallery {
|
||||||
|
tab.gallery = false;
|
||||||
|
return Command::none();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Close menus and context panes in order per message
|
// Close menus and context panes in order per message
|
||||||
// Why: It'd be weird to close everything all at once
|
// Why: It'd be weird to close everything all at once
|
||||||
// Usually, the Escape key (for example) closes menus and panes one by one instead
|
// Usually, the Escape key (for example) closes menus and panes one by one instead
|
||||||
|
|
@ -2392,6 +2400,12 @@ impl Application for App {
|
||||||
tab::Command::PreviewCancel => {
|
tab::Command::PreviewCancel => {
|
||||||
self.preview_opt = None;
|
self.preview_opt = None;
|
||||||
}
|
}
|
||||||
|
tab::Command::WindowDrag => {
|
||||||
|
commands.push(window::drag(self.main_window_id()));
|
||||||
|
}
|
||||||
|
tab::Command::WindowToggleMaximize => {
|
||||||
|
commands.push(window::toggle_maximize(self.main_window_id()));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return Command::batch(commands);
|
return Command::batch(commands);
|
||||||
|
|
@ -2772,6 +2786,17 @@ impl Application for App {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn dialog(&self) -> Option<Element<Message>> {
|
fn dialog(&self) -> Option<Element<Message>> {
|
||||||
|
//TODO: should gallery view just be a dialog?
|
||||||
|
let entity = self.tab_model.active();
|
||||||
|
if let Some(tab) = self.tab_model.data::<Tab>(entity) {
|
||||||
|
if tab.gallery {
|
||||||
|
return Some(
|
||||||
|
tab.gallery_view()
|
||||||
|
.map(move |tab_message| Message::TabMessage(Some(entity), tab_message)),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let dialog_page = match self.dialog_pages.front() {
|
let dialog_page = match self.dialog_pages.front() {
|
||||||
Some(some) => some,
|
Some(some) => some,
|
||||||
None => return None,
|
None => return None,
|
||||||
|
|
@ -3226,6 +3251,7 @@ impl Application for App {
|
||||||
} else {
|
} else {
|
||||||
elements.push(
|
elements.push(
|
||||||
widget::button::icon(widget::icon::from_name("system-search-symbolic"))
|
widget::button::icon(widget::icon::from_name("system-search-symbolic"))
|
||||||
|
.padding(8)
|
||||||
.on_press(Message::SearchActivate)
|
.on_press(Message::SearchActivate)
|
||||||
.into(),
|
.into(),
|
||||||
)
|
)
|
||||||
|
|
|
||||||
253
src/dialog.rs
253
src/dialog.rs
|
|
@ -39,8 +39,8 @@ use std::{
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
app::{Action, Message as AppMessage},
|
app::{Action, ContextPage, Message as AppMessage, PreviewItem, PreviewKind},
|
||||||
config::{Config, Favorite, TabConfig},
|
config::{Config, Favorite, IconSizes, TabConfig},
|
||||||
fl, home_dir,
|
fl, home_dir,
|
||||||
localize::LANGUAGE_SORTER,
|
localize::LANGUAGE_SORTER,
|
||||||
menu,
|
menu,
|
||||||
|
|
@ -315,6 +315,7 @@ enum Message {
|
||||||
NotifyEvents(Vec<DebouncedEvent>),
|
NotifyEvents(Vec<DebouncedEvent>),
|
||||||
NotifyWatcher(WatcherWrapper),
|
NotifyWatcher(WatcherWrapper),
|
||||||
Open,
|
Open,
|
||||||
|
Preview(PreviewKind, time::Duration),
|
||||||
Save(bool),
|
Save(bool),
|
||||||
SearchActivate,
|
SearchActivate,
|
||||||
SearchClear,
|
SearchClear,
|
||||||
|
|
@ -324,6 +325,18 @@ enum Message {
|
||||||
TabRescan(Vec<tab::Item>),
|
TabRescan(Vec<tab::Item>),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<AppMessage> for Message {
|
||||||
|
fn from(app_message: AppMessage) -> Message {
|
||||||
|
match app_message {
|
||||||
|
AppMessage::TabMessage(_entity_opt, tab_message) => Message::TabMessage(tab_message),
|
||||||
|
unsupported => {
|
||||||
|
log::warn!("{unsupported:?} not supported in dialog mode");
|
||||||
|
Message::None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub struct MounterData(MounterKey, MounterItem);
|
pub struct MounterData(MounterKey, MounterItem);
|
||||||
|
|
||||||
struct WatcherWrapper {
|
struct WatcherWrapper {
|
||||||
|
|
@ -355,6 +368,7 @@ struct App {
|
||||||
title: String,
|
title: String,
|
||||||
accept_label: String,
|
accept_label: String,
|
||||||
choices: Vec<DialogChoice>,
|
choices: Vec<DialogChoice>,
|
||||||
|
context_page: ContextPage,
|
||||||
dialog_pages: VecDeque<DialogPage>,
|
dialog_pages: VecDeque<DialogPage>,
|
||||||
dialog_text_input: widget::Id,
|
dialog_text_input: widget::Id,
|
||||||
filters: Vec<DialogFilter>,
|
filters: Vec<DialogFilter>,
|
||||||
|
|
@ -364,6 +378,7 @@ struct App {
|
||||||
mounters: Mounters,
|
mounters: Mounters,
|
||||||
mounter_items: HashMap<MounterKey, MounterItems>,
|
mounter_items: HashMap<MounterKey, MounterItems>,
|
||||||
nav_model: segmented_button::SingleSelectModel,
|
nav_model: segmented_button::SingleSelectModel,
|
||||||
|
preview_opt: Option<(PreviewKind, time::Instant)>,
|
||||||
result_opt: Option<DialogResult>,
|
result_opt: Option<DialogResult>,
|
||||||
search_active: bool,
|
search_active: bool,
|
||||||
search_id: widget::Id,
|
search_id: widget::Id,
|
||||||
|
|
@ -374,6 +389,96 @@ struct App {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl App {
|
impl App {
|
||||||
|
fn button_row(&self) -> Element<Message> {
|
||||||
|
let cosmic_theme::Spacing { space_xxs, .. } = theme::active().cosmic().spacing;
|
||||||
|
|
||||||
|
let mut row = widget::row::with_capacity(
|
||||||
|
if !self.filters.is_empty() { 1 } else { 0 } + self.choices.len() * 2 + 3,
|
||||||
|
)
|
||||||
|
.align_items(Alignment::Center)
|
||||||
|
.padding(space_xxs)
|
||||||
|
.spacing(space_xxs);
|
||||||
|
if !self.filters.is_empty() {
|
||||||
|
row = row.push(widget::dropdown(
|
||||||
|
&self.filters,
|
||||||
|
self.filter_selected,
|
||||||
|
Message::Filter,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
for (choice_i, choice) in self.choices.iter().enumerate() {
|
||||||
|
match choice {
|
||||||
|
DialogChoice::CheckBox { label, value, .. } => {
|
||||||
|
row = row.push(widget::checkbox(label, *value, move |checked| {
|
||||||
|
Message::Choice(choice_i, if checked { 1 } else { 0 })
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
DialogChoice::ComboBox {
|
||||||
|
label,
|
||||||
|
options,
|
||||||
|
selected,
|
||||||
|
..
|
||||||
|
} => {
|
||||||
|
row = row.push(widget::text::heading(label));
|
||||||
|
row = row.push(widget::dropdown(options, *selected, move |option_i| {
|
||||||
|
Message::Choice(choice_i, option_i)
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let DialogKind::SaveFile { filename } = &self.flags.kind {
|
||||||
|
row = row.push(
|
||||||
|
widget::text_input("", filename)
|
||||||
|
.id(self.filename_id.clone())
|
||||||
|
.on_input(Message::Filename)
|
||||||
|
.on_submit(Message::Save(false)),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
row = row.push(widget::horizontal_space(Length::Fill));
|
||||||
|
}
|
||||||
|
row = row.push(widget::button::standard(fl!("cancel")).on_press(Message::Cancel));
|
||||||
|
row = row.push(if self.flags.kind.save() {
|
||||||
|
widget::button::suggested(&self.accept_label).on_press(Message::Save(false))
|
||||||
|
} else {
|
||||||
|
widget::button::suggested(&self.accept_label).on_press(Message::Open)
|
||||||
|
});
|
||||||
|
|
||||||
|
row.into()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn preview(&self, kind: &PreviewKind) -> Element<AppMessage> {
|
||||||
|
let mut children = Vec::with_capacity(1);
|
||||||
|
match kind {
|
||||||
|
PreviewKind::Custom(PreviewItem(item)) => {
|
||||||
|
children.push(item.preview_view(IconSizes::default()));
|
||||||
|
}
|
||||||
|
PreviewKind::Location(location) => {
|
||||||
|
if let Some(items) = self.tab.items_opt() {
|
||||||
|
for item in items.iter() {
|
||||||
|
if item.location_opt.as_ref() == Some(location) {
|
||||||
|
children.push(item.preview_view(self.tab.config.icon_sizes));
|
||||||
|
// Only show one property view to avoid issues like hangs when generating
|
||||||
|
// preview images on thousands of files
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
PreviewKind::Selected => {
|
||||||
|
if let Some(items) = self.tab.items_opt() {
|
||||||
|
for item in items.iter() {
|
||||||
|
if item.selected {
|
||||||
|
children.push(item.preview_view(self.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 rescan_tab(&self) -> Command<Message> {
|
fn rescan_tab(&self) -> Command<Message> {
|
||||||
let location = self.tab.location.clone();
|
let location = self.tab.location.clone();
|
||||||
let mounters = self.mounters.clone();
|
let mounters = self.mounters.clone();
|
||||||
|
|
@ -606,6 +711,7 @@ impl Application for App {
|
||||||
title,
|
title,
|
||||||
accept_label,
|
accept_label,
|
||||||
choices: Vec::new(),
|
choices: Vec::new(),
|
||||||
|
context_page: ContextPage::Settings,
|
||||||
dialog_pages: VecDeque::new(),
|
dialog_pages: VecDeque::new(),
|
||||||
dialog_text_input: widget::Id::unique(),
|
dialog_text_input: widget::Id::unique(),
|
||||||
filters: Vec::new(),
|
filters: Vec::new(),
|
||||||
|
|
@ -615,6 +721,7 @@ impl Application for App {
|
||||||
mounters: mounters(),
|
mounters: mounters(),
|
||||||
mounter_items: HashMap::new(),
|
mounter_items: HashMap::new(),
|
||||||
nav_model: segmented_button::ModelBuilder::default().build(),
|
nav_model: segmented_button::ModelBuilder::default().build(),
|
||||||
|
preview_opt: None,
|
||||||
result_opt: None,
|
result_opt: None,
|
||||||
search_active: false,
|
search_active: false,
|
||||||
search_id: widget::Id::unique(),
|
search_id: widget::Id::unique(),
|
||||||
|
|
@ -638,7 +745,33 @@ impl Application for App {
|
||||||
self.flags.window_id
|
self.flags.window_id
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn context_drawer(&self) -> Option<Element<Message>> {
|
||||||
|
if !self.core.window.show_context {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
match &self.context_page {
|
||||||
|
ContextPage::Preview(_, kind) => Some(self.preview(kind).map(Message::from)),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn dialog(&self) -> Option<Element<Message>> {
|
fn dialog(&self) -> Option<Element<Message>> {
|
||||||
|
//TODO: should gallery view just be a dialog?
|
||||||
|
if self.tab.gallery {
|
||||||
|
return Some(
|
||||||
|
widget::column::with_children(vec![
|
||||||
|
self.tab.gallery_view().map(Message::TabMessage),
|
||||||
|
// Draw button row as part of the overlay
|
||||||
|
widget::container(self.button_row())
|
||||||
|
.width(Length::Fill)
|
||||||
|
.style(theme::Container::WindowBackground)
|
||||||
|
.into(),
|
||||||
|
])
|
||||||
|
.into(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
let dialog_page = match self.dialog_pages.front() {
|
let dialog_page = match self.dialog_pages.front() {
|
||||||
Some(some) => some,
|
Some(some) => some,
|
||||||
None => return None,
|
None => return None,
|
||||||
|
|
@ -752,15 +885,7 @@ impl Application for App {
|
||||||
|
|
||||||
elements.push(
|
elements.push(
|
||||||
menu::dialog_menu(&self.tab, &self.key_binds)
|
menu::dialog_menu(&self.tab, &self.key_binds)
|
||||||
.map(|message| match message {
|
.map(Message::from)
|
||||||
AppMessage::TabMessage(_entity_opt, tab_message) => {
|
|
||||||
Message::TabMessage(tab_message)
|
|
||||||
}
|
|
||||||
unsupported => {
|
|
||||||
log::warn!("{unsupported:?} not supported in dialog mode");
|
|
||||||
Message::None
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.into(),
|
.into(),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
@ -823,6 +948,12 @@ impl Application for App {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn on_escape(&mut self) -> Command<Message> {
|
fn on_escape(&mut self) -> Command<Message> {
|
||||||
|
if self.tab.gallery {
|
||||||
|
// Close gallery if open
|
||||||
|
self.tab.gallery = false;
|
||||||
|
return Command::none();
|
||||||
|
}
|
||||||
|
|
||||||
if self.search_active {
|
if self.search_active {
|
||||||
// Close search if open
|
// Close search if open
|
||||||
self.search_active = false;
|
self.search_active = false;
|
||||||
|
|
@ -1100,6 +1231,17 @@ impl Application for App {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Message::Preview(kind, timeout) => {
|
||||||
|
if self
|
||||||
|
.preview_opt
|
||||||
|
.as_ref()
|
||||||
|
.is_some_and(|(k, i)| *k == kind && i.elapsed() > timeout)
|
||||||
|
{
|
||||||
|
self.context_page = ContextPage::Preview(None, kind);
|
||||||
|
self.set_show_context(true);
|
||||||
|
self.set_context_title(self.context_page.title());
|
||||||
|
}
|
||||||
|
}
|
||||||
Message::Save(replace) => {
|
Message::Save(replace) => {
|
||||||
if let DialogKind::SaveFile { filename } = &self.flags.kind {
|
if let DialogKind::SaveFile { filename } = &self.flags.kind {
|
||||||
if !filename.is_empty() {
|
if !filename.is_empty() {
|
||||||
|
|
@ -1176,14 +1318,9 @@ impl Application for App {
|
||||||
let mut commands = Vec::new();
|
let mut commands = Vec::new();
|
||||||
for tab_command in tab_commands {
|
for tab_command in tab_commands {
|
||||||
match tab_command {
|
match tab_command {
|
||||||
tab::Command::Action(action) => match action.message() {
|
tab::Command::Action(action) => {
|
||||||
AppMessage::TabMessage(_entity_opt, tab_message) => {
|
commands.push(self.update(Message::from(action.message())));
|
||||||
commands.push(self.update(Message::TabMessage(tab_message)));
|
}
|
||||||
}
|
|
||||||
unsupported => {
|
|
||||||
log::warn!("{unsupported:?} not supported in dialog mode");
|
|
||||||
}
|
|
||||||
},
|
|
||||||
tab::Command::ChangeLocation(_tab_title, _tab_path, _selection_path) => {
|
tab::Command::ChangeLocation(_tab_title, _tab_path, _selection_path) => {
|
||||||
commands
|
commands
|
||||||
.push(Command::batch([self.update_watcher(), self.rescan_tab()]));
|
.push(Command::batch([self.update_watcher(), self.rescan_tab()]));
|
||||||
|
|
@ -1202,6 +1339,29 @@ impl Application for App {
|
||||||
commands.push(self.update(Message::Open));
|
commands.push(self.update(Message::Open));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
tab::Command::Preview(kind, mut timeout) => {
|
||||||
|
self.preview_opt = Some((kind.clone(), time::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(kind, timeout))
|
||||||
|
},
|
||||||
|
|x| x,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
tab::Command::PreviewCancel => {
|
||||||
|
self.preview_opt = None;
|
||||||
|
}
|
||||||
|
tab::Command::WindowDrag => {
|
||||||
|
commands.push(window::drag(self.main_window_id()));
|
||||||
|
}
|
||||||
|
tab::Command::WindowToggleMaximize => {
|
||||||
|
commands.push(window::toggle_maximize(self.main_window_id()));
|
||||||
|
}
|
||||||
unsupported => {
|
unsupported => {
|
||||||
log::warn!("{unsupported:?} not supported in dialog mode");
|
log::warn!("{unsupported:?} not supported in dialog mode");
|
||||||
}
|
}
|
||||||
|
|
@ -1282,9 +1442,8 @@ impl Application for App {
|
||||||
|
|
||||||
/// Creates a view after each update.
|
/// Creates a view after each update.
|
||||||
fn view(&self) -> Element<Message> {
|
fn view(&self) -> Element<Message> {
|
||||||
let cosmic_theme::Spacing { space_xxs, .. } = theme::active().cosmic().spacing;
|
|
||||||
|
|
||||||
let mut tab_column = widget::column::with_capacity(2);
|
let mut tab_column = widget::column::with_capacity(2);
|
||||||
|
|
||||||
tab_column = tab_column.push(
|
tab_column = tab_column.push(
|
||||||
//TODO: key binds for dialog
|
//TODO: key binds for dialog
|
||||||
self.tab
|
self.tab
|
||||||
|
|
@ -1292,57 +1451,7 @@ impl Application for App {
|
||||||
.map(move |message| Message::TabMessage(message)),
|
.map(move |message| Message::TabMessage(message)),
|
||||||
);
|
);
|
||||||
|
|
||||||
let mut row = widget::row::with_capacity(
|
tab_column = tab_column.push(self.button_row());
|
||||||
if !self.filters.is_empty() { 1 } else { 0 } + self.choices.len() * 2 + 3,
|
|
||||||
)
|
|
||||||
.align_items(Alignment::Center)
|
|
||||||
.padding(space_xxs)
|
|
||||||
.spacing(space_xxs);
|
|
||||||
if !self.filters.is_empty() {
|
|
||||||
row = row.push(widget::dropdown(
|
|
||||||
&self.filters,
|
|
||||||
self.filter_selected,
|
|
||||||
Message::Filter,
|
|
||||||
));
|
|
||||||
}
|
|
||||||
for (choice_i, choice) in self.choices.iter().enumerate() {
|
|
||||||
match choice {
|
|
||||||
DialogChoice::CheckBox { label, value, .. } => {
|
|
||||||
row = row.push(widget::checkbox(label, *value, move |checked| {
|
|
||||||
Message::Choice(choice_i, if checked { 1 } else { 0 })
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
DialogChoice::ComboBox {
|
|
||||||
label,
|
|
||||||
options,
|
|
||||||
selected,
|
|
||||||
..
|
|
||||||
} => {
|
|
||||||
row = row.push(widget::text::heading(label));
|
|
||||||
row = row.push(widget::dropdown(options, *selected, move |option_i| {
|
|
||||||
Message::Choice(choice_i, option_i)
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if let DialogKind::SaveFile { filename } = &self.flags.kind {
|
|
||||||
row = row.push(
|
|
||||||
widget::text_input("", filename)
|
|
||||||
.id(self.filename_id.clone())
|
|
||||||
.on_input(Message::Filename)
|
|
||||||
.on_submit(Message::Save(false)),
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
row = row.push(widget::horizontal_space(Length::Fill));
|
|
||||||
}
|
|
||||||
row = row.push(widget::button::standard(fl!("cancel")).on_press(Message::Cancel));
|
|
||||||
row = row.push(if self.flags.kind.save() {
|
|
||||||
widget::button::suggested(&self.accept_label).on_press(Message::Save(false))
|
|
||||||
} else {
|
|
||||||
widget::button::suggested(&self.accept_label).on_press(Message::Open)
|
|
||||||
});
|
|
||||||
|
|
||||||
tab_column = tab_column.push(row);
|
|
||||||
|
|
||||||
let content: Element<_> = tab_column.into();
|
let content: Element<_> = tab_column.into();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -283,7 +283,8 @@ pub fn dialog_menu<'a>(
|
||||||
widget::button::icon(widget::icon::from_name(match tab.config.view {
|
widget::button::icon(widget::icon::from_name(match tab.config.view {
|
||||||
tab::View::Grid => "view-grid-symbolic",
|
tab::View::Grid => "view-grid-symbolic",
|
||||||
tab::View::List => "view-list-symbolic",
|
tab::View::List => "view-list-symbolic",
|
||||||
})),
|
}))
|
||||||
|
.padding(8),
|
||||||
menu::items(
|
menu::items(
|
||||||
key_binds,
|
key_binds,
|
||||||
vec![
|
vec![
|
||||||
|
|
@ -305,7 +306,8 @@ pub fn dialog_menu<'a>(
|
||||||
"view-sort-ascending-symbolic"
|
"view-sort-ascending-symbolic"
|
||||||
} else {
|
} else {
|
||||||
"view-sort-descending-symbolic"
|
"view-sort-descending-symbolic"
|
||||||
})),
|
}))
|
||||||
|
.padding(8),
|
||||||
menu::items(
|
menu::items(
|
||||||
key_binds,
|
key_binds,
|
||||||
vec![
|
vec![
|
||||||
|
|
|
||||||
362
src/tab.rs
362
src/tab.rs
|
|
@ -818,6 +818,8 @@ pub enum Command {
|
||||||
OpenInNewWindow(PathBuf),
|
OpenInNewWindow(PathBuf),
|
||||||
Preview(PreviewKind, Duration),
|
Preview(PreviewKind, Duration),
|
||||||
PreviewCancel,
|
PreviewCancel,
|
||||||
|
WindowDrag,
|
||||||
|
WindowToggleMaximize,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
|
|
@ -837,6 +839,9 @@ pub enum Message {
|
||||||
EditLocation(Option<Location>),
|
EditLocation(Option<Location>),
|
||||||
OpenInNewTab(PathBuf),
|
OpenInNewTab(PathBuf),
|
||||||
EmptyTrash,
|
EmptyTrash,
|
||||||
|
Gallery(bool),
|
||||||
|
GalleryPrevious,
|
||||||
|
GalleryNext,
|
||||||
GoNext,
|
GoNext,
|
||||||
GoPrevious,
|
GoPrevious,
|
||||||
ItemDown,
|
ItemDown,
|
||||||
|
|
@ -845,7 +850,7 @@ pub enum Message {
|
||||||
ItemUp,
|
ItemUp,
|
||||||
Location(Location),
|
Location(Location),
|
||||||
LocationUp,
|
LocationUp,
|
||||||
Open,
|
Open(Option<PathBuf>),
|
||||||
RightClick(Option<usize>),
|
RightClick(Option<usize>),
|
||||||
MiddleClick(usize),
|
MiddleClick(usize),
|
||||||
Scroll(Viewport),
|
Scroll(Viewport),
|
||||||
|
|
@ -861,6 +866,8 @@ pub enum Message {
|
||||||
DndHover(Location),
|
DndHover(Location),
|
||||||
DndEnter(Location),
|
DndEnter(Location),
|
||||||
DndLeave(Location),
|
DndLeave(Location),
|
||||||
|
WindowDrag,
|
||||||
|
WindowToggleMaximize,
|
||||||
ZoomDefault,
|
ZoomDefault,
|
||||||
ZoomIn,
|
ZoomIn,
|
||||||
ZoomOut,
|
ZoomOut,
|
||||||
|
|
@ -916,7 +923,7 @@ impl ItemMetadata {
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub enum ItemThumbnail {
|
pub enum ItemThumbnail {
|
||||||
NotImage,
|
NotImage,
|
||||||
Rgba(image::RgbaImage),
|
Rgba(image::RgbaImage, (u32, u32)),
|
||||||
Svg,
|
Svg,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -962,11 +969,9 @@ impl Item {
|
||||||
.unwrap_or(&ItemThumbnail::NotImage)
|
.unwrap_or(&ItemThumbnail::NotImage)
|
||||||
{
|
{
|
||||||
ItemThumbnail::NotImage => icon,
|
ItemThumbnail::NotImage => icon,
|
||||||
ItemThumbnail::Rgba(_) => {
|
ItemThumbnail::Rgba(_, _) => {
|
||||||
if let Some(Location::Path(path)) = &self.location_opt {
|
if let Some(Location::Path(path)) = &self.location_opt {
|
||||||
widget::image::viewer(widget::image::Handle::from_path(path))
|
widget::image(widget::image::Handle::from_path(path)).into()
|
||||||
.min_scale(1.0)
|
|
||||||
.into()
|
|
||||||
} else {
|
} else {
|
||||||
icon
|
icon
|
||||||
}
|
}
|
||||||
|
|
@ -1025,10 +1030,41 @@ impl Item {
|
||||||
column.into()
|
column.into()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn property_view(&self, sizes: IconSizes) -> Element<'static, app::Message> {
|
pub fn preview_view(&self, sizes: IconSizes) -> Element<'static, app::Message> {
|
||||||
let cosmic_theme::Spacing { space_xxxs, .. } = theme::active().cosmic().spacing;
|
let cosmic_theme::Spacing {
|
||||||
|
space_xxxs,
|
||||||
|
space_xxs,
|
||||||
|
space_m,
|
||||||
|
..
|
||||||
|
} = theme::active().cosmic().spacing;
|
||||||
|
|
||||||
let mut column = widget::column().spacing(space_xxxs);
|
let mut column = widget::column().spacing(space_m);
|
||||||
|
|
||||||
|
let mut row = widget::row::with_capacity(3).spacing(space_xxs);
|
||||||
|
row = row.push(
|
||||||
|
widget::button::icon(widget::icon::from_name("go-previous-symbolic"))
|
||||||
|
.on_press(app::Message::TabMessage(None, Message::ItemLeft)),
|
||||||
|
);
|
||||||
|
row = row.push(
|
||||||
|
widget::button::icon(widget::icon::from_name("go-next-symbolic"))
|
||||||
|
.on_press(app::Message::TabMessage(None, Message::ItemRight)),
|
||||||
|
);
|
||||||
|
match self
|
||||||
|
.thumbnail_opt
|
||||||
|
.as_ref()
|
||||||
|
.unwrap_or(&ItemThumbnail::NotImage)
|
||||||
|
{
|
||||||
|
ItemThumbnail::NotImage => {}
|
||||||
|
ItemThumbnail::Rgba(_, _) | ItemThumbnail::Svg => {
|
||||||
|
if let Some(path) = self.path_opt() {
|
||||||
|
row = row.push(
|
||||||
|
widget::button::icon(widget::icon::from_name("view-fullscreen-symbolic"))
|
||||||
|
.on_press(app::Message::TabMessage(None, Message::Gallery(true))),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
column = column.push(row);
|
||||||
|
|
||||||
column = column.push(widget::row::with_children(vec![
|
column = column.push(widget::row::with_children(vec![
|
||||||
widget::horizontal_space(Length::Fill).into(),
|
widget::horizontal_space(Length::Fill).into(),
|
||||||
|
|
@ -1036,79 +1072,97 @@ impl Item {
|
||||||
widget::horizontal_space(Length::Fill).into(),
|
widget::horizontal_space(Length::Fill).into(),
|
||||||
]));
|
]));
|
||||||
|
|
||||||
column = column.push(widget::text::heading(self.name.clone()));
|
let mut details = widget::column().spacing(space_xxxs);
|
||||||
|
details = details.push(widget::text::heading(self.name.clone()));
|
||||||
column = column.push(widget::text(format!("Type: {}", self.mime)));
|
details = details.push(widget::text(format!("Type: {}", self.mime)));
|
||||||
|
let mut settings = Vec::new();
|
||||||
//TODO: translate!
|
//TODO: translate!
|
||||||
//TODO: correct display of folder size?
|
//TODO: correct display of folder size?
|
||||||
match &self.metadata {
|
match &self.metadata {
|
||||||
ItemMetadata::Path { metadata, children } => {
|
ItemMetadata::Path { metadata, children } => {
|
||||||
if metadata.is_dir() {
|
if metadata.is_dir() {
|
||||||
column = column.push(widget::text(format!("Items: {}", children)));
|
details = details.push(widget::text(format!("Items: {}", children)));
|
||||||
} else {
|
} else {
|
||||||
column = column.push(widget::text(format!(
|
details = details.push(widget::text(format!(
|
||||||
"Size: {}",
|
"Size: {}",
|
||||||
format_size(metadata.len())
|
format_size(metadata.len())
|
||||||
)));
|
)));
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Ok(time) = metadata.created() {
|
if let Ok(time) = metadata.created() {
|
||||||
column = column.push(widget::text(format!("Created: {}", format_time(time))));
|
details = details.push(widget::text(format!("Created: {}", format_time(time))));
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Ok(time) = metadata.modified() {
|
if let Ok(time) = metadata.modified() {
|
||||||
column = column.push(widget::text(format!("Modified: {}", format_time(time))));
|
details =
|
||||||
|
details.push(widget::text(format!("Modified: {}", format_time(time))));
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Ok(time) = metadata.accessed() {
|
if let Ok(time) = metadata.accessed() {
|
||||||
column = column.push(widget::text(format!("Accessed: {}", format_time(time))));
|
details =
|
||||||
|
details.push(widget::text(format!("Accessed: {}", format_time(time))));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(not(target_os = "windows"))]
|
#[cfg(not(target_os = "windows"))]
|
||||||
{
|
{
|
||||||
column = column.push(
|
settings.push(
|
||||||
widget::Row::new()
|
widget::settings::item::builder(format_permissions_owner(
|
||||||
.push(widget::text(format!("{}:", fl!("owner"))))
|
metadata,
|
||||||
.push(widget::text(format_permissions_owner(
|
PermissionOwner::Owner,
|
||||||
metadata,
|
))
|
||||||
PermissionOwner::Owner,
|
.description(fl!("owner"))
|
||||||
)))
|
.control(widget::text(format_permissions(
|
||||||
.push(widget::text(format!(
|
metadata,
|
||||||
"({})",
|
PermissionOwner::Owner,
|
||||||
format_permissions(metadata, PermissionOwner::Owner,)
|
))),
|
||||||
)))
|
|
||||||
.spacing(10),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
column = column.push(
|
settings.push(
|
||||||
widget::Row::new()
|
widget::settings::item::builder(format_permissions_owner(
|
||||||
.push(widget::text(format!("{}:", fl!("group"))))
|
metadata,
|
||||||
.push(widget::text(format_permissions_owner(
|
PermissionOwner::Group,
|
||||||
metadata,
|
))
|
||||||
PermissionOwner::Group,
|
.description(fl!("group"))
|
||||||
)))
|
.control(widget::text(format_permissions(
|
||||||
.push(widget::text(format!(
|
metadata,
|
||||||
"({})",
|
PermissionOwner::Group,
|
||||||
format_permissions(metadata, PermissionOwner::Group,)
|
))),
|
||||||
)))
|
|
||||||
.spacing(10),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
column = column.push(
|
settings.push(widget::settings::item::builder(fl!("other")).control(
|
||||||
widget::Row::new()
|
widget::text(format_permissions(metadata, PermissionOwner::Other)),
|
||||||
.push(widget::text(format!("{}", fl!("other"))))
|
));
|
||||||
.push(widget::text(format!(
|
|
||||||
"({})",
|
|
||||||
format_permissions(metadata, PermissionOwner::Other,)
|
|
||||||
)))
|
|
||||||
.spacing(10),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
//TODO: other metadata types
|
//TODO: other metadata types
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
match self
|
||||||
|
.thumbnail_opt
|
||||||
|
.as_ref()
|
||||||
|
.unwrap_or(&ItemThumbnail::NotImage)
|
||||||
|
{
|
||||||
|
ItemThumbnail::Rgba(_, (width, height)) => {
|
||||||
|
details = details.push(widget::text(format!("{}x{}", width, height)));
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
column = column.push(details);
|
||||||
|
|
||||||
|
if let Some(path) = self.path_opt() {
|
||||||
|
column = column.push(widget::button::standard(fl!("open")).on_press(
|
||||||
|
app::Message::TabMessage(None, Message::Open(Some(path.to_path_buf()))),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
if !settings.is_empty() {
|
||||||
|
let mut section = widget::settings::section();
|
||||||
|
for setting in settings {
|
||||||
|
section = section.add(setting);
|
||||||
|
}
|
||||||
|
column = column.push(section);
|
||||||
|
}
|
||||||
|
|
||||||
column.into()
|
column.into()
|
||||||
}
|
}
|
||||||
|
|
@ -1222,6 +1276,7 @@ pub struct Tab {
|
||||||
pub history_i: usize,
|
pub history_i: usize,
|
||||||
pub history: Vec<Location>,
|
pub history: Vec<Location>,
|
||||||
pub config: TabConfig,
|
pub config: TabConfig,
|
||||||
|
pub gallery: bool,
|
||||||
pub(crate) items_opt: Option<Vec<Item>>,
|
pub(crate) items_opt: Option<Vec<Item>>,
|
||||||
pub dnd_hovered: Option<(Location, Instant)>,
|
pub dnd_hovered: Option<(Location, Instant)>,
|
||||||
scrollable_id: widget::Id,
|
scrollable_id: widget::Id,
|
||||||
|
|
@ -1269,6 +1324,7 @@ impl Tab {
|
||||||
history_i: 0,
|
history_i: 0,
|
||||||
history,
|
history,
|
||||||
config,
|
config,
|
||||||
|
gallery: false,
|
||||||
items_opt: None,
|
items_opt: None,
|
||||||
scrollable_id: widget::Id::unique(),
|
scrollable_id: widget::Id::unique(),
|
||||||
select_focus: None,
|
select_focus: None,
|
||||||
|
|
@ -1851,6 +1907,48 @@ impl Tab {
|
||||||
Message::EmptyTrash => {
|
Message::EmptyTrash => {
|
||||||
commands.push(Command::EmptyTrash);
|
commands.push(Command::EmptyTrash);
|
||||||
}
|
}
|
||||||
|
Message::Gallery(gallery) => {
|
||||||
|
self.gallery = gallery;
|
||||||
|
}
|
||||||
|
Message::GalleryPrevious | Message::GalleryNext => {
|
||||||
|
let mut pos_opt = None;
|
||||||
|
if let Some(mut indices) = self.column_sort() {
|
||||||
|
if matches!(message, Message::GalleryPrevious) {
|
||||||
|
indices.reverse();
|
||||||
|
}
|
||||||
|
let mut found = false;
|
||||||
|
for (index, item) in indices {
|
||||||
|
if self.select_focus == None {
|
||||||
|
found = true;
|
||||||
|
}
|
||||||
|
if self.select_focus == Some(index) {
|
||||||
|
found = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if found {
|
||||||
|
if item.mime.type_() == mime::IMAGE {
|
||||||
|
pos_opt = item.pos_opt.get();
|
||||||
|
if pos_opt.is_some() {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let Some((row, col)) = pos_opt {
|
||||||
|
// Should mod_shift be available?
|
||||||
|
self.select_position(row, col, mod_shift);
|
||||||
|
}
|
||||||
|
if let Some(offset) = self.select_focus_scroll() {
|
||||||
|
commands.push(Command::Iced(scrollable::scroll_to(
|
||||||
|
self.scrollable_id.clone(),
|
||||||
|
offset,
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
if let Some(id) = self.select_focus_id() {
|
||||||
|
commands.push(Command::Iced(widget::button::focus(id)));
|
||||||
|
}
|
||||||
|
}
|
||||||
Message::GoNext => {
|
Message::GoNext => {
|
||||||
if let Some(history_i) = self.history_i.checked_add(1) {
|
if let Some(history_i) = self.history_i.checked_add(1) {
|
||||||
if let Some(location) = self.history.get(history_i) {
|
if let Some(location) = self.history.get(history_i) {
|
||||||
|
|
@ -2031,21 +2129,32 @@ impl Tab {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Message::Open => {
|
Message::Open(path_opt) => {
|
||||||
if let Some(ref mut items) = self.items_opt {
|
match path_opt {
|
||||||
for item in items.iter() {
|
Some(path) => {
|
||||||
if item.selected {
|
if path.is_dir() {
|
||||||
if let Some(location) = &item.location_opt {
|
cd = Some(Location::Path(path));
|
||||||
if item.metadata.is_dir() {
|
} else {
|
||||||
//TODO: allow opening multiple tabs?
|
commands.push(Command::OpenFile(path));
|
||||||
cd = Some(location.clone());
|
}
|
||||||
} else {
|
}
|
||||||
if let Location::Path(path) = location {
|
None => {
|
||||||
commands.push(Command::OpenFile(path.clone()));
|
if let Some(ref mut items) = self.items_opt {
|
||||||
|
for item in items.iter() {
|
||||||
|
if item.selected {
|
||||||
|
if let Some(location) = &item.location_opt {
|
||||||
|
if item.metadata.is_dir() {
|
||||||
|
//TODO: allow opening multiple tabs?
|
||||||
|
cd = Some(location.clone());
|
||||||
|
} else {
|
||||||
|
if let Location::Path(path) = location {
|
||||||
|
commands.push(Command::OpenFile(path.clone()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
//TODO: open properties?
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
//TODO: open properties?
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -2121,7 +2230,7 @@ impl Tab {
|
||||||
let location = Location::Path(path);
|
let location = Location::Path(path);
|
||||||
for item in items.iter_mut() {
|
for item in items.iter_mut() {
|
||||||
if item.location_opt.as_ref() == Some(&location) {
|
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(
|
||||||
rgba.width(),
|
rgba.width(),
|
||||||
|
|
@ -2210,6 +2319,12 @@ impl Tab {
|
||||||
self.dnd_hovered = None;
|
self.dnd_hovered = None;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Message::WindowDrag => {
|
||||||
|
commands.push(Command::WindowDrag);
|
||||||
|
}
|
||||||
|
Message::WindowToggleMaximize => {
|
||||||
|
commands.push(Command::WindowToggleMaximize);
|
||||||
|
}
|
||||||
Message::ZoomDefault => match self.config.view {
|
Message::ZoomDefault => match self.config.view {
|
||||||
View::List => self.config.icon_sizes.list = 100.try_into().unwrap(),
|
View::List => self.config.icon_sizes.list = 100.try_into().unwrap(),
|
||||||
View::Grid => self.config.icon_sizes.grid = 100.try_into().unwrap(),
|
View::Grid => self.config.icon_sizes.grid = 100.try_into().unwrap(),
|
||||||
|
|
@ -2436,6 +2551,116 @@ impl Tab {
|
||||||
.into()
|
.into()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn gallery_view(&self) -> Element<Message> {
|
||||||
|
let cosmic_theme::Spacing {
|
||||||
|
space_xxs,
|
||||||
|
space_xs,
|
||||||
|
space_m,
|
||||||
|
..
|
||||||
|
} = theme::active().cosmic().spacing;
|
||||||
|
|
||||||
|
//TODO: display error messages when image not found?
|
||||||
|
let mut name_opt = None;
|
||||||
|
let mut image_opt: Option<Element<Message>> = None;
|
||||||
|
if let Some(index) = self.select_focus {
|
||||||
|
if let Some(items) = &self.items_opt {
|
||||||
|
if let Some(item) = items.get(index) {
|
||||||
|
name_opt = Some(widget::text::heading(&item.display_name));
|
||||||
|
match item
|
||||||
|
.thumbnail_opt
|
||||||
|
.as_ref()
|
||||||
|
.unwrap_or(&ItemThumbnail::NotImage)
|
||||||
|
{
|
||||||
|
ItemThumbnail::NotImage => {}
|
||||||
|
ItemThumbnail::Rgba(_, _) => {
|
||||||
|
if let Some(path) = item.path_opt() {
|
||||||
|
image_opt = Some(
|
||||||
|
//TODO: use widget::image::viewer, when its zoom can be reset
|
||||||
|
widget::image(widget::image::Handle::from_path(path))
|
||||||
|
.width(Length::Fill)
|
||||||
|
.height(Length::Fill)
|
||||||
|
.into(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ItemThumbnail::Svg => {
|
||||||
|
if let Some(path) = item.path_opt() {
|
||||||
|
image_opt = Some(
|
||||||
|
widget::Svg::from_path(path)
|
||||||
|
.width(Length::Fill)
|
||||||
|
.height(Length::Fill)
|
||||||
|
.into(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut column = widget::column::with_capacity(2);
|
||||||
|
column = column.push(widget::vertical_space(Length::Fixed(space_m.into())));
|
||||||
|
{
|
||||||
|
let mut row = widget::row::with_capacity(5).align_items(Alignment::Center);
|
||||||
|
row = row.push(widget::horizontal_space(Length::Fill));
|
||||||
|
if let Some(name) = name_opt {
|
||||||
|
row = row.push(name);
|
||||||
|
}
|
||||||
|
row = row.push(widget::horizontal_space(Length::Fill));
|
||||||
|
row = row.push(
|
||||||
|
widget::button::icon(widget::icon::from_name("window-close-symbolic"))
|
||||||
|
.style(theme::Button::Standard)
|
||||||
|
.on_press(Message::Gallery(false)),
|
||||||
|
);
|
||||||
|
row = row.push(widget::horizontal_space(Length::Fixed(space_m.into())));
|
||||||
|
// This mouse area provides window drag while the header bar is hidden
|
||||||
|
let mouse_area = mouse_area::MouseArea::new(row)
|
||||||
|
.on_drag(|_| Message::WindowDrag)
|
||||||
|
.on_double_click(|_| Message::WindowToggleMaximize);
|
||||||
|
column = column.push(mouse_area);
|
||||||
|
}
|
||||||
|
{
|
||||||
|
let mut row = widget::row::with_capacity(7).align_items(Alignment::Center);
|
||||||
|
row = row.push(widget::horizontal_space(Length::Fixed(space_m.into())));
|
||||||
|
row = row.push(
|
||||||
|
widget::button::icon(widget::icon::from_name("go-previous-symbolic"))
|
||||||
|
.padding(space_xs)
|
||||||
|
.style(theme::Button::Standard)
|
||||||
|
.on_press(Message::GalleryPrevious),
|
||||||
|
);
|
||||||
|
row = row.push(widget::horizontal_space(Length::Fixed(space_xxs.into())));
|
||||||
|
if let Some(image) = image_opt {
|
||||||
|
row = row.push(image);
|
||||||
|
} else {
|
||||||
|
//TODO: what to do when no image?
|
||||||
|
row = row.push(widget::Space::new(Length::Fill, Length::Fill));
|
||||||
|
}
|
||||||
|
row = row.push(widget::horizontal_space(Length::Fixed(space_xxs.into())));
|
||||||
|
row = row.push(
|
||||||
|
widget::button::icon(widget::icon::from_name("go-next-symbolic"))
|
||||||
|
.padding(space_xs)
|
||||||
|
.style(theme::Button::Standard)
|
||||||
|
.on_press(Message::GalleryNext),
|
||||||
|
);
|
||||||
|
row = row.push(widget::horizontal_space(Length::Fixed(space_m.into())));
|
||||||
|
column = column.push(row);
|
||||||
|
}
|
||||||
|
|
||||||
|
widget::container(column)
|
||||||
|
.width(Length::Fill)
|
||||||
|
.height(Length::Fill)
|
||||||
|
.style(theme::Container::Custom(Box::new(|theme| {
|
||||||
|
let cosmic = theme.cosmic();
|
||||||
|
let mut bg = cosmic.bg_color();
|
||||||
|
bg.alpha = 0.75;
|
||||||
|
widget::container::Appearance {
|
||||||
|
background: Some(Color::from(bg).into()),
|
||||||
|
..Default::default()
|
||||||
|
}
|
||||||
|
})))
|
||||||
|
.into()
|
||||||
|
}
|
||||||
|
|
||||||
pub fn location_view(&self) -> Element<Message> {
|
pub fn location_view(&self) -> Element<Message> {
|
||||||
//TODO: responsiveness is done in a hacky way, potentially move this to a custom widget?
|
//TODO: responsiveness is done in a hacky way, potentially move this to a custom widget?
|
||||||
fn text_width<'a>(
|
fn text_width<'a>(
|
||||||
|
|
@ -2533,7 +2758,7 @@ impl Tab {
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
//TODO: make it possible to resize with the mouse
|
//TODO: make it possible to resize with the mouse
|
||||||
return crate::mouse_area::MouseArea::new(row)
|
return mouse_area::MouseArea::new(row)
|
||||||
.on_press(move |_point_opt| Message::ToggleSort(msg))
|
.on_press(move |_point_opt| Message::ToggleSort(msg))
|
||||||
.into();
|
.into();
|
||||||
};
|
};
|
||||||
|
|
@ -3644,7 +3869,10 @@ impl Tab {
|
||||||
(ICON_SIZE_GRID * ICON_SCALE_MAX) as u32;
|
(ICON_SIZE_GRID * ICON_SCALE_MAX) as u32;
|
||||||
let thumbnail =
|
let thumbnail =
|
||||||
image.thumbnail(thumbnail_size, thumbnail_size);
|
image.thumbnail(thumbnail_size, thumbnail_size);
|
||||||
ItemThumbnail::Rgba(thumbnail.to_rgba8())
|
ItemThumbnail::Rgba(
|
||||||
|
thumbnail.to_rgba8(),
|
||||||
|
(image.width(), image.height()),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
log::warn!("failed to decode {:?}: {}", path, err);
|
log::warn!("failed to decode {:?}: {}", path, err);
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue