Give the film list a scrollbar on the desktop
A list cut off at its edge looks, to a mouse, like a list that ends there. The open film list shows ten rows of twenty-eight, and nothing said there were more. The user asked for visible scrollbars on every platform but Android. ScrollBar (widgets.slint) is a vertical bar drawn over a Flickable's right-hand edge. It shows the share on screen and the position, it can be dragged by the thumb from wherever it was grabbed, a click on the track moves a page toward the click, and the wheel over it scrolls. It is the Flickable's sibling rather than a wrapper, bound to `viewport-y <=> flick.viewport-y` and the two heights, so a scroller keeps its own sizing. It is drawn over the content rather than beside it, so the mandated column widths are not reduced. With nothing to scroll it is not drawn and takes no input. Whether to draw it is Scrolling.bars, which Rust sets from dr_plat::is_touch_first(). That is the same function that puts the develop groups in the rail, and it answers the same question: what is the user pointing with? On Android, lists still scroll by flick only. Placing the bar inside another scroller would put it back in that scroller's arbitration. The film list can have one because it is now a popup.
This commit is contained in:
@@ -10,7 +10,7 @@
|
||||
import { Theme } from "theme.slint";
|
||||
import { Develop } from "session.slint";
|
||||
import { Keys } from "keys.slint";
|
||||
import { PanelHeading, Label, Value, Caption, Button, IconButton, Swatch } from "widgets.slint";
|
||||
import { PanelHeading, Label, Value, Caption, Button, IconButton, Swatch, ScrollBar } from "widgets.slint";
|
||||
import { SliderTrack, ControlRow, CurveEditor, Segmented, ChoiceChip, ChipGrid, Check } from "controls.slint";
|
||||
|
||||
// One parameter, flattened for Slint's model system.
|
||||
@@ -1462,6 +1462,17 @@ export component AdjustPanel inherits Rectangle {
|
||||
}
|
||||
}
|
||||
|
||||
// The one scroller the list has, so on a desktop it
|
||||
// wears the bar: ten rows of twenty-eight must say
|
||||
// there are more, or the list reads as ending there.
|
||||
ScrollBar {
|
||||
x: parent.width - self.width;
|
||||
y: 0px;
|
||||
viewport-y <=> film-flick.viewport-y;
|
||||
viewport-height: film-flick.viewport-height;
|
||||
visible-height: film-flick.height;
|
||||
}
|
||||
|
||||
// GESTURE: Choose a film stock from the keyboard
|
||||
// where: Develop
|
||||
// touch: Tap the Film row, flick the list, tap a stock
|
||||
|
||||
@@ -9,7 +9,7 @@ import { LaunchScreen } from "launch.slint";
|
||||
import { IdentityScreen, IdentityPerson, IdentityFace } from "identity.slint";
|
||||
import { LibraryGrid, LibraryCell, TimelineBar, PhotoRoll, KeywordRow, PersonChip, Library } from "library.slint";
|
||||
import { GestureRow, GestureSheet } from "gestures.slint";
|
||||
import { Button, PanelHeading, Label, Value, Caption, Panel, EmptyState, ProgressBar, ActivityRow } from "widgets.slint";
|
||||
import { Button, PanelHeading, Label, Value, Caption, Panel, EmptyState, ProgressBar, ActivityRow, Scrolling } from "widgets.slint";
|
||||
import { CollectionsPanel, CollectionRow, Collections, OfflinePrompt, CollectionMenu,
|
||||
MembershipSheet, MembershipRow } from "collections.slint";
|
||||
import { HistogramPanel, HistogramView, Levels } from "histogram.slint";
|
||||
@@ -38,6 +38,7 @@ export { Collections }
|
||||
export { Library }
|
||||
export { ExportOptions }
|
||||
export { Duplicates, DuplicateRow, DuplicateCopy }
|
||||
export { Scrolling }
|
||||
|
||||
/// Which top-level screen is on display. Exactly one at a time — replacing
|
||||
/// the `show-launch` / `show-library` / `show-identity` booleans, with
|
||||
|
||||
@@ -1056,3 +1056,128 @@ export component ListView {
|
||||
@children
|
||||
}
|
||||
}
|
||||
|
||||
// TRACES: FR-UI-1
|
||||
// Whether this build draws scrollbars.
|
||||
//
|
||||
// Set once by Rust from `dr_plat::is_touch_first()` — the same answer that
|
||||
// puts the adjustment groups in the tool rail, and for the same reason: it
|
||||
// says what the user points with, which is the only thing a scrollbar is
|
||||
// about. A pointer needs one, to see that a list goes on past its edge, how
|
||||
// far, and where it is in it, and to get there without a wheel. A finger
|
||||
// does not: it flicks, and a thin bar along the edge of a tablet is a target
|
||||
// no finger hits on purpose and one a scrolling thumb hits by accident.
|
||||
export global Scrolling {
|
||||
in property <bool> bars: true;
|
||||
}
|
||||
|
||||
// GESTURE: Scroll by the scrollbar
|
||||
// where: Everywhere
|
||||
// pointer: Drag the bar along the right-hand edge of a list, or click the
|
||||
// track above or below it to move by a page
|
||||
// why: A list cut off at its edge looks, to a mouse, like a list that
|
||||
// ends there — the film stocks past the tenth read as deleted.
|
||||
// The bar says there is more and where the view is in it, and it
|
||||
// is the one way to scroll that needs neither a wheel nor a drag
|
||||
// on the content, which may be a row that would take the click.
|
||||
//
|
||||
// A vertical scrollbar for a `Flickable`, drawn over its right-hand edge.
|
||||
//
|
||||
// **A sibling of the Flickable, not a wrapper around it.** Every scroller in
|
||||
// this application has a note on why its viewport is spelled the way it is,
|
||||
// and a wrapper would have to re-expose all of it. The bar instead binds to
|
||||
// the Flickable the caller already has — `viewport-y <=> flick.viewport-y`
|
||||
// and the two heights — so the scroller keeps its sizing and gains a bar.
|
||||
// Put both in a plain `Rectangle` the Flickable fills, and place the bar at
|
||||
// its right edge: `x: parent.width - self.width; y: 0;`.
|
||||
//
|
||||
// **Over the content, not beside it.** Beside it would take its width from
|
||||
// every column it is added to, and some of those widths are mandated
|
||||
// (`panel-width` in `style.yaml`). Over it, it costs the strip it sits on,
|
||||
// and only while there is somewhere to scroll to: with nothing overflowing it
|
||||
// is not drawn and takes no input at all.
|
||||
//
|
||||
// **Not in a scroller that scrolls itself.** A bar inside another Flickable
|
||||
// is inside that Flickable's arbitration, and the outer one claims a vertical
|
||||
// drag before the bar sees the press — the reason the film list is a popup
|
||||
// and not a list inside the develop column.
|
||||
export component ScrollBar {
|
||||
/// The Flickable's `viewport-y`, two-way: zero at the top, negative below.
|
||||
in-out property <length> viewport-y;
|
||||
/// The Flickable's `viewport-height`: how tall the content is.
|
||||
in property <length> viewport-height;
|
||||
/// The Flickable's own height: how much of the content is on screen.
|
||||
in property <length> visible-height;
|
||||
|
||||
/// How far the content can move.
|
||||
property <length> range: max(0px, root.viewport-height - root.visible-height);
|
||||
property <bool> live: Scrolling.bars && root.range > 0.5px;
|
||||
/// In proportion to the share on screen, and never too small to grab.
|
||||
property <length> thumb-height:
|
||||
min(root.height, max(24px, root.height * root.visible-height / max(1px, root.viewport-height)));
|
||||
property <length> travel: max(1px, root.height - root.thumb-height);
|
||||
property <length> thumb-y:
|
||||
root.travel * clamp(-root.viewport-y / max(1px, root.range), 0, 1);
|
||||
property <bool> lit: area.has-hover || area.pressed;
|
||||
|
||||
width: 10px;
|
||||
height: root.visible-height;
|
||||
visible: root.live;
|
||||
|
||||
function scroll-to(top: length) {
|
||||
root.viewport-y = -clamp(top, 0px, root.range);
|
||||
}
|
||||
|
||||
// The track, drawn only while the pointer is on it: at rest a scroller
|
||||
// shows only its thumb, which is all the "there is more" it needs to say.
|
||||
Rectangle {
|
||||
background: root.lit ? Theme.hover : transparent;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
x: root.lit ? 2px : 4px;
|
||||
y: root.thumb-y;
|
||||
width: parent.width - self.x - 2px;
|
||||
height: root.thumb-height;
|
||||
border-radius: self.width / 2;
|
||||
background: area.pressed ? Theme.ink : (area.has-hover ? Theme.ink-dim : Theme.rule);
|
||||
}
|
||||
|
||||
area := TouchArea {
|
||||
enabled: root.live;
|
||||
/// Where on the thumb the press landed, so the thumb does not jump to
|
||||
/// centre itself under the pointer when it is grabbed off-centre.
|
||||
property <length> grip: 0px;
|
||||
property <bool> on-thumb: false;
|
||||
|
||||
pointer-event(event) => {
|
||||
if (event.button == PointerEventButton.left && event.kind == PointerEventKind.down) {
|
||||
if (self.mouse-y >= root.thumb-y && self.mouse-y <= root.thumb-y + root.thumb-height) {
|
||||
self.on-thumb = true;
|
||||
self.grip = self.mouse-y - root.thumb-y;
|
||||
} else {
|
||||
// The track: a page toward the click, as a desktop
|
||||
// scrollbar does. A page and not a jump to the point,
|
||||
// because a page keeps a line of what was on screen in
|
||||
// view, and a jump loses the reader's place.
|
||||
self.on-thumb = false;
|
||||
root.scroll-to(-root.viewport-y
|
||||
+ (self.mouse-y < root.thumb-y ? -1 : 1) * root.visible-height * 0.9);
|
||||
}
|
||||
}
|
||||
}
|
||||
moved => {
|
||||
if (self.pressed && self.on-thumb) {
|
||||
root.scroll-to((self.mouse-y - self.grip) / root.travel * root.range);
|
||||
}
|
||||
}
|
||||
// The wheel over the bar scrolls what it is the bar of. The bar is
|
||||
// the Flickable's sibling rather than its child, so without this a
|
||||
// wheel here would reach nothing.
|
||||
scroll-event(event) => {
|
||||
root.scroll-to(-root.viewport-y - event.delta-y);
|
||||
accept
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user