diff --git a/CHANGELOG.md b/CHANGELOG.md index ebc100511ef..afb56641c92 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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)). @@ -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)). diff --git a/egui/src/data/output.rs b/egui/src/data/output.rs index 66049cb0c80..8f826cd25b1 100644 --- a/egui/src/data/output.rs +++ b/egui/src/data/output.rs @@ -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", diff --git a/egui/src/lib.rs b/egui/src/lib.rs index 1310ce4272e..910b0e1949e 100644 --- a/egui/src/lib.rs +++ b/egui/src/lib.rs @@ -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, diff --git a/egui/src/ui.rs b/egui/src/ui.rs index 04660c78986..804c61a3c1f 100644 --- a/egui/src/ui.rs +++ b/egui/src/ui.rs @@ -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) -> 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| { diff --git a/egui/src/widgets/hyperlink.rs b/egui/src/widgets/hyperlink.rs index 764d4036aba..47994451bd7 100644 --- a/egui/src/widgets/hyperlink.rs +++ b/egui/src/widgets/hyperlink.rs @@ -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) -> 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`]. @@ -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 { @@ -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) } } diff --git a/egui_demo_lib/src/apps/demo/widget_gallery.rs b/egui_demo_lib/src/apps/demo/widget_gallery.rs index 797ec19a5e0..e99b0801863 100644 --- a/egui_demo_lib/src/apps/demo/widget_gallery.rs +++ b/egui_demo_lib/src/apps/demo/widget_gallery.rs @@ -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();