Merge branch 'pop-os:master' into master

This commit is contained in:
David Carvalho 2024-09-24 17:47:17 -03:00 committed by GitHub
commit 9fed3c1011
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 541 additions and 176 deletions

60
Cargo.lock generated
View file

@ -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",

View file

@ -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(),
) )

View file

@ -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();

View file

@ -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![

View file

@ -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);