freya_components/
tooltip.rs1use std::borrow::Cow;
2
3use freya_animation::{
4 easing::Function,
5 hook::{
6 AnimatedValue,
7 Ease,
8 OnChange,
9 OnCreation,
10 ReadAnimatedValue,
11 use_animation,
12 },
13 prelude::AnimNum,
14};
15use freya_core::prelude::*;
16
17use crate::{
18 attached::{
19 Attached,
20 AttachedPosition,
21 },
22 context_menu::ContextMenu,
23 define_theme,
24 get_theme,
25};
26
27define_theme! {
28 %[component]
29 pub Tooltip {
30 %[fields]
31 color: Color,
32 background: Color,
33 border_fill: Color,
34 font_size: f32,
35 }
36}
37
38#[cfg_attr(feature = "docs",
57 doc = embed_doc_image::embed_image!("tooltip", "images/gallery_tooltip.png")
58)]
59#[derive(PartialEq, Clone)]
60pub struct Tooltip {
61 pub(crate) theme: Option<TooltipThemePartial>,
63 text: Cow<'static, str>,
65 key: DiffKey,
66}
67
68impl KeyExt for Tooltip {
69 fn write_key(&mut self) -> &mut DiffKey {
70 &mut self.key
71 }
72}
73
74impl Tooltip {
75 pub fn new(text: impl Into<Cow<'static, str>>) -> Self {
76 Self {
77 theme: None,
78 text: text.into(),
79 key: DiffKey::None,
80 }
81 }
82}
83
84impl Component for Tooltip {
85 fn render(&self) -> impl IntoElement {
86 let theme = get_theme!(&self.theme, TooltipThemePreference, "tooltip");
87 let TooltipTheme {
88 background,
89 color,
90 border_fill,
91 font_size,
92 } = theme;
93
94 rect()
95 .interactive(Interactive::No)
96 .padding((4., 10.))
97 .border(
98 Border::new()
99 .width(1.)
100 .alignment(BorderAlignment::Inner)
101 .fill(border_fill),
102 )
103 .background(background)
104 .corner_radius(8.)
105 .child(
106 label()
107 .max_lines(1)
108 .font_size(font_size)
109 .color(color)
110 .text(self.text.clone()),
111 )
112 }
113
114 fn render_key(&self) -> DiffKey {
115 self.key.clone().or(self.default_key())
116 }
117}
118
119#[derive(PartialEq)]
120pub struct TooltipContainer {
121 tooltip: Tooltip,
122 children: Vec<Element>,
123 position: AttachedPosition,
124 layout: LayoutData,
125 key: DiffKey,
126}
127
128impl KeyExt for TooltipContainer {
129 fn write_key(&mut self) -> &mut DiffKey {
130 &mut self.key
131 }
132}
133
134impl LayoutExt for TooltipContainer {
135 fn get_layout(&mut self) -> &mut LayoutData {
136 &mut self.layout
137 }
138}
139
140impl ChildrenExt for TooltipContainer {
141 fn get_children(&mut self) -> &mut Vec<Element> {
142 &mut self.children
143 }
144}
145
146impl TooltipContainer {
147 pub fn new(tooltip: Tooltip) -> Self {
148 Self {
149 tooltip,
150 children: vec![],
151 position: AttachedPosition::Bottom,
152 layout: LayoutData::default(),
153 key: DiffKey::None,
154 }
155 }
156
157 pub fn position(mut self, position: AttachedPosition) -> Self {
158 self.position = position;
159 self
160 }
161}
162
163impl Component for TooltipContainer {
164 fn render(&self) -> impl IntoElement {
165 let mut is_hovering = use_state(|| false);
166
167 let animation = use_animation(move |conf| {
168 conf.on_change(OnChange::Rerun);
169 conf.on_creation(OnCreation::Finish);
170
171 let scale = AnimNum::new(0.8, 1.)
172 .time(350)
173 .ease(Ease::Out)
174 .function(Function::Expo);
175 let opacity = AnimNum::new(0., 1.)
176 .time(350)
177 .ease(Ease::Out)
178 .function(Function::Expo);
179
180 if is_hovering() {
181 (scale, opacity)
182 } else {
183 (scale.into_reversed(), opacity.into_reversed())
184 }
185 });
186
187 let (scale, opacity) = animation.read().value();
188
189 let on_pointer_over = move |_| {
190 is_hovering.set(true);
191 };
192
193 let on_pointer_out = move |_| {
194 is_hovering.set(false);
195 };
196
197 let is_visible = opacity > 0. && !ContextMenu::is_open();
198
199 let padding = match self.position {
200 AttachedPosition::Top => (0., 0., 5., 0.),
201 AttachedPosition::Bottom => (5., 0., 0., 0.),
202 AttachedPosition::Left => (0., 5., 0., 0.),
203 AttachedPosition::Right => (0., 0., 0., 5.),
204 };
205
206 rect()
207 .layout(self.layout.clone())
208 .a11y_focusable(false)
209 .a11y_role(AccessibilityRole::Tooltip)
210 .on_pointer_over(on_pointer_over)
211 .on_pointer_out(on_pointer_out)
212 .child(
213 Attached::new(rect().children(self.children.clone()))
214 .position(self.position)
215 .maybe_child(is_visible.then(|| {
216 rect()
217 .opacity(opacity)
218 .scale(scale)
219 .padding(padding)
220 .child(self.tooltip.clone())
221 })),
222 )
223 }
224
225 fn render_key(&self) -> DiffKey {
226 self.key.clone().or(self.default_key())
227 }
228}