1use std::sync::Arc;
4use std::sync::atomic::{AtomicUsize, Ordering};
5
6use crate::core::component::{Component, Context, Update};
7use crate::core::element::{Element, IntoElement, Key};
8use crate::style::{BorderStyle, Padding, Style};
9use crate::widgets::{Frame, Popover, PopoverOffset, PopoverPlacement, Text};
10
11static TOOLTIP_ID: AtomicUsize = AtomicUsize::new(1);
12
13#[derive(Clone)]
15pub struct Tooltip {
16 child: Element,
17 text: Arc<str>,
18 open: Option<bool>,
19 auto: bool,
20 show_on_focus: bool,
21 text_style: Style,
22 container_style: Style,
23 border: bool,
24 border_style: BorderStyle,
25 padding: Padding,
26 placement: PopoverPlacement,
27 offset: PopoverOffset,
28 clamp: bool,
29 auto_flip: bool,
30}
31
32impl Tooltip {
33 pub fn new(text: impl Into<Arc<str>>) -> Self {
35 Self {
36 child: crate::widgets::Spacer::new().into(),
37 text: text.into(),
38 open: None,
39 auto: true,
40 show_on_focus: true,
41 text_style: Style::default(),
42 container_style: Style::default(),
43 border: false,
44 border_style: BorderStyle::Plain,
45 padding: 0.into(),
46 placement: PopoverPlacement::BelowStart,
47 offset: PopoverOffset::ZERO,
48 clamp: true,
49 auto_flip: true,
50 }
51 }
52
53 pub fn child(mut self, child: impl IntoElement) -> Self {
55 self.child = child.into();
56 self
57 }
58
59 pub fn open(mut self, open: bool) -> Self {
61 self.open = Some(open);
62 self
63 }
64
65 pub fn auto(mut self, auto: bool) -> Self {
67 self.auto = auto;
68 self
69 }
70
71 pub fn show_on_focus(mut self, show_on_focus: bool) -> Self {
73 self.show_on_focus = show_on_focus;
74 self
75 }
76
77 pub fn text_style(mut self, style: Style) -> Self {
79 self.text_style = style;
80 self
81 }
82
83 pub fn container_style(mut self, style: Style) -> Self {
85 self.container_style = style;
86 self
87 }
88
89 pub fn border(mut self, border: bool) -> Self {
91 self.border = border;
92 self
93 }
94
95 pub fn border_style(mut self, border_style: BorderStyle) -> Self {
97 self.border_style = border_style;
98 self
99 }
100
101 pub fn padding(mut self, padding: impl Into<Padding>) -> Self {
103 self.padding = padding.into();
104 self
105 }
106
107 pub fn placement(mut self, placement: PopoverPlacement) -> Self {
109 self.placement = placement;
110 self
111 }
112
113 pub fn offset(mut self, offset: impl Into<PopoverOffset>) -> Self {
115 self.offset = offset.into();
116 self
117 }
118
119 pub fn clamp(mut self, clamp: bool) -> Self {
121 self.clamp = clamp;
122 self
123 }
124
125 pub fn auto_flip(mut self, auto_flip: bool) -> Self {
127 self.auto_flip = auto_flip;
128 self
129 }
130}
131
132#[derive(Clone)]
133struct TooltipProps {
134 child: Element,
135 text: Arc<str>,
136 open: Option<bool>,
137 auto: bool,
138 show_on_focus: bool,
139 text_style: Style,
140 container_style: Style,
141 border: bool,
142 border_style: BorderStyle,
143 padding: Padding,
144 placement: PopoverPlacement,
145 offset: PopoverOffset,
146 clamp: bool,
147 auto_flip: bool,
148}
149
150impl PartialEq for TooltipProps {
151 fn eq(&self, other: &Self) -> bool {
152 if self.text != other.text
153 || self.open != other.open
154 || self.auto != other.auto
155 || self.show_on_focus != other.show_on_focus
156 || self.text_style != other.text_style
157 || self.container_style != other.container_style
158 || self.border != other.border
159 || self.border_style != other.border_style
160 || self.padding != other.padding
161 || self.placement != other.placement
162 || self.offset != other.offset
163 || self.clamp != other.clamp
164 || self.auto_flip != other.auto_flip
165 {
166 return false;
167 }
168
169 let left = crate::layout::hash::element_layout_hash(&self.child);
170 let right = crate::layout::hash::element_layout_hash(&other.child);
171 match (left, right) {
172 (Some(left), Some(right)) => left == right,
173 _ => false,
174 }
175 }
176}
177
178impl From<Tooltip> for TooltipProps {
179 fn from(tooltip: Tooltip) -> Self {
180 Self {
181 child: tooltip.child,
182 text: tooltip.text,
183 open: tooltip.open,
184 auto: tooltip.auto,
185 show_on_focus: tooltip.show_on_focus,
186 text_style: tooltip.text_style,
187 container_style: tooltip.container_style,
188 border: tooltip.border,
189 border_style: tooltip.border_style,
190 padding: tooltip.padding,
191 placement: tooltip.placement,
192 offset: tooltip.offset,
193 clamp: tooltip.clamp,
194 auto_flip: tooltip.auto_flip,
195 }
196 }
197}
198
199#[derive(Clone, Debug)]
200struct TooltipState {
201 trigger_key: Key,
202}
203
204struct TooltipComponent;
205
206impl TooltipComponent {
207 fn new() -> Self {
208 Self
209 }
210}
211
212impl Component for TooltipComponent {
213 type Message = ();
214 type Properties = TooltipProps;
215 type State = TooltipState;
216
217 fn create_state(&self, _props: &Self::Properties) -> Self::State {
218 let id = TOOLTIP_ID.fetch_add(1, Ordering::Relaxed);
219 TooltipState {
220 trigger_key: Key::from(format!("tooltip-trigger-{}", id)),
221 }
222 }
223
224 fn update(&mut self, _msg: Self::Message, _ctx: &mut Context<Self>) -> Update {
225 Update::none()
226 }
227
228 fn view(&self, ctx: &Context<Self>) -> Element {
229 let trigger = ctx.props.child.clone().key(ctx.state.trigger_key.clone());
230
231 let auto_open = ctx.props.auto
232 && (ctx.has_hover_within_key(ctx.state.trigger_key.clone())
233 || (ctx.props.show_on_focus
234 && ctx.has_focus_within_key(ctx.state.trigger_key.clone())));
235 let open = ctx.props.open.unwrap_or(auto_open);
236
237 let content = Frame::new()
238 .border(ctx.props.border)
239 .border_style(ctx.props.border_style)
240 .padding(ctx.props.padding)
241 .style(ctx.props.container_style)
242 .child(Text::new(ctx.props.text.clone()).style(ctx.props.text_style));
243
244 Popover::new()
245 .trigger(trigger)
246 .content(content)
247 .open(open)
248 .capture_focus(false)
249 .auto_focus(false)
250 .min_trigger_width(false)
251 .placement(ctx.props.placement)
252 .offset(ctx.props.offset)
253 .clamp(ctx.props.clamp)
254 .auto_flip(ctx.props.auto_flip)
255 .into()
256 }
257}
258
259impl From<Tooltip> for Element {
260 fn from(tooltip: Tooltip) -> Self {
261 crate::child(TooltipComponent::new, TooltipProps::from(tooltip))
262 }
263}