diff --git a/docs/src/details/common-widget-features/events.rst b/docs/src/details/common-widget-features/events.rst index 5ab1dc3693..c49c4bed18 100644 --- a/docs/src/details/common-widget-features/events.rst +++ b/docs/src/details/common-widget-features/events.rst @@ -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. diff --git a/src/core/lv_obj.c b/src/core/lv_obj.c index fa0c81f443..159bd70ab2 100644 --- a/src/core/lv_obj.c +++ b/src/core/lv_obj.c @@ -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); } /** diff --git a/src/core/lv_obj_event.c b/src/core/lv_obj_event.c index 36fb7e1f78..bbb44fbf4f 100644 --- a/src/core/lv_obj_event.c +++ b/src/core/lv_obj_event.c @@ -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; + } } /********************** diff --git a/src/core/lv_obj_event.h b/src/core/lv_obj_event.h index 53e5d684a8..878acf70d8 100644 --- a/src/core/lv_obj_event.h +++ b/src/core/lv_obj_event.h @@ -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 **********************/ diff --git a/src/core/lv_obj_private.h b/src/core/lv_obj_private.h index 76a6a542bb..33da163672 100644 --- a/src/core/lv_obj_private.h +++ b/src/core/lv_obj_private.h @@ -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; }; /********************** diff --git a/src/core/lv_observer.c b/src/core/lv_observer.c index f01fc415cc..47cb5ff6b0 100644 --- a/src/core/lv_observer.c +++ b/src/core/lv_observer.c @@ -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; } diff --git a/src/core/lv_refr.c b/src/core/lv_refr.c index e30ffb4f29..e02e1384a1 100644 --- a/src/core/lv_refr.c +++ b/src/core/lv_refr.c @@ -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; } diff --git a/src/misc/lv_event.c b/src/misc/lv_event.c index 761218a8e3..34485becae 100644 --- a/src/misc/lv_event.c +++ b/src/misc/lv_event.c @@ -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); diff --git a/src/misc/lv_event.h b/src/misc/lv_event.h index 8709443265..639f5f1bf1 100644 --- a/src/misc/lv_event.h +++ b/src/misc/lv_event.h @@ -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 */ diff --git a/src/widgets/bar/lv_bar.c b/src/widgets/bar/lv_bar.c index 242bef9f9e..ae724e50bf 100644 --- a/src/widgets/bar/lv_bar.c +++ b/src/widgets/bar/lv_bar.c @@ -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 } diff --git a/src/widgets/roller/lv_roller.c b/src/widgets/roller/lv_roller.c index a6caa9f820..d639aa239d 100644 --- a/src/widgets/roller/lv_roller.c +++ b/src/widgets/roller/lv_roller.c @@ -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); } } diff --git a/src/widgets/slider/lv_slider.c b/src/widgets/slider/lv_slider.c index 93c16330f8..f1ec507d22 100644 --- a/src/widgets/slider/lv_slider.c +++ b/src/widgets/slider/lv_slider.c @@ -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 } diff --git a/src/widgets/switch/lv_switch.c b/src/widgets/switch/lv_switch.c index ae2cedc8fd..f7ea4711ac 100644 --- a/src/widgets/switch/lv_switch.c +++ b/src/widgets/switch/lv_switch.c @@ -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); diff --git a/src/xml/lv_xml_base_types.c b/src/xml/lv_xml_base_types.c index f1f4314540..5c4a906c6c 100644 --- a/src/xml/lv_xml_base_types.c +++ b/src/xml/lv_xml_base_types.c @@ -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; diff --git a/tests/ref_imgs/demo_widgets.png b/tests/ref_imgs/demo_widgets.png index 4708abdd31..8a9452abf3 100644 Binary files a/tests/ref_imgs/demo_widgets.png and b/tests/ref_imgs/demo_widgets.png differ diff --git a/tests/ref_imgs/theme_default_dark.png b/tests/ref_imgs/theme_default_dark.png index 0c051f0621..39ba2db49f 100644 Binary files a/tests/ref_imgs/theme_default_dark.png and b/tests/ref_imgs/theme_default_dark.png differ diff --git a/tests/ref_imgs/theme_default_light.png b/tests/ref_imgs/theme_default_light.png index 7f9cea3852..2a48238c04 100644 Binary files a/tests/ref_imgs/theme_default_light.png and b/tests/ref_imgs/theme_default_light.png differ diff --git a/tests/ref_imgs/theme_mono_dark.png b/tests/ref_imgs/theme_mono_dark.png index c047526981..3af01805c4 100644 Binary files a/tests/ref_imgs/theme_mono_dark.png and b/tests/ref_imgs/theme_mono_dark.png differ diff --git a/tests/ref_imgs/theme_mono_light.png b/tests/ref_imgs/theme_mono_light.png index 22e89dcf55..9bd83c687a 100644 Binary files a/tests/ref_imgs/theme_mono_light.png and b/tests/ref_imgs/theme_mono_light.png differ diff --git a/tests/ref_imgs/widgets/switch_1.png b/tests/ref_imgs/widgets/switch_1.png index 108009aee2..c1b5606a27 100644 Binary files a/tests/ref_imgs/widgets/switch_1.png and b/tests/ref_imgs/widgets/switch_1.png differ diff --git a/tests/ref_imgs/xml/lv_switch.png b/tests/ref_imgs/xml/lv_switch.png index db270ff85a..0f51f51cb3 100644 Binary files a/tests/ref_imgs/xml/lv_switch.png and b/tests/ref_imgs/xml/lv_switch.png differ diff --git a/tests/ref_imgs_vg_lite/demo_widgets.png b/tests/ref_imgs_vg_lite/demo_widgets.png index 5ad31506aa..2dc7eaf38f 100644 Binary files a/tests/ref_imgs_vg_lite/demo_widgets.png and b/tests/ref_imgs_vg_lite/demo_widgets.png differ diff --git a/tests/ref_imgs_vg_lite/theme_default_dark.png b/tests/ref_imgs_vg_lite/theme_default_dark.png index 746c119310..2396922e31 100644 Binary files a/tests/ref_imgs_vg_lite/theme_default_dark.png and b/tests/ref_imgs_vg_lite/theme_default_dark.png differ diff --git a/tests/ref_imgs_vg_lite/theme_default_light.png b/tests/ref_imgs_vg_lite/theme_default_light.png index 16d948dc80..6b8068c758 100644 Binary files a/tests/ref_imgs_vg_lite/theme_default_light.png and b/tests/ref_imgs_vg_lite/theme_default_light.png differ diff --git a/tests/ref_imgs_vg_lite/theme_mono_dark.png b/tests/ref_imgs_vg_lite/theme_mono_dark.png index bb1446bd37..e8d9d13efa 100644 Binary files a/tests/ref_imgs_vg_lite/theme_mono_dark.png and b/tests/ref_imgs_vg_lite/theme_mono_dark.png differ diff --git a/tests/ref_imgs_vg_lite/theme_mono_light.png b/tests/ref_imgs_vg_lite/theme_mono_light.png index bda6579b0b..af6483182f 100644 Binary files a/tests/ref_imgs_vg_lite/theme_mono_light.png and b/tests/ref_imgs_vg_lite/theme_mono_light.png differ diff --git a/tests/ref_imgs_vg_lite/widgets/switch_1.png b/tests/ref_imgs_vg_lite/widgets/switch_1.png index 68a6299594..fbad09cd36 100644 Binary files a/tests/ref_imgs_vg_lite/widgets/switch_1.png and b/tests/ref_imgs_vg_lite/widgets/switch_1.png differ diff --git a/tests/ref_imgs_vg_lite/xml/lv_switch.png b/tests/ref_imgs_vg_lite/xml/lv_switch.png index 0f7c8f27a3..e230268990 100644 Binary files a/tests/ref_imgs_vg_lite/xml/lv_switch.png and b/tests/ref_imgs_vg_lite/xml/lv_switch.png differ diff --git a/tests/src/test_cases/test_theme.c b/tests/src/test_cases/test_theme.c index 92634e475d..88d435c6bb 100644 --- a/tests/src/test_cases/test_theme.c +++ b/tests/src/test_cases/test_theme.c @@ -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); diff --git a/tests/src/test_cases/widgets/test_switch.c b/tests/src/test_cases/widgets/test_switch.c index 0cb6f69215..9222c11ec0 100644 --- a/tests/src/test_cases/widgets/test_switch.c +++ b/tests/src/test_cases/widgets/test_switch.c @@ -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"); } diff --git a/tests/src/test_cases/xml/test_xml_switch.c b/tests/src/test_cases/xml/test_xml_switch.c index e1bc530032..fdcb77f86c 100644 --- a/tests/src/test_cases/xml/test_xml_switch.c +++ b/tests/src/test_cases/xml/test_xml_switch.c @@ -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"); }