1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
use crate::*;

/// A clickable hyperlink, e.g. to `"https://github.com/emilk/egui"`.
///
/// See also [`Ui::hyperlink`] and [`Ui::hyperlink_to`].
///
/// ```
/// # let ui = &mut egui::Ui::__test();
/// ui.hyperlink("https://github.com/emilk/egui");
/// ui.add(egui::Hyperlink::new("https://github.com/emilk/egui").text("My favorite repo").small());
/// ```
#[must_use = "You should put this widget in an ui with `ui.add(widget);`"]
pub struct Hyperlink {
    url: String,
    label: Label,
}

impl Hyperlink {
    #[allow(clippy::needless_pass_by_value)]
    pub fn new(url: impl ToString) -> Self {
        let url = url.to_string();
        Self {
            url: url.clone(),
            label: Label::new(url),
        }
    }

    #[allow(clippy::needless_pass_by_value)]
    pub fn from_label_and_url(label: impl Into<Label>, url: impl ToString) -> Self {
        Self {
            url: url.to_string(),
            label: label.into(),
        }
    }

    /// Show some other text than the url
    #[allow(clippy::needless_pass_by_value)]
    pub fn text(mut self, text: impl ToString) -> Self {
        self.label.text = text.to_string();
        self
    }

    /// The default is [`Style::body_text_style`] (generally [`TextStyle::Body`]).
    pub fn text_style(mut self, text_style: TextStyle) -> Self {
        self.label = self.label.text_style(text_style);
        self
    }

    pub fn small(self) -> Self {
        self.text_style(TextStyle::Small)
    }
}

impl Widget for Hyperlink {
    fn ui(self, ui: &mut Ui) -> Response {
        let Hyperlink { url, label } = self;
        let galley = label.layout(ui);
        let (rect, response) = ui.allocate_exact_size(galley.size, Sense::click());
        response.widget_info(|| WidgetInfo::labeled(WidgetType::Hyperlink, &galley.text));

        if response.hovered() {
            ui.ctx().output().cursor_icon = CursorIcon::PointingHand;
        }
        if response.clicked() {
            let modifiers = ui.ctx().input().modifiers;
            ui.ctx().output().open_url = Some(crate::output::OpenUrl {
                url: url.clone(),
                new_tab: modifiers.any(),
            });
        }
        if response.middle_clicked() {
            ui.ctx().output().open_url = Some(crate::output::OpenUrl {
                url: url.clone(),
                new_tab: true,
            });
        }

        let color = ui.visuals().hyperlink_color;
        let visuals = ui.style().interact(&response);

        if response.hovered() || response.has_focus() {
            // Underline:
            for row in &galley.rows {
                let rect = row.rect().translate(rect.min.to_vec2());
                ui.painter().line_segment(
                    [rect.left_bottom(), rect.right_bottom()],
                    (visuals.fg_stroke.width, color),
                );
            }
        }

        let label = label.text_color(color);
        label.paint_galley(ui, rect.min, galley);

        response.on_hover_text(url)
    }
}