mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-10-26 18:56:55 -06:00
Direct Selection: an anchor of type on a path's path drags and the type reflows along it (part of #3980)
This commit is contained in:
@@ -97,6 +97,15 @@ pub fn specs() -> Vec<CommandSpec> {
|
||||
has_doc,
|
||||
reshape_area
|
||||
),
|
||||
cmd!(
|
||||
"text.reshapePath",
|
||||
"Reshape Type Path",
|
||||
[],
|
||||
None,
|
||||
"{id, anchors: [[subpath, anchor]…], dx, dy} move anchors of type on a path's path by dx, dy points, with their handles; the start and end brackets keep their fractions of the path and the text reflows along it (what Direct Selection does dragging an anchor of the path)",
|
||||
has_doc,
|
||||
reshape_path
|
||||
),
|
||||
cmd!(
|
||||
"text.fitHeadline",
|
||||
"Fit Headline",
|
||||
@@ -1066,6 +1075,37 @@ fn reshape_area(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
ok()
|
||||
}
|
||||
|
||||
fn reshape_path(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
const C: &str = "text.reshapePath";
|
||||
let id = id_param(p, "id").ok_or_else(|| bad(C, "missing id"))?;
|
||||
let mut refs = super::select::checked_refs(p.get("anchors"), C)?;
|
||||
refs.sort_unstable();
|
||||
refs.dedup();
|
||||
if refs.is_empty() {
|
||||
return Err(bad(C, "missing anchors [[subpath, anchor]…]"));
|
||||
}
|
||||
let d = Vec2::new(f64_req(p, "dx", C)?, f64_req(p, "dy", C)?);
|
||||
let node = s.doc()?.doc.node(id);
|
||||
let on_path = matches!(node.map(|n| &n.kind), Some(NodeKind::Text(t)) if matches!(t.kind, vectorcraft_doc::TextKind::OnPath { .. }));
|
||||
if !on_path || node.is_some_and(|n| n.extras().perspective.is_some()) {
|
||||
return Err(bad(C, "not type on a path outside perspective"));
|
||||
}
|
||||
s.edit("Reshape Type Path", |doc, _| {
|
||||
let Some(NodeKind::Text(t)) = doc.node_mut(id).map(|n| &mut n.kind) else { return Err(EngineError::NoNode(id)) };
|
||||
let before = t.kind.clone();
|
||||
let within = |t: &TextObject| {
|
||||
t.type_path().and_then(|p| p.bounds()).is_some_and(|b| [b.x0, b.y0, b.x1, b.y1].iter().all(|v| v.abs() <= crate::MAX_COORD))
|
||||
};
|
||||
if !(t.move_path_anchors(&refs, d) && within(t)) {
|
||||
t.kind = before;
|
||||
return Err(bad(C, "no such path anchor, or the path would reach past the canvas"));
|
||||
}
|
||||
refresh_bounds(t);
|
||||
Ok(())
|
||||
})?;
|
||||
ok()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod area_tests {
|
||||
use super::*;
|
||||
|
||||
@@ -252,3 +252,28 @@ fn dragging_the_start_and_end_brackets_sets_where_the_type_runs() {
|
||||
let r = ink(&s, id);
|
||||
assert!(r.x0 > 199.0 && r.x1 < 401.0, "between the brackets: {r:?}");
|
||||
}
|
||||
|
||||
/// #3980: Direct Selection drags an anchor of type on a path's path, in document space through
|
||||
/// the type's transform; the brackets keep their fractions of the path and the type reflows.
|
||||
#[test]
|
||||
fn direct_selection_drags_an_anchor_of_the_path_and_the_type_reflows() {
|
||||
let mut s = session();
|
||||
let id = path_type(&mut s, "M100 300 L300 300 L500 300", [100.0, 310.0], "Reflowing type");
|
||||
s.execute("type.pathOptions", &json!({"start": 0.0, "end": 0.75})).unwrap();
|
||||
// A quarter turn, so a document-space move isn't the path's own.
|
||||
s.execute("object.transform", &json!({"ids": [id.0], "matrix": [0, 1, -1, 0, 600, 0]})).unwrap();
|
||||
s.execute("select.none", &json!({})).unwrap();
|
||||
let path = |s: &Session| text(s, id).type_path().unwrap();
|
||||
let (first, middle) = (path(&s).subpaths[0].anchors[0].p, path(&s).subpaths[0].anchors[1].p);
|
||||
let (glyphs, n) = (origins(&s, id), undo_len(&s));
|
||||
drag(&mut s, "directSelection", middle, middle + vectorcraft_geom::Vec2::new(40.0, 0.0), Mods::default());
|
||||
let after = path(&s);
|
||||
let moved = after.subpaths[0].anchors[1].p;
|
||||
assert!(near(moved.x, middle.x + 40.0) && near(moved.y, middle.y), "{middle:?} → {moved:?}");
|
||||
assert_eq!(after.subpaths[0].anchors[0].p, first, "the other anchors stay");
|
||||
assert_eq!(brackets(&s, id), (0.0, Some(0.75)));
|
||||
assert_ne!(origins(&s, id), glyphs, "the type reflows along the reshaped path");
|
||||
assert_eq!((undo_len(&s), last_step(&s).as_str()), (n + 1, "Reshape Type Path"));
|
||||
s.execute("edit.undo", &json!({})).unwrap();
|
||||
assert_eq!(path(&s).subpaths[0].anchors[1].p, middle);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user