diff --git a/crates/ui-egui/src/hotbar.rs b/crates/ui-egui/src/hotbar.rs index 5075ffee2e..077d899dfe 100644 --- a/crates/ui-egui/src/hotbar.rs +++ b/crates/ui-egui/src/hotbar.rs @@ -112,7 +112,7 @@ impl Hotbar { fn settings_button(ui: &mut egui::Ui, cell: f32, picker_open: &mut bool) -> bool { let settings = ui .push_id("hotbar-settings", |ui| { - icons::button(ui, "settings", cell, false, tl!("Hotbar settings")).on_hover_text(tl!("Hotbar settings / add tools")) + icons::labeled_button(ui, "settings", cell, false, tl!("Hotbar settings"), tl!("Hotbar settings / add tools")) }) .inner; if settings.clicked() { diff --git a/crates/ui-egui/src/widgets/icons.rs b/crates/ui-egui/src/widgets/icons.rs index 13f50a0d6c..aae365bba3 100644 --- a/crates/ui-egui/src/widgets/icons.rs +++ b/crates/ui-egui/src/widgets/icons.rs @@ -34,7 +34,14 @@ pub fn paint(ui: &egui::Ui, rect: Rect, name: &str, size: f32, tint: Color32) { } /// Square icon button: transparent until hovered; `selected` gets the accent treatment. +/// `tooltip` is both its accessible name and its one tooltip. pub fn button(ui: &mut egui::Ui, name: &str, box_size: f32, selected: bool, tooltip: &str) -> Response { + labeled_button(ui, name, box_size, selected, tooltip, tooltip) +} + +/// [`button`] whose tooltip says more than its accessible name (`label`). It shows that one +/// tooltip, so callers add no `on_hover_text` of their own (#6548: two tooltips stacked). +pub fn labeled_button(ui: &mut egui::Ui, name: &str, box_size: f32, selected: bool, label: &str, tooltip: &str) -> Response { let t = Tokens::get(ui.ctx()); // The shared button paints no fill of its own: the eased press wash below replaces it. let clear = |icon| ButtonVisuals { fill: Color32::TRANSPARENT, stroke: Stroke::NONE, icon }; @@ -46,7 +53,7 @@ pub fn button(ui: &mut egui::Ui, name: &str, box_size: f32, selected: bool, tool corner_radius: t.radius.into(), focus_stroke: Stroke::new(1.0, t.accent), }; - let label = if tooltip.is_empty() { name } else { tooltip }; + let label = if label.is_empty() { name } else { label }; // The wash eases from the response, so reserve its place under the icon and focus ring. let wash = ui.painter().add(egui::Shape::Noop); let mut painted = None; diff --git a/crates/ui-egui/tests/it/comments.rs b/crates/ui-egui/tests/it/comments.rs index fa387f9a18..c9ab522ecd 100644 --- a/crates/ui-egui/tests/it/comments.rs +++ b/crates/ui-egui/tests/it/comments.rs @@ -3590,6 +3590,40 @@ fn hotbar_picker_adds_and_runs_freehand_highlight() { assert_eq!(h.state().quick_tool, QuickTool::Comment(pdfcraft_ui_egui::comments::CommentTool::FreehandHighlight)); } +/// #6548: resting on the hotbar's gear showed two stacked tooltips, "Hotbar settings" and +/// "Hotbar settings / add tools". It shows the longer one only and keeps its short name. +#[test] +fn hotbar_settings_button_shows_one_tooltip() { + let mut h = harness(|_| {}); + h.run_steps(2); + h.get_by_label("Hotbar settings").hover(); + // egui shows a tooltip once the pointer has rested `tooltip_delay` seconds of *input* time, + // and the harness advances that clock by a fixed step every frame, so the wait is counted in + // simulated seconds rather than slept: it cannot pass early or depend on the machine's speed. + let delay = f64::from(h.ctx.global_style().interaction.tooltip_delay); + let start = h.ctx.input(|i| i.time); + let elapsed = |h: &Harness<'static, PdfCraftApp>| h.ctx.input(|i| i.time) - start; + // The button's name; a second tooltip repeating it would make two. Checked after every + // frame, so one that shows only briefly is caught too. + let names = |h: &Harness<'static, PdfCraftApp>| h.query_all_by_label("Hotbar settings").count(); + let mut frames = 0; + while h.query_all_by_label("Hotbar settings / add tools").next().is_none() { + assert!(elapsed(&h) < 4.0 * delay && frames < 100, "no tooltip after {:.2}s of rest", elapsed(&h)); + h.step(); + frames += 1; + assert_eq!(names(&h), 1, "a second tooltip at {:.2}s", elapsed(&h)); + } + // Rest two more delays, so a second tooltip would have had its chance to open. + let shown = elapsed(&h); + while elapsed(&h) < shown + 2.0 * delay && frames < 100 { + h.step(); + frames += 1; + assert_eq!(names(&h), 1, "a second tooltip at {:.2}s", elapsed(&h)); + } + assert_eq!(h.query_all_by_label("Hotbar settings / add tools").count(), 1, "the tooltip shows"); + assert_eq!(names(&h), 1, "the button's name, and no second tooltip"); +} + #[test] fn hotbar_picker_searches_translated_tool_labels() { if crate::support::skip_without_catalogs("hotbar_picker_searches_translated_tool_labels") {