Centre the photo roll on the frame it opens with
The roll brought the open photograph into view by the shortest move, which is right for stepping along it and wrong for the first look: a frame near either end of the loaded window arrived hard against an edge, with nothing on that side to give it any context. It now centres on the first settle of a develop session and steps minimally after that. A one-shot request that the strip itself clears -- the only thing that knows the request has been honoured is the code honouring it -- rather than something recomputed on creation, because the strip is created far more often than a session begins: leaving develop for Settings and coming back rebuilds it, and re-centring then would undo a roll the user had scrolled by hand. Raised on the two ways into develop from the grid, and not on a pick along the roll, which is a step within a session rather than the start of one. Centring is clamped to the ends: the third photograph of a window cannot be centred without scrolling empty space in beside it, and a strip that begins with a gap reads as broken rather than as centred. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -511,6 +511,14 @@ export component AppWindow inherits Window {
|
||||
/// Whether the photo roll is out. Owned here rather than by the roll, so
|
||||
/// it survives the develop view being torn down and rebuilt.
|
||||
in-out property <bool> roll-open: false;
|
||||
/// Centre the roll on the open photograph the next time the strip settles.
|
||||
///
|
||||
/// Raised by Rust when a develop session begins — a photograph opened from
|
||||
/// the grid — and cleared by the strip itself once honoured. Owned here for
|
||||
/// the same reason `roll-open` is: the develop view is rebuilt whenever an
|
||||
/// overlay screen covers it, and a request raised before that must survive
|
||||
/// it. See `PhotoRoll::centre-request`.
|
||||
in-out property <bool> library-roll-centre: false;
|
||||
/// A photograph was chosen from the roll: the row within the loaded
|
||||
/// window, which is what a cell click reports too.
|
||||
callback library-roll-pick(int);
|
||||
@@ -2235,6 +2243,7 @@ in property <bool> panel-visible: true;
|
||||
cells: root.library-cells;
|
||||
current: root.library-roll-current;
|
||||
open <=> root.roll-open;
|
||||
centre-request <=> root.library-roll-centre;
|
||||
pick(i) => { root.library-roll-pick(i); }
|
||||
}
|
||||
|
||||
|
||||
@@ -511,6 +511,24 @@ export component PhotoRoll inherits Rectangle {
|
||||
in property <int> current: -1;
|
||||
in-out property <bool> open: false;
|
||||
|
||||
/// Centre the open photograph the next time the strip settles, instead of
|
||||
/// merely bringing it into view.
|
||||
///
|
||||
/// **A one-shot request, cleared here rather than by Rust**, because the
|
||||
/// only thing that knows the request has been honoured is the code that
|
||||
/// honours it. Rust raises it when a photograph is opened *from the grid* —
|
||||
/// the start of a develop session — and from then on every pick along the
|
||||
/// roll uses `reveal()`, which moves as little as will show the mark. That
|
||||
/// is the difference the user asked for: arriving at a photograph should
|
||||
/// show what is either side of it, and stepping along from there should not
|
||||
/// keep yanking the strip back to centre.
|
||||
///
|
||||
/// It has to be a flag the strip consumes rather than something recomputed
|
||||
/// on creation, because the strip is created more often than a session
|
||||
/// begins: leaving develop for Settings and coming back rebuilds it, and
|
||||
/// re-centring then would undo a roll the user had scrolled by hand.
|
||||
in-out property <bool> centre-request: false;
|
||||
|
||||
/// A thumbnail was chosen. The row within the loaded window, matching what
|
||||
/// a cell click reports.
|
||||
callback pick(int);
|
||||
@@ -613,13 +631,69 @@ export component PhotoRoll inherits Rectangle {
|
||||
}
|
||||
}
|
||||
|
||||
// Put the open photograph in the middle of the strip, with as
|
||||
// much of the roll either side of it as there is.
|
||||
//
|
||||
// Clamped to the ends rather than centred unconditionally: the
|
||||
// third photograph of a window cannot be centred without
|
||||
// scrolling empty space in on the left, and a strip that starts
|
||||
// with a gap reads as broken rather than as centred.
|
||||
function centre() {
|
||||
if (root.current < 0) {
|
||||
return;
|
||||
}
|
||||
let left = root.pad + root.current * (root.thumb + root.pad);
|
||||
let right = max(0px, self.viewport-width - self.width);
|
||||
let want = left + root.thumb / 2 - self.width / 2;
|
||||
self.viewport-x = -min(right, max(0px, want));
|
||||
}
|
||||
|
||||
// Which of the two the strip does, and the only place the
|
||||
// request is cleared.
|
||||
//
|
||||
// **Nothing happens before the strip has a width.** Centring
|
||||
// divides by it, and `init` runs before layout — so honouring
|
||||
// the request there would scroll to an arithmetic answer based
|
||||
// on a width of zero *and* consume the flag, leaving the roll
|
||||
// wrong with nothing left to correct it. Deferring instead is
|
||||
// safe because the width arriving is itself a change, and
|
||||
// `changed width` below settles again once it has.
|
||||
function settle() {
|
||||
if (self.width <= 0px) {
|
||||
return;
|
||||
}
|
||||
if (root.centre-request) {
|
||||
root.centre-request = false;
|
||||
self.centre();
|
||||
} else {
|
||||
self.reveal();
|
||||
}
|
||||
}
|
||||
|
||||
property <int> mark: root.current;
|
||||
changed mark => { self.reveal(); }
|
||||
changed mark => { self.settle(); }
|
||||
property <bool> shown: root.open;
|
||||
changed shown => {
|
||||
if (self.shown) { self.reveal(); }
|
||||
if (self.shown) { self.settle(); }
|
||||
}
|
||||
|
||||
// On creation, and again when the strip is first given a
|
||||
// width. None of the four is redundant.
|
||||
//
|
||||
// The strip is rebuilt on every entry to develop, so `mark` and
|
||||
// `shown` are *initialised* rather than changed and neither
|
||||
// handler above fires — which is precisely the moment a session
|
||||
// begins and the request is waiting to be honoured. A roll left
|
||||
// open from the previous session would otherwise slide in
|
||||
// showing whatever it showed last.
|
||||
//
|
||||
// `init` runs before layout, though, so it usually finds no
|
||||
// width and defers; `changed width` is what actually honours the
|
||||
// request a frame later. Both are kept because the order is not
|
||||
// guaranteed, and a settle that has nothing to do is a no-op.
|
||||
init => { self.settle(); }
|
||||
changed width => { self.settle(); }
|
||||
|
||||
for cell[i] in root.cells: Rectangle {
|
||||
x: root.pad + i * (root.thumb + root.pad);
|
||||
y: root.pad;
|
||||
|
||||
Reference in New Issue
Block a user