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:
+64
-64
File diff suppressed because one or more lines are too long
+26
-26
@@ -25,7 +25,7 @@ Sampling a neutral is the first move of the tonal pass — every colour judgemen
|
|||||||
|
|
||||||
Anchored on the fingers' midpoint, and on the pointer, so the gesture reads as magnifying the picture rather than sliding it about. Double-tap is the way to an exact 1:1; this is the way to everything in between.
|
Anchored on the fingers' midpoint, and on the pointer, so the gesture reads as magnifying the picture rather than sliding it about. Double-tap is the way to an exact 1:1; this is the way to everything in between.
|
||||||
|
|
||||||
<sub>`ui/dr-ui/ui/app.slint:2026`</sub>
|
<sub>`ui/dr-ui/ui/app.slint:1919`</sub>
|
||||||
|
|
||||||
### Move a magnified photograph about
|
### Move a magnified photograph about
|
||||||
|
|
||||||
@@ -34,7 +34,7 @@ Anchored on the fingers' midpoint, and on the pointer, so the gesture reads as m
|
|||||||
|
|
||||||
Only once there is something outside the viewport to reach, which is why the cursor becomes a hand exactly then. The view is clamped to the frame: panning past the edge would show undefined area beside the photograph, and that reads as a rendering fault rather than as the end of the picture.
|
Only once there is something outside the viewport to reach, which is why the cursor becomes a hand exactly then. The view is clamped to the frame: panning past the edge would show undefined area beside the photograph, and that reads as a rendering fault rather than as the end of the picture.
|
||||||
|
|
||||||
<sub>`ui/dr-ui/ui/app.slint:2117`</sub>
|
<sub>`ui/dr-ui/ui/app.slint:2010`</sub>
|
||||||
|
|
||||||
### Paint a mask by hand
|
### Paint a mask by hand
|
||||||
|
|
||||||
@@ -43,7 +43,7 @@ Only once there is something outside the viewport to reach, which is why the cur
|
|||||||
|
|
||||||
A model's mask stops inside a shoulder and leaks into the hair, and no single edge control fixes two errors that go opposite ways. The whole stroke is one step in the history, so taking a mark back costs one press however long it took to make.
|
A model's mask stops inside a shoulder and leaks into the hair, and no single edge control fixes two errors that go opposite ways. The whole stroke is one step in the history, so taking a mark back costs one press however long it took to make.
|
||||||
|
|
||||||
<sub>`ui/dr-ui/ui/app.slint:2204`</sub>
|
<sub>`ui/dr-ui/ui/app.slint:2097`</sub>
|
||||||
|
|
||||||
### Take back the last change
|
### Take back the last change
|
||||||
|
|
||||||
@@ -53,7 +53,7 @@ A model's mask stops inside a shoulder and leaks into the hair, and no single ed
|
|||||||
|
|
||||||
A whole drag is one step, so undo takes back a decision rather than a frame of a gesture. The list is there because arriving six steps back costs what arriving from one does.
|
A whole drag is one step, so undo takes back a decision rather than a frame of a gesture. The list is there because arriving six steps back costs what arriving from one does.
|
||||||
|
|
||||||
<sub>`ui/dr-ui/ui/app.slint:2425`</sub>
|
<sub>`ui/dr-ui/ui/app.slint:2318`</sub>
|
||||||
|
|
||||||
### Do it again after taking it back
|
### Do it again after taking it back
|
||||||
|
|
||||||
@@ -61,7 +61,7 @@ A whole drag is one step, so undo takes back a decision rather than a frame of a
|
|||||||
- **Pointer** — Click it, or press Redo in the History header
|
- **Pointer** — Click it, or press Redo in the History header
|
||||||
- **Keyboard** — Ctrl+Shift+Z
|
- **Keyboard** — Ctrl+Shift+Z
|
||||||
|
|
||||||
<sub>`ui/dr-ui/ui/app.slint:2438`</sub>
|
<sub>`ui/dr-ui/ui/app.slint:2331`</sub>
|
||||||
|
|
||||||
### Copy the settings from this photograph
|
### Copy the settings from this photograph
|
||||||
|
|
||||||
@@ -71,7 +71,7 @@ A whole drag is one step, so undo takes back a decision rather than a frame of a
|
|||||||
|
|
||||||
The panel is the copy that has to work: a tablet has no modifier key to hold and no menu bar to hang the action from. The shortcut is an accelerator for a control that is on screen either way.
|
The panel is the copy that has to work: a tablet has no modifier key to hold and no menu bar to hang the action from. The shortcut is an accelerator for a control that is on screen either way.
|
||||||
|
|
||||||
<sub>`ui/dr-ui/ui/app.slint:2471`</sub>
|
<sub>`ui/dr-ui/ui/app.slint:2364`</sub>
|
||||||
|
|
||||||
### Paste the settings onto this photograph
|
### Paste the settings onto this photograph
|
||||||
|
|
||||||
@@ -81,7 +81,7 @@ The panel is the copy that has to work: a tablet has no modifier key to hold and
|
|||||||
|
|
||||||
The button names what would be pasted — "3 adjustments", and whether the crop is coming with it — which the shortcut cannot say. Both paste the same scope.
|
The button names what would be pasted — "3 adjustments", and whether the crop is coming with it — which the shortcut cannot say. Both paste the same scope.
|
||||||
|
|
||||||
<sub>`ui/dr-ui/ui/app.slint:2483`</sub>
|
<sub>`ui/dr-ui/ui/app.slint:2376`</sub>
|
||||||
|
|
||||||
### Change which group of adjustments is on screen
|
### Change which group of adjustments is on screen
|
||||||
|
|
||||||
@@ -91,7 +91,7 @@ The button names what would be pasted — "3 adjustments", and whether the crop
|
|||||||
|
|
||||||
The groups are whatever the operation set declares itself to be about, so there are as many as the pipeline has and no key can be assigned to one of them by name. Stepping is the binding that survives a node being added.
|
The groups are whatever the operation set declares itself to be about, so there are as many as the pipeline has and no key can be assigned to one of them by name. Stepping is the binding that survives a node being added.
|
||||||
|
|
||||||
<sub>`ui/dr-ui/ui/app.slint:2511`</sub>
|
<sub>`ui/dr-ui/ui/app.slint:2404`</sub>
|
||||||
|
|
||||||
### Look at the photograph at 1:1
|
### Look at the photograph at 1:1
|
||||||
|
|
||||||
@@ -101,7 +101,7 @@ The groups are whatever the operation set declares itself to be about, so there
|
|||||||
|
|
||||||
Noise reduction and capture sharpening are judgements about single pixels, and a fitted view averages several of the file's into each one on screen — so the frame looks softer than it is and the correction goes too far. The point and the magnification survive opening the next photograph, which is what makes checking the same eye across forty portraits forty keystrokes rather than forty pans.
|
Noise reduction and capture sharpening are judgements about single pixels, and a fitted view averages several of the file's into each one on screen — so the frame looks softer than it is and the correction goes too far. The point and the magnification survive opening the next photograph, which is what makes checking the same eye across forty portraits forty keystrokes rather than forty pans.
|
||||||
|
|
||||||
<sub>`ui/dr-ui/ui/app.slint:2546`</sub>
|
<sub>`ui/dr-ui/ui/app.slint:2439`</sub>
|
||||||
|
|
||||||
### Move to the next or previous photograph
|
### Move to the next or previous photograph
|
||||||
|
|
||||||
@@ -111,7 +111,7 @@ Noise reduction and capture sharpening are judgements about single pixels, and a
|
|||||||
|
|
||||||
The edit on screen is saved on the way out, so stepping through a folder is as much a departure as going back to the grid and loses nothing.
|
The edit on screen is saved on the way out, so stepping through a folder is as much a departure as going back to the grid and loses nothing.
|
||||||
|
|
||||||
<sub>`ui/dr-ui/ui/app.slint:2598`</sub>
|
<sub>`ui/dr-ui/ui/app.slint:2491`</sub>
|
||||||
|
|
||||||
### See the photograph before you edited it
|
### See the photograph before you edited it
|
||||||
|
|
||||||
@@ -121,7 +121,7 @@ The edit on screen is saved on the way out, so stepping through a folder is as m
|
|||||||
|
|
||||||
Held rather than toggled, and no split screen: a split halves the working image on the tablet the column was sized for, and the comparison photographers describe making is a flick back and forth. It takes no history step, so checking whether a frame is overcooked costs nothing to undo afterwards.
|
Held rather than toggled, and no split screen: a split halves the working image on the tablet the column was sized for, and the comparison photographers describe making is a flick back and forth. It takes no history step, so checking whether a frame is overcooked costs nothing to undo afterwards.
|
||||||
|
|
||||||
<sub>`ui/dr-ui/ui/app.slint:2722`</sub>
|
<sub>`ui/dr-ui/ui/app.slint:2615`</sub>
|
||||||
|
|
||||||
### Put one control back to its default
|
### Put one control back to its default
|
||||||
|
|
||||||
@@ -264,7 +264,7 @@ The right match confidence is a property of your library, not of the model. "Wha
|
|||||||
|
|
||||||
Touch has no ctrl, so without a mode there is no way to select a second photograph — the first tap would open it. The hold is the fast way in and the button is the one that can be found.
|
Touch has no ctrl, so without a mode there is no way to select a second photograph — the first tap would open it. The hold is the fast way in and the button is the one that can be found.
|
||||||
|
|
||||||
<sub>`ui/dr-ui/ui/library.slint:1302`</sub>
|
<sub>`ui/dr-ui/ui/library.slint:1425`</sub>
|
||||||
|
|
||||||
### Add or remove one photograph
|
### Add or remove one photograph
|
||||||
|
|
||||||
@@ -273,7 +273,7 @@ Touch has no ctrl, so without a mode there is no way to select a second photogra
|
|||||||
|
|
||||||
While selecting, a tap never opens. That is the whole point of the mode: one meaning per gesture at a time. Press Done to get tap-to-open back.
|
While selecting, a tap never opens. That is the whole point of the mode: one meaning per gesture at a time. Press Done to get tap-to-open back.
|
||||||
|
|
||||||
<sub>`ui/dr-ui/ui/library.slint:1311`</sub>
|
<sub>`ui/dr-ui/ui/library.slint:1434`</sub>
|
||||||
|
|
||||||
### Leave selecting
|
### Leave selecting
|
||||||
|
|
||||||
@@ -281,7 +281,7 @@ While selecting, a tap never opens. That is the whole point of the mode: one mea
|
|||||||
- **Pointer** — Press Done in the header
|
- **Pointer** — Press Done in the header
|
||||||
- **Keyboard** — Escape
|
- **Keyboard** — Escape
|
||||||
|
|
||||||
<sub>`ui/dr-ui/ui/library.slint:1319`</sub>
|
<sub>`ui/dr-ui/ui/library.slint:1442`</sub>
|
||||||
|
|
||||||
### Pick a photograph up to drag it
|
### Pick a photograph up to drag it
|
||||||
|
|
||||||
@@ -290,7 +290,7 @@ While selecting, a tap never opens. That is the whole point of the mode: one mea
|
|||||||
|
|
||||||
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.
|
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.
|
||||||
|
|
||||||
<sub>`ui/dr-ui/ui/library.slint:1349`</sub>
|
<sub>`ui/dr-ui/ui/library.slint:1472`</sub>
|
||||||
|
|
||||||
### Select a range
|
### Select a range
|
||||||
|
|
||||||
@@ -299,7 +299,7 @@ A finger on a photograph might be starting a scroll, and for the first half-seco
|
|||||||
|
|
||||||
This replaced a double tap, which had no visible state and could take forty photographs by accident. The run is resolved by the catalog rather than by what is on screen, so the grid can scroll between the two taps — the ranges that hurt on a tablet are longer than a screenful, which is exactly where a finger sweep runs out.
|
This replaced a double tap, which had no visible state and could take forty photographs by accident. The run is resolved by the catalog rather than by what is on screen, so the grid can scroll between the two taps — the ranges that hurt on a tablet are longer than a screenful, which is exactly where a finger sweep runs out.
|
||||||
|
|
||||||
<sub>`ui/dr-ui/ui/library.slint:1414`</sub>
|
<sub>`ui/dr-ui/ui/library.slint:1537`</sub>
|
||||||
|
|
||||||
### Take the blinks out of a burst
|
### Take the blinks out of a burst
|
||||||
|
|
||||||
@@ -308,7 +308,7 @@ This replaced a double tap, which had no visible state and could take forty phot
|
|||||||
|
|
||||||
Face indexing reads each face's eyes. The chip drops frames where the chosen people are caught blinking, and leaves sunglasses and eyes it could not read alone.
|
Face indexing reads each face's eyes. The chip drops frames where the chosen people are caught blinking, and leaves sunglasses and eyes it could not read alone.
|
||||||
|
|
||||||
<sub>`ui/dr-ui/ui/library.slint:2106`</sub>
|
<sub>`ui/dr-ui/ui/library.slint:2229`</sub>
|
||||||
|
|
||||||
### Find photographs with two people in them
|
### Find photographs with two people in them
|
||||||
|
|
||||||
@@ -317,7 +317,7 @@ Face indexing reads each face's eyes. The chip drops frames where the chosen peo
|
|||||||
|
|
||||||
"Any of them" is a union and "all of them" is an intersection. The tray is where both terms and the choice between them live, because a filter belongs on the filter bar.
|
"Any of them" is a union and "all of them" is an intersection. The tray is where both terms and the choice between them live, because a filter belongs on the filter bar.
|
||||||
|
|
||||||
<sub>`ui/dr-ui/ui/library.slint:2135`</sub>
|
<sub>`ui/dr-ui/ui/library.slint:2258`</sub>
|
||||||
|
|
||||||
### Resize the thumbnails
|
### Resize the thumbnails
|
||||||
|
|
||||||
@@ -326,7 +326,7 @@ Face indexing reads each face's eyes. The chip drops frames where the chosen peo
|
|||||||
|
|
||||||
There is no wheel on a tablet, so without the pinch the cell size could only be changed by a control a finger cannot reach.
|
There is no wheel on a tablet, so without the pinch the cell size could only be changed by a control a finger cannot reach.
|
||||||
|
|
||||||
<sub>`ui/dr-ui/ui/library.slint:2795`</sub>
|
<sub>`ui/dr-ui/ui/library.slint:2918`</sub>
|
||||||
|
|
||||||
### File photographs in a collection
|
### File photographs in a collection
|
||||||
|
|
||||||
@@ -335,7 +335,7 @@ There is no wheel on a tablet, so without the pinch the cell size could only be
|
|||||||
|
|
||||||
The selection is what the drag carries, which is why selecting several is worth the mode: forty photographs file in one gesture.
|
The selection is what the drag carries, which is why selecting several is worth the mode: forty photographs file in one gesture.
|
||||||
|
|
||||||
<sub>`ui/dr-ui/ui/library.slint:2992`</sub>
|
<sub>`ui/dr-ui/ui/library.slint:3115`</sub>
|
||||||
|
|
||||||
### Open a photograph
|
### Open a photograph
|
||||||
|
|
||||||
@@ -344,7 +344,7 @@ The selection is what the drag carries, which is why selecting several is worth
|
|||||||
|
|
||||||
A tap opens; a tap that *moved* does not. Travel is what separates a deliberate tap from a hand brushing past, and it is the only thing that does: the two are the same length. An earlier version required the finger to dwell 120 ms instead, and that rejected ordinary taps — a real tap is often quicker than a brush.
|
A tap opens; a tap that *moved* does not. Travel is what separates a deliberate tap from a hand brushing past, and it is the only thing that does: the two are the same length. An earlier version required the finger to dwell 120 ms instead, and that rejected ordinary taps — a real tap is often quicker than a brush.
|
||||||
|
|
||||||
<sub>`ui/dr-ui/ui/library.slint:3260`</sub>
|
<sub>`ui/dr-ui/ui/library.slint:3383`</sub>
|
||||||
|
|
||||||
### Rate a photograph without opening it
|
### Rate a photograph without opening it
|
||||||
|
|
||||||
@@ -354,7 +354,7 @@ A tap opens; a tap that *moved* does not. Travel is what separates a deliberate
|
|||||||
|
|
||||||
A star has to take the press without it also reaching the cell, or every rating throws the user into develop.
|
A star has to take the press without it also reaching the cell, or every rating throws the user into develop.
|
||||||
|
|
||||||
<sub>`ui/dr-ui/ui/library.slint:3380`</sub>
|
<sub>`ui/dr-ui/ui/library.slint:3503`</sub>
|
||||||
|
|
||||||
### Choose the frame a folded burst shows
|
### Choose the frame a folded burst shows
|
||||||
|
|
||||||
@@ -363,7 +363,7 @@ A star has to take the press without it also reaching the cell, or every rating
|
|||||||
|
|
||||||
A folded burst draws its earliest frame, which is a fact about the clock and not a judgement about the photograph — nothing in this application ranks a frame (FR-CULL-5). But the point of a burst is that one of the twelve is better than the other eleven, and the photographer is the only one who knows which. So the choice is offered on the frames themselves, while they are open and side by side, which is the one moment the alternatives are on screen to be compared.
|
A folded burst draws its earliest frame, which is a fact about the clock and not a judgement about the photograph — nothing in this application ranks a frame (FR-CULL-5). But the point of a burst is that one of the twelve is better than the other eleven, and the photographer is the only one who knows which. So the choice is offered on the frames themselves, while they are open and side by side, which is the one moment the alternatives are on screen to be compared.
|
||||||
|
|
||||||
<sub>`ui/dr-ui/ui/library.slint:3511`</sub>
|
<sub>`ui/dr-ui/ui/library.slint:3634`</sub>
|
||||||
|
|
||||||
### Drop the selection but keep selecting
|
### Drop the selection but keep selecting
|
||||||
|
|
||||||
@@ -372,7 +372,7 @@ A folded burst draws its earliest frame, which is a fact about the clock and not
|
|||||||
|
|
||||||
Distinct from Done, which leaves the mode entirely. Clearing keeps it, so the next selection can start straight away.
|
Distinct from Done, which leaves the mode entirely. Clearing keeps it, so the next selection can start straight away.
|
||||||
|
|
||||||
<sub>`ui/dr-ui/ui/library.slint:4177`</sub>
|
<sub>`ui/dr-ui/ui/library.slint:4300`</sub>
|
||||||
|
|
||||||
### Select everything the grid is showing
|
### Select everything the grid is showing
|
||||||
|
|
||||||
@@ -381,7 +381,7 @@ Distinct from Done, which leaves the mode entirely. Clearing keeps it, so the ne
|
|||||||
|
|
||||||
A scoped grid of two hundred frames is two hundred taps otherwise, and "all of them, except those three" is a far more common shape than the taps it took to say it.
|
A scoped grid of two hundred frames is two hundred taps otherwise, and "all of them, except those three" is a far more common shape than the taps it took to say it.
|
||||||
|
|
||||||
<sub>`ui/dr-ui/ui/library.slint:4194`</sub>
|
<sub>`ui/dr-ui/ui/library.slint:4317`</sub>
|
||||||
|
|
||||||
### Take photographs out of a collection
|
### Take photographs out of a collection
|
||||||
|
|
||||||
@@ -390,4 +390,4 @@ A scoped grid of two hundred frames is two hundred taps otherwise, and "all of t
|
|||||||
|
|
||||||
The badge on a cell says a photograph is filed in three collections and never which. This is the sheet that names them, and the only way out of one the grid is not currently scoped to.
|
The badge on a cell says a photograph is filed in three collections and never which. This is the sheet that names them, and the only way out of one the grid is not currently scoped to.
|
||||||
|
|
||||||
<sub>`ui/dr-ui/ui/library.slint:4318`</sub>
|
<sub>`ui/dr-ui/ui/library.slint:4441`</sub>
|
||||||
|
|||||||
@@ -45,9 +45,9 @@ use dr_catalog::bursts::{self, Rules, Signature};
|
|||||||
use dr_catalog::Catalog;
|
use dr_catalog::Catalog;
|
||||||
use dr_thumbs::{ThumbSize, ThumbStore};
|
use dr_thumbs::{ThumbSize, ThumbStore};
|
||||||
use dr_types::ImageId;
|
use dr_types::ImageId;
|
||||||
use slint::Model as _;
|
use slint::{ComponentHandle as _, Model as _};
|
||||||
|
|
||||||
use crate::AppWindow;
|
use crate::{AppWindow, Library};
|
||||||
|
|
||||||
/// How many signatures are written per transaction.
|
/// How many signatures are written per transaction.
|
||||||
///
|
///
|
||||||
@@ -364,7 +364,7 @@ pub fn sync_badges(window: &AppWindow, catalog: &Catalog, ids: &[ImageId]) {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let model = window.get_library_cells();
|
let model = window.global::<Library>().get_library_cells();
|
||||||
for (row, id) in ids.iter().enumerate() {
|
for (row, id) in ids.iter().enumerate() {
|
||||||
let (count, expanded, representative) = match found.get(id) {
|
let (count, expanded, representative) = match found.get(id) {
|
||||||
Some(m) => (m.size as i32, m.expanded, m.representative),
|
Some(m) => (m.size as i32, m.expanded, m.representative),
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ use dr_catalog::Catalog;
|
|||||||
use dr_types::CollectionId;
|
use dr_types::CollectionId;
|
||||||
use slint::ComponentHandle;
|
use slint::ComponentHandle;
|
||||||
|
|
||||||
use crate::AppWindow;
|
use crate::{AppWindow, Library};
|
||||||
|
|
||||||
use super::controller::CollectionsController;
|
use super::controller::CollectionsController;
|
||||||
use super::tree_sync::refresh_tree;
|
use super::tree_sync::refresh_tree;
|
||||||
@@ -189,11 +189,11 @@ pub(super) fn arm_hold(window: &AppWindow, ctl: &Rc<CollectionsController>, row:
|
|||||||
// This half happens whether or not selection mode was already on —
|
// This half happens whether or not selection mode was already on —
|
||||||
// it is the half a *drag* needs, and a drag out of a selection of
|
// it is the half a *drag* needs, and a drag out of a selection of
|
||||||
// forty starts in a mode that is already on.
|
// forty starts in a mode that is already on.
|
||||||
w.set_library_held_row(row);
|
w.global::<Library>().set_library_held_row(row);
|
||||||
|
|
||||||
if !ctl_cb.select_mode.get() {
|
if !ctl_cb.select_mode.get() {
|
||||||
ctl_cb.select_mode.set(true);
|
ctl_cb.select_mode.set(true);
|
||||||
w.set_library_select_mode(true);
|
w.global::<Library>().set_library_select_mode(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
// The release that follows this hold must not also open the image:
|
// The release that follows this hold must not also open the image:
|
||||||
|
|||||||
@@ -6,8 +6,9 @@ use std::collections::BTreeSet;
|
|||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
|
|
||||||
use dr_types::ImageId;
|
use dr_types::ImageId;
|
||||||
|
use slint::ComponentHandle as _;
|
||||||
|
|
||||||
use crate::AppWindow;
|
use crate::{AppWindow, Library};
|
||||||
|
|
||||||
use super::controller::CollectionsController;
|
use super::controller::CollectionsController;
|
||||||
use super::tree_sync::sync_selection;
|
use super::tree_sync::sync_selection;
|
||||||
@@ -292,7 +293,7 @@ pub fn cancel_press(window: &AppWindow, ctl: &Rc<CollectionsController>, ids: &[
|
|||||||
// has to put it back down, or the ring stays open around a cell nobody is
|
// has to put it back down, or the ring stays open around a cell nobody is
|
||||||
// touching and the grid stays frozen with it.
|
// touching and the grid stays frozen with it.
|
||||||
ctl.pending_toggle.set(None);
|
ctl.pending_toggle.set(None);
|
||||||
window.set_library_held_row(-1);
|
window.global::<Library>().set_library_held_row(-1);
|
||||||
|
|
||||||
let Some(undo) = ctl.press_undo.borrow_mut().take() else {
|
let Some(undo) = ctl.press_undo.borrow_mut().take() else {
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ use dr_catalog::Catalog;
|
|||||||
use dr_types::{CollectionId, ImageId};
|
use dr_types::{CollectionId, ImageId};
|
||||||
use slint::{ComponentHandle as _, Model as _};
|
use slint::{ComponentHandle as _, Model as _};
|
||||||
|
|
||||||
use crate::{AppWindow, CollectionRow, Collections};
|
use crate::{AppWindow, CollectionRow, Collections, Library};
|
||||||
|
|
||||||
use super::controller::CollectionsController;
|
use super::controller::CollectionsController;
|
||||||
|
|
||||||
@@ -195,7 +195,7 @@ fn offline_state(
|
|||||||
/// Separate from [`sync_selection`] because the two differ: the selection
|
/// Separate from [`sync_selection`] because the two differ: the selection
|
||||||
/// persists after the drag, the lift lasts only while it is in flight.
|
/// persists after the drag, the lift lasts only while it is in flight.
|
||||||
pub fn sync_lifted(window: &AppWindow, lifted: &[ImageId], ids: &[ImageId]) {
|
pub fn sync_lifted(window: &AppWindow, lifted: &[ImageId], ids: &[ImageId]) {
|
||||||
let model = window.get_library_cells();
|
let model = window.global::<Library>().get_library_cells();
|
||||||
for (row, id) in ids.iter().enumerate() {
|
for (row, id) in ids.iter().enumerate() {
|
||||||
let want = lifted.contains(id);
|
let want = lifted.contains(id);
|
||||||
if let Some(mut cell) = model.row_data(row) {
|
if let Some(mut cell) = model.row_data(row) {
|
||||||
@@ -213,7 +213,7 @@ pub fn sync_lifted(window: &AppWindow, lifted: &[ImageId], ids: &[ImageId]) {
|
|||||||
/// this is what keeps the two in step after a scrub, a drop, or a click.
|
/// this is what keeps the two in step after a scrub, a drop, or a click.
|
||||||
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.global::<Library>().get_library_cells();
|
||||||
|
|
||||||
// The anchor is deliberately **not** pushed to the grid.
|
// The anchor is deliberately **not** pushed to the grid.
|
||||||
//
|
//
|
||||||
@@ -238,7 +238,9 @@ pub fn sync_selection(window: &AppWindow, ctl: &Rc<CollectionsController>, ids:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
window.set_library_selected_count(selection.len() as i32);
|
window
|
||||||
|
.global::<Library>()
|
||||||
|
.set_library_selected_count(selection.len() as i32);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// TRACES: FR-CAT-7
|
/// TRACES: FR-CAT-7
|
||||||
@@ -261,7 +263,7 @@ pub(super) fn sync_reorderable(window: &AppWindow) {
|
|||||||
.filter_map(|i| rows.row_data(i))
|
.filter_map(|i| rows.row_data(i))
|
||||||
.find(|r| r.id == id)
|
.find(|r| r.id == id)
|
||||||
.is_some_and(|r| !r.smart && !r.has_children);
|
.is_some_and(|r| !r.smart && !r.has_children);
|
||||||
window.set_library_reorderable(manual);
|
window.global::<Library>().set_library_reorderable(manual);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Refresh the per-cell "in this many collections" badges.
|
/// Refresh the per-cell "in this many collections" badges.
|
||||||
@@ -300,7 +302,7 @@ pub fn sync_badges(window: &AppWindow, catalog: &Catalog, ids: &[ImageId]) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let model = window.get_library_cells();
|
let model = window.global::<Library>().get_library_cells();
|
||||||
for (row, id) in ids.iter().enumerate() {
|
for (row, id) in ids.iter().enumerate() {
|
||||||
let want = counts.get(&(id.0 as i64)).copied().unwrap_or(0);
|
let want = counts.get(&(id.0 as i64)).copied().unwrap_or(0);
|
||||||
if let Some(mut cell) = model.row_data(row) {
|
if let Some(mut cell) = model.row_data(row) {
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ use dr_types::{CollectionId, ImageId};
|
|||||||
use slint::{ComponentHandle, Model as _};
|
use slint::{ComponentHandle, Model as _};
|
||||||
|
|
||||||
use crate::library;
|
use crate::library;
|
||||||
use crate::{AppWindow, Collections};
|
use crate::{AppWindow, Collections, Library};
|
||||||
|
|
||||||
use super::controller::{decide_drop, CollectionsController, Drop};
|
use super::controller::{decide_drop, CollectionsController, Drop};
|
||||||
use super::drag::{arm_hold, arm_spring, collapse_spring_opened, compose_drag_image};
|
use super::drag::{arm_hold, arm_spring, collapse_spring_opened, compose_drag_image};
|
||||||
@@ -99,7 +99,9 @@ fn wire_selection(
|
|||||||
let weak = window.as_weak();
|
let weak = window.as_weak();
|
||||||
let ctl = ctl.clone();
|
let ctl = ctl.clone();
|
||||||
let visible = visible_ids.clone();
|
let visible = visible_ids.clone();
|
||||||
window.on_library_cell_pressed(move |row, ctrl_held, shift_held| {
|
window
|
||||||
|
.global::<Library>()
|
||||||
|
.on_library_cell_pressed(move |row, ctrl_held, shift_held| {
|
||||||
let Some(w) = weak.upgrade() else { return };
|
let Some(w) = weak.upgrade() else { return };
|
||||||
let ids = visible();
|
let ids = visible();
|
||||||
|
|
||||||
@@ -135,7 +137,9 @@ fn wire_selection(
|
|||||||
// travelled. Either way the hold is off.
|
// travelled. Either way the hold is off.
|
||||||
{
|
{
|
||||||
let ctl = ctl.clone();
|
let ctl = ctl.clone();
|
||||||
window.on_library_cell_press_ended(move || {
|
window
|
||||||
|
.global::<Library>()
|
||||||
|
.on_library_cell_press_ended(move || {
|
||||||
*ctl.hold_timer.borrow_mut() = None;
|
*ctl.hold_timer.borrow_mut() = None;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -152,7 +156,9 @@ fn wire_selection(
|
|||||||
let weak = window.as_weak();
|
let weak = window.as_weak();
|
||||||
let ctl = ctl.clone();
|
let ctl = ctl.clone();
|
||||||
let visible = visible_ids.clone();
|
let visible = visible_ids.clone();
|
||||||
window.on_library_clear_selection(move || {
|
window
|
||||||
|
.global::<Library>()
|
||||||
|
.on_library_clear_selection(move || {
|
||||||
let Some(w) = weak.upgrade() else { return };
|
let Some(w) = weak.upgrade() else { return };
|
||||||
ctl.clear_selection();
|
ctl.clear_selection();
|
||||||
sync_selection(&w, &ctl, &visible());
|
sync_selection(&w, &ctl, &visible());
|
||||||
@@ -179,11 +185,13 @@ fn wire_selection(
|
|||||||
let catalog = catalog.clone();
|
let catalog = catalog.clone();
|
||||||
let visible = visible_ids.clone();
|
let visible = visible_ids.clone();
|
||||||
let reload = on_scope_changed.clone();
|
let reload = on_scope_changed.clone();
|
||||||
window.on_library_reorder_to(move |row, after| {
|
window
|
||||||
|
.global::<Library>()
|
||||||
|
.on_library_reorder_to(move |row, after| {
|
||||||
let Some(w) = weak.upgrade() else { return };
|
let Some(w) = weak.upgrade() else { return };
|
||||||
// Both guards belong here rather than only in `library.slint`: the
|
// Both guards belong here rather than only in `library.slint`: the
|
||||||
// scope can change between the drag starting and the drop landing.
|
// scope can change between the drag starting and the drop landing.
|
||||||
if !w.get_library_reorderable() {
|
if !w.global::<Library>().get_library_reorderable() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let Some(scope) = *ctl.scope.borrow() else {
|
let Some(scope) = *ctl.scope.borrow() else {
|
||||||
@@ -267,10 +275,10 @@ fn wire_selection(
|
|||||||
let weak = window.as_weak();
|
let weak = window.as_weak();
|
||||||
let ctl = ctl.clone();
|
let ctl = ctl.clone();
|
||||||
let visible = visible_ids.clone();
|
let visible = visible_ids.clone();
|
||||||
window.on_library_select_all(move || {
|
window.global::<Library>().on_library_select_all(move || {
|
||||||
let Some(w) = weak.upgrade() else { return };
|
let Some(w) = weak.upgrade() else { return };
|
||||||
let ids = visible();
|
let ids = visible();
|
||||||
let total = w.get_library_total().max(0) as usize;
|
let total = w.global::<Library>().get_library_total().max(0) as usize;
|
||||||
if total == 0 {
|
if total == 0 {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -334,7 +342,9 @@ fn wire_selection_filing(
|
|||||||
let catalog = catalog.clone();
|
let catalog = catalog.clone();
|
||||||
let visible = visible_ids.clone();
|
let visible = visible_ids.clone();
|
||||||
let reload = on_scope_changed.clone();
|
let reload = on_scope_changed.clone();
|
||||||
window.on_library_collection_from_selection(move |name| {
|
window
|
||||||
|
.global::<Library>()
|
||||||
|
.on_library_collection_from_selection(move |name| {
|
||||||
let Some(w) = weak.upgrade() else { return };
|
let Some(w) = weak.upgrade() else { return };
|
||||||
let images = ctl.selected();
|
let images = ctl.selected();
|
||||||
if images.is_empty() {
|
if images.is_empty() {
|
||||||
@@ -407,11 +417,13 @@ fn wire_selection_filing(
|
|||||||
let weak = window.as_weak();
|
let weak = window.as_weak();
|
||||||
let ctl = ctl.clone();
|
let ctl = ctl.clone();
|
||||||
let visible = visible_ids.clone();
|
let visible = visible_ids.clone();
|
||||||
window.on_library_toggle_select_mode(move || {
|
window
|
||||||
|
.global::<Library>()
|
||||||
|
.on_library_toggle_select_mode(move || {
|
||||||
let Some(w) = weak.upgrade() else { return };
|
let Some(w) = weak.upgrade() else { return };
|
||||||
let on = !ctl.select_mode.get();
|
let on = !ctl.select_mode.get();
|
||||||
ctl.select_mode.set(on);
|
ctl.select_mode.set(on);
|
||||||
w.set_library_select_mode(on);
|
w.global::<Library>().set_library_select_mode(on);
|
||||||
*ctl.hold_timer.borrow_mut() = None;
|
*ctl.hold_timer.borrow_mut() = None;
|
||||||
|
|
||||||
// Leaving the mode drops the selection. "Done" reads as finishing
|
// Leaving the mode drops the selection. "Done" reads as finishing
|
||||||
@@ -434,7 +446,9 @@ fn wire_selection_filing(
|
|||||||
let catalog = catalog.clone();
|
let catalog = catalog.clone();
|
||||||
let visible = visible_ids.clone();
|
let visible = visible_ids.clone();
|
||||||
let reload = on_scope_changed.clone();
|
let reload = on_scope_changed.clone();
|
||||||
window.on_library_file_in_collection(move |id, moves| {
|
window
|
||||||
|
.global::<Library>()
|
||||||
|
.on_library_file_in_collection(move |id, moves| {
|
||||||
let Some(w) = weak.upgrade() else { return };
|
let Some(w) = weak.upgrade() else { return };
|
||||||
let target = CollectionId(id as u64);
|
let target = CollectionId(id as u64);
|
||||||
let chosen = ctl.selected();
|
let chosen = ctl.selected();
|
||||||
@@ -526,7 +540,7 @@ fn wire_drag(
|
|||||||
// nothing outside the application can interpret it as a paste.
|
// nothing outside the application can interpret it as a paste.
|
||||||
{
|
{
|
||||||
let ctl = ctl.clone();
|
let ctl = ctl.clone();
|
||||||
window.on_library_drag_payload(move || {
|
window.global::<Library>().on_library_drag_payload(move || {
|
||||||
let carried = ctl.dragging.borrow().clone();
|
let carried = ctl.dragging.borrow().clone();
|
||||||
let mut data = slint::DataTransfer::default();
|
let mut data = slint::DataTransfer::default();
|
||||||
data.set_user_data(Rc::new(carried));
|
data.set_user_data(Rc::new(carried));
|
||||||
@@ -551,7 +565,9 @@ fn wire_drag(
|
|||||||
let weak = window.as_weak();
|
let weak = window.as_weak();
|
||||||
let ctl = ctl.clone();
|
let ctl = ctl.clone();
|
||||||
let visible = visible_ids.clone();
|
let visible = visible_ids.clone();
|
||||||
window.on_library_drag_started(move |row| {
|
window
|
||||||
|
.global::<Library>()
|
||||||
|
.on_library_drag_started(move |row| {
|
||||||
let Some(w) = weak.upgrade() else { return };
|
let Some(w) = weak.upgrade() else { return };
|
||||||
let ids = visible();
|
let ids = visible();
|
||||||
|
|
||||||
@@ -594,7 +610,7 @@ fn wire_drag(
|
|||||||
// whose preview has not landed yet contributes nothing rather than
|
// whose preview has not landed yet contributes nothing rather than
|
||||||
// a placeholder.
|
// a placeholder.
|
||||||
let thumbs: Vec<slint::Image> = {
|
let thumbs: Vec<slint::Image> = {
|
||||||
let model = w.get_library_cells();
|
let model = w.global::<Library>().get_library_cells();
|
||||||
carried
|
carried
|
||||||
.iter()
|
.iter()
|
||||||
.filter_map(|id| ids.iter().position(|v| v == id))
|
.filter_map(|id| ids.iter().position(|v| v == id))
|
||||||
@@ -603,7 +619,8 @@ fn wire_drag(
|
|||||||
.map(|c| c.thumbnail)
|
.map(|c| c.thumbnail)
|
||||||
.collect()
|
.collect()
|
||||||
};
|
};
|
||||||
w.set_library_drag_image(compose_drag_image(&thumbs));
|
w.global::<Library>()
|
||||||
|
.set_library_drag_image(compose_drag_image(&thumbs));
|
||||||
|
|
||||||
*ctl.dragging.borrow_mut() = carried.clone();
|
*ctl.dragging.borrow_mut() = carried.clone();
|
||||||
sync_selection(&w, &ctl, &ids);
|
sync_selection(&w, &ctl, &ids);
|
||||||
@@ -740,7 +757,9 @@ fn wire_drag(
|
|||||||
let visible = visible_ids.clone();
|
let visible = visible_ids.clone();
|
||||||
let reload = on_scope_changed.clone();
|
let reload = on_scope_changed.clone();
|
||||||
let session = session.clone();
|
let session = session.clone();
|
||||||
window.on_library_drag_finished(move || {
|
window
|
||||||
|
.global::<Library>()
|
||||||
|
.on_library_drag_finished(move || {
|
||||||
let Some(w) = weak.upgrade() else { return };
|
let Some(w) = weak.upgrade() else { return };
|
||||||
|
|
||||||
let landed = ctl.dropped_on.borrow_mut().take();
|
let landed = ctl.dropped_on.borrow_mut().take();
|
||||||
@@ -749,7 +768,7 @@ fn wire_drag(
|
|||||||
// The grid clears this itself on the cancel that starts a drag;
|
// The grid clears this itself on the cancel that starts a drag;
|
||||||
// this is for the endings that reach no cell — a drop, or a drag
|
// this is for the endings that reach no cell — a drop, or a drag
|
||||||
// abandoned over nothing.
|
// abandoned over nothing.
|
||||||
w.set_library_held_row(-1);
|
w.global::<Library>().set_library_held_row(-1);
|
||||||
*ctl.hover_id.borrow_mut() = None;
|
*ctl.hover_id.borrow_mut() = None;
|
||||||
*ctl.spring_timer.borrow_mut() = None;
|
*ctl.spring_timer.borrow_mut() = None;
|
||||||
|
|
||||||
@@ -762,7 +781,8 @@ fn wire_drag(
|
|||||||
// The cells settle back into the grid, and the cursor bitmap is
|
// The cells settle back into the grid, and the cursor bitmap is
|
||||||
// released — it holds a copy of every thumbnail it composited.
|
// released — it holds a copy of every thumbnail it composited.
|
||||||
sync_lifted(&w, &[], &visible());
|
sync_lifted(&w, &[], &visible());
|
||||||
w.set_library_drag_image(slint::Image::default());
|
w.global::<Library>()
|
||||||
|
.set_library_drag_image(slint::Image::default());
|
||||||
|
|
||||||
if landed.is_some() {
|
if landed.is_some() {
|
||||||
let borrow = catalog.borrow();
|
let borrow = catalog.borrow();
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ use dr_catalog::Catalog;
|
|||||||
use dr_types::{CollectionId, ImageId};
|
use dr_types::{CollectionId, ImageId};
|
||||||
use slint::{ComponentHandle, Model as _};
|
use slint::{ComponentHandle, Model as _};
|
||||||
|
|
||||||
use crate::{AppWindow, Collections};
|
use crate::{AppWindow, Collections, Library};
|
||||||
|
|
||||||
use super::controller::{
|
use super::controller::{
|
||||||
decide_delete, decide_release, CollectionsController, DeleteStep, Release,
|
decide_delete, decide_release, CollectionsController, DeleteStep, Release,
|
||||||
@@ -236,7 +236,9 @@ pub(super) fn wire_remove(
|
|||||||
let catalog = catalog.clone();
|
let catalog = catalog.clone();
|
||||||
let visible = visible_ids.clone();
|
let visible = visible_ids.clone();
|
||||||
let reload = on_scope_changed.clone();
|
let reload = on_scope_changed.clone();
|
||||||
window.on_library_remove_from_collection(move || {
|
window
|
||||||
|
.global::<Library>()
|
||||||
|
.on_library_remove_from_collection(move || {
|
||||||
let Some(w) = weak.upgrade() else { return };
|
let Some(w) = weak.upgrade() else { return };
|
||||||
let Some(scope) = *ctl.scope.borrow() else {
|
let Some(scope) = *ctl.scope.borrow() else {
|
||||||
// Unscoped, there is no collection to remove from. The button
|
// Unscoped, there is no collection to remove from. The button
|
||||||
@@ -259,7 +261,8 @@ pub(super) fn wire_remove(
|
|||||||
// still in the library, and a user who reads this as a
|
// still in the library, and a user who reads this as a
|
||||||
// delete will not trust the feature again.
|
// delete will not trust the feature again.
|
||||||
w.set_library_status(
|
w.set_library_status(
|
||||||
format!("Removed {n} from this collection; still in the library").into(),
|
format!("Removed {n} from this collection; still in the library")
|
||||||
|
.into(),
|
||||||
);
|
);
|
||||||
ctl.clear_selection();
|
ctl.clear_selection();
|
||||||
refresh_tree(&w, &ctl, cat);
|
refresh_tree(&w, &ctl, cat);
|
||||||
@@ -688,7 +691,9 @@ pub(super) fn wire_membership(
|
|||||||
let weak = window.as_weak();
|
let weak = window.as_weak();
|
||||||
let ctl = ctl.clone();
|
let ctl = ctl.clone();
|
||||||
let catalog = catalog.clone();
|
let catalog = catalog.clone();
|
||||||
window.on_library_open_membership(move || {
|
window
|
||||||
|
.global::<Library>()
|
||||||
|
.on_library_open_membership(move || {
|
||||||
let Some(w) = weak.upgrade() else { return };
|
let Some(w) = weak.upgrade() else { return };
|
||||||
let borrow = catalog.borrow();
|
let borrow = catalog.borrow();
|
||||||
let Some(cat) = borrow.as_ref() else { return };
|
let Some(cat) = borrow.as_ref() else { return };
|
||||||
|
|||||||
+2
-1
@@ -72,6 +72,7 @@ use std::rc::Rc;
|
|||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use dr_decode::{Metadata, PreviewSize};
|
use dr_decode::{Metadata, PreviewSize};
|
||||||
|
use slint::ComponentHandle as _;
|
||||||
|
|
||||||
pub use develop::DevelopSession;
|
pub use develop::DevelopSession;
|
||||||
|
|
||||||
@@ -3436,7 +3437,7 @@ fn back_one_step(w: &AppWindow) -> bool {
|
|||||||
// Files named on the command line have no grid behind them — the same
|
// Files named on the command line have no grid behind them — the same
|
||||||
// condition the status strip uses to decide whether to offer the way
|
// condition the status strip uses to decide whether to offer the way
|
||||||
// back at all.
|
// back at all.
|
||||||
has_grid: w.get_library_total() > 0,
|
has_grid: w.global::<Library>().get_library_total() > 0,
|
||||||
scoped: w.get_collection_selected() != 0,
|
scoped: w.get_collection_selected() != 0,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ use std::rc::Rc;
|
|||||||
use slint::ComponentHandle;
|
use slint::ComponentHandle;
|
||||||
|
|
||||||
use crate::library;
|
use crate::library;
|
||||||
use crate::{AppWindow, PersonChip, View};
|
use crate::{AppWindow, Library, PersonChip, View};
|
||||||
|
|
||||||
use super::controller::LibraryController;
|
use super::controller::LibraryController;
|
||||||
use super::offline::{
|
use super::offline::{
|
||||||
@@ -384,12 +384,14 @@ pub(super) fn wire_filter_scope_and_offline(
|
|||||||
{
|
{
|
||||||
let weak = window.as_weak();
|
let weak = window.as_weak();
|
||||||
let ctl = ctl.clone();
|
let ctl = ctl.clone();
|
||||||
window.on_library_toggle_local_only(move || {
|
window
|
||||||
|
.global::<Library>()
|
||||||
|
.on_library_toggle_local_only(move || {
|
||||||
let Some(w) = weak.upgrade() else { return };
|
let Some(w) = weak.upgrade() else { return };
|
||||||
let on = !ctl.local_only();
|
let on = !ctl.local_only();
|
||||||
ctl.set_local_only(on);
|
ctl.set_local_only(on);
|
||||||
ctl.filter.borrow_mut().local_only = on;
|
ctl.filter.borrow_mut().local_only = on;
|
||||||
w.set_library_local_only(on);
|
w.global::<Library>().set_library_local_only(on);
|
||||||
refilter(&w, &ctl);
|
refilter(&w, &ctl);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ use std::rc::Rc;
|
|||||||
use slint::{ComponentHandle, Model as _};
|
use slint::{ComponentHandle, Model as _};
|
||||||
|
|
||||||
use crate::AppWindow;
|
use crate::AppWindow;
|
||||||
use crate::{GestureRow, View};
|
use crate::{GestureRow, Library, View};
|
||||||
|
|
||||||
use super::controller::{
|
use super::controller::{
|
||||||
LibraryController, OpenImage, MAX_CELL_SIZE, MIN_CELL_SIZE, MIN_WINDOW, SCREENFULS,
|
LibraryController, OpenImage, MAX_CELL_SIZE, MIN_CELL_SIZE, MIN_WINDOW, SCREENFULS,
|
||||||
@@ -49,7 +49,7 @@ fn move_cursor(
|
|||||||
delta: i32,
|
delta: i32,
|
||||||
extend: bool,
|
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 {
|
if total == 0 {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -93,11 +93,11 @@ fn place_cursor(
|
|||||||
// quarter in, exactly as a scroll does it, so continuing in the same
|
// 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
|
// direction has loaded cells to move into rather than another reload on
|
||||||
// the very next press.
|
// 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();
|
let offset = *ctl.offset.borrow();
|
||||||
if at < offset || at >= offset + loaded {
|
if at < offset || at >= offset + loaded {
|
||||||
let size = *ctl.window.borrow();
|
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);
|
*ctl.offset.borrow_mut() = window_start(at, size, total);
|
||||||
load_window(window, ctl);
|
load_window(window, ctl);
|
||||||
}
|
}
|
||||||
@@ -122,7 +122,7 @@ fn place_cursor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
crate::collections_ui::select_row(window, coll, &ids, offset, row, false, extend);
|
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.
|
/// 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) {
|
fn report_position(window: &AppWindow, ctl: &Rc<LibraryController>, row: usize) {
|
||||||
let offset = *ctl.offset.borrow();
|
let offset = *ctl.offset.borrow();
|
||||||
window.set_index((offset + row) as i32);
|
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.
|
/// 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
|
// 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
|
// here means the stars are on screen before the first touch rather than
|
||||||
// after it, which is the whole point of showing them.
|
// 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
|
// TRACES: FR-UI-4
|
||||||
// The gesture reference. Pushed once, here, rather than on demand: the
|
// The gesture reference. Pushed once, here, rather than on demand: the
|
||||||
@@ -209,7 +211,9 @@ pub fn wire<F>(
|
|||||||
let ctl = ctl.clone();
|
let ctl = ctl.clone();
|
||||||
let coll_for_click = coll_ctl.clone();
|
let coll_for_click = coll_ctl.clone();
|
||||||
let on_open_image = on_open_image.clone();
|
let on_open_image = on_open_image.clone();
|
||||||
window.on_library_cell_clicked(move |i| {
|
window
|
||||||
|
.global::<Library>()
|
||||||
|
.on_library_cell_clicked(move |i| {
|
||||||
let Some(w) = weak.upgrade() else { return };
|
let Some(w) = weak.upgrade() else { return };
|
||||||
|
|
||||||
// The press stayed put, so it was a tap and not a drag: whatever it
|
// The press stayed put, so it was a tap and not a drag: whatever it
|
||||||
@@ -229,13 +233,13 @@ pub fn wire<F>(
|
|||||||
w.set_active_view(View::Develop);
|
w.set_active_view(View::Develop);
|
||||||
// Which cell the develop view is now showing, so the photo
|
// Which cell the develop view is now showing, so the photo
|
||||||
// roll opens marking it rather than marking nothing.
|
// roll opens marking it rather than marking nothing.
|
||||||
w.set_library_roll_current(i);
|
w.global::<Library>().set_library_roll_current(i);
|
||||||
// A develop session begins here, so the roll centres on this
|
// A develop session begins here, so the roll centres on this
|
||||||
// photograph the first time it settles rather than merely
|
// photograph the first time it settles rather than merely
|
||||||
// scrolling it into view at one edge. Raised here and not in
|
// scrolling it into view at one edge. Raised here and not in
|
||||||
// `on_library_roll_pick`, which is a step *within* a session —
|
// `on_library_roll_pick`, which is a step *within* a session —
|
||||||
// see `PhotoRoll::centre-request`.
|
// see `PhotoRoll::centre-request`.
|
||||||
w.set_library_roll_centre(true);
|
w.global::<Library>().set_library_roll_centre(true);
|
||||||
on_open_image(path);
|
on_open_image(path);
|
||||||
report_position(&w, &ctl, i as usize);
|
report_position(&w, &ctl, i as usize);
|
||||||
// TRACES: FR-UI-8
|
// TRACES: FR-UI-8
|
||||||
@@ -259,12 +263,12 @@ pub fn wire<F>(
|
|||||||
let weak = window.as_weak();
|
let weak = window.as_weak();
|
||||||
let ctl = ctl.clone();
|
let ctl = ctl.clone();
|
||||||
let on_open_image = on_open_image.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(w) = weak.upgrade() else { return };
|
||||||
let Some(path) = ctl.paths.borrow().get(i as usize).cloned() else {
|
let Some(path) = ctl.paths.borrow().get(i as usize).cloned() else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
w.set_library_roll_current(i);
|
w.global::<Library>().set_library_roll_current(i);
|
||||||
on_open_image(path);
|
on_open_image(path);
|
||||||
report_position(&w, &ctl, i as usize);
|
report_position(&w, &ctl, i as usize);
|
||||||
// TRACES: FR-UI-8
|
// TRACES: FR-UI-8
|
||||||
@@ -307,7 +311,9 @@ fn wire_grid_cursor_and_zoom(
|
|||||||
let weak = window.as_weak();
|
let weak = window.as_weak();
|
||||||
let ctl = ctl.clone();
|
let ctl = ctl.clone();
|
||||||
let coll = coll_ctl.clone();
|
let coll = coll_ctl.clone();
|
||||||
window.on_library_move_cursor(move |delta, extend| {
|
window
|
||||||
|
.global::<Library>()
|
||||||
|
.on_library_move_cursor(move |delta, extend| {
|
||||||
let Some(w) = weak.upgrade() else { return };
|
let Some(w) = weak.upgrade() else { return };
|
||||||
move_cursor(&w, &ctl, &coll, delta, extend);
|
move_cursor(&w, &ctl, &coll, delta, extend);
|
||||||
});
|
});
|
||||||
@@ -318,7 +324,7 @@ fn wire_grid_cursor_and_zoom(
|
|||||||
let ctl = ctl.clone();
|
let ctl = ctl.clone();
|
||||||
let coll = coll_ctl.clone();
|
let coll = coll_ctl.clone();
|
||||||
let on_open = on_open_image.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 };
|
let Some(w) = weak.upgrade() else { return };
|
||||||
// The cursor is a library ordinal and `paths` is the loaded
|
// The cursor is a library ordinal and `paths` is the loaded
|
||||||
// window, so the row is the difference. A cursor outside the
|
// 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);
|
w.set_active_view(View::Develop);
|
||||||
// As on a click: the roll marks what is open, and centres on it
|
// As on a click: the roll marks what is open, and centres on it
|
||||||
// because this too begins a session.
|
// because this too begins a session.
|
||||||
w.set_library_roll_current(row.unwrap_or(0) as i32);
|
w.global::<Library>()
|
||||||
w.set_library_roll_centre(true);
|
.set_library_roll_current(row.unwrap_or(0) as i32);
|
||||||
|
w.global::<Library>().set_library_roll_centre(true);
|
||||||
on_open(path);
|
on_open(path);
|
||||||
report_position(&w, &ctl, row.unwrap_or(0));
|
report_position(&w, &ctl, row.unwrap_or(0));
|
||||||
write_place(&w, &ctl);
|
write_place(&w, &ctl);
|
||||||
@@ -350,10 +357,12 @@ fn wire_grid_cursor_and_zoom(
|
|||||||
{
|
{
|
||||||
let weak = window.as_weak();
|
let weak = window.as_weak();
|
||||||
let ctl = ctl.clone();
|
let ctl = ctl.clone();
|
||||||
window.on_library_zoom_cells(move |delta| {
|
window
|
||||||
|
.global::<Library>()
|
||||||
|
.on_library_zoom_cells(move |delta| {
|
||||||
let Some(w) = weak.upgrade() else { return };
|
let Some(w) = weak.upgrade() else { return };
|
||||||
|
|
||||||
let current = w.get_library_cell_size();
|
let current = w.global::<Library>().get_library_cell_size();
|
||||||
let next = if delta > 0 {
|
let next = if delta > 0 {
|
||||||
current * 1.25
|
current * 1.25
|
||||||
} else {
|
} else {
|
||||||
@@ -364,7 +373,7 @@ fn wire_grid_cursor_and_zoom(
|
|||||||
if (next - current).abs() < 0.5 {
|
if (next - current).abs() < 0.5 {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
w.set_library_cell_size(next);
|
w.global::<Library>().set_library_cell_size(next);
|
||||||
|
|
||||||
// No need to forget anything on a class change: the class is part
|
// 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
|
// of the request key, so cells that now want the large resolution
|
||||||
@@ -391,18 +400,20 @@ fn wire_grid_cursor_and_zoom(
|
|||||||
{
|
{
|
||||||
let weak = window.as_weak();
|
let weak = window.as_weak();
|
||||||
let ctl = ctl.clone();
|
let ctl = ctl.clone();
|
||||||
window.on_library_pinch_cells(move |ratio| {
|
window
|
||||||
|
.global::<Library>()
|
||||||
|
.on_library_pinch_cells(move |ratio| {
|
||||||
let Some(w) = weak.upgrade() else { return };
|
let Some(w) = weak.upgrade() else { return };
|
||||||
if !(ratio.is_finite() && ratio > 0.0) {
|
if !(ratio.is_finite() && ratio > 0.0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let current = w.get_library_cell_size();
|
let current = w.global::<Library>().get_library_cell_size();
|
||||||
let next = (current * ratio).clamp(MIN_CELL_SIZE, MAX_CELL_SIZE);
|
let next = (current * ratio).clamp(MIN_CELL_SIZE, MAX_CELL_SIZE);
|
||||||
if (next - current).abs() < 0.5 {
|
if (next - current).abs() < 0.5 {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
w.set_library_cell_size(next);
|
w.global::<Library>().set_library_cell_size(next);
|
||||||
schedule_reload(&w, &ctl);
|
schedule_reload(&w, &ctl);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -418,7 +429,9 @@ fn wire_grid_cursor_and_zoom(
|
|||||||
let weak = window.as_weak();
|
let weak = window.as_weak();
|
||||||
let ctl = ctl.clone();
|
let ctl = ctl.clone();
|
||||||
let coll = coll_ctl.clone();
|
let coll = coll_ctl.clone();
|
||||||
window.on_library_pinch_started(move || {
|
window
|
||||||
|
.global::<Library>()
|
||||||
|
.on_library_pinch_started(move || {
|
||||||
let Some(w) = weak.upgrade() else { return };
|
let Some(w) = weak.upgrade() else { return };
|
||||||
crate::collections_ui::cancel_press(&w, &coll, &ctl.visible_ids());
|
crate::collections_ui::cancel_press(&w, &coll, &ctl.visible_ids());
|
||||||
});
|
});
|
||||||
@@ -462,7 +475,9 @@ fn wire_grid_sync_and_load(window: &AppWindow, ctl: &Rc<LibraryController>) {
|
|||||||
{
|
{
|
||||||
let weak = window.as_weak();
|
let weak = window.as_weak();
|
||||||
let ctl = ctl.clone();
|
let ctl = ctl.clone();
|
||||||
window.on_library_columns_changed(move || {
|
window
|
||||||
|
.global::<Library>()
|
||||||
|
.on_library_columns_changed(move || {
|
||||||
if let Some(w) = weak.upgrade() {
|
if let Some(w) = weak.upgrade() {
|
||||||
if !w.get_library_visible() {
|
if !w.get_library_visible() {
|
||||||
return;
|
return;
|
||||||
@@ -489,7 +504,9 @@ fn wire_grid_sync_and_load(window: &AppWindow, ctl: &Rc<LibraryController>) {
|
|||||||
{
|
{
|
||||||
let weak = window.as_weak();
|
let weak = window.as_weak();
|
||||||
let ctl = ctl.clone();
|
let ctl = ctl.clone();
|
||||||
window.on_library_viewport_cells(move |on_screen| {
|
window
|
||||||
|
.global::<Library>()
|
||||||
|
.on_library_viewport_cells(move |on_screen| {
|
||||||
let Some(w) = weak.upgrade() else { return };
|
let Some(w) = weak.upgrade() else { return };
|
||||||
if !w.get_library_visible() {
|
if !w.get_library_visible() {
|
||||||
return;
|
return;
|
||||||
@@ -515,7 +532,9 @@ fn wire_grid_sync_and_load(window: &AppWindow, ctl: &Rc<LibraryController>) {
|
|||||||
{
|
{
|
||||||
let weak = window.as_weak();
|
let weak = window.as_weak();
|
||||||
let ctl = ctl.clone();
|
let ctl = ctl.clone();
|
||||||
window.on_library_scrolled(move |first_visible| {
|
window
|
||||||
|
.global::<Library>()
|
||||||
|
.on_library_scrolled(move |first_visible| {
|
||||||
let Some(w) = weak.upgrade() else { return };
|
let Some(w) = weak.upgrade() else { return };
|
||||||
let first_visible = first_visible.max(0) as usize;
|
let first_visible = first_visible.max(0) as usize;
|
||||||
|
|
||||||
@@ -572,7 +591,8 @@ fn wire_grid_sync_and_load(window: &AppWindow, ctl: &Rc<LibraryController>) {
|
|||||||
// through `on_back_to_library` to have the position replayed by
|
// through `on_back_to_library` to have the position replayed by
|
||||||
// hand. Without this they each rebuilt against whatever `scroll-to`
|
// hand. Without this they each rebuilt against whatever `scroll-to`
|
||||||
// was last *set* to — a stale scrub, or zero — and landed there.
|
// was last *set* to — a stale scrub, or zero — and landed there.
|
||||||
w.set_library_scroll_to(first_visible as i32);
|
w.global::<Library>()
|
||||||
|
.set_library_scroll_to(first_visible as i32);
|
||||||
|
|
||||||
// Move the timeline marker with the view. Scrolling the grid is a
|
// Move the timeline marker with the view. Scrolling the grid is a
|
||||||
// way of moving through time just as scrubbing is, and a marker
|
// way of moving through time just as scrubbing is, and a marker
|
||||||
@@ -603,8 +623,8 @@ fn wire_grid_sync_and_load(window: &AppWindow, ctl: &Rc<LibraryController>) {
|
|||||||
let (from, to) = zoomed_span(full, zoom, centre);
|
let (from, to) = zoomed_span(full, zoom, centre);
|
||||||
w.set_library_current_bucket(when as i32);
|
w.set_library_current_bucket(when as i32);
|
||||||
w.set_library_current_fraction(
|
w.set_library_current_fraction(
|
||||||
((when - from) as f64 / (to - from).max(1) as f64).clamp(0.0, 1.0)
|
((when - from) as f64 / (to - from).max(1) as f64)
|
||||||
as f32,
|
.clamp(0.0, 1.0) as f32,
|
||||||
);
|
);
|
||||||
w.set_library_timeline_anchored(true);
|
w.set_library_timeline_anchored(true);
|
||||||
}
|
}
|
||||||
@@ -619,7 +639,7 @@ fn wire_grid_sync_and_load(window: &AppWindow, ctl: &Rc<LibraryController>) {
|
|||||||
*ctl.offset.borrow(),
|
*ctl.offset.borrow(),
|
||||||
*ctl.window.borrow(),
|
*ctl.window.borrow(),
|
||||||
ctl.viewport_cells.get(),
|
ctl.viewport_cells.get(),
|
||||||
w.get_library_total().max(0) as usize,
|
w.global::<Library>().get_library_total().max(0) as usize,
|
||||||
) else {
|
) else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ use dr_catalog::Catalog;
|
|||||||
use slint::ComponentHandle;
|
use slint::ComponentHandle;
|
||||||
|
|
||||||
use crate::library;
|
use crate::library;
|
||||||
use crate::AppWindow;
|
use crate::{AppWindow, Library};
|
||||||
|
|
||||||
use super::controller::{stop, LibraryController};
|
use super::controller::{stop, LibraryController};
|
||||||
|
|
||||||
@@ -499,7 +499,9 @@ fn refresh_local_count(window: &AppWindow, ctl: &Rc<LibraryController>) {
|
|||||||
let Some(catalog) = borrow.as_ref() else {
|
let Some(catalog) = borrow.as_ref() else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
window.set_library_local_count(library::local_original_count(catalog).unwrap_or(0) as i32);
|
window
|
||||||
|
.global::<Library>()
|
||||||
|
.set_library_local_count(library::local_original_count(catalog).unwrap_or(0) as i32);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// TRACES: FR-NC-6a
|
/// TRACES: FR-NC-6a
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ use dr_types::FormatFilter;
|
|||||||
use slint::ComponentHandle;
|
use slint::ComponentHandle;
|
||||||
|
|
||||||
use crate::library::{self, ScanMessage};
|
use crate::library::{self, ScanMessage};
|
||||||
use crate::{AppWindow, View};
|
use crate::{AppWindow, Library, View};
|
||||||
|
|
||||||
use super::controller::{stop, LibraryController};
|
use super::controller::{stop, LibraryController};
|
||||||
use super::offline::refresh_offline;
|
use super::offline::refresh_offline;
|
||||||
@@ -677,11 +677,12 @@ pub(super) fn schedule_reload(window: &AppWindow, ctl: &Rc<LibraryController>) {
|
|||||||
// change to `columns` moves every one of them and a viewport left
|
// change to `columns` moves every one of them and a viewport left
|
||||||
// where it was would be pointing at rows the window no longer covers.
|
// where it was would be pointing at rows the window no longer covers.
|
||||||
let window_size = *ctl_cb.window.borrow();
|
let window_size = *ctl_cb.window.borrow();
|
||||||
let total = w.get_library_total().max(0) as usize;
|
let total = w.global::<Library>().get_library_total().max(0) as usize;
|
||||||
*ctl_cb.offset.borrow_mut() = window_start(anchor, window_size, total);
|
*ctl_cb.offset.borrow_mut() = window_start(anchor, window_size, total);
|
||||||
load_window(&w, &ctl_cb);
|
load_window(&w, &ctl_cb);
|
||||||
w.set_library_scroll_to(anchor as i32);
|
w.global::<Library>().set_library_scroll_to(anchor as i32);
|
||||||
w.set_library_scroll_token(w.get_library_scroll_token() + 1);
|
w.global::<Library>()
|
||||||
|
.set_library_scroll_token(w.global::<Library>().get_library_scroll_token() + 1);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Replacing the slot drops the previous timer, which is what makes this
|
// Replacing the slot drops the previous timer, which is what makes this
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ use dr_catalog::Catalog;
|
|||||||
use slint::{ComponentHandle, Model as _};
|
use slint::{ComponentHandle, Model as _};
|
||||||
|
|
||||||
use crate::library;
|
use crate::library;
|
||||||
use crate::{AppWindow, KeywordRow};
|
use crate::{AppWindow, KeywordRow, Library};
|
||||||
|
|
||||||
use super::controller::{stop, LibraryController};
|
use super::controller::{stop, LibraryController};
|
||||||
use super::window::load_window;
|
use super::window::load_window;
|
||||||
@@ -40,7 +40,7 @@ pub fn sync_ratings(window: &AppWindow, catalog: &Catalog, ids: &[dr_types::Imag
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let model = window.get_library_cells();
|
let model = window.global::<Library>().get_library_cells();
|
||||||
for (row, id) in ids.iter().enumerate() {
|
for (row, id) in ids.iter().enumerate() {
|
||||||
// Absent means unrated, which is a real state rather than missing data.
|
// Absent means unrated, which is a real state rather than missing data.
|
||||||
let j = found.get(id).copied().unwrap_or_default();
|
let j = found.get(id).copied().unwrap_or_default();
|
||||||
@@ -71,7 +71,9 @@ pub(super) fn refresh_rating_counts(window: &AppWindow, catalog: &Catalog) {
|
|||||||
// Shown for the same reason the star counts are: a filter that silently
|
// Shown for the same reason the star counts are: a filter that silently
|
||||||
// empties the grid reads as broken, and this one will legitimately be zero
|
// empties the grid reads as broken, and this one will legitimately be zero
|
||||||
// on a library nothing has been downloaded from yet.
|
// on a library nothing has been downloaded from yet.
|
||||||
window.set_library_local_count(library::local_original_count(catalog).unwrap_or(0) as i32);
|
window
|
||||||
|
.global::<Library>()
|
||||||
|
.set_library_local_count(library::local_original_count(catalog).unwrap_or(0) as i32);
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- keywords (FR-CAT-5, FR-CAT-6) ---------------------------------------
|
// --- keywords (FR-CAT-5, FR-CAT-6) ---------------------------------------
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ use dr_catalog::Catalog;
|
|||||||
use slint::ComponentHandle;
|
use slint::ComponentHandle;
|
||||||
|
|
||||||
use crate::library;
|
use crate::library;
|
||||||
use crate::{AppWindow, TimelineBar, View};
|
use crate::{AppWindow, Library, TimelineBar, View};
|
||||||
|
|
||||||
use super::controller::LibraryController;
|
use super::controller::LibraryController;
|
||||||
use super::filter_bar::{push_people_chips, refilter};
|
use super::filter_bar::{push_people_chips, refilter};
|
||||||
@@ -484,8 +484,12 @@ fn scrub_to(window: &AppWindow, ctl: &Rc<LibraryController>, when: i64) {
|
|||||||
// Move the viewport as well as the window. Cells are drawn at their
|
// Move the viewport as well as the window. Cells are drawn at their
|
||||||
// absolute place in the library, so loading rows around image 15,000 while
|
// absolute place in the library, so loading rows around image 15,000 while
|
||||||
// the viewport sits at row 0 shows an empty grid until the user scrolls.
|
// the viewport sits at row 0 shows an empty grid until the user scrolls.
|
||||||
window.set_library_scroll_to(position as i32);
|
window
|
||||||
window.set_library_scroll_token(window.get_library_scroll_token() + 1);
|
.global::<Library>()
|
||||||
|
.set_library_scroll_to(position as i32);
|
||||||
|
window
|
||||||
|
.global::<Library>()
|
||||||
|
.set_library_scroll_token(window.global::<Library>().get_library_scroll_token() + 1);
|
||||||
load_window(window, ctl);
|
load_window(window, ctl);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -506,7 +510,7 @@ fn scrub_to(window: &AppWindow, ctl: &Rc<LibraryController>, when: i64) {
|
|||||||
/// delete — after removing one frame from a wall of twenty thousand, the next
|
/// delete — after removing one frame from a wall of twenty thousand, the next
|
||||||
/// one you want is the one that just moved into its place.
|
/// one you want is the one that just moved into its place.
|
||||||
pub fn restore_position(window: &AppWindow, ctl: &Rc<LibraryController>) {
|
pub fn restore_position(window: &AppWindow, ctl: &Rc<LibraryController>) {
|
||||||
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 {
|
if total == 0 {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -522,8 +526,12 @@ pub fn restore_position(window: &AppWindow, ctl: &Rc<LibraryController>) {
|
|||||||
// `resume_at` is what the grid last reported as its first visible image,
|
// `resume_at` is what the grid last reported as its first visible image,
|
||||||
// which is the thing the photographer is actually looking at.
|
// which is the thing the photographer is actually looking at.
|
||||||
let anchor = ctl.resume_at.get().min(total - 1);
|
let anchor = ctl.resume_at.get().min(total - 1);
|
||||||
window.set_library_scroll_to(anchor as i32);
|
window
|
||||||
window.set_library_scroll_token(window.get_library_scroll_token() + 1);
|
.global::<Library>()
|
||||||
|
.set_library_scroll_to(anchor as i32);
|
||||||
|
window
|
||||||
|
.global::<Library>()
|
||||||
|
.set_library_scroll_token(window.global::<Library>().get_library_scroll_token() + 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Format a bucket start for the histogram's hover label.
|
/// Format a bucket start for the histogram's hover label.
|
||||||
@@ -765,7 +773,7 @@ pub(crate) fn apply_place(
|
|||||||
|
|
||||||
// The filter and the scope have both moved the window to the top; this is
|
// The filter and the scope have both moved the window to the top; this is
|
||||||
// what puts it back, and it has to run after them for that reason.
|
// what puts it back, and it has to run after them for that reason.
|
||||||
let total = window.get_library_total().max(0) as usize;
|
let total = window.global::<Library>().get_library_total().max(0) as usize;
|
||||||
let resolved = resolved.filter(|(at, _)| *at < total);
|
let resolved = resolved.filter(|(at, _)| *at < total);
|
||||||
match resolved {
|
match resolved {
|
||||||
Some((at, _)) => {
|
Some((at, _)) => {
|
||||||
@@ -786,16 +794,18 @@ pub(crate) fn apply_place(
|
|||||||
if let Some((at, Found::Photograph)) = resolved {
|
if let Some((at, Found::Photograph)) = resolved {
|
||||||
if let Some(open) = ctl.open_image.borrow().clone() {
|
if let Some(open) = ctl.open_image.borrow().clone() {
|
||||||
window.set_active_view(View::Develop);
|
window.set_active_view(View::Develop);
|
||||||
window.set_library_roll_centre(true);
|
window.global::<Library>().set_library_roll_centre(true);
|
||||||
let offset = *ctl.offset.borrow();
|
let offset = *ctl.offset.borrow();
|
||||||
if let Some(row) = at.checked_sub(offset) {
|
if let Some(row) = at.checked_sub(offset) {
|
||||||
window.set_library_roll_current(row as i32);
|
window
|
||||||
|
.global::<Library>()
|
||||||
|
.set_library_roll_current(row as i32);
|
||||||
}
|
}
|
||||||
open(place.path.clone());
|
open(place.path.clone());
|
||||||
// After the open, which resets the readout to "1 of 1" on its
|
// After the open, which resets the readout to "1 of 1" on its
|
||||||
// way in — the same ordering `report_position` documents.
|
// way in — the same ordering `report_position` documents.
|
||||||
window.set_index(at as i32);
|
window.set_index(at as i32);
|
||||||
window.set_total(window.get_library_total());
|
window.set_total(window.global::<Library>().get_library_total());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -884,7 +894,9 @@ fn push_filter(window: &AppWindow, ctl: &Rc<LibraryController>) {
|
|||||||
Some(dr_types::FlagState::Reject) => 2,
|
Some(dr_types::FlagState::Reject) => 2,
|
||||||
_ => 0,
|
_ => 0,
|
||||||
});
|
});
|
||||||
window.set_library_local_only(filter.local_only);
|
window
|
||||||
|
.global::<Library>()
|
||||||
|
.set_library_local_only(filter.local_only);
|
||||||
ctl.local_only.set(filter.local_only);
|
ctl.local_only.set(filter.local_only);
|
||||||
window.set_library_filter_eyes_open(filter.eyes_open);
|
window.set_library_filter_eyes_open(filter.eyes_open);
|
||||||
push_people_chips(window, ctl);
|
push_people_chips(window, ctl);
|
||||||
@@ -925,7 +937,7 @@ pub(super) fn resume_position(
|
|||||||
coll: &Rc<crate::collections_ui::CollectionsController>,
|
coll: &Rc<crate::collections_ui::CollectionsController>,
|
||||||
focus: Option<usize>,
|
focus: Option<usize>,
|
||||||
) {
|
) {
|
||||||
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 {
|
if total == 0 {
|
||||||
// Files named on the command line: there is no grid behind them, and
|
// Files named on the command line: there is no grid behind them, and
|
||||||
// an ordinal read off the readout would name a row of nothing.
|
// an ordinal read off the readout would name a row of nothing.
|
||||||
@@ -955,12 +967,16 @@ pub(super) fn resume_position(
|
|||||||
// Before the grid is shown, not after: the markup gates it on an `if`, and
|
// Before the grid is shown, not after: the markup gates it on an `if`, and
|
||||||
// the rebuilt Flickable reads `scroll-to` in its `init`. Setting these
|
// the rebuilt Flickable reads `scroll-to` in its `init`. Setting these
|
||||||
// afterwards would leave that init to run against the previous position.
|
// afterwards would leave that init to run against the previous position.
|
||||||
window.set_library_scroll_to(anchor as i32);
|
window
|
||||||
window.set_library_scroll_token(window.get_library_scroll_token() + 1);
|
.global::<Library>()
|
||||||
|
.set_library_scroll_to(anchor as i32);
|
||||||
|
window
|
||||||
|
.global::<Library>()
|
||||||
|
.set_library_scroll_token(window.global::<Library>().get_library_scroll_token() + 1);
|
||||||
|
|
||||||
if let Some(at) = focus {
|
if let Some(at) = focus {
|
||||||
coll.set_cursor(Some(at));
|
coll.set_cursor(Some(at));
|
||||||
window.set_library_cursor(at as i32);
|
window.global::<Library>().set_library_cursor(at as i32);
|
||||||
}
|
}
|
||||||
|
|
||||||
// The view has moved, so the capture-time marker has to move with it.
|
// The view has moved, so the capture-time marker has to move with it.
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ use std::sync::mpsc::Receiver;
|
|||||||
use slint::{ComponentHandle, Model as _};
|
use slint::{ComponentHandle, Model as _};
|
||||||
|
|
||||||
use crate::library::{self, ThumbnailMessage};
|
use crate::library::{self, ThumbnailMessage};
|
||||||
use crate::{AppWindow, LibraryCell};
|
use crate::{AppWindow, Library, LibraryCell};
|
||||||
|
|
||||||
use super::controller::{stop, LibraryController, LibraryFacts};
|
use super::controller::{stop, LibraryController, LibraryFacts};
|
||||||
use super::filter_bar::period_headings;
|
use super::filter_bar::period_headings;
|
||||||
@@ -245,8 +245,8 @@ pub(super) fn load_window(window: &AppWindow, ctl: &Rc<LibraryController>) {
|
|||||||
// Read before it is overwritten: the property still holds what the library
|
// Read before it is overwritten: the property still holds what the library
|
||||||
// was last time this ran, and a library that has become shorter is the
|
// was last time this ran, and a library that has become shorter is the
|
||||||
// signal that something was deleted out from under the view.
|
// signal that something was deleted out from under the view.
|
||||||
let was = window.get_library_total().max(0) as usize;
|
let was = window.global::<Library>().get_library_total().max(0) as usize;
|
||||||
window.set_library_total(total as i32);
|
window.global::<Library>().set_library_total(total as i32);
|
||||||
let shrank = total < was;
|
let shrank = total < was;
|
||||||
|
|
||||||
// Clamp so a scrub to the very end still fills the window rather than
|
// Clamp so a scrub to the very end still fills the window rather than
|
||||||
@@ -278,7 +278,7 @@ pub(super) fn load_window(window: &AppWindow, ctl: &Rc<LibraryController>) {
|
|||||||
//
|
//
|
||||||
// The trash is never reorderable. It is a view of what was deleted, ordered
|
// The trash is never reorderable. It is a view of what was deleted, ordered
|
||||||
// by when, and it is not a collection at all.
|
// by when, and it is not a collection at all.
|
||||||
window.set_library_reorderable(
|
window.global::<Library>().set_library_reorderable(
|
||||||
!trash
|
!trash
|
||||||
&& scope.is_some_and(|c| {
|
&& scope.is_some_and(|c| {
|
||||||
dr_catalog::collections::orders_manually(catalog.connection(), c).unwrap_or(false)
|
dr_catalog::collections::orders_manually(catalog.connection(), c).unwrap_or(false)
|
||||||
@@ -321,7 +321,7 @@ pub(super) fn load_window(window: &AppWindow, ctl: &Rc<LibraryController>) {
|
|||||||
// Month headings. The grid is ordered by capture time, so without these a
|
// Month headings. The grid is ordered by capture time, so without these a
|
||||||
// wall of thumbnails gives no sense of *when* you are looking — the
|
// wall of thumbnails gives no sense of *when* you are looking — the
|
||||||
// sidebar says it, but only if you consult it.
|
// sidebar says it, but only if you consult it.
|
||||||
let columns = window.get_library_columns().max(1) as usize;
|
let columns = window.global::<Library>().get_library_columns().max(1) as usize;
|
||||||
let headings = period_headings(
|
let headings = period_headings(
|
||||||
cells.iter().map(|c| {
|
cells.iter().map(|c| {
|
||||||
c.captured_at.map(|t| {
|
c.captured_at.map(|t| {
|
||||||
@@ -335,7 +335,7 @@ pub(super) fn load_window(window: &AppWindow, ctl: &Rc<LibraryController>) {
|
|||||||
|
|
||||||
// What the outgoing model is still holding — see [`hold_thumbnails`].
|
// What the outgoing model is still holding — see [`hold_thumbnails`].
|
||||||
let held = {
|
let held = {
|
||||||
let previous = window.get_library_cells();
|
let previous = window.global::<Library>().get_library_cells();
|
||||||
let ids = ctl.image_ids.borrow();
|
let ids = ctl.image_ids.borrow();
|
||||||
let classes = ctl.thumb_class.borrow();
|
let classes = ctl.thumb_class.borrow();
|
||||||
hold_thumbnails(&previous, &ids, &classes)
|
hold_thumbnails(&previous, &ids, &classes)
|
||||||
@@ -391,7 +391,9 @@ pub(super) fn load_window(window: &AppWindow, ctl: &Rc<LibraryController>) {
|
|||||||
// those fetches would have filled are absent from `requested` above, so the
|
// those fetches would have filled are absent from `requested` above, so the
|
||||||
// batch started below asks for them again.
|
// batch started below asks for them again.
|
||||||
ctl.generation.set(ctl.generation.get().wrapping_add(1));
|
ctl.generation.set(ctl.generation.get().wrapping_add(1));
|
||||||
window.set_library_cells(slint::ModelRc::new(slint::VecModel::from(rows)));
|
window
|
||||||
|
.global::<Library>()
|
||||||
|
.set_library_cells(slint::ModelRc::new(slint::VecModel::from(rows)));
|
||||||
|
|
||||||
// The model is fresh, so the "in this many collections" badges are all zero
|
// The model is fresh, so the "in this many collections" badges are all zero
|
||||||
// until refilled. One query for the whole window, not one per cell.
|
// until refilled. One query for the whole window, not one per cell.
|
||||||
@@ -481,7 +483,7 @@ fn request_thumbnails(window: &AppWindow, ctl: &Rc<LibraryController>) {
|
|||||||
// The drawn cell size decides which class to ask for. Chosen once for the
|
// The drawn cell size decides which class to ask for. Chosen once for the
|
||||||
// batch rather than per row, and carried through to the drain so a cell it
|
// batch rather than per row, and carried through to the drain so a cell it
|
||||||
// fills can record what it is now showing.
|
// fills can record what it is now showing.
|
||||||
let cell_pixels = window.get_library_cell_size().max(1.0) as u32;
|
let cell_pixels = window.global::<Library>().get_library_cell_size().max(1.0) as u32;
|
||||||
let class = dr_thumbs::ThumbSize::for_cell(cell_pixels);
|
let class = dr_thumbs::ThumbSize::for_cell(cell_pixels);
|
||||||
|
|
||||||
let mut wanted: Vec<library::ThumbnailRequest> = {
|
let mut wanted: Vec<library::ThumbnailRequest> = {
|
||||||
@@ -602,7 +604,9 @@ pub fn refresh_thumbnail(
|
|||||||
|
|
||||||
// What the grid is drawing at, so the cell can be corrected on screen
|
// What the grid is drawing at, so the cell can be corrected on screen
|
||||||
// rather than only on disk.
|
// rather than only on disk.
|
||||||
let drawn = dr_thumbs::ThumbSize::for_cell(window.get_library_cell_size().max(1.0) as u32);
|
let drawn = dr_thumbs::ThumbSize::for_cell(
|
||||||
|
window.global::<Library>().get_library_cell_size().max(1.0) as u32,
|
||||||
|
);
|
||||||
|
|
||||||
for class in [dr_thumbs::ThumbSize::Grid, dr_thumbs::ThumbSize::Large] {
|
for class in [dr_thumbs::ThumbSize::Grid, dr_thumbs::ThumbSize::Large] {
|
||||||
let (w, h, rgba) = match render(class.edge()) {
|
let (w, h, rgba) = match render(class.edge()) {
|
||||||
@@ -634,7 +638,7 @@ pub fn refresh_thumbnail(
|
|||||||
// Straight into the model, so the edit is on the cell the moment
|
// Straight into the model, so the edit is on the cell the moment
|
||||||
// the grid comes back rather than after a scroll evicts and
|
// the grid comes back rather than after a scroll evicts and
|
||||||
// refetches it.
|
// refetches it.
|
||||||
let model = window.get_library_cells();
|
let model = window.global::<Library>().get_library_cells();
|
||||||
if let Some(mut cell) = model.row_data(row) {
|
if let Some(mut cell) = model.row_data(row) {
|
||||||
cell.thumbnail = to_slint_image(w, h, &rgba);
|
cell.thumbnail = to_slint_image(w, h, &rgba);
|
||||||
cell.has_thumb = true;
|
cell.has_thumb = true;
|
||||||
@@ -707,7 +711,7 @@ fn drain_thumbnails(
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let model = w.get_library_cells();
|
let model = w.global::<Library>().get_library_cells();
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
let msg = match rx.try_recv() {
|
let msg = match rx.try_recv() {
|
||||||
|
|||||||
+51
-158
@@ -7,7 +7,7 @@ import { CropOverlay } from "crop.slint";
|
|||||||
import { HistoryPanel, HistoryRow, Steps } from "history.slint";
|
import { HistoryPanel, HistoryRow, Steps } from "history.slint";
|
||||||
import { LaunchScreen } from "launch.slint";
|
import { LaunchScreen } from "launch.slint";
|
||||||
import { IdentityScreen, IdentityPerson, IdentityFace } from "identity.slint";
|
import { IdentityScreen, IdentityPerson, IdentityFace } from "identity.slint";
|
||||||
import { LibraryGrid, LibraryCell, TimelineBar, PhotoRoll, KeywordRow, PersonChip } from "library.slint";
|
import { LibraryGrid, LibraryCell, TimelineBar, PhotoRoll, KeywordRow, PersonChip, Library } from "library.slint";
|
||||||
import { GestureRow } from "gestures.slint";
|
import { GestureRow } from "gestures.slint";
|
||||||
import { Button, PanelHeading, Label, Value, Caption, Panel, EmptyState, ProgressBar, ActivityRow } from "widgets.slint";
|
import { Button, PanelHeading, Label, Value, Caption, Panel, EmptyState, ProgressBar, ActivityRow } from "widgets.slint";
|
||||||
import { CollectionsPanel, CollectionRow, Collections, OfflinePrompt, CollectionMenu,
|
import { CollectionsPanel, CollectionRow, Collections, OfflinePrompt, CollectionMenu,
|
||||||
@@ -32,6 +32,7 @@ export { ViewMode, GradientHandle, HandleRole, SpotHandle, SpotRole }
|
|||||||
// panels take their wiring this way and why there is one global per family.
|
// panels take their wiring this way and why there is one global per family.
|
||||||
export { Develop, Capture, Levels, Peaking, Adjustments, Framing, Transfer, Masking, Repair, Steps }
|
export { Develop, Capture, Levels, Peaking, Adjustments, Framing, Transfer, Masking, Repair, Steps }
|
||||||
export { Collections }
|
export { Collections }
|
||||||
|
export { Library }
|
||||||
|
|
||||||
/// Which top-level screen is on display. Exactly one at a time — replacing
|
/// Which top-level screen is on display. Exactly one at a time — replacing
|
||||||
/// the `show-launch` / `show-library` / `show-identity` booleans, with
|
/// the `show-launch` / `show-library` / `show-identity` booleans, with
|
||||||
@@ -288,8 +289,6 @@ export component AppWindow inherits Window {
|
|||||||
/// the two can drift apart again.
|
/// the two can drift apart again.
|
||||||
out property <bool> library-visible:
|
out property <bool> library-visible:
|
||||||
root.active-page == Page.none && root.active-view == View.library;
|
root.active-page == Page.none && root.active-view == View.library;
|
||||||
in-out property <[LibraryCell]> library-cells;
|
|
||||||
in property <int> library-total: 0;
|
|
||||||
// --- background activity (FR-CAT-1, FR-NC-6c) ---
|
// --- background activity (FR-CAT-1, FR-NC-6c) ---
|
||||||
//
|
//
|
||||||
// Every background job in one register, filled by `activity.rs`. The
|
// Every background job in one register, filled by `activity.rs`. The
|
||||||
@@ -338,28 +337,9 @@ export component AppWindow inherits Window {
|
|||||||
in property <bool> library-offline: false;
|
in property <bool> library-offline: false;
|
||||||
in property <string> library-offline-reason: "";
|
in property <string> library-offline-reason: "";
|
||||||
in property <string> library-offline-since: "";
|
in property <string> library-offline-since: "";
|
||||||
// Narrows the grid to images whose RAW is stored locally — the ones that
|
|
||||||
// can actually be opened while offline. Off by default: the catalog is the
|
|
||||||
// library, and hiding most of it the moment a connection drops would read
|
|
||||||
// as data loss rather than as a filter.
|
|
||||||
in-out property <bool> library-local-only: false;
|
|
||||||
in property <int> library-local-count: 0;
|
|
||||||
callback library-toggle-local-only();
|
|
||||||
/// Whether the grid is narrowed to the timeline's visible span.
|
/// Whether the grid is narrowed to the timeline's visible span.
|
||||||
in-out property <bool> library-range-active: false;
|
in-out property <bool> library-range-active: false;
|
||||||
callback library-toggle-date-range();
|
callback library-toggle-date-range();
|
||||||
/// TRACES: FR-CAT-5
|
|
||||||
callback library-clear-selection();
|
|
||||||
callback library-select-all();
|
|
||||||
/// TRACES: FR-CAT-7
|
|
||||||
/// Whether the grid is showing something with a manual order to change —
|
|
||||||
/// a single manual collection, not a set and not a saved filter.
|
|
||||||
in property <bool> library-reorderable: false;
|
|
||||||
/// Move the selection beside the photograph at this row of the loaded
|
|
||||||
/// window: after it where the drop landed on the cell's trailing half,
|
|
||||||
/// before it otherwise.
|
|
||||||
callback library-reorder-to(int, bool);
|
|
||||||
callback library-collection-from-selection(string);
|
|
||||||
/// TRACES: FR-CAT-6
|
/// TRACES: FR-CAT-6
|
||||||
in property <string> library-range-from;
|
in property <string> library-range-from;
|
||||||
in property <string> library-range-to;
|
in property <string> library-range-to;
|
||||||
@@ -425,7 +405,6 @@ export component AppWindow inherits Window {
|
|||||||
callback library-timeline-pinch(float);
|
callback library-timeline-pinch(float);
|
||||||
callback library-timeline-pan(float);
|
callback library-timeline-pan(float);
|
||||||
callback library-timeline-zoom(int);
|
callback library-timeline-zoom(int);
|
||||||
in-out property <int> library-columns: 1;
|
|
||||||
in property <bool> library-syncing: false;
|
in property <bool> library-syncing: false;
|
||||||
/// TRACES: FR-CAT-3 | FR-NC-3
|
/// TRACES: FR-CAT-3 | FR-NC-3
|
||||||
/// The whole-library thumbnail pass, launched from the settings page.
|
/// The whole-library thumbnail pass, launched from the settings page.
|
||||||
@@ -496,42 +475,10 @@ export component AppWindow inherits Window {
|
|||||||
callback identity-check-coverage();
|
callback identity-check-coverage();
|
||||||
callback identity-delete-all();
|
callback identity-delete-all();
|
||||||
callback library-thumbnail-all();
|
callback library-thumbnail-all();
|
||||||
in-out property <length> library-cell-size: 180px;
|
|
||||||
/// Whether this session is a touch one, which decides if the rating strip
|
|
||||||
/// waits to be hovered or stands open. Seeded from the platform by Rust
|
|
||||||
/// and latched by the grid on the first finger it sees.
|
|
||||||
in-out property <bool> library-touched: false;
|
|
||||||
callback library-zoom-cells(int);
|
|
||||||
/// A pinch step on the grid: the ratio since the last update.
|
|
||||||
callback library-pinch-cells(float);
|
|
||||||
/// A pinch on the grid has begun, so the press that opened it was not one.
|
|
||||||
callback library-pinch-started();
|
|
||||||
in property <int> library-scroll-to: 0;
|
|
||||||
in property <int> library-scroll-token: 0;
|
|
||||||
/// Which row of the loaded window is the photograph currently open in
|
|
||||||
/// develop, so the roll can mark it. `-1` when it is not in the window.
|
|
||||||
in property <int> library-roll-current: -1;
|
|
||||||
/// Whether the photo roll is out. Owned here rather than by the roll, so
|
/// Whether the photo roll is out. Owned here rather than by the roll, so
|
||||||
/// it survives the develop view being torn down and rebuilt.
|
/// it survives the develop view being torn down and rebuilt.
|
||||||
in-out property <bool> roll-open: false;
|
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);
|
|
||||||
callback library-sync-now();
|
callback library-sync-now();
|
||||||
callback library-columns-changed();
|
|
||||||
callback library-scrolled(int);
|
|
||||||
/// How many cells the grid's viewport shows at once. Rust sizes the
|
|
||||||
/// loaded window from it — see `library_ui::window_move`.
|
|
||||||
callback library-viewport-cells(int);
|
|
||||||
callback library-cell-clicked(int);
|
|
||||||
callback library-rescan();
|
callback library-rescan();
|
||||||
/// Grid → launch screen, to pick a different folder or account.
|
/// Grid → launch screen, to pick a different folder or account.
|
||||||
callback library-change();
|
callback library-change();
|
||||||
@@ -559,11 +506,6 @@ export component AppWindow inherits Window {
|
|||||||
/// Ask about a collection's local copies: the tray on a row, or a long
|
/// Ask about a collection's local copies: the tray on a row, or a long
|
||||||
/// press anywhere on it.
|
/// press anywhere on it.
|
||||||
callback collection-offline-menu(int);
|
callback collection-offline-menu(int);
|
||||||
/// TRACES: FR-CAT-7 | FR-UI-4
|
|
||||||
/// File the grid's selection in a collection without dragging it there:
|
|
||||||
/// the target's id, and whether to take the images out of the collection
|
|
||||||
/// currently being shown.
|
|
||||||
callback library-file-in-collection(int, bool);
|
|
||||||
/// TRACES: FR-CAT-5 | FR-CAT-6
|
/// TRACES: FR-CAT-5 | FR-CAT-6
|
||||||
/// Keywording the grid's selection. The catalog has been searchable by
|
/// Keywording the grid's selection. The catalog has been searchable by
|
||||||
/// keyword since it existed and there was nowhere to type one; this is it.
|
/// keyword since it existed and there was nowhere to type one; this is it.
|
||||||
@@ -583,58 +525,9 @@ export component AppWindow inherits Window {
|
|||||||
/// Take a keyword off the selection. Never deletes the keyword itself —
|
/// Take a keyword off the selection. Never deletes the keyword itself —
|
||||||
/// it stays in the vocabulary and on every other photograph that carries it.
|
/// it stays in the vocabulary and on every other photograph that carries it.
|
||||||
callback library-unassign-keyword(string);
|
callback library-unassign-keyword(string);
|
||||||
/// TRACES: FR-UI-2
|
|
||||||
/// Whether a tap in the grid selects rather than opens, and the button
|
|
||||||
/// that turns it on. The long press does the same thing without it.
|
|
||||||
in property <bool> library-select-mode: false;
|
|
||||||
callback library-toggle-select-mode();
|
|
||||||
/// TRACES: FR-CAT-7 | FR-UI-4
|
|
||||||
/// The row a press has held long enough to pick up, or `-1`.
|
|
||||||
///
|
|
||||||
/// Set by the same hold that turns on selection mode and cleared by the
|
|
||||||
/// grid when the press ends — `in-out` because both ends write it. See
|
|
||||||
/// `held-row` in `library.slint` for what it draws and what it stops.
|
|
||||||
in-out property <int> library-held-row: -1;
|
|
||||||
/// A press on a cell ended, so the long-press timer can be cancelled.
|
|
||||||
callback library-cell-press-ended();
|
|
||||||
|
|
||||||
// --- drag and drop ---
|
|
||||||
//
|
|
||||||
// Slint's own `DragArea`/`DropArea` carry this: pointer capture, the
|
|
||||||
// click-versus-drag threshold, arbitration against the grid's Flickable,
|
|
||||||
// and the image drawn under the cursor. So there is no badge to position
|
|
||||||
// and no hover state to mirror here — only the payload, which Rust builds
|
|
||||||
// from the selection, and the count the header shows.
|
|
||||||
/// The drag payload, built by Rust from the current selection.
|
|
||||||
pure callback library-drag-payload() -> data-transfer;
|
|
||||||
/// The bitmap that travels under the cursor: one thumbnail, or a fanned
|
|
||||||
/// stack where several images are being dragged. Composited in Rust.
|
|
||||||
in property <image> library-drag-image;
|
|
||||||
/// A drag began on a cell, so an unselected one can be promoted before the
|
|
||||||
/// payload is read.
|
|
||||||
callback library-drag-started(int);
|
|
||||||
callback library-drag-finished();
|
|
||||||
in property <int> library-selected-count: 0;
|
|
||||||
|
|
||||||
callback library-cell-pressed(int, bool, bool);
|
|
||||||
callback library-remove-from-collection();
|
|
||||||
|
|
||||||
// --- where the selection is filed (FR-CAT-7) ---------------------------
|
|
||||||
//
|
|
||||||
// `library-remove-from-collection` can only ever mean the collection the
|
|
||||||
// grid is scoped to — it is the only one that button can name. Opens the
|
|
||||||
// membership sheet, whose state (`Collections.membership-open` and
|
|
||||||
// `.membership-rows`) is on the sidebar's own global — only the library
|
|
||||||
// side knows the selection has changed enough to be worth recomputing
|
|
||||||
// the sheet against.
|
|
||||||
callback library-open-membership();
|
|
||||||
|
|
||||||
// The keyboard cursor: where the arrows are in the library, as an image
|
|
||||||
// ordinal. Rust owns it — clamping it needs the library's length, and
|
|
||||||
// moving it may have to swap the loaded window underneath.
|
|
||||||
in property <int> library-cursor: -1;
|
|
||||||
callback library-move-cursor(int, bool);
|
|
||||||
callback library-open-cursor();
|
|
||||||
|
|
||||||
// --- ratings and flags (FR-CAT-5, FR-CULL-4) ---
|
// --- ratings and flags (FR-CAT-5, FR-CULL-4) ---
|
||||||
//
|
//
|
||||||
@@ -1560,7 +1453,7 @@ in property <bool> panel-visible: true;
|
|||||||
height: 100%;
|
height: 100%;
|
||||||
rows: Collections.collection-rows;
|
rows: Collections.collection-rows;
|
||||||
selected-id: root.collection-selected;
|
selected-id: root.collection-selected;
|
||||||
total-images: root.library-total;
|
total-images: Library.library-total;
|
||||||
error: Collections.collection-error;
|
error: Collections.collection-error;
|
||||||
|
|
||||||
// TRACES: FR-NC-6a
|
// TRACES: FR-NC-6a
|
||||||
@@ -1592,7 +1485,7 @@ in property <bool> panel-visible: true;
|
|||||||
rename-cancel() => { Collections.collection-rename-cancel(); }
|
rename-cancel() => { Collections.collection-rename-cancel(); }
|
||||||
trash-count: Collections.trash-count;
|
trash-count: Collections.trash-count;
|
||||||
trash-label: Collections.trash-label;
|
trash-label: Collections.trash-label;
|
||||||
selected-count: root.library-selected-count;
|
selected-count: Library.library-selected-count;
|
||||||
|
|
||||||
dropped-on(id) => { Collections.collection-dropped(id); }
|
dropped-on(id) => { Collections.collection-dropped(id); }
|
||||||
collection-drag-payload(id) => { return Collections.collection-drag-payload(id); }
|
collection-drag-payload(id) => { return Collections.collection-drag-payload(id); }
|
||||||
@@ -1610,8 +1503,8 @@ in property <bool> panel-visible: true;
|
|||||||
// 768 logical pixels, which fits the title and the status
|
// 768 logical pixels, which fits the title and the status
|
||||||
// line but not the library's buttons beside them.
|
// line but not the library's buttons beside them.
|
||||||
expanded: root.expanded;
|
expanded: root.expanded;
|
||||||
cells: root.library-cells;
|
cells: Library.library-cells;
|
||||||
total: root.library-total;
|
total: Library.library-total;
|
||||||
scanning: root.library-scanning;
|
scanning: root.library-scanning;
|
||||||
opening: root.library-opening;
|
opening: root.library-opening;
|
||||||
scan-status: root.library-status;
|
scan-status: root.library-status;
|
||||||
@@ -1621,17 +1514,17 @@ in property <bool> panel-visible: true;
|
|||||||
offline-reason: root.library-offline-reason;
|
offline-reason: root.library-offline-reason;
|
||||||
offline-since: root.library-offline-since;
|
offline-since: root.library-offline-since;
|
||||||
retry-connection() => { root.library-retry-connection(); }
|
retry-connection() => { root.library-retry-connection(); }
|
||||||
local-only: root.library-local-only;
|
local-only: Library.library-local-only;
|
||||||
local-count: root.library-local-count;
|
local-count: Library.library-local-count;
|
||||||
toggle-local-only() => { root.library-toggle-local-only(); }
|
toggle-local-only() => { Library.library-toggle-local-only(); }
|
||||||
range-active: root.library-range-active;
|
range-active: root.library-range-active;
|
||||||
toggle-date-range() => { root.library-toggle-date-range(); }
|
toggle-date-range() => { root.library-toggle-date-range(); }
|
||||||
clear-selection() => { root.library-clear-selection(); }
|
clear-selection() => { Library.library-clear-selection(); }
|
||||||
select-all() => { root.library-select-all(); }
|
select-all() => { Library.library-select-all(); }
|
||||||
reorderable: root.library-reorderable;
|
reorderable: Library.library-reorderable;
|
||||||
reorder-to(row, after) => { root.library-reorder-to(row, after); }
|
reorder-to(row, after) => { Library.library-reorder-to(row, after); }
|
||||||
collection-from-selection(name) => {
|
collection-from-selection(name) => {
|
||||||
root.library-collection-from-selection(name);
|
Library.library-collection-from-selection(name);
|
||||||
}
|
}
|
||||||
range-from: root.library-range-from;
|
range-from: root.library-range-from;
|
||||||
range-to: root.library-range-to;
|
range-to: root.library-range-to;
|
||||||
@@ -1653,7 +1546,7 @@ in property <bool> panel-visible: true;
|
|||||||
timeline-label: root.library-timeline-label;
|
timeline-label: root.library-timeline-label;
|
||||||
window-label: root.library-window-label;
|
window-label: root.library-window-label;
|
||||||
offset: root.library-offset;
|
offset: root.library-offset;
|
||||||
selected-count: root.library-selected-count;
|
selected-count: Library.library-selected-count;
|
||||||
scope-label: Collections.collection-scope-label;
|
scope-label: Collections.collection-scope-label;
|
||||||
settings-armed: Transfer.armed;
|
settings-armed: Transfer.armed;
|
||||||
paste-settings-to-selection => {
|
paste-settings-to-selection => {
|
||||||
@@ -1664,7 +1557,7 @@ in property <bool> panel-visible: true;
|
|||||||
// sheet says it would act on and what the apply handler
|
// sheet says it would act on and what the apply handler
|
||||||
// reads back to decide it means the batch.
|
// reads back to decide it means the batch.
|
||||||
open-presets => {
|
open-presets => {
|
||||||
root.preset-apply-count = root.library-selected-count;
|
root.preset-apply-count = Library.library-selected-count;
|
||||||
root.preset-can-save = false;
|
root.preset-can-save = false;
|
||||||
root.presets-open = true;
|
root.presets-open = true;
|
||||||
}
|
}
|
||||||
@@ -1689,23 +1582,23 @@ in property <bool> panel-visible: true;
|
|||||||
timeline-pan(d) => { root.library-timeline-pan(d); }
|
timeline-pan(d) => { root.library-timeline-pan(d); }
|
||||||
timeline-zoom(d) => { root.library-timeline-zoom(d); }
|
timeline-zoom(d) => { root.library-timeline-zoom(d); }
|
||||||
syncing: root.library-syncing;
|
syncing: root.library-syncing;
|
||||||
requested-cell-size: root.library-cell-size;
|
requested-cell-size: Library.library-cell-size;
|
||||||
touched <=> root.library-touched;
|
touched <=> Library.library-touched;
|
||||||
zoom-cells(d) => { root.library-zoom-cells(d); }
|
zoom-cells(d) => { Library.library-zoom-cells(d); }
|
||||||
pinch-cells(r) => { root.library-pinch-cells(r); }
|
pinch-cells(r) => { Library.library-pinch-cells(r); }
|
||||||
pinch-started() => { root.library-pinch-started(); }
|
pinch-started() => { Library.library-pinch-started(); }
|
||||||
scroll-to: root.library-scroll-to;
|
scroll-to: Library.library-scroll-to;
|
||||||
scroll-token: root.library-scroll-token;
|
scroll-token: Library.library-scroll-token;
|
||||||
sync-now() => { root.library-sync-now(); }
|
sync-now() => { root.library-sync-now(); }
|
||||||
columns-changed(n) => {
|
columns-changed(n) => {
|
||||||
root.library-columns = n;
|
Library.library-columns = n;
|
||||||
// Which cell begins a row just changed, and month headings sit on
|
// Which cell begins a row just changed, and month headings sit on
|
||||||
// row-leading cells — so they must be recomputed, not just moved.
|
// row-leading cells — so they must be recomputed, not just moved.
|
||||||
root.library-columns-changed();
|
Library.library-columns-changed();
|
||||||
}
|
}
|
||||||
scrolled(i) => { root.library-scrolled(i); }
|
scrolled(i) => { Library.library-scrolled(i); }
|
||||||
viewport-cells-changed(n) => { root.library-viewport-cells(n); }
|
viewport-cells-changed(n) => { Library.library-viewport-cells(n); }
|
||||||
cell-clicked(i) => { root.library-cell-clicked(i); }
|
cell-clicked(i) => { Library.library-cell-clicked(i); }
|
||||||
rescan() => { root.library-rescan(); }
|
rescan() => { root.library-rescan(); }
|
||||||
can-import: root.import-supported;
|
can-import: root.import-supported;
|
||||||
open-import() => { root.import-open(); }
|
open-import() => { root.import-open(); }
|
||||||
@@ -1713,35 +1606,35 @@ in property <bool> panel-visible: true;
|
|||||||
open-people() => { root.identity-open(); }
|
open-people() => { root.identity-open(); }
|
||||||
|
|
||||||
cell-pressed(i, ctrl, shift) => {
|
cell-pressed(i, ctrl, shift) => {
|
||||||
root.library-cell-pressed(i, ctrl, shift);
|
Library.library-cell-pressed(i, ctrl, shift);
|
||||||
}
|
}
|
||||||
cell-press-ended() => { root.library-cell-press-ended(); }
|
cell-press-ended() => { Library.library-cell-press-ended(); }
|
||||||
select-mode: root.library-select-mode;
|
select-mode: Library.library-select-mode;
|
||||||
toggle-select-mode() => { root.library-toggle-select-mode(); }
|
toggle-select-mode() => { Library.library-toggle-select-mode(); }
|
||||||
held-row <=> root.library-held-row;
|
held-row <=> Library.library-held-row;
|
||||||
// The sidebar's rows, not a second model: the sheet files
|
// The sidebar's rows, not a second model: the sheet files
|
||||||
// into the same tree the sidebar draws.
|
// into the same tree the sidebar draws.
|
||||||
collections: Collections.collection-rows;
|
collections: Collections.collection-rows;
|
||||||
file-in-collection(id, moves) => {
|
file-in-collection(id, moves) => {
|
||||||
root.library-file-in-collection(id, moves);
|
Library.library-file-in-collection(id, moves);
|
||||||
}
|
}
|
||||||
keywords: root.library-keywords;
|
keywords: root.library-keywords;
|
||||||
keywords-opened() => { root.library-keywords-opened(); }
|
keywords-opened() => { root.library-keywords-opened(); }
|
||||||
assign-keyword(word) => { root.library-assign-keyword(word); }
|
assign-keyword(word) => { root.library-assign-keyword(word); }
|
||||||
unassign-keyword(word) => { root.library-unassign-keyword(word); }
|
unassign-keyword(word) => { root.library-unassign-keyword(word); }
|
||||||
cursor: root.library-cursor;
|
cursor: Library.library-cursor;
|
||||||
move-cursor(delta, extend) => {
|
move-cursor(delta, extend) => {
|
||||||
root.library-move-cursor(delta, extend);
|
Library.library-move-cursor(delta, extend);
|
||||||
}
|
}
|
||||||
open-cursor() => { root.library-open-cursor(); }
|
open-cursor() => { Library.library-open-cursor(); }
|
||||||
drag-image: root.library-drag-image;
|
drag-image: Library.library-drag-image;
|
||||||
drag-payload() => { return root.library-drag-payload(); }
|
drag-payload() => { return Library.library-drag-payload(); }
|
||||||
drag-started(i) => { root.library-drag-started(i); }
|
drag-started(i) => { Library.library-drag-started(i); }
|
||||||
drag-finished() => { root.library-drag-finished(); }
|
drag-finished() => { Library.library-drag-finished(); }
|
||||||
remove-from-collection() => {
|
remove-from-collection() => {
|
||||||
root.library-remove-from-collection();
|
Library.library-remove-from-collection();
|
||||||
}
|
}
|
||||||
open-membership() => { root.library-open-membership(); }
|
open-membership() => { Library.library-open-membership(); }
|
||||||
|
|
||||||
filter-people: root.library-filter-people;
|
filter-people: root.library-filter-people;
|
||||||
filter-people-all: root.library-filter-people-all;
|
filter-people-all: root.library-filter-people-all;
|
||||||
@@ -1805,7 +1698,7 @@ in property <bool> panel-visible: true;
|
|||||||
position: root.total > 0 ? root.index + 1 + " / " + root.total : "";
|
position: root.total > 0 ? root.index + 1 + " / " + root.total : "";
|
||||||
// Only where a library was opened; command-line files have no
|
// Only where a library was opened; command-line files have no
|
||||||
// grid to return to.
|
// grid to return to.
|
||||||
can-return-to-library: root.library-total > 0;
|
can-return-to-library: Library.library-total > 0;
|
||||||
panel-visible: root.panel-visible;
|
panel-visible: root.panel-visible;
|
||||||
export-label: root.export-label;
|
export-label: root.export-label;
|
||||||
export-busy: root.export-busy;
|
export-busy: root.export-busy;
|
||||||
@@ -2657,7 +2550,7 @@ in property <bool> panel-visible: true;
|
|||||||
// instead — the controls ride on top of the roll's
|
// instead — the controls ride on top of the roll's
|
||||||
// band, at whatever height it currently stands.
|
// band, at whatever height it currently stands.
|
||||||
property <length> roll-clear:
|
property <length> roll-clear:
|
||||||
root.library-total > 0
|
Library.library-total > 0
|
||||||
? (root.roll-open
|
? (root.roll-open
|
||||||
? Theme.roll-strip + Theme.roll-reach
|
? Theme.roll-strip + Theme.roll-reach
|
||||||
: Theme.roll-reach)
|
: Theme.roll-reach)
|
||||||
@@ -2770,14 +2663,14 @@ in property <bool> panel-visible: true;
|
|||||||
//
|
//
|
||||||
// Last in the canvas, so it draws over the photograph
|
// Last in the canvas, so it draws over the photograph
|
||||||
// rather than under it.
|
// rather than under it.
|
||||||
if root.library-total > 0: PhotoRoll {
|
if Library.library-total > 0: PhotoRoll {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
cells: root.library-cells;
|
cells: Library.library-cells;
|
||||||
current: root.library-roll-current;
|
current: Library.library-roll-current;
|
||||||
open <=> root.roll-open;
|
open <=> root.roll-open;
|
||||||
centre-request <=> root.library-roll-centre;
|
centre-request <=> Library.library-roll-centre;
|
||||||
pick(i) => { root.library-roll-pick(i); }
|
pick(i) => { Library.library-roll-pick(i); }
|
||||||
}
|
}
|
||||||
|
|
||||||
// Report size changes so the render target can be resized to
|
// Report size changes so the render target can be resized to
|
||||||
@@ -3179,7 +3072,7 @@ in property <bool> panel-visible: true;
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
open: Collections.membership-open;
|
open: Collections.membership-open;
|
||||||
selected-count: root.library-selected-count;
|
selected-count: Library.library-selected-count;
|
||||||
rows: Collections.membership-rows;
|
rows: Collections.membership-rows;
|
||||||
remove(id) => { Collections.membership-remove(id); }
|
remove(id) => { Collections.membership-remove(id); }
|
||||||
dismiss() => { Collections.membership-dismiss(); }
|
dismiss() => { Collections.membership-dismiss(); }
|
||||||
|
|||||||
@@ -479,6 +479,129 @@ export struct LibraryCell {
|
|||||||
burst-representative: bool,
|
burst-representative: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TRACES: FR-CAT-3 | FR-CAT-4 | FR-CAT-5 | FR-CAT-6 | FR-CAT-7 | FR-CULL-4 | FR-CULL-5
|
||||||
|
// The library grid's own state: the loaded window of cells, the selection,
|
||||||
|
// the timeline, the filter bar, ratings and flags, keywording, drag and drop,
|
||||||
|
// and the routes into and out of the screen.
|
||||||
|
//
|
||||||
|
// A global rather than properties and callbacks on the window root — see
|
||||||
|
// `session.slint` for why the develop panels stopped taking their wiring
|
||||||
|
// through the window root, and `collections.slint` for the sidebar's own
|
||||||
|
// global; the same reasoning applies here. What is shared with the sidebar
|
||||||
|
// (the collection the grid is scoped to, whether the sidebar is shown) stays
|
||||||
|
// on the window, because `run()`'s panel-layout wiring and the sidebar's own
|
||||||
|
// Rust still reach it there.
|
||||||
|
export global Library {
|
||||||
|
in-out property <[LibraryCell]> library-cells;
|
||||||
|
in property <int> library-total: 0;
|
||||||
|
// Narrows the grid to images whose RAW is stored locally — the ones that
|
||||||
|
// can actually be opened while offline. Off by default: the catalog is the
|
||||||
|
// library, and hiding most of it the moment a connection drops would read
|
||||||
|
// as data loss rather than as a filter.
|
||||||
|
in-out property <bool> library-local-only: false;
|
||||||
|
in property <int> library-local-count: 0;
|
||||||
|
callback library-toggle-local-only();
|
||||||
|
/// TRACES: FR-CAT-5
|
||||||
|
callback library-clear-selection();
|
||||||
|
callback library-select-all();
|
||||||
|
/// TRACES: FR-CAT-7
|
||||||
|
/// Whether the grid is showing something with a manual order to change —
|
||||||
|
/// a single manual collection, not a set and not a saved filter.
|
||||||
|
in property <bool> library-reorderable: false;
|
||||||
|
/// Move the selection beside the photograph at this row of the loaded
|
||||||
|
/// window: after it where the drop landed on the cell's trailing half,
|
||||||
|
/// before it otherwise.
|
||||||
|
callback library-reorder-to(int, bool);
|
||||||
|
callback library-collection-from-selection(string);
|
||||||
|
in-out property <int> library-columns: 1;
|
||||||
|
in-out property <length> library-cell-size: 180px;
|
||||||
|
/// Whether this session is a touch one, which decides if the rating strip
|
||||||
|
/// waits to be hovered or stands open. Seeded from the platform by Rust
|
||||||
|
/// and latched by the grid on the first finger it sees.
|
||||||
|
in-out property <bool> library-touched: false;
|
||||||
|
callback library-zoom-cells(int);
|
||||||
|
/// A pinch step on the grid: the ratio since the last update.
|
||||||
|
callback library-pinch-cells(float);
|
||||||
|
/// A pinch on the grid has begun, so the press that opened it was not one.
|
||||||
|
callback library-pinch-started();
|
||||||
|
in property <int> library-scroll-to: 0;
|
||||||
|
in property <int> library-scroll-token: 0;
|
||||||
|
/// Which row of the loaded window is the photograph currently open in
|
||||||
|
/// develop, so the roll can mark it. `-1` when it is not in the window.
|
||||||
|
in property <int> library-roll-current: -1;
|
||||||
|
/// 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);
|
||||||
|
callback library-columns-changed();
|
||||||
|
callback library-scrolled(int);
|
||||||
|
/// How many cells the grid's viewport shows at once. Rust sizes the
|
||||||
|
/// loaded window from it — see `library_ui::window_move`.
|
||||||
|
callback library-viewport-cells(int);
|
||||||
|
callback library-cell-clicked(int);
|
||||||
|
/// TRACES: FR-CAT-7 | FR-UI-4
|
||||||
|
/// File the grid's selection in a collection without dragging it there:
|
||||||
|
/// the target's id, and whether to take the images out of the collection
|
||||||
|
/// currently being shown.
|
||||||
|
callback library-file-in-collection(int, bool);
|
||||||
|
/// TRACES: FR-UI-2
|
||||||
|
/// Whether a tap in the grid selects rather than opens, and the button
|
||||||
|
/// that turns it on. The long press does the same thing without it.
|
||||||
|
in property <bool> library-select-mode: false;
|
||||||
|
callback library-toggle-select-mode();
|
||||||
|
/// TRACES: FR-CAT-7 | FR-UI-4
|
||||||
|
/// The row a press has held long enough to pick up, or `-1`.
|
||||||
|
///
|
||||||
|
/// Set by the same hold that turns on selection mode and cleared by the
|
||||||
|
/// grid when the press ends — `in-out` because both ends write it. See
|
||||||
|
/// `held-row` in `library.slint` for what it draws and what it stops.
|
||||||
|
in-out property <int> library-held-row: -1;
|
||||||
|
/// A press on a cell ended, so the long-press timer can be cancelled.
|
||||||
|
callback library-cell-press-ended();
|
||||||
|
// --- drag and drop ---
|
||||||
|
//
|
||||||
|
// Slint's own `DragArea`/`DropArea` carry this: pointer capture, the
|
||||||
|
// click-versus-drag threshold, arbitration against the grid's Flickable,
|
||||||
|
// and the image drawn under the cursor. So there is no badge to position
|
||||||
|
// and no hover state to mirror here — only the payload, which Rust builds
|
||||||
|
// from the selection, and the count the header shows.
|
||||||
|
/// The drag payload, built by Rust from the current selection.
|
||||||
|
pure callback library-drag-payload() -> data-transfer;
|
||||||
|
/// The bitmap that travels under the cursor: one thumbnail, or a fanned
|
||||||
|
/// stack where several images are being dragged. Composited in Rust.
|
||||||
|
in property <image> library-drag-image;
|
||||||
|
/// A drag began on a cell, so an unselected one can be promoted before the
|
||||||
|
/// payload is read.
|
||||||
|
callback library-drag-started(int);
|
||||||
|
callback library-drag-finished();
|
||||||
|
in property <int> library-selected-count: 0;
|
||||||
|
|
||||||
|
callback library-cell-pressed(int, bool, bool);
|
||||||
|
callback library-remove-from-collection();
|
||||||
|
// --- where the selection is filed (FR-CAT-7) ---------------------------
|
||||||
|
//
|
||||||
|
// `library-remove-from-collection` can only ever mean the collection the
|
||||||
|
// grid is scoped to — it is the only one that button can name. Opens the
|
||||||
|
// membership sheet, whose state (`Collections.membership-open` and
|
||||||
|
// `.membership-rows`) is on the sidebar's own global — only the library
|
||||||
|
// side knows the selection has changed enough to be worth recomputing
|
||||||
|
// the sheet against.
|
||||||
|
callback library-open-membership();
|
||||||
|
// The keyboard cursor: where the arrows are in the library, as an image
|
||||||
|
// ordinal. Rust owns it — clamping it needs the library's length, and
|
||||||
|
// moving it may have to swap the loaded window underneath.
|
||||||
|
in property <int> library-cursor: -1;
|
||||||
|
callback library-move-cursor(int, bool);
|
||||||
|
callback library-open-cursor();
|
||||||
|
}
|
||||||
|
|
||||||
// The photo roll: the grid's loaded window along the foot of the develop view.
|
// The photo roll: the grid's loaded window along the foot of the develop view.
|
||||||
//
|
//
|
||||||
// # Why this exists
|
// # Why this exists
|
||||||
|
|||||||
Reference in New Issue
Block a user