Files
DarkRoom/ui/dr-ui/ui/develop.slint
T
dtourolleandClaude Opus 5 ffc9e1aea7 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>
2026-08-27 21:33:17 +02:00

253 lines
11 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// 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; }
}
}