diff --git a/Kconfig b/Kconfig index 7d5715c2c..a37e2c69b 100644 --- a/Kconfig +++ b/Kconfig @@ -972,6 +972,10 @@ menu "LVGL configuration" help Minimum number of characters in a long word to put on a line after a break + config LV_TXT_COLOR_CMD + string "The control character to use for signalling text recoloring" + default "#" + config LV_USE_BIDI bool "Support bidirectional texts" help diff --git a/lv_conf_template.h b/lv_conf_template.h index bfd715eee..03ed16485 100644 --- a/lv_conf_template.h +++ b/lv_conf_template.h @@ -607,6 +607,9 @@ * In these languages characters should be replaced with another form based on their position in the text */ #define LV_USE_ARABIC_PERSIAN_CHARS 0 +/*The control character to use for signaling text recoloring*/ +#define LV_TXT_COLOR_CMD "#" + /*================== * WIDGETS *================*/ diff --git a/src/draw/lv_draw_label.c b/src/draw/lv_draw_label.c index 341bac82d..f4b76e8ce 100644 --- a/src/draw/lv_draw_label.c +++ b/src/draw/lv_draw_label.c @@ -32,10 +32,17 @@ /********************** * TYPEDEFS **********************/ +enum { + CMD_STATE_WAIT, + CMD_STATE_PAR, + CMD_STATE_IN, +}; +typedef unsigned char cmd_state_t; /********************** * STATIC PROTOTYPES **********************/ +static uint8_t hex_char_to_num(char hex); static void draw_letter(lv_draw_unit_t * draw_unit, lv_draw_glyph_dsc_t * dsc, const lv_point_t * pos, const lv_font_t * font, uint32_t letter, lv_draw_glyph_cb_t cb); @@ -228,14 +235,16 @@ void lv_draw_label_iterate_characters(lv_draw_unit_t * draw_unit, const lv_draw_ /*Align to middle*/ if(align == LV_TEXT_ALIGN_CENTER) { - line_width = lv_text_get_width(&dsc->text[line_start], line_end - line_start, font, dsc->letter_space); + line_width = lv_text_get_width_with_flags(&dsc->text[line_start], line_end - line_start, font, dsc->letter_space, + dsc->flag); pos.x += (lv_area_get_width(coords) - line_width) / 2; } /*Align to the right*/ else if(align == LV_TEXT_ALIGN_RIGHT) { - line_width = lv_text_get_width(&dsc->text[line_start], line_end - line_start, font, dsc->letter_space); + line_width = lv_text_get_width_with_flags(&dsc->text[line_start], line_end - line_start, font, dsc->letter_space, + dsc->flag); pos.x += lv_area_get_width(coords) - line_width; } @@ -260,7 +269,10 @@ void lv_draw_label_iterate_characters(lv_draw_unit_t * draw_unit, const lv_draw_ int32_t underline_width = font->underline_thickness ? font->underline_thickness : 1; int32_t line_start_x; uint32_t i; + uint32_t par_start = 0; int32_t letter_w; + cmd_state_t cmd_state = CMD_STATE_WAIT; + lv_color_t recolor = lv_color_black(); /* Holds the selected color inside the recolor command */ /*Write out all lines*/ while(dsc->text[line_start] != '\0') { @@ -268,6 +280,7 @@ 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; #if LV_USE_BIDI char * bidi_txt = lv_malloc(line_end - line_start + 1); @@ -293,6 +306,49 @@ void lv_draw_label_iterate_characters(lv_draw_unit_t * draw_unit, const lv_draw_ uint32_t letter_next; lv_text_encoded_letter_next_2(bidi_txt, &letter, &letter_next, &i); + /* Handle the recolor command */ + 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; + continue; + } + else if(cmd_state == CMD_STATE_PAR) { /*Other start char in parameter escaped cmd. char*/ + cmd_state = CMD_STATE_WAIT; + } + else if(cmd_state == CMD_STATE_IN) { /*Command end*/ + cmd_state = CMD_STATE_WAIT; + 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]); + + 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*/ + } + continue; + } + } + letter_w = lv_font_get_glyph_width(font, letter, letter_next); /*Always set the bg_coordinates for placeholder drawing*/ @@ -329,6 +385,9 @@ void lv_draw_label_iterate_characters(lv_draw_unit_t * draw_unit, const lv_draw_ fill_dsc.color = dsc->sel_bg_color; cb(draw_unit, NULL, &fill_dsc, &bg_coords); } + else if(cmd_state == CMD_STATE_IN) { + draw_letter_dsc.color = recolor; + } else { draw_letter_dsc.color = dsc->color; } @@ -352,14 +411,14 @@ void lv_draw_label_iterate_characters(lv_draw_unit_t * draw_unit, const lv_draw_ /*Align to middle*/ if(align == LV_TEXT_ALIGN_CENTER) { line_width = - lv_text_get_width(&dsc->text[line_start], line_end - line_start, font, dsc->letter_space); + lv_text_get_width_with_flags(&dsc->text[line_start], line_end - line_start, font, dsc->letter_space, dsc->flag); pos.x += (lv_area_get_width(coords) - line_width) / 2; } /*Align to the right*/ else if(align == LV_TEXT_ALIGN_RIGHT) { line_width = - lv_text_get_width(&dsc->text[line_start], line_end - line_start, font, dsc->letter_space); + lv_text_get_width_with_flags(&dsc->text[line_start], line_end - line_start, font, dsc->letter_space, dsc->flag); pos.x += lv_area_get_width(coords) - line_width; } @@ -378,6 +437,17 @@ void lv_draw_label_iterate_characters(lv_draw_unit_t * draw_unit, const lv_draw_ * STATIC FUNCTIONS **********************/ +/** + * Convert a hexadecimal characters to a number (0..15) + * @param hex Pointer to a hexadecimal character (0..9, A..F) + * @return the numerical value of `hex` or 0 on error + */ +static uint8_t hex_char_to_num(char hex) +{ + if(hex >= '0' && hex <= '9') return hex - '0'; + if(hex >= 'a') hex -= 'a' - 'A'; /*Convert to upper case*/ + return 'A' <= hex && hex <= 'F' ? hex - 'A' + 10 : 0; +} static void draw_letter(lv_draw_unit_t * draw_unit, lv_draw_glyph_dsc_t * dsc, const lv_point_t * pos, const lv_font_t * font, uint32_t letter, lv_draw_glyph_cb_t cb) { diff --git a/src/lv_conf_internal.h b/src/lv_conf_internal.h index 9bb336ed1..439e1ad62 100644 --- a/src/lv_conf_internal.h +++ b/src/lv_conf_internal.h @@ -1790,6 +1790,15 @@ #endif #endif +/*The control character to use for signaling text recoloring*/ +#ifndef LV_TXT_COLOR_CMD + #ifdef CONFIG_LV_TXT_COLOR_CMD + #define LV_TXT_COLOR_CMD CONFIG_LV_TXT_COLOR_CMD + #else + #define LV_TXT_COLOR_CMD "#" + #endif +#endif + /*================== * WIDGETS *================*/ diff --git a/src/misc/lv_text.c b/src/misc/lv_text.c index 11081f959..5b33ada40 100644 --- a/src/misc/lv_text.c +++ b/src/misc/lv_text.c @@ -136,6 +136,37 @@ void lv_text_get_size(lv_point_t * size_res, const char * text, const lv_font_t size_res->y -= line_space; } +bool lv_text_is_cmd(lv_text_cmd_state_t * state, uint32_t c) +{ + bool ret = false; + + if(c == (uint32_t)LV_TXT_COLOR_CMD[0]) { + if(*state == LV_TEXT_CMD_STATE_WAIT) { /*Start char*/ + *state = LV_TEXT_CMD_STATE_PAR; + ret = true; + } + /*Other start char in parameter is escaped cmd. char*/ + else if(*state == LV_TEXT_CMD_STATE_WAIT) { + *state = LV_TEXT_CMD_STATE_WAIT; + } + /*Command end*/ + else if(*state == LV_TEXT_CMD_STATE_IN) { + *state = LV_TEXT_CMD_STATE_WAIT; + ret = true; + } + } + + /*Skip the color parameter and wait the space after it*/ + if(*state == LV_TEXT_CMD_STATE_PAR) { + if(c == ' ') { + *state = LV_TEXT_CMD_STATE_IN; /*After the parameter the text is in the command*/ + } + ret = true; + } + + return ret; +} + /** * Get the next word of text. A word is delimited by break characters. * @@ -164,11 +195,13 @@ void lv_text_get_size(lv_point_t * size_res, const char * text, const lv_font_t * @param max_width max width of the text (break the lines to fit this size). Set COORD_MAX to avoid line breaks * @param flags settings for the text from 'txt_flag_type' enum * @param[out] word_w_ptr width (in pixels) of the parsed word. May be NULL. + * @param cmd_state Pointer to a lv_text_cmd_state_t variable which stored the current state of command proocessing * @return the index of the first char of the next word (in byte index not letter index. With UTF-8 they are different) */ static uint32_t lv_text_get_next_word(const char * txt, const lv_font_t * font, int32_t letter_space, int32_t max_width, - lv_text_flag_t flag, uint32_t * word_w_ptr) + lv_text_flag_t flag, uint32_t * word_w_ptr, + lv_text_cmd_state_t * cmd_state) { if(txt == NULL || txt[0] == '\0') return 0; if(font == NULL) return 0; @@ -192,6 +225,16 @@ static uint32_t lv_text_get_next_word(const char * txt, const lv_font_t * font, letter_next = lv_text_encoded_next(txt, &i_next_next); word_len++; + /*Handle the recolor command*/ + if((flag & LV_TEXT_FLAG_RECOLOR) != 0) { + if(lv_text_is_cmd(cmd_state, letter)) { + i = i_next; + i_next = i_next_next; + letter = letter_next; + continue; /*Skip the letter if it is part of a command*/ + } + } + letter_w = lv_font_get_glyph_width(font, letter, letter_next); cur_w += letter_w; @@ -297,6 +340,8 @@ uint32_t lv_text_get_next_line(const char * txt, const lv_font_t * font, } if(flag & LV_TEXT_FLAG_EXPAND) max_width = LV_COORD_MAX; + lv_text_cmd_state_t cmd_state = LV_TEXT_CMD_STATE_WAIT; + uint32_t i = 0; /*Iterating index into txt*/ while(txt[i] != '\0' && max_width > 0) { @@ -304,7 +349,7 @@ uint32_t lv_text_get_next_line(const char * txt, const lv_font_t * font, if(i == 0) word_flag |= LV_TEXT_FLAG_BREAK_ALL; uint32_t word_w = 0; - uint32_t advance = lv_text_get_next_word(&txt[i], font, letter_space, max_width, word_flag, &word_w); + uint32_t advance = lv_text_get_next_word(&txt[i], font, letter_space, max_width, word_flag, &word_w, &cmd_state); max_width -= word_w; line_w += word_w; @@ -369,6 +414,45 @@ int32_t lv_text_get_width(const char * txt, uint32_t length, const lv_font_t * f return width; } +int32_t lv_text_get_width_with_flags(const char * txt, uint32_t length, const lv_font_t * font, int32_t letter_space, + lv_text_flag_t flags) +{ + if(txt == NULL) return 0; + if(font == NULL) return 0; + if(txt[0] == '\0') return 0; + + uint32_t i = 0; + int32_t width = 0; + lv_text_cmd_state_t cmd_state = LV_TEXT_CMD_STATE_WAIT; + + if(length != 0) { + while(i < length) { + uint32_t letter; + uint32_t letter_next; + lv_text_encoded_letter_next_2(txt, &letter, &letter_next, &i); + + if((flags & LV_TEXT_FLAG_RECOLOR) != 0) { + if(lv_text_is_cmd(&cmd_state, letter) != false) { + continue; + } + } + + int32_t char_width = lv_font_get_glyph_width(font, letter, letter_next); + if(char_width > 0) { + width += char_width; + width += letter_space; + } + } + + if(width > 0) { + width -= letter_space; /*Trim the last letter space. Important if the text is center + aligned*/ + } + } + + return width; +} + void lv_text_ins(char * txt_buf, uint32_t pos, const char * ins_txt) { if(txt_buf == NULL || ins_txt == NULL) return; diff --git a/src/misc/lv_text.h b/src/misc/lv_text.h index f2407dd8e..1381c36b5 100644 --- a/src/misc/lv_text.h +++ b/src/misc/lv_text.h @@ -23,6 +23,9 @@ extern "C" { /********************* * DEFINES *********************/ +#ifndef LV_TXT_COLOR_CMD +#define LV_TXT_COLOR_CMD "#" +#endif #define LV_TXT_ENC_UTF8 1 #define LV_TXT_ENC_ASCII 2 @@ -43,6 +46,7 @@ typedef enum { Otherwise breaks are inserted at word boundaries, as configured via LV_TXT_BREAK_CHARS or according to LV_TXT_LINE_BREAK_LONG_LEN, LV_TXT_LINE_BREAK_LONG_PRE_MIN_LEN, and LV_TXT_LINE_BREAK_LONG_POST_MIN_LEN.*/ + LV_TEXT_FLAG_RECOLOR = 0x08, /**< Enable parsing of recolor command*/ } lv_text_flag_t; /** Label align policy*/ @@ -53,6 +57,13 @@ typedef enum { LV_TEXT_ALIGN_RIGHT, /**< Align text to right*/ } lv_text_align_t; +/** State machine for text renderer. */ +typedef enum { + LV_TEXT_CMD_STATE_WAIT, /**< Waiting for command*/ + LV_TEXT_CMD_STATE_PAR, /**< Processing the parameter*/ + LV_TEXT_CMD_STATE_IN, /**< Processing the command*/ +} lv_text_cmd_state_t; + /********************** * GLOBAL PROTOTYPES **********************/ @@ -83,6 +94,26 @@ void lv_text_get_size(lv_point_t * size_res, const char * text, const lv_font_t */ int32_t lv_text_get_width(const char * txt, uint32_t length, const lv_font_t * font, int32_t letter_space); +/** + * Give the length of a text with a given font with text flags + * @param txt a '\0' terminate string + * @param length length of 'txt' in byte count and not characters (Á is 1 character but 2 bytes in + * UTF-8) + * @param font pointer to a font + * @param letter_space letter space + * @param flags settings for the text from ::lv_text_flag_t + * @return length of a char_num long text + */ +int32_t lv_text_get_width_with_flags(const char * txt, uint32_t length, const lv_font_t * font, int32_t letter_space, + lv_text_flag_t flags); + +/** + * Check if c is command state + * @param state + * @param c + * @return True if c is state + */ +bool lv_text_is_cmd(lv_text_cmd_state_t * state, uint32_t c); /********************** * MACROS **********************/ diff --git a/src/widgets/buttonmatrix/lv_buttonmatrix.c b/src/widgets/buttonmatrix/lv_buttonmatrix.c index e20ac2ebe..adf167665 100644 --- a/src/widgets/buttonmatrix/lv_buttonmatrix.c +++ b/src/widgets/buttonmatrix/lv_buttonmatrix.c @@ -55,6 +55,7 @@ static void allocate_button_areas_and_controls(const lv_obj_t * obj, const char static void invalidate_button_area(const lv_obj_t * obj, uint32_t btn_idx); static void make_one_button_checked(lv_obj_t * obj, uint32_t btn_idx); static bool has_popovers_in_top_row(lv_obj_t * obj); +static bool button_is_recolor(lv_buttonmatrix_ctrl_t ctrl_bits); /********************** * STATIC VARIABLES @@ -749,6 +750,10 @@ static void draw_main(lv_event_t * e) obj->skip_trans = 0; } + bool recolor = button_is_recolor(btnm->ctrl_bits[btn_i]); + if(recolor) draw_label_dsc_act.flag |= LV_TEXT_FLAG_RECOLOR; + else draw_label_dsc_act.flag &= ~LV_TEXT_FLAG_RECOLOR; + draw_rect_dsc_act.base.id1 = btn_i; /*Remove borders on the edges if `LV_BORDER_SIDE_INTERNAL`*/ @@ -1042,4 +1047,9 @@ static bool has_popovers_in_top_row(lv_obj_t * obj) return false; } +static bool button_is_recolor(lv_buttonmatrix_ctrl_t ctrl_bits) +{ + return (ctrl_bits & LV_BUTTONMATRIX_CTRL_RECOLOR) ? true : false; +} + #endif diff --git a/src/widgets/buttonmatrix/lv_buttonmatrix.h b/src/widgets/buttonmatrix/lv_buttonmatrix.h index c2726627c..8333b5641 100644 --- a/src/widgets/buttonmatrix/lv_buttonmatrix.h +++ b/src/widgets/buttonmatrix/lv_buttonmatrix.h @@ -39,7 +39,7 @@ typedef enum { LV_BUTTONMATRIX_CTRL_CHECKED = 0x0100, /**< Button is currently toggled (e.g. checked).*/ LV_BUTTONMATRIX_CTRL_CLICK_TRIG = 0x0200, /**< 1: Send LV_EVENT_VALUE_CHANGE on CLICK, 0: Send LV_EVENT_VALUE_CHANGE on PRESS*/ LV_BUTTONMATRIX_CTRL_POPOVER = 0x0400, /**< Show a popover when pressing this key*/ - LV_BUTTONMATRIX_CTRL_RESERVED_1 = 0x0800, /**< Reserved for later use*/ + LV_BUTTONMATRIX_CTRL_RECOLOR = 0x0800, /**< Enable text recoloring with `#color`*/ LV_BUTTONMATRIX_CTRL_RESERVED_2 = 0x1000, /**< Reserved for later use*/ LV_BUTTONMATRIX_CTRL_RESERVED_3 = 0x2000, /**< Reserved for later use*/ LV_BUTTONMATRIX_CTRL_CUSTOM_1 = 0x4000, /**< Custom free to use flag*/ diff --git a/src/widgets/label/lv_label.c b/src/widgets/label/lv_label.c index 83baa3f54..661f813a2 100644 --- a/src/widgets/label/lv_label.c +++ b/src/widgets/label/lv_label.c @@ -59,7 +59,7 @@ static size_t get_text_length(const char * text); static void copy_text_to_label(lv_label_t * label, const char * text); static lv_text_flag_t get_label_flags(lv_label_t * label); static void calculate_x_coordinate(int32_t * x, const lv_text_align_t align, const char * txt, - uint32_t length, const lv_font_t * font, int32_t letter_space, lv_area_t * txt_coords); + uint32_t length, const lv_font_t * font, int32_t letter_space, lv_area_t * txt_coords, lv_text_flag_t flags); /********************** * STATIC VARIABLES @@ -274,6 +274,19 @@ void lv_label_set_text_selection_end(lv_obj_t * obj, uint32_t index) #endif } +void lv_label_set_recolor(lv_obj_t * obj, bool en) +{ + LV_ASSERT_OBJ(obj, MY_CLASS); + + lv_label_t * label = (lv_label_t *)obj; + if(label->recolor == en) return; + + label->recolor = en == false ? 0 : 1; + + /*Refresh the text because the potential color codes in text needs to be hidden or revealed*/ + lv_label_refr_text(obj); +} + /*===================== * Getter functions *====================*/ @@ -386,11 +399,11 @@ void lv_label_get_letter_pos(const lv_obj_t * obj, uint32_t char_id, lv_point_t #endif /*Calculate the x coordinate*/ - int32_t x = lv_text_get_width(bidi_txt, visual_byte_pos, font, letter_space); + int32_t x = lv_text_get_width_with_flags(bidi_txt, visual_byte_pos, font, letter_space, flag); if(char_id != line_start) x += letter_space; uint32_t length = new_line_start - line_start; - calculate_x_coordinate(&x, align, bidi_txt, length, font, letter_space, &txt_coords); + calculate_x_coordinate(&x, align, bidi_txt, length, font, letter_space, &txt_coords, flag); pos->x = x; pos->y = y; @@ -468,7 +481,9 @@ uint32_t lv_label_get_letter_on(const lv_obj_t * obj, lv_point_t * pos_in, bool int32_t x = 0; const lv_text_align_t align = lv_obj_calculate_style_text_align(obj, LV_PART_MAIN, label->text); uint32_t length = new_line_start - line_start; - calculate_x_coordinate(&x, align, bidi_txt, length, font, letter_space, &txt_coords); + calculate_x_coordinate(&x, align, bidi_txt, length, font, letter_space, &txt_coords, flag); + + lv_text_cmd_state_t cmd_state = LV_TEXT_CMD_STATE_WAIT; uint32_t i = 0; uint32_t i_act = i; @@ -481,6 +496,12 @@ uint32_t lv_label_get_letter_on(const lv_obj_t * obj, lv_point_t * pos_in, bool uint32_t letter_next; lv_text_encoded_letter_next_2(bidi_txt, &letter, &letter_next, &i); + if((flag & LV_TEXT_FLAG_RECOLOR) != 0) { + if(lv_text_is_cmd(&cmd_state, bidi_txt[i]) != false) { + continue; /*Skip the letter if it is part of a command*/ + } + } + int32_t gw = lv_font_get_glyph_width(font, letter, letter_next); /*Finish if the x position or the last char of the next line is reached*/ @@ -558,14 +579,18 @@ bool lv_label_is_char_under_pos(const lv_obj_t * obj, lv_point_t * pos) int32_t x = 0; if(align == LV_TEXT_ALIGN_CENTER) { - const int32_t line_w = lv_text_get_width(&txt[line_start], new_line_start - line_start, font, letter_space); + const int32_t line_w = lv_text_get_width_with_flags(&txt[line_start], new_line_start - line_start, font, letter_space, + flag); x += lv_area_get_width(&txt_coords) / 2 - line_w / 2; } else if(align == LV_TEXT_ALIGN_RIGHT) { - const int32_t line_w = lv_text_get_width(&txt[line_start], new_line_start - line_start, font, letter_space); + const int32_t line_w = lv_text_get_width_with_flags(&txt[line_start], new_line_start - line_start, font, letter_space, + flag); x += lv_area_get_width(&txt_coords) - line_w; } + lv_text_cmd_state_t cmd_state = LV_TEXT_CMD_STATE_WAIT; + int32_t last_x = 0; uint32_t i = line_start; uint32_t i_current = i; @@ -578,6 +603,12 @@ bool lv_label_is_char_under_pos(const lv_obj_t * obj, lv_point_t * pos) /*Be careful 'i' already points to the next character*/ lv_text_encoded_letter_next_2(txt, &letter, &letter_next, &i); + if((flag & LV_TEXT_FLAG_RECOLOR) != 0) { + if(lv_text_is_cmd(&cmd_state, txt[i]) != false) { + continue; /*Skip the letter if it is part of a command*/ + } + } + last_x = x; x += lv_font_get_glyph_width(font, letter, letter_next); if(pos->x < x) { @@ -619,6 +650,14 @@ uint32_t lv_label_get_text_selection_end(const lv_obj_t * obj) #endif } +bool lv_label_get_recolor(const lv_obj_t * obj) +{ + LV_ASSERT_OBJ(obj, MY_CLASS); + + lv_label_t * label = (lv_label_t *)obj; + return label->recolor == 0 ? false : true; +} + /*===================== * Other functions *====================*/ @@ -681,6 +720,7 @@ static void lv_label_constructor(const lv_obj_class_t * class_p, lv_obj_t * obj) lv_label_t * label = (lv_label_t *)obj; label->text = NULL; + label->recolor = 0; label->static_txt = 0; label->dot_end = LV_LABEL_DOT_END_INV; label->long_mode = LV_LABEL_LONG_WRAP; @@ -748,6 +788,7 @@ static void lv_label_event(const lv_obj_class_t * class_p, lv_event_t * e) int32_t letter_space = lv_obj_get_style_text_letter_space(obj, LV_PART_MAIN); int32_t line_space = lv_obj_get_style_text_line_space(obj, LV_PART_MAIN); lv_text_flag_t flag = LV_TEXT_FLAG_NONE; + if(label->recolor != 0) flag |= LV_TEXT_FLAG_RECOLOR; if(label->expand != 0) flag |= LV_TEXT_FLAG_EXPAND; int32_t w = lv_obj_get_content_width(obj); @@ -1302,6 +1343,7 @@ static lv_text_flag_t get_label_flags(lv_label_t * label) { lv_text_flag_t flag = LV_TEXT_FLAG_NONE; + if(label->recolor) flag |= LV_TEXT_FLAG_RECOLOR; if(label->expand) flag |= LV_TEXT_FLAG_EXPAND; lv_obj_t * obj = (lv_obj_t *) label; @@ -1316,14 +1358,14 @@ static lv_text_flag_t get_label_flags(lv_label_t * label) /* Function created because of this pattern be used in multiple functions */ static void calculate_x_coordinate(int32_t * x, const lv_text_align_t align, const char * txt, uint32_t length, - const lv_font_t * font, int32_t letter_space, lv_area_t * txt_coords) + const lv_font_t * font, int32_t letter_space, lv_area_t * txt_coords, lv_text_flag_t flags) { if(align == LV_TEXT_ALIGN_CENTER) { - const int32_t line_w = lv_text_get_width(txt, length, font, letter_space); + const int32_t line_w = lv_text_get_width_with_flags(txt, length, font, letter_space, flags); *x += lv_area_get_width(txt_coords) / 2 - line_w / 2; } else if(align == LV_TEXT_ALIGN_RIGHT) { - const int32_t line_w = lv_text_get_width(txt, length, font, letter_space); + const int32_t line_w = lv_text_get_width_with_flags(txt, length, font, letter_space, flags); *x += lv_area_get_width(txt_coords) - line_w; } else { diff --git a/src/widgets/label/lv_label.h b/src/widgets/label/lv_label.h index 86964f33f..d9fddd817 100644 --- a/src/widgets/label/lv_label.h +++ b/src/widgets/label/lv_label.h @@ -129,6 +129,14 @@ void lv_label_set_text_selection_start(lv_obj_t * obj, uint32_t index); */ void lv_label_set_text_selection_end(lv_obj_t * obj, uint32_t index); +/** + * Enable the recoloring by in-line commands + * @param obj pointer to a label object + * @param en true: enable recoloring, false: disable + * @example "This is a #ff0000 red# word" + */ +void lv_label_set_recolor(lv_obj_t * obj, bool en); + /*===================== * Getter functions *====================*/ @@ -188,6 +196,13 @@ uint32_t lv_label_get_text_selection_start(const lv_obj_t * obj); */ uint32_t lv_label_get_text_selection_end(const lv_obj_t * obj); +/** + * @brief Get the recoloring attribute + * @param obj pointer to a label object. + * @return true: recoloring is enabled, false: recoloring is disabled + */ +bool lv_label_get_recolor(const lv_obj_t * obj); + /*===================== * Other functions *====================*/ diff --git a/src/widgets/label/lv_label_private.h b/src/widgets/label/lv_label_private.h index 5561d67db..d3c3cc889 100644 --- a/src/widgets/label/lv_label_private.h +++ b/src/widgets/label/lv_label_private.h @@ -50,6 +50,7 @@ struct _lv_label_t { lv_point_t offset; /**< Text draw position offset */ lv_label_long_mode_t long_mode : 3; /**< Determine what to do with the long texts */ uint8_t static_txt : 1; /**< Flag to indicate the text is static */ + uint8_t recolor : 1; /**< Enable in-line letter re-coloring*/ uint8_t expand : 1; /**< Ignore real width (used by the library with LV_LABEL_LONG_SCROLL) */ uint8_t dot_tmp_alloc : 1; /**< 1: dot is allocated, 0: dot directly holds up to 4 chars */ uint8_t invalid_size_cache : 1; /**< 1: Recalculate size and update cache */ diff --git a/src/widgets/roller/lv_roller.c b/src/widgets/roller/lv_roller.c index b762a1e25..3fd5fe7a2 100644 --- a/src/widgets/roller/lv_roller.c +++ b/src/widgets/roller/lv_roller.c @@ -511,6 +511,7 @@ static void draw_main(lv_event_t * e) area_ok = lv_area_intersect(&mask_sel, &layer->_clip_area, &sel_area); if(area_ok) { lv_obj_t * label = get_label(obj); + if(lv_label_get_recolor(label)) label_dsc.flag |= LV_TEXT_FLAG_RECOLOR; /*Get the size of the "selected text"*/ lv_point_t label_sel_size; @@ -569,6 +570,8 @@ static void draw_label(lv_event_t * e) lv_draw_label_dsc_t label_draw_dsc; lv_draw_label_dsc_init(&label_draw_dsc); lv_obj_init_draw_label_dsc(roller, LV_PART_MAIN, &label_draw_dsc); + if(lv_label_get_recolor(label_obj)) label_draw_dsc.flag |= LV_TEXT_FLAG_RECOLOR; + lv_layer_t * layer = lv_event_get_layer(e); /*If the roller has shadow or outline it has some ext. draw size diff --git a/src/widgets/span/lv_span.c b/src/widgets/span/lv_span.c index a942dba1e..3561c2e04 100644 --- a/src/widgets/span/lv_span.c +++ b/src/widgets/span/lv_span.c @@ -1025,8 +1025,8 @@ static void lv_draw_span(lv_obj_t * obj, lv_layer_t * layer) if(txt_pos.y + max_line_h + next_line_h - line_space > coords.y2 + 1) { /* for overflow if is end line. */ if(last_snippet->txt[last_snippet->bytes] != '\0') { last_snippet->bytes = lv_strlen(last_snippet->txt); - last_snippet->txt_w = lv_text_get_width(last_snippet->txt, last_snippet->bytes, last_snippet->font, - last_snippet->letter_space); + last_snippet->txt_w = lv_text_get_width_with_flags(last_snippet->txt, last_snippet->bytes, last_snippet->font, + last_snippet->letter_space, label_draw_dsc.flag); } ellipsis_valid = spans->overflow == LV_SPAN_OVERFLOW_ELLIPSIS; is_end_line = true; diff --git a/tests/ref_imgs/widgets/label_recolor.png b/tests/ref_imgs/widgets/label_recolor.png new file mode 100644 index 000000000..3aa91d492 Binary files /dev/null and b/tests/ref_imgs/widgets/label_recolor.png differ diff --git a/tests/src/test_cases/widgets/test_label.c b/tests/src/test_cases/widgets/test_label.c index 3e5a11092..6e640d553 100644 --- a/tests/src/test_cases/widgets/test_label.c +++ b/tests/src/test_cases/widgets/test_label.c @@ -615,4 +615,15 @@ void test_label_max_width(void) TEST_ASSERT_EQUAL_SCREENSHOT("widgets/label_max_width.png"); } +void test_label_with_recolor_cmd(void) +{ + lv_obj_clean(lv_screen_active()); + + lv_obj_t * label_recolor = lv_label_create(lv_screen_active()); + lv_label_set_text(label_recolor, "Write a #ff0000 red# word"); + lv_label_set_recolor(label_recolor, true); + + TEST_ASSERT_EQUAL_SCREENSHOT("widgets/label_recolor.png"); +} + #endif