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,
|
self.gap,
|
||||||
expected_popup_size,
|
expected_popup_size,
|
||||||
)
|
)
|
||||||
|
.unwrap_or_default()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Show the popup.
|
/// Show the popup.
|
||||||
|
|
|
||||||
|
|
@ -146,7 +146,7 @@ impl Align {
|
||||||
// ----------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------
|
||||||
|
|
||||||
/// Two-dimension alignment, e.g. [`Align2::LEFT_TOP`].
|
/// 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))]
|
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
||||||
pub struct Align2(pub [Align; 2]);
|
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 {
|
pub fn center_size_in_rect(size: Vec2, frame: Rect) -> Rect {
|
||||||
Align2::CENTER_CENTER.align_size_within_rect(size, frame)
|
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:
|
/// There are helper constants for the 12 common menu positions:
|
||||||
/// ```text
|
/// ```text
|
||||||
/// ┌───────────┐ ┌────────┐ ┌─────────┐
|
/// ┌───────────┐ ┌────────┐ ┌─────────┐
|
||||||
/// │ TOP_START │ │ TOP │ │ TOP_END │
|
/// │ TOP_START │ │ TOP │ │ TOP_END │
|
||||||
/// └───────────┘ └────────┘ └─────────┘
|
/// └───────────┘ └────────┘ └─────────┘
|
||||||
/// ┌──────────┐ ┌────────────────────────────────────┐ ┌───────────┐
|
/// ┌──────────┐ ┌────────────────────────────────────┐ ┌───────────┐
|
||||||
/// │LEFT_START│ │ │ │RIGHT_START│
|
/// │LEFT_START│ │ │ │RIGHT_START│
|
||||||
/// └──────────┘ │ │ └───────────┘
|
/// └──────────┘ │ │ └───────────┘
|
||||||
|
|
@ -19,9 +19,9 @@ use crate::{Align2, Pos2, Rect, Vec2};
|
||||||
/// ┌──────────┐ │ │ ┌───────────┐
|
/// ┌──────────┐ │ │ ┌───────────┐
|
||||||
/// │ LEFT_END │ │ │ │ RIGHT_END │
|
/// │ 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
|
// There is no `new` function on purpose, since writing out `parent` and `child` is more
|
||||||
// reasonable.
|
// reasonable.
|
||||||
|
|
@ -235,45 +235,34 @@ impl RectAlign {
|
||||||
[self.flip_x(), self.flip_y(), self.flip()]
|
[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:
|
/// See also:
|
||||||
/// - [`RectAlign::symmetries`] to calculate alternatives
|
/// - [`RectAlign::symmetries`] to calculate alternatives
|
||||||
/// - [`RectAlign::MENU_ALIGNS`] for the 12 common menu positions
|
/// - [`RectAlign::MENU_ALIGNS`] for the 12 common menu positions
|
||||||
pub fn find_best_align(
|
pub fn find_best_align(
|
||||||
mut values_to_try: impl Iterator<Item = Self>,
|
values_to_try: impl Iterator<Item = Self>,
|
||||||
available_space: Rect,
|
screen_rect: Rect,
|
||||||
parent_rect: Rect,
|
parent_rect: Rect,
|
||||||
gap: f32,
|
gap: f32,
|
||||||
size: Vec2,
|
expected_size: Vec2,
|
||||||
) -> Self {
|
) -> Option<Self> {
|
||||||
let area = size.x * size.y;
|
let mut first_choice = None;
|
||||||
|
|
||||||
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;
|
|
||||||
|
|
||||||
for align in values_to_try {
|
for align in values_to_try {
|
||||||
let blocked = blocked_area(align);
|
first_choice = first_choice.or(Some(align)); // Remember the first alternative
|
||||||
if blocked == 0.0 {
|
|
||||||
return align;
|
let suggested_popup_rect = align.align_rect(&parent_rect, expected_size, gap);
|
||||||
}
|
|
||||||
if blocked < best_area {
|
if screen_rect.contains_rect(suggested_popup_rect) {
|
||||||
best = align;
|
return Some(align);
|
||||||
best_area = blocked;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
best
|
first_choice
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue