diff --git a/docs/misc/anim-timeline.png b/docs/misc/anim-timeline.png new file mode 100644 index 0000000000..965991ed13 Binary files /dev/null and b/docs/misc/anim-timeline.png differ diff --git a/docs/overview/animation.md b/docs/overview/animation.md index 3c4d98078e..9b5d61a800 100644 --- a/docs/overview/animation.md +++ b/docs/overview/animation.md @@ -104,6 +104,28 @@ The speed is interpreted in _unit/sec_ dimension. For example, `lv_anim_speed_t You can delete an animation with `lv_anim_del(var, func)` if you provide the animated variable and its animator function. +## Timeline +Timeline is a collection of multiple Animations, which makes it easy to create complex composite animations. + +Firstly, create the animation element, but don’t call `lv_anim_start()`. + +Secondly, create an animation timeline object, by calling `lv_anim_timeline_create()`. + +Thirdly, add animation elements to the animation timeline, by calling `lv_anim_timeline_add(at, start_time, &a)`. `start_time` is the start time of the animation on the timeline. Note that `start_time` will override the value of `delay`. + +Finally, call `lv_anim_timeline_start(at)` to start the animation timeline. + +It supports forward and backward playback of the entire animation group, using `lv_anim_timeline_set_reverse(at, reverse)`. + +Call the `lv_anim_timeline_set_progress(at, progress)` function to set the state of the object corresponding to the progress of the timeline. + +Call the `lv_anim_timeline_get_playtime(at)` function to get the total duration of the entire animation timeline. + +Call the `lv_anim_timeline_get_reverse(at)` function to get whether to reverse the animation timeline. + +Call the `lv_anim_timeline_del(at)` function to delete the animation timeline. + +![](/misc/anim-timeline.png "timeline diagram") ## Examples diff --git a/examples/anim/index.rst b/examples/anim/index.rst index 5370618483..f7f3b7895a 100644 --- a/examples/anim/index.rst +++ b/examples/anim/index.rst @@ -10,4 +10,8 @@ Playback animation .. lv_example:: anim/lv_example_anim_2 :language: c +Animation timeline +""""""""""""""""""" +.. lv_example:: anim/lv_example_anim_timeline_1.c + :language: c diff --git a/examples/anim/lv_example_anim.h b/examples/anim/lv_example_anim.h index 32e8985174..8809a733b1 100644 --- a/examples/anim/lv_example_anim.h +++ b/examples/anim/lv_example_anim.h @@ -27,6 +27,7 @@ extern "C" { **********************/ void lv_example_anim_1(void); void lv_example_anim_2(void); +void lv_example_anim_timeline_1(void); /********************** * MACROS diff --git a/examples/anim/lv_example_anim_timeline_1.c b/examples/anim/lv_example_anim_timeline_1.c new file mode 100644 index 0000000000..45a1bb3f3b --- /dev/null +++ b/examples/anim/lv_example_anim_timeline_1.c @@ -0,0 +1,170 @@ +#include "../lv_examples.h" +#if LV_BUILD_EXAMPLES + +static lv_anim_timeline_t * anim_timeline = NULL; + +static lv_obj_t * obj1 = NULL; +static lv_obj_t * obj2 = NULL; +static lv_obj_t * obj3 = NULL; + +static const lv_coord_t obj_width = 150; +static const lv_coord_t obj_height = 200; + +static void set_width(void * var, int32_t v) +{ + lv_obj_set_width((lv_obj_t *)var, v); +} + +static void set_height(void * var, int32_t v) +{ + lv_obj_set_height((lv_obj_t *)var, v); +} + +static void anim_timeline_create(void) +{ + /* obj1 */ + lv_anim_t a1; + lv_anim_init(&a1); + lv_anim_set_var(&a1, obj1); + lv_anim_set_values(&a1, 0, obj_width); + lv_anim_set_early_apply(&a1, false); + lv_anim_set_exec_cb(&a1, (lv_anim_exec_xcb_t)set_width); + lv_anim_set_path_cb(&a1, lv_anim_path_overshoot); + lv_anim_set_time(&a1, 300); + + lv_anim_t a2; + lv_anim_init(&a2); + lv_anim_set_var(&a2, obj1); + lv_anim_set_values(&a2, 0, obj_height); + lv_anim_set_early_apply(&a2, false); + lv_anim_set_exec_cb(&a2, (lv_anim_exec_xcb_t)set_height); + lv_anim_set_path_cb(&a2, lv_anim_path_ease_out); + lv_anim_set_time(&a2, 300); + + /* obj2 */ + lv_anim_t a3; + lv_anim_init(&a3); + lv_anim_set_var(&a3, obj2); + lv_anim_set_values(&a3, 0, obj_width); + lv_anim_set_early_apply(&a3, false); + lv_anim_set_exec_cb(&a3, (lv_anim_exec_xcb_t)set_width); + lv_anim_set_path_cb(&a3, lv_anim_path_overshoot); + lv_anim_set_time(&a3, 300); + + lv_anim_t a4; + lv_anim_init(&a4); + lv_anim_set_var(&a4, obj2); + lv_anim_set_values(&a4, 0, obj_height); + lv_anim_set_early_apply(&a4, false); + lv_anim_set_exec_cb(&a4, (lv_anim_exec_xcb_t)set_height); + lv_anim_set_path_cb(&a4, lv_anim_path_ease_out); + lv_anim_set_time(&a4, 300); + + /* obj3 */ + lv_anim_t a5; + lv_anim_init(&a5); + lv_anim_set_var(&a5, obj3); + lv_anim_set_values(&a5, 0, obj_width); + lv_anim_set_early_apply(&a5, false); + lv_anim_set_exec_cb(&a5, (lv_anim_exec_xcb_t)set_width); + lv_anim_set_path_cb(&a5, lv_anim_path_overshoot); + lv_anim_set_time(&a5, 300); + + lv_anim_t a6; + lv_anim_init(&a6); + lv_anim_set_var(&a6, obj3); + lv_anim_set_values(&a6, 0, obj_height); + lv_anim_set_early_apply(&a6, false); + lv_anim_set_exec_cb(&a6, (lv_anim_exec_xcb_t)set_height); + lv_anim_set_path_cb(&a6, lv_anim_path_ease_out); + lv_anim_set_time(&a6, 300); + + /* Create anim timeline */ + anim_timeline = lv_anim_timeline_create(); + lv_anim_timeline_add(anim_timeline, 0, &a1); + lv_anim_timeline_add(anim_timeline, 0, &a2); + lv_anim_timeline_add(anim_timeline, 200, &a3); + lv_anim_timeline_add(anim_timeline, 200, &a4); + lv_anim_timeline_add(anim_timeline, 400, &a5); + lv_anim_timeline_add(anim_timeline, 400, &a6); +} + +static void btn_run_event_handler(lv_event_t * e) +{ + lv_obj_t * btn = lv_event_get_target(e); + + if (!anim_timeline) { + anim_timeline_create(); + } + + bool reverse = lv_obj_has_state(btn, LV_STATE_CHECKED); + lv_anim_timeline_set_reverse(anim_timeline, reverse); + lv_anim_timeline_start(anim_timeline); +} + +static void btn_del_event_handler(lv_event_t * e) +{ + LV_UNUSED(e); + if (anim_timeline) { + lv_anim_timeline_del(anim_timeline); + anim_timeline = NULL; + } +} + +static void slider_prg_event_handler(lv_event_t * e) +{ + lv_obj_t * slider = lv_event_get_target(e); + + if (!anim_timeline) { + anim_timeline_create(); + } + + int32_t progress = lv_slider_get_value(slider); + lv_anim_timeline_set_progress(anim_timeline, progress); +} + +/** + * Create an animation timeline + */ +void lv_example_anim_timeline_1(void) +{ + lv_obj_t * par = lv_scr_act(); + lv_obj_set_flex_flow(par, LV_FLEX_FLOW_ROW); + lv_obj_set_flex_align(par, LV_FLEX_ALIGN_SPACE_AROUND, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER); + + lv_obj_t * btn_run = lv_btn_create(par); + lv_obj_add_event_cb(btn_run, btn_run_event_handler, LV_EVENT_VALUE_CHANGED, NULL); + lv_obj_add_flag(btn_run, LV_OBJ_FLAG_IGNORE_LAYOUT); + lv_obj_add_flag(btn_run, LV_OBJ_FLAG_CHECKABLE); + lv_obj_align(btn_run, LV_ALIGN_TOP_MID, -50, 20); + + lv_obj_t * label_run = lv_label_create(btn_run); + lv_label_set_text(label_run, "Run"); + lv_obj_center(label_run); + + lv_obj_t * btn_del = lv_btn_create(par); + lv_obj_add_event_cb(btn_del, btn_del_event_handler, LV_EVENT_CLICKED, NULL); + lv_obj_add_flag(btn_del, LV_OBJ_FLAG_IGNORE_LAYOUT); + lv_obj_align(btn_del, LV_ALIGN_TOP_MID, 50, 20); + + lv_obj_t * label_del = lv_label_create(btn_del); + lv_label_set_text(label_del, "Stop"); + lv_obj_center(label_del); + + lv_obj_t * slider_prg = lv_slider_create(par); + lv_obj_add_event_cb(slider_prg, slider_prg_event_handler, LV_EVENT_VALUE_CHANGED, NULL); + lv_obj_add_flag(slider_prg, LV_OBJ_FLAG_IGNORE_LAYOUT); + lv_obj_align(slider_prg, LV_ALIGN_BOTTOM_MID, 0, -20); + lv_slider_set_range(slider_prg, 0, 65535); + + obj1 = lv_obj_create(par); + lv_obj_set_size(obj1, obj_width, obj_height); + + obj2 = lv_obj_create(par); + lv_obj_set_size(obj2, obj_width, obj_height); + + obj3 = lv_obj_create(par); + lv_obj_set_size(obj3, obj_width, obj_height); +} + +#endif diff --git a/lvgl.h b/lvgl.h index 92f35438b8..d413febc0f 100644 --- a/lvgl.h +++ b/lvgl.h @@ -26,6 +26,7 @@ extern "C" { #include "src/misc/lv_timer.h" #include "src/misc/lv_math.h" #include "src/misc/lv_async.h" +#include "src/misc/lv_anim_timeline.h" #include "src/hal/lv_hal.h" diff --git a/src/lv_conf_internal.h b/src/lv_conf_internal.h index 6abd8bdc60..c24fad8d84 100644 --- a/src/lv_conf_internal.h +++ b/src/lv_conf_internal.h @@ -74,7 +74,7 @@ #endif /*Enable more complex drawing routines to manage screens transparency. - *Can be used if the UI is above an other layer, e.g. an OSD menu or video player. + *Can be used if the UI is above another layer, e.g. an OSD menu or video player. *Requires `LV_COLOR_DEPTH = 32` colors and the screen's `bg_opa` should be set to non LV_OPA_COVER value*/ #ifndef LV_COLOR_SCREEN_TRANSP # ifdef CONFIG_LV_COLOR_SCREEN_TRANSP diff --git a/src/misc/lv_anim_timeline.c b/src/misc/lv_anim_timeline.c new file mode 100644 index 0000000000..34969e8664 --- /dev/null +++ b/src/misc/lv_anim_timeline.c @@ -0,0 +1,168 @@ +/** + * @file lv_anim_timeline.c + * + */ + +/********************* + * INCLUDES + *********************/ +#include "lv_anim_timeline.h" +#include "lv_mem.h" +#include "../misc/lv_assert.h" + +/********************* + * DEFINES + *********************/ + +/********************** + * TYPEDEFS + **********************/ + +/*Data of anim_timeline_dsc*/ +typedef struct{ + lv_anim_t anim; + lv_anim_t * new_anim; + uint32_t start_time; +}lv_anim_timeline_dsc_t; + +/*Data of anim_timeline*/ +struct _lv_anim_timeline_t{ + lv_anim_timeline_dsc_t * anim_dsc; /**< Dynamically allocated anim dsc array*/ + uint32_t anim_dsc_cnt; /**< The length of anim dsc array*/ + bool reverse; /**< Reverse playback*/ +}; + +/********************** + * STATIC PROTOTYPES + **********************/ + +/********************** + * STATIC VARIABLES + **********************/ + +/********************** + * MACROS + **********************/ + +/********************** + * GLOBAL FUNCTIONS + **********************/ + +lv_anim_timeline_t * lv_anim_timeline_create(void) +{ + lv_anim_timeline_t * at = (lv_anim_timeline_t *)lv_mem_alloc(sizeof(lv_anim_timeline_t)); + + LV_ASSERT_MALLOC(at); + + if(at) lv_memset_00(at, sizeof(lv_anim_timeline_t)); + + return at; +} + +void lv_anim_timeline_del(lv_anim_timeline_t * at) +{ + LV_ASSERT_NULL(at); + + for(uint32_t i = 0; i < at->anim_dsc_cnt; i++) { + lv_anim_t * a = &(at->anim_dsc[i].anim); + lv_anim_custom_del(at->anim_dsc[i].new_anim, (lv_anim_custom_exec_cb_t)a->exec_cb); + } + + lv_mem_free(at->anim_dsc); + lv_mem_free(at); +} + +void lv_anim_timeline_add(lv_anim_timeline_t * at, uint32_t start_time, lv_anim_t * a) +{ + LV_ASSERT_NULL(at); + + at->anim_dsc_cnt++; + at->anim_dsc = lv_mem_realloc(at->anim_dsc, at->anim_dsc_cnt * sizeof(lv_anim_timeline_dsc_t)); + + LV_ASSERT_MALLOC(at->anim_dsc); + + at->anim_dsc[at->anim_dsc_cnt - 1].anim = *a; + at->anim_dsc[at->anim_dsc_cnt - 1].new_anim = NULL; + at->anim_dsc[at->anim_dsc_cnt - 1].start_time = start_time; +} + +uint32_t lv_anim_timeline_start(lv_anim_timeline_t * at) +{ + LV_ASSERT_NULL(at); + + const uint32_t playtime = lv_anim_timeline_get_playtime(at); + bool reverse = at->reverse; + + for(uint32_t i = 0; i < at->anim_dsc_cnt; i++) { + lv_anim_t a = at->anim_dsc[i].anim; + uint32_t start_time = at->anim_dsc[i].start_time; + + if(reverse) { + int32_t temp = a.start_value; + a.start_value = a.end_value; + a.end_value = temp; + lv_anim_set_delay(&a, playtime - (start_time + a.time)); + } + else { + lv_anim_set_delay(&a, start_time); + } + + at->anim_dsc[i].new_anim = lv_anim_start(&a); + } + + return playtime; +} + +void lv_anim_timeline_set_reverse(lv_anim_timeline_t * at, bool reverse) +{ + LV_ASSERT_NULL(at); + at->reverse = reverse; +} + +void lv_anim_timeline_set_progress(lv_anim_timeline_t * at, uint16_t progress) +{ + LV_ASSERT_NULL(at); + + const uint32_t playtime = lv_anim_timeline_get_playtime(at); + const uint32_t act_time = progress * playtime / 0xFFFF; + + for(uint32_t i = 0; i < at->anim_dsc_cnt; i++) { + lv_anim_t * a = &(at->anim_dsc[i].anim); + uint32_t start_time = at->anim_dsc[i].start_time; + int32_t value = 0; + + if(act_time < start_time) { + value = a->start_value; + } + else if(act_time < (start_time + a->time)) { + a->act_time = act_time - start_time; + value = a->path_cb(a); + } + else { + value = a->end_value; + } + + a->exec_cb(a->var, value); + } +} + +uint32_t lv_anim_timeline_get_playtime(lv_anim_timeline_t * at) +{ + LV_ASSERT_NULL(at); + + uint32_t playtime = 0; + for(uint32_t i = 0; i < at->anim_dsc_cnt; i++) { + uint32_t end = at->anim_dsc[i].start_time + at->anim_dsc[i].anim.time; + if(end > playtime) { + playtime = end; + } + } + + return playtime; +} + +bool lv_anim_timeline_get_reverse(lv_anim_timeline_t * at) +{ + LV_ASSERT_NULL(at); + return at->reverse; +} diff --git a/src/misc/lv_anim_timeline.h b/src/misc/lv_anim_timeline.h new file mode 100644 index 0000000000..ba3cdba52f --- /dev/null +++ b/src/misc/lv_anim_timeline.h @@ -0,0 +1,97 @@ +/** + * @file lv_anim_timeline.h + * + */ + +#ifndef LV_ANIM_TIMELINE_H +#define LV_ANIM_TIMELINE_H + +#ifdef __cplusplus +extern "C" { +#endif + +/********************* + * INCLUDES + *********************/ +#include "lv_anim.h" + +/********************* + * DEFINES + *********************/ + +/********************** + * TYPEDEFS + **********************/ + +struct _lv_anim_timeline_t; + +typedef struct _lv_anim_timeline_t lv_anim_timeline_t; + +/********************** +* GLOBAL PROTOTYPES +**********************/ + +/** + * Create a animation timeline. + * @return pointer to the animation timeline. + */ +lv_anim_timeline_t * lv_anim_timeline_create(void); + +/** + * Delete animation timeline. + * @param at pointer to the animation timeline. + */ +void lv_anim_timeline_del(lv_anim_timeline_t * at); + +/** + * Add animation to the animation timeline. + * @param at pointer to the animation timeline. + * @param start_time the time the animation started on the timeline, note that start_time will override the value of delay. + * @param a pointer to an animation. + */ +void lv_anim_timeline_add(lv_anim_timeline_t * at, uint32_t start_time, lv_anim_t * a); + +/** + * Start the animation timeline. + * @param at pointer to the animation timeline. + * @return total time spent in animation timeline. + */ +uint32_t lv_anim_timeline_start(lv_anim_timeline_t * at); + +/** + * Set the playback direction of the animation timeline. + * @param at pointer to the animation timeline. + * @param reverse whether to play in reverse. + */ +void lv_anim_timeline_set_reverse(lv_anim_timeline_t * at, bool reverse); + +/** + * Set the progress of the animation timeline. + * @param at pointer to the animation timeline. + * @param progress set value 0~65535 to map 0~100% animation progress. + */ +void lv_anim_timeline_set_progress(lv_anim_timeline_t * at, uint16_t progress); + +/** + * Get the time used to play the animation timeline. + * @param at pointer to the animation timeline. + * @return total time spent in animation timeline. + */ +uint32_t lv_anim_timeline_get_playtime(lv_anim_timeline_t * at); + +/** + * Get whether the animation timeline is played in reverse. + * @param at pointer to the animation timeline. + * @return return true if it is reverse playback. + */ +bool lv_anim_timeline_get_reverse(lv_anim_timeline_t * at); + +/********************** + * MACROS + **********************/ + +#ifdef __cplusplus +} /*extern "C"*/ +#endif + +#endif /*LV_ANIM_TIMELINE_H*/ diff --git a/src/misc/lv_misc.mk b/src/misc/lv_misc.mk index 0079a8b7b1..5e053f877c 100644 --- a/src/misc/lv_misc.mk +++ b/src/misc/lv_misc.mk @@ -1,4 +1,5 @@ CSRCS += lv_anim.c +CSRCS += lv_anim_timeline.c CSRCS += lv_area.c CSRCS += lv_async.c CSRCS += lv_bidi.c