mirror of
https://github.com/narrrl/proton-drive-linux.git
synced 2026-11-03 16:41:26 +01:00
feat(gui): shared selection and rubberband in My Files
The grid and column view wrap one MultiSelection instead of one each, so a selection survives switching layout, and both views enable rubberband selection.
This commit is contained in:
1 parent
8d0a575a7c
commit
abf76cd5e8
4 files changed
+40
-47
No files matched your search
@@ -435,8 +435,7 @@ fn build_window(app: &adw::Application) {
|
|||||||
details: browser_widgets.details,
|
details: browser_widgets.details,
|
||||||
details_entry: RefCell::new(None),
|
details_entry: RefCell::new(None),
|
||||||
details_suppress: Cell::new(false),
|
details_suppress: Cell::new(false),
|
||||||
grid_selection: browser_widgets.grid_selection.clone(),
|
selection: browser_widgets.selection.clone(),
|
||||||
list_selection: browser_widgets.list_selection.clone(),
|
|
||||||
},
|
},
|
||||||
trash: TrashState {
|
trash: TrashState {
|
||||||
model: trash_widgets.model.clone(),
|
model: trash_widgets.model.clone(),
|
||||||
|
|||||||
@@ -53,8 +53,7 @@ pub(crate) struct BrowserState {
|
|||||||
/// requested repeatedly, so comparing their text alone cannot reject an
|
/// requested repeatedly, so comparing their text alone cannot reject an
|
||||||
/// older response that finishes after a manual refresh.
|
/// older response that finishes after a manual refresh.
|
||||||
pub(crate) load_generation: Cell<u64>,
|
pub(crate) load_generation: Cell<u64>,
|
||||||
/// The grid/list view stack, read to find out which of the two selections is
|
/// The grid/list view stack, read to find out which view is on screen.
|
||||||
/// the one the user is actually working in.
|
|
||||||
pub(crate) views: gtk4::Stack,
|
pub(crate) views: gtk4::Stack,
|
||||||
/// The bulk-action bar, revealed once more than one entry is selected.
|
/// The bulk-action bar, revealed once more than one entry is selected.
|
||||||
pub(crate) bulk: gtk4::Revealer,
|
pub(crate) bulk: gtk4::Revealer,
|
||||||
@@ -138,11 +137,10 @@ pub(crate) struct BrowserWidgets {
|
|||||||
/// Wraps the views + the details pane; the pane slides in on selection.
|
/// Wraps the views + the details pane; the pane slides in on selection.
|
||||||
pub(crate) split: adw::OverlaySplitView,
|
pub(crate) split: adw::OverlaySplitView,
|
||||||
pub(crate) details: DetailsWidgets,
|
pub(crate) details: DetailsWidgets,
|
||||||
/// The two selection models, so a selection change can drive the details pane
|
/// The selection shared by both views, so a selection change can drive the
|
||||||
/// and so an action can re-read the entries the user has highlighted.
|
/// details pane and an action can re-read the entries the user highlighted.
|
||||||
pub(crate) grid_selection: gtk4::MultiSelection,
|
pub(crate) selection: gtk4::MultiSelection,
|
||||||
pub(crate) list_selection: gtk4::MultiSelection,
|
/// The grid/list stack, so the page can tell which view is on screen.
|
||||||
/// The grid/list stack, so the page can tell which selection is live.
|
|
||||||
pub(crate) views: gtk4::Stack,
|
pub(crate) views: gtk4::Stack,
|
||||||
pub(crate) bulk: gtk4::Revealer,
|
pub(crate) bulk: gtk4::Revealer,
|
||||||
pub(crate) bulk_label: gtk4::Label,
|
pub(crate) bulk_label: gtk4::Label,
|
||||||
@@ -310,11 +308,14 @@ pub(crate) fn build_browser_page() -> (gtk4::Widget, BrowserWidgets) {
|
|||||||
// Icon grid. Multi-select: acting on a batch is the common case for trashing
|
// Icon grid. Multi-select: acting on a batch is the common case for trashing
|
||||||
// and for taking a folder's worth of files offline, and doing it one
|
// and for taking a folder's worth of files offline, and doing it one
|
||||||
// confirmation dialog at a time is not a workflow.
|
// confirmation dialog at a time is not a workflow.
|
||||||
let grid_selection = gtk4::MultiSelection::new(Some(model.clone()));
|
// One selection for both views: switching layout keeps what is selected.
|
||||||
|
// Rubberband drags a selection box from empty space, as a file manager does.
|
||||||
|
let selection = gtk4::MultiSelection::new(Some(model.clone()));
|
||||||
let grid = gtk4::GridView::builder()
|
let grid = gtk4::GridView::builder()
|
||||||
.model(&grid_selection)
|
.model(&selection)
|
||||||
.min_columns(2)
|
.min_columns(2)
|
||||||
.max_columns(10)
|
.max_columns(10)
|
||||||
|
.enable_rubberband(true)
|
||||||
.build();
|
.build();
|
||||||
grid.add_css_class("file-grid");
|
grid.add_css_class("file-grid");
|
||||||
let grid_scroll = gtk4::ScrolledWindow::builder()
|
let grid_scroll = gtk4::ScrolledWindow::builder()
|
||||||
@@ -322,9 +323,11 @@ pub(crate) fn build_browser_page() -> (gtk4::Widget, BrowserWidgets) {
|
|||||||
.child(&grid)
|
.child(&grid)
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
// Column list, with its own selection over the same model.
|
// Column list, over the same selection.
|
||||||
let list_selection = gtk4::MultiSelection::new(Some(model.clone()));
|
let column_view = gtk4::ColumnView::builder()
|
||||||
let column_view = gtk4::ColumnView::builder().model(&list_selection).build();
|
.model(&selection)
|
||||||
|
.enable_rubberband(true)
|
||||||
|
.build();
|
||||||
column_view.add_css_class("data-table");
|
column_view.add_css_class("data-table");
|
||||||
let column_scroll = gtk4::ScrolledWindow::builder()
|
let column_scroll = gtk4::ScrolledWindow::builder()
|
||||||
.vexpand(true)
|
.vexpand(true)
|
||||||
@@ -519,8 +522,7 @@ pub(crate) fn build_browser_page() -> (gtk4::Widget, BrowserWidgets) {
|
|||||||
refresh,
|
refresh,
|
||||||
split,
|
split,
|
||||||
details,
|
details,
|
||||||
grid_selection,
|
selection,
|
||||||
list_selection,
|
|
||||||
views: view_stack,
|
views: view_stack,
|
||||||
bulk,
|
bulk,
|
||||||
bulk_label,
|
bulk_label,
|
||||||
@@ -550,15 +552,9 @@ pub(crate) fn browser_status(ui: &Rc<Ui>, icon: &str, title: &str, description:
|
|||||||
hide_details(ui);
|
hide_details(ui);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The selection model of whichever view is on screen. The grid and the list
|
/// The selection model shared by the grid and the list.
|
||||||
/// each own one over the same model, so "what is selected" depends on which the
|
|
||||||
/// user is looking at.
|
|
||||||
pub(crate) fn active_selection(ui: &Rc<Ui>) -> gtk4::MultiSelection {
|
pub(crate) fn active_selection(ui: &Rc<Ui>) -> gtk4::MultiSelection {
|
||||||
if ui.browser.views.visible_child_name().as_deref() == Some("list") {
|
ui.details.selection.clone()
|
||||||
ui.details.list_selection.clone()
|
|
||||||
} else {
|
|
||||||
ui.details.grid_selection.clone()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Every entry highlighted in the view on screen, in model order.
|
/// Every entry highlighted in the view on screen, in model order.
|
||||||
@@ -575,10 +571,9 @@ pub(crate) fn selected_entries(ui: &Rc<Ui>) -> Vec<DirEntry> {
|
|||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Drop the selection in both views, which also retracts the bulk bar.
|
/// Drop the selection, which also retracts the bulk bar.
|
||||||
pub(crate) fn clear_selection(ui: &Rc<Ui>) {
|
pub(crate) fn clear_selection(ui: &Rc<Ui>) {
|
||||||
ui.details.grid_selection.unselect_all();
|
ui.details.selection.unselect_all();
|
||||||
ui.details.list_selection.unselect_all();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Reflect the current selection in the bulk bar: how many are selected, and
|
/// Reflect the current selection in the bulk bar: how many are selected, and
|
||||||
|
|||||||
@@ -9,10 +9,9 @@ pub(crate) struct DetailsState {
|
|||||||
/// Set while the details pane is being populated, so setting the offline
|
/// Set while the details pane is being populated, so setting the offline
|
||||||
/// switch programmatically doesn't fire a pin/unpin round-trip.
|
/// switch programmatically doesn't fire a pin/unpin round-trip.
|
||||||
pub(crate) details_suppress: Cell<bool>,
|
pub(crate) details_suppress: Cell<bool>,
|
||||||
/// The grid's and list's selection models. Both wrap the one browser model, so
|
/// The one selection model behind both the grid and the list, so switching
|
||||||
/// a selection in either drives the details pane.
|
/// views keeps what is selected; it drives the details pane.
|
||||||
pub(crate) grid_selection: gtk4::MultiSelection,
|
pub(crate) selection: gtk4::MultiSelection,
|
||||||
pub(crate) list_selection: gtk4::MultiSelection,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The widgets in the browser's details pane that a selection repaints.
|
/// The widgets in the browser's details pane that a selection repaints.
|
||||||
@@ -152,23 +151,21 @@ pub(crate) fn build_details_pane() -> (gtk4::Widget, DetailsWidgets) {
|
|||||||
/// Connect the details pane: mirror the grid's and list's selection into it, and
|
/// Connect the details pane: mirror the grid's and list's selection into it, and
|
||||||
/// wire its buttons back onto the entry it's showing.
|
/// wire its buttons back onto the entry it's showing.
|
||||||
pub(crate) fn wire_details(ui: &Rc<Ui>) {
|
pub(crate) fn wire_details(ui: &Rc<Ui>) {
|
||||||
// Both views share one model but have their own selection, so watch both.
|
{
|
||||||
for selection in [
|
|
||||||
ui.details.grid_selection.clone(),
|
|
||||||
ui.details.list_selection.clone(),
|
|
||||||
] {
|
|
||||||
let ui_sel = ui.clone();
|
let ui_sel = ui.clone();
|
||||||
selection.connect_selection_changed(move |_, _, _| {
|
ui.details
|
||||||
// The details pane describes *one* entry. A batch is the bulk bar's
|
.selection
|
||||||
// business, so the pane steps aside rather than picking a member of
|
.connect_selection_changed(move |_, _, _| {
|
||||||
// the selection to speak for the rest.
|
// The details pane describes *one* entry. A batch is the bulk bar's
|
||||||
let entries = selected_entries(&ui_sel);
|
// business, so the pane steps aside rather than picking a member of
|
||||||
match entries.as_slice() {
|
// the selection to speak for the rest.
|
||||||
[entry] => show_details(&ui_sel, entry),
|
let entries = selected_entries(&ui_sel);
|
||||||
_ => hide_details(&ui_sel),
|
match entries.as_slice() {
|
||||||
}
|
[entry] => show_details(&ui_sel, entry),
|
||||||
sync_bulk_bar(&ui_sel);
|
_ => hide_details(&ui_sel),
|
||||||
});
|
}
|
||||||
|
sync_bulk_bar(&ui_sel);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
let ui_close = ui.clone();
|
let ui_close = ui.clone();
|
||||||
|
|||||||
@@ -72,6 +72,8 @@ Schema: **31** (unchanged).
|
|||||||
Copy Link · Rename…, Move To…, Versions… · Move to Trash. Right-clicking empty space offers New
|
Copy Link · Rename…, Move To…, Versions… · Move to Trash. Right-clicking empty space offers New
|
||||||
Folder, the uploads, Select All, Refresh and Open in File Manager. Open With… asks which
|
Folder, the uploads, Select All, Refresh and Open in File Manager. Open With… asks which
|
||||||
application to use.
|
application to use.
|
||||||
|
- **One selection in My Files.** The grid and the list share their selection, so switching views
|
||||||
|
keeps what is selected, and dragging across empty space draws a selection box in both.
|
||||||
- **US spelling throughout.** The app, the CLI and the docs say "Favorite" and "color", matching the
|
- **US spelling throughout.** The app, the CLI and the docs say "Favorite" and "color", matching the
|
||||||
`pdfs favorite` command.
|
`pdfs favorite` command.
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user