Build the People rail rows you can see, not the ones you cannot
`Flickable { VerticalLayout { for ... } }` instantiates every row. On the
reference library that is 14,268 row subtrees, which measures at 4.2
seconds before a pixel is drawn — and it was paid again on every action,
because every action reloads the model. Roughly half the wait between
clicking "Identity" and the screen appearing was this.
Slint's compiler has a virtualising path for a `for`; it is what makes
`std-widgets`' `ListView` cheap. It keys on the parent element's base
being *named* `ListView` and exposing the five lengths its layouting code
writes back, and a custom base is explicitly allowed. So `widgets.slint`
grows one, and `std-widgets` stays out of the file that establishes our
style. Measured on the real screen: 200,000 rail rows now render in 62ms.
Three things had to be true together, and each was silent on its own.
**The row height must be constant.** The rail hid a set-aside person with
`height: cond ? 52px : 0px`, a height that reads the model — so the
layout cannot place row N without building rows 0..N, and Slint builds
them all. The filtering moves to Rust, where the toggle was already
reloading anyway.
**The list must not be wrapped.** It carries its own stretch and preferred
size, having no natural height to offer; a Rectangle in between hands the
layout that Rectangle's constraints, which are taken from the list and
are therefore nothing.
**The panel must let it fill.** `Panel` lays its children out with
`alignment: start`, which gives each its preferred height — right for a
column of sliders, wrong for anything that scrolls. Hence `Panel.fill`.
Get any of them wrong and the rail renders empty, with no error and a
model full of people. All three were, in turn, before a headless render
of the real screen showed a blank rail.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
+32
-13
@@ -15,7 +15,7 @@
|
||||
// the confirm/reject pair sits on the face itself rather than behind a menu.
|
||||
|
||||
import { Theme } from "theme.slint";
|
||||
import { Panel, Button, IconButton, Field } from "widgets.slint";
|
||||
import { Panel, Button, IconButton, Field, ListView } from "widgets.slint";
|
||||
import { SliderRow } from "controls.slint";
|
||||
import { Icon } from "icons.slint";
|
||||
|
||||
@@ -318,6 +318,9 @@ export component IdentityScreen inherits Rectangle {
|
||||
// than disappearing — 132px keeps the 40px cover and elides the
|
||||
// labels, and leaves the faces grid enough width for two columns.
|
||||
width: root.compact ? 132px : 260px;
|
||||
// The rail scrolls, so its column has to be given the panel's
|
||||
// height rather than the sum of its parts — see `Panel.fill`.
|
||||
fill: true;
|
||||
|
||||
VerticalLayout {
|
||||
padding: Theme.gap;
|
||||
@@ -346,18 +349,30 @@ export component IdentityScreen inherits Rectangle {
|
||||
wrap: word-wrap;
|
||||
}
|
||||
|
||||
Flickable {
|
||||
VerticalLayout {
|
||||
spacing: 2px;
|
||||
alignment: start;
|
||||
// A `ListView`, so the rail costs what is on screen rather than
|
||||
// what the library holds. See `widgets.slint` — and heed its
|
||||
// one rule: **the row height below is a constant**, and the
|
||||
// model arrives already filtered. Hiding a row by collapsing it
|
||||
// to 0px, which is what this did, is a height that reads the
|
||||
// model, and it makes Slint build all of them anyway.
|
||||
//
|
||||
// A direct child of this layout, with nothing wrapped around
|
||||
// it. `ListView` carries its own stretch and preferred size —
|
||||
// it has to, having no natural height to offer — and putting a
|
||||
// plain Rectangle in between hands the layout that Rectangle's
|
||||
// constraints instead, which are taken from the list and are
|
||||
// therefore nothing. The rail comes out empty, with every
|
||||
// person still in the model.
|
||||
ListView {
|
||||
for p[i] in root.people: Rectangle {
|
||||
// 52 of a row, then the 2px that used to be the
|
||||
// layout's `spacing`. Virtualisation places row N
|
||||
// at N × this, so the gap lives inside the row.
|
||||
height: 54px;
|
||||
|
||||
for p[i] in root.people: Rectangle {
|
||||
// Height and visibility rather than filtering the
|
||||
// model in Rust: the rail is rebuilt on every
|
||||
// action, and re-deriving a second filtered list
|
||||
// per rebuild is work the toggle can do for free.
|
||||
visible: root.show-ignored || !p.ignored;
|
||||
height: (root.show-ignored || !p.ignored) ? 52px : 0px;
|
||||
Rectangle {
|
||||
y: 0px;
|
||||
height: 52px;
|
||||
border-radius: Theme.radius;
|
||||
background: p.id == root.selected-person
|
||||
? Theme.selected
|
||||
@@ -408,7 +423,11 @@ export component IdentityScreen inherits Rectangle {
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle { }
|
||||
// No spacer between the rail and the buttons below it. There
|
||||
// used to be one, to push them to the bottom of a short rail;
|
||||
// the rail now stretches into that space itself, and a second
|
||||
// stretching child would halve the list to make room for
|
||||
// nothing.
|
||||
|
||||
// What the "not interested" button has hidden, and the way
|
||||
// back to it. A count with no way to reveal it would make the
|
||||
|
||||
Reference in New Issue
Block a user