Merge branch 'lua-api-for-properties' (fix aseprite/api#88)

This commit is contained in:
David Capello 2023-01-03 11:34:20 -03:00
commit 420278d5a4
19 changed files with 950 additions and 14 deletions

View File

@ -1,5 +1,5 @@
# Aseprite
# Copyright (C) 2018-2022 Igara Studio S.A.
# Copyright (C) 2018-2023 Igara Studio S.A.
# Copyright (C) 2001-2018 David Capello
# Generate a ui::Widget for each widget in a XML file
@ -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
@ -494,6 +495,7 @@ add_library(app-lib
cmd/remove_tag.cpp
cmd/remove_tile.cpp
cmd/remove_tileset.cpp
cmd/remove_user_data_property.cpp
cmd/replace_image.cpp
cmd/replace_tileset.cpp
cmd/reselect_mask.cpp
@ -527,6 +529,8 @@ add_library(app-lib
cmd/set_total_frames.cpp
cmd/set_transparent_color.cpp
cmd/set_user_data.cpp
cmd/set_user_data_properties.cpp
cmd/set_user_data_property.cpp
cmd/shift_masked_cel.cpp
cmd/trim_cel.cpp
cmd/unlink_cel.cpp

View File

@ -0,0 +1,53 @@
// Aseprite
// Copyright (C) 2023 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/cmd/remove_user_data_property.h"
#include "app/cmd.h"
#include "app/cmd/with_document.h"
#include "doc/object_id.h"
#include "doc/user_data.h"
#include "doc/with_user_data.h"
namespace app {
namespace cmd {
RemoveUserDataProperty::RemoveUserDataProperty(
doc::WithUserData* obj,
const std::string& group,
const std::string& field)
: m_objId(obj->id())
, m_group(group)
, m_field(field)
, m_oldVariant(obj->userData().properties(m_group)[m_field])
{
}
void RemoveUserDataProperty::onExecute()
{
auto obj = doc::get<doc::WithUserData>(m_objId);
auto& properties = obj->userData().properties(m_group);
auto it = properties.find(m_field);
ASSERT(it != properties.end());
if (it != properties.end()) {
properties.erase(it);
obj->incrementVersion();
}
}
void RemoveUserDataProperty::onUndo()
{
auto obj = doc::get<doc::WithUserData>(m_objId);
obj->userData().properties(m_group)[m_field] = m_oldVariant;
obj->incrementVersion();
}
} // namespace cmd
} // namespace app

View File

@ -0,0 +1,46 @@
// Aseprite
// Copyright (C) 2023 Igara Studio S.A.
//
// This program is distributed under the terms of
// the End-User License Agreement for Aseprite.
#ifndef APP_CMD_REMOVE_USER_DATA_PROPERTY_H_INCLUDED
#define APP_CMD_REMOVE_USER_DATA_PROPERTY_H_INCLUDED
#pragma once
#include "app/cmd.h"
#include "doc/object_id.h"
#include "doc/user_data.h"
namespace doc {
class WithUserData;
}
namespace app {
namespace cmd {
class RemoveUserDataProperty : public Cmd {
public:
RemoveUserDataProperty(
doc::WithUserData* obj,
const std::string& group,
const std::string& field);
protected:
void onExecute() override;
void onUndo() override;
size_t onMemSize() const override {
return sizeof(*this); // TODO + variants size
}
private:
doc::ObjectId m_objId;
std::string m_group;
std::string m_field;
doc::UserData::Variant m_oldVariant;
};
} // namespace cmd
} // namespace app
#endif

View File

@ -0,0 +1,48 @@
// Aseprite
// Copyright (C) 2023 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/cmd/set_user_data_properties.h"
#include "app/cmd.h"
#include "app/cmd/with_document.h"
#include "doc/object_id.h"
#include "doc/user_data.h"
#include "doc/with_user_data.h"
namespace app {
namespace cmd {
SetUserDataProperties::SetUserDataProperties(
doc::WithUserData* obj,
const std::string& group,
doc::UserData::Properties&& newProperties)
: m_objId(obj->id())
, m_group(group)
, m_oldProperties(obj->userData().properties(group))
, m_newProperties(std::move(newProperties))
{
}
void SetUserDataProperties::onExecute()
{
auto obj = doc::get<doc::WithUserData>(m_objId);
obj->userData().properties(m_group) = m_newProperties;
obj->incrementVersion();
}
void SetUserDataProperties::onUndo()
{
auto obj = doc::get<doc::WithUserData>(m_objId);
obj->userData().properties(m_group) = m_oldProperties;
obj->incrementVersion();
}
} // namespace cmd
} // namespace app

View File

@ -0,0 +1,46 @@
// Aseprite
// Copyright (C) 2023 Igara Studio S.A.
//
// This program is distributed under the terms of
// the End-User License Agreement for Aseprite.
#ifndef APP_CMD_SET_USER_DATA_PROPERTIES_H_INCLUDED
#define APP_CMD_SET_USER_DATA_PROPERTIES_H_INCLUDED
#pragma once
#include "app/cmd.h"
#include "doc/object_id.h"
#include "doc/user_data.h"
namespace doc {
class WithUserData;
}
namespace app {
namespace cmd {
class SetUserDataProperties : public Cmd {
public:
SetUserDataProperties(
doc::WithUserData* obj,
const std::string& group,
doc::UserData::Properties&& newProperties);
protected:
void onExecute() override;
void onUndo() override;
size_t onMemSize() const override {
return sizeof(*this); // TODO + properties size
}
private:
doc::ObjectId m_objId;
std::string m_group;
doc::UserData::Properties m_oldProperties;
doc::UserData::Properties m_newProperties;
};
} // namespace cmd
} // namespace app
#endif

View File

@ -0,0 +1,50 @@
// Aseprite
// Copyright (C) 2023 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/cmd/set_user_data_property.h"
#include "app/cmd.h"
#include "app/cmd/with_document.h"
#include "doc/object_id.h"
#include "doc/user_data.h"
#include "doc/with_user_data.h"
namespace app {
namespace cmd {
SetUserDataProperty::SetUserDataProperty(
doc::WithUserData* obj,
const std::string& group,
const std::string& field,
doc::UserData::Variant&& newValue)
: m_objId(obj->id())
, m_group(group)
, m_field(field)
, m_oldValue(obj->userData().properties(group)[m_field])
, m_newValue(std::move(newValue))
{
}
void SetUserDataProperty::onExecute()
{
auto obj = doc::get<doc::WithUserData>(m_objId);
obj->userData().properties(m_group)[m_field] = m_newValue;
obj->incrementVersion();
}
void SetUserDataProperty::onUndo()
{
auto obj = doc::get<doc::WithUserData>(m_objId);
obj->userData().properties(m_group)[m_field] = m_oldValue;
obj->incrementVersion();
}
} // namespace cmd
} // namespace app

View File

@ -0,0 +1,48 @@
// Aseprite
// Copyright (C) 2023 Igara Studio S.A.
//
// This program is distributed under the terms of
// the End-User License Agreement for Aseprite.
#ifndef APP_CMD_SET_USER_DATA_PROPERTY_H_INCLUDED
#define APP_CMD_SET_USER_DATA_PROPERTY_H_INCLUDED
#pragma once
#include "app/cmd.h"
#include "doc/object_id.h"
#include "doc/user_data.h"
namespace doc {
class WithUserData;
}
namespace app {
namespace cmd {
class SetUserDataProperty : public Cmd {
public:
SetUserDataProperty(
doc::WithUserData* obj,
const std::string& group,
const std::string& field,
doc::UserData::Variant&& newValue);
protected:
void onExecute() override;
void onUndo() override;
size_t onMemSize() const override {
return sizeof(*this); // TODO + variant size
}
private:
doc::ObjectId m_objId;
std::string m_group;
std::string m_field;
doc::UserData::Variant m_oldValue;
doc::UserData::Variant m_newValue;
};
} // namespace cmd
} // namespace app
#endif

View File

@ -1,5 +1,5 @@
// Aseprite
// Copyright (C) 2018-2022 Igara Studio S.A.
// Copyright (C) 2018-2023 Igara Studio S.A.
// Copyright (C) 2018 David Capello
//
// This program is distributed under the terms of
@ -160,6 +160,7 @@ const Property Cel_properties[] = {
{ "opacity", Cel_get_opacity, Cel_set_opacity },
{ "color", UserData_get_color<Cel>, UserData_set_color<Cel> },
{ "data", UserData_get_text<Cel>, UserData_set_text<Cel> },
{ "properties", UserData_get_properties<Cel>, UserData_set_properties<Cel> },
{ nullptr, nullptr, nullptr }
};

View File

@ -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);

View File

@ -1,5 +1,5 @@
// Aseprite
// Copyright (C) 2018-2022 Igara Studio S.A.
// Copyright (C) 2018-2023 Igara Studio S.A.
// Copyright (C) 2001-2018 David Capello
//
// This program is distributed under the terms of
@ -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, const std::string& extID);
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);

View File

@ -1,5 +1,5 @@
// Aseprite
// Copyright (C) 2018-2022 Igara Studio S.A.
// Copyright (C) 2018-2023 Igara Studio S.A.
// Copyright (C) 2018 David Capello
//
// This program is distributed under the terms of
@ -406,6 +406,7 @@ const Property Layer_properties[] = {
{ "cels", Layer_get_cels, nullptr },
{ "color", UserData_get_color<Layer>, UserData_set_color<Layer> },
{ "data", UserData_get_text<Layer>, UserData_set_text<Layer> },
{ "properties", UserData_get_properties<Layer>, UserData_set_properties<Layer> },
{ "tileset", Layer_get_tileset, nullptr },
{ nullptr, nullptr, nullptr }
};

View File

@ -0,0 +1,237 @@
// Aseprite
// Copyright (C) 2022-2023 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/cmd/remove_user_data_property.h"
#include "app/cmd/set_user_data_properties.h"
#include "app/cmd/set_user_data_property.h"
#include "app/script/docobj.h"
#include "app/script/engine.h"
#include "app/script/luacpp.h"
#include "app/script/values.h"
#include "app/tx.h"
#include "doc/with_user_data.h"
#include <cstring>
namespace app {
namespace script {
namespace {
struct Properties {
doc::ObjectId id = 0;
std::string extID;
Properties(doc::ObjectId id,
const std::string& extID)
: id(id)
, extID(extID) {
}
};
using PropertiesIterator = doc::UserData::Properties::iterator;
int Properties_len(lua_State* L)
{
auto propObj = get_obj<Properties>(L, 1);
auto obj = static_cast<doc::WithUserData*>(get_object(propObj->id));
if (!obj)
return luaL_error(L, "the object with these properties was destroyed");
auto& properties = obj->userData().properties(propObj->extID);
lua_pushinteger(L, properties.size());
return 1;
}
int Properties_index(lua_State* L)
{
auto propObj = get_obj<Properties>(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<doc::WithUserData*>(get_object(propObj->id));
if (!obj)
return luaL_error(L, "the object with these properties was destroyed");
auto& properties = obj->userData().properties(propObj->extID);
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<Properties>(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<doc::WithUserData*>(get_object(propObj->id));
if (!obj)
return luaL_error(L, "the object with these properties was destroyed");
auto& properties = obj->userData().properties(propObj->extID);
switch (lua_type(L, 3)) {
case LUA_TNONE:
case LUA_TNIL: {
// If we assign nil to a property, we just remove the property.
auto it = properties.find(field);
if (it != properties.end()) {
// TODO add Object::sprite() member function, and fix "Tx" object
// to use the sprite of this object instead of the activeDocument()
//if (obj->sprite()) {
if (App::instance()->context()->activeDocument()) {
Tx tx;
tx(new cmd::RemoveUserDataProperty(obj, propObj->extID, field));
tx.commit();
}
else {
properties.erase(it);
}
}
break;
}
default: {
auto newValue = get_value_from_lua<doc::UserData::Variant>(L, 3);
// TODO add Object::sprite() member function
//if (obj->sprite()) {
if (App::instance()->context()->activeDocument()) {
Tx tx;
tx(new cmd::SetUserDataProperty(obj, propObj->extID, field,
std::move(newValue)));
tx.commit();
}
else {
properties[field] = std::move(newValue);
}
break;
}
}
return 0;
}
int Properties_call(lua_State* L)
{
auto propObj = get_obj<Properties>(L, 1);
const char* extID = lua_tostring(L, 2);
if (!extID)
return luaL_error(L, "extensionID in 'properties(\"extensionID\")' must be a string");
// Special syntax to change the full extension properties using:
//
// object.property("extension", { ...})
//
if (lua_istable(L, 3)) {
auto newProperties = get_value_from_lua<doc::UserData::Properties>(L, 3);
auto obj = static_cast<doc::WithUserData*>(get_object(propObj->id));
if (!obj)
return luaL_error(L, "the object with these properties was destroyed");
// TODO add Object::sprite() member function
//if (obj->sprite()) {
if (App::instance()->context()->activeDocument()) {
Tx tx;
tx(new cmd::SetUserDataProperties(obj, extID, std::move(newProperties)));
tx.commit();
}
else {
auto& properties = obj->userData().properties(extID);
properties = std::move(newProperties);
}
}
push_new<Properties>(L, propObj->id, extID);
return 1;
}
int Properties_pairs_next(lua_State* L)
{
auto propObj = get_obj<Properties>(L, 1);
auto obj = static_cast<doc::WithUserData*>(get_object(propObj->id));
if (!obj)
return luaL_error(L, "the object with these properties was destroyed");
auto& properties = obj->userData().properties(propObj->extID);
auto& it = *get_obj<PropertiesIterator>(L, lua_upvalueindex(1));
if (it == properties.end())
return 0;
lua_pushstring(L, (*it).first.c_str());
push_value_to_lua(L, (*it).second);
++it;
return 2;
}
int Properties_pairs(lua_State* L)
{
auto propObj = get_obj<Properties>(L, 1);
auto obj = static_cast<doc::WithUserData*>(get_object(propObj->id));
if (!obj)
return luaL_error(L, "the object with these properties was destroyed");
auto& properties = obj->userData().properties(propObj->extID);
push_obj(L, properties.begin());
lua_pushcclosure(L, Properties_pairs_next, 1);
lua_pushvalue(L, 1); // Copy the same propObj as the second return value
return 2;
}
int PropertiesIterator_gc(lua_State* L)
{
get_obj<PropertiesIterator>(L, 1)->~PropertiesIterator();
return 0;
}
const luaL_Reg Properties_methods[] = {
{ "__len", Properties_len },
{ "__call", Properties_call },
{ "__index", Properties_index },
{ "__newindex", Properties_newindex },
{ "__pairs", Properties_pairs },
{ nullptr, nullptr }
};
const luaL_Reg PropertiesIterator_methods[] = {
{ "__gc", PropertiesIterator_gc },
{ nullptr, nullptr }
};
} // anonymous namespace
DEF_MTNAME(Properties);
DEF_MTNAME(PropertiesIterator);
void register_properties_class(lua_State* L)
{
REG_CLASS(L, Properties);
REG_CLASS(L, PropertiesIterator);
}
void push_properties(lua_State* L,
doc::WithUserData* userData,
const std::string& extID)
{
push_new<Properties>(L, userData->id(), extID);
}
} // namespace script
} // namespace app

View File

@ -1,5 +1,5 @@
// Aseprite
// Copyright (C) 2018-2022 Igara Studio S.A.
// Copyright (C) 2018-2023 Igara Studio S.A.
// Copyright (C) 2018 David Capello
//
// This program is distributed under the terms of
@ -145,6 +145,7 @@ const Property Slice_properties[] = {
{ "pivot", Slice_get_pivot, Slice_set_pivot },
{ "color", UserData_get_color<Slice>, UserData_set_color<Slice> },
{ "data", UserData_get_text<Slice>, UserData_set_text<Slice> },
{ "properties", UserData_get_properties<Slice>, UserData_set_properties<Slice> },
{ nullptr, nullptr, nullptr }
};

View File

@ -1,5 +1,5 @@
// Aseprite
// Copyright (C) 2018-2022 Igara Studio S.A.
// Copyright (C) 2018-2023 Igara Studio S.A.
// Copyright (C) 2015-2018 David Capello
//
// This program is distributed under the terms of
@ -882,6 +882,7 @@ const Property Sprite_properties[] = {
{ "gridBounds", Sprite_get_gridBounds, Sprite_set_gridBounds },
{ "color", UserData_get_color<Sprite>, UserData_set_color<Sprite> },
{ "data", UserData_get_text<Sprite>, UserData_set_text<Sprite> },
{ "properties", UserData_get_properties<Sprite>, UserData_set_properties<Sprite> },
{ "pixelRatio", Sprite_get_pixelRatio, Sprite_set_pixelRatio },
{ "events", Sprite_get_events, nullptr },
{ nullptr, nullptr, nullptr }

View File

@ -1,5 +1,5 @@
// Aseprite
// Copyright (C) 2018-2022 Igara Studio S.A.
// Copyright (C) 2018-2023 Igara Studio S.A.
// Copyright (C) 2018 David Capello
//
// This program is distributed under the terms of
@ -162,6 +162,7 @@ const Property Tag_properties[] = {
{ "repeats", Tag_get_repeats, Tag_set_repeats }, // Cannot be "repeat" because it's a Lua keyword
{ "color", UserData_get_color<Tag>, UserData_set_color<Tag> },
{ "data", UserData_get_text<Tag>, UserData_set_text<Tag> },
{ "properties", UserData_get_properties<Tag>, UserData_set_properties<Tag> },
{ nullptr, nullptr, nullptr }
};

View File

@ -1,5 +1,5 @@
// Aseprite
// Copyright (C) 2019-2022 Igara Studio S.A.
// Copyright (C) 2019-2023 Igara Studio S.A.
//
// This program is distributed under the terms of
// the End-User License Agreement for Aseprite.
@ -100,6 +100,7 @@ const Property Tileset_properties[] = {
{ "baseIndex", Tileset_get_baseIndex, Tileset_set_baseIndex },
{ "color", UserData_get_color<Tileset>, UserData_set_color<Tileset> },
{ "data", UserData_get_text<Tileset>, UserData_set_text<Tileset> },
{ "properties", UserData_get_properties<Tileset>, UserData_set_properties<Tileset> },
{ nullptr, nullptr, nullptr }
};

View File

@ -1,5 +1,5 @@
// Aseprite
// Copyright (C) 2018-2020 Igara Studio S.A.
// Copyright (C) 2018-2023 Igara Studio S.A.
// Copyright (C) 2018 David Capello
//
// This program is distributed under the terms of
@ -10,9 +10,11 @@
#pragma once
#include "app/cmd/set_user_data.h"
#include "app/cmd/set_user_data_properties.h"
#include "app/color.h"
#include "app/color_utils.h"
#include "app/script/luacpp.h"
#include "app/script/values.h"
#include "app/tx.h"
#include "doc/cel.h"
#include "doc/with_user_data.h"
@ -51,6 +53,13 @@ int UserData_get_color(lua_State* L) {
return 1;
}
template<typename T>
int UserData_get_properties(lua_State* L) {
auto obj = get_docobj<T>(L, 1);
push_properties(L, get_WithUserData<T>(obj), std::string());
return 1;
}
template<typename T>
int UserData_set_text(lua_State* L) {
auto obj = get_docobj<T>(L, 1);
@ -89,6 +98,25 @@ int UserData_set_color(lua_State* L) {
return 0;
}
template<typename T>
int UserData_set_properties(lua_State* L) {
auto obj = get_docobj<T>(L, 1);
auto wud = get_WithUserData<T>(obj);
auto newProperties = get_value_from_lua<doc::UserData::Properties>(L, 2);
if (obj->sprite()) {
Tx tx;
tx(new cmd::SetUserDataProperties(wud,
std::string(),
std::move(newProperties)));
tx.commit();
}
else {
auto& properties = wud->userData().properties();
properties = std::move(newProperties);
}
return 0;
}
} // namespace script
} // namespace app

View File

@ -1,5 +1,5 @@
// Aseprite
// Copyright (C) 2019-2022 Igara Studio S.A.
// Copyright (C) 2019-2023 Igara Studio S.A.
//
// This program is distributed under the terms of
// the End-User License Agreement for Aseprite.
@ -16,6 +16,7 @@
#include "doc/remap.h"
#include <any>
#include <variant>
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,219 @@ FOR_ENUM(filters::HueSaturationFilter::Mode)
FOR_ENUM(filters::TiledMode)
FOR_ENUM(render::OnionskinPosition)
// ----------------------------------------------------------------------
// UserData::Properties / Variant
template<>
void push_value_to_lua(lua_State* L, const doc::UserData::Properties& value);
template<>
void push_value_to_lua(lua_State* L, const doc::UserData::Vector& value);
template<>
doc::UserData::Properties get_value_from_lua(lua_State* L, int index);
template<>
doc::UserData::Vector get_value_from_lua(lua_State* L, int index);
template<>
void push_value_to_lua(lua_State* L, const doc::UserData::Variant& value)
{
#if 1 // We are targetting macOS 10.9, so we don't have the std::visit() available
switch (value.type()) {
case USER_DATA_PROPERTY_TYPE_BOOL:
push_value_to_lua(L, *std::get_if<bool>(&value));
break;
case USER_DATA_PROPERTY_TYPE_INT8:
push_value_to_lua(L, *std::get_if<int8_t>(&value));
break;
case USER_DATA_PROPERTY_TYPE_UINT8:
push_value_to_lua(L, *std::get_if<uint8_t>(&value));
break;
case USER_DATA_PROPERTY_TYPE_INT16:
push_value_to_lua(L, *std::get_if<int16_t>(&value));
break;
case USER_DATA_PROPERTY_TYPE_UINT16:
push_value_to_lua(L, *std::get_if<uint16_t>(&value));
break;
case USER_DATA_PROPERTY_TYPE_INT32:
push_value_to_lua(L, *std::get_if<int32_t>(&value));
break;
case USER_DATA_PROPERTY_TYPE_UINT32:
push_value_to_lua(L, *std::get_if<uint32_t>(&value));
break;
case USER_DATA_PROPERTY_TYPE_INT64:
push_value_to_lua(L, *std::get_if<int64_t>(&value));
break;
case USER_DATA_PROPERTY_TYPE_UINT64:
push_value_to_lua(L, *std::get_if<uint64_t>(&value));
break;
case USER_DATA_PROPERTY_TYPE_FIXED:
push_value_to_lua(L, *std::get_if<doc::UserData::Fixed>(&value));
break;
case USER_DATA_PROPERTY_TYPE_STRING:
push_value_to_lua(L, *std::get_if<std::string>(&value));
break;
case USER_DATA_PROPERTY_TYPE_POINT:
push_value_to_lua(L, *std::get_if<gfx::Point>(&value));
break;
case USER_DATA_PROPERTY_TYPE_SIZE:
push_value_to_lua(L, *std::get_if<gfx::Size>(&value));
break;
case USER_DATA_PROPERTY_TYPE_RECT:
push_value_to_lua(L, *std::get_if<gfx::Rect>(&value));
break;
case USER_DATA_PROPERTY_TYPE_VECTOR:
push_value_to_lua(L, *std::get_if<doc::UserData::Vector>(&value));
break;
case USER_DATA_PROPERTY_TYPE_PROPERTIES:
push_value_to_lua(L, *std::get_if<doc::UserData::Properties>(&value));
break;
}
#else // TODO enable this in the future
std::visit([L](auto&& v){ push_value_to_lua(L, v); }, value);
#endif
}
template<>
doc::UserData::Variant get_value_from_lua(lua_State* L, int index)
{
doc::UserData::Variant v;
switch (lua_type(L, index)) {
case LUA_TNONE:
case LUA_TNIL:
// TODO should we add nullptr_t in Variant?
break;
case LUA_TBOOLEAN:
v = (lua_toboolean(L, index) ? true: false);
break;
case LUA_TNUMBER:
if (lua_isinteger(L, index))
v = lua_tointeger(L, index);
else {
v = doc::UserData::Fixed{
fixmath::ftofix(lua_tonumber(L, index))
};
}
break;
case LUA_TSTRING:
v = std::string(lua_tostring(L, index));
break;
case LUA_TTABLE: {
int i = 0;
bool isArray = true;
if (index < 0)
--index;
lua_pushnil(L);
while (lua_next(L, index) != 0) {
if (lua_isinteger(L, -2)) {
// TODO we should check that all values are of the same type
// to create the vector
if (++i != lua_tointeger(L, -2)) {
isArray = false;
lua_pop(L, 2); // Pop value and key
break;
}
}
else {
isArray = false;
lua_pop(L, 2);
break;
}
lua_pop(L, 1); // Pop the value, leave the key for lua_next()
}
if (index < 0)
++index;
if (isArray) {
v = get_value_from_lua<doc::UserData::Vector>(L, index);
}
else {
v = get_value_from_lua<doc::UserData::Properties>(L, index);
}
break;
}
case LUA_TUSERDATA: {
if (auto rect = may_get_obj<gfx::Rect>(L, index)) {
v = *rect;
}
else if (auto pt = may_get_obj<gfx::Point>(L, index)) {
v = *pt;
}
else if (auto sz = may_get_obj<gfx::Size>(L, index)) {
v = *sz;
}
break;
}
}
return v;
}
template<>
void push_value_to_lua(lua_State* L, const doc::UserData::Properties& value)
{
lua_newtable(L);
for (const auto& kv : value) {
push_value_to_lua(L, kv.second);
lua_setfield(L, -2, kv.first.c_str());
}
}
template<>
void push_value_to_lua(lua_State* L, const doc::UserData::Vector& value)
{
int i = 0;
lua_newtable(L);
for (const auto& kv : value) {
push_value_to_lua(L, kv);
lua_seti(L, -2, ++i);
}
}
template<>
doc::UserData::Properties get_value_from_lua(lua_State* L, int index)
{
doc::UserData::Properties m;
if (index < 0)
--index;
lua_pushnil(L);
while (lua_next(L, index) != 0) {
if (auto k = lua_tostring(L, -2))
m[k] = get_value_from_lua<doc::UserData::Variant>(L, -1);
lua_pop(L, 1);
}
return m;
}
template<>
doc::UserData::Vector get_value_from_lua(lua_State* L, int index)
{
doc::UserData::Vector v;
lua_len(L, index);
int len = lua_tointeger(L, -1);
lua_pop(L, 1);
if (len > 0)
v.reserve(len);
if (index < 0)
--index;
lua_pushnil(L);
while (lua_next(L, index) != 0) {
// TODO we should check that all variants are of the same type
v.push_back(get_value_from_lua<doc::UserData::Variant>(L, -1));
lua_pop(L, 1);
}
return v;
}
} // namespace script
} // namespace app

131
tests/scripts/userdata.lua Normal file
View File

@ -0,0 +1,131 @@
-- Copyright (C) 2022-2023 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)
assert(#spr.properties == 4)
-- Iterate all properties
local t = {}
for k,v in pairs(spr.properties) do
t[k] = v
end
assert(t.a == true)
assert(t.b == 1)
assert(t.c == "hi")
assert(math.abs(t.d - 2.3) < 0.00001)
spr.properties.a = false
assert(spr.properties.a == false)
spr.properties.b = nil
assert(spr.properties.b == nil)
assert(#spr.properties == 3)
spr.properties.v = { 10, 20, 30 }
assert(#spr.properties.v == 3)
assert(spr.properties.v[1] == 10)
assert(spr.properties.v[2] == 20)
assert(spr.properties.v[3] == 30)
spr.properties.u = spr.properties.v -- Copy a property
assert(#spr.properties.u == 3)
assert(spr.properties.u[1] == 10)
assert(spr.properties.u[2] == 20)
assert(spr.properties.u[3] == 30)
spr.properties.m = { a=10,
b="bye",
c={ "a", "b", "c" },
d={ a=1, b="d", c={ x=2, y=0.5, z=0 } },
e=Point(32, 20),
f=Size(40, 80),
g=Rectangle(2, 4, 6, 8) }
assert(#spr.properties == 6)
local m = spr.properties.m
assert(m.a == 10)
assert(m.b == "bye")
assert(m.c[1] == "a")
assert(m.c[2] == "b")
assert(m.c[3] == "c")
assert(m.d.a == 1)
assert(m.d.b == "d")
assert(m.d.c.x == 2)
assert(m.d.c.y == 0.5)
assert(m.d.c.z == 0)
assert(m.e.x == 32)
assert(m.e.y == 20)
assert(m.f.width == 40)
assert(m.f.height == 80)
assert(m.g.x == 2)
assert(m.g.y == 4)
assert(m.g.width == 6)
assert(m.g.height == 8)
-- Set all properties
spr.properties = { a=1000 }
assert(spr.properties.a == 1000)
assert(#spr.properties == 1)
app.undo() -- Test undo/redo
assert(spr.properties.a == false)
assert(#spr.properties == 6)
app.redo()
assert(spr.properties.a == 1000)
assert(#spr.properties == 1)
-- Extension properties
spr.properties.a = 10
spr.properties("ext1").a = 20
assert(spr.properties.a == 10)
assert(spr.properties("ext1").a == 20)
spr.properties("ext1", { a=30, b=35 })
assert(spr.properties("ext1").a == 30)
assert(spr.properties("ext1").b == 35)
app.undo() -- Test undo/redo
assert(spr.properties("ext1").a == 20)
assert(spr.properties("ext1").b == nil)
app.redo()
local ext1 = spr.properties("ext1")
ext1.a = 40
ext1.b = 45
assert(spr.properties("ext1").a == 40)
assert(spr.properties("ext1").b == 45)
spr.properties("", { a=50, b=60 }) -- Empty extension is the user properties
assert(spr.properties.a == 50)
assert(spr.properties.b == 60)
-- Test undo/redo setting/removing one property at a time
spr.properties.a = "hi"
assert(spr.properties.a == "hi")
assert(spr.properties.b == 60)
app.undo()
assert(spr.properties.a == 50)
assert(spr.properties.b == 60)
app.redo()
assert(spr.properties.a == "hi")
assert(spr.properties.b == 60)
assert(#spr.properties == 2)
spr.properties.a = nil
assert(#spr.properties == 1)
assert(spr.properties.a == nil)
assert(spr.properties.b == 60)
app.undo()
assert(#spr.properties == 2)
assert(spr.properties.a == "hi")
assert(spr.properties.b == 60)
end