mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-11-11 02:19:56 -06:00
Type: a selected story's highlight and typing follow its current thread
With a whole story selected, the highlight and typing read the frames saved at Select All, while a pick, a fill and the other story-wide edits read the document's thread. A frame added by text.thread.add took a pick without being highlighted, and typing left its text in place. Both now read the thread through Document::story_of. A frame threaded in during the selection is highlighted and takes the edit, and a frame that leaves the thread is not highlighted and keeps its text.
This commit is contained in:
@@ -1726,6 +1726,58 @@ fn an_eyedropper_pick_from_a_frame_of_the_selected_story_styles_the_other_frames
|
||||
}
|
||||
}
|
||||
|
||||
/// Whether a character in the frame that `text.thread.add` places at [`THREADED_AT`] is highlighted.
|
||||
fn third_frame_highlighted(s: &mut Session) -> bool {
|
||||
let in_frame = |o: &vectorcraft_tools::Overlay| matches!(o, vectorcraft_tools::Overlay::Highlight { quad, color: [60, 120, 255, 90] } if quad.iter().all(|p| p.x > 690.0));
|
||||
s.overlays(ViewInfo::default()).iter().any(in_frame)
|
||||
}
|
||||
|
||||
/// Where a third frame of the story of [`threaded_story_edited_in_its_second_frame`] goes, clear
|
||||
/// of its other frames.
|
||||
const THREADED_AT: [f64; 2] = [700.0, 10.0];
|
||||
|
||||
#[test]
|
||||
fn a_frame_threaded_into_the_selected_story_is_highlighted_and_takes_the_edit() {
|
||||
for mods in eyedropper_picks() {
|
||||
let (mut s, a, b, _) = threaded_story_edited_in_its_second_frame();
|
||||
red_source(&mut s, b);
|
||||
s.execute("select.all", &json!({})).unwrap();
|
||||
s.select_tool("eyedropper", ViewInfo::default()).unwrap();
|
||||
let c = s.execute("text.thread.add", &json!({"from": a.0, "before": true, "at": THREADED_AT})).unwrap()["id"].as_u64().unwrap();
|
||||
let c = NodeId(c);
|
||||
assert!(!obj(&s, c).plain_text().is_empty(), "the story starts in the new frame");
|
||||
assert!(third_frame_highlighted(&mut s), "the new frame's text is highlighted");
|
||||
pick_red_source(&mut s, mods);
|
||||
assert!(all_red(&s, a) && all_red(&s, b) && all_red(&s, c), "every frame takes the pick");
|
||||
s.select_tool("type", ViewInfo::default()).unwrap();
|
||||
assert!(third_frame_highlighted(&mut s), "back in the Type tool too");
|
||||
s.tool_text("x", ViewInfo::default()).unwrap();
|
||||
let text = [a, b, c].map(|f| obj(&s, f).plain_text()).concat();
|
||||
assert_eq!(text, "x", "typing replaces the text of every frame");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_frame_that_left_the_selected_story_is_not_highlighted_and_leaves_the_edit() {
|
||||
for mods in eyedropper_picks() {
|
||||
let (mut s, a, b, _) = threaded_story_edited_in_its_second_frame();
|
||||
let c = s.execute("text.thread.add", &json!({"from": b.0, "at": THREADED_AT})).unwrap()["id"].as_u64().unwrap();
|
||||
let c = NodeId(c);
|
||||
red_source(&mut s, b);
|
||||
assert_eq!(s.execute("select.all", &json!({})).unwrap()["story"], json!([a.0, b.0, c.0]));
|
||||
s.select_tool("eyedropper", ViewInfo::default()).unwrap();
|
||||
s.execute("text.thread.break", &json!({"id": b.0, "port": "out"})).unwrap();
|
||||
s.execute("text.setText", &json!({"id": c.0, "text": "Kept"})).unwrap();
|
||||
assert!(!third_frame_highlighted(&mut s), "the frame that left has no highlight");
|
||||
pick_red_source(&mut s, mods);
|
||||
assert!(all_red(&s, a) && all_red(&s, b), "the frames still threaded take the pick");
|
||||
assert!(!any_red(&s, c), "the frame that left stays as it is");
|
||||
s.select_tool("type", ViewInfo::default()).unwrap();
|
||||
s.tool_text("x", ViewInfo::default()).unwrap();
|
||||
assert_eq!(obj(&s, c).plain_text(), "Kept", "typing leaves the frame that left");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn typing_after_a_thread_is_removed_leaves_the_frame_that_left_the_story() {
|
||||
for through_eyedropper in [false, true] {
|
||||
|
||||
@@ -174,9 +174,10 @@ pub struct TypeTool {
|
||||
/// Caret and selection anchor (byte offsets into the plain text).
|
||||
caret: usize,
|
||||
anchor: usize,
|
||||
/// After Select All in a frame of threaded text: the story's frames in thread order, the edited
|
||||
/// one included. While the selection is the edited frame's whole text, the text of the other
|
||||
/// frames is selected too. Empty otherwise.
|
||||
/// After Select All in a frame of threaded text: the story's frames in thread order then, the
|
||||
/// edited one included. While the selection is the edited frame's whole text and this holds the
|
||||
/// edited frame, the text of the other frames of its current thread is selected too. Empty
|
||||
/// otherwise.
|
||||
story: Vec<NodeId>,
|
||||
/// Horizontal position kept by Up/Down (text space).
|
||||
goal_x: Option<f64>,
|
||||
@@ -441,8 +442,8 @@ impl TypeTool {
|
||||
}
|
||||
|
||||
/// The other frames whose whole text is selected along with the edited frame's after Select
|
||||
/// All in threaded text ([`Self::story`]) that are still threaded with it: none once the
|
||||
/// selection is not all of the edited frame's text.
|
||||
/// All in threaded text ([`Self::story`]), read from the document's current thread: none once
|
||||
/// the selection is not all of the edited frame's text.
|
||||
fn story_others(&self, cx: &ToolContext) -> Vec<NodeId> {
|
||||
let Some(id) = self.editing.filter(|id| self.story.contains(id)) else { return vec![] };
|
||||
let Some(t) = Self::text(cx, id) else { return vec![] };
|
||||
@@ -450,8 +451,7 @@ impl TypeTool {
|
||||
if a != 0 || b < edit::runs_len(&t.runs) {
|
||||
return vec![];
|
||||
}
|
||||
let Some(thread) = cx.doc.text_threads.iter().find(|t| t.contains(&id)) else { return vec![] };
|
||||
self.story.iter().copied().filter(|f| *f != id && thread.contains(f) && Self::text(cx, *f).is_some()).collect()
|
||||
cx.doc.story_of(id).into_iter().filter(|f| *f != id).collect()
|
||||
}
|
||||
|
||||
/// Move the caret (Shift keeps the anchor). Moving ends the typing session.
|
||||
|
||||
+4
-2
@@ -5017,8 +5017,10 @@ every frame (the Character panel sends `text.setRangeStyle {id, story: true}`, w
|
||||
undo step). Remove Line Breaks, Remove Paragraph Breaks and Convert to Text also act on the text of every frame, as
|
||||
one undo step. A caret move or a click ends the story selection. Switching to the Eyedropper keeps the story selected,
|
||||
and switching back to a Type tool restores it. `appearance.copyFrom` and `paint.sampleColor` then apply to the text of
|
||||
every frame as one undo step, and `appearance.copyFrom` leaves out a frame that is its `source`. Once a thread is
|
||||
removed, typing replaces the text of the edited frame only.
|
||||
every frame as one undo step, and `appearance.copyFrom` leaves out a frame that is its `source`. While the story stays
|
||||
selected, its frames are the ones threaded with the edited frame at that moment: a frame that `text.thread.add` adds is
|
||||
highlighted and takes the edit, and a frame that leaves the thread is not highlighted and keeps its text. Once a thread
|
||||
is removed, typing replaces the text of the edited frame only.
|
||||
|
||||
```json
|
||||
{"name":"press_key","arguments":{"key":"A","mods":{"cmd":true}}}
|
||||
|
||||
Reference in New Issue
Block a user