// Aseprite // Copyright (C) 2021 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/context.h" #include "app/context_observer.h" #include "app/doc.h" #include "app/doc_undo.h" #include "app/doc_undo_observer.h" #include "app/script/docobj.h" #include "app/script/engine.h" #include "app/script/luacpp.h" #include "doc/document.h" #include "doc/sprite.h" #include #include #include namespace app { namespace script { using namespace doc; namespace { using EventListener = int; class AppEvents; class SpriteEvents; static std::unique_ptr g_appEvents; static std::map> g_spriteEvents; class Events { public: using EventType = int; Events() { } virtual ~Events() { } Events(const Events&) = delete; Events& operator=(const Events&) = delete; virtual EventType eventType(const char* eventName) const = 0; bool hasListener(EventListener callbackRef) const { for (auto& listeners : m_listeners) { for (EventListener listener : listeners) { if (listener == callbackRef) return true; } } return false; } void add(EventType eventType, EventListener callbackRef) { if (eventType >= m_listeners.size()) m_listeners.resize(eventType+1); auto& listeners = m_listeners[eventType]; listeners.push_back(callbackRef); if (listeners.size() == 1) onAddFirstListener(eventType); } void remove(EventListener callbackRef) { for (int i=0; i= m_listeners.size()) return; script::Engine* engine = App::instance()->scriptEngine(); lua_State* L = engine->luaState(); try { for (EventListener callbackRef : m_listeners[eventType]) { lua_rawgeti(L, LUA_REGISTRYINDEX, callbackRef); if (lua_pcall(L, 0, 0, 0)) { if (const char* s = lua_tostring(L, -1)) engine->consolePrint(s); } } } catch (const std::exception& ex) { engine->consolePrint(ex.what()); } } private: virtual void onAddFirstListener(EventType eventType) = 0; virtual void onRemoveLastListener(EventType eventType) = 0; using EventListeners = std::vector; std::vector m_listeners; }; class AppEvents : public Events , private ContextObserver { public: enum : EventType { Unknown = -1, SiteChange }; AppEvents() { } EventType eventType(const char* eventName) const { if (std::strcmp(eventName, "sitechange") == 0) return SiteChange; else return Unknown; } private: void onAddFirstListener(EventType eventType) override { switch (eventType) { case SiteChange: { App::instance()->context()->add_observer(this); break; } } } void onRemoveLastListener(EventType eventType) override { switch (eventType) { case SiteChange: { App::instance()->context()->remove_observer(this); break; } } } // ContextObserver impl void onActiveSiteChange(const Site& site) override { call(SiteChange); } }; class SpriteEvents : public Events , public DocUndoObserver , public DocObserver { public: enum : EventType { Unknown = -1, Change }; SpriteEvents(const Sprite* sprite) : m_spriteId(sprite->id()) { doc()->add_observer(this); } ~SpriteEvents() { if (m_observingUndo) { doc()->undoHistory()->remove_observer(this); m_observingUndo = false; } doc()->remove_observer(this); } EventType eventType(const char* eventName) const { if (std::strcmp(eventName, "change") == 0) return Change; else return Unknown; } // DocObserver impl void onDestroy(Doc* doc) override { auto it = g_spriteEvents.find(m_spriteId); ASSERT(it != g_spriteEvents.end()); if (it != g_spriteEvents.end()) g_spriteEvents.erase(it); } // DocUndoObserver impl void onAddUndoState(DocUndo* history) override { call(Change); } void onCurrentUndoStateChange(DocUndo* history) override { call(Change); } private: void onAddFirstListener(EventType eventType) override { switch (eventType) { case Change: ASSERT(!m_observingUndo); doc()->undoHistory()->add_observer(this); m_observingUndo = true; break; } } void onRemoveLastListener(EventType eventType) override { switch (eventType) { case Change: { if (m_observingUndo) { doc()->undoHistory()->remove_observer(this); m_observingUndo = false; } break; } } } Doc* doc() { Sprite* sprite = doc::get(m_spriteId); if (sprite) return static_cast(sprite->document()); else return nullptr; } ObjectId m_spriteId; bool m_observingUndo = false; }; int Events_on(lua_State* L) { auto evs = get_ptr(L, 1); const char* eventName = lua_tostring(L, 2); if (!eventName) return 0; const int type = evs->eventType(eventName); if (type < 0) return luaL_error(L, "invalid event name to listen"); if (!lua_isfunction(L, 3)) return luaL_error(L, "second argument must be a function"); // Copy the callback function to add it to the global registry lua_pushvalue(L, 3); int callbackRef = luaL_ref(L, LUA_REGISTRYINDEX); evs->add(type, callbackRef); // Return the callback ref (this is an EventListener easier to use // in Events_off()) lua_pushinteger(L, callbackRef); return 1; } int Events_off(lua_State* L) { auto evs = get_ptr(L, 1); int callbackRef = LUA_REFNIL; // Remove by listener value if (lua_isinteger(L, 2)) { callbackRef = lua_tointeger(L, 2); } // Remove by function reference else if (lua_isfunction(L, 2)) { lua_pushnil(L); while (lua_next(L, LUA_REGISTRYINDEX) != 0) { if (lua_isnumber(L, -2) && lua_isfunction(L, -1)) { int i = lua_tointeger(L, -2); if (// Compare value=function in 2nd argument lua_compare(L, -1, 2, LUA_OPEQ) && // Check that this Events contain this reference evs->hasListener(i)) { callbackRef = i; lua_pop(L, 2); // Pop value and key break; } } lua_pop(L, 1); // Pop the value, leave the key for next lua_next() } } else { return luaL_error(L, "first argument must be a function or a EventListener"); } if (callbackRef != LUA_REFNIL) { evs->remove(callbackRef); luaL_unref(L, LUA_REGISTRYINDEX, callbackRef); } return 0; } // We don't need a __gc (to call ~Events()), because Events instances // will be deleted when the Sprite is deleted or on App Exit const luaL_Reg Events_methods[] = { { "on", Events_on }, { "off", Events_off }, { nullptr, nullptr } }; } // anonymous namespace DEF_MTNAME(Events); void register_events_class(lua_State* L) { REG_CLASS(L, Events); } void push_app_events(lua_State* L) { if (!g_appEvents) { App::instance()->Exit.connect([]{ g_appEvents.reset(); }); g_appEvents.reset(new AppEvents); } push_ptr(L, g_appEvents.get()); } void push_sprite_events(lua_State* L, Sprite* sprite) { ASSERT(sprite); SpriteEvents* spriteEvents; auto it = g_spriteEvents.find(sprite->id()); if (it != g_spriteEvents.end()) spriteEvents = it->second.get(); else { spriteEvents = new SpriteEvents(sprite); g_spriteEvents[sprite->id()].reset(spriteEvents); } push_ptr(L, spriteEvents); } } // namespace script } // namespace app