From 191af673b1815545971cc2068d9ea57df62f75cd Mon Sep 17 00:00:00 2001 From: Jeremy Soller Date: Fri, 4 Oct 2024 11:07:27 -0600 Subject: [PATCH] Show details in popup window in desktop mode, part of #547 --- examples/desktop.rs | 2 +- src/app.rs | 137 ++++++++++++++++++++++++++++++++++++-------- src/dialog.rs | 32 +++++------ src/tab.rs | 36 ++++++------ 4 files changed, 148 insertions(+), 59 deletions(-) diff --git a/examples/desktop.rs b/examples/desktop.rs index 18df071..6ad3d0e 100644 --- a/examples/desktop.rs +++ b/examples/desktop.rs @@ -1,4 +1,4 @@ // This launches the desktop mode as a regular window for easier testing. fn main() -> Result<(), Box> { cosmic_files::desktop() -} \ No newline at end of file +} diff --git a/src/app.rs b/src/app.rs index 60e2fba..76ceadd 100644 --- a/src/app.rs +++ b/src/app.rs @@ -21,8 +21,9 @@ use cosmic::{ futures::{self, SinkExt}, keyboard::{Event as KeyEvent, Key, Modifiers}, subscription::{self, Subscription}, + widget::scrollable, window::{self, Event as WindowEvent, Id as WindowId}, - Alignment, Event, Length, + Alignment, Event, Length, Size, }, iced_runtime::clipboard, style, theme, @@ -175,7 +176,7 @@ impl Action { Action::OpenTerminal => Message::OpenTerminal(entity_opt), Action::OpenWith => Message::OpenWithDialog(entity_opt), Action::Paste => Message::Paste(entity_opt), - Action::Preview => Message::ToggleShowDetails, + Action::Preview => Message::Preview, Action::Rename => Message::Rename(entity_opt), Action::RestoreFromTrash => Message::RestoreFromTrash(entity_opt), Action::SearchActivate => Message::SearchActivate, @@ -297,6 +298,7 @@ pub enum Message { PendingComplete(u64), PendingError(u64, String), PendingProgress(u64, f32), + Preview, RescanTrash, Rename(Option), ReplaceResult(ReplaceResult), @@ -316,12 +318,12 @@ pub enum Message { TabRescan(Entity, Location, Vec, Option), TabView(Option, tab::View), ToggleContextPage(ContextPage), - ToggleShowDetails, ToggleFoldersFirst, Undo(usize), UndoTrash(widget::ToastId, Arc<[PathBuf]>), UndoTrashStart(Vec), WindowClose, + WindowCloseRequested(window::Id), WindowNew, ZoomDefault(Option), ZoomIn(Option), @@ -443,6 +445,12 @@ pub struct FavoriteIndex(usize); pub struct MounterData(MounterKey, MounterItem); +#[derive(Clone, Debug)] +pub enum WindowKind { + Main, + Preview(Option, PreviewKind), +} + pub struct WatcherWrapper { watcher_opt: Option>, } @@ -500,6 +508,7 @@ pub struct App { toasts: widget::toaster::Toasts, watcher_opt: Option<(Debouncer, HashSet)>, window_id_opt: Option, + windows: HashMap, nav_dnd_hover: Option<(Location, Instant)>, tab_dnd_hover: Option<(Entity, Instant)>, nav_drag_id: DragId, @@ -969,19 +978,25 @@ impl App { widget::settings::view_column(children).into() } - fn preview(&self, entity_opt: &Option, kind: &PreviewKind) -> Element { + fn preview( + &self, + entity_opt: &Option, + kind: &PreviewKind, + context_drawer: bool, + ) -> Element { let mut children = Vec::with_capacity(1); let entity = entity_opt.unwrap_or_else(|| self.tab_model.active()); match kind { PreviewKind::Custom(PreviewItem(item)) => { - children.push(item.preview_view(IconSizes::default())); + children.push(item.preview_view(IconSizes::default(), context_drawer)); } PreviewKind::Location(location) => { if let Some(tab) = self.tab_model.data::(entity) { if let Some(items) = tab.items_opt() { for item in items.iter() { if item.location_opt.as_ref() == Some(location) { - children.push(item.preview_view(tab.config.icon_sizes)); + children + .push(item.preview_view(tab.config.icon_sizes, context_drawer)); // Only show one property view to avoid issues like hangs when generating // preview images on thousands of files break; @@ -995,7 +1010,8 @@ impl App { if let Some(items) = tab.items_opt() { for item in items.iter() { if item.selected { - children.push(item.preview_view(tab.config.icon_sizes)); + children + .push(item.preview_view(tab.config.icon_sizes, context_drawer)); // Only show one property view to avoid issues like hangs when generating // preview images on thousands of files break; @@ -1005,7 +1021,13 @@ impl App { } } } - widget::settings::view_column(children).into() + + // View column has extra padding not wanted when not showing in context drawer + if context_drawer { + widget::settings::view_column(children).into() + } else { + widget::column::with_children(children).into() + } } fn settings(&self) -> Element { @@ -1116,6 +1138,7 @@ impl Application for App { toasts: widget::toaster::Toasts::new(Message::CloseToast), watcher_opt: None, window_id_opt: Some(window::Id::MAIN), + windows: HashMap::new(), nav_dnd_hover: None, tab_dnd_hover: None, nav_drag_id: DragId::new(), @@ -1254,12 +1277,16 @@ impl Application for App { Some(Message::WindowClose) } + fn on_close_requested(&self, id: window::Id) -> Option { + Some(Message::WindowCloseRequested(id)) + } + fn on_context_drawer(&mut self) -> Command { match self.context_page { ContextPage::Preview(_, _) => { // Persist state of preview page if self.core.window.show_context != self.config.show_details { - return self.update(Message::ToggleShowDetails); + return self.update(Message::Preview); } } _ => {} @@ -2029,6 +2056,47 @@ impl Application for App { } return self.update_notification(); } + Message::Preview => { + match self.mode { + Mode::App => { + let show_details = !self.config.show_details; + //TODO: move to update_config? + self.context_page = ContextPage::Preview(None, PreviewKind::Selected); + self.core.window.show_context = show_details; + config_set!(show_details, show_details); + return self.update_config(); + } + Mode::Desktop => { + let selected_paths = self.selected_paths(None); + let mut commands = Vec::with_capacity(selected_paths.len()); + for path in selected_paths { + let mut settings = window::Settings::default(); + settings.decorations = true; + settings.resizable = true; + settings.size = Size::new(480.0, 600.0); + settings.transparent = true; + + #[cfg(target_os = "linux")] + { + // Use the dialog ID to make it float + settings.platform_specific.application_id = + "com.system76.CosmicFilesDialog".to_string(); + } + + let (id, command) = window::spawn(settings); + self.windows.insert( + id, + WindowKind::Preview( + None, + PreviewKind::Location(Location::Path(path)), + ), + ); + commands.push(command); + } + return Command::batch(commands); + } + } + } Message::RescanTrash => { // Update trash icon if empty/full let maybe_entity = self.nav_model.iter().find(|&entity| { @@ -2237,14 +2305,6 @@ impl Application for App { return self.update_config(); } } - Message::ToggleShowDetails => { - let show_details = !self.config.show_details; - //TODO: move to update_config? - self.context_page = ContextPage::Preview(None, PreviewKind::Selected); - self.core.window.show_context = show_details; - config_set!(show_details, show_details); - return self.update_config(); - } Message::ToggleFoldersFirst => { let mut config = self.config.tab; config.folders_first = !config.folders_first; @@ -2490,6 +2550,9 @@ impl Application for App { ]); } } + Message::WindowCloseRequested(id) => { + self.windows.remove(&id); + } Message::WindowNew => match env::current_exe() { Ok(exe) => match process::Command::new(&exe).spawn() { Ok(_child) => {} @@ -2840,7 +2903,7 @@ impl Application for App { ContextPage::About => self.about(), ContextPage::EditHistory => self.edit_history(), ContextPage::NetworkDrive => self.network_drive(), - ContextPage::Preview(entity_opt, kind) => self.preview(entity_opt, kind), + ContextPage::Preview(entity_opt, kind) => self.preview(entity_opt, kind, true), ContextPage::Settings => self.settings(), }) } @@ -3462,13 +3525,37 @@ impl Application for App { content } - fn view_window(&self, _id: WindowId) -> Element { - //TODO: distinct views per window? - self.view_main().map(|message| match message { - app::Message::App(app) => app, - app::Message::Cosmic(cosmic) => Message::Cosmic(cosmic), - app::Message::None => Message::None, - }) + fn view_window(&self, id: WindowId) -> Element { + let content = match self.windows.get(&id) { + Some(WindowKind::Main) | None => { + //TODO: distinct views per monitor in desktop mode + return self.view_main().map(|message| match message { + app::Message::App(app) => app, + app::Message::Cosmic(cosmic) => Message::Cosmic(cosmic), + app::Message::None => Message::None, + }); + } + Some(WindowKind::Preview(entity_opt, kind)) => self.preview(entity_opt, kind, false), + }; + //TODO: these are hacks to have a sane scroll bar + let cosmic_theme::Spacing { space_l, .. } = theme::active().cosmic().spacing; + let scrollbar_width = 8; + widget::container( + widget::scrollable(widget::row::with_children(vec![ + content, + widget::horizontal_space(Length::Fixed(scrollbar_width.into())).into(), + ])) + .direction(scrollable::Direction::Vertical( + scrollable::Properties::new() + .width(scrollbar_width) + .scroller_width(scrollbar_width), + )), + ) + .width(Length::Fill) + .height(Length::Fill) + .padding([0, space_l - scrollbar_width, space_l, space_l]) + .style(theme::Container::WindowBackground) + .into() } fn subscription(&self) -> Subscription { diff --git a/src/dialog.rs b/src/dialog.rs index e719918..cc0ba87 100644 --- a/src/dialog.rs +++ b/src/dialog.rs @@ -164,9 +164,9 @@ impl Dialog { let mut settings = window::Settings::default(); settings.decorations = false; settings.exit_on_close_request = false; - settings.transparent = true; - settings.size = Size::new(1024.0, 640.0); settings.resizable = true; + settings.size = Size::new(1024.0, 640.0); + settings.transparent = true; #[cfg(target_os = "linux")] { @@ -317,6 +317,7 @@ enum Message { NotifyEvents(Vec), NotifyWatcher(WatcherWrapper), Open, + Preview, Save(bool), SearchActivate, SearchClear, @@ -324,15 +325,14 @@ enum Message { SearchSubmit, TabMessage(tab::Message), TabRescan(Vec), - ToggleShowDetails, } impl From for Message { fn from(app_message: AppMessage) -> Message { match app_message { + AppMessage::Preview => Message::Preview, AppMessage::SearchActivate => Message::SearchActivate, AppMessage::TabMessage(_entity_opt, tab_message) => Message::TabMessage(tab_message), - AppMessage::ToggleShowDetails => Message::ToggleShowDetails, unsupported => { log::warn!("{unsupported:?} not supported in dialog mode"); Message::None @@ -452,13 +452,13 @@ impl App { let mut children = Vec::with_capacity(1); match kind { PreviewKind::Custom(PreviewItem(item)) => { - children.push(item.preview_view(IconSizes::default())); + children.push(item.preview_view(IconSizes::default(), true)); } 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)); + children.push(item.preview_view(self.tab.config.icon_sizes, true)); // Only show one property view to avoid issues like hangs when generating // preview images on thousands of files break; @@ -470,7 +470,7 @@ impl App { 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)); + children.push(item.preview_view(self.tab.config.icon_sizes, true)); // Only show one property view to avoid issues like hangs when generating // preview images on thousands of files break; @@ -1245,6 +1245,15 @@ impl Application for App { } } } + Message::Preview => match self.context_page { + ContextPage::Preview(_, _) => { + self.core.window.show_context = !self.core.window.show_context; + } + _ => { + self.context_page = ContextPage::Preview(None, PreviewKind::Selected); + self.core.window.show_context = true; + } + }, Message::Save(replace) => { if let DialogKind::SaveFile { filename } = &self.flags.kind { if !filename.is_empty() { @@ -1426,15 +1435,6 @@ impl Application for App { // Reset focus on location change return widget::text_input::focus(self.filename_id.clone()); } - Message::ToggleShowDetails => match self.context_page { - ContextPage::Preview(_, _) => { - self.core.window.show_context = !self.core.window.show_context; - } - _ => { - self.context_page = ContextPage::Preview(None, PreviewKind::Selected); - self.core.window.show_context = true; - } - }, } Command::none() diff --git a/src/tab.rs b/src/tab.rs index 9933061..0d1e7db 100644 --- a/src/tab.rs +++ b/src/tab.rs @@ -1097,7 +1097,7 @@ impl Item { } } - pub fn preview_view(&self, sizes: IconSizes) -> Element<'static, app::Message> { + pub fn preview_view(&self, sizes: IconSizes, nav_row: bool) -> Element<'static, app::Message> { let cosmic_theme::Spacing { space_xxxs, space_xxs, @@ -1107,25 +1107,27 @@ impl Item { 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)), - ); + if nav_row { + 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)), + ); - if self.mime.type_() == mime::IMAGE { - 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))), - ); + if self.mime.type_() == mime::IMAGE { + 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(row); column = column.push(widget::row::with_children(vec![ widget::horizontal_space(Length::Fill).into(),