freya_components/
tooltip.rs

1use std::borrow::Cow;
2
3use freya_core::prelude::*;
4use torin::{
5    prelude::{
6        Alignment,
7        Area,
8        Direction,
9    },
10    size::Size,
11};
12
13use crate::{
14    get_theme,
15    theming::component_themes::{
16        TooltipTheme,
17        TooltipThemePartial,
18    },
19};
20
21/// Tooltip component.
22///
23/// # Example
24///
25/// ```rust
26/// # use freya::prelude::*;
27/// fn app() -> impl IntoElement {
28///     Tooltip::new("Hello, World!")
29/// }
30///
31/// # use freya_testing::prelude::*;
32/// # launch_doc(|| {
33/// #   rect().center().expanded().child(app())
34/// # }, (250., 250.).into(), "./images/gallery_tooltip.png");
35/// ```
36///
37/// # Preview
38/// ![Tooltip Preview][tooltip]
39#[cfg_attr(feature = "docs",
40    doc = embed_doc_image::embed_image!("tooltip", "images/gallery_tooltip.png")
41)]
42#[derive(PartialEq, Clone)]
43pub struct Tooltip {
44    /// Theme override.
45    pub(crate) theme: Option<TooltipThemePartial>,
46    /// Text to show in the [Tooltip].
47    text: Cow<'static, str>,
48    key: DiffKey,
49}
50
51impl KeyExt for Tooltip {
52    fn write_key(&mut self) -> &mut DiffKey {
53        &mut self.key
54    }
55}
56
57impl Tooltip {
58    pub fn new(text: impl Into<Cow<'static, str>>) -> Self {
59        Self {
60            theme: None,
61            text: text.into(),
62            key: DiffKey::None,
63        }
64    }
65}
66
67impl Render for Tooltip {
68    fn render(&self) -> impl IntoElement {
69        let theme = get_theme!(&self.theme, tooltip);
70        let TooltipTheme {
71            background,
72            color,
73            border_fill,
74        } = theme;
75
76        rect()
77            .padding((4., 10.))
78            .border(
79                Border::new()
80                    .width(1.)
81                    .alignment(BorderAlignment::Inner)
82                    .fill(border_fill),
83            )
84            .background(background)
85            .corner_radius(8.)
86            .child(
87                label()
88                    .max_lines(1)
89                    .font_size(14.)
90                    .color(color)
91                    .text(self.text.clone()),
92            )
93    }
94
95    fn render_key(&self) -> DiffKey {
96        self.key.clone().or(self.default_key())
97    }
98}
99
100#[derive(PartialEq, Clone, Copy, Debug, Default)]
101pub enum TooltipPosition {
102    Besides,
103    #[default]
104    Below,
105}
106
107#[derive(PartialEq)]
108pub struct TooltipContainer {
109    tooltip: Tooltip,
110    children: Vec<Element>,
111    position: TooltipPosition,
112    key: DiffKey,
113}
114
115impl KeyExt for TooltipContainer {
116    fn write_key(&mut self) -> &mut DiffKey {
117        &mut self.key
118    }
119}
120
121impl ChildrenExt for TooltipContainer {
122    fn get_children(&mut self) -> &mut Vec<Element> {
123        &mut self.children
124    }
125}
126
127impl TooltipContainer {
128    pub fn new(tooltip: Tooltip) -> Self {
129        Self {
130            tooltip,
131            children: vec![],
132            position: TooltipPosition::Below,
133            key: DiffKey::None,
134        }
135    }
136
137    pub fn position(mut self, position: TooltipPosition) -> Self {
138        self.position = position;
139        self
140    }
141}
142
143impl Render for TooltipContainer {
144    fn render(&self) -> impl IntoElement {
145        let mut is_hovering = use_state(|| false);
146        let mut size = use_state(Area::default);
147
148        let on_pointer_enter = move |_| {
149            is_hovering.set(true);
150        };
151
152        let on_pointer_leave = move |_| {
153            is_hovering.set(false);
154        };
155
156        let on_sized = move |e: Event<SizedEventData>| {
157            size.set(e.area);
158        };
159
160        let direction = match self.position {
161            TooltipPosition::Below => Direction::vertical(),
162            TooltipPosition::Besides => Direction::horizontal(),
163        };
164
165        rect()
166            .direction(direction)
167            .on_sized(on_sized)
168            .on_pointer_enter(on_pointer_enter)
169            .on_pointer_leave(on_pointer_leave)
170            .children(self.children.clone())
171            .child(
172                rect()
173                    .width(Size::px(0.))
174                    .height(Size::px(0.))
175                    .layer(1500)
176                    .maybe_child(if *is_hovering.read() {
177                        Some(match self.position {
178                            TooltipPosition::Below => rect()
179                                .width(Size::px(size.read().width()))
180                                .cross_align(Alignment::Center)
181                                .main_align(Alignment::Center)
182                                .padding((5., 0., 0., 0.))
183                                .child(self.tooltip.clone()),
184                            TooltipPosition::Besides => rect()
185                                .height(Size::px(size.read().height()))
186                                .cross_align(Alignment::Center)
187                                .main_align(Alignment::Center)
188                                .padding((0., 0., 0., 5.))
189                                .child(self.tooltip.clone()),
190                        })
191                    } else {
192                        None
193                    }),
194            )
195    }
196
197    fn render_key(&self) -> DiffKey {
198        self.key.clone().or(self.default_key())
199    }
200}