From 29ee256c8698b59e8d82fb0efc68aff31f29c3ea Mon Sep 17 00:00:00 2001 From: alexanderalber <90604107+alexanderalber@users.noreply.github.com> Date: Mon, 12 Oct 2026 04:30:57 +0200 Subject: [PATCH] Paragraph dialog: OK keeps Exactly and At Least line spacing (#467) (#564) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The dialog shows line spacing as a multiple, so Exactly and At Least read as 1.0, and OK (and Tabs…) always sent that back as Multiple 1.0. Accepting the dialog unchanged, or changing only Keep with next, turned a fixed rule into single spacing. The dialog now remembers the value it showed and sends line spacing only when it changed, as it already does for Asian Typography. A changed value still sets a multiple. generated with Claude Code Co-authored-by: Brandon Thomas --- crates/ui-egui/src/dialogs.rs | 108 +++++++++++++++++++++++++++++++--- 1 file changed, 101 insertions(+), 7 deletions(-) diff --git a/crates/ui-egui/src/dialogs.rs b/crates/ui-egui/src/dialogs.rs index 7a76d260d..09764390a 100644 --- a/crates/ui-egui/src/dialogs.rs +++ b/crates/ui-egui/src/dialogs.rs @@ -159,7 +159,12 @@ pub enum Dialog { first: f32, before: f32, after: f32, + /// Line spacing as a multiple; Exactly and At Least show as 1.0. line: f32, + /// `line` when the dialog opened: OK keeps the paragraph's spacing, whatever its rule, + /// unless this changed (#467). + #[serde(skip)] + line_was: f32, keep_next: bool, keep_lines: bool, page_break: bool, @@ -969,6 +974,7 @@ impl Dialog { before: rp.space_before, after: rp.space_after, line, + line_was: line, keep_next: rp.keep_next, keep_lines: rp.keep_lines, page_break: rp.page_break_before, @@ -1503,6 +1509,7 @@ fn body(app: &mut WordApp, ui: &mut Ui, d: &mut Dialog) -> bool { before, after, line, + line_was, keep_next, keep_lines, page_break, @@ -1620,7 +1627,7 @@ fn body(app: &mut WordApp, ui: &mut Ui, d: &mut Dialog) -> bool { *first, *before, *after, - *line, + (*line, *line_was), [*keep_next, *keep_lines, *page_break, *widow], (*opts, *opts_was), ); @@ -2762,11 +2769,12 @@ fn apply_paragraph( first: f32, before: f32, after: f32, - line: f32, + line: (f32, f32), flags: [bool; 4], opts: ([bool; 4], [bool; 4]), ) { let [keep_next, keep_lines, page_break, widow] = flags; + let (line, line_was) = line; // Direction first: the alignment and indents are as seen on the page, and their logical // values depend on it. let _ = app.run(if rtl { "para.rtl" } else { "para.ltr" }, json!({})); @@ -2774,9 +2782,15 @@ fn apply_paragraph( let (start, end) = if rtl { (right, left) } else { (left, right) }; let mut props = json!({ "indentLeft": start * crate::options::unit().pt_per_unit(), "indentRight": end * crate::options::unit().pt_per_unit(), "indentFirst": first * crate::options::unit().pt_per_unit(), - "spaceBefore": before, "spaceAfter": after, "lineSpacing": {"rule": "multiple", "value": line}, + "spaceBefore": before, "spaceAfter": after, "keepNext": keep_next, "keepLines": keep_lines, "pageBreakBefore": page_break, "widowControl": widow, }); + // The dialog edits only multiples: an untouched value keeps Exactly and At Least. + if line != line_was + && let Some(m) = props.as_object_mut() + { + m.insert("lineSpacing".into(), json!({"rule": "multiple", "value": line})); + } let (now, was) = opts; for (key, (on, was)) in ["mirrorIndents", "contextualSpacing", "suppressLineNumbers", "suppressHyphens"].into_iter().zip(now.into_iter().zip(was)) { @@ -3135,14 +3149,40 @@ mod tests { /// Open the Paragraph dialog, change its options (`opts`) with `edit` and press OK. fn ok_with(app: &mut WordApp, edit: impl FnOnce(&mut [bool; 4])) { let Some(Dialog::Paragraph { - rtl, align, left, right, first, before, after, line, keep_next, keep_lines, page_break, widow, mut opts, .. + rtl, + align, + left, + right, + first, + before, + after, + line, + line_was, + keep_next, + keep_lines, + page_break, + widow, + mut opts, + .. }) = Dialog::open("paragraph", app) else { panic!("no paragraph dialog") }; let was = opts; edit(&mut opts); - apply_paragraph(app, rtl, &align, left, right, first, before, after, line, [keep_next, keep_lines, page_break, widow], (opts, was)); + apply_paragraph( + app, + rtl, + &align, + left, + right, + first, + before, + after, + (line, line_was), + [keep_next, keep_lines, page_break, widow], + (opts, was), + ); } #[test] @@ -3191,6 +3231,60 @@ mod tests { } } + #[test] + fn paragraph_dialog_keeps_fixed_line_spacing() { + use wordcraft_doc::props::LineSpacing; + for (cmd, rule) in [(json!({"exactly": 24}), LineSpacing::Exactly(24.0)), (json!({"atLeast": 24}), LineSpacing::AtLeast(24.0))] { + // OK without changes, and OK after changing only Keep with next (#467). + let mut app = app_with("Example"); + let _ = app.run("para.lineSpacing", cmd.clone()); + ok_unchanged(&mut app); + assert_eq!(props(&app).line_spacing, Some(rule), "unchanged OK, {cmd}"); + let Some(Dialog::Paragraph { rtl, align, left, right, first, before, after, line, line_was, keep_lines, page_break, widow, .. }) = + Dialog::open("paragraph", &mut app) + else { + panic!("no paragraph dialog") + }; + apply_paragraph( + &mut app, + rtl, + &align, + left, + right, + first, + before, + after, + (line, line_was), + [true, keep_lines, page_break, widow], + ([false; 4], [false; 4]), + ); + let p = props(&app); + assert_eq!((p.line_spacing, p.keep_next), (Some(rule), Some(true)), "Keep with next, {cmd}"); + } + // A changed value still sets a multiple. + let mut app = app_with("Example"); + let _ = app.run("para.lineSpacing", json!({"exactly": 24})); + let Some(Dialog::Paragraph { rtl, align, left, right, first, before, after, line_was, keep_next, keep_lines, page_break, widow, .. }) = + Dialog::open("paragraph", &mut app) + else { + panic!("no paragraph dialog") + }; + apply_paragraph( + &mut app, + rtl, + &align, + left, + right, + first, + before, + after, + (1.5, line_was), + [keep_next, keep_lines, page_break, widow], + ([false; 4], [false; 4]), + ); + assert_eq!(props(&app).line_spacing, Some(LineSpacing::Multiple(1.5))); + } + #[test] fn font_dialog_sets_the_asian_and_latin_fonts_separately() { // #505: the Asian text font box and the Latin text font box each set their own font, and @@ -3223,7 +3317,7 @@ mod tests { #[test] fn paragraph_dialog_changes_direction() { let mut app = app_with("Hello"); - let Some(Dialog::Paragraph { align, left, right, first, before, after, line, keep_next, keep_lines, page_break, widow, .. }) = + let Some(Dialog::Paragraph { align, left, right, first, before, after, line, line_was, keep_next, keep_lines, page_break, widow, .. }) = Dialog::open("paragraph", &mut app) else { panic!() @@ -3238,7 +3332,7 @@ mod tests { first, before, after, - line, + (line, line_was), [keep_next, keep_lines, page_break, widow], ([false; 4], [false; 4]), );