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

View file

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

View file

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

View file

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

View file

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