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 widget_base(&self) -> Option<&WidgetBase> {
58 Some(&self.base)
59 }
60 fn widget_base_mut(&mut self) -> Option<&mut WidgetBase> {
61 Some(&mut self.base)
62 }
63 fn h_anchor(&self) -> HAnchor {
64 self.base.h_anchor
65 }
66 fn v_anchor(&self) -> VAnchor {
67 self.base.v_anchor
68 }
69 fn min_size(&self) -> Size {
70 self.base.min_size
71 }
72 fn max_size(&self) -> Size {
73 self.base.max_size
74 }
75
76 fn backbuffer_cache_mut(&mut self) -> Option<&mut BackbufferCache> {
77 Some(&mut self.cache)
78 }
79
80 fn backbuffer_mode(&self) -> BackbufferMode {
81 if crate::font_settings::lcd_enabled() {
82 BackbufferMode::LcdCoverage
83 } else {
84 BackbufferMode::Rgba
85 }
86 }
87
88 fn layout(&mut self, available: Size) -> Size {
89 self.sync_from_text_cell();
90 let st = self.edit.borrow();
94 let font = self.active_font();
95 let sig = TextFieldSig {
96 text: st.text.clone(),
97 cursor: st.cursor,
98 anchor: st.anchor,
99 focused: self.focused,
100 hovered: self.hovered,
101 scroll_x_bits: self.scroll_x.to_bits(),
102 w_bits: self.bounds.width.to_bits(),
103 h_bits: self.bounds.height.to_bits(),
104 font_ptr: Arc::as_ptr(&font) as usize,
105 font_size_bits: self.font_size.to_bits(),
106 };
107 drop(st);
108 if self.last_sig.as_ref() != Some(&sig) {
109 self.last_sig = Some(sig);
110 self.cache.invalidate();
111 }
112 Size::new(available.width, (self.font_size * 2.4).max(28.0))
113 }
114
115 fn paint(&mut self, ctx: &mut dyn DrawCtx) {
116 let w = self.bounds.width;
117 let h = self.bounds.height;
118 let r = 6.0;
119 let pad = self.padding;
120 let (raw_text, raw_cursor, raw_anchor) = {
121 let st = self.edit.borrow();
122 (st.text.clone(), st.cursor, st.anchor)
123 };
124 let (text, cursor, anchor) = if self.password_mode {
127 const BULLET: char = '•';
128 const BULLET_LEN: usize = 3; let n = raw_text.chars().count();
130 let masked = BULLET.to_string().repeat(n);
131 let cur = raw_text[..raw_cursor].chars().count() * BULLET_LEN;
132 let anc = raw_text[..raw_anchor].chars().count() * BULLET_LEN;
133 (masked, cur, anc)
134 } else {
135 (raw_text, raw_cursor, raw_anchor)
136 };
137
138 let v = ctx.visuals();
139
140 ctx.set_fill_color(v.widget_bg);
142 ctx.begin_path();
143 ctx.rounded_rect(0.0, 0.0, w, h, r);
144 ctx.fill();
145
146 ctx.clip_rect(pad, 0.0, (w - pad * 2.0).max(0.0), h);
148
149 let font = self.active_font();
150 ctx.set_font(Arc::clone(&font));
151 ctx.set_font_size(self.font_size);
152
153 let m = ctx.measure_text("Ag").unwrap_or_default();
154 let baseline_y = h * 0.5 - (m.ascent - m.descent) * 0.5;
155 let text_x = pad - self.scroll_x;
156
157 if cursor != anchor {
159 let lo = cursor.min(anchor);
160 let hi = cursor.max(anchor);
161 let lo_x = measure_advance(&font, &text[..lo], self.font_size);
162 let hi_x = measure_advance(&font, &text[..hi], self.font_size);
163 let sx = (text_x + lo_x).max(pad);
164 let sw = (text_x + hi_x).min(w - pad) - sx;
165 if sw > 0.0 {
166 let hl_bot = baseline_y - m.descent;
167 let hl_h = (m.ascent + m.descent) * 1.2;
168 ctx.set_fill_color(if self.focused {
169 v.selection_bg
170 } else {
171 v.selection_bg_unfocused
172 });
173 ctx.begin_path();
174 ctx.rect(sx, hl_bot - hl_h * 0.1, sw, hl_h);
175 ctx.fill();
176 }
177 }
178
179 if text.is_empty() && !self.focused {
181 ctx.set_fill_color(v.text_dim);
182 ctx.fill_text(&self.placeholder, text_x, baseline_y);
183 } else {
184 ctx.set_fill_color(v.text_color);
185 ctx.fill_text(&text, text_x, baseline_y);
186 }
187
188 ctx.reset_clip();
192
193 let border_color = if self.focused {
195 v.accent
196 } else if self.hovered {
197 v.widget_stroke_active
198 } else {
199 v.widget_stroke
200 };
201 ctx.set_stroke_color(border_color);
202 ctx.set_line_width(if self.focused { 2.0 } else { 1.0 });
203 ctx.begin_path();
204 ctx.rounded_rect(0.0, 0.0, w, h, r);
205 ctx.stroke();
206 }
207
208 fn paint_overlay(&mut self, ctx: &mut dyn DrawCtx) {
213 if self.focused {
218 if let Some(t) = self.focus_time {
219 let phase = (t.elapsed().as_millis() / 500) as u64;
220 self.blink_last_phase.set(phase);
221 }
222 }
223
224 let cursor_visible = self.focused
225 && {
226 let st = self.edit.borrow();
227 st.cursor == st.anchor
228 }
229 && match self.focus_time {
230 Some(t) => (t.elapsed().as_millis() / 500) % 2 == 0,
231 None => false,
232 };
233 if !cursor_visible {
234 return;
235 }
236
237 let (text, cursor) = {
238 let st = self.edit.borrow();
239 let text = if self.password_mode {
240 const BULLET: char = '•';
241 let n = st.text.chars().count();
242 BULLET.to_string().repeat(n)
243 } else {
244 st.text.clone()
245 };
246 let cursor = if self.password_mode {
247 const BULLET_LEN: usize = 3;
248 st.text[..st.cursor].chars().count() * BULLET_LEN
249 } else {
250 st.cursor
251 };
252 (text, cursor)
253 };
254
255 let h = self.bounds.height;
256 let pad = self.padding;
257 let v = ctx.visuals();
258
259 let font = self.active_font();
260 ctx.set_font(Arc::clone(&font));
261 ctx.set_font_size(self.font_size);
262 let m = ctx.measure_text("Ag").unwrap_or_default();
263 let baseline_y = h * 0.5 - (m.ascent - m.descent) * 0.5;
264 let text_x = pad - self.scroll_x;
265 let cx = text_x + measure_advance(&font, &text[..cursor], self.font_size);
266 let top = baseline_y + m.ascent;
267 let bot = baseline_y - m.descent;
268
269 ctx.save();
272 ctx.clip_rect(pad, 0.0, (self.bounds.width - pad * 2.0).max(0.0), h);
273 ctx.set_stroke_color(v.accent);
274 ctx.set_line_width(1.5);
275 ctx.begin_path();
276 ctx.move_to(cx, bot);
277 ctx.line_to(cx, top);
278 ctx.stroke();
279 ctx.restore();
280 }
281
282 fn on_event(&mut self, event: &Event) -> EventResult {
283 match event {
284 Event::MouseMove { pos } => {
285 let was = self.hovered;
286 self.hovered = self.hit_test(*pos);
287 if self.mouse_down && self.focused {
288 let tx = pos.x - self.padding + self.scroll_x;
289 let text = self.edit.borrow().text.clone();
290 let new_cur = self.click_to_cursor(&text, tx);
291 self.edit.borrow_mut().cursor = new_cur;
292 crate::animation::request_draw();
293 return EventResult::Consumed;
294 }
295 if was != self.hovered {
296 crate::animation::request_draw();
297 return EventResult::Consumed;
298 }
299 EventResult::Ignored
300 }
301
302 Event::MouseDown {
303 pos,
304 button: MouseButton::Left,
305 modifiers: mods,
306 } => {
307 self.mouse_down = true;
308 let tx = pos.x - self.padding + self.scroll_x;
309 let text = self.edit.borrow().text.clone();
310 let new_cur = self.click_to_cursor(&text, tx);
311
312 let is_double = self
314 .last_click_time
315 .map(|t| t.elapsed().as_millis() < 350)
316 .unwrap_or(false);
317 self.last_click_time = Some(Instant::now());
318
319 if is_double && !mods.shift {
320 let (ws, we) = word_range_at(&text, new_cur);
321 self.edit.borrow_mut().anchor = ws;
322 self.edit.borrow_mut().cursor = we;
323 } else if mods.shift {
324 self.edit.borrow_mut().cursor = new_cur;
325 } else {
326 self.edit.borrow_mut().cursor = new_cur;
327 self.edit.borrow_mut().anchor = new_cur;
328 }
329 self.focus_time = Some(Instant::now());
331 crate::animation::request_draw();
332 EventResult::Consumed
333 }
334
335 Event::MouseUp {
336 button: MouseButton::Left,
337 ..
338 } => {
339 self.mouse_down = false;
340 EventResult::Ignored
341 }
342
343 Event::FocusGained => {
344 self.focused = true;
345 self.focus_time = Some(Instant::now());
346 self.text_on_focus = self.text();
347 if self.select_all_on_focus {
348 let len = self.edit.borrow().text.len();
349 self.edit.borrow_mut().anchor = 0;
350 self.edit.borrow_mut().cursor = len;
351 }
352 crate::animation::request_draw();
353 EventResult::Ignored
354 }
355
356 Event::FocusLost => {
357 let was_focused = self.focused;
358 self.focused = false;
359 self.focus_time = None;
360 self.mouse_down = false;
361 self.flush_pending();
362 if self.text() != self.text_on_focus {
363 self.notify_edit_complete();
364 }
365 if was_focused {
366 crate::animation::request_draw();
367 }
368 EventResult::Ignored
369 }
370
371 Event::KeyDown { key, modifiers } if self.focused => {
372 self.focus_time = Some(Instant::now());
374 let result = self.handle_key(key, *modifiers);
375 if result == EventResult::Consumed {
378 crate::animation::request_draw();
379 }
380 result
381 }
382
383 _ => EventResult::Ignored,
384 }
385 }
386}