agg_gui/widgets/text_field/
widget_impl.rs1use super::*;
2
3impl Widget for TextField {
4 fn type_name(&self) -> &'static str {
5 "TextField"
6 }
7 fn bounds(&self) -> Rect {
8 self.bounds
9 }
10 fn set_bounds(&mut self, b: Rect) {
11 self.bounds = b;
12 }
13 fn children(&self) -> &[Box<dyn Widget>] {
14 &self.children
15 }
16 fn children_mut(&mut self) -> &mut Vec<Box<dyn Widget>> {
17 &mut self.children
18 }
19 fn is_focusable(&self) -> bool {
20 true
21 }
22
23 fn needs_draw(&self) -> bool {
34 if !self.focused {
35 return false;
36 }
37 let Some(t) = self.focus_time else {
38 return false;
39 };
40 let current_phase = (t.elapsed().as_millis() / 500) as u64;
41 current_phase != self.blink_last_phase.get()
42 }
43
44 fn next_draw_deadline(&self) -> Option<web_time::Instant> {
45 if !self.focused {
46 return None;
47 }
48 let t = self.focus_time?;
49 let ms = t.elapsed().as_millis() as u64;
50 let next_phase = (ms / 500) + 1;
51 Some(t + std::time::Duration::from_millis(next_phase * 500))
52 }
53
54 fn margin(&self) -> Insets {
55 self.base.margin
56 }
57 fn h_anchor(&self) -> HAnchor {
58 self.base.h_anchor
59 }
60 fn v_anchor(&self) -> VAnchor {
61 self.base.v_anchor
62 }
63 fn min_size(&self) -> Size {
64 self.base.min_size
65 }
66 fn max_size(&self) -> Size {
67 self.base.max_size
68 }
69
70 fn backbuffer_cache_mut(&mut self) -> Option<&mut BackbufferCache> {
71 Some(&mut self.cache)
72 }
73
74 fn backbuffer_mode(&self) -> BackbufferMode {
75 if crate::font_settings::lcd_enabled() {
76 BackbufferMode::LcdCoverage
77 } else {
78 BackbufferMode::Rgba
79 }
80 }
81
82 fn layout(&mut self, available: Size) -> Size {
83 self.sync_from_text_cell();
84 let st = self.edit.borrow();
88 let font = self.active_font();
89 let sig = TextFieldSig {
90 text: st.text.clone(),
91 cursor: st.cursor,
92 anchor: st.anchor,
93 focused: self.focused,
94 hovered: self.hovered,
95 scroll_x_bits: self.scroll_x.to_bits(),
96 w_bits: self.bounds.width.to_bits(),
97 h_bits: self.bounds.height.to_bits(),
98 font_ptr: Arc::as_ptr(&font) as usize,
99 font_size_bits: self.font_size.to_bits(),
100 };
101 drop(st);
102 if self.last_sig.as_ref() != Some(&sig) {
103 self.last_sig = Some(sig);
104 self.cache.invalidate();
105 }
106 Size::new(available.width, (self.font_size * 2.4).max(28.0))
107 }
108
109 fn paint(&mut self, ctx: &mut dyn DrawCtx) {
110 let w = self.bounds.width;
111 let h = self.bounds.height;
112 let r = 6.0;
113 let pad = self.padding;
114 let (raw_text, raw_cursor, raw_anchor) = {
115 let st = self.edit.borrow();
116 (st.text.clone(), st.cursor, st.anchor)
117 };
118 let (text, cursor, anchor) = if self.password_mode {
121 const BULLET: char = '•';
122 const BULLET_LEN: usize = 3; let n = raw_text.chars().count();
124 let masked = BULLET.to_string().repeat(n);
125 let cur = raw_text[..raw_cursor].chars().count() * BULLET_LEN;
126 let anc = raw_text[..raw_anchor].chars().count() * BULLET_LEN;
127 (masked, cur, anc)
128 } else {
129 (raw_text, raw_cursor, raw_anchor)
130 };
131
132 let v = ctx.visuals();
133
134 ctx.set_fill_color(v.widget_bg);
136 ctx.begin_path();
137 ctx.rounded_rect(0.0, 0.0, w, h, r);
138 ctx.fill();
139
140 ctx.clip_rect(pad, 0.0, (w - pad * 2.0).max(0.0), h);
142
143 let font = self.active_font();
144 ctx.set_font(Arc::clone(&font));
145 ctx.set_font_size(self.font_size);
146
147 let m = ctx.measure_text("Ag").unwrap_or_default();
148 let baseline_y = h * 0.5 - (m.ascent - m.descent) * 0.5;
149 let text_x = pad - self.scroll_x;
150
151 if cursor != anchor {
153 let lo = cursor.min(anchor);
154 let hi = cursor.max(anchor);
155 let lo_x = measure_advance(&font, &text[..lo], self.font_size);
156 let hi_x = measure_advance(&font, &text[..hi], self.font_size);
157 let sx = (text_x + lo_x).max(pad);
158 let sw = (text_x + hi_x).min(w - pad) - sx;
159 if sw > 0.0 {
160 let hl_bot = baseline_y - m.descent;
161 let hl_h = (m.ascent + m.descent) * 1.2;
162 ctx.set_fill_color(if self.focused {
163 v.selection_bg
164 } else {
165 v.selection_bg_unfocused
166 });
167 ctx.begin_path();
168 ctx.rect(sx, hl_bot - hl_h * 0.1, sw, hl_h);
169 ctx.fill();
170 }
171 }
172
173 if text.is_empty() && !self.focused {
175 ctx.set_fill_color(v.text_dim);
176 ctx.fill_text(&self.placeholder, text_x, baseline_y);
177 } else {
178 ctx.set_fill_color(v.text_color);
179 ctx.fill_text(&text, text_x, baseline_y);
180 }
181
182 ctx.reset_clip();
186
187 let border_color = if self.focused {
189 v.accent
190 } else if self.hovered {
191 v.widget_stroke_active
192 } else {
193 v.widget_stroke
194 };
195 ctx.set_stroke_color(border_color);
196 ctx.set_line_width(if self.focused { 2.0 } else { 1.0 });
197 ctx.begin_path();
198 ctx.rounded_rect(0.0, 0.0, w, h, r);
199 ctx.stroke();
200 }
201
202 fn paint_overlay(&mut self, ctx: &mut dyn DrawCtx) {
207 if self.focused {
212 if let Some(t) = self.focus_time {
213 let phase = (t.elapsed().as_millis() / 500) as u64;
214 self.blink_last_phase.set(phase);
215 }
216 }
217
218 let cursor_visible = self.focused
219 && {
220 let st = self.edit.borrow();
221 st.cursor == st.anchor
222 }
223 && match self.focus_time {
224 Some(t) => (t.elapsed().as_millis() / 500) % 2 == 0,
225 None => false,
226 };
227 if !cursor_visible {
228 return;
229 }
230
231 let (text, cursor) = {
232 let st = self.edit.borrow();
233 let text = if self.password_mode {
234 const BULLET: char = '•';
235 let n = st.text.chars().count();
236 BULLET.to_string().repeat(n)
237 } else {
238 st.text.clone()
239 };
240 let cursor = if self.password_mode {
241 const BULLET_LEN: usize = 3;
242 st.text[..st.cursor].chars().count() * BULLET_LEN
243 } else {
244 st.cursor
245 };
246 (text, cursor)
247 };
248
249 let h = self.bounds.height;
250 let pad = self.padding;
251 let v = ctx.visuals();
252
253 let font = self.active_font();
254 ctx.set_font(Arc::clone(&font));
255 ctx.set_font_size(self.font_size);
256 let m = ctx.measure_text("Ag").unwrap_or_default();
257 let baseline_y = h * 0.5 - (m.ascent - m.descent) * 0.5;
258 let text_x = pad - self.scroll_x;
259 let cx = text_x + measure_advance(&font, &text[..cursor], self.font_size);
260 let top = baseline_y + m.ascent;
261 let bot = baseline_y - m.descent;
262
263 ctx.save();
266 ctx.clip_rect(pad, 0.0, (self.bounds.width - pad * 2.0).max(0.0), h);
267 ctx.set_stroke_color(v.accent);
268 ctx.set_line_width(1.5);
269 ctx.begin_path();
270 ctx.move_to(cx, bot);
271 ctx.line_to(cx, top);
272 ctx.stroke();
273 ctx.restore();
274 }
275
276 fn on_event(&mut self, event: &Event) -> EventResult {
277 match event {
278 Event::MouseMove { pos } => {
279 let was = self.hovered;
280 self.hovered = self.hit_test(*pos);
281 if self.mouse_down && self.focused {
282 let tx = pos.x - self.padding + self.scroll_x;
283 let text = self.edit.borrow().text.clone();
284 let new_cur = self.click_to_cursor(&text, tx);
285 self.edit.borrow_mut().cursor = new_cur;
286 crate::animation::request_draw();
287 return EventResult::Consumed;
288 }
289 if was != self.hovered {
290 crate::animation::request_draw();
291 return EventResult::Consumed;
292 }
293 EventResult::Ignored
294 }
295
296 Event::MouseDown {
297 pos,
298 button: MouseButton::Left,
299 modifiers: mods,
300 } => {
301 self.mouse_down = true;
302 let tx = pos.x - self.padding + self.scroll_x;
303 let text = self.edit.borrow().text.clone();
304 let new_cur = self.click_to_cursor(&text, tx);
305
306 let is_double = self
308 .last_click_time
309 .map(|t| t.elapsed().as_millis() < 350)
310 .unwrap_or(false);
311 self.last_click_time = Some(Instant::now());
312
313 if is_double && !mods.shift {
314 let (ws, we) = word_range_at(&text, new_cur);
315 self.edit.borrow_mut().anchor = ws;
316 self.edit.borrow_mut().cursor = we;
317 } else if mods.shift {
318 self.edit.borrow_mut().cursor = new_cur;
319 } else {
320 self.edit.borrow_mut().cursor = new_cur;
321 self.edit.borrow_mut().anchor = new_cur;
322 }
323 self.focus_time = Some(Instant::now());
325 crate::animation::request_draw();
326 EventResult::Consumed
327 }
328
329 Event::MouseUp {
330 button: MouseButton::Left,
331 ..
332 } => {
333 self.mouse_down = false;
334 EventResult::Ignored
335 }
336
337 Event::FocusGained => {
338 self.focused = true;
339 self.focus_time = Some(Instant::now());
340 self.text_on_focus = self.text();
341 if self.select_all_on_focus {
342 let len = self.edit.borrow().text.len();
343 self.edit.borrow_mut().anchor = 0;
344 self.edit.borrow_mut().cursor = len;
345 }
346 crate::animation::request_draw();
347 EventResult::Ignored
348 }
349
350 Event::FocusLost => {
351 let was_focused = self.focused;
352 self.focused = false;
353 self.focus_time = None;
354 self.mouse_down = false;
355 self.flush_pending();
356 if self.text() != self.text_on_focus {
357 self.notify_edit_complete();
358 }
359 if was_focused {
360 crate::animation::request_draw();
361 }
362 EventResult::Ignored
363 }
364
365 Event::KeyDown { key, modifiers } if self.focused => {
366 self.focus_time = Some(Instant::now());
368 let result = self.handle_key(key, *modifiers);
369 if result == EventResult::Consumed {
372 crate::animation::request_draw();
373 }
374 result
375 }
376
377 _ => EventResult::Ignored,
378 }
379 }
380}