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:
+119
-61
@@ -1,5 +1,5 @@
|
||||
import { Theme } from "theme.slint";
|
||||
import { AdjustPanel, GeometryPanel, ModeStrip, ParamRow, TransferPanel, ViewMode } from "adjust.slint";
|
||||
import { AdjustPanel, GeometryPanel, GroupStrip, ParamRow, TransferPanel, ViewMode } from "adjust.slint";
|
||||
import { GradientHandle, GradientHandles, HandleRole, MaskPanel, MaskRow, SubjectRow } from "masks.slint";
|
||||
import { SpotHandle, SpotHandles, SpotPanel, SpotRole } from "spots.slint";
|
||||
import { CropOverlay } from "crop.slint";
|
||||
@@ -16,6 +16,7 @@ import { FocusMarks, FocusPanel } from "peaking.slint";
|
||||
import { SettingsPage } from "settings.slint";
|
||||
import { ImportPage } from "import.slint";
|
||||
import { StatusBar, InfoPanel } from "develop.slint";
|
||||
import { ToolRail } from "toolrail.slint";
|
||||
|
||||
export { LibraryCell, TimelineBar, CollectionRow, ActivityRow, HistogramView, PersonChip }
|
||||
export { GestureRow }
|
||||
@@ -1675,6 +1676,31 @@ in property <bool> panel-visible: true;
|
||||
}
|
||||
|
||||
HorizontalLayout {
|
||||
// **The tools, on the far side from their consequences.**
|
||||
//
|
||||
// A fixed rail rather than a row of chips inside the develop
|
||||
// column, which is where these three used to live. The column
|
||||
// can be put away — that is the whole point of the toggle in
|
||||
// the strip above — and putting the tools away with it meant
|
||||
// the way *out* of crop disappeared along with the way in.
|
||||
// Hence the "Done Cropping" button floating over the canvas
|
||||
// below: one control duplicated to paper over the other being
|
||||
// reachable only sometimes.
|
||||
//
|
||||
// The rail does not close, so a tool can always be put down
|
||||
// where it was picked up. `toolrail.slint` has the rest of the
|
||||
// reasoning and the table that generates it.
|
||||
//
|
||||
// Not conditioned on `total > 0` from out here: the rail
|
||||
// collapses itself on `enabled`, and an `if` in this layout is
|
||||
// the shape that has caused binding loops in this file before
|
||||
// (see the develop column below).
|
||||
ToolRail {
|
||||
enabled: root.total > 0 && root.load-error == "";
|
||||
mode: root.view-mode;
|
||||
picked(m) => { root.mode-picked(m); }
|
||||
}
|
||||
|
||||
// The canvas: compute output composited directly. No CPU
|
||||
// round-trip anywhere in this path (ARCH §6.1).
|
||||
canvas-area := Rectangle {
|
||||
@@ -2052,12 +2078,19 @@ in property <bool> panel-visible: true;
|
||||
// beside what it is reporting on. Crop moved to the panel
|
||||
// because it is an edit, and edits live with the other edits.
|
||||
//
|
||||
// A way out of whichever mode is on is still reachable from
|
||||
// here — the develop column may be closed on a narrow
|
||||
// window, and the strip that enters a mode is pinned inside
|
||||
// it. Stranding the user in a mode with no visible way out
|
||||
// is worse than one duplicated control, and it is worse
|
||||
// still now that there are two modes to be stranded in.
|
||||
// **"Done" is no longer here because it has to be.** It was:
|
||||
// the control that entered a mode was pinned inside the
|
||||
// develop column, the column closes, and stranding someone
|
||||
// in crop with no visible way out was worse than one
|
||||
// duplicated control. The tool rail does not close, so that
|
||||
// reason has gone.
|
||||
//
|
||||
// It stays because of where it is. Finishing a crop is a
|
||||
// decision *about the photograph*, taken while looking at
|
||||
// the photograph, and a confirmation the width of the
|
||||
// window away from the thing being confirmed is a
|
||||
// confirmation you take on trust. The rail is now the
|
||||
// second way out rather than the first.
|
||||
if root.total > 0 && root.load-error == "": HorizontalLayout {
|
||||
x: 12px;
|
||||
y: parent.height - self.preferred-height - 12px;
|
||||
@@ -2126,55 +2159,54 @@ in property <bool> panel-visible: true;
|
||||
// width, which the layout then influences. Slint flags it, and
|
||||
// it can panic at runtime.
|
||||
develop-column := Rectangle {
|
||||
// TRACES: FR-UI-2
|
||||
// **As wide as what it holds.**
|
||||
// TRACES: FR-UI-1
|
||||
// **One width, stated once, and not measured.**
|
||||
//
|
||||
// It was 280px, chosen for a tablet where the column is a
|
||||
// large fraction of the screen and every pixel of it is
|
||||
// taken from the photograph. That was too narrow on a
|
||||
// desktop, so a second number — 380px — was added for the
|
||||
// expanded class. Both were guesses at how much room the
|
||||
// widest row in this column needs, and a guess is exactly
|
||||
// what cannot be right here: the mode strip is two modes, a
|
||||
// separator, "All", and one chip per attribute the
|
||||
// operation set *declares*, so the row is generated and no
|
||||
// constant in this file can track it. When the guess came
|
||||
// up short the strip scrolled sideways, and a control you
|
||||
// have to pan to reach is one you do not know is there.
|
||||
// This column used to size itself from its contents: every
|
||||
// panel published a `content-width`, declared it as its own
|
||||
// `min-width`, and the column took the largest. That was an
|
||||
// answer to a real problem — before it, the width was two
|
||||
// guessed constants (280px for a tablet, 380px for a
|
||||
// desktop) and the generated chip row outgrew both, so a
|
||||
// control ended up somewhere you had to pan to find.
|
||||
//
|
||||
// **So the column asks instead.** Every panel that can
|
||||
// appear in it publishes a `content-width` — how wide it
|
||||
// has to be before it clips itself — and also declares that
|
||||
// as its `min-width`. The `min-width` is what makes this
|
||||
// aggregate on its own: `column` below is a layout, so it
|
||||
// already reports the largest minimum among its children,
|
||||
// and it does so for the panels that come and go with the
|
||||
// mode as well, which sit inside `if`s and cannot be named
|
||||
// from out here. Grep `content-width` in this directory to
|
||||
// see every panel that has a say.
|
||||
// **It solved that by making the photograph pay.** The
|
||||
// pixels a sidebar takes come out of the picture beside it,
|
||||
// and a measured sidebar spends them on whatever happens to
|
||||
// be widest: an axis label gaining a digit made the image
|
||||
// smaller, and switching tools swapped one set of panels for
|
||||
// another and shifted the image sideways under the eye
|
||||
// that was judging it. In an application whose entire job is
|
||||
// showing you a photograph accurately, the frame around it
|
||||
// must not move because a caption changed.
|
||||
//
|
||||
// The mode strip is named explicitly only because it is
|
||||
// pinned *outside* that layout, so nothing else would
|
||||
// measure it.
|
||||
// So the number is mandated: `panel-width`, in
|
||||
// `style.yaml`, next to the reasoning and next to the rail
|
||||
// on the other side, which is fixed for the same reason. A
|
||||
// panel that wants more than that clips, and the Flickable
|
||||
// below is what makes the rest of it reachable — the same
|
||||
// fallback as before, now the first answer rather than the
|
||||
// last. `AdjustPanel` is the panel the number was chosen
|
||||
// against; its note says why.
|
||||
//
|
||||
// There is no floor. A floor is another guess, and the
|
||||
// panels state their own minimums now. The one thing left
|
||||
// over the measurement is `panel-max-width` — not a size
|
||||
// but a policy, that a column may not take the window from
|
||||
// the photograph it exists to serve — and past that the
|
||||
// Flickables inside are the fallback, as they have always
|
||||
// been. It comes from Rust rather than from `root.width`
|
||||
// because reading the window width inside the layout that
|
||||
// sets it is the binding loop the comment on `expanded`
|
||||
// above describes; Rust already measures the window for
|
||||
// `layout-class`, so this is one more thing said in the
|
||||
// same breath.
|
||||
property <length> content-width: max(
|
||||
develop-strip.content-width,
|
||||
column.preferred-width,
|
||||
);
|
||||
// The generated row that started all this is not a problem
|
||||
// any more either, because it is no longer in the column's
|
||||
// way: the tools moved to `ToolRail` and what is left is
|
||||
// group filters, which pan within their own strip without
|
||||
// anything else having to move.
|
||||
//
|
||||
// `panel-max-width` survives, and it is the one thing here
|
||||
// that is not a size: it is the policy that a column may
|
||||
// not take the window from the photograph it exists to
|
||||
// serve, and it bites only on a window narrow enough that
|
||||
// 360px would. It comes from Rust rather than from
|
||||
// `root.width` because reading the window width inside the
|
||||
// layout that sets it is the binding loop the comment on
|
||||
// `expanded` above describes; Rust already measures the
|
||||
// window for `layout-class`, so this is one more thing said
|
||||
// in the same breath.
|
||||
width: root.panel-visible
|
||||
? min(root.panel-max-width, self.content-width)
|
||||
? min(root.panel-max-width, Theme.panel-width)
|
||||
: 0px;
|
||||
visible: root.panel-visible;
|
||||
background: Theme.surface;
|
||||
@@ -2201,13 +2233,11 @@ in property <bool> panel-visible: true;
|
||||
// would both sit at its origin and overlap. The strip is pinned by
|
||||
// being outside the Flickable rather than by any coordinate.
|
||||
VerticalLayout {
|
||||
develop-strip := ModeStrip {
|
||||
GroupStrip {
|
||||
enabled: root.adjust-enabled;
|
||||
mode: root.view-mode;
|
||||
tabs: root.adjust-tabs;
|
||||
active-tab: root.adjust-active-tab;
|
||||
picked(i) => { root.adjust-tab-picked(i); }
|
||||
mode-picked(m) => { root.mode-picked(m); }
|
||||
}
|
||||
|
||||
Flickable {
|
||||
@@ -2226,12 +2256,40 @@ in property <bool> panel-visible: true;
|
||||
// the far side. It looked like a rendering fault
|
||||
// and it was an alignment one.
|
||||
//
|
||||
// Floored at the Flickable's own width so the
|
||||
// ordinary case — a column sized to fit, which is
|
||||
// now every case the cap does not bite — puts the
|
||||
// content at x: 0 and stretches it, rather than
|
||||
// leaving it preferred-width and adrift.
|
||||
viewport-width: max(self.width, column.preferred-width);
|
||||
// **`min-width`, not `preferred-width`, and that is
|
||||
// what makes a mandated column work at all.**
|
||||
//
|
||||
// With the preferred width here, the content laid
|
||||
// itself out at whatever size it would have liked
|
||||
// and the column clipped the difference: Paste came
|
||||
// out sliced down the middle and the panel's reset
|
||||
// was over the window's edge. That is the same
|
||||
// fault as above wearing a different hat — the
|
||||
// content was never told how much room it had, so
|
||||
// it could not adapt to having less.
|
||||
//
|
||||
// The floor is the layout's *minimum* instead. A
|
||||
// preferred width is what a panel would enjoy; a
|
||||
// minimum is what it cannot go below, and between
|
||||
// the two the layout does the work it exists to do
|
||||
// — rows tighten, stretches give way — so the
|
||||
// column fits because it was asked to rather than
|
||||
// by luck.
|
||||
//
|
||||
// That does less here than it sounds like, and it
|
||||
// is worth knowing why: this column's minimum and
|
||||
// its preferred width are within a few pixels of
|
||||
// each other, because a `Text` that does not elide
|
||||
// reports the same for both and most of what is in
|
||||
// here is text. So `panel-width` still has to be a
|
||||
// number the contents actually fit in — see its
|
||||
// note in `style.yaml` for the measurement. What
|
||||
// this binding buys is that the clipping, when it
|
||||
// comes, is of a panel that genuinely cannot
|
||||
// shrink rather than of one that simply was not
|
||||
// asked to, and that panning is then a real
|
||||
// fallback rather than a permanent condition.
|
||||
viewport-width: max(self.width, column.min-width);
|
||||
interactive: !adjust.slider-dragging;
|
||||
|
||||
// **What the column holds is the mode's answer.**
|
||||
@@ -2569,7 +2627,7 @@ in property <bool> panel-visible: true;
|
||||
// thing the dialogue covers.
|
||||
// TRACES: FR-DEV-6
|
||||
// Over the shell rather than inside a view, because both views open
|
||||
// it — and because the develop column is 320px wide, which is not
|
||||
// it — and because the develop column is 360px wide, which is not
|
||||
// enough to list presets and rename one in.
|
||||
if root.presets-open: PresetSheet {
|
||||
width: 100%;
|
||||
|
||||
Reference in New Issue
Block a user