From 76a398b16205f2d003a0bee3f4fd4a168c46585d Mon Sep 17 00:00:00 2001 From: David Capello Date: Fri, 30 Dec 2022 14:51:43 -0300 Subject: [PATCH] [lua] Add access to user data properties in Sprite object (aseprite/api#88) This is a basic implementation where we can only access basic properties (not maps or vectors yet). --- src/app/CMakeLists.txt | 1 + src/app/script/engine.cpp | 2 + src/app/script/engine.h | 2 +- src/app/script/properties_class.cpp | 128 ++++++++++++++++++++++++++++ src/app/script/sprite_class.cpp | 1 + src/app/script/userdata.h | 7 ++ src/app/script/values.cpp | 47 +++++++++- tests/scripts/userdata.lua | 24 ++++++ 8 files changed, 208 insertions(+), 4 deletions(-) create mode 100644 src/app/script/properties_class.cpp create mode 100644 tests/scripts/userdata.lua diff --git a/src/app/CMakeLists.txt b/src/app/CMakeLists.txt index 77777f573..67deee7ee 100644 --- a/src/app/CMakeLists.txt +++ b/src/app/CMakeLists.txt @@ -191,6 +191,7 @@ if(ENABLE_SCRIPTING) script/plugin_class.cpp script/point_class.cpp script/preferences_object.cpp + script/properties_class.cpp script/range_class.cpp script/rectangle_class.cpp script/security.cpp diff --git a/src/app/script/engine.cpp b/src/app/script/engine.cpp index c1c7e7f7c..6703456d0 100644 --- a/src/app/script/engine.cpp +++ b/src/app/script/engine.cpp @@ -176,6 +176,7 @@ void register_palette_class(lua_State* L); void register_palettes_class(lua_State* L); void register_plugin_class(lua_State* L); void register_point_class(lua_State* L); +void register_properties_class(lua_State* L); void register_range_class(lua_State* L); void register_rect_class(lua_State* L); void register_selection_class(lua_State* L); @@ -454,6 +455,7 @@ Engine::Engine() register_palettes_class(L); register_plugin_class(L); register_point_class(L); + register_properties_class(L); register_range_class(L); register_rect_class(L); register_selection_class(L); diff --git a/src/app/script/engine.h b/src/app/script/engine.h index 0b5514d05..525bf7c19 100644 --- a/src/app/script/engine.h +++ b/src/app/script/engine.h @@ -150,6 +150,7 @@ namespace app { void push_layers(lua_State* L, const doc::ObjectIds& layers); void push_palette(lua_State* L, doc::Palette* palette); void push_plugin(lua_State* L, Extension* ext); + void push_properties(lua_State* L, doc::WithUserData* userData); void push_sprite_cel(lua_State* L, doc::Cel* cel); void push_sprite_events(lua_State* L, doc::Sprite* sprite); void push_sprite_frame(lua_State* L, doc::Sprite* sprite, doc::frame_t frame); @@ -166,7 +167,6 @@ namespace app { void push_tileset_image(lua_State* L, doc::Tileset* tileset, doc::Image* image); void push_tilesets(lua_State* L, doc::Tilesets* tilesets); void push_tool(lua_State* L, app::tools::Tool* tool); - void push_userdata(lua_State* L, doc::WithUserData* userData); void push_version(lua_State* L, const base::Version& ver); gfx::Point convert_args_into_point(lua_State* L, int index); diff --git a/src/app/script/properties_class.cpp b/src/app/script/properties_class.cpp new file mode 100644 index 000000000..6b5d38bd0 --- /dev/null +++ b/src/app/script/properties_class.cpp @@ -0,0 +1,128 @@ +// Aseprite +// Copyright (C) 2022 Igara Studio S.A. +// +// This program is distributed under the terms of +// the End-User License Agreement for Aseprite. + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "app/script/docobj.h" +#include "app/script/engine.h" +#include "app/script/luacpp.h" +#include "app/script/values.h" +#include "doc/with_user_data.h" + +#include + +namespace app { +namespace script { + +namespace { + +struct Properties { + doc::ObjectId id = 0; + Properties(doc::ObjectId id) : id(id) { } +}; + +int Properties_index(lua_State* L) +{ + auto propObj = get_obj(L, 1); + const char* field = lua_tostring(L, 2); + if (!field) + return luaL_error(L, "field in 'properties.field' must be a string"); + + auto obj = static_cast(get_object(propObj->id)); + if (!obj) + return luaL_error(L, "the object with these properties was destroyed"); + + auto& properties = obj->userData().properties(); + auto it = properties.find(field); + if (it != properties.end()) { + push_value_to_lua(L, (*it).second); + } + else { + lua_pushnil(L); + } + return 1; +} + +int Properties_newindex(lua_State* L) +{ + auto propObj = get_obj(L, 1); + const char* field = lua_tostring(L, 2); + if (!field) + return luaL_error(L, "field in 'properties.field' must be a string"); + + auto obj = static_cast(get_object(propObj->id)); + if (!obj) + return luaL_error(L, "the object with these properties was destroyed"); + + auto& properties = obj->userData().properties(); + + // TODO add undo information + switch (lua_type(L, 3)) { + + case LUA_TNONE: + case LUA_TNIL: + default: { + // Just erase the property + auto it = properties.find(field); + if (it != properties.end()) + properties.erase(it); + break; + } + + case LUA_TBOOLEAN: + properties[field] = (lua_toboolean(L, 3) ? true: false); + break; + + case LUA_TNUMBER: + if (lua_isinteger(L, 3)) + properties[field] = lua_tointeger(L, 3); + else { + properties[field] = doc::UserData::Fixed{ + fixmath::ftofix(lua_tonumber(L, 3)) + }; + } + break; + + case LUA_TSTRING: + properties[field] = lua_tostring(L, 3); + break; + + case LUA_TTABLE: + // TODO convert a full table into properties recursively + break; + + case LUA_TUSERDATA: + // TODO convert table-like objects (Size, Point, Rectangle, etc.) + break; + + } + return 1; +} + +const luaL_Reg Properties_methods[] = { + { "__index", Properties_index }, + { "__newindex", Properties_newindex }, + { nullptr, nullptr } +}; + +} // anonymous namespace + +DEF_MTNAME(Properties); + +void register_properties_class(lua_State* L) +{ + REG_CLASS(L, Properties); +} + +void push_properties(lua_State* L, doc::WithUserData* userData) +{ + push_obj(L, userData->id()); +} + +} // namespace script +} // namespace app diff --git a/src/app/script/sprite_class.cpp b/src/app/script/sprite_class.cpp index 7b127e1f2..7965babe7 100644 --- a/src/app/script/sprite_class.cpp +++ b/src/app/script/sprite_class.cpp @@ -882,6 +882,7 @@ const Property Sprite_properties[] = { { "gridBounds", Sprite_get_gridBounds, Sprite_set_gridBounds }, { "color", UserData_get_color, UserData_set_color }, { "data", UserData_get_text, UserData_set_text }, + { "properties", UserData_get_properties, nullptr }, { "pixelRatio", Sprite_get_pixelRatio, Sprite_set_pixelRatio }, { "events", Sprite_get_events, nullptr }, { nullptr, nullptr, nullptr } diff --git a/src/app/script/userdata.h b/src/app/script/userdata.h index 65af696b0..038a92e66 100644 --- a/src/app/script/userdata.h +++ b/src/app/script/userdata.h @@ -51,6 +51,13 @@ int UserData_get_color(lua_State* L) { return 1; } +template +int UserData_get_properties(lua_State* L) { + auto obj = get_docobj(L, 1); + push_properties(L, get_WithUserData(obj)); + return 1; +} + template int UserData_set_text(lua_State* L) { auto obj = get_docobj(L, 1); diff --git a/src/app/script/values.cpp b/src/app/script/values.cpp index 47ca4a960..e043a07d3 100644 --- a/src/app/script/values.cpp +++ b/src/app/script/values.cpp @@ -16,6 +16,7 @@ #include "doc/remap.h" #include +#include namespace app { namespace script { @@ -39,9 +40,21 @@ bool get_value_from_lua(lua_State* L, int index) { // int template<> -void push_value_to_lua(lua_State* L, const int& value) { - lua_pushinteger(L, value); -} +void push_value_to_lua(lua_State* L, const int8_t& value) { lua_pushinteger(L, value); } +template<> +void push_value_to_lua(lua_State* L, const int16_t& value) { lua_pushinteger(L, value); } +template<> +void push_value_to_lua(lua_State* L, const int32_t& value) { lua_pushinteger(L, value); } +template<> +void push_value_to_lua(lua_State* L, const int64_t& value) { lua_pushinteger(L, value); } +template<> +void push_value_to_lua(lua_State* L, const uint8_t& value) { lua_pushinteger(L, value); } +template<> +void push_value_to_lua(lua_State* L, const uint16_t& value) { lua_pushinteger(L, value); } +template<> +void push_value_to_lua(lua_State* L, const uint32_t& value) { lua_pushinteger(L, value); } +template<> +void push_value_to_lua(lua_State* L, const uint64_t& value) { lua_pushinteger(L, value); } template<> int get_value_from_lua(lua_State* L, int index) { @@ -61,6 +74,14 @@ double get_value_from_lua(lua_State* L, int index) { return lua_tonumber(L, index); } +// ---------------------------------------------------------------------- +// fixed + +template<> +void push_value_to_lua(lua_State* L, const doc::UserData::Fixed& value) { + lua_pushnumber(L, fixmath::fixtof(value.value)); +} + // ---------------------------------------------------------------------- // std::string @@ -193,10 +214,12 @@ app::tools::InkType get_value_from_lua(lua_State* L, int index) { // ---------------------------------------------------------------------- // doc::tile_t +#if 0 // doc::tile_t matches uint32_t, and we have the uint32_t version already defined template<> void push_value_to_lua(lua_State* L, const doc::tile_t& value) { lua_pushinteger(L, value); } +#endif template<> doc::tile_t get_value_from_lua(lua_State* L, int index) { @@ -251,5 +274,23 @@ FOR_ENUM(filters::HueSaturationFilter::Mode) FOR_ENUM(filters::TiledMode) FOR_ENUM(render::OnionskinPosition) +// ---------------------------------------------------------------------- +// UserData::Properties / VariantStruct + +template<> +void push_value_to_lua(lua_State* L, const doc::UserData::Properties& value) { + // TODO convert a Properties map into a Lua table +} + +template<> +void push_value_to_lua(lua_State* L, const doc::UserData::Vector& value) { + // TODO convert a Vector into a Lua table +} + +template<> +void push_value_to_lua(lua_State* L, const doc::UserData::Variant& value) { + std::visit([L](auto&& v){ push_value_to_lua(L, v); }, value); +} + } // namespace script } // namespace app diff --git a/tests/scripts/userdata.lua b/tests/scripts/userdata.lua new file mode 100644 index 000000000..5b035fcda --- /dev/null +++ b/tests/scripts/userdata.lua @@ -0,0 +1,24 @@ +-- Copyright (C) 2022 Igara Studio S.A. +-- +-- This file is released under the terms of the MIT license. +-- Read LICENSE.txt for more information. + +do + local spr = Sprite(1, 1) + + spr.properties.a = true + spr.properties.b = 1 + spr.properties.c = "hi" + spr.properties.d = 2.3 + assert(spr.properties.a == true) + assert(spr.properties.b == 1) + assert(spr.properties.c == "hi") + -- TODO we don't have too much precision saving fixed points in properties + assert(math.abs(spr.properties.d - 2.3) < 0.00001) + + spr.properties.a = false + assert(spr.properties.a == false) + + spr.properties.b = nil + assert(spr.properties.b == nil) +end