mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-11-03 06:30:00 -06:00
Type: selected type shows its in and out ports, and the in port threads backwards
Squashed from #3817 onto main.
This commit is contained in:
@@ -28,7 +28,7 @@ pub fn specs() -> Vec<CommandSpec> {
|
||||
"Thread Text",
|
||||
[],
|
||||
None,
|
||||
"{from, to?, rect?: [x, y, width, height], at?: [x, y]} thread area type or type on a path `from` (a click on its out port) into one more frame, put after it in its thread: closed path, area type or type on a path `to`, a new rectangle `rect`, or a new frame of `from`'s kind, shape and size with its top-left at `at` (give one of the three) → {id}; the two frames are selected",
|
||||
"{from, to?, rect?: [x, y, width, height], at?: [x, y], before?} thread area type or type on a path `from` (a click on its out port) into one more frame, put after it in its thread, or before it with `before: true` (a click on its in port: the story starts there and flows on into `from`): closed path, area type or type on a path `to`, a new rectangle `rect`, or a new frame of `from`'s kind, shape and size with its top-left at `at` (give one of the three) → {id}; the two frames are selected",
|
||||
has_doc,
|
||||
add
|
||||
),
|
||||
@@ -263,11 +263,12 @@ fn create(s: &mut Session, _: &Value) -> Result<Value> {
|
||||
Ok(json!({ "ids": out.iter().map(|i| i.0).collect::<Vec<_>>() }))
|
||||
}
|
||||
|
||||
/// `text.thread.add`: the out port's loaded text, placed.
|
||||
/// `text.thread.add`: the loaded text of a port, placed.
|
||||
fn add(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
const C: &str = "text.thread.add";
|
||||
let given = |k: &str| p.get(k).filter(|v| !v.is_null());
|
||||
let from = checked_id(s, given("from").unwrap_or(&Value::Null), C)?;
|
||||
let before = switch_or(p, "before", false, C)?;
|
||||
let d = &s.doc()?.doc;
|
||||
let src = text_of(d, from).ok_or_else(|| bad(C, "`from` must be area type or type on a path"))?;
|
||||
let style = src.first_style();
|
||||
@@ -330,9 +331,10 @@ fn add(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
};
|
||||
match d.text_threads.iter_mut().find(|t| t.contains(&from)) {
|
||||
Some(t) => {
|
||||
let at = t.iter().position(|x| *x == from).map_or(t.len(), |i| i + 1);
|
||||
let at = t.iter().position(|x| *x == from).map_or(t.len(), |i| if before { i } else { i + 1 });
|
||||
t.insert(at, to);
|
||||
}
|
||||
None if before => d.text_threads.push(vec![to, from]),
|
||||
None => d.text_threads.push(vec![from, to]),
|
||||
}
|
||||
// Force a re-flow of the thread on this edit: the edit leaves existing area type `to`
|
||||
@@ -618,6 +620,35 @@ mod tests {
|
||||
assert_eq!(threads(&s), [[a, c, d]]);
|
||||
}
|
||||
|
||||
/// #2572: the text loaded from an in port threads back: the new frame or the path clicked comes
|
||||
/// before the frame it was loaded from, so the story starts there and flows on into it; in the
|
||||
/// middle of a thread the new frame goes in between, each in one undo step.
|
||||
#[test]
|
||||
fn add_before_threads_the_story_from_the_new_frame_into_the_clicked_one() {
|
||||
let (mut s, a, b) = setup();
|
||||
let c = new_id(s.execute("text.thread.add", &json!({"from": a, "at": [400, 200], "before": true})).unwrap());
|
||||
assert_eq!(threads(&s), [[c, a]]);
|
||||
assert_eq!(frame_box(&s, c), vectorcraft_geom::Rect::new(400.0, 200.0, 550.0, 260.0), "the clicked frame's size at the click");
|
||||
assert!(TEXT.repeat(4).starts_with(&text(&s, c)) && !text(&s, c).is_empty(), "the story starts in the new frame: {:?}", text(&s, c));
|
||||
assert_eq!(format!("{}{}", text(&s, c), text(&s, a)), TEXT.repeat(4));
|
||||
assert_eq!(s.doc().unwrap().selection.objects, [NodeId(a), NodeId(c)]);
|
||||
// In between the frame and the one before it.
|
||||
let d = new_id(s.execute("text.thread.add", &json!({"from": a, "rect": [10, 100, 300, 150], "before": true})).unwrap());
|
||||
assert_eq!(threads(&s), [[c, d, a]]);
|
||||
assert_eq!(format!("{}{}{}", text(&s, c), text(&s, d), text(&s, a)), TEXT.repeat(4));
|
||||
s.execute("edit.undo", &json!({})).unwrap();
|
||||
assert_eq!(threads(&s), [[c, a]]);
|
||||
// A closed path clicked with the in port's text comes first, with the start of the story.
|
||||
s.execute("edit.undo", &json!({})).unwrap();
|
||||
assert_eq!(new_id(s.execute("text.thread.add", &json!({"from": a, "to": b, "before": true})).unwrap()), b);
|
||||
assert_eq!(threads(&s), [[b, a]]);
|
||||
assert!(TEXT.repeat(4).starts_with(&text(&s, b)) && !text(&s, b).is_empty());
|
||||
assert_eq!(format!("{}{}", text(&s, b), text(&s, a)), TEXT.repeat(4));
|
||||
// `before: false` is the out port's.
|
||||
let e = new_id(s.execute("text.thread.add", &json!({"from": a, "at": [0, 0], "before": false})).unwrap());
|
||||
assert_eq!(threads(&s), [[b, a, e]]);
|
||||
}
|
||||
|
||||
/// Type on a path along a line beside the area type of [`setup`].
|
||||
fn type_on_a_line(s: &mut Session) -> u64 {
|
||||
let line = new_id(s.execute("shape.line", &json!({"x1": 10, "y1": 250, "x2": 110, "y2": 250})).unwrap());
|
||||
@@ -692,6 +723,8 @@ mod tests {
|
||||
json!({"from": a, "rect": [0, 0, 10, 0]}),
|
||||
json!({"from": a, "rect": [0, 0, 10]}),
|
||||
json!({"from": a, "rect": "big"}),
|
||||
json!({"from": a, "at": [0, 0], "before": "yes"}),
|
||||
json!({"from": a, "at": [0, 0], "before": 1}),
|
||||
] {
|
||||
assert!(s.execute("text.thread.add", &p).is_err(), "{p} was accepted");
|
||||
assert!(std::sync::Arc::ptr_eq(&s.doc().unwrap().doc, &before), "{p} changed the document");
|
||||
|
||||
@@ -1432,8 +1432,8 @@ pub struct Session {
|
||||
pub(crate) pointer_held: bool,
|
||||
/// A guide being dragged out of a ruler ([`Session::ruler_guide`]).
|
||||
pub(crate) ruler_guide: Option<vectorcraft_tools::rulerguide::NewGuide>,
|
||||
/// Which selected type overflows its frame, for the markers every tool but the Type tools shows.
|
||||
pub(crate) overflow_marks: vectorcraft_tools::text::OverflowMarks,
|
||||
/// Which selected type overflows its frame, for the ports every tool but the Type tools shows.
|
||||
pub(crate) port_marks: vectorcraft_tools::text::PortMarks,
|
||||
/// The last search for the files of missing fonts (`text.findFontFiles`), until another
|
||||
/// starts; dropping it stops it.
|
||||
pub(crate) font_search: Option<cmd::fontfiles::FontSearch>,
|
||||
@@ -1527,7 +1527,7 @@ impl Session {
|
||||
plane_widget_press: false,
|
||||
pointer_held: false,
|
||||
ruler_guide: None,
|
||||
overflow_marks: Default::default(),
|
||||
port_marks: Default::default(),
|
||||
font_search: None,
|
||||
link_search: None,
|
||||
search_rules: None,
|
||||
|
||||
@@ -1216,3 +1216,33 @@ fn clicking_the_out_port_then_the_canvas_threads_the_text_into_a_new_frame() {
|
||||
assert_eq!(format!("{}{}", obj(&s, id).plain_text(), obj(&s, thread[1]).plain_text()), long);
|
||||
assert_eq!(&undo_labels(&s)[steps..], ["Thread Text"]);
|
||||
}
|
||||
|
||||
/// #2572: with the Selection tool, area type whose text fits shows both ports, and a click on its
|
||||
/// in port then on empty canvas makes a new frame the story starts in, flowing on into the frame
|
||||
/// clicked, in one undo step; the ports then show the thread's arrows.
|
||||
#[test]
|
||||
fn clicking_the_in_port_then_the_canvas_threads_a_new_frame_before_it() {
|
||||
let mut s = session();
|
||||
let v = ViewInfo::default();
|
||||
let words = "Words that fit. ";
|
||||
let r = s.execute("text.create", &json!({"x": 50, "y": 50, "text": words, "size": 12, "area": {"width": 100, "height": 40}})).unwrap();
|
||||
let id = NodeId(r["id"].as_u64().unwrap());
|
||||
s.execute("select.set", &json!({"ids": [id.0]})).unwrap();
|
||||
s.select_tool("selection", v).unwrap();
|
||||
let boxes = |s: &mut Session| {
|
||||
s.overlays(v).iter().filter(|o| matches!(o, vectorcraft_tools::Overlay::Highlight { color: [255, 255, 255, 255], .. })).count()
|
||||
};
|
||||
assert_eq!(boxes(&mut s), 2, "an in port and an out port");
|
||||
let steps = undo_labels(&s).len();
|
||||
click(&mut s, 50.0, 50.0 + vectorcraft_tools::text::PORT_RISE_PX);
|
||||
assert_eq!(s.cursor(Point::new(400.0, 400.0), Mods::default(), v), vectorcraft_tools::Cursor::LoadedText);
|
||||
click(&mut s, 400.0, 400.0);
|
||||
let doc = &s.doc().unwrap().doc;
|
||||
let [thread] = &doc.text_threads[..] else { panic!("one thread: {:?}", doc.text_threads) };
|
||||
assert_eq!(thread[1], id, "the frame clicked comes second");
|
||||
assert_eq!(format!("{}{}", obj(&s, thread[0]).plain_text(), obj(&s, id).plain_text()), words);
|
||||
assert!(!obj(&s, thread[0]).plain_text().is_empty(), "the story starts in the new frame");
|
||||
assert_eq!(&undo_labels(&s)[steps..], ["Thread Text"]);
|
||||
let strokes = s.overlays(v).iter().filter(|o| matches!(o, vectorcraft_tools::Overlay::Line { .. })).count();
|
||||
assert_eq!(strokes, 6, "both frames selected: the first one's out port and the second one's in port show arrows");
|
||||
}
|
||||
|
||||
@@ -473,11 +473,11 @@ impl Session {
|
||||
|
||||
pub fn overlays(&mut self, view: ViewInfo) -> Vec<Overlay> {
|
||||
let mut v = self.with_tool_cx(view, |t, cx| t.overlays(cx));
|
||||
// The Type tools draw the overflow markers of selected type themselves.
|
||||
// The Type tools draw the ports of selected type themselves.
|
||||
if !matches!(self.tool.id(), "type" | "areaType" | "typeOnPath" | "verticalType" | "verticalAreaType" | "verticalTypeOnPath") {
|
||||
let mut marks = std::mem::take(&mut self.overflow_marks);
|
||||
v.extend(self.with_tool_cx(view, |_, cx| vectorcraft_tools::text::overflow_markers(&mut marks, cx)));
|
||||
self.overflow_marks = marks;
|
||||
let mut marks = std::mem::take(&mut self.port_marks);
|
||||
v.extend(self.with_tool_cx(view, |_, cx| vectorcraft_tools::text::thread_ports(&mut marks, cx)));
|
||||
self.port_marks = marks;
|
||||
}
|
||||
if let Some(g) = self.ruler_guide.take() {
|
||||
v.extend(self.with_tool_cx(view, |_, cx| g.overlays(cx)));
|
||||
|
||||
Reference in New Issue
Block a user