mirror of
https://github.com/aseprite/aseprite.git
synced 2025-03-14 13:21:34 +00:00
Add support for scripts on extensions (#1949)
It still need some work to associate the command to menus easily. Related issues: https://github.com/aseprite/aseprite/issues/1403 https://github.com/aseprite/aseprite/issues/1949 https://github.com/aseprite/api/issues/20 https://community.aseprite.org/t/lua-script-extension-and-menu-api/5085
This commit is contained in:
parent
ba51812c89
commit
6b6b9057bf
2
laf
2
laf
@ -1 +1 @@
|
||||
Subproject commit 98506341d68a318f4916a891ab12351a5db05f98
|
||||
Subproject commit 5c0fa215cd6fba21d9bf230d2da62d2890b21baf
|
@ -170,6 +170,7 @@ if(ENABLE_SCRIPTING)
|
||||
script/palette_class.cpp
|
||||
script/palettes_class.cpp
|
||||
script/pixel_color_object.cpp
|
||||
script/plugin_class.cpp
|
||||
script/point_class.cpp
|
||||
script/preferences_object.cpp
|
||||
script/range_class.cpp
|
||||
|
@ -309,6 +309,12 @@ int App::initialize(const AppOptions& options)
|
||||
}
|
||||
#endif // ENABLE_UI
|
||||
|
||||
#ifdef ENABLE_SCRIPTING
|
||||
// Call the init() function from all plugins
|
||||
LOG("APP: Initializing scripts...\n");
|
||||
extensions().executeInitActions();
|
||||
#endif
|
||||
|
||||
// Process options
|
||||
LOG("APP: Processing options...\n");
|
||||
{
|
||||
@ -395,7 +401,7 @@ void App::run()
|
||||
// we've to print errors).
|
||||
Console console;
|
||||
#ifdef ENABLE_SCRIPTING
|
||||
// Use the app::Console() for script erros
|
||||
// Use the app::Console() for script errors
|
||||
ConsoleEngineDelegate delegate;
|
||||
script::ScopedEngineDelegate setEngineDelegate(m_engine.get(), &delegate);
|
||||
#endif
|
||||
@ -414,6 +420,13 @@ void App::run()
|
||||
}
|
||||
#endif // ENABLE_SCRIPTING
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
#ifdef ENABLE_SCRIPTING
|
||||
// Call the exit() function from all plugins
|
||||
extensions().executeExitActions();
|
||||
#endif
|
||||
|
||||
#ifdef ENABLE_UI
|
||||
if (isGui()) {
|
||||
// Select no document
|
||||
|
@ -1,4 +1,5 @@
|
||||
// Aseprite
|
||||
// Copyright (C) 2020 Igara Studio S.A.
|
||||
// Copyright (C) 2001-2017 David Capello
|
||||
//
|
||||
// This program is distributed under the terms of
|
||||
@ -71,6 +72,15 @@ Commands* Commands::add(Command* command)
|
||||
return this;
|
||||
}
|
||||
|
||||
void Commands::remove(Command* command)
|
||||
{
|
||||
auto lid = base::string_to_lower(command->id());
|
||||
auto it = m_commands.find(lid);
|
||||
ASSERT(it != m_commands.end());
|
||||
if (it != m_commands.end())
|
||||
m_commands.erase(it);
|
||||
}
|
||||
|
||||
void Commands::getAllIds(std::vector<std::string>& ids)
|
||||
{
|
||||
for (auto& it : m_commands)
|
||||
|
@ -1,4 +1,5 @@
|
||||
// Aseprite
|
||||
// Copyright (C) 2020 Igara Studio S.A.
|
||||
// Copyright (C) 2001-2017 David Capello
|
||||
//
|
||||
// This program is distributed under the terms of
|
||||
@ -30,6 +31,9 @@ namespace app {
|
||||
Command* byId(const char* id);
|
||||
Commands* add(Command* command);
|
||||
|
||||
// Remove the command but doesn't delete it
|
||||
void remove(Command* command);
|
||||
|
||||
void getAllIds(std::vector<std::string>& ids);
|
||||
|
||||
private:
|
||||
|
@ -1,4 +1,5 @@
|
||||
// Aseprite
|
||||
// Copyright (C) 2020 Igara Studio S.A.
|
||||
// Copyright (C) 2017-2018 David Capello
|
||||
//
|
||||
// This program is distributed under the terms of
|
||||
@ -10,16 +11,26 @@
|
||||
|
||||
#include "app/extensions.h"
|
||||
|
||||
#include "app/app.h"
|
||||
#include "app/commands/command.h"
|
||||
#include "app/commands/commands.h"
|
||||
#include "app/console.h"
|
||||
#include "app/ini_file.h"
|
||||
#include "app/load_matrix.h"
|
||||
#include "app/pref/preferences.h"
|
||||
#include "app/resource_finder.h"
|
||||
#include "base/exception.h"
|
||||
#include "base/file_content.h"
|
||||
#include "base/file_handle.h"
|
||||
#include "base/fs.h"
|
||||
#include "base/fstream_path.h"
|
||||
#include "render/dithering_matrix.h"
|
||||
|
||||
#ifdef ENABLE_SCRIPTING
|
||||
#include "app/script/engine.h"
|
||||
#include "app/script/luacpp.h"
|
||||
#endif
|
||||
|
||||
#include "archive.h"
|
||||
#include "archive_entry.h"
|
||||
#include "json11.hpp"
|
||||
@ -35,6 +46,7 @@ namespace {
|
||||
|
||||
const char* kPackageJson = "package.json";
|
||||
const char* kInfoJson = "__info.json";
|
||||
const char* kPrefLua = "__pref.lua";
|
||||
const char* kAsepriteDefaultThemeExtensionName = "aseprite-theme";
|
||||
|
||||
class ReadArchive {
|
||||
@ -181,6 +193,9 @@ void write_json_file(const std::string& path, const json11::Json& json)
|
||||
|
||||
} // anonymous namespace
|
||||
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
// Extension
|
||||
|
||||
const render::DitheringMatrix& Extension::DitheringMatrixInfo::matrix() const
|
||||
{
|
||||
if (!m_matrix) {
|
||||
@ -219,6 +234,22 @@ Extension::~Extension()
|
||||
it.second.destroyMatrix();
|
||||
}
|
||||
|
||||
void Extension::executeInitActions()
|
||||
{
|
||||
#ifdef ENABLE_SCRIPTING
|
||||
if (isEnabled() && hasScripts())
|
||||
initScripts();
|
||||
#endif
|
||||
}
|
||||
|
||||
void Extension::executeExitActions()
|
||||
{
|
||||
#ifdef ENABLE_SCRIPTING
|
||||
if (isEnabled() && hasScripts())
|
||||
exitScripts();
|
||||
#endif // ENABLE_SCRIPTING
|
||||
}
|
||||
|
||||
void Extension::addLanguage(const std::string& id, const std::string& path)
|
||||
{
|
||||
m_languages[id] = path;
|
||||
@ -242,6 +273,31 @@ void Extension::addDitheringMatrix(const std::string& id,
|
||||
m_ditheringMatrices[id] = info;
|
||||
}
|
||||
|
||||
#ifdef ENABLE_SCRIPTING
|
||||
|
||||
void Extension::addCommand(const std::string& id)
|
||||
{
|
||||
PluginItem item;
|
||||
item.type = PluginItem::Command;
|
||||
item.id = id;
|
||||
m_plugin.items.push_back(item);
|
||||
}
|
||||
|
||||
void Extension::removeCommand(const std::string& id)
|
||||
{
|
||||
for (auto it=m_plugin.items.begin(); it != m_plugin.items.end(); ) {
|
||||
if (it->type == PluginItem::Command &&
|
||||
it->id == id) {
|
||||
it = m_plugin.items.erase(it);
|
||||
}
|
||||
else {
|
||||
++it;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
bool Extension::canBeDisabled() const
|
||||
{
|
||||
return (m_isEnabled &&
|
||||
@ -267,6 +323,15 @@ void Extension::enable(const bool state)
|
||||
flush_config_file();
|
||||
|
||||
m_isEnabled = state;
|
||||
|
||||
#ifdef ENABLE_SCRIPTING
|
||||
if (m_isEnabled) {
|
||||
initScripts();
|
||||
}
|
||||
else {
|
||||
exitScripts();
|
||||
}
|
||||
#endif // ENABLE_SCRIPTING
|
||||
}
|
||||
|
||||
void Extension::uninstall()
|
||||
@ -314,6 +379,13 @@ void Extension::uninstallFiles(const std::string& path)
|
||||
}
|
||||
}
|
||||
|
||||
// Delete __pref.lua file
|
||||
{
|
||||
std::string fn = base::join_path(path, kPrefLua);
|
||||
if (base::is_file(fn))
|
||||
base::delete_file(fn);
|
||||
}
|
||||
|
||||
std::sort(installedDirs.begin(),
|
||||
installedDirs.end(),
|
||||
[](const std::string& a,
|
||||
@ -369,6 +441,230 @@ bool Extension::isDefaultTheme() const
|
||||
return (name() == kAsepriteDefaultThemeExtensionName);
|
||||
}
|
||||
|
||||
#ifdef ENABLE_SCRIPTING
|
||||
|
||||
// TODO move this to app/script/tableutils.h
|
||||
static void serialize_table(lua_State* L, int idx, std::string& result)
|
||||
{
|
||||
bool first = true;
|
||||
|
||||
result.push_back('{');
|
||||
|
||||
idx = lua_absindex(L, idx);
|
||||
lua_pushnil(L);
|
||||
while (lua_next(L, idx) != 0) {
|
||||
if (first) {
|
||||
first = false;
|
||||
}
|
||||
else {
|
||||
result.push_back(',');
|
||||
}
|
||||
|
||||
// Save key
|
||||
if (lua_type(L, -2) == LUA_TSTRING) {
|
||||
if (const char* k = lua_tostring(L, -2)) {
|
||||
result += k;
|
||||
result.push_back('=');
|
||||
}
|
||||
}
|
||||
|
||||
// Save value
|
||||
switch (lua_type(L, -1)) {
|
||||
case LUA_TNIL:
|
||||
default:
|
||||
result += "nil";
|
||||
break;
|
||||
case LUA_TBOOLEAN:
|
||||
if (lua_toboolean(L, -1))
|
||||
result += "true";
|
||||
else
|
||||
result += "false";
|
||||
break;
|
||||
case LUA_TNUMBER:
|
||||
result += lua_tostring(L, -1);
|
||||
break;
|
||||
case LUA_TSTRING:
|
||||
result.push_back('\"');
|
||||
if (const char* p = lua_tostring(L, -1)) {
|
||||
for (; *p; ++p) {
|
||||
switch (*p) {
|
||||
case '\"':
|
||||
result.push_back('\\');
|
||||
result.push_back('\"');
|
||||
break;
|
||||
case '\\':
|
||||
result.push_back('\\');
|
||||
result.push_back('\\');
|
||||
break;
|
||||
case '\t':
|
||||
result.push_back('\\');
|
||||
result.push_back('t');
|
||||
break;
|
||||
case '\r':
|
||||
result.push_back('\\');
|
||||
result.push_back('n');
|
||||
break;
|
||||
case '\n':
|
||||
result.push_back('\\');
|
||||
result.push_back('n');
|
||||
break;
|
||||
default:
|
||||
result.push_back(*p);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
result.push_back('\"');
|
||||
break;
|
||||
case LUA_TTABLE:
|
||||
serialize_table(L, -1, result);
|
||||
break;
|
||||
}
|
||||
lua_pop(L, 1);
|
||||
}
|
||||
|
||||
result.push_back('}');
|
||||
}
|
||||
|
||||
Extension::ScriptItem::ScriptItem(const std::string& fn)
|
||||
: fn(fn)
|
||||
, exitFunctionRef(LUA_REFNIL)
|
||||
{
|
||||
}
|
||||
|
||||
void Extension::initScripts()
|
||||
{
|
||||
script::Engine* engine = App::instance()->scriptEngine();
|
||||
lua_State* L = engine->luaState();
|
||||
|
||||
// Put a new "plugin" object for init()/exit() functions
|
||||
script::push_plugin(L, this);
|
||||
m_plugin.pluginRef = luaL_ref(L, LUA_REGISTRYINDEX);
|
||||
|
||||
// Read plugin.preferences value
|
||||
{
|
||||
std::string fn = base::join_path(m_path, kPrefLua);
|
||||
if (base::is_file(fn)) {
|
||||
lua_rawgeti(L, LUA_REGISTRYINDEX, m_plugin.pluginRef);
|
||||
if (luaL_loadfile(L, fn.c_str()) == LUA_OK) {
|
||||
if (lua_pcall(L, 0, 1, 0) == LUA_OK) {
|
||||
lua_setfield(L, -2, "preferences");
|
||||
}
|
||||
else {
|
||||
const char* s = lua_tostring(L, -1);
|
||||
if (s) {
|
||||
Console().printf("%s\n", s);
|
||||
}
|
||||
}
|
||||
lua_pop(L, 1);
|
||||
}
|
||||
else {
|
||||
lua_pop(L, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (auto& script : m_plugin.scripts) {
|
||||
// Reset global init()/exit() functions
|
||||
engine->evalCode("init=nil exit=nil");
|
||||
|
||||
// Eval the code of the script (it should define an init() and an exit() function)
|
||||
engine->evalFile(script.fn);
|
||||
|
||||
if (lua_getglobal(L, "exit") == LUA_TFUNCTION) {
|
||||
// Save a reference to the exit() function of this script
|
||||
script.exitFunctionRef = luaL_ref(L, LUA_REGISTRYINDEX);
|
||||
}
|
||||
else {
|
||||
lua_pop(L, 1);
|
||||
}
|
||||
|
||||
// Call the init() function of thi sscript with a Plugin object as first parameter
|
||||
if (lua_getglobal(L, "init") == LUA_TFUNCTION) {
|
||||
// Call init(plugin)
|
||||
lua_rawgeti(L, LUA_REGISTRYINDEX, m_plugin.pluginRef);
|
||||
lua_pcall(L, 1, 1, 0);
|
||||
lua_pop(L, 1);
|
||||
}
|
||||
else {
|
||||
lua_pop(L, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Extension::exitScripts()
|
||||
{
|
||||
script::Engine* engine = App::instance()->scriptEngine();
|
||||
lua_State* L = engine->luaState();
|
||||
|
||||
// Call the exit() function of each script
|
||||
for (auto& script : m_plugin.scripts) {
|
||||
if (script.exitFunctionRef != LUA_REFNIL) {
|
||||
// Get the exit() function, the "plugin" object, and call exit(plugin)
|
||||
lua_rawgeti(L, LUA_REGISTRYINDEX, script.exitFunctionRef);
|
||||
lua_rawgeti(L, LUA_REGISTRYINDEX, m_plugin.pluginRef);
|
||||
lua_pcall(L, 1, 0, 0);
|
||||
|
||||
luaL_unref(L, LUA_REGISTRYINDEX, script.exitFunctionRef);
|
||||
script.exitFunctionRef = LUA_REFNIL;
|
||||
}
|
||||
}
|
||||
|
||||
// Save the plugin preferences object
|
||||
if (m_plugin.pluginRef != LUA_REFNIL) {
|
||||
lua_rawgeti(L, LUA_REGISTRYINDEX, m_plugin.pluginRef);
|
||||
lua_getfield(L, -1, "preferences");
|
||||
|
||||
lua_pushnil(L); // Push a nil key, to ask for the first element of the table
|
||||
bool hasPreferences = (lua_next(L, -2) != 0);
|
||||
if (hasPreferences)
|
||||
lua_pop(L, 2); // Remove the value and the key
|
||||
|
||||
if (hasPreferences) {
|
||||
std::string result = "return ";
|
||||
serialize_table(L, -1, result);
|
||||
base::write_file_content(
|
||||
base::join_path(m_path, kPrefLua),
|
||||
(const uint8_t*)result.c_str(), result.size());
|
||||
}
|
||||
lua_pop(L, 2); // Pop preferences table and plugin
|
||||
|
||||
luaL_unref(L, LUA_REGISTRYINDEX, m_plugin.pluginRef);
|
||||
m_plugin.pluginRef = LUA_REFNIL;
|
||||
}
|
||||
|
||||
// Remove plugin items automatically
|
||||
for (const auto& item : m_plugin.items) {
|
||||
switch (item.type) {
|
||||
case PluginItem::Command: {
|
||||
auto cmds = Commands::instance();
|
||||
auto cmd = cmds->byId(item.id.c_str());
|
||||
ASSERT(cmd);
|
||||
|
||||
if (cmd) {
|
||||
cmds->remove(cmd);
|
||||
|
||||
// This will call ~PluginCommand() and unref the command
|
||||
// onclick callback.
|
||||
delete cmd;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
m_plugin.items.clear();
|
||||
}
|
||||
|
||||
void Extension::addScript(const std::string& fn)
|
||||
{
|
||||
m_plugin.scripts.push_back(ScriptItem(fn));
|
||||
}
|
||||
|
||||
#endif // ENABLE_SCRIPTING
|
||||
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
// Extensions
|
||||
|
||||
Extensions::Extensions()
|
||||
{
|
||||
// Create and get the user extensions directory
|
||||
@ -429,6 +725,22 @@ Extensions::~Extensions()
|
||||
delete ext;
|
||||
}
|
||||
|
||||
void Extensions::executeInitActions()
|
||||
{
|
||||
for (auto& ext : m_extensions)
|
||||
ext->executeInitActions();
|
||||
|
||||
ScriptsChange(nullptr);
|
||||
}
|
||||
|
||||
void Extensions::executeExitActions()
|
||||
{
|
||||
for (auto& ext : m_extensions)
|
||||
ext->executeExitActions();
|
||||
|
||||
ScriptsChange(nullptr);
|
||||
}
|
||||
|
||||
std::string Extensions::languagePath(const std::string& langId)
|
||||
{
|
||||
for (auto ext : m_extensions) {
|
||||
@ -739,6 +1051,37 @@ Extension* Extensions::loadExtension(const std::string& path,
|
||||
extension->addDitheringMatrix(matId, matPath, matName);
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef ENABLE_SCRIPTING
|
||||
// Scripts
|
||||
auto scripts = contributes["scripts"];
|
||||
if (scripts.is_array()) {
|
||||
for (const auto& script : scripts.array_items()) {
|
||||
std::string scriptPath = script["path"].string_value();
|
||||
if (scriptPath.empty())
|
||||
continue;
|
||||
|
||||
// The path must be always relative to the extension
|
||||
scriptPath = base::join_path(path, scriptPath);
|
||||
|
||||
LOG("EXT: New script '%s'\n", scriptPath.c_str());
|
||||
|
||||
extension->addScript(scriptPath);
|
||||
}
|
||||
}
|
||||
// Simple version of packages.json with {... "scripts": "file.lua" ...}
|
||||
else if (scripts.is_string() &&
|
||||
!scripts.string_value().empty()) {
|
||||
std::string scriptPath = scripts.string_value();
|
||||
|
||||
// The path must be always relative to the extension
|
||||
scriptPath = base::join_path(path, scriptPath);
|
||||
|
||||
LOG("EXT: New script '%s'\n", scriptPath.c_str());
|
||||
|
||||
extension->addScript(scriptPath);
|
||||
}
|
||||
#endif // ENABLE_SCRIPTING
|
||||
}
|
||||
|
||||
if (extension)
|
||||
@ -752,6 +1095,7 @@ void Extensions::generateExtensionSignals(Extension* extension)
|
||||
if (extension->hasThemes()) ThemesChange(extension);
|
||||
if (extension->hasPalettes()) PalettesChange(extension);
|
||||
if (extension->hasDitheringMatrices()) DitheringMatricesChange(extension);
|
||||
if (extension->hasScripts()) ScriptsChange(extension);
|
||||
}
|
||||
|
||||
} // namespace app
|
||||
|
@ -1,4 +1,5 @@
|
||||
// Aseprite
|
||||
// Copyright (C) 2020 Igara Studio S.A.
|
||||
// Copyright (C) 2017-2018 David Capello
|
||||
//
|
||||
// This program is distributed under the terms of
|
||||
@ -61,6 +62,9 @@ namespace app {
|
||||
const bool isBuiltinExtension);
|
||||
~Extension();
|
||||
|
||||
void executeInitActions();
|
||||
void executeExitActions();
|
||||
|
||||
const std::string& path() const { return m_path; }
|
||||
const std::string& name() const { return m_name; }
|
||||
const std::string& version() const { return m_version; }
|
||||
@ -76,6 +80,10 @@ namespace app {
|
||||
void addDitheringMatrix(const std::string& id,
|
||||
const std::string& path,
|
||||
const std::string& name);
|
||||
#ifdef ENABLE_SCRIPTING
|
||||
void addCommand(const std::string& id);
|
||||
void removeCommand(const std::string& id);
|
||||
#endif
|
||||
|
||||
bool isEnabled() const { return m_isEnabled; }
|
||||
bool isInstalled() const { return m_isInstalled; }
|
||||
@ -86,6 +94,10 @@ namespace app {
|
||||
bool hasThemes() const { return !m_themes.empty(); }
|
||||
bool hasPalettes() const { return !m_palettes.empty(); }
|
||||
bool hasDitheringMatrices() const { return !m_ditheringMatrices.empty(); }
|
||||
#ifdef ENABLE_SCRIPTING
|
||||
bool hasScripts() const { return !m_plugin.scripts.empty(); }
|
||||
void addScript(const std::string& fn);
|
||||
#endif
|
||||
|
||||
private:
|
||||
void enable(const bool state);
|
||||
@ -93,11 +105,34 @@ namespace app {
|
||||
void uninstallFiles(const std::string& path);
|
||||
bool isCurrentTheme() const;
|
||||
bool isDefaultTheme() const;
|
||||
#ifdef ENABLE_SCRIPTING
|
||||
void initScripts();
|
||||
void exitScripts();
|
||||
#endif
|
||||
|
||||
ExtensionItems m_languages;
|
||||
ExtensionItems m_themes;
|
||||
ExtensionItems m_palettes;
|
||||
std::map<std::string, DitheringMatrixInfo> m_ditheringMatrices;
|
||||
|
||||
#ifdef ENABLE_SCRIPTING
|
||||
struct ScriptItem {
|
||||
std::string fn;
|
||||
int exitFunctionRef;
|
||||
ScriptItem(const std::string& fn);
|
||||
};
|
||||
struct PluginItem {
|
||||
enum Type { Command };
|
||||
Type type;
|
||||
std::string id;
|
||||
};
|
||||
struct Plugin {
|
||||
int pluginRef;
|
||||
std::vector<ScriptItem> scripts;
|
||||
std::vector<PluginItem> items;
|
||||
} m_plugin;
|
||||
#endif
|
||||
|
||||
std::string m_path;
|
||||
std::string m_name;
|
||||
std::string m_version;
|
||||
@ -115,6 +150,9 @@ namespace app {
|
||||
Extensions();
|
||||
~Extensions();
|
||||
|
||||
void executeInitActions();
|
||||
void executeExitActions();
|
||||
|
||||
iterator begin() { return m_extensions.begin(); }
|
||||
iterator end() { return m_extensions.end(); }
|
||||
|
||||
@ -136,6 +174,7 @@ namespace app {
|
||||
obs::signal<void(Extension*)> ThemesChange;
|
||||
obs::signal<void(Extension*)> PalettesChange;
|
||||
obs::signal<void(Extension*)> DitheringMatricesChange;
|
||||
obs::signal<void(Extension*)> ScriptsChange;
|
||||
|
||||
private:
|
||||
Extension* loadExtension(const std::string& path,
|
||||
|
@ -161,6 +161,7 @@ void register_layer_class(lua_State* L);
|
||||
void register_layers_class(lua_State* L);
|
||||
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_range_class(lua_State* L);
|
||||
void register_rect_class(lua_State* L);
|
||||
@ -370,6 +371,7 @@ Engine::Engine()
|
||||
register_layers_class(L);
|
||||
register_palette_class(L);
|
||||
register_palettes_class(L);
|
||||
register_plugin_class(L);
|
||||
register_point_class(L);
|
||||
register_range_class(L);
|
||||
register_rect_class(L);
|
||||
|
@ -15,6 +15,7 @@
|
||||
|
||||
#include "app/color.h"
|
||||
#include "app/commands/params.h"
|
||||
#include "app/extensions.h"
|
||||
#include "doc/brush.h"
|
||||
#include "doc/frame.h"
|
||||
#include "doc/object_ids.h"
|
||||
@ -94,6 +95,8 @@ namespace app {
|
||||
return m_returnCode;
|
||||
}
|
||||
|
||||
lua_State* luaState() { return L; }
|
||||
|
||||
private:
|
||||
void onConsolePrint(const char* text);
|
||||
|
||||
@ -131,6 +134,7 @@ namespace app {
|
||||
void push_images(lua_State* L, const doc::ObjectIds& images);
|
||||
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_sprite_cel(lua_State* L, doc::Cel* cel);
|
||||
void push_sprite_frame(lua_State* L, doc::Sprite* sprite, doc::frame_t frame);
|
||||
void push_sprite_frames(lua_State* L, doc::Sprite* sprite);
|
||||
|
190
src/app/script/plugin_class.cpp
Normal file
190
src/app/script/plugin_class.cpp
Normal file
@ -0,0 +1,190 @@
|
||||
// Aseprite
|
||||
// Copyright (C) 2020 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/app.h"
|
||||
#include "app/commands/command.h"
|
||||
#include "app/commands/commands.h"
|
||||
#include "app/script/engine.h"
|
||||
#include "app/script/luacpp.h"
|
||||
|
||||
namespace app {
|
||||
namespace script {
|
||||
|
||||
namespace {
|
||||
|
||||
struct Plugin {
|
||||
Extension* ext;
|
||||
Plugin(Extension* ext) : ext(ext) { }
|
||||
};
|
||||
|
||||
class PluginCommand : public Command {
|
||||
public:
|
||||
PluginCommand(const std::string& id,
|
||||
const std::string& title,
|
||||
int onclickRef)
|
||||
: Command(id.c_str(), CmdUIOnlyFlag)
|
||||
, m_title(title)
|
||||
, m_onclickRef(onclickRef) {
|
||||
}
|
||||
|
||||
~PluginCommand() {
|
||||
auto app = App::instance();
|
||||
ASSERT(app);
|
||||
if (!app)
|
||||
return;
|
||||
|
||||
if (script::Engine* engine = app->scriptEngine()) {
|
||||
lua_State* L = engine->luaState();
|
||||
luaL_unref(L, LUA_REGISTRYINDEX, m_onclickRef);
|
||||
}
|
||||
}
|
||||
|
||||
protected:
|
||||
std::string onGetFriendlyName() const override {
|
||||
return m_title;
|
||||
}
|
||||
|
||||
void onExecute(Context* context) override {
|
||||
script::Engine* engine = App::instance()->scriptEngine();
|
||||
lua_State* L = engine->luaState();
|
||||
|
||||
lua_rawgeti(L, LUA_REGISTRYINDEX, m_onclickRef);
|
||||
lua_pcall(L, 0, 0, 0);
|
||||
}
|
||||
|
||||
std::string m_title;
|
||||
int m_onclickRef;
|
||||
};
|
||||
|
||||
void deleteCommandIfExistent(Extension* ext, const std::string& id)
|
||||
{
|
||||
auto cmd = Commands::instance()->byId(id.c_str());
|
||||
if (cmd) {
|
||||
Commands::instance()->remove(cmd);
|
||||
ext->removeCommand(id);
|
||||
delete cmd;
|
||||
}
|
||||
}
|
||||
|
||||
int Plugin_gc(lua_State* L)
|
||||
{
|
||||
get_obj<Plugin>(L, 1)->~Plugin();
|
||||
return 0;
|
||||
}
|
||||
|
||||
int Plugin_newCommand(lua_State* L)
|
||||
{
|
||||
auto plugin = get_obj<Plugin>(L, 1);
|
||||
if (lua_istable(L, 2)) {
|
||||
std::string id, title;
|
||||
|
||||
lua_getfield(L, 2, "id");
|
||||
if (const char* s = lua_tostring(L, -1)) {
|
||||
id = s;
|
||||
}
|
||||
lua_pop(L, 1);
|
||||
|
||||
if (id.empty())
|
||||
return luaL_error(L, "Empty id field in plugin:newCommand{ id=... }");
|
||||
|
||||
lua_getfield(L, 2, "title");
|
||||
if (const char* s = lua_tostring(L, -1)) {
|
||||
title = s;
|
||||
}
|
||||
lua_pop(L, 1);
|
||||
|
||||
int type = lua_getfield(L, 2, "onclick");
|
||||
if (type == LUA_TFUNCTION) {
|
||||
int onclickRef = luaL_ref(L, LUA_REGISTRYINDEX);
|
||||
|
||||
// Delete the command if it already exist (e.g. we are
|
||||
// overwriting a previous registered command)
|
||||
deleteCommandIfExistent(plugin->ext, id);
|
||||
|
||||
Commands::instance()->add(new PluginCommand(id, title, onclickRef));
|
||||
plugin->ext->addCommand(id);
|
||||
}
|
||||
else {
|
||||
lua_pop(L, 1);
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int Plugin_deleteCommand(lua_State* L)
|
||||
{
|
||||
std::string id;
|
||||
|
||||
auto plugin = get_obj<Plugin>(L, 1);
|
||||
if (lua_istable(L, 2)) {
|
||||
lua_getfield(L, 2, "id");
|
||||
if (const char* s = lua_tostring(L, -1)) {
|
||||
id = s;
|
||||
}
|
||||
lua_pop(L, 1);
|
||||
}
|
||||
else if (const char* s = lua_tostring(L, 2)) {
|
||||
id = s;
|
||||
}
|
||||
|
||||
if (id.empty())
|
||||
return luaL_error(L, "No command id specified in plugin:deleteCommand()");
|
||||
|
||||
// TODO this can crash if we delete the command from the same command
|
||||
deleteCommandIfExistent(plugin->ext, id);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int Plugin_get_preferences(lua_State* L)
|
||||
{
|
||||
if (!lua_getuservalue(L, 1)) {
|
||||
lua_newtable(L);
|
||||
lua_pushvalue(L, -1);
|
||||
lua_setuservalue(L, 1);
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
int Plugin_set_preferences(lua_State* L)
|
||||
{
|
||||
lua_pushvalue(L, 2);
|
||||
lua_setuservalue(L, 1);
|
||||
return 0;
|
||||
}
|
||||
|
||||
const luaL_Reg Plugin_methods[] = {
|
||||
{ "__gc", Plugin_gc },
|
||||
{ "newCommand", Plugin_newCommand },
|
||||
{ "deleteCommand", Plugin_deleteCommand },
|
||||
{ nullptr, nullptr }
|
||||
};
|
||||
|
||||
const Property Plugin_properties[] = {
|
||||
{ "preferences", Plugin_get_preferences, Plugin_set_preferences },
|
||||
{ nullptr, nullptr, nullptr }
|
||||
};
|
||||
|
||||
} // anonymous namespace
|
||||
|
||||
DEF_MTNAME(Plugin);
|
||||
|
||||
void register_plugin_class(lua_State* L)
|
||||
{
|
||||
REG_CLASS(L, Plugin);
|
||||
REG_CLASS_PROPERTIES(L, Plugin);
|
||||
}
|
||||
|
||||
void push_plugin(lua_State* L, Extension* ext)
|
||||
{
|
||||
push_new<Plugin>(L, ext);
|
||||
}
|
||||
|
||||
} // namespace script
|
||||
} // namespace app
|
@ -1,4 +1,5 @@
|
||||
// Aseprite
|
||||
// Copyright (C) 2020 Igara Studio S.A
|
||||
// Copyright (C) 2001-2015 David Capello
|
||||
//
|
||||
// This program is distributed under the terms of
|
||||
@ -10,22 +11,31 @@
|
||||
|
||||
#include "app/ui/main_menu_bar.h"
|
||||
|
||||
#include "app/app.h"
|
||||
#include "app/app_menus.h"
|
||||
#include "app/extensions.h"
|
||||
#include "base/bind.h"
|
||||
|
||||
namespace app {
|
||||
|
||||
MainMenuBar::MainMenuBar()
|
||||
{
|
||||
Extensions& extensions = App::instance()->extensions();
|
||||
|
||||
m_extScripts =
|
||||
extensions.ScriptsChange.connect(
|
||||
base::Bind<void>(&MainMenuBar::reload, this));
|
||||
}
|
||||
|
||||
void MainMenuBar::reload()
|
||||
{
|
||||
setMenu(NULL);
|
||||
setMenu(nullptr);
|
||||
|
||||
// Reload all menus.
|
||||
AppMenus::instance()->reload();
|
||||
|
||||
setMenu(AppMenus::instance()->getRootMenu());
|
||||
parent()->layout();
|
||||
}
|
||||
|
||||
} // namespace app
|
||||
|
@ -1,4 +1,5 @@
|
||||
// Aseprite
|
||||
// Copyright (C) 2020 Igara Studio S.A
|
||||
// Copyright (C) 2001-2015 David Capello
|
||||
//
|
||||
// This program is distributed under the terms of
|
||||
@ -8,6 +9,7 @@
|
||||
#define APP_UI_MAIN_MENU_BAR_H_INCLUDED
|
||||
#pragma once
|
||||
|
||||
#include "obs/connection.h"
|
||||
#include "ui/menu.h"
|
||||
|
||||
namespace app {
|
||||
@ -17,6 +19,9 @@ namespace app {
|
||||
MainMenuBar();
|
||||
|
||||
void reload();
|
||||
|
||||
private:
|
||||
obs::scoped_connection m_extScripts;
|
||||
};
|
||||
|
||||
} // namespace app
|
||||
|
Loading…
x
Reference in New Issue
Block a user