Measure the rail by counting it, so it can scroll instead of clipping

With the adjustment groups in the rail, a short window silently lost them.
At a 1500x680 window the rail drew Photo, Compose, Local, Repair, the seam and
"All", and then stopped: Optics, Light, Colour, Effects and Detail were not
scrolled off, they were gone, with nothing on screen to say so. The develop
view's primary navigation, unreachable by any means.

The Flickable was put here to prevent exactly that, and it could not, because
its viewport asked the layout how tall it wanted to be while the layout was
already taking its height from the viewport. Slint settles that cycle by
handing back the height it was given, so `max(self.height, preferred-height)`
could never exceed `self.height` and there was never anything to scroll.

Counting breaks the cycle. Every entry here is a fixed height by construction —
a tool is `rail-entry-height`, a group is a touch target — so the content is
six plus four fifty-fours plus a gap plus a touch target for each group and
"All", which is exact rather than an estimate and depends on nothing that
depends on it. Four tools and five groups come to 498, against the 340 a
680-pixel window leaves at 2x, and the difference is now scrollable rather
than absent.

Found by shrinking the window with the groups forced into the rail. The
interaction itself is unverified: synthetic input does not reach a Slint
window on this desktop, and the tablet was disconnected, so what is confirmed
is the arithmetic and the clipping it explains, not the scrolling it should
restore.
This commit is contained in:
2026-09-07 00:17:08 +02:00
parent 3dc7c184ee
commit 2cd49d1cb7
3 changed files with 126 additions and 57 deletions
+23 -1
View File
@@ -176,11 +176,33 @@ export component ToolRail inherits Rectangle {
// count is no longer written here, and a rail that overflows loses its
// last entries silently, on the one control the develop view is navigated
// by.
// **How tall the entries actually are, counted rather than measured.**
//
// This used to read `max(self.height, layout.preferred-height)`, with the
// layout below taking its height from the viewport in turn. That is a
// cycle — the viewport asks the layout how tall it wants to be, and the
// layout has already been told — and Slint settles it by handing back the
// height it was given. The viewport then never exceeded the visible area,
// so there was nothing to scroll and everything past the fold was clipped
// in silence: at a 680px window the rail stopped after "All" and the five
// groups below it could not be reached by any means.
//
// Counting works because every entry in this rail is a fixed height by
// construction — a tool is `rail-entry-height` and a group is a touch
// target, both stated a few lines below — so this is exact rather than an
// estimate, and it depends on nothing that depends on it.
private property <length> content-height:
Theme.gap-sm
+ root.tools.length * Theme.rail-entry-height
+ (root.groups-in-rail
? Theme.gap + Theme.touch-target * (1 + root.rail-tabs.length)
: 0px);
Flickable {
width: 100%;
height: 100%;
viewport-width: self.width;
viewport-height: max(self.height, layout.preferred-height);
viewport-height: max(self.height, root.content-height);
layout := VerticalLayout {
height: parent.viewport-height;