freya_components/
tooltip.rs

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