Make the thin ring the only mark a selected photograph carries
Two treatments said "selected" and neither said it well. A selected cell got a 2px border and a lifted fill. The border is drawn on the outside of a cell whose content sits 6px in, so it ate into the thumbnail: selecting appeared to nudge the photograph. Inside it, a second thin ring marked the anchor — the end a shift-click measures from — and that ring was the clearest thing on the cell, so it read as *the* selection to everyone who had not written it. Worse, the ring outlived what it described. An anchor survives a deselection, so a thin box sat around the last photograph touched with nothing selected at all, indistinguishable from a cell that had stayed behind. That is the one thing a selection cue must never be: ambiguous about whether something is selected. So the ring is now what it already looked like. One mark, drawn inside the cell and 4px clear of its edge, so it never touches the thumbnail and never changes a dimension — selecting adds ink and moves nothing. Two pixels rather than one, because it is now carrying the whole cue across forty cells at arm's length against a thumbnail of any brightness. The outer border is hover alone. The anchor keeps no mark, and loses nothing it was earning: the bar says "Tap the last photograph" while a range is armed, which answers the question the ring existed to answer. The ordinal still lives in the controller and still decides where a range extends from; what is gone is the claim that the user needs to see it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -898,19 +898,25 @@ pub fn sync_lifted(window: &AppWindow, lifted: &[ImageId], ids: &[ImageId]) {
|
||||
pub fn sync_selection(window: &AppWindow, ctl: &Rc<CollectionsController>, ids: &[ImageId]) {
|
||||
let selection = ctl.selection.borrow();
|
||||
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));
|
||||
|
||||
// The anchor is deliberately **not** pushed to the grid.
|
||||
//
|
||||
// It used to be, and it was drawn as a thin ring inside the cell. Two
|
||||
// things were wrong with that. It was the clearest mark on the cell, so it
|
||||
// read as *the* selection to anyone who had not written it; and an anchor
|
||||
// outlives a deselection, so the ring sat around the last photograph
|
||||
// touched while nothing was selected at all — indistinguishable from a cell
|
||||
// that had stayed behind.
|
||||
//
|
||||
// The ordinal still lives in the controller and still decides where a range
|
||||
// extends from. What is gone is the claim that the user needs to see it:
|
||||
// the selection bar says "Tap the last photograph" while a range is armed,
|
||||
// which answers the question the ring was there to answer.
|
||||
for (row, id) in ids.iter().enumerate() {
|
||||
let want = selection.contains(id);
|
||||
let anchored = anchor_row == Some(row);
|
||||
if let Some(mut cell) = model.row_data(row) {
|
||||
if cell.selected != want || cell.anchor != anchored {
|
||||
if cell.selected != want {
|
||||
cell.selected = want;
|
||||
cell.anchor = anchored;
|
||||
model.set_row_data(row, cell);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2305,7 +2305,6 @@ fn load_window(window: &AppWindow, ctl: &Rc<LibraryController>) {
|
||||
// which owns the selection and queries the badge counts for the
|
||||
// whole window in one statement rather than one per cell.
|
||||
selected: false,
|
||||
anchor: false,
|
||||
collection_count: 0,
|
||||
// Likewise filled by `sync_ratings` below — one query for the
|
||||
// window, not one per cell.
|
||||
|
||||
Reference in New Issue
Block a user