fix(widgets): make animations on state change more consistent (#9174)

Co-authored-by: Liam Howatt <30486941+liamHowatt@users.noreply.github.com>
This commit is contained in:
Gabor Kiss-Vamosi
2025-11-13 20:02:13 +01:00
committed by GitHub
parent e966f20d66
commit 483cb38f60
31 changed files with 85 additions and 11 deletions
@@ -181,6 +181,9 @@ Special Events
- :cpp:enumerator:`LV_EVENT_REFRESH`: Notify Widget to refresh something on it (for the user) - :cpp:enumerator:`LV_EVENT_REFRESH`: Notify Widget to refresh something on it (for the user)
- :cpp:enumerator:`LV_EVENT_READY`: A process has finished - :cpp:enumerator:`LV_EVENT_READY`: A process has finished
- :cpp:enumerator:`LV_EVENT_CANCEL`: A process has been cancelled - :cpp:enumerator:`LV_EVENT_CANCEL`: A process has been cancelled
- :cpp:enumerator:`LV_EVENT_STATE_CHANGED`: The state of a widget has been changed.
E.g. :cpp:enumerator:`LV_STATE_PRESSED` was added. In the event :cpp:expr:`lv_event_get_prev_state(e)`
returns the previous state and :cpp:expr:`lv_obj_get_state(obj)` returns the current state.
Other Events Other Events
+15
View File
@@ -994,6 +994,7 @@ static void update_obj_state(lv_obj_t * obj, lv_state_t new_state)
/*If there is no difference in styles there is nothing else to do*/ /*If there is no difference in styles there is nothing else to do*/
if(cmp_res == LV_STYLE_STATE_CMP_SAME) { if(cmp_res == LV_STYLE_STATE_CMP_SAME) {
obj->state = new_state; obj->state = new_state;
lv_obj_send_event(obj, LV_EVENT_STATE_CHANGED, &prev_state);
return; return;
} }
@@ -1002,6 +1003,18 @@ static void update_obj_state(lv_obj_t * obj, lv_state_t new_state)
obj->state = new_state; obj->state = new_state;
lv_obj_update_layer_type(obj); lv_obj_update_layer_type(obj);
/*Skip transitions if the widget is not rendered yet. */
if(!obj->rendered) {
lv_obj_invalidate(obj);
if(cmp_res == LV_STYLE_STATE_CMP_DIFF_DRAW_PAD) {
lv_obj_refresh_ext_draw_size(obj);
}
lv_obj_send_event(obj, LV_EVENT_STATE_CHANGED, &prev_state);
return;
}
lv_obj_style_transition_dsc_t * ts = lv_malloc_zeroed(sizeof(lv_obj_style_transition_dsc_t) * STYLE_TRANSITION_MAX); lv_obj_style_transition_dsc_t * ts = lv_malloc_zeroed(sizeof(lv_obj_style_transition_dsc_t) * STYLE_TRANSITION_MAX);
uint32_t tsi = 0; uint32_t tsi = 0;
uint32_t i; uint32_t i;
@@ -1058,6 +1071,8 @@ static void update_obj_state(lv_obj_t * obj, lv_state_t new_state)
lv_obj_invalidate(obj); lv_obj_invalidate(obj);
lv_obj_refresh_ext_draw_size(obj); lv_obj_refresh_ext_draw_size(obj);
} }
lv_obj_send_event(obj, LV_EVENT_STATE_CHANGED, &prev_state);
} }
/** /**
+11
View File
@@ -346,7 +346,18 @@ lv_draw_task_t * lv_event_get_draw_task(lv_event_t * e)
LV_LOG_WARN("Not interpreted with this event code"); LV_LOG_WARN("Not interpreted with this event code");
return NULL; return NULL;
} }
}
lv_state_t lv_event_get_prev_state(lv_event_t * e)
{
if(e->code == LV_EVENT_STATE_CHANGED) {
lv_state_t * state = lv_event_get_param(e);
return state ? *state : 0;
}
else {
LV_LOG_WARN("Not interpreted with this event code");
return 0;
}
} }
/********************** /**********************
+9
View File
@@ -16,6 +16,7 @@ extern "C" {
#include "../misc/lv_types.h" #include "../misc/lv_types.h"
#include "../misc/lv_event.h" #include "../misc/lv_event.h"
#include "../indev/lv_indev.h" #include "../indev/lv_indev.h"
#include "lv_obj_style.h"
/********************* /*********************
* DEFINES * DEFINES
@@ -193,6 +194,14 @@ void lv_event_set_cover_res(lv_event_t * e, lv_cover_res_t res);
*/ */
lv_draw_task_t * lv_event_get_draw_task(lv_event_t * e); lv_draw_task_t * lv_event_get_draw_task(lv_event_t * e);
/**
* Get the previous state before the state change.
* Can be used in `LV_EVENT_STATE_CHANGED` event
* @param e pointer to an event
* @return the previous state
*/
lv_state_t lv_event_get_prev_state(lv_event_t * e);
/********************** /**********************
* MACROS * MACROS
**********************/ **********************/
+4
View File
@@ -77,6 +77,10 @@ struct _lv_obj_t {
uint16_t h_layout : 1; uint16_t h_layout : 1;
uint16_t w_layout : 1; uint16_t w_layout : 1;
uint16_t is_deleting : 1; uint16_t is_deleting : 1;
/** The widget is rendered at least once already.
* It's used to skip initial animations and transitions. */
uint16_t rendered : 1;
}; };
/********************** /**********************
+3
View File
@@ -856,7 +856,9 @@ lv_observer_t * lv_obj_bind_checked(lv_obj_t * obj, lv_subject_t * subject)
{ {
lv_observer_t * observable = bind_to_bitfield(subject, obj, obj_state_observer_cb, LV_STATE_CHECKED, 0, true, lv_observer_t * observable = bind_to_bitfield(subject, obj, obj_state_observer_cb, LV_STATE_CHECKED, 0, true,
FLAG_COND_EQ); FLAG_COND_EQ);
lv_obj_add_event_cb(obj, obj_value_changed_event_cb, LV_EVENT_VALUE_CHANGED, subject); lv_obj_add_event_cb(obj, obj_value_changed_event_cb, LV_EVENT_VALUE_CHANGED, subject);
return observable; return observable;
} }
@@ -996,6 +998,7 @@ static lv_observer_t * bind_to_bitfield(lv_subject_t * subject, lv_obj_t * obj,
lv_observer_t * observable = lv_subject_add_observer_obj(subject, cb, obj, p); lv_observer_t * observable = lv_subject_add_observer_obj(subject, cb, obj, p);
observable->auto_free_user_data = 1; observable->auto_free_user_data = 1;
return observable; return observable;
} }
+5
View File
@@ -107,6 +107,10 @@ void lv_obj_redraw(lv_layer_t * layer, lv_obj_t * obj)
lv_area_t clip_area_ori = layer->_clip_area; lv_area_t clip_area_ori = layer->_clip_area;
lv_area_t clip_coords_for_obj; lv_area_t clip_coords_for_obj;
/*The widget will be rendered.
*So setters from now should use animations. */
obj->rendered = 1;
/*Truncate the clip area to `obj size + ext size` area*/ /*Truncate the clip area to `obj size + ext size` area*/
lv_area_t obj_coords_ext; lv_area_t obj_coords_ext;
lv_obj_get_coords(obj, &obj_coords_ext); lv_obj_get_coords(obj, &obj_coords_ext);
@@ -258,6 +262,7 @@ void lv_obj_redraw(lv_layer_t * layer, lv_obj_t * obj)
} }
layer->_clip_area = clip_area_ori; layer->_clip_area = clip_area_ori;
LV_PROFILER_REFR_END; LV_PROFILER_REFR_END;
} }
+1
View File
@@ -338,6 +338,7 @@ const char * lv_event_code_get_name(lv_event_code_t code)
ENUM_CASE(EVENT_REFRESH); ENUM_CASE(EVENT_REFRESH);
ENUM_CASE(EVENT_READY); ENUM_CASE(EVENT_READY);
ENUM_CASE(EVENT_CANCEL); ENUM_CASE(EVENT_CANCEL);
ENUM_CASE(EVENT_STATE_CHANGED);
/** Other events*/ /** Other events*/
ENUM_CASE(EVENT_CREATE); ENUM_CASE(EVENT_CREATE);
+1
View File
@@ -78,6 +78,7 @@ typedef enum {
LV_EVENT_REFRESH, /**< Notify Widget to refresh something on it (for user)*/ LV_EVENT_REFRESH, /**< Notify Widget to refresh something on it (for user)*/
LV_EVENT_READY, /**< A process has finished */ LV_EVENT_READY, /**< A process has finished */
LV_EVENT_CANCEL, /**< A process has been cancelled */ LV_EVENT_CANCEL, /**< A process has been cancelled */
LV_EVENT_STATE_CHANGED, /**< The state of the widget changed*/
/** Other events */ /** Other events */
LV_EVENT_CREATE, /**< Object is being created */ LV_EVENT_CREATE, /**< Object is being created */
+5 -2
View File
@@ -737,12 +737,15 @@ static void lv_bar_init_anim(lv_obj_t * obj, lv_bar_anim_t * bar_anim)
static void bar_value_observer_cb(lv_observer_t * observer, lv_subject_t * subject) static void bar_value_observer_cb(lv_observer_t * observer, lv_subject_t * subject)
{ {
lv_obj_t * obj = lv_observer_get_target_obj(observer);
/*If the bar is not rendered yet show the new state immediately*/
lv_anim_enable_t anim_on = obj->rendered ? LV_ANIM_ON : LV_ANIM_OFF;
if(subject->type == LV_SUBJECT_TYPE_INT) { if(subject->type == LV_SUBJECT_TYPE_INT) {
lv_bar_set_value(observer->target, subject->value.num, LV_ANIM_OFF); lv_bar_set_value(observer->target, subject->value.num, anim_on);
} }
#if LV_USE_FLOAT #if LV_USE_FLOAT
else { else {
lv_bar_set_value(observer->target, (int32_t)subject->value.float_v, LV_ANIM_OFF); lv_bar_set_value(observer->target, (int32_t)subject->value.float_v, anim_on);
} }
#endif #endif
} }
+4 -1
View File
@@ -964,8 +964,11 @@ static void roller_value_changed_event_cb(lv_event_t * e)
static void roller_value_observer_cb(lv_observer_t * observer, lv_subject_t * subject) static void roller_value_observer_cb(lv_observer_t * observer, lv_subject_t * subject)
{ {
/*If the roller is not rendered yet show the new state immediately*/
lv_obj_t * obj = lv_observer_get_target_obj(observer);
lv_anim_enable_t anim_on = obj->rendered ? LV_ANIM_ON : LV_ANIM_OFF;
if((int32_t)lv_roller_get_selected(observer->target) != subject->value.num) { if((int32_t)lv_roller_get_selected(observer->target) != subject->value.num) {
lv_roller_set_selected(observer->target, subject->value.num, LV_ANIM_OFF); lv_roller_set_selected(observer->target, subject->value.num, anim_on);
} }
} }
+5 -3
View File
@@ -234,7 +234,6 @@ lv_observer_t * lv_slider_bind_value(lv_obj_t * obj, lv_subject_t * subject)
} }
lv_obj_add_event_cb(obj, slider_value_changed_event_cb, LV_EVENT_VALUE_CHANGED, subject); lv_obj_add_event_cb(obj, slider_value_changed_event_cb, LV_EVENT_VALUE_CHANGED, subject);
lv_observer_t * observer = lv_subject_add_observer_obj(subject, slider_value_observer_cb, obj, NULL); lv_observer_t * observer = lv_subject_add_observer_obj(subject, slider_value_observer_cb, obj, NULL);
return observer; return observer;
} }
@@ -682,12 +681,15 @@ static void slider_value_changed_event_cb(lv_event_t * e)
static void slider_value_observer_cb(lv_observer_t * observer, lv_subject_t * subject) static void slider_value_observer_cb(lv_observer_t * observer, lv_subject_t * subject)
{ {
lv_obj_t * obj = lv_observer_get_target_obj(observer);
/*If the slider is not rendered yet show the new state immediately*/
lv_anim_enable_t anim_on = obj->rendered ? LV_ANIM_ON : LV_ANIM_OFF;
if(subject->type == LV_SUBJECT_TYPE_INT) { if(subject->type == LV_SUBJECT_TYPE_INT) {
lv_slider_set_value(observer->target, subject->value.num, LV_ANIM_OFF); lv_slider_set_value(observer->target, subject->value.num, anim_on);
} }
#if LV_USE_FLOAT #if LV_USE_FLOAT
else { else {
lv_slider_set_value(observer->target, (int32_t)subject->value.float_v, LV_ANIM_OFF); lv_slider_set_value(observer->target, (int32_t)subject->value.float_v, anim_on);
} }
#endif #endif
} }
+11 -3
View File
@@ -163,9 +163,14 @@ static void lv_switch_event(const lv_obj_class_t * class_p, lv_event_t * e)
*s = LV_MAX(*s, knob_size); *s = LV_MAX(*s, knob_size);
*s = LV_MAX(*s, lv_obj_calculate_ext_draw_size(obj, LV_PART_INDICATOR)); *s = LV_MAX(*s, lv_obj_calculate_ext_draw_size(obj, LV_PART_INDICATOR));
} }
else if(code == LV_EVENT_VALUE_CHANGED) { else if(code == LV_EVENT_STATE_CHANGED) {
lv_switch_trigger_anim(obj); lv_state_t prev_state = lv_event_get_prev_state(e);
lv_obj_invalidate(obj); lv_state_t diff = prev_state ^ lv_obj_get_state(obj);
if(diff & LV_STATE_CHECKED) {
lv_switch_trigger_anim(obj);
lv_obj_invalidate(obj);
}
} }
else if(code == LV_EVENT_DRAW_MAIN) { else if(code == LV_EVENT_DRAW_MAIN) {
draw_main(e); draw_main(e);
@@ -296,6 +301,9 @@ static void lv_switch_anim_completed(lv_anim_t * a)
static void lv_switch_trigger_anim(lv_obj_t * obj) static void lv_switch_trigger_anim(lv_obj_t * obj)
{ {
LV_ASSERT_OBJ(obj, MY_CLASS); LV_ASSERT_OBJ(obj, MY_CLASS);
/*If the widget is not rendered yet show state changes immediately*/
if(!obj->rendered) return;
lv_switch_t * sw = (lv_switch_t *)obj; lv_switch_t * sw = (lv_switch_t *)obj;
uint32_t anim_dur_full = lv_obj_get_style_anim_duration(obj, LV_PART_MAIN); uint32_t anim_dur_full = lv_obj_get_style_anim_duration(obj, LV_PART_MAIN);
+1
View File
@@ -285,6 +285,7 @@ lv_event_code_t lv_xml_trigger_text_to_enum_value(const char * txt)
if(lv_streq("child_changed", txt)) return LV_EVENT_CHILD_CHANGED; if(lv_streq("child_changed", txt)) return LV_EVENT_CHILD_CHANGED;
if(lv_streq("child_created", txt)) return LV_EVENT_CHILD_CREATED; if(lv_streq("child_created", txt)) return LV_EVENT_CHILD_CREATED;
if(lv_streq("child_deleted", txt)) return LV_EVENT_CHILD_DELETED; if(lv_streq("child_deleted", txt)) return LV_EVENT_CHILD_DELETED;
if(lv_streq("state_changed", txt)) return LV_EVENT_STATE_CHANGED;
if(lv_streq("screen_unload_start", txt)) return LV_EVENT_SCREEN_UNLOAD_START; if(lv_streq("screen_unload_start", txt)) return LV_EVENT_SCREEN_UNLOAD_START;
if(lv_streq("screen_load_start", txt)) return LV_EVENT_SCREEN_LOAD_START; if(lv_streq("screen_load_start", txt)) return LV_EVENT_SCREEN_LOAD_START;
if(lv_streq("screen_loaded", txt)) return LV_EVENT_SCREEN_LOADED; if(lv_streq("screen_loaded", txt)) return LV_EVENT_SCREEN_LOADED;
Binary file not shown.

Before

Width:  |  Height:  |  Size: 71 KiB

After

Width:  |  Height:  |  Size: 71 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 47 KiB

After

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 45 KiB

After

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.2 KiB

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.4 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 70 KiB

After

Width:  |  Height:  |  Size: 70 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 50 KiB

After

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 48 KiB

After

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.7 KiB

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.4 KiB

After

Width:  |  Height:  |  Size: 1.9 KiB

+2
View File
@@ -141,6 +141,8 @@ static void test_widgets(const char * img_name)
lv_spinner_create(scr_act); lv_spinner_create(scr_act);
lv_test_wait(1000); /*Wait for the transitions*/
TEST_ASSERT_EQUAL_SCREENSHOT(img_name); TEST_ASSERT_EQUAL_SCREENSHOT(img_name);
lv_obj_clean(scr_act); lv_obj_clean(scr_act);
@@ -156,6 +156,8 @@ void test_screeshots(void)
lv_obj_add_state(sw, LV_STATE_CHECKED); lv_obj_add_state(sw, LV_STATE_CHECKED);
lv_obj_set_size(sw, 50, 100); lv_obj_set_size(sw, 50, 100);
lv_test_wait(1000); /*Wait for the transitions*/
TEST_ASSERT_EQUAL_SCREENSHOT("widgets/switch_1.png"); TEST_ASSERT_EQUAL_SCREENSHOT("widgets/switch_1.png");
} }
@@ -33,6 +33,7 @@ void test_xml_switch_widget(void)
lv_xml_create(scr, "lv_switch", attrs_1); lv_xml_create(scr, "lv_switch", attrs_1);
lv_test_wait(1000); /*Wait for the transitions*/
TEST_ASSERT_EQUAL_SCREENSHOT("xml/lv_switch.png"); TEST_ASSERT_EQUAL_SCREENSHOT("xml/lv_switch.png");
} }