Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Link widget #1506

Merged
merged 2 commits into from
Apr 16, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ NOTE: [`epaint`](epaint/CHANGELOG.md), [`eframe`](eframe/CHANGELOG.md), [`egui_w
* Added `Ui::push_id` to resolve id clashes ([#1374](https://github.com/emilk/egui/pull/1374)).
* Added `Frame::outer_margin`.
* Added `Painter::hline` and `Painter::vline`.
* Added `Link` and `ui.link` ([#1506](https://github.com/emilk/egui/pull/1506)).

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

### Fixed 🐛
* Fixed ComboBoxes always being rendered left-aligned ([#1304](https://github.com/emilk/egui/pull/1304)).
* Fixed `ComboBox`:es always being rendered left-aligned ([#1304](https://github.com/emilk/egui/pull/1304)).
* Fixed ui code that could lead to a deadlock ([#1380](https://github.com/emilk/egui/pull/1380)).
* Text is darker and more readable in bright mode ([#1412](https://github.com/emilk/egui/pull/1412)).
* Fixed `Ui::add_visible` sometimes leaving the `Ui` in a disabled state. ([#1436](https://github.com/emilk/egui/issues/1436)).
Expand Down
2 changes: 1 addition & 1 deletion egui/src/data/output.rs
Original file line number Diff line number Diff line change
Expand Up @@ -466,7 +466,7 @@ impl WidgetInfo {

// TODO: localization
let widget_type = match typ {
WidgetType::Hyperlink => "link",
WidgetType::Link => "link",
WidgetType::TextEdit => "text edit",
WidgetType::Button => "button",
WidgetType::Checkbox => "checkbox",
Expand Down
3 changes: 2 additions & 1 deletion egui/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -494,7 +494,8 @@ pub mod special_emojis {
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
pub enum WidgetType {
Label, // TODO: emit Label events
Hyperlink,
/// e.g. a hyperlink
Link,
TextEdit,
Button,
Checkbox,
Expand Down
30 changes: 28 additions & 2 deletions egui/src/ui.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1227,14 +1227,40 @@ impl Ui {
Label::new(text.into().weak()).ui(self)
}

/// Shortcut for `add(Hyperlink::new(url))`
/// Looks like a hyperlink.
///
/// Shortcut for `add(Link::new(text))`.
///
/// ```
/// # egui::__run_test_ui(|ui| {
/// if ui.link("Documentation").clicked() {
/// // …
/// }
/// # });
/// ```
///
/// See also [`Link`].
#[must_use = "You should check if the user clicked this with `if ui.link(…).clicked() { … } "]
pub fn link(&mut self, text: impl Into<WidgetText>) -> Response {
Link::new(text).ui(self)
}

/// Link to a web page.
///
/// Shortcut for `add(Hyperlink::new(url))`.
///
/// ```
/// # egui::__run_test_ui(|ui| {
/// ui.hyperlink("https://www.egui.rs/");
/// # });
/// ```
///
/// See also [`Hyperlink`].
pub fn hyperlink(&mut self, url: impl ToString) -> Response {
Hyperlink::new(url).ui(self)
}

/// Shortcut for `add(Hyperlink::new(url).text(label))`
/// Shortcut for `add(Hyperlink::new(url).text(label))`.
///
/// ```
/// # egui::__run_test_ui(|ui| {
Expand Down
94 changes: 66 additions & 28 deletions egui/src/widgets/hyperlink.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,69 @@
use crate::*;

/// Clickable text, that looks like a hyperlink.
///
/// To link to a web page, use [`Hyperlink`], [`Ui::hyperlink`] or [`Ui::hyperlink_to`].
///
/// See also [`Ui::link`].
///
/// ```
/// # egui::__run_test_ui(|ui| {
/// // These are equivalent:
/// if ui.link("Documentation").clicked() {
/// // …
/// }
///
/// if ui.add(egui::Link::new("Documentation")).clicked() {
/// // …
/// }
/// # });
/// ```
#[must_use = "You should put this widget in an ui with `ui.add(widget);`"]
pub struct Link {
text: WidgetText,
}

impl Link {
pub fn new(text: impl Into<WidgetText>) -> Self {
Self { text: text.into() }
}
}

impl Widget for Link {
fn ui(self, ui: &mut Ui) -> Response {
let Link { text } = self;
let label = Label::new(text).sense(Sense::click());

let (pos, text_galley, response) = label.layout_in_ui(ui);
response.widget_info(|| WidgetInfo::labeled(WidgetType::Link, text_galley.text()));

if response.hovered() {
ui.ctx().output().cursor_icon = CursorIcon::PointingHand;
}

if ui.is_rect_visible(response.rect) {
let color = ui.visuals().hyperlink_color;
let visuals = ui.style().interact(&response);

let underline = if response.hovered() || response.has_focus() {
Stroke::new(visuals.fg_stroke.width, color)
} else {
Stroke::none()
};

ui.painter().add(epaint::TextShape {
pos,
galley: text_galley.galley,
override_text_color: Some(color),
underline,
angle: 0.0,
});
}

response
}
}

/// A clickable hyperlink, e.g. to `"https://github.com/emilk/egui"`.
///
/// See also [`Ui::hyperlink`] and [`Ui::hyperlink_to`].
Expand Down Expand Up @@ -42,15 +106,9 @@ impl Hyperlink {

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

let (pos, text_galley, response) = label.layout_in_ui(ui);
response.widget_info(|| WidgetInfo::labeled(WidgetType::Hyperlink, text_galley.text()));

if response.hovered() {
ui.ctx().output().cursor_icon = CursorIcon::PointingHand;
}
let response = ui.add(Link::new(text));
if response.clicked() {
let modifiers = ui.ctx().input().modifiers;
ui.ctx().output().open_url = Some(crate::output::OpenUrl {
Expand All @@ -64,26 +122,6 @@ impl Widget for Hyperlink {
new_tab: true,
});
}

if ui.is_rect_visible(response.rect) {
let color = ui.visuals().hyperlink_color;
let visuals = ui.style().interact(&response);

let underline = if response.hovered() || response.has_focus() {
Stroke::new(visuals.fg_stroke.width, color)
} else {
Stroke::none()
};

ui.painter().add(epaint::TextShape {
pos,
galley: text_galley.galley,
override_text_color: Some(color),
underline,
angle: 0.0,
});
}

response.on_hover_text(url)
}
}
6 changes: 6 additions & 0 deletions egui_demo_lib/src/apps/demo/widget_gallery.rs
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,12 @@ impl WidgetGallery {
}
ui.end_row();

ui.add(doc_link_label("Link", "link"));
if ui.link("Click me!").clicked() {
*boolean = !*boolean;
}
ui.end_row();

ui.add(doc_link_label("Checkbox", "checkbox"));
ui.checkbox(boolean, "Checkbox");
ui.end_row();
Expand Down