Skip to content

Commit 2d2022f

Browse files
authored
Add Link widget (#1506)
This looks like a Hyperlink, but doesn't do anything when clicked. Or rather: it lets the user decide what happens on click. Closes #1152
1 parent 96335d5 commit 2d2022f

File tree

6 files changed

+105
-33
lines changed

6 files changed

+105
-33
lines changed

CHANGELOG.md

+2-1
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ NOTE: [`epaint`](epaint/CHANGELOG.md), [`eframe`](eframe/CHANGELOG.md), [`egui_w
1414
* Added `Ui::push_id` to resolve id clashes ([#1374](https://github.com/emilk/egui/pull/1374)).
1515
* Added `Frame::outer_margin`.
1616
* Added `Painter::hline` and `Painter::vline`.
17+
* Added `Link` and `ui.link` ([#1506](https://github.com/emilk/egui/pull/1506)).
1718

1819
### Changed 🔧
1920
* `ClippedMesh` has been replaced with `ClippedPrimitive` ([#1351](https://github.com/emilk/egui/pull/1351)).
@@ -27,7 +28,7 @@ NOTE: [`epaint`](epaint/CHANGELOG.md), [`eframe`](eframe/CHANGELOG.md), [`egui_w
2728
* Renamed the feature `serialize` to `serde` ([#1467](https://github.com/emilk/egui/pull/1467)).
2829

2930
### Fixed 🐛
30-
* Fixed ComboBoxes always being rendered left-aligned ([#1304](https://github.com/emilk/egui/pull/1304)).
31+
* Fixed `ComboBox`:es always being rendered left-aligned ([#1304](https://github.com/emilk/egui/pull/1304)).
3132
* Fixed ui code that could lead to a deadlock ([#1380](https://github.com/emilk/egui/pull/1380)).
3233
* Text is darker and more readable in bright mode ([#1412](https://github.com/emilk/egui/pull/1412)).
3334
* Fixed `Ui::add_visible` sometimes leaving the `Ui` in a disabled state. ([#1436](https://github.com/emilk/egui/issues/1436)).

egui/src/data/output.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -466,7 +466,7 @@ impl WidgetInfo {
466466

467467
// TODO: localization
468468
let widget_type = match typ {
469-
WidgetType::Hyperlink => "link",
469+
WidgetType::Link => "link",
470470
WidgetType::TextEdit => "text edit",
471471
WidgetType::Button => "button",
472472
WidgetType::Checkbox => "checkbox",

egui/src/lib.rs

+2-1
Original file line numberDiff line numberDiff line change
@@ -494,7 +494,8 @@ pub mod special_emojis {
494494
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
495495
pub enum WidgetType {
496496
Label, // TODO: emit Label events
497-
Hyperlink,
497+
/// e.g. a hyperlink
498+
Link,
498499
TextEdit,
499500
Button,
500501
Checkbox,

egui/src/ui.rs

+28-2
Original file line numberDiff line numberDiff line change
@@ -1227,14 +1227,40 @@ impl Ui {
12271227
Label::new(text.into().weak()).ui(self)
12281228
}
12291229

1230-
/// Shortcut for `add(Hyperlink::new(url))`
1230+
/// Looks like a hyperlink.
1231+
///
1232+
/// Shortcut for `add(Link::new(text))`.
1233+
///
1234+
/// ```
1235+
/// # egui::__run_test_ui(|ui| {
1236+
/// if ui.link("Documentation").clicked() {
1237+
/// // …
1238+
/// }
1239+
/// # });
1240+
/// ```
1241+
///
1242+
/// See also [`Link`].
1243+
#[must_use = "You should check if the user clicked this with `if ui.link(…).clicked() { … } "]
1244+
pub fn link(&mut self, text: impl Into<WidgetText>) -> Response {
1245+
Link::new(text).ui(self)
1246+
}
1247+
1248+
/// Link to a web page.
1249+
///
1250+
/// Shortcut for `add(Hyperlink::new(url))`.
1251+
///
1252+
/// ```
1253+
/// # egui::__run_test_ui(|ui| {
1254+
/// ui.hyperlink("https://www.egui.rs/");
1255+
/// # });
1256+
/// ```
12311257
///
12321258
/// See also [`Hyperlink`].
12331259
pub fn hyperlink(&mut self, url: impl ToString) -> Response {
12341260
Hyperlink::new(url).ui(self)
12351261
}
12361262

1237-
/// Shortcut for `add(Hyperlink::new(url).text(label))`
1263+
/// Shortcut for `add(Hyperlink::new(url).text(label))`.
12381264
///
12391265
/// ```
12401266
/// # egui::__run_test_ui(|ui| {

egui/src/widgets/hyperlink.rs

+66-28
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,69 @@
11
use crate::*;
22

3+
/// Clickable text, that looks like a hyperlink.
4+
///
5+
/// To link to a web page, use [`Hyperlink`], [`Ui::hyperlink`] or [`Ui::hyperlink_to`].
6+
///
7+
/// See also [`Ui::link`].
8+
///
9+
/// ```
10+
/// # egui::__run_test_ui(|ui| {
11+
/// // These are equivalent:
12+
/// if ui.link("Documentation").clicked() {
13+
/// // …
14+
/// }
15+
///
16+
/// if ui.add(egui::Link::new("Documentation")).clicked() {
17+
/// // …
18+
/// }
19+
/// # });
20+
/// ```
21+
#[must_use = "You should put this widget in an ui with `ui.add(widget);`"]
22+
pub struct Link {
23+
text: WidgetText,
24+
}
25+
26+
impl Link {
27+
pub fn new(text: impl Into<WidgetText>) -> Self {
28+
Self { text: text.into() }
29+
}
30+
}
31+
32+
impl Widget for Link {
33+
fn ui(self, ui: &mut Ui) -> Response {
34+
let Link { text } = self;
35+
let label = Label::new(text).sense(Sense::click());
36+
37+
let (pos, text_galley, response) = label.layout_in_ui(ui);
38+
response.widget_info(|| WidgetInfo::labeled(WidgetType::Link, text_galley.text()));
39+
40+
if response.hovered() {
41+
ui.ctx().output().cursor_icon = CursorIcon::PointingHand;
42+
}
43+
44+
if ui.is_rect_visible(response.rect) {
45+
let color = ui.visuals().hyperlink_color;
46+
let visuals = ui.style().interact(&response);
47+
48+
let underline = if response.hovered() || response.has_focus() {
49+
Stroke::new(visuals.fg_stroke.width, color)
50+
} else {
51+
Stroke::none()
52+
};
53+
54+
ui.painter().add(epaint::TextShape {
55+
pos,
56+
galley: text_galley.galley,
57+
override_text_color: Some(color),
58+
underline,
59+
angle: 0.0,
60+
});
61+
}
62+
63+
response
64+
}
65+
}
66+
367
/// A clickable hyperlink, e.g. to `"https://github.com/emilk/egui"`.
468
///
569
/// See also [`Ui::hyperlink`] and [`Ui::hyperlink_to`].
@@ -42,15 +106,9 @@ impl Hyperlink {
42106

43107
impl Widget for Hyperlink {
44108
fn ui(self, ui: &mut Ui) -> Response {
45-
let Hyperlink { url, text } = self;
46-
let label = Label::new(text).sense(Sense::click());
109+
let Self { url, text } = self;
47110

48-
let (pos, text_galley, response) = label.layout_in_ui(ui);
49-
response.widget_info(|| WidgetInfo::labeled(WidgetType::Hyperlink, text_galley.text()));
50-
51-
if response.hovered() {
52-
ui.ctx().output().cursor_icon = CursorIcon::PointingHand;
53-
}
111+
let response = ui.add(Link::new(text));
54112
if response.clicked() {
55113
let modifiers = ui.ctx().input().modifiers;
56114
ui.ctx().output().open_url = Some(crate::output::OpenUrl {
@@ -64,26 +122,6 @@ impl Widget for Hyperlink {
64122
new_tab: true,
65123
});
66124
}
67-
68-
if ui.is_rect_visible(response.rect) {
69-
let color = ui.visuals().hyperlink_color;
70-
let visuals = ui.style().interact(&response);
71-
72-
let underline = if response.hovered() || response.has_focus() {
73-
Stroke::new(visuals.fg_stroke.width, color)
74-
} else {
75-
Stroke::none()
76-
};
77-
78-
ui.painter().add(epaint::TextShape {
79-
pos,
80-
galley: text_galley.galley,
81-
override_text_color: Some(color),
82-
underline,
83-
angle: 0.0,
84-
});
85-
}
86-
87125
response.on_hover_text(url)
88126
}
89127
}

egui_demo_lib/src/apps/demo/widget_gallery.rs

+6
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,12 @@ impl WidgetGallery {
141141
}
142142
ui.end_row();
143143

144+
ui.add(doc_link_label("Link", "link"));
145+
if ui.link("Click me!").clicked() {
146+
*boolean = !*boolean;
147+
}
148+
ui.end_row();
149+
144150
ui.add(doc_link_label("Checkbox", "checkbox"));
145151
ui.checkbox(boolean, "Checkbox");
146152
ui.end_row();

0 commit comments

Comments
 (0)