Put the repair tool on the photograph
A third chip beside Crop and Local, and the mode strip's own comment predicted the shape: a mode that arms a gesture on the canvas and scopes the column. Click a mark to cover it, drag the disc to move the repair, drag the source circle to say where the patch comes from, Delete to remove it. The source starts two and a half radii towards the middle of the frame, which is FR-DEV-8's automatic placement in its cheap form — dust sits on skies and skies are smooth, so it is usually right and always one drag from fixed. Two things are drawn deliberately. The circles are the size the repairs actually are, because whether a disc covers a speck is the whole judgement being made and a fixed-size dot would say nothing about it; the reach around them is padded to a touch target so a spot on a dust mark can still be picked up on a phone. And only the selected repair shows its source: a dusty sky carries a dozen, and two dozen circles with nothing saying which belongs to which is less information rather than more. The panel edits what is stored while the canvas draws what is mapped, and the two are pushed separately for that reason — a slider deriving its value from the drawn radius would move differently at different zoom levels. It is also the one panel built from SliderRow rather than a live track: a repair has no OpId to coalesce a drag under, so a row that fires once per gesture is what keeps undo one step per decision. Verified as far as this environment allows: the strip renders and the column re-scopes, photographed under XWayland. Synthetic clicks do not reach this application, so the gestures are as-written rather than as-felt, and docs/spot-removal.md says so. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -40,6 +40,7 @@ mod segmentation;
|
||||
mod settings_store;
|
||||
mod settings_ui;
|
||||
mod sidecar_cache;
|
||||
mod spots_ui;
|
||||
mod trash;
|
||||
|
||||
use std::cell::RefCell;
|
||||
@@ -292,6 +293,11 @@ fn reset_view_state(window: &AppWindow) {
|
||||
// overlay or the crosshair to the next one would offer a selection of
|
||||
// regions that are not in the picture on screen.
|
||||
masks_ui::reset(window);
|
||||
// TRACES: FR-DEV-8
|
||||
// The repairs belong to one photograph too. The next one's arrive with its
|
||||
// sidecar a moment later, and until they do the canvas must not be showing
|
||||
// the last one's.
|
||||
spots_ui::reset(window);
|
||||
}
|
||||
|
||||
/// Push the framing back to the geometry panel.
|
||||
@@ -1279,6 +1285,18 @@ pub fn run(paths: Vec<PathBuf>) -> Result<()> {
|
||||
// those ends in a redraw.
|
||||
masks_ui::sync_overlay_view(window, s);
|
||||
|
||||
// TRACES: FR-DEV-8
|
||||
// And where the repairs are drawn, for the same reason: a pan or a
|
||||
// zoom moves every circle while touching no repair, and a circle
|
||||
// left where the mark used to be is worse than no circle at all.
|
||||
spots_ui::sync_handles(window, s);
|
||||
// The column's own numbers, pushed from here as well so that a
|
||||
// photograph opened with repairs already on it arrives with the
|
||||
// panel describing them — the sidecar lands after the callbacks
|
||||
// have all been installed, and a redraw is the one path every
|
||||
// arrival takes.
|
||||
spots_ui::sync_panel(window, s);
|
||||
|
||||
let (mut w, mut h) = *viewport.borrow();
|
||||
|
||||
// **Half resolution while the gesture is still moving.**
|
||||
@@ -1951,6 +1969,7 @@ pub fn run(paths: Vec<PathBuf>) -> Result<()> {
|
||||
}
|
||||
|
||||
masks_ui::wire(&window, &session, &rows, &redraw);
|
||||
spots_ui::wire(&window, &session, redraw.clone());
|
||||
|
||||
{
|
||||
let weak = window.as_weak();
|
||||
@@ -2166,14 +2185,32 @@ pub fn run(paths: Vec<PathBuf>) -> Result<()> {
|
||||
w.set_crop_h(c.height);
|
||||
}
|
||||
ViewMode::Local => s.set_overlay(true),
|
||||
// TRACES: FR-DEV-8
|
||||
// Nothing to arm: the circles are drawn whenever there are
|
||||
// repairs, and what the mode changes is whether a click on
|
||||
// the photograph makes another one. Leaving the mask
|
||||
// selection behind would re-point the column at a layer's
|
||||
// chain while the canvas is showing repairs, which is the
|
||||
// fault this whole strip exists to prevent.
|
||||
ViewMode::Spots => {
|
||||
s.set_overlay(false);
|
||||
s.set_active_mask(None);
|
||||
}
|
||||
ViewMode::Photo => {
|
||||
s.set_overlay(false);
|
||||
s.set_active_mask(None);
|
||||
// A repair stays on the photograph; only the *selection*
|
||||
// goes, so the source circle does not hang about over a
|
||||
// frame nobody is repairing any more.
|
||||
s.select_spot(None);
|
||||
}
|
||||
}
|
||||
}
|
||||
w.set_view_mode(mode);
|
||||
masks_ui::sync(&w, &session);
|
||||
if let Some(s) = session.borrow().as_ref() {
|
||||
spots_ui::sync_handles(&w, s);
|
||||
}
|
||||
// The scope may have just changed, so the panel below is now
|
||||
// describing a different chain.
|
||||
sync_rows(&w, &rows, &session);
|
||||
|
||||
Reference in New Issue
Block a user