Move the library grid's cells and selection onto their own Slint global
AppWindow carried the grid's loaded window of cells, the keyboard cursor, drag and drop, the held-row long-press state, columns and cell size, the scroll and viewport bookkeeping, the photo roll's pick and centre-request, and the local-only/reorder/collection-filing gestures that act on a selection, as properties and callbacks on the root component. That state now lives in the `Library` global declared in library.slint, next to the structs (LibraryCell, TimelineBar, KeywordRow, PersonChip) it and the grid's other components already share; Rust reaches it through window.global::<Library>() instead of window.set_/get_/on_/invoke_ on the root, the same change collections.slint's `Collections` global made for the sidebar. library-visible stays on AppWindow: it is computed from active-page and active-view, the shell's own routing state, which a global cannot read. Everything else still prefixed library- — the timeline, the filter bar, ratings and flags, keywording, and the routes and status lines — stays on the window for now and moves in the commits that follow.
This commit is contained in:
+246
-226
@@ -12,7 +12,7 @@ use std::rc::Rc;
|
||||
use slint::{ComponentHandle, Model as _};
|
||||
|
||||
use crate::AppWindow;
|
||||
use crate::{GestureRow, View};
|
||||
use crate::{GestureRow, Library, View};
|
||||
|
||||
use super::controller::{
|
||||
LibraryController, OpenImage, MAX_CELL_SIZE, MIN_CELL_SIZE, MIN_WINDOW, SCREENFULS,
|
||||
@@ -49,7 +49,7 @@ fn move_cursor(
|
||||
delta: i32,
|
||||
extend: bool,
|
||||
) {
|
||||
let total = window.get_library_total().max(0) as usize;
|
||||
let total = window.global::<Library>().get_library_total().max(0) as usize;
|
||||
if total == 0 {
|
||||
return;
|
||||
}
|
||||
@@ -93,11 +93,11 @@ fn place_cursor(
|
||||
// quarter in, exactly as a scroll does it, so continuing in the same
|
||||
// direction has loaded cells to move into rather than another reload on
|
||||
// the very next press.
|
||||
let loaded = window.get_library_cells().row_count();
|
||||
let loaded = window.global::<Library>().get_library_cells().row_count();
|
||||
let offset = *ctl.offset.borrow();
|
||||
if at < offset || at >= offset + loaded {
|
||||
let size = *ctl.window.borrow();
|
||||
let total = window.get_library_total().max(0) as usize;
|
||||
let total = window.global::<Library>().get_library_total().max(0) as usize;
|
||||
*ctl.offset.borrow_mut() = window_start(at, size, total);
|
||||
load_window(window, ctl);
|
||||
}
|
||||
@@ -122,7 +122,7 @@ fn place_cursor(
|
||||
}
|
||||
|
||||
crate::collections_ui::select_row(window, coll, &ids, offset, row, false, extend);
|
||||
window.set_library_cursor(at as i32);
|
||||
window.global::<Library>().set_library_cursor(at as i32);
|
||||
}
|
||||
|
||||
/// Say where in the library the photograph now open sits.
|
||||
@@ -136,7 +136,7 @@ fn place_cursor(
|
||||
fn report_position(window: &AppWindow, ctl: &Rc<LibraryController>, row: usize) {
|
||||
let offset = *ctl.offset.borrow();
|
||||
window.set_index((offset + row) as i32);
|
||||
window.set_total(window.get_library_total());
|
||||
window.set_total(window.global::<Library>().get_library_total());
|
||||
}
|
||||
|
||||
/// Connect the grid's callbacks.
|
||||
@@ -175,7 +175,9 @@ pub fn wire<F>(
|
||||
// claims the gesture before the delay it would forward after. Seeding it
|
||||
// here means the stars are on screen before the first touch rather than
|
||||
// after it, which is the whole point of showing them.
|
||||
window.set_library_touched(cfg!(target_os = "android"));
|
||||
window
|
||||
.global::<Library>()
|
||||
.set_library_touched(cfg!(target_os = "android"));
|
||||
|
||||
// TRACES: FR-UI-4
|
||||
// The gesture reference. Pushed once, here, rather than on demand: the
|
||||
@@ -209,42 +211,44 @@ pub fn wire<F>(
|
||||
let ctl = ctl.clone();
|
||||
let coll_for_click = coll_ctl.clone();
|
||||
let on_open_image = on_open_image.clone();
|
||||
window.on_library_cell_clicked(move |i| {
|
||||
let Some(w) = weak.upgrade() else { return };
|
||||
window
|
||||
.global::<Library>()
|
||||
.on_library_cell_clicked(move |i| {
|
||||
let Some(w) = weak.upgrade() else { return };
|
||||
|
||||
// The press stayed put, so it was a tap and not a drag: whatever it
|
||||
// held back can be applied now. See `collections_ui::Press`.
|
||||
crate::collections_ui::commit_press(&w, &coll_for_click, &ctl.visible_ids());
|
||||
// The press stayed put, so it was a tap and not a drag: whatever it
|
||||
// held back can be applied now. See `collections_ui::Press`.
|
||||
crate::collections_ui::commit_press(&w, &coll_for_click, &ctl.visible_ids());
|
||||
|
||||
// A ctrl- or shift-click is a selection gesture. Opening the image
|
||||
// too would throw the user out of the grid mid-selection.
|
||||
if coll_for_click.press_was_modified() {
|
||||
return;
|
||||
}
|
||||
// A ctrl- or shift-click is a selection gesture. Opening the image
|
||||
// too would throw the user out of the grid mid-selection.
|
||||
if coll_for_click.press_was_modified() {
|
||||
return;
|
||||
}
|
||||
|
||||
let path = ctl.paths.borrow().get(i as usize).cloned();
|
||||
if let Some(path) = path {
|
||||
// Leave the grid for the develop view. The status bar's
|
||||
// "‹ Library" button comes back here.
|
||||
w.set_active_view(View::Develop);
|
||||
// Which cell the develop view is now showing, so the photo
|
||||
// roll opens marking it rather than marking nothing.
|
||||
w.set_library_roll_current(i);
|
||||
// A develop session begins here, so the roll centres on this
|
||||
// photograph the first time it settles rather than merely
|
||||
// scrolling it into view at one edge. Raised here and not in
|
||||
// `on_library_roll_pick`, which is a step *within* a session —
|
||||
// see `PhotoRoll::centre-request`.
|
||||
w.set_library_roll_centre(true);
|
||||
on_open_image(path);
|
||||
report_position(&w, &ctl, i as usize);
|
||||
// TRACES: FR-UI-8
|
||||
// Written now rather than on a settle: which view you are in is
|
||||
// the field a debounce is most likely to lose, and quitting
|
||||
// straight from develop is exactly the case worth getting right.
|
||||
write_place(&w, &ctl);
|
||||
}
|
||||
});
|
||||
let path = ctl.paths.borrow().get(i as usize).cloned();
|
||||
if let Some(path) = path {
|
||||
// Leave the grid for the develop view. The status bar's
|
||||
// "‹ Library" button comes back here.
|
||||
w.set_active_view(View::Develop);
|
||||
// Which cell the develop view is now showing, so the photo
|
||||
// roll opens marking it rather than marking nothing.
|
||||
w.global::<Library>().set_library_roll_current(i);
|
||||
// A develop session begins here, so the roll centres on this
|
||||
// photograph the first time it settles rather than merely
|
||||
// scrolling it into view at one edge. Raised here and not in
|
||||
// `on_library_roll_pick`, which is a step *within* a session —
|
||||
// see `PhotoRoll::centre-request`.
|
||||
w.global::<Library>().set_library_roll_centre(true);
|
||||
on_open_image(path);
|
||||
report_position(&w, &ctl, i as usize);
|
||||
// TRACES: FR-UI-8
|
||||
// Written now rather than on a settle: which view you are in is
|
||||
// the field a debounce is most likely to lose, and quitting
|
||||
// straight from develop is exactly the case worth getting right.
|
||||
write_place(&w, &ctl);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// TRACES: FR-UI-4
|
||||
@@ -259,12 +263,12 @@ pub fn wire<F>(
|
||||
let weak = window.as_weak();
|
||||
let ctl = ctl.clone();
|
||||
let on_open_image = on_open_image.clone();
|
||||
window.on_library_roll_pick(move |i| {
|
||||
window.global::<Library>().on_library_roll_pick(move |i| {
|
||||
let Some(w) = weak.upgrade() else { return };
|
||||
let Some(path) = ctl.paths.borrow().get(i as usize).cloned() else {
|
||||
return;
|
||||
};
|
||||
w.set_library_roll_current(i);
|
||||
w.global::<Library>().set_library_roll_current(i);
|
||||
on_open_image(path);
|
||||
report_position(&w, &ctl, i as usize);
|
||||
// TRACES: FR-UI-8
|
||||
@@ -307,10 +311,12 @@ fn wire_grid_cursor_and_zoom(
|
||||
let weak = window.as_weak();
|
||||
let ctl = ctl.clone();
|
||||
let coll = coll_ctl.clone();
|
||||
window.on_library_move_cursor(move |delta, extend| {
|
||||
let Some(w) = weak.upgrade() else { return };
|
||||
move_cursor(&w, &ctl, &coll, delta, extend);
|
||||
});
|
||||
window
|
||||
.global::<Library>()
|
||||
.on_library_move_cursor(move |delta, extend| {
|
||||
let Some(w) = weak.upgrade() else { return };
|
||||
move_cursor(&w, &ctl, &coll, delta, extend);
|
||||
});
|
||||
}
|
||||
|
||||
{
|
||||
@@ -318,7 +324,7 @@ fn wire_grid_cursor_and_zoom(
|
||||
let ctl = ctl.clone();
|
||||
let coll = coll_ctl.clone();
|
||||
let on_open = on_open_image.clone();
|
||||
window.on_library_open_cursor(move || {
|
||||
window.global::<Library>().on_library_open_cursor(move || {
|
||||
let Some(w) = weak.upgrade() else { return };
|
||||
// The cursor is a library ordinal and `paths` is the loaded
|
||||
// window, so the row is the difference. A cursor outside the
|
||||
@@ -333,8 +339,9 @@ fn wire_grid_cursor_and_zoom(
|
||||
w.set_active_view(View::Develop);
|
||||
// As on a click: the roll marks what is open, and centres on it
|
||||
// because this too begins a session.
|
||||
w.set_library_roll_current(row.unwrap_or(0) as i32);
|
||||
w.set_library_roll_centre(true);
|
||||
w.global::<Library>()
|
||||
.set_library_roll_current(row.unwrap_or(0) as i32);
|
||||
w.global::<Library>().set_library_roll_centre(true);
|
||||
on_open(path);
|
||||
report_position(&w, &ctl, row.unwrap_or(0));
|
||||
write_place(&w, &ctl);
|
||||
@@ -350,33 +357,35 @@ fn wire_grid_cursor_and_zoom(
|
||||
{
|
||||
let weak = window.as_weak();
|
||||
let ctl = ctl.clone();
|
||||
window.on_library_zoom_cells(move |delta| {
|
||||
let Some(w) = weak.upgrade() else { return };
|
||||
window
|
||||
.global::<Library>()
|
||||
.on_library_zoom_cells(move |delta| {
|
||||
let Some(w) = weak.upgrade() else { return };
|
||||
|
||||
let current = w.get_library_cell_size();
|
||||
let next = if delta > 0 {
|
||||
current * 1.25
|
||||
} else {
|
||||
current / 1.25
|
||||
}
|
||||
.clamp(MIN_CELL_SIZE, MAX_CELL_SIZE);
|
||||
let current = w.global::<Library>().get_library_cell_size();
|
||||
let next = if delta > 0 {
|
||||
current * 1.25
|
||||
} else {
|
||||
current / 1.25
|
||||
}
|
||||
.clamp(MIN_CELL_SIZE, MAX_CELL_SIZE);
|
||||
|
||||
if (next - current).abs() < 0.5 {
|
||||
return;
|
||||
}
|
||||
w.set_library_cell_size(next);
|
||||
if (next - current).abs() < 0.5 {
|
||||
return;
|
||||
}
|
||||
w.global::<Library>().set_library_cell_size(next);
|
||||
|
||||
// No need to forget anything on a class change: the class is part
|
||||
// of the request key, so cells that now want the large resolution
|
||||
// simply miss and ask for it, while the 256px ones they already
|
||||
// hold stay served.
|
||||
//
|
||||
// Deferred, like every other geometry change. This one was still
|
||||
// reloading inline — a full catalog re-read and model rebuild per
|
||||
// step, which is what a wheel spun through six steps paid six
|
||||
// times over.
|
||||
schedule_reload(&w, &ctl);
|
||||
});
|
||||
// No need to forget anything on a class change: the class is part
|
||||
// of the request key, so cells that now want the large resolution
|
||||
// simply miss and ask for it, while the 256px ones they already
|
||||
// hold stay served.
|
||||
//
|
||||
// Deferred, like every other geometry change. This one was still
|
||||
// reloading inline — a full catalog re-read and model rebuild per
|
||||
// step, which is what a wheel spun through six steps paid six
|
||||
// times over.
|
||||
schedule_reload(&w, &ctl);
|
||||
});
|
||||
}
|
||||
|
||||
// TRACES: FR-UI-4
|
||||
@@ -391,20 +400,22 @@ fn wire_grid_cursor_and_zoom(
|
||||
{
|
||||
let weak = window.as_weak();
|
||||
let ctl = ctl.clone();
|
||||
window.on_library_pinch_cells(move |ratio| {
|
||||
let Some(w) = weak.upgrade() else { return };
|
||||
if !(ratio.is_finite() && ratio > 0.0) {
|
||||
return;
|
||||
}
|
||||
window
|
||||
.global::<Library>()
|
||||
.on_library_pinch_cells(move |ratio| {
|
||||
let Some(w) = weak.upgrade() else { return };
|
||||
if !(ratio.is_finite() && ratio > 0.0) {
|
||||
return;
|
||||
}
|
||||
|
||||
let current = w.get_library_cell_size();
|
||||
let next = (current * ratio).clamp(MIN_CELL_SIZE, MAX_CELL_SIZE);
|
||||
if (next - current).abs() < 0.5 {
|
||||
return;
|
||||
}
|
||||
w.set_library_cell_size(next);
|
||||
schedule_reload(&w, &ctl);
|
||||
});
|
||||
let current = w.global::<Library>().get_library_cell_size();
|
||||
let next = (current * ratio).clamp(MIN_CELL_SIZE, MAX_CELL_SIZE);
|
||||
if (next - current).abs() < 0.5 {
|
||||
return;
|
||||
}
|
||||
w.global::<Library>().set_library_cell_size(next);
|
||||
schedule_reload(&w, &ctl);
|
||||
});
|
||||
}
|
||||
|
||||
// TRACES: FR-UI-4
|
||||
@@ -418,10 +429,12 @@ fn wire_grid_cursor_and_zoom(
|
||||
let weak = window.as_weak();
|
||||
let ctl = ctl.clone();
|
||||
let coll = coll_ctl.clone();
|
||||
window.on_library_pinch_started(move || {
|
||||
let Some(w) = weak.upgrade() else { return };
|
||||
crate::collections_ui::cancel_press(&w, &coll, &ctl.visible_ids());
|
||||
});
|
||||
window
|
||||
.global::<Library>()
|
||||
.on_library_pinch_started(move || {
|
||||
let Some(w) = weak.upgrade() else { return };
|
||||
crate::collections_ui::cancel_press(&w, &coll, &ctl.visible_ids());
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -462,25 +475,27 @@ fn wire_grid_sync_and_load(window: &AppWindow, ctl: &Rc<LibraryController>) {
|
||||
{
|
||||
let weak = window.as_weak();
|
||||
let ctl = ctl.clone();
|
||||
window.on_library_columns_changed(move || {
|
||||
if let Some(w) = weak.upgrade() {
|
||||
if !w.get_library_visible() {
|
||||
return;
|
||||
}
|
||||
window
|
||||
.global::<Library>()
|
||||
.on_library_columns_changed(move || {
|
||||
if let Some(w) = weak.upgrade() {
|
||||
if !w.get_library_visible() {
|
||||
return;
|
||||
}
|
||||
|
||||
// Deferred, and re-anchored when it lands — see
|
||||
// [`schedule_reload`]. A column change moves every cell in the
|
||||
// grid, because a cell is drawn at its absolute place in the
|
||||
// library and the row that resolves to is `index / columns`.
|
||||
// The viewport does not move with them, so without the
|
||||
// re-anchor the view is left pointing at rows the loaded window
|
||||
// no longer covers and the grid draws nothing at all — the
|
||||
// "gallery randomly goes blank until I scroll" report, whose
|
||||
// triggers are a resize, the sidebar opening, a zoom step, or
|
||||
// turning the tablet over.
|
||||
schedule_reload(&w, &ctl);
|
||||
}
|
||||
});
|
||||
// Deferred, and re-anchored when it lands — see
|
||||
// [`schedule_reload`]. A column change moves every cell in the
|
||||
// grid, because a cell is drawn at its absolute place in the
|
||||
// library and the row that resolves to is `index / columns`.
|
||||
// The viewport does not move with them, so without the
|
||||
// re-anchor the view is left pointing at rows the loaded window
|
||||
// no longer covers and the grid draws nothing at all — the
|
||||
// "gallery randomly goes blank until I scroll" report, whose
|
||||
// triggers are a resize, the sidebar opening, a zoom step, or
|
||||
// turning the tablet over.
|
||||
schedule_reload(&w, &ctl);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// The viewport changed size, so the window it can usefully hold changed
|
||||
@@ -489,23 +504,25 @@ fn wire_grid_sync_and_load(window: &AppWindow, ctl: &Rc<LibraryController>) {
|
||||
{
|
||||
let weak = window.as_weak();
|
||||
let ctl = ctl.clone();
|
||||
window.on_library_viewport_cells(move |on_screen| {
|
||||
let Some(w) = weak.upgrade() else { return };
|
||||
if !w.get_library_visible() {
|
||||
return;
|
||||
}
|
||||
let on_screen = (on_screen.max(0) as usize).max(1);
|
||||
let window_size = (on_screen * SCREENFULS).max(MIN_WINDOW);
|
||||
if window_size == *ctl.window.borrow() && on_screen == ctl.viewport_cells.get() {
|
||||
return;
|
||||
}
|
||||
ctl.viewport_cells.set(on_screen);
|
||||
*ctl.window.borrow_mut() = window_size;
|
||||
// Coalesced with the column change that almost always accompanies
|
||||
// it: resizing the cells alters both, and reloading once per report
|
||||
// meant two full rebuilds per zoom step.
|
||||
schedule_reload(&w, &ctl);
|
||||
});
|
||||
window
|
||||
.global::<Library>()
|
||||
.on_library_viewport_cells(move |on_screen| {
|
||||
let Some(w) = weak.upgrade() else { return };
|
||||
if !w.get_library_visible() {
|
||||
return;
|
||||
}
|
||||
let on_screen = (on_screen.max(0) as usize).max(1);
|
||||
let window_size = (on_screen * SCREENFULS).max(MIN_WINDOW);
|
||||
if window_size == *ctl.window.borrow() && on_screen == ctl.viewport_cells.get() {
|
||||
return;
|
||||
}
|
||||
ctl.viewport_cells.set(on_screen);
|
||||
*ctl.window.borrow_mut() = window_size;
|
||||
// Coalesced with the column change that almost always accompanies
|
||||
// it: resizing the cells alters both, and reloading once per report
|
||||
// meant two full rebuilds per zoom step.
|
||||
schedule_reload(&w, &ctl);
|
||||
});
|
||||
}
|
||||
|
||||
// Scrolling moves the loaded window through the library.
|
||||
@@ -515,118 +532,121 @@ fn wire_grid_sync_and_load(window: &AppWindow, ctl: &Rc<LibraryController>) {
|
||||
{
|
||||
let weak = window.as_weak();
|
||||
let ctl = ctl.clone();
|
||||
window.on_library_scrolled(move |first_visible| {
|
||||
let Some(w) = weak.upgrade() else { return };
|
||||
let first_visible = first_visible.max(0) as usize;
|
||||
window
|
||||
.global::<Library>()
|
||||
.on_library_scrolled(move |first_visible| {
|
||||
let Some(w) = weak.upgrade() else { return };
|
||||
let first_visible = first_visible.max(0) as usize;
|
||||
|
||||
// **A report from a grid that is not on screen is not a scroll.**
|
||||
//
|
||||
// The grid is gated on an `if`, so leaving it tears the whole
|
||||
// subtree down — and a Flickable being destroyed passes its viewport
|
||||
// through zero on the way out, which arrives here indistinguishable
|
||||
// from the user having flung the grid to the top. Everything below
|
||||
// then ran on the way *into* develop: the loaded window was reset to
|
||||
// offset zero, the model was rebuilt against the first rows of the
|
||||
// library, and a thumbnail batch was issued for photographs nobody
|
||||
// had asked to see. Those rebuilds landed while the grid was still
|
||||
// being taken apart, which is what flashed the library over the
|
||||
// develop view for the first few frames after a click — and on a
|
||||
// remote library it also spent a burst of requests on the top of the
|
||||
// catalog every single time an image was opened.
|
||||
//
|
||||
// **`library-visible`, not `show-library`, and the difference is the
|
||||
// whole bug.** `show-library` says "the library rather than develop"
|
||||
// and stays true while Settings, Import, People or the launch screen
|
||||
// replaces the window — all four of which take the grid down just as
|
||||
// opening an image does. So the teardown's scroll-to-zero passed this
|
||||
// guard, `resume_at` was set to 0, and coming back from any of those
|
||||
// four screens landed at the top of the library however deep in it the
|
||||
// user had been. The property is computed once in `app.slint` beside
|
||||
// the `if` it is spelled from, so the two cannot drift apart again.
|
||||
//
|
||||
// The guard used to cover only `resume_at`, for a narrower version
|
||||
// of the same reason. It belongs over the whole handler.
|
||||
if !w.get_library_visible() {
|
||||
return;
|
||||
}
|
||||
// **A report from a grid that is not on screen is not a scroll.**
|
||||
//
|
||||
// The grid is gated on an `if`, so leaving it tears the whole
|
||||
// subtree down — and a Flickable being destroyed passes its viewport
|
||||
// through zero on the way out, which arrives here indistinguishable
|
||||
// from the user having flung the grid to the top. Everything below
|
||||
// then ran on the way *into* develop: the loaded window was reset to
|
||||
// offset zero, the model was rebuilt against the first rows of the
|
||||
// library, and a thumbnail batch was issued for photographs nobody
|
||||
// had asked to see. Those rebuilds landed while the grid was still
|
||||
// being taken apart, which is what flashed the library over the
|
||||
// develop view for the first few frames after a click — and on a
|
||||
// remote library it also spent a burst of requests on the top of the
|
||||
// catalog every single time an image was opened.
|
||||
//
|
||||
// **`library-visible`, not `show-library`, and the difference is the
|
||||
// whole bug.** `show-library` says "the library rather than develop"
|
||||
// and stays true while Settings, Import, People or the launch screen
|
||||
// replaces the window — all four of which take the grid down just as
|
||||
// opening an image does. So the teardown's scroll-to-zero passed this
|
||||
// guard, `resume_at` was set to 0, and coming back from any of those
|
||||
// four screens landed at the top of the library however deep in it the
|
||||
// user had been. The property is computed once in `app.slint` beside
|
||||
// the `if` it is spelled from, so the two cannot drift apart again.
|
||||
//
|
||||
// The guard used to cover only `resume_at`, for a narrower version
|
||||
// of the same reason. It belongs over the whole handler.
|
||||
if !w.get_library_visible() {
|
||||
return;
|
||||
}
|
||||
|
||||
// Remember where the view is, so leaving for the develop view and
|
||||
// coming back returns here. Latched on every event rather than read
|
||||
// at departure: by the time the grid is hidden its scroll position
|
||||
// is only in the Flickable, which is about to be destroyed.
|
||||
ctl.resume_at.set(first_visible);
|
||||
// Remember where the view is, so leaving for the develop view and
|
||||
// coming back returns here. Latched on every event rather than read
|
||||
// at departure: by the time the grid is hidden its scroll position
|
||||
// is only in the Flickable, which is about to be destroyed.
|
||||
ctl.resume_at.set(first_visible);
|
||||
|
||||
// TRACES: FR-UI-8
|
||||
// And where the *next launch* will look for it. Debounced: a flick
|
||||
// reports several of these per screenful.
|
||||
note_place(&w, &ctl);
|
||||
// TRACES: FR-UI-8
|
||||
// And where the *next launch* will look for it. Debounced: a flick
|
||||
// reports several of these per screenful.
|
||||
note_place(&w, &ctl);
|
||||
|
||||
// The same position where a rebuilt grid will look for it.
|
||||
//
|
||||
// `scroll-to` is read by `seek()`, which runs on a `scroll-token`
|
||||
// change and on `init` — so writing it here without bumping the
|
||||
// token cannot move the grid that is on screen, and *is* what the
|
||||
// next one reads when it is built. That is every route back to the
|
||||
// grid at once: Settings, Import, People and the launch screen all
|
||||
// tear the subtree down and rebuild it, and none of them goes
|
||||
// through `on_back_to_library` to have the position replayed by
|
||||
// hand. Without this they each rebuilt against whatever `scroll-to`
|
||||
// was last *set* to — a stale scrub, or zero — and landed there.
|
||||
w.set_library_scroll_to(first_visible as i32);
|
||||
// The same position where a rebuilt grid will look for it.
|
||||
//
|
||||
// `scroll-to` is read by `seek()`, which runs on a `scroll-token`
|
||||
// change and on `init` — so writing it here without bumping the
|
||||
// token cannot move the grid that is on screen, and *is* what the
|
||||
// next one reads when it is built. That is every route back to the
|
||||
// grid at once: Settings, Import, People and the launch screen all
|
||||
// tear the subtree down and rebuild it, and none of them goes
|
||||
// through `on_back_to_library` to have the position replayed by
|
||||
// hand. Without this they each rebuilt against whatever `scroll-to`
|
||||
// was last *set* to — a stale scrub, or zero — and landed there.
|
||||
w.global::<Library>()
|
||||
.set_library_scroll_to(first_visible as i32);
|
||||
|
||||
// Move the timeline marker with the view. Scrolling the grid is a
|
||||
// way of moving through time just as scrubbing is, and a marker
|
||||
// that only ever moved on a scrub sat still while the photographs
|
||||
// beside it advanced by months — the axis said "when you are" and
|
||||
// was wrong the moment the user touched the wheel.
|
||||
//
|
||||
// This runs before the reload guard below, which fires only a few
|
||||
// times per screenful; the marker has to follow every event or it
|
||||
// would advance in visible jerks.
|
||||
//
|
||||
// Only the marker is moved, not the whole histogram: rebuilding
|
||||
// the bars means a `GROUP BY strftime` aggregate over the library,
|
||||
// far too much for every event of a flick. The bars do not change
|
||||
// as the grid scrolls anyway — only where the marker sits on them.
|
||||
//
|
||||
// Re-running the scrub would be wrong for a second reason: it sets
|
||||
// `scroll-to`, which would drive the grid from its own scroll.
|
||||
{
|
||||
let borrow = ctl.catalog.borrow();
|
||||
if let Some(catalog) = borrow.as_ref() {
|
||||
if let Some(when) = capture_time_at(&ctl, catalog, first_visible) {
|
||||
*ctl.current_bucket.borrow_mut() = Some(when);
|
||||
// Move the timeline marker with the view. Scrolling the grid is a
|
||||
// way of moving through time just as scrubbing is, and a marker
|
||||
// that only ever moved on a scrub sat still while the photographs
|
||||
// beside it advanced by months — the axis said "when you are" and
|
||||
// was wrong the moment the user touched the wheel.
|
||||
//
|
||||
// This runs before the reload guard below, which fires only a few
|
||||
// times per screenful; the marker has to follow every event or it
|
||||
// would advance in visible jerks.
|
||||
//
|
||||
// Only the marker is moved, not the whole histogram: rebuilding
|
||||
// the bars means a `GROUP BY strftime` aggregate over the library,
|
||||
// far too much for every event of a flick. The bars do not change
|
||||
// as the grid scrolls anyway — only where the marker sits on them.
|
||||
//
|
||||
// Re-running the scrub would be wrong for a second reason: it sets
|
||||
// `scroll-to`, which would drive the grid from its own scroll.
|
||||
{
|
||||
let borrow = ctl.catalog.borrow();
|
||||
if let Some(catalog) = borrow.as_ref() {
|
||||
if let Some(when) = capture_time_at(&ctl, catalog, first_visible) {
|
||||
*ctl.current_bucket.borrow_mut() = Some(when);
|
||||
|
||||
let zoom = *ctl.timeline_zoom.borrow();
|
||||
let centre = *ctl.timeline_centre.borrow();
|
||||
if let Some(full) = catalog_span(catalog, &ctl) {
|
||||
let (from, to) = zoomed_span(full, zoom, centre);
|
||||
w.set_library_current_bucket(when as i32);
|
||||
w.set_library_current_fraction(
|
||||
((when - from) as f64 / (to - from).max(1) as f64).clamp(0.0, 1.0)
|
||||
as f32,
|
||||
);
|
||||
w.set_library_timeline_anchored(true);
|
||||
let zoom = *ctl.timeline_zoom.borrow();
|
||||
let centre = *ctl.timeline_centre.borrow();
|
||||
if let Some(full) = catalog_span(catalog, &ctl) {
|
||||
let (from, to) = zoomed_span(full, zoom, centre);
|
||||
w.set_library_current_bucket(when as i32);
|
||||
w.set_library_current_fraction(
|
||||
((when - from) as f64 / (to - from).max(1) as f64)
|
||||
.clamp(0.0, 1.0) as f32,
|
||||
);
|
||||
w.set_library_timeline_anchored(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Centre the window on the view, so scrolling either way has
|
||||
// loaded rows ahead of it rather than only below.
|
||||
let Some(offset) = window_move(
|
||||
first_visible,
|
||||
*ctl.offset.borrow(),
|
||||
*ctl.window.borrow(),
|
||||
ctl.viewport_cells.get(),
|
||||
w.get_library_total().max(0) as usize,
|
||||
) else {
|
||||
return;
|
||||
};
|
||||
// Centre the window on the view, so scrolling either way has
|
||||
// loaded rows ahead of it rather than only below.
|
||||
let Some(offset) = window_move(
|
||||
first_visible,
|
||||
*ctl.offset.borrow(),
|
||||
*ctl.window.borrow(),
|
||||
ctl.viewport_cells.get(),
|
||||
w.global::<Library>().get_library_total().max(0) as usize,
|
||||
) else {
|
||||
return;
|
||||
};
|
||||
|
||||
*ctl.offset.borrow_mut() = offset;
|
||||
load_window(&w, &ctl);
|
||||
});
|
||||
*ctl.offset.borrow_mut() = offset;
|
||||
load_window(&w, &ctl);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user