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