Fix tooltips sometimes changing position each frame (#7304)
There was a bug in how we decide where to place a `Tooltip` (or other `Popup`), which could lead to tooltips jumping around every frame, especially if it changed size slightly. The new code is simpler and bug-free.
This commit is contained in:
parent
a811b975c2
commit
6d80707422
|
|
@ -484,6 +484,7 @@ impl<'a> Popup<'a> {
|
|||
self.gap,
|
||||
expected_popup_size,
|
||||
)
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
/// Show the popup.
|
||||
|
|
|
|||
|
|
@ -146,7 +146,7 @@ impl Align {
|
|||
// ----------------------------------------------------------------------------
|
||||
|
||||
/// Two-dimension alignment, e.g. [`Align2::LEFT_TOP`].
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Hash)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
||||
pub struct Align2(pub [Align; 2]);
|
||||
|
||||
|
|
@ -298,3 +298,9 @@ impl std::ops::IndexMut<usize> for Align2 {
|
|||
pub fn center_size_in_rect(size: Vec2, frame: Rect) -> Rect {
|
||||
Align2::CENTER_CENTER.align_size_within_rect(size, frame)
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for Align2 {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "Align2({:?}, {:?})", self.x(), self.y())
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,9 +7,9 @@ use crate::{Align2, Pos2, Rect, Vec2};
|
|||
///
|
||||
/// There are helper constants for the 12 common menu positions:
|
||||
/// ```text
|
||||
/// ┌───────────┐ ┌────────┐ ┌─────────┐
|
||||
/// │ TOP_START │ │ TOP │ │ TOP_END │
|
||||
/// └───────────┘ └────────┘ └─────────┘
|
||||
/// ┌───────────┐ ┌────────┐ ┌─────────┐
|
||||
/// │ TOP_START │ │ TOP │ │ TOP_END │
|
||||
/// └───────────┘ └────────┘ └─────────┘
|
||||
/// ┌──────────┐ ┌────────────────────────────────────┐ ┌───────────┐
|
||||
/// │LEFT_START│ │ │ │RIGHT_START│
|
||||
/// └──────────┘ │ │ └───────────┘
|
||||
|
|
@ -19,9 +19,9 @@ use crate::{Align2, Pos2, Rect, Vec2};
|
|||
/// ┌──────────┐ │ │ ┌───────────┐
|
||||
/// │ LEFT_END │ │ │ │ RIGHT_END │
|
||||
/// └──────────┘ └────────────────────────────────────┘ └───────────┘
|
||||
/// ┌────────────┐ ┌──────┐ ┌──────────┐
|
||||
/// │BOTTOM_START│ │BOTTOM│ │BOTTOM_END│
|
||||
/// └────────────┘ └──────┘ └──────────┘
|
||||
/// ┌────────────┐ ┌──────┐ ┌──────────┐
|
||||
/// │BOTTOM_START│ │BOTTOM│ │BOTTOM_END│
|
||||
/// └────────────┘ └──────┘ └──────────┘
|
||||
/// ```
|
||||
// There is no `new` function on purpose, since writing out `parent` and `child` is more
|
||||
// reasonable.
|
||||
|
|
@ -235,45 +235,34 @@ impl RectAlign {
|
|||
[self.flip_x(), self.flip_y(), self.flip()]
|
||||
}
|
||||
|
||||
/// Look for the [`RectAlign`] that fits best in the available space.
|
||||
/// Look for the first alternative [`RectAlign`] that allows the child rect to fit
|
||||
/// inside the `screen_rect`.
|
||||
///
|
||||
/// If no alternative fits, the first is returned.
|
||||
/// If no alternatives are given, `None` is returned.
|
||||
///
|
||||
/// See also:
|
||||
/// - [`RectAlign::symmetries`] to calculate alternatives
|
||||
/// - [`RectAlign::MENU_ALIGNS`] for the 12 common menu positions
|
||||
pub fn find_best_align(
|
||||
mut values_to_try: impl Iterator<Item = Self>,
|
||||
available_space: Rect,
|
||||
values_to_try: impl Iterator<Item = Self>,
|
||||
screen_rect: Rect,
|
||||
parent_rect: Rect,
|
||||
gap: f32,
|
||||
size: Vec2,
|
||||
) -> Self {
|
||||
let area = size.x * size.y;
|
||||
|
||||
let blocked_area = |pos: Self| {
|
||||
let rect = pos.align_rect(&parent_rect, size, gap);
|
||||
area - available_space.intersect(rect).area()
|
||||
};
|
||||
|
||||
let first = values_to_try.next().unwrap_or_default();
|
||||
|
||||
if blocked_area(first) == 0.0 {
|
||||
return first;
|
||||
}
|
||||
|
||||
let mut best_area = blocked_area(first);
|
||||
let mut best = first;
|
||||
expected_size: Vec2,
|
||||
) -> Option<Self> {
|
||||
let mut first_choice = None;
|
||||
|
||||
for align in values_to_try {
|
||||
let blocked = blocked_area(align);
|
||||
if blocked == 0.0 {
|
||||
return align;
|
||||
}
|
||||
if blocked < best_area {
|
||||
best = align;
|
||||
best_area = blocked;
|
||||
first_choice = first_choice.or(Some(align)); // Remember the first alternative
|
||||
|
||||
let suggested_popup_rect = align.align_rect(&parent_rect, expected_size, gap);
|
||||
|
||||
if screen_rect.contains_rect(suggested_popup_rect) {
|
||||
return Some(align);
|
||||
}
|
||||
}
|
||||
|
||||
best
|
||||
first_choice
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue