Dock the develop column under the photograph on a tall window
D-N2 dismissed portrait with one number: a 12-inch tablet is about 1024 logical pixels across, which clears the expanded breakpoint. That was worked out for a 4:3 panel. The tablet's is 3000 by 1920, and on that aspect a column beside the photograph in portrait leaves it a strip 540 wide and 1456 tall: a 3:2 frame gets 540 by 360 where a column below it would give 900 by 600, and the portrait frame gains too. D-N7 records the decision: a third property beside the layout class, derived from the window's aspect with hysteresis, that lays the same rail, canvas and column out on the other axis. Not a sheet, not a second layout, and the rail does not move. N6 measures the device the numbers were guessed for, N7 does the frame with the stack stretched as a stopgap, N8 moves the develop callbacks onto a global so the panels can be declared twice cheaply, and N9 is the three-column composition the dock's width is actually for. N5's "no strip along the bottom" is struck where D-N7 reverses it and kept where it does not.
This commit is contained in:
+226
-2
@@ -153,6 +153,12 @@ histogram reduction, which already runs per frame over the displayed frame.
|
||||
> still right about *size* and still right that `cfg(target_os)` is the wrong
|
||||
> axis. It is wrong in one place, and the wrong bit is the sentence "touch
|
||||
> changes **hit regions, not layout**". See D-N6.
|
||||
>
|
||||
> **Reversed for portrait, 2026-09-06, by arithmetic.** "Both orientations of
|
||||
> both targets are the expanded class" is still true and is no longer the
|
||||
> point. It was worked out for a 4:3 panel; the tablet's is 25:16, and on that
|
||||
> aspect a column *beside* the photograph in portrait leaves it a strip. See
|
||||
> D-N7, which keeps the layout class and adds an axis D-N2 did not consider.
|
||||
|
||||
The question was whether desktop and Android should diverge. The answer turns
|
||||
out to be that **neither the platform nor the width axis separates DarkRoom's
|
||||
@@ -265,6 +271,95 @@ one list of seven. It is the tidier model and a much larger change; deferred
|
||||
until the two-section rail has been lived with. §1.1's complaint was that scope
|
||||
was invisible, so this is the same argument arriving from the other end.
|
||||
|
||||
### D-N7 — The column docks under the photograph on a tall window · **DECIDED**
|
||||
|
||||
D-N2 dismissed portrait with one number: a 12-inch tablet is about 1024
|
||||
logical pixels across in portrait, which clears `EXPANDED_MIN_WIDTH`, so
|
||||
portrait is expanded, so there is nothing to design. The number was for a 4:3
|
||||
panel. **The tablet's panel is 3000 × 1920**, which is 25:16 — closer to a
|
||||
sheet of A4 than to an iPad — and the same arithmetic on that aspect comes out
|
||||
the other way.
|
||||
|
||||
**What the photograph gets.** Logical size depends on the density Android
|
||||
reports, which nothing in this repository records (N6 measures it). At a scale
|
||||
of 2.0 the window is 960 × 1500 in portrait; at 1.75 it is 1097 × 1714. Take
|
||||
the first, subtract the 60px rail, the 360px column and the 44px status bar,
|
||||
and the canvas beside the column is **540 × 1456** — a strip two and a half
|
||||
times taller than it is wide. Against a column *under* the canvas, 480px tall:
|
||||
|
||||
| Photograph | Beside the column | Under the column | Gain |
|
||||
|---|---|---|---|
|
||||
| 3:2, landscape | 540 × 360 | 900 × 600 | 2.8× the area |
|
||||
| 2:3, portrait | 540 × 810 | 651 × 976 | 1.4× the area |
|
||||
|
||||
At 1.75 the figures move and the ratios hold (2.4× and 1.3×). So on this panel
|
||||
the dock wins for **both** orientations of the photograph, not only the
|
||||
landscape frame one would guess it was for. That is what makes it a decision
|
||||
rather than a preference: the column is on the right because the eye's path is
|
||||
tool, photograph, adjustment, and in portrait the photograph in the middle of
|
||||
that path is the thing being starved.
|
||||
|
||||
**What it is not.** N5 said "no bottom sheet, no second layout, no tool strip
|
||||
along the bottom — those solve a phone". Still true of all three. This is not
|
||||
a sheet: the column does not slide over the photograph, it sits beside it on
|
||||
the other axis, with the same contents, the same collapse and the same toggle.
|
||||
It is not a second layout in D-N2's sense: the layout class is still decided
|
||||
by width, the compact class still means what it meant, and a tall narrow
|
||||
desktop window gets exactly what a portrait tablet gets, which is FR-UI-1's
|
||||
rule. And the rail does not move — `toolrail.slint` argued it never should,
|
||||
and a vertical list of finger-sized entries wants height, which portrait has
|
||||
more of.
|
||||
|
||||
**The axis is aspect, not width, and it is independent of the class.** A 960
|
||||
wide portrait window is expanded by width and wants the dock; a 1500 wide
|
||||
landscape one is expanded by width and does not. So this is a third property
|
||||
beside `layout-class` and `panel-max-width`, set from the same place for the
|
||||
same reason — a size that both derives from and feeds the layout is a binding
|
||||
loop in Slint, and `apply_layout_class` already measures the window. The
|
||||
window-resized callback reports width alone today and grows a height. The
|
||||
threshold is height above 1.2 × width, with hysteresis wide enough that a
|
||||
window resized across square does not flap.
|
||||
|
||||
**Slint cannot turn a layout on its side**, and does not need to. The develop
|
||||
view is one `HorizontalLayout` of rail, canvas and column; it becomes a
|
||||
`Rectangle` whose three children take `x`, `y`, `width` and `height` from the
|
||||
flag. The column's own `VerticalLayout` and `Flickable` are untouched. The
|
||||
alternative — the column subtree declared twice under two `if`s — is 400 lines
|
||||
of bindings copied, in a file whose own notes record conditional children in
|
||||
layouts as the shape that has produced binding loops before.
|
||||
|
||||
**The contents are the real cost.** Everything in the column was drawn for a
|
||||
360px vertical scroll, and the dock is 900 to 1040 wide by about 480 tall.
|
||||
Stretched to that width the stack works — sliders get longer tracks, the
|
||||
histogram divides the width, text wraps — and it is one long scroll in a short
|
||||
box, with the histogram scrolling away from the sliders it serves, which is
|
||||
§1.2 again. The width is two and a half to three of today's columns, so the
|
||||
composition that fits it is three of them side by side (N9): the instruments,
|
||||
the sliders, and the panels the mode adds. That needs the panels declared a
|
||||
second time, which is cheap only once their callbacks stop being forwarded
|
||||
through the window root by hand (N8). The stretched stack ships first as the
|
||||
stopgap (N7), because the photograph gets its area back on day one and the
|
||||
dock's contents can be got right afterwards.
|
||||
|
||||
**The dock's height is mandated, as the column's width is.** `panel-width`
|
||||
exists because a column that sizes itself to its contents is a photograph
|
||||
that changes size when a caption does; a dock has the same disease on the
|
||||
other axis. `dock-height` in `style.yaml`, 480 until N6 says otherwise: room
|
||||
for the pinned instruments (about 260px per N4) and a group of sliders under
|
||||
them, and a 3:2 frame at 900 wide still fits above it at either scale.
|
||||
|
||||
**The filmstrip stays where it is.** It takes its strip off the bottom of the
|
||||
photograph on demand and it keeps doing so; with the dock below it sits
|
||||
between the two. The photograph loses 108px while the roll is open, which is
|
||||
what it loses today, and the roll is not made part of the dock because it is a
|
||||
different kind of thing — navigation, not adjustment — and D-N6 has already
|
||||
been through why two kinds of entry in one control need a rule between them.
|
||||
|
||||
**Not remembered separately.** `PanelChoices` keeps the user's open-or-closed
|
||||
override per layout class. The dock does not add a class and does not add a
|
||||
remembered state: closing the column in landscape closes the dock in
|
||||
portrait, because it is the same column.
|
||||
|
||||
### D-N3 — Collapsible panels, not tool tabs · **OPEN**
|
||||
|
||||
For the expanded layout, extend `ui-refinement.md` Workstream C from
|
||||
@@ -486,13 +581,132 @@ overlays rather than sits beside the canvas, and `apply_layout_class` already
|
||||
remembers the user's override per class. With N4's collapse in place a narrow
|
||||
window is a one-panel-at-a-time column by consequence rather than by design.
|
||||
|
||||
**Deliverable.** Confirm the narrow case is usable and fix what is not. No
|
||||
**Deliverable.** Confirm the narrow case is usable and fix what is not. ~~No
|
||||
bottom sheet, no second layout, no tool strip along the bottom — those solve a
|
||||
phone, and there is no phone.
|
||||
phone, and there is no phone.~~ Still no sheet and still no strip; but a
|
||||
*tall* window is not a narrow one, and D-N7 (2026-09-06) puts the column under
|
||||
the photograph there. N6–N9 carry it. This workstream keeps the narrow case.
|
||||
|
||||
**Done when.** A desktop window dragged to 700px shows the photograph and a
|
||||
usable column, and nothing is unreachable that was reachable at 1400px.
|
||||
|
||||
### N6 — Measure the tablet
|
||||
|
||||
**Depends on** nothing. Hours.
|
||||
|
||||
Every figure in D-N7 is computed at a guessed scale factor. The tablet's
|
||||
panel is 3000 × 1920 physical; its logical size is that divided by whatever
|
||||
density Android reports, and the two plausible answers (2.0 and 1.75) put the
|
||||
dock's width at 900 or 1037 and its available height 200px apart.
|
||||
|
||||
**Deliverable.** Log the window's scale factor and logical size once at
|
||||
startup, beside the existing `apply_layout_class` call, at a level that
|
||||
reaches `adb logcat`. Run it on the tablet in both orientations. Record the
|
||||
four numbers in D-N7 and, if they move the table, correct it. #30
|
||||
(NFR-COMPAT-1) wants a reference device named; this is one of the numbers
|
||||
that names it.
|
||||
|
||||
**Done when.** D-N7 cites measured logical sizes, not a scale it assumed, and
|
||||
`dock-height` has been checked against the measured portrait height.
|
||||
|
||||
### N7 — The column docks on a tall window
|
||||
|
||||
**Depends on** N6 only for the value of `dock-height`; the shape does not
|
||||
wait for it.
|
||||
|
||||
**Deliverable, three parts.**
|
||||
|
||||
*The flag.* `window-resized` reports height as well as width.
|
||||
`apply_layout_class` derives a third property, `column-below`, from the
|
||||
aspect with hysteresis (enter above 1.25, leave below 1.15, or thereabouts —
|
||||
the point is that a window resized across square does not flap), and sets it
|
||||
beside `layout-class` and `panel-max-width`. It is not a layout class and
|
||||
`PanelChoices` does not learn about it.
|
||||
|
||||
*The frame.* The `HorizontalLayout` holding `ToolRail`, `canvas-area` and
|
||||
`develop-column` becomes a `Rectangle` and each child takes `x`, `y`, `width`
|
||||
and `height` from the flag. The rail is full height on the left in both
|
||||
cases. With the flag off the geometry is what the layout produced, to the
|
||||
pixel — screenshot before and after and diff them. With it on, the column is
|
||||
`dock-height` tall and runs from the rail's edge to the window's; the canvas
|
||||
has what is left above it. `panel-visible` collapses the dock to zero height
|
||||
exactly as it collapses the column to zero width.
|
||||
|
||||
*The contents, as a stopgap.* The column's stack stretches to the dock's
|
||||
width: the Flickable's viewport width follows the dock, the `min-width` floor
|
||||
that keeps a mandated column honest is still there and is simply not binding.
|
||||
Sliders take the width. Nothing is reflowed; N9 does that.
|
||||
|
||||
`dock-height` goes in `style.yaml` next to `panel-width`, with the reasoning
|
||||
D-N7 gives, and is read as `Theme.dock-height`.
|
||||
|
||||
**Done when.** On the tablet in portrait, a 3:2 photograph is drawn at the
|
||||
width of the canvas, not at the width the old column left it; turning the
|
||||
tablet moves the column back beside it with the same scroll position and the
|
||||
same panels open; a desktop window dragged taller than it is wide does the
|
||||
same; and the screenshot diff in landscape is empty.
|
||||
|
||||
### N8 — Develop callbacks onto a global
|
||||
|
||||
**Depends on** nothing; can run beside N7.
|
||||
|
||||
The develop panels — `AdjustPanel`, `MaskPanel`, `SpotPanel`, `ComposePanel`,
|
||||
`TransferPanel`, `FocusPanel`, `HistogramPanel`, `InfoPanel` — each forward
|
||||
their callbacks and take their inputs through the window root, and the
|
||||
instantiation in `app.slint` that wires one up is twenty to forty lines. N9
|
||||
needs each of them declared a second time, and copying that wiring is the
|
||||
kind of duplication that drifts.
|
||||
|
||||
**Deliverable.** A Slint global (or one per panel family, if a single one
|
||||
reads badly) carrying the develop callbacks and the inputs the panels bind
|
||||
to. A panel calls the global directly; Rust hooks the global instead of the
|
||||
window. The existing instantiation in the column shrinks to the properties
|
||||
that genuinely differ by placement, which should be none. `Readout` in
|
||||
`adjust.slint` is the precedent for a global in this codebase.
|
||||
|
||||
The tests in `ui/dr-ui/src` that drive these callbacks through the window
|
||||
move to the global; count them before starting, so the ticket knows its own
|
||||
size.
|
||||
|
||||
**Done when.** No develop panel's instantiation in `app.slint` forwards a
|
||||
callback by hand, every existing test passes, and a second instantiation of
|
||||
any panel is under five lines.
|
||||
|
||||
### N9 — Three columns in the dock
|
||||
|
||||
**Depends on** N7 and N8.
|
||||
|
||||
**Deliverable.** A second composition of the same panels for the dock, in
|
||||
three columns of equal width side by side, each its own scroll:
|
||||
|
||||
1. **Instruments** — `InfoPanel`, `HistogramPanel`, `FocusPanel`. What the
|
||||
sliders are judged against, pinned by construction: it does not scroll
|
||||
with them because it is not in their column.
|
||||
2. **Sliders** — `GroupStrip` above `AdjustPanel`, exactly as in the column.
|
||||
With a group selected this is one screen of sliders; with All it scrolls.
|
||||
3. **The mode's panels** — `ComposePanel` and `TransferPanel` in photo mode,
|
||||
`SpotPanel` in repair, `MaskPanel` in local. Empty otherwise, which is
|
||||
a signal of its own about which mode the view is in (§1.1).
|
||||
|
||||
Three columns at 900 wide are 300 each, and at 1037 they are 346: inside the
|
||||
280–360 band `PANEL_MIN_WIDTH`'s note says the sliders stay accurate over.
|
||||
The dock takes the three-column composition only when a third of its width
|
||||
is at least `PANEL_MIN_WIDTH` — 840px of dock, which both scales of the
|
||||
tablet exceed. Below that it keeps N7's stretched stack. Two compositions,
|
||||
not three: a dock too narrow for three columns is a desktop window in an odd
|
||||
shape, and N5 says that case degrades.
|
||||
|
||||
The panels are declared twice, once per composition, which is what N8 made
|
||||
cheap. Declaring each once and positioning it by hand in both modes was
|
||||
considered and rejected: a column that scrolls is a `Flickable`, a
|
||||
`Flickable`'s children are its children, and a panel cannot be in two.
|
||||
|
||||
**Done when.** On the tablet in portrait the histogram is visible while any
|
||||
slider in any group is dragged, with no scrolling to arrange it — N4's own
|
||||
criterion, met in the dock by construction; every panel reachable in
|
||||
landscape is reachable in portrait; and the instantiation of each panel in
|
||||
the dock is a handful of lines.
|
||||
|
||||
---
|
||||
|
||||
## 5. Sequencing
|
||||
@@ -501,6 +715,10 @@ usable column, and nothing is unreachable that was reachable at 1400px.
|
||||
ui-refinement A ──┬── C ──── N4 ──┐
|
||||
└── D ──── F ├── N5
|
||||
N1 ── N2 ── N3 ───────────────────┘
|
||||
|
||||
N6 ── N7 ──┐
|
||||
├── N9
|
||||
N8 ──┘
|
||||
```
|
||||
|
||||
N1–N3 are independent of `ui-refinement.md` and can start now; they touch the
|
||||
@@ -508,6 +726,12 @@ canvas and the develop column's contents, not its layout. N4 needs C's
|
||||
`Section`. N5 needs both and should land last, as F does — it is the one that
|
||||
rearranges everything.
|
||||
|
||||
N6–N9 are the portrait dock (D-N7) and run beside the first row rather than
|
||||
after it. N7 is the one that touches the develop view's frame, so it should
|
||||
not land in the same wave as F; N8 touches only plumbing and can. N9 waits
|
||||
for both, and gains from N4 if N4 is in by then — a collapsible panel in a
|
||||
300px column is worth more than in a 360px one.
|
||||
|
||||
## 6. Invariants, for every workstream
|
||||
|
||||
- **FR-DEV-3a.** Adding a pipeline operation must still surface in both
|
||||
|
||||
Reference in New Issue
Block a user