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