use std::sync::Arc;
use crate::color::Color;
use crate::draw_ctx::DrawCtx;
use crate::event::{Event, EventResult};
use crate::geometry::{Point, Rect, Size};
use crate::layout_props::{HAnchor, Insets, VAnchor, WidgetBase};
use crate::text::Font;
use crate::widget::{Widget, paint_subtree};
use crate::widgets::label::Label;
const HOVER_DELAY_FRAMES: u32 = 30;
pub struct Tooltip {
bounds: Rect,
children: Vec<Box<dyn Widget>>,
base: WidgetBase,
hover_frames: u32,
hovered: bool,
cursor: Point,
tip_label: Label,
}
impl Tooltip {
pub fn new(child: Box<dyn Widget>, text: impl Into<String>, font: Arc<Font>) -> Self {
Self {
bounds: Rect::default(),
children: vec![child],
base: WidgetBase::new(),
hover_frames: 0,
hovered: false,
cursor: Point::ORIGIN,
tip_label: Label::new(text, font).with_font_size(11.5),
}
}
pub fn with_margin(mut self, m: Insets) -> Self { self.base.margin = m; self }
pub fn with_h_anchor(mut self, h: HAnchor) -> Self { self.base.h_anchor = h; self }
pub fn with_v_anchor(mut self, v: VAnchor) -> Self { self.base.v_anchor = v; self }
fn show_tip(&self) -> bool {
self.hovered && self.hover_frames >= HOVER_DELAY_FRAMES
}
}
impl Widget for Tooltip {
fn type_name(&self) -> &'static str { "Tooltip" }
fn bounds(&self) -> Rect { self.bounds }
fn set_bounds(&mut self, b: Rect) { self.bounds = b; }
fn children(&self) -> &[Box<dyn Widget>] { &self.children }
fn children_mut(&mut self) -> &mut Vec<Box<dyn Widget>> { &mut self.children }
fn margin(&self) -> Insets { self.base.margin }
fn h_anchor(&self) -> HAnchor { self.base.h_anchor }
fn v_anchor(&self) -> VAnchor { self.base.v_anchor }
fn is_focusable(&self) -> bool {
self.children.first().map(|c| c.is_focusable()).unwrap_or(false)
}
fn layout(&mut self, available: Size) -> Size {
let s = if let Some(child) = self.children.first_mut() {
let cs = child.layout(available);
child.set_bounds(Rect::new(0.0, 0.0, cs.width, cs.height));
cs
} else {
available
};
self.bounds = Rect::new(0.0, 0.0, s.width, s.height);
s
}
fn paint(&mut self, ctx: &mut dyn DrawCtx) {
if self.hovered {
self.hover_frames = self.hover_frames.saturating_add(1);
}
if let Some(child) = self.children.first_mut() {
paint_subtree(child.as_mut(), ctx);
}
if !self.show_tip() { return; }
let v = ctx.visuals();
let pad_x = 8.0_f64;
let pad_y = 5.0_f64;
let max_tip_w = self.bounds.width.max(120.0).min(260.0);
let ls = self.tip_label.layout(Size::new(max_tip_w, 100.0));
let panel_w = ls.width + pad_x * 2.0;
let panel_h = ls.height + pad_y * 2.0;
let cursor_y = self.cursor.y;
let cursor_x = self.cursor.x;
let panel_x = (cursor_x - panel_w * 0.5).max(0.0).min(self.bounds.width - panel_w);
let panel_y = cursor_y + 10.0;
ctx.set_fill_color(Color::rgba(0.0, 0.0, 0.0, 0.20));
ctx.begin_path();
ctx.rounded_rect(panel_x + 1.0, panel_y - 1.0, panel_w, panel_h, 4.0);
ctx.fill();
ctx.set_fill_color(v.window_fill);
ctx.begin_path();
ctx.rounded_rect(panel_x, panel_y, panel_w, panel_h, 4.0);
ctx.fill();
ctx.set_stroke_color(v.widget_stroke);
ctx.set_line_width(1.0);
ctx.begin_path();
ctx.rounded_rect(panel_x, panel_y, panel_w, panel_h, 4.0);
ctx.stroke();
self.tip_label.set_color(v.text_color);
self.tip_label.set_bounds(Rect::new(0.0, 0.0, ls.width, ls.height));
let lx = panel_x + pad_x;
let ly = panel_y + pad_y;
ctx.save();
ctx.translate(lx, ly);
paint_subtree(&mut self.tip_label, ctx);
ctx.restore();
}
fn on_event(&mut self, event: &Event) -> EventResult {
match event {
Event::MouseMove { pos } => {
let was = self.hovered;
self.hovered = self.hit_test(*pos);
self.cursor = *pos;
if !self.hovered {
self.hover_frames = 0;
}
let result = if let Some(child) = self.children.first_mut() {
child.on_event(event)
} else {
EventResult::Ignored
};
if self.hovered != was {
crate::animation::request_tick();
EventResult::Consumed
} else { result }
}
_ => {
if let Some(child) = self.children.first_mut() {
child.on_event(event)
} else {
EventResult::Ignored
}
}
}
}
fn hit_test(&self, local_pos: Point) -> bool {
local_pos.x >= 0.0 && local_pos.x <= self.bounds.width
&& local_pos.y >= 0.0 && local_pos.y <= self.bounds.height
}
}