Merge branch 'master' into feat/library-toolbar
# Conflicts: # docs/gestures.md # docs/traceability.md
This commit is contained in:
+141
-9
@@ -649,6 +649,7 @@ export component PhotoRoll inherits Rectangle {
|
||||
}
|
||||
}
|
||||
|
||||
// TRACES: NFR-A11Y-3
|
||||
// A row of five stars, readable at a glance and clickable to set a rating.
|
||||
//
|
||||
// **Filled versus empty carries the meaning, not colour.** NFR-A11Y-3 forbids
|
||||
@@ -796,6 +797,7 @@ export component StarStrip inherits Rectangle {
|
||||
}
|
||||
}
|
||||
|
||||
// TRACES: NFR-A11Y-3
|
||||
// The pick/reject mark.
|
||||
//
|
||||
// A shape rather than a colour, for the same NFR-A11Y-3 reason as the stars:
|
||||
@@ -1203,6 +1205,40 @@ export component LibraryGrid inherits Rectangle {
|
||||
// keys: Escape
|
||||
in property <bool> select-mode: false;
|
||||
callback toggle-select-mode();
|
||||
|
||||
/// TRACES: FR-CAT-7 | FR-UI-4
|
||||
/// The photograph a press has held long enough to pick up, or `-1`.
|
||||
///
|
||||
/// **The visible half of a gesture that was folklore.** A finger on a cell
|
||||
/// is ambiguous — it may be starting a scroll or taking hold of a
|
||||
/// photograph — and Slint resolves that by giving the `Flickable` the first
|
||||
/// half-second: any press that travels more than a few pixels vertically
|
||||
/// inside it becomes a scroll, and the drag never begins. Only a fast
|
||||
/// sideways flick, or waiting the half-second out, ever picked a
|
||||
/// photograph up, and nothing on the screen said so. The user's account of
|
||||
/// it was that dragging "sometimes works".
|
||||
///
|
||||
/// So the wait is given a mark. The same hold that turns on selection mode
|
||||
/// sets this, a ring opens outward around the cell, and from that moment
|
||||
/// the drag is the only thing the finger can be doing — the grid below is
|
||||
/// no longer `interactive`, so there is no scroll left to lose to. The cue
|
||||
/// can only ever arrive *after* the ambiguity has passed, which is the
|
||||
/// honest direction: once the ring is open, dragging works.
|
||||
///
|
||||
/// A row of the loaded window, like every other row here. It is cleared
|
||||
/// when the press ends and when a drag finishes, and the grid cannot
|
||||
/// scroll while it is set, so it cannot outlive the window it indexes.
|
||||
// GESTURE: Pick a photograph up to drag it
|
||||
// where: Library grid
|
||||
// touch: Press and hold it until a ring opens around it, then drag
|
||||
// pointer: Drag it
|
||||
// why: A finger on a photograph might be starting a scroll, and for
|
||||
// the first half-second the grid assumes it is. Holding says
|
||||
// otherwise, and the ring is the grid saying it heard — from
|
||||
// there the drag cannot be lost to a scroll. A mouse never
|
||||
// waits: the cursor is precise enough that a sideways drag is
|
||||
// unambiguous from the first pixel.
|
||||
in-out property <int> held-row: -1;
|
||||
/// TRACES: FR-CAT-5
|
||||
// --- reordering a manual collection (FR-CAT-7) --------------------------
|
||||
//
|
||||
@@ -1285,15 +1321,29 @@ export component LibraryGrid inherits Rectangle {
|
||||
/// selected. The name arrives from the sheet below rather than being
|
||||
/// invented by Rust and corrected afterwards — see `naming`.
|
||||
callback collection-from-selection(string);
|
||||
/// The drag payload: the selected image ids, wrapped by Rust. Called when a
|
||||
/// drag starts, so it always reflects the selection as it is at that moment.
|
||||
/// **What arms the drag, not what it carries.**
|
||||
///
|
||||
/// `DragArea` refuses to begin a drag while its `data` is empty, and it
|
||||
/// asks on every pointer event — including the first one, long before
|
||||
/// anything is being dragged. A binding that calls a callback is evaluated
|
||||
/// once and cached, because there is nothing for Slint to invalidate it on,
|
||||
/// so whatever this answers the first time a finger touches a cell is what
|
||||
/// that cell's `DragArea` believes for the rest of its life.
|
||||
///
|
||||
/// It is therefore *not* the payload the drop reads, whatever it looks
|
||||
/// like: what travels is `dragging` in `collections_ui`, recorded by
|
||||
/// `drag-started` and read back by `dropped-on`. See the Rust side for why
|
||||
/// this has to answer something non-empty unconditionally.
|
||||
pure callback drag-payload() -> data-transfer;
|
||||
/// What travels under the cursor: the dragged thumbnail, or a fanned stack
|
||||
/// of them where several are being carried. Composited in Rust, because
|
||||
/// Slint accepts one bitmap here and cannot draw a pile of images into it.
|
||||
in property <image> drag-image;
|
||||
/// A drag began on this cell. Lets Rust promote an unselected cell to the
|
||||
/// selection before the payload is read.
|
||||
/// A drag began on this cell.
|
||||
///
|
||||
/// This, not `drag-payload`, is where what the drag carries is decided: it
|
||||
/// fires with the selection as it stands at the moment the drag starts, and
|
||||
/// lets Rust promote an unselected cell into the selection first.
|
||||
callback drag-started(int);
|
||||
/// The drag ended — dropped or cancelled. Clears the transient UI state.
|
||||
callback drag-finished();
|
||||
@@ -2516,11 +2566,27 @@ export component LibraryGrid inherits Rectangle {
|
||||
|
||||
// --- the grid -----------------------------------------------------
|
||||
//
|
||||
// `interactive` stays true: `DragArea` and `Flickable` arbitrate
|
||||
// properly, so dragging a cell drags the cell and dragging the
|
||||
// background still flicks the grid. (This is the part a hand-rolled
|
||||
// TouchArea gesture could not do — see the drag comments above.)
|
||||
// **Scrolls until a photograph has been picked up, and not after.**
|
||||
//
|
||||
// `DragArea` and `Flickable` do arbitrate, but not evenly: the
|
||||
// Flickable claims any press that travels more than eight pixels
|
||||
// along its own axis within half a second of landing, and it holds
|
||||
// that claim until the finger lifts. That is right for the ordinary
|
||||
// case — a finger that moves is almost always scrolling — and it is
|
||||
// why dragging the background still flicks the grid.
|
||||
//
|
||||
// It is wrong once the user has said otherwise. `held-row` is that
|
||||
// saying: a press that has stayed put long enough to be a pick-up,
|
||||
// marked on the cell so the user can see it. From there this stops
|
||||
// being interactive and the drag has nothing left to lose to.
|
||||
//
|
||||
// Today the hold outlasts the Flickable's window anyway, so this
|
||||
// mostly makes an accident into a guarantee — the arbitration stops
|
||||
// depending on two constants in different crates staying in the
|
||||
// order they happen to be in. The wheel is unaffected: `interactive`
|
||||
// does not gate it.
|
||||
if root.total > 0: grid-scroll := Flickable {
|
||||
interactive: root.held-row < 0;
|
||||
// Ctrl+wheel resizes the cells; a plain wheel is declined and
|
||||
// falls through to the Flickable's own scrolling. Two jobs on
|
||||
// one gesture, distinguished by the modifier — the convention
|
||||
@@ -2740,6 +2806,7 @@ export component LibraryGrid inherits Rectangle {
|
||||
// is what a join table means, and it is why the modifier-free
|
||||
// gesture must not be `move`.
|
||||
allow-copy: true;
|
||||
// Not the payload — the arming. See `drag-payload`.
|
||||
data: root.drag-payload();
|
||||
// What travels under the cursor is the photograph itself — and
|
||||
// where several are being dragged, a stack of them. Composited
|
||||
@@ -2815,7 +2882,19 @@ export component LibraryGrid inherits Rectangle {
|
||||
// photograph, which is exactly the wrong feedback for a
|
||||
// gesture whose whole job is to say "this one". One
|
||||
// treatment, drawn inside, in one place.
|
||||
border-width: cell-touch.has-hover ? 1px : 0px;
|
||||
//
|
||||
// **Not drawn at all once there has been a finger.** A
|
||||
// hand has no hover to give, so on a tablet this ring can
|
||||
// only ever be wrong — and it is wrong in the worst way,
|
||||
// because it is the selection ring's own colour a pixel
|
||||
// thinner. `has-hover` is not reliably cleared on touch:
|
||||
// a release normally brings an `Exit` with it, but the one
|
||||
// that ends a pinch does not, so every pinch to resize the
|
||||
// thumbnails left a ring around whichever cell a finger
|
||||
// happened to have started on. The grid then showed boxes
|
||||
// around photographs that were not selected, with no way
|
||||
// to tell them from ones that were. See `touched`.
|
||||
border-width: cell-touch.has-hover && !root.touched ? 1px : 0px;
|
||||
border-color: Theme.selected-ring;
|
||||
clip: true;
|
||||
|
||||
@@ -3055,6 +3134,8 @@ export component LibraryGrid inherits Rectangle {
|
||||
root.cell-clicked(i);
|
||||
}
|
||||
self.click-pending = false;
|
||||
// Down again, whether or not it was ever up.
|
||||
root.held-row = -1;
|
||||
root.cell-press-ended();
|
||||
}
|
||||
// `cancel` is the important ending: the Flickable
|
||||
@@ -3063,6 +3144,12 @@ export component LibraryGrid inherits Rectangle {
|
||||
// would come to rest as a long press and select it.
|
||||
if (ev.kind == PointerEventKind.cancel) {
|
||||
self.click-pending = false;
|
||||
// Including the cancel a starting drag sends:
|
||||
// by then the `DragArea` has the gesture and
|
||||
// `lifted` is the mark that matters, so there
|
||||
// is no scroll left to lose and nothing to
|
||||
// keep the ring open for.
|
||||
root.held-row = -1;
|
||||
root.cell-press-ended();
|
||||
}
|
||||
}
|
||||
@@ -3236,6 +3323,51 @@ export component LibraryGrid inherits Rectangle {
|
||||
border-radius: 1.5px;
|
||||
}
|
||||
}
|
||||
|
||||
// **The photograph is in your hand now.**
|
||||
//
|
||||
// A ring that opens outward around the cell a press has held
|
||||
// long enough to pick up (see `held-row`), so the wait the
|
||||
// gesture needs has something to end in. Without it the user
|
||||
// is holding a finger on glass with no way to know whether
|
||||
// anything has happened — which is what made dragging feel
|
||||
// like a coin toss.
|
||||
//
|
||||
// **Drawn after the cells, not on one.** Cells are one `for`,
|
||||
// and z-order inside a `for` is the loop order — a cell that
|
||||
// grew past its bounds would stand over its left and top
|
||||
// neighbours and be cut off by its right and bottom ones,
|
||||
// which reads as a rendering fault rather than as a lift. One
|
||||
// element after the loop is above every cell by construction,
|
||||
// and there is only ever one photograph in the hand.
|
||||
//
|
||||
// `visible` rather than `if`, so it has somewhere to animate
|
||||
// *from*: an `if` builds the ring at its final size and it
|
||||
// would appear rather than open.
|
||||
//
|
||||
// `Theme.active` and 3px, deliberately unlike the selection
|
||||
// ring inside the cell — two marks that meant different things
|
||||
// in one colour is the mistake the hover ring made.
|
||||
Rectangle {
|
||||
property <length> pitch: root.cell-size + Theme.gap;
|
||||
property <length> reach: root.held-row >= 0 ? 5px : 0px;
|
||||
|
||||
visible: root.held-row >= 0;
|
||||
x: Theme.gap
|
||||
+ mod(root.held-row + root.offset, root.columns) * self.pitch
|
||||
- self.reach;
|
||||
y: Theme.gap
|
||||
+ floor((root.held-row + root.offset) / root.columns) * self.pitch
|
||||
- self.reach;
|
||||
width: root.cell-size + 2 * self.reach;
|
||||
height: root.cell-size + 2 * self.reach;
|
||||
animate x, y, width, height { duration: 120ms; easing: ease-out; }
|
||||
|
||||
background: transparent;
|
||||
border-width: 3px;
|
||||
border-color: Theme.active;
|
||||
border-radius: Theme.radius;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user