agg_gui/widgets/text_area/
widget_impl.rs1use super::*;
2
3impl Widget for TextArea {
4 fn type_name(&self) -> &'static str {
5 "TextArea"
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
20 fn is_focusable(&self) -> bool {
21 true
22 }
23
24 fn accepts_text_input(&self) -> bool {
25 true
26 }
27
28 fn text_input_value(&self) -> Option<String> {
29 Some(self.text())
30 }
31
32 fn margin(&self) -> Insets {
33 self.base.margin
34 }
35 fn widget_base(&self) -> Option<&WidgetBase> {
36 Some(&self.base)
37 }
38 fn widget_base_mut(&mut self) -> Option<&mut WidgetBase> {
39 Some(&mut self.base)
40 }
41 fn h_anchor(&self) -> HAnchor {
42 self.base.h_anchor
43 }
44 fn v_anchor(&self) -> VAnchor {
45 self.base.v_anchor
46 }
47 fn min_size(&self) -> Size {
48 self.base.min_size
49 }
50 fn max_size(&self) -> Size {
51 self.base.max_size
52 }
53
54 fn measure_min_height(&self, available_w: f64) -> f64 {
55 let inner_w = (available_w - self.padding * 2.0).max(1.0);
66 let lines = wrap_text_indexed(
67 &self.font,
68 &self.edit.borrow().text,
69 self.font_size,
70 inner_w,
71 );
72 let line_h = self.font_size * 1.35;
73 (lines.len().max(1) as f64) * line_h + self.padding * 2.0
74 }
75
76 fn layout(&mut self, available: Size) -> Size {
77 let w = available.width.max(self.padding * 2.0 + 20.0);
82 let h = available
83 .height
84 .max(self.padding * 2.0 + self.font_size * 1.6);
85 self.bounds = Rect::new(0.0, 0.0, w, h);
86 let inner_w = (w - self.padding * 2.0).max(1.0);
87 self.refresh_wrap(inner_w);
88 Size::new(w, h)
89 }
90
91 fn paint(&mut self, ctx: &mut dyn DrawCtx) {
92 let v = ctx.visuals();
93 let w = self.bounds.width;
94 let h = self.bounds.height;
95
96 ctx.set_fill_color(v.widget_bg);
98 ctx.begin_path();
99 ctx.rounded_rect(0.0, 0.0, w, h, 4.0);
100 ctx.fill();
101
102 ctx.clip_rect(
105 self.padding,
106 self.padding,
107 (w - self.padding * 2.0).max(0.0),
108 (h - self.padding * 2.0).max(0.0),
109 );
110
111 ctx.set_font(Arc::clone(&self.font));
112 ctx.set_font_size(self.font_size);
113
114 let st = self.edit.borrow().clone();
116 if st.cursor != st.anchor {
117 let lo = st.cursor.min(st.anchor);
118 let hi = st.cursor.max(st.anchor);
119 let hl_color = if self.focused {
120 v.selection_bg
121 } else {
122 v.selection_bg_unfocused
123 };
124 ctx.set_fill_color(hl_color);
125 for (i, line) in self.cached_lines.iter().enumerate() {
126 if line.end < lo || line.start > hi {
127 continue;
128 }
129 let sel_s = lo.max(line.start) - line.start;
130 let sel_e = hi.min(line.end) - line.start;
131 let sel_e = sel_e.min(line.text.len());
132 if sel_e <= sel_s {
133 continue;
134 }
135 let x0 =
136 self.padding + measure_advance(&self.font, &line.text[..sel_s], self.font_size);
137 let x1 =
138 self.padding + measure_advance(&self.font, &line.text[..sel_e], self.font_size);
139 let line_top = h - self.padding - i as f64 * self.cached_line_h;
140 let line_bottom = line_top - self.cached_line_h;
141 ctx.begin_path();
142 ctx.rect(x0, line_bottom, x1 - x0, self.cached_line_h);
143 ctx.fill();
144 }
145 }
146
147 ctx.set_fill_color(v.text_color);
149 let m = ctx.measure_text("Ag").unwrap_or_default();
152 for (i, line) in self.cached_lines.iter().enumerate() {
153 if line.text.is_empty() {
154 continue;
155 }
156 let line_top = h - self.padding - i as f64 * self.cached_line_h;
157 let line_bottom = line_top - self.cached_line_h;
158 let baseline_y =
159 line_bottom + (self.cached_line_h - (m.ascent - m.descent)) * 0.5 + m.descent;
160 ctx.fill_text(&line.text, self.padding, baseline_y);
161 }
162
163 if st.text.is_empty() && !self.focused {
165 ctx.set_fill_color(v.text_dim);
166 let line_top = h - self.padding;
167 let line_bottom = line_top - self.cached_line_h;
168 let baseline_y =
169 line_bottom + (self.cached_line_h - (m.ascent - m.descent)) * 0.5 + m.descent;
170 ctx.fill_text("Type here…", self.padding, baseline_y);
171 }
172
173 ctx.reset_clip();
174
175 let border = if self.focused {
177 v.accent
178 } else if self.hovered {
179 v.widget_stroke_active
180 } else {
181 v.widget_stroke
182 };
183 let line_width = if self.focused { 2.0 } else { 1.0 };
184 ctx.set_stroke_color(border);
185 ctx.set_line_width(line_width);
186 ctx.begin_path();
187 let inset = line_width * 0.5;
188 ctx.rounded_rect(
189 inset,
190 inset,
191 (w - line_width).max(0.0),
192 (h - line_width).max(0.0),
193 4.0,
194 );
195 ctx.stroke();
196 }
197
198 fn paint_overlay(&mut self, ctx: &mut dyn DrawCtx) {
199 if !self.focused {
202 return;
203 }
204 if let Some(t) = self.focus_time {
205 let phase = (t.elapsed().as_millis() / 500) as u64;
206 self.blink_last_phase.set(phase);
207 if phase % 2 == 1 {
208 return;
209 }
210 }
211 let st = self.edit.borrow().clone();
212 let p = self.pos_for_cursor(st.cursor);
213 let v = ctx.visuals();
214 ctx.set_stroke_color(v.text_color);
215 ctx.set_line_width(1.5);
216 ctx.begin_path();
217 ctx.move_to(p.x, p.y);
218 ctx.line_to(p.x, p.y + self.cached_line_h);
219 ctx.stroke();
220 }
221
222 fn on_event(&mut self, event: &Event) -> EventResult {
223 match event {
224 Event::MouseMove { pos } => {
225 let was = self.hovered;
226 self.hovered = self.hit_test(*pos);
227 if self.hovered {
228 set_cursor_icon(CursorIcon::Text);
229 }
230 if self.selecting_drag {
231 let off = self.byte_offset_at(*pos);
232 self.move_cursor_to(off, true);
233 crate::animation::request_draw();
234 return EventResult::Consumed;
235 }
236 if was != self.hovered {
237 crate::animation::request_draw();
238 return EventResult::Consumed;
239 }
240 EventResult::Ignored
241 }
242 Event::MouseDown {
243 button: MouseButton::Left,
244 pos,
245 modifiers,
246 } => {
247 let off = self.byte_offset_at(*pos);
248 self.move_cursor_to(off, modifiers.shift);
249 self.selecting_drag = true;
250 crate::animation::request_draw();
251 EventResult::Consumed
252 }
253 Event::MouseUp {
254 button: MouseButton::Left,
255 ..
256 } => {
257 self.selecting_drag = false;
258 EventResult::Consumed
259 }
260 Event::FocusGained => {
261 self.focused = true;
262 self.focus_time = Some(Instant::now());
263 crate::animation::request_draw();
264 EventResult::Ignored
265 }
266 Event::FocusLost => {
267 self.focused = false;
268 self.selecting_drag = false;
269 crate::animation::request_draw();
270 EventResult::Ignored
271 }
272 Event::KeyDown { key, modifiers } => {
273 let shift = modifiers.shift;
274 let cmd = modifiers.ctrl || modifiers.meta;
275 match key {
276 Key::ArrowLeft => {
277 self.move_char(-1, shift);
278 }
279 Key::ArrowRight => {
280 self.move_char(1, shift);
281 }
282 Key::ArrowUp => {
283 self.move_line(-1, shift);
284 }
285 Key::ArrowDown => {
286 self.move_line(1, shift);
287 }
288 Key::Home => {
289 let cur = self.edit.borrow().cursor;
290 let line = self.line_for_cursor(cur);
291 let start = self.cached_lines[line].start;
292 self.move_cursor_to(start, shift);
293 }
294 Key::End => {
295 let cur = self.edit.borrow().cursor;
296 let line = self.line_for_cursor(cur);
297 let end = self.cached_lines[line].end;
298 self.move_cursor_to(end, shift);
299 }
300 Key::Backspace => {
301 self.delete(-1);
302 }
303 Key::Delete => {
304 self.delete(1);
305 }
306 Key::Enter => {
307 self.insert_str("\n");
308 }
309 Key::Tab => {
310 self.insert_str(" ");
311 }
312 Key::Char('a') | Key::Char('A') if cmd => {
313 let len = self.edit.borrow().text.len();
316 self.move_cursor_to(0, false);
317 self.move_cursor_to(len, true);
318 }
319 Key::Char('c') | Key::Char('C') if cmd => {
320 let st = self.edit.borrow();
321 let (lo, hi) = (st.cursor.min(st.anchor), st.cursor.max(st.anchor));
322 if hi > lo {
323 let sel = st.text[lo..hi].to_string();
324 drop(st);
325 clipboard_set(&sel);
326 }
327 }
328 Key::Char('x') | Key::Char('X') if cmd => {
329 let st = self.edit.borrow();
330 let (lo, hi) = (st.cursor.min(st.anchor), st.cursor.max(st.anchor));
331 if hi > lo {
332 let sel = st.text[lo..hi].to_string();
333 drop(st);
334 clipboard_set(&sel);
335 self.delete(0);
336 }
337 }
338 Key::Char('v') | Key::Char('V') if cmd => {
339 if let Some(t) = clipboard_get() {
340 self.insert_str(&t);
341 }
342 }
343 Key::Char(c) if !cmd => {
344 let mut s = [0u8; 4];
345 self.insert_str(c.encode_utf8(&mut s));
346 }
347 _ => return EventResult::Ignored,
348 }
349 self.focus_time = Some(Instant::now());
350 crate::animation::request_draw();
351 EventResult::Consumed
352 }
353 _ => EventResult::Ignored,
354 }
355 }
356
357 fn hit_test(&self, local_pos: Point) -> bool {
358 local_pos.x >= 0.0
359 && local_pos.x <= self.bounds.width
360 && local_pos.y >= 0.0
361 && local_pos.y <= self.bounds.height
362 }
363
364 fn properties(&self) -> Vec<(&'static str, String)> {
365 let st = self.edit.borrow();
366 vec![
367 ("len", st.text.len().to_string()),
368 ("cursor", st.cursor.to_string()),
369 ("lines", self.cached_lines.len().to_string()),
370 ("focused", self.focused.to_string()),
371 ]
372 }
373
374 fn needs_draw(&self) -> bool {
375 self.focused
376 }
377}