egui/widgets/
hyperlink.rs

1use crate::*;
2
3use self::text_selection::LabelSelectionState;
4
5/// Clickable text, that looks like a hyperlink.
6///
7/// To link to a web page, use [`Hyperlink`], [`Ui::hyperlink`] or [`Ui::hyperlink_to`].
8///
9/// See also [`Ui::link`].
10///
11/// ```
12/// # egui::__run_test_ui(|ui| {
13/// // These are equivalent:
14/// if ui.link("Documentation").clicked() {
15///     // …
16/// }
17///
18/// if ui.add(egui::Link::new("Documentation")).clicked() {
19///     // …
20/// }
21/// # });
22/// ```
23#[must_use = "You should put this widget in an ui with `ui.add(widget);`"]
24pub struct Link {
25    text: WidgetText,
26}
27
28impl Link {
29    pub fn new(text: impl Into<WidgetText>) -> Self {
30        Self { text: text.into() }
31    }
32}
33
34impl Widget for Link {
35    fn ui(self, ui: &mut Ui) -> Response {
36        let Self { text } = self;
37        let label = Label::new(text).sense(Sense::click());
38
39        let (galley_pos, galley, response) = label.layout_in_ui(ui);
40        response
41            .widget_info(|| WidgetInfo::labeled(WidgetType::Link, ui.is_enabled(), galley.text()));
42
43        if ui.is_rect_visible(response.rect) {
44            let color = ui.visuals().hyperlink_color;
45            let visuals = ui.style().interact(&response);
46
47            let underline = if response.hovered() || response.has_focus() {
48                Stroke::new(visuals.fg_stroke.width, color)
49            } else {
50                Stroke::NONE
51            };
52
53            ui.painter().add(
54                epaint::TextShape::new(galley_pos, galley.clone(), color).with_underline(underline),
55            );
56
57            let selectable = ui.style().interaction.selectable_labels;
58            if selectable {
59                LabelSelectionState::label_text_selection(ui, &response, galley_pos, &galley);
60            }
61
62            if response.hovered() {
63                ui.ctx().set_cursor_icon(CursorIcon::PointingHand);
64            }
65        }
66
67        response
68    }
69}
70
71/// A clickable hyperlink, e.g. to `"https://github.com/emilk/egui"`.
72///
73/// See also [`Ui::hyperlink`] and [`Ui::hyperlink_to`].
74///
75/// ```
76/// # egui::__run_test_ui(|ui| {
77/// // These are equivalent:
78/// ui.hyperlink("https://github.com/emilk/egui");
79/// ui.add(egui::Hyperlink::new("https://github.com/emilk/egui"));
80///
81/// // These are equivalent:
82/// ui.hyperlink_to("My favorite repo", "https://github.com/emilk/egui");
83/// ui.add(egui::Hyperlink::from_label_and_url("My favorite repo", "https://github.com/emilk/egui"));
84/// # });
85/// ```
86#[must_use = "You should put this widget in an ui with `ui.add(widget);`"]
87pub struct Hyperlink {
88    url: String,
89    text: WidgetText,
90    new_tab: bool,
91}
92
93impl Hyperlink {
94    #[allow(clippy::needless_pass_by_value)]
95    pub fn new(url: impl ToString) -> Self {
96        let url = url.to_string();
97        Self {
98            url: url.clone(),
99            text: url.into(),
100            new_tab: false,
101        }
102    }
103
104    #[allow(clippy::needless_pass_by_value)]
105    pub fn from_label_and_url(text: impl Into<WidgetText>, url: impl ToString) -> Self {
106        Self {
107            url: url.to_string(),
108            text: text.into(),
109            new_tab: false,
110        }
111    }
112
113    /// Always open this hyperlink in a new browser tab.
114    #[inline]
115    pub fn open_in_new_tab(mut self, new_tab: bool) -> Self {
116        self.new_tab = new_tab;
117        self
118    }
119}
120
121impl Widget for Hyperlink {
122    fn ui(self, ui: &mut Ui) -> Response {
123        let Self { url, text, new_tab } = self;
124
125        let response = ui.add(Link::new(text));
126
127        if response.clicked() {
128            let modifiers = ui.ctx().input(|i| i.modifiers);
129            ui.ctx().open_url(crate::OpenUrl {
130                url: url.clone(),
131                new_tab: new_tab || modifiers.any(),
132            });
133        }
134        if response.middle_clicked() {
135            ui.ctx().open_url(crate::OpenUrl {
136                url: url.clone(),
137                new_tab: true,
138            });
139        }
140
141        if ui.style().url_in_tooltip {
142            response.on_hover_text(url)
143        } else {
144            response
145        }
146    }
147}