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:
@@ -968,6 +968,47 @@ fn wire_zoom_pan_crop(
|
||||
redraw(&w);
|
||||
});
|
||||
}
|
||||
{
|
||||
// TRACES: FR-DEV-17
|
||||
// The end of a crop gesture, carrying the rect it was pressed from.
|
||||
// Measured here and never in `on_crop_changed`: the drag reports every
|
||||
// frame, and a handle passing over a mask on its way somewhere else
|
||||
// is not an event.
|
||||
let weak = window.as_weak();
|
||||
let session = session.clone();
|
||||
let redraw = redraw.clone();
|
||||
window
|
||||
.global::<Framing>()
|
||||
.on_crop_released(move |x, y, width, height| {
|
||||
let Some(w) = weak.upgrade() else { return };
|
||||
if let Some(s) = session.borrow_mut().as_mut() {
|
||||
let mut before = s.framing();
|
||||
before.set_crop(dr_pipeline::CropRect {
|
||||
x,
|
||||
y,
|
||||
width,
|
||||
height,
|
||||
});
|
||||
s.notice_hidden_masks(&before);
|
||||
}
|
||||
redraw(&w);
|
||||
});
|
||||
}
|
||||
{
|
||||
// TRACES: FR-DEV-17
|
||||
// Keep the crop. The only thing to do is stop saying so: the crop
|
||||
// was applied when it was let go and has been in the edit since.
|
||||
let weak = window.as_weak();
|
||||
let session = session.clone();
|
||||
let redraw = redraw.clone();
|
||||
window.global::<Framing>().on_hidden_masks_kept(move || {
|
||||
let Some(w) = weak.upgrade() else { return };
|
||||
if let Some(s) = session.borrow_mut().as_mut() {
|
||||
s.dismiss_crop_notice();
|
||||
}
|
||||
redraw(&w);
|
||||
});
|
||||
}
|
||||
{
|
||||
// TRACES: FR-DEV-3
|
||||
// Choosing a ratio reshapes the crop there and then rather than
|
||||
@@ -1017,6 +1058,27 @@ fn wire_zoom_pan_crop(
|
||||
}
|
||||
}
|
||||
|
||||
/// TRACES: FR-DEV-17
|
||||
/// Mirror the session's crop notice into the window: how many mask layers the
|
||||
/// crop on top of the history left outside the frame, and which.
|
||||
///
|
||||
/// Called on every redraw, which is every path that can move the history —
|
||||
/// so the notice goes the frame an undo takes its crop back. Written only
|
||||
/// when it differs, since a drag redraws sixty times a second.
|
||||
pub(crate) fn sync_crop_notice(window: &AppWindow, s: &DevelopSession) {
|
||||
let framing = window.global::<Framing>();
|
||||
let (count, names) = match s.crop_notice() {
|
||||
Some(n) => (n.names().len() as i32, n.names().join(", ")),
|
||||
None => (0, String::new()),
|
||||
};
|
||||
if framing.get_hidden_masks() != count {
|
||||
framing.set_hidden_masks(count);
|
||||
}
|
||||
if framing.get_hidden_mask_names().as_str() != names {
|
||||
framing.set_hidden_mask_names(names.into());
|
||||
}
|
||||
}
|
||||
|
||||
/// ---- rotation, flips and straightening -------------------------------
|
||||
///
|
||||
/// Framing edits, so unlike zoom and pan they mark the image modified — but
|
||||
|
||||
Reference in New Issue
Block a user