141 lines
7.1 KiB
Rust
141 lines
7.1 KiB
Rust
//! The width profile library (`stroke.widthProfile.*`): saved profiles kept with the preferences
|
|
//! next to the built-in ones, applied by name with `stroke.set {profile}`.
|
|
|
|
use serde_json::{Value, json};
|
|
use vectorcraft_doc::WidthProfile;
|
|
|
|
use super::*;
|
|
|
|
fn session_with_line() -> (Session, NodeId) {
|
|
let mut s = Session::new();
|
|
s.execute("file.new", &json!({"width": 200, "height": 200})).unwrap();
|
|
let r = s.execute("shape.line", &json!({"x1": 10, "y1": 100, "x2": 150, "y2": 100})).unwrap();
|
|
(s, NodeId(r["id"].as_u64().unwrap()))
|
|
}
|
|
|
|
fn profile(s: &Session, id: NodeId) -> Option<WidthProfile> {
|
|
s.doc().unwrap().doc.node(id).unwrap().appearance.stroke().unwrap().profile.clone()
|
|
}
|
|
|
|
fn custom() -> WidthProfile {
|
|
WidthProfile { points: vec![(0.0, 0.2, 0.2), (0.3, 1.6, 0.8), (1.0, 0.5, 0.5)] }
|
|
}
|
|
|
|
/// Give the line `p` as its variable width (as the Width tool would).
|
|
fn set_profile(s: &mut Session, id: NodeId, p: WidthProfile) {
|
|
s.edit("Width", |d, _| {
|
|
d.node_mut(id).unwrap().appearance.stroke_mut().unwrap().profile = Some(p);
|
|
Ok(())
|
|
})
|
|
.unwrap();
|
|
}
|
|
|
|
fn ids(list: &Value) -> Vec<&str> {
|
|
list["profiles"].as_array().unwrap().iter().map(|p| p["id"].as_str().unwrap()).collect()
|
|
}
|
|
|
|
#[test]
|
|
fn add_list_apply_delete_reset() {
|
|
let (mut s, id) = session_with_line();
|
|
let built_in: Vec<&str> = WidthProfile::PRESETS.iter().map(|p| p.id).collect();
|
|
let list = s.execute("stroke.widthProfile.list", &json!({})).unwrap();
|
|
assert_eq!(ids(&list), built_in);
|
|
assert_eq!(list["current"], "uniform");
|
|
// Nothing to save from a plain stroke, or from a built-in profile.
|
|
assert!(s.execute("stroke.widthProfile.add", &json!({})).is_err());
|
|
s.execute("stroke.set", &json!({"profile": "lens"})).unwrap();
|
|
assert!(s.execute("stroke.widthProfile.add", &json!({})).is_err());
|
|
|
|
set_profile(&mut s, id, custom());
|
|
assert_eq!(s.execute("stroke.widthProfile.list", &json!({})).unwrap()["current"], "custom");
|
|
let undo_depth = s.doc().unwrap().history.undo.len();
|
|
assert_eq!(s.execute("stroke.widthProfile.add", &json!({})).unwrap()["name"], "Width Profile 1");
|
|
assert_eq!(s.doc().unwrap().history.undo.len(), undo_depth, "the library is not part of the document");
|
|
// Listed after the built-ins, and now the stroke's profile.
|
|
let list = s.execute("stroke.widthProfile.list", &json!({})).unwrap();
|
|
assert_eq!(ids(&list).last(), Some(&"Width Profile 1"));
|
|
assert_eq!(list["current"], "Width Profile 1");
|
|
let row = list["profiles"].as_array().unwrap().last().unwrap().clone();
|
|
assert_eq!((row["builtIn"].clone(), row["points"][1].clone()), (json!(false), json!([0.3, 1.6, 0.8])));
|
|
// The same profile can't be saved twice.
|
|
assert!(s.execute("stroke.widthProfile.add", &json!({"name": "Again"})).is_err());
|
|
|
|
// Apply it by name to another stroke.
|
|
let other = NodeId(s.execute("shape.line", &json!({"x1": 10, "y1": 150, "x2": 150, "y2": 150})).unwrap()["id"].as_u64().unwrap());
|
|
assert!(profile(&s, other).is_none());
|
|
s.execute("stroke.set", &json!({"profile": "Width Profile 1"})).unwrap();
|
|
assert_eq!(profile(&s, other), Some(custom()));
|
|
assert!(s.execute("stroke.set", &json!({"profile": "Nope"})).is_err());
|
|
|
|
// Delete it (the selected stroke's): strokes keep their widths.
|
|
assert_eq!(s.execute("stroke.widthProfile.delete", &json!({})).unwrap()["deleted"], "Width Profile 1");
|
|
assert_eq!(ids(&s.execute("stroke.widthProfile.list", &json!({})).unwrap()), built_in);
|
|
assert_eq!(profile(&s, other), Some(custom()));
|
|
assert!(s.execute("stroke.set", &json!({"profile": "Width Profile 1"})).is_err());
|
|
|
|
// Reset removes every saved profile.
|
|
s.execute("stroke.widthProfile.add", &json!({"name": " Ribbon "})).unwrap();
|
|
set_profile(&mut s, other, WidthProfile { points: vec![(0.0, 1.0, 0.0), (1.0, 0.0, 1.0)] });
|
|
s.execute("stroke.widthProfile.add", &json!({"name": "Twist"})).unwrap();
|
|
assert_eq!(s.prefs.width_profiles.iter().map(|p| p.name.as_str()).collect::<Vec<_>>(), ["Ribbon", "Twist"]);
|
|
assert_eq!(s.execute("stroke.widthProfile.reset", &json!({})).unwrap()["removed"], 2);
|
|
assert!(s.prefs.width_profiles.is_empty());
|
|
assert!(s.execute("stroke.widthProfile.reset", &json!({})).is_err(), "nothing left to reset");
|
|
}
|
|
|
|
#[test]
|
|
fn built_ins_cant_be_deleted_and_names_stay_unique() {
|
|
let (mut s, id) = session_with_line();
|
|
set_profile(&mut s, id, custom());
|
|
for taken in ["lens", "Taper Start", "UNIFORM"] {
|
|
assert!(s.execute("stroke.widthProfile.add", &json!({"name": taken})).is_err(), "{taken}");
|
|
}
|
|
assert!(s.execute("stroke.widthProfile.add", &json!({"name": " "})).is_err());
|
|
s.execute("stroke.widthProfile.add", &json!({"name": "Mine"})).unwrap();
|
|
for b in ["lens", "Lens", "uniform", "wave"] {
|
|
let e = s.execute("stroke.widthProfile.delete", &json!({"name": b})).unwrap_err().to_string();
|
|
assert!(e.contains("built in"), "{b}: {e}");
|
|
}
|
|
// With a built-in profile selected, Delete has nothing to delete.
|
|
s.execute("stroke.set", &json!({"profile": "pinch"})).unwrap();
|
|
assert!(s.execute("stroke.widthProfile.delete", &json!({})).is_err());
|
|
assert!(s.execute("stroke.widthProfile.delete", &json!({"name": "Other"})).is_err());
|
|
assert_eq!(s.next_profile_name(), "Width Profile 1");
|
|
assert_eq!(s.execute("stroke.widthProfile.delete", &json!({"name": "Mine"})).unwrap()["deleted"], "Mine");
|
|
}
|
|
|
|
#[test]
|
|
fn saved_profiles_round_trip_with_the_preferences() {
|
|
let (mut s, id) = session_with_line();
|
|
set_profile(&mut s, id, custom());
|
|
s.execute("stroke.widthProfile.add", &json!({"name": "Mine"})).unwrap();
|
|
let json = s.prefs.to_json();
|
|
assert_eq!(json["widthProfiles"][0]["name"], "Mine");
|
|
let back: Prefs = serde_json::from_value(json).unwrap();
|
|
assert_eq!(back, s.prefs);
|
|
// Without saved profiles nothing is written, and older preference files load with none.
|
|
assert!(Prefs::default().to_json().get("widthProfiles").is_none());
|
|
let old: Prefs = serde_json::from_value(json!({"keyboardIncrement": 2})).unwrap();
|
|
assert!(old.width_profiles.is_empty());
|
|
// Setting and resetting other preferences keeps them.
|
|
s.execute("prefs.set", &json!({"key": "keyboardIncrement", "value": 3})).unwrap();
|
|
s.execute("prefs.reset", &json!({})).unwrap();
|
|
assert_eq!(s.prefs.width_profiles.len(), 1);
|
|
// A restarted session (preferences applied from the saved file) lists them again.
|
|
let mut fresh = Session::new();
|
|
fresh.apply_prefs(back);
|
|
assert!(fresh.resolve_profile("Mine").is_some_and(|p| p == Some(custom())));
|
|
}
|
|
|
|
#[test]
|
|
fn every_built_in_applies_and_the_params_doc_names_them() {
|
|
let (mut s, id) = session_with_line();
|
|
let spec = command_specs().iter().find(|c| c.id == "stroke.set").unwrap();
|
|
for p in WidthProfile::PRESETS {
|
|
assert!(spec.params.contains(&format!("\"{}\"", p.id)), "{}", p.id);
|
|
s.execute("stroke.set", &json!({"profile": p.id})).unwrap();
|
|
assert_eq!(s.execute("stroke.widthProfile.list", &json!({})).unwrap()["current"], p.id);
|
|
assert_eq!(profile(&s, id).map(|w| w.points), (p.id != "uniform").then(|| p.points.to_vec()));
|
|
}
|
|
}
|