Browse / github.com/storytold/wordcraft / patch
Dialogs: draw real tabs in five more dialogs
Dialogs: draw real tabs in five more dialogs Follow-up to #4029 (closed issue #4026). Five dialogs still drew their tabs as selectable buttons. They now use `widgets::dialog_tabs`: - Colors (Standard / Custom) - Symbol (Symbols / Special Characters) - Borders and Shading (Borders / Page Border / Shading) - AutoCorrect (AutoCorrect / AutoFormat As You Type / Math AutoCorrect) - Customize Ribbon (Ribbon / Quick Access Toolbar) The Navigation pane is done in #4480. Option lists ("Go to what", "Link to") are not tabs and stay as they are. A dialog still changes size when you switch tabs; that is a separate issue. **Tests.** `tes
- Status
- open
- Score
- 0.00
- Adopters
- 0 other operators
- Author
- fluorine (imported from pull request #4481, open)
- Base
2cf1d6c7ae94723a45bd6e7e8c884fe375148750- Size
- +126 −43 in 7 files
- Review
- pending
- Shared
- 2026-10-28
- Id
607a58bcd8bff3ce0b649468
How it went
No other operator has reported using it yet.
Diff
raw · crates/ui-egui/src/colors.rs crates/ui-egui/src/dialogs/dialogs_insert/symbol.rs crates/ui-egui/src/dialogs/dialogs_para.rs crates/ui-egui/src/dialogs/dialogs_review.rs crates/ui-egui/src/dialogs/dialogs_ribbon.rs crates/ui-egui/src/lib.rs crates/ui-egui/src/tests_dialog_tabs.rs
@@ -488,14 +488,11 @@ fn body(ui: &mut Ui, d: &mut ColorsDialog) -> Option<bool> { ui.set_width(430.0); ui.label(egui::RichText::new(tl!("Colors")).font(crate::theme::semibold(14.0))); ui.add_space(6.0);- ui.horizontal(|ui| {- for (tab, label) in [(ColorsTab::Standard, "Standard"), (ColorsTab::Custom, "Custom")] {- if ui.selectable_label(d.tab == tab, tl!(label)).clicked() {- d.tab = tab;- }- }- });- ui.separator();+ let names = ["Standard", "Custom"].map(|n| tl!(n).to_string());+ if let Some(k) = crate::widgets::dialog_tabs(ui, &names, d.tab as usize) {+ d.tab = if k == 0 { ColorsTab::Standard } else { ColorsTab::Custom };+ }+ ui.add_space(6.0); ui.horizontal(|ui| { ui.vertical(|ui| { ui.set_width(330.0);@@ -269,14 +269,11 @@ fn symbol_cell(app: &mut WordApp, ui: &mut Ui, font: &str, c: char, selected: bo pub(super) fn symbol_ui(app: &mut WordApp, ui: &mut Ui, f: &mut SymbolForm) -> bool { let t = Tokens::get(ui.ctx()); ui.set_width(GRID_COLS as f32 * CELL + 24.0);- ui.horizontal(|ui| {- for (i, l) in ["Symbols", "Special Characters"].into_iter().enumerate() {- if ui.selectable_label(f.tab == i as u8, tl!(l)).clicked() {- f.tab = i as u8;- }- }- });- ui.separator();+ let names = ["Symbols", "Special Characters"].map(|n| tl!(n).to_string());+ if let Some(k) = crate::widgets::dialog_tabs(ui, &names, f.tab as usize) {+ f.tab = k as u8;+ }+ ui.add_space(6.0); let mut insert: Option<(Value, String, String)> = None; if f.tab == 1 { if special_ui(app, ui, f, &mut insert, &t) {@@ -552,14 +552,11 @@ impl BordersForm { /// The Borders and Shading dialog body. Returns true to close. pub fn borders(app: &mut WordApp, ui: &mut Ui, f: &mut BordersForm) -> bool {- ui.horizontal(|ui| {- for (i, l) in ["Borders", "Page Border", "Shading"].into_iter().enumerate() {- if ui.selectable_label(f.tab == i as u8, tl!(l)).clicked() {- f.tab = i as u8;- }- }- });- ui.separator();+ let names = ["Borders", "Page Border", "Shading"].map(|n| tl!(n).to_string());+ if let Some(k) = crate::widgets::dialog_tabs(ui, &names, f.tab as usize) {+ f.tab = k as u8;+ }+ ui.add_space(6.0); let theme = app.session.doc.settings.theme_colors.clone(); match f.tab { 0 => {@@ -383,14 +383,11 @@ fn list_change(app: &mut WordApp, f: &mut AutoCorrectForm, params: Value) -> boo } fn autocorrect_ui(app: &mut WordApp, ui: &mut Ui, f: &mut AutoCorrectForm) -> bool {- ui.horizontal(|ui| {- for (i, l) in ["AutoCorrect", "AutoFormat As You Type", "Math AutoCorrect"].into_iter().enumerate() {- if ui.selectable_label(f.tab == i as u8, tl!(l)).clicked() {- f.tab = i as u8;- }- }- });- ui.separator();+ let names = ["AutoCorrect", "AutoFormat As You Type", "Math AutoCorrect"].map(|n| tl!(n).to_string());+ if let Some(k) = crate::widgets::dialog_tabs(ui, &names, f.tab as usize) {+ f.tab = k as u8;+ }+ ui.add_space(6.0); match f.tab { 0 => autocorrect_tab(app, ui, f), 1 => {@@ -88,17 +88,14 @@ fn label_of(app: &WordApp, id: &str) -> String { /// The dialog body; true closes it. pub fn customize_ribbon(app: &mut WordApp, ui: &mut Ui, f: &mut RibbonForm) -> bool {- ui.horizontal(|ui| {- for (page, name) in [(Page::Ribbon, "Ribbon"), (Page::QuickAccess, "Quick Access Toolbar")] {- if ui.selectable_label(f.page == page, tl!(name)).clicked() && f.page != page {- f.page = page;- f.renaming = None;- f.confirm_reset = false;- f.message.clear();- }- }- });- ui.separator();+ let names = ["Ribbon", "Quick Access Toolbar"].map(|n| tl!(n).to_string());+ if let Some(k) = crate::widgets::dialog_tabs(ui, &names, (f.page == Page::QuickAccess) as usize) {+ f.page = if k == 0 { Page::Ribbon } else { Page::QuickAccess };+ f.renaming = None;+ f.confirm_reset = false;+ f.message.clear();+ }+ ui.add_space(6.0); let (left, right, h) = (150.0, 230.0, 300.0); ui.add(egui::TextEdit::singleline(&mut f.filter).hint_text(tl!("Search")).desired_width(left + right + 8.0)); let mut cats: Vec<(String, String)> = categories(app).into_iter().map(|c| (c.clone(), tl!(&c).to_string())).collect();@@ -639,5 +639,8 @@ pub(crate) fn rust_sources(dir: &std::path::Path) -> Vec<std::path::PathBuf> { files } +#[cfg(test)]+mod tests_dialog_tabs;+ #[cfg(test)] mod tests;new file mode 100644@@ -0,0 +1,95 @@+//! Dialogs with real tabs: a click on a tab shows its page.++use egui_kittest::kittest::Queryable;+use serde_json::json;+use wordcraft_engine::Session;++use crate::WordApp;+use crate::dialogs::Dialog;++fn harness(name: &str) -> egui_kittest::Harness<'static, WordApp> {+ let mut app = WordApp::new(Session::new(wordcraft_doc::Document::new()), crate::Services::default());+ let _ = app.run("ui.language", json!({"value": "en"}));+ let _ = app.run("document.setText", json!({"text": "Some text"}));+ let _ = app.run("select.all", json!({}));+ let _ = app.run("ui.dialog", json!({"name": name}));+ if app.dialog.is_none() && app.colors.is_none() {+ app.dialog = Dialog::open(name, &mut app);+ }+ let mut h = egui_kittest::Harness::builder().with_size(egui::vec2(1440.0, 900.0)).with_accessibility_check(false).build_ui_state(+ |ui, app: &mut WordApp| {+ let ctx = ui.ctx().clone();+ app.logic(&ctx);+ app.ui(ui);+ },+ app,+ );+ h.ctx.set_fonts(crate::theme::font_definitions(false, false));+ h.ctx.set_missing_glyph_policy(egui::MissingGlyphPolicy::Tofu);+ settle(&mut h);+ h+}++fn settle(h: &mut egui_kittest::Harness<'static, WordApp>) {+ for _ in 0..4 {+ h.step();+ }+}++fn tab_button<'a>(h: &'a egui_kittest::Harness<'static, WordApp>, label: &'a str) -> egui_kittest::Node<'a> {+ h.query_all_by_role_and_label(egui::accesskit::Role::Button, label).last().unwrap_or_else(|| panic!("no {label} button"))+}++/// Click each tab in turn. After every click `page` must report its index.+fn check(name: &str, tabs: &[&str], page: impl Fn(&WordApp) -> usize) {+ let mut h = harness(name);+ assert_eq!(page(h.state()), 0, "{name}: opens on the first tab");+ for (i, label) in tabs.iter().enumerate() {+ tab_button(&h, label).click();+ settle(&mut h);+ assert_eq!(page(h.state()), i, "{name}: the {label} tab shows its page");+ }+}++#[test]+fn colors_tabs_switch_pages() {+ check("colors", &["Standard", "Custom"], |app| app.colors.as_ref().map_or(9, |d| d.tab as usize));+}++#[test]+fn symbol_tabs_switch_pages() {+ check("symbol", &["Symbols", "Special Characters"], |app| match &app.dialog {+ Some(Dialog::Insert(d)) => match &**d {+ crate::dialogs_insert::InsertDialog::Symbol(f) => f.tab as usize,+ _ => 9,+ },+ _ => 9,+ });+}++#[test]+fn borders_tabs_switch_pages() {+ check("borders", &["Borders", "Page Border", "Shading"], |app| match &app.dialog {+ Some(Dialog::Borders { form }) => form.tab as usize,+ _ => 9,+ });+}++#[test]+fn autocorrect_tabs_switch_pages() {+ check("autoCorrect", &["AutoCorrect", "AutoFormat As You Type", "Math AutoCorrect"], |app| match &app.dialog {+ Some(Dialog::Review(d)) => match &**d {+ crate::dialogs_review::ReviewDialog::AutoCorrect(f) => f.tab as usize,+ _ => 9,+ },+ _ => 9,+ });+}++#[test]+fn customize_ribbon_tabs_switch_pages() {+ check("customizeRibbon", &["Ribbon", "Quick Access Toolbar"], |app| match &app.dialog {+ Some(Dialog::CustomizeRibbon { form }) => (form.page == crate::dialogs_ribbon::Page::QuickAccess) as usize,+ _ => 9,+ });+}