diff --git a/Cargo.lock b/Cargo.lock index eabe9e52e..4d1d8b80b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1004,7 +1004,7 @@ dependencies = [ [[package]] name = "craft-atomic" version = "0.1.0" -source = "git+https://github.com/storytold/craft-libs?rev=b926b5c03e256c86ca10934f58d03f628810b5b7#b926b5c03e256c86ca10934f58d03f628810b5b7" +source = "git+https://github.com/storytold/craft-libs?rev=92d392cff132eaf53e13afff77f38b5c11b73aa0#92d392cff132eaf53e13afff77f38b5c11b73aa0" dependencies = [ "rustix 1.1.5", ] @@ -1012,7 +1012,7 @@ dependencies = [ [[package]] name = "craft-cms" version = "0.1.0" -source = "git+https://github.com/storytold/craft-libs?rev=b926b5c03e256c86ca10934f58d03f628810b5b7#b926b5c03e256c86ca10934f58d03f628810b5b7" +source = "git+https://github.com/storytold/craft-libs?rev=92d392cff132eaf53e13afff77f38b5c11b73aa0#92d392cff132eaf53e13afff77f38b5c11b73aa0" dependencies = [ "rayon", ] @@ -1020,17 +1020,17 @@ dependencies = [ [[package]] name = "craft-docpath" version = "0.1.0" -source = "git+https://github.com/storytold/craft-libs?rev=b926b5c03e256c86ca10934f58d03f628810b5b7#b926b5c03e256c86ca10934f58d03f628810b5b7" +source = "git+https://github.com/storytold/craft-libs?rev=92d392cff132eaf53e13afff77f38b5c11b73aa0#92d392cff132eaf53e13afff77f38b5c11b73aa0" [[package]] name = "craft-fonts-build" version = "0.1.0" -source = "git+https://github.com/storytold/craft-libs?rev=b926b5c03e256c86ca10934f58d03f628810b5b7#b926b5c03e256c86ca10934f58d03f628810b5b7" +source = "git+https://github.com/storytold/craft-libs?rev=92d392cff132eaf53e13afff77f38b5c11b73aa0#92d392cff132eaf53e13afff77f38b5c11b73aa0" [[package]] name = "craft-i18n" version = "0.1.0" -source = "git+https://github.com/storytold/craft-libs?rev=b926b5c03e256c86ca10934f58d03f628810b5b7#b926b5c03e256c86ca10934f58d03f628810b5b7" +source = "git+https://github.com/storytold/craft-libs?rev=92d392cff132eaf53e13afff77f38b5c11b73aa0#92d392cff132eaf53e13afff77f38b5c11b73aa0" dependencies = [ "fixed_decimal", "icu_decimal", @@ -1053,7 +1053,7 @@ dependencies = [ [[package]] name = "craft-keymap" version = "0.1.0" -source = "git+https://github.com/storytold/craft-libs?rev=b926b5c03e256c86ca10934f58d03f628810b5b7#b926b5c03e256c86ca10934f58d03f628810b5b7" +source = "git+https://github.com/storytold/craft-libs?rev=92d392cff132eaf53e13afff77f38b5c11b73aa0#92d392cff132eaf53e13afff77f38b5c11b73aa0" dependencies = [ "craft-i18n", "craft-ui", @@ -1065,7 +1065,7 @@ dependencies = [ [[package]] name = "craft-pdf" version = "0.1.0" -source = "git+https://github.com/storytold/craft-libs?rev=b926b5c03e256c86ca10934f58d03f628810b5b7#b926b5c03e256c86ca10934f58d03f628810b5b7" +source = "git+https://github.com/storytold/craft-libs?rev=92d392cff132eaf53e13afff77f38b5c11b73aa0#92d392cff132eaf53e13afff77f38b5c11b73aa0" dependencies = [ "bytemuck", "hayro", @@ -1078,7 +1078,7 @@ dependencies = [ [[package]] name = "craft-prepress" version = "0.1.0" -source = "git+https://github.com/storytold/craft-libs?rev=b926b5c03e256c86ca10934f58d03f628810b5b7#b926b5c03e256c86ca10934f58d03f628810b5b7" +source = "git+https://github.com/storytold/craft-libs?rev=92d392cff132eaf53e13afff77f38b5c11b73aa0#92d392cff132eaf53e13afff77f38b5c11b73aa0" dependencies = [ "craft-cms", "craft-pdf", @@ -1091,7 +1091,7 @@ dependencies = [ [[package]] name = "craft-print" version = "0.1.0" -source = "git+https://github.com/storytold/craft-libs?rev=b926b5c03e256c86ca10934f58d03f628810b5b7#b926b5c03e256c86ca10934f58d03f628810b5b7" +source = "git+https://github.com/storytold/craft-libs?rev=92d392cff132eaf53e13afff77f38b5c11b73aa0#92d392cff132eaf53e13afff77f38b5c11b73aa0" dependencies = [ "craft-winprint-gdi", "miniz_oxide 0.9.1", @@ -1104,7 +1104,7 @@ dependencies = [ [[package]] name = "craft-ui" version = "0.1.0" -source = "git+https://github.com/storytold/craft-libs?rev=b926b5c03e256c86ca10934f58d03f628810b5b7#b926b5c03e256c86ca10934f58d03f628810b5b7" +source = "git+https://github.com/storytold/craft-libs?rev=92d392cff132eaf53e13afff77f38b5c11b73aa0#92d392cff132eaf53e13afff77f38b5c11b73aa0" dependencies = [ "craft-fonts-build", "craft-i18n", @@ -1125,7 +1125,7 @@ dependencies = [ [[package]] name = "craft-update" version = "0.1.0" -source = "git+https://github.com/storytold/craft-libs?rev=b926b5c03e256c86ca10934f58d03f628810b5b7#b926b5c03e256c86ca10934f58d03f628810b5b7" +source = "git+https://github.com/storytold/craft-libs?rev=92d392cff132eaf53e13afff77f38b5c11b73aa0#92d392cff132eaf53e13afff77f38b5c11b73aa0" dependencies = [ "chrono", "dunce", @@ -1150,7 +1150,7 @@ dependencies = [ [[package]] name = "craft-window" version = "0.1.0" -source = "git+https://github.com/storytold/craft-libs?rev=b926b5c03e256c86ca10934f58d03f628810b5b7#b926b5c03e256c86ca10934f58d03f628810b5b7" +source = "git+https://github.com/storytold/craft-libs?rev=92d392cff132eaf53e13afff77f38b5c11b73aa0#92d392cff132eaf53e13afff77f38b5c11b73aa0" dependencies = [ "egui", "objc2 0.6.5", @@ -2635,7 +2635,7 @@ dependencies = [ [[package]] name = "hayro" version = "0.7.1" -source = "git+https://github.com/storytold/craft-libs?rev=b926b5c03e256c86ca10934f58d03f628810b5b7#b926b5c03e256c86ca10934f58d03f628810b5b7" +source = "git+https://github.com/storytold/craft-libs?rev=92d392cff132eaf53e13afff77f38b5c11b73aa0#92d392cff132eaf53e13afff77f38b5c11b73aa0" dependencies = [ "bytemuck", "hayro-interpret", @@ -2655,7 +2655,7 @@ checksum = "9f4d0e94ddd48749f06bbe4e5389fb9799a0c45bcaf00495042076ef05e3241a" [[package]] name = "hayro-cmap" version = "0.1.0" -source = "git+https://github.com/storytold/craft-libs?rev=b926b5c03e256c86ca10934f58d03f628810b5b7#b926b5c03e256c86ca10934f58d03f628810b5b7" +source = "git+https://github.com/storytold/craft-libs?rev=92d392cff132eaf53e13afff77f38b5c11b73aa0#92d392cff132eaf53e13afff77f38b5c11b73aa0" dependencies = [ "brotli", "hayro-postscript", @@ -2664,7 +2664,7 @@ dependencies = [ [[package]] name = "hayro-interpret" version = "0.7.0" -source = "git+https://github.com/storytold/craft-libs?rev=b926b5c03e256c86ca10934f58d03f628810b5b7#b926b5c03e256c86ca10934f58d03f628810b5b7" +source = "git+https://github.com/storytold/craft-libs?rev=92d392cff132eaf53e13afff77f38b5c11b73aa0#92d392cff132eaf53e13afff77f38b5c11b73aa0" dependencies = [ "bitflags 2.13.2", "hayro-cmap", @@ -2682,7 +2682,7 @@ dependencies = [ [[package]] name = "hayro-jbig2" version = "0.3.0" -source = "git+https://github.com/storytold/craft-libs?rev=b926b5c03e256c86ca10934f58d03f628810b5b7#b926b5c03e256c86ca10934f58d03f628810b5b7" +source = "git+https://github.com/storytold/craft-libs?rev=92d392cff132eaf53e13afff77f38b5c11b73aa0#92d392cff132eaf53e13afff77f38b5c11b73aa0" dependencies = [ "fearless_simd", "hayro-ccitt", @@ -2700,12 +2700,12 @@ dependencies = [ [[package]] name = "hayro-postscript" version = "0.1.0" -source = "git+https://github.com/storytold/craft-libs?rev=b926b5c03e256c86ca10934f58d03f628810b5b7#b926b5c03e256c86ca10934f58d03f628810b5b7" +source = "git+https://github.com/storytold/craft-libs?rev=92d392cff132eaf53e13afff77f38b5c11b73aa0#92d392cff132eaf53e13afff77f38b5c11b73aa0" [[package]] name = "hayro-syntax" version = "0.7.2" -source = "git+https://github.com/storytold/craft-libs?rev=b926b5c03e256c86ca10934f58d03f628810b5b7#b926b5c03e256c86ca10934f58d03f628810b5b7" +source = "git+https://github.com/storytold/craft-libs?rev=92d392cff132eaf53e13afff77f38b5c11b73aa0#92d392cff132eaf53e13afff77f38b5c11b73aa0" dependencies = [ "flate2", "hayro-ccitt", @@ -3300,7 +3300,7 @@ dependencies = [ [[package]] name = "krilla" version = "0.8.2" -source = "git+https://github.com/storytold/craft-libs?rev=b926b5c03e256c86ca10934f58d03f628810b5b7#b926b5c03e256c86ca10934f58d03f628810b5b7" +source = "git+https://github.com/storytold/craft-libs?rev=92d392cff132eaf53e13afff77f38b5c11b73aa0#92d392cff132eaf53e13afff77f38b5c11b73aa0" dependencies = [ "base64 0.22.1", "bumpalo", diff --git a/Cargo.toml b/Cargo.toml index 195f39e67..0afeca737 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,12 +12,12 @@ repository = "https://github.com/storytold/designcraft" [workspace.dependencies] # Shared reader and writer; publish the immutable revision before this consumer. -craft-pdf = { git = "https://github.com/storytold/craft-libs", rev = "b926b5c03e256c86ca10934f58d03f628810b5b7", version = "=0.1.0", default-features = false } +craft-pdf = { git = "https://github.com/storytold/craft-libs", rev = "92d392cff132eaf53e13afff77f38b5c11b73aa0", version = "=0.1.0", default-features = false } # Shared keyboard bindings: chords, overrides, conflicts, egui dispatch, the Keyboard Shortcuts dialog. -craft-keymap = { git = "https://github.com/storytold/craft-libs", rev = "b926b5c03e256c86ca10934f58d03f628810b5b7", version = "=0.1.0", features = ["egui", "ui"] } +craft-keymap = { git = "https://github.com/storytold/craft-libs", rev = "92d392cff132eaf53e13afff77f38b5c11b73aa0", version = "=0.1.0", features = ["egui", "ui"] } # Separations, ink coverage and the ink manager; same revision as craft-pdf and the krilla patch. -craft-prepress = { git = "https://github.com/storytold/craft-libs", rev = "b926b5c03e256c86ca10934f58d03f628810b5b7", version = "=0.1.0", features = ["flatten"] } -craft-update = { git = "https://github.com/storytold/craft-libs", version = "=0.1.0", rev = "b926b5c03e256c86ca10934f58d03f628810b5b7", features = ["egui"] } +craft-prepress = { git = "https://github.com/storytold/craft-libs", rev = "92d392cff132eaf53e13afff77f38b5c11b73aa0", version = "=0.1.0", features = ["flatten"] } +craft-update = { git = "https://github.com/storytold/craft-libs", version = "=0.1.0", rev = "92d392cff132eaf53e13afff77f38b5c11b73aa0", features = ["egui"] } # internal designcraft-geom = { path = "crates/geom" } designcraft-color = { path = "crates/color" } @@ -80,20 +80,20 @@ zip = { version = "3", default-features = false, features = ["deflate"] } indd = { git = "https://github.com/storytold/indd-utils", branch = "main" } # Which paths stored in documents may be followed (craft-libs). -craft-docpath = { version = "0.1.0", git = "https://github.com/storytold/craft-libs", rev = "b926b5c03e256c86ca10934f58d03f628810b5b7" } +craft-docpath = { version = "0.1.0", git = "https://github.com/storytold/craft-libs", rev = "92d392cff132eaf53e13afff77f38b5c11b73aa0" } # Atomic file replacement for saves and exports (craft-libs). -craft-atomic = { version = "0.1.0", git = "https://github.com/storytold/craft-libs", rev = "b926b5c03e256c86ca10934f58d03f628810b5b7" } +craft-atomic = { version = "0.1.0", git = "https://github.com/storytold/craft-libs", rev = "92d392cff132eaf53e13afff77f38b5c11b73aa0" } # File › Print through the system print spooler (craft-libs). -craft-print = { version = "0.1.0", git = "https://github.com/storytold/craft-libs", rev = "b926b5c03e256c86ca10934f58d03f628810b5b7" } +craft-print = { version = "0.1.0", git = "https://github.com/storytold/craft-libs", rev = "92d392cff132eaf53e13afff77f38b5c11b73aa0" } # ICC colour management: working spaces, transforms, black point compensation (craft-libs). -craft-cms = { version = "0.1.0", git = "https://github.com/storytold/craft-libs", rev = "b926b5c03e256c86ca10934f58d03f628810b5b7" } +craft-cms = { version = "0.1.0", git = "https://github.com/storytold/craft-libs", rev = "92d392cff132eaf53e13afff77f38b5c11b73aa0" } # ui # Shared frontend components; keep this dependency above the engine boundary. -craft-ui = { version = "0.1.0", git = "https://github.com/storytold/craft-libs", rev = "b926b5c03e256c86ca10934f58d03f628810b5b7", features = ["serde"] } +craft-ui = { version = "0.1.0", git = "https://github.com/storytold/craft-libs", rev = "92d392cff132eaf53e13afff77f38b5c11b73aa0", features = ["serde"] } # The application bar as the window's title bar (craft-libs). -craft-window = { version = "0.1.0", git = "https://github.com/storytold/craft-libs", rev = "b926b5c03e256c86ca10934f58d03f628810b5b7", features = ["egui"] } +craft-window = { version = "0.1.0", git = "https://github.com/storytold/craft-libs", rev = "92d392cff132eaf53e13afff77f38b5c11b73aa0", features = ["egui"] } egui = "0.37" egui_extras = { version = "0.37", default-features = false, features = ["svg"] } eframe = { version = "0.37", default-features = false } @@ -131,16 +131,16 @@ debug = 0 # Opt-in cancelable macOS termination on the pinned winit 0.30 line. [patch.crates-io] -krilla = { git = "https://github.com/storytold/craft-libs", rev = "b926b5c03e256c86ca10934f58d03f628810b5b7", version = "=0.8.2" } +krilla = { git = "https://github.com/storytold/craft-libs", rev = "92d392cff132eaf53e13afff77f38b5c11b73aa0", version = "=0.8.2" } winit = { git = "https://github.com/storytold/winit.git", rev = "a517b3846ee8457d84ee4975110bfda6797c44dc" } # The hayro crates from craft-libs' vendor/ directory (pdfcraft's, plus the plate mode and decode # limits craft-pdf and craft-prepress need); same revision as craft-pdf. -hayro = { git = "https://github.com/storytold/craft-libs", rev = "b926b5c03e256c86ca10934f58d03f628810b5b7", version = "=0.7.1" } -hayro-interpret = { git = "https://github.com/storytold/craft-libs", rev = "b926b5c03e256c86ca10934f58d03f628810b5b7", version = "=0.7.0" } -hayro-syntax = { git = "https://github.com/storytold/craft-libs", rev = "b926b5c03e256c86ca10934f58d03f628810b5b7", version = "=0.7.2" } -hayro-jbig2 = { git = "https://github.com/storytold/craft-libs", rev = "b926b5c03e256c86ca10934f58d03f628810b5b7", version = "=0.3.0" } -hayro-cmap = { git = "https://github.com/storytold/craft-libs", rev = "b926b5c03e256c86ca10934f58d03f628810b5b7", version = "=0.1.0" } -hayro-postscript = { git = "https://github.com/storytold/craft-libs", rev = "b926b5c03e256c86ca10934f58d03f628810b5b7", version = "=0.1.0" } +hayro = { git = "https://github.com/storytold/craft-libs", rev = "92d392cff132eaf53e13afff77f38b5c11b73aa0", version = "=0.7.1" } +hayro-interpret = { git = "https://github.com/storytold/craft-libs", rev = "92d392cff132eaf53e13afff77f38b5c11b73aa0", version = "=0.7.0" } +hayro-syntax = { git = "https://github.com/storytold/craft-libs", rev = "92d392cff132eaf53e13afff77f38b5c11b73aa0", version = "=0.7.2" } +hayro-jbig2 = { git = "https://github.com/storytold/craft-libs", rev = "92d392cff132eaf53e13afff77f38b5c11b73aa0", version = "=0.3.0" } +hayro-cmap = { git = "https://github.com/storytold/craft-libs", rev = "92d392cff132eaf53e13afff77f38b5c11b73aa0", version = "=0.1.0" } +hayro-postscript = { git = "https://github.com/storytold/craft-libs", rev = "92d392cff132eaf53e13afff77f38b5c11b73aa0", version = "=0.1.0" } ecolor = { git = "https://github.com/storytold/egui.git", rev = "6d518fc799681571963842f95b55a6b68431b15a" } emath = { git = "https://github.com/storytold/egui.git", rev = "6d518fc799681571963842f95b55a6b68431b15a" } epaint = { git = "https://github.com/storytold/egui.git", rev = "6d518fc799681571963842f95b55a6b68431b15a" } diff --git a/clippy.toml b/clippy.toml index 4b6c79f55..287e14edc 100644 --- a/clippy.toml +++ b/clippy.toml @@ -2,3 +2,6 @@ allow-unwrap-in-tests = true allow-expect-in-tests = true allow-panic-in-tests = true allow-indexing-slicing-in-tests = true +disallowed-types = [ + { path = "egui::ComboBox", reason = "use craft_ui::ComboBox: egui's combo box menu can't grow back while open (egui#5225)" }, +] diff --git a/crates/ui-egui/src/chrome/control_bar.rs b/crates/ui-egui/src/chrome/control_bar.rs index de44bd4b5..331caa446 100644 --- a/crates/ui-egui/src/chrome/control_bar.rs +++ b/crates/ui-egui/src/chrome/control_bar.rs @@ -38,7 +38,7 @@ fn page_preset_and_orientation(app: &mut DesignApp, ui: &mut egui::Ui, lang: &st let landscape = size.0 > size.1; ui.vertical(|ui| { let cur = panels::properties::preset_name(size.0, size.1); - egui::ComboBox::from_id_salt("ctl_page_preset").selected_text(crate::rtl::widget(ui, crate::i18n::tr(lang, cur))).width(130.0).show_ui( + craft_ui::ComboBox::from_id_salt("ctl_page_preset").selected_text(crate::rtl::widget(ui, crate::i18n::tr(lang, cur))).width(130.0).show_ui( ui, |ui| { for (name, w, h) in panels::properties::PAGE_PRESETS { @@ -105,7 +105,7 @@ fn control_page(app: &mut DesignApp, ui: &mut egui::Ui) { ui.vertical(|ui| { caption(ui, crate::i18n::tr(&lang, "Liquid Page Rule:")); let label = panels::interactive::liquid_rule_label(pg.liquid); - egui::ComboBox::from_id_salt("ctl_page_rule").selected_text(crate::rtl::widget(ui, crate::i18n::tr(&lang, label))).width(150.0).show_ui( + craft_ui::ComboBox::from_id_salt("ctl_page_rule").selected_text(crate::rtl::widget(ui, crate::i18n::tr(&lang, label))).width(150.0).show_ui( ui, |ui| { for (r, l) in panels::interactive::LIQUID_RULES { @@ -368,7 +368,7 @@ fn control_character( let cs = attrs.as_ref().and_then(|a| a["characterStyle"].as_str()).unwrap_or(designcraft_doc::NO_CHAR_STYLE).to_string(); let names: Vec = app.session.active().map(|d| d.doc.styles.character.iter().map(|c| c.name.clone()).collect()).unwrap_or_default(); let mut pick = None; - egui::ComboBox::from_id_salt("ctl_char_style") + craft_ui::ComboBox::from_id_salt("ctl_char_style") .selected_text(crate::rtl::widget(ui, crate::i18n::style_name(&app.ui.language, &cs))) .width(150.0) .show_ui(ui, |ui| { @@ -382,7 +382,7 @@ fn control_character( let _ = app.run("style.character.apply", json!({"name": n})); } let lang = chars["language"].as_str().unwrap_or("English: USA").to_string(); - egui::ComboBox::from_id_salt("ctl_language").selected_text(crate::rtl::widget(ui, tr(&lang))).width(150.0).show_ui(ui, |ui| { + craft_ui::ComboBox::from_id_salt("ctl_language").selected_text(crate::rtl::widget(ui, tr(&lang))).width(150.0).show_ui(ui, |ui| { egui::ScrollArea::vertical().max_height(320.0).show(ui, |ui| crate::panels::properties::language_menu(app, ui, &lang)); }); }); diff --git a/crates/ui-egui/src/credits.rs b/crates/ui-egui/src/credits.rs index 22937978d..45f821b09 100644 --- a/crates/ui-egui/src/credits.rs +++ b/crates/ui-egui/src/credits.rs @@ -223,7 +223,7 @@ fn controls(ui: &mut egui::Ui, v: &mut View, lang: &str) { } ui.separator(); ui.label(tr(lang, "Sort")); - egui::ComboBox::from_id_salt("credits_sort").selected_text(tr(lang, v.key.label().0)).show_ui(ui, |ui| { + craft_ui::ComboBox::from_id_salt("credits_sort").selected_text(tr(lang, v.key.label().0)).show_ui(ui, |ui| { for k in SortKey::ALL { if ui.selectable_label(v.key == k, tr(lang, k.label().0)).clicked() { v.key = k; diff --git a/crates/ui-egui/src/dialogs.rs b/crates/ui-egui/src/dialogs.rs index 948c84c83..3fb6a657d 100644 --- a/crates/ui-egui/src/dialogs.rs +++ b/crates/ui-egui/src/dialogs.rs @@ -314,7 +314,7 @@ fn epub_export(app: &crate::DesignApp, ui: &mut egui::Ui, d: &mut Dialog) { d.fields.insert("mode".into(), json!("style")); } let cur = d.s("style"); - egui::ComboBox::from_id_salt("epub_split_style").selected_text(crate::rtl::widget(ui, &cur)).width(180.0).show_ui(ui, |ui| { + craft_ui::ComboBox::from_id_salt("epub_split_style").selected_text(crate::rtl::widget(ui, &cur)).width(180.0).show_ui(ui, |ui| { for name in &styles { if ui.selectable_label(cur == *name, crate::rtl::widget(ui, name)).clicked() { d.fields.insert("style".into(), json!(name)); @@ -343,7 +343,7 @@ fn print_dialog(app: &crate::DesignApp, ui: &mut egui::Ui, d: &mut Dialog) { if printers.is_empty() { crate::rtl::label(ui, egui::RichText::new(crate::i18n::tr(&app.ui.language, "System default")).italics()); } else { - egui::ComboBox::from_id_salt("printer").selected_text(&cur).width(220.0).show_ui(ui, |ui| { + craft_ui::ComboBox::from_id_salt("printer").selected_text(&cur).width(220.0).show_ui(ui, |ui| { for p in &printers { if ui.selectable_label(*p == cur, p).clicked() { d.fields.insert("printer".into(), json!(p)); @@ -423,7 +423,7 @@ fn pdf_export(app: &crate::DesignApp, ui: &mut egui::Ui, d: &mut Dialog) { ui.horizontal(|ui| { crate::rtl::label(ui, crate::i18n::tr(lang, "Preset:")); let cur = d.s("preset"); - egui::ComboBox::from_id_salt("pdf_preset").selected_text(crate::rtl::widget(ui, crate::i18n::tr(lang, &cur))).width(200.0).show_ui( + craft_ui::ComboBox::from_id_salt("pdf_preset").selected_text(crate::rtl::widget(ui, crate::i18n::tr(lang, &cur))).width(200.0).show_ui( ui, |ui| { for p in PDF_EXPORT_PRESETS { @@ -1508,7 +1508,7 @@ fn document_setup(app: &crate::DesignApp, ui: &mut egui::Ui, d: &mut Dialog) -> setup_row(ui, rtl, |ui| { setup_caption(ui, tr("Intent:"), label_w); let cur = d.s("intent"); - egui::ComboBox::from_id_salt("ds_intent") + craft_ui::ComboBox::from_id_salt("ds_intent") .selected_text(crate::rtl::widget( ui, tr(match cur.as_str() { @@ -1542,7 +1542,7 @@ fn document_setup(app: &crate::DesignApp, ui: &mut egui::Ui, d: &mut Dialog) -> setup_row(ui, rtl, |ui| { setup_caption(ui, tr("Binding:"), label_w); let right_to_left = d.s("binding") == "rightToLeft"; - egui::ComboBox::from_id_salt("ds_binding") + craft_ui::ComboBox::from_id_salt("ds_binding") .selected_text(crate::rtl::widget(ui, tr(if right_to_left { "Right to Left" } else { "Left to Right" }))) .width(150.0) .show_ui(ui, |ui| { @@ -1563,7 +1563,7 @@ fn document_setup(app: &crate::DesignApp, ui: &mut egui::Ui, d: &mut Dialog) -> setup_group(ui, |ui| { setup_row(ui, rtl, |ui| { crate::rtl::label(ui, egui::RichText::new(tr("Page Size:")).color(t.text_strong)); - egui::ComboBox::from_id_salt("ds_preset").selected_text(crate::rtl::widget(ui, &preset)).width(260.0).show_ui(ui, |ui| { + craft_ui::ComboBox::from_id_salt("ds_preset").selected_text(crate::rtl::widget(ui, &preset)).width(260.0).show_ui(ui, |ui| { for p in designcraft_doc::build::PRESETS { if ui.selectable_label(tr(p.name) == preset, crate::rtl::widget(ui, tr(p.name))).clicked() { // Keep the orientation; the preset's size in the document's units. @@ -1915,7 +1915,7 @@ fn preferences(app: &crate::DesignApp, ui: &mut egui::Ui, d: &mut Dialog, max_he ui.horizontal(|ui| { crate::rtl::label(ui, crate::i18n::tr(&app.ui.language, "On Screen:")); let rich = d.b("richBlack"); - egui::ComboBox::from_id_salt("pref_black") + craft_ui::ComboBox::from_id_salt("pref_black") .selected_text(crate::rtl::widget( ui, crate::i18n::tr( @@ -1935,7 +1935,7 @@ fn preferences(app: &crate::DesignApp, ui: &mut egui::Ui, d: &mut Dialog, max_he ui.horizontal(|ui| { crate::rtl::label(ui, crate::i18n::tr(&app.ui.language, "Printing / Exporting:")); let rich = d.b("richBlackOutput"); - egui::ComboBox::from_id_salt("pref_black_out") + craft_ui::ComboBox::from_id_salt("pref_black_out") .selected_text(crate::rtl::widget( ui, crate::i18n::tr( @@ -1985,7 +1985,7 @@ fn preferences(app: &crate::DesignApp, ui: &mut egui::Ui, d: &mut Dialog, max_he ui.end_row(); crate::rtl::label(ui, crate::i18n::tr(&app.ui.language, "Relative To:")); let cur = d.s("bg.relativeTo"); - egui::ComboBox::from_id_salt("bg_rel") + craft_ui::ComboBox::from_id_salt("bg_rel") .selected_text(crate::rtl::widget( ui, crate::i18n::tr(&app.ui.language, if cur == "topMargin" { "Top Margin" } else { "Top of Page" }), @@ -2066,7 +2066,7 @@ fn preferences(app: &crate::DesignApp, ui: &mut egui::Ui, d: &mut Dialog, max_he let cur = d.s("displayQuality"); ui.horizontal(|ui| { crate::rtl::label(ui, crate::i18n::tr(&app.ui.language, "Default View:")); - egui::ComboBox::from_id_salt("pref_dq") + craft_ui::ComboBox::from_id_salt("pref_dq") .selected_text(match cur.as_str() { "fast" => crate::i18n::tr(&app.ui.language, "Fast"), "typical" => crate::i18n::tr(&app.ui.language, "Typical"), @@ -2088,7 +2088,7 @@ fn preferences(app: &crate::DesignApp, ui: &mut egui::Ui, d: &mut Dialog, max_he let cur = d.fields.get("rulerOrigin").and_then(Value::as_str).unwrap_or("spread").to_string(); const ORIGINS: [(&str, &str); 3] = [("spread", "Spread"), ("page", "Page"), ("spine", "Spine")]; let shown = ORIGINS.iter().find(|o| o.0 == cur).map_or("Spread", |o| o.1); - egui::ComboBox::from_id_salt("rulerOrigin") + craft_ui::ComboBox::from_id_salt("rulerOrigin") .selected_text(crate::rtl::widget(ui, crate::i18n::tr(&app.ui.language, shown))) .width(140.0) .show_ui(ui, |ui| { @@ -2102,7 +2102,7 @@ fn preferences(app: &crate::DesignApp, ui: &mut egui::Ui, d: &mut Dialog, max_he for (key, label) in [("horizontalUnits", "Horizontal:"), ("verticalUnits", "Vertical:")] { crate::rtl::label(ui, crate::i18n::tr(&app.ui.language, label)); let cur: Unit = d.fields.get(key).and_then(|v| serde_json::from_value(v.clone()).ok()).unwrap_or_default(); - egui::ComboBox::from_id_salt(key) + craft_ui::ComboBox::from_id_salt(key) .selected_text(crate::rtl::widget(ui, crate::i18n::tr(&app.ui.language, cur.label()))) .width(140.0) .show_ui(ui, |ui| { @@ -2137,17 +2137,18 @@ fn preferences(app: &crate::DesignApp, ui: &mut egui::Ui, d: &mut Dialog, max_he crate::rtl::label(ui, egui::RichText::new(crate::i18n::tr(&app.ui.language, "Interface Language")).font(semibold(12.0))); // Each language by its own name, so the list reads the same in every interface language. let current = crate::i18n::LANGUAGES.iter().find(|(code, _)| *code == app.ui.language).map_or("English", |(_, name)| *name); - egui::ComboBox::from_id_salt("pref_language").width(180.0).height(400.0).selected_text(crate::rtl::widget(ui, current)).show_ui( - ui, - |ui| { + craft_ui::ComboBox::from_id_salt("pref_language") + .width(180.0) + .height(400.0) + .selected_text(crate::rtl::widget(ui, current)) + .show_ui(ui, |ui| { for (code, name) in crate::i18n::picker_languages() { if ui.selectable_label(code == app.ui.language, crate::rtl::widget(ui, name)).clicked() { // Applied by `show` right after this frame, which has `&mut DesignApp`. d.fields.insert("applyLanguage".into(), json!(code)); } } - }, - ); + }); ui.add_space(6.0); crate::rtl::label(ui, egui::RichText::new(crate::i18n::tr(&app.ui.language, "When Scaling")).font(semibold(12.0))); check(ui, d, "scaleStrokes", crate::i18n::tr(&app.ui.language, "Include Stroke Weight")); @@ -2392,7 +2393,7 @@ pub fn show(app: &mut DesignApp, ctx: &egui::Context) { for (key, label, any) in [("objFill", "Find fill:", "(any)"), ("objChangeFill", "Change fill to:", "(unchanged)")] { crate::rtl::label(ui, crate::i18n::tr(&app.ui.language, label)); let cur = d.s(key); - egui::ComboBox::from_id_salt(key) + craft_ui::ComboBox::from_id_salt(key) .selected_text(if cur.is_empty() { crate::i18n::tr(&app.ui.language, any) } else { cur.as_str() }) .width(180.0) .show_ui(ui, |ui| { @@ -2412,7 +2413,7 @@ pub fn show(app: &mut DesignApp, ctx: &egui::Context) { } crate::rtl::label(ui, crate::i18n::tr(&app.ui.language, "Kind:")); let cur = d.s("objKind"); - egui::ComboBox::from_id_salt("objKind") + craft_ui::ComboBox::from_id_salt("objKind") .selected_text(if cur.is_empty() { crate::i18n::tr(&app.ui.language, "(any)") } else { cur.as_str() }) .show_ui(ui, |ui| { for v in ["", "text", "graphic", "shape", "line", "group"] { @@ -2461,7 +2462,7 @@ pub fn show(app: &mut DesignApp, ctx: &egui::Context) { ui.end_row(); crate::rtl::label(ui, crate::i18n::tr(&app.ui.language, "Search:")); let cur = d.s("scope"); - egui::ComboBox::from_id_salt("fcscope") + craft_ui::ComboBox::from_id_salt("fcscope") .selected_text(crate::rtl::widget(ui, crate::i18n::tr(&app.ui.language, &cur))) .show_ui(ui, |ui| { for v in ["document", "story", "selection"] { @@ -2571,7 +2572,7 @@ pub fn show(app: &mut DesignApp, ctx: &egui::Context) { "media" => crate::i18n::tr(&app.ui.language, "Media"), _ => crate::i18n::tr(&app.ui.language, "Crop"), }; - egui::ComboBox::from_id_salt("pdf_crop").selected_text(crate::rtl::widget(ui, label(&cur))).show_ui(ui, |ui| { + craft_ui::ComboBox::from_id_salt("pdf_crop").selected_text(crate::rtl::widget(ui, label(&cur))).show_ui(ui, |ui| { for v in designcraft_doc::PdfCrop::ALL.map(designcraft_doc::PdfCrop::name) { if ui.selectable_label(label(&cur) == label(v), label(v)).clicked() { d.fields.insert("crop".into(), json!(v)); @@ -2608,7 +2609,7 @@ pub fn show(app: &mut DesignApp, ctx: &egui::Context) { for (key, label, kind) in [("rgb", "RGB:", "rgb"), ("cmyk", "CMYK:", "cmyk")] { crate::rtl::label(ui, crate::i18n::tr(&app.ui.language, label)); let cur = d.s(key); - egui::ComboBox::from_id_salt(("ws", key)).selected_text(&cur).width(260.0).show_ui(ui, |ui| { + craft_ui::ComboBox::from_id_salt(("ws", key)).selected_text(&cur).width(260.0).show_ui(ui, |ui| { for pr in profiles.iter().filter(|p| p["kind"] == kind) { let name = pr["name"].as_str().unwrap_or(""); if ui.selectable_label(name == cur, name).clicked() { @@ -2620,7 +2621,7 @@ pub fn show(app: &mut DesignApp, ctx: &egui::Context) { } crate::rtl::label(ui, crate::i18n::tr(&app.ui.language, "Intent:")); let cur = d.s("intent"); - egui::ComboBox::from_id_salt("ws_intent") + craft_ui::ComboBox::from_id_salt("ws_intent") .selected_text(crate::rtl::widget( ui, crate::i18n::tr(&app.ui.language, designcraft_color::cms::Intent::parse(&cur).map_or("", |i| i.label())), @@ -2731,7 +2732,7 @@ pub fn show(app: &mut DesignApp, ctx: &egui::Context) { } else { mapped.clone() }; - egui::ComboBox::from_id_salt(("imp_map", w)) + craft_ui::ComboBox::from_id_salt(("imp_map", w)) .selected_text(crate::rtl::widget(ui, &shown)) .width(170.0) .show_ui(ui, |ui| { @@ -2852,7 +2853,7 @@ pub fn show(app: &mut DesignApp, ctx: &egui::Context) { }; ui.horizontal(|ui| { crate::rtl::label(ui, crate::i18n::tr(&app.ui.language, "Fitting:")); - egui::ComboBox::from_id_salt("ff_fit").selected_text(crate::rtl::widget(ui, label(&cur))).width(200.0).show_ui( + craft_ui::ComboBox::from_id_salt("ff_fit").selected_text(crate::rtl::widget(ui, label(&cur))).width(200.0).show_ui( ui, |ui| { for v in ["none", "fillProportionally", "fitProportionally", "fitContentToFrame", "centerContent"] { @@ -2910,7 +2911,7 @@ pub fn show(app: &mut DesignApp, ctx: &egui::Context) { "vcard" => crate::i18n::tr(&app.ui.language, "Business Card"), _ => crate::i18n::tr(&app.ui.language, "Web Hyperlink"), }; - egui::ComboBox::from_id_salt("qr_type").selected_text(crate::rtl::widget(ui, label(&cur))).width(160.0).show_ui( + craft_ui::ComboBox::from_id_salt("qr_type").selected_text(crate::rtl::widget(ui, label(&cur))).width(160.0).show_ui( ui, |ui| { for v in ["url", "text", "sms", "email", "vcard"] { @@ -4143,7 +4144,7 @@ fn paragraph_style_options(app: &mut DesignApp, ui: &mut egui::Ui, d: &mut Dialo ui.horizontal(|ui| { crate::rtl::label(ui, crate::i18n::tr(&app.ui.language, "Tag:")); let cur = d.s("x.tag"); - egui::ComboBox::from_id_salt("export_tag") + craft_ui::ComboBox::from_id_salt("export_tag") .selected_text(crate::rtl::widget(ui, crate::i18n::tr(&app.ui.language, &cur))) .show_ui(ui, |ui| { for t in ["[Automatic]", "p", "h1", "h2", "h3", "h4", "h5", "h6", "blockquote", "pre", "li", "figcaption", "aside", "div"] @@ -4167,7 +4168,7 @@ fn paragraph_style_options(app: &mut DesignApp, ui: &mut egui::Ui, d: &mut Dialo egui::Grid::new("psi").num_columns(2).spacing([8.0, 6.0]).show(ui, |ui| { let alignment_label = crate::rtl::label(ui, crate::i18n::tr(&app.ui.language, "Alignment:")); let a: designcraft_doc::Align = serde_json::from_value(cur(d, "p.align", &pv["align"])).unwrap_or_default(); - egui::ComboBox::from_id_salt("psalign") + craft_ui::ComboBox::from_id_salt("psalign") .selected_text(crate::rtl::widget(ui, crate::i18n::tr(&app.ui.language, a.label()))) .width(240.0) .show_ui(ui, |ui| { @@ -4201,7 +4202,7 @@ fn paragraph_style_options(app: &mut DesignApp, ui: &mut egui::Ui, d: &mut Dialo let grid_label = crate::rtl::label(ui, crate::i18n::tr(&app.ui.language, "Align to Grid:")); let g: GridAlign = serde_json::from_value(cur(d, "p.gridAlign", &pv["gridAlign"])).unwrap_or_default(); let shown = GRID_ALIGN.iter().find(|o| o.0 == g).map_or("", |o| o.1); - egui::ComboBox::from_id_salt("psgridalign") + craft_ui::ComboBox::from_id_salt("psgridalign") .selected_text(crate::rtl::widget(ui, crate::i18n::tr(&app.ui.language, shown))) .width(240.0) .show_ui(ui, |ui| { @@ -4265,7 +4266,7 @@ fn paragraph_style_options(app: &mut DesignApp, ui: &mut egui::Ui, d: &mut Dialo .map(str::to_string) .or(style.based_on.clone()) .unwrap_or_else(|| "[No Paragraph Style]".into()); - let combo = egui::ComboBox::from_id_salt("psbased") + let combo = craft_ui::ComboBox::from_id_salt("psbased") .selected_text(crate::rtl::widget(ui, crate::i18n::style_name(&app.ui.language, &based))) .width(220.0) .show_ui(ui, |ui| { @@ -4372,7 +4373,7 @@ fn delete_paragraph_style(app: &mut DesignApp, ui: &mut egui::Ui, d: &mut Dialog d.fields.insert("replaceWith".into(), json!(designcraft_doc::BASIC_PARAGRAPH)); } let replace = d.s("replaceWith"); - egui::ComboBox::from_id_salt("del_para_replace") + craft_ui::ComboBox::from_id_salt("del_para_replace") .selected_text(crate::rtl::widget(ui, crate::i18n::style_name(&app.ui.language, &replace))) .width(260.0) .show_ui(ui, |ui| { @@ -4506,7 +4507,7 @@ fn para_measure(ui: &mut egui::Ui, d: &mut Dialog, pv: &Value, key: &str, unit: fn para_choice(lang: &str, ui: &mut egui::Ui, d: &mut Dialog, pv: &Value, key: &str, opts: &[(&str, &str)]) { let cur = para_value(d, pv, key); let shown = opts.iter().find(|o| cur == o.0).map_or("", |o| crate::i18n::tr(lang, o.1)); - egui::ComboBox::from_id_salt(("pschoice", key)).selected_text(crate::rtl::widget(ui, shown)).width(200.0).show_ui(ui, |ui| { + craft_ui::ComboBox::from_id_salt(("pschoice", key)).selected_text(crate::rtl::widget(ui, shown)).width(200.0).show_ui(ui, |ui| { for (v, label) in opts { if ui.selectable_label(cur == *v, crate::rtl::widget(ui, crate::i18n::tr(lang, label))).clicked() { para_set(d, key, json!(v)); @@ -4543,7 +4544,7 @@ fn para_sides(lang: &str, ui: &mut egui::Ui, d: &mut Dialog, pv: &Value, key: &s /// picked. fn swatch_menu(ui: &mut egui::Ui, doc: &designcraft_doc::Document, id: impl egui::AsIdSalt, current: &str, width: f32) -> Option { let mut picked = None; - egui::ComboBox::from_id_salt(id).selected_text(current).width(width).show_ui(ui, |ui| { + craft_ui::ComboBox::from_id_salt(id).selected_text(current).width(width).show_ui(ui, |ui| { for w in doc.swatches.iter().filter(|w| !w.hidden) { let (c, g) = crate::widgets::swatch_colors(doc, &w.name, 1.0); ui.horizontal(|ui| { @@ -4570,7 +4571,7 @@ fn border_type_menu(lang: &str, ui: &mut egui::Ui, d: &mut Dialog, pv: &Value, d StrokeType::Style { name } => name.clone(), k => crate::i18n::tr(lang, k.label()).to_string(), }; - egui::ComboBox::from_id_salt("psbordertype").selected_text(crate::rtl::widget(ui, shown)).width(160.0).show_ui(ui, |ui| { + craft_ui::ComboBox::from_id_salt("psbordertype").selected_text(crate::rtl::widget(ui, shown)).width(160.0).show_ui(ui, |ui| { for k in BORDER_TYPES { let ty = if k == "dashed" { json!({"kind": k, "pattern": []}) } else { json!({"kind": k}) }; let Ok(t) = serde_json::from_value::(ty.clone()) else { continue }; @@ -4602,7 +4603,7 @@ fn para_corners(lang: &str, ui: &mut egui::Ui, d: &mut Dialog, pv: &Value, key: c.size = if v.is_finite() { v.clamp(0.0, 10_000.0) } else { 0.0 }; changed = true; } - egui::ComboBox::from_id_salt(("pscornershape", key, i)) + craft_ui::ComboBox::from_id_salt(("pscornershape", key, i)) .selected_text(crate::rtl::widget(ui, crate::i18n::tr(lang, c.shape.label()))) .width(130.0) .show_ui(ui, |ui| { @@ -4761,7 +4762,7 @@ fn drop_cap_fields(lang: &str, ui: &mut egui::Ui, d: &mut Dialog, pv: &Value, cn crate::rtl::label(ui, crate::i18n::tr(lang, "Character Style:")); let style = field_or(d, "p.dropCapStyle", &pv["dropCapStyle"]).as_str().unwrap_or("").to_string(); let style = if style.is_empty() { designcraft_doc::NO_CHAR_STYLE.to_string() } else { style }; - egui::ComboBox::from_id_salt("ps_dropcap_style") + craft_ui::ComboBox::from_id_salt("ps_dropcap_style") .selected_text(crate::rtl::widget(ui, crate::i18n::style_name(lang, &style))) .width(160.0) .show_ui(ui, |ui| { @@ -4812,19 +4813,19 @@ fn nested_style_rows(lang: &str, ui: &mut egui::Ui, d: &mut Dialog, pv: &Value, fn nested_style_row(lang: &str, ui: &mut egui::Ui, ns: &mut Value, i: usize, cnames: &[String]) -> (bool, bool) { let mut changed = false; let style = ns["style"].as_str().unwrap_or("[None]").to_string(); - egui::ComboBox::from_id_salt(("ns_style", i)).selected_text(crate::rtl::widget(ui, crate::i18n::style_name(lang, &style))).width(110.0).show_ui( - ui, - |ui| { + craft_ui::ComboBox::from_id_salt(("ns_style", i)) + .selected_text(crate::rtl::widget(ui, crate::i18n::style_name(lang, &style))) + .width(110.0) + .show_ui(ui, |ui| { for c in cnames { if ui.selectable_label(*c == style, crate::rtl::widget(ui, crate::i18n::style_name(lang, c))).clicked() { ns["style"] = json!(c); changed = true; } } - }, - ); + }); let through = ns["through"].as_bool().unwrap_or(true); - egui::ComboBox::from_id_salt(("ns_thr", i)) + craft_ui::ComboBox::from_id_salt(("ns_thr", i)) .selected_text(crate::rtl::widget(ui, crate::i18n::tr(lang, if through { "through" } else { "up to" }))) .width(70.0) .show_ui(ui, |ui| { @@ -4846,7 +4847,7 @@ fn nested_style_row(lang: &str, ui: &mut egui::Ui, ns: &mut Value, i: usize, cna count_value(ui, ns, &mut changed); } let kind = ns["until"]["kind"].as_str().unwrap_or("words").to_string(); - egui::ComboBox::from_id_salt(("ns_until", i)) + craft_ui::ComboBox::from_id_salt(("ns_until", i)) .selected_text(crate::rtl::widget( ui, if crate::i18n::category_first(lang) { format!("{}:", crate::i18n::tr(lang, &kind)) } else { crate::i18n::tr(lang, &kind).to_owned() }, @@ -4910,17 +4911,17 @@ fn nested_line_style_rows(lang: &str, ui: &mut egui::Ui, d: &mut Dialog, pv: &Va fn nested_line_style_row(lang: &str, ui: &mut egui::Ui, l: &mut Value, i: usize, cnames: &[String]) -> (bool, bool) { let mut changed = false; let style = l["style"].as_str().unwrap_or("").to_string(); - egui::ComboBox::from_id_salt(("nls_style", i)).selected_text(crate::rtl::widget(ui, crate::i18n::style_name(lang, &style))).width(130.0).show_ui( - ui, - |ui| { + craft_ui::ComboBox::from_id_salt(("nls_style", i)) + .selected_text(crate::rtl::widget(ui, crate::i18n::style_name(lang, &style))) + .width(130.0) + .show_ui(ui, |ui| { for c in cnames { if ui.selectable_label(*c == style, crate::rtl::widget(ui, crate::i18n::style_name(lang, c))).clicked() { l["style"] = json!(c); changed = true; } } - }, - ); + }); crate::rtl::label( ui, if crate::i18n::category_first(lang) { format!("{}:", crate::i18n::tr(lang, "lines")) } else { crate::i18n::tr(lang, "for").to_owned() }, @@ -4953,7 +4954,7 @@ fn grep_styles(lang: &str, ui: &mut egui::Ui, d: &mut Dialog, pv: &Value, cnames ui.horizontal(|ui| { crate::rtl::label(ui, crate::i18n::tr(lang, "Apply Style:")); let style = g["style"].as_str().unwrap_or("").to_string(); - egui::ComboBox::from_id_salt(("gs_style", i)) + craft_ui::ComboBox::from_id_salt(("gs_style", i)) .selected_text(crate::rtl::widget(ui, crate::i18n::style_name(lang, &style))) .width(110.0) .show_ui(ui, |ui| { @@ -5068,7 +5069,7 @@ fn span_columns(lang: &str, ui: &mut egui::Ui, d: &mut Dialog, pv: &Value, doc: // "Span Columns" names both the section and this option, which some languages word differently. let kind_label = |k: &'static str| -> &'static str { crate::i18n::tr_context(lang, k, "paragraphLayout") }; let shown = kinds.iter().find(|k| same(&k.1, &cur)).map_or("", |k| kind_label(k.0)); - egui::ComboBox::from_id_salt("psspankind").selected_text(crate::rtl::widget(ui, shown)).width(200.0).show_ui(ui, |ui| { + craft_ui::ComboBox::from_id_salt("psspankind").selected_text(crate::rtl::widget(ui, shown)).width(200.0).show_ui(ui, |ui| { for (label, v) in kinds { if ui.selectable_label(same(&v, &cur), crate::rtl::widget(ui, kind_label(label))).clicked() && !same(&v, &cur) { next = Some(v); @@ -5081,7 +5082,7 @@ fn span_columns(lang: &str, ui: &mut egui::Ui, d: &mut Dialog, pv: &Value, doc: SpanColumns::Span(n) => { crate::rtl::label(ui, crate::i18n::tr(lang, "Columns:")); let shown = if n == 0 { crate::i18n::tr(lang, "All").to_string() } else { n.to_string() }; - egui::ComboBox::from_id_salt("psspann").selected_text(crate::rtl::widget(ui, shown)).width(80.0).show_ui(ui, |ui| { + craft_ui::ComboBox::from_id_salt("psspann").selected_text(crate::rtl::widget(ui, shown)).width(80.0).show_ui(ui, |ui| { if ui.selectable_label(n == 0, crate::rtl::widget(ui, crate::i18n::tr(lang, "All"))).clicked() { next = Some(SpanColumns::Span(0)); } @@ -5174,9 +5175,10 @@ impl ListUi<'_> { /// A character style for a bullet or number (`key`), [None] first. fn list_char_style(lang: &str, ui: &mut egui::Ui, d: &mut Dialog, pv: &Value, key: &str, styles: &[String]) { let cur = para_value(d, pv, key).as_str().unwrap_or(designcraft_doc::NO_CHAR_STYLE).to_string(); - egui::ComboBox::from_id_salt(("pslistcs", key)).selected_text(crate::rtl::widget(ui, crate::i18n::style_name(lang, &cur))).width(200.0).show_ui( - ui, - |ui| { + craft_ui::ComboBox::from_id_salt(("pslistcs", key)) + .selected_text(crate::rtl::widget(ui, crate::i18n::style_name(lang, &cur))) + .width(200.0) + .show_ui(ui, |ui| { for n in std::iter::once(designcraft_doc::NO_CHAR_STYLE) .chain(styles.iter().map(String::as_str).filter(|n| *n != designcraft_doc::NO_CHAR_STYLE)) { @@ -5184,8 +5186,7 @@ fn list_char_style(lang: &str, ui: &mut egui::Ui, d: &mut Dialog, pv: &Value, ke para_set(d, key, json!(n)); } } - }, - ); + }); } /// The bullet character: common bullets, a field, and the font's glyphs in a grid to pick from. @@ -5244,7 +5245,7 @@ fn bullet_font(lang: &str, ui: &mut egui::Ui, d: &mut Dialog, pv: &Value, lu: &L let font = para_value(d, pv, "bulletFont").as_str().unwrap_or("").to_string(); let inherit = crate::i18n::tr(lang, "(Paragraph Font)"); crate::rtl::label(ui, crate::i18n::tr(lang, "Font Family:")); - egui::ComboBox::from_id_salt("psbulletfont").selected_text(if font.is_empty() { inherit } else { font.as_str() }).width(200.0).show_ui( + craft_ui::ComboBox::from_id_salt("psbulletfont").selected_text(if font.is_empty() { inherit } else { font.as_str() }).width(200.0).show_ui( ui, |ui| { if ui.selectable_label(font.is_empty(), inherit).clicked() { @@ -5263,7 +5264,7 @@ fn bullet_font(lang: &str, ui: &mut egui::Ui, d: &mut Dialog, pv: &Value, lu: &L crate::rtl::label(ui, crate::i18n::tr(lang, "Font Style:")); let style = para_value(d, pv, "bulletFontStyle").as_str().unwrap_or("").to_string(); ui.add_enabled_ui(!font.is_empty(), |ui| { - egui::ComboBox::from_id_salt("psbulletstyle").selected_text(style.as_str()).width(200.0).show_ui(ui, |ui| { + craft_ui::ComboBox::from_id_salt("psbulletstyle").selected_text(style.as_str()).width(200.0).show_ui(ui, |ui| { for st in lu.fonts.styles(&font) { if ui.selectable_label(st == style, &st).clicked() { para_set(d, "bulletFontStyle", json!(st)); @@ -5306,7 +5307,7 @@ fn bullets_and_numbering(lang: &str, ui: &mut egui::Ui, d: &mut Dialog, pv: &Val let list = para_value(d, pv, "listName").as_str().unwrap_or("").to_string(); // "" is the story's own list. let name = |n: &str| if n.is_empty() { crate::i18n::tr(lang, "[Default]").to_string() } else { n.to_string() }; - egui::ComboBox::from_id_salt("pslist").selected_text(name(&list)).width(200.0).show_ui(ui, |ui| { + craft_ui::ComboBox::from_id_salt("pslist").selected_text(name(&list)).width(200.0).show_ui(ui, |ui| { for n in std::iter::once("").chain(doc.settings.lists.iter().map(|l| l.name.as_str())) { if ui.selectable_label(n == list, name(n)).clicked() { para_set(d, "listName", json!(n)); @@ -5325,7 +5326,7 @@ fn bullets_and_numbering(lang: &str, ui: &mut egui::Ui, d: &mut Dialog, pv: &Val let start = para_value(d, pv, "startAt").as_u64(); ui.horizontal(|ui| { let shown = crate::i18n::tr(lang, if start.is_some() { "Start At" } else { "Continue from Previous Number" }); - egui::ComboBox::from_id_salt("psmode").selected_text(crate::rtl::widget(ui, shown)).width(200.0).show_ui(ui, |ui| { + craft_ui::ComboBox::from_id_salt("psmode").selected_text(crate::rtl::widget(ui, shown)).width(200.0).show_ui(ui, |ui| { if ui .selectable_label(start.is_none(), crate::rtl::widget(ui, crate::i18n::tr(lang, "Continue from Previous Number"))) .clicked() @@ -5352,16 +5353,16 @@ fn bullets_and_numbering(lang: &str, ui: &mut egui::Ui, d: &mut Dialog, pv: &Val para_check(ui, d, pv, "restartNumbers", crate::i18n::tr(lang, "Restart Numbers at This Level After:")); let after = para_value(d, pv, "restartAfterLevel").as_u64().unwrap_or(0) as u32; let label = |n: u32| if n == 0 { crate::i18n::tr(lang, "Any Previous Level").to_string() } else { n.to_string() }; - egui::ComboBox::from_id_salt("psrestartafter").selected_text(crate::rtl::widget(ui, label(after))).width(140.0).show_ui( - ui, - |ui| { + craft_ui::ComboBox::from_id_salt("psrestartafter") + .selected_text(crate::rtl::widget(ui, label(after))) + .width(140.0) + .show_ui(ui, |ui| { for n in 0..level { if ui.selectable_label(n == after, crate::rtl::widget(ui, label(n))).clicked() { para_set(d, "restartAfterLevel", json!(n)); } } - }, - ); + }); }); }); ui.end_row(); @@ -5497,7 +5498,7 @@ fn character_style_general( text_field_named(ui, d, "rename", 220.0, crate::i18n::tr(lang, "Style Name")); ui.end_row(); crate::rtl::label(ui, crate::i18n::tr(lang, "Based On:")); - let combo = egui::ComboBox::from_id_salt("csbased") + let combo = craft_ui::ComboBox::from_id_salt("csbased") .selected_text(crate::rtl::widget(ui, crate::i18n::style_name(lang, based))) .width(220.0) .show_ui(ui, |ui| { @@ -5656,7 +5657,7 @@ fn char_number(ui: &mut egui::Ui, d: &mut Dialog, cf: &CharFields, key: &str, su fn char_choice(lang: &str, ui: &mut egui::Ui, d: &mut Dialog, cf: &CharFields, key: &str, opts: &[(Value, &str)]) { let cur = cf.get(d, key); let shown = opts.iter().find(|o| o.0 == cur).map_or("", |o| crate::i18n::tr(lang, o.1)); - egui::ComboBox::from_id_salt(("cs", key)).selected_text(crate::rtl::widget(ui, shown)).width(200.0).show_ui(ui, |ui| { + craft_ui::ComboBox::from_id_salt(("cs", key)).selected_text(crate::rtl::widget(ui, shown)).width(200.0).show_ui(ui, |ui| { if cf.sparse && ui.selectable_label(cur.is_null(), " ").clicked() { cf.set(d, key, Value::Null); } @@ -5703,22 +5704,29 @@ fn basic_character_formats(app: &mut DesignApp, ui: &mut egui::Ui, d: &mut Dialo crate::rtl::label(ui, crate::i18n::tr(lang, "Font Family:")); let fam = cf.get(d, "fontFamily").as_str().unwrap_or("").to_string(); let shown = if fam.is_empty() { "—".to_string() } else { crate::panels::font_label(app, &menu, &fam) }; - egui::ComboBox::from_id_salt("psfam") + let picked = craft_ui::ComboBox::from_id_salt("psfam") .selected_text(shown) .width(200.0) .height(440.0) .close_behavior(egui::PopupCloseBehavior::CloseOnClickOutside) - .show_ui(ui, |ui| { - if cf.sparse && ui.selectable_label(fam.is_empty(), " ").clicked() { - cf.set(d, "fontFamily", Value::Null); - } - if let Some(f) = crate::panels::font_menu_body(app, ui, &menu, &fam, 200.0) { - cf.set(d, "fontFamily", json!(f.family)); - if let Some(style) = f.style { - cf.set(d, "fontStyle", json!(style)); + .show_ui_with_header( + ui, + |ui| { + if cf.sparse && ui.selectable_label(fam.is_empty(), " ").clicked() { + cf.set(d, "fontFamily", Value::Null); } - } - }); + crate::panels::font_menu_header(app, ui, &menu, &fam, 200.0) + }, + crate::panels::font_menu_list, + ) + .inner + .flatten(); + if let Some(f) = picked { + cf.set(d, "fontFamily", json!(f.family)); + if let Some(style) = f.style { + cf.set(d, "fontStyle", json!(style)); + } + } ui.end_row(); crate::rtl::label(ui, crate::i18n::tr(lang, "Font Style:")); let sty = cf.get(d, "fontStyle").as_str().unwrap_or("").to_string(); @@ -5729,7 +5737,7 @@ fn basic_character_formats(app: &mut DesignApp, ui: &mut egui::Ui, d: &mut Dialo cf.set(d, "fontStyle", if s.trim().is_empty() { Value::Null } else { json!(s) }); } } else { - egui::ComboBox::from_id_salt("pssty").selected_text(&sty).width(200.0).show_ui(ui, |ui| { + craft_ui::ComboBox::from_id_salt("pssty").selected_text(&sty).width(200.0).show_ui(ui, |ui| { if cf.sparse && ui.selectable_label(sty.is_empty(), " ").clicked() { cf.set(d, "fontStyle", Value::Null); } @@ -5837,7 +5845,7 @@ fn advanced_character_formats(lang: &str, ui: &mut egui::Ui, d: &mut Dialog, cf: let labels: Vec = crate::panels::properties::DIGIT_OPTIONS.iter().map(|o| crate::panels::properties::digit_label(lang, *o)).collect(); let cur = cf.get(d, "digits"); let shown = crate::panels::properties::DIGIT_OPTIONS.iter().position(|o| json!(o.0) == cur).map_or(String::new(), |i| labels[i].clone()); - egui::ComboBox::from_id_salt(("cs", "digits")).selected_text(crate::rtl::widget(ui, &shown)).width(200.0).show_ui(ui, |ui| { + craft_ui::ComboBox::from_id_salt(("cs", "digits")).selected_text(crate::rtl::widget(ui, &shown)).width(200.0).show_ui(ui, |ui| { if cf.sparse && ui.selectable_label(cur.is_null(), " ").clicked() { cf.set(d, "digits", Value::Null); } @@ -5913,14 +5921,18 @@ fn open_type_features(lang: &str, ui: &mut egui::Ui, d: &mut Dialog, cf: &CharFi crate::rtl::label(ui, crate::i18n::tr(lang, "Figure Style:")); let fig = otf::figures(&list); let shown = otf::FIGURES.iter().find(|f| f.0 == fig).map_or("", |f| f.1); - egui::ComboBox::from_id_salt("csfig").selected_text(crate::rtl::widget(ui, crate::i18n::tr(lang, shown))).width(180.0).show_ui(ui, |ui| { - for (id, label, ..) in otf::FIGURES { - if ui.selectable_label(fig == *id, crate::rtl::widget(ui, crate::i18n::tr(lang, label))).clicked() && otf::set_figures(&mut list, id) - { - changed = true; + craft_ui::ComboBox::from_id_salt("csfig").selected_text(crate::rtl::widget(ui, crate::i18n::tr(lang, shown))).width(180.0).show_ui( + ui, + |ui| { + for (id, label, ..) in otf::FIGURES { + if ui.selectable_label(fig == *id, crate::rtl::widget(ui, crate::i18n::tr(lang, label))).clicked() + && otf::set_figures(&mut list, id) + { + changed = true; + } } - } - }); + }, + ); }); ui.add_space(4.0); crate::rtl::label(ui, crate::i18n::tr(lang, "Stylistic Sets")); @@ -5995,7 +6007,7 @@ fn delete_character_style(app: &mut DesignApp, ui: &mut egui::Ui, d: &mut Dialog d.fields.insert("replaceWith".into(), json!(designcraft_doc::NO_CHAR_STYLE)); } let replace = d.s("replaceWith"); - egui::ComboBox::from_id_salt("del_char_replace") + craft_ui::ComboBox::from_id_salt("del_char_replace") .selected_text(crate::rtl::widget(ui, crate::i18n::style_name(&app.ui.language, &replace))) .width(260.0) .show_ui(ui, |ui| { @@ -6010,7 +6022,7 @@ fn delete_character_style(app: &mut DesignApp, ui: &mut egui::Ui, d: &mut Dialog fn combo(ui: &mut egui::Ui, d: &mut Dialog, key: &str, opts: &[(&str, &str)]) { let cur = d.s(key); let shown = opts.iter().find(|o| o.0 == cur).map_or(cur.as_str(), |o| o.1).to_string(); - egui::ComboBox::from_id_salt(key).selected_text(crate::rtl::widget(ui, &shown)).width(170.0).show_ui(ui, |ui| { + craft_ui::ComboBox::from_id_salt(key).selected_text(crate::rtl::widget(ui, &shown)).width(170.0).show_ui(ui, |ui| { for (v, label) in opts { if ui.selectable_label(cur == *v, crate::rtl::widget(ui, *label)).clicked() { d.fields.insert(key.into(), json!(v)); @@ -6351,7 +6363,7 @@ fn paragraph_rules(app: &DesignApp, ui: &mut egui::Ui, d: &mut Dialog, prefix: & }; ui.horizontal(|ui| { let shown = RULES.iter().find(|r| r.0 == which).map_or(which, |r| r.1); - egui::ComboBox::from_id_salt("rule_which").selected_text(crate::rtl::widget(ui, crate::i18n::tr(lang, shown))).width(130.0).show_ui( + craft_ui::ComboBox::from_id_salt("rule_which").selected_text(crate::rtl::widget(ui, crate::i18n::tr(lang, shown))).width(130.0).show_ui( ui, |ui| { for (v, label) in RULES { @@ -6387,7 +6399,7 @@ fn paragraph_rules(app: &DesignApp, ui: &mut egui::Ui, d: &mut Dialog, prefix: & } crate::rtl::label(ui, crate::i18n::tr(lang, "Width:")); let column = rule["columnWidth"].as_bool().unwrap_or(true); - egui::ComboBox::from_id_salt(("rule_width", which)) + craft_ui::ComboBox::from_id_salt(("rule_width", which)) .selected_text(crate::rtl::widget(ui, crate::i18n::tr(lang, if column { "Column" } else { "Text" }))) .width(130.0) .show_ui(ui, |ui| { @@ -6794,7 +6806,7 @@ fn toc_params(app: &DesignApp, d: &Dialog) -> Value { fn toc_style_combo(ui: &mut egui::Ui, salt: &str, styles: &[String], auto: &str, cur: &mut String) -> bool { let shown = if cur.is_empty() { auto.to_string() } else { cur.clone() }; let mut changed = false; - egui::ComboBox::from_id_salt(("toc_style", salt)).selected_text(shown).width(200.0).show_ui(ui, |ui| { + craft_ui::ComboBox::from_id_salt(("toc_style", salt)).selected_text(shown).width(200.0).show_ui(ui, |ui| { if ui.selectable_label(cur.is_empty(), auto).clicked() { cur.clear(); changed = true; @@ -6885,7 +6897,7 @@ fn toc_dialog(app: &mut DesignApp, ui: &mut egui::Ui, d: &mut Dialog) { crate::rtl::label(ui, tr("Page Number:")); let choices = [("after", "After Entry"), ("before", "Before Entry"), ("none", "No Page Number")]; let cur = choices.iter().find(|(v, _)| *v == r.page_number).map_or("After Entry", |(_, l)| l); - egui::ComboBox::from_id_salt("toc_page_number").selected_text(tr(cur)).width(200.0).show_ui(ui, |ui| { + craft_ui::ComboBox::from_id_salt("toc_page_number").selected_text(tr(cur)).width(200.0).show_ui(ui, |ui| { for (v, l) in choices { if ui.selectable_label(r.page_number == v, tr(l)).clicked() { r.page_number = v.into(); @@ -6904,7 +6916,7 @@ fn toc_dialog(app: &mut DesignApp, ui: &mut egui::Ui, d: &mut Dialog) { ui.checkbox(&mut r.sort, tr("Sort Entries in Alphabetical Order")); ui.end_row(); crate::rtl::label(ui, tr("Level:")); - egui::ComboBox::from_id_salt("toc_level").selected_text(r.level.to_string()).width(60.0).show_ui(ui, |ui| { + craft_ui::ComboBox::from_id_salt("toc_level").selected_text(r.level.to_string()).width(60.0).show_ui(ui, |ui| { for l in 1..=9u8 { if ui.selectable_label(r.level == l, l.to_string()).clicked() { r.level = l; diff --git a/crates/ui-egui/src/dialogs/options.rs b/crates/ui-egui/src/dialogs/options.rs index 28005e7ac..191436fa4 100644 --- a/crates/ui-egui/src/dialogs/options.rs +++ b/crates/ui-egui/src/dialogs/options.rs @@ -130,7 +130,7 @@ pub fn swatch_options(app: &DesignApp, ui: &mut egui::Ui, d: &mut Dialog) { if kind == "color" { caption(ui, lang, "Color Type:"); let cur = d.s("colorType"); - let combo = egui::ComboBox::from_id_salt("swatch_color_type") + let combo = craft_ui::ComboBox::from_id_salt("swatch_color_type") .selected_text(crate::rtl::widget(ui, tr(lang, if cur == "spot" { "Spot" } else { "Process" }))) .show_ui(ui, |ui| { for (v, l) in [("process", "Process"), ("spot", "Spot")] { @@ -149,9 +149,9 @@ pub fn swatch_options(app: &DesignApp, ui: &mut egui::Ui, d: &mut Dialog) { "gray" => "Gray", _ => "CMYK", }; - let combo = egui::ComboBox::from_id_salt("swatch_color_mode").selected_text(crate::rtl::widget(ui, tr(lang, label(&cur)))).show_ui( - ui, - |ui| { + let combo = craft_ui::ComboBox::from_id_salt("swatch_color_mode") + .selected_text(crate::rtl::widget(ui, tr(lang, label(&cur)))) + .show_ui(ui, |ui| { for m in ["cmyk", "rgb", "gray"] { if ui.selectable_label(cur == m, crate::rtl::widget(ui, tr(lang, label(m)))).clicked() && cur != m @@ -160,8 +160,7 @@ pub fn swatch_options(app: &DesignApp, ui: &mut egui::Ui, d: &mut Dialog) { set_color(&mut d.fields, c, Some(m)); } } - }, - ); + }); let _ = crate::widgets::name_combo(combo, tr(lang, "Color Mode")); ui.end_row(); match d.s("mode").as_str() { @@ -283,7 +282,7 @@ pub fn layer_options(app: &DesignApp, ui: &mut egui::Ui, d: &mut Dialog) { ui.end_row(); caption(ui, lang, "Color:"); ui.horizontal(|ui| { - let combo = egui::ComboBox::from_id_salt("layer_color") + let combo = craft_ui::ComboBox::from_id_salt("layer_color") .selected_text(crate::rtl::widget(ui, tr(lang, named.unwrap_or("Custom")))) .width(140.0) .show_ui(ui, |ui| { diff --git a/crates/ui-egui/src/dialogs/style_cjk.rs b/crates/ui-egui/src/dialogs/style_cjk.rs index 674cf44a7..acb6a6bc7 100644 --- a/crates/ui-egui/src/dialogs/style_cjk.rs +++ b/crates/ui-egui/src/dialogs/style_cjk.rs @@ -143,33 +143,40 @@ fn font_fields(app: &mut DesignApp, ui: &mut egui::Ui, d: &mut Dialog, cf: &Char }; crate::rtl::label(ui, tr(lang, "Font Family:")); ui.add_enabled_ui(enabled, |ui| { - egui::ComboBox::from_id_salt(("cjkfam", fam_key)) + let picked = craft_ui::ComboBox::from_id_salt(("cjkfam", fam_key)) .selected_text(shown) .width(200.0) .height(440.0) .close_behavior(egui::PopupCloseBehavior::CloseOnClickOutside) - .show_ui(ui, |ui| { - if cf.sparse && ui.selectable_label(raw.is_null(), " ").clicked() { - cf.set(d, fam_key, Value::Null); - cf.set(d, style_key, Value::Null); - } - if ui.selectable_label(!raw.is_null() && fam.is_empty(), crate::rtl::widget(ui, parent.as_str())).clicked() { - cf.set(d, fam_key, json!("")); - cf.set(d, style_key, json!("")); - } - if let Some(f) = crate::panels::font_menu_body(app, ui, &menu, &fam, 200.0) { - cf.set(d, fam_key, json!(f.family)); - if let Some(style) = f.style { - cf.set(d, style_key, json!(style)); + .show_ui_with_header( + ui, + |ui| { + if cf.sparse && ui.selectable_label(raw.is_null(), " ").clicked() { + cf.set(d, fam_key, Value::Null); + cf.set(d, style_key, Value::Null); } - } - }); + if ui.selectable_label(!raw.is_null() && fam.is_empty(), crate::rtl::widget(ui, parent.as_str())).clicked() { + cf.set(d, fam_key, json!("")); + cf.set(d, style_key, json!("")); + } + crate::panels::font_menu_header(app, ui, &menu, &fam, 200.0) + }, + crate::panels::font_menu_list, + ) + .inner + .flatten(); + if let Some(f) = picked { + cf.set(d, fam_key, json!(f.family)); + if let Some(style) = f.style { + cf.set(d, style_key, json!(style)); + } + } }); ui.end_row(); crate::rtl::label(ui, tr(lang, "Font Style:")); let sty = cf.get(d, style_key).as_str().unwrap_or("").to_string(); ui.add_enabled_ui(enabled && !fam.is_empty(), |ui| { - egui::ComboBox::from_id_salt(("cjksty", style_key)).selected_text(&sty).width(200.0).show_ui(ui, |ui| { + craft_ui::ComboBox::from_id_salt(("cjksty", style_key)).selected_text(&sty).width(200.0).show_ui(ui, |ui| { for s in fonts.styles(&fam) { if ui.selectable_label(s == sty, &s).clicked() { cf.set(d, style_key, json!(s)); @@ -527,7 +534,7 @@ fn kenten(app: &mut DesignApp, ui: &mut egui::Ui, d: &mut Dialog, cf: &CharField Some(false) => tr(lang, "None").to_string(), Some(true) => tr(lang, kenten_label(kind)).to_string(), }; - egui::ComboBox::from_id_salt("cjkkententype").selected_text(crate::rtl::widget(ui, shown)).width(200.0).show_ui(ui, |ui| { + craft_ui::ComboBox::from_id_salt("cjkkententype").selected_text(crate::rtl::widget(ui, shown)).width(200.0).show_ui(ui, |ui| { if cf.sparse && ui.selectable_label(on.is_none(), " ").clicked() { for key in ["kenten", "kentenKind", "kentenCharacter"] { cf.set(d, key, Value::Null); @@ -654,7 +661,7 @@ fn para_value(d: &Dialog, pv: &Value, key: &str) -> Value { /// A paragraph attribute from `opts` (value, label); `current` is the value shown. fn para_choice(lang: &str, ui: &mut egui::Ui, d: &mut Dialog, key: &str, current: &str, opts: &[(&str, &str)]) { let shown = opts.iter().find(|o| o.0 == current).map_or("", |o| crate::i18n::tr(lang, o.1)); - egui::ComboBox::from_id_salt(("cjkp", key)).selected_text(crate::rtl::widget(ui, shown)).width(220.0).show_ui(ui, |ui| { + craft_ui::ComboBox::from_id_salt(("cjkp", key)).selected_text(crate::rtl::widget(ui, shown)).width(220.0).show_ui(ui, |ui| { for (v, label) in opts { if ui.selectable_label(*v == current, crate::rtl::widget(ui, crate::i18n::tr(lang, label))).clicked() { d.fields.insert(format!("p.{key}"), json!(v)); @@ -731,7 +738,7 @@ fn japanese_composition(lang: &str, ui: &mut egui::Ui, d: &mut Dialog, cf: &Char // `null`: no set given, so the composer's default rules apply. let name = para_value(d, pv, "kinsoku").get("name").and_then(Value::as_str).map(str::to_string); let shown = name.as_deref().map_or_else(|| crate::i18n::tr(lang, "Default Rules").to_string(), |n| kinsoku_label(lang, n)); - egui::ComboBox::from_id_salt("cjkkinsoku").selected_text(crate::rtl::widget(ui, shown)).width(220.0).show_ui(ui, |ui| { + craft_ui::ComboBox::from_id_salt("cjkkinsoku").selected_text(crate::rtl::widget(ui, shown)).width(220.0).show_ui(ui, |ui| { // `style.paragraph.edit` takes `null` as the default rules, also over a Based On set. if ui.selectable_label(name.is_none(), crate::rtl::widget(ui, crate::i18n::tr(lang, "Default Rules"))).clicked() { d.fields.insert("p.kinsoku".into(), Value::Null); @@ -780,7 +787,7 @@ fn japanese_composition(lang: &str, ui: &mut egui::Ui, d: &mut Dialog, cf: &Char let sets = mojikumi_sets(doc, ¤t); let label = |l: &str| if l.is_empty() { crate::i18n::tr(lang, "None").to_string() } else { l.to_string() }; let shown = sets.iter().find(|s| s.0 == current).map_or_else(String::new, |s| label(s.1.as_str())); - egui::ComboBox::from_id_salt("cjkmojikumi").selected_text(crate::rtl::widget(ui, shown)).width(220.0).show_ui(ui, |ui| { + craft_ui::ComboBox::from_id_salt("cjkmojikumi").selected_text(crate::rtl::widget(ui, shown)).width(220.0).show_ui(ui, |ui| { for (v, l) in &sets { if ui.selectable_label(*v == current, crate::rtl::widget(ui, label(l.as_str()))).clicked() { d.fields.insert("p.mojikumi".into(), json!(v)); @@ -834,7 +841,7 @@ fn grid_settings(lang: &str, ui: &mut egui::Ui, d: &mut Dialog, pv: &Value) { grid("cjkgrid").show(ui, |ui| { crate::rtl::label(ui, tr(lang, "Grid Alignment:")); let shown = if on { REFERENCES.iter().find(|r| r.0 == reference).map_or("", |r| r.1) } else { "None" }; - egui::ComboBox::from_id_salt("cjkgridalign").selected_text(crate::rtl::widget(ui, tr(lang, shown))).width(220.0).show_ui(ui, |ui| { + craft_ui::ComboBox::from_id_salt("cjkgridalign").selected_text(crate::rtl::widget(ui, tr(lang, shown))).width(220.0).show_ui(ui, |ui| { if ui.selectable_label(!on, crate::rtl::widget(ui, tr(lang, "None"))).clicked() { set(d, GridAlign::None); } diff --git a/crates/ui-egui/src/effects_dialog.rs b/crates/ui-egui/src/effects_dialog.rs index bcfe4b26a..0fc2b4efc 100644 --- a/crates/ui-egui/src/effects_dialog.rs +++ b/crates/ui-egui/src/effects_dialog.rs @@ -250,7 +250,7 @@ fn swatch(app: &DesignApp, ui: &mut egui::Ui, d: &mut Dialog, path: &[&str]) { .session .active() .map_or_else(Vec::new, |st| st.doc.swatches.iter().filter(|w| !w.hidden || w.name == cur).map(|w| w.name.clone()).collect()); - egui::ComboBox::from_id_salt(("fx_swatch", path.join("."))).selected_text(&cur).width(150.0).show_ui(ui, |ui| { + craft_ui::ComboBox::from_id_salt(("fx_swatch", path.join("."))).selected_text(&cur).width(150.0).show_ui(ui, |ui| { for n in names { if ui.selectable_label(n == cur, &n).clicked() { set(d, path, json!(n)); @@ -299,7 +299,7 @@ fn page_ui(app: &DesignApp, ui: &mut egui::Ui, d: &mut Dialog, page: &str) { grid("t").show(ui, |ui| { row(ui, "Mode:"); let cur = d.fields.get("blend").and_then(Value::as_str).unwrap_or("Normal").to_string(); - egui::ComboBox::from_id_salt("fx_blend").selected_text(tr(&cur)).width(150.0).show_ui(ui, |ui| { + craft_ui::ComboBox::from_id_salt("fx_blend").selected_text(tr(&cur)).width(150.0).show_ui(ui, |ui| { for m in designcraft_color::BlendMode::ALL { if ui.selectable_label(m.label() == cur, tr(m.label())).clicked() { d.fields.insert("blend".into(), json!(m.label())); @@ -367,7 +367,7 @@ fn page_ui(app: &DesignApp, ui: &mut egui::Ui, d: &mut Dialog, page: &str) { serde_json::to_value(m).ok().and_then(|v| v.as_str().map(str::to_string)).unwrap_or_default() }; let shown = designcraft_color::BlendMode::ALL.iter().find(|m| label(m) == cur).map_or("Screen", |m| m.label()); - egui::ComboBox::from_id_salt("fx_glow_mode").selected_text(tr(shown)).width(150.0).show_ui(ui, |ui| { + craft_ui::ComboBox::from_id_salt("fx_glow_mode").selected_text(tr(shown)).width(150.0).show_ui(ui, |ui| { for m in designcraft_color::BlendMode::ALL { if ui.selectable_label(label(&m) == cur, tr(m.label())).clicked() { set(d, &["innerGlow", "mode"], json!(label(&m))); @@ -395,7 +395,7 @@ fn page_ui(app: &DesignApp, ui: &mut egui::Ui, d: &mut Dialog, page: &str) { ui.end_row(); row(ui, "Technique:"); let cur = d.fields.get("fx").and_then(|f| f["innerGlow"]["technique"].as_str()).unwrap_or("softer").to_string(); - egui::ComboBox::from_id_salt("fx_technique").selected_text(tr(if cur == "precise" { "Precise" } else { "Softer" })).show_ui( + craft_ui::ComboBox::from_id_salt("fx_technique").selected_text(tr(if cur == "precise" { "Precise" } else { "Softer" })).show_ui( ui, |ui| { for (v, l) in [("softer", "Softer"), ("precise", "Precise")] { @@ -409,7 +409,7 @@ fn page_ui(app: &DesignApp, ui: &mut egui::Ui, d: &mut Dialog, page: &str) { row(ui, "Source:"); let mut center = d.fields.get("fx").is_some_and(|f| f["innerGlow"]["center"].as_bool().unwrap_or(false)); let label = |c: bool| tr(if c { "Center" } else { "Edge" }); - egui::ComboBox::from_id_salt("fx_source").selected_text(label(center)).show_ui(ui, |ui| { + craft_ui::ComboBox::from_id_salt("fx_source").selected_text(label(center)).show_ui(ui, |ui| { for c in [false, true] { if ui.selectable_value(&mut center, c, label(c)).clicked() { set(d, &["innerGlow", "center"], json!(c)); @@ -490,7 +490,7 @@ fn page_ui(app: &DesignApp, ui: &mut egui::Ui, d: &mut Dialog, page: &str) { "rounded" => "Rounded", _ => "Diffused", }; - egui::ComboBox::from_id_salt("fx_corners").selected_text(tr(shown)).show_ui(ui, |ui| { + craft_ui::ComboBox::from_id_salt("fx_corners").selected_text(tr(shown)).show_ui(ui, |ui| { for (v, l) in [("sharp", "Sharp"), ("rounded", "Rounded"), ("diffused", "Diffused")] { if ui.selectable_label(cur == v, tr(l)).clicked() { set(d, &["featherOptions", "corners"], json!(v)); @@ -539,7 +539,7 @@ fn page_ui(app: &DesignApp, ui: &mut egui::Ui, d: &mut Dialog, page: &str) { row(ui, "Type:"); let mut radial = d.fields.get("fx").is_some_and(|f| f["gradientFeather"]["radial"].as_bool().unwrap_or(false)); let label = |r: bool| tr(if r { "Radial" } else { "Linear" }); - egui::ComboBox::from_id_salt("fx_gtype").selected_text(label(radial)).show_ui(ui, |ui| { + craft_ui::ComboBox::from_id_salt("fx_gtype").selected_text(label(radial)).show_ui(ui, |ui| { for r in [false, true] { if ui.selectable_value(&mut radial, r, label(r)).clicked() { set(d, &["gradientFeather", "radial"], json!(r)); @@ -573,7 +573,7 @@ pub fn show(app: &mut DesignApp, ui: &mut egui::Ui, d: &mut Dialog) { crate::rtl::label(ui, tr("Settings for:")); // Stroke, Fill and Text levels aren't in the document model yet: the object only. ui.add_enabled_ui(false, |ui| { - egui::ComboBox::from_id_salt("fx_level").selected_text(tr("Object")).width(110.0).show_ui(ui, |_| {}); + craft_ui::ComboBox::from_id_salt("fx_level").selected_text(tr("Object")).width(110.0).show_ui(ui, |_| {}); }); }); ui.add_space(6.0); diff --git a/crates/ui-egui/src/new_document.rs b/crates/ui-egui/src/new_document.rs index f52adf38c..08b1a3e69 100644 --- a/crates/ui-egui/src/new_document.rs +++ b/crates/ui-egui/src/new_document.rs @@ -771,14 +771,17 @@ fn details(app: &mut DesignApp, ui: &mut Ui, d: &mut Dialog) { row(ui, rtl, |ui| { cell(ui, rtl, tr("Width"), |ui| spin(ui, d, "width", COLUMN_W - 8.0, Step::Measure(1.0))); cell(ui, rtl, tr("Units"), |ui| { - egui::ComboBox::from_id_salt("nd_units").selected_text(crate::rtl::widget(ui, tr(u.label()))).width(COLUMN_W - 8.0).show_ui(ui, |ui| { - // InDesign's eight; a preset in another unit keeps it on the list. - for v in NEW_DOCUMENT_UNITS.into_iter().chain((!NEW_DOCUMENT_UNITS.contains(&u)).then_some(u)) { - if ui.selectable_label(v == u, crate::rtl::widget(ui, tr(v.label()))).clicked() && v != u { - set_units(d, v); + craft_ui::ComboBox::from_id_salt("nd_units").selected_text(crate::rtl::widget(ui, tr(u.label()))).width(COLUMN_W - 8.0).show_ui( + ui, + |ui| { + // InDesign's eight; a preset in another unit keeps it on the list. + for v in NEW_DOCUMENT_UNITS.into_iter().chain((!NEW_DOCUMENT_UNITS.contains(&u)).then_some(u)) { + if ui.selectable_label(v == u, crate::rtl::widget(ui, tr(v.label()))).clicked() && v != u { + set_units(d, v); + } } - } - }); + }, + ); }); }); row(ui, rtl, |ui| { diff --git a/crates/ui-egui/src/panels.rs b/crates/ui-egui/src/panels.rs index bf18de841..5f1b8829c 100644 --- a/crates/ui-egui/src/panels.rs +++ b/crates/ui-egui/src/panels.rs @@ -3,7 +3,7 @@ pub mod cell_stroke; pub mod conditions; mod font_menu; -pub use font_menu::{FontPick, SAMPLES as FONT_SAMPLES, font_menu_body}; +pub use font_menu::{FONT_MENU_HEIGHT, FontMenu, FontPick, SAMPLES as FONT_SAMPLES, font_menu_header, font_menu_list}; pub mod datamerge; pub mod glyphs; pub mod interactive; @@ -383,7 +383,8 @@ pub fn swatch_picker( Some(resp) } -/// A font family combo box showing `current` by its menu name, with [`font_menu_body`] as its menu. +/// A font family combo box showing `current` by its menu name, with the Font menu +/// ([`font_menu_header`]) as its menu. /// Returns the font picked. pub fn font_combo( app: &mut DesignApp, @@ -394,15 +395,20 @@ pub fn font_combo( ) -> Option { let menu = font_menu(app); let shown = if current.is_empty() { "—".to_string() } else { font_label(app, &menu, current) }; - // A field and a plain popup rather than egui's combo box: its own scroll area would keep the - // menu at the size it last had (a family opening, a filter clearing, wouldn't show). let field = ui.push_id(id_salt, |ui| crate::widgets::dropdown(ui, &shown, width)).inner; - font_popup(app, &field, &menu, current) + font_popup(app, ui, &field, &menu, current) } -/// [`font_menu_body`] in a popup under `field` (a font field the caller draws), which screen -/// readers hear as a combo box with the current family as its value. Returns the font picked. -pub fn font_popup(app: &mut DesignApp, field: &egui::Response, menu: &[designcraft_fonts::FamilyInfo], current: &str) -> Option { +/// The Font menu ([`font_menu_header`]) in a menu under `field` (a font field the caller draws in +/// `ui`), which screen readers hear as a combo box with the current family as its value. Returns +/// the font picked. +pub fn font_popup( + app: &mut DesignApp, + ui: &egui::Ui, + field: &egui::Response, + menu: &[designcraft_fonts::FamilyInfo], + current: &str, +) -> Option { let shown = if current.is_empty() { "—".to_string() } else { font_label(app, menu, current) }; field.widget_info(|| { let mut info = egui::WidgetInfo::new(egui::Role::ComboBox); @@ -411,10 +417,12 @@ pub fn font_popup(app: &mut DesignApp, field: &egui::Response, menu: &[designcra info.label = Some(crate::i18n::tr(&app.ui.language, "Font Family").to_owned()); info }); - egui::Popup::menu(field) + let width = field.rect.width(); + craft_ui::combo::Menu::new(ui, field) + .height(FONT_MENU_HEIGHT) .close_behavior(egui::PopupCloseBehavior::CloseOnClickOutside) - .show(|ui| font_menu_body(app, ui, menu, current, field.rect.width())) - .and_then(|r| r.inner) + .show_ui_with_header(|ui| font_menu_header(app, ui, menu, current, width), font_menu_list) + .flatten() } /// The style a pick sets: the one picked, else the family's Regular (else its first). @@ -454,7 +462,7 @@ pub fn style_shown(current: &str) -> String { pub fn font_style_picker(app: &mut DesignApp, ui: &mut egui::Ui, family: &str, current: &str, width: f32) { let styles = fonts(app).styles(family); let mut pick = None; - let combo = egui::ComboBox::from_id_salt("font_style").selected_text(style_shown(current)).width(width).show_ui(ui, |ui| { + let combo = craft_ui::ComboBox::from_id_salt("font_style").selected_text(style_shown(current)).width(width).show_ui(ui, |ui| { for s in &styles { if ui.selectable_label(s == current, s).clicked() { pick = Some(s.clone()); @@ -472,7 +480,7 @@ pub fn para_style_picker(app: &mut DesignApp, ui: &mut egui::Ui, current: &str, let mut pick = None; let shown = crate::i18n::style_name(&app.ui.language, current); let label = if overridden { format!("{shown}+") } else { shown.to_string() }; - let combo = egui::ComboBox::from_id_salt("para_style").selected_text(crate::rtl::widget(ui, label)).width(width).show_ui(ui, |ui| { + let combo = craft_ui::ComboBox::from_id_salt("para_style").selected_text(crate::rtl::widget(ui, label)).width(width).show_ui(ui, |ui| { for n in names.iter().filter(|n| *n != designcraft_doc::NO_PARA_STYLE) { if ui.selectable_label(n == current, crate::rtl::widget(ui, crate::i18n::style_name(&app.ui.language, n))).clicked() { pick = Some(n.clone()); diff --git a/crates/ui-egui/src/panels/cell_stroke.rs b/crates/ui-egui/src/panels/cell_stroke.rs index b7f4eaa2e..2c99edc34 100644 --- a/crates/ui-egui/src/panels/cell_stroke.rs +++ b/crates/ui-egui/src/panels/cell_stroke.rs @@ -192,7 +192,7 @@ pub fn stroke_fields(app: &mut DesignApp, ui: &mut Ui, id: &str, v: &Value) -> O ui.end_row(); caption(ui, tr(&lang, "Type:")); let shown = type_label(&lang, &v["type"]); - let combo = egui::ComboBox::from_id_salt((id, "type")).selected_text(crate::rtl::widget(ui, &shown)).width(140.0).show_ui(ui, |ui| { + let combo = craft_ui::ComboBox::from_id_salt((id, "type")).selected_text(crate::rtl::widget(ui, &shown)).width(140.0).show_ui(ui, |ui| { for kind in TYPES { let text = tr(&lang, builtin(kind).label()); if ui.selectable_label(shown == text, crate::rtl::widget(ui, text)).clicked() { diff --git a/crates/ui-egui/src/panels/datamerge.rs b/crates/ui-egui/src/panels/datamerge.rs index cbeb7f327..e42055aec 100644 --- a/crates/ui-egui/src/panels/datamerge.rs +++ b/crates/ui-egui/src/panels/datamerge.rs @@ -197,7 +197,7 @@ fn select_source(app: &mut DesignApp) { fn choice(ui: &mut egui::Ui, id: &str, current: &str, options: &[(&str, &str)], lang: &str, name: &str) -> Option { let label = options.iter().find(|(value, _)| *value == current).map(|(_, text)| tr(lang, text)).unwrap_or(current); let mut picked = None; - let combo = egui::ComboBox::from_id_salt(id).selected_text(label).width(ui.available_width().min(200.0)).show_ui(ui, |ui| { + let combo = craft_ui::ComboBox::from_id_salt(id).selected_text(label).width(ui.available_width().min(200.0)).show_ui(ui, |ui| { for (value, text) in options { if ui.selectable_label(*value == current, tr(lang, text)).clicked() { picked = Some((*value).to_string()); diff --git a/crates/ui-egui/src/panels/font_menu.rs b/crates/ui-egui/src/panels/font_menu.rs index f07e8a810..1ce58506f 100644 --- a/crates/ui-egui/src/panels/font_menu.rs +++ b/crates/ui-egui/src/panels/font_menu.rs @@ -297,13 +297,41 @@ fn clock(p: &egui::Painter, r: Rect, color: Color32) { /// one. A click on a family picks it (its Regular style), on one of its styles that style; the /// arrow opens a family, the star marks a favourite and ≈ shows similar fonts, all keeping the menu /// open (it closes on a click outside only). -pub fn font_menu_body(app: &mut DesignApp, ui: &mut Ui, menu: &[FamilyInfo], current: &str, width: f32) -> Option { +/// +/// [`font_menu_header`] lays out the search field and the filters, [`font_menu_list`] the rows +/// under them: the header and list of a [`craft_ui::combo::Menu`] or [`craft_ui::ComboBox`], so +/// only the rows scroll. Rows a field adds above the search (a blank "no font" row) go in the +/// header before [`font_menu_header`]. +pub struct FontMenu<'a> { + app: &'a mut DesignApp, + menu: &'a [FamilyInfo], + current: &'a str, + st: MenuState, + rows: Vec, + highlighted_row: Option, + loading: bool, + sample: String, + row_h: f32, + sample_size: f32, + down: bool, + up: bool, + escape: bool, + pick: Option, +} + +/// The Font menu's maximum height, its search field and filters included. +pub const FONT_MENU_HEIGHT: f32 = 500.0; + +const STATE_ID: &str = "font_menu_state"; + +/// The Font menu's search field and filters, for the header of a menu whose list is +/// [`font_menu_list`]. See [`FontMenu`]. +pub fn font_menu_header<'a>(app: &'a mut DesignApp, ui: &mut Ui, menu: &'a [FamilyInfo], current: &'a str, width: f32) -> FontMenu<'a> { let lang = app.ui.language.clone(); let tr = |s: &'static str| crate::i18n::tr(&lang, s); let (fonts, scope) = (super::fonts(app), super::font_scope(app)); - let english = app.session.prefs.show_font_names_in_english; - let mut favs = app.session.prefs.favorite_fonts.clone(); - let state_id = egui::Id::unique("font_menu_state"); + let favs = app.session.prefs.favorite_fonts.clone(); + let state_id = egui::Id::unique(STATE_ID); let mut st: MenuState = ui.data(|d| d.get_temp(state_id)).unwrap_or_default(); let frame = ui.ctx().cumulative_frame_nr(); if st.frame.is_none_or(|f| f.saturating_add(1) < frame) { @@ -315,7 +343,6 @@ pub fn font_menu_body(app: &mut DesignApp, ui: &mut Ui, menu: &[FamilyInfo], cur let t = Tokens::get(ui.ctx()); ui.set_width(width.max(560.0)); let mut pick: Option = None; - let mut favs_changed = false; // The keys the menu uses are its own: a dialog under it never sees Return or Escape. let (down, up, enter, escape) = ui.input_mut(|i| { let mut key = |k| i.consume_key(egui::Modifiers::NONE, k); @@ -506,125 +533,126 @@ pub fn font_menu_body(app: &mut DesignApp, ui: &mut Ui, menu: &[FamilyInfo], cur }; } + FontMenu { app, menu, current, st, rows, highlighted_row, loading, sample, row_h, sample_size, down, up, escape, pick } +} + +/// The Font menu's rows, under [`font_menu_header`]. Returns the font picked. +pub fn font_menu_list(ui: &mut Ui, m: FontMenu<'_>) -> Option { + let FontMenu { app, menu, current, mut st, rows, highlighted_row, loading, sample, row_h, sample_size, down, up, escape, mut pick } = m; + let lang = app.ui.language.clone(); + let tr = |s: &'static str| crate::i18n::tr(&lang, s); + let (fonts, scope) = (super::fonts(app), super::font_scope(app)); + let english = app.session.prefs.show_font_names_in_english; + let mut favs = app.session.prefs.favorite_fonts.clone(); + let state_id = egui::Id::unique(STATE_ID); + let t = Tokens::get(ui.ctx()); + let mut favs_changed = false; let mut toggle_expand = None; let mut toggle_fav = None; let mut similar = None; - // A popup only offers last frame's height: ask for the rows' own (a family just opened, a - // filter just cleared), up to the list's limit. - let list_h: f32 = rows - .iter() - .map(|r| match r { - Row::Family { .. } => row_h + ui.spacing().item_spacing.y, - Row::Style { .. } => row_h.min(30.0) + ui.spacing().item_spacing.y, - Row::Separator => 8.0, - }) - .sum(); - egui::ScrollArea::vertical().max_height(420.0).min_scrolled_height(list_h.clamp(24.0, 420.0)).auto_shrink([false, true]).show(ui, |ui| { - if loading { - crate::rtl::label(ui, egui::RichText::new(tr("Loading fonts…")).color(t.text_dim)); - } else if rows.is_empty() { - crate::rtl::label(ui, egui::RichText::new(tr("No fonts match")).color(t.text_dim)); - } - for (i, row) in rows.iter().enumerate() { - let (family, style, recent) = match row { - Row::Separator => { - ui.separator(); - continue; - } - Row::Family { info, recent } => (info, None, *recent), - Row::Style { family, style } => { - let Some(info) = menu.iter().find(|f| f.family == *family) else { continue }; - (info, Some(style.as_str()), false) - } - }; - let f = &family.family; - let (r, mut resp) = - ui.allocate_exact_size(vec2(ui.available_width(), if style.is_some() { row_h.min(30.0) } else { row_h }), Sense::click()); - let highlighted = highlighted_row == Some(i); - if highlighted && (down || up) { - ui.scroll_to_rect(r, None); - } - if !ui.is_rect_visible(r) { + if loading { + crate::rtl::label(ui, egui::RichText::new(tr("Loading fonts…")).color(t.text_dim)); + } else if rows.is_empty() { + crate::rtl::label(ui, egui::RichText::new(tr("No fonts match")).color(t.text_dim)); + } + for (i, row) in rows.iter().enumerate() { + let (family, style, recent) = match row { + Row::Separator => { + ui.separator(); continue; } - let label = match style { - Some(s) => s.to_string(), - None => family.label(english).to_string(), - }; - let is_current = style.is_none() && f == current; - resp.widget_info(|| egui::WidgetInfo::selected(egui::Role::Button, true, is_current, &label)); - if style.is_none() && label != *f { - resp = resp.on_hover_text(f); + Row::Family { info, recent } => (info, None, *recent), + Row::Style { family, style } => { + let Some(info) = menu.iter().find(|f| f.family == *family) else { continue }; + (info, Some(style.as_str()), false) } - // Over the row or one of its buttons (the star, the arrow, ≈ are on top of it). - let hovered = resp.contains_pointer(); - if is_current { - ui.painter().rect_filled(r, 0.0, t.row_selected); - } else if highlighted || hovered { - ui.painter().rect_filled(r, 0.0, t.hover); + }; + let f = &family.family; + let (r, mut resp) = ui.allocate_exact_size(vec2(ui.available_width(), if style.is_some() { row_h.min(30.0) } else { row_h }), Sense::click()); + let highlighted = highlighted_row == Some(i); + if highlighted && (down || up) { + ui.scroll_to_rect(r, None); + } + if !ui.is_rect_visible(r) { + continue; + } + let label = match style { + Some(s) => s.to_string(), + None => family.label(english).to_string(), + }; + let is_current = style.is_none() && f == current; + resp.widget_info(|| egui::WidgetInfo::selected(egui::Role::Button, true, is_current, &label)); + if style.is_none() && label != *f { + resp = resp.on_hover_text(f); + } + // Over the row or one of its buttons (the star, the arrow, ≈ are on top of it). + let hovered = resp.contains_pointer(); + if is_current { + ui.painter().rect_filled(r, 0.0, t.row_selected); + } else if highlighted || hovered { + ui.painter().rect_filled(r, 0.0, t.hover); + } + let p = ui.painter_at(r); + let m = meta(ui, &fonts, scope, f); + let cy = r.center().y; + let mut handled = false; + if style.is_none() { + // Favourite star. + let star = Rect::from_center_size(pos2(r.min.x + 10.0, cy), vec2(16.0, 16.0)); + let fav = favs.contains(f); + let sr = ui.interact(star, ui.scope_id().with(("fav", f, recent)), Sense::click()); + paint_star(&p, star.center(), 6.5, fav, if fav { t.accent } else { t.text_dim }); + if sr.clicked() { + toggle_fav = Some(f.clone()); + handled = true; } - let p = ui.painter_at(r); - let m = meta(ui, &fonts, scope, f); - let cy = r.center().y; - let mut handled = false; - if style.is_none() { - // Favourite star. - let star = Rect::from_center_size(pos2(r.min.x + 10.0, cy), vec2(16.0, 16.0)); - let fav = favs.contains(f); - let sr = ui.interact(star, ui.scope_id().with(("fav", f, recent)), Sense::click()); - paint_star(&p, star.center(), 6.5, fav, if fav { t.accent } else { t.text_dim }); - if sr.clicked() { - toggle_fav = Some(f.clone()); + // The arrow opens a family of several styles. + if m.styles.len() > 1 && !recent { + let arrow = Rect::from_center_size(pos2(r.min.x + 28.0, cy), vec2(16.0, 16.0)); + let ar = ui.interact(arrow, ui.scope_id().with(("open", f)), Sense::click()); + ar.widget_info(|| { + egui::WidgetInfo::labeled(egui::Role::Button, true, tr(if st.expanded.contains(f) { "Hide Styles" } else { "Show Styles" })) + }); + let open = st.expanded.contains(f); + crate::icons::paint(&p, arrow.shrink(2.0), if open { "chevron-down" } else { "chevron-right" }, t.icon); + if ar.clicked() { + toggle_expand = Some(f.clone()); handled = true; } - // The arrow opens a family of several styles. - if m.styles.len() > 1 && !recent { - let arrow = Rect::from_center_size(pos2(r.min.x + 28.0, cy), vec2(16.0, 16.0)); - let ar = ui.interact(arrow, ui.scope_id().with(("open", f)), Sense::click()); - ar.widget_info(|| { - egui::WidgetInfo::labeled(egui::Role::Button, true, tr(if st.expanded.contains(f) { "Hide Styles" } else { "Show Styles" })) - }); - let open = st.expanded.contains(f); - crate::icons::paint(&p, arrow.shrink(2.0), if open { "chevron-down" } else { "chevron-right" }, t.icon); - if ar.clicked() { - toggle_expand = Some(f.clone()); - handled = true; - } - } - } - // Name (and how many styles), then the sample in the face, then the format. - let name_x = r.min.x + if style.is_some() { 48.0 } else { 40.0 }; - let name = if style.is_none() && m.styles.len() > 1 { format!("{label} ({})", m.styles.len()) } else { label.clone() }; - p.text(pos2(name_x, cy), Align2::LEFT_CENTER, &name, FontId::proportional(12.5), t.text); - let preview_x = r.min.x + (r.width() * 0.42).max(190.0); - let icons_x = r.max.x - 52.0; - let face_style = style.map_or_else(|| regular(&m.styles), str::to_string); - let tex = preview(ui, &fonts, scope, f, &face_style, &sample, sample_size, t.text); - let size = tex.size_vec2() / ui.ctx().pixels_per_point(); - let shown = Rect::from_min_size(pos2(preview_x, cy - size.y / 2.0), size); - let clip = Rect::from_min_max(pos2(preview_x, r.min.y), pos2(icons_x - 6.0, r.max.y)); - ui.painter_at(clip).image(tex.id(), shown, Rect::from_min_max(pos2(0.0, 0.0), pos2(1.0, 1.0)), Color32::WHITE); - // Show Similar Fonts (on hover), and the format. - if style.is_none() && (hovered || st.similar.as_deref() == Some(f.as_str())) { - let sim = Rect::from_center_size(pos2(icons_x + 12.0, cy), vec2(18.0, 18.0)); - let sr = ui.interact(sim, ui.scope_id().with(("similar", f, recent)), Sense::click()); - sr.widget_info(|| egui::WidgetInfo::labeled(egui::Role::Button, true, tr("Show Similar Fonts"))); - p.text(sim.center(), Align2::CENTER_CENTER, "≈", FontId::proportional(15.0), if sr.hovered() { t.text_strong } else { t.icon }); - if sr.on_hover_text(tr("Show Similar Fonts")).clicked() { - similar = Some(f.clone()); - handled = true; - } - } - if style.is_none() { - let badge = Rect::from_center_size(pos2(r.max.x - 16.0, cy), vec2(20.0, 16.0)); - paint_format(&p, badge, m.kind.format, t.text_dim, t.accent); - ui.interact(badge, ui.scope_id().with(("format", f, recent)), Sense::hover()).on_hover_text(tr(format_name(m.kind.format))); - } - if resp.clicked() && !handled { - pick = Some(FontPick { family: f.clone(), style: style.map(str::to_string) }); } } - }); + // Name (and how many styles), then the sample in the face, then the format. + let name_x = r.min.x + if style.is_some() { 48.0 } else { 40.0 }; + let name = if style.is_none() && m.styles.len() > 1 { format!("{label} ({})", m.styles.len()) } else { label.clone() }; + p.text(pos2(name_x, cy), Align2::LEFT_CENTER, &name, FontId::proportional(12.5), t.text); + let preview_x = r.min.x + (r.width() * 0.42).max(190.0); + let icons_x = r.max.x - 52.0; + let face_style = style.map_or_else(|| regular(&m.styles), str::to_string); + let tex = preview(ui, &fonts, scope, f, &face_style, &sample, sample_size, t.text); + let size = tex.size_vec2() / ui.ctx().pixels_per_point(); + let shown = Rect::from_min_size(pos2(preview_x, cy - size.y / 2.0), size); + let clip = Rect::from_min_max(pos2(preview_x, r.min.y), pos2(icons_x - 6.0, r.max.y)); + ui.painter_at(clip).image(tex.id(), shown, Rect::from_min_max(pos2(0.0, 0.0), pos2(1.0, 1.0)), Color32::WHITE); + // Show Similar Fonts (on hover), and the format. + if style.is_none() && (hovered || st.similar.as_deref() == Some(f.as_str())) { + let sim = Rect::from_center_size(pos2(icons_x + 12.0, cy), vec2(18.0, 18.0)); + let sr = ui.interact(sim, ui.scope_id().with(("similar", f, recent)), Sense::click()); + sr.widget_info(|| egui::WidgetInfo::labeled(egui::Role::Button, true, tr("Show Similar Fonts"))); + p.text(sim.center(), Align2::CENTER_CENTER, "≈", FontId::proportional(15.0), if sr.hovered() { t.text_strong } else { t.icon }); + if sr.on_hover_text(tr("Show Similar Fonts")).clicked() { + similar = Some(f.clone()); + handled = true; + } + } + if style.is_none() { + let badge = Rect::from_center_size(pos2(r.max.x - 16.0, cy), vec2(20.0, 16.0)); + paint_format(&p, badge, m.kind.format, t.text_dim, t.accent); + ui.interact(badge, ui.scope_id().with(("format", f, recent)), Sense::hover()).on_hover_text(tr(format_name(m.kind.format))); + } + if resp.clicked() && !handled { + pick = Some(FontPick { family: f.clone(), style: style.map(str::to_string) }); + } + } if let Some(f) = toggle_expand && !st.expanded.remove(&f) { diff --git a/crates/ui-egui/src/panels/font_menu_ui_tests.rs b/crates/ui-egui/src/panels/font_menu_ui_tests.rs index daad2d632..8680ccfdb 100644 --- a/crates/ui-egui/src/panels/font_menu_ui_tests.rs +++ b/crates/ui-egui/src/panels/font_menu_ui_tests.rs @@ -301,3 +301,66 @@ fn font_fields_in_panels_and_dialogs_search() { assert_eq!(h.state().app.ui.dialog.as_ref().map(|d| d.id.clone()), dialog_id, "{what}: Escape in the menu leaves the dialog open"); } } + +/// The font rows the open menu shows under its search field `search`: how many, and the bottom +/// of the lowest. +fn rows_shown(h: &Harness<'static, Window>, search: egui::Rect) -> (usize, f32) { + let families: Vec = crate::panels::font_menu(&h.state().app).iter().map(|f| f.label(true).to_string()).collect(); + let rows: Vec = h + .query_all_by(|n| n.role() == egui::accesskit::Role::Button && n.label().is_some_and(|l| families.contains(&l))) + .map(|n| n.rect()) + .filter(|r| r.min.y > search.max.y) + .collect(); + (rows.len(), rows.iter().map(|r| r.max.y).fold(f32::MIN, f32::max)) +} + +/// Opens the font menu of the field at `at`, searches for a font that doesn't exist and clears +/// the search: the menu grows back to the rows it showed, with the search field where it was. +/// The menu lists every family by its English name: the test UI has no CJK font for a system +/// CJK family's native name. +fn menu_regrows_after_a_search_with_no_matches(h: &mut Harness<'static, Window>, at: egui::Rect, what: &str) { + h.state_mut().app.session.prefs.show_font_names_in_english = true; + click_at(h, at.center()); + h.run_steps(4); + let focused = |h: &Harness<'static, Window>| h.query_by(|n| n.role() == egui::accesskit::Role::TextInput && n.is_focused()).map(|n| n.rect()); + let field = focused(h).unwrap_or_else(|| panic!("{what}: the menu opens with the search field focused")); + let (count, bottom) = rows_shown(h, field); + assert!(count > 1, "{what}: the menu lists the fonts: {count}"); + + let query = "zzqqxxnomatch"; + h.event(egui::Event::Text(query.into())); + h.run_steps(4); + assert!(h.query_by_label("No fonts match").is_some(), "{what}: nothing matches"); + assert_eq!(rows_shown(h, field).0, 0, "{what}: no rows while nothing matches"); + + for _ in query.chars() { + h.key_press(egui::Key::Backspace); + } + h.run_steps(4); + assert_eq!(search(h), "", "{what}: the search is cleared"); + let again = focused(h).unwrap_or_else(|| panic!("{what}: the menu is still open")); + assert!((again.min.y - field.min.y).abs() < 0.5, "{what}: the search field stays at {}, not {}", field.min.y, again.min.y); + let (count_again, bottom_again) = rows_shown(h, again); + assert_eq!(count_again, count, "{what}: the menu shows as many rows as before the search"); + assert!((bottom_again - bottom).abs() < 1.0, "{what}: the menu grows back to {bottom}, not {bottom_again}"); +} + +#[test] +fn the_font_field_menu_grows_back_after_a_search_with_no_matches() { + let (mut h, _) = typing(); + let at = field(&h, designcraft_fonts::DEFAULT_FAMILY); + menu_regrows_after_a_search_with_no_matches(&mut h, at, "Control bar"); +} + +/// Paragraph Style Options › Basic Character Formats' Font Family combo box. The CJK style +/// fields' combo box hosts the same menu the same way. +#[test] +fn the_style_dialog_font_menu_grows_back_after_a_search_with_no_matches() { + let app = crate::DesignApp::new(designcraft_engine::Session::new(), crate::Services::default()); + let mut h = test_window::open(app, vec2(1440.0, 900.0)); + let section = json!({"name": designcraft_doc::BASIC_PARAGRAPH, "section": "chars"}); + h.state_mut().app.ui.dialog = Some(crate::dialogs::Dialog::new("paragraphStyleOptions", section)); + h.run_steps(4); + let at = *fields(&h, designcraft_fonts::DEFAULT_FAMILY).first().expect("the dialog's font field is shown"); + menu_regrows_after_a_search_with_no_matches(&mut h, at, "Paragraph Style Options"); +} diff --git a/crates/ui-egui/src/panels/glyphs.rs b/crates/ui-egui/src/panels/glyphs.rs index 7d1da4ea3..965509ff4 100644 --- a/crates/ui-egui/src/panels/glyphs.rs +++ b/crates/ui-egui/src/panels/glyphs.rs @@ -38,7 +38,7 @@ pub fn show(app: &mut DesignApp, ui: &mut egui::Ui) { st.style = f.style.clone().or_else(|| db.styles(&f.family).into_iter().next()).unwrap_or_else(|| "Regular".into()); st.family = f.family; } - let combo = egui::ComboBox::from_id_salt("glyph_style").selected_text(&st.style).width(80.0).show_ui(ui, |ui| { + let combo = craft_ui::ComboBox::from_id_salt("glyph_style").selected_text(&st.style).width(80.0).show_ui(ui, |ui| { for s in db.styles(&st.family) { if ui.selectable_label(s == st.style, &s).clicked() { st.style = s; diff --git a/crates/ui-egui/src/panels/interactive.rs b/crates/ui-egui/src/panels/interactive.rs index 7ca6cf595..5c7ae92ed 100644 --- a/crates/ui-egui/src/panels/interactive.rs +++ b/crates/ui-egui/src/panels/interactive.rs @@ -365,7 +365,7 @@ pub fn buttons(app: &mut DesignApp, ui: &mut egui::Ui) { let mut pick = None; ui.horizontal(|ui| { crate::rtl::label(ui, crate::i18n::tr(&app.ui.language, "On Release")); - let combo = egui::ComboBox::from_id_salt("button_action") + let combo = craft_ui::ComboBox::from_id_salt("button_action") .selected_text(crate::rtl::widget(ui, crate::i18n::tr(&app.ui.language, labels.iter().find(|l| l.0 == kind).map_or("", |l| l.1)))) .show_ui(ui, |ui| { for (k, l) in labels { @@ -426,7 +426,7 @@ pub fn buttons(app: &mut DesignApp, ui: &mut egui::Ui) { let cur = ff.as_ref().map(|f| f.kind); ui.horizontal(|ui| { crate::rtl::label(ui, crate::i18n::tr(&app.ui.language, "Form Field")); - let combo = egui::ComboBox::from_id_salt("form_kind") + let combo = craft_ui::ComboBox::from_id_salt("form_kind") .selected_text(crate::rtl::widget(ui, crate::i18n::tr(&app.ui.language, kinds.iter().find(|k| k.0 == cur).map_or("", |k| k.1)))) .show_ui(ui, |ui| { for (k, l) in kinds { @@ -539,7 +539,7 @@ pub fn liquid(app: &mut DesignApp, ui: &mut egui::Ui) { crate::widgets::caption(ui, &format!("{} {}", crate::i18n::tr(&lang, "Page:"), d.page_name(page))); }); }); - let combo = egui::ComboBox::from_id_salt("liquid_rule") + let combo = craft_ui::ComboBox::from_id_salt("liquid_rule") .selected_text(crate::rtl::widget(ui, crate::i18n::tr(&lang, liquid_rule_label(pg.liquid)))) .width(190.0) .show_ui(ui, |ui| { @@ -669,7 +669,7 @@ pub fn transitions(app: &mut DesignApp, ui: &mut egui::Ui) { use designcraft_doc::TransitionKind as K; crate::rtl::label(ui, egui::RichText::new(format!("{} {}", crate::i18n::tr(&app.ui.language, "Spread"), si + 1)).strong()); let mut kind = cur.map(|c| c.kind); - let combo = egui::ComboBox::from_id_salt("transition_kind") + let combo = craft_ui::ComboBox::from_id_salt("transition_kind") .selected_text(crate::rtl::widget(ui, crate::i18n::tr(&app.ui.language, kind.map_or("None", K::label)))) .show_ui(ui, |ui| { ui.selectable_value(&mut kind, None, crate::rtl::widget(ui, crate::i18n::tr(&app.ui.language, "None"))); diff --git a/crates/ui-egui/src/panels/properties/object_panels.rs b/crates/ui-egui/src/panels/properties/object_panels.rs index d8ad4743b..6de8daaaf 100644 --- a/crates/ui-egui/src/panels/properties/object_panels.rs +++ b/crates/ui-egui/src/panels/properties/object_panels.rs @@ -66,7 +66,7 @@ pub fn stroke_panel(app: &mut DesignApp, ui: &mut egui::Ui) { StrokeType::Style { name } => name.clone(), k => k.label().to_string(), }; - let combo = egui::ComboBox::from_id_salt("stype") + let combo = craft_ui::ComboBox::from_id_salt("stype") .selected_text(crate::rtl::widget( ui, if matches!(st.kind, StrokeType::Style { .. }) { &shown } else { crate::i18n::tr(&app.ui.language, &shown) }, @@ -106,7 +106,7 @@ pub fn stroke_panel(app: &mut DesignApp, ui: &mut egui::Ui) { ui.end_row(); for (key, label, cur, flip) in [("start", "Start/End:", st.start, true), ("end", "", st.end, false)] { caption(ui, crate::i18n::tr(&app.ui.language, label)); - let combo = egui::ComboBox::from_id_salt(key) + let combo = craft_ui::ComboBox::from_id_salt(key) .selected_text(crate::rtl::widget(ui, crate::i18n::tr(&app.ui.language, cur.label()))) .width(140.0) .show_ui(ui, |ui| { @@ -305,7 +305,7 @@ fn wrap_combo( ui.horizontal(|ui| { caption(ui, crate::i18n::tr(&app.ui.language, caption_text)); let shown = opts.iter().chain(shown_only).find(|o| o.0 == cur).map_or(cur, |o| o.1).to_string(); - let combo = egui::ComboBox::from_id_salt(salt) + let combo = craft_ui::ComboBox::from_id_salt(salt) .selected_text(crate::rtl::widget(ui, crate::i18n::tr(&app.ui.language, &shown))) .width(170.0) .show_ui(ui, |ui| { @@ -468,7 +468,7 @@ pub fn effects_panel(app: &mut DesignApp, ui: &mut egui::Ui) { B::Luminosity, ]; ui.horizontal(|ui| { - let combo = egui::ComboBox::from_id_salt("blend") + let combo = craft_ui::ComboBox::from_id_salt("blend") .selected_text(crate::rtl::widget(ui, crate::i18n::tr(&app.ui.language, it.blend.label()))) .width(130.0) .show_ui(ui, |ui| { diff --git a/crates/ui-egui/src/panels/properties/text.rs b/crates/ui-egui/src/panels/properties/text.rs index 229d11a23..96c0a7e69 100644 --- a/crates/ui-egui/src/panels/properties/text.rs +++ b/crates/ui-egui/src/panels/properties/text.rs @@ -179,7 +179,7 @@ pub(super) fn character_section(app: &mut DesignApp, ui: &mut Ui) { egui::FontId::proportional(11.5), t.text, ); - if let Some(f) = crate::panels::font_popup(app, &resp, &menu, &fam) { + if let Some(f) = crate::panels::font_popup(app, ui, &resp, &menu, &fam) { crate::panels::apply_font(app, &f); } ui.add_space(1.0); diff --git a/crates/ui-egui/src/panels/properties/typography.rs b/crates/ui-egui/src/panels/properties/typography.rs index b349e11a1..ce823f5db 100644 --- a/crates/ui-egui/src/panels/properties/typography.rs +++ b/crates/ui-egui/src/panels/properties/typography.rs @@ -16,7 +16,7 @@ pub fn character_panel(app: &mut DesignApp, ui: &mut egui::Ui) { let lang = c["language"].as_str().unwrap_or("English: USA").to_string(); ui.horizontal(|ui| { caption(ui, crate::i18n::tr(&app.ui.language, "Language")); - let combo = egui::ComboBox::from_id_salt("char_language") + let combo = craft_ui::ComboBox::from_id_salt("char_language") .selected_text(crate::rtl::widget(ui, crate::i18n::tr(&app.ui.language, &lang))) .width(170.0) .show_ui(ui, |ui| language_menu(app, ui, &lang)); @@ -30,7 +30,7 @@ pub fn character_panel(app: &mut DesignApp, ui: &mut egui::Ui) { .iter() .find(|o| o.0 == digits) .map_or_else(|| digit_label(&app.ui.language, DIGIT_OPTIONS[0]), |o| digit_label(&app.ui.language, *o)); - let combo = egui::ComboBox::from_id_salt("char_digits").selected_text(crate::rtl::widget(ui, cur)).width(150.0).show_ui(ui, |ui| { + let combo = craft_ui::ComboBox::from_id_salt("char_digits").selected_text(crate::rtl::widget(ui, cur)).width(150.0).show_ui(ui, |ui| { for o in DIGIT_OPTIONS { if ui.selectable_label(o.0 == digits, crate::rtl::widget(ui, digit_label(&app.ui.language, o))).clicked() { let _ = app.run("type.char", json!({"attrs": {"digits": o.0}})); diff --git a/crates/ui-egui/src/panels/swatches.rs b/crates/ui-egui/src/panels/swatches.rs index 08b41ac87..1c6f41b27 100644 --- a/crates/ui-egui/src/panels/swatches.rs +++ b/crates/ui-egui/src/panels/swatches.rs @@ -401,7 +401,7 @@ pub fn color_panel(app: &mut DesignApp, ui: &mut egui::Ui) { } ui.label(egui::RichText::new(&name).size(11.0).color(t.text_dim)); ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| { - let combo = egui::ComboBox::from_id_salt("color_mode") + let combo = craft_ui::ComboBox::from_id_salt("color_mode") .width(64.0) .selected_text(match mode { ColorMode::Cmyk => "CMYK", @@ -741,7 +741,7 @@ pub fn gradient_panel(app: &mut DesignApp, ui: &mut egui::Ui) { crate::widgets::caption_w(ui, crate::i18n::tr(&lang, "Type:"), 52.0); let shown = if is_gradient { crate::i18n::tr(&lang, g.kind.label()) } else { "" }; let combo = - egui::ComboBox::from_id_salt("grad_type").selected_text(crate::rtl::widget(ui, shown)).width(104.0).show_ui(ui, |ui| { + craft_ui::ComboBox::from_id_salt("grad_type").selected_text(crate::rtl::widget(ui, shown)).width(104.0).show_ui(ui, |ui| { for (k, v) in [("Linear", "linear"), ("Radial", "radial")] { if ui .selectable_label(is_gradient && g.kind.label() == k, crate::rtl::widget(ui, crate::i18n::tr(&lang, k))) diff --git a/crates/ui-egui/src/panels/table.rs b/crates/ui-egui/src/panels/table.rs index 5bda05989..90cf274ea 100644 --- a/crates/ui-egui/src/panels/table.rs +++ b/crates/ui-egui/src/panels/table.rs @@ -69,7 +69,7 @@ pub fn show(app: &mut DesignApp, ui: &mut egui::Ui) { for (label, kind, names, cur) in [("Table Style", "table", tnames, tcur), ("Cell Style", "cell", cnames, ccur)] { ui.horizontal(|ui| { caption(ui, crate::i18n::tr(&app.ui.language, label)); - let combo = egui::ComboBox::from_id_salt(("tp_style", kind)).selected_text(&cur).width(130.0).show_ui(ui, |ui| { + let combo = craft_ui::ComboBox::from_id_salt(("tp_style", kind)).selected_text(&cur).width(130.0).show_ui(ui, |ui| { for n in &names { if ui.selectable_label(*n == cur, n).clicked() { run(app, &format!("style.{kind}.apply"), json!({"name": n, "table": tid})); @@ -126,7 +126,7 @@ pub fn show(app: &mut DesignApp, ui: &mut egui::Ui) { let row = rows.get(r0).cloned().unwrap_or(Value::Null); let exactly = row["mode"] == "exactly"; caption(ui, crate::i18n::tr(&app.ui.language, "Row Height")); - let combo = egui::ComboBox::from_id_salt("tp_rowmode") + let combo = craft_ui::ComboBox::from_id_salt("tp_rowmode") .selected_text(crate::rtl::widget(ui, crate::i18n::tr(&app.ui.language, if exactly { "Exactly" } else { "At Least" }))) .width(70.0) .show_ui(ui, |ui| { diff --git a/crates/ui-egui/src/section_options.rs b/crates/ui-egui/src/section_options.rs index 899b9fbed..ea96f9f60 100644 --- a/crates/ui-egui/src/section_options.rs +++ b/crates/ui-egui/src/section_options.rs @@ -105,7 +105,7 @@ fn text(ui: &mut Ui, d: &mut Dialog, key: &str, width: f32) { fn style_menu(ui: &mut Ui, d: &mut Dialog, key: &str) { let current: NumberStyle = d.fields.get(key).and_then(|v| serde_json::from_value(v.clone()).ok()).unwrap_or_default(); let shown = STYLES.iter().find(|(s, _)| *s == current).map_or(STYLES[0].1, |(_, l)| l); - egui::ComboBox::from_id_salt(("section_style", key)).selected_text(shown).width(150.0).show_ui(ui, |ui| { + craft_ui::ComboBox::from_id_salt(("section_style", key)).selected_text(shown).width(150.0).show_ui(ui, |ui| { for (style, label) in STYLES { if ui.selectable_label(style == current, label).clicked() { d.fields.insert(key.into(), json!(style)); diff --git a/crates/ui-egui/src/taskbar.rs b/crates/ui-egui/src/taskbar.rs index 88c152f5b..4411d93dd 100644 --- a/crates/ui-egui/src/taskbar.rs +++ b/crates/ui-egui/src/taskbar.rs @@ -145,7 +145,7 @@ fn text_controls(app: &mut DesignApp, ui: &mut Ui, in_text: bool) { let menu = panels::font_menu(app); let shown = if fam.is_empty() { "—".to_string() } else { panels::font_label(app, &menu, &fam) }; let field = widgets::dropdown(ui, &shown, 150.0); - if let Some(f) = panels::font_popup(app, &field, &menu, &fam) { + if let Some(f) = panels::font_popup(app, ui, &field, &menu, &fam) { panels::apply_font(app, &f); } let styles = fonts.styles(&fam); diff --git a/docs/pdf-backend.md b/docs/pdf-backend.md index 41509a770..6dc2a3330 100644 --- a/docs/pdf-backend.md +++ b/docs/pdf-backend.md @@ -1,7 +1,7 @@ # Shared PDF backend DesignCraft pins `craft-pdf`, patched Krilla 0.8.2 and the six Hayro reader crates (with -`hayro-cmap` and `hayro-postscript`) to craft-libs revision `b926b5c03e256c86ca10934f58d03f628810b5b7`. `craft-ui`, +`hayro-cmap` and `hayro-postscript`) to craft-libs revision `92d392cff132eaf53e13afff77f38b5c11b73aa0`. `craft-ui`, `craft-print` and the other craft-libs crates are pinned to the same revision. The reader migration is reviewed on top of the separate writer change. ## Writing diff --git a/xtask/src/pdf_deps.rs b/xtask/src/pdf_deps.rs index b39c2538c..2f33f3109 100644 --- a/xtask/src/pdf_deps.rs +++ b/xtask/src/pdf_deps.rs @@ -5,7 +5,7 @@ use std::process::Command; use serde_json::Value; const REPO: &str = "https://github.com/storytold/craft-libs"; -const REV: &str = "b926b5c03e256c86ca10934f58d03f628810b5b7"; +const REV: &str = "92d392cff132eaf53e13afff77f38b5c11b73aa0"; const PACKAGES: &[(&str, &str)] = &[ ("craft-pdf", "0.1.0"), ("krilla", "0.8.2"),