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::*;
16use torin::{
17 prelude::{
18 Alignment,
19 Area,
20 Direction,
21 },
22 size::Size,
23};
24
25use crate::{
26 context_menu::ContextMenu,
27 get_theme,
28 theming::component_themes::{
29 TooltipTheme,
30 TooltipThemePartial,
31 },
32};
33
34#[cfg_attr(feature = "docs",
53 doc = embed_doc_image::embed_image!("tooltip", "images/gallery_tooltip.png")
54)]
55#[derive(PartialEq, Clone)]
56pub struct Tooltip {
57 pub(crate) theme: Option<TooltipThemePartial>,
59 text: Cow<'static, str>,
61 key: DiffKey,
62}
63
64impl KeyExt for Tooltip {
65 fn write_key(&mut self) -> &mut DiffKey {
66 &mut self.key
67 }
68}
69
70impl Tooltip {
71 pub fn new(text: impl Into<Cow<'static, str>>) -> Self {
72 Self {
73 theme: None,
74 text: text.into(),
75 key: DiffKey::None,
76 }
77 }
78}
79
80impl Component for Tooltip {
81 fn render(&self) -> impl IntoElement {
82 let theme = get_theme!(&self.theme, tooltip);
83 let TooltipTheme {
84 background,
85 color,
86 border_fill,
87 font_size,
88 } = theme;
89
90 rect()
91 .interactive(Interactive::No)
92 .padding((4., 10.))
93 .border(
94 Border::new()
95 .width(1.)
96 .alignment(BorderAlignment::Inner)
97 .fill(border_fill),
98 )
99 .background(background)
100 .corner_radius(8.)
101 .child(
102 label()
103 .max_lines(1)
104 .font_size(font_size)
105 .color(color)
106 .text(self.text.clone()),
107 )
108 }
109
110 fn render_key(&self) -> DiffKey {
111 self.key.clone().or(self.default_key())
112 }
113}
114
115#[derive(PartialEq, Clone, Copy, Debug, Default)]
116pub enum TooltipPosition {
117 Besides,
118 #[default]
119 Below,
120}
121
122#[derive(PartialEq)]
123pub struct TooltipContainer {
124 tooltip: Tooltip,
125 children: Vec<Element>,
126 position: TooltipPosition,
127 key: DiffKey,
128}
129
130impl KeyExt for TooltipContainer {
131 fn write_key(&mut self) -> &mut DiffKey {
132 &mut self.key
133 }
134}
135
136impl ChildrenExt for TooltipContainer {
137 fn get_children(&mut self) -> &mut Vec<Element> {
138 &mut self.children
139 }
140}
141
142impl TooltipContainer {
143 pub fn new(tooltip: Tooltip) -> Self {
144 Self {
145 tooltip,
146 children: vec![],
147 position: TooltipPosition::Below,
148 key: DiffKey::None,
149 }
150 }
151
152 pub fn position(mut self, position: TooltipPosition) -> Self {
153 self.position = position;
154 self
155 }
156}
157
158impl Component for TooltipContainer {
159 fn render(&self) -> impl IntoElement {
160 let mut is_hovering = use_state(|| false);
161 let mut size = use_state(Area::default);
162
163 let animation = use_animation(move |conf| {
164 conf.on_change(OnChange::Rerun);
165 conf.on_creation(OnCreation::Finish);
166
167 let scale = AnimNum::new(0.8, 1.)
168 .time(350)
169 .ease(Ease::Out)
170 .function(Function::Expo);
171 let opacity = AnimNum::new(0., 1.)
172 .time(350)
173 .ease(Ease::Out)
174 .function(Function::Expo);
175
176 if is_hovering() {
177 (scale, opacity)
178 } else {
179 (scale.into_reversed(), opacity.into_reversed())
180 }
181 });
182
183 let (scale, opacity) = animation.read().value();
184
185 let on_pointer_over = move |_| {
186 is_hovering.set(true);
187 };
188
189 let on_pointer_out = move |_| {
190 is_hovering.set(false);
191 };
192
193 let on_sized = move |e: Event<SizedEventData>| {
194 size.set(e.area);
195 };
196
197 let direction = match self.position {
198 TooltipPosition::Below => Direction::vertical(),
199 TooltipPosition::Besides => Direction::horizontal(),
200 };
201
202 let is_visible = opacity > 0. && !ContextMenu::is_open();
203
204 rect()
205 .a11y_focusable(false)
206 .a11y_role(AccessibilityRole::Tooltip)
207 .direction(direction)
208 .on_sized(on_sized)
209 .on_pointer_over(on_pointer_over)
210 .on_pointer_out(on_pointer_out)
211 .children(self.children.clone())
212 .child(
213 rect()
214 .width(Size::px(0.))
215 .height(Size::px(0.))
216 .layer(Layer::Overlay)
217 .opacity(opacity)
218 .overflow(if is_visible {
219 Overflow::None
220 } else {
221 Overflow::Clip
222 })
223 .child({
224 match self.position {
225 TooltipPosition::Below => rect()
226 .width(Size::px(size.read().width()))
227 .cross_align(Alignment::Center)
228 .main_align(Alignment::Center)
229 .scale(scale)
230 .padding((5., 0., 0., 0.))
231 .child(self.tooltip.clone()),
232 TooltipPosition::Besides => rect()
233 .height(Size::px(size.read().height()))
234 .cross_align(Alignment::Center)
235 .main_align(Alignment::Center)
236 .scale(scale)
237 .padding((0., 0., 0., 5.))
238 .child(self.tooltip.clone()),
239 }
240 }),
241 )
242 }
243
244 fn render_key(&self) -> DiffKey {
245 self.key.clone().or(self.default_key())
246 }
247}