Rate under the pointer, filter a star range, and name Help as help
Benchmarks / CPU and I/O (per commit) (push) Successful in 1m53s
Benchmarks / Frame budget (on demand) (push) Skipped
Build and test / Desktop (Linux) (push) Failing after 2m50s
Build and test / Layer separation (push) Successful in 33s
Traceability / Requirement traces (push) Successful in 40s
🐳 Android image / Build and push (push) Successful in 4s
🐳 Windows image / Build and push (push) Successful in 1s
Build and test / android-image (push) Successful in 4s
Build and test / windows-image (push) Successful in 1s
Build and test / Android (aarch64) (push) Successful in 14m19s
Build and test / Windows (x86_64, cross) (push) Successful in 17m50s
Build and test / Publish the release (push) Skipped
Benchmarks / CPU and I/O (per commit) (push) Successful in 1m53s
Benchmarks / Frame budget (on demand) (push) Skipped
Build and test / Desktop (Linux) (push) Failing after 2m50s
Build and test / Layer separation (push) Successful in 33s
Traceability / Requirement traces (push) Successful in 40s
🐳 Android image / Build and push (push) Successful in 4s
🐳 Windows image / Build and push (push) Successful in 1s
Build and test / android-image (push) Successful in 4s
Build and test / windows-image (push) Successful in 1s
Build and test / Android (aarch64) (push) Successful in 14m19s
Build and test / Windows (x86_64, cross) (push) Successful in 17m50s
Build and test / Publish the release (push) Skipped
Rating keys in the grid follow darktable's rule: with the pointer over a photograph outside the selection, 0-5, P, X and U judge that photograph alone; over one inside it, the whole selection, as before; off the grid, the selection. The hover is cleared when the grid scrolls, so a key after a wheel turn cannot judge whatever used to be under the pointer. Holding F and tapping digits filters by stars: one digit for exactly that many, two for everything between them, F alone to show every rating again. The filter gains a ceiling to do it (`max_rating`, one BETWEEN in the query). The place record carries it, and a record from an older build reads as having none. The star chips light across a capped range and the bar says "2-3★ only" beside them. The grid also takes Ctrl+E and Ctrl+Shift+E for the selection, Ctrl+V to paste onto it and Ctrl+A to select all. The "Gestures" button is now "Help", its sheet "Controls and shortcuts", and F1 opens it.
This commit is contained in:
@@ -76,6 +76,11 @@ pub(super) const TRASH_ORDER: &str = "ORDER BY i.trashed_at DESC, i.source_ref A
|
||||
pub struct RatingFilter {
|
||||
/// Minimum stars. 0 means no star constraint.
|
||||
pub min_rating: u8,
|
||||
/// TRACES: FR-UI-5
|
||||
/// Maximum stars, for a range — `None` for no ceiling. Set by holding F
|
||||
/// and tapping two digits in the grid; the filter bar's chips set only
|
||||
/// the floor and clear this.
|
||||
pub max_rating: Option<u8>,
|
||||
/// Only images nothing has judged yet — neither starred nor flagged.
|
||||
/// This is what lets a culling session resume where it stopped.
|
||||
pub unjudged: bool,
|
||||
@@ -175,6 +180,7 @@ impl RatingFilter {
|
||||
/// Whether this narrows anything, so the caller can skip the join.
|
||||
pub fn is_unfiltered(&self) -> bool {
|
||||
self.min_rating == 0
|
||||
&& self.max_rating.is_none()
|
||||
&& !self.unjudged
|
||||
&& self.flag.is_none()
|
||||
&& !self.local_only
|
||||
@@ -216,13 +222,15 @@ impl RatingFilter {
|
||||
pub(super) fn sql(&self) -> String {
|
||||
let mut terms = Vec::new();
|
||||
|
||||
if self.min_rating > 0 {
|
||||
terms.push(format!(
|
||||
"coalesce((SELECT dv.rating FROM versions dv
|
||||
// One subquery for both ends of a range, not one per end: it is a
|
||||
// correlated read per image either way, and two would be two.
|
||||
let stars = "coalesce((SELECT dv.rating FROM versions dv
|
||||
WHERE dv.image_id = i.id AND dv.is_default = 1
|
||||
LIMIT 1), 0) >= {}",
|
||||
self.min_rating
|
||||
));
|
||||
LIMIT 1), 0)";
|
||||
match (self.min_rating, self.max_rating) {
|
||||
(0, None) => {}
|
||||
(min, None) => terms.push(format!("{stars} >= {min}")),
|
||||
(min, Some(max)) => terms.push(format!("{stars} BETWEEN {min} AND {max}")),
|
||||
}
|
||||
|
||||
// Integers this code owns, formatted straight in like the rating terms
|
||||
@@ -453,6 +461,35 @@ mod tests {
|
||||
assert_eq!(total_images_filtered(&catalog, &filter).unwrap(), 2);
|
||||
}
|
||||
|
||||
// --- star ranges (FR-UI-5) -------------------------------------------
|
||||
|
||||
#[test]
|
||||
fn a_star_range_keeps_both_ends_and_drops_the_rest() {
|
||||
// Hold F, tap 2 then 3: the twos and threes, not the fours above them
|
||||
// and not the unrated below.
|
||||
let catalog = with_images(6);
|
||||
let ids = image_ids(&catalog);
|
||||
for (id, stars) in ids.iter().zip([1u8, 2, 3, 4, 5]) {
|
||||
dr_catalog::rating::set_rating(catalog.connection(), *id, stars).unwrap();
|
||||
}
|
||||
|
||||
let range = RatingFilter {
|
||||
min_rating: 2,
|
||||
max_rating: Some(3),
|
||||
..Default::default()
|
||||
};
|
||||
assert_eq!(read_cells_all(&catalog, &range, 0, 120).unwrap().len(), 2);
|
||||
assert_eq!(total_images_filtered(&catalog, &range).unwrap(), 2);
|
||||
|
||||
// A ceiling with no floor reaches down to the unrated.
|
||||
let no_stars = RatingFilter {
|
||||
max_rating: Some(0),
|
||||
..Default::default()
|
||||
};
|
||||
assert!(!no_stars.is_unfiltered());
|
||||
assert_eq!(total_images_filtered(&catalog, &no_stars).unwrap(), 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn an_empty_cache_is_not_an_empty_library() {
|
||||
// The unfiltered grid must not depend on the cache table having rows —
|
||||
|
||||
Reference in New Issue
Block a user