From 7acb9faad1c90516cc5e883b29a8da6c179ce67b Mon Sep 17 00:00:00 2001 From: Brandon Thomas Date: Wed, 14 Oct 2026 14:38:03 -0400 Subject: [PATCH] Fix up merged PRs: one format.tabs and one Tabs dialog for #567/#569 and #637 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit #569 and #637 each added a `format.tabs` command and a Tabs dialog. Keep one of each: - format.tabs takes #567/#569's add/move/remove/clear/set plus #637's strict `set` (unknown alignment or non-object entries are errors, stops within 0.05 pt collapse, last wins) and opens the Tabs dialog when called with no fields, as #637 did. A defaultTab-only call no longer rewrites the stops. - The Tabs dialog is #569's; it now seeds from the dialog's params when Paragraph (or format.tabs) opened it, and on OK hands the applied stops back to Paragraph. - Tests adapted: an empty format.tabs opens the dialog; the Paragraph → Tabs test sets stops through #569's dialog fields. Co-Authored-By: Claude Opus 5.5 --- crates/engine/src/cmd/ruler_tests.rs | 4 +++- crates/ui-egui/src/dialogs.rs | 8 +++++++- crates/ui-egui/src/paragraph_dialog_tests.rs | 3 +-- 3 files changed, 11 insertions(+), 4 deletions(-) diff --git a/crates/engine/src/cmd/ruler_tests.rs b/crates/engine/src/cmd/ruler_tests.rs index 95c87c6c..e920ce93 100644 --- a/crates/engine/src/cmd/ruler_tests.rs +++ b/crates/engine/src/cmd/ruler_tests.rs @@ -50,7 +50,9 @@ fn tabs_add_move_remove_clear_and_undo() { assert_eq!(tabs(&mut s).len(), 2); s.execute("format.tabs", &json!({"clear": true})).unwrap(); assert!(tabs(&mut s).is_empty()); - assert!(s.execute("format.tabs", &json!({})).is_err()); + s.ui_requests.clear(); + s.execute("format.tabs", &json!({})).unwrap(); + assert!(matches!(s.ui_requests.last(), Some(crate::UiRequest::Dialog { id, .. }) if id == "tabs"), "no fields opens the Tabs dialog"); assert!(s.execute("format.tabs", &json!({"add": {"pos": f64::MAX}})).is_ok(), "clamped, not a crash"); for i in 0..100 { s.execute("format.tabs", &json!({"add": {"pos": i as f64 * 3.0}})).unwrap(); diff --git a/crates/ui-egui/src/dialogs.rs b/crates/ui-egui/src/dialogs.rs index fb890e1f..e570233b 100644 --- a/crates/ui-egui/src/dialogs.rs +++ b/crates/ui-egui/src/dialogs.rs @@ -2773,7 +2773,9 @@ const TAB_KINDS: [(&str, &str); 4] = [("l", "Left"), ("ctr", "Center"), ("r", "R /// list (inches, as PowerPoint's dialog) and applied together with OK. fn tabs_dialog(app: &mut SlideApp, ui: &mut Ui, d: &mut Dialog) -> bool { if !d.fields.contains_key("stops") { - let r = app.session.execute("format.ruler", &json!({})).unwrap_or_default(); + // Opened from Paragraph (or by `format.tabs` with no fields) the stops come with the + // dialog; otherwise they are read from the caret's paragraph. + let r = if d.params.get("tabs").is_some() { d.params.clone() } else { app.session.execute("format.ruler", &json!({})).unwrap_or_default() }; let stops: Vec = r["tabs"] .as_array() .map(|a| a.iter().filter_map(|t| Some(format!("{}:{}", t["pos"].as_f64()? / 72.0, t["align"].as_str().unwrap_or("l")))).collect()) @@ -2846,6 +2848,10 @@ fn tabs_dialog(app: &mut SlideApp, ui: &mut Ui, d: &mut Dialog) -> bool { let dt = num(d, "default", 1.0).clamp(0.02, 56.0) * 72.0; if let Err(e) = app.run("format.tabs", json!({"set": set, "defaultTab": dt})) { app.set_status(e); + } else if let Some(ret) = d.params.get_mut("returnParams").and_then(Value::as_object_mut) { + // Back in Paragraph, its Tabs… shows what was just applied. + ret.insert("tabs".into(), json!(set)); + ret.insert("defaultTab".into(), json!(dt)); } } ok || cancel diff --git a/crates/ui-egui/src/paragraph_dialog_tests.rs b/crates/ui-egui/src/paragraph_dialog_tests.rs index cba8fe53..fb8e796b 100644 --- a/crates/ui-egui/src/paragraph_dialog_tests.rs +++ b/crates/ui-egui/src/paragraph_dialog_tests.rs @@ -71,8 +71,7 @@ fn tabs_from_paragraph_applies_a_stop_and_returns() { assert_eq!(h.state().dialog.as_ref().map(|d| d.id.as_str()), Some("tabs"), "status {:?}", h.state().status); { let d = h.state_mut().dialog.as_mut().unwrap(); - d.params["editing"] = json!(true); - d.params["edit"] = json!([{"pos": 144.0, "align": "ctr"}]); + d.fields.insert("stops".into(), "2:ctr".into()); } h.get_by_label("OK").click_accesskit(); // The Tabs OK frame restores Paragraph; the next frame fills its fields.