Text Frame Options: link the insets only when every selected frame has even sides

The dialog turned the inset link on when the first selected frame had the
same inset on all four sides. With a second frame whose sides differ, typing
one side then set all four sides of every selected frame, so the second
frame lost its other insets.

The link is now on only when every frame the dialog changes has even sides:
the selected items, else the caret's frame, else every frame of the caret's
story.
This commit is contained in:
dotCooCoo committed 2026-11-12 08:07:12 -08:00
1 parent 92043b4c48
commit 20319e991e
2 files changed
+71 -1

No files matched your search

+20 -1
View File
@@ -1005,6 +1005,24 @@ fn selected_frame_options(app: &DesignApp) -> Option<designcraft_doc::TextFrameO
st.doc.item(fid)?.text_frame().map(|t| t.options.clone())
}
/// True when every text frame the dialog changes has the same inset on all four sides: the
/// selected items, else the caret's frame, else every frame of the caret's story.
fn selected_frames_have_even_insets(app: &DesignApp) -> 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(),
};
}
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))
}
/// Object › Text Frame Options shows the selected frame's options. `_shown` keeps what it showed,
/// so OK sends only the fields the user changed. Fields given when the dialog opened count as
/// changed.
@@ -1056,7 +1074,8 @@ fn seed_text_frame_options(app: &DesignApp, d: &mut Dialog) {
d.fields.entry(k.clone()).or_insert_with(|| v.clone());
}
}
d.fields.entry("insetLinked".to_string()).or_insert_with(|| json!(o.inset.iter().all(|v| (v - o.inset[0]).abs() < 1e-9)));
// Linked only when no selected frame has uneven sides, so a typed side cannot overwrite them.
d.fields.entry("insetLinked".to_string()).or_insert_with(|| json!(selected_frames_have_even_insets(app)));
// The frame's width: Fixed Number shows each column's width from it as the fields change.
if let Some(w) = selected_frame_width(app) {
d.fields.insert("_frameWidth".into(), json!(w));
+51
View File
@@ -1485,6 +1485,57 @@ fn text_frame_options_change_only_what_was_edited() {
assert_eq!(opts(&app), before);
}
#[test]
fn text_frame_options_link_the_insets_only_when_every_selected_frame_has_even_sides() {
let mut app = crate::DesignApp::new(designcraft_engine::Session::new(), crate::Services::default());
app.run("file.new", json!({})).unwrap();
let frame = |app: &mut DesignApp, rect: [u32; 4], inset: Value| {
let r = app.run("frame.create", json!({"rect": rect, "content": "text", "text": "Hello"})).unwrap();
let id = r["id"].as_u64().unwrap();
app.run("object.textFrameOptions", json!({"ids": [id], "inset": inset})).unwrap();
(designcraft_doc::ItemId(id), r["story"].clone())
};
let (even, _) = frame(&mut app, [36, 36, 236, 236], json!(6));
let (uneven, uneven_story) = frame(&mut app, [300, 36, 500, 236], json!([1, 2, 3, 4]));
let (other_even, other_even_story) = frame(&mut app, [36, 300, 236, 500], json!(9));
let inset = |app: &DesignApp, id| app.session.doc().unwrap().doc.item(id).unwrap().text_frame().unwrap().options.inset;
let seeded = |app: &DesignApp| {
let mut d = Dialog::new("textFrameOptions", json!({}));
seed_text_frame_options(app, &mut d);
d
};
// The frame with even sides is selected first: the uneven one still turns the link off.
app.run("selection.set", json!({"ids": [even.0, uneven.0]})).unwrap();
let mut d = seeded(&app);
assert_eq!(d.s("insetTop"), "0p6");
assert!(!d.b("insetLinked"), "a selected frame with uneven sides leaves the link off");
// Top edited while unlinked: only Top changes, and each frame keeps its other sides.
d.fields.insert("insetTop".into(), json!("1p0"));
app.ui.dialog = Some(d);
confirm(&mut app).unwrap();
assert_eq!(inset(&app, even), [12.0, 6.0, 6.0, 6.0]);
assert_eq!(inset(&app, uneven), [12.0, 2.0, 3.0, 4.0]);
// A frame with even sides alone keeps the link on; selected with the uneven one, the link is off.
app.run("selection.set", json!({"ids": [other_even.0]})).unwrap();
assert!(seeded(&app).b("insetLinked"));
app.run("selection.set", json!({"ids": [other_even.0, uneven.0]})).unwrap();
assert!(!seeded(&app).b("insetLinked"));
// A caret: the frames the dialog changes decide.
app.run("text.select", json!({"story": uneven_story, "anchor": 0, "focus": 0})).unwrap();
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.
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"));
}
#[test]
fn toc_dialog_adds_a_style_and_ok_makes_the_table_of_contents() {
let mut app = DesignApp::new(designcraft_engine::Session::new(), crate::Services::default());