use std::any::Any;
use std::sync::RwLock;
use super::{Element, ElementPtr, ViewLimits, ViewStretch, share};
use super::context::{BasicContext, Context};
use crate::support::point::Point;
use crate::support::rect::Rect;
use crate::support::color::Color;
use crate::support::theme::get_theme;
use crate::view::CursorTracking;
pub struct Tooltip {
content: Option<ElementPtr>,
tooltip_text: String,
visible: RwLock<bool>,
position: RwLock<Point>,
background_color: Color,
text_color: Color,
font_size: f32,
padding: f32,
corner_radius: f32,
delay_ms: u32,
}
impl Tooltip {
pub fn new(text: impl Into<String>) -> Self {
let theme = get_theme();
Self {
content: None,
tooltip_text: text.into(),
visible: RwLock::new(false),
position: RwLock::new(Point::zero()),
background_color: theme.tooltip_color,
text_color: theme.tooltip_text_color,
font_size: theme.tooltip_font_size,
padding: 6.0,
corner_radius: 4.0,
delay_ms: 500,
}
}
pub fn content<E: Element + 'static>(mut self, content: E) -> Self {
self.content = Some(share(content));
self
}
pub fn text(mut self, text: impl Into<String>) -> Self {
self.tooltip_text = text.into();
self
}
pub fn background_color(mut self, color: Color) -> Self {
self.background_color = color;
self
}
pub fn text_color(mut self, color: Color) -> Self {
self.text_color = color;
self
}
pub fn delay(mut self, ms: u32) -> Self {
self.delay_ms = ms;
self
}
pub fn show(&self, position: Point) {
*self.position.write().unwrap() = position;
*self.visible.write().unwrap() = true;
}
pub fn hide(&self) {
*self.visible.write().unwrap() = false;
}
pub fn is_visible(&self) -> bool {
*self.visible.read().unwrap()
}
fn tooltip_bounds(&self) -> Rect {
let pos = *self.position.read().unwrap();
let width = self.tooltip_text.len() as f32 * self.font_size * 0.55 + self.padding * 2.0;
let height = self.font_size + self.padding * 2.0;
Rect::new(
pos.x + 10.0,
pos.y + 20.0,
pos.x + 10.0 + width,
pos.y + 20.0 + height,
)
}
fn draw_tooltip(&self, _ctx: &Context) {
if !self.is_visible() || self.tooltip_text.is_empty() {
return;
}
}
}
impl Element for Tooltip {
fn limits(&self, ctx: &BasicContext) -> ViewLimits {
if let Some(ref content) = self.content {
content.limits(ctx)
} else {
ViewLimits::full()
}
}
fn stretch(&self) -> ViewStretch {
if let Some(ref content) = self.content {
content.stretch()
} else {
ViewStretch::default()
}
}
fn draw(&self, ctx: &Context) {
if let Some(ref content) = self.content {
content.draw(ctx);
}
if self.is_visible() && !self.tooltip_text.is_empty() {
let bounds = self.tooltip_bounds();
let mut canvas = ctx.canvas.borrow_mut();
let shadow_rect = bounds.translate(2.0, 2.0);
canvas.fill_style(Color::new(0.0, 0.0, 0.0, 0.3));
canvas.fill_round_rect(shadow_rect, self.corner_radius);
canvas.fill_style(self.background_color);
canvas.fill_round_rect(bounds, self.corner_radius);
canvas.fill_style(self.text_color);
canvas.font_size(self.font_size);
let x = bounds.left + self.padding;
let y = bounds.center().y + self.font_size * 0.35;
canvas.fill_text(&self.tooltip_text, Point::new(x, y));
}
}
fn hit_test(&self, ctx: &Context, p: Point, leaf: bool, control: bool) -> Option<&dyn Element> {
if let Some(ref content) = self.content {
content.hit_test(ctx, p, leaf, control)
} else {
if ctx.bounds.contains(p) {
Some(self)
} else {
None
}
}
}
fn wants_control(&self) -> bool {
if let Some(ref content) = self.content {
content.wants_control()
} else {
false
}
}
fn handle_click(&self, ctx: &Context, btn: crate::view::MouseButton) -> bool {
self.hide();
if let Some(ref content) = self.content {
content.handle_click(ctx, btn)
} else {
false
}
}
fn cursor(&mut self, ctx: &Context, p: Point, status: CursorTracking) -> bool {
match status {
CursorTracking::Entering | CursorTracking::Hovering => {
if ctx.bounds.contains(p) {
self.show(p);
}
}
CursorTracking::Leaving => {
self.hide();
}
}
true
}
fn as_any(&self) -> &dyn Any {
self
}
fn as_any_mut(&mut self) -> &mut dyn Any {
self
}
}
pub struct TooltipOverlay {
text: RwLock<String>,
visible: RwLock<bool>,
position: RwLock<Point>,
background_color: Color,
text_color: Color,
font_size: f32,
padding: f32,
corner_radius: f32,
}
impl TooltipOverlay {
pub fn new() -> Self {
let theme = get_theme();
Self {
text: RwLock::new(String::new()),
visible: RwLock::new(false),
position: RwLock::new(Point::zero()),
background_color: theme.tooltip_color,
text_color: theme.tooltip_text_color,
font_size: theme.tooltip_font_size,
padding: 6.0,
corner_radius: 4.0,
}
}
pub fn show(&self, text: impl Into<String>, position: Point) {
*self.text.write().unwrap() = text.into();
*self.position.write().unwrap() = position;
*self.visible.write().unwrap() = true;
}
pub fn hide(&self) {
*self.visible.write().unwrap() = false;
}
pub fn is_visible(&self) -> bool {
*self.visible.read().unwrap()
}
fn tooltip_bounds(&self) -> Rect {
let pos = *self.position.read().unwrap();
let text = self.text.read().unwrap();
let width = text.len() as f32 * self.font_size * 0.55 + self.padding * 2.0;
let height = self.font_size + self.padding * 2.0;
Rect::new(
pos.x + 10.0,
pos.y + 20.0,
pos.x + 10.0 + width,
pos.y + 20.0 + height,
)
}
}
impl Default for TooltipOverlay {
fn default() -> Self {
Self::new()
}
}
impl Element for TooltipOverlay {
fn limits(&self, _ctx: &BasicContext) -> ViewLimits {
ViewLimits::fixed(0.0, 0.0)
}
fn stretch(&self) -> ViewStretch {
ViewStretch::new(0.0, 0.0)
}
fn draw(&self, ctx: &Context) {
if !self.is_visible() {
return;
}
let text = self.text.read().unwrap();
if text.is_empty() {
return;
}
let bounds = self.tooltip_bounds();
let mut canvas = ctx.canvas.borrow_mut();
let shadow_rect = bounds.translate(2.0, 2.0);
canvas.fill_style(Color::new(0.0, 0.0, 0.0, 0.3));
canvas.fill_round_rect(shadow_rect, self.corner_radius);
canvas.fill_style(self.background_color);
canvas.fill_round_rect(bounds, self.corner_radius);
canvas.fill_style(self.text_color);
canvas.font_size(self.font_size);
let x = bounds.left + self.padding;
let y = bounds.center().y + self.font_size * 0.35;
canvas.fill_text(&text, Point::new(x, y));
}
fn as_any(&self) -> &dyn Any {
self
}
fn as_any_mut(&mut self) -> &mut dyn Any {
self
}
}
pub fn tooltip(text: impl Into<String>) -> Tooltip {
Tooltip::new(text)
}
pub fn tooltip_overlay() -> TooltipOverlay {
TooltipOverlay::new()
}