feat(obj): add delete_cb utility (#10030)

This commit is contained in:
André Costa
2026-05-11 14:04:13 +02:00
committed by GitHub
parent 1219d2b75b
commit f306d45e06
6 changed files with 216 additions and 0 deletions
+28
View File
@@ -121,3 +121,31 @@ void some_timer_callback(lv_timer_t * t)
}
}
```
### Delete Callbacks
If you need to perform cleanup when a widget is deleted, you can register a delete
callback using <ApiLink name="lv_obj_add_delete_cb" display="lv_obj_add_delete_cb(widget, cb, user_data)" />.
The callback will be automatically invoked with `user_data` as its argument when the widget
is deleted.
This is a convenience utility built on top of <ApiLink name="LV_EVENT_DELETE" />, useful for freeing
resources that are tied to a widget's lifetime without manually managing event handlers.
The following example automatically deletes a timer when the widget is deleted:
```c
lv_timer_t * timer = lv_timer_create_basic();
lv_obj_add_delete_cb(obj, (lv_delete_cb_t)lv_timer_delete, timer);
```
The following example frees a heap-allocated buffer when the widget is deleted:
```c
void * my_data = malloc(64);
lv_obj_add_delete_cb(obj, free, my_data);
```
<ApiLink name="lv_obj_add_delete_cb" /> returns a pointer to the created <ApiLink name="lv_delete_dsc_t"/>
descriptor, which can be used to remove the callback later if needed via <ApiLink name="lv_obj_remove_delete_cb"/>.
Multiple delete callbacks can be attached to the same widget, and all of them will be called when the widget is deleted.
+34
View File
@@ -38,6 +38,9 @@ extern "C" {
/**********************
* TYPEDEFS
**********************/
typedef void(*lv_delete_cb_t)(void * user_data);
/**
* On/Off features controlling the object's behavior.
* OR-ed values are possible
@@ -352,6 +355,37 @@ bool lv_obj_is_valid(const lv_obj_t * obj);
*/
void lv_obj_null_on_delete(lv_obj_t ** obj_ptr);
/**
* Attach a delete callback to an object.
*
* The registered callback will be automatically invoked with `user_data` as
* its argument when the object is deleted, allowing associated resources to
* be released or any other cleanup logic to be executed by the callback.
*
* This is a utility function that simplifies attaching an `LV_EVENT_DELETE`
* event callback to `obj` and passing `user_data` to that callback when the
* object is deleted.
*
* The `lv_delete_dsc_t` returned by this function is automatically released when
* the object is deleted as well.
*
* @param obj Pointer to the LVGL object to attach the delete callback to.
* @param cb The delete callback function to register.
* @param user_data User data pointer passed to `cb` when the object is deleted.
*
* @return Pointer to the delete descriptor or NULL if the operation failed.
*/
lv_delete_dsc_t * lv_obj_add_delete_cb(lv_obj_t * obj, lv_delete_cb_t cb, void * user_data);
/**
* Detach a delete callback from an object.
*
* Removes a delete descriptor previously created via @ref lv_obj_add_delete_cb
*
* @param dsc Pointer to the delete descriptor. Passing NULL results in a no-op
*/
void lv_obj_remove_delete_cb(lv_delete_dsc_t * dsc);
/**
* Add an event handler to a widget that will load a screen on a trigger.
* @param obj pointer to widget which should load the screen
+2
View File
@@ -112,6 +112,8 @@ typedef uint8_t lv_style_prop_t;
typedef struct _lv_obj_class_t lv_obj_class_t;
typedef struct _lv_delete_dsc_t lv_delete_dsc_t;
typedef struct _lv_group_t lv_group_t;
typedef struct _lv_display_t lv_display_t;
+55
View File
@@ -45,6 +45,12 @@ typedef struct {
bool reverse;
} timeline_play_dsc_t;
struct _lv_delete_dsc_t {
lv_obj_t * obj;
lv_delete_cb_t cb;
void * user_data;
};
/**********************
* STATIC PROTOTYPES
**********************/
@@ -63,6 +69,7 @@ static void screen_load_on_trigger_event_cb(lv_event_t * e);
static void screen_create_on_trigger_event_cb(lv_event_t * e);
static void play_timeline_on_trigger_event_cb(lv_event_t * e);
static void delete_on_screen_unloaded_event_cb(lv_event_t * e);
static void call_delete_cb(lv_event_t * e);
#if LV_USE_OBJ_PROPERTY
static lv_result_t lv_obj_set_any(lv_obj_t *, lv_prop_id_t, const lv_property_t *);
@@ -620,6 +627,41 @@ void * lv_obj_get_user_data(lv_obj_t * obj)
return obj->user_data;
}
lv_delete_dsc_t * lv_obj_add_delete_cb(lv_obj_t * obj, lv_delete_cb_t cb, void * user_data)
{
LV_CHECK_ARG(obj != NULL, return NULL);
LV_CHECK_ARG(cb != NULL, return NULL);
lv_delete_dsc_t * dsc = lv_malloc(sizeof(*dsc));
if(!dsc) {
return NULL;
}
dsc->obj = obj;
dsc->cb = cb;
dsc->user_data = user_data;
lv_event_dsc_t * event_dsc = lv_obj_add_event_cb(obj, call_delete_cb, LV_EVENT_DELETE, dsc);
if(!event_dsc) {
lv_free(dsc);
return NULL;
}
return dsc;
}
void lv_obj_remove_delete_cb(lv_delete_dsc_t * dsc)
{
if(!dsc) {
return;
}
uint32_t count = lv_obj_remove_event_cb_with_user_data(dsc->obj, call_delete_cb, dsc);
if(count == 0) {
LV_LOG_WARN("Delete callback descriptor not found on object or already removed");
return;
}
lv_free(dsc);
}
/**********************
* STATIC FUNCTIONS
**********************/
@@ -1344,6 +1386,19 @@ static void delete_on_screen_unloaded_event_cb(lv_event_t * e)
lv_obj_delete(lv_event_get_target_obj(e));
}
static void call_delete_cb(lv_event_t * e)
{
LV_ASSERT(e != NULL);
lv_obj_t * obj = lv_event_get_target_obj(e);
lv_delete_dsc_t * dsc = lv_event_get_user_data(e);
LV_ASSERT(dsc != NULL);
LV_ASSERT(dsc->cb != NULL);
LV_ASSERT(dsc->obj == obj);
dsc->cb(dsc->user_data);
lv_obj_remove_delete_cb(dsc);
}
#if LV_USE_OBJ_PROPERTY
static lv_point_t lv_obj_get_scroll_end_helper(lv_obj_t * obj)
{
@@ -0,0 +1,71 @@
#if LV_BUILD_TEST
#include "../lvgl.h"
#include "../../lvgl_private.h"
#include "unity/unity.h"
static uint32_t cb_called_count;
static void * cb_item;
static void my_delete_cb(void * item)
{
cb_called_count ++;
cb_item = item;
}
void setUp(void)
{
cb_called_count = 0;
cb_item = NULL;
}
void tearDown(void)
{
lv_obj_clean(lv_screen_active());
}
void test_add_delete_cb_is_called_on_delete(void)
{
lv_obj_t * obj = lv_obj_create(lv_screen_active());
int item = 42;
lv_delete_dsc_t * dsc = lv_obj_add_delete_cb(obj, my_delete_cb, &item);
TEST_ASSERT_NOT_NULL(dsc);
TEST_ASSERT_EQUAL(0, cb_called_count);
lv_obj_delete(obj);
TEST_ASSERT_EQUAL(1, cb_called_count);
TEST_ASSERT_EQUAL_PTR(&item, cb_item);
}
void test_add_delete_cb_multiple_cbs_all_called(void)
{
lv_obj_t * obj = lv_obj_create(lv_screen_active());
int item = 0;
lv_obj_add_delete_cb(obj, my_delete_cb, &item);
lv_obj_add_delete_cb(obj, my_delete_cb, &item);
lv_obj_add_delete_cb(obj, my_delete_cb, &item);
lv_obj_delete(obj);
TEST_ASSERT_EQUAL(3, cb_called_count);
TEST_ASSERT_EQUAL_PTR(&item, cb_item);
}
void test_add_delete_cb_can_be_removed(void)
{
lv_obj_t * obj = lv_obj_create(lv_screen_active());
int item = 42;
lv_delete_dsc_t * dsc = lv_obj_add_delete_cb(obj, my_delete_cb, &item);
TEST_ASSERT_NOT_NULL(dsc);
TEST_ASSERT_EQUAL(0, cb_called_count);
lv_obj_remove_delete_cb(dsc);
lv_obj_delete(obj);
TEST_ASSERT_EQUAL(0, cb_called_count);
TEST_ASSERT_NULL(cb_item);
}
#endif
@@ -0,0 +1,26 @@
#if LV_BUILD_TEST
#include "../lvgl.h"
#include "../../lvgl_private.h"
#include "unity/unity.h"
#include <stdlib.h>
void setUp(void)
{
}
void tearDown(void)
{
lv_obj_clean(lv_screen_active());
}
void test_delete_cb_null(void)
{
lv_obj_t * obj = lv_obj_create(lv_screen_active());
TEST_ASSERT_NULL(lv_obj_add_delete_cb(NULL, free, NULL));
TEST_ASSERT_NULL(lv_obj_add_delete_cb(obj, NULL, NULL));
/*NULL is okay in remove functions*/
lv_obj_remove_delete_cb(NULL);
TEST_PASS();
}
#endif /*LV_BUILD_TEST*/