[lua] Add undo information when we modify user data properties

This commit is contained in:
David Capello 2023-01-03 11:14:19 -03:00
parent e2024c6edd
commit 645605305f
10 changed files with 392 additions and 18 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
@ -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

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

@ -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 <cstring>
@ -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<doc::UserData::Variant>(L, 3);
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;
}
@ -112,12 +140,23 @@ int Properties_call(lua_State* L)
// 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");
auto& properties = obj->userData().properties(extID);
properties = get_value_from_lua<doc::UserData::Properties>(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<Properties>(L, propObj->id, extID);

View File

@ -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<typename T>
int UserData_set_properties(lua_State* L) {
auto obj = get_docobj<T>(L, 1);
auto& properties = get_WithUserData<T>(obj)->userData().properties();
// TODO add undo information
properties = get_value_from_lua<doc::UserData::Properties>(L, 2);
return 0;
}
template<typename T>
int UserData_set_text(lua_State* L) {
auto obj = get_docobj<T>(L, 1);
@ -106,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

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