They sat in the develop column under a "SETTINGS" heading, which read as application settings, and went away with the panel toggle and in the mask and spot modes. The strip is where undo already is for the same reason: these act on the whole edit, not on any one panel. The paste button still names what it would apply. The TransferPanel component is gone; the Transfer global and its Rust wiring are unchanged.
316 lines
14 KiB
Plaintext
316 lines
14 KiB
Plaintext
// 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 { Develop } from "session.slint";
|
||
import { Transfer } from "adjust.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(); }
|
||
}
|
||
|
||
// TRACES: FR-DEV-6
|
||
// Copy and paste, in the strip beside undo for the reason undo is
|
||
// here: they act on the whole edit, not on any one panel, and the
|
||
// column can be put away. They sat in the column under a "SETTINGS"
|
||
// heading, which read as application settings and hid them behind a
|
||
// scroll and behind the panel toggle.
|
||
//
|
||
// The paste button names what would be pasted — "3 adjustments", and
|
||
// whether the crop is staying behind — because "Paste" alone asks the
|
||
// user to remember what they copied.
|
||
Button {
|
||
text: "Copy";
|
||
enabled: Develop.enabled;
|
||
y: (parent.height - self.height) / 2;
|
||
clicked => { Transfer.copy(); }
|
||
}
|
||
|
||
// Enabled on `armed` rather than on a non-empty summary, so pasting a
|
||
// neutral copy — which clears this image — stays available.
|
||
Button {
|
||
text: !Transfer.armed ? "Paste"
|
||
: "Paste " + Transfer.summary
|
||
+ (Transfer.framing-withheld ? ", no crop" : "");
|
||
enabled: Develop.enabled && Transfer.armed;
|
||
y: (parent.height - self.height) / 2;
|
||
clicked => { Transfer.paste(); }
|
||
}
|
||
|
||
// The saved half: this edit, kept under a name.
|
||
Button {
|
||
text: "Presets…";
|
||
enabled: Develop.enabled;
|
||
y: (parent.height - self.height) / 2;
|
||
clicked => { Transfer.open-presets(); }
|
||
}
|
||
|
||
// 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;
|
||
}
|
||
}
|
||
|
||
/// What the file says about how the photograph was taken.
|
||
///
|
||
/// A global rather than four properties on the panel, for the reason
|
||
/// `session.slint` gives at length: the panel is drawn twice — once in the
|
||
/// develop column and once in the portrait dock — and four lines of forwarding
|
||
/// copied into the second composition is four lines that can fall behind the
|
||
/// first without anything failing.
|
||
///
|
||
/// Nothing here is a control. It is the one panel in the column that only
|
||
/// reports, which is why this global carries no callback at all.
|
||
export global Capture {
|
||
in property <string> camera;
|
||
/// TRACES: FR-DEV-3
|
||
/// The lens, and whether it matched a correction profile.
|
||
///
|
||
/// Here rather than beside the optical sliders, and the reason is what the
|
||
/// line is *for*. `dr-lens` states the rule — an automatic correction that
|
||
/// silently did nothing is worse than one the user can see is unavailable
|
||
/// — and the fact it reports is a fact about the file: which lens took
|
||
/// this photograph, and whether the database has heard of it. That is the
|
||
/// same kind of thing as the body and the exposure, and it wants reading
|
||
/// once on opening rather than hunting for under a group filter.
|
||
in property <string> lens;
|
||
in property <string> exposure;
|
||
in property <string> dimensions;
|
||
}
|
||
|
||
// 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 {
|
||
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: Capture.camera == "" ? "—" : Capture.camera;
|
||
placeholder: Capture.camera == "";
|
||
wrap: word-wrap;
|
||
}
|
||
|
||
Label { text: Capture.exposure; }
|
||
|
||
// Dim, like the dimensions below it: this is something the file says,
|
||
// not something the photographer chose. An empty string draws nothing
|
||
// — a session built from pixels with no header has no lens to report,
|
||
// and an empty row is honest where "Unknown" would be noise.
|
||
Caption {
|
||
text: Capture.lens;
|
||
visible: Capture.lens != "";
|
||
wrap: word-wrap;
|
||
}
|
||
|
||
Caption { text: Capture.dimensions; }
|
||
}
|
||
}
|