From 645605305f3f2c453b6d1032edb3116a9a4acb4e Mon Sep 17 00:00:00 2001 From: David Capello Date: Tue, 3 Jan 2023 11:14:19 -0300 Subject: [PATCH] [lua] Add undo information when we modify user data properties --- src/app/CMakeLists.txt | 5 ++- src/app/cmd/remove_user_data_property.cpp | 53 ++++++++++++++++++++++ src/app/cmd/remove_user_data_property.h | 46 +++++++++++++++++++ src/app/cmd/set_user_data_properties.cpp | 48 ++++++++++++++++++++ src/app/cmd/set_user_data_properties.h | 46 +++++++++++++++++++ src/app/cmd/set_user_data_property.cpp | 50 +++++++++++++++++++++ src/app/cmd/set_user_data_property.h | 48 ++++++++++++++++++++ src/app/script/properties_class.cpp | 55 +++++++++++++++++++---- src/app/script/userdata.h | 29 ++++++++---- tests/scripts/userdata.lua | 30 +++++++++++++ 10 files changed, 392 insertions(+), 18 deletions(-) create mode 100644 src/app/cmd/remove_user_data_property.cpp create mode 100644 src/app/cmd/remove_user_data_property.h create mode 100644 src/app/cmd/set_user_data_properties.cpp create mode 100644 src/app/cmd/set_user_data_properties.h create mode 100644 src/app/cmd/set_user_data_property.cpp create mode 100644 src/app/cmd/set_user_data_property.h diff --git a/src/app/CMakeLists.txt b/src/app/CMakeLists.txt index 67deee7ee..df8a27705 100644 --- a/src/app/CMakeLists.txt +++ b/src/app/CMakeLists.txt @@ -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 @@ -495,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 @@ -528,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 diff --git a/src/app/cmd/remove_user_data_property.cpp b/src/app/cmd/remove_user_data_property.cpp new file mode 100644 index 000000000..835430c64 --- /dev/null +++ b/src/app/cmd/remove_user_data_property.cpp @@ -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(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(m_objId); + obj->userData().properties(m_group)[m_field] = m_oldVariant; + obj->incrementVersion(); +} + +} // namespace cmd +} // namespace app diff --git a/src/app/cmd/remove_user_data_property.h b/src/app/cmd/remove_user_data_property.h new file mode 100644 index 000000000..01fce682a --- /dev/null +++ b/src/app/cmd/remove_user_data_property.h @@ -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 diff --git a/src/app/cmd/set_user_data_properties.cpp b/src/app/cmd/set_user_data_properties.cpp new file mode 100644 index 000000000..f36a7fd12 --- /dev/null +++ b/src/app/cmd/set_user_data_properties.cpp @@ -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(m_objId); + obj->userData().properties(m_group) = m_newProperties; + obj->incrementVersion(); +} + +void SetUserDataProperties::onUndo() +{ + auto obj = doc::get(m_objId); + obj->userData().properties(m_group) = m_oldProperties; + obj->incrementVersion(); +} + +} // namespace cmd +} // namespace app diff --git a/src/app/cmd/set_user_data_properties.h b/src/app/cmd/set_user_data_properties.h new file mode 100644 index 000000000..e123e71e1 --- /dev/null +++ b/src/app/cmd/set_user_data_properties.h @@ -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 diff --git a/src/app/cmd/set_user_data_property.cpp b/src/app/cmd/set_user_data_property.cpp new file mode 100644 index 000000000..36ffe4d87 --- /dev/null +++ b/src/app/cmd/set_user_data_property.cpp @@ -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(m_objId); + obj->userData().properties(m_group)[m_field] = m_newValue; + obj->incrementVersion(); +} + +void SetUserDataProperty::onUndo() +{ + auto obj = doc::get(m_objId); + obj->userData().properties(m_group)[m_field] = m_oldValue; + obj->incrementVersion(); +} + +} // namespace cmd +} // namespace app diff --git a/src/app/cmd/set_user_data_property.h b/src/app/cmd/set_user_data_property.h new file mode 100644 index 000000000..16e7ed093 --- /dev/null +++ b/src/app/cmd/set_user_data_property.h @@ -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 diff --git a/src/app/script/properties_class.cpp b/src/app/script/properties_class.cpp index d62009d63..e164f5ff9 100644 --- a/src/app/script/properties_class.cpp +++ b/src/app/script/properties_class.cpp @@ -8,10 +8,14 @@ #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 @@ -81,21 +85,45 @@ int Properties_newindex(lua_State* L) auto& properties = obj->userData().properties(propObj->extID); - // TODO add undo information switch (lua_type(L, 3)) { case LUA_TNONE: case LUA_TNIL: { - // Just erase the property + // If we assign nil to a property, we just remove the property. + auto it = properties.find(field); - if (it != properties.end()) - properties.erase(it); + 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: - properties[field] = get_value_from_lua(L, 3); + default: { + auto newValue = get_value_from_lua(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; } @@ -112,12 +140,23 @@ int Properties_call(lua_State* L) // object.property("extension", { ...}) // if (lua_istable(L, 3)) { + auto newProperties = get_value_from_lua(L, 3); + 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(extID); - properties = get_value_from_lua(L, 3); + // 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(L, propObj->id, extID); diff --git a/src/app/script/userdata.h b/src/app/script/userdata.h index 75d789435..b7ca94e2b 100644 --- a/src/app/script/userdata.h +++ b/src/app/script/userdata.h @@ -10,6 +10,7 @@ #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" @@ -59,15 +60,6 @@ int UserData_get_properties(lua_State* L) { return 1; } -template -int UserData_set_properties(lua_State* L) { - auto obj = get_docobj(L, 1); - auto& properties = get_WithUserData(obj)->userData().properties(); - // TODO add undo information - properties = get_value_from_lua(L, 2); - return 0; -} - template int UserData_set_text(lua_State* L) { auto obj = get_docobj(L, 1); @@ -106,6 +98,25 @@ int UserData_set_color(lua_State* L) { return 0; } +template +int UserData_set_properties(lua_State* L) { + auto obj = get_docobj(L, 1); + auto wud = get_WithUserData(obj); + auto newProperties = get_value_from_lua(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 diff --git a/tests/scripts/userdata.lua b/tests/scripts/userdata.lua index b5ce39378..14fa943fd 100644 --- a/tests/scripts/userdata.lua +++ b/tests/scripts/userdata.lua @@ -78,6 +78,12 @@ do 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 @@ -88,6 +94,10 @@ do 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 @@ -98,4 +108,24 @@ do 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