mirror of
https://github.com/lvgl/lvgl.git
synced 2026-08-17 09:42:19 +08:00
feat(obj): add delete_cb utility (#10030)
This commit is contained in:
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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*/
|
||||
Reference in New Issue
Block a user