Give the canvas tools a rail of their own, and the column one width
Build and test / Desktop (Linux) (push) Failing after 1h14m38s
Build and test / Layer separation (push) Successful in 48s
🐳 Android image / Build and push (push) Successful in 16m30s
Build and test / android-image (push) Successful in 16m31s
Traceability / Requirement traces (push) Successful in 1m47s
Build and test / Android (aarch64) (push) Successful in 1h0m21s
Build and test / Desktop (Linux) (push) Failing after 1h14m38s
Build and test / Layer separation (push) Successful in 48s
🐳 Android image / Build and push (push) Successful in 16m30s
Build and test / android-image (push) Successful in 16m31s
Traceability / Requirement traces (push) Successful in 1m47s
Build and test / Android (aarch64) (push) Successful in 1h0m21s
Crop, Local and Repair were chips at the head of the develop column, sharing a row with the adjustment groups and told apart from them by the shape of their highlight. Three things followed from that, and only the last is cosmetic: the column closes, so the way out of a mode went away with the way in — hence the duplicate "Done Cropping" over the canvas; the chips are generated from the operation set, so the widest thing in the sidebar was a row nobody had chosen the contents of; and a mode and a filter are different kinds of state wearing one control. They are a fixed 60px rail down the left now, generated from a single table in toolrail.slint. A tool is one row of it plus a drawing plus a ViewMode variant; nothing in app.slint is touched to add one. What is left of the strip is the group filters, so it is GroupStrip. The column stops measuring itself. Every panel published a content-width and declared it as min-width, and the column took the largest — which spent the photograph's pixels on whatever happened to be widest, and moved the image sideways when switching tools swapped one set of panels for another. It is panel-width now, one number in style.yaml. That number is 360 and it is measured, not picked: the contents report a minimum of 344 in every mode, and they do not compress below it because a Text that does not elide reports the same minimum as preferred. 320 was tried and sliced Paste down the middle. The Flickable's viewport is floored at the layout's minimum rather than its preferred width for the same reason — content that is never told how much room it has cannot adapt to having less. Removing the eight content-width declarations repairs three comments an earlier edit had spliced sentences into. The raw histogram's note on keeping its hint short is rewritten rather than dropped: an over-long hint no longer widens the column, it pushes the column's minimum past the width it has and clips the panel, which makes that constraint sharper rather than obsolete. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
+42
-132
@@ -541,21 +541,6 @@ export component GeometryPanel inherits Rectangle {
|
||||
callback portrait-toggled();
|
||||
callback reset();
|
||||
|
||||
/// TRACES: FR-UI-2
|
||||
/// How wide this panel has to be before it starts clipping itself.
|
||||
///
|
||||
/// Every panel in the develop column declares one, and the column takes
|
||||
/// the largest — that is the whole of how the column is sized. It replaced
|
||||
/// two guessed constants (280px for a tablet, 380px for a desktop) that
|
||||
/// could not track a panel gaining a control, and did not.
|
||||
///
|
||||
/// Published as `min-width` as well as read by name: the first is what
|
||||
/// makes the enclosing layout aggregate these automatically, including for
|
||||
/// the panels that come and go with the mode and so cannot be referenced
|
||||
/// from outside their `if`.
|
||||
out property <length> content-width: layout.preferred-width;
|
||||
min-width: root.content-width;
|
||||
|
||||
height: layout.preferred-height;
|
||||
|
||||
layout := VerticalLayout {
|
||||
@@ -738,15 +723,6 @@ export component TransferPanel inherits VerticalLayout {
|
||||
/// same thought given a name — this edit, kept.
|
||||
callback open-presets();
|
||||
|
||||
/// TRACES: FR-UI-2
|
||||
/// How wide this panel has to be before it starts clipping itself — the
|
||||
/// same question every panel in the develop column answers.
|
||||
///
|
||||
/// No `min-width` beside it, unlike the others: this component *is* a
|
||||
/// layout, so it already reports its own minimum to whatever contains it,
|
||||
/// and a `min-width` bound to its own preferred size is a binding loop.
|
||||
out property <length> content-width: self.preferred-width;
|
||||
|
||||
padding: Theme.gap;
|
||||
spacing: Theme.gap-sm;
|
||||
|
||||
@@ -817,24 +793,22 @@ export enum ViewMode {
|
||||
spots,
|
||||
}
|
||||
|
||||
/// The strip: what the photographer is working on.
|
||||
/// The group strip: which kind of adjustment the column is showing.
|
||||
///
|
||||
/// Two kinds of entry, deliberately together.
|
||||
/// One kind of entry, which is the change. This was `ModeStrip` and carried
|
||||
/// two: the canvas tools — crop, local, repair — as filled chips, and the
|
||||
/// adjustment groups as underlined words, in a single row that asked the eye
|
||||
/// to tell a mode from a filter by the shape of its highlight. The tools are
|
||||
/// in `ToolRail` now (`toolrail.slint`, which carries the reasoning), and what
|
||||
/// is left here is one row of one thing.
|
||||
///
|
||||
/// **Modes** — crop and local — change the canvas as well as the column. They
|
||||
/// are drawn as chips and lit with the accent, which means *active* everywhere
|
||||
/// else in this interface and means exactly that here.
|
||||
///
|
||||
/// **Groups** — the rest — filter the adjustments to one kind. They are
|
||||
/// underlined instead, because the accent is already spoken for and because
|
||||
/// they are a different sort of state: a mode is something you are *in*, a
|
||||
/// group is something you are *looking at*.
|
||||
///
|
||||
/// Keeping them apart visually is what lets them be independent. Picking a
|
||||
/// group in local mode filters the selected layer's chain and does not leave
|
||||
/// the mode, so "Light" means the same thing wherever it is pressed — the
|
||||
/// alternative, where a group press silently dropped the scope, would be the
|
||||
/// §1.1 fault reintroduced from the other end.
|
||||
/// A group is something you are *looking at*, not something you are *in*, and
|
||||
/// that distinction is now made by the two controls being in different places
|
||||
/// rather than by two treatments of one control. It is also why the two stay
|
||||
/// independent: picking a group while a tool is held filters the selected
|
||||
/// layer's chain and does not put the tool down, so "Light" means the same
|
||||
/// thing wherever it is pressed — the alternative, where a group press
|
||||
/// silently dropped the scope, would be the §1.1 fault from the other end.
|
||||
///
|
||||
/// **Pinned above the scrolling column, not inside a panel.** It began inside
|
||||
/// `AdjustPanel`, which put it below five other panels and off the bottom of a
|
||||
@@ -844,29 +818,15 @@ export enum ViewMode {
|
||||
///
|
||||
/// **This file names no group.** The strings arrive already resolved from
|
||||
/// whatever the operations declared themselves to be about, so a new operation
|
||||
/// joins a group without an edit here (FR-DEV-3a). The two modes are not
|
||||
/// operations — crop is a gesture on the canvas and local is a scope — so
|
||||
/// naming them breaks nothing.
|
||||
export component ModeStrip inherits Rectangle {
|
||||
/// joins a group without an edit here (FR-DEV-3a). With the tools gone there is
|
||||
/// no longer any hand-written word in this row at all, save "All".
|
||||
export component GroupStrip inherits Rectangle {
|
||||
in property <[string]> tabs;
|
||||
/// Index into `tabs`, or -1 for "everything".
|
||||
in property <int> active-tab: -1;
|
||||
in property <ViewMode> mode: ViewMode.photo;
|
||||
in property <bool> enabled: true;
|
||||
|
||||
callback picked(int);
|
||||
callback mode-picked(ViewMode);
|
||||
|
||||
/// How much width this row actually needs, for the column that pins it.
|
||||
///
|
||||
/// Read rather than guessed: the chips are one per mode plus one per group
|
||||
/// the operation set declares, so the number is generated and no constant
|
||||
/// in the column could stay in step with it. The Flickable below is still
|
||||
/// the answer when the column cannot be given that much — it is the floor
|
||||
/// under a control that must never become unreachable — but it is no longer
|
||||
/// the *first* answer, which is what made a generated row into a row you
|
||||
/// had to know to pan.
|
||||
out property <length> content-width: layout.preferred-width;
|
||||
|
||||
background: Theme.surface;
|
||||
height: root.enabled ? layout.preferred-height : 0px;
|
||||
@@ -874,9 +834,15 @@ export component ModeStrip inherits Rectangle {
|
||||
|
||||
// Scrolls rather than overflowing, exactly as the develop status strip
|
||||
// does and for the same reason: a `HorizontalLayout` given less width than
|
||||
// its children need does not shrink them, it runs off the end. Two modes
|
||||
// plus however many groups the operation set declares is already more than
|
||||
// a 280px column holds, and the column is where this is pinned.
|
||||
// its children need does not shrink them, it runs off the end.
|
||||
//
|
||||
// This is now the *only* answer, and it is the right one here. The column
|
||||
// this sits in is a mandated width (`panel-width` in `style.yaml`) while
|
||||
// the row's contents are one word per group the operation set declares —
|
||||
// generated, and so unbounded in principle. It used to publish a
|
||||
// `content-width` that the column sized itself from, which made a rich
|
||||
// operation set quietly take width from the photograph. A row that pans is
|
||||
// the price of a photograph that does not move.
|
||||
Flickable {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
@@ -890,67 +856,17 @@ export component ModeStrip inherits Rectangle {
|
||||
spacing: Theme.gap-sm;
|
||||
alignment: start;
|
||||
|
||||
// A chip per mode. Drawn as an outline that fills when active rather
|
||||
// than as an underline, so at a glance the strip reads as two runs
|
||||
// and it is never ambiguous which half a lit entry belongs to.
|
||||
// **Where a canvas tool goes: not here.** A mode that arms a gesture
|
||||
// on the photograph is a row in `ToolRail`'s table, one file over.
|
||||
//
|
||||
// **Where a canvas tool goes.** A mode that arms a gesture on the
|
||||
// photograph and scopes the column joins this list and the `ViewMode`
|
||||
// enum, and needs nothing else here. *Repair* is the first to arrive
|
||||
// that way (FR-DEV-8); painting a mask is the same shape and still to
|
||||
// come — `MaskSource::Brush` and the stroke calls on `MaskLayer`
|
||||
// already exist in the core, and what is missing on this side is only
|
||||
// the canvas interaction the repairs now have a pattern for.
|
||||
for entry in [
|
||||
{ label: "Crop", value: ViewMode.crop },
|
||||
{ label: "Local", value: ViewMode.local },
|
||||
{ label: "Repair", value: ViewMode.spots },
|
||||
]: mode-chip := TouchArea {
|
||||
width: mode-name.preferred-width + 2 * Theme.gap-sm;
|
||||
height: Theme.touch-target;
|
||||
mouse-cursor: pointer;
|
||||
|
||||
property <bool> on: root.mode == entry.value;
|
||||
|
||||
// Pressing the mode you are already in leaves it, which is what
|
||||
// makes the strip the way out as well as the way in — the same
|
||||
// control both directions, as the crop button was.
|
||||
clicked => {
|
||||
root.mode-picked(self.on ? ViewMode.photo : entry.value);
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
y: (parent.height - self.height) / 2;
|
||||
height: Theme.control-height;
|
||||
width: parent.width;
|
||||
border-radius: Theme.radius;
|
||||
border-width: 1px;
|
||||
border-color: mode-chip.on ? Theme.active : Theme.rule;
|
||||
background: mode-chip.on
|
||||
? Theme.active-dim
|
||||
: (mode-chip.has-hover ? Theme.hover : transparent);
|
||||
}
|
||||
|
||||
mode-name := Text {
|
||||
text: entry.label;
|
||||
// Dark on the lit fill, which is near-white: the same
|
||||
// inversion `Button`'s primary state makes.
|
||||
color: mode-chip.on ? Theme.ground : Theme.ink-dim;
|
||||
font-size: Theme.text-sm;
|
||||
font-weight: 600;
|
||||
vertical-alignment: center;
|
||||
horizontal-alignment: center;
|
||||
}
|
||||
}
|
||||
|
||||
// The divider between the two kinds. One pixel, and it is what stops
|
||||
// the strip reading as one undifferentiated row of five words.
|
||||
Rectangle {
|
||||
width: 1px;
|
||||
height: Theme.touch-target;
|
||||
background: Theme.rule;
|
||||
}
|
||||
|
||||
// Three of them used to sit at the head of this row as filled chips,
|
||||
// with a one-pixel divider after them. Both are gone, and nothing has
|
||||
// replaced them — no heading, no lead-in. A run of words with one
|
||||
// underlined is a tab bar, which is exactly what this is, and it needs
|
||||
// saying only while there is something else in the row to be told
|
||||
// apart from. The width that buys back is the point: this row pans
|
||||
// when the operation set is rich, so anything permanently occupying
|
||||
// its left-hand end is paid for by every group after the third.
|
||||
all := TouchArea {
|
||||
width: 34px;
|
||||
height: Theme.touch-target;
|
||||
@@ -1069,18 +985,12 @@ export component AdjustPanel inherits Rectangle {
|
||||
/// scrolls as one, so the panel reports the drag and the column obeys it.
|
||||
out property <bool> slider-dragging: false;
|
||||
|
||||
/// TRACES: FR-UI-2
|
||||
/// How wide this panel has to be before it starts clipping itself. The
|
||||
/// develop column is the largest of these and nothing else; publishing it
|
||||
/// as `min-width` too is what makes the enclosing layout aggregate them
|
||||
/// without this panel having to be referenced by name.
|
||||
///
|
||||
/// The sliders are the part that matters. Each is a label, a value and a
|
||||
/// track, and a track squeezed below the width its handle needs is a
|
||||
/// control that cannot be set accurately — which is the whole job.
|
||||
out property <length> content-width: layout.preferred-width;
|
||||
min-width: root.content-width;
|
||||
|
||||
// **The sliders are what `panel-width` was chosen against.** No panel gets
|
||||
// a say in the column's width any more — it is one number in `style.yaml`
|
||||
// — but this is the panel that number has to be right for. Each row is a
|
||||
// label, a value and a track, and a track squeezed below the width its
|
||||
// handle needs is a control that cannot be set accurately, which is the
|
||||
// whole job. If the column is ever narrowed, narrow it against this.
|
||||
layout := VerticalLayout {
|
||||
padding: Theme.gap;
|
||||
spacing: Theme.gap-sm;
|
||||
|
||||
Reference in New Issue
Block a user