From 2aa93ea807aa7b0da2c95c342d6270ee4981c4e0 Mon Sep 17 00:00:00 2001 From: dotCooCoo Date: Thu, 12 Nov 2026 11:10:13 -0800 Subject: [PATCH] Text: Text Frame Options from the Type tool show the inset of the frames they change The one Inset field of a non-rectangular frame and the inset link now follow the frames the dialog changes: the frame that shows the caret, or every frame holding selected text. Before, a caret with no stored frame showed four sides for an oval frame, and the link checked every frame of the story. --- crates/ui-egui/src/dialogs.rs | 47 ++++++++++++++++++----------- crates/ui-egui/src/dialogs/tests.rs | 6 ++-- 2 files changed, 33 insertions(+), 20 deletions(-) diff --git a/crates/ui-egui/src/dialogs.rs b/crates/ui-egui/src/dialogs.rs index ab53948f9..7b5ff93ca 100644 --- a/crates/ui-egui/src/dialogs.rs +++ b/crates/ui-egui/src/dialogs.rs @@ -589,6 +589,30 @@ mod color_theme_tests { assert_eq!((inset(&app, a), inset(&app, b)), (24.0, 0.0)); } + /// With the caret in the oval second frame of a thread whose first frame has uneven insets, the + /// dialog shows the one Inset of the oval and keeps the link on: both follow the frame it changes. + #[test] + fn text_frame_options_from_the_type_tool_show_the_inset_of_the_caret_frame() { + let mut app = crate::DesignApp::new(designcraft_engine::Session::new(), crate::Services::default()); + app.run("file.new", json!({})).unwrap(); + let text = (0..40).map(|i| format!("Line {i} of the threaded story.")).collect::>().join(" "); + let a = app.run("frame.create", json!({"rect": [36, 36, 300, 120], "content": "text", "text": text})).unwrap(); + let b = + app.run("frame.create", json!({"rect": [36, 200, 300, 700], "shape": "ellipse", "content": "text", "text": "", "caret": false})).unwrap(); + let (a, b, sid) = + (designcraft_doc::ItemId(a["id"].as_u64().unwrap()), designcraft_doc::ItemId(b["id"].as_u64().unwrap()), a["story"].as_u64().unwrap()); + app.run("text.thread", json!({"from": a.0, "to": b.0})).unwrap(); + app.run("object.textFrameOptions", json!({"ids": [a.0], "inset": [1, 2, 3, 4]})).unwrap(); + let cs = designcraft_compose::compose_story(&app.session.doc().unwrap().doc, designcraft_doc::StoryId(sid), &Default::default()); + let caret = cs.frame(b).unwrap().range.start + 2; + app.run("text.select", json!({"story": sid, "anchor": caret})).unwrap(); + let mut d = Dialog::new("textFrameOptions", json!({})); + seed_text_frame_options(&app, &mut d); + assert_eq!(d.fields.get("_targets"), Some(&json!([b.0]))); + assert!(d.b("_shaped"), "the oval shows one Inset"); + assert!(d.b("insetLinked"), "the first frame's uneven sides do not turn the link off"); + } + /// A preview shown in one document goes when OK is refused, or Cancel is chosen, after another /// document became active. #[test] @@ -1105,25 +1129,17 @@ fn selected_frame_options(app: &DesignApp) -> Option bool { let Some(st) = app.session.active() else { return true; }; - let mut ids = st.selection.items.clone(); - if ids.is_empty() - && let Some(t) = st.selection.text - { - ids = match t.frame { - Some(f) => vec![f], - None => st.doc.story(t.story).map(|s| s.frames.clone()).unwrap_or_default(), - }; - } + let ids = designcraft_engine::cmd::text_frame_options_frames(&app.session); ids.iter().filter_map(|id| st.doc.item(*id)?.text_frame()).all(|t| t.options.inset.iter().all(|v| (v - t.options.inset[0]).abs() < 1e-9)) } -/// Whether every selected text frame (the selected items, or the frame of the caret) has an -/// outline that is not its own axis-aligned rectangle: an oval, a polygon, rounded or other corner +/// Whether every text frame the dialog changes (the selected items, else the frame that shows the +/// caret or every frame holding selected text) has an outline that is not its own axis-aligned rectangle: an oval, a polygon, rounded or other corner /// options. Its text keeps one inset from the outline, as compose sets it; type on a path doesn't. /// A selection that mixes them with rectangular frames shows the four sides, so each frame keeps /// the sides left as shown. @@ -1137,11 +1153,8 @@ fn selected_frame_is_shaped(app: &DesignApp) -> bool { let bp = if item.corners.is_none() { item.path.to_bezpath() } else { designcraft_geom::corners::apply(&item.path, &item.corners) }; Some(designcraft_compose::FrameOutline::new(&bp, 0.0).is_some()) }; - if st.selection.items.is_empty() { - let Some(fid) = st.selection.text.and_then(|t| t.frame) else { return false }; - return shaped(fid).unwrap_or(false); - } - st.selection.items.iter().all(|&fid| shaped(fid).unwrap_or(false)) + let ids = designcraft_engine::cmd::text_frame_options_frames(&app.session); + !ids.is_empty() && ids.iter().all(|&fid| shaped(fid).unwrap_or(false)) } /// Object › Text Frame Options shows the selected frame's options. `_shown` keeps what it showed, diff --git a/crates/ui-egui/src/dialogs/tests.rs b/crates/ui-egui/src/dialogs/tests.rs index 9f66fb4f8..0bad3ce0e 100644 --- a/crates/ui-egui/src/dialogs/tests.rs +++ b/crates/ui-egui/src/dialogs/tests.rs @@ -1831,15 +1831,15 @@ fn text_frame_options_link_the_insets_only_when_every_selected_frame_has_even_si assert!(!seeded(&app).b("insetLinked")); app.run("text.select", json!({"story": other_even_story, "anchor": 0, "focus": 0})).unwrap(); assert!(seeded(&app).b("insetLinked")); - // A story threaded on into the uneven frame: a caret placed in the first frame changes only - // that frame, and a caret with no frame changes every frame of the story. + // A story threaded on into the uneven frame: a caret placed in the first frame, or a caret + // with no stored frame drawn in the first frame, changes only that frame. let (first, story) = frame(&mut app, [300, 300, 500, 500], json!(6)); let (last, _) = frame(&mut app, [300, 520, 500, 700], json!([1, 2, 3, 4])); app.run("text.thread", json!({"from": first.0, "to": last.0})).unwrap(); app.run("text.placeCaret", json!({"frame": first.0, "point": [310, 310]})).unwrap(); assert!(seeded(&app).b("insetLinked")); app.run("text.select", json!({"story": story, "anchor": 0, "focus": 0})).unwrap(); - assert!(!seeded(&app).b("insetLinked")); + assert!(seeded(&app).b("insetLinked")); } #[test]