feat(dropdown): Support LV_SIZE_CONTENT and fix layout nested invalidation (#9307)

Co-authored-by: cubic-dev-ai[bot] <191113872+cubic-dev-ai[bot]@users.noreply.github.com>
This commit is contained in:
Andy Everitt
2026-02-24 07:44:33 +00:00
committed by GitHub
parent 9be768db42
commit 0c899bb62b
19 changed files with 213 additions and 33 deletions
+1 -3
View File
@@ -1035,11 +1035,9 @@ static void lv_obj_event(const lv_obj_class_t * class_p, lv_event_t * e)
}
}
else if(code == LV_EVENT_CHILD_CHANGED) {
int32_t w = lv_obj_get_style_width(obj, LV_PART_MAIN);
int32_t h = lv_obj_get_style_height(obj, LV_PART_MAIN);
int32_t align = lv_obj_get_style_align(obj, LV_PART_MAIN);
uint16_t layout = lv_obj_get_style_layout(obj, LV_PART_MAIN);
if(layout || align || w == LV_SIZE_CONTENT || h == LV_SIZE_CONTENT) {
if(layout || align || lv_obj_is_style_any_width_content(obj) || lv_obj_is_style_any_height_content(obj)) {
lv_obj_mark_layout_as_dirty(obj);
}
}
+32 -3
View File
@@ -728,6 +728,26 @@ int32_t lv_obj_get_style_clamped_height(lv_obj_t * obj)
return h;
}
bool lv_obj_is_style_any_width_content(lv_obj_t * obj)
{
LV_ASSERT_OBJ(obj, MY_CLASS);
int32_t w = lv_obj_get_style_width(obj, LV_PART_MAIN);
int32_t minw = lv_obj_get_style_min_width(obj, LV_PART_MAIN);
int32_t maxw = lv_obj_get_style_max_width(obj, LV_PART_MAIN);
return (w == LV_SIZE_CONTENT || minw == LV_SIZE_CONTENT || maxw == LV_SIZE_CONTENT);
}
bool lv_obj_is_style_any_height_content(lv_obj_t * obj)
{
LV_ASSERT_OBJ(obj, MY_CLASS);
int32_t h = lv_obj_get_style_height(obj, LV_PART_MAIN);
int32_t minh = lv_obj_get_style_min_height(obj, LV_PART_MAIN);
int32_t maxh = lv_obj_get_style_max_height(obj, LV_PART_MAIN);
return (h == LV_SIZE_CONTENT || minh == LV_SIZE_CONTENT || maxh == LV_SIZE_CONTENT);
}
bool lv_obj_is_width_min(lv_obj_t * obj)
{
LV_ASSERT_OBJ(obj, MY_CLASS);
@@ -766,10 +786,19 @@ bool lv_obj_is_height_max(lv_obj_t * obj)
bool lv_obj_refresh_self_size(lv_obj_t * obj)
{
int32_t w_set = lv_obj_get_style_width(obj, LV_PART_MAIN);
int32_t h_set = lv_obj_get_style_height(obj, LV_PART_MAIN);
if(w_set != LV_SIZE_CONTENT && h_set != LV_SIZE_CONTENT) return false;
if(!lv_obj_is_style_any_width_content(obj) && !lv_obj_is_style_any_height_content(obj))
return false;
/**
* Refresh the parent's layout, because the childs size is in some way dependent on its contents we need to force a
* recalculation of the parents layout
*/
lv_obj_t * parent = lv_obj_get_parent(obj);
if(parent != NULL) {
parent->w_layout = 0;
parent->h_layout = 0;
lv_obj_mark_layout_as_dirty(parent);
}
lv_obj_mark_layout_as_dirty(obj);
return true;
}
+16
View File
@@ -362,6 +362,22 @@ int32_t lv_obj_get_style_clamped_width(lv_obj_t * obj);
*/
int32_t lv_obj_get_style_clamped_height(lv_obj_t * obj);
/**
* Determine if any of the object's width style properties are set to `LV_SIZE_CONTENT`.
* @param obj Pointer to a valid object.
* @return `true` At least one of the following width style properties is `LV_SIZE_CONTENT`: `LV_STYLE_WIDTH`, `LV_STYLE_MIN_WIDTH`, `LV_STYLE_MAX_WIDTH`.
* @return `false` No width style properties are `LV_SIZE_CONTENT`.
*/
bool lv_obj_is_style_any_width_content(lv_obj_t * obj);
/**
* Determine if any of the object's height style properties are set to `LV_SIZE_CONTENT`.
* @param obj Pointer to a valid object.
* @return `true` At least one of the following height style properties is `LV_SIZE_CONTENT`: `LV_STYLE_HEIGHT`, `LV_STYLE_MIN_HEIGHT`, `LV_STYLE_MAX_HEIGHT`.
* @return `false` No height style properties are `LV_SIZE_CONTENT`.
*/
bool lv_obj_is_style_any_height_content(lv_obj_t * obj);
/**
* @brief Determine if the object's resolved width was limited by its minimum width constraint.
*
+104 -16
View File
@@ -45,6 +45,7 @@ static void lv_dropdown_constructor(const lv_obj_class_t * class_p, lv_obj_t * o
static void lv_dropdown_destructor(const lv_obj_class_t * class_p, lv_obj_t * obj);
static void lv_dropdown_event(const lv_obj_class_t * class_p, lv_event_t * e);
static void draw_main(lv_event_t * e);
static void refresh_size(lv_obj_t * obj);
static void lv_dropdownlist_constructor(const lv_obj_class_t * class_p, lv_obj_t * obj);
static void lv_dropdownlist_destructor(const lv_obj_class_t * class_p, lv_obj_t * list_obj);
@@ -165,6 +166,9 @@ void lv_dropdown_set_text(lv_obj_t * obj, const char * text)
{
LV_ASSERT_OBJ(obj, MY_CLASS);
lv_dropdown_t * dropdown = (lv_dropdown_t *)obj;
if(!dropdown->static_text && dropdown->text && text && lv_strcmp(dropdown->text, text) == 0) {
return;
}
char * copied_text = NULL;
if(text) {
@@ -184,11 +188,16 @@ void lv_dropdown_set_text_static(lv_obj_t * obj, const char * text)
LV_ASSERT_OBJ(obj, MY_CLASS);
lv_dropdown_t * dropdown = (lv_dropdown_t *)obj;
if(!dropdown->static_text) lv_free(dropdown->text);
if(dropdown->static_text && dropdown->text && text && lv_strcmp(dropdown->text, text) == 0) {
return;
}
if(!dropdown->static_text)
lv_free(dropdown->text);
dropdown->static_text = 1;
dropdown->text = (char *)text;
lv_obj_invalidate(obj);
refresh_size(obj);
}
void lv_dropdown_set_options(lv_obj_t * obj, const char * options)
@@ -234,8 +243,9 @@ void lv_dropdown_set_options(lv_obj_t * obj, const char * options)
/*Now the text is dynamically allocated*/
dropdown->static_options = 0;
lv_obj_invalidate(obj);
if(dropdown->list) lv_obj_invalidate(dropdown->list);
refresh_size(obj);
if(dropdown->list)
lv_obj_invalidate(dropdown->list);
}
void lv_dropdown_set_options_static(lv_obj_t * obj, const char * options)
@@ -263,8 +273,9 @@ void lv_dropdown_set_options_static(lv_obj_t * obj, const char * options)
dropdown->static_options = 1;
dropdown->options = (char *)options;
lv_obj_invalidate(obj);
if(dropdown->list) lv_obj_invalidate(dropdown->list);
refresh_size(obj);
if(dropdown->list)
lv_obj_invalidate(dropdown->list);
}
void lv_dropdown_add_option(lv_obj_t * obj, const char * option, uint32_t pos)
@@ -335,8 +346,9 @@ void lv_dropdown_add_option(lv_obj_t * obj, const char * option, uint32_t pos)
dropdown->option_cnt++;
lv_obj_invalidate(obj);
if(dropdown->list) lv_obj_invalidate(dropdown->list);
refresh_size(obj);
if(dropdown->list)
lv_obj_invalidate(dropdown->list);
}
void lv_dropdown_clear_options(lv_obj_t * obj)
@@ -352,8 +364,9 @@ void lv_dropdown_clear_options(lv_obj_t * obj)
dropdown->static_options = 1;
dropdown->option_cnt = 0;
lv_obj_invalidate(obj);
if(dropdown->list) lv_obj_invalidate(dropdown->list);
refresh_size(obj);
if(dropdown->list)
lv_obj_invalidate(dropdown->list);
}
void lv_dropdown_set_selected(lv_obj_t * obj, uint32_t sel_opt)
@@ -370,7 +383,7 @@ void lv_dropdown_set_selected(lv_obj_t * obj, uint32_t sel_opt)
position_to_selected(obj, LV_ANIM_OFF);
}
lv_obj_invalidate(obj);
refresh_size(obj);
}
void lv_dropdown_set_dir(lv_obj_t * obj, lv_dir_t dir)
@@ -391,7 +404,7 @@ void lv_dropdown_set_symbol(lv_obj_t * obj, const void * symbol)
lv_dropdown_t * dropdown = (lv_dropdown_t *)obj;
dropdown->symbol = symbol;
lv_obj_invalidate(obj);
refresh_size(obj);
}
void lv_dropdown_set_selected_highlight(lv_obj_t * obj, bool en)
@@ -795,7 +808,11 @@ static void lv_dropdown_event(const lv_obj_class_t * class_p, lv_event_t * e)
}
else if(code == LV_EVENT_RELEASED) {
res = btn_release_handler(obj);
if(res != LV_RESULT_OK) return;
if(res != LV_RESULT_OK)
return;
}
else if(code == LV_EVENT_VALUE_CHANGED) {
refresh_size(obj);
}
else if(code == LV_EVENT_STYLE_CHANGED) {
lv_obj_refresh_self_size(obj);
@@ -804,9 +821,73 @@ static void lv_dropdown_event(const lv_obj_class_t * class_p, lv_event_t * e)
lv_obj_refresh_self_size(obj);
}
else if(code == LV_EVENT_GET_SELF_SIZE) {
lv_point_t * p = lv_event_get_param(e);
const lv_font_t * font = lv_obj_get_style_text_font(obj, LV_PART_MAIN);
p->y = lv_font_get_line_height(font);
lv_point_t size;
size.x = 0;
size.y = 0;
/* Calculate the symbol width */
int32_t symbol_w = 0;
if(dropdown->symbol) {
lv_draw_label_dsc_t symbol_dsc;
lv_draw_label_dsc_init(&symbol_dsc);
lv_image_src_t symbol_type = lv_image_src_get_type(dropdown->symbol);
if(symbol_type == LV_IMAGE_SRC_SYMBOL) {
lv_point_t text_size;
lv_text_get_size(&text_size,
dropdown->symbol,
symbol_dsc.font,
symbol_dsc.letter_space,
symbol_dsc.line_space,
LV_COORD_MAX,
symbol_dsc.flag);
symbol_w = text_size.x;
}
else {
lv_image_header_t header;
lv_result_t decoder_res = lv_image_decoder_get_info(dropdown->symbol, &header);
if(decoder_res == LV_RESULT_OK) {
symbol_w = header.w;
}
else {
symbol_w = 0;
}
}
size.x += symbol_w;
size.x += lv_obj_get_style_pad_column(obj, LV_PART_MAIN);
}
/* Calculate the text width */
const char * opt_txt;
char buf[128];
if(dropdown->text)
opt_txt = dropdown->text;
else {
lv_dropdown_get_selected_str(obj, buf, 128);
opt_txt = buf;
}
if(opt_txt == NULL) {
size.y = LV_MAX(size.y, lv_font_get_line_height(font));
}
else {
lv_draw_label_dsc_t dsc;
lv_draw_label_dsc_init(&dsc);
lv_point_t text_size;
int32_t max_width = lv_obj_calc_dynamic_width(obj, LV_STYLE_MAX_WIDTH) - size.x;
lv_text_get_size(&text_size, opt_txt, font, dsc.letter_space, dsc.line_space, max_width, dsc.flag);
size.x += text_size.x;
size.y = LV_MAX(size.y, text_size.y);
}
lv_point_t * p = lv_event_get_param(e);
p->x = LV_MAX(p->x, size.x);
p->y = LV_MAX(p->y, size.y);
}
else if(code == LV_EVENT_KEY) {
uint32_t c = lv_event_get_key(e);
@@ -839,7 +920,8 @@ static void lv_dropdown_event(const lv_obj_class_t * class_p, lv_event_t * e)
lv_obj_t * indev_obj = lv_indev_get_active_obj();
if(indev_obj != obj) {
res = btn_release_handler(obj);
if(res != LV_RESULT_OK) return;
if(res != LV_RESULT_OK)
return;
}
}
}
@@ -1032,6 +1114,12 @@ static void draw_main(lv_event_t * e)
lv_draw_label(layer, &label_dsc, &txt_area);
}
static void refresh_size(lv_obj_t * obj)
{
lv_obj_invalidate(obj);
lv_obj_refresh_self_size(obj);
}
static void draw_list(lv_event_t * e)
{
lv_obj_t * list_obj = lv_event_get_current_target(e);
+10 -1
View File
@@ -1063,7 +1063,16 @@ static void lv_label_mark_need_refr_text(lv_obj_t * obj)
label->invalid_size_cache = true;
lv_obj_invalidate(obj);
lv_obj_refresh_self_size(obj);
/**
* Ideally we would use `lv_obj_refresh_self_size(obj);` here but it can cause an infinite loop due to the way label
* self size is implemented.
* The implementation should be revisited in the future since it currently doesn't handle fixed height, content
* width in all scenarios properly.
* Once that is fixed we should be able to use `lv_obj_refresh_self_size(obj);` here.
*/
if(lv_obj_is_style_any_height_content(obj) || lv_obj_is_style_any_width_content(obj))
lv_obj_mark_layout_as_dirty(obj);
if(!label->need_refr_text) {
label->need_refr_text = true;
Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

@@ -540,5 +540,22 @@ void test_dropdown_properties(void)
#endif
}
void test_dropdown_content_size()
{
lv_obj_t * dd = lv_dropdown_create(lv_screen_active());
lv_dropdown_set_options(dd, "Short\nA bit longer option\nThe longest option in the list");
lv_obj_set_width(dd, LV_SIZE_CONTENT);
TEST_ASSERT_EQUAL_SCREENSHOT("widgets/dropdown_content_size_1.png");
lv_dropdown_open(dd);
TEST_ASSERT_EQUAL_SCREENSHOT("widgets/dropdown_content_size_1_open.png");
lv_dropdown_set_selected(dd, 1);
lv_obj_refresh_self_size(lv_screen_active());
lv_obj_invalidate(lv_screen_active());
TEST_ASSERT_EQUAL_SCREENSHOT("widgets/dropdown_content_size_2.png");
lv_dropdown_set_selected(dd, 2);
TEST_ASSERT_EQUAL_SCREENSHOT("widgets/dropdown_content_size_3.png");
}
#endif
+33 -10
View File
@@ -81,17 +81,40 @@ void test_style_min_size(void)
lv_obj_set_style_min_width(child, LV_SIZE_CONTENT, 0);
lv_obj_set_style_min_height(child, LV_SIZE_CONTENT, 0);
TEST_ASSERT_EQUAL_SCREENSHOT("widgets/obj_pos_content_min_size.png");
TEST_ASSERT_TRUE(lv_obj_is_width_min(child));
TEST_ASSERT_TRUE(lv_obj_is_height_min(child));
}
lv_obj_set_size(parent, 100, 100);
lv_obj_set_style_min_width(child, 0, 0);
lv_obj_set_style_min_height(child, 0, 0);
lv_refr_now(NULL);
TEST_ASSERT_FALSE(lv_obj_is_width_min(child));
TEST_ASSERT_FALSE(lv_obj_is_height_min(child));
TEST_ASSERT_EQUAL(LV_PCT(100), lv_obj_get_style_clamped_width(child));
TEST_ASSERT_EQUAL(LV_PCT(100), lv_obj_get_style_clamped_height(child));
void test_chaining_invalidation_layout(void)
{
lv_obj_t * cont = lv_obj_create(lv_screen_active());
lv_obj_set_name(cont, "cont");
lv_obj_set_size(cont, 500, LV_SIZE_CONTENT);
lv_obj_set_style_bg_color(cont, lv_palette_main(LV_PALETTE_RED), 0);
lv_obj_set_style_bg_opa(cont, LV_OPA_COVER, 0);
lv_obj_set_flex_flow(cont, LV_FLEX_FLOW_ROW_WRAP);
lv_obj_t * label = lv_label_create(cont);
lv_obj_set_name(label, "label");
lv_label_set_text(label, "Dropdown with size content:");
lv_obj_t * sub_cont = lv_obj_create(cont);
lv_obj_set_name(sub_cont, "sub_cont");
lv_obj_set_style_bg_color(sub_cont, lv_palette_main(LV_PALETTE_GREEN), 0);
lv_obj_set_style_bg_opa(sub_cont, LV_OPA_COVER, 0);
lv_obj_set_height(sub_cont, LV_SIZE_CONTENT);
lv_obj_set_flex_flow(sub_cont, LV_FLEX_FLOW_ROW);
lv_obj_set_flex_grow(sub_cont, 1);
lv_obj_set_style_min_width(sub_cont, LV_SIZE_CONTENT, LV_PART_MAIN);
lv_obj_t * dd = lv_dropdown_create(sub_cont);
lv_obj_set_name(dd, "dropdown");
lv_dropdown_set_options(dd, "Short\nA bit longer option\nThe longest option in the list");
lv_obj_set_width(dd, 0);
lv_obj_set_style_min_width(dd, LV_SIZE_CONTENT, 0);
lv_obj_set_flex_grow(dd, 1);
TEST_ASSERT_EQUAL_SCREENSHOT("widgets/obj_pos_chained_layout_invalidation_pre.png");
lv_dropdown_set_selected(dd, 2);
TEST_ASSERT_EQUAL_SCREENSHOT("widgets/obj_pos_chained_layout_invalidation_post.png");
}
void test_circular_height_dependency(void)