use crate::render::{RenderContext, TextAlign, TextBaseline};
use crate::types::Rect;
use super::settings::TooltipSettings;
use super::style::TooltipStyle;
use super::types::{TooltipConfig, TooltipPosition};
pub fn measure_tooltip(text: &str, style: &dyn TooltipStyle) -> (f64, f64) {
let w = text.len() as f64 * 7.0 + style.padding_x() * 2.0;
let h = style.font_size() + style.padding_y() * 2.0;
(w, h)
}
pub fn tooltip_rect_from_anchor(
anchor: Rect,
position: TooltipPosition,
text_width: f64,
style: &dyn TooltipStyle,
) -> Rect {
let w = text_width + style.padding_x() * 2.0;
let h = style.font_size() + style.padding_y() * 2.0;
let gap = style.anchor_gap();
match position {
TooltipPosition::Above => Rect::new(
anchor.x + (anchor.width - w) / 2.0,
anchor.y - h - gap,
w, h,
),
TooltipPosition::Below => Rect::new(
anchor.x + (anchor.width - w) / 2.0,
anchor.y + anchor.height + gap,
w, h,
),
TooltipPosition::Left => Rect::new(
anchor.x - w - gap,
anchor.y + (anchor.height - h) / 2.0,
w, h,
),
TooltipPosition::Right => Rect::new(
anchor.x + anchor.width + gap,
anchor.y + (anchor.height - h) / 2.0,
w, h,
),
}
}
pub fn tooltip_multiline_rect(
cursor: (f64, f64),
lines: &[&str],
style: &dyn TooltipStyle,
ctx: &dyn RenderContext,
container: Rect,
) -> Rect {
let font_size = style.font_size();
let line_height = font_size * style.line_height_factor();
let pad_x = style.padding_x();
let pad_y = style.padding_y();
let gap = style.anchor_gap();
let mut max_w = style.min_content_width();
for line in lines {
let w = ctx.measure_text(line);
if w > max_w {
max_w = w;
}
}
let tw = max_w + pad_x * 2.0;
let th = lines.len() as f64 * line_height + pad_y * 2.0;
let raw_x = cursor.0 + gap;
let raw_y = cursor.1 + gap;
let clamped_x = raw_x.clamp(container.x, (container.x + container.width - tw).max(container.x));
let clamped_y = raw_y.clamp(container.y, (container.y + container.height - th).max(container.y));
Rect::new(clamped_x, clamped_y, tw, th)
}
fn chrome_tooltip_position(
cursor: (f64, f64),
tooltip_size: (f64, f64),
screen: Rect,
anchor_gap: f64,
) -> (f64, f64) {
let (tw, th) = tooltip_size;
let mut x = cursor.0;
let mut y = cursor.1 + anchor_gap;
if x + tw > screen.x + screen.width {
x = cursor.0 - tw;
if x < screen.x {
x = screen.x + screen.width - tw;
}
}
if x < screen.x { x = screen.x; }
if y + th > screen.y + screen.height {
y = cursor.1 - th - anchor_gap;
if y < screen.y {
y = screen.y + screen.height - th;
}
}
if y < screen.y { y = screen.y; }
(x, y)
}
pub fn draw_tooltip(
ctx: &mut dyn RenderContext,
rect: Rect,
config: &TooltipConfig,
alpha: f64,
settings: &TooltipSettings,
) {
if alpha <= 0.0 {
return;
}
let style = settings.style.as_ref();
let theme = settings.theme.as_ref();
ctx.set_fill_color_alpha(theme.bg(), alpha);
ctx.fill_rounded_rect(rect.x, rect.y, rect.width, rect.height, style.radius());
if style.border_width() > 0.0 {
ctx.set_stroke_color(theme.border());
ctx.set_stroke_width(style.border_width());
ctx.stroke_rounded_rect(rect.x, rect.y, rect.width, rect.height, style.radius());
}
ctx.set_font(&format!("{}px sans-serif", style.font_size()));
ctx.set_fill_color_alpha(theme.text(), alpha);
ctx.set_text_align(TextAlign::Left);
ctx.set_text_baseline(TextBaseline::Middle);
ctx.fill_text(&config.text, rect.x + style.padding_x(), rect.y + rect.height / 2.0);
}
pub fn draw_chrome_tooltip(
ctx: &mut dyn RenderContext,
cursor: (f64, f64),
text: &str,
alpha: f64,
settings: &TooltipSettings,
screen: Rect,
) {
if alpha <= 0.0 {
return;
}
let style = settings.style.as_ref();
let theme = settings.theme.as_ref();
ctx.set_font(&format!("{}px sans-serif", style.font_size()));
let text_w = ctx.measure_text(text);
let pad_x = style.padding_x();
let pad_y = style.padding_y();
let tw = text_w + pad_x * 2.0;
let th = style.font_size() + pad_y * 2.0;
let (tx, ty) = chrome_tooltip_position(cursor, (tw, th), screen, style.anchor_gap());
ctx.save();
ctx.set_global_alpha(alpha);
if style.has_shadow() {
ctx.set_fill_color(theme.shadow());
ctx.fill_rounded_rect(tx + 1.0, ty + 1.0, tw, th, style.radius());
}
ctx.set_fill_color(theme.bg());
ctx.fill_rounded_rect(tx, ty, tw, th, style.radius());
ctx.set_fill_color(theme.text());
ctx.set_text_align(TextAlign::Left);
ctx.set_text_baseline(TextBaseline::Middle);
ctx.fill_text(text, tx + pad_x, ty + th / 2.0);
ctx.restore();
}
pub fn draw_crosshair_tooltip(
ctx: &mut dyn RenderContext,
cursor: (f64, f64),
config: &TooltipConfig,
settings: &TooltipSettings,
container: Rect,
) {
let style = settings.style.as_ref();
let theme = settings.theme.as_ref();
let lines = config.resolved_lines();
if lines.is_empty() {
return;
}
ctx.set_font(&format!("{}px sans-serif", style.font_size()));
let rect = tooltip_multiline_rect(cursor, &lines, style, ctx, container);
let pad_x = style.padding_x();
let pad_y = style.padding_y();
let font_size = style.font_size();
let line_height = font_size * style.line_height_factor();
let radius = style.radius();
ctx.set_fill_color(theme.bg());
ctx.fill_rounded_rect(rect.x, rect.y, rect.width, rect.height, radius);
if style.border_width() > 0.0 {
ctx.set_stroke_color(theme.border());
ctx.set_stroke_width(style.border_width());
ctx.stroke_rounded_rect(rect.x, rect.y, rect.width, rect.height, radius);
}
ctx.set_fill_color(theme.text());
ctx.set_text_align(TextAlign::Left);
ctx.set_text_baseline(TextBaseline::Middle);
let mut text_y = rect.y + pad_y + line_height * 0.5;
for line in &lines {
ctx.fill_text(line, rect.x + pad_x, text_y);
text_y += line_height;
}
}