From 436d444ef0ee35c2e2b9fb44f87aa7d08d72641f Mon Sep 17 00:00:00 2001 From: aiongg <67543387+aiongg@users.noreply.github.com> Date: Tue, 27 Oct 2026 10:27:21 -0400 Subject: [PATCH] fix(ui): dropdown menus grow back after a search with no matches (#4577) * ui: craft_ui::ComboBox everywhere and the combo arrow popup on craft_ui::combo::Menu Repin craft-libs to aiongg/craft-libs 62bb0e1d (dropdown regrow). Ban egui::ComboBox in clippy.toml. * chore(deps): craft-libs 60d60d48 (dropdown fixes) * chore(deps): craft-libs 6af63481 (dropdown fixes) * chore(deps): craft-libs 4d7b82f7 (dropdown fixes) * chore(deps): craft-libs 92d392cf (#387 merged) --- Cargo.lock | 16 +-- Cargo.toml | 6 +- apps/wordcraft/Cargo.toml | 2 +- clippy.toml | 3 + crates/ui-egui/Cargo.toml | 6 +- crates/ui-egui/src/colors.rs | 2 +- crates/ui-egui/src/credits.rs | 2 +- .../src/dialogs/dialogs_insert/columns.rs | 2 +- .../src/dialogs/dialogs_insert/field.rs | 8 +- .../src/dialogs/dialogs_insert/symbol.rs | 2 +- .../src/dialogs/dialogs_layout/page.rs | 2 +- .../src/dialogs/dialogs_layout/print.rs | 4 +- .../src/dialogs/dialogs_layout/table.rs | 4 +- crates/ui-egui/src/dialogs/dialogs_lists.rs | 12 +- crates/ui-egui/src/dialogs/dialogs_notes.rs | 2 +- crates/ui-egui/src/dialogs/dialogs_para.rs | 8 +- .../src/dialogs/dialogs_refs/caption.rs | 4 +- .../src/dialogs/dialogs_refs/common.rs | 2 +- .../ui-egui/src/dialogs/dialogs_refs/index.rs | 2 +- .../src/dialogs/dialogs_refs/sources.rs | 2 +- .../ui-egui/src/dialogs/dialogs_refs/toc.rs | 2 +- crates/ui-egui/src/dialogs/dialogs_review.rs | 2 +- .../src/dialogs/dialogs_table_style.rs | 8 +- crates/ui-egui/src/dialogs/dispatch.rs | 6 +- crates/ui-egui/src/dialogs/find.rs | 2 +- crates/ui-egui/src/dialogs/forms.rs | 2 +- crates/ui-egui/src/dialogs/mailings.rs | 16 +-- crates/ui-egui/src/dialogs/page_setup.rs | 4 +- crates/ui-egui/src/dialogs/styles.rs | 2 +- crates/ui-egui/src/file_browser.rs | 2 +- crates/ui-egui/src/format_shape.rs | 2 +- crates/ui-egui/src/options.rs | 25 ++-- crates/ui-egui/src/print_page.rs | 4 +- crates/ui-egui/src/widgets/combo.rs | 112 +++++++++++------- 34 files changed, 156 insertions(+), 124 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b8a2c6be0..b0b896f52 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1014,12 +1014,12 @@ dependencies = [ [[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", @@ -1042,7 +1042,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 = [ "krilla", "thiserror 2.0.21", @@ -1051,7 +1051,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", @@ -1064,7 +1064,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", @@ -1084,7 +1084,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", @@ -1109,7 +1109,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", @@ -2854,7 +2854,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 fe7d7c9c7..2e2b787d1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -58,8 +58,8 @@ wuff = "0.2.9" brotli-decompressor = "5" vello_cpu = "0.2" image = { version = "0.25", default-features = false, features = ["png", "jpeg", "webp", "gif", "bmp", "tiff"] } -craft-pdf = { git = "https://github.com/storytold/craft-libs", rev = "b926b5c03e256c86ca10934f58d03f628810b5b7", version = "=0.1.0", default-features = false, features = ["write"] } -craft-update = { git = "https://github.com/storytold/craft-libs", version = "=0.1.0", rev = "b926b5c03e256c86ca10934f58d03f628810b5b7", features = ["egui"] } +craft-pdf = { git = "https://github.com/storytold/craft-libs", rev = "92d392cff132eaf53e13afff77f38b5c11b73aa0", version = "=0.1.0", default-features = false, features = ["write"] } +craft-update = { git = "https://github.com/storytold/craft-libs", version = "=0.1.0", rev = "92d392cff132eaf53e13afff77f38b5c11b73aa0", features = ["egui"] } quick-xml = "0.41" zip = { version = "3", default-features = false, features = ["deflate"] } # zlib streams (compressed metafile pictures in `.doc` drawings); already in the lock as a dependency of other crates. @@ -120,7 +120,7 @@ debug = 0 # Required by craft-pdf: the backend rejects writers that failed during unwinding. [patch.crates-io] -krilla = { git = "https://github.com/storytold/craft-libs", rev = "b926b5c03e256c86ca10934f58d03f628810b5b7" } +krilla = { git = "https://github.com/storytold/craft-libs", rev = "92d392cff132eaf53e13afff77f38b5c11b73aa0" } 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/apps/wordcraft/Cargo.toml b/apps/wordcraft/Cargo.toml index 85227c169..f60cf295e 100644 --- a/apps/wordcraft/Cargo.toml +++ b/apps/wordcraft/Cargo.toml @@ -73,7 +73,7 @@ windows = { version = "0.62", features = ["UI_ViewManagement", "System_UserProfi clipboard-win = { version = "5.4", features = ["std"] } [target.'cfg(not(target_arch = "wasm32"))'.dependencies] -craft-print = { git = "https://github.com/storytold/craft-libs", rev = "b926b5c03e256c86ca10934f58d03f628810b5b7" } +craft-print = { git = "https://github.com/storytold/craft-libs", rev = "92d392cff132eaf53e13afff77f38b5c11b73aa0" } rfd = { workspace = true } # Waits for an `rfd::AsyncFileDialog` on a background thread (the dialogs don't block the UI, #94); # rfd itself already depends on it. 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/Cargo.toml b/crates/ui-egui/Cargo.toml index e52538a37..76f539258 100644 --- a/crates/ui-egui/Cargo.toml +++ b/crates/ui-egui/Cargo.toml @@ -9,14 +9,14 @@ description = "WordCraft egui front end (Word-style ribbon UI); swappable: nothi workspace = true [dependencies] -craft-ui = { git = "https://github.com/storytold/craft-libs", rev = "b926b5c03e256c86ca10934f58d03f628810b5b7" } +craft-ui = { git = "https://github.com/storytold/craft-libs", rev = "92d392cff132eaf53e13afff77f38b5c11b73aa0" } # Interface translations: lookup, fallback, plurals and system-locale matching over the craft-i18n # catalogs that `build.rs` embeds from `CRAFT_I18N_DIR` (storytold/craft-i18n, pinned in # `craft-i18n.rev`). Same craft-libs rev as craft-ui; it brings lazy per-locale parsing # (storytold/craft-libs#247), ICU4X plural rules and bidi isolates around inserted values. -craft-i18n = { git = "https://github.com/storytold/craft-libs", rev = "b926b5c03e256c86ca10934f58d03f628810b5b7" } +craft-i18n = { git = "https://github.com/storytold/craft-libs", rev = "92d392cff132eaf53e13afff77f38b5c11b73aa0" } # One native window per open document: the window list, the Window menu and the egui viewports. -craft-window = { git = "https://github.com/storytold/craft-libs", rev = "b926b5c03e256c86ca10934f58d03f628810b5b7", features = ["egui"] } +craft-window = { git = "https://github.com/storytold/craft-libs", rev = "92d392cff132eaf53e13afff77f38b5c11b73aa0", features = ["egui"] } wordcraft-geom = { workspace = true, features = ["image"] } wordcraft-doc = { workspace = true } wordcraft-fonts = { workspace = true } diff --git a/crates/ui-egui/src/colors.rs b/crates/ui-egui/src/colors.rs index 211113b7b..4685fbcbb 100644 --- a/crates/ui-egui/src/colors.rs +++ b/crates/ui-egui/src/colors.rs @@ -672,7 +672,7 @@ fn custom(ui: &mut Ui, d: &mut ColorsDialog) { ui.add_space(8.0); egui::Grid::new("colors_custom").num_columns(2).spacing(vec2(10.0, 5.0)).show(ui, |ui| { ui.label(tl!("Color model:")); - egui::ComboBox::from_id_salt("colors_model") + craft_ui::ComboBox::from_id_salt("colors_model") .selected_text(match d.model { Model::Rgb => "RGB", Model::Hsl => "HSL", diff --git a/crates/ui-egui/src/credits.rs b/crates/ui-egui/src/credits.rs index be72aa333..3eaf4eecd 100644 --- a/crates/ui-egui/src/credits.rs +++ b/crates/ui-egui/src/credits.rs @@ -223,7 +223,7 @@ pub fn contributors_ui(ui: &mut egui::Ui) { } ui.separator(); ui.label(tl!("Sort")); - egui::ComboBox::from_id_salt("credits_sort").selected_text(tl!(v.key.label().0)).show_ui(ui, |ui| { + craft_ui::ComboBox::from_id_salt("credits_sort").selected_text(tl!(v.key.label().0)).show_ui(ui, |ui| { for k in SortKey::ALL { if ui.selectable_label(v.key == k, tl!(k.label().0)).clicked() { v.key = k; diff --git a/crates/ui-egui/src/dialogs/dialogs_insert/columns.rs b/crates/ui-egui/src/dialogs/dialogs_insert/columns.rs index c2edf3bcb..a15b8dc67 100644 --- a/crates/ui-egui/src/dialogs/dialogs_insert/columns.rs +++ b/crates/ui-egui/src/dialogs/dialogs_insert/columns.rs @@ -440,7 +440,7 @@ pub(super) fn columns_ui(app: &mut WordApp, ui: &mut Ui, f: &mut ColumnsForm) -> options.push(("selection", "Selected text")); } let shown = options.iter().find(|(k, _)| *k == f.apply).map(|(_, l)| *l).unwrap_or("This section"); - egui::ComboBox::from_id_salt("columns_apply").selected_text(tl!(shown)).width(180.0).show_ui(ui, |ui| { + craft_ui::ComboBox::from_id_salt("columns_apply").selected_text(tl!(shown)).width(180.0).show_ui(ui, |ui| { for (k, l) in &options { ui.selectable_value(&mut f.apply, (*k).to_string(), tl!(l)); } diff --git a/crates/ui-egui/src/dialogs/dialogs_insert/field.rs b/crates/ui-egui/src/dialogs/dialogs_insert/field.rs index 6dbfdacff..9387ad932 100644 --- a/crates/ui-egui/src/dialogs/dialogs_insert/field.rs +++ b/crates/ui-egui/src/dialogs/dialogs_insert/field.rs @@ -271,7 +271,7 @@ pub(super) fn field_ui(app: &mut WordApp, ui: &mut Ui, f: &mut FieldForm) -> boo ui.label(egui::RichText::new(tl!("Pick a field from the list")).font(semibold(12.5))); ui.label(tl!("Categories:")); let shown = tl!(f.category.as_str()).to_string(); - egui::ComboBox::from_id_salt("field_category").selected_text(shown).width(190.0).show_ui(ui, |ui| { + craft_ui::ComboBox::from_id_salt("field_category").selected_text(shown).width(190.0).show_ui(ui, |ui| { for c in FIELD_CATEGORIES { ui.selectable_value(&mut f.category, (*c).to_string(), tl!(c)); } @@ -355,7 +355,7 @@ fn field_options(ui: &mut Ui, f: &mut FieldForm, t: &Tokens) { } ui.label(tl!("Format:")); let shown = NUMBER_FORMATS.iter().find(|(v, _)| *v == f.number_format).map(|(_, l)| *l).unwrap_or("(none)"); - egui::ComboBox::from_id_salt("field_number").selected_text(if shown == "(none)" { tl!(shown) } else { shown }).width(200.0).show_ui( + craft_ui::ComboBox::from_id_salt("field_number").selected_text(if shown == "(none)" { tl!(shown) } else { shown }).width(200.0).show_ui( ui, |ui| { for (v, l) in NUMBER_FORMATS { @@ -370,7 +370,7 @@ fn field_options(ui: &mut Ui, f: &mut FieldForm, t: &Tokens) { ui.add(egui::TextEdit::singleline(&mut f.target).desired_width(200.0)); ui.label(egui::RichText::new(tl!("The document has no bookmarks yet.")).small().color(t.text_dim)); } else { - egui::ComboBox::from_id_salt("field_bookmark").selected_text(f.target.clone()).width(200.0).show_ui(ui, |ui| { + craft_ui::ComboBox::from_id_salt("field_bookmark").selected_text(f.target.clone()).width(200.0).show_ui(ui, |ui| { for b in &f.bookmarks { ui.selectable_value(&mut f.target, b.clone(), b); } @@ -392,7 +392,7 @@ fn field_options(ui: &mut Ui, f: &mut FieldForm, t: &Tokens) { } FieldKind::Style => { ui.label(tl!("Style name:")); - egui::ComboBox::from_id_salt("field_style").selected_text(f.target.clone()).width(200.0).show_ui(ui, |ui| { + craft_ui::ComboBox::from_id_salt("field_style").selected_text(f.target.clone()).width(200.0).show_ui(ui, |ui| { for n in &f.styles { ui.selectable_value(&mut f.target, n.clone(), n); } diff --git a/crates/ui-egui/src/dialogs/dialogs_insert/symbol.rs b/crates/ui-egui/src/dialogs/dialogs_insert/symbol.rs index 7d97e8bdb..aa1a326b3 100644 --- a/crates/ui-egui/src/dialogs/dialogs_insert/symbol.rs +++ b/crates/ui-egui/src/dialogs/dialogs_insert/symbol.rs @@ -313,7 +313,7 @@ pub(super) fn symbol_ui(app: &mut WordApp, ui: &mut Ui, f: &mut SymbolForm) -> b ui.label(tl!("Subset:")); let current = f.selected.and_then(subset_of).unwrap_or(""); let mut chosen = None; - egui::ComboBox::from_id_salt("symbol_subset").selected_text(current).width(170.0).show_ui(ui, |ui| { + craft_ui::ComboBox::from_id_salt("symbol_subset").selected_text(current).width(170.0).show_ui(ui, |ui| { for (name, a, b) in SUBSETS { let i = f.chars.partition_point(|c| (*c as u32) < *a); if f.chars.get(i).is_some_and(|c| (*c as u32) <= *b) && ui.selectable_label(*name == current, *name).clicked() { diff --git a/crates/ui-egui/src/dialogs/dialogs_layout/page.rs b/crates/ui-egui/src/dialogs/dialogs_layout/page.rs index 0a5a570ee..2e49f425b 100644 --- a/crates/ui-egui/src/dialogs/dialogs_layout/page.rs +++ b/crates/ui-egui/src/dialogs/dialogs_layout/page.rs @@ -261,7 +261,7 @@ pub(super) fn page_numbers_ui(app: &mut WordApp, ui: &mut Ui, f: &mut PageNumber ui.add_space(4.0); ui.label(tl!("Number format")); let label = crate::dialogs_notes::FORMATS.iter().find(|(v, _)| *v == f.format.as_str()).map(|(_, l)| *l).unwrap_or_default(); - egui::ComboBox::from_id_salt("pnum_format").selected_text(label).width(150.0).show_ui(ui, |ui| { + craft_ui::ComboBox::from_id_salt("pnum_format").selected_text(label).width(150.0).show_ui(ui, |ui| { for (v, l) in crate::dialogs_notes::FORMATS { ui.selectable_value(&mut f.format, v.to_string(), l); } diff --git a/crates/ui-egui/src/dialogs/dialogs_layout/print.rs b/crates/ui-egui/src/dialogs/dialogs_layout/print.rs index db36e378e..037bcd87a 100644 --- a/crates/ui-egui/src/dialogs/dialogs_layout/print.rs +++ b/crates/ui-egui/src/dialogs/dialogs_layout/print.rs @@ -39,7 +39,7 @@ pub(super) fn envelopes_ui(app: &mut WordApp, ui: &mut Ui, f: &mut EnvelopeForm) ui.horizontal(|ui| { ui.label(tl!("Envelope size:")); let label = |i: usize| ENVELOPE_SIZES.get(i).map(|(n, w, h)| envelope_label(n, *w, *h)).unwrap_or_default(); - egui::ComboBox::from_id_salt("env_size").selected_text(label(f.size)).width(220.0).show_ui(ui, |ui| { + craft_ui::ComboBox::from_id_salt("env_size").selected_text(label(f.size)).width(220.0).show_ui(ui, |ui| { for i in 0..ENVELOPE_SIZES.len() { ui.selectable_value(&mut f.size, i, label(i)); } @@ -156,7 +156,7 @@ pub(super) fn labels_ui(app: &mut WordApp, ui: &mut Ui, f: &mut LabelsForm) -> b }); ui.horizontal(|ui| { ui.label(tl!("Label:")); - egui::ComboBox::from_id_salt("label_product").selected_text(product_label(f.product)).width(300.0).show_ui(ui, |ui| { + craft_ui::ComboBox::from_id_salt("label_product").selected_text(product_label(f.product)).width(300.0).show_ui(ui, |ui| { for i in 0..LABEL_PRODUCTS.len() { ui.selectable_value(&mut f.product, i, product_label(i)); } diff --git a/crates/ui-egui/src/dialogs/dialogs_layout/table.rs b/crates/ui-egui/src/dialogs/dialogs_layout/table.rs index 94319a827..9eb411b34 100644 --- a/crates/ui-egui/src/dialogs/dialogs_layout/table.rs +++ b/crates/ui-egui/src/dialogs/dialogs_layout/table.rs @@ -55,7 +55,7 @@ pub(super) fn formula_ui(app: &mut WordApp, ui: &mut Ui, f: &mut FormulaForm) -> ui.horizontal(|ui| { ui.label(tl!("Paste function:")); let mut picked = None; - egui::ComboBox::from_id_salt("formula_paste").selected_text("").width(140.0).show_ui(ui, |ui| { + craft_ui::ComboBox::from_id_salt("formula_paste").selected_text("").width(140.0).show_ui(ui, |ui| { for name in FORMULA_FUNCTIONS { if ui.selectable_label(false, name).clicked() { picked = Some(name); @@ -120,7 +120,7 @@ impl SortForm { pub(super) fn sort_ui(app: &mut WordApp, ui: &mut Ui, f: &mut SortForm) -> bool { heading(ui, "Sort by"); ui.horizontal(|ui| { - egui::ComboBox::from_id_salt("sort_column").selected_text(f.column_label(f.column)).width(180.0).show_ui(ui, |ui| { + craft_ui::ComboBox::from_id_salt("sort_column").selected_text(f.column_label(f.column)).width(180.0).show_ui(ui, |ui| { for i in 0..f.headers.len() { let label = f.column_label(i); ui.selectable_value(&mut f.column, i, label); diff --git a/crates/ui-egui/src/dialogs/dialogs_lists.rs b/crates/ui-egui/src/dialogs/dialogs_lists.rs index ab3db4657..b3acb0e75 100644 --- a/crates/ui-egui/src/dialogs/dialogs_lists.rs +++ b/crates/ui-egui/src/dialogs/dialogs_lists.rs @@ -195,7 +195,7 @@ pub fn define_list(app: &mut WordApp, ui: &mut Ui, f: &mut ListForm) -> bool { ui.end_row(); ui.label(tl!("Number style for this level:")); let current = STYLES.iter().find(|(k, _)| *k == l.format).map(|(_, s)| *s).unwrap_or(""); - egui::ComboBox::from_id_salt("mll_style").selected_text(style_label(current)).show_ui(ui, |ui| { + craft_ui::ComboBox::from_id_salt("mll_style").selected_text(style_label(current)).show_ui(ui, |ui| { for (k, s) in STYLES { if ui.selectable_label(l.format == k, style_label(s)).clicked() { // A new style puts this level's number in the text if it had none (a bullet @@ -212,7 +212,7 @@ pub fn define_list(app: &mut WordApp, ui: &mut Ui, f: &mut ListForm) -> bool { ui.end_row(); if i > 0 { ui.label(tl!("Include level number from:")); - egui::ComboBox::from_id_salt("mll_include").selected_text("").show_ui(ui, |ui| { + craft_ui::ComboBox::from_id_salt("mll_include").selected_text("").show_ui(ui, |ui| { for k in 0..i { if ui.selectable_label(false, crate::i18n::fmt(tl!("Level {n}"), &[("n", &(k + 1).to_string())])).clicked() { l.text.push_str(&format!("%{}", k + 1)); @@ -228,7 +228,7 @@ pub fn define_list(app: &mut WordApp, ui: &mut Ui, f: &mut ListForm) -> bool { ui.checkbox(&mut l.restart, tl!("Restart list after:")); ui.add_enabled_ui(l.restart, |ui| { let shown = crate::i18n::fmt(tl!("Level {n}"), &[("n", &l.restart_after.clamp(1, i as u8).to_string())]); - egui::ComboBox::from_id_salt("mll_restart").selected_text(shown).show_ui(ui, |ui| { + craft_ui::ComboBox::from_id_salt("mll_restart").selected_text(shown).show_ui(ui, |ui| { for k in 1..=i as u8 { ui.selectable_value(&mut l.restart_after, k, crate::i18n::fmt(tl!("Level {n}"), &[("n", &k.to_string())])); } @@ -238,7 +238,7 @@ pub fn define_list(app: &mut WordApp, ui: &mut Ui, f: &mut ListForm) -> bool { } ui.label(tl!("Link level to style:")); let shown = styles.iter().find(|(id, _)| *id == l.style).map(|(_, n)| n.clone()).unwrap_or_else(|| tl!("(no style)").to_string()); - egui::ComboBox::from_id_salt("mll_link").selected_text(shown).show_ui(ui, |ui| { + craft_ui::ComboBox::from_id_salt("mll_link").selected_text(shown).show_ui(ui, |ui| { ui.selectable_value(&mut l.style, String::new(), tl!("(no style)")); for (id, name) in &styles { ui.selectable_value(&mut l.style, id.clone(), name); @@ -277,7 +277,7 @@ pub fn define_list(app: &mut WordApp, ui: &mut Ui, f: &mut ListForm) -> bool { "nothing" => tl!("Nothing"), _ => tl!("Tab character"), }; - egui::ComboBox::from_id_salt("mll_follow").selected_text(follow(&l.follow)).show_ui(ui, |ui| { + craft_ui::ComboBox::from_id_salt("mll_follow").selected_text(follow(&l.follow)).show_ui(ui, |ui| { for k in ["tab", "space", "nothing"] { ui.selectable_value(&mut l.follow, k.to_string(), follow(k)); } @@ -423,7 +423,7 @@ impl TrackForm { /// A choice from `options` ((value, label)) in a drop-down. fn choice(ui: &mut Ui, id: &str, value: &mut String, options: &[(&str, &str)]) { let label = |k: &str| options.iter().find(|(v, _)| *v == k).map(|(_, l)| tl!(l).to_string()).unwrap_or_default(); - egui::ComboBox::from_id_salt(id).selected_text(label(value)).width(150.0).show_ui(ui, |ui| { + craft_ui::ComboBox::from_id_salt(id).selected_text(label(value)).width(150.0).show_ui(ui, |ui| { for (v, l) in options { ui.selectable_value(value, v.to_string(), tl!(l)); } diff --git a/crates/ui-egui/src/dialogs/dialogs_notes.rs b/crates/ui-egui/src/dialogs/dialogs_notes.rs index b52ee7f59..627906f35 100644 --- a/crates/ui-egui/src/dialogs/dialogs_notes.rs +++ b/crates/ui-egui/src/dialogs/dialogs_notes.rs @@ -71,7 +71,7 @@ impl NoteForm { fn choice(ui: &mut Ui, id: &str, value: &mut String, options: &[(&str, &str)], tr: bool) { let show = |l: &str| if tr { tl!(l).to_string() } else { l.to_string() }; let label = options.iter().find(|(v, _)| *v == value.as_str()).map(|(_, l)| show(l)).unwrap_or_default(); - egui::ComboBox::from_id_salt(id).selected_text(label).width(170.0).show_ui(ui, |ui| { + craft_ui::ComboBox::from_id_salt(id).selected_text(label).width(170.0).show_ui(ui, |ui| { for (v, l) in options { ui.selectable_value(value, v.to_string(), show(l)); } diff --git a/crates/ui-egui/src/dialogs/dialogs_para.rs b/crates/ui-egui/src/dialogs/dialogs_para.rs index 34654ea8f..8694bd2eb 100644 --- a/crates/ui-egui/src/dialogs/dialogs_para.rs +++ b/crates/ui-egui/src/dialogs/dialogs_para.rs @@ -248,7 +248,7 @@ pub fn tabs(app: &mut WordApp, ui: &mut Ui, f: &mut TabsForm) -> bool { } // Alignment, picked in place. let mut a = t.align; - egui::ComboBox::from_id_salt("tab_align") + craft_ui::ComboBox::from_id_salt("tab_align") .width(86.0) .selected_text(tl!(crate::ruler_tabs::kind_label(a))) .show_ui(ui, |ui| { @@ -269,7 +269,7 @@ pub fn tabs(app: &mut WordApp, ui: &mut Ui, f: &mut TabsForm) -> bool { .find(|(v, _, _)| *v == l) .map(|(_, _, fill)| if fill.is_empty() { tl!("None").to_string() } else { (*fill).to_string() }) .unwrap_or_default(); - egui::ComboBox::from_id_salt("tab_leader") + craft_ui::ComboBox::from_id_salt("tab_leader") .width(64.0) .selected_text(shown) .show_ui(ui, |ui| { @@ -632,7 +632,7 @@ fn lines(ui: &mut Ui, id: &str, l: &mut LineForm, theme: &[Rgb], fill: Option egui::Grid::new("cap_opts").num_columns(2).spacing(vec2(10.0, 6.0)).show(ui, |ui| { let label_label = ui.label(tl!("Label:")); let labels: Vec<(String, &str)> = f.labels.iter().map(|l| (l.clone(), l.as_str())).collect(); - let combo = egui::ComboBox::from_id_salt("cap_label").selected_text(tl!(&f.label)).width(150.0).show_ui(ui, |ui| { + let combo = craft_ui::ComboBox::from_id_salt("cap_label").selected_text(tl!(&f.label)).width(150.0).show_ui(ui, |ui| { for (v, l) in &labels { ui.selectable_value(&mut f.label, v.clone(), tl!(l)); } @@ -162,7 +162,7 @@ pub(super) fn numbering_ui(ui: &mut Ui, f: &mut CaptionForm, was: &CaptionForm) let chapter_label = ui.label(tl!("Chapters begin at:")); let opts: Vec<(u8, String)> = (1..=9u8).map(|l| (l, crate::i18n::fmt(tl!("Heading {n}"), &[("n", &l.to_string())]))).collect(); let shown = opts.iter().find(|(l, _)| *l == f.chapter_level).map(|(_, s)| s.clone()).unwrap_or_default(); - let combo = egui::ComboBox::from_id_salt("cap_level").selected_text(shown).width(150.0).show_ui(ui, |ui| { + let combo = craft_ui::ComboBox::from_id_salt("cap_level").selected_text(shown).width(150.0).show_ui(ui, |ui| { for (l, s) in &opts { ui.selectable_value(&mut f.chapter_level, *l, s); } diff --git a/crates/ui-egui/src/dialogs/dialogs_refs/common.rs b/crates/ui-egui/src/dialogs/dialogs_refs/common.rs index 771c31dd5..4973caacb 100644 --- a/crates/ui-egui/src/dialogs/dialogs_refs/common.rs +++ b/crates/ui-egui/src/dialogs/dialogs_refs/common.rs @@ -8,7 +8,7 @@ pub(super) fn heading(ui: &mut Ui, s: &str) { /// A choice from `options` ((value, label)) in a drop-down, named by its visible label. pub(super) fn choice(ui: &mut Ui, id: &str, value: &mut T, options: &[(T, &str)], label: &egui::Response) { let shown = options.iter().find(|(v, _)| v == value).map(|(_, l)| tl!(l).to_string()).unwrap_or_default(); - let combo = egui::ComboBox::from_id_salt(id).selected_text(shown).width(150.0).show_ui(ui, |ui| { + let combo = craft_ui::ComboBox::from_id_salt(id).selected_text(shown).width(150.0).show_ui(ui, |ui| { for (v, l) in options { ui.selectable_value(value, v.clone(), tl!(l)); } diff --git a/crates/ui-egui/src/dialogs/dialogs_refs/index.rs b/crates/ui-egui/src/dialogs/dialogs_refs/index.rs index 36585f197..7be327413 100644 --- a/crates/ui-egui/src/dialogs/dialogs_refs/index.rs +++ b/crates/ui-egui/src/dialogs/dialogs_refs/index.rs @@ -172,7 +172,7 @@ pub(super) fn mark_ui(app: &mut WordApp, ui: &mut Ui, f: &mut MarkForm) -> Next ui.add_enabled_ui(!f.bookmarks.is_empty(), |ui| ui.radio_value(&mut f.option, "range".to_string(), tl!("Pages of bookmark:"))).inner; ui.add_enabled_ui(f.option == "range", |ui| { let opts: Vec<(String, &str)> = f.bookmarks.iter().map(|b| (b.clone(), b.as_str())).collect(); - let combo = egui::ComboBox::from_id_salt("xe_bm").selected_text(f.bookmark.clone()).width(140.0).show_ui(ui, |ui| { + let combo = craft_ui::ComboBox::from_id_salt("xe_bm").selected_text(f.bookmark.clone()).width(140.0).show_ui(ui, |ui| { for (v, l) in &opts { ui.selectable_value(&mut f.bookmark, v.clone(), *l); } diff --git a/crates/ui-egui/src/dialogs/dialogs_refs/sources.rs b/crates/ui-egui/src/dialogs/dialogs_refs/sources.rs index 3c55e9c2e..cc9196d71 100644 --- a/crates/ui-egui/src/dialogs/dialogs_refs/sources.rs +++ b/crates/ui-egui/src/dialogs/dialogs_refs/sources.rs @@ -150,7 +150,7 @@ pub(super) fn source_ui(app: &mut WordApp, ui: &mut Ui, f: &mut SourceForm) -> N egui::Grid::new("src").num_columns(2).spacing(vec2(10.0, 5.0)).show(ui, |ui| { let kind_label = ui.label(tl!("Source type:")); let shown = SOURCE_TYPES.iter().find(|(k, ..)| *k == f.source.kind).map(|(_, l, _)| tl!(l).to_string()).unwrap_or_default(); - let combo = egui::ComboBox::from_id_salt("src_kind").selected_text(shown).width(200.0).show_ui(ui, |ui| { + let combo = craft_ui::ComboBox::from_id_salt("src_kind").selected_text(shown).width(200.0).show_ui(ui, |ui| { for (k, l, _) in SOURCE_TYPES { ui.selectable_value(&mut f.source.kind, k.to_string(), tl!(l)); } diff --git a/crates/ui-egui/src/dialogs/dialogs_refs/toc.rs b/crates/ui-egui/src/dialogs/dialogs_refs/toc.rs index ccf407074..8b6e4b004 100644 --- a/crates/ui-egui/src/dialogs/dialogs_refs/toc.rs +++ b/crates/ui-egui/src/dialogs/dialogs_refs/toc.rs @@ -256,7 +256,7 @@ pub(super) fn figures_ui(app: &mut WordApp, ui: &mut Ui, f: &mut FiguresForm) -> ui.horizontal(|ui| { let label_label = ui.label(tl!("Label:")); let labels: Vec<(String, &str)> = f.labels.iter().map(|l| (l.clone(), l.as_str())).collect(); - let combo = egui::ComboBox::from_id_salt("tof_label").selected_text(tl!(&f.label)).width(150.0).show_ui(ui, |ui| { + let combo = craft_ui::ComboBox::from_id_salt("tof_label").selected_text(tl!(&f.label)).width(150.0).show_ui(ui, |ui| { for (v, l) in &labels { ui.selectable_value(&mut f.label, v.clone(), tl!(l)); } diff --git a/crates/ui-egui/src/dialogs/dialogs_review.rs b/crates/ui-egui/src/dialogs/dialogs_review.rs index b1f38ccb9..2c3f5d736 100644 --- a/crates/ui-egui/src/dialogs/dialogs_review.rs +++ b/crates/ui-egui/src/dialogs/dialogs_review.rs @@ -647,7 +647,7 @@ fn setup_ui(app: &mut WordApp, ui: &mut Ui, st: &mut RestrictPane) { ui.add_enabled_ui(st.editing, |ui| { let current = EDIT_MODES.get(st.mode).map(|m| m.1).unwrap_or(""); let combo = - egui::ComboBox::from_id_salt("restrict_mode").selected_text(tl!(current)).width(ui.available_width().min(220.0)).show_ui(ui, |ui| { + craft_ui::ComboBox::from_id_salt("restrict_mode").selected_text(tl!(current)).width(ui.available_width().min(220.0)).show_ui(ui, |ui| { for (i, (_, label)) in EDIT_MODES.iter().enumerate() { ui.selectable_value(&mut st.mode, i, tl!(label)); } diff --git a/crates/ui-egui/src/dialogs/dialogs_table_style.rs b/crates/ui-egui/src/dialogs/dialogs_table_style.rs index b7bbc1f8f..1cc1afd56 100644 --- a/crates/ui-egui/src/dialogs/dialogs_table_style.rs +++ b/crates/ui-egui/src/dialogs/dialogs_table_style.rs @@ -474,7 +474,7 @@ pub fn body(app: &mut WordApp, ui: &mut Ui, f: Form) -> bool { ui.text_edit_singleline(name).labelled_by(name_label.id); ui.end_row(); let base_label = ui.label(tl!("Style based on:")); - let base = egui::ComboBox::from_id_salt("tstyle_base").width(200.0).selected_text(base_name).show_ui(ui, |ui| { + let base = craft_ui::ComboBox::from_id_salt("tstyle_base").width(200.0).selected_text(base_name).show_ui(ui, |ui| { for (sid, sname) in &styles { if ui.selectable_label(sid == based_on, sname).clicked() && sid != based_on { *based_on = sid.clone(); @@ -485,7 +485,7 @@ pub fn body(app: &mut WordApp, ui: &mut Ui, f: Form) -> bool { base.response.labelled_by(base_label.id); ui.end_row(); let region_label = ui.label(tl!("Apply formatting to:")); - let part = egui::ComboBox::from_id_salt("tstyle_region") + let part = craft_ui::ComboBox::from_id_salt("tstyle_region") .width(200.0) .selected_text(tl!(PARTS.get(*region).map_or("Whole Table", |(_, l)| *l))) .show_ui(ui, |ui| { @@ -528,14 +528,14 @@ pub fn body(app: &mut WordApp, ui: &mut Ui, f: Form) -> bool { ui.horizontal(|ui| { let current = LINE_STYLES.iter().find(|(s, _)| *s == pen.style).map_or("Single", |(_, n)| *n); let style_label = ui.label(tl!("Style:")); - let style = egui::ComboBox::from_id_salt("tstyle_pen_style").selected_text(tl!(current)).width(90.0).show_ui(ui, |ui| { + let style = craft_ui::ComboBox::from_id_salt("tstyle_pen_style").selected_text(tl!(current)).width(90.0).show_ui(ui, |ui| { for (s, n) in LINE_STYLES { ui.selectable_value(&mut pen.style, s, tl!(n)); } }); style.response.labelled_by(style_label.id); let width_label = ui.label(tl!("Width:")); - let width = egui::ComboBox::from_id_salt("tstyle_pen_width") + let width = craft_ui::ComboBox::from_id_salt("tstyle_pen_width") .selected_text(crate::i18n::fmt(tl!("{n} pt"), &[("n", &pen.width.to_string())])) .width(70.0) .show_ui(ui, |ui| { diff --git a/crates/ui-egui/src/dialogs/dispatch.rs b/crates/ui-egui/src/dialogs/dispatch.rs index 59eb4531f..4af75b926 100644 --- a/crates/ui-egui/src/dialogs/dispatch.rs +++ b/crates/ui-egui/src/dialogs/dispatch.rs @@ -213,7 +213,7 @@ pub(super) fn body(app: &mut WordApp, ui: &mut Ui, d: &mut Dialog) -> bool { } else { ALIGNS.iter().find(|(v, _)| v == align).map_or(align.as_str(), |(_, l)| tl!(l)) }; - let combo = egui::ComboBox::from_id_salt("para_align").selected_text(shown).show_ui(ui, |ui| { + let combo = craft_ui::ComboBox::from_id_salt("para_align").selected_text(shown).show_ui(ui, |ui| { for (v, l) in ALIGNS { if ui.selectable_value(align, v.to_string(), tl!(l)).clicked() { mixed.remove("align"); @@ -767,7 +767,7 @@ pub(super) fn body(app: &mut WordApp, ui: &mut Ui, d: &mut Dialog) -> bool { ui.end_row(); let align_label = ui.label(tl!("Alignment:")); let shown = ALIGNS.iter().find(|(k, _)| k == align).map_or("Center", |(_, l)| l); - let combo = egui::ComboBox::from_id_salt("phonetic_align").selected_text(tl!(shown)).show_ui(ui, |ui| { + let combo = craft_ui::ComboBox::from_id_salt("phonetic_align").selected_text(tl!(shown)).show_ui(ui, |ui| { for (k, l) in ALIGNS { ui.selectable_value(align, k.to_string(), tl!(l)); } @@ -1304,7 +1304,7 @@ pub(super) fn body(app: &mut WordApp, ui: &mut Ui, d: &mut Dialog) -> bool { if fields.is_empty() { ui.text_edit_singleline(field); } else { - egui::ComboBox::from_id_salt("merge_rule_field").selected_text(field.as_str()).width(200.0).show_ui(ui, |ui| { + craft_ui::ComboBox::from_id_salt("merge_rule_field").selected_text(field.as_str()).width(200.0).show_ui(ui, |ui| { for f in fields.iter() { ui.selectable_value(field, f.clone(), f); } diff --git a/crates/ui-egui/src/dialogs/find.rs b/crates/ui-egui/src/dialogs/find.rs index ca096c760..faecd8cff 100644 --- a/crates/ui-egui/src/dialogs/find.rs +++ b/crates/ui-egui/src/dialogs/find.rs @@ -200,7 +200,7 @@ pub(super) fn goto_body(app: &mut WordApp, ui: &mut Ui, what: &mut GoToWhat, tar } GoToWhat::Bookmark => { let label = ui.label(tl!("Enter bookmark name:")); - egui::ComboBox::from_id_salt("goto_bookmark") + craft_ui::ComboBox::from_id_salt("goto_bookmark") .width(220.0) .selected_text(target.as_str()) .show_ui(ui, |ui| { diff --git a/crates/ui-egui/src/dialogs/forms.rs b/crates/ui-egui/src/dialogs/forms.rs index d0e10a2e4..8108d6d91 100644 --- a/crates/ui-egui/src/dialogs/forms.rs +++ b/crates/ui-egui/src/dialogs/forms.rs @@ -392,7 +392,7 @@ pub(super) fn table_properties(ui: &mut Ui, f: &mut TableForm) { ui.horizontal(|ui| { ui.add_enabled(f.row_height_on, len(&mut f.row_height, 0.02, unit)); ui.add_enabled_ui(f.row_height_on, |ui| { - egui::ComboBox::from_id_salt("tp_rule").selected_text(tl!(if f.row_exact { "Exactly" } else { "At least" })).show_ui(ui, |ui| { + craft_ui::ComboBox::from_id_salt("tp_rule").selected_text(tl!(if f.row_exact { "Exactly" } else { "At least" })).show_ui(ui, |ui| { ui.selectable_value(&mut f.row_exact, false, tl!("At least")); ui.selectable_value(&mut f.row_exact, true, tl!("Exactly")); }); diff --git a/crates/ui-egui/src/dialogs/mailings.rs b/crates/ui-egui/src/dialogs/mailings.rs index 20d58a4b8..e5ba3289e 100644 --- a/crates/ui-egui/src/dialogs/mailings.rs +++ b/crates/ui-egui/src/dialogs/mailings.rs @@ -414,7 +414,7 @@ fn recipient_filter(ui: &mut Ui, fields: &[String], filter: &mut RecipientFilter let names: Vec = fields.iter().map(|f| f.trim().to_string()).filter(|f| !f.is_empty()).collect(); let field_combo = |ui: &mut Ui, id: (&str, usize), value: &mut String, none: bool| { let shown = if value.is_empty() { tl!("(none)").to_string() } else { value.clone() }; - egui::ComboBox::from_id_salt(id).width(130.0).selected_text(shown).show_ui(ui, |ui| { + craft_ui::ComboBox::from_id_salt(id).width(130.0).selected_text(shown).show_ui(ui, |ui| { if none { ui.selectable_value(value, String::new(), tl!("(none)")); } @@ -434,13 +434,13 @@ fn recipient_filter(ui: &mut Ui, fields: &[String], filter: &mut RecipientFilter if i == 0 { ui.label(""); } else { - egui::ComboBox::from_id_salt(("rf_join", i)).width(60.0).selected_text(tl!(if r.or { "Or" } else { "And" })).show_ui(ui, |ui| { + craft_ui::ComboBox::from_id_salt(("rf_join", i)).width(60.0).selected_text(tl!(if r.or { "Or" } else { "And" })).show_ui(ui, |ui| { ui.selectable_value(&mut r.or, false, tl!("And")); ui.selectable_value(&mut r.or, true, tl!("Or")); }); } field_combo(ui, ("rf_field", i), &mut r.field, false); - egui::ComboBox::from_id_salt(("rf_op", i)).width(150.0).selected_text(tl!(r.op.label())).show_ui(ui, |ui| { + craft_ui::ComboBox::from_id_salt(("rf_op", i)).width(150.0).selected_text(tl!(r.op.label())).show_ui(ui, |ui| { for op in FilterOp::ALL { ui.selectable_value(&mut r.op, op, tl!(op.label())); } @@ -471,13 +471,13 @@ fn recipient_filter(ui: &mut Ui, fields: &[String], filter: &mut RecipientFilter ui.label(tl!(if i == 0 { "Sort by" } else { "Then by" })); field_combo(ui, ("rs_field", i), field, true); ui.add_enabled_ui(!field.is_empty(), |ui| { - egui::ComboBox::from_id_salt(("rs_dir", i)).width(110.0).selected_text(tl!(if *desc { "Descending" } else { "Ascending" })).show_ui( - ui, - |ui| { + craft_ui::ComboBox::from_id_salt(("rs_dir", i)) + .width(110.0) + .selected_text(tl!(if *desc { "Descending" } else { "Ascending" })) + .show_ui(ui, |ui| { ui.selectable_value(desc, false, tl!("Ascending")); ui.selectable_value(desc, true, tl!("Descending")); - }, - ); + }); }); ui.end_row(); } diff --git a/crates/ui-egui/src/dialogs/page_setup.rs b/crates/ui-egui/src/dialogs/page_setup.rs index 082cac2eb..fb232d563 100644 --- a/crates/ui-egui/src/dialogs/page_setup.rs +++ b/crates/ui-egui/src/dialogs/page_setup.rs @@ -91,7 +91,7 @@ pub(super) fn page_paper_group(ui: &mut Ui, paper: &mut (f32, f32), landscape: & let label = ui.label(tl!(l.size)); let entry = paper_entry(*paper).filter(|_| !*custom); let shown = tl!(entry.unwrap_or(l.custom)); - let combo = egui::ComboBox::from_id_salt("ps_paper_size").width(180.0).height(400.0).selected_text(shown).show_ui(ui, |ui| { + let combo = craft_ui::ComboBox::from_id_salt("ps_paper_size").width(180.0).height(400.0).selected_text(shown).show_ui(ui, |ui| { for (n, w, h) in wordcraft_geom::PAPER_SIZES { if ui.selectable_label(entry == Some(*n), tl!(n)).clicked() { let (w, h) = (w / ppu, h / ppu); @@ -130,7 +130,7 @@ pub(super) fn page_valign_combo(ui: &mut egui::Ui, valign: &mut wordcraft_doc::s ui.horizontal(|ui| { let label = ui.label(tl!("Vertical alignment:")); let shown = CHOICES.iter().find(|(v, _)| v == valign).map_or("", |(_, l)| tl!(l)); - let combo = egui::ComboBox::from_id_salt("ps_valign").selected_text(shown).show_ui(ui, |ui| { + let combo = craft_ui::ComboBox::from_id_salt("ps_valign").selected_text(shown).show_ui(ui, |ui| { for (v, l) in CHOICES { ui.selectable_value(valign, v, tl!(l)); } diff --git a/crates/ui-egui/src/dialogs/styles.rs b/crates/ui-egui/src/dialogs/styles.rs index 72b25f01f..87ba91307 100644 --- a/crates/ui-egui/src/dialogs/styles.rs +++ b/crates/ui-egui/src/dialogs/styles.rs @@ -12,7 +12,7 @@ pub(super) fn manage_styles(app: &mut WordApp, ui: &mut Ui, alphabetical: &mut b } ui.horizontal(|ui| { ui.label(tl!("Sort order:")); - egui::ComboBox::from_id_salt("manage_styles_sort") + craft_ui::ComboBox::from_id_salt("manage_styles_sort") .selected_text(if *alphabetical { tl!("Alphabetical") } else { tl!("As Recommended") }) .show_ui(ui, |ui| { ui.selectable_value(alphabetical, false, tl!("As Recommended")); diff --git a/crates/ui-egui/src/file_browser.rs b/crates/ui-egui/src/file_browser.rs index 463467bff..e05f39bac 100644 --- a/crates/ui-egui/src/file_browser.rs +++ b/crates/ui-egui/src/file_browser.rs @@ -440,7 +440,7 @@ fn body(ui: &mut Ui, b: &mut FileBrowser, t: &Tokens) -> Option> } let before = b.filter; let current = b.filters.get(b.filter).map(|(l, _)| tl!(l.as_str()).to_string()).unwrap_or_default(); - egui::ComboBox::from_id_salt("builtin_file_browser_type") + craft_ui::ComboBox::from_id_salt("builtin_file_browser_type") .selected_text(current) .width(150.0) .show_ui(ui, |ui| { diff --git a/crates/ui-egui/src/format_shape.rs b/crates/ui-egui/src/format_shape.rs index 699e2ff0a..b71045eb5 100644 --- a/crates/ui-egui/src/format_shape.rs +++ b/crates/ui-egui/src/format_shape.rs @@ -229,7 +229,7 @@ fn gradient(app: &mut WordApp, ui: &mut Ui, theme: &[Rgb], g: &Gradient) { ui.label(tl!("Type")); let t = type_of(&g.kind); let mut pick = t; - egui::ComboBox::from_id_salt("format_shape.type").selected_text(tl!(TYPES[t].0)).show_ui(ui, |ui| { + craft_ui::ComboBox::from_id_salt("format_shape.type").selected_text(tl!(TYPES[t].0)).show_ui(ui, |ui| { for (i, (label, _)) in TYPES.iter().enumerate() { ui.selectable_value(&mut pick, i, tl!(label)); } diff --git a/crates/ui-egui/src/options.rs b/crates/ui-egui/src/options.rs index b2a22a60d..83f9d77de 100644 --- a/crates/ui-egui/src/options.rs +++ b/crates/ui-egui/src/options.rs @@ -541,7 +541,7 @@ fn advanced(app: &mut WordApp, ui: &mut Ui) { let auto = app.session.prefs.units.is_none(); let name = UNITS.iter().find(|(u, _)| *u == units).map_or("Inches", |(_, n)| n); let shown = if auto { format!("{} ({})", tl!("Automatic"), tl!(name)) } else { tl!(name).to_string() }; - let combo = egui::ComboBox::from_id_salt("options_units").selected_text(shown).width(200.0).show_ui(ui, |ui| { + let combo = craft_ui::ComboBox::from_id_salt("options_units").selected_text(shown).width(200.0).show_ui(ui, |ui| { if ui.selectable_label(auto, tl!("Automatic")).clicked() { set(app, json!({"units": "auto"})); } @@ -561,7 +561,7 @@ fn advanced(app: &mut WordApp, ui: &mut Ui) { let system_tag = if system.territory.is_empty() { system.language.clone() } else { format!("{}-{}", system.language, system.territory) }; let automatic = crate::i18n::fmt(tl!("Automatic ({region})"), &[("region", system_tag.as_str())]); let shown = chosen.clone().unwrap_or_else(|| automatic.clone()); - let combo = egui::ComboBox::from_id_salt("options_region").selected_text(shown).width(200.0).show_ui(ui, |ui| { + let combo = craft_ui::ComboBox::from_id_salt("options_region").selected_text(shown).width(200.0).show_ui(ui, |ui| { if ui.selectable_label(chosen.is_none(), automatic.as_str()).clicked() { set(app, json!({"region": "auto"})); } @@ -652,7 +652,7 @@ fn theme_picker(app: &mut WordApp, ui: &mut Ui) { use crate::theme::Appearance; ui.horizontal(|ui| { let label = ui.label(tl!("Interface theme:")); - let combo = egui::ComboBox::from_id_salt("interface_theme").selected_text(tl!(app.ui.theme.label())).width(220.0).show_ui(ui, |ui| { + let combo = craft_ui::ComboBox::from_id_salt("interface_theme").selected_text(tl!(app.ui.theme.label())).width(220.0).show_ui(ui, |ui| { for a in Appearance::ALL { if ui.selectable_label(app.ui.theme == a, tl!(a.label())).clicked() { let _ = app.run("ui.theme", json!({"value": a.code()})); @@ -673,7 +673,7 @@ fn interface_size_picker(app: &mut WordApp, ui: &mut Ui) { let selected = size.percent().map_or_else(|| auto.clone(), |pct| format!("{pct}%")); ui.horizontal(|ui| { let label = ui.label(tl!("Interface size:")); - let combo = egui::ComboBox::from_id_salt("interface_size").selected_text(selected).width(220.0).show_ui(ui, |ui| { + let combo = craft_ui::ComboBox::from_id_salt("interface_size").selected_text(selected).width(220.0).show_ui(ui, |ui| { if ui.selectable_label(size.is_auto(), auto).clicked() { let _ = app.run("ui.interfaceSize", json!({"value": "auto"})); } @@ -700,13 +700,14 @@ fn animations_picker(app: &mut WordApp, ui: &mut Ui) { use crate::theme::Animations; ui.horizontal(|ui| { let label = ui.label(tl!("Animation effects:")); - let combo = egui::ComboBox::from_id_salt("animation_effects").selected_text(tl!(app.ui.animations.label())).width(220.0).show_ui(ui, |ui| { - for a in Animations::ALL { - if ui.selectable_label(app.ui.animations == a, tl!(a.label())).clicked() { - let _ = app.run("ui.animations", json!({"value": a.code()})); + let combo = + craft_ui::ComboBox::from_id_salt("animation_effects").selected_text(tl!(app.ui.animations.label())).width(220.0).show_ui(ui, |ui| { + for a in Animations::ALL { + if ui.selectable_label(app.ui.animations == a, tl!(a.label())).clicked() { + let _ = app.run("ui.animations", json!({"value": a.code()})); + } } - } - }); + }); combo.response.labelled_by(label.id); }); note(ui, "Smooth scrolling and zooming, and menus and buttons that fade in. Off shows every change at once."); @@ -730,7 +731,7 @@ fn language_picker(app: &mut WordApp, ui: &mut Ui) { let current = if app.ui.language == AUTO { auto_label.clone() } else { Lang::from_pref(&app.ui.language).name().to_string() }; ui.horizontal(|ui| { let label = ui.label(tl!("Interface language:")); - let combo = egui::ComboBox::from_id_salt("interface_language").selected_text(current).width(220.0).show_ui(ui, |ui| { + let combo = craft_ui::ComboBox::from_id_salt("interface_language").selected_text(current).width(220.0).show_ui(ui, |ui| { if ui.selectable_label(app.ui.language == AUTO, auto_label.as_str()).clicked() { let _ = app.run("ui.language", json!({"value": AUTO})); } @@ -841,7 +842,7 @@ fn numeral_picker(app: &mut WordApp, ui: &mut Ui) { ui.horizontal(|ui| { let label = ui.label(tl!("Numerals:")); let mut sel = app.session.view.numeral; - let combo = egui::ComboBox::from_id_salt("numerals").selected_text(tl!(sel.label())).width(220.0).show_ui(ui, |ui| { + let combo = craft_ui::ComboBox::from_id_salt("numerals").selected_text(tl!(sel.label())).width(220.0).show_ui(ui, |ui| { for m in [NumeralMode::Arabic, NumeralMode::Hindi, NumeralMode::Context, NumeralMode::System] { ui.selectable_value(&mut sel, m, tl!(m.label())); } diff --git a/crates/ui-egui/src/print_page.rs b/crates/ui-egui/src/print_page.rs index fa8b7c2a7..e496268bb 100644 --- a/crates/ui-egui/src/print_page.rs +++ b/crates/ui-egui/src/print_page.rs @@ -85,7 +85,7 @@ const ORIENTATIONS: [(&str, &str); 3] = [("auto", "Auto orientation"), ("portrai fn choice(ui: &mut Ui, salt: &str, current: &mut String, items: &[(&str, &str)], label: &egui::Response) -> bool { let shown = items.iter().find(|(id, _)| *id == current.as_str()).map_or("", |(_, l)| *l); let mut changed = false; - let combo = egui::ComboBox::from_id_salt(salt).width(260.0).selected_text(tl!(shown)).show_ui(ui, |ui| { + let combo = craft_ui::ComboBox::from_id_salt(salt).width(260.0).selected_text(tl!(shown)).show_ui(ui, |ui| { for (id, item) in items { if ui.selectable_label(current == id, tl!(item)).clicked() && current != id { *current = (*id).to_string(); @@ -101,7 +101,7 @@ fn choice(ui: &mut Ui, salt: &str, current: &mut String, items: &[(&str, &str)], fn choice_named(ui: &mut Ui, salt: &str, current: &mut String, items: &[(&str, &str)], name: &str) -> bool { let shown = items.iter().find(|(id, _)| *id == current.as_str()).map_or("", |(_, l)| *l); let mut changed = false; - let combo = egui::ComboBox::from_id_salt(salt).width(260.0).selected_text(tl!(shown)).show_ui(ui, |ui| { + let combo = craft_ui::ComboBox::from_id_salt(salt).width(260.0).selected_text(tl!(shown)).show_ui(ui, |ui| { for (id, item) in items { if ui.selectable_label(current == id, tl!(item)).clicked() && current != id { *current = (*id).to_string(); diff --git a/crates/ui-egui/src/widgets/combo.rs b/crates/ui-egui/src/widgets/combo.rs index e48b59b9c..fd74f66ad 100644 --- a/crates/ui-egui/src/widgets/combo.rs +++ b/crates/ui-egui/src/widgets/combo.rs @@ -307,26 +307,36 @@ fn combo_impl( // The list drops from the box's left edge, as in Word, not from the arrow (#535). // The font list closes on a chosen entry or a click outside it, not on its headings. let close = if search { egui::PopupCloseBehavior::CloseOnClickOutside } else { egui::PopupCloseBehavior::CloseOnClick }; - egui::Popup::menu(&aresp).anchor(resp.rect.union(ar)).close_behavior(close).show(|ui| { - let item = |k: usize| items.get(k); - let row = |k: usize| section_row(k, recent, &item); - if let Some(v) = list_rows(ui, width, current, section_rows(recent.len(), items.len()), &row, None, None, preview).0 { - out = Some(v); + let mut menu_field = aresp.clone(); + menu_field.rect = resp.rect.union(ar); + let menu = craft_ui::combo::Menu::new(ui, &menu_field).close_behavior(close).height(420.0); + let row_h = if preview.is_some() { COMBO_PREVIEW_ROW_H } else { ui.spacing().interact_size.y }; + let item = |k: usize| items.get(k); + let row = |k: usize| section_row(k, recent, &item); + let total = section_rows(recent.len(), items.len()); + let list = |ui: &mut Ui, range: std::ops::Range, ()| draw_rows(ui, width, current, range, &row, None, preview, row_h); + let footer = |ui: &mut Ui| { + let mut picked = None; + ui.separator(); + if ui.button(tl!("Add Font…")).clicked() { + picked = Some(ADD_FONT.to_string()); ui.close(); } - if search { - ui.separator(); - if ui.button(tl!("Add Font…")).clicked() { - out = Some(ADD_FONT.to_string()); - ui.close(); - } - #[cfg(target_arch = "wasm32")] - if ui.button(tl!("Load System Installed Fonts…")).clicked() { - out = Some(LOAD_SYSTEM_FONTS.to_string()); - ui.close(); - } + #[cfg(target_arch = "wasm32")] + if ui.button(tl!("Load System Installed Fonts…")).clicked() { + picked = Some(LOAD_SYSTEM_FONTS.to_string()); + ui.close(); } - }); + picked + }; + let picked = if search { + menu.show_rows_with_header_footer(|_| (), row_h, |()| total, list, footer).map(|(v, f)| v.or(f)) + } else { + menu.show_rows_with_header(|_| (), row_h, |()| total, list) + }; + if let Some(v) = picked.flatten() { + out = Some(v); + } let n = section_rows(recent_shown.len(), s.matches.len()); if s.open && n > 0 { let matches = s.matches.clone(); @@ -449,6 +459,47 @@ fn heading_row(ui: &mut Ui, text: &str, size: egui::Vec2, rule: bool) { }); } +/// The rows `range` of a combo's list, in `ui`'s scroll area. Returns the clicked item. +fn draw_rows<'a>( + ui: &mut Ui, + width: f32, + current: &str, + range: std::ops::Range, + row: &dyn Fn(usize) -> Option>, + highlight: Option, + preview: Option<&dyn Fn(&mut Ui, &str) -> Response>, + row_h: f32, +) -> Option { + let mut out = None; + let fill = Tokens::get(ui.ctx()).checked; + ui.set_min_width(width + 60.0); + for k in range { + let it = match row(k) { + Some(Row::Item(it)) => it, + Some(Row::Heading(h)) => { + heading_row(ui, h, vec2(width + 60.0, row_h), k > 0); + continue; + } + None => continue, + }; + let lit = highlight == Some(k); + let clicked = match preview { + Some(p) => { + if lit { + let r = Rect::from_min_size(ui.cursor().min, vec2(ui.available_width(), row_h)); + ui.painter().rect_filled(r, 3.0, fill); + } + p(ui, it).clicked() + } + None => ui.selectable_label(if highlight.is_some() { lit } else { it == current }, it.as_str()).clicked(), + }; + if clicked { + out = Some(it.clone()); + } + } + out +} + /// The rows of [`combo_list`], virtualised, for `n` rows fetched by index. With a `highlight` /// that row is marked instead of `current`; `scroll` (last frame's offset and visible height) /// scrolls the highlight into view. Returns the clicked item, the scroll offset and the height. @@ -483,32 +534,9 @@ fn list_rows<'a>( }; area = area.vertical_scroll_offset(offset.max(0.0)); } - let fill = Tokens::get(ui.ctx()).checked; let shown = area.show_rows(ui, row_h, n, |ui, range| { - ui.set_min_width(width + 60.0); - for k in range { - let it = match row(k) { - Some(Row::Item(it)) => it, - Some(Row::Heading(h)) => { - heading_row(ui, h, vec2(width + 60.0, row_h), k > 0); - continue; - } - None => continue, - }; - let lit = highlight == Some(k); - let clicked = match preview { - Some(p) => { - if lit { - let r = Rect::from_min_size(ui.cursor().min, vec2(ui.available_width(), row_h)); - ui.painter().rect_filled(r, 3.0, fill); - } - p(ui, it).clicked() - } - None => ui.selectable_label(if highlight.is_some() { lit } else { it == current }, it.as_str()).clicked(), - }; - if clicked { - out = Some(it.clone()); - } + if let Some(v) = draw_rows(ui, width, current, range, row, highlight, preview, row_h) { + out = Some(v); } }); (out, shown.state.clamped_offset().y, shown.inner_rect.height())