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)
This commit is contained in:
aiongg
2026-10-27 10:27:21 -04:00
committed by GitHub
parent 2cf1d6c7ae
commit 436d444ef0
34 changed files with 156 additions and 124 deletions
Generated
+8 -8
View File
@@ -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",
+3 -3
View File
@@ -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" }
+1 -1
View File
@@ -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.
+3
View File
@@ -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)" },
]
+3 -3
View File
@@ -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 }
+1 -1
View File
@@ -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",
+1 -1
View File
@@ -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;
@@ -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));
}
@@ -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);
}
@@ -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() {
@@ -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);
}
@@ -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));
}
@@ -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);
+6 -6
View File
@@ -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));
}
+1 -1
View File
@@ -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));
}
+4 -4
View File
@@ -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<Rg
ui.vertical(|ui| {
ui.label(tl!("Style:"));
let current = STYLES.iter().find(|(s, _)| *s == l.style).map(|(_, n)| *n).unwrap_or("Single");
egui::ComboBox::from_id_salt((id, "style")).selected_text(tl!(current)).width(120.0).show_ui(ui, |ui| {
craft_ui::ComboBox::from_id_salt((id, "style")).selected_text(tl!(current)).width(120.0).show_ui(ui, |ui| {
for (s, n) in STYLES {
ui.selectable_value(&mut l.style, s, tl!(n));
}
@@ -640,7 +640,7 @@ fn lines(ui: &mut Ui, id: &str, l: &mut LineForm, theme: &[Rgb], fill: Option<Rg
ui.label(tl!("Color:"));
color_menu(ui, theme, &mut l.color);
ui.label(tl!("Width:"));
egui::ComboBox::from_id_salt((id, "width"))
craft_ui::ComboBox::from_id_salt((id, "width"))
.selected_text(crate::i18n::fmt(tl!("{n} pt"), &[("n", &l.width.to_string())]))
.width(120.0)
.show_ui(ui, |ui| {
@@ -108,7 +108,7 @@ pub(super) fn caption_ui(app: &mut WordApp, ui: &mut Ui, f: &mut CaptionForm) ->
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);
}
@@ -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<T: PartialEq + Clone>(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));
}
@@ -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);
}
@@ -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));
}
@@ -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));
}
+1 -1
View File
@@ -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));
}
@@ -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| {
+3 -3
View File
@@ -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);
}
+1 -1
View File
@@ -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| {
+1 -1
View File
@@ -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"));
});
+8 -8
View File
@@ -414,7 +414,7 @@ fn recipient_filter(ui: &mut Ui, fields: &[String], filter: &mut RecipientFilter
let names: Vec<String> = 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();
}
+2 -2
View File
@@ -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));
}
+1 -1
View File
@@ -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"));
+1 -1
View File
@@ -440,7 +440,7 @@ fn body(ui: &mut Ui, b: &mut FileBrowser, t: &Tokens) -> Option<Option<PathBuf>>
}
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| {
+1 -1
View File
@@ -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));
}
+13 -12
View File
@@ -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()));
}
+2 -2
View File
@@ -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();
+70 -42
View File
@@ -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<usize>, ()| 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<usize>,
row: &dyn Fn(usize) -> Option<Row<'a>>,
highlight: Option<usize>,
preview: Option<&dyn Fn(&mut Ui, &str) -> Response>,
row_h: f32,
) -> Option<String> {
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())