use crate::*;
#[derive(Clone, Debug, Default)]
pub(crate) struct MonoState {
last_id: Option<Id>,
last_size: Vec<Vec2>,
}
impl MonoState {
fn load(ctx: &Context) -> Option<Self> {
ctx.data().get_temp(Id::null())
}
fn store(self, ctx: &Context) {
ctx.data().insert_temp(Id::null(), self);
}
fn tooltip_size(&self, id: Id, index: usize) -> Option<Vec2> {
if self.last_id == Some(id) {
self.last_size.get(index).cloned()
} else {
None
}
}
fn set_tooltip_size(&mut self, id: Id, index: usize, size: Vec2) {
if self.last_id == Some(id) {
if let Some(stored_size) = self.last_size.get_mut(index) {
*stored_size = size;
} else {
self.last_size
.extend((0..index - self.last_size.len()).map(|_| Vec2::ZERO));
self.last_size.push(size);
}
return;
}
self.last_id = Some(id);
self.last_size.clear();
self.last_size.extend((0..index).map(|_| Vec2::ZERO));
self.last_size.push(size);
}
}
pub fn show_tooltip<R>(
ctx: &Context,
id: Id,
add_contents: impl FnOnce(&mut Ui) -> R,
) -> Option<R> {
show_tooltip_at_pointer(ctx, id, add_contents)
}
pub fn show_tooltip_at_pointer<R>(
ctx: &Context,
id: Id,
add_contents: impl FnOnce(&mut Ui) -> R,
) -> Option<R> {
let suggested_pos = ctx
.input()
.pointer
.hover_pos()
.map(|pointer_pos| pointer_pos + vec2(16.0, 16.0));
show_tooltip_at(ctx, id, suggested_pos, add_contents)
}
pub fn show_tooltip_for<R>(
ctx: &Context,
id: Id,
rect: &Rect,
add_contents: impl FnOnce(&mut Ui) -> R,
) -> Option<R> {
let expanded_rect = rect.expand2(vec2(2.0, 4.0));
let (above, position) = if ctx.input().any_touches() {
(true, expanded_rect.left_top())
} else {
(false, expanded_rect.left_bottom())
};
show_tooltip_at_avoid_dyn(
ctx,
id,
Some(position),
above,
expanded_rect,
Box::new(add_contents),
)
}
pub fn show_tooltip_at<R>(
ctx: &Context,
id: Id,
suggested_position: Option<Pos2>,
add_contents: impl FnOnce(&mut Ui) -> R,
) -> Option<R> {
let above = false;
show_tooltip_at_avoid_dyn(
ctx,
id,
suggested_position,
above,
Rect::NOTHING,
Box::new(add_contents),
)
}
fn show_tooltip_at_avoid_dyn<'c, R>(
ctx: &Context,
mut id: Id,
suggested_position: Option<Pos2>,
above: bool,
mut avoid_rect: Rect,
add_contents: Box<dyn FnOnce(&mut Ui) -> R + 'c>,
) -> Option<R> {
let mut tooltip_rect = Rect::NOTHING;
let mut count = 0;
let mut position = if let Some((stored_id, stored_tooltip_rect, stored_count)) =
ctx.frame_state().tooltip_rect
{
id = stored_id;
tooltip_rect = stored_tooltip_rect;
count = stored_count;
avoid_rect = avoid_rect.union(tooltip_rect);
if above {
tooltip_rect.left_top()
} else {
tooltip_rect.left_bottom()
}
} else if let Some(position) = suggested_position {
position
} else if ctx.memory().everything_is_visible() {
Pos2::ZERO
} else {
return None; };
let mut state = MonoState::load(ctx).unwrap_or_default();
let expected_size = state.tooltip_size(id, count);
let expected_size = expected_size.unwrap_or_else(|| vec2(64.0, 32.0));
if above {
position.y -= expected_size.y;
}
position = position.at_most(ctx.input().screen_rect().max - expected_size);
{
let new_rect = Rect::from_min_size(position, expected_size);
if new_rect.shrink(1.0).intersects(avoid_rect) {
if above {
position = avoid_rect.left_bottom();
} else {
position = Pos2::new(position.x, avoid_rect.min.y - expected_size.y);
}
}
}
let position = position.at_least(ctx.input().screen_rect().min);
let InnerResponse { inner, response } = show_tooltip_area_dyn(ctx, id, position, add_contents);
state.set_tooltip_size(id, count, response.rect.size());
state.store(ctx);
ctx.frame_state().tooltip_rect = Some((id, tooltip_rect.union(response.rect), count + 1));
Some(inner)
}
pub fn show_tooltip_text(ctx: &Context, id: Id, text: impl Into<WidgetText>) -> Option<()> {
show_tooltip(ctx, id, |ui| {
crate::widgets::Label::new(text).ui(ui);
})
}
fn show_tooltip_area_dyn<'c, R>(
ctx: &Context,
id: Id,
window_pos: Pos2,
add_contents: Box<dyn FnOnce(&mut Ui) -> R + 'c>,
) -> InnerResponse<R> {
use containers::*;
Area::new(id)
.order(Order::Tooltip)
.fixed_pos(window_pos)
.interactable(false)
.drag_bounds(Rect::EVERYTHING) .show(ctx, |ui| {
Frame::popup(&ctx.style())
.show(ui, |ui| {
ui.set_max_width(ui.spacing().tooltip_width);
add_contents(ui)
})
.inner
})
}
pub fn popup_below_widget<R>(
ui: &Ui,
popup_id: Id,
widget_response: &Response,
add_contents: impl FnOnce(&mut Ui) -> R,
) -> Option<R> {
if ui.memory().is_popup_open(popup_id) {
let inner = Area::new(popup_id)
.order(Order::Foreground)
.fixed_pos(widget_response.rect.left_bottom())
.show(ui.ctx(), |ui| {
let frame = Frame::popup(ui.style());
let frame_margin = frame.inner_margin + frame.outer_margin;
frame
.show(ui, |ui| {
ui.with_layout(Layout::top_down_justified(Align::LEFT), |ui| {
ui.set_width(widget_response.rect.width() - frame_margin.sum().x);
add_contents(ui)
})
.inner
})
.inner
})
.inner;
if ui.input().key_pressed(Key::Escape) || widget_response.clicked_elsewhere() {
ui.memory().close_popup();
}
Some(inner)
} else {
None
}
}