Say so when a crop leaves a mask outside the frame
Cropping tighter past a mask layer made it invisible without a word: the layer stayed in the panel and the sidecar, and its adjustment went on landing on pixels nobody would see again. When a crop is let go, develop now measures what the gesture did to the mask stack (dr_pipeline::orphan) and, if any layer is now entirely or mostly outside the frame, shows a notice over the photograph: how many layers, their names, "Undo crop" and "Keep crop". The crop is already applied and nothing waits on the answer. The crop overlay gains a release callback carrying the rect the press began from, so the measurement runs once per gesture and never on the drag's per-frame changes. Choosing a ratio is measured the same way, being a crop committed in one click. "Undo crop" is the ordinary undo, and the notice is tied to the history revision it was raised at: the redraw that follows any history move clears it, so the crop and its warning go back as one step. A second drag folded into the same step is measured from where that step began. A crop that strands nothing shows nothing.
This commit is contained in:
@@ -679,6 +679,22 @@ export global Framing {
|
||||
callback portrait-toggled();
|
||||
/// Crop, angle, rotation and flips back to neutral, leaving colour alone.
|
||||
callback reset();
|
||||
|
||||
/// TRACES: FR-DEV-17
|
||||
/// A crop gesture was let go; the rect it was pressed from. See
|
||||
/// `CropOverlay.crop-released`.
|
||||
callback crop-released(float, float, float, float);
|
||||
/// TRACES: FR-DEV-17
|
||||
/// How many mask layers the crop just let go took out of the frame, and
|
||||
/// their names, already joined. Zero, and the notice is not drawn — the
|
||||
/// common case says nothing.
|
||||
///
|
||||
/// Rust clears it when the history moves off that crop, so an undo takes
|
||||
/// the notice away with the crop it was about.
|
||||
in property <int> hidden-masks: 0;
|
||||
in property <string> hidden-mask-names: "";
|
||||
/// Keep the crop anyway: the notice has been read.
|
||||
callback hidden-masks-kept();
|
||||
}
|
||||
|
||||
// Crop, rotation, flips and straightening — the framing controls.
|
||||
|
||||
@@ -2024,6 +2024,9 @@ in property <bool> panel-visible: true;
|
||||
crop-changed(x, y, w, h, hx, hy) => {
|
||||
root.crop-changed(x, y, w, h, hx, hy);
|
||||
}
|
||||
crop-released(x, y, w, h) => {
|
||||
Framing.crop-released(x, y, w, h);
|
||||
}
|
||||
}
|
||||
|
||||
// --- gradient handles ---------------------------------------
|
||||
@@ -2545,6 +2548,64 @@ in property <bool> panel-visible: true;
|
||||
pick(i) => { Library.library-roll-pick(i); }
|
||||
}
|
||||
|
||||
// --- a crop that stranded a mask (FR-DEV-17) ----------------
|
||||
//
|
||||
// TRACES: FR-DEV-17
|
||||
// A notice, not a dialog. The crop is already applied and
|
||||
// nothing waits on an answer: the photograph can go on
|
||||
// being edited with this up, and the notice goes the
|
||||
// moment the history moves off the crop it is about —
|
||||
// Rust clears it — so ignoring it is keeping the crop.
|
||||
//
|
||||
// "Undo crop" is the ordinary undo, not a second route
|
||||
// back. The crop is one step, so taking it back is that
|
||||
// step, and the notice leaves with it rather than needing
|
||||
// a step of its own.
|
||||
//
|
||||
// At the top of the canvas and last in it, so it draws
|
||||
// over the photograph and clear of the roll's band at the
|
||||
// foot, which takes every press that lands in it.
|
||||
if Framing.hidden-masks > 0 && root.total > 0: Rectangle {
|
||||
x: (parent.width - self.width) / 2;
|
||||
y: Theme.gap;
|
||||
width: min(parent.width - 2 * Theme.gap, notice-row.preferred-width);
|
||||
height: notice-row.preferred-height;
|
||||
background: Theme.surface;
|
||||
border-radius: 6px;
|
||||
border-width: 1px;
|
||||
border-color: Theme.rule;
|
||||
// Swallow presses on the notice's own background, so
|
||||
// a click that misses a button does not pan or paint
|
||||
// the photograph underneath.
|
||||
TouchArea { }
|
||||
|
||||
notice-row := HorizontalLayout {
|
||||
padding: Theme.gap;
|
||||
spacing: Theme.gap;
|
||||
|
||||
Caption {
|
||||
text: (Framing.hidden-masks == 1
|
||||
? "This crop leaves 1 mask outside the frame: "
|
||||
: "This crop leaves " + Framing.hidden-masks
|
||||
+ " masks outside the frame: ")
|
||||
+ Framing.hidden-mask-names;
|
||||
warn: true;
|
||||
vertical-alignment: center;
|
||||
overflow: elide;
|
||||
horizontal-stretch: 1;
|
||||
}
|
||||
Button {
|
||||
text: "Undo crop";
|
||||
clicked => { Steps.undo(); }
|
||||
}
|
||||
Button {
|
||||
text: "Keep crop";
|
||||
primary: true;
|
||||
clicked => { Framing.hidden-masks-kept(); }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Report size changes so the render target can be resized to
|
||||
// match. Width and height are tracked separately because
|
||||
// Slint has no single "geometry changed" hook.
|
||||
|
||||
@@ -48,6 +48,14 @@ export component CropOverlay inherits Rectangle {
|
||||
/// written here — Rust takes the opposite corner.
|
||||
callback crop-changed(float, float, float, float, float, float);
|
||||
|
||||
/// TRACES: FR-DEV-17
|
||||
/// A drag of the rect or a handle was let go. Carries the rect as it was
|
||||
/// when the press began, so Rust can measure what the whole gesture did.
|
||||
///
|
||||
/// The one boundary a crop gesture reports. `crop-changed` fires on
|
||||
/// every pointer move and a handle passing over a mask on its way
|
||||
/// somewhere else is not an event; only the release is a decision.
|
||||
callback crop-released(float, float, float, float);
|
||||
|
||||
property <length> rx: root.crop-x * self.width;
|
||||
property <length> ry: root.crop-y * self.height;
|
||||
@@ -135,6 +143,8 @@ export component CropOverlay inherits Rectangle {
|
||||
|
||||
property <float> start-x;
|
||||
property <float> start-y;
|
||||
property <float> start-w;
|
||||
property <float> start-h;
|
||||
property <float> from-x;
|
||||
property <float> from-y;
|
||||
|
||||
@@ -149,9 +159,16 @@ export component CropOverlay inherits Rectangle {
|
||||
if (ev.kind == PointerEventKind.down) {
|
||||
self.start-x = root.crop-x;
|
||||
self.start-y = root.crop-y;
|
||||
self.start-w = root.crop-w;
|
||||
self.start-h = root.crop-h;
|
||||
self.from-x = self.fraction-x(self.pressed-x);
|
||||
self.from-y = self.fraction-y(self.pressed-y);
|
||||
}
|
||||
if (ev.kind == PointerEventKind.up || ev.kind == PointerEventKind.cancel) {
|
||||
// The extent too, not only the origin: Rust clamps a rect
|
||||
// moved past an edge, and that can narrow it.
|
||||
root.crop-released(self.start-x, self.start-y, self.start-w, self.start-h);
|
||||
}
|
||||
}
|
||||
|
||||
moved => {
|
||||
@@ -224,6 +241,9 @@ export component CropOverlay inherits Rectangle {
|
||||
self.from-x = self.fraction-x(self.pressed-x);
|
||||
self.from-y = self.fraction-y(self.pressed-y);
|
||||
}
|
||||
if (ev.kind == PointerEventKind.up || ev.kind == PointerEventKind.cancel) {
|
||||
root.crop-released(self.ox, self.oy, self.ow, self.oh);
|
||||
}
|
||||
}
|
||||
|
||||
// Movement as a fraction of the frame, live while
|
||||
|
||||
Reference in New Issue
Block a user