1use egui::{FontId, Response, Sense, Stroke, Ui, Widget};
13use egui_components_theme::Theme;
14
15use crate::common::Size;
16
17pub struct Link {
18 text: String,
19 url: Option<String>,
20 size: Size,
21 underline: UnderlineMode,
22}
23
24#[derive(Clone, Copy, PartialEq, Eq)]
25enum UnderlineMode {
26 OnHover,
27 Always,
28 Never,
29}
30
31impl Link {
32 pub fn new(text: impl Into<String>) -> Self {
33 Self {
34 text: text.into(),
35 url: None,
36 size: Size::Medium,
37 underline: UnderlineMode::OnHover,
38 }
39 }
40 pub fn url(mut self, url: impl Into<String>) -> Self {
41 self.url = Some(url.into());
42 self
43 }
44 pub fn size(mut self, s: Size) -> Self {
45 self.size = s;
46 self
47 }
48 pub fn underline(mut self) -> Self {
49 self.underline = UnderlineMode::Always;
50 self
51 }
52 pub fn no_underline(mut self) -> Self {
53 self.underline = UnderlineMode::Never;
54 self
55 }
56}
57
58impl Widget for Link {
59 fn ui(self, ui: &mut Ui) -> Response {
60 let theme = Theme::get(ui.ctx());
61 let c = theme.colors;
62 let font = FontId::proportional(self.size.font_size(&theme.metrics));
63
64 let galley = ui.ctx().fonts_mut(|f| {
65 f.layout_no_wrap(self.text.clone(), font, c.link_foreground)
66 });
67 let (rect, response) = ui.allocate_exact_size(galley.size(), Sense::click());
68
69 if ui.is_rect_visible(rect) {
70 let color = if response.is_pointer_button_down_on() {
71 c.link_active_foreground
72 } else if response.hovered() {
73 c.link_hover_foreground
74 } else {
75 c.link_foreground
76 };
77 let painter = ui.painter();
78 painter.galley_with_override_text_color(rect.min, galley.clone(), color);
79
80 let underline = match self.underline {
81 UnderlineMode::Always => true,
82 UnderlineMode::OnHover => response.hovered(),
83 UnderlineMode::Never => false,
84 };
85 if underline {
86 let y = rect.bottom() - 1.0;
87 painter.line_segment(
88 [egui::pos2(rect.left(), y), egui::pos2(rect.right(), y)],
89 Stroke::new(1.0, color),
90 );
91 }
92 if response.hovered() {
93 ui.ctx().set_cursor_icon(egui::CursorIcon::PointingHand);
94 }
95 }
96
97 if response.clicked() {
98 if let Some(url) = &self.url {
99 ui.ctx().open_url(egui::OpenUrl::new_tab(url));
100 }
101 }
102
103 response
104 }
105}