egui/widgets/
hyperlink.rs1use crate::{
2 epaint, text_selection, CursorIcon, Label, Response, Sense, Stroke, Ui, Widget, WidgetInfo,
3 WidgetText, WidgetType,
4};
5
6use self::text_selection::LabelSelectionState;
7
8#[must_use = "You should put this widget in a ui with `ui.add(widget);`"]
27pub struct Link {
28 text: WidgetText,
29}
30
31impl Link {
32 pub fn new(text: impl Into<WidgetText>) -> Self {
33 Self { text: text.into() }
34 }
35}
36
37impl Widget for Link {
38 fn ui(self, ui: &mut Ui) -> Response {
39 let Self { text } = self;
40 let label = Label::new(text).sense(Sense::click());
41
42 let (galley_pos, galley, response) = label.layout_in_ui(ui);
43 response
44 .widget_info(|| WidgetInfo::labeled(WidgetType::Link, ui.is_enabled(), galley.text()));
45
46 if ui.is_rect_visible(response.rect) {
47 let color = ui.visuals().hyperlink_color;
48 let visuals = ui.style().interact(&response);
49
50 let underline = if response.hovered() || response.has_focus() {
51 Stroke::new(visuals.fg_stroke.width, color)
52 } else {
53 Stroke::NONE
54 };
55
56 let selectable = ui.style().interaction.selectable_labels;
57 if selectable {
58 LabelSelectionState::label_text_selection(
59 ui, &response, galley_pos, galley, color, underline,
60 );
61 } else {
62 ui.painter().add(
63 epaint::TextShape::new(galley_pos, galley, color).with_underline(underline),
64 );
65 }
66
67 if response.hovered() {
68 ui.ctx().set_cursor_icon(CursorIcon::PointingHand);
69 }
70 }
71
72 response
73 }
74}
75
76#[must_use = "You should put this widget in a ui with `ui.add(widget);`"]
92pub struct Hyperlink {
93 url: String,
94 text: WidgetText,
95 new_tab: bool,
96}
97
98impl Hyperlink {
99 #[allow(clippy::needless_pass_by_value)]
100 pub fn new(url: impl ToString) -> Self {
101 let url = url.to_string();
102 Self {
103 url: url.clone(),
104 text: url.into(),
105 new_tab: false,
106 }
107 }
108
109 #[allow(clippy::needless_pass_by_value)]
110 pub fn from_label_and_url(text: impl Into<WidgetText>, url: impl ToString) -> Self {
111 Self {
112 url: url.to_string(),
113 text: text.into(),
114 new_tab: false,
115 }
116 }
117
118 #[inline]
120 pub fn open_in_new_tab(mut self, new_tab: bool) -> Self {
121 self.new_tab = new_tab;
122 self
123 }
124}
125
126impl Widget for Hyperlink {
127 fn ui(self, ui: &mut Ui) -> Response {
128 let Self { url, text, new_tab } = self;
129
130 let response = ui.add(Link::new(text));
131
132 if response.clicked() {
133 let modifiers = ui.ctx().input(|i| i.modifiers);
134 ui.ctx().open_url(crate::OpenUrl {
135 url: url.clone(),
136 new_tab: new_tab || modifiers.any(),
137 });
138 }
139 if response.middle_clicked() {
140 ui.ctx().open_url(crate::OpenUrl {
141 url: url.clone(),
142 new_tab: true,
143 });
144 }
145
146 if ui.style().url_in_tooltip {
147 response.on_hover_text(url)
148 } else {
149 response
150 }
151 }
152}