Sketch text: live Edit Text from the Text tool, a font list, handles, and whole-text picking (#821)
Live Edit Text from the Text tool, a font list, handles, and picking the whole text.
This commit is contained in:
1 parent
69050c81d9
commit
ec5d7f9789
17 files changed
+1248
-80
No files matched your search
@@ -89,8 +89,9 @@ pub static COMMANDS: &[CommandSpec] = &[
|
||||
)),
|
||||
CommandSpec::new("sketch.edit_text", "Edit Text", edit_text)
|
||||
.enabled(in_sketch)
|
||||
.live(edit_text_live)
|
||||
.params(concat!(
|
||||
"link: the text's link id (or entity: one of its curves), text?, at?, height?, angle?: deg, font?: family or file path (empty: the built-in font)",
|
||||
"link: the text's link id (or entity: one of its curves), text?, at?, height?, angle?: deg, font?: family or file path (empty: the built-in font); live?: bool: one frame of an interactive resize or rotation (the sketch is solved, the features after it stay as they were until `sketch.drag_end`)",
|
||||
created_doc!()
|
||||
)),
|
||||
];
|
||||
@@ -175,6 +176,25 @@ fn edit_text(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
let id = s.active_sketch.ok_or_else(|| bad(cmd, "no sketch is being edited"))?;
|
||||
let mut doc = (*s.doc).clone();
|
||||
let mut sk = doc.sketch(id)?.clone();
|
||||
let out = apply_text_edit(&mut sk, p)?;
|
||||
*doc.sketch_mut(id)? = sk;
|
||||
*s.doc_mut() = doc;
|
||||
Ok(out)
|
||||
}
|
||||
|
||||
/// One frame of an interactive resize or rotation: the sketch is solved and shown, the
|
||||
/// features after it keep their last result until `sketch.drag_end` evaluates the model.
|
||||
fn edit_text_live(s: &mut Session, p: &Value) -> Result<(Value, Option<crate::LiveEdit>)> {
|
||||
let cmd = "sketch.edit_text";
|
||||
let id = s.active_sketch.ok_or_else(|| bad(cmd, "no sketch is being edited"))?;
|
||||
let (out, report) = super::sketch::edit_solved(s, id, false, |sk, _| Ok((apply_text_edit(sk, p)?, super::sketch::Left::Unsolved)))?;
|
||||
Ok((out, report.map(|report| crate::LiveEdit { sketch: id, report })))
|
||||
}
|
||||
|
||||
/// `sketch.edit_text`'s change to its sketch: the text source's new values and its outlines
|
||||
/// regenerated from them.
|
||||
fn apply_text_edit(sk: &mut Sketch, p: &Value) -> Result<Value> {
|
||||
let cmd = "sketch.edit_text";
|
||||
let l = match (str_(p, "link"), str_(p, "entity")) {
|
||||
(Some(l), _) => l.to_string(),
|
||||
(None, Some(e)) => sk.curves.iter().find(|c| c.id == e).and_then(|c| c.link.clone()).ok_or_else(|| bad(cmd, format!("`{e}` is not text")))?,
|
||||
@@ -189,9 +209,7 @@ fn edit_text(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
let geom = solidcraft_sketch::text_geometry_in(font.as_deref(), text, *at, *height, *angle)?;
|
||||
sk.set_link_source(&l, src.clone());
|
||||
sk.update_link(&l, &geom)?;
|
||||
let curves = ids_of(&sk, &sk.link_curves(&l));
|
||||
*doc.sketch_mut(id)? = sk;
|
||||
*s.doc_mut() = doc;
|
||||
let curves = ids_of(sk, &sk.link_curves(&l));
|
||||
Ok(json!({"link": l, "curves": curves}))
|
||||
}
|
||||
|
||||
|
||||
@@ -69,6 +69,50 @@ fn text_drag_moves_all_letters_and_rebuilds_downstream_on_release() {
|
||||
assert!(matches!(sk.link(&link).unwrap().source, LinkSource::Text { .. }));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn live_text_resize_follows_each_frame_and_rebuilds_downstream_on_release() {
|
||||
let (mut s, link, _) = setup();
|
||||
run(&mut s, "sketch.finish", json!({}));
|
||||
run(&mut s, "solid.extrude", json!({"distance":3}));
|
||||
run(&mut s, "sketch.edit", json!({"sketch":1}));
|
||||
let curves = s.doc.sketch(1).unwrap().link_curves(&link).len();
|
||||
for (frame, height) in [12.0, 16.0, 20.0].into_iter().enumerate() {
|
||||
let at = Vec2::new(3.0 - frame as f64, 4.0);
|
||||
run(&mut s, "sketch.edit_text", json!({"link":link,"at":[at.x,at.y],"height":height,"angle":-10,"live":true}));
|
||||
assert!(s.model.is_stale(), "a live frame leaves the features after the sketch as they were");
|
||||
let sk = s.doc.sketch(1).unwrap();
|
||||
let LinkSource::Text { at: now, height: h, angle, text, .. } = &sk.link(&link).unwrap().source else { panic!() };
|
||||
assert!(now.dist(at) < 1e-12);
|
||||
assert_eq!((*h, text.as_str()), (height, "II"));
|
||||
assert!((*angle + 10_f64.to_radians()).abs() < 1e-12);
|
||||
// Same letters, same outlines: what references them keeps working.
|
||||
assert_eq!(sk.link_curves(&link).len(), curves);
|
||||
let shown = s.world_state();
|
||||
assert!(shown.sketch(1).unwrap().profiles.len() >= 2, "the sketch shown follows the frame");
|
||||
}
|
||||
run(&mut s, "sketch.drag_end", json!({}));
|
||||
assert!(!s.model.is_stale());
|
||||
assert!(s.model.result(2).is_some_and(|r| r.error.is_none()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn live_text_edit_rejects_bad_values_and_leaves_the_design_as_it_was() {
|
||||
let (mut s, link, _) = setup();
|
||||
let before = s.doc.to_json();
|
||||
let undo = s.undo.len();
|
||||
for params in [
|
||||
json!({"link":link,"height":0,"live":true}),
|
||||
json!({"link":link,"height":-3,"live":true}),
|
||||
json!({"link":link,"height":2e5,"live":true}),
|
||||
json!({"link":"no-such-link","height":5,"live":true}),
|
||||
json!({"height":5,"live":true}),
|
||||
] {
|
||||
assert!(s.execute("sketch.edit_text", ¶ms).is_err(), "{params}");
|
||||
assert_eq!(s.doc.to_json(), before);
|
||||
assert_eq!(s.undo.len(), undo);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn text_move_in_a_rotated_component_preserves_font_and_local_coordinates() {
|
||||
let mut s = Session::default();
|
||||
|
||||
Reference in new issue
Block a user