feat(events): add event trickle mechanism to propagate events to children (#8415)
Some checks failed
Arduino Lint / lint (push) Has been cancelled
Build Examples with C++ Compiler / build-examples (push) Has been cancelled
MicroPython CI / Build esp32 port (push) Has been cancelled
MicroPython CI / Build rp2 port (push) Has been cancelled
MicroPython CI / Build stm32 port (push) Has been cancelled
MicroPython CI / Build unix port (push) Has been cancelled
C/C++ CI / Build OPTIONS_16BIT - Ubuntu (push) Has been cancelled
C/C++ CI / Build OPTIONS_24BIT - Ubuntu (push) Has been cancelled
C/C++ CI / Build OPTIONS_FULL_32BIT - Ubuntu (push) Has been cancelled
C/C++ CI / Build OPTIONS_NORMAL_8BIT - Ubuntu (push) Has been cancelled
C/C++ CI / Build OPTIONS_SDL - Ubuntu (push) Has been cancelled
C/C++ CI / Build OPTIONS_VG_LITE - Ubuntu (push) Has been cancelled
C/C++ CI / Build OPTIONS_16BIT - cl - Windows (push) Has been cancelled
C/C++ CI / Build OPTIONS_16BIT - gcc - Windows (push) Has been cancelled
C/C++ CI / Build OPTIONS_24BIT - cl - Windows (push) Has been cancelled
C/C++ CI / Build OPTIONS_24BIT - gcc - Windows (push) Has been cancelled
C/C++ CI / Build OPTIONS_FULL_32BIT - cl - Windows (push) Has been cancelled
C/C++ CI / Build OPTIONS_FULL_32BIT - gcc - Windows (push) Has been cancelled
C/C++ CI / Build OPTIONS_VG_LITE - cl - Windows (push) Has been cancelled
C/C++ CI / Build OPTIONS_VG_LITE - gcc - Windows (push) Has been cancelled
C/C++ CI / Build ESP IDF ESP32S3 (push) Has been cancelled
C/C++ CI / Run tests with 32bit build (push) Has been cancelled
C/C++ CI / Run tests with 64bit build (push) Has been cancelled
BOM Check / bom-check (push) Has been cancelled
Verify that lv_conf_internal.h matches repository state / verify-conf-internal (push) Has been cancelled
Verify the widget property name / verify-property-name (push) Has been cancelled
Verify code formatting / verify-formatting (push) Has been cancelled
Compare file templates with file names / template-check (push) Has been cancelled
Build docs / build-and-deploy (push) Has been cancelled
Test API JSON generator / Test API JSON (push) Has been cancelled
Check Makefile / Build using Makefile (push) Has been cancelled
Check Makefile for UEFI / Build using Makefile for UEFI (push) Has been cancelled
Performance Tests CI / Perf Tests OPTIONS_TEST_PERF_32B - Ubuntu (push) Has been cancelled
Performance Tests CI / Perf Tests OPTIONS_TEST_PERF_64B - Ubuntu (push) Has been cancelled
Port repo release update / run-release-branch-updater (push) Has been cancelled
Verify Font License / verify-font-license (push) Has been cancelled
Verify Kconfig / verify-kconfig (push) Has been cancelled

Co-authored-by: Gauthier Provost <gpr@interel.com>
This commit is contained in:
Gauthier Provost
2025-07-03 19:20:41 +08:00
committed by GitHub
parent dec36ddb69
commit 6ec45fa613
15 changed files with 234 additions and 2 deletions

View File

@@ -467,6 +467,7 @@ There are some Widget attributes which can be enabled/disabled by
- :cpp:enumerator:`LV_OBJ_FLAG_SNAPPABLE` If scroll snap is enabled on the parent it can snap to this Widget
- :cpp:enumerator:`LV_OBJ_FLAG_PRESS_LOCK` Keep the Widget pressed even if the press slid from the Widget
- :cpp:enumerator:`LV_OBJ_FLAG_EVENT_BUBBLE` Propagate the events to the parent as well
- :cpp:enumerator:`LV_OBJ_FLAG_EVENT_TRICKLE` Propagate the events to the children as well
- :cpp:enumerator:`LV_OBJ_FLAG_GESTURE_BUBBLE` Propagate the gestures to the parent
- :cpp:enumerator:`LV_OBJ_FLAG_ADV_HITTEST` Allow performing more accurate hit (click) test. E.g. accounting for rounded corners
- :cpp:enumerator:`LV_OBJ_FLAG_IGNORE_LAYOUT` Make the Widget not positioned by the layouts

View File

@@ -265,6 +265,24 @@ The *target* parameter of the event is always the current target Widget,
not the original Widget. To get the original target call
:cpp:expr:`lv_event_get_target_obj(e)` in the event handler.
.. _event_trickle:
Event Trickle
*************
Also known as Event Capturing, if :cpp:expr:`lv_obj_add_flag(widget, LV_OBJ_FLAG_EVENT_TRICKLE)`
is enabled all events will be sent to the Widget's children as well. This is the opposite of
event bubbling --- instead of propagating up the parent, events propagate down to the children.
The trickle mechanism only affects immediate children, not grandchildren or
deeper descendants. If you need events to propagate to deeper levels, each child
would need to have the :cpp:enumerator:`LV_OBJ_FLAG_EVENT_TRICKLE` flag enabled.
Like with bubbling, the *target* parameter of the event is always the current target Widget,
not the original Widget. To get the original target call
:cpp:expr:`lv_event_get_target_obj(e)` in the event handler.
.. _events_examples:

View File

@@ -22,6 +22,11 @@ Event bubbling
.. lv_example:: event/lv_example_event_bubble
:language: c
Event trickle-down
------------------
.. lv_example:: event/lv_example_event_trickle
:language: c
Draw event
----------
.. lv_example:: event/lv_example_event_draw

View File

@@ -29,6 +29,7 @@ void lv_example_event_click(void);
void lv_example_event_streak(void);
void lv_example_event_button(void);
void lv_example_event_bubble(void);
void lv_example_event_trickle(void);
void lv_example_event_draw(void);
/**********************

View File

@@ -0,0 +1,37 @@
#include "../lv_examples.h"
#if LV_BUILD_EXAMPLES && LV_USE_FLEX
/**
* Demonstrate event trickle
*/
void lv_example_event_trickle(void)
{
lv_obj_t * cont = lv_obj_create(lv_screen_active());
lv_obj_set_size(cont, 290, 200);
lv_obj_center(cont);
lv_obj_set_flex_flow(cont, LV_FLEX_FLOW_ROW_WRAP);
static lv_style_t style_black;
lv_style_init(&style_black);
lv_style_set_text_color(&style_black, lv_color_white());
lv_style_set_bg_color(&style_black, lv_color_black());
/*Enable event trickle-down on the container*/
lv_obj_add_flag(cont, LV_OBJ_FLAG_EVENT_TRICKLE);
lv_obj_add_style(cont, &style_black, LV_STATE_PRESSED);
uint32_t i;
for(i = 0; i < 9; i++) {
lv_obj_t * subcont = lv_obj_create(cont);
lv_obj_set_size(subcont, 70, 50);
lv_obj_t * label = lv_label_create(subcont);
lv_label_set_text_fmt(label, "%" LV_PRIu32, i);
/*Add style to the label when clicked*/
lv_obj_add_style(subcont, &style_black, LV_STATE_FOCUSED);
}
}
#endif

View File

@@ -115,6 +115,7 @@ typedef enum {
#if LV_USE_FLEX
LV_OBJ_FLAG_FLEX_IN_NEW_TRACK = (1L << 21), /**< Start a new flex track on this item*/
#endif
LV_OBJ_FLAG_EVENT_TRICKLE = (1L << 22), /**< Propagate the events to the children too*/
LV_OBJ_FLAG_LAYOUT_1 = (1L << 23), /**< Custom flag, free to use by layouts*/
LV_OBJ_FLAG_LAYOUT_2 = (1L << 24), /**< Custom flag, free to use by layouts*/
@@ -153,6 +154,7 @@ enum _lv_signed_prop_id_t {
LV_PROPERTY_ID(OBJ, FLAG_SEND_DRAW_TASK_EVENTS, LV_PROPERTY_TYPE_INT, 19),
LV_PROPERTY_ID(OBJ, FLAG_OVERFLOW_VISIBLE, LV_PROPERTY_TYPE_INT, 20),
LV_PROPERTY_ID(OBJ, FLAG_FLEX_IN_NEW_TRACK, LV_PROPERTY_TYPE_INT, 21),
LV_PROPERTY_ID(OBJ, FLAG_EVENT_TRICKLE, LV_PROPERTY_TYPE_INT, 22),
LV_PROPERTY_ID(OBJ, FLAG_LAYOUT_1, LV_PROPERTY_TYPE_INT, 23),
LV_PROPERTY_ID(OBJ, FLAG_LAYOUT_2, LV_PROPERTY_TYPE_INT, 24),
LV_PROPERTY_ID(OBJ, FLAG_WIDGET_1, LV_PROPERTY_TYPE_INT, 25),

View File

@@ -27,6 +27,7 @@
**********************/
static lv_result_t event_send_core(lv_event_t * e);
static bool event_is_bubbled(lv_event_t * e);
static bool event_is_trickled(lv_event_t * e);
/**********************
* STATIC VARIABLES
@@ -60,6 +61,7 @@ lv_result_t lv_obj_send_event(lv_obj_t * obj, lv_event_code_t event_code, void *
e.deleted = 0;
e.stop_bubbling = 0;
e.stop_processing = 0;
e.stop_trickling = 0;
lv_event_push(&e);
@@ -378,6 +380,26 @@ static lv_result_t event_send_core(lv_event_t * e)
res = event_send_core(e);
}
/*Trickle down to children if enabled*/
if(event_is_trickled(e)) {
uint32_t child_count = lv_obj_get_child_count(target);
/* we don't want the event to bubble up again when trickling down */
e->stop_bubbling = 1;
for(uint32_t i = 0; i < child_count && res == LV_RESULT_OK && !e->stop_processing; i++) {
lv_obj_t * child = lv_obj_get_child(target, i);
if(child) {
e->current_target = child;
res = event_send_core(e);
if(res != LV_RESULT_OK) {
LV_LOG_WARN("Trickle down event %d to child %p failed", e->code, (void *)child);
break;
}
}
}
}
return res;
}
@@ -421,3 +443,35 @@ static bool event_is_bubbled(lv_event_t * e)
return true;
}
}
static bool event_is_trickled(lv_event_t * e)
{
if(e->stop_trickling) return false;
/*Check other codes only if trickle is enabled*/
if(lv_obj_has_flag(e->current_target, LV_OBJ_FLAG_EVENT_TRICKLE) == false) return false;
switch(e->code) {
case LV_EVENT_HIT_TEST:
case LV_EVENT_COVER_CHECK:
case LV_EVENT_REFR_EXT_DRAW_SIZE:
case LV_EVENT_DRAW_MAIN_BEGIN:
case LV_EVENT_DRAW_MAIN:
case LV_EVENT_DRAW_MAIN_END:
case LV_EVENT_DRAW_POST_BEGIN:
case LV_EVENT_DRAW_POST:
case LV_EVENT_DRAW_POST_END:
case LV_EVENT_DRAW_TASK_ADDED:
case LV_EVENT_REFRESH:
case LV_EVENT_DELETE:
case LV_EVENT_CHILD_CREATED:
case LV_EVENT_CHILD_DELETED:
case LV_EVENT_CHILD_CHANGED:
case LV_EVENT_SIZE_CHANGED:
case LV_EVENT_STYLE_CHANGED:
case LV_EVENT_GET_SELF_SIZE:
return false;
default:
return true;
}
}

View File

@@ -229,6 +229,11 @@ void lv_event_stop_bubbling(lv_event_t * e)
e->stop_bubbling = 1;
}
void lv_event_stop_trickling(lv_event_t * e)
{
e->stop_trickling = 1;
}
void lv_event_stop_processing(lv_event_t * e)
{
e->stop_processing = 1;

View File

@@ -195,6 +195,13 @@ void * lv_event_get_user_data(lv_event_t * e);
*/
void lv_event_stop_bubbling(lv_event_t * e);
/**
* Stop event from trickling down to children.
* This is only valid when called in the middle of an event processing chain.
* @param e pointer to the event descriptor
*/
void lv_event_stop_trickling(lv_event_t * e);
/**
* Stop processing this event.
* This is only valid when called in the middle of an event processing chain.

View File

@@ -40,6 +40,7 @@ struct _lv_event_t {
uint8_t deleted : 1;
uint8_t stop_processing : 1;
uint8_t stop_bubbling : 1;
uint8_t stop_trickling : 1;
};

View File

@@ -116,6 +116,8 @@ void lv_xml_obj_apply(lv_xml_parser_state_t * state, const char ** attrs)
else if(lv_streq("press_lock", name)) lv_obj_set_flag(item, LV_OBJ_FLAG_PRESS_LOCK, lv_xml_to_bool(value));
else if(lv_streq("event_bubble", name)) lv_obj_set_flag(item, LV_OBJ_FLAG_EVENT_BUBBLE,
lv_xml_to_bool(value));
else if(lv_streq("event_trickle", name)) lv_obj_set_flag(item, LV_OBJ_FLAG_EVENT_TRICKLE,
lv_xml_to_bool(value));
else if(lv_streq("gesture_bubble", name)) lv_obj_set_flag(item, LV_OBJ_FLAG_GESTURE_BUBBLE,
lv_xml_to_bool(value));
else if(lv_streq("adv_hittest", name)) lv_obj_set_flag(item, LV_OBJ_FLAG_ADV_HITTEST,
@@ -646,6 +648,7 @@ static lv_obj_flag_t flag_to_enum(const char * txt)
if(lv_streq("snappable", txt)) return LV_OBJ_FLAG_SNAPPABLE;
if(lv_streq("press_lock", txt)) return LV_OBJ_FLAG_PRESS_LOCK;
if(lv_streq("event_bubble", txt)) return LV_OBJ_FLAG_EVENT_BUBBLE;
if(lv_streq("event_trickle", txt)) return LV_OBJ_FLAG_EVENT_TRICKLE;
if(lv_streq("gesture_bubble", txt)) return LV_OBJ_FLAG_GESTURE_BUBBLE;
if(lv_streq("adv_hittest", txt)) return LV_OBJ_FLAG_ADV_HITTEST;
if(lv_streq("ignore_layout", txt)) return LV_OBJ_FLAG_IGNORE_LAYOUT;

View File

@@ -14,7 +14,7 @@
* Generated code from properties.py
*/
/* *INDENT-OFF* */
const lv_property_name_t lv_obj_property_names[73] = {
const lv_property_name_t lv_obj_property_names[74] = {
{"align", LV_PROPERTY_OBJ_ALIGN,},
{"child_count", LV_PROPERTY_OBJ_CHILD_COUNT,},
{"content_height", LV_PROPERTY_OBJ_CONTENT_HEIGHT,},
@@ -28,6 +28,7 @@ const lv_property_name_t lv_obj_property_names[73] = {
{"flag_clickable", LV_PROPERTY_OBJ_FLAG_CLICKABLE,},
{"flag_end", LV_PROPERTY_OBJ_FLAG_END,},
{"flag_event_bubble", LV_PROPERTY_OBJ_FLAG_EVENT_BUBBLE,},
{"flag_event_trickle", LV_PROPERTY_OBJ_FLAG_EVENT_TRICKLE,},
{"flag_flex_in_new_track", LV_PROPERTY_OBJ_FLAG_FLEX_IN_NEW_TRACK,},
{"flag_floating", LV_PROPERTY_OBJ_FLAG_FLOATING,},
{"flag_gesture_bubble", LV_PROPERTY_OBJ_FLAG_GESTURE_BUBBLE,},

View File

@@ -15,7 +15,7 @@
extern const lv_property_name_t lv_image_property_names[11];
extern const lv_property_name_t lv_keyboard_property_names[4];
extern const lv_property_name_t lv_label_property_names[4];
extern const lv_property_name_t lv_obj_property_names[73];
extern const lv_property_name_t lv_obj_property_names[74];
extern const lv_property_name_t lv_roller_property_names[3];
extern const lv_property_name_t lv_slider_property_names[8];
extern const lv_property_name_t lv_style_property_names[120];

View File

@@ -0,0 +1,96 @@
#if LV_BUILD_TEST
#include "../lvgl.h"
#include "../../lvgl_private.h"
#include "unity/unity.h"
static uint32_t event_count = 0;
static lv_obj_t * last_target = NULL;
static void test_event_cb(lv_event_t * e)
{
event_count++;
last_target = lv_event_get_current_target_obj(e);
}
void test_event_trickle_basic(void)
{
/*Create a parent container*/
lv_obj_t * parent = lv_obj_create(lv_screen_active());
lv_obj_add_flag(parent, LV_OBJ_FLAG_EVENT_TRICKLE);
lv_obj_add_event_cb(parent, test_event_cb, LV_EVENT_CLICKED, NULL);
/*Create children*/
lv_obj_t * child1 = lv_obj_create(parent);
lv_obj_add_event_cb(child1, test_event_cb, LV_EVENT_CLICKED, NULL);
lv_obj_t * child2 = lv_obj_create(parent);
lv_obj_add_event_cb(child2, test_event_cb, LV_EVENT_CLICKED, NULL);
/*Reset counters*/
event_count = 0;
last_target = NULL;
/*Send event to parent - should trickle down to children*/
lv_obj_send_event(parent, LV_EVENT_CLICKED, NULL);
/*Should have received 3 events: parent + 2 children*/
TEST_ASSERT_EQUAL(3, event_count);
/*Clean up*/
lv_obj_delete(parent);
}
void test_event_trickle_stop(void)
{
/*Create a parent container*/
lv_obj_t * parent = lv_obj_create(lv_screen_active());
lv_obj_add_flag(parent, LV_OBJ_FLAG_EVENT_TRICKLE);
/*Add event handler that stops trickle down*/
lv_obj_add_event_cb(parent, test_event_cb, LV_EVENT_CLICKED, NULL);
lv_obj_add_event_cb(parent, (lv_event_cb_t)lv_event_stop_trickling, LV_EVENT_CLICKED, NULL);
/*Create children*/
lv_obj_t * child1 = lv_obj_create(parent);
lv_obj_add_event_cb(child1, test_event_cb, LV_EVENT_CLICKED, NULL);
/*Reset counters*/
event_count = 0;
last_target = NULL;
/*Send event to parent - should NOT trickle down due to stop*/
lv_obj_send_event(parent, LV_EVENT_CLICKED, NULL);
/*Should have received only 1 event: parent only*/
TEST_ASSERT_EQUAL(1, event_count); /* parent event handler + stop handler */
/*Clean up*/
lv_obj_delete(parent);
}
void test_event_trickle_disabled(void)
{
/*Create a parent container WITHOUT trickle down flag*/
lv_obj_t * parent = lv_obj_create(lv_screen_active());
lv_obj_add_event_cb(parent, test_event_cb, LV_EVENT_CLICKED, NULL);
/*Create children*/
lv_obj_t * child1 = lv_obj_create(parent);
lv_obj_add_event_cb(child1, test_event_cb, LV_EVENT_CLICKED, NULL);
/*Reset counters*/
event_count = 0;
last_target = NULL;
/*Send event to parent - should NOT trickle down*/
lv_obj_send_event(parent, LV_EVENT_CLICKED, NULL);
/*Should have received only 1 event: parent only*/
TEST_ASSERT_EQUAL(1, event_count);
/*Clean up*/
lv_obj_delete(parent);
}
#endif

View File

@@ -343,6 +343,7 @@ Example
<prop name="snappable" type="flag:flag lv_obj_flag"/>
<prop name="press_lock" type="flag:flag lv_obj_flag"/>
<prop name="event_bubble" type="flag:flag lv_obj_flag"/>
<prop name="event_trickle" type="flag:flag lv_obj_flag"/>
<prop name="gesture_bubble" type="flag:flag lv_obj_flag"/>
<prop name="adv_hittest" type="flag:flag lv_obj_flag"/>
<prop name="ignore_layout" type="flag:flag lv_obj_flag"/>