Put a scrollbar on the collections sidebar

A tree longer than the panel looked, to a mouse, like the whole tree.
Same shape as the develop column: the Flickable fills a Rectangle that
takes its stretch, and a ScrollBar is drawn over its right edge. It is
drawn only when the tree overflows.
This commit is contained in:
2026-09-26 07:25:07 -04:00
parent 825015a58e
commit 005dc3a835
3 changed files with 86 additions and 72 deletions
File diff suppressed because one or more lines are too long
+1 -1
View File
@@ -407,7 +407,7 @@ Double-click is what a file manager and a Lightroom panel use for the same thing
Under the trash because the trash is where the spare copies go, and only while the catalog holds any: a row that is always there and usually empty is noise.
<sub>`ui/dr-ui/ui/collections.slint:870`</sub>
<sub>`ui/dr-ui/ui/collections.slint:884`</sub>
## Duplicate originals
+77 -63
View File
@@ -34,7 +34,7 @@
import { Theme } from "theme.slint";
import { Duplicates } from "duplicates.slint";
import { Keys } from "keys.slint";
import { Button, Icon } from "widgets.slint";
import { Button, Icon, ScrollBar } from "widgets.slint";
// One row of the collection tree.
export struct CollectionRow {
@@ -673,76 +673,90 @@ export component CollectionsPanel inherits Rectangle {
}
// --- the tree -----------------------------------------------------
Flickable {
vertical-stretch: 1;
viewport-height: root.rows.length * (Theme.row-height + 2px);
// Wrapped rather than made draggable from the inside: the grid's
// cells take the same shape, and a `DragArea` around the whole row
// keeps the row's own TouchArea nested under it, which is what lets
// a click still select while a drag reparents.
for row[i] in root.rows: DragArea {
y: i * (Theme.row-height + 2px);
Rectangle {
vertical-stretch: 1;
tree-flick := Flickable {
width: parent.width;
height: Theme.row-height;
height: parent.height;
viewport-height: root.rows.length * (Theme.row-height + 2px);
// Move, not copy. A collection has one parent, so dragging it
// somewhere takes it out of where it was — unlike a photograph,
// which is filed in as many collections as you like.
allow-move: true;
data: root.collection-drag-payload(row.id);
// Wrapped rather than made draggable from the inside: the grid's
// cells take the same shape, and a `DragArea` around the whole row
// keeps the row's own TouchArea nested under it, which is what lets
// a click still select while a drag reparents.
for row[i] in root.rows: DragArea {
y: i * (Theme.row-height + 2px);
width: parent.width;
height: Theme.row-height;
// The hold armed this drag and lifted the row; this is where
// Rust learns the movement actually began, so the release that
// ends it opens no menu.
changed dragging => { root.drag-active(row.id, self.dragging); }
// Move, not copy. A collection has one parent, so dragging it
// somewhere takes it out of where it was — unlike a photograph,
// which is filed in as many collections as you like.
allow-move: true;
data: root.collection-drag-payload(row.id);
TreeRow {
width: 100%;
height: 100%;
entry: row;
selected: row.id == root.selected-id;
lifted: row.id == root.lifted-id;
// A saved filter's membership is computed, so a drop cannot
// land there. Refused in `can-drop`, so the cursor says no
// before the release rather than after.
drop-allowed: !row.smart;
renaming: row.id == root.renaming-id;
// The hold armed this drag and lifted the row; this is where
// Rust learns the movement actually began, so the release that
// ends it opens no menu.
changed dragging => { root.drag-active(row.id, self.dragging); }
clicked => { root.select(row.id); }
toggle => { root.toggle(row.id); }
context-menu => { root.row-menu(row.id); }
offline-menu => { root.offline-menu(row.id); }
press-changed(down) => { root.row-press(row.id, down); }
dropped => { root.dropped-on(row.id); }
drag-over(over) => { root.drag-over(row.id, over); }
rename-requested => { root.rename-start(row.id); }
rename-committed(name) => { root.rename-commit(row.id, name); }
rename-cancelled => { root.rename-cancel(); }
keys-released => { root.keys-released(); }
TreeRow {
width: 100%;
height: 100%;
entry: row;
selected: row.id == root.selected-id;
lifted: row.id == root.lifted-id;
// A saved filter's membership is computed, so a drop cannot
// land there. Refused in `can-drop`, so the cursor says no
// before the release rather than after.
drop-allowed: !row.smart;
renaming: row.id == root.renaming-id;
clicked => { root.select(row.id); }
toggle => { root.toggle(row.id); }
context-menu => { root.row-menu(row.id); }
offline-menu => { root.offline-menu(row.id); }
press-changed(down) => { root.row-press(row.id, down); }
dropped => { root.dropped-on(row.id); }
drag-over(over) => { root.drag-over(row.id, over); }
rename-requested => { root.rename-start(row.id); }
rename-committed(name) => { root.rename-commit(row.id, name); }
rename-cancelled => { root.rename-cancel(); }
keys-released => { root.keys-released(); }
}
}
// Empty state. A blank panel gives no hint that collections exist
// at all, let alone that images can be dragged into them.
if root.rows.length == 0: VerticalLayout {
alignment: center;
spacing: Theme.gap-sm;
padding: Theme.gap;
Text {
text: "No collections yet";
color: Theme.ink-dim;
font-size: Theme.text-sm;
horizontal-alignment: center;
}
Text {
text: "Press + to make one, then drag photographs onto it.";
color: Theme.ink-faint;
font-size: Theme.text-sm;
horizontal-alignment: center;
wrap: word-wrap;
}
}
}
// Empty state. A blank panel gives no hint that collections exist
// at all, let alone that images can be dragged into them.
if root.rows.length == 0: VerticalLayout {
alignment: center;
spacing: Theme.gap-sm;
padding: Theme.gap;
Text {
text: "No collections yet";
color: Theme.ink-dim;
font-size: Theme.text-sm;
horizontal-alignment: center;
}
Text {
text: "Press + to make one, then drag photographs onto it.";
color: Theme.ink-faint;
font-size: Theme.text-sm;
horizontal-alignment: center;
wrap: word-wrap;
}
// A bar for a pointer: a tree of collections longer than the panel
// otherwise looks, to a mouse, like the whole tree.
ScrollBar {
x: parent.width - self.width;
y: 0px;
viewport-y <=> tree-flick.viewport-y;
viewport-height: tree-flick.viewport-height;
visible-height: tree-flick.height;
}
}