mirror of
https://github.com/storytold/lightcraft.git
synced 2026-10-29 12:37:51 -06:00
Faces: resize a face box with eight drag handles
Hovering a box shows handles at its corners and edge midpoints; dragging one
previews the new box live and commits once on release, as one undo step, through
the new `photo.setRegion {id?, index, rect}`: catalog-only like removal, never
rewriting the sidecar, with the rectangle validated (finite numbers only,
clamped to the photo, at least 0.5 % each way) and a test with a real sidecar.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 5.5
parent
e5848aa1b2
commit
9bb06cc9dc
@@ -472,6 +472,40 @@ pub fn specs() -> Vec<CommandSpec> {
|
||||
Ok(json!({"removed": gone.name}))
|
||||
}
|
||||
),
|
||||
cmd!(
|
||||
"photo.setRegion",
|
||||
"Resize Face Box",
|
||||
[],
|
||||
None,
|
||||
"{id?, index, rect: {x0, y0, x1, y1}} — set one face / pet region's box (normalized, in the photo's upright frame; clamped to the photo, at least 0.5 % each way) in the catalog; undoable. Like photo.removeRegion it never rewrites the XMP sidecar",
|
||||
always,
|
||||
|s, p| {
|
||||
const C: &str = "photo.setRegion";
|
||||
// a drag is one undo step however many frames it took: the caller commits once, on release
|
||||
let id = p.get("id").and_then(Value::as_u64).map(PhotoId).or_else(|| s.active()).ok_or_else(|| bad(C, "no photo"))?;
|
||||
let index = p
|
||||
.get("index")
|
||||
.and_then(Value::as_u64)
|
||||
.and_then(|i| usize::try_from(i).ok())
|
||||
.ok_or_else(|| bad(C, "missing or invalid `index`"))?;
|
||||
let r = p.get("rect").ok_or_else(|| bad(C, "missing `rect`"))?;
|
||||
let num = |k: &str| {
|
||||
r.get(k).and_then(Value::as_f64).filter(|v| v.is_finite()).ok_or_else(|| bad(C, format!("`rect.{k}` must be a finite number")))
|
||||
};
|
||||
let (a, b, c, d) = (num("x0")?, num("y0")?, num("x1")?, num("y1")?);
|
||||
let (x0, x1) = (a.min(c).clamp(0.0, 1.0), a.max(c).clamp(0.0, 1.0));
|
||||
let (y0, y1) = (b.min(d).clamp(0.0, 1.0), b.max(d).clamp(0.0, 1.0));
|
||||
if x1 - x0 < 0.005 || y1 - y0 < 0.005 {
|
||||
return Err(bad(C, "the box would be too small"));
|
||||
}
|
||||
let mut meta = s.catalog.photo(id).ok_or_else(|| bad(C, "no such photo"))?.meta.clone();
|
||||
let region = meta.regions.get_mut(index).ok_or_else(|| bad(C, "no such region"))?;
|
||||
region.rect = lightcraft_geom::Rect { x0, y0, x1, y1 };
|
||||
s.commit("Resize Face Box", Op::SetMeta { id, meta: Box::new(meta) })?;
|
||||
s.skip_auto_write = true;
|
||||
Ok(json!({"rect": {"x0": x0, "y0": y0, "x1": x1, "y1": y1}}))
|
||||
}
|
||||
),
|
||||
// ---- delete / restore
|
||||
cmd!("photo.delete", "Delete Photo", ["Photo"], Some("Delete"), "{ids?} — moves to Recently Deleted", has_selection, |s, p| {
|
||||
let v = for_targets(s, p, "Delete", |id| Some(Op::SetDeleted { id, deleted: true }))?;
|
||||
|
||||
Reference in New Issue
Block a user