Skip to main content

freya_components/
link.rs

1use freya_core::prelude::*;
2use freya_router::prelude::{
3    NavigationTarget,
4    RouterContext,
5};
6
7use crate::{
8    define_theme,
9    get_theme,
10    tooltip::{
11        Tooltip,
12        TooltipContainer,
13    },
14};
15
16define_theme! {
17    %[component]
18    pub Link {
19        %[fields]
20        color: Color,
21    }
22}
23
24/// Tooltip configuration for the [`Link`] component.
25#[derive(Clone, PartialEq)]
26pub enum LinkTooltip {
27    /// No tooltip at all.
28    None,
29    /// Default tooltip.
30    ///
31    /// - For a route, this is the same as [`None`](LinkTooltip::None).
32    /// - For a URL, this is the value of that URL.
33    Default,
34    /// Custom tooltip to always show.
35    Custom(String),
36}
37
38#[derive(PartialEq)]
39pub struct Link {
40    /// Theme override.
41    pub(crate) theme: Option<LinkThemePartial>,
42    /// The route or external URL string to navigate to.
43    to: NavigationTarget,
44    /// Inner children for the Link.
45    children: Vec<Element>,
46    /// A text hint to show when hovering over the link.
47    tooltip: LinkTooltip,
48    /// Key for the component.
49    key: DiffKey,
50}
51
52impl ChildrenExt for Link {
53    fn get_children(&mut self) -> &mut Vec<Element> {
54        &mut self.children
55    }
56}
57
58impl KeyExt for Link {
59    fn write_key(&mut self) -> &mut DiffKey {
60        &mut self.key
61    }
62}
63
64impl Link {
65    pub fn new(to: impl Into<NavigationTarget>) -> Self {
66        Self {
67            to: to.into(),
68            children: Vec::new(),
69            tooltip: LinkTooltip::Default,
70            theme: None,
71            key: DiffKey::None,
72        }
73    }
74
75    pub fn tooltip(mut self, tooltip: impl Into<LinkTooltip>) -> Self {
76        self.tooltip = tooltip.into();
77        self
78    }
79}
80
81impl Component for Link {
82    fn render(&self) -> impl IntoElement {
83        let theme = get_theme!(&self.theme, LinkThemePreference, "link");
84        let mut is_hovering = use_state(|| false);
85
86        let url = if let NavigationTarget::External(ref url) = self.to {
87            Some(url.clone())
88        } else {
89            None
90        };
91
92        let on_pointer_enter = move |_| {
93            is_hovering.set(true);
94        };
95
96        let on_pointer_leave = move |_| {
97            is_hovering.set(false);
98        };
99
100        let on_press = {
101            let to = self.to.clone();
102            let url = url.clone();
103            move |_| {
104                // Open the url if there is any
105                // otherwise change the freya router route
106                if let Some(url) = &url {
107                    let _ = open::that(url);
108                } else {
109                    let _ = RouterContext::get().push(to.clone());
110                }
111            }
112        };
113
114        let color = if *is_hovering.read() {
115            Some(theme.color)
116        } else {
117            None
118        };
119
120        let tooltip_text = match &self.tooltip {
121            LinkTooltip::Default => url,
122            LinkTooltip::None => None,
123            LinkTooltip::Custom(str) => Some(str.clone()),
124        };
125
126        let link = rect()
127            .on_press(on_press)
128            .on_pointer_enter(on_pointer_enter)
129            .on_pointer_leave(on_pointer_leave)
130            .map(color, |rect, color| rect.color(color))
131            .children(self.children.clone());
132
133        if let Some(tooltip_text) = tooltip_text {
134            TooltipContainer::new(Tooltip::new(tooltip_text))
135                .child(link)
136                .into_element()
137        } else {
138            link.into()
139        }
140    }
141
142    fn render_key(&self) -> DiffKey {
143        self.key.clone().or(self.default_key())
144    }
145}