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 focus_id(&self) -> Option<crate::focus::FocusId> {
24 self.focus_request_id
25 }
26
27 fn accepts_text_input(&self) -> bool {
28 !self.read_only
29 }
30
31 fn text_input_value(&self) -> Option<String> {
32 Some(self.text())
33 }
34
35 fn text_input_mode(&self) -> crate::widgets::on_screen_keyboard::KeyboardInputMode {
36 self.keyboard_mode()
37 }
38
39 fn set_label_text(&mut self, text: &str) {
46 if self.focused {
47 return;
48 }
49 if self.edit.borrow().text == text {
50 return;
51 }
52 self.set_text(text);
53 }
54
55 fn needs_draw(&self) -> bool {
66 if !self.focused {
67 return false;
68 }
69 let Some(t) = self.focus_time else {
70 return false;
71 };
72 let current_phase = (t.elapsed().as_millis() / 500) as u64;
73 current_phase != self.blink_last_phase.get()
74 }
75
76 fn next_draw_deadline(&self) -> Option<web_time::Instant> {
77 if !self.focused {
78 return None;
79 }
80 let t = self.focus_time?;
81 let ms = t.elapsed().as_millis() as u64;
82 let next_phase = (ms / 500) + 1;
83 Some(t + std::time::Duration::from_millis(next_phase * 500))
84 }
85
86 fn margin(&self) -> Insets {
87 self.base.margin
88 }
89 fn widget_base(&self) -> Option<&WidgetBase> {
90 Some(&self.base)
91 }
92 fn widget_base_mut(&mut self) -> Option<&mut WidgetBase> {
93 Some(&mut self.base)
94 }
95 fn h_anchor(&self) -> HAnchor {
96 self.base.h_anchor
97 }
98 fn v_anchor(&self) -> VAnchor {
99 self.base.v_anchor
100 }
101 fn min_size(&self) -> Size {
102 self.base.min_size
103 }
104 fn max_size(&self) -> Size {
105 self.base.max_size
106 }
107
108 fn backbuffer_cache_mut(&mut self) -> Option<&mut BackbufferCache> {
109 Some(&mut self.cache)
110 }
111
112 fn backbuffer_mode(&self) -> BackbufferMode {
113 if crate::font_settings::lcd_enabled() {
114 BackbufferMode::LcdCoverage
115 } else {
116 BackbufferMode::Rgba
117 }
118 }
119
120 fn layout(&mut self, available: Size) -> Size {
121 self.sync_from_text_cell();
122 let st = self.edit.borrow();
126 let font = self.active_font();
127 let sig = TextFieldSig {
128 text: st.text.clone(),
129 cursor: st.cursor,
130 anchor: st.anchor,
131 focused: self.focused,
132 hovered: self.hovered,
133 scroll_x_bits: self.scroll_x.to_bits(),
134 w_bits: self.bounds.width.to_bits(),
135 h_bits: self.bounds.height.to_bits(),
136 font_ptr: Arc::as_ptr(&font) as usize,
137 font_size_bits: self.font_size.to_bits(),
138 };
139 drop(st);
140 if self.last_sig.as_ref() != Some(&sig) {
141 self.last_sig = Some(sig);
142 self.cache.invalidate();
143 }
144 Size::new(available.width, (self.font_size * 2.4).max(28.0))
145 }
146
147 fn paint(&mut self, ctx: &mut dyn DrawCtx) {
148 let w = self.bounds.width;
149 let h = self.bounds.height;
150 let r = self.theme.border_radius.unwrap_or(6.0);
151 let pad = self.padding;
152 let (raw_text, raw_cursor, raw_anchor) = {
153 let st = self.edit.borrow();
154 (st.text.clone(), st.cursor, st.anchor)
155 };
156 let (text, cursor, anchor) = if self.password_mode {
159 const BULLET: char = '•';
160 const BULLET_LEN: usize = 3; let n = raw_text.chars().count();
162 let masked = BULLET.to_string().repeat(n);
163 let cur = raw_text[..raw_cursor].chars().count() * BULLET_LEN;
164 let anc = raw_text[..raw_anchor].chars().count() * BULLET_LEN;
165 (masked, cur, anc)
166 } else {
167 (raw_text, raw_cursor, raw_anchor)
168 };
169
170 let v = ctx.visuals();
171 let t = &self.theme;
172
173 ctx.set_fill_color(t.background.unwrap_or(v.widget_bg));
175 ctx.begin_path();
176 ctx.rounded_rect(0.0, 0.0, w, h, r);
177 ctx.fill();
178
179 ctx.clip_rect(pad, 0.0, (w - pad * 2.0).max(0.0), h);
181
182 let font = self.active_font();
183 ctx.set_font(Arc::clone(&font));
184 ctx.set_font_size(self.font_size);
185
186 let m = ctx.measure_text("Ag").unwrap_or_default();
187 let baseline_y = h * 0.5 - (m.ascent - m.descent) * 0.5;
188 let text_x = pad - self.scroll_x;
189
190 if cursor != anchor {
192 let lo = cursor.min(anchor);
193 let hi = cursor.max(anchor);
194 let lo_x = measure_advance(&font, &text[..lo], self.font_size);
195 let hi_x = measure_advance(&font, &text[..hi], self.font_size);
196 let sx = (text_x + lo_x).max(pad);
197 let sw = (text_x + hi_x).min(w - pad) - sx;
198 if sw > 0.0 {
199 let hl_bot = baseline_y - m.descent;
200 let hl_h = (m.ascent + m.descent) * 1.2;
201 ctx.set_fill_color(if self.focused {
202 t.selection_bg.unwrap_or(v.selection_bg)
203 } else {
204 t.selection_bg_unfocused.unwrap_or(v.selection_bg_unfocused)
205 });
206 ctx.begin_path();
207 ctx.rect(sx, hl_bot - hl_h * 0.1, sw, hl_h);
208 ctx.fill();
209 }
210 }
211
212 if text.is_empty() && !self.focused {
214 ctx.set_fill_color(t.placeholder_color.unwrap_or(v.text_dim));
215 ctx.fill_text(&self.placeholder, text_x, baseline_y);
216 } else {
217 ctx.set_fill_color(t.text_color.unwrap_or(v.text_color));
218 ctx.fill_text(&text, text_x, baseline_y);
219 }
220
221 ctx.reset_clip();
225
226 let border_color = if self.focused {
228 t.border_color_focused.unwrap_or(v.accent)
229 } else if self.hovered {
230 t.border_color_hovered.unwrap_or(v.widget_stroke_active)
231 } else {
232 t.border_color.unwrap_or(v.widget_stroke)
233 };
234 ctx.set_stroke_color(border_color);
235 let border_w = if self.focused { 2.0 } else { 1.0 };
236 ctx.set_line_width(border_w);
237 ctx.begin_path();
238 let inset = border_w * 0.5;
239 ctx.rounded_rect(
240 inset,
241 inset,
242 (w - border_w).max(0.0),
243 (h - border_w).max(0.0),
244 r,
245 );
246 ctx.stroke();
247 }
248
249 fn paint_overlay(&mut self, ctx: &mut dyn DrawCtx) {
254 if self.focused {
259 if let Some(t) = self.focus_time {
260 let phase = (t.elapsed().as_millis() / 500) as u64;
261 self.blink_last_phase.set(phase);
262 }
263 }
264
265 let cursor_visible = self.focused
266 && {
267 let st = self.edit.borrow();
268 st.cursor == st.anchor
269 }
270 && match self.focus_time {
271 Some(t) => (t.elapsed().as_millis() / 500) % 2 == 0,
272 None => false,
273 };
274 if !cursor_visible {
275 return;
276 }
277
278 let (text, cursor) = {
279 let st = self.edit.borrow();
280 let text = if self.password_mode {
281 const BULLET: char = '•';
282 let n = st.text.chars().count();
283 BULLET.to_string().repeat(n)
284 } else {
285 st.text.clone()
286 };
287 let cursor = if self.password_mode {
288 const BULLET_LEN: usize = 3;
289 st.text[..st.cursor].chars().count() * BULLET_LEN
290 } else {
291 st.cursor
292 };
293 (text, cursor)
294 };
295
296 let h = self.bounds.height;
297 let pad = self.padding;
298 let v = ctx.visuals();
299
300 let font = self.active_font();
301 ctx.set_font(Arc::clone(&font));
302 ctx.set_font_size(self.font_size);
303 let m = ctx.measure_text("Ag").unwrap_or_default();
304 let baseline_y = h * 0.5 - (m.ascent - m.descent) * 0.5;
305 let text_x = pad - self.scroll_x;
306 let cx = text_x + measure_advance(&font, &text[..cursor], self.font_size);
307 let top = baseline_y + m.ascent;
308 let bot = baseline_y - m.descent;
309
310 ctx.save();
313 ctx.clip_rect(pad, 0.0, (self.bounds.width - pad * 2.0).max(0.0), h);
314 ctx.set_stroke_color(self.theme.cursor_color.unwrap_or(v.accent));
315 ctx.set_line_width(1.5);
316 ctx.begin_path();
317 ctx.move_to(cx, bot);
318 ctx.line_to(cx, top);
319 ctx.stroke();
320 ctx.restore();
321 }
322
323 fn on_event(&mut self, event: &Event) -> EventResult {
324 match event {
325 Event::MouseMove { pos } => {
326 let was = self.hovered;
327 self.hovered = self.hit_test(*pos);
328 if self.mouse_down && self.focused {
329 let tx = pos.x - self.padding + self.scroll_x;
330 let text = self.edit.borrow().text.clone();
331 let new_cur = self.click_to_cursor(&text, tx);
332 self.edit.borrow_mut().cursor = new_cur;
333 crate::animation::request_draw();
334 return EventResult::Consumed;
335 }
336 if was != self.hovered {
337 crate::animation::request_draw();
338 return EventResult::Consumed;
339 }
340 EventResult::Ignored
341 }
342
343 Event::MouseDown {
344 pos,
345 button: MouseButton::Left,
346 modifiers: mods,
347 } => {
348 self.mouse_down = true;
349 let tx = pos.x - self.padding + self.scroll_x;
350 let text = self.edit.borrow().text.clone();
351 let new_cur = self.click_to_cursor(&text, tx);
352
353 let is_double = self
355 .last_click_time
356 .map(|t| t.elapsed().as_millis() < 350)
357 .unwrap_or(false);
358 self.last_click_time = Some(Instant::now());
359
360 if is_double && !mods.shift {
361 let (ws, we) = word_range_at(&text, new_cur);
362 self.edit.borrow_mut().anchor = ws;
363 self.edit.borrow_mut().cursor = we;
364 } else if mods.shift {
365 self.edit.borrow_mut().cursor = new_cur;
366 } else {
367 self.edit.borrow_mut().cursor = new_cur;
368 self.edit.borrow_mut().anchor = new_cur;
369 }
370 self.focus_time = Some(Instant::now());
372 crate::animation::request_draw();
373 EventResult::Consumed
374 }
375
376 Event::MouseUp {
377 button: MouseButton::Left,
378 ..
379 } => {
380 self.mouse_down = false;
381 EventResult::Ignored
382 }
383
384 Event::FocusGained => {
385 self.focused = true;
386 self.focus_time = Some(Instant::now());
387 self.text_on_focus = self.text();
388 if self.select_all_on_focus {
389 let len = self.edit.borrow().text.len();
390 self.edit.borrow_mut().anchor = 0;
391 self.edit.borrow_mut().cursor = len;
392 }
393 crate::animation::request_draw();
394 EventResult::Ignored
395 }
396
397 Event::FocusLost => {
398 let was_focused = self.focused;
399 self.focused = false;
400 self.focus_time = None;
401 self.mouse_down = false;
402 self.flush_pending();
403 if self.text() != self.text_on_focus {
404 self.notify_edit_complete();
405 }
406 if was_focused {
407 crate::animation::request_draw();
408 }
409 EventResult::Ignored
410 }
411
412 Event::KeyDown { key, modifiers } if self.focused => {
413 self.focus_time = Some(Instant::now());
415 let result = self.handle_key(key, *modifiers);
416 if result == EventResult::Consumed {
419 crate::animation::request_draw();
420 }
421 result
422 }
423
424 _ => EventResult::Ignored,
425 }
426 }
427}