mirror of
https://github.com/aseprite/aseprite.git
synced 2025-03-29 19:20:09 +00:00
Merge branch 'lua-api-for-properties' (fix aseprite/api#88)
This commit is contained in:
commit
420278d5a4
@ -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
|
||||
|
53
src/app/cmd/remove_user_data_property.cpp
Normal file
53
src/app/cmd/remove_user_data_property.cpp
Normal 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
|
46
src/app/cmd/remove_user_data_property.h
Normal file
46
src/app/cmd/remove_user_data_property.h
Normal 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
|
48
src/app/cmd/set_user_data_properties.cpp
Normal file
48
src/app/cmd/set_user_data_properties.cpp
Normal 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
|
46
src/app/cmd/set_user_data_properties.h
Normal file
46
src/app/cmd/set_user_data_properties.h
Normal 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
|
50
src/app/cmd/set_user_data_property.cpp
Normal file
50
src/app/cmd/set_user_data_property.cpp
Normal 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
|
48
src/app/cmd/set_user_data_property.h
Normal file
48
src/app/cmd/set_user_data_property.h
Normal 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
|
@ -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 }
|
||||
};
|
||||
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
@ -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 }
|
||||
};
|
||||
|
237
src/app/script/properties_class.cpp
Normal file
237
src/app/script/properties_class.cpp
Normal 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
|
@ -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 }
|
||||
};
|
||||
|
||||
|
@ -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 }
|
||||
|
@ -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 }
|
||||
};
|
||||
|
||||
|
@ -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 }
|
||||
};
|
||||
|
||||
|
@ -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
|
||||
|
||||
|
@ -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
131
tests/scripts/userdata.lua
Normal 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
|
Loading…
x
Reference in New Issue
Block a user