mirror of
https://github.com/storytold/designcraft.git
synced 2026-11-01 06:06:49 -06:00
Hyphenation Settings: a mixed checkbox turns on first; refused settings show why
A checkbox that differs across the selected paragraphs starts off, so the first click turns it on for all (egui toggles the value under the indeterminate look). Settings type.para refuses no longer count as previewed: the paragraphs go back, the reason shows and they are not tried again until they change; OK keeps the dialog open with the reason. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
7971b8e9bd
commit
08b356cf44
@@ -2373,6 +2373,10 @@ pub fn show(app: &mut DesignApp, ctx: &egui::Context) {
|
||||
check(ui, &mut d, "preview", &preview);
|
||||
});
|
||||
hyphenation_preview(app, &mut d);
|
||||
if let Some(st) = d.fields.get("status").and_then(Value::as_str) {
|
||||
ui.add_space(6.0);
|
||||
crate::rtl::label(ui, egui::RichText::new(st).color(ui.visuals().error_fg_color));
|
||||
}
|
||||
}
|
||||
"insertXref" => insert_xref(app, ui, &mut d),
|
||||
"findFont" => find_font(app, ui, &mut d),
|
||||
@@ -3191,7 +3195,14 @@ pub fn confirm(app: &mut DesignApp) -> Result<Value, String> {
|
||||
if attrs.is_empty() {
|
||||
return Ok(Value::Null);
|
||||
}
|
||||
app.run("type.para", json!({"attrs": attrs}))
|
||||
app.run("type.para", json!({"attrs": attrs})).inspect_err(|e| {
|
||||
// The dialog stays open, with the reason, to be corrected.
|
||||
app.status(e.clone());
|
||||
let mut d = d;
|
||||
d.fields.remove(PREVIEWED);
|
||||
d.fields.insert("status".into(), json!(e));
|
||||
app.ui.dialog = Some(d);
|
||||
})
|
||||
}
|
||||
"justification" => {
|
||||
let current = d.fields.get("current").cloned().unwrap_or_default();
|
||||
@@ -5827,7 +5838,8 @@ fn hyphenation(lang: &str, ui: &mut egui::Ui, d: &mut Dialog, prefix: &str, curr
|
||||
};
|
||||
let checkbox = |ui: &mut egui::Ui, d: &mut Dialog, key: &str, label: &str| {
|
||||
let v = cur(d, key);
|
||||
let mut on = v.as_bool().unwrap_or(true);
|
||||
// Mixed shows indeterminate and starts off, so the first click turns it on for all.
|
||||
let mut on = v.as_bool().unwrap_or(false);
|
||||
if ui.add(egui::Checkbox::new(&mut on, crate::rtl::widget(ui, tr(label))).indeterminate(v.is_null())).changed() {
|
||||
d.fields.insert(format!("{prefix}{key}"), json!(on));
|
||||
}
|
||||
@@ -5913,16 +5925,31 @@ fn hyphenation_preview(app: &mut DesignApp, d: &mut Dialog) {
|
||||
return;
|
||||
}
|
||||
let params = json!({"attrs": hyphenation_edits(d)});
|
||||
if shown.as_ref() == Some(¶ms) {
|
||||
if shown.as_ref() == Some(¶ms) || d.fields.get(PREVIEW_FAILED) == Some(¶ms) {
|
||||
return;
|
||||
}
|
||||
if shown.is_none() && app.session.begin_interaction("Hyphenation").is_err() {
|
||||
return;
|
||||
}
|
||||
let _ = app.session.preview("type.para", params.clone());
|
||||
d.fields.insert(PREVIEWED.into(), params);
|
||||
match app.session.preview("type.para", params.clone()) {
|
||||
Ok(_) => {
|
||||
d.fields.remove("status");
|
||||
d.fields.remove(PREVIEW_FAILED);
|
||||
d.fields.insert(PREVIEWED.into(), params);
|
||||
}
|
||||
// Rejected settings: the paragraphs go back, and the reason shows until they change.
|
||||
Err(e) => {
|
||||
app.session.cancel_interaction();
|
||||
d.fields.remove(PREVIEWED);
|
||||
d.fields.insert("status".into(), json!(e.to_string()));
|
||||
d.fields.insert(PREVIEW_FAILED.into(), params);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The hyphenation settings a preview was refused for (not tried again until they change).
|
||||
const PREVIEW_FAILED: &str = "previewFailed";
|
||||
|
||||
fn justification(lang: &str, ui: &mut egui::Ui, d: &mut Dialog, prefix: &str, current: &Value) {
|
||||
let cur = |d: &Dialog, k: &str| d.fields.get(&format!("{prefix}{k}")).cloned().unwrap_or_else(|| current[k].clone());
|
||||
egui::Grid::new("psj").num_columns(4).spacing([8.0, 6.0]).show(ui, |ui| {
|
||||
@@ -7820,6 +7847,50 @@ mod tests {
|
||||
assert!(!crate::menus::menu_enabled(&app, "app.hyphenationDialog"), "no paragraph");
|
||||
}
|
||||
|
||||
/// Hyphenate differs across the selection: it shows mixed, and the first click turns it on for
|
||||
/// all. Settings the command refuses show why: a preview goes back and isn't retried until they
|
||||
/// change, and OK keeps the dialog open.
|
||||
#[test]
|
||||
fn hyphenation_settings_mixed_checkbox_and_refused_settings() {
|
||||
let (mut app, story) = two_paragraphs();
|
||||
let ctx = dialog_context("en");
|
||||
let screen = egui::Rect::from_min_size(egui::Pos2::ZERO, egui::vec2(1400.0, 900.0));
|
||||
app.run("type.para", json!({"attrs": {"hyphenate": false}})).unwrap();
|
||||
app.run("text.select", json!({"story": story, "anchor": 0, "focus": 7})).unwrap();
|
||||
app.run("app.hyphenationDialog", json!({})).unwrap();
|
||||
assert!(app.ui.dialog.as_ref().unwrap().fields["current"]["hyphenate"].is_null(), "mixed");
|
||||
for _ in 0..3 {
|
||||
dialog_frame(&mut app, &ctx, screen, vec![]);
|
||||
}
|
||||
let labels = dialog_frame(&mut app, &ctx, screen, vec![]);
|
||||
let at = labels.iter().find(|(t, _, _)| t == "Hyphenate").map(|(_, r, _)| r.left_center() - egui::vec2(10.0, 0.0)).unwrap();
|
||||
click_dialog(&mut app, &ctx, screen, at);
|
||||
assert_eq!(app.ui.dialog.as_ref().unwrap().fields.get("hyphenate"), Some(&json!(true)), "the first click turns it on");
|
||||
// A refused setting (as a control-channel caller could send it): no preview, the reason.
|
||||
let before = undo_steps(&app);
|
||||
let d = app.ui.dialog.as_mut().unwrap();
|
||||
d.fields.insert("hyphWeight".into(), json!("heavy"));
|
||||
d.fields.insert("preview".into(), json!(true));
|
||||
dialog_frame(&mut app, &ctx, screen, vec![]);
|
||||
let d = app.ui.dialog.as_ref().unwrap();
|
||||
assert!(!d.fields.contains_key(PREVIEWED) && d.fields.contains_key("status"), "{:?}", d.fields);
|
||||
assert!(app.session.doc().unwrap().interaction.is_none(), "the preview went back");
|
||||
dialog_frame(&mut app, &ctx, screen, vec![]);
|
||||
assert!(app.session.doc().unwrap().interaction.is_none(), "not tried again");
|
||||
// OK with it: an error, and the dialog stays open with the reason.
|
||||
assert!(confirm(&mut app).is_err());
|
||||
let d = app.ui.dialog.as_ref().expect("the dialog stays open");
|
||||
assert!(d.fields.contains_key("status"));
|
||||
assert_eq!(undo_steps(&app), before);
|
||||
// Corrected, it previews and applies.
|
||||
app.ui.dialog.as_mut().unwrap().fields.insert("hyphWeight".into(), json!(0.9));
|
||||
dialog_frame(&mut app, &ctx, screen, vec![]);
|
||||
assert!(!app.ui.dialog.as_ref().unwrap().fields.contains_key("status"));
|
||||
confirm(&mut app).unwrap();
|
||||
assert_eq!(para_attr(&mut app, &story, 0, "hyphenate"), true);
|
||||
assert_eq!(para_attr(&mut app, &story, 4, "hyphWeight"), 0.9);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn justification_dialog_applies_to_a_local_paragraph_and_cancel_changes_nothing() {
|
||||
let (mut app, story) = two_paragraphs();
|
||||
|
||||
Reference in New Issue
Block a user