mirror of
https://github.com/storytold/lightcraft.git
synced 2026-10-27 19:47:35 -06:00
library.up / library.down (Up / Down) go to the cell above / below in the grids (nearest by position in justified rows; the top / bottom row stays put), and step one photo in the filmstrip. Sources: own design. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
85 lines
4.3 KiB
Rust
85 lines
4.3 KiB
Rust
//! Remove tool commands: add (automatic source), select, move target/source, edit, refresh, delete.
|
|
|
|
use serde_json::json;
|
|
|
|
use crate::Session;
|
|
|
|
/// `library.up` / `library.down` (#1171): move by the given number of photos, clamped at the ends.
|
|
#[test]
|
|
fn up_and_down_move_by_rows_and_stop_at_the_ends() {
|
|
let mut s = Session::with_demo();
|
|
let vis = s.visible_cloned();
|
|
assert!(vis.len() >= 6);
|
|
s.selection = crate::Selection::single(vis[0]);
|
|
s.execute("library.down", &json!({"rows": 4})).unwrap();
|
|
assert_eq!(s.selection.active, Some(vis[4]));
|
|
s.execute("library.up", &json!({})).unwrap();
|
|
assert_eq!(s.selection.active, Some(vis[3]), "default is one photo (the filmstrip)");
|
|
s.execute("library.up", &json!({"rows": 99})).unwrap();
|
|
assert_eq!(s.selection.active, Some(vis[0]), "clamped at the first photo");
|
|
s.execute("library.down", &json!({"rows": u64::MAX})).unwrap();
|
|
assert_eq!(s.selection.active, vis.last().copied(), "hostile counts clamp, never overflow");
|
|
}
|
|
|
|
fn spots(s: &Session) -> Vec<lightcraft_develop::Spot> {
|
|
s.develop_of(s.active().unwrap()).unwrap().spots.clone()
|
|
}
|
|
|
|
#[test]
|
|
fn spots_select_move_edit_refresh_and_delete() {
|
|
let mut s = Session::with_demo();
|
|
// a remove spot gets a resolved source (a pin to show) and is selected
|
|
let r = s.execute("spot.add", &json!({"points": [[0.4, 0.5]], "size": 0.02})).unwrap();
|
|
assert_eq!(r["index"], 0);
|
|
assert_eq!(s.active_spot, Some(0));
|
|
let src0 = spots(&s)[0].source_offset.expect("automatic source resolved");
|
|
assert!(src0.x.hypot(src0.y) > 0.02, "the source is outside the spot: {src0:?}");
|
|
s.execute("spot.add", &json!({"mode": "clone", "points": [[0.7, 0.3]], "source": [0.1, 0.0]})).unwrap();
|
|
assert_eq!(s.active_spot, Some(1));
|
|
assert_eq!(s.execute("library.state", &json!({})).unwrap()["activeSpot"], 1);
|
|
// select
|
|
s.execute("spot.select", &json!({"index": 0})).unwrap();
|
|
assert_eq!(s.active_spot, Some(0));
|
|
assert!(s.execute("spot.select", &json!({"index": 9})).is_err());
|
|
// move the target (the source follows: its offset is relative), then the source alone
|
|
s.execute("spot.update", &json!({"move": [0.05, -0.1]})).unwrap();
|
|
let sp = &spots(&s)[0];
|
|
assert!((sp.points[0].x - 0.45).abs() < 1e-9 && (sp.points[0].y - 0.4).abs() < 1e-9);
|
|
assert_eq!(sp.source_offset, Some(src0));
|
|
s.execute("spot.update", &json!({"moveSource": [0.01, 0.02]})).unwrap();
|
|
let o = spots(&s)[0].source_offset.unwrap();
|
|
assert!((o.x - src0.x - 0.01).abs() < 1e-9 && (o.y - src0.y - 0.02).abs() < 1e-9);
|
|
// size / feather / opacity of the selected spot (clamped), one undo step each
|
|
s.execute("spot.update", &json!({"size": 0.03, "feather": 20, "opacity": 150})).unwrap();
|
|
let sp = &spots(&s)[0];
|
|
assert_eq!((sp.size, sp.feather, sp.opacity), (0.03, 20.0, 100.0));
|
|
s.execute("edit.undo", &json!({})).unwrap();
|
|
assert_eq!(spots(&s)[0].size, 0.02);
|
|
// refresh: a different source, at least a spot radius away
|
|
let before = spots(&s)[0].source_offset.unwrap();
|
|
let r = s.execute("spot.refreshSource", &json!({})).unwrap();
|
|
let after = spots(&s)[0].source_offset.unwrap();
|
|
assert!((after.x - before.x).hypot(after.y - before.y) > 0.01, "{r}");
|
|
// delete the selected spot: the selection clears; deleting before a selected one shifts it
|
|
s.execute("spot.delete", &json!({})).unwrap();
|
|
assert_eq!((spots(&s).len(), s.active_spot), (1, None));
|
|
assert_eq!(spots(&s)[0].mode, lightcraft_develop::SpotMode::Clone);
|
|
assert!(s.execute("spot.delete", &json!({})).is_err(), "nothing selected");
|
|
s.execute("spot.add", &json!({"points": [[0.2, 0.2]], "source": [0.05, 0.0]})).unwrap();
|
|
s.execute("spot.delete", &json!({"index": 0})).unwrap();
|
|
assert_eq!(s.active_spot, Some(0));
|
|
// another photo: no selection
|
|
s.execute("library.next", &json!({})).unwrap();
|
|
assert_eq!(s.active_spot, None);
|
|
}
|
|
|
|
#[test]
|
|
fn a_content_aware_spot_has_no_source() {
|
|
let mut s = Session::with_demo();
|
|
s.execute("spot.add", &json!({"mode": "fill", "points": [[0.4, 0.5]], "size": 0.03})).unwrap();
|
|
let sp = &spots(&s)[0];
|
|
assert_eq!((sp.mode, sp.source_offset), (lightcraft_develop::SpotMode::Fill, None));
|
|
assert_eq!(serde_json::to_value(sp).unwrap()["mode"], "fill");
|
|
assert!(s.execute("spot.update", &json!({"mode": "nonsense"})).is_err());
|
|
}
|