/** * @file lv_table.c * */ /********************* * INCLUDES *********************/ #include "lv_table.h" #if LV_USE_TABLE != 0 #include "../lv_misc/lv_debug.h" #include "../lv_core/lv_indev.h" #include "../lv_misc/lv_txt.h" #include "../lv_misc/lv_math.h" #include "../lv_draw/lv_draw_label.h" #include "../lv_themes/lv_theme.h" /********************* * DEFINES *********************/ #define LV_OBJX_NAME "lv_table" /********************** * TYPEDEFS **********************/ /********************** * STATIC PROTOTYPES **********************/ static lv_design_res_t lv_table_design(lv_obj_t * table, const lv_area_t * clip_area, lv_design_mode_t mode); static lv_res_t lv_table_signal(lv_obj_t * table, lv_signal_t sign, void * param); static lv_style_list_t * lv_table_get_style(lv_obj_t * table, uint8_t part); static lv_coord_t get_row_height(lv_obj_t * table, uint16_t row_id, const lv_font_t ** font, lv_style_int_t * letter_space, lv_style_int_t * line_space, lv_style_int_t * cell_left, lv_style_int_t * cell_right, lv_style_int_t * cell_top, lv_style_int_t * cell_bottom); static void refr_size(lv_obj_t * table); /********************** * STATIC VARIABLES **********************/ static lv_signal_cb_t ancestor_signal; static lv_design_cb_t ancestor_design; /********************** * MACROS **********************/ /********************** * GLOBAL FUNCTIONS **********************/ /** * Create a table object * @param par pointer to an object, it will be the parent of the new table * @param copy pointer to a table object, if not NULL then the new object will be copied from it * @return pointer to the created table */ lv_obj_t * lv_table_create(lv_obj_t * par, const lv_obj_t * copy) { LV_LOG_TRACE("table create started"); /*Create the ancestor of table*/ lv_obj_t * table = lv_obj_create(par, copy); LV_ASSERT_MEM(table); if(table == NULL) return NULL; /*Allocate the table type specific extended data*/ lv_table_ext_t * ext = lv_obj_allocate_ext_attr(table, sizeof(lv_table_ext_t)); LV_ASSERT_MEM(ext); if(ext == NULL) { lv_obj_del(table); return NULL; } if(ancestor_signal == NULL) ancestor_signal = lv_obj_get_signal_cb(table); if(ancestor_design == NULL) ancestor_design = lv_obj_get_design_cb(table); /*Initialize the allocated 'ext' */ ext->cell_data = NULL; ext->col_cnt = 0; ext->row_cnt = 0; ext->row_h = NULL; ext->cell_types = 1; uint16_t i; for(i = 0; i < LV_TABLE_CELL_STYLE_CNT; i++) { lv_style_list_init(&ext->cell_style[i]); } for(i = 0; i < LV_TABLE_COL_MAX; i++) { ext->col_w[i] = LV_DPI; } /*The signal and design functions are not copied so set them here*/ lv_obj_set_signal_cb(table, lv_table_signal); lv_obj_set_design_cb(table, lv_table_design); /*Init the new table table*/ if(copy == NULL) { lv_theme_apply(table, LV_THEME_TABLE); } /*Copy an existing table*/ else { lv_table_ext_t * copy_ext = lv_obj_get_ext_attr(copy); for(i = 0; i < LV_TABLE_CELL_STYLE_CNT; i++) { lv_style_list_copy(&ext->cell_style[i], ©_ext->cell_style[i]); } lv_table_set_row_cnt(table, copy_ext->row_cnt); lv_table_set_col_cnt(table, copy_ext->col_cnt); /*Refresh the style with new signal function*/ lv_obj_refresh_style(table, LV_OBJ_PART_ALL, LV_STYLE_PROP_ALL); } LV_LOG_INFO("table created"); return table; } /*===================== * Setter functions *====================*/ /** * Set the value of a cell. * @param table pointer to a Table object * @param row id of the row [0 .. row_cnt -1] * @param col id of the column [0 .. col_cnt -1] * @param txt text to display in the cell. It will be copied and saved so this variable is not * required after this function call. */ void lv_table_set_cell_value(lv_obj_t * table, uint16_t row, uint16_t col, const char * txt) { LV_ASSERT_OBJ(table, LV_OBJX_NAME); LV_ASSERT_NULL(txt); lv_table_ext_t * ext = lv_obj_get_ext_attr(table); if(col >= ext->col_cnt) { LV_LOG_WARN("lv_table_set_cell_value: invalid column"); return; } /*Auto expand*/ if(row >= ext->row_cnt) { lv_table_set_row_cnt(table, row + 1); } uint32_t cell = row * ext->col_cnt + col; lv_table_cell_format_t format; /*Save the format byte*/ if(ext->cell_data[cell]) { format.format_byte = ext->cell_data[cell][0]; } /*Initialize the format byte*/ else { lv_bidi_dir_t base_dir = lv_obj_get_base_dir(table); if(base_dir == LV_BIDI_DIR_LTR) format.s.align = LV_LABEL_ALIGN_LEFT; else if(base_dir == LV_BIDI_DIR_RTL) format.s.align = LV_LABEL_ALIGN_RIGHT; else if(base_dir == LV_BIDI_DIR_AUTO) #if LV_USE_BIDI format.s.align = _lv_bidi_detect_base_dir(txt); #else format.s.align = LV_LABEL_ALIGN_LEFT; #endif format.s.right_merge = 0; format.s.type = 0; format.s.crop = 0; } ext->cell_data[cell] = lv_mem_realloc(ext->cell_data[cell], strlen(txt) + 2); /*+1: trailing '\0; +1: format byte*/ LV_ASSERT_MEM(ext->cell_data[cell]); if(ext->cell_data[cell] == NULL) return; strcpy(ext->cell_data[cell] + 1, txt); /*+1 to skip the format byte*/ ext->cell_data[cell][0] = format.format_byte; refr_size(table); } /** * Set the number of rows * @param table table pointer to a Table object * @param row_cnt number of rows */ void lv_table_set_row_cnt(lv_obj_t * table, uint16_t row_cnt) { LV_ASSERT_OBJ(table, LV_OBJX_NAME); lv_table_ext_t * ext = lv_obj_get_ext_attr(table); uint16_t old_row_cnt = ext->row_cnt; ext->row_cnt = row_cnt; if(ext->row_cnt > 0) { ext->row_h = lv_mem_realloc(ext->row_h, ext->row_cnt * sizeof(ext->row_h[0])); LV_ASSERT_MEM(ext->row_h); if(ext->row_h == NULL) return; } else { lv_mem_free(ext->row_h); ext->row_h = NULL; } if(ext->row_cnt > 0 && ext->col_cnt > 0) { ext->cell_data = lv_mem_realloc(ext->cell_data, ext->row_cnt * ext->col_cnt * sizeof(char *)); LV_ASSERT_MEM(ext->cell_data); if(ext->cell_data == NULL) return; /*Initialize the new fields*/ if(old_row_cnt < row_cnt) { uint16_t old_cell_cnt = old_row_cnt * ext->col_cnt; uint32_t new_cell_cnt = ext->col_cnt * ext->row_cnt; _lv_memset_00(&ext->cell_data[old_cell_cnt], (new_cell_cnt - old_cell_cnt) * sizeof(ext->cell_data[0])); } } else { lv_mem_free(ext->cell_data); ext->cell_data = NULL; } refr_size(table); } /** * Set the number of columns * @param table table pointer to a Table object * @param col_cnt number of columns. Must be < LV_TABLE_COL_MAX */ void lv_table_set_col_cnt(lv_obj_t * table, uint16_t col_cnt) { LV_ASSERT_OBJ(table, LV_OBJX_NAME); if(col_cnt >= LV_TABLE_COL_MAX) { LV_LOG_WARN("lv_table_set_col_cnt: too many columns. Must be < LV_TABLE_COL_MAX."); return; } lv_table_ext_t * ext = lv_obj_get_ext_attr(table); uint16_t old_col_cnt = ext->col_cnt; ext->col_cnt = col_cnt; if(ext->row_cnt > 0 && ext->col_cnt > 0) { ext->cell_data = lv_mem_realloc(ext->cell_data, ext->row_cnt * ext->col_cnt * sizeof(char *)); LV_ASSERT_MEM(ext->cell_data); if(ext->cell_data == NULL) return; /*Initialize the new fields*/ if(old_col_cnt < col_cnt) { uint16_t old_cell_cnt = old_col_cnt * ext->row_cnt; uint32_t new_cell_cnt = ext->col_cnt * ext->row_cnt; _lv_memset_00(&ext->cell_data[old_cell_cnt], (new_cell_cnt - old_cell_cnt) * sizeof(ext->cell_data[0])); } } else { lv_mem_free(ext->cell_data); ext->cell_data = NULL; } refr_size(table); } /** * Set the width of a column * @param table table pointer to a Table object * @param col_id id of the column [0 .. LV_TABLE_COL_MAX -1] * @param w width of the column */ void lv_table_set_col_width(lv_obj_t * table, uint16_t col_id, lv_coord_t w) { LV_ASSERT_OBJ(table, LV_OBJX_NAME); if(col_id >= LV_TABLE_COL_MAX) { LV_LOG_WARN("lv_table_set_col_width: too big 'col_id'. Must be < LV_TABLE_COL_MAX."); return; } lv_table_ext_t * ext = lv_obj_get_ext_attr(table); ext->col_w[col_id] = w; refr_size(table); } /** * Set the text align in a cell * @param table pointer to a Table object * @param row id of the row [0 .. row_cnt -1] * @param col id of the column [0 .. col_cnt -1] * @param align LV_LABEL_ALIGN_LEFT or LV_LABEL_ALIGN_CENTER or LV_LABEL_ALIGN_RIGHT */ void lv_table_set_cell_align(lv_obj_t * table, uint16_t row, uint16_t col, lv_label_align_t align) { LV_ASSERT_OBJ(table, LV_OBJX_NAME); lv_table_ext_t * ext = lv_obj_get_ext_attr(table); if(col >= ext->col_cnt) { LV_LOG_WARN("lv_table_set_cell_align: invalid column"); return; } /*Auto expand*/ if(row >= ext->row_cnt) { lv_table_set_row_cnt(table, row + 1); } uint32_t cell = row * ext->col_cnt + col; if(ext->cell_data[cell] == NULL) { ext->cell_data[cell] = lv_mem_alloc(2); /*+1: trailing '\0; +1: format byte*/ LV_ASSERT_MEM(ext->cell_data[cell]); if(ext->cell_data[cell] == NULL) return; ext->cell_data[cell][0] = 0; ext->cell_data[cell][1] = '\0'; } lv_table_cell_format_t format; format.format_byte = ext->cell_data[cell][0]; format.s.align = align; ext->cell_data[cell][0] = format.format_byte; } /** * Set the type of a cell. * @param table pointer to a Table object * @param row id of the row [0 .. row_cnt -1] * @param col id of the column [0 .. col_cnt -1] * @param type 1,2,3 or 4. The cell style will be chosen accordingly. */ void lv_table_set_cell_type(lv_obj_t * table, uint16_t row, uint16_t col, uint8_t type) { LV_ASSERT_OBJ(table, LV_OBJX_NAME); lv_table_ext_t * ext = lv_obj_get_ext_attr(table); if(col >= ext->col_cnt) { LV_LOG_WARN("lv_table_set_cell_type: invalid column"); return; } /*Auto expand*/ if(row >= ext->row_cnt) { lv_table_set_row_cnt(table, row + 1); } uint32_t cell = row * ext->col_cnt + col; if(ext->cell_data[cell] == NULL) { ext->cell_data[cell] = lv_mem_alloc(2); /*+1: trailing '\0; +1: format byte*/ LV_ASSERT_MEM(ext->cell_data[cell]); if(ext->cell_data[cell] == NULL) return; ext->cell_data[cell][0] = 0; ext->cell_data[cell][1] = '\0'; } if(type > 0) type--; /*User gives 1,2,3,4 but easier to handle 0, 1, 2, 3*/ if(type >= LV_TABLE_CELL_STYLE_CNT) type = LV_TABLE_CELL_STYLE_CNT - 1; lv_table_cell_format_t format; format.format_byte = ext->cell_data[cell][0]; format.s.type = type; ext->cell_data[cell][0] = format.format_byte; ext->cell_types |= 1 << type; } /** * Set the cell crop. (Don't adjust the height of the cell according to its content) * @param table pointer to a Table object * @param row id of the row [0 .. row_cnt -1] * @param col id of the column [0 .. col_cnt -1] * @param crop true: crop the cell content; false: set the cell height to the content. */ void lv_table_set_cell_crop(lv_obj_t * table, uint16_t row, uint16_t col, bool crop) { LV_ASSERT_OBJ(table, LV_OBJX_NAME); lv_table_ext_t * ext = lv_obj_get_ext_attr(table); if(col >= ext->col_cnt) { LV_LOG_WARN("lv_table_set_cell_crop: invalid column"); return; } /*Auto expand*/ if(row >= ext->row_cnt) { lv_table_set_row_cnt(table, row + 1); } uint32_t cell = row * ext->col_cnt + col; if(ext->cell_data[cell] == NULL) { ext->cell_data[cell] = lv_mem_alloc(2); /*+1: trailing '\0; +1: format byte*/ LV_ASSERT_MEM(ext->cell_data[cell]); if(ext->cell_data[cell] == NULL) return; ext->cell_data[cell][0] = 0; ext->cell_data[cell][1] = '\0'; } lv_table_cell_format_t format; format.format_byte = ext->cell_data[cell][0]; format.s.crop = crop; ext->cell_data[cell][0] = format.format_byte; } /** * Merge a cell with the right neighbor. The value of the cell to the right won't be displayed. * @param table table pointer to a Table object * @param row id of the row [0 .. row_cnt -1] * @param col id of the column [0 .. col_cnt -1] * @param en true: merge right; false: don't merge right */ void lv_table_set_cell_merge_right(lv_obj_t * table, uint16_t row, uint16_t col, bool en) { LV_ASSERT_OBJ(table, LV_OBJX_NAME); lv_table_ext_t * ext = lv_obj_get_ext_attr(table); if(col >= ext->col_cnt) { LV_LOG_WARN("lv_table_set_cell_merge_right: invalid column"); return; } /*Auto expand*/ if(row >= ext->row_cnt) { lv_table_set_row_cnt(table, row + 1); } uint32_t cell = row * ext->col_cnt + col; if(ext->cell_data[cell] == NULL) { ext->cell_data[cell] = lv_mem_alloc(2); /*+1: trailing '\0; +1: format byte*/ LV_ASSERT_MEM(ext->cell_data[cell]); if(ext->cell_data[cell] == NULL) return; ext->cell_data[cell][0] = 0; ext->cell_data[cell][1] = '\0'; } lv_table_cell_format_t format; format.format_byte = ext->cell_data[cell][0]; format.s.right_merge = en ? 1 : 0; ext->cell_data[cell][0] = format.format_byte; refr_size(table); } /*===================== * Getter functions *====================*/ /** * Get the value of a cell. * @param table pointer to a Table object * @param row id of the row [0 .. row_cnt -1] * @param col id of the column [0 .. col_cnt -1] * @return text in the cell */ const char * lv_table_get_cell_value(lv_obj_t * table, uint16_t row, uint16_t col) { LV_ASSERT_OBJ(table, LV_OBJX_NAME); lv_table_ext_t * ext = lv_obj_get_ext_attr(table); if(row >= ext->row_cnt || col >= ext->col_cnt) { LV_LOG_WARN("lv_table_set_cell_value: invalid row or column"); return ""; } uint32_t cell = row * ext->col_cnt + col; if(ext->cell_data[cell] == NULL) return ""; return &ext->cell_data[cell][1]; /*Skip the format byte*/ } /** * Get the number of rows. * @param table table pointer to a Table object * @return number of rows. */ uint16_t lv_table_get_row_cnt(lv_obj_t * table) { LV_ASSERT_OBJ(table, LV_OBJX_NAME); lv_table_ext_t * ext = lv_obj_get_ext_attr(table); return ext->row_cnt; } /** * Get the number of columns. * @param table table pointer to a Table object * @return number of columns. */ uint16_t lv_table_get_col_cnt(lv_obj_t * table) { LV_ASSERT_OBJ(table, LV_OBJX_NAME); lv_table_ext_t * ext = lv_obj_get_ext_attr(table); return ext->col_cnt; } /** * Get the width of a column * @param table table pointer to a Table object * @param col_id id of the column [0 .. LV_TABLE_COL_MAX -1] * @return width of the column */ lv_coord_t lv_table_get_col_width(lv_obj_t * table, uint16_t col_id) { LV_ASSERT_OBJ(table, LV_OBJX_NAME); if(col_id >= LV_TABLE_COL_MAX) { LV_LOG_WARN("lv_table_set_col_width: too big 'col_id'. Must be < LV_TABLE_COL_MAX."); return 0; } lv_table_ext_t * ext = lv_obj_get_ext_attr(table); return ext->col_w[col_id]; } /** * Get the text align of a cell * @param table pointer to a Table object * @param row id of the row [0 .. row_cnt -1] * @param col id of the column [0 .. col_cnt -1] * @return LV_LABEL_ALIGN_LEFT (default in case of error) or LV_LABEL_ALIGN_CENTER or * LV_LABEL_ALIGN_RIGHT */ lv_label_align_t lv_table_get_cell_align(lv_obj_t * table, uint16_t row, uint16_t col) { LV_ASSERT_OBJ(table, LV_OBJX_NAME); lv_table_ext_t * ext = lv_obj_get_ext_attr(table); if(row >= ext->row_cnt || col >= ext->col_cnt) { LV_LOG_WARN("lv_table_set_cell_align: invalid row or column"); return LV_LABEL_ALIGN_LEFT; /*Just return with something*/ } uint32_t cell = row * ext->col_cnt + col; if(ext->cell_data[cell] == NULL) return LV_LABEL_ALIGN_LEFT; /*Just return with something*/ else { lv_table_cell_format_t format; format.format_byte = ext->cell_data[cell][0]; return format.s.align; } } /** * Get the type of a cell * @param table pointer to a Table object * @param row id of the row [0 .. row_cnt -1] * @param col id of the column [0 .. col_cnt -1] * @return 1,2,3 or 4 */ lv_label_align_t lv_table_get_cell_type(lv_obj_t * table, uint16_t row, uint16_t col) { LV_ASSERT_OBJ(table, LV_OBJX_NAME); lv_table_ext_t * ext = lv_obj_get_ext_attr(table); if(row >= ext->row_cnt || col >= ext->col_cnt) { LV_LOG_WARN("lv_table_get_cell_type: invalid row or column"); return 1; /*Just return with something*/ } uint32_t cell = row * ext->col_cnt + col; if(ext->cell_data[cell] == NULL) return 1; /*Just return with something*/ else { lv_table_cell_format_t format; format.format_byte = ext->cell_data[cell][0]; return format.s.type + 1; /*0,1,2,3 is stored but user sees 1,2,3,4*/ } } /** * Get the crop property of a cell * @param table pointer to a Table object * @param row id of the row [0 .. row_cnt -1] * @param col id of the column [0 .. col_cnt -1] * @return true: text crop enabled; false: disabled */ lv_label_align_t lv_table_get_cell_crop(lv_obj_t * table, uint16_t row, uint16_t col) { LV_ASSERT_OBJ(table, LV_OBJX_NAME); lv_table_ext_t * ext = lv_obj_get_ext_attr(table); if(row >= ext->row_cnt || col >= ext->col_cnt) { LV_LOG_WARN("lv_table_get_cell_crop: invalid row or column"); return false; /*Just return with something*/ } uint32_t cell = row * ext->col_cnt + col; if(ext->cell_data[cell] == NULL) return false; /*Just return with something*/ else { lv_table_cell_format_t format; format.format_byte = ext->cell_data[cell][0]; return format.s.crop; } } /** * Get the cell merge attribute. * @param table table pointer to a Table object * @param row id of the row [0 .. row_cnt -1] * @param col id of the column [0 .. col_cnt -1] * @return true: merge right; false: don't merge right */ bool lv_table_get_cell_merge_right(lv_obj_t * table, uint16_t row, uint16_t col) { LV_ASSERT_OBJ(table, LV_OBJX_NAME); lv_table_ext_t * ext = lv_obj_get_ext_attr(table); if(row >= ext->row_cnt || col >= ext->col_cnt) { LV_LOG_WARN("lv_table_get_cell_merge_right: invalid row or column"); return false; } uint32_t cell = row * ext->col_cnt + col; if(ext->cell_data[cell] == NULL) return false; else { lv_table_cell_format_t format; format.format_byte = ext->cell_data[cell][0]; return format.s.right_merge ? true : false; } } /** * Get the last pressed or being pressed cell * @param table pointer to a table object * @param row pointer to variable to store the pressed row * @param col pointer to variable to store the pressed column * @return LV_RES_OK: a valid pressed cell was found, LV_RES_INV: no valid cell is pressed */ lv_res_t lv_table_get_pressed_cell(lv_obj_t * table, uint16_t * row, uint16_t * col) { lv_table_ext_t * ext = lv_obj_get_ext_attr(table); lv_indev_type_t type = lv_indev_get_type(lv_indev_get_act()); if(type != LV_INDEV_TYPE_POINTER && type != LV_INDEV_TYPE_BUTTON) { if(col) *col = 0xFFFF; if(row) *row = 0xFFFF; return LV_RES_INV; } lv_point_t p; lv_indev_get_point(lv_indev_get_act(), &p); lv_coord_t tmp; if(col) { lv_coord_t x = p.x; x -= table->coords.x1; x -= lv_obj_get_style_pad_left(table, LV_TABLE_PART_BG); *col = 0; tmp = 0; for(*col = 0; *col < ext->col_cnt; (*col)++) { tmp += ext->col_w[*col]; if(x < tmp) break; } } if(row) { lv_coord_t y = p.y; y -= table->coords.y1; y -= lv_obj_get_style_pad_top(table, LV_TABLE_PART_BG); *row = 0; tmp = 0; for(*row = 0; *row < ext->row_cnt; (*row)++) { tmp += ext->row_h[*row]; if(y < tmp) break; } } return LV_RES_OK; } /********************** * STATIC FUNCTIONS **********************/ /** * Handle the drawing related tasks of the tables * @param table pointer to an object * @param clip_area the object will be drawn only in this area * @param mode LV_DESIGN_COVER_CHK: only check if the object fully covers the 'mask_p' area * (return 'true' if yes) * LV_DESIGN_DRAW: draw the object (always return 'true') * LV_DESIGN_DRAW_POST: drawing after every children are drawn * @param return an element of `lv_design_res_t` */ static lv_design_res_t lv_table_design(lv_obj_t * table, const lv_area_t * clip_area, lv_design_mode_t mode) { /*Return false if the object is not covers the mask_p area*/ if(mode == LV_DESIGN_COVER_CHK) { return ancestor_design(table, clip_area, mode); } /*Draw the object*/ else if(mode == LV_DESIGN_DRAW_MAIN) { /*Draw the background*/ ancestor_design(table, clip_area, mode); lv_table_ext_t * ext = lv_obj_get_ext_attr(table); lv_point_t txt_size; lv_area_t cell_area; lv_area_t txt_area; lv_txt_flag_t txt_flags; lv_style_int_t bg_top = lv_obj_get_style_pad_top(table, LV_TABLE_PART_BG); lv_style_int_t bg_bottom = lv_obj_get_style_pad_bottom(table, LV_TABLE_PART_BG); lv_style_int_t bg_left = lv_obj_get_style_pad_left(table, LV_TABLE_PART_BG); lv_style_int_t bg_right = lv_obj_get_style_pad_right(table, LV_TABLE_PART_BG); lv_draw_rect_dsc_t rect_dsc[LV_TABLE_CELL_STYLE_CNT]; lv_draw_label_dsc_t label_dsc[LV_TABLE_CELL_STYLE_CNT]; lv_draw_line_dsc_t line_dsc[LV_TABLE_CELL_STYLE_CNT]; lv_style_int_t cell_left[LV_TABLE_CELL_STYLE_CNT]; lv_style_int_t cell_right[LV_TABLE_CELL_STYLE_CNT]; lv_style_int_t cell_top[LV_TABLE_CELL_STYLE_CNT]; lv_style_int_t cell_bottom[LV_TABLE_CELL_STYLE_CNT]; uint16_t i; for(i = 0; i < LV_TABLE_CELL_STYLE_CNT; i++) { if((ext->cell_types & (1 << i)) == 0) continue; /*Skip unused cell types*/ lv_draw_rect_dsc_init(&rect_dsc[i]); lv_obj_init_draw_rect_dsc(table, LV_TABLE_PART_CELL1 + i, &rect_dsc[i]); lv_draw_label_dsc_init(&label_dsc[i]); lv_obj_init_draw_label_dsc(table, LV_TABLE_PART_CELL1 + i, &label_dsc[i]); lv_draw_line_dsc_init(&line_dsc[i]); lv_obj_init_draw_line_dsc(table, LV_TABLE_PART_CELL1 + i, &line_dsc[i]); cell_left[i] = lv_obj_get_style_pad_left(table, LV_TABLE_PART_CELL1 + i); cell_right[i] = lv_obj_get_style_pad_right(table, LV_TABLE_PART_CELL1 + i); cell_top[i] = lv_obj_get_style_pad_top(table, LV_TABLE_PART_CELL1 + i); cell_bottom[i] = lv_obj_get_style_pad_bottom(table, LV_TABLE_PART_CELL1 + i); } uint16_t col; uint16_t row; uint16_t cell = 0; cell_area.y2 = table->coords.y1 + bg_top - 1; for(row = 0; row < ext->row_cnt; row++) { lv_coord_t h_row = ext->row_h[row]; cell_area.y1 = cell_area.y2 + 1; cell_area.y2 = cell_area.y1 + h_row - 1; if(cell_area.y1 > clip_area->y2) return LV_DESIGN_RES_OK; cell_area.x2 = table->coords.x1 + bg_left - 1; for(col = 0; col < ext->col_cnt; col++) { lv_table_cell_format_t format; if(ext->cell_data[cell]) { format.format_byte = ext->cell_data[cell][0]; } else { format.s.right_merge = 0; format.s.align = LV_LABEL_ALIGN_LEFT; format.s.type = 0; format.s.crop = 1; } cell_area.x1 = cell_area.x2 + 1; cell_area.x2 = cell_area.x1 + ext->col_w[col] - 1; uint16_t col_merge = 0; for(col_merge = 0; col_merge + col < ext->col_cnt - 1; col_merge++) { if(ext->cell_data[cell + col_merge] != NULL) { format.format_byte = ext->cell_data[cell + col_merge][0]; if(format.s.right_merge) cell_area.x2 += ext->col_w[col + col_merge + 1]; else break; } else { break; } } if(cell_area.y2 < clip_area->y1) { cell += col_merge + 1; col += col_merge; continue; } uint8_t cell_type = format.s.type; /*Expand the cell area with a half border to avoid drawing 2 borders next to each other*/ lv_area_t cell_area_border; lv_area_copy(&cell_area_border, &cell_area); if((rect_dsc[cell_type].border_side & LV_BORDER_SIDE_LEFT) && cell_area_border.x1 > table->coords.x1 + bg_left) { cell_area_border.x1 -= rect_dsc[cell_type].border_width / 2; } if((rect_dsc[cell_type].border_side & LV_BORDER_SIDE_TOP) && cell_area_border.y1 > table->coords.y1 + bg_top) { cell_area_border.y1 -= rect_dsc[cell_type].border_width / 2; } if((rect_dsc[cell_type].border_side & LV_BORDER_SIDE_RIGHT) && cell_area_border.x2 < table->coords.x2 - bg_right - 1) { cell_area_border.x2 += rect_dsc[cell_type].border_width / 2 + (rect_dsc[cell_type].border_width & 0x1); } if((rect_dsc[cell_type].border_side & LV_BORDER_SIDE_BOTTOM) && cell_area_border.y2 < table->coords.y2 - bg_bottom - 1) { cell_area_border.y2 += rect_dsc[cell_type].border_width / 2 + (rect_dsc[cell_type].border_width & 0x1); } lv_draw_rect(&cell_area_border, clip_area, &rect_dsc[cell_type]); if(ext->cell_data[cell]) { txt_area.x1 = cell_area.x1 + cell_left[cell_type]; txt_area.x2 = cell_area.x2 - cell_right[cell_type]; txt_area.y1 = cell_area.y1 + cell_top[cell_type]; txt_area.y2 = cell_area.y2 - cell_bottom[cell_type]; /*Align the content to the middle if not cropped*/ if(format.s.crop == 0) { txt_flags = LV_TXT_FLAG_NONE; } else { txt_flags = LV_TXT_FLAG_EXPAND; } _lv_txt_get_size(&txt_size, ext->cell_data[cell] + 1, label_dsc[cell_type].font, label_dsc[cell_type].letter_space, label_dsc[cell_type].line_space, lv_area_get_width(&txt_area), txt_flags); label_dsc[cell_type].flag = 0; /*Align the content to the middle if not cropped*/ if(format.s.crop == 0) { txt_area.y1 = cell_area.y1 + h_row / 2 - txt_size.y / 2; txt_area.y2 = cell_area.y1 + h_row / 2 + txt_size.y / 2; } switch(format.s.align) { default: case LV_LABEL_ALIGN_LEFT: label_dsc[cell_type].flag |= LV_TXT_FLAG_NONE; break; case LV_LABEL_ALIGN_RIGHT: label_dsc[cell_type].flag |= LV_TXT_FLAG_RIGHT; break; case LV_LABEL_ALIGN_CENTER: label_dsc[cell_type].flag |= LV_TXT_FLAG_CENTER; break; } lv_area_t label_mask; bool label_mask_ok; label_mask_ok = _lv_area_intersect(&label_mask, clip_area, &cell_area); if(label_mask_ok) { lv_draw_label(&txt_area, &label_mask, &label_dsc[cell_type], ext->cell_data[cell] + 1, NULL); } /*Draw lines after '\n's*/ lv_point_t p1; lv_point_t p2; p1.x = cell_area.x1; p2.x = cell_area.x2; for(i = 1; ext->cell_data[cell][i] != '\0'; i++) { if(ext->cell_data[cell][i] == '\n') { ext->cell_data[cell][i] = '\0'; _lv_txt_get_size(&txt_size, ext->cell_data[cell] + 1, label_dsc[cell_type].font, label_dsc[cell_type].letter_space, label_dsc[cell_type].line_space, lv_area_get_width(&txt_area), txt_flags); p1.y = txt_area.y1 + txt_size.y + label_dsc[cell_type].line_space / 2; p2.y = txt_area.y1 + txt_size.y + label_dsc[cell_type].line_space / 2; lv_draw_line(&p1, &p2, clip_area, &line_dsc[cell_type]); ext->cell_data[cell][i] = '\n'; } } } cell += col_merge + 1; col += col_merge; } } } /*Post draw when the children are drawn*/ else if(mode == LV_DESIGN_DRAW_POST) { ancestor_design(table, clip_area, mode); } return LV_DESIGN_RES_OK; } /** * Signal function of the table * @param table pointer to a table object * @param sign a signal type from lv_signal_t enum * @param param pointer to a signal specific variable * @return LV_RES_OK: the object is not deleted in the function; LV_RES_INV: the object is deleted */ static lv_res_t lv_table_signal(lv_obj_t * table, lv_signal_t sign, void * param) { lv_res_t res; if(sign == LV_SIGNAL_GET_STYLE) { lv_get_style_info_t * info = param; info->result = lv_table_get_style(table, info->part); if(info->result != NULL) return LV_RES_OK; else return ancestor_signal(table, sign, param); } /* Include the ancient signal function */ res = ancestor_signal(table, sign, param); if(res != LV_RES_OK) return res; if(sign == LV_SIGNAL_GET_TYPE) return lv_obj_handle_get_type_signal(param, LV_OBJX_NAME); if(sign == LV_SIGNAL_CLEANUP) { /*Free the cell texts*/ lv_table_ext_t * ext = lv_obj_get_ext_attr(table); uint16_t i; for(i = 0; i < ext->col_cnt * ext->row_cnt; i++) { if(ext->cell_data[i]) { lv_mem_free(ext->cell_data[i]); ext->cell_data[i] = NULL; } } if(ext->cell_data) lv_mem_free(ext->cell_data); if(ext->row_h) lv_mem_free(ext->row_h); for(i = 0; i < LV_TABLE_CELL_STYLE_CNT; i++) { lv_obj_clean_style_list(table, LV_TABLE_PART_CELL1 + i); } } else if(sign == LV_SIGNAL_STYLE_CHG) { refr_size(table); } return res; } /** * Get the style descriptor of a part of the object * @param table pointer the object * @param part the part from. (LV_TABLE_PART_...) * @return pointer to the style descriptor of the specified part */ static lv_style_list_t * lv_table_get_style(lv_obj_t * table, uint8_t part) { LV_ASSERT_OBJ(table, LV_OBJX_NAME); lv_table_ext_t * ext = lv_obj_get_ext_attr(table); lv_style_list_t * style_dsc_p; switch(part) { case LV_TABLE_PART_BG: style_dsc_p = &table->style_list; break; case LV_TABLE_PART_CELL1: style_dsc_p = &ext->cell_style[0]; break; case LV_TABLE_PART_CELL2: style_dsc_p = &ext->cell_style[1]; break; case LV_TABLE_PART_CELL3: style_dsc_p = &ext->cell_style[2]; break; case LV_TABLE_PART_CELL4: style_dsc_p = &ext->cell_style[3]; break; default: style_dsc_p = NULL; } return style_dsc_p; } static void refr_size(lv_obj_t * table) { lv_coord_t h = 0; lv_coord_t w = 0; lv_table_ext_t * ext = lv_obj_get_ext_attr(table); if(ext->row_cnt == 0 || ext->col_cnt == 0) { lv_obj_set_size(table, w, h); return; } uint16_t i; for(i = 0; i < ext->col_cnt; i++) { w += ext->col_w[i]; } lv_style_int_t cell_left[LV_TABLE_CELL_STYLE_CNT]; lv_style_int_t cell_right[LV_TABLE_CELL_STYLE_CNT]; lv_style_int_t cell_top[LV_TABLE_CELL_STYLE_CNT]; lv_style_int_t cell_bottom[LV_TABLE_CELL_STYLE_CNT]; lv_style_int_t letter_space[LV_TABLE_CELL_STYLE_CNT]; lv_style_int_t line_space[LV_TABLE_CELL_STYLE_CNT]; const lv_font_t * font[LV_TABLE_CELL_STYLE_CNT]; for(i = 0; i < LV_TABLE_CELL_STYLE_CNT; i++) { if((ext->cell_types & (1 << i)) == 0) continue; /*Skip unused cell types*/ cell_left[i] = lv_obj_get_style_pad_left(table, LV_TABLE_PART_CELL1 + i); cell_right[i] = lv_obj_get_style_pad_right(table, LV_TABLE_PART_CELL1 + i); cell_top[i] = lv_obj_get_style_pad_top(table, LV_TABLE_PART_CELL1 + i); cell_bottom[i] = lv_obj_get_style_pad_bottom(table, LV_TABLE_PART_CELL1 + i); letter_space[i] = lv_obj_get_style_text_letter_space(table, LV_TABLE_PART_CELL1 + i); line_space[i] = lv_obj_get_style_text_line_space(table, LV_TABLE_PART_CELL1 + i); font[i] = lv_obj_get_style_text_font(table, LV_TABLE_PART_CELL1 + i); } for(i = 0; i < ext->row_cnt; i++) { ext->row_h[i] = get_row_height(table, i, font, letter_space, line_space, cell_left, cell_right, cell_top, cell_bottom); h += ext->row_h[i]; } lv_style_int_t bg_top = lv_obj_get_style_pad_top(table, LV_TABLE_PART_BG); lv_style_int_t bg_bottom = lv_obj_get_style_pad_bottom(table, LV_TABLE_PART_BG); lv_style_int_t bg_left = lv_obj_get_style_pad_left(table, LV_TABLE_PART_BG); lv_style_int_t bg_right = lv_obj_get_style_pad_right(table, LV_TABLE_PART_BG); w += bg_left + bg_right; h += bg_top + bg_bottom; lv_obj_set_size(table, w + 1, h + 1); lv_obj_invalidate(table); /*Always invalidate even if the size hasn't changed*/ } static lv_coord_t get_row_height(lv_obj_t * table, uint16_t row_id, const lv_font_t ** font, lv_style_int_t * letter_space, lv_style_int_t * line_space, lv_style_int_t * cell_left, lv_style_int_t * cell_right, lv_style_int_t * cell_top, lv_style_int_t * cell_bottom) { lv_table_ext_t * ext = lv_obj_get_ext_attr(table); lv_point_t txt_size; lv_coord_t txt_w; uint16_t row_start = row_id * ext->col_cnt; uint16_t cell; uint16_t col; lv_coord_t h_max = lv_font_get_line_height(font[0]) + cell_top[0] + cell_bottom[0]; for(cell = row_start, col = 0; cell < row_start + ext->col_cnt; cell++, col++) { if(ext->cell_data[cell] != NULL) { txt_w = ext->col_w[col]; uint16_t col_merge = 0; for(col_merge = 0; col_merge + col < ext->col_cnt - 1; col_merge++) { if(ext->cell_data[cell + col_merge] != NULL) { lv_table_cell_format_t format; format.format_byte = ext->cell_data[cell + col_merge][0]; if(format.s.right_merge) txt_w += ext->col_w[col + col_merge + 1]; else break; } else { break; } } lv_table_cell_format_t format; format.format_byte = ext->cell_data[cell][0]; uint8_t cell_type = format.s.type; /*With text crop assume 1 line*/ if(format.s.crop) { h_max = LV_MATH_MAX(lv_font_get_line_height(font[cell_type]) + cell_top[cell_type] + cell_bottom[cell_type], h_max); } /*Without text crop calculate the height of the text in the cell*/ else { txt_w -= cell_left[cell_type] + cell_right[cell_type]; _lv_txt_get_size(&txt_size, ext->cell_data[cell] + 1, font[cell_type], letter_space[cell_type], line_space[cell_type], txt_w, LV_TXT_FLAG_NONE); h_max = LV_MATH_MAX(txt_size.y + cell_top[cell_type] + cell_bottom[cell_type], h_max); cell += col_merge; col += col_merge; } } } return h_max; } #endif