Send each canvas overlay home to its own domain
The crop rectangle, the gradient handles and the repair discs were 480 lines inside `canvas-area` in app.slint, while the panels that drive them already lived in adjust.slint, masks.slint and spots.slint. spots.slint even opens by describing "what is drawn over the photograph, and what a finger can take hold of" -- which was not in it. They stayed behind because all three are positioned against `shown-*`, the fitted image rect the develop view derives because Slint does not report it. That is now the interface rather than the obstacle: each overlay is *given* that rect as its own bounds, so every position inside is a plain fraction of `root.width`, and none of them reaches out to `canvas-area` for an origin any more. GradientHandles joins MaskPanel in masks.slint and SpotHandles joins SpotPanel in spots.slint, each file now holding one domain's panel and its canvas overlay together, matching masks_ui.rs and spots_ui.rs. CropOverlay gets crop.slint of its own rather than growing adjust.slint. Arithmetic is unchanged: the old fraction-x subtracted shown-x from a coordinate measured relative to canvas-area, and the new one measures from an origin that already is shown-x. The handles stay unconditional rather than gaining an emptiness guard, so the repeater identity that spots_ui::sync_handles warns about is untouched. app.slint: 2834 -> 2490 lines. Verified by running the desktop app on a photograph, with the crop overlay's guard temporarily forced open so all three instantiate -- no binding loop, no panic. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -176,3 +176,122 @@ export component SpotPanel inherits Rectangle {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// TRACES: FR-DEV-8
|
||||
/// The repairs, drawn on the photograph.
|
||||
///
|
||||
/// **Drawn at the size they are.** A gradient's handles are dots because a
|
||||
/// gradient has no edge to show; a repair is a disc, and whether that disc
|
||||
/// covers a speck of dust is the entire judgement a photographer is making. A
|
||||
/// fixed-size dot standing in for it would say nothing about the edit.
|
||||
///
|
||||
/// The source circle appears for the **selected** repair only. Every repair
|
||||
/// showing both would double the circles on a dusty sky and leave no way to
|
||||
/// tell which source belongs to which disc; Rust decides, and simply does not
|
||||
/// send the others.
|
||||
///
|
||||
/// Sized by the develop view to the fitted image rect, so every position in
|
||||
/// here is a plain fraction of `root.width` and `root.height`. The fractions
|
||||
/// themselves arrive already mapped through the framing, so a repair follows a
|
||||
/// crop, a zoom and a rotation rather than sitting where it used to be.
|
||||
export component SpotHandles inherits Rectangle {
|
||||
in property <[SpotHandle]> handles;
|
||||
|
||||
/// Touching a repair chooses it.
|
||||
callback selected(string);
|
||||
/// id, role, from-x, from-y, to-x, to-y — fractions of this rect.
|
||||
callback dragged(string, SpotRole, float, float, float, float);
|
||||
callback released();
|
||||
|
||||
background: transparent;
|
||||
|
||||
for spot in root.handles: Rectangle {
|
||||
property <length> disc: max(2 * spot.radius * root.height, 8px);
|
||||
property <bool> is-source: spot.role == SpotRole.source;
|
||||
|
||||
x: spot.x * root.width - self.width / 2;
|
||||
y: spot.y * root.height - self.height / 2;
|
||||
// Never smaller than a finger, whatever the repair's own size: a spot
|
||||
// on a dust speck is a few pixels across at fit-to-window, and a
|
||||
// target that small cannot be picked up again on a phone (FR-UI-3).
|
||||
// The *drawn* circle keeps its true size; only the reach is padded.
|
||||
width: max(self.disc, Theme.touch-target);
|
||||
height: self.width;
|
||||
|
||||
Rectangle {
|
||||
width: parent.disc;
|
||||
height: self.width;
|
||||
x: (parent.width - self.width) / 2;
|
||||
y: (parent.height - self.height) / 2;
|
||||
border-radius: self.width / 2;
|
||||
border-width: spot.selected ? 2px : 1px;
|
||||
// White on a dark ring, so a repair is visible on a blown sky and
|
||||
// on a black frame alike — the same treatment the gradient handles
|
||||
// use, and no theme token, because this is drawn over the
|
||||
// photograph rather than over the interface.
|
||||
border-color: spot.enabled ? #ffffffcc : #ffffff55;
|
||||
background: parent.is-source
|
||||
? #ffffff14
|
||||
: (spot.selected ? #ffffff22 : transparent);
|
||||
}
|
||||
|
||||
// A second, darker ring just outside the first. One white circle
|
||||
// vanishes against a white sky however it is drawn; two rings of
|
||||
// opposite tone cannot both vanish against anything.
|
||||
Rectangle {
|
||||
width: parent.disc + 2px;
|
||||
height: self.width;
|
||||
x: (parent.width - self.width) / 2;
|
||||
y: (parent.height - self.height) / 2;
|
||||
border-radius: self.width / 2;
|
||||
border-width: 1px;
|
||||
border-color: #00000066;
|
||||
}
|
||||
|
||||
grab := TouchArea {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
mouse-cursor: MouseCursor.move;
|
||||
|
||||
// Where the press landed, in the fractions the callback reports
|
||||
// in — captured on the way down so the gesture is measured from
|
||||
// one origin.
|
||||
property <float> from-x;
|
||||
property <float> from-y;
|
||||
|
||||
function fraction-x(local-x: length) -> float {
|
||||
return (parent.x + local-x) / max(root.width, 1px);
|
||||
}
|
||||
function fraction-y(local-y: length) -> float {
|
||||
return (parent.y + local-y) / max(root.height, 1px);
|
||||
}
|
||||
|
||||
pointer-event(ev) => {
|
||||
if (ev.kind == PointerEventKind.down) {
|
||||
self.from-x = self.fraction-x(self.pressed-x);
|
||||
self.from-y = self.fraction-y(self.pressed-y);
|
||||
// Touching a repair is choosing it. On the way *down*, so
|
||||
// the column has re-scoped by the time the drag begins and
|
||||
// the controls describe what is moving.
|
||||
root.selected(spot.id);
|
||||
}
|
||||
if (ev.kind == PointerEventKind.up) {
|
||||
root.released();
|
||||
}
|
||||
}
|
||||
|
||||
moved => {
|
||||
if (self.pressed) {
|
||||
root.dragged(
|
||||
spot.id,
|
||||
spot.role,
|
||||
self.from-x,
|
||||
self.from-y,
|
||||
self.fraction-x(self.mouse-x),
|
||||
self.fraction-y(self.mouse-y),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user