From e4d346d23ca73b8efb217b3dbff78859f17485f5 Mon Sep 17 00:00:00 2001 From: lucasmerlin Date: Mon, 30 Jun 2025 13:21:51 +0200 Subject: [PATCH] Atom link (wip) --- crates/egui/src/ui.rs | 4 +- crates/egui/src/widgets/hyperlink.rs | 66 ++++++++++++------- .../egui_demo_lib/src/demo/widget_gallery.rs | 3 +- 3 files changed, 48 insertions(+), 25 deletions(-) diff --git a/crates/egui/src/ui.rs b/crates/egui/src/ui.rs index 2629bbbe9..3eba8c1d1 100644 --- a/crates/egui/src/ui.rs +++ b/crates/egui/src/ui.rs @@ -1974,8 +1974,8 @@ impl Ui { /// /// 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) + pub fn link<'a>(&mut self, atoms: impl IntoAtoms<'a>) -> Response { + Link::new(atoms).ui(self) } /// Link to a web page. diff --git a/crates/egui/src/widgets/hyperlink.rs b/crates/egui/src/widgets/hyperlink.rs index 3e5ff88dc..42ba479df 100644 --- a/crates/egui/src/widgets/hyperlink.rs +++ b/crates/egui/src/widgets/hyperlink.rs @@ -1,6 +1,6 @@ use crate::{ - epaint, text_selection, CursorIcon, Label, Response, Sense, Stroke, Ui, Widget, WidgetInfo, - WidgetText, WidgetType, + epaint, text_selection, AtomLayout, CursorIcon, IntoAtoms, Response, Sense, Stroke, Ui, Widget, + WidgetInfo, WidgetText, WidgetType, }; use self::text_selection::LabelSelectionState; @@ -24,27 +24,38 @@ use self::text_selection::LabelSelectionState; /// # }); /// ``` #[must_use = "You should put this widget in a ui with `ui.add(widget);`"] -pub struct Link { - text: WidgetText, +pub struct Link<'a> { + layout: AtomLayout<'a>, } -impl Link { - pub fn new(text: impl Into) -> Self { - Self { text: text.into() } +impl<'a> Link<'a> { + pub fn new(atoms: impl IntoAtoms<'a>) -> Self { + Self { + layout: AtomLayout::new(atoms).sense(Sense::click()), + } } } -impl Widget for Link { +impl Widget for Link<'_> { fn ui(self, ui: &mut Ui) -> Response { - let Self { text } = self; - let label = Label::new(text).sense(Sense::click()); + let Self { layout } = self; - let (galley_pos, galley, response) = label.layout_in_ui(ui); - response - .widget_info(|| WidgetInfo::labeled(WidgetType::Link, ui.is_enabled(), galley.text())); + let color = ui.visuals().hyperlink_color; + let text = layout.atoms.text().map(String::from); + let layout_with_color = layout.fallback_text_color(color); + + let allocated = layout_with_color.allocate(ui); + let response = allocated.response.clone(); + + response.widget_info(|| { + WidgetInfo::labeled( + WidgetType::Link, + ui.is_enabled(), + text.as_deref().unwrap_or(""), + ) + }); 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() { @@ -54,14 +65,25 @@ impl Widget for Link { }; let selectable = ui.style().interaction.selectable_labels; - if selectable { - LabelSelectionState::label_text_selection( - ui, &response, galley_pos, galley, color, underline, - ); - } else { - ui.painter().add( - epaint::TextShape::new(galley_pos, galley, color).with_underline(underline), - ); + + for galley in allocated.iter_texts() { + let galley_pos = response.rect.min; + + if selectable { + LabelSelectionState::label_text_selection( + ui, + &response, + galley_pos, + galley.clone(), + color, + underline, + ); + } else { + ui.painter().add( + epaint::TextShape::new(galley_pos, galley.clone(), color) + .with_underline(underline), + ); + } } if response.hovered() { diff --git a/crates/egui_demo_lib/src/demo/widget_gallery.rs b/crates/egui_demo_lib/src/demo/widget_gallery.rs index 31f5d279a..cab32c6d6 100644 --- a/crates/egui_demo_lib/src/demo/widget_gallery.rs +++ b/crates/egui_demo_lib/src/demo/widget_gallery.rs @@ -168,7 +168,8 @@ impl WidgetGallery { ui.end_row(); ui.add(doc_link_label("Link", "link")); - if ui.link("Click me!").clicked() { + let egui_icon = egui::include_image!("../../data/icon.png"); + if ui.link((egui_icon, "Click me!")).clicked() { *boolean = !*boolean; } ui.end_row();