Give the develop view's chrome a file of its own

StatusBar and InfoPanel sat above AppWindow in app.slint, which read as
though they were part of the application shell. They are not: neither is
instantiated anywhere but the develop view, and the shell's actual job --
choosing which of the five screens is up -- is easier to follow without
two unrelated components standing in front of it.

Moved verbatim to develop.slint, matching src/develop.rs. No behaviour
change; app.slint loses 232 lines and gains one import.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-08-27 21:33:17 +02:00
co-authored by Claude Opus 5
parent cafa63ca6f
commit ffc9e1aea7
3 changed files with 271 additions and 259 deletions
+1 -241
View File
@@ -11,251 +11,11 @@ import { CollectionsPanel, CollectionRow, OfflinePrompt } from "collections.slin
import { HistogramPanel, HistogramView } from "histogram.slint";
import { SettingsPage } from "settings.slint";
import { ImportPage } from "import.slint";
import { StatusBar, InfoPanel } from "develop.slint";
export { LibraryCell, TimelineBar, CollectionRow, ActivityRow, HistogramView }
export { ViewMode, GradientHandle, HandleRole, SpotHandle, SpotRole }
// Status strip — surfaces the GPU backend and adapter, which matters during
// v0.1 because assumption A1 is exactly "does this compositing path work on
// this hardware". Seeing the adapter at a glance makes vendor differences
// obvious during the S1/S2 spikes.
component StatusBar inherits Rectangle {
in property <string> adapter;
in property <string> backend;
in property <string> layout-class;
in property <int> fps;
/// FR-UI-1: the layout class, so the instrumentation can stand down on a
/// strip too narrow to carry both it and the controls.
in property <bool> expanded: true;
in property <string> filename;
in property <string> position;
/// Only offered where there is a library to go back to — with files named
/// on the command line there is no grid behind this view.
in property <bool> can-return-to-library: false;
/// Whether the develop column is currently shown, for the toggle's label.
in property <bool> panel-visible: true;
/// What the export button says. Rust owns it because the answer depends
/// on settings this component does not see — the format, and whether the
/// destination is this device or the server.
in property <string> export-label: "Export";
in property <bool> export-busy: false;
/// What the last export did. Sits beside the button rather than in a
/// dialogue: an export that succeeded needs no acknowledging, and one
/// that failed needs its reason where the retry is.
in property <string> export-status;
/// Whether the edit can be stepped either way (FR-DEV-5).
in property <bool> can-undo: false;
in property <bool> can-redo: false;
callback back-to-library();
callback open-people();
callback open-settings();
callback toggle-panel();
callback export-image();
callback undo();
callback redo();
// 44px and `surface`, the same bar the library and settings draw.
//
// It was 28px — the control height — which was a reasonable size for a
// readout and the wrong size for the thing it actually is: this strip
// carries the only way out of develop, and it was half the height of the
// identical control on the two screens either side of it. Three screens
// whose headers do not agree read as three applications, and the one place
// the user needs to find a way back was the one drawn smallest.
//
// Controls keep their own 28px and are centred in it, exactly as the
// library header centres the same buttons: a `Button` sets a fixed height,
// which a HorizontalLayout parks at the top of the row rather than
// centring for it.
height: 44px;
background: Theme.surface;
// Scrolls rather than overflowing. A layout given less width than its
// children need does not shrink them — it overruns the edge and reports
// the oversized minimum to whatever contains it. Collapsing the
// instrumentation below buys back about 200 logical pixels, which is
// enough for a tablet; it is not enough for a phone, and nothing here
// should be unreachable merely because the window is narrow.
Flickable {
width: 100%;
height: 100%;
viewport-height: self.height;
viewport-width: max(self.width, strip.preferred-width);
strip := HorizontalLayout {
width: parent.viewport-width;
height: parent.viewport-height;
padding-left: Theme.gap;
padding-right: Theme.gap;
spacing: Theme.gap;
alignment: start;
// Kept instantiated with `visible` rather than wrapped in an `if`:
// the strip is inside the layout that `expanded` feeds, and adding a
// conditional child here is the shape that has already caused binding
// loops in this file (see the panel below).
Button {
text: "‹ Library";
y: (parent.height - self.height) / 2;
visible: root.can-return-to-library;
clicked => { root.back-to-library(); }
}
// The third screen, reachable from the second. Beside the way back
// because it is the same kind of move — leaving this photograph for
// somewhere else in the library — and because a mode you can only
// reach from one of the other two is not a peer of them, whatever the
// navigation model says.
Button {
text: "Identity";
y: (parent.height - self.height) / 2;
visible: root.can-return-to-library;
clicked => { root.open-people(); }
}
Value { text: root.filename; compact: true; overflow: elide; }
Caption { text: root.position; }
Rectangle { horizontal-stretch: 1; }
// --- instrumentation lives in Settings now ------------------------
//
// The render backend, the layout class and the frame rate are
// developer readouts. They cost about 200 logical pixels of a strip
// that also carries the only way out of develop, the undo pair, the
// panel toggle and the export button — and a `HorizontalLayout` given
// less width than its children's minimums does not shrink them, it
// runs off the end.
//
// A tablet in portrait is 768 logical pixels and was already below the
// breakpoint that hid them, so it kept its header. Landscape is 1200
// and is not, so the readouts came back and the header ran off the
// right-hand edge there instead — a breakpoint that hides a problem at
// one size and not another is a workaround, not a fix.
//
// The backend, the frame rate and the layout class used to live here.
// They are diagnostics — read once when something looks wrong, and
// never again — and as permanent furniture in a 44px strip they cost
// three slots that the controls beside them actually needed. On a
// tablet in landscape the header ran off the right-hand edge, and this
// was a third of the reason.
//
// They are in Settings under ABOUT now, next to the version, which is
// where someone goes when they have a bug to report rather than a
// photograph to edit.
// Undo and redo, in the strip rather than in the develop column: the
// panel can be put away, and the one control that takes back a
// mis-drag must not go away with it. Disabled rather than hidden, so
// the pair keeps its place and the keyboard shortcut has something
// visible to correspond to.
Button {
text: "Undo";
enabled: root.can-undo;
y: (parent.height - self.height) / 2;
clicked => { root.undo(); }
}
Button {
text: "Redo";
enabled: root.can-redo;
y: (parent.height - self.height) / 2;
clicked => { root.redo(); }
}
// Show or hide the develop column. On a tablet the panel is 280px of a
// screen that is mostly photograph, and the whole point of opening an
// image is to look at it — so being able to put the instruments away
// and bring them back is worth a control of its own. The glyph points
// the way the panel will move.
Button {
text: root.panel-visible ? "Hide panel ›" : "‹ Panel";
active: root.panel-visible;
y: (parent.height - self.height) / 2;
clicked => { root.toggle-panel(); }
}
Caption {
text: root.export-status;
vertical-alignment: center;
overflow: elide;
}
// The export itself, beside the settings that shape it. This is the
// screen where a photograph is finished, so it is the screen where
// one is asked for — the grid can export a selection later, but a
// single finished frame is exported from in front of it.
Button {
text: root.export-busy ? "Exporting…" : root.export-label;
enabled: !root.export-busy;
y: (parent.height - self.height) / 2;
clicked => { root.export-image(); }
}
// Reachable from develop as well as from the grid: export defaults are
// most likely to be wanted with a finished photograph on screen, which
// is exactly where this bar is and the library header is not.
Button {
text: "Settings";
y: (parent.height - self.height) / 2;
clicked => { root.open-settings(); }
}
}
}
Rectangle {
y: parent.height - 1px;
height: 1px;
background: Theme.rule;
}
}
// Capture metadata. Read-only; the adjustment controls live in AdjustPanel,
// which is generated from pipeline capabilities rather than written here.
//
// Flat, like everything else in this column. This was briefly a collapsible
// section so that every panel could be put away; the sidebar as a whole now
// closes from the status strip instead, which is the control that was actually
// wanted, and the per-group lids only stood between the user and the controls.
component InfoPanel inherits Rectangle {
in property <string> camera;
in property <string> exposure;
in property <string> dimensions;
/// 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.
out property <length> content-width: panel.preferred-width;
min-width: root.content-width;
background: transparent;
height: panel.preferred-height;
panel := Panel {
// Flat: this abuts the adjust panel below it and the rule between
// them is drawn by the column that stacks the two.
flat: true;
width: 100%;
PanelHeading { text: "IMAGE"; }
Value {
text: root.camera == "" ? "—" : root.camera;
placeholder: root.camera == "";
wrap: word-wrap;
}
Label { text: root.exposure; }
Caption { text: root.dimensions; }
}
}
export component AppWindow inherits Window {
title: "DarkRoom";
// The one raster asset in the UI, and the exception that the header of
+252
View File
@@ -0,0 +1,252 @@
// The develop view's own chrome: the strip across the top of the window and
// the capture-metadata block in the column beside the canvas.
//
// Split out of `app.slint`, where they sat above `AppWindow` and were read as
// part of the application shell. They are not: neither is instantiated
// anywhere but the develop view, and the shell's job — choosing which of the
// five screens is up — is easier to read without two unrelated components in
// front of it.
import { Theme } from "theme.slint";
import { Button, PanelHeading, Label, Value, Caption, Panel } from "widgets.slint";
// Status strip — surfaces the GPU backend and adapter, which matters during
// v0.1 because assumption A1 is exactly "does this compositing path work on
// this hardware". Seeing the adapter at a glance makes vendor differences
// obvious during the S1/S2 spikes.
export component StatusBar inherits Rectangle {
in property <string> adapter;
in property <string> backend;
in property <string> layout-class;
in property <int> fps;
/// FR-UI-1: the layout class, so the instrumentation can stand down on a
/// strip too narrow to carry both it and the controls.
in property <bool> expanded: true;
in property <string> filename;
in property <string> position;
/// Only offered where there is a library to go back to — with files named
/// on the command line there is no grid behind this view.
in property <bool> can-return-to-library: false;
/// Whether the develop column is currently shown, for the toggle's label.
in property <bool> panel-visible: true;
/// What the export button says. Rust owns it because the answer depends
/// on settings this component does not see — the format, and whether the
/// destination is this device or the server.
in property <string> export-label: "Export";
in property <bool> export-busy: false;
/// What the last export did. Sits beside the button rather than in a
/// dialogue: an export that succeeded needs no acknowledging, and one
/// that failed needs its reason where the retry is.
in property <string> export-status;
/// Whether the edit can be stepped either way (FR-DEV-5).
in property <bool> can-undo: false;
in property <bool> can-redo: false;
callback back-to-library();
callback open-people();
callback open-settings();
callback toggle-panel();
callback export-image();
callback undo();
callback redo();
// 44px and `surface`, the same bar the library and settings draw.
//
// It was 28px — the control height — which was a reasonable size for a
// readout and the wrong size for the thing it actually is: this strip
// carries the only way out of develop, and it was half the height of the
// identical control on the two screens either side of it. Three screens
// whose headers do not agree read as three applications, and the one place
// the user needs to find a way back was the one drawn smallest.
//
// Controls keep their own 28px and are centred in it, exactly as the
// library header centres the same buttons: a `Button` sets a fixed height,
// which a HorizontalLayout parks at the top of the row rather than
// centring for it.
height: 44px;
background: Theme.surface;
// Scrolls rather than overflowing. A layout given less width than its
// children need does not shrink them — it overruns the edge and reports
// the oversized minimum to whatever contains it. Collapsing the
// instrumentation below buys back about 200 logical pixels, which is
// enough for a tablet; it is not enough for a phone, and nothing here
// should be unreachable merely because the window is narrow.
Flickable {
width: 100%;
height: 100%;
viewport-height: self.height;
viewport-width: max(self.width, strip.preferred-width);
strip := HorizontalLayout {
width: parent.viewport-width;
height: parent.viewport-height;
padding-left: Theme.gap;
padding-right: Theme.gap;
spacing: Theme.gap;
alignment: start;
// Kept instantiated with `visible` rather than wrapped in an `if`:
// the strip is inside the layout that `expanded` feeds, and adding a
// conditional child here is the shape that has already caused binding
// loops in this file (see the panel below).
Button {
text: "‹ Library";
y: (parent.height - self.height) / 2;
visible: root.can-return-to-library;
clicked => { root.back-to-library(); }
}
// The third screen, reachable from the second. Beside the way back
// because it is the same kind of move — leaving this photograph for
// somewhere else in the library — and because a mode you can only
// reach from one of the other two is not a peer of them, whatever the
// navigation model says.
Button {
text: "Identity";
y: (parent.height - self.height) / 2;
visible: root.can-return-to-library;
clicked => { root.open-people(); }
}
Value { text: root.filename; compact: true; overflow: elide; }
Caption { text: root.position; }
Rectangle { horizontal-stretch: 1; }
// --- instrumentation lives in Settings now ------------------------
//
// The render backend, the layout class and the frame rate are
// developer readouts. They cost about 200 logical pixels of a strip
// that also carries the only way out of develop, the undo pair, the
// panel toggle and the export button — and a `HorizontalLayout` given
// less width than its children's minimums does not shrink them, it
// runs off the end.
//
// A tablet in portrait is 768 logical pixels and was already below the
// breakpoint that hid them, so it kept its header. Landscape is 1200
// and is not, so the readouts came back and the header ran off the
// right-hand edge there instead — a breakpoint that hides a problem at
// one size and not another is a workaround, not a fix.
//
// The backend, the frame rate and the layout class used to live here.
// They are diagnostics — read once when something looks wrong, and
// never again — and as permanent furniture in a 44px strip they cost
// three slots that the controls beside them actually needed. On a
// tablet in landscape the header ran off the right-hand edge, and this
// was a third of the reason.
//
// They are in Settings under ABOUT now, next to the version, which is
// where someone goes when they have a bug to report rather than a
// photograph to edit.
// Undo and redo, in the strip rather than in the develop column: the
// panel can be put away, and the one control that takes back a
// mis-drag must not go away with it. Disabled rather than hidden, so
// the pair keeps its place and the keyboard shortcut has something
// visible to correspond to.
Button {
text: "Undo";
enabled: root.can-undo;
y: (parent.height - self.height) / 2;
clicked => { root.undo(); }
}
Button {
text: "Redo";
enabled: root.can-redo;
y: (parent.height - self.height) / 2;
clicked => { root.redo(); }
}
// Show or hide the develop column. On a tablet the panel is 280px of a
// screen that is mostly photograph, and the whole point of opening an
// image is to look at it — so being able to put the instruments away
// and bring them back is worth a control of its own. The glyph points
// the way the panel will move.
Button {
text: root.panel-visible ? "Hide panel ›" : "‹ Panel";
active: root.panel-visible;
y: (parent.height - self.height) / 2;
clicked => { root.toggle-panel(); }
}
Caption {
text: root.export-status;
vertical-alignment: center;
overflow: elide;
}
// The export itself, beside the settings that shape it. This is the
// screen where a photograph is finished, so it is the screen where
// one is asked for — the grid can export a selection later, but a
// single finished frame is exported from in front of it.
Button {
text: root.export-busy ? "Exporting…" : root.export-label;
enabled: !root.export-busy;
y: (parent.height - self.height) / 2;
clicked => { root.export-image(); }
}
// Reachable from develop as well as from the grid: export defaults are
// most likely to be wanted with a finished photograph on screen, which
// is exactly where this bar is and the library header is not.
Button {
text: "Settings";
y: (parent.height - self.height) / 2;
clicked => { root.open-settings(); }
}
}
}
Rectangle {
y: parent.height - 1px;
height: 1px;
background: Theme.rule;
}
}
// Capture metadata. Read-only; the adjustment controls live in AdjustPanel,
// which is generated from pipeline capabilities rather than written here.
//
// Flat, like everything else in this column. This was briefly a collapsible
// section so that every panel could be put away; the sidebar as a whole now
// closes from the status strip instead, which is the control that was actually
// wanted, and the per-group lids only stood between the user and the controls.
export component InfoPanel inherits Rectangle {
in property <string> camera;
in property <string> exposure;
in property <string> dimensions;
/// 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.
out property <length> content-width: panel.preferred-width;
min-width: root.content-width;
background: transparent;
height: panel.preferred-height;
panel := Panel {
// Flat: this abuts the adjust panel below it and the rule between
// them is drawn by the column that stacks the two.
flat: true;
width: 100%;
PanelHeading { text: "IMAGE"; }
Value {
text: root.camera == "" ? "—" : root.camera;
placeholder: root.camera == "";
wrap: word-wrap;
}
Label { text: root.exposure; }
Caption { text: root.dimensions; }
}
}