collective

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

diff --git a/crates/ui-egui/src/colors.rs b/crates/ui-egui/src/colors.rs--- a/crates/ui-egui/src/colors.rs+++ b/crates/ui-egui/src/colors.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);diff --git a/crates/ui-egui/src/dialogs/dialogs_insert/symbol.rs b/crates/ui-egui/src/dialogs/dialogs_insert/symbol.rs--- a/crates/ui-egui/src/dialogs/dialogs_insert/symbol.rs+++ b/crates/ui-egui/src/dialogs/dialogs_insert/symbol.rs@@ -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) {diff --git a/crates/ui-egui/src/dialogs/dialogs_para.rs b/crates/ui-egui/src/dialogs/dialogs_para.rs--- a/crates/ui-egui/src/dialogs/dialogs_para.rs+++ b/crates/ui-egui/src/dialogs/dialogs_para.rs@@ -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 => {diff --git a/crates/ui-egui/src/dialogs/dialogs_review.rs b/crates/ui-egui/src/dialogs/dialogs_review.rs--- a/crates/ui-egui/src/dialogs/dialogs_review.rs+++ b/crates/ui-egui/src/dialogs/dialogs_review.rs@@ -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 => {diff --git a/crates/ui-egui/src/dialogs/dialogs_ribbon.rs b/crates/ui-egui/src/dialogs/dialogs_ribbon.rs--- a/crates/ui-egui/src/dialogs/dialogs_ribbon.rs+++ b/crates/ui-egui/src/dialogs/dialogs_ribbon.rs@@ -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();diff --git a/crates/ui-egui/src/lib.rs b/crates/ui-egui/src/lib.rs--- a/crates/ui-egui/src/lib.rs+++ b/crates/ui-egui/src/lib.rs@@ -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;diff --git a/crates/ui-egui/src/tests_dialog_tabs.rs b/crates/ui-egui/src/tests_dialog_tabs.rsnew file mode 100644--- /dev/null+++ b/crates/ui-egui/src/tests_dialog_tabs.rs@@ -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,+    });+}