Wrap the chip rows, so one six-choice parameter stops sizing the sidebar
The develop column asked for 482px. Every comment in it, a dozen of them, describes it as a 280px column — and on a tablet it was taking 40% of the screen from the photograph it exists to serve. Measured rather than guessed, because none of it is visible in the source: the column takes the widest width any panel declares, `AdjustPanel` wanted 482 of it, its rows wanted 458, and after the 44px scroll gutter the widest single row was 414. That row is `film_sim`'s `format` — six film formats from 35mm to 8x10, laid out by `Segmented` as six 64px chips in a `HorizontalLayout` that cannot wrap. 6 x 64 + 5 x 6 = 414, exactly. Nothing about that is the film simulation's fault. An operation declares its parameters and the panel decides how to draw them (FR-DEV-3a), so a node is entitled to offer six choices; it is the drawing that has to cope. Any future operation with a five-choice enum would have done the same thing, silently, to every screen in the application. `ChipGrid` is the general answer: chips placed by index arithmetic inside a plain `Rectangle`, wrapping at a column count, declaring a width that depends on the columns rather than on the number of choices. `Segmented` takes a `columns` property and uses it when asked — zero, one row however many chips, stays the settings page's behaviour, where the page is full-width and reading the alternatives side by side is the whole argument for chips over a dropdown. The generated enum rows and the curve-channel picker now wrap at three, and the crop ratio chips use the shared grid instead of the private copy of it they shipped with last week. The column measures 351 now, down from 482, and what sets it is the mode strip rather than a parameter — which is a control the user chose to have on screen rather than an accident of one node's variant list. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
+16
-41
@@ -9,7 +9,7 @@
|
||||
|
||||
import { Theme } from "theme.slint";
|
||||
import { PanelHeading, Label, Value, Caption, Button, IconButton, Swatch } from "widgets.slint";
|
||||
import { SliderTrack, ControlRow, CurveEditor, Segmented, ChoiceChip } from "controls.slint";
|
||||
import { SliderTrack, ControlRow, CurveEditor, Segmented, ChoiceChip, ChipGrid } from "controls.slint";
|
||||
|
||||
// One parameter, flattened for Slint's model system.
|
||||
//
|
||||
@@ -434,6 +434,11 @@ component ParamControl inherits Rectangle {
|
||||
label: root.data.param-label;
|
||||
options: root.data.choices;
|
||||
selected: Math.round(root.data.value);
|
||||
// Wrapped, because this column's width is the largest any panel
|
||||
// asks for and an operation may declare as many choices as it
|
||||
// likes. `film_sim`'s six film formats laid in one row came to
|
||||
// 414px, which set the width of the sidebar on every screen.
|
||||
columns: 3;
|
||||
picked(i) => {
|
||||
root.param-changed(root.data.op-index, root.data.param-index, i);
|
||||
}
|
||||
@@ -457,6 +462,7 @@ component ParamControl inherits Rectangle {
|
||||
// draws: a curve row heads no group, so without this the plot
|
||||
// would sit in the panel unlabelled.
|
||||
label: root.data.op-label;
|
||||
columns: 3;
|
||||
options: root.curve-channels;
|
||||
selected: root.curve-channel;
|
||||
picked(i) => { root.curve-channel-picked(i); }
|
||||
@@ -647,45 +653,14 @@ export component GeometryPanel inherits Rectangle {
|
||||
// for the same reason.
|
||||
if root.cropping: Label { text: "Ratio"; body: true; }
|
||||
|
||||
// **The chips are placed, not laid out, and that is not a
|
||||
// shortcut.** Slint has no wrapping layout, and a `for` inside a
|
||||
// `GridLayout` compiles and then fails at run time —
|
||||
// `RepeatedItemTree::grid_layout_input_data() not implemented`,
|
||||
// printed once per frame while every chip piles into a single row
|
||||
// that runs off the panel. A `HorizontalLayout` is worse still: six
|
||||
// chips side by side need over 400px, and this column's width is
|
||||
// the largest any panel declares, so it would widen every other
|
||||
// panel in the application to fit a control that is on screen only
|
||||
// while cropping.
|
||||
//
|
||||
// So the grid is arithmetic over the index. It costs the layout
|
||||
// engine nothing, it wraps a seventh ratio onto a third row by
|
||||
// itself, and — because a bare `Rectangle` declares no preferred
|
||||
// width — it takes whatever width the column already has instead
|
||||
// of setting it.
|
||||
if root.cropping: Rectangle {
|
||||
property <int> columns: 3;
|
||||
property <int> rows: ceil(root.aspects.length / self.columns);
|
||||
property <length> pitch: Theme.control-height + Theme.gap-sm;
|
||||
property <length> chip-width:
|
||||
(self.width - (self.columns - 1) * Theme.gap-sm) / self.columns;
|
||||
|
||||
// A floor, so a narrow window shrinks the column rather than
|
||||
// the chips: three chips at the width `ChoiceChip` calls
|
||||
// comfortable is what this row is for.
|
||||
min-width: self.columns * 64px + (self.columns - 1) * Theme.gap-sm;
|
||||
height: self.rows * self.pitch - Theme.gap-sm;
|
||||
|
||||
for name[i] in root.aspects: ChoiceChip {
|
||||
x: mod(i, parent.columns) * (parent.chip-width + Theme.gap-sm);
|
||||
y: floor(i / parent.columns) * parent.pitch;
|
||||
width: parent.chip-width;
|
||||
height: Theme.control-height;
|
||||
label: name;
|
||||
selected: i == root.aspect;
|
||||
enabled: root.enabled;
|
||||
clicked => { root.aspect-picked(i); }
|
||||
}
|
||||
// The same wrapping grid the generated enum rows use, for the
|
||||
// same reason: six chips in a row would be 414px of chips setting
|
||||
// the width of a column that is meant to be 280.
|
||||
if root.cropping: ChipGrid {
|
||||
options: root.aspects;
|
||||
selected: root.aspect;
|
||||
enabled: root.enabled;
|
||||
picked(i) => { root.aspect-picked(i); }
|
||||
}
|
||||
|
||||
// One chip, lit when the ratio is standing on its short edge,
|
||||
@@ -705,7 +680,7 @@ export component GeometryPanel inherits Rectangle {
|
||||
|
||||
ChoiceChip {
|
||||
x: 0;
|
||||
width: (parent.width - 2 * Theme.gap-sm) / 3;
|
||||
width: 88px;
|
||||
height: 100%;
|
||||
label: "Portrait";
|
||||
selected: root.portrait;
|
||||
|
||||
@@ -471,6 +471,64 @@ export component FieldRow inherits HorizontalLayout {
|
||||
}
|
||||
}
|
||||
|
||||
// TRACES: FR-UI-2
|
||||
// A block of chips that wraps, laid out by arithmetic rather than by a layout.
|
||||
//
|
||||
// **Slint has no wrapping layout, and a `HorizontalLayout` of chips is a
|
||||
// hazard in a narrow column.** Chips carry a comfortable minimum width, so a
|
||||
// row of them reports that minimum times their count as the width it needs —
|
||||
// and in the develop column, where every panel's declared width is `max`ed
|
||||
// into one number, a single six-choice parameter silently sets the width of
|
||||
// the whole application's sidebar. `film_sim`'s six film formats did exactly
|
||||
// that: 414px of chips holding a column open that documents itself, in a
|
||||
// dozen comments, as 280px wide.
|
||||
//
|
||||
// A `GridLayout` is not the alternative — a `for` inside one compiles and then
|
||||
// fails at run time with `RepeatedItemTree::grid_layout_input_data() not
|
||||
// implemented`, piling every chip into a single row. So the grid is index
|
||||
// arithmetic inside a plain `Rectangle`: it costs the layout engine nothing,
|
||||
// it wraps a seventh choice onto a third row by itself, and it declares a
|
||||
// width that depends on the column count rather than on the choice count.
|
||||
//
|
||||
// **The chip width is fixed, not a share of the container's.** Dividing
|
||||
// `self.width` looks right and clips: the develop column sets its width to
|
||||
// `min(panel-max-width, content-width)` with `clip: true`, so when the policy
|
||||
// bites, `self.width` here is the width that was *asked for* and the visible
|
||||
// column is narrower.
|
||||
export component ChipGrid inherits Rectangle {
|
||||
in property <[string]> options;
|
||||
in property <int> selected: -1;
|
||||
in property <bool> enabled: true;
|
||||
/// How many chips to a row. Three is what fits the narrowest column the
|
||||
/// application supports.
|
||||
in property <int> columns: 3;
|
||||
/// Comfortable for a one-word label at `Theme.text`, and three of them
|
||||
/// plus their gaps fit a 280px column.
|
||||
in property <length> chip-width: 88px;
|
||||
|
||||
callback picked(int);
|
||||
|
||||
property <int> rows: max(1, ceil(root.options.length / max(1, root.columns)));
|
||||
property <length> pitch: Theme.control-height + Theme.gap-sm;
|
||||
|
||||
// Stated so a column measuring itself counts this block, and counts it at
|
||||
// the width it will actually draw at rather than at the width a single row
|
||||
// of every choice would need.
|
||||
min-width: root.columns * root.chip-width + (root.columns - 1) * Theme.gap-sm;
|
||||
height: root.rows * root.pitch - Theme.gap-sm;
|
||||
|
||||
for option[i] in root.options: ChoiceChip {
|
||||
x: mod(i, root.columns) * (root.chip-width + Theme.gap-sm);
|
||||
y: floor(i / root.columns) * root.pitch;
|
||||
width: root.chip-width;
|
||||
height: Theme.control-height;
|
||||
label: option;
|
||||
selected: i == root.selected;
|
||||
enabled: root.enabled;
|
||||
clicked => { root.picked(i); }
|
||||
}
|
||||
}
|
||||
|
||||
// A labelled row of chips: one choice out of a short list.
|
||||
//
|
||||
// Not a dropdown. Every choice set on the settings page is short and the
|
||||
@@ -486,6 +544,15 @@ export component Segmented inherits VerticalLayout {
|
||||
in property <[string]> options;
|
||||
in property <int> selected: 0;
|
||||
in property <bool> enabled: true;
|
||||
/// Wrap onto this many chips per row instead of laying them all in one.
|
||||
///
|
||||
/// Zero — one row, however many chips — is right for the settings page,
|
||||
/// which is a full-width page and where reading the alternatives side by
|
||||
/// side is the whole argument for chips over a dropdown. The develop
|
||||
/// column is the opposite case: it is narrow, its width is the largest any
|
||||
/// panel asks for, and a row of six chips there sets the width of the
|
||||
/// sidebar for every other panel. See `ChipGrid`.
|
||||
in property <int> columns: 0;
|
||||
|
||||
callback picked(int);
|
||||
|
||||
@@ -493,7 +560,7 @@ export component Segmented inherits VerticalLayout {
|
||||
|
||||
FieldRow { label: root.label; hint: root.hint; }
|
||||
|
||||
HorizontalLayout {
|
||||
if root.columns <= 0: HorizontalLayout {
|
||||
spacing: Theme.gap-sm;
|
||||
alignment: start;
|
||||
|
||||
@@ -504,6 +571,14 @@ export component Segmented inherits VerticalLayout {
|
||||
clicked => { root.picked(i); }
|
||||
}
|
||||
}
|
||||
|
||||
if root.columns > 0: ChipGrid {
|
||||
options: root.options;
|
||||
selected: root.selected;
|
||||
enabled: root.enabled;
|
||||
columns: root.columns;
|
||||
picked(i) => { root.picked(i); }
|
||||
}
|
||||
}
|
||||
|
||||
// A text entry with its label above and an optional unit after it.
|
||||
|
||||
Reference in New Issue
Block a user