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 } = theme;
88
89 rect()
90 .interactive(Interactive::No)
91 .padding((4., 10.))
92 .border(
93 Border::new()
94 .width(1.)
95 .alignment(BorderAlignment::Inner)
96 .fill(border_fill),
97 )
98 .background(background)
99 .corner_radius(8.)
100 .child(
101 label()
102 .max_lines(1)
103 .font_size(14.)
104 .color(color)
105 .text(self.text.clone()),
106 )
107 }
108
109 fn render_key(&self) -> DiffKey {
110 self.key.clone().or(self.default_key())
111 }
112}
113
114#[derive(PartialEq, Clone, Copy, Debug, Default)]
115pub enum TooltipPosition {
116 Besides,
117 #[default]
118 Below,
119}
120
121#[derive(PartialEq)]
122pub struct TooltipContainer {
123 tooltip: Tooltip,
124 children: Vec<Element>,
125 position: TooltipPosition,
126 key: DiffKey,
127}
128
129impl KeyExt for TooltipContainer {
130 fn write_key(&mut self) -> &mut DiffKey {
131 &mut self.key
132 }
133}
134
135impl ChildrenExt for TooltipContainer {
136 fn get_children(&mut self) -> &mut Vec<Element> {
137 &mut self.children
138 }
139}
140
141impl TooltipContainer {
142 pub fn new(tooltip: Tooltip) -> Self {
143 Self {
144 tooltip,
145 children: vec![],
146 position: TooltipPosition::Below,
147 key: DiffKey::None,
148 }
149 }
150
151 pub fn position(mut self, position: TooltipPosition) -> Self {
152 self.position = position;
153 self
154 }
155}
156
157impl Component for TooltipContainer {
158 fn render(&self) -> impl IntoElement {
159 let mut is_hovering = use_state(|| false);
160 let mut size = use_state(Area::default);
161
162 let animation = use_animation(move |conf| {
163 conf.on_change(OnChange::Rerun);
164 conf.on_creation(OnCreation::Finish);
165
166 let scale = AnimNum::new(0.8, 1.)
167 .time(350)
168 .ease(Ease::Out)
169 .function(Function::Expo);
170 let opacity = AnimNum::new(0., 1.)
171 .time(350)
172 .ease(Ease::Out)
173 .function(Function::Expo);
174
175 if is_hovering() {
176 (scale, opacity)
177 } else {
178 (scale.into_reversed(), opacity.into_reversed())
179 }
180 });
181
182 let (scale, opacity) = animation.read().value();
183
184 let on_pointer_enter = move |_| {
185 is_hovering.set(true);
186 };
187
188 let on_pointer_leave = move |_| {
189 is_hovering.set(false);
190 };
191
192 let on_sized = move |e: Event<SizedEventData>| {
193 size.set(e.area);
194 };
195
196 let direction = match self.position {
197 TooltipPosition::Below => Direction::vertical(),
198 TooltipPosition::Besides => Direction::horizontal(),
199 };
200
201 let is_visible = opacity > 0. && !ContextMenu::is_open();
202
203 rect()
204 .a11y_focusable(false)
205 .a11y_role(AccessibilityRole::Tooltip)
206 .direction(direction)
207 .on_sized(on_sized)
208 .on_pointer_enter(on_pointer_enter)
209 .on_pointer_leave(on_pointer_leave)
210 .children(self.children.clone())
211 .child(
212 rect()
213 .width(Size::px(0.))
214 .height(Size::px(0.))
215 .layer(Layer::Overlay)
216 .opacity(opacity)
217 .overflow(if is_visible {
218 Overflow::None
219 } else {
220 Overflow::Clip
221 })
222 .child({
223 match self.position {
224 TooltipPosition::Below => rect()
225 .width(Size::px(size.read().width()))
226 .cross_align(Alignment::Center)
227 .main_align(Alignment::Center)
228 .scale(scale)
229 .padding((5., 0., 0., 0.))
230 .child(self.tooltip.clone()),
231 TooltipPosition::Besides => rect()
232 .height(Size::px(size.read().height()))
233 .cross_align(Alignment::Center)
234 .main_align(Alignment::Center)
235 .scale(scale)
236 .padding((0., 0., 0., 5.))
237 .child(self.tooltip.clone()),
238 }
239 }),
240 )
241 }
242
243 fn render_key(&self) -> DiffKey {
244 self.key.clone().or(self.default_key())
245 }
246}