diff --git a/crates/ui/src/layout.rs b/crates/ui/src/layout.rs index 7e4e7f4c9dabd518821f5fabe695ed762cb7703a..5a9f4203eb805b4390196afefe8b77f0e118e065 100644 --- a/crates/ui/src/layout.rs +++ b/crates/ui/src/layout.rs @@ -62,7 +62,7 @@ fn resolve(nodes: &mut [Built], popup: &[usize], index: usize, states: &HashMap< let around = match anchor { Anchor::Point([x, y]) => [x, y, x, y], Anchor::Dialog | Anchor::Top => [0.0; 4], - Anchor::Below(id) | Anchor::Right(id) | Anchor::Over(id) => { + Anchor::Below(id) | Anchor::Tip(id) | Anchor::Right(id) | Anchor::Over(id) => { let (rect, holder) = laid_out(id).unwrap_or_default(); let [left, top, right, bottom] = rect; match anchor { diff --git a/crates/ui/src/lib.rs b/crates/ui/src/lib.rs index 48c67718855d2ad80f955ac8b65144cad820950f..796580640613a36bbf3d6d83d5d47cc77bad1220 100644 --- a/crates/ui/src/lib.rs +++ b/crates/ui/src/lib.rs @@ -230,6 +230,8 @@ pub enum Anchor { /// Over the box from its corner, the popup's contents covering it, as a combo box opens /// into its own list; at least as wide as it. Over(Id), + /// Under the box, centred on it, as a tooltip shows. + Tip(Id), /// At a point, as a context menu opens. Point([f32; 2]), /// Centred across the window near its top, over the interface, which dims, as a dialog @@ -252,6 +254,10 @@ impl Anchor { Anchor::Below(_) | Anchor::Point(_) => Some(1), Anchor::Right(_) => Some(0), Anchor::Over(_) => None, + Anchor::Tip(_) if axis == 0 => { + return ((around[0] + around[2] - size) / 2.0).clamp(POPUP_MARGIN, most); + } + Anchor::Tip(_) => Some(1), Anchor::Dialog | Anchor::Top if axis == 0 => { return ((room - size) / 2.0).max(POPUP_MARGIN); } @@ -593,6 +599,7 @@ impl Placed { } = self; let pivot = match anchor { Anchor::Below(_) | Anchor::Point(_) => [around[0], around[3]], + Anchor::Tip(_) => [(around[0] + around[2]) / 2.0, around[3]], Anchor::Right(_) => [around[2], around[1]], Anchor::Over(_) => [around[0], around[1]], Anchor::Dialog | Anchor::Top => [(rect[0] + rect[2]) / 2.0, rect[1]], diff --git a/crates/ui/src/popup.rs b/crates/ui/src/popup.rs index 06594aff22411a47a77440930f692b2016e47dcd..f11931f28ee16f2386b9a71a7539fe3bf2da3203 100644 --- a/crates/ui/src/popup.rs +++ b/crates/ui/src/popup.rs @@ -220,7 +220,7 @@ pub fn submenus(ui: &mut Ui, id: Id, items: &[Item], submenu: impl Fn(usize) -> } /// Shows `title`, with the `keys` that run it and a `description` under it, in a tooltip -/// below the box built last while the pointer rests on it or on a box inside it: +/// centred below the box built last while the pointer rests on it or on a box inside it: /// after a delay, or at once while another has just shown. A press or the wheel hides it /// until the pointer leaves. It names the box to assistive technology, or where the box has /// no role, the unnamed controls inside it. @@ -316,7 +316,7 @@ fn tooltip_below( radius: 4.0, pad: [8.0, 5.0], gap: 3.0, - anchor: Some(Anchor::Below(part.unwrap_or(id))), + anchor: Some(Anchor::Tip(part.unwrap_or(id))), ..Spec::default() }; ui.open_as(id.child("tooltip"), spec); diff --git a/crates/ui/src/tests.rs b/crates/ui/src/tests.rs index 2d2a061f2f0bcedd0fbb8d8439906edc4f7cc7a2..0834cd0bdb40cf4cfe324733979497d6a72cacc7 100644 --- a/crates/ui/src/tests.rs +++ b/crates/ui/src/tests.rs @@ -1266,6 +1266,48 @@ fn tooltips_wait_then_switch_at_once_and_hide_on_press_until_left() { assert!(rest(&mut ui, 500)[0].is_some()); } +#[test] +fn a_tooltip_centres_under_its_control_and_shifts_only_to_stay_in_the_window() { + let mut ui = Ui::new(Theme::dark(), DOUBLE_CLICK); + let mut shown_at = |x: f32| { + ui.event(Event::PointerMoved([x + 10.0, 110.0])); + let mut tip = None; + for _ in 0..40 { + frame(&mut ui, |ui| { + let id = ui.open( + "tool", + Spec { + flags: Flags::CLICKABLE | Flags::FLOAT, + size: [px(20.0), px(20.0)], + position: [x, 100.0], + ..Spec::default() + }, + ); + ui.close(); + popup::tooltip(ui, "Bold", "Ctrl+B", Some("Makes the selected text bold.")); + tip = Some(id.child("tooltip")); + }); + } + ui.laid_out(tip.unwrap()).unwrap() + }; + let middle = shown_at(190.0); + assert_eq!(middle[1], 120.0 + popup::PAD, "under the control"); + assert!( + ((middle[0] + middle[2]) / 2.0 - 200.0).abs() <= 0.5, + "centred on it: {middle:?}" + ); + let width = middle[2] - middle[0]; + assert_eq!( + shown_at(2.0)[..3], + [POPUP_MARGIN, 120.0 + popup::PAD, POPUP_MARGIN + width] + ); + assert_eq!( + shown_at(378.0)[0], + 400.0 - POPUP_MARGIN - width, + "shifted only to fit" + ); +} + #[test] fn a_popup_opens_beside_a_box_first_laid_out_in_the_same_frame() { let mut ui = Ui::new(Theme::dark(), DOUBLE_CLICK);