feat(xml): support <include_timeline> (#8902)

This commit is contained in:
Gabor Kiss-Vamosi
2025-09-29 19:17:36 +02:00
committed by GitHub
parent e070798c12
commit 3e5ab289f9
10 changed files with 274 additions and 136 deletions
@@ -366,13 +366,6 @@ Call :cpp:expr:`lv_anim_timeline_delete(timeline)` function to delete the Animat
Timeline before deleting the Widget. Otherwise, the program may crash or behave
abnormally.
If a base object is set with :cpp:expr:`lv_anim_timeline_set_base_object(timeline, obj)`,
``var`` in the added animations is assumed to be a widget name (or path) string.
The actual widgets are retrieved by :cpp:expr:`lv_obj_get_child_by_name` before
calling the ``exec_cb`` of the animation. That is, the ``exec_cb`` gets a pointer to
the widget, and not the name/path.
.. image:: /_static/images/anim-timeline.png
.. _animations_example:
+38 -18
View File
@@ -5,7 +5,7 @@ Animations
==========
Overview
--------
********
XML animations are built on top of :ref:`Timeline animations <animations_timeline>`.
@@ -16,7 +16,7 @@ Each component can define its own timeline animations, which can then be played
component itself or by any parent component.
Defining Timelines
------------------
******************
Timelines can be defined inside ``<screen>``\ s and ``<component>``\ s.
For ``<widget>``\ s, timelines are supported only in LVGL's UI Editor,
@@ -32,8 +32,9 @@ Example:
<!-- Show the component and its children -->
<timeline name="load">
<animation prop="translate_x" target="self" start="-30" end="0" duration="500"/>
<animation prop="opa" target="icon" start="0" end="255" duration="500"/>
<animation prop="opa" target="text" start="0" end="255" duration="500" delay="200"/>
<include_timeline target="icon" timeline="show_up" delay="300"/>
</timeline>
<!-- Shake horizontally -->
@@ -52,7 +53,7 @@ Example:
</view>
</component>
In summary: inside ``<animations>``, you can define ``<timeline>``\s, each with a unique name
In summary: inside ``<animations>``, you can define ``<timeline>``\ s, each with a unique name
that you can reference later.
Inside a ``<timeline>``, you add ``<animation>``\ s to describe each step.
@@ -64,11 +65,22 @@ Supported properties of ``<animation>`` are:
- ``start``: Start value (integer only).
- ``end``: End value (integer only).
- ``duration``: Duration of the animation in milliseconds.
- ``delay``: Delay before starting in milliseconds.
- ``early_apply``: If ``true``, the start value is applied immediately, even during the delay.
- ``delay``: Delay before starting in milliseconds. Default is 0.
- ``early_apply``: If ``true``, the start value is applied immediately, even during the delay. Default is ``false``.
``<include_timeline>`` also can be used in ``<timeline>``\ s to "merge" the animations
of another timeline. Imagine that in the example above ``my_icon`` defines a ``"show_up"`` timeline
which fades in and scales up the icon. All these are described in the ``my_icon.xml`` in an
encapsulated way but can be referenced in other timelines.
To include a timeline, the following properties shall be used:
- ``target``: name of the target UI element whose timeline should be included. ``self`` refers to the root element of the component (the ``<view>``).
- ``timeline``: name of the timeline to include. Shall be defined in the ``target``'s XML file.
- ``delay``: Delay before starting in milliseconds. Default is 0.
Playing Timelines
-----------------
*****************
Timelines can be triggered by events (e.g. click) using ``<play_timeline_event>``
as a child of any widget.
@@ -90,25 +102,33 @@ If ``target="self"``, the timeline is looked up in the current component/widget/
(i.e. in the current XML file).
You can also set a ``delay`` and ``reverse="true"`` when playing a timeline.
Under the Hood
--------------
**************
Understanding how timelines work internally helps in using them effectively.
When an XML file is registered, the contents of the ``<animations>`` section are parsed,
and the animation data is stored as a blueprint.
and the ``<timeline>``'s data is stored as a "blueprint". The descriptors store the targets'
names as strings.
When an instance of a component or screen is created, ``lv_anim_timeline``\s are
created and initialized from the saved blueprint. Each instance gets its own copy of the timelines.
When an instance of a component or screen is created, as the last step ``lv_anim_timeline``\ s are
created and initialized from the saved "blueprints". If ``<include_timeline>``\ s are also used,
the requested timeline is included in the component's timeline at this point too.
As all the children are also created at this point, the saved animation target names are resolved
to pointers by using :cpp:expr:`lv_obj_find_by_name`.
When a ``<play_timeline_event>`` is added to a UI element, the target and timeline
names are saved as strings. (It can't use pointers as the event can reference UI elements
that will be created only later in the ``<view>``.)
Finally, when the play timeline event is triggered, LVGL finds the target widget by the saved name,
retrieves the specified timeline, and starts it.
The created timeline instances and their names are saved in the component's instance.
Since each instance has its own timeline, you can have multiple components (e.g. 10 ``<list_item>``\s)
Since each instance has its own timeline, you can have multiple components (e.g. 10 ``<list_item>``\ s)
and play their ``load`` timelines independently with different delays.
When a ``<play_timeline_event>`` is added to a UI element, the target and timeline
names are saved as strings. It cannot use pointers as the event can reference UI elements
that will be created only later in the ``<view>``.
Finally, when the play timeline event is triggered, the selected timeline is retrieved by its name from the target
and started according to the other parameters (reverse, delay, etc).
+13 -48
View File
@@ -29,7 +29,7 @@
static void anim_timeline_exec_cb(void * var, int32_t v);
static void anim_timeline_set_act_time(lv_anim_timeline_t * at, uint32_t act_time);
static int32_t anim_timeline_path_cb(const lv_anim_t * a);
static void exec_anim(lv_anim_timeline_t * at, lv_anim_t * a, int32_t v);
static void exec_anim(lv_anim_t * a, int32_t v);
/**********************
* STATIC VARIABLES
@@ -156,16 +156,6 @@ void lv_anim_timeline_set_user_data(lv_anim_timeline_t * at, void * user_data)
at->user_data = user_data;
}
#if LV_USE_OBJ_NAME
void lv_anim_timeline_set_base_obj(lv_anim_timeline_t * at, lv_obj_t * base_obj)
{
LV_ASSERT_NULL(at);
at->base_obj = base_obj;
}
#endif
uint32_t lv_anim_timeline_get_playtime(lv_anim_timeline_t * at)
{
LV_ASSERT_NULL(at);
@@ -222,16 +212,16 @@ void * lv_anim_timeline_get_user_data(lv_anim_timeline_t * at)
return at->user_data;
}
#if LV_USE_OBJ_NAME
lv_obj_t * lv_anim_timeline_get_base_obj(lv_anim_timeline_t * at)
void lv_anim_timeline_merge(lv_anim_timeline_t * dest, const lv_anim_timeline_t * src, int32_t delay)
{
LV_ASSERT_NULL(at);
return at->base_obj;
uint32_t i;
for(i = 0; i < src->anim_dsc_cnt; i++) {
uint32_t anim_delay = src->anim_dsc[i].start_time + delay;
lv_anim_timeline_add(dest, anim_delay, &src->anim_dsc[i].anim);
}
}
#endif
/**********************
* STATIC FUNCTIONS
**********************/
@@ -259,7 +249,7 @@ static void anim_timeline_set_act_time(lv_anim_timeline_t * at, uint32_t act_tim
}
value = a->start_value;
exec_anim(at, a, value);
exec_anim(a, value);
if(anim_timeline_is_started) {
if(at->reverse) {
@@ -279,7 +269,7 @@ static void anim_timeline_set_act_time(lv_anim_timeline_t * at, uint32_t act_tim
a->act_time = act_time - start_time;
value = a->path_cb(a);
exec_anim(at, a, value);
exec_anim(a, value);
if(anim_timeline_is_started) {
if(at->reverse) {
@@ -314,7 +304,7 @@ static void anim_timeline_set_act_time(lv_anim_timeline_t * at, uint32_t act_tim
}
value = a->end_value;
exec_anim(at, a, value);
exec_anim(a, value);
if(anim_timeline_is_started) {
if(at->reverse) {
@@ -341,38 +331,13 @@ static void anim_timeline_exec_cb(void * var, int32_t v)
anim_timeline_set_act_time(at, v);
}
static void exec_anim(lv_anim_timeline_t * at, lv_anim_t * a, int32_t v)
static void exec_anim(lv_anim_t * a, int32_t v)
{
/*a->var stores children names if at->base_obj is set. */
#if LV_USE_OBJ_NAME
lv_obj_t * obj_resolved;
if(at->base_obj) {
if(lv_streq(a->var, "self")) obj_resolved = at->base_obj;
else if(lv_streq(a->var, "")) obj_resolved = at->base_obj;
else obj_resolved = lv_obj_find_by_name(at->base_obj, a->var);
if(obj_resolved == NULL) {
LV_LOG_WARN("Widget was not found with name `%s` as child of %p", (const char *)a->var, (void *)at->base_obj);
return;
}
}
else {
obj_resolved = a->var;
}
#else
LV_UNUSED(at);
lv_obj_t * obj_resolved = a->var;
#endif
if(a->exec_cb) {
a->exec_cb(obj_resolved, v);
a->exec_cb(a->var, v);
}
if(a->custom_exec_cb) {
/*Temporarily replace the var with the resolved object*/
void * var_ori = a->var;
a->var = obj_resolved;
a->custom_exec_cb(a, v);
a->var = var_ori;
}
}
+5 -21
View File
@@ -114,17 +114,6 @@ void lv_anim_timeline_set_progress(lv_anim_timeline_t * at, uint16_t progress);
*/
void lv_anim_timeline_set_user_data(lv_anim_timeline_t * at, void * user_data);
#if LV_USE_OBJ_NAME
/**
* Set base object.
* If set, it's assumed that the `var` of animations is a widget name (path).
* The widget pointer will be retrieved by finding them by name on this widget.
* @param at pointer to the animation timeline.
* @param base_obj pointer to a widget
*/
void lv_anim_timeline_set_base_obj(lv_anim_timeline_t * at, lv_obj_t * base_obj);
#endif
/**
* Get the time used to play the animation timeline.
* @param at pointer to the animation timeline.
@@ -171,18 +160,13 @@ uint32_t lv_anim_timeline_get_repeat_delay(lv_anim_timeline_t * at);
*/
void * lv_anim_timeline_get_user_data(lv_anim_timeline_t * at);
#if LV_USE_OBJ_NAME
/**
* Get base object.
* If set, it's assumed that the `var` of animations is a widget name (path).
* The widget pointer will be retrieved by finding them by name on this widget.
* @param at pointer to the animation timeline.
* @return pointer to the base widget
* Merge (add) all animations of a timeline to another
* @param dest merge animation into this timeline
* @param src merge the animations of this timeline
* @param delay add the animations with this extra delay
*/
lv_obj_t * lv_anim_timeline_get_base_obj(lv_anim_timeline_t * at);
#endif
void lv_anim_timeline_merge(lv_anim_timeline_t * dest, const lv_anim_timeline_t * src, int32_t delay);
/**********************
* MACROS
-4
View File
@@ -48,10 +48,6 @@ struct _lv_anim_timeline_t {
/** Wait before repeat*/
uint32_t repeat_delay;
/** If set, it's assumed that the `var` of animations is a widget name (path).
* The widget pointer will be retrieved by finding them by name on this widget.*/
lv_obj_t * base_obj;
/** For any custom data*/
void * user_data;
};
+121 -29
View File
@@ -71,6 +71,7 @@
**********************/
static void view_start_element_handler(void * user_data, const char * name, const char ** attrs);
static void view_end_element_handler(void * user_data, const char * name);
static void create_timeline_instances(lv_xml_parser_state_t * state);
static void get_timeline_from_event_cb(lv_event_t * e);
static void free_timelines_event_cb(lv_event_t * e);
@@ -298,34 +299,7 @@ void * lv_xml_create_in_scope(lv_obj_t * parent, lv_xml_component_scope_t * pare
}
#endif
/*Create the timelines as well*/
if(!lv_ll_is_empty(&scope->timeline_ll)) {
lv_xml_timeline_t * at_xml;
lv_anim_timeline_t ** timeline_array;
timeline_array = lv_malloc((lv_ll_get_len(&scope->timeline_ll) + 1) * sizeof(lv_anim_timeline_t *));
uint32_t i = 0;
LV_LL_READ(&scope->timeline_ll, at_xml) {
lv_anim_timeline_t * at = lv_anim_timeline_create();
at->user_data = lv_strdup(at_xml->name);
lv_anim_t * a_stored;
LV_LL_READ(&at_xml->anims_ll, a_stored) {
int32_t delay = -a_stored->act_time;
lv_anim_timeline_add(at, delay, a_stored);
}
at->base_obj = state.view;
timeline_array[i] = at;
i++;
}
timeline_array[i] = NULL; /*Closing to avoid storing the length*/
lv_obj_add_event_cb(state.view, get_timeline_from_event_cb, lv_event_xml_store_timeline, timeline_array);
lv_obj_add_event_cb(state.view, free_timelines_event_cb, LV_EVENT_DELETE, timeline_array);
}
create_timeline_instances(&state);
lv_ll_clear(&state.parent_ll);
XML_ParserFree(parser);
@@ -525,7 +499,7 @@ lv_result_t lv_xml_register_timeline(lv_xml_component_scope_t * scope, const cha
at = lv_ll_ins_head(&scope->timeline_ll);
at->name = lv_strdup(name);
lv_ll_init(&at->anims_ll, sizeof(lv_anim_t));
lv_ll_init(&at->anims_ll, sizeof(lv_xml_anim_timeline_child_t));
return LV_RESULT_OK;
}
@@ -893,6 +867,124 @@ static void view_end_element_handler(void * user_data, const char * name)
}
}
static lv_anim_timeline_t * get_timeline_by_name(lv_obj_t * obj, const char * timeline_name)
{
/*Get all the timelines of the target*/
lv_anim_timeline_t ** timeline_array = NULL;
lv_obj_send_event(obj, lv_event_xml_store_timeline, &timeline_array);
if(timeline_array == NULL) {
LV_LOG_WARN("No time lines are stored in target");
return NULL;
}
/*Find the timeline with the requested timeline name*/
uint32_t i;
for(i = 0; timeline_array[i]; i++) {
const char * name = lv_anim_timeline_get_user_data(timeline_array[i]);
if(lv_streq(name, timeline_name)) return timeline_array[i];
}
return NULL;
}
static void create_timeline_instances(lv_xml_parser_state_t * state)
{
/*The timeline descriptors ("blueprints") created when the components was registered
*are stored in the "scope".
*Based on the descriptors timeline and animation instances will be created for this this component*/
lv_xml_component_scope_t * scope = &state->scope;
if(lv_ll_is_empty(&scope->timeline_ll)) return;
/*At this stage all children are created so any UI elements that
*the animations and timelines can reference are exist. */
lv_xml_timeline_t * timeline_dsc;
/*Create an array to store the created timeline pointers*/
lv_anim_timeline_t ** timeline_array;
timeline_array = lv_malloc((lv_ll_get_len(&scope->timeline_ll) + 1) * sizeof(lv_anim_timeline_t *));
LV_ASSERT_MALLOC(timeline_array);
if(timeline_array == NULL) {
LV_LOG_WARN("Couldn't allocate memory");
return;
}
/*Read the timeline descriptors of the component and create
*timeline instances based on them.*/
uint32_t timeline_index = 0;
LV_LL_READ(&scope->timeline_ll, timeline_dsc) {
/*Save the name of the timeline. It will reference by this name in XML
* (e.g. <play_animation_event target="comp_name" timeline="timeline_name">)*/
lv_anim_timeline_t * my_timeline = lv_anim_timeline_create();
my_timeline->user_data = lv_strdup(timeline_dsc->name);
LV_ASSERT_MALLOC(my_timeline->user_data);
if(my_timeline->user_data == NULL) {
lv_anim_timeline_delete(my_timeline);
lv_free(timeline_array);
LV_LOG_WARN("Couldn't allocate memory");
return;
}
/*Check all saved animation or incluce_timeline data of the component
*and add them to the timeline instance. */
lv_xml_anim_timeline_child_t * timeline_child;
LV_LL_READ(&timeline_dsc->anims_ll, timeline_child) {
/*Simple add the animation descriptors to instance's timeline*/
if(timeline_child->is_anim) {
lv_anim_t * a = &timeline_child->data.anim;
lv_obj_t * target = NULL;
if(lv_streq(a->var, "self")) target = state->view;
else target = lv_obj_find_by_name(state->view, a->var);
if(target == NULL) {
LV_LOG_WARN("No target widget is found with `%s` name", (char *)a->var);
continue;
}
int32_t delay = -a->act_time;
lv_anim_timeline_add(my_timeline, delay, a);
/*Once the animation descriptor is duplicated and saved in the timeline
*replace the target name a pointer to the target.
*TODO add an event to every referenced widget to remove their anim from the
* timeline when they are deleted.*/
lv_anim_t * new_a = &my_timeline->anim_dsc[my_timeline->anim_dsc_cnt - 1].anim;
new_a->var = target;
}
/*Or include (merge) the referenced timelines*/
else {
lv_xml_anim_timeline_include_t * incl = &timeline_child->data.incl;
/*Get the target first*/
lv_obj_t * target;
if(lv_streq(incl->target_name, "self")) target = state->view;
else target = lv_obj_find_by_name(state->view, incl->target_name);
if(target == NULL) {
LV_LOG_WARN("No target widget is found with `%s` name", incl->target_name);
continue;
}
lv_anim_timeline_t * include_timeline = get_timeline_by_name(target, incl->timeline_name);
if(include_timeline == NULL) {
LV_LOG_WARN("Timeline `%s` is not found in `%s` component", incl->timeline_name, incl->target_name);
continue;
}
/*Copy all animations of include_timeline to this instance's timeline*/
lv_anim_timeline_merge(my_timeline, include_timeline, incl->delay);
}
}
timeline_array[timeline_index] = my_timeline;
timeline_index++;
}
timeline_array[timeline_index] = NULL; /*Closing to avoid storing the length*/
lv_obj_add_event_cb(state->view, get_timeline_from_event_cb, lv_event_xml_store_timeline, timeline_array);
lv_obj_add_event_cb(state->view, free_timelines_event_cb, LV_EVENT_DELETE, timeline_array);
}
static void get_timeline_from_event_cb(lv_event_t * e)
{
void ** out = lv_event_get_param(e);
+76 -9
View File
@@ -315,9 +315,15 @@ lv_result_t lv_xml_component_unregister(const char * name)
lv_xml_timeline_t * timeline;
LV_LL_READ(&scope->timeline_ll, timeline) {
lv_anim_t * a;
LV_LL_READ(&timeline->anims_ll, a) {
lv_free(a->var); /*It was the name of the target object*/
lv_xml_anim_timeline_child_t * child;
LV_LL_READ(&timeline->anims_ll, child) {
if(child->is_anim) {
lv_free(child->data.anim.var); /*It was the name of the target object*/
}
else {
lv_free((void *) child->data.incl.target_name);
lv_free((void *) child->data.incl.timeline_name);
}
}
lv_ll_clear(&timeline->anims_ll);
lv_free((char *)timeline->name);
@@ -579,11 +585,6 @@ static void process_animation_element(lv_xml_parser_state_t * state, const char
int32_t start = anim_value_to_int(prop_type, start_str);
int32_t end = anim_value_to_int(prop_type, end_str);
lv_xml_timeline_t * at = state->context;
if(at == NULL) {
LV_LOG_WARN("There was no parent timeline for the animation");
return;
}
if(target_str[0] == '#') target_str = lv_xml_get_const(&state->scope, &target_str[1]);
if(prop_str[0] == '#') prop_str = lv_xml_get_const(&state->scope, &prop_str[1]);
@@ -593,6 +594,12 @@ static void process_animation_element(lv_xml_parser_state_t * state, const char
if(delay_str[0] == '#') delay_str = lv_xml_get_const(&state->scope, &delay_str[1]);
if(early_apply_str[0] == '#') early_apply_str = lv_xml_get_const(&state->scope, &early_apply_str[1]);
lv_xml_timeline_t * at = state->context;
if(at == NULL) {
LV_LOG_WARN("There was no parent timeline for the animation");
return;
}
if(!target_str || !prop_str || !start_str || !end_str || !duration_str || !delay_str || !early_apply_str) {
LV_LOG_WARN("Couldn't resolve one or more constants. Skipping the animation.");
return;
@@ -600,7 +607,9 @@ static void process_animation_element(lv_xml_parser_state_t * state, const char
uint32_t selector_and_prop = ((prop & 0xff) << 24) | selector;
lv_anim_t * a = lv_ll_ins_tail(&at->anims_ll);
lv_xml_anim_timeline_child_t * child = lv_ll_ins_tail(&at->anims_ll);
child->is_anim = true;
lv_anim_t * a = &child->data.anim;
lv_anim_init(a);
lv_anim_set_var(a, lv_strdup(target_str));
@@ -612,6 +621,61 @@ static void process_animation_element(lv_xml_parser_state_t * state, const char
lv_anim_set_user_data(a, (void *)((uintptr_t)selector_and_prop));
}
static void process_include_timeline_element(lv_xml_parser_state_t * state, const char ** attrs)
{
lv_xml_timeline_t * at = state->context;
if(at == NULL) {
LV_LOG_INFO("No parent timeline is set, skipping");
return;
}
const char * target_str = lv_xml_get_value_of(attrs, "target");
const char * timeline_str = lv_xml_get_value_of(attrs, "timeline");
const char * delay_str = lv_xml_get_value_of(attrs, "delay");
if(target_str == NULL) {
LV_LOG_WARN("'target' is missing from timeline include");
return;
}
if(timeline_str == NULL) {
LV_LOG_WARN("'timeline' is missing from timeline include");
return;
}
if(delay_str == NULL) delay_str = "0";
if(target_str[0] == '#') target_str = lv_xml_get_const(&state->scope, &target_str[1]);
if(timeline_str[0] == '#') timeline_str = lv_xml_get_const(&state->scope, &timeline_str[1]);
if(delay_str[0] == '#') delay_str = lv_xml_get_const(&state->scope, &delay_str[1]);
if(!target_str || !timeline_str || !delay_str) {
LV_LOG_WARN("Couldn't resolve one or more constants. Skipping the timeline include.");
return;
}
lv_xml_anim_timeline_child_t * child = lv_ll_ins_tail(&at->anims_ll);
LV_ASSERT_MALLOC(child);
if(child == NULL) {
LV_LOG_WARN("Couldn't allocate memory");
return;
}
child->is_anim = false;
child->data.incl.delay = lv_xml_atoi(delay_str);
child->data.incl.target_name = lv_strdup(target_str);
LV_ASSERT_MALLOC(child->data.incl.target_name);
child->data.incl.timeline_name = lv_strdup(timeline_str);
LV_ASSERT_MALLOC(child->data.incl.timeline_name);
if(child->data.incl.target_name == NULL || child->data.incl.timeline_name == NULL) {
LV_LOG_WARN("Couldn't allocate memory");
lv_free((void *)child->data.incl.target_name);
lv_free((void *)child->data.incl.timeline_name);
lv_ll_remove(&at->anims_ll, child);
}
}
static void process_grad_element(lv_xml_parser_state_t * state, const char * tag_name, const char ** attrs)
{
lv_xml_grad_t * grad = lv_ll_ins_tail(&state->scope.gradient_ll);
@@ -846,6 +910,9 @@ static void start_metadata_handler(void * user_data, const char * name, const ch
case LV_XML_PARSER_SECTION_ANIMATION:
process_animation_element(state, attrs);
break;
case LV_XML_PARSER_SECTION_INCLUDE_TIMELINE:
process_include_timeline_element(state, attrs);
break;
default:
break;
}
+4
View File
@@ -85,6 +85,10 @@ void lv_xml_parser_start_section(lv_xml_parser_state_t * state, const char * nam
state->section = LV_XML_PARSER_SECTION_ANIMATION;
return;
}
else if(lv_streq(name, "include_timeline")) {
state->section = LV_XML_PARSER_SECTION_INCLUDE_TIMELINE;
return;
}
else if(lv_streq(name, "timeline")) {
state->section = LV_XML_PARSER_SECTION_TIMELINE;
return;
+1
View File
@@ -39,6 +39,7 @@ typedef enum {
LV_XML_PARSER_SECTION_IMAGES,
LV_XML_PARSER_SECTION_SUBJECTS,
LV_XML_PARSER_SECTION_ANIMATION,
LV_XML_PARSER_SECTION_INCLUDE_TIMELINE,
LV_XML_PARSER_SECTION_TIMELINE,
LV_XML_PARSER_SECTION_VIEW
} lv_xml_parser_section_t;
+16
View File
@@ -46,6 +46,22 @@ typedef struct {
lv_event_cb_t cb;
} lv_xml_event_cb_t;
/**
* Store the data of <include_timeline>
*/
typedef struct {
const char * target_name; /**< Include the timeline of this widget*/
const char * timeline_name; /**< Include this timeline */
int32_t delay;
} lv_xml_anim_timeline_include_t;
typedef struct {
bool is_anim;
union {
lv_anim_t anim;
lv_xml_anim_timeline_include_t incl;
} data;
} lv_xml_anim_timeline_child_t;
/**********************
* GLOBAL PROTOTYPES