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_READY`: A process has finished
- :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
@@ -265,11 +268,11 @@ contains all data about the event. The following values can be retrieved from it
- :cpp:expr:`lv_event_get_param(e)`: get the parameter passed as the last parameter of :cpp:func:`lv_obj_send_event_cb`
.. tip::
When using C++, prefer :cpp:expr:`lv_event_get_target_obj(e)` over :cpp:expr:`lv_event_get_target(e)`
When using C++, prefer :cpp:expr:`lv_event_get_target_obj(e)` over :cpp:expr:`lv_event_get_target(e)`
when you know the target is a Widget, as it returns the correct type without requiring a cast.
.. warning::
Only call :cpp:expr:`lv_event_get_target_obj(e)` when the event target is known to be a Widget.
Only call :cpp:expr:`lv_event_get_target_obj(e)` when the event target is known to be a Widget.
Calling it for Display or Indev targets is considered Undefined Behavior.
+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(cmp_res == LV_STYLE_STATE_CMP_SAME) {
obj->state = new_state;
lv_obj_send_event(obj, LV_EVENT_STATE_CHANGED, &prev_state);
return;
}
@@ -1002,6 +1003,18 @@ static void update_obj_state(lv_obj_t * obj, lv_state_t new_state)
obj->state = new_state;
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);
uint32_t tsi = 0;
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_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");
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_event.h"
#include "../indev/lv_indev.h"
#include "lv_obj_style.h"
/*********************
* 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);
/**
* 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
**********************/
+4
View File
@@ -77,6 +77,10 @@ struct _lv_obj_t {
uint16_t h_layout : 1;
uint16_t w_layout : 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,
FLAG_COND_EQ);
lv_obj_add_event_cb(obj, obj_value_changed_event_cb, LV_EVENT_VALUE_CHANGED, subject);
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);
observable->auto_free_user_data = 1;
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_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*/
lv_area_t 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;
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_READY);
ENUM_CASE(EVENT_CANCEL);
ENUM_CASE(EVENT_STATE_CHANGED);
/** Other events*/
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_READY, /**< A process has finished */
LV_EVENT_CANCEL, /**< A process has been cancelled */
LV_EVENT_STATE_CHANGED, /**< The state of the widget changed*/
/** Other events */
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)
{
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) {
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
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
}
+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)
{
/*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) {
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_observer_t * observer = lv_subject_add_observer_obj(subject, slider_value_observer_cb, obj, NULL);
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)
{
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) {
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
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
}
+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, lv_obj_calculate_ext_draw_size(obj, LV_PART_INDICATOR));
}
else if(code == LV_EVENT_VALUE_CHANGED) {
lv_switch_trigger_anim(obj);
lv_obj_invalidate(obj);
else if(code == LV_EVENT_STATE_CHANGED) {
lv_state_t prev_state = lv_event_get_prev_state(e);
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) {
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)
{
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;
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_created", txt)) return LV_EVENT_CHILD_CREATED;
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_load_start", txt)) return LV_EVENT_SCREEN_LOAD_START;
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_test_wait(1000); /*Wait for the transitions*/
TEST_ASSERT_EQUAL_SCREENSHOT(img_name);
lv_obj_clean(scr_act);
@@ -156,6 +156,8 @@ void test_screeshots(void)
lv_obj_add_state(sw, LV_STATE_CHECKED);
lv_obj_set_size(sw, 50, 100);
lv_test_wait(1000); /*Wait for the transitions*/
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_test_wait(1000); /*Wait for the transitions*/
TEST_ASSERT_EQUAL_SCREENSHOT("xml/lv_switch.png");
}