use crate::theme::get_global_color;
use egui::{
pos2, Area, FontId, Id, Order, Rect, Response, Sense, Stroke, Ui, Vec2,
};
#[derive(Clone, Copy, PartialEq, Debug)]
pub enum TooltipPosition {
Top,
Bottom,
Left,
Right,
Auto,
}
pub struct MaterialTooltip {
text: String,
position: TooltipPosition,
max_width: f32,
padding: Vec2,
font_size: f32,
}
impl MaterialTooltip {
pub fn new(text: impl Into<String>) -> Self {
Self {
text: text.into(),
position: TooltipPosition::Auto,
max_width: 200.0,
padding: Vec2::new(8.0, 6.0),
font_size: 12.0,
}
}
pub fn position(mut self, position: TooltipPosition) -> Self {
self.position = position;
self
}
pub fn max_width(mut self, width: f32) -> Self {
self.max_width = width;
self
}
pub fn padding(mut self, padding: Vec2) -> Self {
self.padding = padding;
self
}
pub fn font_size(mut self, size: f32) -> Self {
self.font_size = size;
self
}
pub fn show(&self, ui: &mut Ui, target_rect: Rect) {
let inverse_surface = get_global_color("inverseSurface");
let inverse_on_surface = get_global_color("inverseOnSurface");
let text_galley = ui.painter().layout(
self.text.clone(),
FontId::proportional(self.font_size),
inverse_on_surface,
self.max_width - self.padding.x * 2.0,
);
let tooltip_size = Vec2::new(
text_galley.size().x + self.padding.x * 2.0,
text_galley.size().y + self.padding.y * 2.0,
);
let screen_rect = ui.ctx().screen_rect();
let tooltip_pos = self.calculate_position(target_rect, tooltip_size, screen_rect);
let tooltip_id = Id::new("tooltip").with(&self.text);
Area::new(tooltip_id)
.fixed_pos(tooltip_pos)
.order(Order::Tooltip)
.interactable(false)
.show(ui.ctx(), |ui| {
let (rect, _) = ui.allocate_exact_size(tooltip_size, Sense::hover());
ui.painter().rect_filled(rect, 4.0, inverse_surface);
ui.painter().rect_stroke(
rect,
4.0,
Stroke::new(1.0, inverse_on_surface.linear_multiply(0.2)),
egui::epaint::StrokeKind::Outside,
);
let text_pos = pos2(
rect.min.x + self.padding.x,
rect.min.y + self.padding.y,
);
ui.painter().galley(text_pos, text_galley, inverse_on_surface);
});
}
fn calculate_position(
&self,
target_rect: Rect,
tooltip_size: Vec2,
screen_rect: Rect,
) -> egui::Pos2 {
let spacing = 8.0;
let position = match self.position {
TooltipPosition::Auto => {
self.auto_position(target_rect, tooltip_size, screen_rect)
}
pos => pos,
};
match position {
TooltipPosition::Top => pos2(
target_rect.center().x - tooltip_size.x / 2.0,
target_rect.min.y - tooltip_size.y - spacing,
),
TooltipPosition::Bottom => pos2(
target_rect.center().x - tooltip_size.x / 2.0,
target_rect.max.y + spacing,
),
TooltipPosition::Left => pos2(
target_rect.min.x - tooltip_size.x - spacing,
target_rect.center().y - tooltip_size.y / 2.0,
),
TooltipPosition::Right => pos2(
target_rect.max.x + spacing,
target_rect.center().y - tooltip_size.y / 2.0,
),
TooltipPosition::Auto => {
pos2(target_rect.max.x + spacing, target_rect.min.y)
}
}
}
fn auto_position(
&self,
target_rect: Rect,
tooltip_size: Vec2,
screen_rect: Rect,
) -> TooltipPosition {
let spacing = 8.0;
let space_above = target_rect.min.y - screen_rect.min.y;
let space_below = screen_rect.max.y - target_rect.max.y;
let space_left = target_rect.min.x - screen_rect.min.x;
let space_right = screen_rect.max.x - target_rect.max.x;
if space_below >= tooltip_size.y + spacing {
TooltipPosition::Bottom
} else if space_above >= tooltip_size.y + spacing {
TooltipPosition::Top
} else if space_right >= tooltip_size.x + spacing {
TooltipPosition::Right
} else if space_left >= tooltip_size.x + spacing {
TooltipPosition::Left
} else {
TooltipPosition::Bottom
}
}
}
pub fn show_tooltip_on_hover(
ui: &mut Ui,
target_response: &Response,
text: impl Into<String>,
position: TooltipPosition,
) {
if target_response.hovered() {
MaterialTooltip::new(text).position(position).show(ui, target_response.rect);
}
}
pub fn show_tooltip_on_hover_custom(
ui: &mut Ui,
target_response: &Response,
tooltip: MaterialTooltip,
) {
if target_response.hovered() {
tooltip.show(ui, target_response.rect);
}
}
pub fn with_tooltip<R>(
ui: &mut Ui,
_text: impl Into<String>,
_position: TooltipPosition,
add_contents: impl FnOnce(&mut Ui) -> R,
) -> R {
add_contents(ui)
}
pub fn tooltip(text: impl Into<String>) -> MaterialTooltip {
MaterialTooltip::new(text)
}