1 /**
2  * @file lv_ta.c
3  *
4  */
5 
6 /*********************
7  *      INCLUDES
8  *********************/
9 #include "lv_textarea.h"
10 #if LV_USE_TEXTAREA != 0
11 
12 #include <string.h>
13 #include "../lv_misc/lv_debug.h"
14 #include "../lv_core/lv_group.h"
15 #include "../lv_core/lv_refr.h"
16 #include "../lv_draw/lv_draw.h"
17 #include "../lv_themes/lv_theme.h"
18 #include "../lv_misc/lv_anim.h"
19 #include "../lv_misc/lv_txt.h"
20 #include "../lv_misc/lv_math.h"
21 
22 /*********************
23  *      DEFINES
24  *********************/
25 #define LV_OBJX_NAME "lv_textarea"
26 
27 /*Test configuration*/
28 #ifndef LV_TEXTAREA_DEF_CURSOR_BLINK_TIME
29     #define LV_TEXTAREA_DEF_CURSOR_BLINK_TIME 400 /*ms*/
30 #endif
31 
32 #ifndef LV_TEXTAREA_DEF_PWD_SHOW_TIME
33     #define LV_TEXTAREA_DEF_PWD_SHOW_TIME 1500 /*ms*/
34 #endif
35 
36 #define LV_TEXTAREA_DEF_WIDTH (2 * LV_DPI)
37 #define LV_TEXTAREA_DEF_HEIGHT (1 * LV_DPI)
38 
39 #define LV_TEXTAREA_PWD_BULLET_UNICODE      0x2022
40 
41 /**********************
42  *      TYPEDEFS
43  **********************/
44 
45 /**********************
46  *  STATIC PROTOTYPES
47  **********************/
48 static lv_design_res_t lv_textarea_design(lv_obj_t * ta, const lv_area_t * clip_area, lv_design_mode_t mode);
49 static lv_design_res_t lv_textarea_scrollable_design(lv_obj_t * scrl, const lv_area_t * clip_area,
50                                                      lv_design_mode_t mode);
51 static lv_res_t lv_textarea_signal(lv_obj_t * ta, lv_signal_t sign, void * param);
52 static lv_res_t lv_textarea_scrollable_signal(lv_obj_t * scrl, lv_signal_t sign, void * param);
53 static lv_style_list_t * lv_textarea_get_style(lv_obj_t * ta, uint8_t part);
54 #if LV_USE_ANIMATION
55     static void cursor_blink_anim(lv_obj_t * ta, lv_anim_value_t show);
56     static void pwd_char_hider_anim(lv_obj_t * ta, lv_anim_value_t x);
57     static void pwd_char_hider_anim_ready(lv_anim_t * a);
58 #endif
59 static void pwd_char_hider(lv_obj_t * ta);
60 static bool char_is_accepted(lv_obj_t * ta, uint32_t c);
61 static void refr_cursor_area(lv_obj_t * ta);
62 static void update_cursor_position_on_click(lv_obj_t * ta, lv_signal_t sign, lv_indev_t * click_source);
63 static lv_res_t insert_handler(lv_obj_t * ta, const char * txt);
64 
65 /**********************
66  *  STATIC VARIABLES
67  **********************/
68 static lv_design_cb_t ancestor_design;
69 static lv_design_cb_t scrl_design;
70 static lv_signal_cb_t ancestor_signal;
71 static lv_signal_cb_t scrl_signal;
72 static const char * ta_insert_replace;
73 
74 /**********************
75  *      MACROS
76  **********************/
77 
78 /**********************
79  *   GLOBAL FUNCTIONS
80  **********************/
81 
82 /**
83  * Create a text area objects
84  * @param par pointer to an object, it will be the parent of the new text area
85  * @param copy pointer to a text area object, if not NULL then the new object will be copied from it
86  * @return pointer to the created text area
87  */
lv_textarea_create(lv_obj_t * par,const lv_obj_t * copy)88 lv_obj_t * lv_textarea_create(lv_obj_t * par, const lv_obj_t * copy)
89 {
90     LV_LOG_TRACE("text area create started");
91 
92     /*Create the ancestor object*/
93     lv_obj_t * ta = lv_page_create(par, copy);
94     LV_ASSERT_MEM(ta);
95     if(ta == NULL) return NULL;
96 
97     if(ancestor_signal == NULL) ancestor_signal = lv_obj_get_signal_cb(ta);
98     if(ancestor_design == NULL) ancestor_design = lv_obj_get_design_cb(ta);
99     if(scrl_signal == NULL) scrl_signal = lv_obj_get_signal_cb(lv_page_get_scrollable(ta));
100     if(scrl_design == NULL) scrl_design = lv_obj_get_design_cb(lv_page_get_scrollable(ta));
101 
102     /*Allocate the object type specific extended data*/
103     lv_textarea_ext_t * ext = lv_obj_allocate_ext_attr(ta, sizeof(lv_textarea_ext_t));
104     LV_ASSERT_MEM(ext);
105     if(ext == NULL) {
106         lv_obj_del(ta);
107         return NULL;
108     }
109 
110     ext->pwd_mode          = 0;
111     ext->pwd_tmp           = NULL;
112     ext->pwd_show_time     = LV_TEXTAREA_DEF_PWD_SHOW_TIME;
113     ext->accapted_chars    = NULL;
114     ext->max_length        = 0;
115     ext->cursor.state      = 1;
116     ext->cursor.hidden     = 0;
117     ext->cursor.blink_time = LV_TEXTAREA_DEF_CURSOR_BLINK_TIME;
118     ext->cursor.pos        = 0;
119     ext->cursor.click_pos  = 1;
120     ext->cursor.valid_x    = 0;
121     ext->one_line          = 0;
122 #if LV_LABEL_TEXT_SEL
123     ext->text_sel_en = 0;
124 #endif
125     ext->label       = NULL;
126     ext->placeholder_txt = NULL;
127 
128     lv_style_list_init(&ext->cursor.style);
129     lv_style_list_init(&ext->style_placeholder);
130 
131 #if LV_USE_ANIMATION == 0
132     ext->pwd_show_time     = 0;
133     ext->cursor.blink_time = 0;
134 #endif
135 
136     lv_obj_set_signal_cb(ta, lv_textarea_signal);
137     lv_obj_set_signal_cb(lv_page_get_scrollable(ta), lv_textarea_scrollable_signal);
138     lv_obj_set_design_cb(ta, lv_textarea_design);
139 
140     /*Init the new text area object*/
141     if(copy == NULL) {
142         lv_page_set_scrollable_fit2(ta, LV_FIT_PARENT, LV_FIT_TIGHT);
143 
144         ext->label = lv_label_create(ta, NULL);
145 
146         lv_obj_set_design_cb(ext->page.scrl, lv_textarea_scrollable_design);
147 
148         lv_label_set_long_mode(ext->label, LV_LABEL_LONG_BREAK);
149         lv_label_set_text(ext->label, "Text area");
150         lv_obj_set_click(ext->label, false);
151         lv_obj_set_size(ta, LV_TEXTAREA_DEF_WIDTH, LV_TEXTAREA_DEF_HEIGHT);
152         lv_textarea_set_scrollbar_mode(ta, LV_SCROLLBAR_MODE_DRAG);
153 
154         lv_obj_reset_style_list(ta, LV_PAGE_PART_SCROLLABLE);
155         lv_theme_apply(ta, LV_THEME_TEXTAREA);
156 
157     }
158     /*Copy an existing object*/
159     else {
160         lv_obj_set_design_cb(ext->page.scrl, lv_textarea_scrollable_design);
161         lv_textarea_ext_t * copy_ext = lv_obj_get_ext_attr(copy);
162         ext->label             = lv_label_create(ta, copy_ext->label);
163         ext->pwd_mode          = copy_ext->pwd_mode;
164         ext->accapted_chars    = copy_ext->accapted_chars;
165         ext->max_length        = copy_ext->max_length;
166         ext->cursor.pos        = copy_ext->cursor.pos;
167         ext->cursor.valid_x    = copy_ext->cursor.valid_x;
168         ext->cursor.hidden    = copy_ext->cursor.hidden;
169 
170         lv_style_list_copy(&ext->cursor.style, &copy_ext->cursor.style);
171         lv_style_list_copy(&ext->style_placeholder, &copy_ext->style_placeholder);
172 
173         if(ext->pwd_mode != 0) pwd_char_hider(ta);
174 
175         if(copy_ext->placeholder_txt) {
176             lv_textarea_set_placeholder_text(ta, copy_ext->placeholder_txt);
177         }
178 
179         if(copy_ext->pwd_tmp) {
180             uint32_t len = _lv_mem_get_size(copy_ext->pwd_tmp);
181             ext->pwd_tmp = lv_mem_alloc(len);
182             LV_ASSERT_MEM(ext->pwd_tmp);
183             if(ext->pwd_tmp == NULL) return NULL;
184 
185             _lv_memcpy(ext->pwd_tmp, copy_ext->pwd_tmp, len);
186         }
187 
188         if(copy_ext->one_line) lv_textarea_set_one_line(ta, true);
189 
190         /*Refresh the style with new signal function*/
191         lv_obj_refresh_style(ta, LV_OBJ_PART_ALL, LV_STYLE_PROP_ALL);
192     }
193 
194 #if LV_USE_ANIMATION
195     if(ext->cursor.blink_time) {
196         /*Create a cursor blinker animation*/
197         lv_anim_path_t path;
198         lv_anim_path_init(&path);
199         lv_anim_path_set_cb(&path, lv_anim_path_step);
200 
201         lv_anim_t a;
202         lv_anim_init(&a);
203         lv_anim_set_var(&a, ta);
204         lv_anim_set_exec_cb(&a, (lv_anim_exec_xcb_t)cursor_blink_anim);
205         lv_anim_set_time(&a, ext->cursor.blink_time);
206         lv_anim_set_playback_time(&a, ext->cursor.blink_time);
207         lv_anim_set_values(&a, 0, 1);
208         lv_anim_set_path(&a, &path);
209         lv_anim_set_repeat_count(&a, LV_ANIM_REPEAT_INFINITE);
210         lv_anim_start(&a);
211     }
212 #endif
213 
214     LV_LOG_INFO("text area created");
215 
216     return ta;
217 }
218 
219 /*======================
220  * Add/remove functions
221  *=====================*/
222 
223 /**
224  * Insert a character to the current cursor position.
225  * To add a wide char, e.g. 'Á' use `_lv_txt_encoded_conv_wc('Á')`
226  * @param ta pointer to a text area object
227  * @param c a character (e.g. 'a')
228  */
lv_textarea_add_char(lv_obj_t * ta,uint32_t c)229 void lv_textarea_add_char(lv_obj_t * ta, uint32_t c)
230 {
231     LV_ASSERT_OBJ(ta, LV_OBJX_NAME);
232 
233     lv_textarea_ext_t * ext = lv_obj_get_ext_attr(ta);
234 
235     const char * letter_buf;
236 
237     uint32_t u32_buf[2];
238     u32_buf[0] = c;
239     u32_buf[1] = 0;
240 
241     letter_buf = (char *)&u32_buf;
242 
243 #if LV_BIG_ENDIAN_SYSTEM
244     if(c != 0) while(*letter_buf == 0) ++letter_buf;
245 #endif
246 
247     lv_res_t res = insert_handler(ta, letter_buf);
248     if(res != LV_RES_OK) return;
249 
250     if(ext->one_line && (c == '\n' || c == '\r')) {
251         LV_LOG_INFO("Text area: line break ignored in one-line mode");
252         return;
253     }
254 
255     uint32_t c_uni = _lv_txt_encoded_next((const char *)&c, NULL);
256 
257     if(char_is_accepted(ta, c_uni) == false) {
258         LV_LOG_INFO("Character is no accepted by the text area (too long text or not in the "
259                     "accepted list)");
260         return;
261     }
262 
263     /*If a new line was added it shouldn't show edge flash effect*/
264     bool edge_flash_en = lv_textarea_get_edge_flash(ta);
265     lv_textarea_set_edge_flash(ta, false);
266 
267     if(ext->pwd_mode != 0) pwd_char_hider(ta); /*Make sure all the current text contains only '*'*/
268 
269     /*If the textarea is empty, invalidate it to hide the placeholder*/
270     if(ext->placeholder_txt) {
271         const char * txt = lv_label_get_text(ext->label);
272         if(txt[0] == '\0') lv_obj_invalidate(ta);
273     }
274 
275     lv_label_ins_text(ext->label, ext->cursor.pos, letter_buf); /*Insert the character*/
276     lv_textarea_clear_selection(ta);                                                /*Clear selection*/
277 
278     if(ext->pwd_mode != 0) {
279 
280         ext->pwd_tmp = lv_mem_realloc(ext->pwd_tmp, strlen(ext->pwd_tmp) + 2); /*+2: the new char + \0 */
281         LV_ASSERT_MEM(ext->pwd_tmp);
282         if(ext->pwd_tmp == NULL) return;
283 
284         _lv_txt_ins(ext->pwd_tmp, ext->cursor.pos, (const char *)letter_buf);
285 
286 #if LV_USE_ANIMATION
287         /*Auto hide characters*/
288         if(ext->pwd_show_time == 0) {
289             pwd_char_hider(ta);
290         }
291         else {
292             lv_anim_path_t path;
293             lv_anim_path_init(&path);
294             lv_anim_path_set_cb(&path, lv_anim_path_step);
295 
296             lv_anim_t a;
297             lv_anim_init(&a);
298             lv_anim_set_var(&a, ta);
299             lv_anim_set_exec_cb(&a, (lv_anim_exec_xcb_t)pwd_char_hider_anim);
300             lv_anim_set_time(&a, ext->pwd_show_time);
301             lv_anim_set_values(&a, 0, 1);
302             lv_anim_set_path(&a, &path);
303             lv_anim_set_ready_cb(&a, pwd_char_hider_anim_ready);
304             lv_anim_start(&a);
305         }
306 
307 #else
308         pwd_char_hider(ta);
309 #endif
310     }
311 
312     /*Move the cursor after the new character*/
313     lv_textarea_set_cursor_pos(ta, lv_textarea_get_cursor_pos(ta) + 1);
314 
315     /*Revert the original edge flash state*/
316     lv_textarea_set_edge_flash(ta, edge_flash_en);
317 
318     lv_event_send(ta, LV_EVENT_VALUE_CHANGED, NULL);
319 }
320 
321 /**
322  * Insert a text to the current cursor position
323  * @param ta pointer to a text area object
324  * @param txt a '\0' terminated string to insert
325  */
lv_textarea_add_text(lv_obj_t * ta,const char * txt)326 void lv_textarea_add_text(lv_obj_t * ta, const char * txt)
327 {
328     LV_ASSERT_OBJ(ta, LV_OBJX_NAME);
329     LV_ASSERT_NULL(txt);
330 
331     lv_textarea_ext_t * ext = lv_obj_get_ext_attr(ta);
332 
333     if(ext->pwd_mode != 0) pwd_char_hider(ta); /*Make sure all the current text contains only '*'*/
334 
335     /*Add the character one-by-one if not all characters are accepted or there is character limit.*/
336     if(lv_textarea_get_accepted_chars(ta) || lv_textarea_get_max_length(ta)) {
337         uint32_t i = 0;
338         while(txt[i] != '\0') {
339             uint32_t c = _lv_txt_encoded_next(txt, &i);
340             lv_textarea_add_char(ta, _lv_txt_unicode_to_encoded(c));
341         }
342         return;
343     }
344 
345     lv_res_t res = insert_handler(ta, txt);
346     if(res != LV_RES_OK) return;
347 
348     /*If a new line was added it shouldn't show edge flash effect*/
349     bool edge_flash_en = lv_textarea_get_edge_flash(ta);
350     lv_textarea_set_edge_flash(ta, false);
351 
352     /*If the textarea is empty, invalidate it to hide the placeholder*/
353     if(ext->placeholder_txt) {
354         const char * txt_act = lv_label_get_text(ext->label);
355         if(txt_act[0] == '\0') lv_obj_invalidate(ta);
356     }
357 
358     /*Insert the text*/
359     lv_label_ins_text(ext->label, ext->cursor.pos, txt);
360     lv_textarea_clear_selection(ta);
361 
362     if(ext->pwd_mode != 0) {
363         ext->pwd_tmp = lv_mem_realloc(ext->pwd_tmp, strlen(ext->pwd_tmp) + strlen(txt) + 1);
364         LV_ASSERT_MEM(ext->pwd_tmp);
365         if(ext->pwd_tmp == NULL) return;
366 
367         _lv_txt_ins(ext->pwd_tmp, ext->cursor.pos, txt);
368 
369 #if LV_USE_ANIMATION
370         /*Auto hide characters*/
371         if(ext->pwd_show_time == 0) {
372             pwd_char_hider(ta);
373         }
374         else {
375             lv_anim_path_t path;
376             lv_anim_path_init(&path);
377             lv_anim_path_set_cb(&path, lv_anim_path_step);
378 
379             lv_anim_t a;
380             lv_anim_init(&a);
381             lv_anim_set_var(&a, ta);
382             lv_anim_set_exec_cb(&a, (lv_anim_exec_xcb_t)pwd_char_hider_anim);
383             lv_anim_set_time(&a, ext->pwd_show_time);
384             lv_anim_set_values(&a, 0, 1);
385             lv_anim_set_path(&a, &path);
386             lv_anim_set_ready_cb(&a, pwd_char_hider_anim_ready);
387             lv_anim_start(&a);
388         }
389 #else
390         pwd_char_hider(ta);
391 #endif
392     }
393 
394     /*Move the cursor after the new text*/
395     lv_textarea_set_cursor_pos(ta, lv_textarea_get_cursor_pos(ta) + _lv_txt_get_encoded_length(txt));
396 
397     /*Revert the original edge flash state*/
398     lv_textarea_set_edge_flash(ta, edge_flash_en);
399 
400     lv_event_send(ta, LV_EVENT_VALUE_CHANGED, NULL);
401 }
402 
403 /**
404  * Delete a the left character from the current cursor position
405  * @param ta pointer to a text area object
406  */
lv_textarea_del_char(lv_obj_t * ta)407 void lv_textarea_del_char(lv_obj_t * ta)
408 {
409     LV_ASSERT_OBJ(ta, LV_OBJX_NAME);
410 
411     lv_textarea_ext_t * ext = lv_obj_get_ext_attr(ta);
412     uint32_t cur_pos  = ext->cursor.pos;
413 
414     if(cur_pos == 0) return;
415 
416     char del_buf[2]   = {LV_KEY_DEL, '\0'};
417 
418     lv_res_t res = insert_handler(ta, del_buf);
419     if(res != LV_RES_OK) return;
420 
421     char * label_txt = lv_label_get_text(ext->label);
422 
423     /*Delete a character*/
424     _lv_txt_cut(label_txt, ext->cursor.pos - 1, 1);
425     /*Refresh the label*/
426     lv_label_set_text(ext->label, label_txt);
427     lv_textarea_clear_selection(ta);
428 
429 
430     /*If the textarea became empty, invalidate it to hide the placeholder*/
431     if(ext->placeholder_txt) {
432         const char * txt = lv_label_get_text(ext->label);
433         if(txt[0] == '\0') lv_obj_invalidate(ta);
434     }
435 
436     /*Don't let 'width == 0' because cursor will not be visible*/
437     if(lv_obj_get_width(ext->label) == 0) {
438         lv_style_int_t border_width = lv_obj_get_style_border_width(ta, LV_TEXTAREA_PART_CURSOR);
439         lv_obj_set_width(ext->label, border_width == 0 ? 1 : border_width);
440     }
441 
442     if(ext->pwd_mode != 0) {
443         uint32_t byte_pos = _lv_txt_encoded_get_byte_id(ext->pwd_tmp, ext->cursor.pos - 1);
444         _lv_txt_cut(ext->pwd_tmp, ext->cursor.pos - 1, _lv_txt_encoded_size(&ext->pwd_tmp[byte_pos]));
445 
446         ext->pwd_tmp = lv_mem_realloc(ext->pwd_tmp, strlen(ext->pwd_tmp) + 1);
447         LV_ASSERT_MEM(ext->pwd_tmp);
448         if(ext->pwd_tmp == NULL) return;
449     }
450 
451     /*Move the cursor to the place of the deleted character*/
452     lv_textarea_set_cursor_pos(ta, ext->cursor.pos - 1);
453 
454     lv_event_send(ta, LV_EVENT_VALUE_CHANGED, NULL);
455 
456 }
457 
458 /**
459  * Delete the right character from the current cursor position
460  * @param ta pointer to a text area object
461  */
lv_textarea_del_char_forward(lv_obj_t * ta)462 void lv_textarea_del_char_forward(lv_obj_t * ta)
463 {
464     LV_ASSERT_OBJ(ta, LV_OBJX_NAME);
465 
466     uint32_t cp = lv_textarea_get_cursor_pos(ta);
467     lv_textarea_set_cursor_pos(ta, cp + 1);
468     if(cp != lv_textarea_get_cursor_pos(ta)) lv_textarea_del_char(ta);
469 }
470 
471 /*=====================
472  * Setter functions
473  *====================*/
474 
475 /**
476  * Set the text of a text area
477  * @param ta pointer to a text area
478  * @param txt pointer to the text
479  */
lv_textarea_set_text(lv_obj_t * ta,const char * txt)480 void lv_textarea_set_text(lv_obj_t * ta, const char * txt)
481 {
482     LV_ASSERT_OBJ(ta, LV_OBJX_NAME);
483     LV_ASSERT_NULL(txt);
484 
485     lv_textarea_ext_t * ext = lv_obj_get_ext_attr(ta);
486 
487     /*Clear the existing selection*/
488     lv_textarea_clear_selection(ta);
489 
490     /*Add the character one-by-one if not all characters are accepted or there is character limit.*/
491     if(lv_textarea_get_accepted_chars(ta) || lv_textarea_get_max_length(ta)) {
492         lv_label_set_text(ext->label, "");
493         lv_textarea_set_cursor_pos(ta, LV_TEXTAREA_CURSOR_LAST);
494         if(ext->pwd_mode != 0) {
495             ext->pwd_tmp[0] = '\0'; /*Clear the password too*/
496         }
497         uint32_t i = 0;
498         while(txt[i] != '\0') {
499             uint32_t c = _lv_txt_encoded_next(txt, &i);
500             lv_textarea_add_char(ta, _lv_txt_unicode_to_encoded(c));
501         }
502     }
503     else {
504         lv_label_set_text(ext->label, txt);
505         lv_textarea_set_cursor_pos(ta, LV_TEXTAREA_CURSOR_LAST);
506     }
507 
508 
509     /*If the textarea is empty, invalidate it to hide the placeholder*/
510     if(ext->placeholder_txt) {
511         const char * txt_act = lv_label_get_text(ext->label);
512         if(txt_act[0] == '\0') lv_obj_invalidate(ta);
513     }
514 
515     /*Don't let 'width == 0' because the cursor will not be visible*/
516     if(lv_obj_get_width(ext->label) == 0) {
517         lv_style_int_t border_width = lv_obj_get_style_border_width(ta, LV_TEXTAREA_PART_CURSOR);
518         lv_obj_set_width(ext->label, border_width == 0 ? 1 : border_width);
519     }
520 
521     if(ext->pwd_mode != 0) {
522         ext->pwd_tmp = lv_mem_realloc(ext->pwd_tmp, strlen(txt) + 1);
523         LV_ASSERT_MEM(ext->pwd_tmp);
524         if(ext->pwd_tmp == NULL) return;
525         strcpy(ext->pwd_tmp, txt);
526 
527 #if LV_USE_ANIMATION
528         /*Auto hide characters*/
529         if(ext->pwd_show_time == 0) {
530             pwd_char_hider(ta);
531         }
532         else {
533             lv_anim_path_t path;
534             lv_anim_path_init(&path);
535             lv_anim_path_set_cb(&path, lv_anim_path_step);
536 
537             lv_anim_t a;
538             lv_anim_init(&a);
539             lv_anim_set_var(&a, ta);
540             lv_anim_set_exec_cb(&a, (lv_anim_exec_xcb_t)pwd_char_hider_anim);
541             lv_anim_set_time(&a, ext->pwd_show_time);
542             lv_anim_set_values(&a, 0, 1);
543             lv_anim_set_path(&a, &path);
544             lv_anim_set_ready_cb(&a, pwd_char_hider_anim_ready);
545             lv_anim_start(&a);
546         }
547 #else
548         pwd_char_hider(ta);
549 #endif
550     }
551 
552     lv_event_send(ta, LV_EVENT_VALUE_CHANGED, NULL);
553 }
554 
555 /**
556  * Set the placeholder_txt text of a text area
557  * @param ta pointer to a text area
558  * @param txt pointer to the text
559  */
lv_textarea_set_placeholder_text(lv_obj_t * ta,const char * txt)560 void lv_textarea_set_placeholder_text(lv_obj_t * ta, const char * txt)
561 {
562     LV_ASSERT_OBJ(ta, LV_OBJX_NAME);
563     LV_ASSERT_NULL(txt);
564 
565     lv_textarea_ext_t * ext = lv_obj_get_ext_attr(ta);
566 
567     size_t txt_len = strlen(txt);
568 
569     if(txt_len == 0) {
570         if(ext->placeholder_txt) {
571             lv_mem_free(ext->placeholder_txt);
572             ext->placeholder_txt = NULL;
573         }
574     }
575     else {
576 
577         /*Allocate memory for the placeholder_txt text*/
578         if(ext->placeholder_txt == NULL) {
579             ext->placeholder_txt = lv_mem_alloc(txt_len + 1);
580         }
581         else {
582             ext->placeholder_txt = lv_mem_realloc(ext->placeholder_txt, txt_len + 1);
583 
584         }
585         LV_ASSERT_MEM(ext->placeholder_txt);
586         if(ext->placeholder_txt == NULL) {
587             LV_LOG_ERROR("lv_textarea_set_placeholder_text: couldn't allocate memory for placeholder");
588             return;
589         }
590         strcpy(ext->placeholder_txt, txt);
591     }
592 
593     lv_obj_invalidate(ta);
594 }
595 
596 /**
597  * Set the cursor position
598  * @param obj pointer to a text area object
599  * @param pos the new cursor position in character index
600  *             < 0 : index from the end of the text
601  *             LV_TEXTAREA_CURSOR_LAST: go after the last character
602  */
lv_textarea_set_cursor_pos(lv_obj_t * ta,int32_t pos)603 void lv_textarea_set_cursor_pos(lv_obj_t * ta, int32_t pos)
604 {
605     LV_ASSERT_OBJ(ta, LV_OBJX_NAME);
606 
607     lv_textarea_ext_t * ext = lv_obj_get_ext_attr(ta);
608     if((uint32_t)ext->cursor.pos == (uint32_t)pos) return;
609 
610     uint32_t len = _lv_txt_get_encoded_length(lv_label_get_text(ext->label));
611 
612     if(pos < 0) pos = len + pos;
613 
614     if(pos > (int32_t)len || pos == LV_TEXTAREA_CURSOR_LAST) pos = len;
615 
616     ext->cursor.pos = pos;
617 
618     /*Position the label to make the cursor visible*/
619     lv_obj_t * label_par = lv_obj_get_parent(ext->label);
620     lv_point_t cur_pos;
621     const lv_font_t * font = lv_obj_get_style_text_font(ta, LV_TEXTAREA_PART_BG);
622     lv_style_int_t top = lv_obj_get_style_pad_top(ta, LV_TEXTAREA_PART_BG);
623     lv_style_int_t bottom = lv_obj_get_style_pad_bottom(ta, LV_TEXTAREA_PART_BG);
624     lv_area_t label_cords;
625     lv_area_t ta_cords;
626     lv_label_get_letter_pos(ext->label, pos, &cur_pos);
627     lv_obj_get_coords(ta, &ta_cords);
628     lv_obj_get_coords(ext->label, &label_cords);
629 
630     /*Check the top*/
631     lv_coord_t font_h = lv_font_get_line_height(font);
632     if(lv_obj_get_y(label_par) + cur_pos.y < 0) {
633         lv_obj_set_y(label_par, -cur_pos.y + top);
634     }
635 
636     /*Check the bottom*/
637     if(label_cords.y1 + cur_pos.y + font_h + bottom > ta_cords.y2) {
638         lv_obj_set_y(label_par, -(cur_pos.y - lv_obj_get_height(ta) + font_h + top + bottom));
639     }
640     /*Check the left (use the font_h as general unit)*/
641     if(lv_obj_get_x(label_par) + cur_pos.x < font_h) {
642         lv_obj_set_x(label_par, -cur_pos.x + font_h);
643     }
644 
645     lv_style_int_t right = lv_obj_get_style_pad_right(ta, LV_TEXTAREA_PART_BG);
646     lv_style_int_t left = lv_obj_get_style_pad_left(ta, LV_TEXTAREA_PART_BG);
647     /*Check the right (use the font_h as general unit)*/
648     if(label_cords.x1 + cur_pos.x + font_h + right > ta_cords.x2) {
649         lv_obj_set_x(label_par, -(cur_pos.x - lv_obj_get_width(ta) + font_h + left + right));
650     }
651 
652     ext->cursor.valid_x = cur_pos.x;
653 
654 #if LV_USE_ANIMATION
655     if(ext->cursor.blink_time) {
656         /*Reset cursor blink animation*/
657         lv_anim_path_t path;
658         lv_anim_path_init(&path);
659         lv_anim_path_set_cb(&path, lv_anim_path_step);
660 
661         lv_anim_t a;
662         lv_anim_init(&a);
663         lv_anim_set_var(&a, ta);
664         lv_anim_set_exec_cb(&a, (lv_anim_exec_xcb_t)cursor_blink_anim);
665         lv_anim_set_time(&a, ext->cursor.blink_time);
666         lv_anim_set_playback_time(&a, ext->cursor.blink_time);
667         lv_anim_set_values(&a, 1, 0);
668         lv_anim_set_path(&a, &path);
669         lv_anim_set_repeat_count(&a, LV_ANIM_REPEAT_INFINITE);
670         lv_anim_start(&a);
671     }
672 #endif
673 
674     refr_cursor_area(ta);
675 }
676 
677 /**
678  * Hide/Unhide the cursor.
679  * @param ta pointer to a text area object
680  * @param hide: true: hide the cursor
681  */
lv_textarea_set_cursor_hidden(lv_obj_t * ta,bool hide)682 void lv_textarea_set_cursor_hidden(lv_obj_t * ta, bool hide)
683 {
684     LV_ASSERT_OBJ(ta, LV_OBJX_NAME);
685 
686     lv_textarea_ext_t * ext = lv_obj_get_ext_attr(ta);
687 
688     ext->cursor.hidden = hide ? 1 : 0;
689 
690     refr_cursor_area(ta);
691 }
692 
693 /**
694  * Enable/Disable the positioning of the the cursor by clicking the text on the text area.
695  * @param ta pointer to a text area object
696  * @param en true: enable click positions; false: disable
697  */
lv_textarea_set_cursor_click_pos(lv_obj_t * ta,bool en)698 void lv_textarea_set_cursor_click_pos(lv_obj_t * ta, bool en)
699 {
700     LV_ASSERT_OBJ(ta, LV_OBJX_NAME);
701 
702     lv_textarea_ext_t * ext     = lv_obj_get_ext_attr(ta);
703     ext->cursor.click_pos = en ? 1 : 0;
704 }
705 
706 /**
707  * Enable/Disable password mode
708  * @param ta pointer to a text area object
709  * @param en true: enable, false: disable
710  */
lv_textarea_set_pwd_mode(lv_obj_t * ta,bool en)711 void lv_textarea_set_pwd_mode(lv_obj_t * ta, bool en)
712 {
713     LV_ASSERT_OBJ(ta, LV_OBJX_NAME);
714 
715     lv_textarea_ext_t * ext = lv_obj_get_ext_attr(ta);
716     if(ext->pwd_mode == en) return;
717 
718     /*Pwd mode is now enabled*/
719     if(ext->pwd_mode == 0 && en != false) {
720         char * txt   = lv_label_get_text(ext->label);
721         size_t len = strlen(txt);
722         ext->pwd_tmp = lv_mem_alloc(len + 1);
723         LV_ASSERT_MEM(ext->pwd_tmp);
724         if(ext->pwd_tmp == NULL) return;
725 
726         strcpy(ext->pwd_tmp, txt);
727 
728         pwd_char_hider(ta);
729 
730         lv_textarea_clear_selection(ta);
731     }
732     /*Pwd mode is now disabled*/
733     else if(ext->pwd_mode == 1 && en == false) {
734         lv_textarea_clear_selection(ta);
735         lv_label_set_text(ext->label, ext->pwd_tmp);
736         lv_mem_free(ext->pwd_tmp);
737         ext->pwd_tmp = NULL;
738     }
739 
740     ext->pwd_mode = en == false ? 0 : 1;
741 
742     refr_cursor_area(ta);
743 }
744 
745 /**
746  * Configure the text area to one line or back to normal
747  * @param ta pointer to a Text area object
748  * @param en true: one line, false: normal
749  */
lv_textarea_set_one_line(lv_obj_t * ta,bool en)750 void lv_textarea_set_one_line(lv_obj_t * ta, bool en)
751 {
752     LV_ASSERT_OBJ(ta, LV_OBJX_NAME);
753 
754     lv_textarea_ext_t * ext = lv_obj_get_ext_attr(ta);
755     if(ext->one_line == en) return;
756     lv_label_align_t old_align = lv_label_get_align(ext->label);
757 
758     if(en) {
759         lv_style_int_t top = lv_obj_get_style_pad_top(ta, LV_TEXTAREA_PART_BG);
760         lv_style_int_t bottom = lv_obj_get_style_pad_bottom(ta, LV_TEXTAREA_PART_BG);
761         lv_style_int_t left = lv_obj_get_style_pad_left(ta, LV_TEXTAREA_PART_BG);
762         const lv_font_t * font = lv_obj_get_style_text_font(ta, LV_TEXTAREA_PART_BG);
763         lv_coord_t font_h              = lv_font_get_line_height(font);
764 
765         ext->one_line = 1;
766         lv_page_set_scrollable_fit2(ta, LV_FIT_MAX, LV_FIT_PARENT);
767         lv_obj_set_height(ta, font_h + top + bottom);
768         lv_label_set_long_mode(ext->label, LV_LABEL_LONG_EXPAND);
769         lv_obj_set_pos(lv_page_get_scrollable(ta), left, top);
770     }
771     else {
772         lv_style_int_t top = lv_obj_get_style_pad_top(ta, LV_TEXTAREA_PART_BG);
773         lv_style_int_t left = lv_obj_get_style_pad_left(ta, LV_TEXTAREA_PART_BG);
774         ext->one_line = 0;
775         lv_page_set_scrollable_fit2(ta, LV_FIT_PARENT, LV_FIT_TIGHT);
776         lv_label_set_long_mode(ext->label, LV_LABEL_LONG_BREAK);
777 
778         lv_obj_set_height(ta, LV_TEXTAREA_DEF_HEIGHT);
779         lv_obj_set_pos(lv_page_get_scrollable(ta), left, top);
780     }
781 
782     /* `refr_cursor_area` is called at the end of lv_ta_set_text_align */
783     lv_textarea_set_text_align(ta, old_align);
784 }
785 
786 /**
787  * Set the alignment of the text area.
788  * In one line mode the text can be scrolled only with `LV_LABEL_ALIGN_LEFT`.
789  * This function should be called if the size of text area changes.
790  * @param ta pointer to a text are object
791  * @param align the desired alignment from `lv_label_align_t`. (LV_LABEL_ALIGN_LEFT/CENTER/RIGHT)
792  */
lv_textarea_set_text_align(lv_obj_t * ta,lv_label_align_t align)793 void lv_textarea_set_text_align(lv_obj_t * ta, lv_label_align_t align)
794 {
795     LV_ASSERT_OBJ(ta, LV_OBJX_NAME);
796 
797     lv_textarea_ext_t * ext = lv_obj_get_ext_attr(ta);
798     lv_obj_t * label  = lv_textarea_get_label(ta);
799     if(!ext->one_line) {
800         lv_label_set_align(label, align);
801     }
802     else {
803         /*Normal left align. Just let the text expand*/
804         if(align == LV_LABEL_ALIGN_LEFT) {
805             lv_label_set_long_mode(label, LV_LABEL_LONG_EXPAND);
806             lv_page_set_scrollable_fit2(ta, LV_FIT_MAX, LV_FIT_PARENT);
807             lv_label_set_align(label, align);
808         }
809         /*Else use fix label width equal to the Text area width*/
810         else {
811             lv_label_set_long_mode(label, LV_LABEL_LONG_CROP);
812             lv_obj_set_width(label, lv_page_get_width_fit(ta));
813             lv_label_set_align(label, align);
814             lv_page_set_scrollable_fit2(ta, LV_FIT_PARENT, LV_FIT_PARENT);
815         }
816     }
817 
818     refr_cursor_area(ta);
819 }
820 
821 /**
822  * Set a list of characters. Only these characters will be accepted by the text area
823  * @param ta pointer to  Text Area
824  * @param list list of characters. Only the pointer is saved. E.g. "+-.,0123456789"
825  */
lv_textarea_set_accepted_chars(lv_obj_t * ta,const char * list)826 void lv_textarea_set_accepted_chars(lv_obj_t * ta, const char * list)
827 {
828     LV_ASSERT_OBJ(ta, LV_OBJX_NAME);
829 
830     lv_textarea_ext_t * ext = lv_obj_get_ext_attr(ta);
831 
832     ext->accapted_chars = list;
833 }
834 
835 /**
836  * Set max length of a Text Area.
837  * @param ta pointer to  Text Area
838  * @param num the maximal number of characters can be added (`lv_textarea_set_text` ignores it)
839  */
lv_textarea_set_max_length(lv_obj_t * ta,uint32_t num)840 void lv_textarea_set_max_length(lv_obj_t * ta, uint32_t num)
841 {
842     LV_ASSERT_OBJ(ta, LV_OBJX_NAME);
843 
844     lv_textarea_ext_t * ext = lv_obj_get_ext_attr(ta);
845 
846     ext->max_length = num;
847 }
848 
849 /**
850  * In `LV_EVENT_INSERT` the text which planned to be inserted can be replaced by an other text.
851  * It can be used to add automatic formatting to the text area.
852  * @param ta pointer to a text area.
853  * @param txt pointer to a new string to insert. If `""` no text will be added.
854  *            The variable must be live after the `event_cb` exists. (Should be `global` or
855  * `static`)
856  */
lv_textarea_set_insert_replace(lv_obj_t * ta,const char * txt)857 void lv_textarea_set_insert_replace(lv_obj_t * ta, const char * txt)
858 {
859     LV_ASSERT_OBJ(ta, LV_OBJX_NAME);
860 
861     (void)ta; /*Unused*/
862     ta_insert_replace = txt;
863 }
864 
865 /**
866  * Enable/disable selection mode.
867  * @param ta pointer to a text area object
868  * @param en true or false to enable/disable selection mode
869  */
lv_textarea_set_text_sel(lv_obj_t * ta,bool en)870 void lv_textarea_set_text_sel(lv_obj_t * ta, bool en)
871 {
872     LV_ASSERT_OBJ(ta, LV_OBJX_NAME);
873 
874 #if LV_LABEL_TEXT_SEL
875     lv_textarea_ext_t * ext = lv_obj_get_ext_attr(ta);
876 
877     ext->text_sel_en = en;
878 
879     if(!en) lv_textarea_clear_selection(ta);
880 #else
881     (void)ta; /*Unused*/
882     (void)en; /*Unused*/
883 #endif
884 }
885 
886 /**
887  * Set how long show the password before changing it to '*'
888  * @param ta pointer to Text area
889  * @param time show time in milliseconds. 0: hide immediately.
890  */
lv_textarea_set_pwd_show_time(lv_obj_t * ta,uint16_t time)891 void lv_textarea_set_pwd_show_time(lv_obj_t * ta, uint16_t time)
892 {
893     LV_ASSERT_OBJ(ta, LV_OBJX_NAME);
894 
895 #if LV_USE_ANIMATION == 0
896     time = 0;
897 #endif
898 
899     lv_textarea_ext_t * ext  = lv_obj_get_ext_attr(ta);
900     ext->pwd_show_time = time;
901 }
902 
903 /**
904  * Set cursor blink animation time
905  * @param ta pointer to Text area
906  * @param time blink period. 0: disable blinking
907  */
lv_textarea_set_cursor_blink_time(lv_obj_t * ta,uint16_t time)908 void lv_textarea_set_cursor_blink_time(lv_obj_t * ta, uint16_t time)
909 {
910     LV_ASSERT_OBJ(ta, LV_OBJX_NAME);
911 
912 #if LV_USE_ANIMATION == 0
913     time = 0;
914 #endif
915 
916     lv_textarea_ext_t * ext      = lv_obj_get_ext_attr(ta);
917     ext->cursor.blink_time = time;
918 
919 #if LV_USE_ANIMATION
920     if(ext->cursor.blink_time) {
921         /*Reset cursor blink animation*/
922         lv_anim_path_t path;
923         lv_anim_path_init(&path);
924         lv_anim_path_set_cb(&path, lv_anim_path_step);
925 
926         lv_anim_t a;
927         lv_anim_init(&a);
928         lv_anim_set_var(&a, ta);
929         lv_anim_set_exec_cb(&a, (lv_anim_exec_xcb_t)cursor_blink_anim);
930         lv_anim_set_time(&a, ext->cursor.blink_time);
931         lv_anim_set_playback_time(&a, ext->cursor.blink_time);
932         lv_anim_set_values(&a, 1, 0);
933         lv_anim_set_path(&a, &path);
934         lv_anim_start(&a);
935     }
936     else {
937         lv_anim_del(ta, (lv_anim_exec_xcb_t)cursor_blink_anim);
938         ext->cursor.state = 1;
939     }
940 #else
941     ext->cursor.state = 1;
942 #endif
943 }
944 
945 /*=====================
946  * Getter functions
947  *====================*/
948 
949 /**
950  * Get the text of a text area. In password mode it gives the real text (not '*'s).
951  * @param ta pointer to a text area object
952  * @return pointer to the text
953  */
lv_textarea_get_text(const lv_obj_t * ta)954 const char * lv_textarea_get_text(const lv_obj_t * ta)
955 {
956     LV_ASSERT_OBJ(ta, LV_OBJX_NAME);
957 
958     lv_textarea_ext_t * ext = lv_obj_get_ext_attr(ta);
959 
960     const char * txt;
961     if(ext->pwd_mode == 0) {
962         txt = lv_label_get_text(ext->label);
963     }
964     else {
965         txt = ext->pwd_tmp;
966     }
967 
968     return txt;
969 }
970 
971 /**
972  * Get the placeholder_txt text of a text area
973  * @param ta pointer to a text area object
974  * @return pointer to the text
975  */
lv_textarea_get_placeholder_text(lv_obj_t * ta)976 const char * lv_textarea_get_placeholder_text(lv_obj_t * ta)
977 {
978     LV_ASSERT_OBJ(ta, LV_OBJX_NAME);
979 
980     lv_textarea_ext_t * ext = lv_obj_get_ext_attr(ta);
981     if(ext->placeholder_txt) return ext->placeholder_txt;
982     else return "";
983 }
984 
985 /**
986  * Get the label of a text area
987  * @param ta pointer to a text area object
988  * @return pointer to the label object
989  */
lv_textarea_get_label(const lv_obj_t * ta)990 lv_obj_t * lv_textarea_get_label(const lv_obj_t * ta)
991 {
992     LV_ASSERT_OBJ(ta, LV_OBJX_NAME);
993 
994     lv_textarea_ext_t * ext = lv_obj_get_ext_attr(ta);
995     return ext->label;
996 }
997 
998 /**
999  * Get the current cursor position in character index
1000  * @param ta pointer to a text area object
1001  * @return the cursor position
1002  */
lv_textarea_get_cursor_pos(const lv_obj_t * ta)1003 uint32_t lv_textarea_get_cursor_pos(const lv_obj_t * ta)
1004 {
1005     LV_ASSERT_OBJ(ta, LV_OBJX_NAME);
1006 
1007     lv_textarea_ext_t * ext = lv_obj_get_ext_attr(ta);
1008     return ext->cursor.pos;
1009 }
1010 
1011 /**
1012  * Get whether the cursor is hidden or not
1013  * @param ta pointer to a text area object
1014  * @return true: the cursor is hidden
1015  */
lv_textarea_get_cursor_hidden(const lv_obj_t * ta)1016 bool lv_textarea_get_cursor_hidden(const lv_obj_t * ta)
1017 {
1018     LV_ASSERT_OBJ(ta, LV_OBJX_NAME);
1019 
1020     lv_textarea_ext_t * ext = lv_obj_get_ext_attr(ta);
1021     return ext->cursor.hidden ? true : false;
1022 }
1023 
1024 /**
1025  * Get whether the cursor click positioning is enabled or not.
1026  * @param ta pointer to a text area object
1027  * @return true: enable click positions; false: disable
1028  */
lv_textarea_get_cursor_click_pos(lv_obj_t * ta)1029 bool lv_textarea_get_cursor_click_pos(lv_obj_t * ta)
1030 {
1031     LV_ASSERT_OBJ(ta, LV_OBJX_NAME);
1032 
1033     lv_textarea_ext_t * ext = lv_obj_get_ext_attr(ta);
1034     return ext->cursor.click_pos ? true : false;
1035 }
1036 
1037 /**
1038  * Get the password mode attribute
1039  * @param ta pointer to a text area object
1040  * @return true: password mode is enabled, false: disabled
1041  */
lv_textarea_get_pwd_mode(const lv_obj_t * ta)1042 bool lv_textarea_get_pwd_mode(const lv_obj_t * ta)
1043 {
1044     LV_ASSERT_OBJ(ta, LV_OBJX_NAME);
1045 
1046     lv_textarea_ext_t * ext = lv_obj_get_ext_attr(ta);
1047     return ext->pwd_mode == 0 ? false : true;
1048 }
1049 
1050 /**
1051  * Get the one line configuration attribute
1052  * @param ta pointer to a text area object
1053  * @return true: one line configuration is enabled, false: disabled
1054  */
lv_textarea_get_one_line(const lv_obj_t * ta)1055 bool lv_textarea_get_one_line(const lv_obj_t * ta)
1056 {
1057     LV_ASSERT_OBJ(ta, LV_OBJX_NAME);
1058 
1059     lv_textarea_ext_t * ext = lv_obj_get_ext_attr(ta);
1060     return ext->one_line == 0 ? false : true;
1061 }
1062 
1063 /**
1064  * Get a list of accepted characters.
1065  * @param ta pointer to  Text Area
1066  * @return list of accented characters.
1067  */
lv_textarea_get_accepted_chars(lv_obj_t * ta)1068 const char * lv_textarea_get_accepted_chars(lv_obj_t * ta)
1069 {
1070     LV_ASSERT_OBJ(ta, LV_OBJX_NAME);
1071 
1072     lv_textarea_ext_t * ext = lv_obj_get_ext_attr(ta);
1073 
1074     return ext->accapted_chars;
1075 }
1076 
1077 /**
1078  * Set max length of a Text Area.
1079  * @param ta pointer to  Text Area
1080  * @return the maximal number of characters to be add
1081  */
lv_textarea_get_max_length(lv_obj_t * ta)1082 uint32_t lv_textarea_get_max_length(lv_obj_t * ta)
1083 {
1084     LV_ASSERT_OBJ(ta, LV_OBJX_NAME);
1085 
1086     lv_textarea_ext_t * ext = lv_obj_get_ext_attr(ta);
1087     return ext->max_length;
1088 }
1089 
1090 /**
1091  * Find whether text is selected or not.
1092  * @param ta Text area object
1093  * @return whether text is selected or not
1094  */
lv_textarea_text_is_selected(const lv_obj_t * ta)1095 bool lv_textarea_text_is_selected(const lv_obj_t * ta)
1096 {
1097     LV_ASSERT_OBJ(ta, LV_OBJX_NAME);
1098 
1099 #if LV_LABEL_TEXT_SEL
1100     lv_textarea_ext_t * ext = lv_obj_get_ext_attr(ta);
1101 
1102     if((lv_label_get_text_sel_start(ext->label) == LV_DRAW_LABEL_NO_TXT_SEL ||
1103         lv_label_get_text_sel_end(ext->label) == LV_DRAW_LABEL_NO_TXT_SEL)) {
1104         return true;
1105     }
1106     else {
1107         return false;
1108     }
1109 #else
1110     (void)ta; /*Unused*/
1111     return false;
1112 #endif
1113 }
1114 
1115 /**
1116  * Find whether selection mode is enabled.
1117  * @param ta pointer to a text area object
1118  * @return true: selection mode is enabled, false: disabled
1119  */
lv_textarea_get_text_sel_en(lv_obj_t * ta)1120 bool lv_textarea_get_text_sel_en(lv_obj_t * ta)
1121 {
1122     LV_ASSERT_OBJ(ta, LV_OBJX_NAME);
1123 
1124 #if LV_LABEL_TEXT_SEL
1125     lv_textarea_ext_t * ext = lv_obj_get_ext_attr(ta);
1126     return ext->text_sel_en;
1127 #else
1128     (void)ta; /*Unused*/
1129     return false;
1130 #endif
1131 }
1132 
1133 /**
1134  * Set how long show the password before changing it to '*'
1135  * @param ta pointer to Text area
1136  * @return show time in milliseconds. 0: hide immediately.
1137  */
lv_textarea_get_pwd_show_time(lv_obj_t * ta)1138 uint16_t lv_textarea_get_pwd_show_time(lv_obj_t * ta)
1139 {
1140     LV_ASSERT_OBJ(ta, LV_OBJX_NAME);
1141 
1142     lv_textarea_ext_t * ext = lv_obj_get_ext_attr(ta);
1143 
1144     return ext->pwd_show_time;
1145 }
1146 
1147 /**
1148  * Set cursor blink animation time
1149  * @param ta pointer to Text area
1150  * @return time blink period. 0: disable blinking
1151  */
lv_textarea_get_cursor_blink_time(lv_obj_t * ta)1152 uint16_t lv_textarea_get_cursor_blink_time(lv_obj_t * ta)
1153 {
1154     LV_ASSERT_OBJ(ta, LV_OBJX_NAME);
1155 
1156     lv_textarea_ext_t * ext = lv_obj_get_ext_attr(ta);
1157     return ext->cursor.blink_time;
1158 }
1159 
1160 /*=====================
1161  * Other functions
1162  *====================*/
1163 
1164 /**
1165  * Clear the selection on the text area.
1166  * @param ta Text area object
1167  */
lv_textarea_clear_selection(lv_obj_t * ta)1168 void lv_textarea_clear_selection(lv_obj_t * ta)
1169 {
1170     LV_ASSERT_OBJ(ta, LV_OBJX_NAME);
1171 
1172 #if LV_LABEL_TEXT_SEL
1173     lv_textarea_ext_t * ext = lv_obj_get_ext_attr(ta);
1174 
1175     if(lv_label_get_text_sel_start(ext->label) != LV_DRAW_LABEL_NO_TXT_SEL ||
1176        lv_label_get_text_sel_end(ext->label) != LV_DRAW_LABEL_NO_TXT_SEL) {
1177         lv_label_set_text_sel_start(ext->label, LV_DRAW_LABEL_NO_TXT_SEL);
1178         lv_label_set_text_sel_end(ext->label, LV_DRAW_LABEL_NO_TXT_SEL);
1179     }
1180 #else
1181     (void)ta; /*Unused*/
1182 #endif
1183 }
1184 
1185 /**
1186  * Move the cursor one character right
1187  * @param ta pointer to a text area object
1188  */
lv_textarea_cursor_right(lv_obj_t * ta)1189 void lv_textarea_cursor_right(lv_obj_t * ta)
1190 {
1191     LV_ASSERT_OBJ(ta, LV_OBJX_NAME);
1192 
1193     uint32_t cp = lv_textarea_get_cursor_pos(ta);
1194     cp++;
1195     lv_textarea_set_cursor_pos(ta, cp);
1196 }
1197 
1198 /**
1199  * Move the cursor one character left
1200  * @param ta pointer to a text area object
1201  */
lv_textarea_cursor_left(lv_obj_t * ta)1202 void lv_textarea_cursor_left(lv_obj_t * ta)
1203 {
1204     LV_ASSERT_OBJ(ta, LV_OBJX_NAME);
1205 
1206     uint32_t cp = lv_textarea_get_cursor_pos(ta);
1207     if(cp > 0) {
1208         cp--;
1209         lv_textarea_set_cursor_pos(ta, cp);
1210     }
1211 }
1212 
1213 /**
1214  * Move the cursor one line down
1215  * @param ta pointer to a text area object
1216  */
lv_textarea_cursor_down(lv_obj_t * ta)1217 void lv_textarea_cursor_down(lv_obj_t * ta)
1218 {
1219     LV_ASSERT_OBJ(ta, LV_OBJX_NAME);
1220 
1221     lv_textarea_ext_t * ext = lv_obj_get_ext_attr(ta);
1222     lv_point_t pos;
1223 
1224     /*Get the position of the current letter*/
1225     lv_label_get_letter_pos(ext->label, lv_textarea_get_cursor_pos(ta), &pos);
1226 
1227     /*Increment the y with one line and keep the valid x*/
1228 
1229     lv_style_int_t line_space = lv_obj_get_style_text_line_space(ta, LV_TEXTAREA_PART_BG);
1230     const lv_font_t * font = lv_obj_get_style_text_font(ta, LV_TEXTAREA_PART_BG);
1231     lv_coord_t font_h              = lv_font_get_line_height(font);
1232     pos.y += font_h + line_space + 1;
1233     pos.x = ext->cursor.valid_x;
1234 
1235     /*Do not go below the last line*/
1236     if(pos.y < lv_obj_get_height(ext->label)) {
1237         /*Get the letter index on the new cursor position and set it*/
1238         uint32_t new_cur_pos = lv_label_get_letter_on(ext->label, &pos);
1239 
1240         lv_coord_t cur_valid_x_tmp = ext->cursor.valid_x; /*Cursor position set overwrites the valid position */
1241         lv_textarea_set_cursor_pos(ta, new_cur_pos);
1242         ext->cursor.valid_x = cur_valid_x_tmp;
1243     }
1244 }
1245 
1246 /**
1247  * Move the cursor one line up
1248  * @param ta pointer to a text area object
1249  */
lv_textarea_cursor_up(lv_obj_t * ta)1250 void lv_textarea_cursor_up(lv_obj_t * ta)
1251 {
1252     LV_ASSERT_OBJ(ta, LV_OBJX_NAME);
1253 
1254     lv_textarea_ext_t * ext = lv_obj_get_ext_attr(ta);
1255     lv_point_t pos;
1256 
1257     /*Get the position of the current letter*/
1258     lv_label_get_letter_pos(ext->label, lv_textarea_get_cursor_pos(ta), &pos);
1259 
1260     /*Decrement the y with one line and keep the valid x*/
1261     lv_style_int_t line_space = lv_obj_get_style_text_line_space(ta, LV_TEXTAREA_PART_BG);
1262     const lv_font_t * font = lv_obj_get_style_text_font(ta, LV_TEXTAREA_PART_BG);
1263     lv_coord_t font_h              = lv_font_get_line_height(font);
1264     pos.y -= font_h + line_space - 1;
1265     pos.x = ext->cursor.valid_x;
1266 
1267     /*Get the letter index on the new cursor position and set it*/
1268     uint32_t new_cur_pos       = lv_label_get_letter_on(ext->label, &pos);
1269     lv_coord_t cur_valid_x_tmp = ext->cursor.valid_x; /*Cursor position set overwrites the valid position */
1270     lv_textarea_set_cursor_pos(ta, new_cur_pos);
1271     ext->cursor.valid_x = cur_valid_x_tmp;
1272 }
1273 
1274 /**********************
1275  *   STATIC FUNCTIONS
1276  **********************/
1277 
1278 /**
1279  * Handle the drawing related tasks of the text areas
1280  * @param ta pointer to an object
1281  * @param clip_area the object will be drawn only in this area
1282  * @param mode LV_DESIGN_COVER_CHK: only check if the object fully covers the 'mask_p' area
1283  *                                  (return 'true' if yes)
1284  *             LV_DESIGN_DRAW_MAIN: draw the object (always return 'true')
1285  *             LV_DESIGN_DRAW_POST: drawing after every children are drawn
1286  * @param return an element of `lv_design_res_t`
1287  */
lv_textarea_design(lv_obj_t * ta,const lv_area_t * clip_area,lv_design_mode_t mode)1288 static lv_design_res_t lv_textarea_design(lv_obj_t * ta, const lv_area_t * clip_area, lv_design_mode_t mode)
1289 {
1290     if(mode == LV_DESIGN_COVER_CHK) {
1291         /*Return false if the object is not covers the mask_p area*/
1292         return ancestor_design(ta, clip_area, mode);
1293     }
1294     else if(mode == LV_DESIGN_DRAW_MAIN) {
1295         /*Draw the object*/
1296         ancestor_design(ta, clip_area, mode);
1297 
1298     }
1299     else if(mode == LV_DESIGN_DRAW_POST) {
1300         ancestor_design(ta, clip_area, mode);
1301     }
1302     return LV_DESIGN_RES_OK;
1303 }
1304 
1305 /**
1306  * An extended scrollable design of the page. Calls the normal design function and draws a cursor.
1307  * @param scrl pointer to the scrollable part of the Text area
1308  * @param clip_area  the object will be drawn only in this area
1309  * @param mode LV_DESIGN_COVER_CHK: only check if the object fully covers the 'mask_p' area
1310  *                                  (return 'true' if yes)
1311  *             LV_DESIGN_DRAW_MAIN: draw the object (always return 'true')
1312  *             LV_DESIGN_DRAW_POST: drawing after every children are drawn
1313  * @return return true/false, depends on 'mode'
1314  */
lv_textarea_scrollable_design(lv_obj_t * scrl,const lv_area_t * clip_area,lv_design_mode_t mode)1315 static lv_design_res_t lv_textarea_scrollable_design(lv_obj_t * scrl, const lv_area_t * clip_area,
1316                                                      lv_design_mode_t mode)
1317 {
1318     if(mode == LV_DESIGN_COVER_CHK) {
1319         /*Return false if the object is not covers the mask_p area*/
1320         return scrl_design(scrl, clip_area, mode);
1321     }
1322     else if(mode == LV_DESIGN_DRAW_MAIN) {
1323         /*Draw the object*/
1324         scrl_design(scrl, clip_area, mode);
1325     }
1326     else if(mode == LV_DESIGN_DRAW_POST) {
1327         scrl_design(scrl, clip_area, mode);
1328 
1329         lv_obj_t * ta     = lv_obj_get_parent(scrl);
1330         lv_textarea_ext_t * ext = lv_obj_get_ext_attr(ta);
1331         const char * txt = lv_label_get_text(ext->label);
1332 
1333         /*Draw the place holder*/
1334         if(txt[0] == '\0' && ext->placeholder_txt && ext->placeholder_txt[0] != 0) {
1335             lv_draw_label_dsc_t ph_dsc;
1336             lv_draw_label_dsc_init(&ph_dsc);
1337             lv_obj_init_draw_label_dsc(ta, LV_TEXTAREA_PART_PLACEHOLDER, &ph_dsc);
1338             switch(lv_label_get_align(ext->label)) {
1339                 case LV_LABEL_ALIGN_CENTER:
1340                     ph_dsc.flag |= LV_TXT_FLAG_CENTER;
1341                     break;
1342                 case LV_LABEL_ALIGN_RIGHT:
1343                     ph_dsc.flag |= LV_TXT_FLAG_RIGHT;
1344                     break;
1345                 default:
1346                     break;
1347             }
1348 
1349             if(ext->one_line) ph_dsc.flag |= LV_TXT_FLAG_EXPAND;
1350 
1351             lv_draw_label(&scrl->coords, clip_area, &ph_dsc, ext->placeholder_txt, NULL);
1352         }
1353 
1354         /*Draw the cursor*/
1355 
1356         if(ext->cursor.hidden || ext->cursor.state == 0) {
1357             return LV_DESIGN_RES_OK; /*The cursor is not visible now*/
1358         }
1359 
1360         lv_draw_rect_dsc_t cur_dsc;
1361         lv_draw_rect_dsc_init(&cur_dsc);
1362         lv_obj_init_draw_rect_dsc(ta, LV_TEXTAREA_PART_CURSOR, &cur_dsc);
1363 
1364         /*Draw he cursor according to the type*/
1365         lv_area_t cur_area;
1366         lv_area_copy(&cur_area, &ext->cursor.area);
1367 
1368         cur_area.x1 += ext->label->coords.x1;
1369         cur_area.y1 += ext->label->coords.y1;
1370         cur_area.x2 += ext->label->coords.x1;
1371         cur_area.y2 += ext->label->coords.y1;
1372 
1373         lv_draw_rect(&cur_area, clip_area, &cur_dsc);
1374 
1375         char letter_buf[8] = {0};
1376         _lv_memcpy(letter_buf, &txt[ext->cursor.txt_byte_pos], _lv_txt_encoded_size(&txt[ext->cursor.txt_byte_pos]));
1377 
1378         if(cur_dsc.bg_opa == LV_OPA_COVER) {
1379             lv_style_int_t left = lv_obj_get_style_pad_left(ta, LV_TEXTAREA_PART_CURSOR);
1380             lv_style_int_t top = lv_obj_get_style_pad_top(ta, LV_TEXTAREA_PART_CURSOR);
1381             cur_area.x1 += left;
1382             cur_area.y1 += top;
1383 
1384             lv_draw_label_dsc_t cur_label_dsc;
1385             lv_draw_label_dsc_init(&cur_label_dsc);
1386             lv_obj_init_draw_label_dsc(ta, LV_TEXTAREA_PART_CURSOR, &cur_label_dsc);
1387             lv_draw_label(&cur_area, clip_area, &cur_label_dsc, letter_buf, NULL);
1388         }
1389     }
1390 
1391     return LV_DESIGN_RES_OK;
1392 }
1393 
1394 /**
1395  * Signal function of the text area
1396  * @param ta pointer to a text area object
1397  * @param sign a signal type from lv_signal_t enum
1398  * @param param pointer to a signal specific variable
1399  * @return LV_RES_OK: the object is not deleted in the function; LV_RES_INV: the object is deleted
1400  */
lv_textarea_signal(lv_obj_t * ta,lv_signal_t sign,void * param)1401 static lv_res_t lv_textarea_signal(lv_obj_t * ta, lv_signal_t sign, void * param)
1402 {
1403     lv_res_t res;
1404     if(sign == LV_SIGNAL_GET_STYLE) {
1405         lv_get_style_info_t * info = param;
1406         info->result = lv_textarea_get_style(ta, info->part);
1407         if(info->result != NULL) return LV_RES_OK;
1408         else return ancestor_signal(ta, sign, param);
1409     }
1410     else if(sign == LV_SIGNAL_GET_STATE_DSC) {
1411         return ancestor_signal(ta, sign, param);
1412     }
1413 
1414     /* Include the ancient signal function */
1415     res = ancestor_signal(ta, sign, param);
1416     if(res != LV_RES_OK) return res;
1417     if(sign == LV_SIGNAL_GET_TYPE) return lv_obj_handle_get_type_signal(param, LV_OBJX_NAME);
1418 
1419     lv_textarea_ext_t * ext = lv_obj_get_ext_attr(ta);
1420     if(sign == LV_SIGNAL_CLEANUP) {
1421         if(ext->pwd_tmp != NULL) lv_mem_free(ext->pwd_tmp);
1422         if(ext->placeholder_txt != NULL) lv_mem_free(ext->placeholder_txt);
1423 
1424         ext->pwd_tmp = NULL;
1425         ext->placeholder_txt = NULL;
1426 
1427         lv_obj_clean_style_list(ta, LV_TEXTAREA_PART_CURSOR);
1428         lv_obj_clean_style_list(ta, LV_TEXTAREA_PART_PLACEHOLDER);
1429 
1430         /* (The created label will be deleted automatically) */
1431 
1432     }
1433     else if(sign == LV_SIGNAL_STYLE_CHG) {
1434         if(ext->label) {
1435             if(ext->one_line) {
1436                 lv_style_int_t top = lv_obj_get_style_pad_top(ta, LV_TEXTAREA_PART_BG);
1437                 lv_style_int_t bottom = lv_obj_get_style_pad_bottom(ta, LV_TEXTAREA_PART_BG);
1438                 const lv_font_t * font = lv_obj_get_style_text_font(ta, LV_TEXTAREA_PART_BG);
1439 
1440                 /*In one line mode refresh the Text Area height because 'vpad' can modify it*/
1441                 lv_coord_t font_h              = lv_font_get_line_height(font);
1442                 lv_obj_set_height(ext->label, font_h);
1443                 lv_obj_set_height(ta, font_h + top + bottom);
1444             }
1445             else {
1446                 /*In not one line mode refresh the Label width because 'hpad' can modify it*/
1447                 lv_obj_set_width(ext->label, lv_page_get_width_fit(ta));
1448                 lv_obj_set_pos(ext->label, 0, 0); /*Be sure the Label is in the correct position*/
1449 
1450             }
1451             lv_label_set_text(ext->label, NULL);
1452             refr_cursor_area(ta);
1453         }
1454     }
1455     else if(sign == LV_SIGNAL_COORD_CHG) {
1456         /*Set the label width according to the text area width*/
1457         if(ext->label) {
1458             if(lv_obj_get_width(ta) != lv_area_get_width(param) || lv_obj_get_height(ta) != lv_area_get_height(param)) {
1459                 lv_obj_set_width(ext->label, lv_page_get_width_fit(ta));
1460                 lv_obj_set_pos(ext->label, 0, 0);
1461                 lv_label_set_text(ext->label, NULL); /*Refresh the label*/
1462 
1463                 refr_cursor_area(ta);
1464             }
1465         }
1466     }
1467     else if(sign == LV_SIGNAL_CONTROL) {
1468 #if LV_USE_GROUP
1469         uint32_t c = *((uint32_t *)param); /*uint32_t because can be UTF-8*/
1470         if(c == LV_KEY_RIGHT)
1471             lv_textarea_cursor_right(ta);
1472         else if(c == LV_KEY_LEFT)
1473             lv_textarea_cursor_left(ta);
1474         else if(c == LV_KEY_UP)
1475             lv_textarea_cursor_up(ta);
1476         else if(c == LV_KEY_DOWN)
1477             lv_textarea_cursor_down(ta);
1478         else if(c == LV_KEY_BACKSPACE)
1479             lv_textarea_del_char(ta);
1480         else if(c == LV_KEY_DEL)
1481             lv_textarea_del_char_forward(ta);
1482         else if(c == LV_KEY_HOME)
1483             lv_textarea_set_cursor_pos(ta, 0);
1484         else if(c == LV_KEY_END)
1485             lv_textarea_set_cursor_pos(ta, LV_TEXTAREA_CURSOR_LAST);
1486         else if(c == LV_KEY_ENTER && lv_textarea_get_one_line(ta))
1487             lv_event_send(ta, LV_EVENT_APPLY, NULL);
1488         else {
1489             lv_textarea_add_char(ta, c);
1490         }
1491 #endif
1492     }
1493     else if(sign == LV_SIGNAL_GET_EDITABLE) {
1494 #if LV_USE_GROUP
1495         bool * editable = (bool *)param;
1496         *editable       = true;
1497 #endif
1498     }
1499     else if(sign == LV_SIGNAL_PRESSED || sign == LV_SIGNAL_PRESSING || sign == LV_SIGNAL_PRESS_LOST ||
1500             sign == LV_SIGNAL_RELEASED) {
1501         update_cursor_position_on_click(ta, sign, (lv_indev_t *)param);
1502     }
1503     return res;
1504 }
1505 
1506 /**
1507  * Signal function of the scrollable part of the text area
1508  * @param scrl pointer to scrollable part of a text area object
1509  * @param sign a signal type from lv_signal_t enum
1510  * @param param pointer to a signal specific variable
1511  * @return LV_RES_OK: the object is not deleted in the function; LV_RES_INV: the object is deleted
1512  */
lv_textarea_scrollable_signal(lv_obj_t * scrl,lv_signal_t sign,void * param)1513 static lv_res_t lv_textarea_scrollable_signal(lv_obj_t * scrl, lv_signal_t sign, void * param)
1514 {
1515     lv_res_t res;
1516 
1517     /* Include the ancient signal function */
1518     res = scrl_signal(scrl, sign, param);
1519 
1520     if(res != LV_RES_OK) return res;
1521     if(sign == LV_SIGNAL_GET_TYPE) return lv_obj_handle_get_type_signal(param, "");
1522 
1523     lv_obj_t * ta     = lv_obj_get_parent(scrl);
1524     lv_textarea_ext_t * ext = lv_obj_get_ext_attr(ta);
1525 
1526     if(sign == LV_SIGNAL_REFR_EXT_DRAW_PAD) {
1527         /*Set ext. size because the cursor might be out of this object*/
1528         lv_style_int_t line_space = lv_obj_get_style_text_line_space(ta, LV_TEXTAREA_PART_BG);
1529         const lv_font_t * font = lv_obj_get_style_text_font(ta, LV_TEXTAREA_PART_BG);
1530         lv_coord_t font_h              = lv_font_get_line_height(font);
1531         scrl->ext_draw_pad             = LV_MATH_MAX(scrl->ext_draw_pad, line_space + font_h);
1532     }
1533     else if(sign == LV_SIGNAL_COORD_CHG) {
1534         /*Set the label width according to the text area width*/
1535         if(ext->label) {
1536             if(lv_obj_get_width(scrl) != lv_area_get_width(param) ||
1537                lv_obj_get_height(scrl) != lv_area_get_height(param)) {
1538 
1539                 lv_obj_set_width(ext->label, lv_page_get_width_fit(ta));
1540                 lv_obj_set_pos(ext->label, 0, 0);
1541 
1542                 lv_label_set_text(ext->label, NULL); /*Refresh the label*/
1543                 refr_cursor_area(ta);
1544             }
1545 
1546         }
1547 
1548     }
1549     else if(sign == LV_SIGNAL_PRESSING || sign == LV_SIGNAL_PRESSED || sign == LV_SIGNAL_PRESS_LOST ||
1550             sign == LV_SIGNAL_RELEASED) {
1551         update_cursor_position_on_click(ta, sign, (lv_indev_t *)param);
1552     }
1553 
1554     return res;
1555 }
1556 
1557 /**
1558  * Get the style descriptor of a part of the object
1559  * @param page pointer the object
1560  * @param part the part from `lv_textarea_part_t`. (LV_TEXTAREA_PART_...)
1561  * @return pointer to the style descriptor of the specified part
1562  */
lv_textarea_get_style(lv_obj_t * ta,uint8_t part)1563 static lv_style_list_t * lv_textarea_get_style(lv_obj_t * ta, uint8_t part)
1564 {
1565     LV_ASSERT_OBJ(ta, LV_OBJX_NAME);
1566 
1567     lv_textarea_ext_t * ext = lv_obj_get_ext_attr(ta);
1568     lv_style_list_t * style_dsc_p;
1569 
1570     switch(part) {
1571         case LV_TEXTAREA_PART_BG:
1572             style_dsc_p = &ta->style_list;
1573             break;
1574         case LV_TEXTAREA_PART_SCROLLBAR:
1575             style_dsc_p = &ext->page.scrlbar.style;
1576             break;
1577         case LV_TEXTAREA_PART_CURSOR:
1578             style_dsc_p = &ext->cursor.style;
1579             break;
1580 #if LV_USE_ANIMATION
1581         case LV_TEXTAREA_PART_EDGE_FLASH:
1582             style_dsc_p = &ext->page.edge_flash.style;
1583             break;
1584 #endif
1585         case LV_TEXTAREA_PART_PLACEHOLDER:
1586             style_dsc_p = &ext->style_placeholder;
1587             break;
1588         default:
1589             style_dsc_p = NULL;
1590     }
1591 
1592     return style_dsc_p;
1593 }
1594 
1595 #if LV_USE_ANIMATION
1596 
1597 /**
1598  * Called to blink the cursor
1599  * @param ta pointer to a text area
1600  * @param hide 1: hide the cursor, 0: show it
1601  */
cursor_blink_anim(lv_obj_t * ta,lv_anim_value_t show)1602 static void cursor_blink_anim(lv_obj_t * ta, lv_anim_value_t show)
1603 {
1604     lv_textarea_ext_t * ext = lv_obj_get_ext_attr(ta);
1605     if(show != ext->cursor.state) {
1606         ext->cursor.state = show == 0 ? 0 : 1;
1607         if(ext->cursor.hidden == 0) {
1608             lv_area_t area_tmp;
1609             lv_area_copy(&area_tmp, &ext->cursor.area);
1610             area_tmp.x1 += ext->label->coords.x1;
1611             area_tmp.y1 += ext->label->coords.y1;
1612             area_tmp.x2 += ext->label->coords.x1;
1613             area_tmp.y2 += ext->label->coords.y1;
1614             lv_obj_invalidate_area(ta, &area_tmp);
1615         }
1616     }
1617 }
1618 
1619 /**
1620  * Dummy function to animate char hiding in pwd mode.
1621  * Does nothing, but a function is required in car hiding anim.
1622  * (pwd_char_hider callback do the real job)
1623  * @param ta unused
1624  * @param x unused
1625  */
pwd_char_hider_anim(lv_obj_t * ta,lv_anim_value_t x)1626 static void pwd_char_hider_anim(lv_obj_t * ta, lv_anim_value_t x)
1627 {
1628     (void)ta;
1629     (void)x;
1630 }
1631 
1632 /**
1633  * Call when an animation is ready to convert all characters to '*'
1634  * @param a pointer to the animation
1635  */
pwd_char_hider_anim_ready(lv_anim_t * a)1636 static void pwd_char_hider_anim_ready(lv_anim_t * a)
1637 {
1638     lv_obj_t * ta = a->var;
1639     pwd_char_hider(ta);
1640 }
1641 #endif
1642 
1643 /**
1644  * Hide all characters (convert them to '*')
1645  * @param ta: pointer to text area object
1646  */
pwd_char_hider(lv_obj_t * ta)1647 static void pwd_char_hider(lv_obj_t * ta)
1648 {
1649     lv_textarea_ext_t * ext = lv_obj_get_ext_attr(ta);
1650     if(ext->pwd_mode != 0) {
1651         char * txt  = lv_label_get_text(ext->label);
1652         int32_t enc_len = _lv_txt_get_encoded_length(txt);
1653         if(enc_len == 0) return;
1654 
1655         /*If the textarea's font has "bullet" character use it else fallback to "*"*/
1656         const lv_font_t * font = lv_obj_get_style_text_font(ta, LV_TEXTAREA_PART_BG);
1657         lv_font_glyph_dsc_t g;
1658         bool has_bullet;
1659         has_bullet = lv_font_get_glyph_dsc(font, &g, LV_TEXTAREA_PWD_BULLET_UNICODE, 0);
1660         const char * bullet;
1661         if(has_bullet) bullet = LV_SYMBOL_BULLET;
1662         else bullet = "*";
1663 
1664         size_t bullet_len = strlen(bullet);
1665         char * txt_tmp = _lv_mem_buf_get(enc_len * bullet_len + 1);
1666         int32_t i;
1667         for(i = 0; i < enc_len; i++) {
1668             _lv_memcpy(&txt_tmp[i * bullet_len], bullet, bullet_len);
1669         }
1670 
1671         txt_tmp[i * bullet_len] = '\0';
1672 
1673         lv_label_set_text(ext->label, txt_tmp);
1674         _lv_mem_buf_release(txt_tmp);
1675     }
1676 }
1677 
1678 /**
1679  * Test an unicode character if it is accepted or not. Checks max length and accepted char list.
1680  * @param ta pointer to a test area object
1681  * @param c an unicode character
1682  * @return true: accepted; false: rejected
1683  */
char_is_accepted(lv_obj_t * ta,uint32_t c)1684 static bool char_is_accepted(lv_obj_t * ta, uint32_t c)
1685 {
1686     lv_textarea_ext_t * ext = lv_obj_get_ext_attr(ta);
1687 
1688     /*If no restriction accept it*/
1689     if(ext->accapted_chars == NULL && ext->max_length == 0) return true;
1690 
1691     /*Too many characters?*/
1692     if(ext->max_length > 0 && _lv_txt_get_encoded_length(lv_textarea_get_text(ta)) >= ext->max_length) {
1693         return false;
1694     }
1695 
1696     /*Accepted character?*/
1697     if(ext->accapted_chars) {
1698         uint32_t i = 0;
1699 
1700         while(ext->accapted_chars[i] != '\0') {
1701             uint32_t a = _lv_txt_encoded_next(ext->accapted_chars, &i);
1702             if(a == c) return true; /*Accepted*/
1703         }
1704 
1705         return false; /*The character wasn't in the list*/
1706     }
1707     else {
1708         return true; /*If the accepted char list in not specified the accept the character*/
1709     }
1710 }
1711 
1712 
refr_cursor_area(lv_obj_t * ta)1713 static void refr_cursor_area(lv_obj_t * ta)
1714 {
1715     lv_textarea_ext_t * ext = lv_obj_get_ext_attr(ta);
1716 
1717     const lv_font_t * font = lv_obj_get_style_text_font(ta, LV_TEXTAREA_PART_BG);
1718     lv_style_int_t line_space = lv_obj_get_style_text_line_space(ta, LV_TEXTAREA_PART_BG);
1719 
1720     uint32_t cur_pos = lv_textarea_get_cursor_pos(ta);
1721     const char * txt = lv_label_get_text(ext->label);
1722 
1723     uint32_t byte_pos;
1724     byte_pos = _lv_txt_encoded_get_byte_id(txt, cur_pos);
1725 
1726     uint32_t letter = _lv_txt_encoded_next(&txt[byte_pos], NULL);
1727 
1728     lv_coord_t letter_h = lv_font_get_line_height(font);
1729 
1730     /*Set letter_w (set not 0 on non printable but valid chars)*/
1731     lv_coord_t letter_w;
1732     if(letter == '\0' || letter == '\n' || letter == '\r') {
1733         letter_w = lv_font_get_glyph_width(font, ' ', '\0');
1734     }
1735     else {
1736         /*`letter_next` parameter is '\0' to ignore kerning*/
1737         letter_w = lv_font_get_glyph_width(font, letter, '\0');
1738     }
1739 
1740     lv_point_t letter_pos;
1741     lv_label_get_letter_pos(ext->label, cur_pos, &letter_pos);
1742 
1743     /*If the cursor is out of the text (most right) draw it to the next line*/
1744     if(letter_pos.x + ext->label->coords.x1 + letter_w > ext->label->coords.x2 && ext->one_line == 0 &&
1745        lv_label_get_align(ext->label) != LV_LABEL_ALIGN_RIGHT) {
1746         letter_pos.x = 0;
1747         letter_pos.y += letter_h + line_space;
1748 
1749         if(letter != '\0') {
1750             byte_pos += _lv_txt_encoded_size(&txt[byte_pos]);
1751             letter = _lv_txt_encoded_next(&txt[byte_pos], NULL);
1752         }
1753 
1754         if(letter == '\0' || letter == '\n' || letter == '\r') {
1755             letter_w = lv_font_get_glyph_width(font, ' ', '\0');
1756         }
1757         else {
1758             letter_w = lv_font_get_glyph_width(font, letter, '\0');
1759         }
1760     }
1761 
1762     /*Save the byte position. It is required to draw `LV_CURSOR_BLOCK`*/
1763     ext->cursor.txt_byte_pos = byte_pos;
1764 
1765     /*Calculate the cursor according to its type*/
1766     lv_style_int_t top = lv_obj_get_style_pad_top(ta, LV_TEXTAREA_PART_CURSOR);
1767     lv_style_int_t bottom = lv_obj_get_style_pad_bottom(ta, LV_TEXTAREA_PART_CURSOR);
1768     lv_style_int_t left = lv_obj_get_style_pad_left(ta, LV_TEXTAREA_PART_CURSOR);
1769     lv_style_int_t right = lv_obj_get_style_pad_right(ta, LV_TEXTAREA_PART_CURSOR);
1770 
1771     lv_area_t cur_area;
1772     cur_area.x1 = letter_pos.x - left;
1773     cur_area.y1 = letter_pos.y - top;
1774     cur_area.x2 = letter_pos.x + right + letter_w;
1775     cur_area.y2 = letter_pos.y + bottom + letter_h;
1776 
1777     /*Save the new area*/
1778     lv_area_t area_tmp;
1779     lv_area_copy(&area_tmp, &ext->cursor.area);
1780     area_tmp.x1 += ext->label->coords.x1;
1781     area_tmp.y1 += ext->label->coords.y1;
1782     area_tmp.x2 += ext->label->coords.x1;
1783     area_tmp.y2 += ext->label->coords.y1;
1784     lv_obj_invalidate_area(ta, &area_tmp);
1785 
1786     lv_area_copy(&ext->cursor.area, &cur_area);
1787 
1788     lv_area_copy(&area_tmp, &ext->cursor.area);
1789     area_tmp.x1 += ext->label->coords.x1;
1790     area_tmp.y1 += ext->label->coords.y1;
1791     area_tmp.x2 += ext->label->coords.x1;
1792     area_tmp.y2 += ext->label->coords.y1;
1793     lv_obj_invalidate_area(ta, &area_tmp);
1794 }
1795 
update_cursor_position_on_click(lv_obj_t * ta,lv_signal_t sign,lv_indev_t * click_source)1796 static void update_cursor_position_on_click(lv_obj_t * ta, lv_signal_t sign, lv_indev_t * click_source)
1797 {
1798 
1799     if(click_source == NULL) return;
1800 
1801     lv_textarea_ext_t * ext = lv_obj_get_ext_attr(ta);
1802     if(ext->cursor.click_pos == 0) return;
1803     if(ext->cursor.hidden) return;
1804 
1805     if(lv_indev_get_type(click_source) == LV_INDEV_TYPE_KEYPAD ||
1806        lv_indev_get_type(click_source) == LV_INDEV_TYPE_ENCODER) {
1807         return;
1808     }
1809 
1810     lv_area_t label_coords;
1811     lv_obj_get_coords(ext->label, &label_coords);
1812 
1813     lv_point_t point_act, vect_act;
1814     lv_indev_get_point(click_source, &point_act);
1815     lv_indev_get_vect(click_source, &vect_act);
1816 
1817     if(point_act.x < 0 || point_act.y < 0) return; /*Ignore event from keypad*/
1818     lv_point_t rel_pos;
1819     rel_pos.x = point_act.x - label_coords.x1;
1820     rel_pos.y = point_act.y - label_coords.y1;
1821 
1822     lv_coord_t label_width = lv_obj_get_width(ext->label);
1823 
1824     uint16_t char_id_at_click;
1825 
1826 #if LV_LABEL_TEXT_SEL
1827     lv_label_ext_t * ext_label = lv_obj_get_ext_attr(ext->label);
1828     bool click_outside_label;
1829     /*Check if the click happened on the left side of the area outside the label*/
1830     if(rel_pos.x < 0) {
1831         char_id_at_click = 0;
1832         click_outside_label       = true;
1833     }
1834     /*Check if the click happened on the right side of the area outside the label*/
1835     else if(rel_pos.x >= label_width) {
1836         char_id_at_click = LV_TEXTAREA_CURSOR_LAST;
1837         click_outside_label       = true;
1838     }
1839     else {
1840         char_id_at_click = lv_label_get_letter_on(ext->label, &rel_pos);
1841         click_outside_label       = !lv_label_is_char_under_pos(ext->label, &rel_pos);
1842     }
1843 
1844     if(ext->text_sel_en) {
1845         if(!ext->text_sel_in_prog && !click_outside_label && sign == LV_SIGNAL_PRESSED) {
1846             /*Input device just went down. Store the selection start position*/
1847             ext->sel_start    = char_id_at_click;
1848             ext->sel_end      = LV_LABEL_TEXT_SEL_OFF;
1849             ext->text_sel_in_prog = 1;
1850             lv_obj_set_drag(lv_page_get_scrollable(ta), false);
1851         }
1852         else if(ext->text_sel_in_prog && sign == LV_SIGNAL_PRESSING) {
1853             /*Input device may be moving. Store the end position */
1854             ext->sel_end = char_id_at_click;
1855         }
1856         else if(ext->text_sel_in_prog && (sign == LV_SIGNAL_PRESS_LOST || sign == LV_SIGNAL_RELEASED)) {
1857             /*Input device is released. Check if anything was selected.*/
1858             lv_obj_set_drag(lv_page_get_scrollable(ta), true);
1859         }
1860     }
1861 
1862     if(ext->text_sel_in_prog || sign == LV_SIGNAL_PRESSED) lv_textarea_set_cursor_pos(ta, char_id_at_click);
1863 
1864     if(ext->text_sel_in_prog) {
1865         /*If the selected area has changed then update the real values and*/
1866 
1867         /*Invalidate the text area.*/
1868         if(ext->sel_start > ext->sel_end) {
1869             if(ext_label->sel_start != ext->sel_end || ext_label->sel_end != ext->sel_start) {
1870                 ext_label->sel_start = ext->sel_end;
1871                 ext_label->sel_end   = ext->sel_start;
1872                 lv_obj_invalidate(ta);
1873             }
1874         }
1875         else if(ext->sel_start < ext->sel_end) {
1876             if(ext_label->sel_start != ext->sel_start || ext_label->sel_end != ext->sel_end) {
1877                 ext_label->sel_start = ext->sel_start;
1878                 ext_label->sel_end   = ext->sel_end;
1879                 lv_obj_invalidate(ta);
1880             }
1881         }
1882         else {
1883             if(ext_label->sel_start != LV_DRAW_LABEL_NO_TXT_SEL || ext_label->sel_end != LV_DRAW_LABEL_NO_TXT_SEL) {
1884                 ext_label->sel_start = LV_DRAW_LABEL_NO_TXT_SEL;
1885                 ext_label->sel_end   = LV_DRAW_LABEL_NO_TXT_SEL;
1886                 lv_obj_invalidate(ta);
1887             }
1888         }
1889         /*Finish selection if necessary */
1890         if(sign == LV_SIGNAL_PRESS_LOST || sign == LV_SIGNAL_RELEASED) {
1891             ext->text_sel_in_prog = 0;
1892         }
1893     }
1894 #else
1895     /*Check if the click happened on the left side of the area outside the label*/
1896     if(rel_pos.x < 0) {
1897         char_id_at_click = 0;
1898     }
1899     /*Check if the click happened on the right side of the area outside the label*/
1900     else if(rel_pos.x >= label_width) {
1901         char_id_at_click = LV_TEXTAREA_CURSOR_LAST;
1902     }
1903     else {
1904         char_id_at_click = lv_label_get_letter_on(ext->label, &rel_pos);
1905     }
1906 
1907     if(sign == LV_SIGNAL_PRESSED) lv_textarea_set_cursor_pos(ta, char_id_at_click);
1908 #endif
1909 }
1910 
insert_handler(lv_obj_t * ta,const char * txt)1911 static lv_res_t insert_handler(lv_obj_t * ta, const char * txt)
1912 {
1913     ta_insert_replace = NULL;
1914     lv_event_send(ta, LV_EVENT_INSERT, txt);
1915     if(ta_insert_replace) {
1916         if(ta_insert_replace[0] == '\0') return LV_RES_INV; /*Drop this text*/
1917 
1918         /*Add the replaced text directly it's different from the original*/
1919         if(strcmp(ta_insert_replace, txt)) {
1920             lv_textarea_add_text(ta, ta_insert_replace);
1921             return LV_RES_INV;
1922         }
1923     }
1924 
1925     return LV_RES_OK;
1926 }
1927 
1928 #endif
1929