From f457fa68fcf8f357db39b3e547363370808aac95 Mon Sep 17 00:00:00 2001 From: Nils Pukropp Date: Wed, 23 Sep 2026 03:38:48 +0200 Subject: [PATCH] fix(gui): open the details pane on request, not on selection Revealing the pane on the first click of a double-click reflowed the files under the pointer, so the second click missed and the folder did not open. The pane now follows a header toggle, Alt+Enter and a Details menu item. --- crates/pdfs-gui/src/app/main.rs | 3 ++ crates/pdfs-gui/src/app/pages/browser.rs | 8 +++- crates/pdfs-gui/src/app/widgets/details.rs | 48 ++++++++++++++++++---- docs/CHANGELOG.md | 5 +++ 4 files changed, 54 insertions(+), 10 deletions(-) diff --git a/crates/pdfs-gui/src/app/main.rs b/crates/pdfs-gui/src/app/main.rs index 779a3c6..b3bd6eb 100644 --- a/crates/pdfs-gui/src/app/main.rs +++ b/crates/pdfs-gui/src/app/main.rs @@ -1338,6 +1338,9 @@ fn install_shortcuts(ui: &Rc, window: &adw::ApplicationWindow) { } gtk4::gdk::Key::u | gtk4::gdk::Key::U if ctrl && on_browser => prompt_upload(&ui), gtk4::gdk::Key::Menu if on_browser => popup_keyboard_context_menu(&ui), + gtk4::gdk::Key::Return | gtk4::gdk::Key::KP_Enter if alt && on_browser => { + toggle_details(&ui) + } gtk4::gdk::Key::F10 if on_browser && state.contains(gtk4::gdk::ModifierType::SHIFT_MASK) => { diff --git a/crates/pdfs-gui/src/app/pages/browser.rs b/crates/pdfs-gui/src/app/pages/browser.rs index 4e3b022..97d3a75 100644 --- a/crates/pdfs-gui/src/app/pages/browser.rs +++ b/crates/pdfs-gui/src/app/pages/browser.rs @@ -140,7 +140,7 @@ pub(crate) struct BrowserWidgets { pub(crate) quota: gtk4::ProgressBar, pub(crate) quota_text: gtk4::Label, pub(crate) refresh: gtk4::Button, - /// Wraps the views + the details pane; the pane slides in on selection. + /// Wraps the views + the details pane; the pane shows the selection while its header toggle is on. pub(crate) split: adw::OverlaySplitView, pub(crate) details: DetailsWidgets, /// The selection shared by both views, so a selection change can drive the @@ -362,8 +362,9 @@ pub(crate) fn build_browser_page() -> (gtk4::Widget, BrowserWidgets) { content.add_named(&view_stack, Some("views")); content.add_named(&status, Some("status")); - // The details pane slides in from the right when an entry is selected. + // The details pane slides in from the right while its header toggle is on. let (details_pane, details) = build_details_pane(); + let details_toggle = details.toggle.clone(); let split = adw::OverlaySplitView::builder() .sidebar_position(gtk4::PackType::End) .collapsed(true) @@ -507,6 +508,7 @@ pub(crate) fn build_browser_page() -> (gtk4::Widget, BrowserWidgets) { header.pack_start(&new_button); header.pack_end(&folder_button); header.pack_end(&refresh); + header.pack_end(&details_toggle); header.pack_end(&view_button); header.pack_end(&search); header.pack_end(&search_scope); @@ -1178,6 +1180,8 @@ pub(crate) fn entry_context_menu(ui: &Rc, entry: &DirEntry) -> ActionMenu { let (ui_c, entry_c) = (ui.clone(), entry.clone()); menu.item("Versions…", move || open_versions_dialog(&ui_c, &entry_c)); } + let (ui_c, entry_c) = (ui.clone(), entry.clone()); + menu.item("Details", move || open_details(&ui_c, &entry_c)); menu.section(); let (ui_c, entry_c) = (ui.clone(), entry.clone()); diff --git a/crates/pdfs-gui/src/app/widgets/details.rs b/crates/pdfs-gui/src/app/widgets/details.rs index a89830e..09448c1 100644 --- a/crates/pdfs-gui/src/app/widgets/details.rs +++ b/crates/pdfs-gui/src/app/widgets/details.rs @@ -35,6 +35,10 @@ pub(crate) struct DetailsWidgets { pub(crate) versions_button: gtk4::Button, pub(crate) trash_button: gtk4::Button, pub(crate) close_button: gtk4::Button, + /// The header toggle that asks for the pane. Selecting an entry only + /// fills the pane; it is shown while this is on, so a click never moves + /// the files under the pointer halfway through a double-click. + pub(crate) toggle: gtk4::ToggleButton, } /// The details pane shown beside the file views: a big type icon over the entry's @@ -42,6 +46,11 @@ pub(crate) struct DetailsWidgets { /// actions. Built empty; [`show_details`] fills it from the selected /// [`DirEntry`] and [`wire_details`] connects the buttons. pub(crate) fn build_details_pane() -> (gtk4::Widget, DetailsWidgets) { + let toggle = gtk4::ToggleButton::builder() + .icon_name("sidebar-show-right-symbolic") + .tooltip_text("Details (Alt+Enter)") + .build(); + let close_button = gtk4::Button::builder() .icon_name("window-close-symbolic") .tooltip_text("Close details") @@ -186,6 +195,7 @@ pub(crate) fn build_details_pane() -> (gtk4::Widget, DetailsWidgets) { versions_button, trash_button, close_button, + toggle, }, ) } @@ -212,8 +222,13 @@ pub(crate) fn wire_details(ui: &Rc) { let ui_close = ui.clone(); ui.details.details.close_button.connect_clicked(move |_| { - clear_selection(&ui_close); - hide_details(&ui_close); + ui_close.details.details.toggle.set_active(false); + }); + + let ui_toggle = ui.clone(); + ui.details.details.toggle.connect_toggled(move |toggle| { + let show = toggle.is_active() && ui_toggle.details.details_entry.borrow().is_some(); + ui_toggle.browser.split.set_show_sidebar(show); }); let ui_open = ui.clone(); @@ -333,12 +348,17 @@ pub(crate) fn show_details(ui: &Rc, entry: &DirEntry) { if mounted { load_details_extras(ui, entry); } - // Reveal the pane on idle, not inline. This runs from `selection_changed`, - // which fires on the *first* press of a double-click; mutating the widget - // tree here cancels GtkGridView's multi-press tracking, so the second press - // restarts the count and `activate` never fires — the folder needs two - // double-clicks. Deferring lets the click gesture finish first. Guard on the - // entry still being present so a navigation that clears it wins the race. + // Only a pane the user asked for is revealed. This runs from + // `selection_changed`, which fires on the *first* press of a double-click: + // sliding the pane in there reflows the files, so the second press lands on + // a different spot (or item) and the folder never opens. With the pane + // already up, a new selection only repaints it. Deferred to idle all the + // same, since mutating the widget tree mid-gesture cancels GtkGridView's + // multi-press tracking; the guard lets a navigation that clears the entry + // win the race. + if !ui.details.details.toggle.is_active() { + return; + } let ui = ui.clone(); glib::idle_add_local_once(move || { if ui.details.details_entry.borrow().is_some() { @@ -431,6 +451,18 @@ pub(crate) fn versions_summary(items: &[RevisionInfo]) -> String { /// Hide the details pane and forget the entry it was showing, so a stale entry /// can't be acted on after the listing moves on. +/// Show the pane for `entry`, turning the header toggle on. +pub(crate) fn open_details(ui: &Rc, entry: &DirEntry) { + ui.details.details.toggle.set_active(true); + show_details(ui, entry); +} + +/// Alt+Enter: show or hide the pane for the selected entry. +pub(crate) fn toggle_details(ui: &Rc) { + let toggle = &ui.details.details.toggle; + toggle.set_active(!toggle.is_active()); +} + pub(crate) fn hide_details(ui: &Rc) { ui.browser.split.set_show_sidebar(false); *ui.details.details_entry.borrow_mut() = None; diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index 6a1e6a3..163f049 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -111,6 +111,11 @@ Schema: **31** (unchanged). Schema: **31** (unchanged). ### Fixed +- **Double-click opens folders reliably.** Selecting a file no longer slides the details pane in + by itself: the pane moved the files under the pointer between the two clicks of a double-click, + so the folder did not open. The pane now shows on request, from the header's Details toggle, + Alt+Enter or the item menu's Details, and then follows the selection. Closing it keeps the + selection. - **An upload parked behind a temp-file rename no longer stays parked forever.** A create for a name that looks like a writer's temporary file waits for the rename to the final name. If that rename never came — the writer crashed, the file was deleted, or the name was never temporary —