From 3963ee2bddba2e959dacf98d2fcd19a63dae2b86 Mon Sep 17 00:00:00 2001 From: Carlos Diaz Date: Sat, 26 Oct 2024 12:20:08 -0600 Subject: [PATCH] feat(draw_label): Support simultaneous text selection and recolor (#7116) --- src/draw/lv_draw_label.c | 119 +++++++++++------- .../draw/label_selection_and_recolor.png | Bin 0 -> 4791 bytes .../draw/label_selection_and_recolor.png | Bin 0 -> 4683 bytes tests/src/test_cases/draw/test_draw_label.c | 22 ++++ 4 files changed, 99 insertions(+), 42 deletions(-) create mode 100644 tests/ref_imgs/draw/label_selection_and_recolor.png create mode 100644 tests/ref_imgs_vg_lite/draw/label_selection_and_recolor.png diff --git a/src/draw/lv_draw_label.c b/src/draw/lv_draw_label.c index e39b312f7e..d2e5f2a11a 100644 --- a/src/draw/lv_draw_label.c +++ b/src/draw/lv_draw_label.c @@ -33,9 +33,9 @@ * TYPEDEFS **********************/ enum { - CMD_STATE_WAIT, - CMD_STATE_PAR, - CMD_STATE_IN, + RECOLOR_CMD_STATE_WAIT_FOR_PARAMETER, + RECOLOR_CMD_STATE_PARAMETER, + RECOLOR_CMD_STATE_TEXT_INPUT, }; typedef unsigned char cmd_state_t; @@ -269,11 +269,12 @@ void lv_draw_label_iterate_characters(lv_draw_unit_t * draw_unit, const lv_draw_ fill_dsc.opa = dsc->opa; int32_t underline_width = font->underline_thickness ? font->underline_thickness : 1; int32_t line_start_x; - uint32_t i; - uint32_t par_start = 0; + uint32_t next_char_offset; + uint32_t recolor_command_start_index = 0; int32_t letter_w; - cmd_state_t cmd_state = CMD_STATE_WAIT; + cmd_state_t recolor_cmd_state = RECOLOR_CMD_STATE_WAIT_FOR_PARAMETER; lv_color_t recolor = lv_color_black(); /* Holds the selected color inside the recolor command */ + uint8_t is_first_space_after_cmd = 0; /*Write out all lines*/ while(dsc->text[line_start] != '\0') { @@ -281,8 +282,8 @@ void lv_draw_label_iterate_characters(lv_draw_unit_t * draw_unit, const lv_draw_ line_start_x = pos.x; /*Write all letter of a line*/ - cmd_state = CMD_STATE_WAIT; - i = 0; + recolor_cmd_state = RECOLOR_CMD_STATE_WAIT_FOR_PARAMETER; + next_char_offset = 0; #if LV_USE_BIDI char * bidi_txt = lv_malloc(line_end - line_start + 1); LV_ASSERT_MALLOC(bidi_txt); @@ -291,65 +292,98 @@ void lv_draw_label_iterate_characters(lv_draw_unit_t * draw_unit, const lv_draw_ const char * bidi_txt = dsc->text + line_start; #endif - while(i < line_end - line_start) { + while(next_char_offset < line_end - line_start) { uint32_t logical_char_pos = 0; + + /* Check if the text selection is enabled */ if(sel_start != 0xFFFF && sel_end != 0xFFFF) { #if LV_USE_BIDI logical_char_pos = lv_text_encoded_get_char_id(dsc->text, line_start); - uint32_t t = lv_text_encoded_get_char_id(bidi_txt, i); + uint32_t t = lv_text_encoded_get_char_id(bidi_txt, next_char_offset); logical_char_pos += lv_bidi_get_logical_pos(bidi_txt, NULL, line_end - line_start, base_dir, t, NULL); #else - logical_char_pos = lv_text_encoded_get_char_id(dsc->text, line_start + i); + logical_char_pos = lv_text_encoded_get_char_id(dsc->text, line_start + next_char_offset); #endif } uint32_t letter; uint32_t letter_next; - lv_text_encoded_letter_next_2(bidi_txt, &letter, &letter_next, &i); + lv_text_encoded_letter_next_2(bidi_txt, &letter, &letter_next, &next_char_offset); - /* Handle the recolor command */ + /* If recolor is enabled */ if((dsc->flag & LV_TEXT_FLAG_RECOLOR) != 0) { + if(letter == (uint32_t)LV_TXT_COLOR_CMD[0]) { - if(cmd_state == CMD_STATE_WAIT) { /*Start char*/ - par_start = i; - cmd_state = CMD_STATE_PAR; + /* Handle the recolor command marker depending of the current recolor state */ + + if(recolor_cmd_state == RECOLOR_CMD_STATE_WAIT_FOR_PARAMETER) { + recolor_command_start_index = next_char_offset; + recolor_cmd_state = RECOLOR_CMD_STATE_PARAMETER; continue; } - else if(cmd_state == CMD_STATE_PAR) { /*Other start char in parameter escaped cmd. char*/ - cmd_state = CMD_STATE_WAIT; + /*Other start char in parameter escaped cmd. char*/ + else if(recolor_cmd_state == RECOLOR_CMD_STATE_PARAMETER) { + recolor_cmd_state = RECOLOR_CMD_STATE_WAIT_FOR_PARAMETER; } - else if(cmd_state == CMD_STATE_IN) { /*Command end*/ - cmd_state = CMD_STATE_WAIT; + /* If letter is LV_TXT_COLOR_CMD and we were in the CMD_STATE_IN then the recolor close marked has been found */ + else if(recolor_cmd_state == RECOLOR_CMD_STATE_TEXT_INPUT) { + recolor_cmd_state = RECOLOR_CMD_STATE_WAIT_FOR_PARAMETER; continue; } } - /*Skip the color parameter and wait the space after it*/ - if(cmd_state == CMD_STATE_PAR) { - if(letter == ' ') { - /*Get the parameter*/ - if(i - par_start == LABEL_RECOLOR_PAR_LENGTH + 1) { - char buf[LABEL_RECOLOR_PAR_LENGTH + 1]; - lv_memcpy(buf, &bidi_txt[par_start], LABEL_RECOLOR_PAR_LENGTH); - buf[LABEL_RECOLOR_PAR_LENGTH] = '\0'; - int r, g, b; - r = (hex_char_to_num(buf[0]) << 4) + hex_char_to_num(buf[1]); - g = (hex_char_to_num(buf[2]) << 4) + hex_char_to_num(buf[3]); - b = (hex_char_to_num(buf[4]) << 4) + hex_char_to_num(buf[5]); + /* Find the first space (aka ' ') after the recolor command parameter, we need to skip rendering it */ + if((recolor_cmd_state == RECOLOR_CMD_STATE_PARAMETER) && (letter == ' ') && (is_first_space_after_cmd == 0)) { + is_first_space_after_cmd = 1; + } + else { + is_first_space_after_cmd = 0; + } - recolor = lv_color_make(r, g, b); - } - else { - recolor.red = dsc->color.red; - recolor.blue = dsc->color.blue; - recolor.green = dsc->color.green; - } - cmd_state = CMD_STATE_IN; /*After the parameter the text is in the command*/ + /* Skip the color parameter and wait the space after it + * Once we have reach the space ' ', then we will extract the color information + * and store it into the recolor variable */ + if(recolor_cmd_state == RECOLOR_CMD_STATE_PARAMETER) { + /* Not an space? Continue with the next character */ + if(letter != ' ') { + continue; } + + /*Get the recolor parameter*/ + if((next_char_offset - recolor_command_start_index) == LABEL_RECOLOR_PAR_LENGTH + 1) { + /* Temporary buffer to hold the recolor information */ + char buf[LABEL_RECOLOR_PAR_LENGTH + 1]; + lv_memcpy(buf, &bidi_txt[recolor_command_start_index], LABEL_RECOLOR_PAR_LENGTH); + buf[LABEL_RECOLOR_PAR_LENGTH] = '\0'; + + uint8_t r, g, b; + r = (hex_char_to_num(buf[0]) << 4) + hex_char_to_num(buf[1]); + g = (hex_char_to_num(buf[2]) << 4) + hex_char_to_num(buf[3]); + b = (hex_char_to_num(buf[4]) << 4) + hex_char_to_num(buf[5]); + + recolor = lv_color_make(r, g, b); + } + else { + recolor.red = dsc->color.red; + recolor.blue = dsc->color.blue; + recolor.green = dsc->color.green; + } + + /*After the parameter the text is in the command*/ + recolor_cmd_state = RECOLOR_CMD_STATE_TEXT_INPUT; + } + + /* Don't draw the first space after the recolor command */ + if(is_first_space_after_cmd) { continue; } } + /* If we're in the CMD_STATE_IN state then we need to subtract the recolor command length */ + if(((dsc->flag & LV_TEXT_FLAG_RECOLOR) != 0) && (recolor_cmd_state == RECOLOR_CMD_STATE_TEXT_INPUT)) { + logical_char_pos -= (LABEL_RECOLOR_PAR_LENGTH + 1); + } + letter_w = lv_font_get_glyph_width(font, letter, letter_next); /*Always set the bg_coordinates for placeholder drawing*/ @@ -358,7 +392,7 @@ void lv_draw_label_iterate_characters(lv_draw_unit_t * draw_unit, const lv_draw_ bg_coords.x2 = pos.x + letter_w - 1; bg_coords.y2 = pos.y + line_height - 1; - if(i >= line_end - line_start) { + if(next_char_offset >= line_end - line_start) { if(dsc->decor & LV_TEXT_DECOR_UNDERLINE) { lv_area_t fill_area; fill_area.x1 = line_start_x; @@ -381,12 +415,13 @@ void lv_draw_label_iterate_characters(lv_draw_unit_t * draw_unit, const lv_draw_ } } + /* Handle text selection */ if(sel_start != 0xFFFF && sel_end != 0xFFFF && logical_char_pos >= sel_start && logical_char_pos < sel_end) { draw_letter_dsc.color = dsc->sel_color; fill_dsc.color = dsc->sel_bg_color; cb(draw_unit, NULL, &fill_dsc, &bg_coords); } - else if(cmd_state == CMD_STATE_IN) { + else if(recolor_cmd_state == RECOLOR_CMD_STATE_TEXT_INPUT) { draw_letter_dsc.color = recolor; } else { diff --git a/tests/ref_imgs/draw/label_selection_and_recolor.png b/tests/ref_imgs/draw/label_selection_and_recolor.png new file mode 100644 index 0000000000000000000000000000000000000000..3ff60c09bca02b7af14a6cee341bb09f35450387 GIT binary patch literal 4791 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 literal 0 HcmV?d00001 diff --git a/tests/src/test_cases/draw/test_draw_label.c b/tests/src/test_cases/draw/test_draw_label.c index 749e9fcb83..8e09f8c196 100644 --- a/tests/src/test_cases/draw/test_draw_label.c +++ b/tests/src/test_cases/draw/test_draw_label.c @@ -123,4 +123,26 @@ void test_label_decor(void) TEST_ASSERT_EQUAL_SCREENSHOT("draw/label_decor.png"); } +void test_label_selection_and_recolor(void) +{ + lv_text_decor_t decor = LV_TEXT_DECOR_NONE; + lv_color_t color = lv_palette_main(LV_PALETTE_BLUE); + lv_color_t sel_bg_color = lv_palette_lighten(LV_PALETTE_RED, 4); + lv_color_t sel_color = lv_palette_darken(LV_PALETTE_RED, 4); + + lv_obj_t * label = lv_label_create(lv_screen_active()); + lv_label_set_recolor(label, true); + lv_label_set_text(label, "Hi, Testing the #00ff00 colored labels.#"); + lv_obj_set_style_text_decor(label, decor, 0); + lv_obj_set_style_text_color(label, color, 0); + lv_obj_set_style_text_opa(label, LV_OPA_COVER, 0); + lv_obj_set_style_text_align(label, LV_TEXT_ALIGN_LEFT, 0); + lv_obj_set_style_bg_color(label, sel_bg_color, LV_PART_SELECTED); + lv_obj_set_style_text_color(label, sel_color, LV_PART_SELECTED); + lv_label_set_text_selection_start(label, 10); + lv_label_set_text_selection_end(label, 25); + + TEST_ASSERT_EQUAL_SCREENSHOT("draw/label_selection_and_recolor.png"); +} + #endif