#include "luavgl.h" #include "private.h" /** * Different to lvgl anim, anim in lua can be restarted after it's done. * anim.stop() * anim.start() * anim.delete() this will mark anim as no longer needed and waiting for gc. */ typedef struct luavgl_anim_s { lua_State *L; lv_anim_t *aa; /* the handler returns be lv_anim_start */ lv_anim_t cfg; /* the configuration, must not be the first element. */ int obj_ref; /* the object used for animation, used as in exec_cb */ int exec_cb; int done_cb; int self_ref; /* ref in registry to this anim */ bool deleted; /* manually deleted from lua */ } luavgl_anim_t; typedef luavgl_anim_t *luavgl_anim_handle_t; static luavgl_anim_t *luavgl_check_anim(lua_State *L, int index) { luavgl_anim_t *a = luaL_checkudata(L, index, "lv_anim"); if (a->deleted) { luaL_argerror(L, index, "anim already deleted"); return NULL; } return a; } static void luavgl_anim_exec_cb(void *var, int32_t value) { luavgl_anim_t *a = var; lua_State *L = a->L; if (a->exec_cb == LUA_NOREF || a->obj_ref == LUA_NOREF) { LV_LOG_ERROR("anim error, callback or obj not found"); luaL_error(L, "anim founds no callback or obj"); return; } /* stack: 1. function, 2. obj-userdata, 3. value */ lua_rawgeti(L, LUA_REGISTRYINDEX, a->exec_cb); lua_rawgeti(L, LUA_REGISTRYINDEX, a->obj_ref); lua_pushinteger(L, value); luavgl_pcall_int(L, 2, 0); } /* callback when anim is deleted by lvgl */ static void luavgl_anim_delete_cb(lv_anim_t *_a) { luavgl_anim_t *a = _a->var; a->aa = NULL; /* handler deleted by lvgl */ lua_State *L = a->L; if (a->done_cb != LUA_NOREF) { /* stack: 1. function, 2. this anim, 3. obj-userdata */ lua_rawgeti(L, LUA_REGISTRYINDEX, a->done_cb); lua_rawgeti(L, LUA_REGISTRYINDEX, a->self_ref); lua_rawgeti(L, LUA_REGISTRYINDEX, a->obj_ref); luavgl_pcall_int(L, 2, 0); } /* it's paused or deleted, thus can be gc'ed */ luaL_unref(L, LUA_REGISTRYINDEX, a->self_ref); a->self_ref = LUA_NOREF; } static void _lv_anim_set_start_value(lv_anim_t *a, int32_t v) { a->start_value = v; } static void _lv_anim_set_end_value(lv_anim_t *a, int32_t v) { a->end_value = v; } static void _lv_anim_set_path(void *obj, lua_State *L) { lv_anim_t *a = obj; const char *path = NULL; if (lua_isstring(L, -1)) { path = lua_tostring(L, -1); } a->path_cb = lv_anim_path_linear; if (path == NULL || lv_strcmp(path, "linear") == 0) { ; /* use default linear path */ } else if (lv_strcmp(path, "ease_in") == 0) { a->path_cb = lv_anim_path_ease_in; } else if (lv_strcmp(path, "ease_out") == 0) { a->path_cb = lv_anim_path_ease_out; } else if (lv_strcmp(path, "ease_in_out") == 0) { a->path_cb = lv_anim_path_ease_in_out; } else if (lv_strcmp(path, "overshoot") == 0) { a->path_cb = lv_anim_path_overshoot; } else if (lv_strcmp(path, "bounce") == 0) { a->path_cb = lv_anim_path_bounce; } else if (lv_strcmp(path, "step") == 0) { a->path_cb = lv_anim_path_step; } } /* clang-format off */ static const luavgl_value_setter_t anim_property_table[] = { { "run", SETTER_TYPE_STACK, { .setter_stack = _lv_dummy_set } }, { "start_value", 0, { .setter = (setter_int_t)_lv_anim_set_start_value } }, { "end_value", 0, { .setter = (setter_int_t)_lv_anim_set_end_value } }, { "time", 0, { .setter = (setter_int_t)lv_anim_set_time } }, { "duration", 0, { .setter = (setter_int_t)lv_anim_set_time } }, { "delay", 0, { .setter = (setter_int_t)lv_anim_set_delay } }, { "repeat_count", 0, { .setter = (setter_int_t)lv_anim_set_repeat_count } }, { "repeat_delay", 0, { .setter = (setter_int_t)lv_anim_set_repeat_delay } }, { "early_apply", 0, { .setter = (setter_int_t)lv_anim_set_early_apply } }, { "playback_time", 0, { .setter = (setter_int_t)lv_anim_set_playback_time } }, { "playback_delay", 0, { .setter = (setter_int_t)lv_anim_set_playback_delay } }, { "path", SETTER_TYPE_STACK, { .setter_stack = _lv_anim_set_path } }, { "exec_cb", SETTER_TYPE_STACK, { .setter_stack = _lv_dummy_set } }, }; /* clang-format on */ static int anim_set_para_cb(lua_State *L, void *data) { int ret = luavgl_set_property(L, data, anim_property_table); if (ret != 0) { LV_LOG_ERROR("failed"); } return ret; } static int luavgl_anim_stop(lua_State *L) { luavgl_anim_t *a = luavgl_check_anim(L, 1); if (a->aa == NULL || a->self_ref == LUA_NOREF) { LV_LOG_INFO("already stopped"); return 0; } lv_anim_del(a, luavgl_anim_exec_cb); /* work done in luavgl_anim_delete_cb */ return 0; } static int luavgl_anim_delete(lua_State *L) { luavgl_anim_stop(L); luavgl_anim_t *a = luavgl_check_anim(L, 1); /* object and callbacks can be gc'ed now */ luaL_unref(L, LUA_REGISTRYINDEX, a->done_cb); luaL_unref(L, LUA_REGISTRYINDEX, a->exec_cb); luaL_unref(L, LUA_REGISTRYINDEX, a->obj_ref); luaL_unref(L, LUA_REGISTRYINDEX, a->self_ref); a->done_cb = LUA_NOREF; a->exec_cb = LUA_NOREF; a->obj_ref = LUA_NOREF; a->self_ref = LUA_NOREF; a->deleted = true; return 0; } static int luavgl_anim_start(lua_State *L) { luavgl_anim_t *a = luavgl_check_anim(L, 1); if (a->aa) { LV_LOG_INFO("we have an anim ongoing, stop it"); luavgl_anim_stop(L); } lv_anim_t *new_a = lv_anim_start(&a->cfg); a->aa = new_a; LV_LOG_INFO("anim %p, aa: %p", a, a->aa); if (a->self_ref == LUA_NOREF) { /* it's started, thus cannot be gc'ed */ lua_pushvalue(L, 1); a->self_ref = luaL_ref(L, LUA_REGISTRYINDEX); } return 1; } /** * anim:set { para } * update anim parameter, stop anim if it's ongoing. */ static int luavgl_anim_set(lua_State *L) { luavgl_anim_t *a = luavgl_check_anim(L, 1); if (a->aa) { luavgl_anim_stop(L); } /* update anim parameters */ lv_anim_t *cfg = &a->cfg; lua_getfield(L, 2, "exec_cb"); if (!lua_isnoneornil(L, -1)) { luaL_unref(L, LUA_REGISTRYINDEX, a->exec_cb); a->exec_cb = luavgl_check_continuation(L, -1); } lua_pop(L, 1); lua_getfield(L, 2, "done_cb"); if (!lua_isnoneornil(L, -1)) { luaL_unref(L, LUA_REGISTRYINDEX, a->done_cb); a->done_cb = luavgl_check_continuation(L, -1); } lua_pop(L, 1); lua_getfield(L, 2, "run"); bool run = lua_toboolean(L, -1); lua_pop(L, 1); luavgl_iterate(L, 2, anim_set_para_cb, cfg); if (run) { luavgl_anim_start(L); } return 1; } /** * a = obj:Anim({anim parameters}) * a = obj:Anim{anim parameters} * a = lvgl.Anim(var, anim_para) * * a:start() */ static int luavgl_anim_create(lua_State *L) { if (lua_isnoneornil(L, 1)) { return luaL_argerror(L, 1, "anim var must not be nil or none"); } if (!lua_istable(L, 2)) { return luaL_argerror(L, 2, "expect anim para table"); } luavgl_anim_t *a = lua_newuserdata(L, sizeof(luavgl_anim_t)); luaL_getmetatable(L, "lv_anim"); lua_setmetatable(L, -2); lua_pushvalue(L, 1); a->obj_ref = luaL_ref(L, LUA_REGISTRYINDEX); a->exec_cb = LUA_NOREF; a->done_cb = LUA_NOREF; a->self_ref = LUA_NOREF; a->aa = NULL; a->L = L; a->deleted = false; lv_anim_t *cfg = &a->cfg; lv_anim_init(cfg); cfg->var = a; #if LV_VERSION_CHECK(8, 3, 0) cfg->deleted_cb = luavgl_anim_delete_cb; #else cfg->completed_cb = luavgl_anim_delete_cb; #endif cfg->exec_cb = luavgl_anim_exec_cb; /* leave only anim userdata and para table on stack */ lua_remove(L, 1); /* para, anim */ lua_insert(L, 1); /* anim, para */ luavgl_anim_set(L); lua_pop(L, 1); /* anim */ LV_LOG_INFO("create anim: %p, aa: %p", a, a->aa); return 1; } static int luavgl_anim_gc(lua_State *L) { LV_LOG_INFO("enter"); luavgl_anim_t *a = luaL_checkudata(L, 1, "lv_anim"); if (a->deleted) return 0; luavgl_anim_delete(L); return 0; } static int luavgl_anim_tostring(lua_State *L) { luavgl_anim_t *a = luavgl_check_anim(L, -1); lua_pushfstring( L, "anim %p: %s, value: [%d..%d], current: %d, repeat_cnt: %d", a->aa, a->aa ? "running" : "stopped", a->cfg.start_value, a->cfg.end_value, a->aa ? a->aa->current_value : -1, a->cfg.repeat_cnt); return 1; } static const luaL_Reg luavgl_anim_meta[] = { {"__gc", luavgl_anim_gc }, {"__tostring", luavgl_anim_tostring}, {"__index", NULL }, /* place holder */ {NULL, NULL }, }; static const luaL_Reg luavgl_anim_methods[] = { {"set", luavgl_anim_set }, {"start", luavgl_anim_start }, /* in lua anim, stopped anim can be restarted. */ {"stop", luavgl_anim_stop }, {"delete", luavgl_anim_delete}, {NULL, NULL } }; static void luavgl_anim_init(lua_State *L) { /* create metatable */ luaL_newmetatable(L, "lv_anim"); luaL_setfuncs(L, luavgl_anim_meta, 0); luaL_newlib(L, luavgl_anim_methods); lua_setfield(L, -2, "__index"); lua_pop(L, 1); /* pop metatable */ }