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 has_active_modal(&self) -> bool {
89 self.context_menu.is_open()
90 }
91
92 fn paint_global_overlay(&mut self, ctx: &mut dyn DrawCtx) {
95 let font = self.active_font();
96 self.context_menu.paint(ctx, font, self.font_size);
97 }
98
99 fn margin(&self) -> Insets {
100 self.base.margin
101 }
102 fn widget_base(&self) -> Option<&WidgetBase> {
103 Some(&self.base)
104 }
105 fn widget_base_mut(&mut self) -> Option<&mut WidgetBase> {
106 Some(&mut self.base)
107 }
108 fn h_anchor(&self) -> HAnchor {
109 self.base.h_anchor
110 }
111 fn v_anchor(&self) -> VAnchor {
112 self.base.v_anchor
113 }
114 fn min_size(&self) -> Size {
115 self.base.min_size
116 }
117 fn max_size(&self) -> Size {
118 self.base.max_size
119 }
120
121 fn backbuffer_cache_mut(&mut self) -> Option<&mut BackbufferCache> {
122 Some(&mut self.cache)
123 }
124
125 fn backbuffer_mode(&self) -> BackbufferMode {
126 if crate::font_settings::lcd_enabled() {
127 BackbufferMode::LcdCoverage
128 } else {
129 BackbufferMode::Rgba
130 }
131 }
132
133 fn layout(&mut self, available: Size) -> Size {
134 self.sync_from_text_cell();
135 let st = self.edit.borrow();
139 let font = self.active_font();
140 let sig = TextFieldSig {
141 text: st.text.clone(),
142 cursor: st.cursor,
143 anchor: st.anchor,
144 focused: self.focused,
145 hovered: self.hovered,
146 scroll_x_bits: self.scroll_x.to_bits(),
147 w_bits: self.bounds.width.to_bits(),
148 h_bits: self.bounds.height.to_bits(),
149 font_ptr: Arc::as_ptr(&font) as usize,
150 font_size_bits: self.font_size.to_bits(),
151 masking: self.masking_active(),
152 };
153 drop(st);
154 if self.last_sig.as_ref() != Some(&sig) {
155 self.last_sig = Some(sig);
156 self.cache.invalidate();
157 }
158 Size::new(available.width, (self.font_size * 2.4).max(28.0))
159 }
160
161 fn paint(&mut self, ctx: &mut dyn DrawCtx) {
162 let w = self.bounds.width;
163 let h = self.bounds.height;
164 let r = self.theme.border_radius.unwrap_or(6.0);
165 let pad = self.padding;
166 let (raw_text, raw_cursor, raw_anchor) = {
167 let st = self.edit.borrow();
168 (st.text.clone(), st.cursor, st.anchor)
169 };
170 let (text, cursor, anchor) = if self.masking_active() {
173 const BULLET: char = '•';
174 const BULLET_LEN: usize = 3; let n = raw_text.chars().count();
176 let masked = BULLET.to_string().repeat(n);
177 let cur = raw_text[..raw_cursor].chars().count() * BULLET_LEN;
178 let anc = raw_text[..raw_anchor].chars().count() * BULLET_LEN;
179 (masked, cur, anc)
180 } else {
181 (raw_text, raw_cursor, raw_anchor)
182 };
183
184 let v = ctx.visuals();
185 let t = &self.theme;
186
187 ctx.set_fill_color(t.background.unwrap_or(v.widget_bg));
189 ctx.begin_path();
190 ctx.rounded_rect(0.0, 0.0, w, h, r);
191 ctx.fill();
192
193 ctx.clip_rect(pad, 0.0, (w - pad * 2.0).max(0.0), h);
195
196 let font = self.active_font();
197 ctx.set_font(Arc::clone(&font));
198 ctx.set_font_size(self.font_size);
199
200 let m = ctx.measure_text("Ag").unwrap_or_default();
201 let baseline_y = h * 0.5 - (m.ascent - m.descent) * 0.5;
202 let text_x = pad - self.scroll_x;
203
204 if cursor != anchor {
206 let lo = cursor.min(anchor);
207 let hi = cursor.max(anchor);
208 let lo_x = measure_advance(&font, &text[..lo], self.font_size);
209 let hi_x = measure_advance(&font, &text[..hi], self.font_size);
210 let sx = (text_x + lo_x).max(pad);
211 let sw = (text_x + hi_x).min(w - pad) - sx;
212 if sw > 0.0 {
213 let hl_bot = baseline_y - m.descent;
214 let hl_h = (m.ascent + m.descent) * 1.2;
215 ctx.set_fill_color(if self.focused {
216 t.selection_bg.unwrap_or(v.selection_bg)
217 } else {
218 t.selection_bg_unfocused.unwrap_or(v.selection_bg_unfocused)
219 });
220 ctx.begin_path();
221 ctx.rect(sx, hl_bot - hl_h * 0.1, sw, hl_h);
222 ctx.fill();
223 }
224 }
225
226 if text.is_empty() && !self.focused {
228 ctx.set_fill_color(t.placeholder_color.unwrap_or(v.text_dim));
229 ctx.fill_text(&self.placeholder, text_x, baseline_y);
230 } else {
231 ctx.set_fill_color(t.text_color.unwrap_or(v.text_color));
232 ctx.fill_text(&text, text_x, baseline_y);
233 }
234
235 ctx.reset_clip();
239
240 let border_color = if self.focused {
242 t.border_color_focused.unwrap_or(v.accent)
243 } else if self.hovered {
244 t.border_color_hovered.unwrap_or(v.widget_stroke_active)
245 } else {
246 t.border_color.unwrap_or(v.widget_stroke)
247 };
248 ctx.set_stroke_color(border_color);
249 let border_w = if self.focused { 2.0 } else { 1.0 };
250 ctx.set_line_width(border_w);
251 ctx.begin_path();
252 let inset = border_w * 0.5;
253 ctx.rounded_rect(
254 inset,
255 inset,
256 (w - border_w).max(0.0),
257 (h - border_w).max(0.0),
258 r,
259 );
260 ctx.stroke();
261 }
262
263 fn paint_overlay(&mut self, ctx: &mut dyn DrawCtx) {
268 if self.focused {
273 if let Some(t) = self.focus_time {
274 let phase = (t.elapsed().as_millis() / 500) as u64;
275 self.blink_last_phase.set(phase);
276 }
277 }
278
279 let cursor_visible = self.focused
280 && {
281 let st = self.edit.borrow();
282 st.cursor == st.anchor
283 }
284 && match self.focus_time {
285 Some(t) => (t.elapsed().as_millis() / 500) % 2 == 0,
286 None => false,
287 };
288 if !cursor_visible {
289 return;
290 }
291
292 let (text, cursor) = {
293 let st = self.edit.borrow();
294 let masking = self.masking_active();
295 let text = if masking {
296 const BULLET: char = '•';
297 let n = st.text.chars().count();
298 BULLET.to_string().repeat(n)
299 } else {
300 st.text.clone()
301 };
302 let cursor = if masking {
303 const BULLET_LEN: usize = 3;
304 st.text[..st.cursor].chars().count() * BULLET_LEN
305 } else {
306 st.cursor
307 };
308 (text, cursor)
309 };
310
311 let h = self.bounds.height;
312 let pad = self.padding;
313 let v = ctx.visuals();
314
315 let font = self.active_font();
316 ctx.set_font(Arc::clone(&font));
317 ctx.set_font_size(self.font_size);
318 let m = ctx.measure_text("Ag").unwrap_or_default();
319 let baseline_y = h * 0.5 - (m.ascent - m.descent) * 0.5;
320 let text_x = pad - self.scroll_x;
321 let cx = text_x + measure_advance(&font, &text[..cursor], self.font_size);
322 let top = baseline_y + m.ascent;
323 let bot = baseline_y - m.descent;
324
325 ctx.save();
328 ctx.clip_rect(pad, 0.0, (self.bounds.width - pad * 2.0).max(0.0), h);
329 ctx.set_stroke_color(self.theme.cursor_color.unwrap_or(v.accent));
330 ctx.set_line_width(1.5);
331 ctx.begin_path();
332 ctx.move_to(cx, bot);
333 ctx.line_to(cx, top);
334 ctx.stroke();
335 ctx.restore();
336 }
337
338 fn on_event(&mut self, event: &Event) -> EventResult {
339 if let Some(result) = self.route_context_menu(event) {
342 return result;
343 }
344 match event {
345 Event::MouseMove { pos } => {
346 let was = self.hovered;
347 self.hovered = self.hit_test(*pos);
348 if self.mouse_down && self.focused {
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 self.extend_selection_drag(&text, new_cur);
353 crate::animation::request_draw();
354 return EventResult::Consumed;
355 }
356 if was != self.hovered {
357 crate::animation::request_draw();
358 return EventResult::Consumed;
359 }
360 EventResult::Ignored
361 }
362
363 Event::MouseDown {
364 pos,
365 button: MouseButton::Left,
366 modifiers: mods,
367 } => {
368 self.mouse_down = true;
369 let tx = pos.x - self.padding + self.scroll_x;
370 let text = self.edit.borrow().text.clone();
371 let new_cur = self.click_to_cursor(&text, tx);
372
373 let clicks = self.multi_click.register(*pos);
378 use crate::widgets::multi_click::SelectGranularity;
379
380 if mods.shift {
381 self.select_granularity = SelectGranularity::Char;
382 self.select_pivot = (new_cur, new_cur);
383 self.edit.borrow_mut().cursor = new_cur;
384 } else if clicks >= 3 {
385 self.select_granularity = SelectGranularity::Line;
386 let len = text.len();
387 self.select_pivot = (0, len);
388 self.edit.borrow_mut().anchor = 0;
389 self.edit.borrow_mut().cursor = len;
390 } else if clicks == 2 {
391 self.select_granularity = SelectGranularity::Word;
392 let (ws, we) = word_range_at(&text, new_cur);
393 self.select_pivot = (ws, we);
394 self.edit.borrow_mut().anchor = ws;
395 self.edit.borrow_mut().cursor = we;
396 } else {
397 self.select_granularity = SelectGranularity::Char;
398 self.select_pivot = (new_cur, new_cur);
399 self.edit.borrow_mut().cursor = new_cur;
400 self.edit.borrow_mut().anchor = new_cur;
401 }
402 self.focus_time = Some(Instant::now());
404 crate::animation::request_draw();
405 EventResult::Consumed
406 }
407
408 Event::MouseDown {
409 pos,
410 button: MouseButton::Right,
411 ..
412 } => {
413 if self.context_menu_enabled {
414 self.open_context_menu(*pos);
415 EventResult::Consumed
416 } else {
417 EventResult::Ignored
418 }
419 }
420
421 Event::MouseUp {
422 button: MouseButton::Left,
423 ..
424 } => {
425 self.mouse_down = false;
426 EventResult::Ignored
427 }
428
429 Event::FocusGained => {
430 self.focused = true;
431 self.focus_time = Some(Instant::now());
432 self.text_on_focus = self.text();
433 if self.select_all_on_focus {
434 let len = self.edit.borrow().text.len();
435 self.edit.borrow_mut().anchor = 0;
436 self.edit.borrow_mut().cursor = len;
437 }
438 crate::animation::request_draw();
439 EventResult::Ignored
440 }
441
442 Event::FocusLost => {
443 let was_focused = self.focused;
444 self.focused = false;
445 self.focus_time = None;
446 self.mouse_down = false;
447 self.flush_pending();
448 if self.text() != self.text_on_focus {
449 self.notify_edit_complete();
450 }
451 if was_focused {
452 crate::animation::request_draw();
453 }
454 EventResult::Ignored
455 }
456
457 Event::KeyDown { key, modifiers } if self.focused => {
458 self.focus_time = Some(Instant::now());
460 let result = self.handle_key(key, *modifiers);
461 if result.is_consumed() {
464 crate::animation::request_draw();
465 }
466 result
467 }
468
469 _ => EventResult::Ignored,
470 }
471 }
472}