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]]
|
||||
name = "cfg-expr"
|
||||
version = "0.16.0"
|
||||
version = "0.17.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "345c78335be0624ed29012dc10c49102196c6882c12dde65d9f35b02da2aada8"
|
||||
checksum = "d0890061c4d3223e7267f3bad2ec40b997d64faac1c2815a4a9d95018e2b9e9c"
|
||||
dependencies = [
|
||||
"smallvec",
|
||||
"target-lexicon",
|
||||
|
|
@ -1212,7 +1212,7 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "cosmic-config"
|
||||
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 = [
|
||||
"atomicwrites",
|
||||
"cosmic-config-derive",
|
||||
|
|
@ -1231,7 +1231,7 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "cosmic-config-derive"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/pop-os/libcosmic.git#ddb678ca6966dfdf10911feac5e5ac02d3b2b97e"
|
||||
source = "git+https://github.com/pop-os/libcosmic.git#701638009df09a254b7d077ddc4d1076cd87a147"
|
||||
dependencies = [
|
||||
"quote",
|
||||
"syn 1.0.109",
|
||||
|
|
@ -1317,7 +1317,7 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "cosmic-text"
|
||||
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 = [
|
||||
"bitflags 2.6.0",
|
||||
"fontdb",
|
||||
|
|
@ -1340,7 +1340,7 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "cosmic-theme"
|
||||
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 = [
|
||||
"almost",
|
||||
"cosmic-config",
|
||||
|
|
@ -2783,7 +2783,7 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "iced"
|
||||
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 = [
|
||||
"dnd",
|
||||
"iced_accessibility",
|
||||
|
|
@ -2802,7 +2802,7 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "iced_accessibility"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/pop-os/libcosmic.git#ddb678ca6966dfdf10911feac5e5ac02d3b2b97e"
|
||||
source = "git+https://github.com/pop-os/libcosmic.git#701638009df09a254b7d077ddc4d1076cd87a147"
|
||||
dependencies = [
|
||||
"accesskit",
|
||||
"accesskit_unix",
|
||||
|
|
@ -2812,7 +2812,7 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "iced_core"
|
||||
version = "0.12.0"
|
||||
source = "git+https://github.com/pop-os/libcosmic.git#ddb678ca6966dfdf10911feac5e5ac02d3b2b97e"
|
||||
source = "git+https://github.com/pop-os/libcosmic.git#701638009df09a254b7d077ddc4d1076cd87a147"
|
||||
dependencies = [
|
||||
"bitflags 2.6.0",
|
||||
"dnd",
|
||||
|
|
@ -2834,7 +2834,7 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "iced_futures"
|
||||
version = "0.12.0"
|
||||
source = "git+https://github.com/pop-os/libcosmic.git#ddb678ca6966dfdf10911feac5e5ac02d3b2b97e"
|
||||
source = "git+https://github.com/pop-os/libcosmic.git#701638009df09a254b7d077ddc4d1076cd87a147"
|
||||
dependencies = [
|
||||
"futures",
|
||||
"iced_core",
|
||||
|
|
@ -2847,7 +2847,7 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "iced_graphics"
|
||||
version = "0.12.0"
|
||||
source = "git+https://github.com/pop-os/libcosmic.git#ddb678ca6966dfdf10911feac5e5ac02d3b2b97e"
|
||||
source = "git+https://github.com/pop-os/libcosmic.git#701638009df09a254b7d077ddc4d1076cd87a147"
|
||||
dependencies = [
|
||||
"bitflags 2.6.0",
|
||||
"bytemuck",
|
||||
|
|
@ -2871,7 +2871,7 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "iced_renderer"
|
||||
version = "0.12.0"
|
||||
source = "git+https://github.com/pop-os/libcosmic.git#ddb678ca6966dfdf10911feac5e5ac02d3b2b97e"
|
||||
source = "git+https://github.com/pop-os/libcosmic.git#701638009df09a254b7d077ddc4d1076cd87a147"
|
||||
dependencies = [
|
||||
"iced_graphics",
|
||||
"iced_tiny_skia",
|
||||
|
|
@ -2883,7 +2883,7 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "iced_runtime"
|
||||
version = "0.12.0"
|
||||
source = "git+https://github.com/pop-os/libcosmic.git#ddb678ca6966dfdf10911feac5e5ac02d3b2b97e"
|
||||
source = "git+https://github.com/pop-os/libcosmic.git#701638009df09a254b7d077ddc4d1076cd87a147"
|
||||
dependencies = [
|
||||
"dnd",
|
||||
"iced_accessibility",
|
||||
|
|
@ -2897,7 +2897,7 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "iced_sctk"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/pop-os/libcosmic.git#ddb678ca6966dfdf10911feac5e5ac02d3b2b97e"
|
||||
source = "git+https://github.com/pop-os/libcosmic.git#701638009df09a254b7d077ddc4d1076cd87a147"
|
||||
dependencies = [
|
||||
"enum-repr",
|
||||
"float-cmp",
|
||||
|
|
@ -2924,7 +2924,7 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "iced_style"
|
||||
version = "0.12.0"
|
||||
source = "git+https://github.com/pop-os/libcosmic.git#ddb678ca6966dfdf10911feac5e5ac02d3b2b97e"
|
||||
source = "git+https://github.com/pop-os/libcosmic.git#701638009df09a254b7d077ddc4d1076cd87a147"
|
||||
dependencies = [
|
||||
"iced_core",
|
||||
"once_cell",
|
||||
|
|
@ -2934,7 +2934,7 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "iced_tiny_skia"
|
||||
version = "0.12.0"
|
||||
source = "git+https://github.com/pop-os/libcosmic.git#ddb678ca6966dfdf10911feac5e5ac02d3b2b97e"
|
||||
source = "git+https://github.com/pop-os/libcosmic.git#701638009df09a254b7d077ddc4d1076cd87a147"
|
||||
dependencies = [
|
||||
"bytemuck",
|
||||
"cosmic-text",
|
||||
|
|
@ -2951,7 +2951,7 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "iced_wgpu"
|
||||
version = "0.12.0"
|
||||
source = "git+https://github.com/pop-os/libcosmic.git#ddb678ca6966dfdf10911feac5e5ac02d3b2b97e"
|
||||
source = "git+https://github.com/pop-os/libcosmic.git#701638009df09a254b7d077ddc4d1076cd87a147"
|
||||
dependencies = [
|
||||
"as-raw-xcb-connection",
|
||||
"bitflags 2.6.0",
|
||||
|
|
@ -2980,7 +2980,7 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "iced_widget"
|
||||
version = "0.12.0"
|
||||
source = "git+https://github.com/pop-os/libcosmic.git#ddb678ca6966dfdf10911feac5e5ac02d3b2b97e"
|
||||
source = "git+https://github.com/pop-os/libcosmic.git#701638009df09a254b7d077ddc4d1076cd87a147"
|
||||
dependencies = [
|
||||
"dnd",
|
||||
"iced_accessibility",
|
||||
|
|
@ -2998,7 +2998,7 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "iced_winit"
|
||||
version = "0.12.0"
|
||||
source = "git+https://github.com/pop-os/libcosmic.git#ddb678ca6966dfdf10911feac5e5ac02d3b2b97e"
|
||||
source = "git+https://github.com/pop-os/libcosmic.git#701638009df09a254b7d077ddc4d1076cd87a147"
|
||||
dependencies = [
|
||||
"dnd",
|
||||
"iced_accessibility",
|
||||
|
|
@ -3507,14 +3507,14 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.158"
|
||||
version = "0.2.159"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d8adc4bb1803a324070e64a98ae98f38934d91957a99cfb3a43dcbc01bc56439"
|
||||
checksum = "561d97a539a36e26a9a5fad1ea11a3039a67714694aaa379433e580854bc3dc5"
|
||||
|
||||
[[package]]
|
||||
name = "libcosmic"
|
||||
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 = [
|
||||
"apply",
|
||||
"ashpd 0.9.1",
|
||||
|
|
@ -4563,9 +4563,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "pkg-config"
|
||||
version = "0.3.30"
|
||||
version = "0.3.31"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec"
|
||||
checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2"
|
||||
|
||||
[[package]]
|
||||
name = "png"
|
||||
|
|
@ -5591,9 +5591,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "system-deps"
|
||||
version = "7.0.2"
|
||||
version = "7.0.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "070a0a5e7da2d24be457809c4b3baa57a835fd2829ad8b86f9a049052fe71031"
|
||||
checksum = "66d23aaf9f331227789a99e8de4c91bf46703add012bdfd45fdecdfb2975a005"
|
||||
dependencies = [
|
||||
"cfg-expr",
|
||||
"heck 0.5.0",
|
||||
|
|
@ -5693,18 +5693,18 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "thiserror"
|
||||
version = "1.0.63"
|
||||
version = "1.0.64"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c0342370b38b6a11b6cc11d6a805569958d54cfa061a29969c3b5ce2ea405724"
|
||||
checksum = "d50af8abc119fb8bb6dbabcfa89656f46f84aa0ac7688088608076ad2b459a84"
|
||||
dependencies = [
|
||||
"thiserror-impl",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror-impl"
|
||||
version = "1.0.63"
|
||||
version = "1.0.64"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a4558b58466b9ad7ca0f102865eccc95938dca1a74a856f2b57b6629050da261"
|
||||
checksum = "08904e7672f5eb876eaaf87e0ce17857500934f4981c4a0ab2b4aa98baac7fc3"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
|
|
|
|||
36
src/app.rs
36
src/app.rs
|
|
@ -157,7 +157,7 @@ impl Action {
|
|||
Action::MoveToTrash => Message::MoveToTrash(entity_opt),
|
||||
Action::NewFile => Message::NewItem(entity_opt, false),
|
||||
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::OpenInNewWindow => Message::OpenInNewWindow(entity_opt),
|
||||
Action::OpenItemLocation => Message::OpenItemLocation(entity_opt),
|
||||
|
|
@ -342,7 +342,7 @@ pub enum ContextPage {
|
|||
}
|
||||
|
||||
impl ContextPage {
|
||||
fn title(&self) -> String {
|
||||
pub fn title(&self) -> String {
|
||||
match self {
|
||||
Self::About => String::new(),
|
||||
Self::EditHistory => fl!("edit-history"),
|
||||
|
|
@ -969,14 +969,14 @@ impl App {
|
|||
let entity = entity_opt.unwrap_or_else(|| self.tab_model.active());
|
||||
match kind {
|
||||
PreviewKind::Custom(PreviewItem(item)) => {
|
||||
children.push(item.property_view(IconSizes::default()));
|
||||
children.push(item.preview_view(IconSizes::default()));
|
||||
}
|
||||
PreviewKind::Location(location) => {
|
||||
if let Some(tab) = self.tab_model.data::<Tab>(entity) {
|
||||
if let Some(items) = tab.items_opt() {
|
||||
for item in items.iter() {
|
||||
if item.location_opt.as_ref() == Some(location) {
|
||||
children.push(item.property_view(tab.config.icon_sizes));
|
||||
children.push(item.preview_view(tab.config.icon_sizes));
|
||||
// Only show one property view to avoid issues like hangs when generating
|
||||
// preview images on thousands of files
|
||||
break;
|
||||
|
|
@ -990,7 +990,7 @@ impl App {
|
|||
if let Some(items) = tab.items_opt() {
|
||||
for item in items.iter() {
|
||||
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
|
||||
// preview images on thousands of files
|
||||
break;
|
||||
|
|
@ -1360,6 +1360,14 @@ impl Application for App {
|
|||
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
|
||||
// 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
|
||||
|
|
@ -2392,6 +2400,12 @@ impl Application for App {
|
|||
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()));
|
||||
}
|
||||
}
|
||||
}
|
||||
return Command::batch(commands);
|
||||
|
|
@ -2772,6 +2786,17 @@ impl Application for App {
|
|||
}
|
||||
|
||||
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() {
|
||||
Some(some) => some,
|
||||
None => return None,
|
||||
|
|
@ -3226,6 +3251,7 @@ impl Application for App {
|
|||
} else {
|
||||
elements.push(
|
||||
widget::button::icon(widget::icon::from_name("system-search-symbolic"))
|
||||
.padding(8)
|
||||
.on_press(Message::SearchActivate)
|
||||
.into(),
|
||||
)
|
||||
|
|
|
|||
253
src/dialog.rs
253
src/dialog.rs
|
|
@ -39,8 +39,8 @@ use std::{
|
|||
};
|
||||
|
||||
use crate::{
|
||||
app::{Action, Message as AppMessage},
|
||||
config::{Config, Favorite, TabConfig},
|
||||
app::{Action, ContextPage, Message as AppMessage, PreviewItem, PreviewKind},
|
||||
config::{Config, Favorite, IconSizes, TabConfig},
|
||||
fl, home_dir,
|
||||
localize::LANGUAGE_SORTER,
|
||||
menu,
|
||||
|
|
@ -315,6 +315,7 @@ enum Message {
|
|||
NotifyEvents(Vec<DebouncedEvent>),
|
||||
NotifyWatcher(WatcherWrapper),
|
||||
Open,
|
||||
Preview(PreviewKind, time::Duration),
|
||||
Save(bool),
|
||||
SearchActivate,
|
||||
SearchClear,
|
||||
|
|
@ -324,6 +325,18 @@ enum Message {
|
|||
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);
|
||||
|
||||
struct WatcherWrapper {
|
||||
|
|
@ -355,6 +368,7 @@ struct App {
|
|||
title: String,
|
||||
accept_label: String,
|
||||
choices: Vec<DialogChoice>,
|
||||
context_page: ContextPage,
|
||||
dialog_pages: VecDeque<DialogPage>,
|
||||
dialog_text_input: widget::Id,
|
||||
filters: Vec<DialogFilter>,
|
||||
|
|
@ -364,6 +378,7 @@ struct App {
|
|||
mounters: Mounters,
|
||||
mounter_items: HashMap<MounterKey, MounterItems>,
|
||||
nav_model: segmented_button::SingleSelectModel,
|
||||
preview_opt: Option<(PreviewKind, time::Instant)>,
|
||||
result_opt: Option<DialogResult>,
|
||||
search_active: bool,
|
||||
search_id: widget::Id,
|
||||
|
|
@ -374,6 +389,96 @@ struct 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> {
|
||||
let location = self.tab.location.clone();
|
||||
let mounters = self.mounters.clone();
|
||||
|
|
@ -606,6 +711,7 @@ impl Application for App {
|
|||
title,
|
||||
accept_label,
|
||||
choices: Vec::new(),
|
||||
context_page: ContextPage::Settings,
|
||||
dialog_pages: VecDeque::new(),
|
||||
dialog_text_input: widget::Id::unique(),
|
||||
filters: Vec::new(),
|
||||
|
|
@ -615,6 +721,7 @@ impl Application for App {
|
|||
mounters: mounters(),
|
||||
mounter_items: HashMap::new(),
|
||||
nav_model: segmented_button::ModelBuilder::default().build(),
|
||||
preview_opt: None,
|
||||
result_opt: None,
|
||||
search_active: false,
|
||||
search_id: widget::Id::unique(),
|
||||
|
|
@ -638,7 +745,33 @@ impl Application for App {
|
|||
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>> {
|
||||
//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() {
|
||||
Some(some) => some,
|
||||
None => return None,
|
||||
|
|
@ -752,15 +885,7 @@ impl Application for App {
|
|||
|
||||
elements.push(
|
||||
menu::dialog_menu(&self.tab, &self.key_binds)
|
||||
.map(|message| match message {
|
||||
AppMessage::TabMessage(_entity_opt, tab_message) => {
|
||||
Message::TabMessage(tab_message)
|
||||
}
|
||||
unsupported => {
|
||||
log::warn!("{unsupported:?} not supported in dialog mode");
|
||||
Message::None
|
||||
}
|
||||
})
|
||||
.map(Message::from)
|
||||
.into(),
|
||||
);
|
||||
|
||||
|
|
@ -823,6 +948,12 @@ impl Application for App {
|
|||
}
|
||||
|
||||
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 {
|
||||
// Close search if open
|
||||
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) => {
|
||||
if let DialogKind::SaveFile { filename } = &self.flags.kind {
|
||||
if !filename.is_empty() {
|
||||
|
|
@ -1176,14 +1318,9 @@ impl Application for App {
|
|||
let mut commands = Vec::new();
|
||||
for tab_command in tab_commands {
|
||||
match tab_command {
|
||||
tab::Command::Action(action) => match action.message() {
|
||||
AppMessage::TabMessage(_entity_opt, tab_message) => {
|
||||
commands.push(self.update(Message::TabMessage(tab_message)));
|
||||
}
|
||||
unsupported => {
|
||||
log::warn!("{unsupported:?} not supported in dialog mode");
|
||||
}
|
||||
},
|
||||
tab::Command::Action(action) => {
|
||||
commands.push(self.update(Message::from(action.message())));
|
||||
}
|
||||
tab::Command::ChangeLocation(_tab_title, _tab_path, _selection_path) => {
|
||||
commands
|
||||
.push(Command::batch([self.update_watcher(), self.rescan_tab()]));
|
||||
|
|
@ -1202,6 +1339,29 @@ impl Application for App {
|
|||
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 => {
|
||||
log::warn!("{unsupported:?} not supported in dialog mode");
|
||||
}
|
||||
|
|
@ -1282,9 +1442,8 @@ impl Application for App {
|
|||
|
||||
/// Creates a view after each update.
|
||||
fn view(&self) -> Element<Message> {
|
||||
let cosmic_theme::Spacing { space_xxs, .. } = theme::active().cosmic().spacing;
|
||||
|
||||
let mut tab_column = widget::column::with_capacity(2);
|
||||
|
||||
tab_column = tab_column.push(
|
||||
//TODO: key binds for dialog
|
||||
self.tab
|
||||
|
|
@ -1292,57 +1451,7 @@ impl Application for App {
|
|||
.map(move |message| Message::TabMessage(message)),
|
||||
);
|
||||
|
||||
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)
|
||||
});
|
||||
|
||||
tab_column = tab_column.push(row);
|
||||
tab_column = tab_column.push(self.button_row());
|
||||
|
||||
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 {
|
||||
tab::View::Grid => "view-grid-symbolic",
|
||||
tab::View::List => "view-list-symbolic",
|
||||
})),
|
||||
}))
|
||||
.padding(8),
|
||||
menu::items(
|
||||
key_binds,
|
||||
vec![
|
||||
|
|
@ -305,7 +306,8 @@ pub fn dialog_menu<'a>(
|
|||
"view-sort-ascending-symbolic"
|
||||
} else {
|
||||
"view-sort-descending-symbolic"
|
||||
})),
|
||||
}))
|
||||
.padding(8),
|
||||
menu::items(
|
||||
key_binds,
|
||||
vec![
|
||||
|
|
|
|||
362
src/tab.rs
362
src/tab.rs
|
|
@ -818,6 +818,8 @@ pub enum Command {
|
|||
OpenInNewWindow(PathBuf),
|
||||
Preview(PreviewKind, Duration),
|
||||
PreviewCancel,
|
||||
WindowDrag,
|
||||
WindowToggleMaximize,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
|
|
@ -837,6 +839,9 @@ pub enum Message {
|
|||
EditLocation(Option<Location>),
|
||||
OpenInNewTab(PathBuf),
|
||||
EmptyTrash,
|
||||
Gallery(bool),
|
||||
GalleryPrevious,
|
||||
GalleryNext,
|
||||
GoNext,
|
||||
GoPrevious,
|
||||
ItemDown,
|
||||
|
|
@ -845,7 +850,7 @@ pub enum Message {
|
|||
ItemUp,
|
||||
Location(Location),
|
||||
LocationUp,
|
||||
Open,
|
||||
Open(Option<PathBuf>),
|
||||
RightClick(Option<usize>),
|
||||
MiddleClick(usize),
|
||||
Scroll(Viewport),
|
||||
|
|
@ -861,6 +866,8 @@ pub enum Message {
|
|||
DndHover(Location),
|
||||
DndEnter(Location),
|
||||
DndLeave(Location),
|
||||
WindowDrag,
|
||||
WindowToggleMaximize,
|
||||
ZoomDefault,
|
||||
ZoomIn,
|
||||
ZoomOut,
|
||||
|
|
@ -916,7 +923,7 @@ impl ItemMetadata {
|
|||
#[derive(Clone, Debug)]
|
||||
pub enum ItemThumbnail {
|
||||
NotImage,
|
||||
Rgba(image::RgbaImage),
|
||||
Rgba(image::RgbaImage, (u32, u32)),
|
||||
Svg,
|
||||
}
|
||||
|
||||
|
|
@ -962,11 +969,9 @@ impl Item {
|
|||
.unwrap_or(&ItemThumbnail::NotImage)
|
||||
{
|
||||
ItemThumbnail::NotImage => icon,
|
||||
ItemThumbnail::Rgba(_) => {
|
||||
ItemThumbnail::Rgba(_, _) => {
|
||||
if let Some(Location::Path(path)) = &self.location_opt {
|
||||
widget::image::viewer(widget::image::Handle::from_path(path))
|
||||
.min_scale(1.0)
|
||||
.into()
|
||||
widget::image(widget::image::Handle::from_path(path)).into()
|
||||
} else {
|
||||
icon
|
||||
}
|
||||
|
|
@ -1025,10 +1030,41 @@ impl Item {
|
|||
column.into()
|
||||
}
|
||||
|
||||
pub fn property_view(&self, sizes: IconSizes) -> Element<'static, app::Message> {
|
||||
let cosmic_theme::Spacing { space_xxxs, .. } = theme::active().cosmic().spacing;
|
||||
pub fn preview_view(&self, sizes: IconSizes) -> Element<'static, app::Message> {
|
||||
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![
|
||||
widget::horizontal_space(Length::Fill).into(),
|
||||
|
|
@ -1036,79 +1072,97 @@ impl Item {
|
|||
widget::horizontal_space(Length::Fill).into(),
|
||||
]));
|
||||
|
||||
column = column.push(widget::text::heading(self.name.clone()));
|
||||
|
||||
column = column.push(widget::text(format!("Type: {}", self.mime)));
|
||||
|
||||
let mut details = widget::column().spacing(space_xxxs);
|
||||
details = details.push(widget::text::heading(self.name.clone()));
|
||||
details = details.push(widget::text(format!("Type: {}", self.mime)));
|
||||
let mut settings = Vec::new();
|
||||
//TODO: translate!
|
||||
//TODO: correct display of folder size?
|
||||
match &self.metadata {
|
||||
ItemMetadata::Path { metadata, children } => {
|
||||
if metadata.is_dir() {
|
||||
column = column.push(widget::text(format!("Items: {}", children)));
|
||||
details = details.push(widget::text(format!("Items: {}", children)));
|
||||
} else {
|
||||
column = column.push(widget::text(format!(
|
||||
details = details.push(widget::text(format!(
|
||||
"Size: {}",
|
||||
format_size(metadata.len())
|
||||
)));
|
||||
}
|
||||
|
||||
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() {
|
||||
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() {
|
||||
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"))]
|
||||
{
|
||||
column = column.push(
|
||||
widget::Row::new()
|
||||
.push(widget::text(format!("{}:", fl!("owner"))))
|
||||
.push(widget::text(format_permissions_owner(
|
||||
metadata,
|
||||
PermissionOwner::Owner,
|
||||
)))
|
||||
.push(widget::text(format!(
|
||||
"({})",
|
||||
format_permissions(metadata, PermissionOwner::Owner,)
|
||||
)))
|
||||
.spacing(10),
|
||||
settings.push(
|
||||
widget::settings::item::builder(format_permissions_owner(
|
||||
metadata,
|
||||
PermissionOwner::Owner,
|
||||
))
|
||||
.description(fl!("owner"))
|
||||
.control(widget::text(format_permissions(
|
||||
metadata,
|
||||
PermissionOwner::Owner,
|
||||
))),
|
||||
);
|
||||
|
||||
column = column.push(
|
||||
widget::Row::new()
|
||||
.push(widget::text(format!("{}:", fl!("group"))))
|
||||
.push(widget::text(format_permissions_owner(
|
||||
metadata,
|
||||
PermissionOwner::Group,
|
||||
)))
|
||||
.push(widget::text(format!(
|
||||
"({})",
|
||||
format_permissions(metadata, PermissionOwner::Group,)
|
||||
)))
|
||||
.spacing(10),
|
||||
settings.push(
|
||||
widget::settings::item::builder(format_permissions_owner(
|
||||
metadata,
|
||||
PermissionOwner::Group,
|
||||
))
|
||||
.description(fl!("group"))
|
||||
.control(widget::text(format_permissions(
|
||||
metadata,
|
||||
PermissionOwner::Group,
|
||||
))),
|
||||
);
|
||||
|
||||
column = column.push(
|
||||
widget::Row::new()
|
||||
.push(widget::text(format!("{}", fl!("other"))))
|
||||
.push(widget::text(format!(
|
||||
"({})",
|
||||
format_permissions(metadata, PermissionOwner::Other,)
|
||||
)))
|
||||
.spacing(10),
|
||||
);
|
||||
settings.push(widget::settings::item::builder(fl!("other")).control(
|
||||
widget::text(format_permissions(metadata, PermissionOwner::Other)),
|
||||
));
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
//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()
|
||||
}
|
||||
|
|
@ -1222,6 +1276,7 @@ pub struct Tab {
|
|||
pub history_i: usize,
|
||||
pub history: Vec<Location>,
|
||||
pub config: TabConfig,
|
||||
pub gallery: bool,
|
||||
pub(crate) items_opt: Option<Vec<Item>>,
|
||||
pub dnd_hovered: Option<(Location, Instant)>,
|
||||
scrollable_id: widget::Id,
|
||||
|
|
@ -1269,6 +1324,7 @@ impl Tab {
|
|||
history_i: 0,
|
||||
history,
|
||||
config,
|
||||
gallery: false,
|
||||
items_opt: None,
|
||||
scrollable_id: widget::Id::unique(),
|
||||
select_focus: None,
|
||||
|
|
@ -1851,6 +1907,48 @@ impl Tab {
|
|||
Message::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 => {
|
||||
if let Some(history_i) = self.history_i.checked_add(1) {
|
||||
if let Some(location) = self.history.get(history_i) {
|
||||
|
|
@ -2031,21 +2129,32 @@ impl Tab {
|
|||
}
|
||||
}
|
||||
}
|
||||
Message::Open => {
|
||||
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()));
|
||||
Message::Open(path_opt) => {
|
||||
match path_opt {
|
||||
Some(path) => {
|
||||
if path.is_dir() {
|
||||
cd = Some(Location::Path(path));
|
||||
} else {
|
||||
commands.push(Command::OpenFile(path));
|
||||
}
|
||||
}
|
||||
None => {
|
||||
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);
|
||||
for item in items.iter_mut() {
|
||||
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
|
||||
let handle = widget::icon::from_raster_pixels(
|
||||
rgba.width(),
|
||||
|
|
@ -2210,6 +2319,12 @@ impl Tab {
|
|||
self.dnd_hovered = None;
|
||||
}
|
||||
}
|
||||
Message::WindowDrag => {
|
||||
commands.push(Command::WindowDrag);
|
||||
}
|
||||
Message::WindowToggleMaximize => {
|
||||
commands.push(Command::WindowToggleMaximize);
|
||||
}
|
||||
Message::ZoomDefault => match self.config.view {
|
||||
View::List => self.config.icon_sizes.list = 100.try_into().unwrap(),
|
||||
View::Grid => self.config.icon_sizes.grid = 100.try_into().unwrap(),
|
||||
|
|
@ -2436,6 +2551,116 @@ impl Tab {
|
|||
.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> {
|
||||
//TODO: responsiveness is done in a hacky way, potentially move this to a custom widget?
|
||||
fn text_width<'a>(
|
||||
|
|
@ -2533,7 +2758,7 @@ impl Tab {
|
|||
_ => {}
|
||||
}
|
||||
//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))
|
||||
.into();
|
||||
};
|
||||
|
|
@ -3644,7 +3869,10 @@ impl Tab {
|
|||
(ICON_SIZE_GRID * ICON_SCALE_MAX) as u32;
|
||||
let thumbnail =
|
||||
image.thumbnail(thumbnail_size, thumbnail_size);
|
||||
ItemThumbnail::Rgba(thumbnail.to_rgba8())
|
||||
ItemThumbnail::Rgba(
|
||||
thumbnail.to_rgba8(),
|
||||
(image.width(), image.height()),
|
||||
)
|
||||
}
|
||||
Err(err) => {
|
||||
log::warn!("failed to decode {:?}: {}", path, err);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue