agg_gui/widgets/
hyperlink.rs1use std::sync::Arc;
8
9use crate::draw_ctx::DrawCtx;
10use crate::event::{Event, EventResult, MouseButton};
11use crate::geometry::{Rect, Size};
12use crate::layout_props::{HAnchor, Insets, VAnchor, WidgetBase};
13use crate::text::{measure_text_metrics, Font};
14use crate::widget::Widget;
15
16pub struct Hyperlink {
21 bounds: Rect,
22 children: Vec<Box<dyn Widget>>,
23 base: WidgetBase,
24
25 text: String,
26 font: Arc<Font>,
27 font_size: f64,
28
29 hovered: bool,
30 on_click: Option<Box<dyn FnMut()>>,
31
32 cache: crate::widget::BackbufferCache,
33 last_sig: Option<(bool, u64, u64)>, }
35
36impl Hyperlink {
37 pub fn new(text: impl Into<String>, font: Arc<Font>) -> Self {
38 Self {
39 bounds: Rect::default(),
40 children: Vec::new(),
41 base: WidgetBase::new(),
42 text: text.into(),
43 font,
44 font_size: 14.0,
45 hovered: false,
46 on_click: None,
47 cache: crate::widget::BackbufferCache::default(),
48 last_sig: None,
49 }
50 }
51
52 pub fn with_font_size(mut self, size: f64) -> Self {
53 self.font_size = size;
54 self
55 }
56 pub fn on_click(mut self, cb: impl FnMut() + 'static) -> Self {
57 self.on_click = Some(Box::new(cb));
58 self
59 }
60
61 pub fn with_margin(mut self, m: Insets) -> Self {
62 self.base.margin = m;
63 self
64 }
65 pub fn with_h_anchor(mut self, h: HAnchor) -> Self {
66 self.base.h_anchor = h;
67 self
68 }
69 pub fn with_v_anchor(mut self, v: VAnchor) -> Self {
70 self.base.v_anchor = v;
71 self
72 }
73 pub fn with_min_size(mut self, s: Size) -> Self {
74 self.base.min_size = s;
75 self
76 }
77 pub fn with_max_size(mut self, s: Size) -> Self {
78 self.base.max_size = s;
79 self
80 }
81}
82
83impl Widget for Hyperlink {
84 fn type_name(&self) -> &'static str {
85 "Hyperlink"
86 }
87 fn bounds(&self) -> Rect {
88 self.bounds
89 }
90 fn set_bounds(&mut self, b: Rect) {
91 self.bounds = b;
92 }
93 fn children(&self) -> &[Box<dyn Widget>] {
94 &self.children
95 }
96 fn children_mut(&mut self) -> &mut Vec<Box<dyn Widget>> {
97 &mut self.children
98 }
99
100 fn margin(&self) -> Insets {
101 self.base.margin
102 }
103 fn widget_base(&self) -> Option<&WidgetBase> {
104 Some(&self.base)
105 }
106 fn widget_base_mut(&mut self) -> Option<&mut WidgetBase> {
107 Some(&mut self.base)
108 }
109 fn h_anchor(&self) -> HAnchor {
110 self.base.h_anchor
111 }
112 fn v_anchor(&self) -> VAnchor {
113 self.base.v_anchor
114 }
115 fn min_size(&self) -> Size {
116 self.base.min_size
117 }
118 fn max_size(&self) -> Size {
119 self.base.max_size
120 }
121
122 fn is_focusable(&self) -> bool {
123 true
124 }
125
126 fn backbuffer_cache_mut(&mut self) -> Option<&mut crate::widget::BackbufferCache> {
127 Some(&mut self.cache)
128 }
129
130 fn backbuffer_mode(&self) -> crate::widget::BackbufferMode {
131 if crate::font_settings::lcd_enabled() {
132 crate::widget::BackbufferMode::LcdCoverage
133 } else {
134 crate::widget::BackbufferMode::Rgba
135 }
136 }
137
138 fn layout(&mut self, _available: Size) -> Size {
139 let sig = (
140 self.hovered,
141 self.bounds.width.to_bits(),
142 self.bounds.height.to_bits(),
143 );
144 if self.last_sig != Some(sig) {
145 self.last_sig = Some(sig);
146 self.cache.invalidate();
147 }
148 let h = self.font_size * 1.5;
149 let w = measure_text_metrics(&self.font, &self.text, self.font_size).width;
150 Size::new(w, h)
151 }
152
153 fn paint(&mut self, ctx: &mut dyn DrawCtx) {
154 let v = ctx.visuals();
155 let color = if self.hovered {
156 v.text_link_hovered
157 } else {
158 v.text_link
159 };
160 ctx.set_font(Arc::clone(&self.font));
161 ctx.set_font_size(self.font_size);
162 ctx.set_fill_color(color);
163
164 let h = self.bounds.height;
165 let m = ctx.measure_text(&self.text).unwrap_or_default();
166 let ty = h * 0.5 - (m.ascent - m.descent) * 0.5;
167 ctx.fill_text(&self.text, 0.0, ty);
168
169 let uw = m.width;
171 let uy = ty - m.descent - 1.0; ctx.set_stroke_color(color);
173 ctx.set_line_width(1.0);
174 ctx.begin_path();
175 ctx.move_to(0.0, uy);
176 ctx.line_to(uw, uy);
177 ctx.stroke();
178 }
179
180 fn on_event(&mut self, event: &Event) -> EventResult {
181 match event {
182 Event::MouseMove { pos } => {
183 let was = self.hovered;
184 self.hovered = self.hit_test(*pos);
185 if was != self.hovered {
186 crate::animation::request_draw();
187 return EventResult::Consumed;
188 }
189 EventResult::Ignored
190 }
191 Event::MouseDown {
192 button: MouseButton::Left,
193 ..
194 } => EventResult::Consumed,
195 Event::MouseUp {
196 button: MouseButton::Left,
197 pos,
198 ..
199 } => {
200 if self.hit_test(*pos) {
201 if let Some(cb) = self.on_click.as_mut() {
202 cb();
203 }
204 crate::animation::request_draw();
206 }
207 EventResult::Consumed
208 }
209 _ => EventResult::Ignored,
210 }
211 }
212}