From ea44aba110c0ee164c1de069d1056fa74804bb42 Mon Sep 17 00:00:00 2001 From: Duncan Tourolle Date: Sat, 26 Sep 2026 07:10:36 -0400 Subject: [PATCH] Put a scrollbar on the library grid The timeline beside the grid says *when* the view is. It does not say how far through the library the view is, and scrubbing it jumps by date. The bar gives the plain desktop answer: a thumb in proportion to one screen of the whole grid, dragged or paged by clicking the track. It reads the viewport that the rows are counted into, so it spans every photograph and not only the loaded window of cells. The Flickable keeps its id, its `interactive` arbitration with the hold-to-pick-up and its pinch and zoom catchers. It now fills a Rectangle that takes its place, and its stretch, in the grid's layout. --- docs/dev/traceability.md | 22 +- docs/gestures.md | 16 +- ui/dr-ui/ui/library.slint | 1830 +++++++++++++++++++------------------ 3 files changed, 943 insertions(+), 925 deletions(-) diff --git a/docs/dev/traceability.md b/docs/dev/traceability.md index 3651ea6..0120db1 100644 --- a/docs/dev/traceability.md +++ b/docs/dev/traceability.md @@ -45,9 +45,9 @@ _None._ | FR-CAT-2 | [`core/dr-catalog/src/lib.rs:1`](../../core/dr-catalog/src/lib.rs#L1), [`core/dr-catalog/src/schema.rs:1`](../../core/dr-catalog/src/schema.rs#L1) | | FR-CAT-3 | [`core/dr-catalog/src/jobs.rs:1`](../../core/dr-catalog/src/jobs.rs#L1), [`core/dr-catalog/src/walk.rs:66`](../../core/dr-catalog/src/walk.rs#L66), [`core/dr-sync/src/scan.rs:105`](../../core/dr-sync/src/scan.rs#L105), [`core/dr-thumbs/src/codec.rs:1`](../../core/dr-thumbs/src/codec.rs#L1), [`core/dr-thumbs/src/lib.rs:1`](../../core/dr-thumbs/src/lib.rs#L1), [`ui/dr-ui/src/derived_sync.rs:1`](../../ui/dr-ui/src/derived_sync.rs#L1), [`ui/dr-ui/src/import.rs:474`](../../ui/dr-ui/src/import.rs#L474), [`ui/dr-ui/src/import.rs:504`](../../ui/dr-ui/src/import.rs#L504), [`ui/dr-ui/src/library/sweep.rs:562`](../../ui/dr-ui/src/library/sweep.rs#L562), [`ui/dr-ui/src/library_ui/controller.rs:187`](../../ui/dr-ui/src/library_ui/controller.rs#L187), [`ui/dr-ui/src/library_ui/grid.rs:544`](../../ui/dr-ui/src/library_ui/grid.rs#L544), [`ui/dr-ui/src/library_ui/sync.rs:392`](../../ui/dr-ui/src/library_ui/sync.rs#L392), [`ui/dr-ui/src/repairs.rs:1`](../../ui/dr-ui/src/repairs.rs#L1), [`ui/dr-ui/src/repairs.rs:695`](../../ui/dr-ui/src/repairs.rs#L695), [`ui/dr-ui/ui/library.slint:488`](../../ui/dr-ui/ui/library.slint#L488), [`ui/dr-ui/ui/library.slint:776`](../../ui/dr-ui/ui/library.slint#L776), [`ui/dr-ui/ui/settings.slint:375`](../../ui/dr-ui/ui/settings.slint#L375), [`ui/dr-ui/ui/settings.slint:74`](../../ui/dr-ui/ui/settings.slint#L74) | | FR-CAT-4 | [`core/dr-catalog/src/lib.rs:1`](../../core/dr-catalog/src/lib.rs#L1), [`core/dr-catalog/src/query.rs:1`](../../core/dr-catalog/src/query.rs#L1), [`core/dr-catalog/src/schema.rs:591`](../../core/dr-catalog/src/schema.rs#L591), [`ui/dr-ui/src/library/filters.rs:39`](../../ui/dr-ui/src/library/filters.rs#L39), [`ui/dr-ui/src/library/mod.rs:1`](../../ui/dr-ui/src/library/mod.rs#L1), [`ui/dr-ui/src/library_ui/mod.rs:1`](../../ui/dr-ui/src/library_ui/mod.rs#L1), [`ui/dr-ui/ui/library.slint:488`](../../ui/dr-ui/ui/library.slint#L488) | -| FR-CAT-5 | [`core/dr-catalog/src/keywords.rs:1`](../../core/dr-catalog/src/keywords.rs#L1), [`core/dr-catalog/src/merge.rs:1`](../../core/dr-catalog/src/merge.rs#L1), [`core/dr-catalog/src/rating.rs:1`](../../core/dr-catalog/src/rating.rs#L1), [`core/dr-catalog/src/rating.rs:396`](../../core/dr-catalog/src/rating.rs#L396), [`core/dr-catalog/src/rating.rs:411`](../../core/dr-catalog/src/rating.rs#L411), [`core/dr-catalog/src/rating.rs:422`](../../core/dr-catalog/src/rating.rs#L422), [`core/dr-catalog/src/rating.rs:449`](../../core/dr-catalog/src/rating.rs#L449), [`core/dr-catalog/src/rating.rs:52`](../../core/dr-catalog/src/rating.rs#L52), [`core/dr-catalog/src/rating.rs:782`](../../core/dr-catalog/src/rating.rs#L782), [`core/dr-catalog/src/rating.rs:806`](../../core/dr-catalog/src/rating.rs#L806), [`core/dr-catalog/src/rating.rs:825`](../../core/dr-catalog/src/rating.rs#L825), [`core/dr-catalog/src/schema.rs:1152`](../../core/dr-catalog/src/schema.rs#L1152), [`core/dr-catalog/src/schema.rs:1739`](../../core/dr-catalog/src/schema.rs#L1739), [`core/dr-decode/src/lib.rs:307`](../../core/dr-decode/src/lib.rs#L307), [`core/dr-decode/src/lib.rs:313`](../../core/dr-decode/src/lib.rs#L313), [`core/dr-decode/src/lib.rs:323`](../../core/dr-decode/src/lib.rs#L323), [`core/dr-decode/src/lib.rs:450`](../../core/dr-decode/src/lib.rs#L450), [`core/dr-export/src/dng.rs:308`](../../core/dr-export/src/dng.rs#L308), [`core/dr-pipeline/src/sidecar.rs:1093`](../../core/dr-pipeline/src/sidecar.rs#L1093), [`core/dr-pipeline/src/sidecar.rs:174`](../../core/dr-pipeline/src/sidecar.rs#L174), [`core/dr-pipeline/src/sidecar.rs:193`](../../core/dr-pipeline/src/sidecar.rs#L193), [`core/dr-pipeline/src/sidecar.rs:2874`](../../core/dr-pipeline/src/sidecar.rs#L2874), [`core/dr-pipeline/src/sidecar.rs:2892`](../../core/dr-pipeline/src/sidecar.rs#L2892), [`core/dr-pipeline/src/sidecar.rs:590`](../../core/dr-pipeline/src/sidecar.rs#L590), [`ui/dr-ui/src/collections_ui/controller.rs:16`](../../ui/dr-ui/src/collections_ui/controller.rs#L16), [`ui/dr-ui/src/collections_ui/controller.rs:204`](../../ui/dr-ui/src/collections_ui/controller.rs#L204), [`ui/dr-ui/src/collections_ui/controller.rs:413`](../../ui/dr-ui/src/collections_ui/controller.rs#L413), [`ui/dr-ui/src/collections_ui/wiring_grid.rs:150`](../../ui/dr-ui/src/collections_ui/wiring_grid.rs#L150), [`ui/dr-ui/src/collections_ui/wiring_grid.rs:264`](../../ui/dr-ui/src/collections_ui/wiring_grid.rs#L264), [`ui/dr-ui/src/collections_ui/wiring_grid.rs:323`](../../ui/dr-ui/src/collections_ui/wiring_grid.rs#L323), [`ui/dr-ui/src/decoder_seam.rs:184`](../../ui/dr-ui/src/decoder_seam.rs#L184), [`ui/dr-ui/src/library/cells.rs:206`](../../ui/dr-ui/src/library/cells.rs#L206), [`ui/dr-ui/src/library/scan.rs:526`](../../ui/dr-ui/src/library/scan.rs#L526), [`ui/dr-ui/src/library/scan.rs:933`](../../ui/dr-ui/src/library/scan.rs#L933), [`ui/dr-ui/src/library_ui/controller.rs:802`](../../ui/dr-ui/src/library_ui/controller.rs#L802), [`ui/dr-ui/src/library_ui/ratings_keywords.rs:1144`](../../ui/dr-ui/src/library_ui/ratings_keywords.rs#L1144), [`ui/dr-ui/src/library_ui/ratings_keywords.rs:1168`](../../ui/dr-ui/src/library_ui/ratings_keywords.rs#L1168), [`ui/dr-ui/src/library_ui/ratings_keywords.rs:1185`](../../ui/dr-ui/src/library_ui/ratings_keywords.rs#L1185), [`ui/dr-ui/src/library_ui/ratings_keywords.rs:118`](../../ui/dr-ui/src/library_ui/ratings_keywords.rs#L118), [`ui/dr-ui/src/library_ui/ratings_keywords.rs:1391`](../../ui/dr-ui/src/library_ui/ratings_keywords.rs#L1391), [`ui/dr-ui/src/library_ui/ratings_keywords.rs:1402`](../../ui/dr-ui/src/library_ui/ratings_keywords.rs#L1402), [`ui/dr-ui/src/library_ui/ratings_keywords.rs:1415`](../../ui/dr-ui/src/library_ui/ratings_keywords.rs#L1415), [`ui/dr-ui/src/library_ui/ratings_keywords.rs:1430`](../../ui/dr-ui/src/library_ui/ratings_keywords.rs#L1430), [`ui/dr-ui/src/library_ui/ratings_keywords.rs:1439`](../../ui/dr-ui/src/library_ui/ratings_keywords.rs#L1439), [`ui/dr-ui/src/library_ui/ratings_keywords.rs:97`](../../ui/dr-ui/src/library_ui/ratings_keywords.rs#L97), [`ui/dr-ui/tests/status_is_not_colour_alone.rs:177`](../../ui/dr-ui/tests/status_is_not_colour_alone.rs#L177), [`ui/dr-ui/tests/status_is_not_colour_alone.rs:215`](../../ui/dr-ui/tests/status_is_not_colour_alone.rs#L215), [`ui/dr-ui/ui/app.slint:1627`](../../ui/dr-ui/ui/app.slint#L1627), [`ui/dr-ui/ui/app.slint:2756`](../../ui/dr-ui/ui/app.slint#L2756), [`ui/dr-ui/ui/develop.slint:142`](../../ui/dr-ui/ui/develop.slint#L142), [`ui/dr-ui/ui/develop.slint:48`](../../ui/dr-ui/ui/develop.slint#L48), [`ui/dr-ui/ui/labels.slint:19`](../../ui/dr-ui/ui/labels.slint#L19), [`ui/dr-ui/ui/labels.slint:1`](../../ui/dr-ui/ui/labels.slint#L1), [`ui/dr-ui/ui/labels.slint:40`](../../ui/dr-ui/ui/labels.slint#L40), [`ui/dr-ui/ui/labels.slint:74`](../../ui/dr-ui/ui/labels.slint#L74), [`ui/dr-ui/ui/library.slint:1609`](../../ui/dr-ui/ui/library.slint#L1609), [`ui/dr-ui/ui/library.slint:1612`](../../ui/dr-ui/ui/library.slint#L1612), [`ui/dr-ui/ui/library.slint:1763`](../../ui/dr-ui/ui/library.slint#L1763), [`ui/dr-ui/ui/library.slint:1787`](../../ui/dr-ui/ui/library.slint#L1787), [`ui/dr-ui/ui/library.slint:1844`](../../ui/dr-ui/ui/library.slint#L1844), [`ui/dr-ui/ui/library.slint:21`](../../ui/dr-ui/ui/library.slint#L21), [`ui/dr-ui/ui/library.slint:3196`](../../ui/dr-ui/ui/library.slint#L3196), [`ui/dr-ui/ui/library.slint:3203`](../../ui/dr-ui/ui/library.slint#L3203), [`ui/dr-ui/ui/library.slint:3223`](../../ui/dr-ui/ui/library.slint#L3223), [`ui/dr-ui/ui/library.slint:3312`](../../ui/dr-ui/ui/library.slint#L3312), [`ui/dr-ui/ui/library.slint:3924`](../../ui/dr-ui/ui/library.slint#L3924), [`ui/dr-ui/ui/library.slint:467`](../../ui/dr-ui/ui/library.slint#L467), [`ui/dr-ui/ui/library.slint:488`](../../ui/dr-ui/ui/library.slint#L488), [`ui/dr-ui/ui/library.slint:5036`](../../ui/dr-ui/ui/library.slint#L5036), [`ui/dr-ui/ui/library.slint:5052`](../../ui/dr-ui/ui/library.slint#L5052), [`ui/dr-ui/ui/library.slint:510`](../../ui/dr-ui/ui/library.slint#L510), [`ui/dr-ui/ui/library.slint:616`](../../ui/dr-ui/ui/library.slint#L616), [`ui/dr-ui/ui/library.slint:660`](../../ui/dr-ui/ui/library.slint#L660), [`ui/dr-ui/ui/library.slint:666`](../../ui/dr-ui/ui/library.slint#L666), [`ui/dr-ui/ui/library.slint:670`](../../ui/dr-ui/ui/library.slint#L670) | -| FR-CAT-6 | [`core/dr-catalog/src/collections.rs:1`](../../core/dr-catalog/src/collections.rs#L1), [`core/dr-catalog/src/keywords.rs:1`](../../core/dr-catalog/src/keywords.rs#L1), [`core/dr-catalog/src/lib.rs:1`](../../core/dr-catalog/src/lib.rs#L1), [`core/dr-catalog/src/query.rs:1`](../../core/dr-catalog/src/query.rs#L1), [`core/dr-catalog/src/rating.rs:1`](../../core/dr-catalog/src/rating.rs#L1), [`core/dr-catalog/src/rating.rs:449`](../../core/dr-catalog/src/rating.rs#L449), [`core/dr-catalog/src/rating.rs:835`](../../core/dr-catalog/src/rating.rs#L835), [`core/dr-catalog/src/schema.rs:1152`](../../core/dr-catalog/src/schema.rs#L1152), [`core/dr-types/src/place.rs:138`](../../core/dr-types/src/place.rs#L138), [`core/dr-types/src/place.rs:1`](../../core/dr-types/src/place.rs#L1), [`core/dr-types/src/selector.rs:1`](../../core/dr-types/src/selector.rs#L1), [`core/dr-types/src/settings.rs:64`](../../core/dr-types/src/settings.rs#L64), [`core/dr-types/src/time.rs:67`](../../core/dr-types/src/time.rs#L67), [`core/dr-types/src/time.rs:90`](../../core/dr-types/src/time.rs#L90), [`ui/dr-ui/src/library/filters.rs:156`](../../ui/dr-ui/src/library/filters.rs#L156), [`ui/dr-ui/src/library/filters.rs:357`](../../ui/dr-ui/src/library/filters.rs#L357), [`ui/dr-ui/src/library/filters.rs:442`](../../ui/dr-ui/src/library/filters.rs#L442), [`ui/dr-ui/src/library/filters.rs:64`](../../ui/dr-ui/src/library/filters.rs#L64), [`ui/dr-ui/src/library/spans.rs:253`](../../ui/dr-ui/src/library/spans.rs#L253), [`ui/dr-ui/src/library_ui/controller.rs:407`](../../ui/dr-ui/src/library_ui/controller.rs#L407), [`ui/dr-ui/src/library_ui/controller.rs:518`](../../ui/dr-ui/src/library_ui/controller.rs#L518), [`ui/dr-ui/src/library_ui/filter_bar.rs:256`](../../ui/dr-ui/src/library_ui/filter_bar.rs#L256), [`ui/dr-ui/src/library_ui/filter_bar.rs:351`](../../ui/dr-ui/src/library_ui/filter_bar.rs#L351), [`ui/dr-ui/src/library_ui/filter_bar.rs:406`](../../ui/dr-ui/src/library_ui/filter_bar.rs#L406), [`ui/dr-ui/src/library_ui/filter_bar.rs:840`](../../ui/dr-ui/src/library_ui/filter_bar.rs#L840), [`ui/dr-ui/src/library_ui/ratings_keywords.rs:85`](../../ui/dr-ui/src/library_ui/ratings_keywords.rs#L85), [`ui/dr-ui/ui/app.slint:621`](../../ui/dr-ui/ui/app.slint#L621), [`ui/dr-ui/ui/library.slint:113`](../../ui/dr-ui/ui/library.slint#L113), [`ui/dr-ui/ui/library.slint:1931`](../../ui/dr-ui/ui/library.slint#L1931), [`ui/dr-ui/ui/library.slint:1968`](../../ui/dr-ui/ui/library.slint#L1968), [`ui/dr-ui/ui/library.slint:2697`](../../ui/dr-ui/ui/library.slint#L2697), [`ui/dr-ui/ui/library.slint:3223`](../../ui/dr-ui/ui/library.slint#L3223), [`ui/dr-ui/ui/library.slint:488`](../../ui/dr-ui/ui/library.slint#L488), [`ui/dr-ui/ui/library.slint:5036`](../../ui/dr-ui/ui/library.slint#L5036), [`ui/dr-ui/ui/library.slint:616`](../../ui/dr-ui/ui/library.slint#L616), [`ui/dr-ui/ui/library.slint:674`](../../ui/dr-ui/ui/library.slint#L674), [`ui/dr-ui/ui/library.slint:696`](../../ui/dr-ui/ui/library.slint#L696), [`ui/dr-ui/ui/settings.slint:202`](../../ui/dr-ui/ui/settings.slint#L202), [`ui/dr-ui/ui/widgets.slint:271`](../../ui/dr-ui/ui/widgets.slint#L271) | -| FR-CAT-7 | [`core/dr-catalog/src/collections.rs:1`](../../core/dr-catalog/src/collections.rs#L1), [`core/dr-catalog/src/merge.rs:1`](../../core/dr-catalog/src/merge.rs#L1), [`core/dr-catalog/src/sync.rs:1`](../../core/dr-catalog/src/sync.rs#L1), [`core/dr-types/src/selector.rs:1`](../../core/dr-types/src/selector.rs#L1), [`ui/dr-ui/src/collections_ui/controller.rs:258`](../../ui/dr-ui/src/collections_ui/controller.rs#L258), [`ui/dr-ui/src/collections_ui/controller.rs:268`](../../ui/dr-ui/src/collections_ui/controller.rs#L268), [`ui/dr-ui/src/collections_ui/controller.rs:291`](../../ui/dr-ui/src/collections_ui/controller.rs#L291), [`ui/dr-ui/src/collections_ui/controller.rs:312`](../../ui/dr-ui/src/collections_ui/controller.rs#L312), [`ui/dr-ui/src/collections_ui/controller.rs:46`](../../ui/dr-ui/src/collections_ui/controller.rs#L46), [`ui/dr-ui/src/collections_ui/drag.rs:274`](../../ui/dr-ui/src/collections_ui/drag.rs#L274), [`ui/dr-ui/src/collections_ui/mod.rs:1`](../../ui/dr-ui/src/collections_ui/mod.rs#L1), [`ui/dr-ui/src/collections_ui/press.rs:258`](../../ui/dr-ui/src/collections_ui/press.rs#L258), [`ui/dr-ui/src/collections_ui/press.rs:490`](../../ui/dr-ui/src/collections_ui/press.rs#L490), [`ui/dr-ui/src/collections_ui/press.rs:54`](../../ui/dr-ui/src/collections_ui/press.rs#L54), [`ui/dr-ui/src/collections_ui/rename_menu.rs:196`](../../ui/dr-ui/src/collections_ui/rename_menu.rs#L196), [`ui/dr-ui/src/collections_ui/rename_menu.rs:21`](../../ui/dr-ui/src/collections_ui/rename_menu.rs#L21), [`ui/dr-ui/src/collections_ui/tree_sync.rs:246`](../../ui/dr-ui/src/collections_ui/tree_sync.rs#L246), [`ui/dr-ui/src/collections_ui/tree_sync.rs:317`](../../ui/dr-ui/src/collections_ui/tree_sync.rs#L317), [`ui/dr-ui/src/collections_ui/tree_sync.rs:339`](../../ui/dr-ui/src/collections_ui/tree_sync.rs#L339), [`ui/dr-ui/src/collections_ui/wiring_grid.rs:121`](../../ui/dr-ui/src/collections_ui/wiring_grid.rs#L121), [`ui/dr-ui/src/collections_ui/wiring_grid.rs:171`](../../ui/dr-ui/src/collections_ui/wiring_grid.rs#L171), [`ui/dr-ui/src/collections_ui/wiring_grid.rs:444`](../../ui/dr-ui/src/collections_ui/wiring_grid.rs#L444), [`ui/dr-ui/src/collections_ui/wiring_grid.rs:522`](../../ui/dr-ui/src/collections_ui/wiring_grid.rs#L522), [`ui/dr-ui/src/collections_ui/wiring_grid.rs:586`](../../ui/dr-ui/src/collections_ui/wiring_grid.rs#L586), [`ui/dr-ui/src/collections_ui/wiring_tree.rs:287`](../../ui/dr-ui/src/collections_ui/wiring_tree.rs#L287), [`ui/dr-ui/src/collections_ui/wiring_tree.rs:344`](../../ui/dr-ui/src/collections_ui/wiring_tree.rs#L344), [`ui/dr-ui/src/collections_ui/wiring_tree.rs:394`](../../ui/dr-ui/src/collections_ui/wiring_tree.rs#L394), [`ui/dr-ui/src/collections_ui/wiring_tree.rs:517`](../../ui/dr-ui/src/collections_ui/wiring_tree.rs#L517), [`ui/dr-ui/src/collections_ui/wiring_tree.rs:581`](../../ui/dr-ui/src/collections_ui/wiring_tree.rs#L581), [`ui/dr-ui/src/derived_sync.rs:1`](../../ui/dr-ui/src/derived_sync.rs#L1), [`ui/dr-ui/src/library/spans.rs:139`](../../ui/dr-ui/src/library/spans.rs#L139), [`ui/dr-ui/src/library/spans.rs:58`](../../ui/dr-ui/src/library/spans.rs#L58), [`ui/dr-ui/src/library/spans.rs:84`](../../ui/dr-ui/src/library/spans.rs#L84), [`ui/dr-ui/src/library_ui/sync.rs:235`](../../ui/dr-ui/src/library_ui/sync.rs#L235), [`ui/dr-ui/src/library_ui/timeline.rs:513`](../../ui/dr-ui/src/library_ui/timeline.rs#L513), [`ui/dr-ui/src/library_ui/window.rs:376`](../../ui/dr-ui/src/library_ui/window.rs#L376), [`ui/dr-ui/ui/app.slint:3454`](../../ui/dr-ui/ui/app.slint#L3454), [`ui/dr-ui/ui/collections.slint:1269`](../../ui/dr-ui/ui/collections.slint#L1269), [`ui/dr-ui/ui/collections.slint:4`](../../ui/dr-ui/ui/collections.slint#L4), [`ui/dr-ui/ui/collections.slint:615`](../../ui/dr-ui/ui/collections.slint#L615), [`ui/dr-ui/ui/library.slint:1362`](../../ui/dr-ui/ui/library.slint#L1362), [`ui/dr-ui/ui/library.slint:1729`](../../ui/dr-ui/ui/library.slint#L1729), [`ui/dr-ui/ui/library.slint:3675`](../../ui/dr-ui/ui/library.slint#L3675), [`ui/dr-ui/ui/library.slint:488`](../../ui/dr-ui/ui/library.slint#L488), [`ui/dr-ui/ui/library.slint:5014`](../../ui/dr-ui/ui/library.slint#L5014), [`ui/dr-ui/ui/library.slint:513`](../../ui/dr-ui/ui/library.slint#L513), [`ui/dr-ui/ui/library.slint:5156`](../../ui/dr-ui/ui/library.slint#L5156), [`ui/dr-ui/ui/library.slint:562`](../../ui/dr-ui/ui/library.slint#L562), [`ui/dr-ui/ui/library.slint:572`](../../ui/dr-ui/ui/library.slint#L572) | +| FR-CAT-5 | [`core/dr-catalog/src/keywords.rs:1`](../../core/dr-catalog/src/keywords.rs#L1), [`core/dr-catalog/src/merge.rs:1`](../../core/dr-catalog/src/merge.rs#L1), [`core/dr-catalog/src/rating.rs:1`](../../core/dr-catalog/src/rating.rs#L1), [`core/dr-catalog/src/rating.rs:396`](../../core/dr-catalog/src/rating.rs#L396), [`core/dr-catalog/src/rating.rs:411`](../../core/dr-catalog/src/rating.rs#L411), [`core/dr-catalog/src/rating.rs:422`](../../core/dr-catalog/src/rating.rs#L422), [`core/dr-catalog/src/rating.rs:449`](../../core/dr-catalog/src/rating.rs#L449), [`core/dr-catalog/src/rating.rs:52`](../../core/dr-catalog/src/rating.rs#L52), [`core/dr-catalog/src/rating.rs:782`](../../core/dr-catalog/src/rating.rs#L782), [`core/dr-catalog/src/rating.rs:806`](../../core/dr-catalog/src/rating.rs#L806), [`core/dr-catalog/src/rating.rs:825`](../../core/dr-catalog/src/rating.rs#L825), [`core/dr-catalog/src/schema.rs:1152`](../../core/dr-catalog/src/schema.rs#L1152), [`core/dr-catalog/src/schema.rs:1739`](../../core/dr-catalog/src/schema.rs#L1739), [`core/dr-decode/src/lib.rs:307`](../../core/dr-decode/src/lib.rs#L307), [`core/dr-decode/src/lib.rs:313`](../../core/dr-decode/src/lib.rs#L313), [`core/dr-decode/src/lib.rs:323`](../../core/dr-decode/src/lib.rs#L323), [`core/dr-decode/src/lib.rs:450`](../../core/dr-decode/src/lib.rs#L450), [`core/dr-export/src/dng.rs:308`](../../core/dr-export/src/dng.rs#L308), [`core/dr-pipeline/src/sidecar.rs:1093`](../../core/dr-pipeline/src/sidecar.rs#L1093), [`core/dr-pipeline/src/sidecar.rs:174`](../../core/dr-pipeline/src/sidecar.rs#L174), [`core/dr-pipeline/src/sidecar.rs:193`](../../core/dr-pipeline/src/sidecar.rs#L193), [`core/dr-pipeline/src/sidecar.rs:2874`](../../core/dr-pipeline/src/sidecar.rs#L2874), [`core/dr-pipeline/src/sidecar.rs:2892`](../../core/dr-pipeline/src/sidecar.rs#L2892), [`core/dr-pipeline/src/sidecar.rs:590`](../../core/dr-pipeline/src/sidecar.rs#L590), [`ui/dr-ui/src/collections_ui/controller.rs:16`](../../ui/dr-ui/src/collections_ui/controller.rs#L16), [`ui/dr-ui/src/collections_ui/controller.rs:204`](../../ui/dr-ui/src/collections_ui/controller.rs#L204), [`ui/dr-ui/src/collections_ui/controller.rs:413`](../../ui/dr-ui/src/collections_ui/controller.rs#L413), [`ui/dr-ui/src/collections_ui/wiring_grid.rs:150`](../../ui/dr-ui/src/collections_ui/wiring_grid.rs#L150), [`ui/dr-ui/src/collections_ui/wiring_grid.rs:264`](../../ui/dr-ui/src/collections_ui/wiring_grid.rs#L264), [`ui/dr-ui/src/collections_ui/wiring_grid.rs:323`](../../ui/dr-ui/src/collections_ui/wiring_grid.rs#L323), [`ui/dr-ui/src/decoder_seam.rs:184`](../../ui/dr-ui/src/decoder_seam.rs#L184), [`ui/dr-ui/src/library/cells.rs:206`](../../ui/dr-ui/src/library/cells.rs#L206), [`ui/dr-ui/src/library/scan.rs:526`](../../ui/dr-ui/src/library/scan.rs#L526), [`ui/dr-ui/src/library/scan.rs:933`](../../ui/dr-ui/src/library/scan.rs#L933), [`ui/dr-ui/src/library_ui/controller.rs:802`](../../ui/dr-ui/src/library_ui/controller.rs#L802), [`ui/dr-ui/src/library_ui/ratings_keywords.rs:1144`](../../ui/dr-ui/src/library_ui/ratings_keywords.rs#L1144), [`ui/dr-ui/src/library_ui/ratings_keywords.rs:1168`](../../ui/dr-ui/src/library_ui/ratings_keywords.rs#L1168), [`ui/dr-ui/src/library_ui/ratings_keywords.rs:1185`](../../ui/dr-ui/src/library_ui/ratings_keywords.rs#L1185), [`ui/dr-ui/src/library_ui/ratings_keywords.rs:118`](../../ui/dr-ui/src/library_ui/ratings_keywords.rs#L118), [`ui/dr-ui/src/library_ui/ratings_keywords.rs:1391`](../../ui/dr-ui/src/library_ui/ratings_keywords.rs#L1391), [`ui/dr-ui/src/library_ui/ratings_keywords.rs:1402`](../../ui/dr-ui/src/library_ui/ratings_keywords.rs#L1402), [`ui/dr-ui/src/library_ui/ratings_keywords.rs:1415`](../../ui/dr-ui/src/library_ui/ratings_keywords.rs#L1415), [`ui/dr-ui/src/library_ui/ratings_keywords.rs:1430`](../../ui/dr-ui/src/library_ui/ratings_keywords.rs#L1430), [`ui/dr-ui/src/library_ui/ratings_keywords.rs:1439`](../../ui/dr-ui/src/library_ui/ratings_keywords.rs#L1439), [`ui/dr-ui/src/library_ui/ratings_keywords.rs:97`](../../ui/dr-ui/src/library_ui/ratings_keywords.rs#L97), [`ui/dr-ui/tests/status_is_not_colour_alone.rs:177`](../../ui/dr-ui/tests/status_is_not_colour_alone.rs#L177), [`ui/dr-ui/tests/status_is_not_colour_alone.rs:215`](../../ui/dr-ui/tests/status_is_not_colour_alone.rs#L215), [`ui/dr-ui/ui/app.slint:1627`](../../ui/dr-ui/ui/app.slint#L1627), [`ui/dr-ui/ui/app.slint:2756`](../../ui/dr-ui/ui/app.slint#L2756), [`ui/dr-ui/ui/develop.slint:142`](../../ui/dr-ui/ui/develop.slint#L142), [`ui/dr-ui/ui/develop.slint:48`](../../ui/dr-ui/ui/develop.slint#L48), [`ui/dr-ui/ui/labels.slint:19`](../../ui/dr-ui/ui/labels.slint#L19), [`ui/dr-ui/ui/labels.slint:1`](../../ui/dr-ui/ui/labels.slint#L1), [`ui/dr-ui/ui/labels.slint:40`](../../ui/dr-ui/ui/labels.slint#L40), [`ui/dr-ui/ui/labels.slint:74`](../../ui/dr-ui/ui/labels.slint#L74), [`ui/dr-ui/ui/library.slint:1609`](../../ui/dr-ui/ui/library.slint#L1609), [`ui/dr-ui/ui/library.slint:1612`](../../ui/dr-ui/ui/library.slint#L1612), [`ui/dr-ui/ui/library.slint:1763`](../../ui/dr-ui/ui/library.slint#L1763), [`ui/dr-ui/ui/library.slint:1787`](../../ui/dr-ui/ui/library.slint#L1787), [`ui/dr-ui/ui/library.slint:1844`](../../ui/dr-ui/ui/library.slint#L1844), [`ui/dr-ui/ui/library.slint:21`](../../ui/dr-ui/ui/library.slint#L21), [`ui/dr-ui/ui/library.slint:3196`](../../ui/dr-ui/ui/library.slint#L3196), [`ui/dr-ui/ui/library.slint:3203`](../../ui/dr-ui/ui/library.slint#L3203), [`ui/dr-ui/ui/library.slint:3223`](../../ui/dr-ui/ui/library.slint#L3223), [`ui/dr-ui/ui/library.slint:3312`](../../ui/dr-ui/ui/library.slint#L3312), [`ui/dr-ui/ui/library.slint:3928`](../../ui/dr-ui/ui/library.slint#L3928), [`ui/dr-ui/ui/library.slint:467`](../../ui/dr-ui/ui/library.slint#L467), [`ui/dr-ui/ui/library.slint:488`](../../ui/dr-ui/ui/library.slint#L488), [`ui/dr-ui/ui/library.slint:5054`](../../ui/dr-ui/ui/library.slint#L5054), [`ui/dr-ui/ui/library.slint:5070`](../../ui/dr-ui/ui/library.slint#L5070), [`ui/dr-ui/ui/library.slint:510`](../../ui/dr-ui/ui/library.slint#L510), [`ui/dr-ui/ui/library.slint:616`](../../ui/dr-ui/ui/library.slint#L616), [`ui/dr-ui/ui/library.slint:660`](../../ui/dr-ui/ui/library.slint#L660), [`ui/dr-ui/ui/library.slint:666`](../../ui/dr-ui/ui/library.slint#L666), [`ui/dr-ui/ui/library.slint:670`](../../ui/dr-ui/ui/library.slint#L670) | +| FR-CAT-6 | [`core/dr-catalog/src/collections.rs:1`](../../core/dr-catalog/src/collections.rs#L1), [`core/dr-catalog/src/keywords.rs:1`](../../core/dr-catalog/src/keywords.rs#L1), [`core/dr-catalog/src/lib.rs:1`](../../core/dr-catalog/src/lib.rs#L1), [`core/dr-catalog/src/query.rs:1`](../../core/dr-catalog/src/query.rs#L1), [`core/dr-catalog/src/rating.rs:1`](../../core/dr-catalog/src/rating.rs#L1), [`core/dr-catalog/src/rating.rs:449`](../../core/dr-catalog/src/rating.rs#L449), [`core/dr-catalog/src/rating.rs:835`](../../core/dr-catalog/src/rating.rs#L835), [`core/dr-catalog/src/schema.rs:1152`](../../core/dr-catalog/src/schema.rs#L1152), [`core/dr-types/src/place.rs:138`](../../core/dr-types/src/place.rs#L138), [`core/dr-types/src/place.rs:1`](../../core/dr-types/src/place.rs#L1), [`core/dr-types/src/selector.rs:1`](../../core/dr-types/src/selector.rs#L1), [`core/dr-types/src/settings.rs:64`](../../core/dr-types/src/settings.rs#L64), [`core/dr-types/src/time.rs:67`](../../core/dr-types/src/time.rs#L67), [`core/dr-types/src/time.rs:90`](../../core/dr-types/src/time.rs#L90), [`ui/dr-ui/src/library/filters.rs:156`](../../ui/dr-ui/src/library/filters.rs#L156), [`ui/dr-ui/src/library/filters.rs:357`](../../ui/dr-ui/src/library/filters.rs#L357), [`ui/dr-ui/src/library/filters.rs:442`](../../ui/dr-ui/src/library/filters.rs#L442), [`ui/dr-ui/src/library/filters.rs:64`](../../ui/dr-ui/src/library/filters.rs#L64), [`ui/dr-ui/src/library/spans.rs:253`](../../ui/dr-ui/src/library/spans.rs#L253), [`ui/dr-ui/src/library_ui/controller.rs:407`](../../ui/dr-ui/src/library_ui/controller.rs#L407), [`ui/dr-ui/src/library_ui/controller.rs:518`](../../ui/dr-ui/src/library_ui/controller.rs#L518), [`ui/dr-ui/src/library_ui/filter_bar.rs:256`](../../ui/dr-ui/src/library_ui/filter_bar.rs#L256), [`ui/dr-ui/src/library_ui/filter_bar.rs:351`](../../ui/dr-ui/src/library_ui/filter_bar.rs#L351), [`ui/dr-ui/src/library_ui/filter_bar.rs:406`](../../ui/dr-ui/src/library_ui/filter_bar.rs#L406), [`ui/dr-ui/src/library_ui/filter_bar.rs:840`](../../ui/dr-ui/src/library_ui/filter_bar.rs#L840), [`ui/dr-ui/src/library_ui/ratings_keywords.rs:85`](../../ui/dr-ui/src/library_ui/ratings_keywords.rs#L85), [`ui/dr-ui/ui/app.slint:621`](../../ui/dr-ui/ui/app.slint#L621), [`ui/dr-ui/ui/library.slint:113`](../../ui/dr-ui/ui/library.slint#L113), [`ui/dr-ui/ui/library.slint:1931`](../../ui/dr-ui/ui/library.slint#L1931), [`ui/dr-ui/ui/library.slint:1968`](../../ui/dr-ui/ui/library.slint#L1968), [`ui/dr-ui/ui/library.slint:2697`](../../ui/dr-ui/ui/library.slint#L2697), [`ui/dr-ui/ui/library.slint:3223`](../../ui/dr-ui/ui/library.slint#L3223), [`ui/dr-ui/ui/library.slint:488`](../../ui/dr-ui/ui/library.slint#L488), [`ui/dr-ui/ui/library.slint:5054`](../../ui/dr-ui/ui/library.slint#L5054), [`ui/dr-ui/ui/library.slint:616`](../../ui/dr-ui/ui/library.slint#L616), [`ui/dr-ui/ui/library.slint:674`](../../ui/dr-ui/ui/library.slint#L674), [`ui/dr-ui/ui/library.slint:696`](../../ui/dr-ui/ui/library.slint#L696), [`ui/dr-ui/ui/settings.slint:202`](../../ui/dr-ui/ui/settings.slint#L202), [`ui/dr-ui/ui/widgets.slint:271`](../../ui/dr-ui/ui/widgets.slint#L271) | +| FR-CAT-7 | [`core/dr-catalog/src/collections.rs:1`](../../core/dr-catalog/src/collections.rs#L1), [`core/dr-catalog/src/merge.rs:1`](../../core/dr-catalog/src/merge.rs#L1), [`core/dr-catalog/src/sync.rs:1`](../../core/dr-catalog/src/sync.rs#L1), [`core/dr-types/src/selector.rs:1`](../../core/dr-types/src/selector.rs#L1), [`ui/dr-ui/src/collections_ui/controller.rs:258`](../../ui/dr-ui/src/collections_ui/controller.rs#L258), [`ui/dr-ui/src/collections_ui/controller.rs:268`](../../ui/dr-ui/src/collections_ui/controller.rs#L268), [`ui/dr-ui/src/collections_ui/controller.rs:291`](../../ui/dr-ui/src/collections_ui/controller.rs#L291), [`ui/dr-ui/src/collections_ui/controller.rs:312`](../../ui/dr-ui/src/collections_ui/controller.rs#L312), [`ui/dr-ui/src/collections_ui/controller.rs:46`](../../ui/dr-ui/src/collections_ui/controller.rs#L46), [`ui/dr-ui/src/collections_ui/drag.rs:274`](../../ui/dr-ui/src/collections_ui/drag.rs#L274), [`ui/dr-ui/src/collections_ui/mod.rs:1`](../../ui/dr-ui/src/collections_ui/mod.rs#L1), [`ui/dr-ui/src/collections_ui/press.rs:258`](../../ui/dr-ui/src/collections_ui/press.rs#L258), [`ui/dr-ui/src/collections_ui/press.rs:490`](../../ui/dr-ui/src/collections_ui/press.rs#L490), [`ui/dr-ui/src/collections_ui/press.rs:54`](../../ui/dr-ui/src/collections_ui/press.rs#L54), [`ui/dr-ui/src/collections_ui/rename_menu.rs:196`](../../ui/dr-ui/src/collections_ui/rename_menu.rs#L196), [`ui/dr-ui/src/collections_ui/rename_menu.rs:21`](../../ui/dr-ui/src/collections_ui/rename_menu.rs#L21), [`ui/dr-ui/src/collections_ui/tree_sync.rs:246`](../../ui/dr-ui/src/collections_ui/tree_sync.rs#L246), [`ui/dr-ui/src/collections_ui/tree_sync.rs:317`](../../ui/dr-ui/src/collections_ui/tree_sync.rs#L317), [`ui/dr-ui/src/collections_ui/tree_sync.rs:339`](../../ui/dr-ui/src/collections_ui/tree_sync.rs#L339), [`ui/dr-ui/src/collections_ui/wiring_grid.rs:121`](../../ui/dr-ui/src/collections_ui/wiring_grid.rs#L121), [`ui/dr-ui/src/collections_ui/wiring_grid.rs:171`](../../ui/dr-ui/src/collections_ui/wiring_grid.rs#L171), [`ui/dr-ui/src/collections_ui/wiring_grid.rs:444`](../../ui/dr-ui/src/collections_ui/wiring_grid.rs#L444), [`ui/dr-ui/src/collections_ui/wiring_grid.rs:522`](../../ui/dr-ui/src/collections_ui/wiring_grid.rs#L522), [`ui/dr-ui/src/collections_ui/wiring_grid.rs:586`](../../ui/dr-ui/src/collections_ui/wiring_grid.rs#L586), [`ui/dr-ui/src/collections_ui/wiring_tree.rs:287`](../../ui/dr-ui/src/collections_ui/wiring_tree.rs#L287), [`ui/dr-ui/src/collections_ui/wiring_tree.rs:344`](../../ui/dr-ui/src/collections_ui/wiring_tree.rs#L344), [`ui/dr-ui/src/collections_ui/wiring_tree.rs:394`](../../ui/dr-ui/src/collections_ui/wiring_tree.rs#L394), [`ui/dr-ui/src/collections_ui/wiring_tree.rs:517`](../../ui/dr-ui/src/collections_ui/wiring_tree.rs#L517), [`ui/dr-ui/src/collections_ui/wiring_tree.rs:581`](../../ui/dr-ui/src/collections_ui/wiring_tree.rs#L581), [`ui/dr-ui/src/derived_sync.rs:1`](../../ui/dr-ui/src/derived_sync.rs#L1), [`ui/dr-ui/src/library/spans.rs:139`](../../ui/dr-ui/src/library/spans.rs#L139), [`ui/dr-ui/src/library/spans.rs:58`](../../ui/dr-ui/src/library/spans.rs#L58), [`ui/dr-ui/src/library/spans.rs:84`](../../ui/dr-ui/src/library/spans.rs#L84), [`ui/dr-ui/src/library_ui/sync.rs:235`](../../ui/dr-ui/src/library_ui/sync.rs#L235), [`ui/dr-ui/src/library_ui/timeline.rs:513`](../../ui/dr-ui/src/library_ui/timeline.rs#L513), [`ui/dr-ui/src/library_ui/window.rs:376`](../../ui/dr-ui/src/library_ui/window.rs#L376), [`ui/dr-ui/ui/app.slint:3454`](../../ui/dr-ui/ui/app.slint#L3454), [`ui/dr-ui/ui/collections.slint:1269`](../../ui/dr-ui/ui/collections.slint#L1269), [`ui/dr-ui/ui/collections.slint:4`](../../ui/dr-ui/ui/collections.slint#L4), [`ui/dr-ui/ui/collections.slint:615`](../../ui/dr-ui/ui/collections.slint#L615), [`ui/dr-ui/ui/library.slint:1362`](../../ui/dr-ui/ui/library.slint#L1362), [`ui/dr-ui/ui/library.slint:1729`](../../ui/dr-ui/ui/library.slint#L1729), [`ui/dr-ui/ui/library.slint:3679`](../../ui/dr-ui/ui/library.slint#L3679), [`ui/dr-ui/ui/library.slint:488`](../../ui/dr-ui/ui/library.slint#L488), [`ui/dr-ui/ui/library.slint:5032`](../../ui/dr-ui/ui/library.slint#L5032), [`ui/dr-ui/ui/library.slint:513`](../../ui/dr-ui/ui/library.slint#L513), [`ui/dr-ui/ui/library.slint:5174`](../../ui/dr-ui/ui/library.slint#L5174), [`ui/dr-ui/ui/library.slint:562`](../../ui/dr-ui/ui/library.slint#L562), [`ui/dr-ui/ui/library.slint:572`](../../ui/dr-ui/ui/library.slint#L572) | | FR-CAT-8 | [`core/dr-catalog/src/schema.rs:767`](../../core/dr-catalog/src/schema.rs#L767), [`core/dr-pipeline/src/coverage.rs:1`](../../core/dr-pipeline/src/coverage.rs#L1), [`core/dr-pipeline/src/graph.rs:587`](../../core/dr-pipeline/src/graph.rs#L587), [`core/dr-pipeline/src/graph.rs:640`](../../core/dr-pipeline/src/graph.rs#L640), [`core/dr-pipeline/src/ops/curve.rs:137`](../../core/dr-pipeline/src/ops/curve.rs#L137), [`core/dr-pipeline/src/ops/curve.rs:656`](../../core/dr-pipeline/src/ops/curve.rs#L656), [`core/dr-pipeline/src/sidecar.rs:1433`](../../core/dr-pipeline/src/sidecar.rs#L1433), [`core/dr-pipeline/src/sidecar.rs:2273`](../../core/dr-pipeline/src/sidecar.rs#L2273), [`core/dr-pipeline/src/sidecar.rs:99`](../../core/dr-pipeline/src/sidecar.rs#L99), [`core/dr-pipeline/src/state.rs:1`](../../core/dr-pipeline/src/state.rs#L1), [`core/dr-pipeline/src/state.rs:75`](../../core/dr-pipeline/src/state.rs#L75), [`core/dr-pipeline/tests/tone_curve.rs:34`](../../core/dr-pipeline/tests/tone_curve.rs#L34), [`core/dr-sync/src/scan.rs:31`](../../core/dr-sync/src/scan.rs#L31), [`core/dr-sync/src/scan.rs:56`](../../core/dr-sync/src/scan.rs#L56), [`core/dr-sync/src/scan.rs:926`](../../core/dr-sync/src/scan.rs#L926), [`ui/dr-ui/src/develop/mask_ops.rs:1076`](../../ui/dr-ui/src/develop/mask_ops.rs#L1076), [`ui/dr-ui/src/develop/mask_ops.rs:1130`](../../ui/dr-ui/src/develop/mask_ops.rs#L1130), [`ui/dr-ui/src/develop/mask_ops.rs:178`](../../ui/dr-ui/src/develop/mask_ops.rs#L178), [`ui/dr-ui/src/develop/masks.rs:743`](../../ui/dr-ui/src/develop/masks.rs#L743), [`ui/dr-ui/src/develop/session.rs:566`](../../ui/dr-ui/src/develop/session.rs#L566), [`ui/dr-ui/src/develop/session.rs:595`](../../ui/dr-ui/src/develop/session.rs#L595), [`ui/dr-ui/src/export.rs:831`](../../ui/dr-ui/src/export.rs#L831), [`ui/dr-ui/src/lib.rs:1180`](../../ui/dr-ui/src/lib.rs#L1180), [`ui/dr-ui/src/lib.rs:1240`](../../ui/dr-ui/src/lib.rs#L1240), [`ui/dr-ui/src/lib.rs:2710`](../../ui/dr-ui/src/lib.rs#L2710), [`ui/dr-ui/src/lib.rs:2857`](../../ui/dr-ui/src/lib.rs#L2857), [`ui/dr-ui/src/lib.rs:791`](../../ui/dr-ui/src/lib.rs#L791), [`ui/dr-ui/src/library/scan.rs:226`](../../ui/dr-ui/src/library/scan.rs#L226), [`ui/dr-ui/src/library/scan.rs:261`](../../ui/dr-ui/src/library/scan.rs#L261), [`ui/dr-ui/src/library/scan.rs:34`](../../ui/dr-ui/src/library/scan.rs#L34), [`ui/dr-ui/src/library/sidecar.rs:100`](../../ui/dr-ui/src/library/sidecar.rs#L100), [`ui/dr-ui/src/library/sidecar.rs:11`](../../ui/dr-ui/src/library/sidecar.rs#L11), [`ui/dr-ui/src/library/sidecar.rs:378`](../../ui/dr-ui/src/library/sidecar.rs#L378), [`ui/dr-ui/src/library/sidecar.rs:63`](../../ui/dr-ui/src/library/sidecar.rs#L63), [`ui/dr-ui/src/library/thumbnails_fetch.rs:413`](../../ui/dr-ui/src/library/thumbnails_fetch.rs#L413), [`ui/dr-ui/src/library_ui/grid.rs:778`](../../ui/dr-ui/src/library_ui/grid.rs#L778), [`ui/dr-ui/src/library_ui/open.rs:455`](../../ui/dr-ui/src/library_ui/open.rs#L455), [`ui/dr-ui/src/sidecar_cache.rs:1`](../../ui/dr-ui/src/sidecar_cache.rs#L1) | | FR-CAT-9 | [`core/dr-catalog/src/cache.rs:1`](../../core/dr-catalog/src/cache.rs#L1), [`core/dr-catalog/src/scan.rs:1`](../../core/dr-catalog/src/scan.rs#L1), [`core/dr-catalog/src/schema.rs:1209`](../../core/dr-catalog/src/schema.rs#L1209), [`core/dr-catalog/src/walk.rs:1022`](../../core/dr-catalog/src/walk.rs#L1022), [`core/dr-catalog/src/walk.rs:162`](../../core/dr-catalog/src/walk.rs#L162), [`core/dr-catalog/src/walk.rs:1`](../../core/dr-catalog/src/walk.rs#L1), [`core/dr-catalog/src/walk.rs:435`](../../core/dr-catalog/src/walk.rs#L435), [`core/dr-catalog/src/walk.rs:728`](../../core/dr-catalog/src/walk.rs#L728), [`core/dr-sync-nextcloud/src/desktop_client.rs:35`](../../core/dr-sync-nextcloud/src/desktop_client.rs#L35), [`core/dr-sync/src/error.rs:113`](../../core/dr-sync/src/error.rs#L113), [`core/dr-sync/src/reachability.rs:1`](../../core/dr-sync/src/reachability.rs#L1), [`core/dr-sync/src/scan.rs:211`](../../core/dr-sync/src/scan.rs#L211), [`core/dr-sync/src/scan.rs:519`](../../core/dr-sync/src/scan.rs#L519), [`core/dr-types/src/lib.rs:122`](../../core/dr-types/src/lib.rs#L122), [`ui/dr-ui/src/develop/render.rs:624`](../../ui/dr-ui/src/develop/render.rs#L624), [`ui/dr-ui/src/develop/render.rs:667`](../../ui/dr-ui/src/develop/render.rs#L667), [`ui/dr-ui/src/library/filters.rs:89`](../../ui/dr-ui/src/library/filters.rs#L89), [`ui/dr-ui/src/library/scan.rs:126`](../../ui/dr-ui/src/library/scan.rs#L126), [`ui/dr-ui/src/library/scan.rs:179`](../../ui/dr-ui/src/library/scan.rs#L179), [`ui/dr-ui/src/library/scan.rs:210`](../../ui/dr-ui/src/library/scan.rs#L210), [`ui/dr-ui/src/library/scan.rs:261`](../../ui/dr-ui/src/library/scan.rs#L261), [`ui/dr-ui/src/library/scan.rs:629`](../../ui/dr-ui/src/library/scan.rs#L629), [`ui/dr-ui/src/library/sidecar.rs:100`](../../ui/dr-ui/src/library/sidecar.rs#L100), [`ui/dr-ui/src/library/sidecar.rs:362`](../../ui/dr-ui/src/library/sidecar.rs#L362), [`ui/dr-ui/src/library/sidecar.rs:378`](../../ui/dr-ui/src/library/sidecar.rs#L378), [`ui/dr-ui/src/library/sidecar.rs:432`](../../ui/dr-ui/src/library/sidecar.rs#L432), [`ui/dr-ui/src/library/spans.rs:361`](../../ui/dr-ui/src/library/spans.rs#L361), [`ui/dr-ui/src/library/thumbnails_fetch.rs:393`](../../ui/dr-ui/src/library/thumbnails_fetch.rs#L393), [`ui/dr-ui/src/library/thumbnails_fetch.rs:449`](../../ui/dr-ui/src/library/thumbnails_fetch.rs#L449), [`ui/dr-ui/src/library/thumbnails_gen.rs:74`](../../ui/dr-ui/src/library/thumbnails_gen.rs#L74), [`ui/dr-ui/src/library_ui/controller.rs:295`](../../ui/dr-ui/src/library_ui/controller.rs#L295), [`ui/dr-ui/src/library_ui/controller.rs:331`](../../ui/dr-ui/src/library_ui/controller.rs#L331), [`ui/dr-ui/src/library_ui/controller.rs:339`](../../ui/dr-ui/src/library_ui/controller.rs#L339), [`ui/dr-ui/src/library_ui/controller.rs:586`](../../ui/dr-ui/src/library_ui/controller.rs#L586), [`ui/dr-ui/src/library_ui/controller.rs:675`](../../ui/dr-ui/src/library_ui/controller.rs#L675), [`ui/dr-ui/src/library_ui/filter_bar.rs:461`](../../ui/dr-ui/src/library_ui/filter_bar.rs#L461), [`ui/dr-ui/src/library_ui/filter_bar.rs:547`](../../ui/dr-ui/src/library_ui/filter_bar.rs#L547), [`ui/dr-ui/src/library_ui/offline.rs:561`](../../ui/dr-ui/src/library_ui/offline.rs#L561), [`ui/dr-ui/src/library_ui/offline.rs:607`](../../ui/dr-ui/src/library_ui/offline.rs#L607), [`ui/dr-ui/src/library_ui/offline.rs:623`](../../ui/dr-ui/src/library_ui/offline.rs#L623), [`ui/dr-ui/src/library_ui/open.rs:535`](../../ui/dr-ui/src/library_ui/open.rs#L535), [`ui/dr-ui/src/library_ui/open.rs:615`](../../ui/dr-ui/src/library_ui/open.rs#L615), [`ui/dr-ui/src/library_ui/ratings_keywords.rs:75`](../../ui/dr-ui/src/library_ui/ratings_keywords.rs#L75), [`ui/dr-ui/src/library_ui/ratings_keywords.rs:883`](../../ui/dr-ui/src/library_ui/ratings_keywords.rs#L883), [`ui/dr-ui/src/library_ui/sync.rs:287`](../../ui/dr-ui/src/library_ui/sync.rs#L287), [`ui/dr-ui/src/library_ui/sync.rs:421`](../../ui/dr-ui/src/library_ui/sync.rs#L421), [`ui/dr-ui/src/library_ui/sync.rs:83`](../../ui/dr-ui/src/library_ui/sync.rs#L83), [`ui/dr-ui/src/library_ui/window.rs:659`](../../ui/dr-ui/src/library_ui/window.rs#L659), [`ui/dr-ui/src/library_ui/window.rs:939`](../../ui/dr-ui/src/library_ui/window.rs#L939), [`ui/dr-ui/src/presets.rs:486`](../../ui/dr-ui/src/presets.rs#L486), [`ui/dr-ui/src/presets.rs:498`](../../ui/dr-ui/src/presets.rs#L498), [`ui/dr-ui/src/sidecar_cache.rs:1`](../../ui/dr-ui/src/sidecar_cache.rs#L1), [`ui/dr-ui/tests/export_ignores_the_viewport.rs:1`](../../ui/dr-ui/tests/export_ignores_the_viewport.rs#L1) | | FR-CULL-1 | [`core/dr-decode/src/preview.rs:121`](../../core/dr-decode/src/preview.rs#L121) | @@ -57,7 +57,7 @@ _None._ | FR-CULL-13 | [`core/dr-catalog/src/schema.rs:944`](../../core/dr-catalog/src/schema.rs#L944), [`core/dr-types/src/place.rs:133`](../../core/dr-types/src/place.rs#L133), [`ui/dr-ui/src/identity.rs:104`](../../ui/dr-ui/src/identity.rs#L104), [`ui/dr-ui/src/library/filters.rs:136`](../../ui/dr-ui/src/library/filters.rs#L136), [`ui/dr-ui/src/library/filters.rs:763`](../../ui/dr-ui/src/library/filters.rs#L763), [`ui/dr-ui/src/library/filters.rs:806`](../../ui/dr-ui/src/library/filters.rs#L806), [`ui/dr-ui/src/library/filters.rs:844`](../../ui/dr-ui/src/library/filters.rs#L844), [`ui/dr-ui/ui/identity.slint:64`](../../ui/dr-ui/ui/identity.slint#L64), [`ui/dr-ui/ui/library.slint:1923`](../../ui/dr-ui/ui/library.slint#L1923), [`ui/dr-ui/ui/library.slint:730`](../../ui/dr-ui/ui/library.slint#L730) | | FR-CULL-2 | [`core/dr-decode/src/locate.rs:1`](../../core/dr-decode/src/locate.rs#L1), [`core/dr-decode/src/preview.rs:148`](../../core/dr-decode/src/preview.rs#L148), [`ui/dr-ui/src/decoder_seam.rs:215`](../../ui/dr-ui/src/decoder_seam.rs#L215), [`ui/dr-ui/src/import.rs:474`](../../ui/dr-ui/src/import.rs#L474) | | FR-CULL-3 | [`core/dr-gpu/src/focus.rs:154`](../../core/dr-gpu/src/focus.rs#L154), [`core/dr-gpu/src/focus.rs:186`](../../core/dr-gpu/src/focus.rs#L186), [`core/dr-gpu/src/focus.rs:1`](../../core/dr-gpu/src/focus.rs#L1), [`core/dr-gpu/src/focus.rs:317`](../../core/dr-gpu/src/focus.rs#L317), [`core/dr-gpu/src/raw_histogram.rs:129`](../../core/dr-gpu/src/raw_histogram.rs#L129), [`core/dr-gpu/src/raw_histogram.rs:1`](../../core/dr-gpu/src/raw_histogram.rs#L1), [`core/dr-gpu/src/raw_histogram.rs:272`](../../core/dr-gpu/src/raw_histogram.rs#L272), [`core/dr-gpu/src/raw_histogram.rs:407`](../../core/dr-gpu/src/raw_histogram.rs#L407), [`core/dr-gpu/src/shaders/focus_peak.wgsl:1`](../../core/dr-gpu/src/shaders/focus_peak.wgsl#L1), [`core/dr-gpu/src/shaders/raw_histogram.wgsl:1`](../../core/dr-gpu/src/shaders/raw_histogram.wgsl#L1), [`ui/dr-ui/src/develop/render.rs:1362`](../../ui/dr-ui/src/develop/render.rs#L1362), [`ui/dr-ui/src/develop/render.rs:1426`](../../ui/dr-ui/src/develop/render.rs#L1426), [`ui/dr-ui/src/develop/render.rs:1449`](../../ui/dr-ui/src/develop/render.rs#L1449), [`ui/dr-ui/src/develop/render.rs:317`](../../ui/dr-ui/src/develop/render.rs#L317), [`ui/dr-ui/src/develop/render.rs:330`](../../ui/dr-ui/src/develop/render.rs#L330), [`ui/dr-ui/src/develop/render.rs:375`](../../ui/dr-ui/src/develop/render.rs#L375), [`ui/dr-ui/src/develop/render.rs:386`](../../ui/dr-ui/src/develop/render.rs#L386), [`ui/dr-ui/src/develop/render.rs:392`](../../ui/dr-ui/src/develop/render.rs#L392), [`ui/dr-ui/src/develop/render.rs:409`](../../ui/dr-ui/src/develop/render.rs#L409), [`ui/dr-ui/src/develop/session.rs:122`](../../ui/dr-ui/src/develop/session.rs#L122), [`ui/dr-ui/src/develop/session.rs:142`](../../ui/dr-ui/src/develop/session.rs#L142), [`ui/dr-ui/src/develop/session.rs:148`](../../ui/dr-ui/src/develop/session.rs#L148), [`ui/dr-ui/src/develop_ui.rs:1359`](../../ui/dr-ui/src/develop_ui.rs#L1359), [`ui/dr-ui/src/histogram.rs:1`](../../ui/dr-ui/src/histogram.rs#L1), [`ui/dr-ui/src/histogram.rs:208`](../../ui/dr-ui/src/histogram.rs#L208), [`ui/dr-ui/src/histogram.rs:228`](../../ui/dr-ui/src/histogram.rs#L228), [`ui/dr-ui/src/histogram.rs:272`](../../ui/dr-ui/src/histogram.rs#L272), [`ui/dr-ui/src/histogram.rs:544`](../../ui/dr-ui/src/histogram.rs#L544), [`ui/dr-ui/src/histogram.rs:565`](../../ui/dr-ui/src/histogram.rs#L565), [`ui/dr-ui/src/histogram.rs:593`](../../ui/dr-ui/src/histogram.rs#L593), [`ui/dr-ui/src/histogram.rs:621`](../../ui/dr-ui/src/histogram.rs#L621), [`ui/dr-ui/src/histogram.rs:658`](../../ui/dr-ui/src/histogram.rs#L658), [`ui/dr-ui/src/lib.rs:1269`](../../ui/dr-ui/src/lib.rs#L1269), [`ui/dr-ui/src/lib.rs:2244`](../../ui/dr-ui/src/lib.rs#L2244), [`ui/dr-ui/src/lib.rs:2374`](../../ui/dr-ui/src/lib.rs#L2374), [`ui/dr-ui/src/lib.rs:2417`](../../ui/dr-ui/src/lib.rs#L2417), [`ui/dr-ui/src/lib.rs:468`](../../ui/dr-ui/src/lib.rs#L468), [`ui/dr-ui/src/peaking.rs:1`](../../ui/dr-ui/src/peaking.rs#L1), [`ui/dr-ui/ui/app.slint:153`](../../ui/dr-ui/ui/app.slint#L153), [`ui/dr-ui/ui/app.slint:1866`](../../ui/dr-ui/ui/app.slint#L1866), [`ui/dr-ui/ui/app.slint:3228`](../../ui/dr-ui/ui/app.slint#L3228), [`ui/dr-ui/ui/peaking.slint:1`](../../ui/dr-ui/ui/peaking.slint#L1), [`ui/dr-ui/ui/peaking.slint:25`](../../ui/dr-ui/ui/peaking.slint#L25), [`ui/dr-ui/ui/peaking.slint:56`](../../ui/dr-ui/ui/peaking.slint#L56), [`ui/dr-ui/ui/peaking.slint:91`](../../ui/dr-ui/ui/peaking.slint#L91) | -| FR-CULL-4 | [`core/dr-catalog/src/rating.rs:1`](../../core/dr-catalog/src/rating.rs#L1), [`core/dr-pipeline/src/sidecar.rs:174`](../../core/dr-pipeline/src/sidecar.rs#L174), [`core/dr-xmp/src/lib.rs:442`](../../core/dr-xmp/src/lib.rs#L442), [`ui/dr-ui/src/library/filters.rs:64`](../../ui/dr-ui/src/library/filters.rs#L64), [`ui/dr-ui/src/library/sidecar.rs:11`](../../ui/dr-ui/src/library/sidecar.rs#L11), [`ui/dr-ui/src/library_ui/ratings_keywords.rs:1028`](../../ui/dr-ui/src/library_ui/ratings_keywords.rs#L1028), [`ui/dr-ui/src/library_ui/ratings_keywords.rs:1301`](../../ui/dr-ui/src/library_ui/ratings_keywords.rs#L1301), [`ui/dr-ui/src/library_ui/ratings_keywords.rs:457`](../../ui/dr-ui/src/library_ui/ratings_keywords.rs#L457), [`ui/dr-ui/ui/app.slint:1635`](../../ui/dr-ui/ui/app.slint#L1635), [`ui/dr-ui/ui/app.slint:2725`](../../ui/dr-ui/ui/app.slint#L2725), [`ui/dr-ui/ui/develop.slint:168`](../../ui/dr-ui/ui/develop.slint#L168), [`ui/dr-ui/ui/develop.slint:55`](../../ui/dr-ui/ui/develop.slint#L55), [`ui/dr-ui/ui/library.slint:1039`](../../ui/dr-ui/ui/library.slint#L1039), [`ui/dr-ui/ui/library.slint:1618`](../../ui/dr-ui/ui/library.slint#L1618), [`ui/dr-ui/ui/library.slint:488`](../../ui/dr-ui/ui/library.slint#L488), [`ui/dr-ui/ui/library.slint:5069`](../../ui/dr-ui/ui/library.slint#L5069), [`ui/dr-ui/ui/library.slint:642`](../../ui/dr-ui/ui/library.slint#L642) | +| FR-CULL-4 | [`core/dr-catalog/src/rating.rs:1`](../../core/dr-catalog/src/rating.rs#L1), [`core/dr-pipeline/src/sidecar.rs:174`](../../core/dr-pipeline/src/sidecar.rs#L174), [`core/dr-xmp/src/lib.rs:442`](../../core/dr-xmp/src/lib.rs#L442), [`ui/dr-ui/src/library/filters.rs:64`](../../ui/dr-ui/src/library/filters.rs#L64), [`ui/dr-ui/src/library/sidecar.rs:11`](../../ui/dr-ui/src/library/sidecar.rs#L11), [`ui/dr-ui/src/library_ui/ratings_keywords.rs:1028`](../../ui/dr-ui/src/library_ui/ratings_keywords.rs#L1028), [`ui/dr-ui/src/library_ui/ratings_keywords.rs:1301`](../../ui/dr-ui/src/library_ui/ratings_keywords.rs#L1301), [`ui/dr-ui/src/library_ui/ratings_keywords.rs:457`](../../ui/dr-ui/src/library_ui/ratings_keywords.rs#L457), [`ui/dr-ui/ui/app.slint:1635`](../../ui/dr-ui/ui/app.slint#L1635), [`ui/dr-ui/ui/app.slint:2725`](../../ui/dr-ui/ui/app.slint#L2725), [`ui/dr-ui/ui/develop.slint:168`](../../ui/dr-ui/ui/develop.slint#L168), [`ui/dr-ui/ui/develop.slint:55`](../../ui/dr-ui/ui/develop.slint#L55), [`ui/dr-ui/ui/library.slint:1039`](../../ui/dr-ui/ui/library.slint#L1039), [`ui/dr-ui/ui/library.slint:1618`](../../ui/dr-ui/ui/library.slint#L1618), [`ui/dr-ui/ui/library.slint:488`](../../ui/dr-ui/ui/library.slint#L488), [`ui/dr-ui/ui/library.slint:5087`](../../ui/dr-ui/ui/library.slint#L5087), [`ui/dr-ui/ui/library.slint:642`](../../ui/dr-ui/ui/library.slint#L642) | | FR-CULL-5 | [`core/dr-catalog/src/bursts.rs:1`](../../core/dr-catalog/src/bursts.rs#L1), [`core/dr-catalog/src/schema.rs:673`](../../core/dr-catalog/src/schema.rs#L673), [`ui/dr-ui/src/bursts.rs:1`](../../ui/dr-ui/src/bursts.rs#L1), [`ui/dr-ui/src/library/cells.rs:19`](../../ui/dr-ui/src/library/cells.rs#L19), [`ui/dr-ui/src/library/cells.rs:769`](../../ui/dr-ui/src/library/cells.rs#L769), [`ui/dr-ui/ui/library.slint:488`](../../ui/dr-ui/ui/library.slint#L488) | | FR-CULL-8 | [`core/dr-catalog/src/face_shard.rs:1`](../../core/dr-catalog/src/face_shard.rs#L1), [`core/dr-catalog/src/faces.rs:1`](../../core/dr-catalog/src/faces.rs#L1), [`core/dr-catalog/src/schema.rs:1038`](../../core/dr-catalog/src/schema.rs#L1038), [`core/dr-catalog/src/schema.rs:741`](../../core/dr-catalog/src/schema.rs#L741), [`core/dr-catalog/src/schema.rs:997`](../../core/dr-catalog/src/schema.rs#L997), [`core/dr-face/src/align.rs:296`](../../core/dr-face/src/align.rs#L296), [`core/dr-types/src/settings.rs:176`](../../core/dr-types/src/settings.rs#L176), [`ui/dr-ui/examples/face_detectors.rs:1`](../../ui/dr-ui/examples/face_detectors.rs#L1), [`ui/dr-ui/examples/face_native.rs:1`](../../ui/dr-ui/examples/face_native.rs#L1), [`ui/dr-ui/src/derived_sync.rs:127`](../../ui/dr-ui/src/derived_sync.rs#L127), [`ui/dr-ui/src/faces.rs:1`](../../ui/dr-ui/src/faces.rs#L1), [`ui/dr-ui/src/faces.rs:446`](../../ui/dr-ui/src/faces.rs#L446), [`ui/dr-ui/src/faces.rs:462`](../../ui/dr-ui/src/faces.rs#L462), [`ui/dr-ui/src/faces.rs:584`](../../ui/dr-ui/src/faces.rs#L584), [`ui/dr-ui/src/identity_ui.rs:1472`](../../ui/dr-ui/src/identity_ui.rs#L1472), [`ui/dr-ui/src/identity_ui.rs:617`](../../ui/dr-ui/src/identity_ui.rs#L617), [`ui/dr-ui/src/lib.rs:1739`](../../ui/dr-ui/src/lib.rs#L1739), [`ui/dr-ui/src/lib.rs:3117`](../../ui/dr-ui/src/lib.rs#L3117), [`ui/dr-ui/src/library/sweep.rs:242`](../../ui/dr-ui/src/library/sweep.rs#L242), [`ui/dr-ui/src/library/sweep.rs:510`](../../ui/dr-ui/src/library/sweep.rs#L510), [`ui/dr-ui/src/library_ui/controller.rs:417`](../../ui/dr-ui/src/library_ui/controller.rs#L417), [`ui/dr-ui/src/library_ui/controller.rs:506`](../../ui/dr-ui/src/library_ui/controller.rs#L506), [`ui/dr-ui/src/repairs.rs:1`](../../ui/dr-ui/src/repairs.rs#L1), [`ui/dr-ui/src/settings_ui.rs:210`](../../ui/dr-ui/src/settings_ui.rs#L210), [`ui/dr-ui/src/settings_ui.rs:645`](../../ui/dr-ui/src/settings_ui.rs#L645), [`ui/dr-ui/ui/app.slint:638`](../../ui/dr-ui/ui/app.slint#L638), [`ui/dr-ui/ui/settings.slint:101`](../../ui/dr-ui/ui/settings.slint#L101), [`ui/dr-ui/ui/settings.slint:407`](../../ui/dr-ui/ui/settings.slint#L407), [`ui/dr-ui/ui/settings.slint:469`](../../ui/dr-ui/ui/settings.slint#L469), [`ui/dr-ui/ui/settings.slint:83`](../../ui/dr-ui/ui/settings.slint#L83), [`ui/dr-ui/ui/settings.slint:95`](../../ui/dr-ui/ui/settings.slint#L95) | | FR-CULL-8a | [`core/dr-catalog/src/faces.rs:124`](../../core/dr-catalog/src/faces.rs#L124), [`core/dr-catalog/src/schema.rs:944`](../../core/dr-catalog/src/schema.rs#L944), [`core/dr-face/src/classify.rs:1`](../../core/dr-face/src/classify.rs#L1), [`core/dr-face/src/eyes.rs:1`](../../core/dr-face/src/eyes.rs#L1), [`core/dr-face/src/landmarks.rs:1`](../../core/dr-face/src/landmarks.rs#L1), [`core/dr-types/src/place.rs:133`](../../core/dr-types/src/place.rs#L133), [`ui/dr-ui/src/faces.rs:166`](../../ui/dr-ui/src/faces.rs#L166), [`ui/dr-ui/src/faces.rs:1821`](../../ui/dr-ui/src/faces.rs#L1821), [`ui/dr-ui/src/faces.rs:308`](../../ui/dr-ui/src/faces.rs#L308), [`ui/dr-ui/src/identity.rs:104`](../../ui/dr-ui/src/identity.rs#L104), [`ui/dr-ui/src/library/filters.rs:136`](../../ui/dr-ui/src/library/filters.rs#L136), [`ui/dr-ui/src/library/filters.rs:763`](../../ui/dr-ui/src/library/filters.rs#L763), [`ui/dr-ui/src/library/filters.rs:806`](../../ui/dr-ui/src/library/filters.rs#L806), [`ui/dr-ui/src/library/filters.rs:844`](../../ui/dr-ui/src/library/filters.rs#L844), [`ui/dr-ui/src/repairs.rs:579`](../../ui/dr-ui/src/repairs.rs#L579), [`ui/dr-ui/ui/identity.slint:276`](../../ui/dr-ui/ui/identity.slint#L276), [`ui/dr-ui/ui/identity.slint:64`](../../ui/dr-ui/ui/identity.slint#L64), [`ui/dr-ui/ui/library.slint:1923`](../../ui/dr-ui/ui/library.slint#L1923), [`ui/dr-ui/ui/library.slint:730`](../../ui/dr-ui/ui/library.slint#L730) | @@ -85,7 +85,7 @@ _None._ | FR-DEV-3i | [`core/dr-pipeline/src/mask.rs:558`](../../core/dr-pipeline/src/mask.rs#L558), [`core/dr-segment/src/lib.rs:1`](../../core/dr-segment/src/lib.rs#L1), [`ui/dr-ui/src/segmentation.rs:1`](../../ui/dr-ui/src/segmentation.rs#L1) | | FR-DEV-4 | [`core/dr-gpu/src/adjust.rs:1039`](../../core/dr-gpu/src/adjust.rs#L1039), [`core/dr-gpu/src/lib.rs:368`](../../core/dr-gpu/src/lib.rs#L368), [`ui/dr-ui/ui/crop.slint:1`](../../ui/dr-ui/ui/crop.slint#L1) | | FR-DEV-5 | [`core/dr-pipeline/src/graph.rs:1440`](../../core/dr-pipeline/src/graph.rs#L1440), [`core/dr-pipeline/src/graph.rs:587`](../../core/dr-pipeline/src/graph.rs#L587), [`core/dr-pipeline/src/graph.rs:640`](../../core/dr-pipeline/src/graph.rs#L640), [`core/dr-pipeline/src/history.rs:102`](../../core/dr-pipeline/src/history.rs#L102), [`core/dr-pipeline/src/history.rs:110`](../../core/dr-pipeline/src/history.rs#L110), [`core/dr-pipeline/src/history.rs:127`](../../core/dr-pipeline/src/history.rs#L127), [`core/dr-pipeline/src/history.rs:184`](../../core/dr-pipeline/src/history.rs#L184), [`core/dr-pipeline/src/history.rs:1`](../../core/dr-pipeline/src/history.rs#L1), [`core/dr-pipeline/src/history.rs:214`](../../core/dr-pipeline/src/history.rs#L214), [`core/dr-pipeline/src/history.rs:234`](../../core/dr-pipeline/src/history.rs#L234), [`core/dr-pipeline/src/history.rs:293`](../../core/dr-pipeline/src/history.rs#L293), [`core/dr-pipeline/src/history.rs:479`](../../core/dr-pipeline/src/history.rs#L479), [`core/dr-pipeline/src/history.rs:489`](../../core/dr-pipeline/src/history.rs#L489), [`core/dr-pipeline/src/history.rs:499`](../../core/dr-pipeline/src/history.rs#L499), [`core/dr-pipeline/src/history.rs:526`](../../core/dr-pipeline/src/history.rs#L526), [`core/dr-pipeline/src/history.rs:86`](../../core/dr-pipeline/src/history.rs#L86), [`core/dr-pipeline/src/sidecar.rs:1067`](../../core/dr-pipeline/src/sidecar.rs#L1067), [`core/dr-pipeline/src/sidecar.rs:147`](../../core/dr-pipeline/src/sidecar.rs#L147), [`core/dr-pipeline/src/sidecar.rs:2513`](../../core/dr-pipeline/src/sidecar.rs#L2513), [`core/dr-pipeline/src/sidecar.rs:2549`](../../core/dr-pipeline/src/sidecar.rs#L2549), [`core/dr-pipeline/src/sidecar.rs:2561`](../../core/dr-pipeline/src/sidecar.rs#L2561), [`core/dr-pipeline/src/sidecar.rs:318`](../../core/dr-pipeline/src/sidecar.rs#L318), [`core/dr-pipeline/src/sidecar.rs:713`](../../core/dr-pipeline/src/sidecar.rs#L713), [`core/dr-pipeline/src/sidecar.rs:729`](../../core/dr-pipeline/src/sidecar.rs#L729), [`core/dr-pipeline/src/sidecar.rs:876`](../../core/dr-pipeline/src/sidecar.rs#L876), [`core/dr-pipeline/src/state.rs:1`](../../core/dr-pipeline/src/state.rs#L1), [`core/dr-pipeline/src/state.rs:75`](../../core/dr-pipeline/src/state.rs#L75), [`ui/dr-ui/src/develop/framing.rs:870`](../../ui/dr-ui/src/develop/framing.rs#L870), [`ui/dr-ui/src/develop/history.rs:11`](../../ui/dr-ui/src/develop/history.rs#L11), [`ui/dr-ui/src/develop/history.rs:152`](../../ui/dr-ui/src/develop/history.rs#L152), [`ui/dr-ui/src/develop/history.rs:165`](../../ui/dr-ui/src/develop/history.rs#L165), [`ui/dr-ui/src/develop/history.rs:178`](../../ui/dr-ui/src/develop/history.rs#L178), [`ui/dr-ui/src/develop/history.rs:190`](../../ui/dr-ui/src/develop/history.rs#L190), [`ui/dr-ui/src/develop/history.rs:206`](../../ui/dr-ui/src/develop/history.rs#L206), [`ui/dr-ui/src/develop/history.rs:238`](../../ui/dr-ui/src/develop/history.rs#L238), [`ui/dr-ui/src/develop/history.rs:242`](../../ui/dr-ui/src/develop/history.rs#L242), [`ui/dr-ui/src/develop/history.rs:261`](../../ui/dr-ui/src/develop/history.rs#L261), [`ui/dr-ui/src/develop/history.rs:277`](../../ui/dr-ui/src/develop/history.rs#L277), [`ui/dr-ui/src/develop/history.rs:299`](../../ui/dr-ui/src/develop/history.rs#L299), [`ui/dr-ui/src/develop/history.rs:29`](../../ui/dr-ui/src/develop/history.rs#L29), [`ui/dr-ui/src/develop/history.rs:359`](../../ui/dr-ui/src/develop/history.rs#L359), [`ui/dr-ui/src/develop/history.rs:68`](../../ui/dr-ui/src/develop/history.rs#L68), [`ui/dr-ui/src/develop/history.rs:82`](../../ui/dr-ui/src/develop/history.rs#L82), [`ui/dr-ui/src/develop/history.rs:93`](../../ui/dr-ui/src/develop/history.rs#L93), [`ui/dr-ui/src/develop/render.rs:711`](../../ui/dr-ui/src/develop/render.rs#L711), [`ui/dr-ui/src/develop/render.rs:887`](../../ui/dr-ui/src/develop/render.rs#L887), [`ui/dr-ui/src/develop/render.rs:923`](../../ui/dr-ui/src/develop/render.rs#L923), [`ui/dr-ui/src/develop/session.rs:80`](../../ui/dr-ui/src/develop/session.rs#L80), [`ui/dr-ui/src/develop/session.rs:89`](../../ui/dr-ui/src/develop/session.rs#L89), [`ui/dr-ui/src/develop/white_balance.rs:11`](../../ui/dr-ui/src/develop/white_balance.rs#L11), [`ui/dr-ui/src/develop/white_balance.rs:162`](../../ui/dr-ui/src/develop/white_balance.rs#L162), [`ui/dr-ui/src/develop_ui.rs:705`](../../ui/dr-ui/src/develop_ui.rs#L705), [`ui/dr-ui/src/develop_ui.rs:725`](../../ui/dr-ui/src/develop_ui.rs#L725), [`ui/dr-ui/src/labels.rs:12`](../../ui/dr-ui/src/labels.rs#L12), [`ui/dr-ui/src/labels.rs:284`](../../ui/dr-ui/src/labels.rs#L284), [`ui/dr-ui/src/labels.rs:31`](../../ui/dr-ui/src/labels.rs#L31), [`ui/dr-ui/src/lib.rs:2151`](../../ui/dr-ui/src/lib.rs#L2151), [`ui/dr-ui/src/lib.rs:2156`](../../ui/dr-ui/src/lib.rs#L2156), [`ui/dr-ui/src/lib.rs:2188`](../../ui/dr-ui/src/lib.rs#L2188), [`ui/dr-ui/src/lib.rs:2197`](../../ui/dr-ui/src/lib.rs#L2197), [`ui/dr-ui/src/lib.rs:2214`](../../ui/dr-ui/src/lib.rs#L2214), [`ui/dr-ui/src/library/sidecar.rs:348`](../../ui/dr-ui/src/library/sidecar.rs#L348), [`ui/dr-ui/src/library/sidecar.rs:58`](../../ui/dr-ui/src/library/sidecar.rs#L58), [`ui/dr-ui/src/presets.rs:1678`](../../ui/dr-ui/src/presets.rs#L1678), [`ui/dr-ui/src/presets.rs:373`](../../ui/dr-ui/src/presets.rs#L373), [`ui/dr-ui/src/presets.rs:437`](../../ui/dr-ui/src/presets.rs#L437), [`ui/dr-ui/src/presets.rs:557`](../../ui/dr-ui/src/presets.rs#L557), [`ui/dr-ui/ui/history.slint:192`](../../ui/dr-ui/ui/history.slint#L192), [`ui/dr-ui/ui/history.slint:1`](../../ui/dr-ui/ui/history.slint#L1), [`ui/dr-ui/ui/history.slint:219`](../../ui/dr-ui/ui/history.slint#L219), [`ui/dr-ui/ui/history.slint:304`](../../ui/dr-ui/ui/history.slint#L304), [`ui/dr-ui/ui/history.slint:43`](../../ui/dr-ui/ui/history.slint#L43), [`ui/dr-ui/ui/history.slint:53`](../../ui/dr-ui/ui/history.slint#L53) | -| FR-DEV-6 | [`core/dr-pipeline/src/preset.rs:1`](../../core/dr-pipeline/src/preset.rs#L1), [`core/dr-pipeline/src/preset.rs:390`](../../core/dr-pipeline/src/preset.rs#L390), [`core/dr-pipeline/src/preset.rs:423`](../../core/dr-pipeline/src/preset.rs#L423), [`core/dr-pipeline/src/starter.rs:1`](../../core/dr-pipeline/src/starter.rs#L1), [`core/dr-preset-xmp/src/lib.rs:1`](../../core/dr-preset-xmp/src/lib.rs#L1), [`core/dr-types/src/settings.rs:443`](../../core/dr-types/src/settings.rs#L443), [`ui/dr-ui/src/develop/session.rs:560`](../../ui/dr-ui/src/develop/session.rs#L560), [`ui/dr-ui/src/develop/session.rs:581`](../../ui/dr-ui/src/develop/session.rs#L581), [`ui/dr-ui/src/develop_ui.rs:167`](../../ui/dr-ui/src/develop_ui.rs#L167), [`ui/dr-ui/src/develop_ui.rs:196`](../../ui/dr-ui/src/develop_ui.rs#L196), [`ui/dr-ui/src/lib.rs:1240`](../../ui/dr-ui/src/lib.rs#L1240), [`ui/dr-ui/src/library/sidecar.rs:11`](../../ui/dr-ui/src/library/sidecar.rs#L11), [`ui/dr-ui/src/library/sidecar.rs:39`](../../ui/dr-ui/src/library/sidecar.rs#L39), [`ui/dr-ui/src/library/thumbnails_fetch.rs:413`](../../ui/dr-ui/src/library/thumbnails_fetch.rs#L413), [`ui/dr-ui/src/library_ui/controller.rs:643`](../../ui/dr-ui/src/library_ui/controller.rs#L643), [`ui/dr-ui/src/library_ui/ratings_keywords.rs:488`](../../ui/dr-ui/src/library_ui/ratings_keywords.rs#L488), [`ui/dr-ui/src/library_ui/window.rs:659`](../../ui/dr-ui/src/library_ui/window.rs#L659), [`ui/dr-ui/src/preset_store.rs:1`](../../ui/dr-ui/src/preset_store.rs#L1), [`ui/dr-ui/src/presets.rs:1`](../../ui/dr-ui/src/presets.rs#L1), [`ui/dr-ui/src/presets.rs:707`](../../ui/dr-ui/src/presets.rs#L707), [`ui/dr-ui/src/presets.rs:741`](../../ui/dr-ui/src/presets.rs#L741), [`ui/dr-ui/ui/adjust.slint:937`](../../ui/dr-ui/ui/adjust.slint#L937), [`ui/dr-ui/ui/adjust.slint:962`](../../ui/dr-ui/ui/adjust.slint#L962), [`ui/dr-ui/ui/app.slint:1055`](../../ui/dr-ui/ui/app.slint#L1055), [`ui/dr-ui/ui/app.slint:1471`](../../ui/dr-ui/ui/app.slint#L1471), [`ui/dr-ui/ui/app.slint:2429`](../../ui/dr-ui/ui/app.slint#L2429), [`ui/dr-ui/ui/app.slint:3367`](../../ui/dr-ui/ui/app.slint#L3367), [`ui/dr-ui/ui/app.slint:3411`](../../ui/dr-ui/ui/app.slint#L3411), [`ui/dr-ui/ui/app.slint:494`](../../ui/dr-ui/ui/app.slint#L494), [`ui/dr-ui/ui/app.slint:505`](../../ui/dr-ui/ui/app.slint#L505), [`ui/dr-ui/ui/app.slint:659`](../../ui/dr-ui/ui/app.slint#L659), [`ui/dr-ui/ui/develop.slint:257`](../../ui/dr-ui/ui/develop.slint#L257), [`ui/dr-ui/ui/library.slint:1995`](../../ui/dr-ui/ui/library.slint#L1995), [`ui/dr-ui/ui/library.slint:2001`](../../ui/dr-ui/ui/library.slint#L2001), [`ui/dr-ui/ui/library.slint:3190`](../../ui/dr-ui/ui/library.slint#L3190), [`ui/dr-ui/ui/library.slint:5108`](../../ui/dr-ui/ui/library.slint#L5108), [`ui/dr-ui/ui/library.slint:5126`](../../ui/dr-ui/ui/library.slint#L5126), [`ui/dr-ui/ui/presets.slint:105`](../../ui/dr-ui/ui/presets.slint#L105), [`ui/dr-ui/ui/presets.slint:113`](../../ui/dr-ui/ui/presets.slint#L113), [`ui/dr-ui/ui/presets.slint:19`](../../ui/dr-ui/ui/presets.slint#L19), [`ui/dr-ui/ui/presets.slint:276`](../../ui/dr-ui/ui/presets.slint#L276), [`ui/dr-ui/ui/presets.slint:297`](../../ui/dr-ui/ui/presets.slint#L297), [`ui/dr-ui/ui/presets.slint:332`](../../ui/dr-ui/ui/presets.slint#L332), [`ui/dr-ui/ui/presets.slint:5`](../../ui/dr-ui/ui/presets.slint#L5), [`ui/dr-ui/ui/presets.slint:60`](../../ui/dr-ui/ui/presets.slint#L60), [`ui/dr-ui/ui/settings.slint:126`](../../ui/dr-ui/ui/settings.slint#L126), [`ui/dr-ui/ui/settings.slint:690`](../../ui/dr-ui/ui/settings.slint#L690) | +| FR-DEV-6 | [`core/dr-pipeline/src/preset.rs:1`](../../core/dr-pipeline/src/preset.rs#L1), [`core/dr-pipeline/src/preset.rs:390`](../../core/dr-pipeline/src/preset.rs#L390), [`core/dr-pipeline/src/preset.rs:423`](../../core/dr-pipeline/src/preset.rs#L423), [`core/dr-pipeline/src/starter.rs:1`](../../core/dr-pipeline/src/starter.rs#L1), [`core/dr-preset-xmp/src/lib.rs:1`](../../core/dr-preset-xmp/src/lib.rs#L1), [`core/dr-types/src/settings.rs:443`](../../core/dr-types/src/settings.rs#L443), [`ui/dr-ui/src/develop/session.rs:560`](../../ui/dr-ui/src/develop/session.rs#L560), [`ui/dr-ui/src/develop/session.rs:581`](../../ui/dr-ui/src/develop/session.rs#L581), [`ui/dr-ui/src/develop_ui.rs:167`](../../ui/dr-ui/src/develop_ui.rs#L167), [`ui/dr-ui/src/develop_ui.rs:196`](../../ui/dr-ui/src/develop_ui.rs#L196), [`ui/dr-ui/src/lib.rs:1240`](../../ui/dr-ui/src/lib.rs#L1240), [`ui/dr-ui/src/library/sidecar.rs:11`](../../ui/dr-ui/src/library/sidecar.rs#L11), [`ui/dr-ui/src/library/sidecar.rs:39`](../../ui/dr-ui/src/library/sidecar.rs#L39), [`ui/dr-ui/src/library/thumbnails_fetch.rs:413`](../../ui/dr-ui/src/library/thumbnails_fetch.rs#L413), [`ui/dr-ui/src/library_ui/controller.rs:643`](../../ui/dr-ui/src/library_ui/controller.rs#L643), [`ui/dr-ui/src/library_ui/ratings_keywords.rs:488`](../../ui/dr-ui/src/library_ui/ratings_keywords.rs#L488), [`ui/dr-ui/src/library_ui/window.rs:659`](../../ui/dr-ui/src/library_ui/window.rs#L659), [`ui/dr-ui/src/preset_store.rs:1`](../../ui/dr-ui/src/preset_store.rs#L1), [`ui/dr-ui/src/presets.rs:1`](../../ui/dr-ui/src/presets.rs#L1), [`ui/dr-ui/src/presets.rs:707`](../../ui/dr-ui/src/presets.rs#L707), [`ui/dr-ui/src/presets.rs:741`](../../ui/dr-ui/src/presets.rs#L741), [`ui/dr-ui/ui/adjust.slint:937`](../../ui/dr-ui/ui/adjust.slint#L937), [`ui/dr-ui/ui/adjust.slint:962`](../../ui/dr-ui/ui/adjust.slint#L962), [`ui/dr-ui/ui/app.slint:1055`](../../ui/dr-ui/ui/app.slint#L1055), [`ui/dr-ui/ui/app.slint:1471`](../../ui/dr-ui/ui/app.slint#L1471), [`ui/dr-ui/ui/app.slint:2429`](../../ui/dr-ui/ui/app.slint#L2429), [`ui/dr-ui/ui/app.slint:3367`](../../ui/dr-ui/ui/app.slint#L3367), [`ui/dr-ui/ui/app.slint:3411`](../../ui/dr-ui/ui/app.slint#L3411), [`ui/dr-ui/ui/app.slint:494`](../../ui/dr-ui/ui/app.slint#L494), [`ui/dr-ui/ui/app.slint:505`](../../ui/dr-ui/ui/app.slint#L505), [`ui/dr-ui/ui/app.slint:659`](../../ui/dr-ui/ui/app.slint#L659), [`ui/dr-ui/ui/develop.slint:257`](../../ui/dr-ui/ui/develop.slint#L257), [`ui/dr-ui/ui/library.slint:1995`](../../ui/dr-ui/ui/library.slint#L1995), [`ui/dr-ui/ui/library.slint:2001`](../../ui/dr-ui/ui/library.slint#L2001), [`ui/dr-ui/ui/library.slint:3190`](../../ui/dr-ui/ui/library.slint#L3190), [`ui/dr-ui/ui/library.slint:5126`](../../ui/dr-ui/ui/library.slint#L5126), [`ui/dr-ui/ui/library.slint:5144`](../../ui/dr-ui/ui/library.slint#L5144), [`ui/dr-ui/ui/presets.slint:105`](../../ui/dr-ui/ui/presets.slint#L105), [`ui/dr-ui/ui/presets.slint:113`](../../ui/dr-ui/ui/presets.slint#L113), [`ui/dr-ui/ui/presets.slint:19`](../../ui/dr-ui/ui/presets.slint#L19), [`ui/dr-ui/ui/presets.slint:276`](../../ui/dr-ui/ui/presets.slint#L276), [`ui/dr-ui/ui/presets.slint:297`](../../ui/dr-ui/ui/presets.slint#L297), [`ui/dr-ui/ui/presets.slint:332`](../../ui/dr-ui/ui/presets.slint#L332), [`ui/dr-ui/ui/presets.slint:5`](../../ui/dr-ui/ui/presets.slint#L5), [`ui/dr-ui/ui/presets.slint:60`](../../ui/dr-ui/ui/presets.slint#L60), [`ui/dr-ui/ui/settings.slint:126`](../../ui/dr-ui/ui/settings.slint#L126), [`ui/dr-ui/ui/settings.slint:690`](../../ui/dr-ui/ui/settings.slint#L690) | | FR-DEV-7 | [`core/dr-pipeline/src/history.rs:214`](../../core/dr-pipeline/src/history.rs#L214), [`core/dr-pipeline/src/history.rs:499`](../../core/dr-pipeline/src/history.rs#L499), [`core/dr-pipeline/src/history.rs:526`](../../core/dr-pipeline/src/history.rs#L526), [`ui/dr-ui/src/develop/history.rs:107`](../../ui/dr-ui/src/develop/history.rs#L107), [`ui/dr-ui/src/develop/history.rs:124`](../../ui/dr-ui/src/develop/history.rs#L124), [`ui/dr-ui/src/develop/history.rs:206`](../../ui/dr-ui/src/develop/history.rs#L206), [`ui/dr-ui/src/develop/history.rs:238`](../../ui/dr-ui/src/develop/history.rs#L238), [`ui/dr-ui/src/develop/history.rs:359`](../../ui/dr-ui/src/develop/history.rs#L359), [`ui/dr-ui/src/develop/render.rs:493`](../../ui/dr-ui/src/develop/render.rs#L493), [`ui/dr-ui/src/develop/render.rs:534`](../../ui/dr-ui/src/develop/render.rs#L534), [`ui/dr-ui/src/develop/render.rs:923`](../../ui/dr-ui/src/develop/render.rs#L923), [`ui/dr-ui/src/develop/session.rs:105`](../../ui/dr-ui/src/develop/session.rs#L105), [`ui/dr-ui/src/develop_ui.rs:646`](../../ui/dr-ui/src/develop_ui.rs#L646), [`ui/dr-ui/src/develop_ui.rs:705`](../../ui/dr-ui/src/develop_ui.rs#L705), [`ui/dr-ui/src/develop_ui.rs:777`](../../ui/dr-ui/src/develop_ui.rs#L777), [`ui/dr-ui/src/lib.rs:2197`](../../ui/dr-ui/src/lib.rs#L2197), [`ui/dr-ui/src/lib.rs:2274`](../../ui/dr-ui/src/lib.rs#L2274), [`ui/dr-ui/src/lib.rs:2288`](../../ui/dr-ui/src/lib.rs#L2288), [`ui/dr-ui/src/lib.rs:436`](../../ui/dr-ui/src/lib.rs#L436), [`ui/dr-ui/ui/app.slint:195`](../../ui/dr-ui/ui/app.slint#L195), [`ui/dr-ui/ui/app.slint:2296`](../../ui/dr-ui/ui/app.slint#L2296), [`ui/dr-ui/ui/app.slint:2342`](../../ui/dr-ui/ui/app.slint#L2342), [`ui/dr-ui/ui/app.slint:2911`](../../ui/dr-ui/ui/app.slint#L2911), [`ui/dr-ui/ui/history.slint:103`](../../ui/dr-ui/ui/history.slint#L103), [`ui/dr-ui/ui/history.slint:1`](../../ui/dr-ui/ui/history.slint#L1), [`ui/dr-ui/ui/history.slint:227`](../../ui/dr-ui/ui/history.slint#L227), [`ui/dr-ui/ui/history.slint:53`](../../ui/dr-ui/ui/history.slint#L53), [`ui/dr-ui/ui/widgets.slint:72`](../../ui/dr-ui/ui/widgets.slint#L72) | | FR-DEV-8 | [`core/dr-gpu/src/detail.rs:405`](../../core/dr-gpu/src/detail.rs#L405), [`core/dr-gpu/src/detail.rs:623`](../../core/dr-gpu/src/detail.rs#L623), [`core/dr-gpu/tests/detail_instances.rs:1`](../../core/dr-gpu/tests/detail_instances.rs#L1), [`core/dr-gpu/tests/spot_removal.rs:1`](../../core/dr-gpu/tests/spot_removal.rs#L1), [`core/dr-pipeline/src/detail.rs:410`](../../core/dr-pipeline/src/detail.rs#L410), [`core/dr-pipeline/src/detail.rs:452`](../../core/dr-pipeline/src/detail.rs#L452), [`core/dr-pipeline/src/detail.rs:487`](../../core/dr-pipeline/src/detail.rs#L487), [`core/dr-pipeline/src/detail.rs:573`](../../core/dr-pipeline/src/detail.rs#L573), [`core/dr-pipeline/src/graph.rs:1092`](../../core/dr-pipeline/src/graph.rs#L1092), [`core/dr-pipeline/src/graph.rs:115`](../../core/dr-pipeline/src/graph.rs#L115), [`core/dr-pipeline/src/graph.rs:247`](../../core/dr-pipeline/src/graph.rs#L247), [`core/dr-pipeline/src/operation.rs:330`](../../core/dr-pipeline/src/operation.rs#L330), [`core/dr-pipeline/src/operation.rs:749`](../../core/dr-pipeline/src/operation.rs#L749), [`core/dr-pipeline/src/sidecar.rs:1104`](../../core/dr-pipeline/src/sidecar.rs#L1104), [`core/dr-pipeline/src/sidecar.rs:1158`](../../core/dr-pipeline/src/sidecar.rs#L1158), [`core/dr-pipeline/src/sidecar.rs:1188`](../../core/dr-pipeline/src/sidecar.rs#L1188), [`core/dr-pipeline/src/sidecar.rs:233`](../../core/dr-pipeline/src/sidecar.rs#L233), [`core/dr-pipeline/src/sidecar.rs:410`](../../core/dr-pipeline/src/sidecar.rs#L410), [`core/dr-pipeline/src/sidecar.rs:910`](../../core/dr-pipeline/src/sidecar.rs#L910), [`core/dr-pipeline/src/spot.rs:115`](../../core/dr-pipeline/src/spot.rs#L115), [`core/dr-pipeline/src/spot.rs:151`](../../core/dr-pipeline/src/spot.rs#L151), [`core/dr-pipeline/src/spot.rs:1`](../../core/dr-pipeline/src/spot.rs#L1), [`core/dr-pipeline/src/spot.rs:207`](../../core/dr-pipeline/src/spot.rs#L207), [`core/dr-pipeline/src/spot.rs:387`](../../core/dr-pipeline/src/spot.rs#L387), [`core/dr-pipeline/src/spot.rs:472`](../../core/dr-pipeline/src/spot.rs#L472), [`core/dr-pipeline/src/spot.rs:582`](../../core/dr-pipeline/src/spot.rs#L582), [`core/dr-pipeline/src/spot.rs:673`](../../core/dr-pipeline/src/spot.rs#L673), [`core/dr-pipeline/src/state.rs:103`](../../core/dr-pipeline/src/state.rs#L103), [`core/dr-pipeline/tests/spot_sidecar.rs:1`](../../core/dr-pipeline/tests/spot_sidecar.rs#L1), [`core/dr-pipeline/tests/spots.rs:1`](../../core/dr-pipeline/tests/spots.rs#L1), [`ui/dr-ui/src/develop/repairs.rs:114`](../../ui/dr-ui/src/develop/repairs.rs#L114), [`ui/dr-ui/src/develop/repairs.rs:11`](../../ui/dr-ui/src/develop/repairs.rs#L11), [`ui/dr-ui/src/develop/repairs.rs:197`](../../ui/dr-ui/src/develop/repairs.rs#L197), [`ui/dr-ui/src/develop/repairs.rs:211`](../../ui/dr-ui/src/develop/repairs.rs#L211), [`ui/dr-ui/src/develop/repairs.rs:49`](../../ui/dr-ui/src/develop/repairs.rs#L49), [`ui/dr-ui/src/develop/session.rs:216`](../../ui/dr-ui/src/develop/session.rs#L216), [`ui/dr-ui/src/develop_ui.rs:932`](../../ui/dr-ui/src/develop_ui.rs#L932), [`ui/dr-ui/src/labels.rs:80`](../../ui/dr-ui/src/labels.rs#L80), [`ui/dr-ui/src/lib.rs:2232`](../../ui/dr-ui/src/lib.rs#L2232), [`ui/dr-ui/src/lib.rs:479`](../../ui/dr-ui/src/lib.rs#L479), [`ui/dr-ui/src/spots_ui.rs:19`](../../ui/dr-ui/src/spots_ui.rs#L19), [`ui/dr-ui/src/spots_ui.rs:1`](../../ui/dr-ui/src/spots_ui.rs#L1), [`ui/dr-ui/src/spots_ui.rs:265`](../../ui/dr-ui/src/spots_ui.rs#L265), [`ui/dr-ui/ui/app.slint:2149`](../../ui/dr-ui/ui/app.slint#L2149), [`ui/dr-ui/ui/app.slint:2392`](../../ui/dr-ui/ui/app.slint#L2392), [`ui/dr-ui/ui/app.slint:3260`](../../ui/dr-ui/ui/app.slint#L3260), [`ui/dr-ui/ui/icons.slint:378`](../../ui/dr-ui/ui/icons.slint#L378), [`ui/dr-ui/ui/session.slint:102`](../../ui/dr-ui/ui/session.slint#L102), [`ui/dr-ui/ui/session.slint:52`](../../ui/dr-ui/ui/session.slint#L52), [`ui/dr-ui/ui/spots.slint:191`](../../ui/dr-ui/ui/spots.slint#L191), [`ui/dr-ui/ui/spots.slint:49`](../../ui/dr-ui/ui/spots.slint#L49), [`ui/dr-ui/ui/spots.slint:6`](../../ui/dr-ui/ui/spots.slint#L6), [`ui/dr-ui/ui/spots.slint:91`](../../ui/dr-ui/ui/spots.slint#L91) | | FR-DSP-1 | [`core/dr-gpu/src/adjust.rs:1039`](../../core/dr-gpu/src/adjust.rs#L1039), [`core/dr-gpu/src/adjust.rs:2698`](../../core/dr-gpu/src/adjust.rs#L2698), [`core/dr-gpu/src/adjust.rs:2775`](../../core/dr-gpu/src/adjust.rs#L2775), [`core/dr-gpu/src/adjust.rs:2860`](../../core/dr-gpu/src/adjust.rs#L2860), [`core/dr-gpu/src/adjust.rs:54`](../../core/dr-gpu/src/adjust.rs#L54), [`core/dr-gpu/src/lib.rs:174`](../../core/dr-gpu/src/lib.rs#L174), [`core/dr-gpu/src/lib.rs:194`](../../core/dr-gpu/src/lib.rs#L194), [`core/dr-gpu/src/lib.rs:72`](../../core/dr-gpu/src/lib.rs#L72), [`core/dr-gpu/src/lib.rs:95`](../../core/dr-gpu/src/lib.rs#L95), [`core/dr-gpu/tests/capture_sharpen.rs:200`](../../core/dr-gpu/tests/capture_sharpen.rs#L200), [`core/dr-gpu/tests/detail_stage.rs:328`](../../core/dr-gpu/tests/detail_stage.rs#L328), [`core/dr-gpu/tests/local_contrast.rs:264`](../../core/dr-gpu/tests/local_contrast.rs#L264), [`core/dr-gpu/tests/noise_reduction.rs:378`](../../core/dr-gpu/tests/noise_reduction.rs#L378), [`core/dr-pipeline/src/detail.rs:136`](../../core/dr-pipeline/src/detail.rs#L136), [`core/dr-pipeline/src/detail.rs:542`](../../core/dr-pipeline/src/detail.rs#L542), [`core/dr-pipeline/src/graph.rs:922`](../../core/dr-pipeline/src/graph.rs#L922), [`core/dr-pipeline/src/graph.rs:952`](../../core/dr-pipeline/src/graph.rs#L952), [`core/dr-pipeline/src/ops/capture_sharpen.rs:1`](../../core/dr-pipeline/src/ops/capture_sharpen.rs#L1), [`core/dr-pipeline/src/ops/capture_sharpen.rs:659`](../../core/dr-pipeline/src/ops/capture_sharpen.rs#L659), [`core/dr-pipeline/src/ops/dehaze.rs:1`](../../core/dr-pipeline/src/ops/dehaze.rs#L1), [`core/dr-pipeline/src/ops/dehaze.rs:592`](../../core/dr-pipeline/src/ops/dehaze.rs#L592), [`core/dr-pipeline/src/ops/local_contrast.rs:1`](../../core/dr-pipeline/src/ops/local_contrast.rs#L1), [`core/dr-pipeline/src/ops/local_contrast.rs:971`](../../core/dr-pipeline/src/ops/local_contrast.rs#L971), [`core/dr-pipeline/src/ops/noise_reduction.rs:700`](../../core/dr-pipeline/src/ops/noise_reduction.rs#L700), [`core/dr-pipeline/src/spot.rs:673`](../../core/dr-pipeline/src/spot.rs#L673), [`ui/dr-ui/src/develop/framing.rs:594`](../../ui/dr-ui/src/develop/framing.rs#L594), [`ui/dr-ui/src/develop/mod.rs:55`](../../ui/dr-ui/src/develop/mod.rs#L55), [`ui/dr-ui/src/develop/render.rs:1014`](../../ui/dr-ui/src/develop/render.rs#L1014), [`ui/dr-ui/src/develop/render.rs:1152`](../../ui/dr-ui/src/develop/render.rs#L1152), [`ui/dr-ui/src/develop/render.rs:217`](../../ui/dr-ui/src/develop/render.rs#L217), [`ui/dr-ui/src/develop/render.rs:79`](../../ui/dr-ui/src/develop/render.rs#L79), [`ui/dr-ui/src/develop/render.rs:980`](../../ui/dr-ui/src/develop/render.rs#L980), [`ui/dr-ui/src/lib.rs:1060`](../../ui/dr-ui/src/lib.rs#L1060), [`ui/dr-ui/src/lib.rs:110`](../../ui/dr-ui/src/lib.rs#L110) | @@ -101,11 +101,11 @@ _None._ | FR-EXP-4 | [`core/dr-export/src/lib.rs:1`](../../core/dr-export/src/lib.rs#L1), [`core/dr-export/src/sharpen.rs:1`](../../core/dr-export/src/sharpen.rs#L1), [`core/dr-export/src/size.rs:1`](../../core/dr-export/src/size.rs#L1), [`ui/dr-ui/src/settings_ui.rs:1`](../../ui/dr-ui/src/settings_ui.rs#L1) | | FR-EXP-5 | [`ui/dr-ui/src/settings_store.rs:1`](../../ui/dr-ui/src/settings_store.rs#L1) | | FR-EXP-6 | [`core/dr-export/src/lib.rs:1`](../../core/dr-export/src/lib.rs#L1), [`core/dr-export/src/name.rs:1`](../../core/dr-export/src/name.rs#L1), [`core/dr-types/src/settings.rs:1`](../../core/dr-types/src/settings.rs#L1), [`ui/dr-ui/src/export.rs:1`](../../ui/dr-ui/src/export.rs#L1), [`ui/dr-ui/src/lib.rs:640`](../../ui/dr-ui/src/lib.rs#L640), [`ui/dr-ui/src/settings_ui.rs:1`](../../ui/dr-ui/src/settings_ui.rs#L1), [`ui/dr-ui/src/settings_ui.rs:49`](../../ui/dr-ui/src/settings_ui.rs#L49), [`ui/dr-ui/src/settings_ui.rs:941`](../../ui/dr-ui/src/settings_ui.rs#L941) | -| FR-EXP-7 | [`ui/dr-ui/src/activity.rs:83`](../../ui/dr-ui/src/activity.rs#L83), [`ui/dr-ui/src/develop_ui.rs:309`](../../ui/dr-ui/src/develop_ui.rs#L309), [`ui/dr-ui/src/export.rs:1024`](../../ui/dr-ui/src/export.rs#L1024), [`ui/dr-ui/src/export.rs:1`](../../ui/dr-ui/src/export.rs#L1), [`ui/dr-ui/src/lib.rs:287`](../../ui/dr-ui/src/lib.rs#L287), [`ui/dr-ui/src/lib.rs:640`](../../ui/dr-ui/src/lib.rs#L640), [`ui/dr-ui/src/lib.rs:686`](../../ui/dr-ui/src/lib.rs#L686), [`ui/dr-ui/src/lib.rs:714`](../../ui/dr-ui/src/lib.rs#L714), [`ui/dr-ui/src/library_ui/controller.rs:1022`](../../ui/dr-ui/src/library_ui/controller.rs#L1022), [`ui/dr-ui/src/library_ui/controller.rs:1034`](../../ui/dr-ui/src/library_ui/controller.rs#L1034), [`ui/dr-ui/src/library_ui/controller.rs:1046`](../../ui/dr-ui/src/library_ui/controller.rs#L1046), [`ui/dr-ui/src/library_ui/controller.rs:734`](../../ui/dr-ui/src/library_ui/controller.rs#L734), [`ui/dr-ui/src/library_ui/controller.rs:834`](../../ui/dr-ui/src/library_ui/controller.rs#L834), [`ui/dr-ui/src/library_ui/controller.rs:891`](../../ui/dr-ui/src/library_ui/controller.rs#L891), [`ui/dr-ui/src/library_ui/sync.rs:100`](../../ui/dr-ui/src/library_ui/sync.rs#L100), [`ui/dr-ui/src/settings_ui.rs:502`](../../ui/dr-ui/src/settings_ui.rs#L502), [`ui/dr-ui/ui/app.slint:1055`](../../ui/dr-ui/ui/app.slint#L1055), [`ui/dr-ui/ui/app.slint:1481`](../../ui/dr-ui/ui/app.slint#L1481), [`ui/dr-ui/ui/app.slint:2485`](../../ui/dr-ui/ui/app.slint#L2485), [`ui/dr-ui/ui/app.slint:3392`](../../ui/dr-ui/ui/app.slint#L3392), [`ui/dr-ui/ui/app.slint:652`](../../ui/dr-ui/ui/app.slint#L652), [`ui/dr-ui/ui/app.slint:669`](../../ui/dr-ui/ui/app.slint#L669), [`ui/dr-ui/ui/app.slint:905`](../../ui/dr-ui/ui/app.slint#L905), [`ui/dr-ui/ui/export.slint:1`](../../ui/dr-ui/ui/export.slint#L1), [`ui/dr-ui/ui/export.slint:384`](../../ui/dr-ui/ui/export.slint#L384), [`ui/dr-ui/ui/library.slint:2004`](../../ui/dr-ui/ui/library.slint#L2004), [`ui/dr-ui/ui/library.slint:2012`](../../ui/dr-ui/ui/library.slint#L2012), [`ui/dr-ui/ui/library.slint:3171`](../../ui/dr-ui/ui/library.slint#L3171), [`ui/dr-ui/ui/library.slint:5181`](../../ui/dr-ui/ui/library.slint#L5181) | +| FR-EXP-7 | [`ui/dr-ui/src/activity.rs:83`](../../ui/dr-ui/src/activity.rs#L83), [`ui/dr-ui/src/develop_ui.rs:309`](../../ui/dr-ui/src/develop_ui.rs#L309), [`ui/dr-ui/src/export.rs:1024`](../../ui/dr-ui/src/export.rs#L1024), [`ui/dr-ui/src/export.rs:1`](../../ui/dr-ui/src/export.rs#L1), [`ui/dr-ui/src/lib.rs:287`](../../ui/dr-ui/src/lib.rs#L287), [`ui/dr-ui/src/lib.rs:640`](../../ui/dr-ui/src/lib.rs#L640), [`ui/dr-ui/src/lib.rs:686`](../../ui/dr-ui/src/lib.rs#L686), [`ui/dr-ui/src/lib.rs:714`](../../ui/dr-ui/src/lib.rs#L714), [`ui/dr-ui/src/library_ui/controller.rs:1022`](../../ui/dr-ui/src/library_ui/controller.rs#L1022), [`ui/dr-ui/src/library_ui/controller.rs:1034`](../../ui/dr-ui/src/library_ui/controller.rs#L1034), [`ui/dr-ui/src/library_ui/controller.rs:1046`](../../ui/dr-ui/src/library_ui/controller.rs#L1046), [`ui/dr-ui/src/library_ui/controller.rs:734`](../../ui/dr-ui/src/library_ui/controller.rs#L734), [`ui/dr-ui/src/library_ui/controller.rs:834`](../../ui/dr-ui/src/library_ui/controller.rs#L834), [`ui/dr-ui/src/library_ui/controller.rs:891`](../../ui/dr-ui/src/library_ui/controller.rs#L891), [`ui/dr-ui/src/library_ui/sync.rs:100`](../../ui/dr-ui/src/library_ui/sync.rs#L100), [`ui/dr-ui/src/settings_ui.rs:502`](../../ui/dr-ui/src/settings_ui.rs#L502), [`ui/dr-ui/ui/app.slint:1055`](../../ui/dr-ui/ui/app.slint#L1055), [`ui/dr-ui/ui/app.slint:1481`](../../ui/dr-ui/ui/app.slint#L1481), [`ui/dr-ui/ui/app.slint:2485`](../../ui/dr-ui/ui/app.slint#L2485), [`ui/dr-ui/ui/app.slint:3392`](../../ui/dr-ui/ui/app.slint#L3392), [`ui/dr-ui/ui/app.slint:652`](../../ui/dr-ui/ui/app.slint#L652), [`ui/dr-ui/ui/app.slint:669`](../../ui/dr-ui/ui/app.slint#L669), [`ui/dr-ui/ui/app.slint:905`](../../ui/dr-ui/ui/app.slint#L905), [`ui/dr-ui/ui/export.slint:1`](../../ui/dr-ui/ui/export.slint#L1), [`ui/dr-ui/ui/export.slint:384`](../../ui/dr-ui/ui/export.slint#L384), [`ui/dr-ui/ui/library.slint:2004`](../../ui/dr-ui/ui/library.slint#L2004), [`ui/dr-ui/ui/library.slint:2012`](../../ui/dr-ui/ui/library.slint#L2012), [`ui/dr-ui/ui/library.slint:3171`](../../ui/dr-ui/ui/library.slint#L3171), [`ui/dr-ui/ui/library.slint:5199`](../../ui/dr-ui/ui/library.slint#L5199) | | FR-EXP-8 | [`core/dr-decode/src/lib.rs:372`](../../core/dr-decode/src/lib.rs#L372), [`core/dr-decode/src/lib.rs:396`](../../core/dr-decode/src/lib.rs#L396), [`core/dr-decode/src/lib.rs:410`](../../core/dr-decode/src/lib.rs#L410), [`core/dr-decode/src/lib.rs:77`](../../core/dr-decode/src/lib.rs#L77), [`core/dr-decode/src/lib.rs:85`](../../core/dr-decode/src/lib.rs#L85), [`core/dr-decode/src/lib.rs:88`](../../core/dr-decode/src/lib.rs#L88), [`core/dr-decode/src/locate.rs:1233`](../../core/dr-decode/src/locate.rs#L1233), [`core/dr-decode/src/locate.rs:1292`](../../core/dr-decode/src/locate.rs#L1292), [`core/dr-decode/src/locate.rs:359`](../../core/dr-decode/src/locate.rs#L359), [`core/dr-decode/src/locate.rs:556`](../../core/dr-decode/src/locate.rs#L556), [`core/dr-decode/src/locate.rs:640`](../../core/dr-decode/src/locate.rs#L640), [`core/dr-decode/src/locate.rs:653`](../../core/dr-decode/src/locate.rs#L653), [`core/dr-decode/src/locate.rs:736`](../../core/dr-decode/src/locate.rs#L736), [`core/dr-export/examples/export.rs:99`](../../core/dr-export/examples/export.rs#L99), [`core/dr-export/src/encode.rs:117`](../../core/dr-export/src/encode.rs#L117), [`core/dr-export/src/encode.rs:161`](../../core/dr-export/src/encode.rs#L161), [`core/dr-export/src/encode.rs:1`](../../core/dr-export/src/encode.rs#L1), [`core/dr-export/src/encode.rs:206`](../../core/dr-export/src/encode.rs#L206), [`core/dr-export/src/encode.rs:235`](../../core/dr-export/src/encode.rs#L235), [`core/dr-export/src/encode.rs:311`](../../core/dr-export/src/encode.rs#L311), [`core/dr-export/src/encode.rs:325`](../../core/dr-export/src/encode.rs#L325), [`core/dr-export/src/encode.rs:408`](../../core/dr-export/src/encode.rs#L408), [`core/dr-export/src/encode.rs:456`](../../core/dr-export/src/encode.rs#L456), [`core/dr-export/src/encode.rs:70`](../../core/dr-export/src/encode.rs#L70), [`core/dr-export/src/encode.rs:795`](../../core/dr-export/src/encode.rs#L795), [`core/dr-export/src/encode.rs:809`](../../core/dr-export/src/encode.rs#L809), [`core/dr-export/src/encode.rs:850`](../../core/dr-export/src/encode.rs#L850), [`core/dr-export/src/encode.rs:898`](../../core/dr-export/src/encode.rs#L898), [`core/dr-export/src/exif.rs:1`](../../core/dr-export/src/exif.rs#L1), [`core/dr-export/src/lib.rs:140`](../../core/dr-export/src/lib.rs#L140), [`core/dr-export/src/metadata.rs:1`](../../core/dr-export/src/metadata.rs#L1), [`core/dr-export/src/metadata.rs:41`](../../core/dr-export/src/metadata.rs#L41), [`core/dr-export/src/metadata.rs:74`](../../core/dr-export/src/metadata.rs#L74), [`core/dr-types/src/lib.rs:655`](../../core/dr-types/src/lib.rs#L655), [`core/dr-types/src/settings.rs:671`](../../core/dr-types/src/settings.rs#L671), [`ui/dr-ui/src/decoder_seam.rs:256`](../../ui/dr-ui/src/decoder_seam.rs#L256), [`ui/dr-ui/src/develop/session.rs:37`](../../ui/dr-ui/src/develop/session.rs#L37), [`ui/dr-ui/src/develop/session.rs:417`](../../ui/dr-ui/src/develop/session.rs#L417), [`ui/dr-ui/src/develop/session.rs:529`](../../ui/dr-ui/src/develop/session.rs#L529), [`ui/dr-ui/src/export.rs:475`](../../ui/dr-ui/src/export.rs#L475), [`ui/dr-ui/src/export.rs:646`](../../ui/dr-ui/src/export.rs#L646), [`ui/dr-ui/src/export.rs:692`](../../ui/dr-ui/src/export.rs#L692), [`ui/dr-ui/src/export.rs:720`](../../ui/dr-ui/src/export.rs#L720), [`ui/dr-ui/src/export.rs:757`](../../ui/dr-ui/src/export.rs#L757), [`ui/dr-ui/src/export.rs:858`](../../ui/dr-ui/src/export.rs#L858), [`ui/dr-ui/src/export.rs:876`](../../ui/dr-ui/src/export.rs#L876), [`ui/dr-ui/src/lib.rs:287`](../../ui/dr-ui/src/lib.rs#L287), [`ui/dr-ui/src/lib.rs:311`](../../ui/dr-ui/src/lib.rs#L311), [`ui/dr-ui/src/lib.rs:672`](../../ui/dr-ui/src/lib.rs#L672), [`ui/dr-ui/src/settings_ui.rs:1`](../../ui/dr-ui/src/settings_ui.rs#L1) | | FR-EXP-9 | [`core/dr-decode/src/lib.rs:552`](../../core/dr-decode/src/lib.rs#L552), [`core/dr-export/src/lib.rs:132`](../../core/dr-export/src/lib.rs#L132), [`core/dr-export/src/lib.rs:1`](../../core/dr-export/src/lib.rs#L1), [`core/dr-gpu/src/adjust.rs:1400`](../../core/dr-gpu/src/adjust.rs#L1400), [`ui/dr-ui/examples/face_native.rs:1`](../../ui/dr-ui/examples/face_native.rs#L1), [`ui/dr-ui/src/decoder_seam.rs:256`](../../ui/dr-ui/src/decoder_seam.rs#L256), [`ui/dr-ui/src/develop/mask_ops.rs:1130`](../../ui/dr-ui/src/develop/mask_ops.rs#L1130), [`ui/dr-ui/src/develop/render.rs:586`](../../ui/dr-ui/src/develop/render.rs#L586), [`ui/dr-ui/src/develop/render.rs:624`](../../ui/dr-ui/src/develop/render.rs#L624), [`ui/dr-ui/src/lib.rs:640`](../../ui/dr-ui/src/lib.rs#L640), [`ui/dr-ui/src/library/sweep.rs:510`](../../ui/dr-ui/src/library/sweep.rs#L510), [`ui/dr-ui/tests/export_ignores_the_viewport.rs:1`](../../ui/dr-ui/tests/export_ignores_the_viewport.rs#L1) | | FR-INF-1 | [`ui/dr-ui/src/lib.rs:1436`](../../ui/dr-ui/src/lib.rs#L1436), [`ui/dr-ui/ui/settings.slint:824`](../../ui/dr-ui/ui/settings.slint#L824) | -| FR-MRG-1 | [`core/dr-pano/src/align.rs:1`](../../core/dr-pano/src/align.rs#L1), [`core/dr-pano/src/lib.rs:1`](../../core/dr-pano/src/lib.rs#L1), [`ui/dr-ui/src/lib.rs:1844`](../../ui/dr-ui/src/lib.rs#L1844), [`ui/dr-ui/src/merge.rs:1`](../../ui/dr-ui/src/merge.rs#L1), [`ui/dr-ui/src/merge_ui.rs:1`](../../ui/dr-ui/src/merge_ui.rs#L1), [`ui/dr-ui/ui/app.slint:1486`](../../ui/dr-ui/ui/app.slint#L1486), [`ui/dr-ui/ui/app.slint:557`](../../ui/dr-ui/ui/app.slint#L557), [`ui/dr-ui/ui/library.slint:2017`](../../ui/dr-ui/ui/library.slint#L2017), [`ui/dr-ui/ui/library.slint:5138`](../../ui/dr-ui/ui/library.slint#L5138), [`ui/dr-ui/ui/merge.slint:1`](../../ui/dr-ui/ui/merge.slint#L1) | +| FR-MRG-1 | [`core/dr-pano/src/align.rs:1`](../../core/dr-pano/src/align.rs#L1), [`core/dr-pano/src/lib.rs:1`](../../core/dr-pano/src/lib.rs#L1), [`ui/dr-ui/src/lib.rs:1844`](../../ui/dr-ui/src/lib.rs#L1844), [`ui/dr-ui/src/merge.rs:1`](../../ui/dr-ui/src/merge.rs#L1), [`ui/dr-ui/src/merge_ui.rs:1`](../../ui/dr-ui/src/merge_ui.rs#L1), [`ui/dr-ui/ui/app.slint:1486`](../../ui/dr-ui/ui/app.slint#L1486), [`ui/dr-ui/ui/app.slint:557`](../../ui/dr-ui/ui/app.slint#L557), [`ui/dr-ui/ui/library.slint:2017`](../../ui/dr-ui/ui/library.slint#L2017), [`ui/dr-ui/ui/library.slint:5156`](../../ui/dr-ui/ui/library.slint#L5156), [`ui/dr-ui/ui/merge.slint:1`](../../ui/dr-ui/ui/merge.slint#L1) | | FR-MRG-10 | [`core/dr-gpu/src/merge.rs:1`](../../core/dr-gpu/src/merge.rs#L1), [`core/dr-gpu/src/shaders/merge.wgsl:1`](../../core/dr-gpu/src/shaders/merge.wgsl#L1), [`core/dr-pano/src/lib.rs:1`](../../core/dr-pano/src/lib.rs#L1) | | FR-MRG-11 | [`core/dr-gpu/src/merge.rs:1`](../../core/dr-gpu/src/merge.rs#L1), [`core/dr-gpu/src/shaders/merge.wgsl:1`](../../core/dr-gpu/src/shaders/merge.wgsl#L1) | | FR-MRG-2 | [`core/dr-gpu/src/adjust.rs:104`](../../core/dr-gpu/src/adjust.rs#L104), [`core/dr-gpu/src/adjust.rs:1423`](../../core/dr-gpu/src/adjust.rs#L1423), [`core/dr-gpu/src/adjust.rs:1552`](../../core/dr-gpu/src/adjust.rs#L1552), [`core/dr-gpu/src/adjust.rs:518`](../../core/dr-gpu/src/adjust.rs#L518), [`core/dr-pipeline/src/graph.rs:852`](../../core/dr-pipeline/src/graph.rs#L852), [`core/dr-pipeline/src/operation.rs:2255`](../../core/dr-pipeline/src/operation.rs#L2255), [`core/dr-pipeline/src/operation.rs:427`](../../core/dr-pipeline/src/operation.rs#L427), [`core/dr-pipeline/src/operation.rs:644`](../../core/dr-pipeline/src/operation.rs#L644), [`core/dr-pipeline/src/operation.rs:956`](../../core/dr-pipeline/src/operation.rs#L956), [`ui/dr-ui/src/merge.rs:1`](../../ui/dr-ui/src/merge.rs#L1) | @@ -151,15 +151,15 @@ _None._ | FR-UI-1 | [`core/dr-types/src/settings.rs:1377`](../../core/dr-types/src/settings.rs#L1377), [`core/dr-types/src/settings.rs:492`](../../core/dr-types/src/settings.rs#L492), [`core/dr-types/src/settings.rs:512`](../../core/dr-types/src/settings.rs#L512), [`ui/dr-ui/src/develop_ui.rs:204`](../../ui/dr-ui/src/develop_ui.rs#L204), [`ui/dr-ui/src/lib.rs:118`](../../ui/dr-ui/src/lib.rs#L118), [`ui/dr-ui/src/lib.rs:133`](../../ui/dr-ui/src/lib.rs#L133), [`ui/dr-ui/src/lib.rs:155`](../../ui/dr-ui/src/lib.rs#L155), [`ui/dr-ui/src/lib.rs:3220`](../../ui/dr-ui/src/lib.rs#L3220), [`ui/dr-ui/src/lib.rs:3239`](../../ui/dr-ui/src/lib.rs#L3239), [`ui/dr-ui/src/lib.rs:3266`](../../ui/dr-ui/src/lib.rs#L3266), [`ui/dr-ui/src/lib.rs:3317`](../../ui/dr-ui/src/lib.rs#L3317), [`ui/dr-ui/src/lib.rs:3350`](../../ui/dr-ui/src/lib.rs#L3350), [`ui/dr-ui/src/library_ui/grid.rs:232`](../../ui/dr-ui/src/library_ui/grid.rs#L232), [`ui/dr-ui/src/masks_ui.rs:1608`](../../ui/dr-ui/src/masks_ui.rs#L1608), [`ui/dr-ui/src/settings_ui.rs:179`](../../ui/dr-ui/src/settings_ui.rs#L179), [`ui/dr-ui/ui/adjust.slint:1000`](../../ui/dr-ui/ui/adjust.slint#L1000), [`ui/dr-ui/ui/app.slint:3031`](../../ui/dr-ui/ui/app.slint#L3031), [`ui/dr-ui/ui/app.slint:631`](../../ui/dr-ui/ui/app.slint#L631), [`ui/dr-ui/ui/app.slint:776`](../../ui/dr-ui/ui/app.slint#L776), [`ui/dr-ui/ui/identity.slint:249`](../../ui/dr-ui/ui/identity.slint#L249), [`ui/dr-ui/ui/library.slint:1442`](../../ui/dr-ui/ui/library.slint#L1442), [`ui/dr-ui/ui/settings.slint:129`](../../ui/dr-ui/ui/settings.slint#L129), [`ui/dr-ui/ui/settings.slint:660`](../../ui/dr-ui/ui/settings.slint#L660), [`ui/dr-ui/ui/toolrail.slint:156`](../../ui/dr-ui/ui/toolrail.slint#L156), [`ui/dr-ui/ui/toolrail.slint:89`](../../ui/dr-ui/ui/toolrail.slint#L89), [`ui/dr-ui/ui/widgets.slint:1060`](../../ui/dr-ui/ui/widgets.slint#L1060) | | FR-UI-2 | [`ui/dr-ui/src/collections_ui/controller.rs:45`](../../ui/dr-ui/src/collections_ui/controller.rs#L45), [`ui/dr-ui/src/collections_ui/controller.rs:83`](../../ui/dr-ui/src/collections_ui/controller.rs#L83), [`ui/dr-ui/src/collections_ui/drag.rs:246`](../../ui/dr-ui/src/collections_ui/drag.rs#L246), [`ui/dr-ui/src/collections_ui/drag.rs:256`](../../ui/dr-ui/src/collections_ui/drag.rs#L256), [`ui/dr-ui/src/collections_ui/press.rs:453`](../../ui/dr-ui/src/collections_ui/press.rs#L453), [`ui/dr-ui/src/collections_ui/wiring_grid.rs:121`](../../ui/dr-ui/src/collections_ui/wiring_grid.rs#L121), [`ui/dr-ui/src/collections_ui/wiring_grid.rs:138`](../../ui/dr-ui/src/collections_ui/wiring_grid.rs#L138), [`ui/dr-ui/src/collections_ui/wiring_grid.rs:415`](../../ui/dr-ui/src/collections_ui/wiring_grid.rs#L415), [`ui/dr-ui/src/collections_ui/wiring_tree.rs:287`](../../ui/dr-ui/src/collections_ui/wiring_tree.rs#L287), [`ui/dr-ui/src/gestures.rs:1`](../../ui/dr-ui/src/gestures.rs#L1), [`ui/dr-ui/src/lib.rs:118`](../../ui/dr-ui/src/lib.rs#L118), [`ui/dr-ui/src/library_ui/filter_bar.rs:351`](../../ui/dr-ui/src/library_ui/filter_bar.rs#L351), [`ui/dr-ui/src/library_ui/window.rs:981`](../../ui/dr-ui/src/library_ui/window.rs#L981), [`ui/dr-ui/ui/app.slint:134`](../../ui/dr-ui/ui/app.slint#L134), [`ui/dr-ui/ui/app.slint:3427`](../../ui/dr-ui/ui/app.slint#L3427), [`ui/dr-ui/ui/app.slint:760`](../../ui/dr-ui/ui/app.slint#L760), [`ui/dr-ui/ui/collections.slint:1340`](../../ui/dr-ui/ui/collections.slint#L1340), [`ui/dr-ui/ui/controls.slint:626`](../../ui/dr-ui/ui/controls.slint#L626), [`ui/dr-ui/ui/gestures.slint:1`](../../ui/dr-ui/ui/gestures.slint#L1), [`ui/dr-ui/ui/library.slint:1329`](../../ui/dr-ui/ui/library.slint#L1329), [`ui/dr-ui/ui/library.slint:1362`](../../ui/dr-ui/ui/library.slint#L1362), [`ui/dr-ui/ui/library.slint:1686`](../../ui/dr-ui/ui/library.slint#L1686), [`ui/dr-ui/ui/library.slint:1693`](../../ui/dr-ui/ui/library.slint#L1693), [`ui/dr-ui/ui/library.slint:1794`](../../ui/dr-ui/ui/library.slint#L1794), [`ui/dr-ui/ui/library.slint:3129`](../../ui/dr-ui/ui/library.slint#L3129), [`ui/dr-ui/ui/library.slint:567`](../../ui/dr-ui/ui/library.slint#L567), [`ui/dr-ui/ui/presets.slint:19`](../../ui/dr-ui/ui/presets.slint#L19), [`ui/dr-ui/ui/settings.slint:143`](../../ui/dr-ui/ui/settings.slint#L143) | | FR-UI-3 | [`ui/dr-ui/src/collections_ui/controller.rs:138`](../../ui/dr-ui/src/collections_ui/controller.rs#L138), [`ui/dr-ui/src/collections_ui/controller.rs:343`](../../ui/dr-ui/src/collections_ui/controller.rs#L343), [`ui/dr-ui/src/collections_ui/controller.rs:356`](../../ui/dr-ui/src/collections_ui/controller.rs#L356), [`ui/dr-ui/src/collections_ui/wiring_tree.rs:287`](../../ui/dr-ui/src/collections_ui/wiring_tree.rs#L287), [`ui/dr-ui/src/develop/masks.rs:361`](../../ui/dr-ui/src/develop/masks.rs#L361), [`ui/dr-ui/src/develop/repairs.rs:49`](../../ui/dr-ui/src/develop/repairs.rs#L49), [`ui/dr-ui/src/library_ui/grid.rs:218`](../../ui/dr-ui/src/library_ui/grid.rs#L218), [`ui/dr-ui/src/masks_ui.rs:1700`](../../ui/dr-ui/src/masks_ui.rs#L1700), [`ui/dr-ui/src/masks_ui.rs:1722`](../../ui/dr-ui/src/masks_ui.rs#L1722), [`ui/dr-ui/src/masks_ui.rs:339`](../../ui/dr-ui/src/masks_ui.rs#L339), [`ui/dr-ui/src/spots_ui.rs:19`](../../ui/dr-ui/src/spots_ui.rs#L19), [`ui/dr-ui/ui/app.slint:2235`](../../ui/dr-ui/ui/app.slint#L2235), [`ui/dr-ui/ui/collections.slint:1214`](../../ui/dr-ui/ui/collections.slint#L1214), [`ui/dr-ui/ui/collections.slint:4`](../../ui/dr-ui/ui/collections.slint#L4), [`ui/dr-ui/ui/collections.slint:77`](../../ui/dr-ui/ui/collections.slint#L77), [`ui/dr-ui/ui/collections.slint:964`](../../ui/dr-ui/ui/collections.slint#L964), [`ui/dr-ui/ui/masks.slint:1246`](../../ui/dr-ui/ui/masks.slint#L1246), [`ui/dr-ui/ui/widgets.slint:72`](../../ui/dr-ui/ui/widgets.slint#L72) | -| FR-UI-4 | [`tools/traceability/src/gestures.rs:1`](../../tools/traceability/src/gestures.rs#L1), [`tools/traceability/src/keymap.rs:1`](../../tools/traceability/src/keymap.rs#L1), [`ui/dr-ui/src/collections_ui/controller.rs:138`](../../ui/dr-ui/src/collections_ui/controller.rs#L138), [`ui/dr-ui/src/collections_ui/controller.rs:343`](../../ui/dr-ui/src/collections_ui/controller.rs#L343), [`ui/dr-ui/src/collections_ui/controller.rs:356`](../../ui/dr-ui/src/collections_ui/controller.rs#L356), [`ui/dr-ui/src/collections_ui/controller.rs:45`](../../ui/dr-ui/src/collections_ui/controller.rs#L45), [`ui/dr-ui/src/collections_ui/controller.rs:46`](../../ui/dr-ui/src/collections_ui/controller.rs#L46), [`ui/dr-ui/src/collections_ui/controller.rs:55`](../../ui/dr-ui/src/collections_ui/controller.rs#L55), [`ui/dr-ui/src/collections_ui/controller.rs:83`](../../ui/dr-ui/src/collections_ui/controller.rs#L83), [`ui/dr-ui/src/collections_ui/drag.rs:246`](../../ui/dr-ui/src/collections_ui/drag.rs#L246), [`ui/dr-ui/src/collections_ui/drag.rs:256`](../../ui/dr-ui/src/collections_ui/drag.rs#L256), [`ui/dr-ui/src/collections_ui/press.rs:258`](../../ui/dr-ui/src/collections_ui/press.rs#L258), [`ui/dr-ui/src/collections_ui/press.rs:278`](../../ui/dr-ui/src/collections_ui/press.rs#L278), [`ui/dr-ui/src/collections_ui/press.rs:453`](../../ui/dr-ui/src/collections_ui/press.rs#L453), [`ui/dr-ui/src/collections_ui/press.rs:490`](../../ui/dr-ui/src/collections_ui/press.rs#L490), [`ui/dr-ui/src/collections_ui/press.rs:54`](../../ui/dr-ui/src/collections_ui/press.rs#L54), [`ui/dr-ui/src/collections_ui/tree_sync.rs:339`](../../ui/dr-ui/src/collections_ui/tree_sync.rs#L339), [`ui/dr-ui/src/collections_ui/wiring_grid.rs:121`](../../ui/dr-ui/src/collections_ui/wiring_grid.rs#L121), [`ui/dr-ui/src/collections_ui/wiring_grid.rs:138`](../../ui/dr-ui/src/collections_ui/wiring_grid.rs#L138), [`ui/dr-ui/src/collections_ui/wiring_grid.rs:264`](../../ui/dr-ui/src/collections_ui/wiring_grid.rs#L264), [`ui/dr-ui/src/collections_ui/wiring_grid.rs:415`](../../ui/dr-ui/src/collections_ui/wiring_grid.rs#L415), [`ui/dr-ui/src/collections_ui/wiring_grid.rs:444`](../../ui/dr-ui/src/collections_ui/wiring_grid.rs#L444), [`ui/dr-ui/src/collections_ui/wiring_grid.rs:577`](../../ui/dr-ui/src/collections_ui/wiring_grid.rs#L577), [`ui/dr-ui/src/collections_ui/wiring_tree.rs:287`](../../ui/dr-ui/src/collections_ui/wiring_tree.rs#L287), [`ui/dr-ui/src/collections_ui/wiring_tree.rs:344`](../../ui/dr-ui/src/collections_ui/wiring_tree.rs#L344), [`ui/dr-ui/src/develop/framing.rs:257`](../../ui/dr-ui/src/develop/framing.rs#L257), [`ui/dr-ui/src/develop/framing.rs:594`](../../ui/dr-ui/src/develop/framing.rs#L594), [`ui/dr-ui/src/develop/framing.rs:619`](../../ui/dr-ui/src/develop/framing.rs#L619), [`ui/dr-ui/src/develop/framing.rs:630`](../../ui/dr-ui/src/develop/framing.rs#L630), [`ui/dr-ui/src/develop/framing.rs:644`](../../ui/dr-ui/src/develop/framing.rs#L644), [`ui/dr-ui/src/develop/framing.rs:818`](../../ui/dr-ui/src/develop/framing.rs#L818), [`ui/dr-ui/src/develop/framing.rs:849`](../../ui/dr-ui/src/develop/framing.rs#L849), [`ui/dr-ui/src/develop/framing.rs:870`](../../ui/dr-ui/src/develop/framing.rs#L870), [`ui/dr-ui/src/develop/framing.rs:995`](../../ui/dr-ui/src/develop/framing.rs#L995), [`ui/dr-ui/src/develop/render.rs:1065`](../../ui/dr-ui/src/develop/render.rs#L1065), [`ui/dr-ui/src/develop/render.rs:1090`](../../ui/dr-ui/src/develop/render.rs#L1090), [`ui/dr-ui/src/develop/render.rs:1113`](../../ui/dr-ui/src/develop/render.rs#L1113), [`ui/dr-ui/src/develop/render.rs:1152`](../../ui/dr-ui/src/develop/render.rs#L1152), [`ui/dr-ui/src/develop/render.rs:47`](../../ui/dr-ui/src/develop/render.rs#L47), [`ui/dr-ui/src/develop/render.rs:66`](../../ui/dr-ui/src/develop/render.rs#L66), [`ui/dr-ui/src/develop/render.rs:79`](../../ui/dr-ui/src/develop/render.rs#L79), [`ui/dr-ui/src/develop_ui.rs:852`](../../ui/dr-ui/src/develop_ui.rs#L852), [`ui/dr-ui/src/duplicates_ui.rs:1`](../../ui/dr-ui/src/duplicates_ui.rs#L1), [`ui/dr-ui/src/gesture_book.rs:3`](../../ui/dr-ui/src/gesture_book.rs#L3), [`ui/dr-ui/src/gestures.rs:127`](../../ui/dr-ui/src/gestures.rs#L127), [`ui/dr-ui/src/gestures.rs:139`](../../ui/dr-ui/src/gestures.rs#L139), [`ui/dr-ui/src/gestures.rs:1`](../../ui/dr-ui/src/gestures.rs#L1), [`ui/dr-ui/src/lib.rs:1281`](../../ui/dr-ui/src/lib.rs#L1281), [`ui/dr-ui/src/lib.rs:1572`](../../ui/dr-ui/src/lib.rs#L1572), [`ui/dr-ui/src/lib.rs:2592`](../../ui/dr-ui/src/lib.rs#L2592), [`ui/dr-ui/src/lib.rs:2811`](../../ui/dr-ui/src/lib.rs#L2811), [`ui/dr-ui/src/lib.rs:2871`](../../ui/dr-ui/src/lib.rs#L2871), [`ui/dr-ui/src/lib.rs:3030`](../../ui/dr-ui/src/lib.rs#L3030), [`ui/dr-ui/src/lib.rs:3432`](../../ui/dr-ui/src/lib.rs#L3432), [`ui/dr-ui/src/lib.rs:3476`](../../ui/dr-ui/src/lib.rs#L3476), [`ui/dr-ui/src/lib.rs:486`](../../ui/dr-ui/src/lib.rs#L486), [`ui/dr-ui/src/lib.rs:515`](../../ui/dr-ui/src/lib.rs#L515), [`ui/dr-ui/src/library/thumbnails_fetch.rs:718`](../../ui/dr-ui/src/library/thumbnails_fetch.rs#L718), [`ui/dr-ui/src/library/thumbnails_fetch.rs:733`](../../ui/dr-ui/src/library/thumbnails_fetch.rs#L733), [`ui/dr-ui/src/library_ui/controller.rs:398`](../../ui/dr-ui/src/library_ui/controller.rs#L398), [`ui/dr-ui/src/library_ui/controller.rs:540`](../../ui/dr-ui/src/library_ui/controller.rs#L540), [`ui/dr-ui/src/library_ui/controller.rs:617`](../../ui/dr-ui/src/library_ui/controller.rs#L617), [`ui/dr-ui/src/library_ui/filter_bar.rs:500`](../../ui/dr-ui/src/library_ui/filter_bar.rs#L500), [`ui/dr-ui/src/library_ui/grid.rs:218`](../../ui/dr-ui/src/library_ui/grid.rs#L218), [`ui/dr-ui/src/library_ui/grid.rs:241`](../../ui/dr-ui/src/library_ui/grid.rs#L241), [`ui/dr-ui/src/library_ui/grid.rs:262`](../../ui/dr-ui/src/library_ui/grid.rs#L262), [`ui/dr-ui/src/library_ui/grid.rs:336`](../../ui/dr-ui/src/library_ui/grid.rs#L336), [`ui/dr-ui/src/library_ui/grid.rs:479`](../../ui/dr-ui/src/library_ui/grid.rs#L479), [`ui/dr-ui/src/library_ui/grid.rs:509`](../../ui/dr-ui/src/library_ui/grid.rs#L509), [`ui/dr-ui/src/library_ui/ratings_keywords.rs:1168`](../../ui/dr-ui/src/library_ui/ratings_keywords.rs#L1168), [`ui/dr-ui/src/manual.rs:1`](../../ui/dr-ui/src/manual.rs#L1), [`ui/dr-ui/src/settings_ui.rs:157`](../../ui/dr-ui/src/settings_ui.rs#L157), [`ui/dr-ui/src/settings_ui.rs:616`](../../ui/dr-ui/src/settings_ui.rs#L616), [`ui/dr-ui/ui/app.slint:1771`](../../ui/dr-ui/ui/app.slint#L1771), [`ui/dr-ui/ui/app.slint:1923`](../../ui/dr-ui/ui/app.slint#L1923), [`ui/dr-ui/ui/app.slint:2049`](../../ui/dr-ui/ui/app.slint#L2049), [`ui/dr-ui/ui/app.slint:2335`](../../ui/dr-ui/ui/app.slint#L2335), [`ui/dr-ui/ui/app.slint:235`](../../ui/dr-ui/ui/app.slint#L235), [`ui/dr-ui/ui/app.slint:2497`](../../ui/dr-ui/ui/app.slint#L2497), [`ui/dr-ui/ui/app.slint:2527`](../../ui/dr-ui/ui/app.slint#L2527), [`ui/dr-ui/ui/app.slint:2672`](../../ui/dr-ui/ui/app.slint#L2672), [`ui/dr-ui/ui/app.slint:2872`](../../ui/dr-ui/ui/app.slint#L2872), [`ui/dr-ui/ui/app.slint:3427`](../../ui/dr-ui/ui/app.slint#L3427), [`ui/dr-ui/ui/app.slint:3454`](../../ui/dr-ui/ui/app.slint#L3454), [`ui/dr-ui/ui/app.slint:663`](../../ui/dr-ui/ui/app.slint#L663), [`ui/dr-ui/ui/collections.slint:1340`](../../ui/dr-ui/ui/collections.slint#L1340), [`ui/dr-ui/ui/collections.slint:77`](../../ui/dr-ui/ui/collections.slint#L77), [`ui/dr-ui/ui/develop.slint:142`](../../ui/dr-ui/ui/develop.slint#L142), [`ui/dr-ui/ui/develop.slint:332`](../../ui/dr-ui/ui/develop.slint#L332), [`ui/dr-ui/ui/develop.slint:66`](../../ui/dr-ui/ui/develop.slint#L66), [`ui/dr-ui/ui/duplicates.slint:1`](../../ui/dr-ui/ui/duplicates.slint#L1), [`ui/dr-ui/ui/gestures.slint:1`](../../ui/dr-ui/ui/gestures.slint#L1), [`ui/dr-ui/ui/icons.slint:367`](../../ui/dr-ui/ui/icons.slint#L367), [`ui/dr-ui/ui/labels.slint:74`](../../ui/dr-ui/ui/labels.slint#L74), [`ui/dr-ui/ui/library.slint:1329`](../../ui/dr-ui/ui/library.slint#L1329), [`ui/dr-ui/ui/library.slint:1356`](../../ui/dr-ui/ui/library.slint#L1356), [`ui/dr-ui/ui/library.slint:1362`](../../ui/dr-ui/ui/library.slint#L1362), [`ui/dr-ui/ui/library.slint:1417`](../../ui/dr-ui/ui/library.slint#L1417), [`ui/dr-ui/ui/library.slint:1612`](../../ui/dr-ui/ui/library.slint#L1612), [`ui/dr-ui/ui/library.slint:1663`](../../ui/dr-ui/ui/library.slint#L1663), [`ui/dr-ui/ui/library.slint:1686`](../../ui/dr-ui/ui/library.slint#L1686), [`ui/dr-ui/ui/library.slint:1693`](../../ui/dr-ui/ui/library.slint#L1693), [`ui/dr-ui/ui/library.slint:1729`](../../ui/dr-ui/ui/library.slint#L1729), [`ui/dr-ui/ui/library.slint:1787`](../../ui/dr-ui/ui/library.slint#L1787), [`ui/dr-ui/ui/library.slint:1794`](../../ui/dr-ui/ui/library.slint#L1794), [`ui/dr-ui/ui/library.slint:3129`](../../ui/dr-ui/ui/library.slint#L3129), [`ui/dr-ui/ui/library.slint:3234`](../../ui/dr-ui/ui/library.slint#L3234), [`ui/dr-ui/ui/library.slint:3385`](../../ui/dr-ui/ui/library.slint#L3385), [`ui/dr-ui/ui/library.slint:5014`](../../ui/dr-ui/ui/library.slint#L5014), [`ui/dr-ui/ui/library.slint:5052`](../../ui/dr-ui/ui/library.slint#L5052), [`ui/dr-ui/ui/library.slint:5156`](../../ui/dr-ui/ui/library.slint#L5156), [`ui/dr-ui/ui/library.slint:562`](../../ui/dr-ui/ui/library.slint#L562), [`ui/dr-ui/ui/library.slint:572`](../../ui/dr-ui/ui/library.slint#L572), [`ui/dr-ui/ui/library.slint:666`](../../ui/dr-ui/ui/library.slint#L666), [`ui/dr-ui/ui/library.slint:735`](../../ui/dr-ui/ui/library.slint#L735), [`ui/dr-ui/ui/settings.slint:121`](../../ui/dr-ui/ui/settings.slint#L121), [`ui/dr-ui/ui/settings.slint:172`](../../ui/dr-ui/ui/settings.slint#L172), [`ui/dr-ui/ui/settings.slint:635`](../../ui/dr-ui/ui/settings.slint#L635) | -| FR-UI-5 | [`core/dr-types/src/place.rs:118`](../../core/dr-types/src/place.rs#L118), [`tools/traceability/src/chord.rs:1`](../../tools/traceability/src/chord.rs#L1), [`tools/traceability/src/keymap.rs:1`](../../tools/traceability/src/keymap.rs#L1), [`ui/dr-ui/src/collections_ui/mod.rs:1`](../../ui/dr-ui/src/collections_ui/mod.rs#L1), [`ui/dr-ui/src/develop_ui.rs:897`](../../ui/dr-ui/src/develop_ui.rs#L897), [`ui/dr-ui/src/gestures.rs:139`](../../ui/dr-ui/src/gestures.rs#L139), [`ui/dr-ui/src/identity_ui.rs:1643`](../../ui/dr-ui/src/identity_ui.rs#L1643), [`ui/dr-ui/src/identity_ui.rs:802`](../../ui/dr-ui/src/identity_ui.rs#L802), [`ui/dr-ui/src/lib.rs:3414`](../../ui/dr-ui/src/lib.rs#L3414), [`ui/dr-ui/src/lib.rs:3432`](../../ui/dr-ui/src/lib.rs#L3432), [`ui/dr-ui/src/lib.rs:3446`](../../ui/dr-ui/src/lib.rs#L3446), [`ui/dr-ui/src/lib.rs:3627`](../../ui/dr-ui/src/lib.rs#L3627), [`ui/dr-ui/src/library/filters.rs:79`](../../ui/dr-ui/src/library/filters.rs#L79), [`ui/dr-ui/src/library_ui/filter_bar.rs:57`](../../ui/dr-ui/src/library_ui/filter_bar.rs#L57), [`ui/dr-ui/src/library_ui/grid.rs:356`](../../ui/dr-ui/src/library_ui/grid.rs#L356), [`ui/dr-ui/src/library_ui/ratings_keywords.rs:1028`](../../ui/dr-ui/src/library_ui/ratings_keywords.rs#L1028), [`ui/dr-ui/src/library_ui/ratings_keywords.rs:1144`](../../ui/dr-ui/src/library_ui/ratings_keywords.rs#L1144), [`ui/dr-ui/src/library_ui/ratings_keywords.rs:1205`](../../ui/dr-ui/src/library_ui/ratings_keywords.rs#L1205), [`ui/dr-ui/src/library_ui/ratings_keywords.rs:1275`](../../ui/dr-ui/src/library_ui/ratings_keywords.rs#L1275), [`ui/dr-ui/src/library_ui/ratings_keywords.rs:1301`](../../ui/dr-ui/src/library_ui/ratings_keywords.rs#L1301), [`ui/dr-ui/src/library_ui/ratings_keywords.rs:457`](../../ui/dr-ui/src/library_ui/ratings_keywords.rs#L457), [`ui/dr-ui/src/masks_ui.rs:1655`](../../ui/dr-ui/src/masks_ui.rs#L1655), [`ui/dr-ui/src/place.rs:331`](../../ui/dr-ui/src/place.rs#L331), [`ui/dr-ui/src/settings_ui.rs:502`](../../ui/dr-ui/src/settings_ui.rs#L502), [`ui/dr-ui/ui/app.slint:1055`](../../ui/dr-ui/ui/app.slint#L1055), [`ui/dr-ui/ui/app.slint:1635`](../../ui/dr-ui/ui/app.slint#L1635), [`ui/dr-ui/ui/app.slint:2335`](../../ui/dr-ui/ui/app.slint#L2335), [`ui/dr-ui/ui/app.slint:2485`](../../ui/dr-ui/ui/app.slint#L2485), [`ui/dr-ui/ui/app.slint:2627`](../../ui/dr-ui/ui/app.slint#L2627), [`ui/dr-ui/ui/app.slint:2672`](../../ui/dr-ui/ui/app.slint#L2672), [`ui/dr-ui/ui/app.slint:2725`](../../ui/dr-ui/ui/app.slint#L2725), [`ui/dr-ui/ui/app.slint:2756`](../../ui/dr-ui/ui/app.slint#L2756), [`ui/dr-ui/ui/app.slint:3392`](../../ui/dr-ui/ui/app.slint#L3392), [`ui/dr-ui/ui/app.slint:3411`](../../ui/dr-ui/ui/app.slint#L3411), [`ui/dr-ui/ui/app.slint:393`](../../ui/dr-ui/ui/app.slint#L393), [`ui/dr-ui/ui/app.slint:652`](../../ui/dr-ui/ui/app.slint#L652), [`ui/dr-ui/ui/app.slint:659`](../../ui/dr-ui/ui/app.slint#L659), [`ui/dr-ui/ui/app.slint:663`](../../ui/dr-ui/ui/app.slint#L663), [`ui/dr-ui/ui/app.slint:669`](../../ui/dr-ui/ui/app.slint#L669), [`ui/dr-ui/ui/app.slint:725`](../../ui/dr-ui/ui/app.slint#L725), [`ui/dr-ui/ui/collections.slint:110`](../../ui/dr-ui/ui/collections.slint#L110), [`ui/dr-ui/ui/collections.slint:4`](../../ui/dr-ui/ui/collections.slint#L4), [`ui/dr-ui/ui/develop.slint:168`](../../ui/dr-ui/ui/develop.slint#L168), [`ui/dr-ui/ui/develop.slint:55`](../../ui/dr-ui/ui/develop.slint#L55), [`ui/dr-ui/ui/export.slint:1`](../../ui/dr-ui/ui/export.slint#L1), [`ui/dr-ui/ui/export.slint:384`](../../ui/dr-ui/ui/export.slint#L384), [`ui/dr-ui/ui/identity.slint:294`](../../ui/dr-ui/ui/identity.slint#L294), [`ui/dr-ui/ui/identity.slint:400`](../../ui/dr-ui/ui/identity.slint#L400), [`ui/dr-ui/ui/keys.slint:1`](../../ui/dr-ui/ui/keys.slint#L1), [`ui/dr-ui/ui/library.slint:1039`](../../ui/dr-ui/ui/library.slint#L1039), [`ui/dr-ui/ui/library.slint:1609`](../../ui/dr-ui/ui/library.slint#L1609), [`ui/dr-ui/ui/library.slint:1618`](../../ui/dr-ui/ui/library.slint#L1618), [`ui/dr-ui/ui/library.slint:1623`](../../ui/dr-ui/ui/library.slint#L1623), [`ui/dr-ui/ui/library.slint:1635`](../../ui/dr-ui/ui/library.slint#L1635), [`ui/dr-ui/ui/library.slint:1883`](../../ui/dr-ui/ui/library.slint#L1883), [`ui/dr-ui/ui/library.slint:2012`](../../ui/dr-ui/ui/library.slint#L2012), [`ui/dr-ui/ui/library.slint:2652`](../../ui/dr-ui/ui/library.slint#L2652), [`ui/dr-ui/ui/library.slint:3095`](../../ui/dr-ui/ui/library.slint#L3095), [`ui/dr-ui/ui/library.slint:3171`](../../ui/dr-ui/ui/library.slint#L3171), [`ui/dr-ui/ui/library.slint:3190`](../../ui/dr-ui/ui/library.slint#L3190), [`ui/dr-ui/ui/library.slint:3196`](../../ui/dr-ui/ui/library.slint#L3196), [`ui/dr-ui/ui/library.slint:3203`](../../ui/dr-ui/ui/library.slint#L3203), [`ui/dr-ui/ui/library.slint:3223`](../../ui/dr-ui/ui/library.slint#L3223), [`ui/dr-ui/ui/library.slint:3234`](../../ui/dr-ui/ui/library.slint#L3234), [`ui/dr-ui/ui/library.slint:3312`](../../ui/dr-ui/ui/library.slint#L3312), [`ui/dr-ui/ui/library.slint:3385`](../../ui/dr-ui/ui/library.slint#L3385), [`ui/dr-ui/ui/library.slint:3988`](../../ui/dr-ui/ui/library.slint#L3988), [`ui/dr-ui/ui/library.slint:5069`](../../ui/dr-ui/ui/library.slint#L5069), [`ui/dr-ui/ui/library.slint:642`](../../ui/dr-ui/ui/library.slint#L642), [`ui/dr-ui/ui/library.slint:660`](../../ui/dr-ui/ui/library.slint#L660), [`ui/dr-ui/ui/library.slint:681`](../../ui/dr-ui/ui/library.slint#L681), [`ui/dr-ui/ui/presets.slint:332`](../../ui/dr-ui/ui/presets.slint#L332), [`ui/dr-ui/ui/session.slint:75`](../../ui/dr-ui/ui/session.slint#L75) | +| FR-UI-4 | [`tools/traceability/src/gestures.rs:1`](../../tools/traceability/src/gestures.rs#L1), [`tools/traceability/src/keymap.rs:1`](../../tools/traceability/src/keymap.rs#L1), [`ui/dr-ui/src/collections_ui/controller.rs:138`](../../ui/dr-ui/src/collections_ui/controller.rs#L138), [`ui/dr-ui/src/collections_ui/controller.rs:343`](../../ui/dr-ui/src/collections_ui/controller.rs#L343), [`ui/dr-ui/src/collections_ui/controller.rs:356`](../../ui/dr-ui/src/collections_ui/controller.rs#L356), [`ui/dr-ui/src/collections_ui/controller.rs:45`](../../ui/dr-ui/src/collections_ui/controller.rs#L45), [`ui/dr-ui/src/collections_ui/controller.rs:46`](../../ui/dr-ui/src/collections_ui/controller.rs#L46), [`ui/dr-ui/src/collections_ui/controller.rs:55`](../../ui/dr-ui/src/collections_ui/controller.rs#L55), [`ui/dr-ui/src/collections_ui/controller.rs:83`](../../ui/dr-ui/src/collections_ui/controller.rs#L83), [`ui/dr-ui/src/collections_ui/drag.rs:246`](../../ui/dr-ui/src/collections_ui/drag.rs#L246), [`ui/dr-ui/src/collections_ui/drag.rs:256`](../../ui/dr-ui/src/collections_ui/drag.rs#L256), [`ui/dr-ui/src/collections_ui/press.rs:258`](../../ui/dr-ui/src/collections_ui/press.rs#L258), [`ui/dr-ui/src/collections_ui/press.rs:278`](../../ui/dr-ui/src/collections_ui/press.rs#L278), [`ui/dr-ui/src/collections_ui/press.rs:453`](../../ui/dr-ui/src/collections_ui/press.rs#L453), [`ui/dr-ui/src/collections_ui/press.rs:490`](../../ui/dr-ui/src/collections_ui/press.rs#L490), [`ui/dr-ui/src/collections_ui/press.rs:54`](../../ui/dr-ui/src/collections_ui/press.rs#L54), [`ui/dr-ui/src/collections_ui/tree_sync.rs:339`](../../ui/dr-ui/src/collections_ui/tree_sync.rs#L339), [`ui/dr-ui/src/collections_ui/wiring_grid.rs:121`](../../ui/dr-ui/src/collections_ui/wiring_grid.rs#L121), [`ui/dr-ui/src/collections_ui/wiring_grid.rs:138`](../../ui/dr-ui/src/collections_ui/wiring_grid.rs#L138), [`ui/dr-ui/src/collections_ui/wiring_grid.rs:264`](../../ui/dr-ui/src/collections_ui/wiring_grid.rs#L264), [`ui/dr-ui/src/collections_ui/wiring_grid.rs:415`](../../ui/dr-ui/src/collections_ui/wiring_grid.rs#L415), [`ui/dr-ui/src/collections_ui/wiring_grid.rs:444`](../../ui/dr-ui/src/collections_ui/wiring_grid.rs#L444), [`ui/dr-ui/src/collections_ui/wiring_grid.rs:577`](../../ui/dr-ui/src/collections_ui/wiring_grid.rs#L577), [`ui/dr-ui/src/collections_ui/wiring_tree.rs:287`](../../ui/dr-ui/src/collections_ui/wiring_tree.rs#L287), [`ui/dr-ui/src/collections_ui/wiring_tree.rs:344`](../../ui/dr-ui/src/collections_ui/wiring_tree.rs#L344), [`ui/dr-ui/src/develop/framing.rs:257`](../../ui/dr-ui/src/develop/framing.rs#L257), [`ui/dr-ui/src/develop/framing.rs:594`](../../ui/dr-ui/src/develop/framing.rs#L594), [`ui/dr-ui/src/develop/framing.rs:619`](../../ui/dr-ui/src/develop/framing.rs#L619), [`ui/dr-ui/src/develop/framing.rs:630`](../../ui/dr-ui/src/develop/framing.rs#L630), [`ui/dr-ui/src/develop/framing.rs:644`](../../ui/dr-ui/src/develop/framing.rs#L644), [`ui/dr-ui/src/develop/framing.rs:818`](../../ui/dr-ui/src/develop/framing.rs#L818), [`ui/dr-ui/src/develop/framing.rs:849`](../../ui/dr-ui/src/develop/framing.rs#L849), [`ui/dr-ui/src/develop/framing.rs:870`](../../ui/dr-ui/src/develop/framing.rs#L870), [`ui/dr-ui/src/develop/framing.rs:995`](../../ui/dr-ui/src/develop/framing.rs#L995), [`ui/dr-ui/src/develop/render.rs:1065`](../../ui/dr-ui/src/develop/render.rs#L1065), [`ui/dr-ui/src/develop/render.rs:1090`](../../ui/dr-ui/src/develop/render.rs#L1090), [`ui/dr-ui/src/develop/render.rs:1113`](../../ui/dr-ui/src/develop/render.rs#L1113), [`ui/dr-ui/src/develop/render.rs:1152`](../../ui/dr-ui/src/develop/render.rs#L1152), [`ui/dr-ui/src/develop/render.rs:47`](../../ui/dr-ui/src/develop/render.rs#L47), [`ui/dr-ui/src/develop/render.rs:66`](../../ui/dr-ui/src/develop/render.rs#L66), [`ui/dr-ui/src/develop/render.rs:79`](../../ui/dr-ui/src/develop/render.rs#L79), [`ui/dr-ui/src/develop_ui.rs:852`](../../ui/dr-ui/src/develop_ui.rs#L852), [`ui/dr-ui/src/duplicates_ui.rs:1`](../../ui/dr-ui/src/duplicates_ui.rs#L1), [`ui/dr-ui/src/gesture_book.rs:3`](../../ui/dr-ui/src/gesture_book.rs#L3), [`ui/dr-ui/src/gestures.rs:127`](../../ui/dr-ui/src/gestures.rs#L127), [`ui/dr-ui/src/gestures.rs:139`](../../ui/dr-ui/src/gestures.rs#L139), [`ui/dr-ui/src/gestures.rs:1`](../../ui/dr-ui/src/gestures.rs#L1), [`ui/dr-ui/src/lib.rs:1281`](../../ui/dr-ui/src/lib.rs#L1281), [`ui/dr-ui/src/lib.rs:1572`](../../ui/dr-ui/src/lib.rs#L1572), [`ui/dr-ui/src/lib.rs:2592`](../../ui/dr-ui/src/lib.rs#L2592), [`ui/dr-ui/src/lib.rs:2811`](../../ui/dr-ui/src/lib.rs#L2811), [`ui/dr-ui/src/lib.rs:2871`](../../ui/dr-ui/src/lib.rs#L2871), [`ui/dr-ui/src/lib.rs:3030`](../../ui/dr-ui/src/lib.rs#L3030), [`ui/dr-ui/src/lib.rs:3432`](../../ui/dr-ui/src/lib.rs#L3432), [`ui/dr-ui/src/lib.rs:3476`](../../ui/dr-ui/src/lib.rs#L3476), [`ui/dr-ui/src/lib.rs:486`](../../ui/dr-ui/src/lib.rs#L486), [`ui/dr-ui/src/lib.rs:515`](../../ui/dr-ui/src/lib.rs#L515), [`ui/dr-ui/src/library/thumbnails_fetch.rs:718`](../../ui/dr-ui/src/library/thumbnails_fetch.rs#L718), [`ui/dr-ui/src/library/thumbnails_fetch.rs:733`](../../ui/dr-ui/src/library/thumbnails_fetch.rs#L733), [`ui/dr-ui/src/library_ui/controller.rs:398`](../../ui/dr-ui/src/library_ui/controller.rs#L398), [`ui/dr-ui/src/library_ui/controller.rs:540`](../../ui/dr-ui/src/library_ui/controller.rs#L540), [`ui/dr-ui/src/library_ui/controller.rs:617`](../../ui/dr-ui/src/library_ui/controller.rs#L617), [`ui/dr-ui/src/library_ui/filter_bar.rs:500`](../../ui/dr-ui/src/library_ui/filter_bar.rs#L500), [`ui/dr-ui/src/library_ui/grid.rs:218`](../../ui/dr-ui/src/library_ui/grid.rs#L218), [`ui/dr-ui/src/library_ui/grid.rs:241`](../../ui/dr-ui/src/library_ui/grid.rs#L241), [`ui/dr-ui/src/library_ui/grid.rs:262`](../../ui/dr-ui/src/library_ui/grid.rs#L262), [`ui/dr-ui/src/library_ui/grid.rs:336`](../../ui/dr-ui/src/library_ui/grid.rs#L336), [`ui/dr-ui/src/library_ui/grid.rs:479`](../../ui/dr-ui/src/library_ui/grid.rs#L479), [`ui/dr-ui/src/library_ui/grid.rs:509`](../../ui/dr-ui/src/library_ui/grid.rs#L509), [`ui/dr-ui/src/library_ui/ratings_keywords.rs:1168`](../../ui/dr-ui/src/library_ui/ratings_keywords.rs#L1168), [`ui/dr-ui/src/manual.rs:1`](../../ui/dr-ui/src/manual.rs#L1), [`ui/dr-ui/src/settings_ui.rs:157`](../../ui/dr-ui/src/settings_ui.rs#L157), [`ui/dr-ui/src/settings_ui.rs:616`](../../ui/dr-ui/src/settings_ui.rs#L616), [`ui/dr-ui/ui/app.slint:1771`](../../ui/dr-ui/ui/app.slint#L1771), [`ui/dr-ui/ui/app.slint:1923`](../../ui/dr-ui/ui/app.slint#L1923), [`ui/dr-ui/ui/app.slint:2049`](../../ui/dr-ui/ui/app.slint#L2049), [`ui/dr-ui/ui/app.slint:2335`](../../ui/dr-ui/ui/app.slint#L2335), [`ui/dr-ui/ui/app.slint:235`](../../ui/dr-ui/ui/app.slint#L235), [`ui/dr-ui/ui/app.slint:2497`](../../ui/dr-ui/ui/app.slint#L2497), [`ui/dr-ui/ui/app.slint:2527`](../../ui/dr-ui/ui/app.slint#L2527), [`ui/dr-ui/ui/app.slint:2672`](../../ui/dr-ui/ui/app.slint#L2672), [`ui/dr-ui/ui/app.slint:2872`](../../ui/dr-ui/ui/app.slint#L2872), [`ui/dr-ui/ui/app.slint:3427`](../../ui/dr-ui/ui/app.slint#L3427), [`ui/dr-ui/ui/app.slint:3454`](../../ui/dr-ui/ui/app.slint#L3454), [`ui/dr-ui/ui/app.slint:663`](../../ui/dr-ui/ui/app.slint#L663), [`ui/dr-ui/ui/collections.slint:1340`](../../ui/dr-ui/ui/collections.slint#L1340), [`ui/dr-ui/ui/collections.slint:77`](../../ui/dr-ui/ui/collections.slint#L77), [`ui/dr-ui/ui/develop.slint:142`](../../ui/dr-ui/ui/develop.slint#L142), [`ui/dr-ui/ui/develop.slint:332`](../../ui/dr-ui/ui/develop.slint#L332), [`ui/dr-ui/ui/develop.slint:66`](../../ui/dr-ui/ui/develop.slint#L66), [`ui/dr-ui/ui/duplicates.slint:1`](../../ui/dr-ui/ui/duplicates.slint#L1), [`ui/dr-ui/ui/gestures.slint:1`](../../ui/dr-ui/ui/gestures.slint#L1), [`ui/dr-ui/ui/icons.slint:367`](../../ui/dr-ui/ui/icons.slint#L367), [`ui/dr-ui/ui/labels.slint:74`](../../ui/dr-ui/ui/labels.slint#L74), [`ui/dr-ui/ui/library.slint:1329`](../../ui/dr-ui/ui/library.slint#L1329), [`ui/dr-ui/ui/library.slint:1356`](../../ui/dr-ui/ui/library.slint#L1356), [`ui/dr-ui/ui/library.slint:1362`](../../ui/dr-ui/ui/library.slint#L1362), [`ui/dr-ui/ui/library.slint:1417`](../../ui/dr-ui/ui/library.slint#L1417), [`ui/dr-ui/ui/library.slint:1612`](../../ui/dr-ui/ui/library.slint#L1612), [`ui/dr-ui/ui/library.slint:1663`](../../ui/dr-ui/ui/library.slint#L1663), [`ui/dr-ui/ui/library.slint:1686`](../../ui/dr-ui/ui/library.slint#L1686), [`ui/dr-ui/ui/library.slint:1693`](../../ui/dr-ui/ui/library.slint#L1693), [`ui/dr-ui/ui/library.slint:1729`](../../ui/dr-ui/ui/library.slint#L1729), [`ui/dr-ui/ui/library.slint:1787`](../../ui/dr-ui/ui/library.slint#L1787), [`ui/dr-ui/ui/library.slint:1794`](../../ui/dr-ui/ui/library.slint#L1794), [`ui/dr-ui/ui/library.slint:3129`](../../ui/dr-ui/ui/library.slint#L3129), [`ui/dr-ui/ui/library.slint:3234`](../../ui/dr-ui/ui/library.slint#L3234), [`ui/dr-ui/ui/library.slint:3385`](../../ui/dr-ui/ui/library.slint#L3385), [`ui/dr-ui/ui/library.slint:5032`](../../ui/dr-ui/ui/library.slint#L5032), [`ui/dr-ui/ui/library.slint:5070`](../../ui/dr-ui/ui/library.slint#L5070), [`ui/dr-ui/ui/library.slint:5174`](../../ui/dr-ui/ui/library.slint#L5174), [`ui/dr-ui/ui/library.slint:562`](../../ui/dr-ui/ui/library.slint#L562), [`ui/dr-ui/ui/library.slint:572`](../../ui/dr-ui/ui/library.slint#L572), [`ui/dr-ui/ui/library.slint:666`](../../ui/dr-ui/ui/library.slint#L666), [`ui/dr-ui/ui/library.slint:735`](../../ui/dr-ui/ui/library.slint#L735), [`ui/dr-ui/ui/settings.slint:121`](../../ui/dr-ui/ui/settings.slint#L121), [`ui/dr-ui/ui/settings.slint:172`](../../ui/dr-ui/ui/settings.slint#L172), [`ui/dr-ui/ui/settings.slint:635`](../../ui/dr-ui/ui/settings.slint#L635) | +| FR-UI-5 | [`core/dr-types/src/place.rs:118`](../../core/dr-types/src/place.rs#L118), [`tools/traceability/src/chord.rs:1`](../../tools/traceability/src/chord.rs#L1), [`tools/traceability/src/keymap.rs:1`](../../tools/traceability/src/keymap.rs#L1), [`ui/dr-ui/src/collections_ui/mod.rs:1`](../../ui/dr-ui/src/collections_ui/mod.rs#L1), [`ui/dr-ui/src/develop_ui.rs:897`](../../ui/dr-ui/src/develop_ui.rs#L897), [`ui/dr-ui/src/gestures.rs:139`](../../ui/dr-ui/src/gestures.rs#L139), [`ui/dr-ui/src/identity_ui.rs:1643`](../../ui/dr-ui/src/identity_ui.rs#L1643), [`ui/dr-ui/src/identity_ui.rs:802`](../../ui/dr-ui/src/identity_ui.rs#L802), [`ui/dr-ui/src/lib.rs:3414`](../../ui/dr-ui/src/lib.rs#L3414), [`ui/dr-ui/src/lib.rs:3432`](../../ui/dr-ui/src/lib.rs#L3432), [`ui/dr-ui/src/lib.rs:3446`](../../ui/dr-ui/src/lib.rs#L3446), [`ui/dr-ui/src/lib.rs:3627`](../../ui/dr-ui/src/lib.rs#L3627), [`ui/dr-ui/src/library/filters.rs:79`](../../ui/dr-ui/src/library/filters.rs#L79), [`ui/dr-ui/src/library_ui/filter_bar.rs:57`](../../ui/dr-ui/src/library_ui/filter_bar.rs#L57), [`ui/dr-ui/src/library_ui/grid.rs:356`](../../ui/dr-ui/src/library_ui/grid.rs#L356), [`ui/dr-ui/src/library_ui/ratings_keywords.rs:1028`](../../ui/dr-ui/src/library_ui/ratings_keywords.rs#L1028), [`ui/dr-ui/src/library_ui/ratings_keywords.rs:1144`](../../ui/dr-ui/src/library_ui/ratings_keywords.rs#L1144), [`ui/dr-ui/src/library_ui/ratings_keywords.rs:1205`](../../ui/dr-ui/src/library_ui/ratings_keywords.rs#L1205), [`ui/dr-ui/src/library_ui/ratings_keywords.rs:1275`](../../ui/dr-ui/src/library_ui/ratings_keywords.rs#L1275), [`ui/dr-ui/src/library_ui/ratings_keywords.rs:1301`](../../ui/dr-ui/src/library_ui/ratings_keywords.rs#L1301), [`ui/dr-ui/src/library_ui/ratings_keywords.rs:457`](../../ui/dr-ui/src/library_ui/ratings_keywords.rs#L457), [`ui/dr-ui/src/masks_ui.rs:1655`](../../ui/dr-ui/src/masks_ui.rs#L1655), [`ui/dr-ui/src/place.rs:331`](../../ui/dr-ui/src/place.rs#L331), [`ui/dr-ui/src/settings_ui.rs:502`](../../ui/dr-ui/src/settings_ui.rs#L502), [`ui/dr-ui/ui/app.slint:1055`](../../ui/dr-ui/ui/app.slint#L1055), [`ui/dr-ui/ui/app.slint:1635`](../../ui/dr-ui/ui/app.slint#L1635), [`ui/dr-ui/ui/app.slint:2335`](../../ui/dr-ui/ui/app.slint#L2335), [`ui/dr-ui/ui/app.slint:2485`](../../ui/dr-ui/ui/app.slint#L2485), [`ui/dr-ui/ui/app.slint:2627`](../../ui/dr-ui/ui/app.slint#L2627), [`ui/dr-ui/ui/app.slint:2672`](../../ui/dr-ui/ui/app.slint#L2672), [`ui/dr-ui/ui/app.slint:2725`](../../ui/dr-ui/ui/app.slint#L2725), [`ui/dr-ui/ui/app.slint:2756`](../../ui/dr-ui/ui/app.slint#L2756), [`ui/dr-ui/ui/app.slint:3392`](../../ui/dr-ui/ui/app.slint#L3392), [`ui/dr-ui/ui/app.slint:3411`](../../ui/dr-ui/ui/app.slint#L3411), [`ui/dr-ui/ui/app.slint:393`](../../ui/dr-ui/ui/app.slint#L393), [`ui/dr-ui/ui/app.slint:652`](../../ui/dr-ui/ui/app.slint#L652), [`ui/dr-ui/ui/app.slint:659`](../../ui/dr-ui/ui/app.slint#L659), [`ui/dr-ui/ui/app.slint:663`](../../ui/dr-ui/ui/app.slint#L663), [`ui/dr-ui/ui/app.slint:669`](../../ui/dr-ui/ui/app.slint#L669), [`ui/dr-ui/ui/app.slint:725`](../../ui/dr-ui/ui/app.slint#L725), [`ui/dr-ui/ui/collections.slint:110`](../../ui/dr-ui/ui/collections.slint#L110), [`ui/dr-ui/ui/collections.slint:4`](../../ui/dr-ui/ui/collections.slint#L4), [`ui/dr-ui/ui/develop.slint:168`](../../ui/dr-ui/ui/develop.slint#L168), [`ui/dr-ui/ui/develop.slint:55`](../../ui/dr-ui/ui/develop.slint#L55), [`ui/dr-ui/ui/export.slint:1`](../../ui/dr-ui/ui/export.slint#L1), [`ui/dr-ui/ui/export.slint:384`](../../ui/dr-ui/ui/export.slint#L384), [`ui/dr-ui/ui/identity.slint:294`](../../ui/dr-ui/ui/identity.slint#L294), [`ui/dr-ui/ui/identity.slint:400`](../../ui/dr-ui/ui/identity.slint#L400), [`ui/dr-ui/ui/keys.slint:1`](../../ui/dr-ui/ui/keys.slint#L1), [`ui/dr-ui/ui/library.slint:1039`](../../ui/dr-ui/ui/library.slint#L1039), [`ui/dr-ui/ui/library.slint:1609`](../../ui/dr-ui/ui/library.slint#L1609), [`ui/dr-ui/ui/library.slint:1618`](../../ui/dr-ui/ui/library.slint#L1618), [`ui/dr-ui/ui/library.slint:1623`](../../ui/dr-ui/ui/library.slint#L1623), [`ui/dr-ui/ui/library.slint:1635`](../../ui/dr-ui/ui/library.slint#L1635), [`ui/dr-ui/ui/library.slint:1883`](../../ui/dr-ui/ui/library.slint#L1883), [`ui/dr-ui/ui/library.slint:2012`](../../ui/dr-ui/ui/library.slint#L2012), [`ui/dr-ui/ui/library.slint:2652`](../../ui/dr-ui/ui/library.slint#L2652), [`ui/dr-ui/ui/library.slint:3095`](../../ui/dr-ui/ui/library.slint#L3095), [`ui/dr-ui/ui/library.slint:3171`](../../ui/dr-ui/ui/library.slint#L3171), [`ui/dr-ui/ui/library.slint:3190`](../../ui/dr-ui/ui/library.slint#L3190), [`ui/dr-ui/ui/library.slint:3196`](../../ui/dr-ui/ui/library.slint#L3196), [`ui/dr-ui/ui/library.slint:3203`](../../ui/dr-ui/ui/library.slint#L3203), [`ui/dr-ui/ui/library.slint:3223`](../../ui/dr-ui/ui/library.slint#L3223), [`ui/dr-ui/ui/library.slint:3234`](../../ui/dr-ui/ui/library.slint#L3234), [`ui/dr-ui/ui/library.slint:3312`](../../ui/dr-ui/ui/library.slint#L3312), [`ui/dr-ui/ui/library.slint:3385`](../../ui/dr-ui/ui/library.slint#L3385), [`ui/dr-ui/ui/library.slint:3992`](../../ui/dr-ui/ui/library.slint#L3992), [`ui/dr-ui/ui/library.slint:5087`](../../ui/dr-ui/ui/library.slint#L5087), [`ui/dr-ui/ui/library.slint:642`](../../ui/dr-ui/ui/library.slint#L642), [`ui/dr-ui/ui/library.slint:660`](../../ui/dr-ui/ui/library.slint#L660), [`ui/dr-ui/ui/library.slint:681`](../../ui/dr-ui/ui/library.slint#L681), [`ui/dr-ui/ui/presets.slint:332`](../../ui/dr-ui/ui/presets.slint#L332), [`ui/dr-ui/ui/session.slint:75`](../../ui/dr-ui/ui/session.slint#L75) | | FR-UI-6 | [`ui/dr-ui/ui/widgets.slint:1`](../../ui/dr-ui/ui/widgets.slint#L1) | | FR-UI-7 | [`core/dr-pipeline/src/descriptor.rs:195`](../../core/dr-pipeline/src/descriptor.rs#L195), [`core/dr-pipeline/src/framing.rs:555`](../../core/dr-pipeline/src/framing.rs#L555), [`core/dr-types/src/settings.rs:492`](../../core/dr-types/src/settings.rs#L492), [`ui/dr-ui/src/develop/rows.rs:118`](../../ui/dr-ui/src/develop/rows.rs#L118), [`ui/dr-ui/src/lib.rs:3239`](../../ui/dr-ui/src/lib.rs#L3239), [`ui/dr-ui/ui/adjust.slint:1000`](../../ui/dr-ui/ui/adjust.slint#L1000), [`ui/dr-ui/ui/app.slint:2173`](../../ui/dr-ui/ui/app.slint#L2173), [`ui/dr-ui/ui/settings.slint:660`](../../ui/dr-ui/ui/settings.slint#L660), [`ui/dr-ui/ui/toolrail.slint:89`](../../ui/dr-ui/ui/toolrail.slint#L89) | | FR-UI-8 | [`core/dr-catalog/src/collections.rs:654`](../../core/dr-catalog/src/collections.rs#L654), [`core/dr-catalog/src/collections.rs:675`](../../core/dr-catalog/src/collections.rs#L675), [`core/dr-types/src/place.rs:1`](../../core/dr-types/src/place.rs#L1), [`ui/dr-ui/src/derived_sync.rs:226`](../../ui/dr-ui/src/derived_sync.rs#L226), [`ui/dr-ui/src/derived_sync.rs:84`](../../ui/dr-ui/src/derived_sync.rs#L84), [`ui/dr-ui/src/derived_sync.rs:905`](../../ui/dr-ui/src/derived_sync.rs#L905), [`ui/dr-ui/src/derived_sync.rs:915`](../../ui/dr-ui/src/derived_sync.rs#L915), [`ui/dr-ui/src/derived_sync.rs:997`](../../ui/dr-ui/src/derived_sync.rs#L997), [`ui/dr-ui/src/library/cells.rs:274`](../../ui/dr-ui/src/library/cells.rs#L274), [`ui/dr-ui/src/library/cells.rs:553`](../../ui/dr-ui/src/library/cells.rs#L553), [`ui/dr-ui/src/library/cells.rs:619`](../../ui/dr-ui/src/library/cells.rs#L619), [`ui/dr-ui/src/library/paths.rs:22`](../../ui/dr-ui/src/library/paths.rs#L22), [`ui/dr-ui/src/library_ui/controller.rs:205`](../../ui/dr-ui/src/library_ui/controller.rs#L205), [`ui/dr-ui/src/library_ui/controller.rs:942`](../../ui/dr-ui/src/library_ui/controller.rs#L942), [`ui/dr-ui/src/library_ui/filter_bar.rs:793`](../../ui/dr-ui/src/library_ui/filter_bar.rs#L793), [`ui/dr-ui/src/library_ui/grid.rs:172`](../../ui/dr-ui/src/library_ui/grid.rs#L172), [`ui/dr-ui/src/library_ui/grid.rs:285`](../../ui/dr-ui/src/library_ui/grid.rs#L285), [`ui/dr-ui/src/library_ui/grid.rs:327`](../../ui/dr-ui/src/library_ui/grid.rs#L327), [`ui/dr-ui/src/library_ui/grid.rs:668`](../../ui/dr-ui/src/library_ui/grid.rs#L668), [`ui/dr-ui/src/library_ui/grid.rs:794`](../../ui/dr-ui/src/library_ui/grid.rs#L794), [`ui/dr-ui/src/library_ui/open.rs:118`](../../ui/dr-ui/src/library_ui/open.rs#L118), [`ui/dr-ui/src/library_ui/open.rs:36`](../../ui/dr-ui/src/library_ui/open.rs#L36), [`ui/dr-ui/src/library_ui/open.rs:71`](../../ui/dr-ui/src/library_ui/open.rs#L71), [`ui/dr-ui/src/library_ui/open.rs:839`](../../ui/dr-ui/src/library_ui/open.rs#L839), [`ui/dr-ui/src/library_ui/open.rs:856`](../../ui/dr-ui/src/library_ui/open.rs#L856), [`ui/dr-ui/src/library_ui/open.rs:954`](../../ui/dr-ui/src/library_ui/open.rs#L954), [`ui/dr-ui/src/place.rs:1`](../../ui/dr-ui/src/place.rs#L1) | | NFR-A11Y-2 | [`ui/dr-ui/tests/ui_controls_are_accessible.rs:1`](../../ui/dr-ui/tests/ui_controls_are_accessible.rs#L1), [`ui/dr-ui/ui/adjust.slint:173`](../../ui/dr-ui/ui/adjust.slint#L173), [`ui/dr-ui/ui/app.slint:2872`](../../ui/dr-ui/ui/app.slint#L2872), [`ui/dr-ui/ui/app.slint:2911`](../../ui/dr-ui/ui/app.slint#L2911) | -| NFR-A11Y-3 | [`ui/dr-ui/src/histogram.rs:356`](../../ui/dr-ui/src/histogram.rs#L356), [`ui/dr-ui/tests/status_is_not_colour_alone.rs:1`](../../ui/dr-ui/tests/status_is_not_colour_alone.rs#L1), [`ui/dr-ui/ui/app.slint:2756`](../../ui/dr-ui/ui/app.slint#L2756), [`ui/dr-ui/ui/develop.slint:142`](../../ui/dr-ui/ui/develop.slint#L142), [`ui/dr-ui/ui/develop.slint:168`](../../ui/dr-ui/ui/develop.slint#L168), [`ui/dr-ui/ui/histogram.slint:137`](../../ui/dr-ui/ui/histogram.slint#L137), [`ui/dr-ui/ui/labels.slint:19`](../../ui/dr-ui/ui/labels.slint#L19), [`ui/dr-ui/ui/labels.slint:1`](../../ui/dr-ui/ui/labels.slint#L1), [`ui/dr-ui/ui/labels.slint:40`](../../ui/dr-ui/ui/labels.slint#L40), [`ui/dr-ui/ui/labels.slint:74`](../../ui/dr-ui/ui/labels.slint#L74), [`ui/dr-ui/ui/library.slint:1039`](../../ui/dr-ui/ui/library.slint#L1039), [`ui/dr-ui/ui/library.slint:1110`](../../ui/dr-ui/ui/library.slint#L1110), [`ui/dr-ui/ui/library.slint:1273`](../../ui/dr-ui/ui/library.slint#L1273), [`ui/dr-ui/ui/library.slint:2697`](../../ui/dr-ui/ui/library.slint#L2697), [`ui/dr-ui/ui/library.slint:3312`](../../ui/dr-ui/ui/library.slint#L3312), [`ui/dr-ui/ui/library.slint:3924`](../../ui/dr-ui/ui/library.slint#L3924), [`ui/dr-ui/ui/peaking.slint:1`](../../ui/dr-ui/ui/peaking.slint#L1), [`ui/dr-ui/ui/widgets.slint:271`](../../ui/dr-ui/ui/widgets.slint#L271) | +| NFR-A11Y-3 | [`ui/dr-ui/src/histogram.rs:356`](../../ui/dr-ui/src/histogram.rs#L356), [`ui/dr-ui/tests/status_is_not_colour_alone.rs:1`](../../ui/dr-ui/tests/status_is_not_colour_alone.rs#L1), [`ui/dr-ui/ui/app.slint:2756`](../../ui/dr-ui/ui/app.slint#L2756), [`ui/dr-ui/ui/develop.slint:142`](../../ui/dr-ui/ui/develop.slint#L142), [`ui/dr-ui/ui/develop.slint:168`](../../ui/dr-ui/ui/develop.slint#L168), [`ui/dr-ui/ui/histogram.slint:137`](../../ui/dr-ui/ui/histogram.slint#L137), [`ui/dr-ui/ui/labels.slint:19`](../../ui/dr-ui/ui/labels.slint#L19), [`ui/dr-ui/ui/labels.slint:1`](../../ui/dr-ui/ui/labels.slint#L1), [`ui/dr-ui/ui/labels.slint:40`](../../ui/dr-ui/ui/labels.slint#L40), [`ui/dr-ui/ui/labels.slint:74`](../../ui/dr-ui/ui/labels.slint#L74), [`ui/dr-ui/ui/library.slint:1039`](../../ui/dr-ui/ui/library.slint#L1039), [`ui/dr-ui/ui/library.slint:1110`](../../ui/dr-ui/ui/library.slint#L1110), [`ui/dr-ui/ui/library.slint:1273`](../../ui/dr-ui/ui/library.slint#L1273), [`ui/dr-ui/ui/library.slint:2697`](../../ui/dr-ui/ui/library.slint#L2697), [`ui/dr-ui/ui/library.slint:3312`](../../ui/dr-ui/ui/library.slint#L3312), [`ui/dr-ui/ui/library.slint:3928`](../../ui/dr-ui/ui/library.slint#L3928), [`ui/dr-ui/ui/peaking.slint:1`](../../ui/dr-ui/ui/peaking.slint#L1), [`ui/dr-ui/ui/widgets.slint:271`](../../ui/dr-ui/ui/widgets.slint#L271) | | NFR-ARCH-2 | [`core/dr-catalog/src/jobs.rs:1`](../../core/dr-catalog/src/jobs.rs#L1), [`ui/dr-ui/src/faces.rs:1`](../../ui/dr-ui/src/faces.rs#L1), [`ui/dr-ui/src/repairs.rs:1`](../../ui/dr-ui/src/repairs.rs#L1) | -| NFR-ARCH-3 | [`ui/dr-ui/src/develop_ui.rs:348`](../../ui/dr-ui/src/develop_ui.rs#L348), [`ui/dr-ui/src/export.rs:1842`](../../ui/dr-ui/src/export.rs#L1842), [`ui/dr-ui/src/export.rs:1869`](../../ui/dr-ui/src/export.rs#L1869), [`ui/dr-ui/src/export.rs:420`](../../ui/dr-ui/src/export.rs#L420), [`ui/dr-ui/src/export.rs:446`](../../ui/dr-ui/src/export.rs#L446), [`ui/dr-ui/ui/library.slint:5181`](../../ui/dr-ui/ui/library.slint#L5181), [`ui/dr-ui/ui/library.slint:794`](../../ui/dr-ui/ui/library.slint#L794) | +| NFR-ARCH-3 | [`ui/dr-ui/src/develop_ui.rs:348`](../../ui/dr-ui/src/develop_ui.rs#L348), [`ui/dr-ui/src/export.rs:1842`](../../ui/dr-ui/src/export.rs#L1842), [`ui/dr-ui/src/export.rs:1869`](../../ui/dr-ui/src/export.rs#L1869), [`ui/dr-ui/src/export.rs:420`](../../ui/dr-ui/src/export.rs#L420), [`ui/dr-ui/src/export.rs:446`](../../ui/dr-ui/src/export.rs#L446), [`ui/dr-ui/ui/library.slint:5199`](../../ui/dr-ui/ui/library.slint#L5199), [`ui/dr-ui/ui/library.slint:794`](../../ui/dr-ui/ui/library.slint#L794) | | NFR-ARCH-4 | [`core/dr-catalog/src/error.rs:1`](../../core/dr-catalog/src/error.rs#L1), [`core/dr-export/src/error.rs:1`](../../core/dr-export/src/error.rs#L1), [`core/dr-thumbs/src/error.rs:1`](../../core/dr-thumbs/src/error.rs#L1), [`core/dr-xmp/src/lib.rs:798`](../../core/dr-xmp/src/lib.rs#L798), [`platform/dr-plat/src/storage.rs:148`](../../platform/dr-plat/src/storage.rs#L148), [`ui/dr-ui/src/export.rs:526`](../../ui/dr-ui/src/export.rs#L526) | | NFR-COMPAT-1 | [`core/dr-gpu/src/lib.rs:286`](../../core/dr-gpu/src/lib.rs#L286) | | NFR-OPS-1 | [`core/dr-gpu/src/lib.rs:339`](../../core/dr-gpu/src/lib.rs#L339), [`platform/dr-plat/src/diagnostics.rs:1`](../../platform/dr-plat/src/diagnostics.rs#L1), [`platform/dr-plat/src/diagnostics/bundle.rs:1`](../../platform/dr-plat/src/diagnostics/bundle.rs#L1), [`platform/dr-plat/src/state.rs:1`](../../platform/dr-plat/src/state.rs#L1), [`ui/dr-ui/src/lib.rs:1462`](../../ui/dr-ui/src/lib.rs#L1462), [`ui/dr-ui/ui/app.slint:1233`](../../ui/dr-ui/ui/app.slint#L1233), [`ui/dr-ui/ui/app.slint:138`](../../ui/dr-ui/ui/app.slint#L138), [`ui/dr-ui/ui/settings.slint:161`](../../ui/dr-ui/ui/settings.slint#L161), [`ui/dr-ui/ui/settings.slint:911`](../../ui/dr-ui/ui/settings.slint#L911) | diff --git a/docs/gestures.md b/docs/gestures.md index 8cafaee..d28f9e5 100644 --- a/docs/gestures.md +++ b/docs/gestures.md @@ -700,7 +700,7 @@ The cursor selects what it lands on, so walking and judging are one hand's work. 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. -`ui/dr-ui/ui/library.slint:3526` +`ui/dr-ui/ui/library.slint:3530` ### File photographs in a collection @@ -710,7 +710,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. -`ui/dr-ui/ui/library.slint:3725` +`ui/dr-ui/ui/library.slint:3729` ### Open a photograph @@ -721,7 +721,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. -`ui/dr-ui/ui/library.slint:4030` +`ui/dr-ui/ui/library.slint:4034` ### Rate a photograph without opening it @@ -732,7 +732,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. -`ui/dr-ui/ui/library.slint:4153` +`ui/dr-ui/ui/library.slint:4157` ### Choose the frame a folded burst shows @@ -742,7 +742,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. -`ui/dr-ui/ui/library.slint:4286` +`ui/dr-ui/ui/library.slint:4290` ### Drop the selection but keep selecting @@ -753,7 +753,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. -`ui/dr-ui/ui/library.slint:4963` +`ui/dr-ui/ui/library.slint:4981` ### Select everything the grid is showing @@ -764,7 +764,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. -`ui/dr-ui/ui/library.slint:4982` +`ui/dr-ui/ui/library.slint:5000` ### Take photographs out of a collection @@ -774,7 +774,7 @@ 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. -`ui/dr-ui/ui/library.slint:5163` +`ui/dr-ui/ui/library.slint:5181` ## Settings diff --git a/ui/dr-ui/ui/library.slint b/ui/dr-ui/ui/library.slint index 1c3dcae..855a76b 100644 --- a/ui/dr-ui/ui/library.slint +++ b/ui/dr-ui/ui/library.slint @@ -10,7 +10,7 @@ import { Theme } from "theme.slint"; import { Keys } from "keys.slint"; -import { Button, IconButton, Label, Value, Caption, EmptyState, FilterChip, ProgressBar, Icon, Field } from "widgets.slint"; +import { Button, IconButton, Label, Value, Caption, EmptyState, FilterChip, ProgressBar, Icon, Field, ScrollBar } from "widgets.slint"; // The filing sheet lists the same rows the sidebar draws, from the same model: // two lists of collections that could disagree about what exists is one list // too many. @@ -3488,954 +3488,972 @@ export component LibraryGrid inherits Rectangle { // depending on two constants in different crates staying in the // order they happen to be in. The wheel is unaffected: `interactive` // does not gate it. - if root.total > 0: grid-scroll := Flickable { - interactive: root.held-row < 0; - // See `hover-ordinal`: the cell under a still pointer changes - // as this moves, and nothing re-announces the hover. - changed viewport-y => { root.hover-ordinal = -1; } - // Ctrl+wheel resizes the cells; a plain wheel is declined and - // falls through to the Flickable's own scrolling. Two jobs on - // one gesture, distinguished by the modifier — the convention - // every image browser uses. - // - // Declared *first* so it sits beneath the cells in z-order: - // their own touch areas still take clicks and drags, and only - // a wheel event nothing else claimed reaches this. - zoom-catcher := TouchArea { - width: 100%; - height: parent.viewport-height; - scroll-event(e) => { - if (e.modifiers.control) { - root.zoom-cells(e.delta-y > 0 ? 1 : -1); - return accept; - } - return reject; - } - } - - // Two-finger pinch, for tablet: the same gesture the timeline - // uses, applied to cell size rather than to time. - // - // Sized to the **viewport**, like `zoom-catcher` above and for - // the same reason. `100%` inside a Flickable is the Flickable's - // own height, and the pinch is delivered to whatever lies under - // the midpoint of the two fingers — so a handler one screenful - // tall sat at the top of a viewport thousands of rows long and - // was under the fingers only while the grid had not been - // scrolled. Anywhere else the gesture found nothing to land on. - // GESTURE: Resize the thumbnails - // where: Library grid - // touch: Pinch the grid with two fingers - // pointer: Ctrl and the scroll wheel - // keys: `=` or `Plus` for larger, `-` for smaller - // why: 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. - // manual: getting-about - grid-pinch := ScaleRotateGestureHandler { - width: 100%; - height: parent.viewport-height; - - property last-scale: 1.0; - - started => { - self.last-scale = 1.0; - root.pinching = true; - settle.running = false; - // The finger that opened this gesture landed on a cell - // and selected it. It was reaching for the grid, not - // for that photograph. - root.pinch-started(); - } - // Continuous, not stepped. Thresholding this into ±1 zoom - // steps meant the grid lurched 25% at a time and sat still - // in between, which is the whole of "pinching is not - // smooth". The ratio since the last update is what tracks - // the fingers; where the drawn cell lands is still a whole - // number of columns, because the columns divide the width. - updated => { - root.pinch-cells(self.scale / max(0.01, self.last-scale)); - self.last-scale = self.scale; - } - // The latch outlives the gesture — see `pinching`. - ended => { - self.last-scale = 1.0; - settle.running = true; - } - cancelled => { - self.last-scale = 1.0; - settle.running = true; - } - } - - // Follow a requested position. Without this a scrub moves the - // *loaded window* while the viewport stays where it was, so - // the cells are drawn thousands of rows away and the grid - // looks empty until the user scrolls to find them. - function seek() { - self.viewport-y = -min( - max(0px, self.viewport-height - self.height), - floor(root.scroll-to / max(1, root.columns)) - * (root.cell-size + Theme.gap)); - } - - property token: root.scroll-token; - changed token => { self.seek(); } - - // Keep the keyboard cursor in view, moving as little as will - // do it. - // - // Deliberately *not* `seek()`: that puts the requested row at - // the top, which is right for a scrub — the user asked to go - // to a date and expects to arrive there — and wrong for an - // arrow key, where the grid jumping a row upward on every - // press makes the row impossible to read. So a cursor already - // on screen moves nothing at all, and one that has just left - // brings in exactly its own row. - property pitch: root.cell-size + Theme.gap; - property cursor-row: floor(root.cursor / max(1, root.columns)); - changed cursor-row => { self.reveal(); } - - function reveal() { - if (root.cursor < 0) { - return; - } - // Nothing to reveal *into* before layout has run, and this - // is now called from `init` as well as on a cursor move — - // where a height of zero would make every row look off - // screen and scroll the grid to the cursor when `seek` had - // just put it somewhere deliberate. - if (self.height <= 0px) { - return; - } - self.revealed = true; - let top = Theme.gap + self.cursor-row * self.pitch; - let shown = -self.viewport-y; - let bottom = max(0px, self.viewport-height - self.height); - if (top < shown) { - self.viewport-y = -min(bottom, top); - } else if (top + self.pitch > shown + self.height) { - self.viewport-y = -min(bottom, top + self.pitch - self.height); - } - } - - /// Whether a reveal has run against real geometry yet. - /// - /// The safety net for the deferral above, and deliberately a - /// latch rather than a plain `changed height` handler: the first - /// height a rebuilt grid is given must bring the cursor into - /// view, and every height *after* that is a window being - /// resized, where dragging a corner should not keep hauling the - /// viewport back to a cursor the user is not looking at. - property revealed: false; - changed height => { - if (!self.revealed) { - self.reveal(); - } - } - - // Also on creation, which is what returning from the develop - // view needs. The grid is gated on an `if`, so it is built anew - // and `token` is *initialised* to the already-bumped value - // rather than changing to it — no `changed` handler fires, and - // without this the restored position would be dropped and the - // view would sit at the top. - // - // `reveal()` after `seek()`, and the order is the point: seek - // puts the viewport where the grid was left, reveal then moves - // it as little as will bring the cursor's row into view. Coming - // back from develop the cursor is on the photograph that was - // open, so a frame inside the remembered screenful moves - // nothing at all and one outside it is scrolled just far enough - // to be seen. `cursor-row` is *initialised* here too, so its own - // `changed` handler never fires and this is the only thing that - // can do it. - init => { self.seek(); self.reveal(); } - - // Sized to the **whole library**, not the loaded window. The - // scrollbar has to represent 23,971 images or there is no way to - // reach image 20,000 — dragging it must be a real address, and the - // window is swapped underneath to match. - // Plus room for the selection bar, which floats over the - // foot of the grid rather than sitting above it. Extending the - // viewport rather than shrinking the Flickable is what keeps - // the change invisible: every cell stays exactly where it was - // and there is simply further to scroll, so the last row can be - // brought clear of the bar instead of being trapped under it. - // Same condition as the bar itself, or a running export with - // the selection since cleared would leave the last row of - // thumbnails trapped under a bar the grid did not know was - // there. - property selection-inset: - (root.selected-count > 0 || root.exporting) ? 40px : 0px; - - viewport-height: root.total-rows * (root.cell-size + Theme.gap) - + Theme.gap + self.selection-inset; - - // TRACES: FR-CAT-7 - // Stay inside the content when the content shrinks. - // - // Deleting photographs makes the library shorter, and the - // viewport is sized to the *whole* library — so a view that was - // scrolled near the end is suddenly scrolled past it. Slint - // does not pull a Flickable back on its own, so the grid went - // blank: the cells were still there, above a viewport looking - // at empty space below them. - // - // Worse than blank, it also *moved*. Cells are drawn at - // `(i + offset) / columns`, and a delete re-clamps `offset` - // downward to keep the window full — so the same `viewport-y` - // now points at a different part of the library, and the grid - // appeared to jump somewhere arbitrary. Re-seeking below is the - // other half of this; this half stops the blank. - changed viewport-height => { - if (-self.viewport-y > max(0px, self.viewport-height - self.height)) { - self.viewport-y = -max(0px, self.viewport-height - self.height); - } - } - - // Report the first fully-scrolled-past row so Rust can move the - // window. Derived rather than eventful: Slint has no scroll - // callback, and a `changed` handler on a derived integer fires only - // when the row actually changes rather than on every pixel. - property first-visible-row: max(0, - floor((-self.viewport-y - Theme.gap) / (root.cell-size + Theme.gap))); - changed first-visible-row => { - root.scrolled(self.first-visible-row * root.columns); - } - - // Month headings, drawn over the grid at the row where each - // period begins. A separate pass rather than part of the cell, - // because the heading spans the full width and a cell does not. - for cell[i] in root.cells: Text { - x: Theme.gap; - // Sits in the gap above its row, so it labels the row - // rather than displacing it. - y: Theme.gap - + floor((i + root.offset) / root.columns) * (root.cell-size + Theme.gap) - - 15px; - width: parent.width - 2 * Theme.gap; - text: cell.period-heading; - color: Theme.ink-dim; - font-size: Theme.text-sm; - font-weight: 700; - visible: cell.period-heading != ""; - } - - // GESTURE: File photographs in a collection - // where: Library grid - // touch: Drag a photograph — or a whole selection — onto a - // collection in the sidebar. Starting a drag stops - // the press becoming a hold, so it cannot leave you - // in selection mode. - // pointer: Drag a photograph — or a whole selection — onto a - // collection in the sidebar - // why: The selection is what the drag carries, which is - // why selecting several is worth the mode: forty - // photographs file in one gesture. - // manual: collections - for cell[i] in root.cells: DragArea { - // A cell is its photograph, named by its file: the name - // printed under the thumbnail, and the one a screen - // reader or the manual's recording scripts ask for. - accessible-role: list-item; - accessible-label: cell.name; - accessible-item-selectable: true; - accessible-item-selected: cell.selected; - - // Cells are positioned at their **absolute** place in the - // library, not their index in the loaded window: the window - // starts at `offset`, so a cell drawn at window-index 0 belongs - // wherever `offset` sits in the full grid. - x: Theme.gap + mod(i + root.offset, root.columns) * (root.cell-size + Theme.gap); - y: Theme.gap + floor((i + root.offset) / root.columns) * (root.cell-size + Theme.gap); - width: root.cell-size; - height: root.cell-size; - - // Copy, not move: dropping into a collection files the - // photograph there without taking it out of anywhere else. That - // is what a join table means, and it is why the modifier-free - // gesture must not be `move`. - allow-copy: true; - // Not the payload — the arming. See `drag-payload`. - data: root.drag-payload(); - // What travels under the cursor is the photograph itself — and - // where several are being dragged, a stack of them. Composited - // in Rust (`drag_image`), because Slint takes a single bitmap - // here and cannot render a pile of thumbnails into one. + if root.total > 0: Rectangle { + vertical-stretch: 1; + grid-scroll := Flickable { + width: parent.width; + height: parent.height; + interactive: root.held-row < 0; + // See `hover-ordinal`: the cell under a still pointer changes + // as this moves, and nothing re-announces the hover. + changed viewport-y => { root.hover-ordinal = -1; } + // Ctrl+wheel resizes the cells; a plain wheel is declined and + // falls through to the Flickable's own scrolling. Two jobs on + // one gesture, distinguished by the modifier — the convention + // every image browser uses. // - // Read on `dragging` rather than bound continuously: the - // composite costs a copy per thumbnail, and the grid must not - // pay it per cell per frame. - drag-image: root.drag-image; - - changed dragging => { - if (self.dragging) { - root.drag-started(i); - } - } - drag-finished(action) => { root.drag-finished(); } - - // Where a reorder lands. Behind the cell's content and - // before it in the file, for the reason `TreeRow`'s drop - // area gives: a `DropArea` only takes part in a drag, so it - // does not block the presses the cell's own TouchArea - // needs — but drawn first it cannot paint over the - // thumbnail either. - // - // Present on every cell rather than wrapped in an `if`, so - // the marker below can name it. `can-drop` is where the - // refusal lives, which also means the cursor says no while - // the user can still aim somewhere else. - reorder-drop := DropArea { + // Declared *first* so it sits beneath the cells in z-order: + // their own touch areas still take clicks and drags, and only + // a wheel event nothing else claimed reaches this. + zoom-catcher := TouchArea { width: 100%; - height: 100%; - - /// Whether the run would land after this photograph - /// rather than before it. Tracked during the hover so - /// the marker can move to the edge the drop will - /// actually use. - property after: false; - - can-drop(ev) => { - if (!root.reorderable) { - return DragAction.none; + height: parent.viewport-height; + scroll-event(e) => { + if (e.modifiers.control) { + root.zoom-cells(e.delta-y > 0 ? 1 : -1); + return accept; } - self.after = ev.position.x > self.width / 2; - return DragAction.copy; - } - - dropped(ev) => { - root.reorder-to(i, ev.position.x > self.width / 2); - return DragAction.copy; + return reject; } } - Rectangle { - // Lifted cells shrink toward their own centre, as though pulled - // off the page. Inset rather than scaled: Slint has no transform - // on a plain Rectangle, and insetting keeps the cell's slot in - // the grid so nothing reflows mid-drag. - x: cell.lifted ? 10px : 0px; - y: cell.lifted ? 10px : 0px; - width: parent.width - 2 * self.x; - height: parent.height - 2 * self.y; - animate x, y, width, height { duration: 120ms; easing: ease-out; } - - background: Theme.surface; - border-radius: Theme.radius; - // Hover only. **Selection is the inset ring below**, and - // nothing out here changes when a cell is selected. + // Two-finger pinch, for tablet: the same gesture the timeline + // uses, applied to cell size rather than to time. // - // It used to be a 2px border and a lifted fill, and a - // border drawn on the outside of a 6px-padded cell eats - // into the thumbnail: selecting appeared to nudge the - // photograph, which is exactly the wrong feedback for a - // gesture whose whole job is to say "this one". One - // treatment, drawn inside, in one place. + // Sized to the **viewport**, like `zoom-catcher` above and for + // the same reason. `100%` inside a Flickable is the Flickable's + // own height, and the pinch is delivered to whatever lies under + // the midpoint of the two fingers — so a handler one screenful + // tall sat at the top of a viewport thousands of rows long and + // was under the fingers only while the grid had not been + // scrolled. Anywhere else the gesture found nothing to land on. + // GESTURE: Resize the thumbnails + // where: Library grid + // touch: Pinch the grid with two fingers + // pointer: Ctrl and the scroll wheel + // keys: `=` or `Plus` for larger, `-` for smaller + // why: 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. + // manual: getting-about + grid-pinch := ScaleRotateGestureHandler { + width: 100%; + height: parent.viewport-height; + + property last-scale: 1.0; + + started => { + self.last-scale = 1.0; + root.pinching = true; + settle.running = false; + // The finger that opened this gesture landed on a cell + // and selected it. It was reaching for the grid, not + // for that photograph. + root.pinch-started(); + } + // Continuous, not stepped. Thresholding this into ±1 zoom + // steps meant the grid lurched 25% at a time and sat still + // in between, which is the whole of "pinching is not + // smooth". The ratio since the last update is what tracks + // the fingers; where the drawn cell lands is still a whole + // number of columns, because the columns divide the width. + updated => { + root.pinch-cells(self.scale / max(0.01, self.last-scale)); + self.last-scale = self.scale; + } + // The latch outlives the gesture — see `pinching`. + ended => { + self.last-scale = 1.0; + settle.running = true; + } + cancelled => { + self.last-scale = 1.0; + settle.running = true; + } + } + + // Follow a requested position. Without this a scrub moves the + // *loaded window* while the viewport stays where it was, so + // the cells are drawn thousands of rows away and the grid + // looks empty until the user scrolls to find them. + function seek() { + self.viewport-y = -min( + max(0px, self.viewport-height - self.height), + floor(root.scroll-to / max(1, root.columns)) + * (root.cell-size + Theme.gap)); + } + + property token: root.scroll-token; + changed token => { self.seek(); } + + // Keep the keyboard cursor in view, moving as little as will + // do it. // - // **Not drawn at all once there has been a finger.** A - // hand has no hover to give, so on a tablet this ring can - // only ever be wrong — and it is wrong in the worst way, - // because it is the selection ring's own colour a pixel - // thinner. `has-hover` is not reliably cleared on touch: - // a release normally brings an `Exit` with it, but the one - // that ends a pinch does not, so every pinch to resize the - // thumbnails left a ring around whichever cell a finger - // happened to have started on. The grid then showed boxes - // around photographs that were not selected, with no way - // to tell them from ones that were. See `touched`. - border-width: cell-touch.has-hover && !root.touched ? 1px : 0px; - border-color: Theme.selected-ring; - clip: true; + // Deliberately *not* `seek()`: that puts the requested row at + // the top, which is right for a scrub — the user asked to go + // to a date and expects to arrive there — and wrong for an + // arrow key, where the grid jumping a row upward on every + // press makes the row impossible to read. So a cursor already + // on screen moves nothing at all, and one that has just left + // brings in exactly its own row. + property pitch: root.cell-size + Theme.gap; + property cursor-row: floor(root.cursor / max(1, root.columns)); + changed cursor-row => { self.reveal(); } - VerticalLayout { - padding: 6px; - spacing: 4px; + function reveal() { + if (root.cursor < 0) { + return; + } + // Nothing to reveal *into* before layout has run, and this + // is now called from `init` as well as on a cursor move — + // where a height of zero would make every row look off + // screen and scroll the grid to the cursor when `seek` had + // just put it somewhere deliberate. + if (self.height <= 0px) { + return; + } + self.revealed = true; + let top = Theme.gap + self.cursor-row * self.pitch; + let shown = -self.viewport-y; + let bottom = max(0px, self.viewport-height - self.height); + if (top < shown) { + self.viewport-y = -min(bottom, top); + } else if (top + self.pitch > shown + self.height) { + self.viewport-y = -min(bottom, top + self.pitch - self.height); + } + } - Rectangle { - vertical-stretch: 1; - background: Theme.ground; + /// Whether a reveal has run against real geometry yet. + /// + /// The safety net for the deferral above, and deliberately a + /// latch rather than a plain `changed height` handler: the first + /// height a rebuilt grid is given must bring the cursor into + /// view, and every height *after* that is a window being + /// resized, where dragging a corner should not keep hauling the + /// viewport back to a cursor the user is not looking at. + property revealed: false; + changed height => { + if (!self.revealed) { + self.reveal(); + } + } - Image { - width: 100%; - height: 100%; - source: cell.thumbnail; - image-fit: contain; - visible: cell.has-thumb; - // Faded while lifted, so the grid reads as the place - // the photograph came *from* and the cursor as where - // it is now. `colorize` would flatten it to one - // tint, which loses the picture; dropping opacity - // toward the ground keeps it recognisable as a ghost - // of itself. - // A rejected frame is held back rather than - // hidden: the cull is reversible, and a photo - // that vanished on one keypress would make the - // gesture frightening to use. Dimming is the - // cue that reads at grid scale — the cross on - // the mark confirms it up close. - opacity: cell.lifted ? 0.25 - : (cell.flag == 2 ? 0.4 : 1.0); - animate opacity { duration: 120ms; } + // Also on creation, which is what returning from the develop + // view needs. The grid is gated on an `if`, so it is built anew + // and `token` is *initialised* to the already-bumped value + // rather than changing to it — no `changed` handler fires, and + // without this the restored position would be dropped and the + // view would sit at the top. + // + // `reveal()` after `seek()`, and the order is the point: seek + // puts the viewport where the grid was left, reveal then moves + // it as little as will bring the cursor's row into view. Coming + // back from develop the cursor is on the photograph that was + // open, so a frame inside the remembered screenful moves + // nothing at all and one outside it is scrolled just far enough + // to be seen. `cursor-row` is *initialised* here too, so its own + // `changed` handler never fires and this is the only thing that + // can do it. + init => { self.seek(); self.reveal(); } + + // Sized to the **whole library**, not the loaded window. The + // scrollbar has to represent 23,971 images or there is no way to + // reach image 20,000 — dragging it must be a real address, and the + // window is swapped underneath to match. + // Plus room for the selection bar, which floats over the + // foot of the grid rather than sitting above it. Extending the + // viewport rather than shrinking the Flickable is what keeps + // the change invisible: every cell stays exactly where it was + // and there is simply further to scroll, so the last row can be + // brought clear of the bar instead of being trapped under it. + // Same condition as the bar itself, or a running export with + // the selection since cleared would leave the last row of + // thumbnails trapped under a bar the grid did not know was + // there. + property selection-inset: + (root.selected-count > 0 || root.exporting) ? 40px : 0px; + + viewport-height: root.total-rows * (root.cell-size + Theme.gap) + + Theme.gap + self.selection-inset; + + // TRACES: FR-CAT-7 + // Stay inside the content when the content shrinks. + // + // Deleting photographs makes the library shorter, and the + // viewport is sized to the *whole* library — so a view that was + // scrolled near the end is suddenly scrolled past it. Slint + // does not pull a Flickable back on its own, so the grid went + // blank: the cells were still there, above a viewport looking + // at empty space below them. + // + // Worse than blank, it also *moved*. Cells are drawn at + // `(i + offset) / columns`, and a delete re-clamps `offset` + // downward to keep the window full — so the same `viewport-y` + // now points at a different part of the library, and the grid + // appeared to jump somewhere arbitrary. Re-seeking below is the + // other half of this; this half stops the blank. + changed viewport-height => { + if (-self.viewport-y > max(0px, self.viewport-height - self.height)) { + self.viewport-y = -max(0px, self.viewport-height - self.height); + } + } + + // Report the first fully-scrolled-past row so Rust can move the + // window. Derived rather than eventful: Slint has no scroll + // callback, and a `changed` handler on a derived integer fires only + // when the row actually changes rather than on every pixel. + property first-visible-row: max(0, + floor((-self.viewport-y - Theme.gap) / (root.cell-size + Theme.gap))); + changed first-visible-row => { + root.scrolled(self.first-visible-row * root.columns); + } + + // Month headings, drawn over the grid at the row where each + // period begins. A separate pass rather than part of the cell, + // because the heading spans the full width and a cell does not. + for cell[i] in root.cells: Text { + x: Theme.gap; + // Sits in the gap above its row, so it labels the row + // rather than displacing it. + y: Theme.gap + + floor((i + root.offset) / root.columns) * (root.cell-size + Theme.gap) + - 15px; + width: parent.width - 2 * Theme.gap; + text: cell.period-heading; + color: Theme.ink-dim; + font-size: Theme.text-sm; + font-weight: 700; + visible: cell.period-heading != ""; + } + + // GESTURE: File photographs in a collection + // where: Library grid + // touch: Drag a photograph — or a whole selection — onto a + // collection in the sidebar. Starting a drag stops + // the press becoming a hold, so it cannot leave you + // in selection mode. + // pointer: Drag a photograph — or a whole selection — onto a + // collection in the sidebar + // why: The selection is what the drag carries, which is + // why selecting several is worth the mode: forty + // photographs file in one gesture. + // manual: collections + for cell[i] in root.cells: DragArea { + // A cell is its photograph, named by its file: the name + // printed under the thumbnail, and the one a screen + // reader or the manual's recording scripts ask for. + accessible-role: list-item; + accessible-label: cell.name; + accessible-item-selectable: true; + accessible-item-selected: cell.selected; + + // Cells are positioned at their **absolute** place in the + // library, not their index in the loaded window: the window + // starts at `offset`, so a cell drawn at window-index 0 belongs + // wherever `offset` sits in the full grid. + x: Theme.gap + mod(i + root.offset, root.columns) * (root.cell-size + Theme.gap); + y: Theme.gap + floor((i + root.offset) / root.columns) * (root.cell-size + Theme.gap); + width: root.cell-size; + height: root.cell-size; + + // Copy, not move: dropping into a collection files the + // photograph there without taking it out of anywhere else. That + // is what a join table means, and it is why the modifier-free + // gesture must not be `move`. + allow-copy: true; + // Not the payload — the arming. See `drag-payload`. + data: root.drag-payload(); + // What travels under the cursor is the photograph itself — and + // where several are being dragged, a stack of them. Composited + // in Rust (`drag_image`), because Slint takes a single bitmap + // here and cannot render a pile of thumbnails into one. + // + // Read on `dragging` rather than bound continuously: the + // composite costs a copy per thumbnail, and the grid must not + // pay it per cell per frame. + drag-image: root.drag-image; + + changed dragging => { + if (self.dragging) { + root.drag-started(i); } + } + drag-finished(action) => { root.drag-finished(); } - if !cell.has-thumb: Caption { - text: cell.unavailable ? "no preview" : "…"; - horizontal-alignment: center; - } + // Where a reorder lands. Behind the cell's content and + // before it in the file, for the reason `TreeRow`'s drop + // area gives: a `DropArea` only takes part in a drag, so it + // does not block the presses the cell's own TouchArea + // needs — but drawn first it cannot paint over the + // thumbnail either. + // + // Present on every cell rather than wrapped in an `if`, so + // the marker below can name it. `can-drop` is where the + // refusal lives, which also means the cursor says no while + // the user can still aim somewhere else. + reorder-drop := DropArea { + width: 100%; + height: 100%; - // "Already filed, in this many collections." Without it - // there is no way to tell a filed photograph from an - // unfiled one, and the user re-files what is already - // in place. - if cell.collection-count > 0: Rectangle { - x: parent.width - self.width - 4px; - y: 4px; - width: 16px; - height: 16px; - border-radius: 8px; - background: Theme.selected; - opacity: 0.9; + /// Whether the run would land after this photograph + /// rather than before it. Tracked during the hover so + /// the marker can move to the edge the drop will + /// actually use. + property after: false; - Text { - text: cell.collection-count; - color: Theme.ink; - font-size: 9px; - font-weight: 700; - width: 100%; - height: 100%; - horizontal-alignment: center; - vertical-alignment: center; + can-drop(ev) => { + if (!root.reorderable) { + return DragAction.none; } + self.after = ev.position.x > self.width / 2; + return DragAction.copy; } - // Pick or reject, top left — the opposite corner - // from the collection badge so the two never - // collide on a cell that carries both. - FlagMark { - x: 4px; - y: 4px; - flag: cell.flag; + dropped(ev) => { + root.reorder-to(i, ev.position.x > self.width / 2); + return DragAction.copy; } - } - // TRACES: FR-CAT-5 | NFR-A11Y-3 - // The colour label leads the caption: on the - // photograph it would compete with the flag and the - // badges for four corners, and here it is read with - // the name, in the row every cell already has. - HorizontalLayout { + Rectangle { + // Lifted cells shrink toward their own centre, as though pulled + // off the page. Inset rather than scaled: Slint has no transform + // on a plain Rectangle, and insetting keeps the cell's slot in + // the grid so nothing reflows mid-drag. + x: cell.lifted ? 10px : 0px; + y: cell.lifted ? 10px : 0px; + width: parent.width - 2 * self.x; + height: parent.height - 2 * self.y; + animate x, y, width, height { duration: 120ms; easing: ease-out; } + + background: Theme.surface; + border-radius: Theme.radius; + // Hover only. **Selection is the inset ring below**, and + // nothing out here changes when a cell is selected. + // + // It used to be a 2px border and a lifted fill, and a + // border drawn on the outside of a 6px-padded cell eats + // into the thumbnail: selecting appeared to nudge the + // photograph, which is exactly the wrong feedback for a + // gesture whose whole job is to say "this one". One + // treatment, drawn inside, in one place. + // + // **Not drawn at all once there has been a finger.** A + // hand has no hover to give, so on a tablet this ring can + // only ever be wrong — and it is wrong in the worst way, + // because it is the selection ring's own colour a pixel + // thinner. `has-hover` is not reliably cleared on touch: + // a release normally brings an `Exit` with it, but the one + // that ends a pinch does not, so every pinch to resize the + // thumbnails left a ring around whichever cell a finger + // happened to have started on. The grid then showed boxes + // around photographs that were not selected, with no way + // to tell them from ones that were. See `touched`. + border-width: cell-touch.has-hover && !root.touched ? 1px : 0px; + border-color: Theme.selected-ring; + clip: true; + + VerticalLayout { + padding: 6px; spacing: 4px; - if cell.label > 0: LabelMark { - code: cell.label; - size: 14px; - y: (parent.height - self.height) / 2; - } - - Label { - text: cell.name; - emphasised: cell.selected; - overflow: elide; - } - } - } - - // **Selection, and the only mark of it.** - // - // This ring used to mean something narrower — the end a - // shift-click measures from — and it was the clearest thing - // on the cell, so it read as the selection to everyone who - // had not written it. It also outlived what it described: an - // anchor survives a deselection, so a thin box sat around - // the last photograph touched with nothing selected at all, - // and there was no way to tell it apart from a cell that had - // stayed behind. - // - // So the ring is now what it already looked like. The anchor - // has no mark of its own, which costs nothing it was earning: - // the range gesture announces itself in the bar — "Tap the - // last photograph" — and does not need the user to find - // where it will measure from. - // - // Drawn *inside* the cell and 4px clear of the edge, so it - // never touches the thumbnail and never changes a single - // dimension. Selecting adds ink and moves nothing. - Rectangle { - visible: cell.selected; - x: 4px; - y: 4px; - width: parent.width - 8px; - height: parent.height - 8px; - background: transparent; - // Two, not one: this is now the whole of the cue, and it - // has to survive being read across forty cells at arm's - // length against a thumbnail of any brightness. - border-width: 2px; - border-color: Theme.selected-ring; - border-radius: Theme.radius-sm; - } - - // Selection only. The drag is the enclosing `DragArea`'s - // business, and Slint keeps a click distinct from a drag for - // us — which is exactly the arbitration the hand-rolled version - // had to fake with a travel threshold. - cell-touch := TouchArea { - mouse-cursor: pointer; - - // TRACES: FR-UI-5 - // Which photograph a judgement key would reach - // without a selection. Cleared only by the cell that - // set it, so moving from one cell to the next cannot - // be undone by the old cell's leave arriving second. - changed has-hover => { - if (self.has-hover) { - root.hover-ordinal = i + root.offset; - } else if (root.hover-ordinal == i + root.offset) { - root.hover-ordinal = -1; - } - } - - // Selected on *press*, not on release: the drag that may - // follow reads the selection to build its payload, and by - // release the pointer is over the sidebar. - // - // In selection mode the press is reported as though ctrl - // were held. That is not a shortcut: toggling one cell - // while keeping the rest *is* what ctrl-click means, and - // giving touch its own policy would be a second copy of - // the rules in `collections_ui::apply_press` to keep in - // step with the first. - // --- which finger, and whether it finished -------- - // - // A pinch begins as an ordinary press. When the second - // finger lands Slint closes the first one's gesture by - // synthesising a `Released` at its position — that is - // how a Flickable is made to let go of a scroll it had - // already claimed. A TouchArea cannot tell that release - // from a real one and fires `clicked`, so every attempt - // to pinch-zoom the grid opened whichever photograph - // the first finger happened to be resting on. - // - // The finger id is what separates them: the synthetic - // release carries the id of the finger that *arrived*, - // never the one that pressed. A mouse reports 0 for - // both, so the desktop path is unchanged. - // - // `clicked` fires before the `up` that follows it, so - // it can only raise a flag — the decision to open has - // to wait for the event that names the finger. - // GESTURE: Open a photograph - // where: Library grid - // touch: Tap it — a single tap, any length - // pointer: Click it - // keys: `Enter`, on the photograph the arrow - // keys have walked to - // why: 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. - // manual: developing-a-photograph - property down-finger: -1; - property click-pending: false; - - // Where the press landed, so the release can tell how - // far the finger travelled. - // - // The Flickable already cancels a press that becomes a - // scroll, but only once it has claimed the gesture, - // which takes more travel than a graze has. This - // catches the rest: a contact that slid across the cell - // and lifted was reaching for something else. - property press-x: 0; - property press-y: 0; - - /// How far a finger may slide and still be a tap. - /// - /// A thumb's contact patch is wider than this, so no - /// stationary tap approaches it; a hand moving across - /// the glass passes it within one frame. Below about - /// this the number stops describing intent and starts - /// describing how steady the user's hand is. - property tap-slop: 12px; - - pointer-event(ev) => { - if (ev.kind == PointerEventKind.down) { - self.down-finger = ev.touch-finger-id; - self.click-pending = false; - self.press-x = self.mouse-x; - self.press-y = self.mouse-y; - // A finger, not a pointer — see `touched`. - if (ev.touch-finger-id != 0) { - root.touched = true; - } - // Not while the grid is being pinched, nor in - // the moment after: this is the finger left - // over from the gesture, handed back as a new - // press. See `pinching`. - if (!root.pinching) { - // An armed range reaches Rust as shift, - // which is what it is: `apply_press` reads - // ctrl+shift as "add the run from the - // anchor to here", and in selection mode - // ctrl is already set. Sent this way rather - // than as a third selection policy, so the - // rules stay in one place. - root.cell-pressed( - i, - ev.modifiers.control || root.select-mode, - ev.modifiers.shift || root.ranging, - ); - // One shot. The range was between two taps - // and the second has landed; left armed, it - // would turn every tap after it into - // another run. - root.ranging = false; - } - } - if (ev.kind == PointerEventKind.up) { - // A *plain* click opens the image; a modified - // one is purely a selection gesture and must - // not navigate away from the grid the user is - // building a selection in. The modifier state - // is not carried here, so the press above - // records it and Rust decides — `cell-clicked` - // is only honoured when the press was - // unmodified. - if (self.click-pending - && !root.pinching - && ev.touch-finger-id == self.down-finger - && abs(self.mouse-x - self.press-x) < self.tap-slop - && abs(self.mouse-y - self.press-y) < self.tap-slop) { - root.cell-clicked(i); - } - self.click-pending = false; - // Down again, whether or not it was ever up. - root.held-row = -1; - root.cell-press-ended(); - } - // `cancel` is the important ending: the Flickable - // takes the pointer as soon as the finger travels, - // so without this a scroll that began on a cell - // would come to rest as a long press and select it. - if (ev.kind == PointerEventKind.cancel) { - self.click-pending = false; - // Including the cancel a starting drag sends: - // by then the `DragArea` has the gesture and - // `lifted` is the mark that matters, so there - // is no scroll left to lose and nothing to - // keep the ring open for. - root.held-row = -1; - root.cell-press-ended(); - } - } - - clicked => { self.click-pending = true; } - // **No `double-clicked` here, deliberately.** A double - // tap used to take a range in selection mode. It was - // the only gesture touch had for shift-click, and - // "Select to…" replaced it with a control that says - // what it is about to do. Leaving both meant two quick - // taps on one cell — a thing a hand does by accident — - // silently selecting a run of forty photographs, with - // no visible state to explain where they came from. - // - // Now two taps are two toggles and land back where they - // started, which is the only thing a user can predict - // from what is on screen. - - // GESTURE: Rate a photograph without opening it - // where: Library grid - // touch: Tap a star on the cell - // pointer: Hover the cell, then click a star - // keys: `0`–`5` with the pointer over it, or - // on the selection - // why: A star has to take the press without it - // also reaching the cell, or every rating - // throws the user into develop. - // manual: rating-and-flagging - // - // --- the rating strip, INSIDE the cell's hit area --- - // - // **A child of `cell-touch`, not a sibling of it.** Two - // things have to be true at once here, and only this - // nesting gets both. - // - // A star must take the click without it also reaching - // `cell-clicked`, or every rating throws the user into - // develop. Children are hit-tested before the element - // they sit in, and a child that accepts ends the walk - // before the TouchArea's own handler runs — so the star - // wins, and it wins for the same reason a later sibling - // used to. - // - // And the strip must not vanish as the pointer arrives - // at it. As a *sibling* it did: hover is tracked per - // TouchArea, Slint sends `Exit` to whatever drops out of - // the hit path, and the strip taking the pointer dropped - // `cell-touch` out of it. `has-hover` went false, which - // took `show-empty` with it, which hid the very stars - // the pointer was travelling towards — on an unrated - // cell they disappeared, the click landed on the cell - // behind them, and the image opened. That is the whole - // of the "stars vanish when I click one" report. - // - // An *ancestor* stays in the path: it keeps its place on - // the item stack, gets no `Exit`, and during a grab it is - // handed only the filter and never the event. So hover - // holds for as long as the pointer is anywhere in the - // cell, stars included. - // - // It sits over the foot of the thumbnail rather than - // below it: the caption row is spoken for by the - // filename, and a third row would cost thumbnail height - // on every cell to show something that is usually empty. - StarStrip { - x: (parent.width - self.width) / 2; - // Clear of the caption, which is the cell's last row. - y: parent.height - self.height - 26px; - rating: cell.rating; - // Empty stars appear once the pointer is over the - // cell, so there is something to aim at without - // filling the grid with chrome. That trade only - // works where there is a pointer to hover with: on - // touch the strip stands open, because a control - // that appears under the finger is a control that - // appears too late to aim at. See `touched`. - show-empty: cell-touch.has-hover || root.touched; - can-trash: !root.viewing-trash; - rate(n) => { root.cell-rated(i, n); } - trash() => { root.cell-trashed(i); } - } - - // The burst mark (FR-CULL-5): how many frames this - // moment holds, - // and the way in and out of them. - // - // A child of `cell-touch` for exactly the reason the - // stars above are: a click here must not also reach - // `cell-clicked` and throw the user into develop, and - // children are hit-tested before the element they sit - // in. Unlike the stars it is never hidden — a collapsed - // burst is standing in for frames that are not on - // screen, and there has to be something visible saying - // so whether or not a pointer is anywhere near. - // - // Bottom left, clear of the centred star strip and of - // both top corners, which the flag and the collection - // badge already have. - if cell.burst-count > 1: Rectangle { - x: 6px; - y: parent.height - self.height - 26px; - width: 30px; - height: 18px; - - // The pile behind the top card, drawn only while the - // group is folded up. It is the whole of the "there - // is more than one of these" cue; once the burst is - // open the frames themselves say it. Rectangle { - x: 3px; - y: -3px; - width: parent.width - 3px; - height: parent.height; - visible: !cell.burst-expanded; - background: Theme.surface; - border-radius: Theme.radius-sm; - border-width: 1px; - border-color: Theme.rule; - } + vertical-stretch: 1; + background: Theme.ground; - Rectangle { - width: 100%; - height: 100%; - background: cell.burst-expanded ? Theme.selected - : Theme.surface; - border-radius: Theme.radius-sm; - border-width: 1px; - border-color: cell.burst-expanded ? Theme.selected-ring - : Theme.rule; - - Text { + Image { width: 100%; height: 100%; + source: cell.thumbnail; + image-fit: contain; + visible: cell.has-thumb; + // Faded while lifted, so the grid reads as the place + // the photograph came *from* and the cursor as where + // it is now. `colorize` would flatten it to one + // tint, which loses the picture; dropping opacity + // toward the ground keeps it recognisable as a ghost + // of itself. + // A rejected frame is held back rather than + // hidden: the cull is reversible, and a photo + // that vanished on one keypress would make the + // gesture frightening to use. Dimming is the + // cue that reads at grid scale — the cross on + // the mark confirms it up close. + opacity: cell.lifted ? 0.25 + : (cell.flag == 2 ? 0.4 : 1.0); + animate opacity { duration: 120ms; } + } + + if !cell.has-thumb: Caption { + text: cell.unavailable ? "no preview" : "…"; horizontal-alignment: center; - vertical-alignment: center; - // No "of": the number is the size of the - // group, and a cell this small cannot - // afford a word to say so. - text: cell.burst-count; - color: Theme.ink; - font-size: 10px; - font-weight: 700; + } + + // "Already filed, in this many collections." Without it + // there is no way to tell a filed photograph from an + // unfiled one, and the user re-files what is already + // in place. + if cell.collection-count > 0: Rectangle { + x: parent.width - self.width - 4px; + y: 4px; + width: 16px; + height: 16px; + border-radius: 8px; + background: Theme.selected; + opacity: 0.9; + + Text { + text: cell.collection-count; + color: Theme.ink; + font-size: 9px; + font-weight: 700; + width: 100%; + height: 100%; + horizontal-alignment: center; + vertical-alignment: center; + } + } + + // Pick or reject, top left — the opposite corner + // from the collection badge so the two never + // collide on a cell that carries both. + FlagMark { + x: 4px; + y: 4px; + flag: cell.flag; + } + + } + + // TRACES: FR-CAT-5 | NFR-A11Y-3 + // The colour label leads the caption: on the + // photograph it would compete with the flag and the + // badges for four corners, and here it is read with + // the name, in the row every cell already has. + HorizontalLayout { + spacing: 4px; + + if cell.label > 0: LabelMark { + code: cell.label; + size: 14px; + y: (parent.height - self.height) / 2; + } + + Label { + text: cell.name; + emphasised: cell.selected; + overflow: elide; + } + } + } + + // **Selection, and the only mark of it.** + // + // This ring used to mean something narrower — the end a + // shift-click measures from — and it was the clearest thing + // on the cell, so it read as the selection to everyone who + // had not written it. It also outlived what it described: an + // anchor survives a deselection, so a thin box sat around + // the last photograph touched with nothing selected at all, + // and there was no way to tell it apart from a cell that had + // stayed behind. + // + // So the ring is now what it already looked like. The anchor + // has no mark of its own, which costs nothing it was earning: + // the range gesture announces itself in the bar — "Tap the + // last photograph" — and does not need the user to find + // where it will measure from. + // + // Drawn *inside* the cell and 4px clear of the edge, so it + // never touches the thumbnail and never changes a single + // dimension. Selecting adds ink and moves nothing. + Rectangle { + visible: cell.selected; + x: 4px; + y: 4px; + width: parent.width - 8px; + height: parent.height - 8px; + background: transparent; + // Two, not one: this is now the whole of the cue, and it + // has to survive being read across forty cells at arm's + // length against a thumbnail of any brightness. + border-width: 2px; + border-color: Theme.selected-ring; + border-radius: Theme.radius-sm; + } + + // Selection only. The drag is the enclosing `DragArea`'s + // business, and Slint keeps a click distinct from a drag for + // us — which is exactly the arbitration the hand-rolled version + // had to fake with a travel threshold. + cell-touch := TouchArea { + mouse-cursor: pointer; + + // TRACES: FR-UI-5 + // Which photograph a judgement key would reach + // without a selection. Cleared only by the cell that + // set it, so moving from one cell to the next cannot + // be undone by the old cell's leave arriving second. + changed has-hover => { + if (self.has-hover) { + root.hover-ordinal = i + root.offset; + } else if (root.hover-ordinal == i + root.offset) { + root.hover-ordinal = -1; } } - TouchArea { - mouse-cursor: pointer; - clicked => { root.burst-toggled(i); } - } - } + // Selected on *press*, not on release: the drag that may + // follow reads the selection to build its payload, and by + // release the pointer is over the sidebar. + // + // In selection mode the press is reported as though ctrl + // were held. That is not a shortcut: toggling one cell + // while keeping the rest *is* what ctrl-click means, and + // giving touch its own policy would be a second copy of + // the rules in `collections_ui::apply_press` to keep in + // step with the first. + // --- which finger, and whether it finished -------- + // + // A pinch begins as an ordinary press. When the second + // finger lands Slint closes the first one's gesture by + // synthesising a `Released` at its position — that is + // how a Flickable is made to let go of a scroll it had + // already claimed. A TouchArea cannot tell that release + // from a real one and fires `clicked`, so every attempt + // to pinch-zoom the grid opened whichever photograph + // the first finger happened to be resting on. + // + // The finger id is what separates them: the synthetic + // release carries the id of the finger that *arrived*, + // never the one that pressed. A mouse reports 0 for + // both, so the desktop path is unchanged. + // + // `clicked` fires before the `up` that follows it, so + // it can only raise a flag — the decision to open has + // to wait for the event that names the finger. + // GESTURE: Open a photograph + // where: Library grid + // touch: Tap it — a single tap, any length + // pointer: Click it + // keys: `Enter`, on the photograph the arrow + // keys have walked to + // why: 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. + // manual: developing-a-photograph + property down-finger: -1; + property click-pending: false; - // GESTURE: Choose the frame a folded burst shows - // where: Library grid - // touch: Open the burst, then tap the ring on the - // frame you want - // pointer: Open the burst, then click the ring on the - // frame you want - // why: 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. - // manual: bursts - // - // Drawn on every frame of an open group and on none of - // a folded one. Folded, the single cell showing *is* - // the representative, so the mark would state the - // obvious and the frames it offers to choose between - // would not be on screen to choose from. - // - // Bottom right, opposite the count in the other corner: - // "this many frames" at one end and "this is the one" - // at the other, clear of the flag and the collection - // badge in the top two. A slip between it and ★5 sets a - // rating — the harmless direction for an ambiguous - // press, which is why it is on this side of the cell - // and not beside the trash target. - // - // A child of `cell-touch` for the reason the stars are: - // a press here must be spent on the choice and must not - // also reach `cell-clicked` and open the photograph. - if cell.burst-expanded: Rectangle { - x: parent.width - self.width - 6px; - y: parent.height - self.height - 26px; - width: 18px; - height: 18px; - border-radius: self.height / 2; - background: Theme.surface; - border-width: 1px; - border-color: cell.burst-representative - ? Theme.selected-ring : Theme.rule; + // Where the press landed, so the release can tell how + // far the finger travelled. + // + // The Flickable already cancels a press that becomes a + // scroll, but only once it has claimed the gesture, + // which takes more travel than a graze has. This + // catches the rest: a contact that slid across the cell + // and lifted was reaching for something else. + property press-x: 0; + property press-y: 0; - // Ticked or empty, and that is the whole of the - // state: which frame stands for the moment is said - // by the presence of a mark rather than by a colour - // (NFR-A11Y-3), so it survives being read at grid - // scale and by an eye that does not separate the - // two hues. - if cell.burst-representative: Icon { - name: "check"; - ink: Theme.active; - size: 11px; - x: (parent.width - self.width) / 2; - y: (parent.height - self.height) / 2; + /// How far a finger may slide and still be a tap. + /// + /// A thumb's contact patch is wider than this, so no + /// stationary tap approaches it; a hand moving across + /// the glass passes it within one frame. Below about + /// this the number stops describing intent and starts + /// describing how steady the user's hand is. + property tap-slop: 12px; + + pointer-event(ev) => { + if (ev.kind == PointerEventKind.down) { + self.down-finger = ev.touch-finger-id; + self.click-pending = false; + self.press-x = self.mouse-x; + self.press-y = self.mouse-y; + // A finger, not a pointer — see `touched`. + if (ev.touch-finger-id != 0) { + root.touched = true; + } + // Not while the grid is being pinched, nor in + // the moment after: this is the finger left + // over from the gesture, handed back as a new + // press. See `pinching`. + if (!root.pinching) { + // An armed range reaches Rust as shift, + // which is what it is: `apply_press` reads + // ctrl+shift as "add the run from the + // anchor to here", and in selection mode + // ctrl is already set. Sent this way rather + // than as a third selection policy, so the + // rules stay in one place. + root.cell-pressed( + i, + ev.modifiers.control || root.select-mode, + ev.modifiers.shift || root.ranging, + ); + // One shot. The range was between two taps + // and the second has landed; left armed, it + // would turn every tap after it into + // another run. + root.ranging = false; + } + } + if (ev.kind == PointerEventKind.up) { + // A *plain* click opens the image; a modified + // one is purely a selection gesture and must + // not navigate away from the grid the user is + // building a selection in. The modifier state + // is not carried here, so the press above + // records it and Rust decides — `cell-clicked` + // is only honoured when the press was + // unmodified. + if (self.click-pending + && !root.pinching + && ev.touch-finger-id == self.down-finger + && abs(self.mouse-x - self.press-x) < self.tap-slop + && abs(self.mouse-y - self.press-y) < self.tap-slop) { + root.cell-clicked(i); + } + self.click-pending = false; + // Down again, whether or not it was ever up. + root.held-row = -1; + root.cell-press-ended(); + } + // `cancel` is the important ending: the Flickable + // takes the pointer as soon as the finger travels, + // so without this a scroll that began on a cell + // would come to rest as a long press and select it. + if (ev.kind == PointerEventKind.cancel) { + self.click-pending = false; + // Including the cancel a starting drag sends: + // by then the `DragArea` has the gesture and + // `lifted` is the mark that matters, so there + // is no scroll left to lose and nothing to + // keep the ring open for. + root.held-row = -1; + root.cell-press-ended(); + } } - // Live on the chosen frame too, rather than inert - // there. A disabled TouchArea lets the press fall - // through to the cell behind it, so tapping the one - // mark that is already ticked would open the - // photograph — a control that does something - // unrelated when pressed twice. Pressing it instead - // says "yes, this one", which is a thing the user - // may mean: the default is the earliest frame, and - // recording the choice keeps it through a regroup - // that finds an earlier one. - TouchArea { - // Grown past the drawn ring to FR-UI-3's - // minimum and centred on it, the way the stars - // beside it are. - width: max(parent.width, Theme.touch-target); - height: max(parent.height, Theme.touch-target); + clicked => { self.click-pending = true; } + // **No `double-clicked` here, deliberately.** A double + // tap used to take a range in selection mode. It was + // the only gesture touch had for shift-click, and + // "Select to…" replaced it with a control that says + // what it is about to do. Leaving both meant two quick + // taps on one cell — a thing a hand does by accident — + // silently selecting a run of forty photographs, with + // no visible state to explain where they came from. + // + // Now two taps are two toggles and land back where they + // started, which is the only thing a user can predict + // from what is on screen. + + // GESTURE: Rate a photograph without opening it + // where: Library grid + // touch: Tap a star on the cell + // pointer: Hover the cell, then click a star + // keys: `0`–`5` with the pointer over it, or + // on the selection + // why: A star has to take the press without it + // also reaching the cell, or every rating + // throws the user into develop. + // manual: rating-and-flagging + // + // --- the rating strip, INSIDE the cell's hit area --- + // + // **A child of `cell-touch`, not a sibling of it.** Two + // things have to be true at once here, and only this + // nesting gets both. + // + // A star must take the click without it also reaching + // `cell-clicked`, or every rating throws the user into + // develop. Children are hit-tested before the element + // they sit in, and a child that accepts ends the walk + // before the TouchArea's own handler runs — so the star + // wins, and it wins for the same reason a later sibling + // used to. + // + // And the strip must not vanish as the pointer arrives + // at it. As a *sibling* it did: hover is tracked per + // TouchArea, Slint sends `Exit` to whatever drops out of + // the hit path, and the strip taking the pointer dropped + // `cell-touch` out of it. `has-hover` went false, which + // took `show-empty` with it, which hid the very stars + // the pointer was travelling towards — on an unrated + // cell they disappeared, the click landed on the cell + // behind them, and the image opened. That is the whole + // of the "stars vanish when I click one" report. + // + // An *ancestor* stays in the path: it keeps its place on + // the item stack, gets no `Exit`, and during a grab it is + // handed only the filter and never the event. So hover + // holds for as long as the pointer is anywhere in the + // cell, stars included. + // + // It sits over the foot of the thumbnail rather than + // below it: the caption row is spoken for by the + // filename, and a third row would cost thumbnail height + // on every cell to show something that is usually empty. + StarStrip { x: (parent.width - self.width) / 2; - y: (parent.height - self.height) / 2; - mouse-cursor: pointer; - clicked => { root.burst-representative-chosen(i); } + // Clear of the caption, which is the cell's last row. + y: parent.height - self.height - 26px; + rating: cell.rating; + // Empty stars appear once the pointer is over the + // cell, so there is something to aim at without + // filling the grid with chrome. That trade only + // works where there is a pointer to hover with: on + // touch the strip stands open, because a control + // that appears under the finger is a control that + // appears too late to aim at. See `touched`. + show-empty: cell-touch.has-hover || root.touched; + can-trash: !root.viewing-trash; + rate(n) => { root.cell-rated(i, n); } + trash() => { root.cell-trashed(i); } + } + + // The burst mark (FR-CULL-5): how many frames this + // moment holds, + // and the way in and out of them. + // + // A child of `cell-touch` for exactly the reason the + // stars above are: a click here must not also reach + // `cell-clicked` and throw the user into develop, and + // children are hit-tested before the element they sit + // in. Unlike the stars it is never hidden — a collapsed + // burst is standing in for frames that are not on + // screen, and there has to be something visible saying + // so whether or not a pointer is anywhere near. + // + // Bottom left, clear of the centred star strip and of + // both top corners, which the flag and the collection + // badge already have. + if cell.burst-count > 1: Rectangle { + x: 6px; + y: parent.height - self.height - 26px; + width: 30px; + height: 18px; + + // The pile behind the top card, drawn only while the + // group is folded up. It is the whole of the "there + // is more than one of these" cue; once the burst is + // open the frames themselves say it. + Rectangle { + x: 3px; + y: -3px; + width: parent.width - 3px; + height: parent.height; + visible: !cell.burst-expanded; + background: Theme.surface; + border-radius: Theme.radius-sm; + border-width: 1px; + border-color: Theme.rule; + } + + Rectangle { + width: 100%; + height: 100%; + background: cell.burst-expanded ? Theme.selected + : Theme.surface; + border-radius: Theme.radius-sm; + border-width: 1px; + border-color: cell.burst-expanded ? Theme.selected-ring + : Theme.rule; + + Text { + width: 100%; + height: 100%; + horizontal-alignment: center; + vertical-alignment: center; + // No "of": the number is the size of the + // group, and a cell this small cannot + // afford a word to say so. + text: cell.burst-count; + color: Theme.ink; + font-size: 10px; + font-weight: 700; + } + } + + TouchArea { + mouse-cursor: pointer; + clicked => { root.burst-toggled(i); } + } + } + + // GESTURE: Choose the frame a folded burst shows + // where: Library grid + // touch: Open the burst, then tap the ring on the + // frame you want + // pointer: Open the burst, then click the ring on the + // frame you want + // why: 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. + // manual: bursts + // + // Drawn on every frame of an open group and on none of + // a folded one. Folded, the single cell showing *is* + // the representative, so the mark would state the + // obvious and the frames it offers to choose between + // would not be on screen to choose from. + // + // Bottom right, opposite the count in the other corner: + // "this many frames" at one end and "this is the one" + // at the other, clear of the flag and the collection + // badge in the top two. A slip between it and ★5 sets a + // rating — the harmless direction for an ambiguous + // press, which is why it is on this side of the cell + // and not beside the trash target. + // + // A child of `cell-touch` for the reason the stars are: + // a press here must be spent on the choice and must not + // also reach `cell-clicked` and open the photograph. + if cell.burst-expanded: Rectangle { + x: parent.width - self.width - 6px; + y: parent.height - self.height - 26px; + width: 18px; + height: 18px; + border-radius: self.height / 2; + background: Theme.surface; + border-width: 1px; + border-color: cell.burst-representative + ? Theme.selected-ring : Theme.rule; + + // Ticked or empty, and that is the whole of the + // state: which frame stands for the moment is said + // by the presence of a mark rather than by a colour + // (NFR-A11Y-3), so it survives being read at grid + // scale and by an eye that does not separate the + // two hues. + if cell.burst-representative: Icon { + name: "check"; + ink: Theme.active; + size: 11px; + x: (parent.width - self.width) / 2; + y: (parent.height - self.height) / 2; + } + + // Live on the chosen frame too, rather than inert + // there. A disabled TouchArea lets the press fall + // through to the cell behind it, so tapping the one + // mark that is already ticked would open the + // photograph — a control that does something + // unrelated when pressed twice. Pressing it instead + // says "yes, this one", which is a thing the user + // may mean: the default is the earliest frame, and + // recording the choice keeps it through a regroup + // that finds an earlier one. + TouchArea { + // Grown past the drawn ring to FR-UI-3's + // minimum and centred on it, the way the stars + // beside it are. + width: max(parent.width, Theme.touch-target); + height: max(parent.height, Theme.touch-target); + x: (parent.width - self.width) / 2; + y: (parent.height - self.height) / 2; + mouse-cursor: pointer; + clicked => { root.burst-representative-chosen(i); } + } } } } - } - // Where the run would land. A bar in the gutter beside the - // cell it would sit next to, on whichever side the drop - // will actually use — the trailing edge is what makes the - // last place in a collection reachable, and a marker that - // did not move with it would be pointing at the wrong gap - // half the time. + // Where the run would land. A bar in the gutter beside the + // cell it would sit next to, on whichever side the drop + // will actually use — the trailing edge is what makes the + // last place in a collection reachable, and a marker that + // did not move with it would be pointing at the wrong gap + // half the time. + // + // Last child of the `DragArea`, so it draws over the + // thumbnail rather than under it. In the gutter rather than + // on the cell, because a bar drawn *on* the first cell of a + // row reads as belonging to that cell instead of to the + // space before it. + if reorder-drop.has-drag: Rectangle { + x: reorder-drop.after + ? parent.width + Theme.gap / 2 - 1.5px + : -Theme.gap / 2 - 1.5px; + y: 0; + width: 3px; + height: parent.height; + background: Theme.active; + border-radius: 1.5px; + } + } + + // **The photograph is in your hand now.** // - // Last child of the `DragArea`, so it draws over the - // thumbnail rather than under it. In the gutter rather than - // on the cell, because a bar drawn *on* the first cell of a - // row reads as belonging to that cell instead of to the - // space before it. - if reorder-drop.has-drag: Rectangle { - x: reorder-drop.after - ? parent.width + Theme.gap / 2 - 1.5px - : -Theme.gap / 2 - 1.5px; - y: 0; - width: 3px; - height: parent.height; - background: Theme.active; - border-radius: 1.5px; + // A ring that opens outward around the cell a press has held + // long enough to pick up (see `held-row`), so the wait the + // gesture needs has something to end in. Without it the user + // is holding a finger on glass with no way to know whether + // anything has happened — which is what made dragging feel + // like a coin toss. + // + // **Drawn after the cells, not on one.** Cells are one `for`, + // and z-order inside a `for` is the loop order — a cell that + // grew past its bounds would stand over its left and top + // neighbours and be cut off by its right and bottom ones, + // which reads as a rendering fault rather than as a lift. One + // element after the loop is above every cell by construction, + // and there is only ever one photograph in the hand. + // + // `visible` rather than `if`, so it has somewhere to animate + // *from*: an `if` builds the ring at its final size and it + // would appear rather than open. + // + // `Theme.active` and 3px, deliberately unlike the selection + // ring inside the cell — two marks that meant different things + // in one colour is the mistake the hover ring made. + Rectangle { + property pitch: root.cell-size + Theme.gap; + property reach: root.held-row >= 0 ? 5px : 0px; + + visible: root.held-row >= 0; + x: Theme.gap + + mod(root.held-row + root.offset, root.columns) * self.pitch + - self.reach; + y: Theme.gap + + floor((root.held-row + root.offset) / root.columns) * self.pitch + - self.reach; + width: root.cell-size + 2 * self.reach; + height: root.cell-size + 2 * self.reach; + animate x, y, width, height { duration: 120ms; easing: ease-out; } + + background: transparent; + border-width: 3px; + border-color: Theme.active; + border-radius: Theme.radius; } } - // **The photograph is in your hand now.** - // - // A ring that opens outward around the cell a press has held - // long enough to pick up (see `held-row`), so the wait the - // gesture needs has something to end in. Without it the user - // is holding a finger on glass with no way to know whether - // anything has happened — which is what made dragging feel - // like a coin toss. - // - // **Drawn after the cells, not on one.** Cells are one `for`, - // and z-order inside a `for` is the loop order — a cell that - // grew past its bounds would stand over its left and top - // neighbours and be cut off by its right and bottom ones, - // which reads as a rendering fault rather than as a lift. One - // element after the loop is above every cell by construction, - // and there is only ever one photograph in the hand. - // - // `visible` rather than `if`, so it has somewhere to animate - // *from*: an `if` builds the ring at its final size and it - // would appear rather than open. - // - // `Theme.active` and 3px, deliberately unlike the selection - // ring inside the cell — two marks that meant different things - // in one colour is the mistake the hover ring made. - Rectangle { - property pitch: root.cell-size + Theme.gap; - property reach: root.held-row >= 0 ? 5px : 0px; - - visible: root.held-row >= 0; - x: Theme.gap - + mod(root.held-row + root.offset, root.columns) * self.pitch - - self.reach; - y: Theme.gap - + floor((root.held-row + root.offset) / root.columns) * self.pitch - - self.reach; - width: root.cell-size + 2 * self.reach; - height: root.cell-size + 2 * self.reach; - animate x, y, width, height { duration: 120ms; easing: ease-out; } - - background: transparent; - border-width: 3px; - border-color: Theme.active; - border-radius: Theme.radius; + // A bar for a pointer, beside the timeline rather than instead of it: + // the timeline says *when* the view is, the bar how far through the + // grid, and dragging it is the plain way to cross a large library + // without a wheel. It reads the viewport the rows are counted into, so + // it spans every photograph, not just the loaded window. + ScrollBar { + x: parent.width - self.width; + y: 0px; + viewport-y <=> grid-scroll.viewport-y; + viewport-height: grid-scroll.viewport-height; + visible-height: grid-scroll.height; } }