fix(gui): clear the selection on an empty-space click

A click between the files in the grid or list drops the selection, as
in a file manager, unless Ctrl or Shift is held. Backspace goes back
while the focus is on the files.
This commit is contained in:
nvrl committed 2026-09-23 03:48:57 +02:00
1 parent fe8fb8cdcf
commit 4fa61a14c3
3 files changed
+51

No files matched your search

+5
View File
@@ -1342,6 +1342,11 @@ fn install_shortcuts(ui: &Rc<Ui>, window: &adw::ApplicationWindow) {
gtk4::gdk::Key::Up if alt && on_browser => {
ui.browser.actions.activate_action("up", None)
}
// A focused text field takes Backspace first, so this only fires
// with the focus on the files, as in a file manager.
gtk4::gdk::Key::BackSpace if on_browser => {
ui.browser.actions.activate_action("back", None)
}
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 => {
+44
View File
@@ -815,6 +815,8 @@ pub(crate) fn browser_views(ui: &Rc<Ui>) {
pub(crate) fn wire_browser(ui: &Rc<Ui>, grid: &gtk4::GridView, column_view: &gtk4::ColumnView) {
attach_background_menu(ui, grid);
attach_background_menu(ui, column_view);
attach_background_deselect(ui, grid);
attach_background_deselect(ui, column_view);
// Resize only realised grid cells. Rebuilding the whole model for every
// slider step would repeatedly tear down selection state while the pointer
// is still moving.
@@ -1102,6 +1104,48 @@ pub(crate) fn attach_background_menu(ui: &Rc<Ui>, view: &impl IsA<gtk4::Widget>)
view.add_controller(gesture);
}
/// Clicking the empty space of a view drops the selection, as in a file
/// manager. GTK's list views leave it alone, so a stray highlight otherwise
/// sticks around until another item is clicked.
pub(crate) fn attach_background_deselect(ui: &Rc<Ui>, view: &impl IsA<gtk4::Widget>) {
let gesture = gtk4::GestureClick::new();
gesture.set_button(gtk4::gdk::BUTTON_PRIMARY);
// Capture sees the press before an item's own gesture claims it; this only
// looks, so the item still gets its click.
gesture.set_propagation_phase(gtk4::PropagationPhase::Capture);
let ui = ui.clone();
let target = view.clone().upcast::<gtk4::Widget>();
gesture.connect_pressed(move |gesture, _, x, y| {
let modifiers = gesture.current_event_state();
if modifiers
.intersects(gtk4::gdk::ModifierType::CONTROL_MASK | gtk4::gdk::ModifierType::SHIFT_MASK)
{
return;
}
if !on_item(&target, x, y) {
clear_selection(&ui);
sync_bulk_bar(&ui);
}
});
view.add_controller(gesture);
}
/// Whether `(x, y)` in `view` lands on an item: a grid tile ("child"), a list
/// row ("row"), or a column header ("header"), which has clicks of its own.
fn on_item(view: &gtk4::Widget, x: f64, y: f64) -> bool {
let mut widget = view.pick(x, y, gtk4::PickFlags::DEFAULT);
while let Some(w) = widget {
if &w == view {
return false;
}
if matches!(w.css_name().as_str(), "child" | "row" | "header") {
return true;
}
widget = w.parent();
}
false
}
/// The Menu key or Shift+F10: the menu for the selection, or for the folder
/// when nothing is selected, opened at the focused item.
pub(crate) fn popup_keyboard_context_menu(ui: &Rc<Ui>) {