rosace_widgets/tree/
tooltip.rs1use rosace_core::types::{Point, Size};
2use rosace_render::Color;
3
4use super::overlay::{FocusBehavior, InputBehavior, LayerPosition, OverlayEntry};
5use super::{BoxedWidget, Children, LayoutCtx, PaintCtx, Widget};
6
7#[derive(Debug, Clone, Copy)]
13pub struct TooltipStyle {
14 pub background: Color,
15 pub text_color: Color,
16 pub radius: f32,
17 pub font_size: f32,
18 pub pad_h: f32,
21 pub elevation: f32,
23}
24
25impl Default for TooltipStyle {
26 fn default() -> Self {
27 Self {
28 background: Color::rgba(40, 42, 58, 245),
29 text_color: Color::rgb(228, 230, 244),
30 radius: 6.0,
31 font_size: 12.0,
32 pad_h: 10.0,
33 elevation: 1.0,
34 }
35 }
36}
37
38impl TooltipStyle {
39 fn resolve(theme: &rosace_theme::ThemeData) -> Self {
41 theme.ext::<TooltipStyle>().copied().unwrap_or_default()
42 }
43}
44
45pub struct Tooltip {
52 label: String,
53 style: Option<TooltipStyle>,
54 child: BoxedWidget,
55}
56
57impl Tooltip {
58 pub fn new(label: impl Into<String>, child: impl Widget + 'static) -> Self {
59 Self { label: label.into(), style: None, child: Box::new(child) }
60 }
61 pub fn style(mut self, style: TooltipStyle) -> Self {
63 self.style = Some(style);
64 self
65 }
66 pub fn font_size(mut self, s: f32) -> Self {
68 let mut st = self.style.unwrap_or_default();
69 st.font_size = s;
70 self.style = Some(st);
71 self
72 }
73}
74
75impl Widget for Tooltip {
76 fn children(&self) -> Children<'_> { Children::One(&*self.child) }
77
78 fn paint(&self, ctx: &mut PaintCtx) {
79 let r = ctx.rect;
80 self.child.paint(&mut ctx.child(r));
81 ctx.hoverable();
82 if ctx.hovered() {
83 let style = self.style.unwrap_or_else(|| TooltipStyle::resolve(&ctx.theme));
84 let w = ctx.font.measure_text(&self.label, style.font_size) + style.pad_h * 2.0;
85 let h = style.font_size * 1.7;
86 let label = self.label.clone();
87 ctx.attach_overlay(
93 OverlayEntry::new(
94 LayerPosition::AboveCentered(r),
95 TooltipLabel { label, w, h, style },
96 )
97 .input(InputBehavior::PassThrough)
98 .focus(FocusBehavior::Inert),
99 );
100 }
101 }
102}
103
104struct TooltipLabel {
105 label: String,
106 w: f32,
107 h: f32,
108 style: TooltipStyle,
109}
110
111impl Widget for TooltipLabel {
112 fn layout(&self, ctx: &LayoutCtx) -> Size {
113 ctx.constraints.constrain(Size { width: self.w, height: self.h })
114 }
115 fn paint(&self, ctx: &mut PaintCtx) {
116 let r = ctx.rect;
117 if self.style.elevation > 0.0 {
118 ctx.fill_shadow_rrect(r, self.style.radius, Color::rgba(0, 0, 0, 90), 8.0);
119 }
120 ctx.fill_rrect(r, self.style.radius, self.style.background);
121 let ty = r.origin.y + (self.h - ctx.font.line_height(self.style.font_size)) / 2.0;
122 ctx.draw_text_at(
123 &self.label,
124 Point { x: r.origin.x + self.style.pad_h, y: ty },
125 self.style.text_color,
126 self.style.font_size,
127 );
128 }
129}
130
131pub trait WidgetExt: Widget + Sized + 'static {
137 fn tooltip(self, label: impl Into<String>) -> Tooltip {
139 Tooltip::new(label, self)
140 }
141}
142
143impl<W: Widget + Sized + 'static> WidgetExt for W {}