Show which cell a shift-click is measuring from
The gesture had a hidden operand. A range runs from the anchor — the last cell plainly clicked — to the cell shift-clicked, and nothing on screen said which one the anchor was. A user who could not tell where the range was being measured from had no way to predict what it would take and no clue why a wrong one came out wrong; often the anchor is not on screen at all, which is itself the answer to "why did that select so much". The anchor is marked with an inner ring, drawn inside the selection ring rather than in a colour of its own: it has to stay legible against a thumbnail of any brightness, and a hue would read as a second kind of selection. It is an ordinal, so it marks a row only while the photograph it names is in the loaded window — off screen it marks nothing, which is the honest answer, and `anchor` rides in the cell model beside `selected` so both are pushed by the one pass that already keeps the grid in step with the selection. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -914,12 +914,19 @@ pub fn sync_lifted(window: &AppWindow, lifted: &[ImageId], ids: &[ImageId]) {
|
|||||||
pub fn sync_selection(window: &AppWindow, ctl: &Rc<CollectionsController>, ids: &[ImageId]) {
|
pub fn sync_selection(window: &AppWindow, ctl: &Rc<CollectionsController>, ids: &[ImageId]) {
|
||||||
let selection = ctl.selection.borrow();
|
let selection = ctl.selection.borrow();
|
||||||
let model = window.get_library_cells();
|
let model = window.get_library_cells();
|
||||||
|
// The anchor is an ordinal and the model is a window, so it marks a row
|
||||||
|
// only while the photograph it names is on screen. Off screen it marks
|
||||||
|
// nothing, which is the honest answer: the range still extends from there.
|
||||||
|
let offset = window.get_library_offset().max(0) as usize;
|
||||||
|
let anchor_row = ctl.anchor.borrow().and_then(|at| at.checked_sub(offset));
|
||||||
|
|
||||||
for (row, id) in ids.iter().enumerate() {
|
for (row, id) in ids.iter().enumerate() {
|
||||||
let want = selection.contains(id);
|
let want = selection.contains(id);
|
||||||
|
let anchored = anchor_row == Some(row);
|
||||||
if let Some(mut cell) = model.row_data(row) {
|
if let Some(mut cell) = model.row_data(row) {
|
||||||
if cell.selected != want {
|
if cell.selected != want || cell.anchor != anchored {
|
||||||
cell.selected = want;
|
cell.selected = want;
|
||||||
|
cell.anchor = anchored;
|
||||||
model.set_row_data(row, cell);
|
model.set_row_data(row, cell);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2129,10 +2129,11 @@ fn load_window(window: &AppWindow, ctl: &Rc<LibraryController>) {
|
|||||||
thumbnail: carried.map(|h| h.thumbnail.clone()).unwrap_or_default(),
|
thumbnail: carried.map(|h| h.thumbnail.clone()).unwrap_or_default(),
|
||||||
has_thumb: carried.is_some_and(|h| h.has_thumb),
|
has_thumb: carried.is_some_and(|h| h.has_thumb),
|
||||||
unavailable: carried.is_some_and(|h| h.unavailable),
|
unavailable: carried.is_some_and(|h| h.unavailable),
|
||||||
// Both are filled straight after by `collections_ui`, which owns
|
// All three are filled straight after by `collections_ui`,
|
||||||
// the selection and queries the badge counts for the whole window
|
// which owns the selection and queries the badge counts for the
|
||||||
// in one statement rather than one per cell.
|
// whole window in one statement rather than one per cell.
|
||||||
selected: false,
|
selected: false,
|
||||||
|
anchor: false,
|
||||||
collection_count: 0,
|
collection_count: 0,
|
||||||
// Likewise filled by `sync_ratings` below — one query for the
|
// Likewise filled by `sync_ratings` below — one query for the
|
||||||
// window, not one per cell.
|
// window, not one per cell.
|
||||||
|
|||||||
@@ -430,6 +430,14 @@ export struct LibraryCell {
|
|||||||
// Part of the current selection. Selection is what a drag carries, so this
|
// Part of the current selection. Selection is what a drag carries, so this
|
||||||
// has to be per-cell state rather than a single "current" index.
|
// has to be per-cell state rather than a single "current" index.
|
||||||
selected: bool,
|
selected: bool,
|
||||||
|
// The cell a shift-click would extend *from* — the last one plainly
|
||||||
|
// clicked. Marked because otherwise the gesture has a hidden operand: the
|
||||||
|
// user picks the far end of a run with no way to see which end the range
|
||||||
|
// is being measured from, and a range that comes out wrong gives no clue
|
||||||
|
// why. At most one cell in the library carries this, and often none of
|
||||||
|
// them is on screen — which is itself the answer to "why did that select
|
||||||
|
// so much".
|
||||||
|
anchor: bool,
|
||||||
// How many collections this image belongs to. An image can be in many at
|
// How many collections this image belongs to. An image can be in many at
|
||||||
// once, and without a cue the grid gives no hint that a photograph has
|
// once, and without a cue the grid gives no hint that a photograph has
|
||||||
// already been filed — the user re-files it, or hunts for where it went.
|
// already been filed — the user re-files it, or hunts for where it went.
|
||||||
@@ -2687,6 +2695,22 @@ export component LibraryGrid inherits Rectangle {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The end a shift-click measures from. An inner ring rather
|
||||||
|
// than a colour of its own: it has to be legible against a
|
||||||
|
// thumbnail of any brightness and must not read as a second
|
||||||
|
// kind of selection, which a hue would.
|
||||||
|
Rectangle {
|
||||||
|
visible: cell.anchor;
|
||||||
|
x: 4px;
|
||||||
|
y: 4px;
|
||||||
|
width: parent.width - 8px;
|
||||||
|
height: parent.height - 8px;
|
||||||
|
background: transparent;
|
||||||
|
border-width: 1px;
|
||||||
|
border-color: Theme.selected-ring;
|
||||||
|
border-radius: Theme.radius-sm;
|
||||||
|
}
|
||||||
|
|
||||||
// Selection only. The drag is the enclosing `DragArea`'s
|
// Selection only. The drag is the enclosing `DragArea`'s
|
||||||
// business, and Slint keeps a click distinct from a drag for
|
// business, and Slint keeps a click distinct from a drag for
|
||||||
// us — which is exactly the arbitration the hand-rolled version
|
// us — which is exactly the arbitration the hand-rolled version
|
||||||
|
|||||||
Reference in New Issue
Block a user