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