1 /**
2  * @file lv_ta.c
3  *
4  */
5 
6 /*********************
7  *      INCLUDES
8  *********************/
9 #include "lv_textarea_private.h"
10 #include "../label/lv_label_private.h"
11 #include "../../core/lv_obj_class_private.h"
12 #if LV_USE_TEXTAREA != 0
13 
14 #include "../../core/lv_group.h"
15 #include "../../core/lv_refr.h"
16 #include "../../indev/lv_indev.h"
17 #include "../../draw/lv_draw.h"
18 #include "../../misc/lv_assert.h"
19 #include "../../misc/lv_anim_private.h"
20 #include "../../misc/lv_text_private.h"
21 #include "../../misc/lv_math.h"
22 #include "../../stdlib/lv_string.h"
23 
24 /*********************
25  *      DEFINES
26  *********************/
27 #define MY_CLASS (&lv_textarea_class)
28 
29 /*Test configuration*/
30 #ifndef LV_TEXTAREA_DEF_CURSOR_BLINK_TIME
31     #define LV_TEXTAREA_DEF_CURSOR_BLINK_TIME 400 /*ms*/
32 #endif
33 
34 #ifndef LV_TEXTAREA_DEF_PWD_SHOW_TIME
35     #define LV_TEXTAREA_DEF_PWD_SHOW_TIME 1500 /*ms*/
36 #endif
37 
38 #define LV_TEXTAREA_PWD_BULLET_UNICODE      0x2022
39 #define IGNORE_KERNING                      '\0'
40 
41 /**********************
42  *      TYPEDEFS
43  **********************/
44 
45 /**********************
46  *  STATIC PROTOTYPES
47  **********************/
48 static void lv_textarea_constructor(const lv_obj_class_t * class_p, lv_obj_t * obj);
49 static void lv_textarea_destructor(const lv_obj_class_t * class_p, lv_obj_t * obj);
50 static void lv_textarea_event(const lv_obj_class_t * class_p, lv_event_t * e);
51 static void label_event_cb(lv_event_t * e);
52 static void cursor_blink_anim_cb(void * obj, int32_t show);
53 static void pwd_char_hider_anim(void * obj, int32_t x);
54 static void pwd_char_hider_anim_completed(lv_anim_t * a);
55 static void pwd_char_hider(lv_obj_t * obj);
56 static bool char_is_accepted(lv_obj_t * obj, uint32_t c);
57 static void start_cursor_blink(lv_obj_t * obj);
58 static void refr_cursor_area(lv_obj_t * obj);
59 static void update_cursor_position_on_click(lv_event_t * e);
60 static lv_result_t insert_handler(lv_obj_t * obj, const char * txt);
61 static void draw_placeholder(lv_event_t * e);
62 static void draw_cursor(lv_event_t * e);
63 static void auto_hide_characters(lv_obj_t * obj);
64 static void auto_hide_characters_cancel(lv_obj_t * obj);
65 static inline bool is_valid_but_non_printable_char(const uint32_t letter);
66 
67 /**********************
68  *  STATIC VARIABLES
69  **********************/
70 #if LV_USE_OBJ_PROPERTY
71 static const lv_property_ops_t properties[] = {
72     {
73         .id = LV_PROPERTY_TEXTAREA_TEXT,
74         .setter = lv_textarea_set_text,
75         .getter = lv_textarea_get_text,
76     },
77     {
78         .id = LV_PROPERTY_TEXTAREA_PLACEHOLDER_TEXT,
79         .setter = lv_textarea_set_placeholder_text,
80         .getter = lv_textarea_get_placeholder_text,
81     },
82     {
83         .id = LV_PROPERTY_TEXTAREA_CURSOR_POS,
84         .setter = lv_textarea_set_cursor_pos,
85         .getter = lv_textarea_get_cursor_pos,
86     },
87     {
88         .id = LV_PROPERTY_TEXTAREA_CURSOR_CLICK_POS,
89         .setter = lv_textarea_set_cursor_click_pos,
90         .getter = lv_textarea_get_cursor_click_pos,
91     },
92     {
93         .id = LV_PROPERTY_TEXTAREA_PASSWORD_MODE,
94         .setter = lv_textarea_set_password_mode,
95         .getter = lv_textarea_get_password_mode,
96     },
97     {
98         .id = LV_PROPERTY_TEXTAREA_PASSWORD_BULLET,
99         .setter = lv_textarea_set_password_bullet,
100         .getter = lv_textarea_get_password_bullet,
101     },
102     {
103         .id = LV_PROPERTY_TEXTAREA_ONE_LINE,
104         .setter = lv_textarea_set_one_line,
105         .getter = lv_textarea_get_one_line,
106     },
107     {
108         .id = LV_PROPERTY_TEXTAREA_ACCEPTED_CHARS,
109         .setter = lv_textarea_set_accepted_chars,
110         .getter = lv_textarea_get_accepted_chars,
111     },
112     {
113         .id = LV_PROPERTY_TEXTAREA_MAX_LENGTH,
114         .setter = lv_textarea_set_max_length,
115         .getter = lv_textarea_get_max_length,
116     },
117     {
118         .id = LV_PROPERTY_TEXTAREA_INSERT_REPLACE,
119         .setter = lv_textarea_set_insert_replace,
120         .getter = NULL,
121     },
122     {
123         .id = LV_PROPERTY_TEXTAREA_TEXT_SELECTION,
124         .setter = lv_textarea_set_text_selection,
125         .getter = lv_textarea_get_text_selection,
126     },
127     {
128         .id = LV_PROPERTY_TEXTAREA_PASSWORD_SHOW_TIME,
129         .setter = lv_textarea_set_password_show_time,
130         .getter = lv_textarea_get_password_show_time,
131     },
132     {
133         .id = LV_PROPERTY_TEXTAREA_LABEL,
134         .setter = NULL,
135         .getter = lv_textarea_get_label,
136     },
137     {
138         .id = LV_PROPERTY_TEXTAREA_TEXT_IS_SELECTED,
139         .setter = NULL,
140         .getter = lv_textarea_text_is_selected,
141     },
142     {
143         .id = LV_PROPERTY_TEXTAREA_CURRENT_CHAR,
144         .setter = NULL,
145         .getter = lv_textarea_get_current_char,
146     },
147 };
148 #endif
149 
150 const lv_obj_class_t lv_textarea_class = {
151     .constructor_cb = lv_textarea_constructor,
152     .destructor_cb = lv_textarea_destructor,
153     .event_cb = lv_textarea_event,
154     .group_def = LV_OBJ_CLASS_GROUP_DEF_TRUE,
155     .width_def = LV_DPI_DEF * 2,
156     .height_def = LV_DPI_DEF,
157     .editable = LV_OBJ_CLASS_EDITABLE_TRUE,
158     .instance_size = sizeof(lv_textarea_t),
159     .base_class = &lv_obj_class,
160     .name = "textarea",
161 #if LV_USE_OBJ_PROPERTY
162     .prop_index_start = LV_PROPERTY_TEXTAREA_START,
163     .prop_index_end = LV_PROPERTY_TEXTAREA_END,
164     .properties = properties,
165     .properties_count = sizeof(properties) / sizeof(properties[0]),
166 
167 #if LV_USE_OBJ_PROPERTY_NAME
168     .property_names = lv_textarea_property_names,
169     .names_count = sizeof(lv_textarea_property_names) / sizeof(lv_property_name_t),
170 #endif
171 
172 #endif
173 };
174 
175 static const char * ta_insert_replace;
176 
177 /**********************
178  *      MACROS
179  **********************/
180 
181 /**********************
182  *   GLOBAL FUNCTIONS
183  **********************/
184 
lv_textarea_create(lv_obj_t * parent)185 lv_obj_t * lv_textarea_create(lv_obj_t * parent)
186 {
187     LV_LOG_INFO("begin");
188     lv_obj_t * obj = lv_obj_class_create_obj(MY_CLASS, parent);
189     lv_obj_class_init_obj(obj);
190     return obj;
191 }
192 
193 /*======================
194  * Add/remove functions
195  *=====================*/
196 
lv_textarea_add_char(lv_obj_t * obj,uint32_t c)197 void lv_textarea_add_char(lv_obj_t * obj, uint32_t c)
198 {
199     LV_ASSERT_OBJ(obj, MY_CLASS);
200 
201     lv_textarea_t * ta = (lv_textarea_t *)obj;
202 
203     if(ta->one_line && (c == '\n' || c == '\r')) {
204         LV_LOG_INFO("Text area: line break ignored in one-line mode");
205         return;
206     }
207 
208     uint32_t u32_buf[2];
209     u32_buf[0] = c;
210     u32_buf[1] = 0;
211 
212     const char * letter_buf = (char *)&u32_buf;
213 
214     uint32_t c2 = c;
215 #if LV_BIG_ENDIAN_SYSTEM
216     if(c != 0) while(*letter_buf == 0) ++letter_buf;
217 
218     /*The byte order may or may not need to be swapped here to get correct c_uni below,
219       since lv_textarea_add_text is ordering bytes correctly before calling lv_textarea_add_char.
220       Assume swapping is needed if MSB is zero. May not be foolproof. */
221     if((c != 0) && ((c & 0xff000000) == 0)) {
222         c2 = ((c >> 24) & 0xff) | /*move byte 3 to byte 0*/
223              ((c << 8) & 0xff0000) | /*move byte 1 to byte 2*/
224              ((c >> 8) & 0xff00) | /*move byte 2 to byte 1*/
225              ((c << 24) & 0xff000000); /*byte 0 to byte 3*/
226     }
227 #endif
228 
229     lv_result_t res = insert_handler(obj, letter_buf);
230     if(res != LV_RESULT_OK) return;
231 
232     uint32_t c_uni = lv_text_encoded_next((const char *)&c2, NULL);
233 
234     if(char_is_accepted(obj, c_uni) == false) {
235         LV_LOG_INFO("Character is not accepted by the text area (too long text or not in the accepted list)");
236         return;
237     }
238 
239     if(ta->pwd_mode) pwd_char_hider(obj); /*Make sure all the current text contains only '*'*/
240 
241     /*If the textarea is empty, invalidate it to hide the placeholder*/
242     if(ta->placeholder_txt) {
243         const char * txt = lv_label_get_text(ta->label);
244         if(txt[0] == '\0') lv_obj_invalidate(obj);
245     }
246 
247     lv_label_ins_text(ta->label, ta->cursor.pos, letter_buf); /*Insert the character*/
248     lv_textarea_clear_selection(obj); /*Clear selection*/
249 
250     if(ta->pwd_mode) {
251         /*+2: the new char + \0*/
252         size_t realloc_size = lv_strlen(ta->pwd_tmp) + lv_strlen(letter_buf) + 1;
253         ta->pwd_tmp = lv_realloc(ta->pwd_tmp, realloc_size);
254         LV_ASSERT_MALLOC(ta->pwd_tmp);
255         if(ta->pwd_tmp == NULL) return;
256 
257         lv_text_ins(ta->pwd_tmp, ta->cursor.pos, (const char *)letter_buf);
258 
259         /*Auto hide characters*/
260         auto_hide_characters(obj);
261     }
262 
263     /*Move the cursor after the new character*/
264     lv_textarea_set_cursor_pos(obj, lv_textarea_get_cursor_pos(obj) + 1);
265 
266     lv_obj_send_event(obj, LV_EVENT_VALUE_CHANGED, NULL);
267 }
268 
lv_textarea_add_text(lv_obj_t * obj,const char * txt)269 void lv_textarea_add_text(lv_obj_t * obj, const char * txt)
270 {
271     LV_ASSERT_OBJ(obj, MY_CLASS);
272     LV_ASSERT_NULL(txt);
273 
274     lv_textarea_t * ta = (lv_textarea_t *)obj;
275 
276     if(ta->pwd_mode) pwd_char_hider(obj); /*Make sure all the current text contains only '*'*/
277 
278     /*Add the character one-by-one if not all characters are accepted or there is character limit.*/
279     if(lv_textarea_get_accepted_chars(obj) || lv_textarea_get_max_length(obj)) {
280         uint32_t i = 0;
281         while(txt[i] != '\0') {
282             uint32_t c = lv_text_encoded_next(txt, &i);
283             lv_textarea_add_char(obj, lv_text_unicode_to_encoded(c));
284         }
285         return;
286     }
287 
288     lv_result_t res = insert_handler(obj, txt);
289     if(res != LV_RESULT_OK) return;
290 
291     /*If the textarea is empty, invalidate it to hide the placeholder*/
292     if(ta->placeholder_txt) {
293         const char * txt_act = lv_label_get_text(ta->label);
294         if(txt_act[0] == '\0') lv_obj_invalidate(obj);
295     }
296 
297     /*Insert the text*/
298     lv_label_ins_text(ta->label, ta->cursor.pos, txt);
299     lv_textarea_clear_selection(obj);
300 
301     if(ta->pwd_mode) {
302         size_t realloc_size = lv_strlen(ta->pwd_tmp) + lv_strlen(txt) + 1;
303         ta->pwd_tmp = lv_realloc(ta->pwd_tmp, realloc_size);
304         LV_ASSERT_MALLOC(ta->pwd_tmp);
305         if(ta->pwd_tmp == NULL) return;
306 
307         lv_text_ins(ta->pwd_tmp, ta->cursor.pos, txt);
308 
309         /*Auto hide characters*/
310         auto_hide_characters(obj);
311     }
312 
313     /*Move the cursor after the new text*/
314     lv_textarea_set_cursor_pos(obj, lv_textarea_get_cursor_pos(obj) + lv_text_get_encoded_length(txt));
315 
316     lv_obj_send_event(obj, LV_EVENT_VALUE_CHANGED, NULL);
317 }
318 
lv_textarea_delete_char(lv_obj_t * obj)319 void lv_textarea_delete_char(lv_obj_t * obj)
320 {
321     LV_ASSERT_OBJ(obj, MY_CLASS);
322 
323     lv_textarea_t * ta = (lv_textarea_t *)obj;
324     uint32_t cur_pos  = ta->cursor.pos;
325 
326     if(cur_pos == 0) return;
327 
328     char del_buf[2]   = {LV_KEY_DEL, '\0'};
329 
330     lv_result_t res = insert_handler(obj, del_buf);
331     if(res != LV_RESULT_OK) return;
332 
333     char * label_txt = lv_label_get_text(ta->label);
334 
335     /*Delete a character*/
336     lv_text_cut(label_txt, ta->cursor.pos - 1, 1);
337 
338     /*Refresh the label*/
339     lv_label_set_text(ta->label, label_txt);
340     lv_textarea_clear_selection(obj);
341 
342     /*If the textarea became empty, invalidate it to hide the placeholder*/
343     if(ta->placeholder_txt) {
344         const char * txt = lv_label_get_text(ta->label);
345         if(txt[0] == '\0') lv_obj_invalidate(obj);
346     }
347 
348     if(ta->pwd_mode) {
349         lv_text_cut(ta->pwd_tmp, ta->cursor.pos - 1, 1);
350 
351         ta->pwd_tmp = lv_realloc(ta->pwd_tmp, lv_strlen(ta->pwd_tmp) + 1);
352         LV_ASSERT_MALLOC(ta->pwd_tmp);
353         if(ta->pwd_tmp == NULL) return;
354     }
355 
356     /*Move the cursor to the place of the deleted character*/
357     lv_textarea_set_cursor_pos(obj, ta->cursor.pos - 1);
358 
359     lv_obj_send_event(obj, LV_EVENT_VALUE_CHANGED, NULL);
360 
361 }
362 
lv_textarea_delete_char_forward(lv_obj_t * obj)363 void lv_textarea_delete_char_forward(lv_obj_t * obj)
364 {
365     LV_ASSERT_OBJ(obj, MY_CLASS);
366 
367     uint32_t cp = lv_textarea_get_cursor_pos(obj);
368     lv_textarea_set_cursor_pos(obj, cp + 1);
369     if(cp != lv_textarea_get_cursor_pos(obj)) lv_textarea_delete_char(obj);
370 }
371 
372 /*=====================
373  * Setter functions
374  *====================*/
375 
lv_textarea_set_text(lv_obj_t * obj,const char * txt)376 void lv_textarea_set_text(lv_obj_t * obj, const char * txt)
377 {
378     LV_ASSERT_OBJ(obj, MY_CLASS);
379     LV_ASSERT_NULL(txt);
380 
381     lv_textarea_t * ta = (lv_textarea_t *)obj;
382 
383     /*Clear the existing selection*/
384     lv_textarea_clear_selection(obj);
385 
386     /*Add the character one-by-one if not all characters are accepted or there is character limit.*/
387     if(lv_textarea_get_accepted_chars(obj) || lv_textarea_get_max_length(obj)) {
388         lv_label_set_text(ta->label, "");
389         lv_textarea_set_cursor_pos(obj, LV_TEXTAREA_CURSOR_LAST);
390         if(ta->pwd_mode) {
391             ta->pwd_tmp[0] = '\0'; /*Clear the password too*/
392         }
393         uint32_t i = 0;
394         while(txt[i] != '\0') {
395             uint32_t c = lv_text_encoded_next(txt, &i);
396             lv_textarea_add_char(obj, lv_text_unicode_to_encoded(c));
397         }
398     }
399     else {
400         lv_label_set_text(ta->label, txt);
401         lv_textarea_set_cursor_pos(obj, LV_TEXTAREA_CURSOR_LAST);
402     }
403 
404     /*If the textarea is empty, invalidate it to hide the placeholder*/
405     if(ta->placeholder_txt) {
406         const char * txt_act = lv_label_get_text(ta->label);
407         if(txt_act[0] == '\0') lv_obj_invalidate(obj);
408     }
409 
410     if(ta->pwd_mode) {
411         lv_free(ta->pwd_tmp);
412         ta->pwd_tmp = lv_strdup(txt);
413         LV_ASSERT_MALLOC(ta->pwd_tmp);
414         if(ta->pwd_tmp == NULL) return;
415 
416         pwd_char_hider(obj);
417     }
418 
419     lv_obj_send_event(obj, LV_EVENT_VALUE_CHANGED, NULL);
420 }
421 
lv_textarea_set_placeholder_text(lv_obj_t * obj,const char * txt)422 void lv_textarea_set_placeholder_text(lv_obj_t * obj, const char * txt)
423 {
424     LV_ASSERT_OBJ(obj, MY_CLASS);
425     LV_ASSERT_NULL(txt);
426 
427     lv_textarea_t * ta = (lv_textarea_t *)obj;
428 
429     size_t txt_len = lv_strlen(txt);
430     if((txt_len == 0) && (ta->placeholder_txt)) {
431         lv_free(ta->placeholder_txt);
432         ta->placeholder_txt = NULL;
433     }
434     else {
435         /*Allocate memory for the placeholder_txt text*/
436         /*NOTE: Using special realloc behavior, malloc-like when data_p is NULL*/
437         ta->placeholder_txt = lv_realloc(ta->placeholder_txt, txt_len + 1);
438         LV_ASSERT_MALLOC(ta->placeholder_txt);
439         if(ta->placeholder_txt == NULL) {
440             LV_LOG_ERROR("couldn't allocate memory for placeholder");
441             return;
442         }
443 
444         lv_strcpy(ta->placeholder_txt, txt);
445         ta->placeholder_txt[txt_len] = '\0';
446     }
447 
448     lv_obj_invalidate(obj);
449 }
450 
lv_textarea_set_cursor_pos(lv_obj_t * obj,int32_t pos)451 void lv_textarea_set_cursor_pos(lv_obj_t * obj, int32_t pos)
452 {
453     LV_ASSERT_OBJ(obj, MY_CLASS);
454 
455     lv_textarea_t * ta = (lv_textarea_t *)obj;
456     if((uint32_t)ta->cursor.pos == (uint32_t)pos) return;
457 
458     uint32_t len = lv_text_get_encoded_length(lv_label_get_text(ta->label));
459 
460     if(pos < 0) pos = len + pos;
461 
462     if(pos > (int32_t)len || pos == LV_TEXTAREA_CURSOR_LAST) pos = len;
463 
464     ta->cursor.pos = pos;
465 
466     /*Position the label to make the cursor visible*/
467     lv_obj_update_layout(obj);
468 
469     lv_point_t cur_pos;
470     const lv_font_t * font = lv_obj_get_style_text_font(obj, LV_PART_MAIN);
471     lv_label_get_letter_pos(ta->label, pos, &cur_pos);
472 
473     /*The text area needs to have it's final size to see if the cursor is out of the area or not*/
474 
475     /*Check the top*/
476     int32_t font_h = lv_font_get_line_height(font);
477     if(cur_pos.y < lv_obj_get_scroll_top(obj)) {
478         lv_obj_scroll_to_y(obj, cur_pos.y, LV_ANIM_ON);
479     }
480     /*Check the bottom*/
481     int32_t h = lv_obj_get_content_height(obj);
482     if(cur_pos.y + font_h - lv_obj_get_scroll_top(obj) > h) {
483         lv_obj_scroll_to_y(obj, cur_pos.y - h + font_h, LV_ANIM_ON);
484     }
485 
486     /*Check the left*/
487     if(cur_pos.x < lv_obj_get_scroll_left(obj)) {
488         lv_obj_scroll_to_x(obj, cur_pos.x, LV_ANIM_ON);
489     }
490     /*Check the right*/
491     int32_t w = lv_obj_get_content_width(obj);
492     if(cur_pos.x + font_h - lv_obj_get_scroll_left(obj) > w) {
493         lv_obj_scroll_to_x(obj, cur_pos.x - w + font_h, LV_ANIM_ON);
494     }
495 
496     ta->cursor.valid_x = cur_pos.x;
497 
498     start_cursor_blink(obj);
499 
500     refr_cursor_area(obj);
501 }
502 
lv_textarea_set_cursor_click_pos(lv_obj_t * obj,bool en)503 void lv_textarea_set_cursor_click_pos(lv_obj_t * obj, bool en)
504 {
505     LV_ASSERT_OBJ(obj, MY_CLASS);
506 
507     lv_textarea_t * ta = (lv_textarea_t *)obj;
508     ta->cursor.click_pos = en ? 1U : 0U;
509 }
510 
lv_textarea_set_password_mode(lv_obj_t * obj,bool en)511 void lv_textarea_set_password_mode(lv_obj_t * obj, bool en)
512 {
513     LV_ASSERT_OBJ(obj, MY_CLASS);
514 
515     lv_textarea_t * ta = (lv_textarea_t *)obj;
516     if(ta->pwd_mode == en) return;
517 
518     ta->pwd_mode = en ? 1U : 0U;
519     /*Pwd mode is now enabled*/
520     if(en) {
521         char * txt = lv_label_get_text(ta->label);
522         lv_free(ta->pwd_tmp);
523         ta->pwd_tmp = lv_strdup(txt);
524         LV_ASSERT_MALLOC(ta->pwd_tmp);
525         if(ta->pwd_tmp == NULL) return;
526 
527         pwd_char_hider(obj);
528 
529         lv_textarea_clear_selection(obj);
530     }
531     /*Pwd mode is now disabled*/
532     else {
533         lv_textarea_clear_selection(obj);
534         lv_label_set_text(ta->label, ta->pwd_tmp);
535         lv_free(ta->pwd_tmp);
536         ta->pwd_tmp = NULL;
537     }
538 
539     refr_cursor_area(obj);
540 }
541 
lv_textarea_set_password_bullet(lv_obj_t * obj,const char * bullet)542 void lv_textarea_set_password_bullet(lv_obj_t * obj, const char * bullet)
543 {
544     LV_ASSERT_OBJ(obj, MY_CLASS);
545     LV_ASSERT_NULL(bullet);
546 
547     lv_textarea_t * ta = (lv_textarea_t *)obj;
548 
549     if(!bullet && (ta->pwd_bullet)) {
550         lv_free(ta->pwd_bullet);
551         ta->pwd_bullet = NULL;
552     }
553     else {
554         size_t txt_len = lv_strlen(bullet);
555 
556         /*Allocate memory for the pwd_bullet text*/
557         /*NOTE: Using special realloc behavior, malloc-like when data_p is NULL*/
558         ta->pwd_bullet = lv_realloc(ta->pwd_bullet, txt_len + 1);
559         LV_ASSERT_MALLOC(ta->pwd_bullet);
560         if(ta->pwd_bullet == NULL) {
561             LV_LOG_ERROR("couldn't allocate memory for bullet");
562             return;
563         }
564 
565         lv_memcpy(ta->pwd_bullet, bullet, txt_len);
566         ta->pwd_bullet[txt_len] = '\0';
567     }
568 
569     pwd_char_hider(obj);
570 }
571 
lv_textarea_set_one_line(lv_obj_t * obj,bool en)572 void lv_textarea_set_one_line(lv_obj_t * obj, bool en)
573 {
574     LV_ASSERT_OBJ(obj, MY_CLASS);
575 
576     lv_textarea_t * ta = (lv_textarea_t *)obj;
577     if(ta->one_line == en) return;
578 
579     ta->one_line = en ? 1U : 0U;
580     int32_t width = en ? LV_SIZE_CONTENT : lv_pct(100);
581     int32_t min_width_value = en ? lv_pct(100) : 0;
582 
583     lv_obj_set_width(ta->label, width);
584     lv_obj_set_style_min_width(ta->label, min_width_value, 0);
585 
586     if(en) {
587         lv_obj_set_height(obj, LV_SIZE_CONTENT);
588     }
589     else {
590         lv_obj_remove_local_style_prop(obj, LV_STYLE_HEIGHT, LV_PART_MAIN);
591     }
592 
593     lv_obj_scroll_to(obj, 0, 0, LV_ANIM_OFF);
594 }
595 
lv_textarea_set_accepted_chars(lv_obj_t * obj,const char * list)596 void lv_textarea_set_accepted_chars(lv_obj_t * obj, const char * list)
597 {
598     LV_ASSERT_OBJ(obj, MY_CLASS);
599 
600     lv_textarea_t * ta = (lv_textarea_t *)obj;
601 
602     ta->accepted_chars = list;
603 }
604 
lv_textarea_set_max_length(lv_obj_t * obj,uint32_t num)605 void lv_textarea_set_max_length(lv_obj_t * obj, uint32_t num)
606 {
607     LV_ASSERT_OBJ(obj, MY_CLASS);
608 
609     lv_textarea_t * ta = (lv_textarea_t *)obj;
610 
611     ta->max_length = num;
612 }
613 
lv_textarea_set_insert_replace(lv_obj_t * obj,const char * txt)614 void lv_textarea_set_insert_replace(lv_obj_t * obj, const char * txt)
615 {
616     LV_ASSERT_OBJ(obj, MY_CLASS);
617 
618     LV_UNUSED(obj);
619     ta_insert_replace = txt;
620 }
621 
lv_textarea_set_text_selection(lv_obj_t * obj,bool en)622 void lv_textarea_set_text_selection(lv_obj_t * obj, bool en)
623 {
624     LV_ASSERT_OBJ(obj, MY_CLASS);
625 
626 #if LV_LABEL_TEXT_SELECTION
627     lv_textarea_t * ta = (lv_textarea_t *)obj;
628 
629     ta->text_sel_en = en;
630 
631     if(!en) lv_textarea_clear_selection(obj);
632 #else
633     LV_UNUSED(obj); /*Unused*/
634     LV_UNUSED(en);  /*Unused*/
635 #endif
636 }
637 
lv_textarea_set_password_show_time(lv_obj_t * obj,uint32_t time)638 void lv_textarea_set_password_show_time(lv_obj_t * obj, uint32_t time)
639 {
640     LV_ASSERT_OBJ(obj, MY_CLASS);
641 
642     lv_textarea_t * ta = (lv_textarea_t *)obj;
643     ta->pwd_show_time = time;
644     pwd_char_hider(obj);
645 }
646 
lv_textarea_set_align(lv_obj_t * obj,lv_text_align_t align)647 void lv_textarea_set_align(lv_obj_t * obj, lv_text_align_t align)
648 {
649     LV_LOG_WARN("Deprecated: use the normal text_align style property instead");
650     lv_obj_set_style_text_align(obj, align, 0);
651 
652     switch(align) {
653         default:
654         case LV_TEXT_ALIGN_LEFT:
655             lv_obj_align(lv_textarea_get_label(obj), LV_ALIGN_TOP_LEFT, 0, 0);
656             break;
657         case LV_TEXT_ALIGN_RIGHT:
658             lv_obj_align(lv_textarea_get_label(obj), LV_ALIGN_TOP_RIGHT, 0, 0);
659             break;
660         case LV_TEXT_ALIGN_CENTER:
661             lv_obj_align(lv_textarea_get_label(obj), LV_ALIGN_TOP_MID, 0, 0);
662             break;
663     }
664 }
665 
666 /*=====================
667  * Getter functions
668  *====================*/
669 
lv_textarea_get_text(const lv_obj_t * obj)670 const char * lv_textarea_get_text(const lv_obj_t * obj)
671 {
672     LV_ASSERT_OBJ(obj, MY_CLASS);
673 
674     lv_textarea_t * ta = (lv_textarea_t *)obj;
675 
676     const char * txt;
677     if(ta->pwd_mode == 0) {
678         txt = lv_label_get_text(ta->label);
679     }
680     else {
681         txt = ta->pwd_tmp;
682     }
683 
684     return txt;
685 }
686 
lv_textarea_get_placeholder_text(lv_obj_t * obj)687 const char * lv_textarea_get_placeholder_text(lv_obj_t * obj)
688 {
689     LV_ASSERT_OBJ(obj, MY_CLASS);
690 
691     lv_textarea_t * ta = (lv_textarea_t *)obj;
692     if(ta->placeholder_txt) return ta->placeholder_txt;
693     else return "";
694 }
695 
lv_textarea_get_label(const lv_obj_t * obj)696 lv_obj_t * lv_textarea_get_label(const lv_obj_t * obj)
697 {
698     LV_ASSERT_OBJ(obj, MY_CLASS);
699 
700     lv_textarea_t * ta = (lv_textarea_t *)obj;
701     return ta->label;
702 }
703 
lv_textarea_get_cursor_pos(const lv_obj_t * obj)704 uint32_t lv_textarea_get_cursor_pos(const lv_obj_t * obj)
705 {
706     LV_ASSERT_OBJ(obj, MY_CLASS);
707 
708     lv_textarea_t * ta = (lv_textarea_t *)obj;
709     return ta->cursor.pos;
710 }
711 
lv_textarea_get_cursor_click_pos(lv_obj_t * obj)712 bool lv_textarea_get_cursor_click_pos(lv_obj_t * obj)
713 {
714     LV_ASSERT_OBJ(obj, MY_CLASS);
715 
716     lv_textarea_t * ta = (lv_textarea_t *)obj;
717     return ta->cursor.click_pos;
718 }
719 
lv_textarea_get_password_mode(const lv_obj_t * obj)720 bool lv_textarea_get_password_mode(const lv_obj_t * obj)
721 {
722     LV_ASSERT_OBJ(obj, MY_CLASS);
723 
724     lv_textarea_t * ta = (lv_textarea_t *)obj;
725     return ta->pwd_mode == 1U;
726 }
727 
lv_textarea_get_password_bullet(lv_obj_t * obj)728 const char * lv_textarea_get_password_bullet(lv_obj_t * obj)
729 {
730     LV_ASSERT_OBJ(obj, MY_CLASS);
731 
732     lv_textarea_t * ta = (lv_textarea_t *)obj;
733 
734     if(ta->pwd_bullet) return ta->pwd_bullet;
735 
736     lv_font_glyph_dsc_t g;
737     const lv_font_t * font = lv_obj_get_style_text_font(obj, LV_PART_MAIN);
738 
739     /*If the textarea's font has the bullet character use it else fallback to "*"*/
740     if(lv_font_get_glyph_dsc(font, &g, LV_TEXTAREA_PWD_BULLET_UNICODE, 0))
741         return LV_SYMBOL_BULLET;
742     return "*";
743 }
744 
lv_textarea_get_one_line(const lv_obj_t * obj)745 bool lv_textarea_get_one_line(const lv_obj_t * obj)
746 {
747     LV_ASSERT_OBJ(obj, MY_CLASS);
748 
749     lv_textarea_t * ta = (lv_textarea_t *)obj;
750     return ta->one_line == 1U;
751 }
752 
lv_textarea_get_accepted_chars(lv_obj_t * obj)753 const char * lv_textarea_get_accepted_chars(lv_obj_t * obj)
754 {
755     LV_ASSERT_OBJ(obj, MY_CLASS);
756 
757     lv_textarea_t * ta = (lv_textarea_t *)obj;
758 
759     return ta->accepted_chars;
760 }
761 
lv_textarea_get_max_length(lv_obj_t * obj)762 uint32_t lv_textarea_get_max_length(lv_obj_t * obj)
763 {
764     LV_ASSERT_OBJ(obj, MY_CLASS);
765 
766     lv_textarea_t * ta = (lv_textarea_t *)obj;
767     return ta->max_length;
768 }
769 
lv_textarea_text_is_selected(const lv_obj_t * obj)770 bool lv_textarea_text_is_selected(const lv_obj_t * obj)
771 {
772     LV_ASSERT_OBJ(obj, MY_CLASS);
773 
774 #if LV_LABEL_TEXT_SELECTION
775     lv_textarea_t * ta = (lv_textarea_t *)obj;
776 
777     if((lv_label_get_text_selection_start(ta->label) != LV_DRAW_LABEL_NO_TXT_SEL ||
778         lv_label_get_text_selection_end(ta->label) != LV_DRAW_LABEL_NO_TXT_SEL)) {
779         return true;
780     }
781     else {
782         return false;
783     }
784 #else
785     LV_UNUSED(obj); /*Unused*/
786     return false;
787 #endif
788 }
789 
lv_textarea_get_text_selection(lv_obj_t * obj)790 bool lv_textarea_get_text_selection(lv_obj_t * obj)
791 {
792     LV_ASSERT_OBJ(obj, MY_CLASS);
793 
794 #if LV_LABEL_TEXT_SELECTION
795     lv_textarea_t * ta = (lv_textarea_t *)obj;
796     return ta->text_sel_en;
797 #else
798     LV_UNUSED(obj); /*Unused*/
799     return false;
800 #endif
801 }
802 
lv_textarea_get_password_show_time(lv_obj_t * obj)803 uint32_t lv_textarea_get_password_show_time(lv_obj_t * obj)
804 {
805     LV_ASSERT_OBJ(obj, MY_CLASS);
806 
807     lv_textarea_t * ta = (lv_textarea_t *)obj;
808 
809     return ta->pwd_show_time;
810 }
811 
lv_textarea_get_current_char(lv_obj_t * obj)812 uint32_t lv_textarea_get_current_char(lv_obj_t * obj)
813 {
814     LV_ASSERT_OBJ(obj, MY_CLASS);
815 
816     const char * txt = lv_textarea_get_text(obj);
817     lv_textarea_t * ta = (lv_textarea_t *)obj;
818     uint32_t pos = ta->cursor.pos;
819     if(lv_text_get_encoded_length(txt) >= pos && pos > 0)
820         return lv_text_encoded_prev(txt, &pos);
821     else
822         return 0;
823 }
824 
825 /*=====================
826  * Other functions
827  *====================*/
828 
lv_textarea_clear_selection(lv_obj_t * obj)829 void lv_textarea_clear_selection(lv_obj_t * obj)
830 {
831     LV_ASSERT_OBJ(obj, MY_CLASS);
832 
833 #if LV_LABEL_TEXT_SELECTION
834     lv_textarea_t * ta = (lv_textarea_t *)obj;
835 
836     if(lv_label_get_text_selection_start(ta->label) != LV_DRAW_LABEL_NO_TXT_SEL ||
837        lv_label_get_text_selection_end(ta->label) != LV_DRAW_LABEL_NO_TXT_SEL) {
838         lv_label_set_text_selection_start(ta->label, LV_DRAW_LABEL_NO_TXT_SEL);
839         lv_label_set_text_selection_end(ta->label, LV_DRAW_LABEL_NO_TXT_SEL);
840     }
841 #else
842     LV_UNUSED(obj); /*Unused*/
843 #endif
844 }
845 
lv_textarea_cursor_right(lv_obj_t * obj)846 void lv_textarea_cursor_right(lv_obj_t * obj)
847 {
848     LV_ASSERT_OBJ(obj, MY_CLASS);
849 
850     uint32_t cp = lv_textarea_get_cursor_pos(obj);
851     cp++;
852     lv_textarea_set_cursor_pos(obj, cp);
853 }
854 
lv_textarea_cursor_left(lv_obj_t * obj)855 void lv_textarea_cursor_left(lv_obj_t * obj)
856 {
857     LV_ASSERT_OBJ(obj, MY_CLASS);
858 
859     uint32_t cp = lv_textarea_get_cursor_pos(obj);
860     if(cp > 0) {
861         cp--;
862         lv_textarea_set_cursor_pos(obj, cp);
863     }
864 }
865 
lv_textarea_cursor_down(lv_obj_t * obj)866 void lv_textarea_cursor_down(lv_obj_t * obj)
867 {
868     LV_ASSERT_OBJ(obj, MY_CLASS);
869 
870     lv_textarea_t * ta = (lv_textarea_t *)obj;
871     lv_point_t pos;
872 
873     /*Get the position of the current letter*/
874     lv_label_get_letter_pos(ta->label, lv_textarea_get_cursor_pos(obj), &pos);
875 
876     /*Increment the y with one line and keep the valid x*/
877 
878     int32_t line_space = lv_obj_get_style_text_line_space(obj, LV_PART_MAIN);
879     const lv_font_t * font = lv_obj_get_style_text_font(obj, LV_PART_MAIN);
880     int32_t font_h              = lv_font_get_line_height(font);
881     pos.y += font_h + line_space + 1;
882     pos.x = ta->cursor.valid_x;
883 
884     /*Do not go below the last line*/
885     if(pos.y < lv_obj_get_height(ta->label)) {
886         /*Get the letter index on the new cursor position and set it*/
887         uint32_t new_cur_pos = lv_label_get_letter_on(ta->label, &pos, true);
888 
889         int32_t cur_valid_x_tmp = ta->cursor.valid_x; /*Cursor position set overwrites the valid position*/
890         lv_textarea_set_cursor_pos(obj, new_cur_pos);
891         ta->cursor.valid_x = cur_valid_x_tmp;
892     }
893 }
894 
lv_textarea_cursor_up(lv_obj_t * obj)895 void lv_textarea_cursor_up(lv_obj_t * obj)
896 {
897     LV_ASSERT_OBJ(obj, MY_CLASS);
898 
899     lv_textarea_t * ta = (lv_textarea_t *)obj;
900     lv_point_t pos;
901 
902     /*Get the position of the current letter*/
903     lv_label_get_letter_pos(ta->label, lv_textarea_get_cursor_pos(obj), &pos);
904 
905     /*Decrement the y with one line and keep the valid x*/
906     int32_t line_space = lv_obj_get_style_text_line_space(obj, LV_PART_MAIN);
907     const lv_font_t * font = lv_obj_get_style_text_font(obj, LV_PART_MAIN);
908     int32_t font_h              = lv_font_get_line_height(font);
909     pos.y -= font_h + line_space - 1;
910     pos.x = ta->cursor.valid_x;
911 
912     /*Get the letter index on the new cursor position and set it*/
913     uint32_t new_cur_pos       = lv_label_get_letter_on(ta->label, &pos, true);
914     int32_t cur_valid_x_tmp = ta->cursor.valid_x; /*Cursor position set overwrites the valid position*/
915     lv_textarea_set_cursor_pos(obj, new_cur_pos);
916     ta->cursor.valid_x = cur_valid_x_tmp;
917 }
918 
919 /**********************
920  *   STATIC FUNCTIONS
921  **********************/
922 
lv_textarea_constructor(const lv_obj_class_t * class_p,lv_obj_t * obj)923 static void lv_textarea_constructor(const lv_obj_class_t * class_p, lv_obj_t * obj)
924 {
925     LV_UNUSED(class_p);
926     LV_TRACE_OBJ_CREATE("begin");
927 
928     lv_textarea_t * ta = (lv_textarea_t *)obj;
929 
930     ta->pwd_mode          = 0;
931     ta->pwd_tmp           = NULL;
932     ta->pwd_bullet        = NULL;
933     ta->pwd_show_time     = LV_TEXTAREA_DEF_PWD_SHOW_TIME;
934     ta->accepted_chars    = NULL;
935     ta->max_length        = 0;
936     ta->cursor.show      = 1;
937     /*It will be set to zero later (with zero value lv_textarea_set_cursor_pos(obj, 0); wouldn't do anything as there is no difference)*/
938     ta->cursor.pos        = 1;
939     ta->cursor.click_pos  = 1;
940     ta->cursor.valid_x    = 0;
941     ta->one_line          = 0;
942 #if LV_LABEL_TEXT_SELECTION
943     ta->text_sel_en = 0;
944 #endif
945     ta->label       = NULL;
946     ta->placeholder_txt = NULL;
947 
948     ta->label = lv_label_create(obj);
949     lv_obj_set_width(ta->label, lv_pct(100));
950     lv_label_set_text(ta->label, "");
951     lv_obj_add_event_cb(ta->label, label_event_cb, LV_EVENT_ALL, NULL);
952     lv_obj_add_flag(obj, LV_OBJ_FLAG_SCROLL_ON_FOCUS);
953     lv_obj_remove_flag(obj, LV_OBJ_FLAG_SCROLL_WITH_ARROW);
954 
955     lv_textarea_set_cursor_pos(obj, 0);
956 
957     start_cursor_blink(obj);
958 
959     LV_TRACE_OBJ_CREATE("finished");
960 }
961 
lv_textarea_destructor(const lv_obj_class_t * class_p,lv_obj_t * obj)962 static void lv_textarea_destructor(const lv_obj_class_t * class_p, lv_obj_t * obj)
963 {
964     LV_UNUSED(class_p);
965 
966     lv_textarea_t * ta = (lv_textarea_t *)obj;
967     if(ta->pwd_tmp != NULL) {
968         lv_free(ta->pwd_tmp);
969         ta->pwd_tmp = NULL;
970     }
971     if(ta->pwd_bullet != NULL) {
972         lv_free(ta->pwd_bullet);
973         ta->pwd_bullet = NULL;
974     }
975     if(ta->placeholder_txt != NULL) {
976         lv_free(ta->placeholder_txt);
977         ta->placeholder_txt = NULL;
978     }
979 }
980 
lv_textarea_event(const lv_obj_class_t * class_p,lv_event_t * e)981 static void lv_textarea_event(const lv_obj_class_t * class_p, lv_event_t * e)
982 {
983     LV_UNUSED(class_p);
984 
985     lv_result_t res;
986     /*Call the ancestor's event handler*/
987     res = lv_obj_event_base(MY_CLASS, e);
988     if(res != LV_RESULT_OK) return;
989 
990     lv_event_code_t code = lv_event_get_code(e);
991     lv_obj_t * obj = lv_event_get_current_target(e);
992 
993     if(code == LV_EVENT_FOCUSED) {
994         start_cursor_blink(obj);
995     }
996     else if(code == LV_EVENT_KEY) {
997         uint32_t c = *((uint32_t *)lv_event_get_param(e)); /*uint32_t because can be UTF-8*/
998         if(c == LV_KEY_RIGHT)
999             lv_textarea_cursor_right(obj);
1000         else if(c == LV_KEY_LEFT)
1001             lv_textarea_cursor_left(obj);
1002         else if(c == LV_KEY_UP)
1003             lv_textarea_cursor_up(obj);
1004         else if(c == LV_KEY_DOWN)
1005             lv_textarea_cursor_down(obj);
1006         else if(c == LV_KEY_BACKSPACE)
1007             lv_textarea_delete_char(obj);
1008         else if(c == LV_KEY_DEL)
1009             lv_textarea_delete_char_forward(obj);
1010         else if(c == LV_KEY_HOME)
1011             lv_textarea_set_cursor_pos(obj, 0);
1012         else if(c == LV_KEY_END)
1013             lv_textarea_set_cursor_pos(obj, LV_TEXTAREA_CURSOR_LAST);
1014         else if(c == LV_KEY_ENTER && lv_textarea_get_one_line(obj))
1015             lv_obj_send_event(obj, LV_EVENT_READY, NULL);
1016         else {
1017             lv_textarea_add_char(obj, c);
1018         }
1019     }
1020     else if(code == LV_EVENT_PRESSED || code == LV_EVENT_PRESSING || code == LV_EVENT_PRESS_LOST ||
1021             code == LV_EVENT_RELEASED) {
1022         update_cursor_position_on_click(e);
1023     }
1024     else if(code == LV_EVENT_DRAW_MAIN) {
1025         draw_placeholder(e);
1026     }
1027     else if(code == LV_EVENT_DRAW_POST) {
1028         draw_cursor(e);
1029     }
1030 }
1031 
label_event_cb(lv_event_t * e)1032 static void label_event_cb(lv_event_t * e)
1033 {
1034     lv_event_code_t code = lv_event_get_code(e);
1035     lv_obj_t * label = lv_event_get_current_target(e);
1036     lv_obj_t * ta = lv_obj_get_parent(label);
1037 
1038     if(code == LV_EVENT_STYLE_CHANGED || code == LV_EVENT_SIZE_CHANGED) {
1039         lv_label_set_text(label, NULL);
1040         refr_cursor_area(ta);
1041         start_cursor_blink(ta);
1042     }
1043 }
1044 
1045 /**
1046  * Called to blink the cursor
1047  * @param ta pointer to a text area
1048  * @param hide 1: hide the cursor, 0: show it
1049  */
cursor_blink_anim_cb(void * obj,int32_t show)1050 static void cursor_blink_anim_cb(void * obj, int32_t show)
1051 {
1052     lv_textarea_t * ta = (lv_textarea_t *)obj;
1053     if(show != ta->cursor.show) {
1054         ta->cursor.show = show ? 1U : 0U;
1055         lv_area_t area_tmp;
1056         lv_area_copy(&area_tmp, &ta->cursor.area);
1057         area_tmp.x1 += ta->label->coords.x1;
1058         area_tmp.y1 += ta->label->coords.y1;
1059         area_tmp.x2 += ta->label->coords.x1;
1060         area_tmp.y2 += ta->label->coords.y1;
1061         lv_obj_invalidate_area(obj, &area_tmp);
1062     }
1063 }
1064 
1065 /**
1066  * Dummy function to animate char hiding in pwd mode.
1067  * Does nothing, but a function is required in car hiding anim.
1068  * (pwd_char_hider callback do the real job)
1069  * @param ta unused
1070  * @param x unused
1071  */
pwd_char_hider_anim(void * obj,int32_t x)1072 static void pwd_char_hider_anim(void * obj, int32_t x)
1073 {
1074     LV_UNUSED(obj);
1075     LV_UNUSED(x);
1076 }
1077 
1078 /**
1079  * Call when an animation is ready to convert all characters to '*'
1080  * @param a pointer to the animation
1081  */
pwd_char_hider_anim_completed(lv_anim_t * a)1082 static void pwd_char_hider_anim_completed(lv_anim_t * a)
1083 {
1084     lv_obj_t * obj = a->var;
1085     pwd_char_hider(obj);
1086 }
1087 
1088 /**
1089  * Hide all characters (convert them to '*')
1090  * @param ta pointer to text area object
1091  */
pwd_char_hider(lv_obj_t * obj)1092 static void pwd_char_hider(lv_obj_t * obj)
1093 {
1094     lv_textarea_t * ta = (lv_textarea_t *)obj;
1095     if(ta->pwd_mode == 0) {
1096         return;
1097     }
1098 
1099     /* When ta->label is empty we get 0 back */
1100     char * txt = lv_label_get_text(ta->label);
1101     uint32_t enc_len = lv_text_get_encoded_length(txt);
1102     if(enc_len == 0) return;
1103 
1104     const char * bullet = lv_textarea_get_password_bullet(obj);
1105     const size_t bullet_len = lv_strlen(bullet);
1106     char * txt_tmp = lv_malloc(enc_len * bullet_len + 1);
1107 
1108     uint32_t i;
1109     for(i = 0; i < enc_len; i++) {
1110         lv_memcpy(&txt_tmp[i * bullet_len], bullet, bullet_len);
1111     }
1112     txt_tmp[i * bullet_len] = '\0';
1113 
1114     lv_label_set_text(ta->label, txt_tmp);
1115     lv_free(txt_tmp);
1116 
1117     auto_hide_characters_cancel(obj);
1118 
1119     refr_cursor_area(obj);
1120 }
1121 
1122 /**
1123  * Test a unicode character if it is accepted or not. Checks max length and accepted char list.
1124  * @param ta pointer to a test area object
1125  * @param c a unicode character
1126  * @return true: accepted; false: rejected
1127  */
char_is_accepted(lv_obj_t * obj,uint32_t c)1128 static bool char_is_accepted(lv_obj_t * obj, uint32_t c)
1129 {
1130     lv_textarea_t * ta = (lv_textarea_t *)obj;
1131 
1132     /*Too many characters?*/
1133     if(ta->max_length > 0 && lv_text_get_encoded_length(lv_textarea_get_text(obj)) >= ta->max_length) {
1134         return false;
1135     }
1136 
1137     if(ta->accepted_chars == NULL || ta->accepted_chars[0] == '\0') return true;
1138     /*Accepted character?*/
1139     uint32_t i = 0;
1140 
1141     while(ta->accepted_chars[i] != '\0') {
1142         uint32_t a = lv_text_encoded_next(ta->accepted_chars, &i);
1143         if(a == c) return true; /*Accepted*/
1144     }
1145 
1146     return false; /*The character wasn't in the list*/
1147 }
1148 
start_cursor_blink(lv_obj_t * obj)1149 static void start_cursor_blink(lv_obj_t * obj)
1150 {
1151     lv_textarea_t * ta = (lv_textarea_t *)obj;
1152     uint32_t blink_time = lv_obj_get_style_anim_duration(obj, LV_PART_CURSOR);
1153     if(blink_time == 0) {
1154         lv_anim_delete(obj, cursor_blink_anim_cb);
1155         ta->cursor.show = 1;
1156     }
1157     else {
1158         lv_anim_t a;
1159         lv_anim_init(&a);
1160         lv_anim_set_var(&a, ta);
1161         lv_anim_set_exec_cb(&a, cursor_blink_anim_cb);
1162         lv_anim_set_duration(&a, blink_time);
1163         lv_anim_set_reverse_duration(&a, blink_time);
1164         lv_anim_set_values(&a, 1, 0);
1165         lv_anim_set_path_cb(&a, lv_anim_path_step);
1166         lv_anim_set_repeat_count(&a, LV_ANIM_REPEAT_INFINITE);
1167         lv_anim_start(&a);
1168     }
1169 }
1170 
refr_cursor_area(lv_obj_t * obj)1171 static void refr_cursor_area(lv_obj_t * obj)
1172 {
1173     lv_textarea_t * ta = (lv_textarea_t *)obj;
1174 
1175     const lv_font_t * font = lv_obj_get_style_text_font(obj, LV_PART_MAIN);
1176     int32_t line_space = lv_obj_get_style_text_line_space(obj, LV_PART_MAIN);
1177 
1178     uint32_t cur_pos = lv_textarea_get_cursor_pos(obj);
1179     const char * txt = lv_label_get_text(ta->label);
1180 
1181     uint32_t byte_pos = lv_text_encoded_get_byte_id(txt, cur_pos);
1182     uint32_t letter = lv_text_encoded_next(&txt[byte_pos], NULL);
1183 
1184     /* Letter height and width */
1185     const int32_t letter_h = lv_font_get_line_height(font);
1186     /*Set letter_w (set not 0 on non printable but valid chars)*/
1187     uint32_t letter_space = letter;
1188     if(is_valid_but_non_printable_char(letter)) {
1189         letter_space = ' ';
1190     }
1191     int32_t letter_w = lv_font_get_glyph_width(font, letter_space, IGNORE_KERNING);
1192 
1193     lv_point_t letter_pos;
1194     lv_label_get_letter_pos(ta->label, cur_pos, &letter_pos);
1195 
1196     lv_text_align_t align = lv_obj_calculate_style_text_align(ta->label, LV_PART_MAIN, lv_label_get_text(ta->label));
1197 
1198     /*If the cursor is out of the text (most right) draw it to the next line*/
1199     if(((letter_pos.x + ta->label->coords.x1) + letter_w > ta->label->coords.x2) &&
1200        (ta->one_line == 0 && align != LV_TEXT_ALIGN_RIGHT)) {
1201 
1202         letter_pos.x = 0;
1203         letter_pos.y += letter_h + line_space;
1204 
1205         if(letter != '\0') {
1206             byte_pos += lv_text_encoded_size(&txt[byte_pos]);
1207             letter = lv_text_encoded_next(&txt[byte_pos], NULL);
1208         }
1209 
1210         uint32_t tmp = letter;
1211         if(is_valid_but_non_printable_char(letter)) {
1212             tmp = ' ';
1213         }
1214         letter_w = lv_font_get_glyph_width(font, tmp, IGNORE_KERNING);
1215     }
1216 
1217     /*Save the byte position. It is required to draw `LV_CURSOR_BLOCK`*/
1218     ta->cursor.txt_byte_pos = byte_pos;
1219 
1220     /*Calculate the cursor according to its type*/
1221     int32_t border_width = lv_obj_get_style_border_width(obj, LV_PART_CURSOR);
1222     int32_t top = lv_obj_get_style_pad_top(obj, LV_PART_CURSOR) + border_width;
1223     int32_t bottom = lv_obj_get_style_pad_bottom(obj, LV_PART_CURSOR) + border_width;
1224     int32_t left = lv_obj_get_style_pad_left(obj, LV_PART_CURSOR) + border_width;
1225     int32_t right = lv_obj_get_style_pad_right(obj, LV_PART_CURSOR) + border_width;
1226 
1227     lv_area_t cur_area;
1228     cur_area.x1 = letter_pos.x - left;
1229     cur_area.y1 = letter_pos.y - top;
1230     cur_area.x2 = letter_pos.x + right + letter_w - 1;
1231     cur_area.y2 = letter_pos.y + bottom + letter_h - 1;
1232 
1233     /*Save the new area*/
1234     lv_area_t area_tmp;
1235     lv_area_copy(&area_tmp, &ta->cursor.area);
1236     area_tmp.x1 += ta->label->coords.x1;
1237     area_tmp.y1 += ta->label->coords.y1;
1238     area_tmp.x2 += ta->label->coords.x1;
1239     area_tmp.y2 += ta->label->coords.y1;
1240     lv_obj_invalidate_area(obj, &area_tmp);
1241 
1242     lv_area_copy(&ta->cursor.area, &cur_area);
1243 
1244     lv_area_copy(&area_tmp, &ta->cursor.area);
1245     area_tmp.x1 += ta->label->coords.x1;
1246     area_tmp.y1 += ta->label->coords.y1;
1247     area_tmp.x2 += ta->label->coords.x1;
1248     area_tmp.y2 += ta->label->coords.y1;
1249     lv_obj_invalidate_area(obj, &area_tmp);
1250 }
1251 
update_cursor_position_on_click(lv_event_t * e)1252 static void update_cursor_position_on_click(lv_event_t * e)
1253 {
1254     lv_indev_t * click_source = lv_indev_active();
1255     if(click_source == NULL) return;
1256 
1257     lv_obj_t * obj = lv_event_get_current_target(e);
1258     lv_textarea_t * ta = (lv_textarea_t *)obj;
1259     if(ta->cursor.click_pos == 0) return;
1260 
1261     if(lv_indev_get_type(click_source) == LV_INDEV_TYPE_KEYPAD ||
1262        lv_indev_get_type(click_source) == LV_INDEV_TYPE_ENCODER) {
1263         return;
1264     }
1265 
1266     lv_area_t label_coords;
1267     lv_obj_get_coords(ta->label, &label_coords);
1268 
1269     lv_point_t point_act, vect_act;
1270     lv_indev_get_point(click_source, &point_act);
1271     lv_indev_get_vect(click_source, &vect_act);
1272 
1273     if(point_act.x < 0 || point_act.y < 0) return; /*Ignore event from keypad*/
1274     lv_point_t rel_pos;
1275     rel_pos.x = point_act.x - label_coords.x1;
1276     rel_pos.y = point_act.y - label_coords.y1;
1277 
1278     const lv_event_code_t code = lv_event_get_code(e);
1279 
1280     int32_t label_width = lv_obj_get_width(ta->label);
1281     uint32_t char_id_at_click = 0;
1282 
1283 #if LV_LABEL_TEXT_SELECTION
1284     lv_label_t * label_data = (lv_label_t *)ta->label;
1285     bool click_outside_label = false;
1286     /*Check if the click happened on the left side of the area outside the label*/
1287     if(rel_pos.x < 0) {
1288         char_id_at_click = 0;
1289         click_outside_label = true;
1290     }
1291     /*Check if the click happened on the right side of the area outside the label*/
1292     else if(rel_pos.x >= label_width) {
1293         char_id_at_click = LV_TEXTAREA_CURSOR_LAST;
1294         click_outside_label = true;
1295     }
1296     else {
1297         char_id_at_click = lv_label_get_letter_on(ta->label, &rel_pos, true);
1298         click_outside_label = !lv_label_is_char_under_pos(ta->label, &rel_pos);
1299     }
1300 
1301     if(ta->text_sel_en) {
1302         if(!ta->text_sel_in_prog && !click_outside_label && code == LV_EVENT_PRESSED) {
1303             /*Input device just went down. Store the selection start position*/
1304             ta->sel_start    = char_id_at_click;
1305             ta->sel_end      = LV_LABEL_TEXT_SELECTION_OFF;
1306             ta->text_sel_in_prog = 1;
1307             lv_obj_remove_flag(obj, LV_OBJ_FLAG_SCROLL_CHAIN);
1308         }
1309         else if(ta->text_sel_in_prog && code == LV_EVENT_PRESSING) {
1310             /*Input device may be moving. Store the end position*/
1311             ta->sel_end = char_id_at_click;
1312         }
1313         else if(ta->text_sel_in_prog && (code == LV_EVENT_PRESS_LOST || code == LV_EVENT_RELEASED)) {
1314             /*Input device is released. Check if anything was selected.*/
1315             lv_obj_add_flag(obj, LV_OBJ_FLAG_SCROLL_CHAIN);
1316         }
1317     }
1318 
1319     if(ta->text_sel_in_prog || code == LV_EVENT_PRESSED) lv_textarea_set_cursor_pos(obj, char_id_at_click);
1320 
1321     if(ta->text_sel_in_prog) {
1322         /*If the selected area has changed then update the real values and*/
1323 
1324         /*Invalidate the text area.*/
1325         if(ta->sel_start > ta->sel_end) {
1326             if(label_data->sel_start != ta->sel_end || label_data->sel_end != ta->sel_start) {
1327                 label_data->sel_start = ta->sel_end;
1328                 label_data->sel_end   = ta->sel_start;
1329                 lv_obj_invalidate(obj);
1330             }
1331         }
1332         else if(ta->sel_start < ta->sel_end) {
1333             if(label_data->sel_start != ta->sel_start || label_data->sel_end != ta->sel_end) {
1334                 label_data->sel_start = ta->sel_start;
1335                 label_data->sel_end   = ta->sel_end;
1336                 lv_obj_invalidate(obj);
1337             }
1338         }
1339         else {
1340             if(label_data->sel_start != LV_DRAW_LABEL_NO_TXT_SEL || label_data->sel_end != LV_DRAW_LABEL_NO_TXT_SEL) {
1341                 label_data->sel_start = LV_DRAW_LABEL_NO_TXT_SEL;
1342                 label_data->sel_end   = LV_DRAW_LABEL_NO_TXT_SEL;
1343                 lv_obj_invalidate(obj);
1344             }
1345         }
1346         /*Finish selection if necessary*/
1347         if(code == LV_EVENT_PRESS_LOST || code == LV_EVENT_RELEASED) {
1348             ta->text_sel_in_prog = 0;
1349         }
1350     }
1351 #else
1352     /*Check if the click happened on the left side of the area outside the label*/
1353     if(rel_pos.x < 0) {
1354         char_id_at_click = 0;
1355     }
1356     /*Check if the click happened on the right side of the area outside the label*/
1357     else if(rel_pos.x >= label_width) {
1358         char_id_at_click = LV_TEXTAREA_CURSOR_LAST;
1359     }
1360     else {
1361         char_id_at_click = lv_label_get_letter_on(ta->label, &rel_pos, true);
1362     }
1363 
1364     if(code == LV_EVENT_PRESSED) lv_textarea_set_cursor_pos(obj, char_id_at_click);
1365 #endif
1366 }
1367 
1368 /* Returns LV_RESULT_OK when no operation were performed
1369  * Returns LV_RESULT_INVALID when a user defined text was inserted */
insert_handler(lv_obj_t * obj,const char * txt)1370 static lv_result_t insert_handler(lv_obj_t * obj, const char * txt)
1371 {
1372     ta_insert_replace = NULL;
1373     lv_obj_send_event(obj, LV_EVENT_INSERT, (char *)txt);
1374 
1375     /* Drop txt if insert replace is set to '\0' */
1376     if(ta_insert_replace && ta_insert_replace[0] == '\0')
1377         return LV_RESULT_INVALID;
1378 
1379     if(ta_insert_replace) {
1380         /*Add the replaced text directly it's different from the original*/
1381         if(lv_strcmp(ta_insert_replace, txt)) {
1382             lv_textarea_add_text(obj, ta_insert_replace);
1383             return LV_RESULT_INVALID;
1384         }
1385     }
1386 
1387     return LV_RESULT_OK;
1388 }
1389 
draw_placeholder(lv_event_t * e)1390 static void draw_placeholder(lv_event_t * e)
1391 {
1392     lv_obj_t * obj = lv_event_get_current_target(e);
1393     lv_textarea_t * ta = (lv_textarea_t *)obj;
1394     lv_layer_t * layer = lv_event_get_layer(e);
1395     const char * txt = lv_label_get_text(ta->label);
1396 
1397     /*Draw the place holder*/
1398     if(txt[0] == '\0' && ta->placeholder_txt && ta->placeholder_txt[0] != 0) {
1399         lv_draw_label_dsc_t ph_dsc;
1400         lv_draw_label_dsc_init(&ph_dsc);
1401         ph_dsc.base.layer = layer;
1402         lv_obj_init_draw_label_dsc(obj, LV_PART_TEXTAREA_PLACEHOLDER, &ph_dsc);
1403 
1404         if(ta->one_line) ph_dsc.flag |= LV_TEXT_FLAG_EXPAND;
1405 
1406         int32_t left = lv_obj_get_style_pad_left(obj, LV_PART_MAIN);
1407         int32_t right = lv_obj_get_style_pad_right(obj, LV_PART_MAIN);
1408         int32_t top = lv_obj_get_style_pad_top(obj, LV_PART_MAIN);
1409         int32_t bottom = lv_obj_get_style_pad_bottom(obj, LV_PART_MAIN);
1410         int32_t border_width = lv_obj_get_style_border_width(obj, LV_PART_MAIN);
1411         lv_area_t ph_coords;
1412         lv_area_copy(&ph_coords, &obj->coords);
1413         ph_coords.x1 += left + border_width;
1414         ph_coords.x2 -= right + border_width;
1415         ph_coords.y1 += top + border_width;
1416         ph_coords.y2 -= bottom + border_width;
1417         ph_dsc.text = ta->placeholder_txt;
1418         lv_draw_label(layer, &ph_dsc, &ph_coords);
1419     }
1420 }
1421 
draw_cursor(lv_event_t * e)1422 static void draw_cursor(lv_event_t * e)
1423 {
1424     lv_obj_t * obj = lv_event_get_current_target(e);
1425     lv_textarea_t * ta = (lv_textarea_t *)obj;
1426     lv_layer_t * layer = lv_event_get_layer(e);
1427     const char * txt = lv_label_get_text(ta->label);
1428 
1429     if(ta->cursor.show == 0) return;
1430 
1431     lv_draw_rect_dsc_t cur_dsc;
1432     lv_draw_rect_dsc_init(&cur_dsc);
1433     cur_dsc.base.layer = layer;
1434     lv_obj_init_draw_rect_dsc(obj, LV_PART_CURSOR, &cur_dsc);
1435 
1436     /*Draw he cursor according to the type*/
1437     lv_area_t cur_area;
1438     lv_area_copy(&cur_area, &ta->cursor.area);
1439 
1440     cur_area.x1 += ta->label->coords.x1;
1441     cur_area.y1 += ta->label->coords.y1;
1442     cur_area.x2 += ta->label->coords.x1;
1443     cur_area.y2 += ta->label->coords.y1;
1444 
1445     lv_draw_rect(layer, &cur_dsc, &cur_area);
1446 
1447     int32_t border_width = lv_obj_get_style_border_width(obj, LV_PART_CURSOR);
1448     int32_t left = lv_obj_get_style_pad_left(obj, LV_PART_CURSOR) + border_width;
1449     int32_t top = lv_obj_get_style_pad_top(obj, LV_PART_CURSOR) + border_width;
1450     char letter_buf[8] = {0};
1451     lv_memcpy(letter_buf, &txt[ta->cursor.txt_byte_pos], lv_text_encoded_size(&txt[ta->cursor.txt_byte_pos]));
1452 
1453     cur_area.x1 += left;
1454     cur_area.y1 += top;
1455 
1456     /*Draw the letter over the cursor only if
1457      *the cursor has background or the letter has different color than the original.
1458      *Else the original letter is drawn twice which makes it look bolder*/
1459     lv_color_t label_color = lv_obj_get_style_text_color(ta->label, 0);
1460     lv_draw_label_dsc_t cur_label_dsc;
1461     lv_draw_label_dsc_init(&cur_label_dsc);
1462     cur_label_dsc.base.layer = layer;
1463     lv_obj_init_draw_label_dsc(obj, LV_PART_CURSOR, &cur_label_dsc);
1464     if(cur_dsc.bg_opa > LV_OPA_MIN || !lv_color_eq(cur_label_dsc.color, label_color)) {
1465         cur_label_dsc.text = letter_buf;
1466         cur_label_dsc.text_local = true;
1467         lv_draw_label(layer, &cur_label_dsc, &cur_area);
1468     }
1469 }
1470 
auto_hide_characters(lv_obj_t * obj)1471 static void auto_hide_characters(lv_obj_t * obj)
1472 {
1473     lv_textarea_t * ta = (lv_textarea_t *) obj;
1474 
1475     if(ta->pwd_show_time == 0) {
1476         pwd_char_hider(obj);
1477     }
1478     else {
1479         lv_anim_t a;
1480         lv_anim_init(&a);
1481         lv_anim_set_var(&a, ta);
1482         lv_anim_set_exec_cb(&a, pwd_char_hider_anim);
1483         lv_anim_set_duration(&a, ta->pwd_show_time);
1484         lv_anim_set_values(&a, 0, 1);
1485         lv_anim_set_path_cb(&a, lv_anim_path_step);
1486         lv_anim_set_completed_cb(&a, pwd_char_hider_anim_completed);
1487         lv_anim_start(&a);
1488     }
1489 }
1490 
auto_hide_characters_cancel(lv_obj_t * obj)1491 static void auto_hide_characters_cancel(lv_obj_t * obj)
1492 {
1493     lv_anim_delete(obj, pwd_char_hider_anim);
1494 }
1495 
is_valid_but_non_printable_char(const uint32_t letter)1496 static inline bool is_valid_but_non_printable_char(const uint32_t letter)
1497 {
1498     if(letter == '\0' || letter == '\n' || letter == '\r') {
1499         return true;
1500     }
1501 
1502     return false;
1503 }
1504 
1505 #endif
1506