diff --git a/crates/ui-egui/src/dialogs.rs b/crates/ui-egui/src/dialogs.rs index 111ebe139..c57b51351 100644 --- a/crates/ui-egui/src/dialogs.rs +++ b/crates/ui-egui/src/dialogs.rs @@ -491,6 +491,8 @@ fn table_style_preview(ui: &mut Ui, style: Option<&wordcraft_doc::styles::TableS && !header && (ri.saturating_sub(usize::from(look.header_row)) / p.band_size.unwrap_or(1).clamp(1, 1000) as usize).is_multiple_of(2); for (ci, txt) in row.iter().enumerate() { + // Sample words in the interface language; numbers as they are. + let txt = &if txt.chars().any(char::is_alphabetic) { tl!(txt) } else { *txt }; let first = look.first_column && ci == 0; let last = look.last_column && ci + 1 == COLS; let col_band = look.banded_columns && !first && ci.saturating_sub(usize::from(look.first_column)).is_multiple_of(2); diff --git a/crates/ui-egui/src/i18n/audit.rs b/crates/ui-egui/src/i18n/audit.rs index 253129994..36e0f12b8 100644 --- a/crates/ui-egui/src/i18n/audit.rs +++ b/crates/ui-egui/src/i18n/audit.rs @@ -26,7 +26,8 @@ fn texts(shape: &egui::Shape, out: &mut Vec) { /// Run a frame per entry of `steps` (its events), then a few quiet ones, and return every text /// drawn in the last. fn frames(ctx: &egui::Context, app: &mut WordApp, steps: Vec>) -> Vec { - let screen = egui::Rect::from_min_size(egui::Pos2::ZERO, egui::vec2(1440.0, 900.0)); + // Wide enough that no ribbon tab overflows behind its scroll arrows. + let screen = egui::Rect::from_min_size(egui::Pos2::ZERO, egui::vec2(2560.0, 900.0)); let mut drawn = Vec::new(); let n = steps.len() + 3; let mut steps = steps.into_iter(); @@ -61,18 +62,22 @@ fn escape() -> Vec> { vec![vec![egui::Event::Key { key: egui::Key::Escape, physical_key: None, pressed: true, repeat: false, modifiers: Default::default() }]] } -/// Centres of the enabled, clickable widgets drawn last frame in the ribbon band. +/// Centres of the enabled, clickable widgets drawn last frame in the ribbon: below the tab row +/// and above the document area (the largest clickable widget). fn ribbon_controls(ctx: &egui::Context) -> Vec { ctx.viewport(|v| { - v.prev_pass + let widgets: Vec<&egui::WidgetRect> = v + .prev_pass .widgets .layers() .filter(|(layer, _)| matches!(layer.order, egui::Order::Background | egui::Order::Middle)) .flat_map(|(_, w)| w.iter()) - .filter(|w| w.enabled && w.sense.senses_click() && w.rect.width() < 300.0) - .map(|w| w.rect.center()) - .filter(|c| (52.0..125.0).contains(&c.y)) - .collect() + .filter(|w| w.enabled && w.sense.senses_click()) + .collect(); + let page_top = widgets.iter().max_by(|a, b| a.rect.area().total_cmp(&b.rect.area())).map_or(f32::INFINITY, |w| w.rect.min.y); + // The tab row ends where the ribbon's first group label row starts; tabs sit near y = 53. + let tabs_bottom = 64.0; + widgets.iter().filter(|w| w.rect.width() < 300.0).map(|w| w.rect.center()).filter(|c| c.y > tabs_bottom && c.y < page_top).collect() }) } @@ -123,7 +128,10 @@ impl Exempt { let status = app.status_msg.iter().map(|(m, _)| m).chain(&app.read_aloud_error).any(|m| text.contains(m.as_str())); // A theme's fonts after its (translated) name: `Studio: Georgia / Georgia`. let theme_fonts = plain.strip_prefix(": ").is_some_and(|f| f.split(" / ").count() == 2); - plain.chars().count() <= 3 // lettering inside icons (`Aa`, `abc`) and initials + let icon_lettering = matches!(plain, "Aa" | "ab" | "abc" | "ac" | "cd" | "ab-" | "fx" | "ABC"); + let initials = plain.chars().count() == 2 && plain.chars().all(|c| c.is_ascii_uppercase()); + icon_lettering // drawn inside icons + || initials // the account button || status || theme_fonts || is_shortcut() @@ -202,8 +210,8 @@ fn untranslated_interface_text() { record(&app, &format!("pane {pane}"), drawn); let _ = app.run(pane, json!({"value": false})); } - // Right-click on the page. - let drawn = frames(&ctx, &mut app, click(egui::pos2(720.0, 520.0), egui::PointerButton::Secondary)); + // Right-click on the page (centred in the window). + let drawn = frames(&ctx, &mut app, click(egui::pos2(1280.0, 520.0), egui::PointerButton::Secondary)); record(&app, "context menu", drawn); frames(&ctx, &mut app, escape()); // Contextual tabs: a table, then an equation. @@ -219,28 +227,31 @@ fn untranslated_interface_text() { record(&app, "tab Equation", drawn); // Every ribbon control on every tab, and whatever menu or dialog it opens. A fresh app per // tab keeps one tab's commands from changing the next. + // A fresh app per control, set up the same way, so no click changes what the next one hits + // (Escape, for one, leaves an equation and takes the Equation tab with it). let contextual = ["Table Design", "Table Layout", "Equation"]; - for tab in tabs.iter().chain(&contextual) { + let open = |tab: &str| { let ctx = egui::Context::default(); let mut app = fresh_app(&ctx); - match *tab { + match tab { "Table Design" | "Table Layout" => drop(app.run("insert.table", json!({"rows": 2, "cols": 2}))), "Equation" => drop(app.run("insert.equation", json!({}))), _ => {} } let _ = app.run("ui.tab", json!({"tab": tab})); frame(&ctx, &mut app); - for at in ribbon_controls(&ctx) { + (ctx, app) + }; + for tab in tabs.iter().chain(&contextual) { + let (ctx, _) = open(tab); + let controls = ribbon_controls(&ctx); + for at in controls { + let (ctx, mut app) = open(tab); // Hover first (tooltips), then click (menus, dialogs). let hovered = frames(&ctx, &mut app, vec![vec![egui::Event::PointerMoved(at)]; 3]); record(&app, &format!("{tab} › hover"), hovered); let drawn = frames(&ctx, &mut app, click(at, egui::PointerButton::Primary)); record(&app, &format!("{tab} › click"), drawn); - frames(&ctx, &mut app, escape()); - app.dialog = None; - let _ = app.run("ui.backstage", json!({"value": false})); - let _ = app.run("ui.tab", json!({"tab": tab})); - frame(&ctx, &mut app); } } pseudo::set(false);