[lua] Add new AfterAddTile event for tile management plugins

Replaced the App BeforePaintEmptyTilemap event (introduced in
c26351712a21dece24ffee1b0a05ed4686412792) with the new Sprite
AfterAddTile event triggered by draw_image_into_new_tilemap_cel().
This new event handles two cases:

1) When the user paints on an empty tilemap cel, a new tile is
   created, so the tile management plugin can handle AfterAddTile
   to know the existence of this new tile.  (This case was disabled
   with fae3c6566cd87e0b5fcf788deab742b781876c4a, then handled with
   c26351712a21dece24ffee1b0a05ed4686412792, now handled with this
   patch).

2) When we copy & paste cels (or drag & drop cels) in the timeline
   between layers, if a new tile is created, the AfterAddTile is
   called and the plugin can associate tiles with its internal
   information (e.g. folders)

Related to: https://github.com/aseprite/Attachment-System/issues/135
This commit is contained in:
David Capello 2023-07-07 18:40:06 -03:00
parent 9db01bf82f
commit 48275d51c2
10 changed files with 69 additions and 43 deletions

View File

@ -127,12 +127,6 @@ namespace app {
obs::signal<void()> ColorSpaceChange;
obs::signal<void()> PalettePresetsChange;
// Signal triggered for TileManagementPlugin that want to create a
// tile on-the-fly when the active tilemap cel is empty (it's like
// a way to customize the "tilemap/tileset", instead of
// Manual/Auto/Semi, the plugin can offer a custom behavior).
obs::signal<void()> BeforePaintEmptyTilemap;
private:
class CoreModules;
class LoadLanguage;

View File

@ -27,6 +27,7 @@
#include "base/memory.h"
#include "doc/cel.h"
#include "doc/layer.h"
#include "doc/layer_tilemap.h"
#include "doc/mask.h"
#include "doc/mask_boundaries.h"
#include "doc/palette.h"
@ -279,6 +280,17 @@ void Doc::notifyLayerGroupCollapseChange(Layer* layer)
notify_observers<DocEvent&>(&DocObserver::onLayerCollapsedChanged, ev);
}
void Doc::notifyAfterAddTile(LayerTilemap* layer, frame_t frame, tile_index ti)
{
DocEvent ev(this);
ev.sprite(layer->sprite());
ev.layer(layer);
ev.frame(frame);
ev.tileset(layer->tileset());
ev.tileIndex(ti);
notify_observers<DocEvent&>(&DocObserver::onAfterAddTile, ev);
}
bool Doc::isModified() const
{
return !m_undo->isInSavedStateOrSimilar();

View File

@ -32,6 +32,7 @@
namespace doc {
class Cel;
class Layer;
class LayerTilemap;
class Mask;
class Sprite;
class Tileset;
@ -119,6 +120,7 @@ namespace app {
void notifySelectionBoundariesChanged();
void notifyTilesetChanged(Tileset* tileset);
void notifyLayerGroupCollapseChange(Layer* layer);
void notifyAfterAddTile(LayerTilemap* layer, frame_t frame, tile_index ti);
//////////////////////////////////////////////////////////////////////
// File related properties

View File

@ -1,5 +1,5 @@
// Aseprite
// Copyright (C) 2019 Igara Studio S.A.
// Copyright (C) 2019-2023 Igara Studio S.A.
// Copyright (C) 2001-2018 David Capello
//
// This program is distributed under the terms of
@ -9,8 +9,9 @@
#define APP_DOC_EVENT_H_INCLUDED
#pragma once
#include "gfx/region.h"
#include "doc/frame.h"
#include "doc/tile.h"
#include "gfx/region.h"
namespace doc {
class Cel;
@ -55,6 +56,7 @@ namespace app {
doc::Tag* tag() const { return m_tag; }
doc::Slice* slice() const { return m_slice; }
doc::Tileset* tileset() const { return m_tileset; }
doc::tile_index tileIndex() const { return m_ti; }
const gfx::Region& region() const { return m_region; }
doc::WithUserData* withUserData() const { return m_withUserData; }
@ -67,6 +69,7 @@ namespace app {
void tag(doc::Tag* tag) { m_tag = tag; }
void slice(doc::Slice* slice) { m_slice = slice; }
void tileset(doc::Tileset* tileset) { m_tileset = tileset; }
void tileIndex(doc::tile_index ti) { m_ti = ti; }
void region(const gfx::Region& rgn) { m_region = rgn; }
void withUserData(doc::WithUserData* withUserData) { m_withUserData = withUserData; }
@ -88,6 +91,7 @@ namespace app {
doc::Tag* m_tag;
doc::Slice* m_slice;
doc::Tileset* m_tileset;
doc::tile_index m_ti = doc::notile;
gfx::Region m_region;
doc::WithUserData* m_withUserData;

View File

@ -103,6 +103,19 @@ namespace app {
// When the tile management plugin property is changed.
virtual void onTileManagementPluginChange(DocEvent& ev) { }
// When a new tilemap cel/tile is created in certain situations,
// like after drawing in an empty tilemap cel, or when we paste a
// cel into an empty tilemap cel, so a new tile is created.
//
// This is useful for a tile management plugin to know when a new
// tile is added automatically by the editor or the timeline
// through draw_image_into_new_tilemap_cel(), and the plugin can
// do some extra work with it.
//
// Warning: This must be triggered from the UI thread (because
// scripts will listen this event).
virtual void onAfterAddTile(DocEvent& ev) { }
};
} // namespace app

View File

@ -10,6 +10,6 @@
// Increment this value if the scripting API is modified between two
// released Aseprite versions.
#define API_VERSION 24
#define API_VERSION 25
#endif

View File

@ -158,7 +158,6 @@ public:
BgColorChange,
BeforeCommand,
AfterCommand,
BeforePaintEmptyTilemap,
};
AppEvents() {
@ -175,8 +174,6 @@ public:
return BeforeCommand;
else if (std::strcmp(eventName, "aftercommand") == 0)
return AfterCommand;
else if (std::strcmp(eventName, "beforepaintemptytilemap") == 0)
return BeforePaintEmptyTilemap;
else
return Unknown;
}
@ -207,10 +204,6 @@ private:
m_afterCmdConn = ctx->AfterCommandExecution
.connect(&AppEvents::onAfterCommand, this);
break;
case BeforePaintEmptyTilemap:
m_beforePaintConn = app->BeforePaintEmptyTilemap
.connect(&AppEvents::onBeforePaintEmptyTilemap, this);
break;
}
}
@ -231,9 +224,6 @@ private:
case AfterCommand:
m_afterCmdConn.disconnect();
break;
case BeforePaintEmptyTilemap:
m_beforePaintConn.disconnect();
break;
}
}
@ -264,10 +254,6 @@ private:
{ "params", ev.params() } });
}
void onBeforePaintEmptyTilemap() {
call(BeforePaintEmptyTilemap);
}
// ContextObserver impl
void onActiveSiteChange(const Site& site) override {
const bool fromUndo = (site.document() &&
@ -290,6 +276,7 @@ public:
Unknown = -1,
Change,
FilenameChange,
AfterAddTile,
#if ENABLE_REMAP_TILESET_EVENT
RemapTileset,
#endif
@ -319,6 +306,8 @@ public:
return Change;
else if (std::strcmp(eventName, "filenamechange") == 0)
return FilenameChange;
else if (std::strcmp(eventName, "afteraddtile") == 0)
return AfterAddTile;
#if ENABLE_REMAP_TILESET_EVENT
else if (std::strcmp(eventName, "remaptileset") == 0)
return RemapTileset;
@ -341,11 +330,20 @@ public:
call(FilenameChange);
}
void onAfterAddTile(DocEvent& ev) override {
call(AfterAddTile, { { "sprite", ev.sprite() },
{ "layer", ev.layer() },
// This is detected as a "int" type
{ "frameNumber", ev.frame()+1 },
{ "tileset", ev.tileset() },
{ "tileIndex", ev.tileIndex() } });
}
#if ENABLE_REMAP_TILESET_EVENT
void onRemapTileset(DocEvent& ev, const doc::Remap& remap) override {
const bool fromUndo = (ev.document()->transaction() == nullptr);
call(RemapTileset, { { "remap", std::any(&remap) },
{ "tileset", std::any((const doc::Tileset*)ev.tileset()) },
{ "tileset", ev.tileset() },
{ "fromUndo", fromUndo } });
}
#endif

View File

@ -11,9 +11,13 @@
#include "app/script/values.h"
#include "app/pref/preferences.h"
#include "app/script/docobj.h"
#include "app/script/engine.h"
#include "app/script/luacpp.h"
#include "doc/frame.h"
#include "doc/layer.h"
#include "doc/remap.h"
#include "doc/sprite.h"
#include <any>
#include <cstddef>
@ -166,14 +170,24 @@ void push_value_to_lua(lua_State* L, const std::any& value) {
push_value_to_lua(L, *v);
else if (auto v = std::any_cast<int>(&value))
push_value_to_lua(L, *v);
// TODO "doc::frame_t" type matches "int", we could add a doc::Frame()
// kind of object in the future
//else if (auto v = std::any_cast<doc::frame_t>(&value))
// push_sprite_frame(L, nullptr, *v);
else if (auto v = std::any_cast<doc::tile_index>(&value))
push_value_to_lua(L, *v);
else if (auto v = std::any_cast<std::string>(&value))
push_value_to_lua(L, *v);
else if (auto v = std::any_cast<lua_CFunction>(&value))
lua_pushcfunction(L, *v);
else if (auto v = std::any_cast<const doc::Remap*>(&value))
push_value_to_lua(L, **v);
else if (auto v = std::any_cast<const doc::Tileset*>(&value))
else if (auto v = std::any_cast<doc::Tileset*>(&value))
push_tileset(L, *v);
else if (auto v = std::any_cast<doc::Sprite*>(&value))
push_docobj(L, *v);
else if (auto v = std::any_cast<doc::Layer*>(&value))
push_docobj(L, *v);
else if (auto v = std::any_cast<const Params>(&value)) {
push_value_to_lua(L, *v);
}

View File

@ -57,6 +57,7 @@
#include "base/vector2d.h"
#include "doc/grid.h"
#include "doc/layer.h"
#include "doc/layer_tilemap.h"
#include "doc/mask.h"
#include "doc/slice.h"
#include "doc/sprite.h"
@ -642,19 +643,6 @@ DrawingState* StandbyState::startDrawingState(
const DrawingType drawingType,
const tools::Pointer& pointer)
{
if (editor->layer()->isTilemap() &&
editor->sprite()->hasTileManagementPlugin() &&
!editor->layer()->cel(editor->frame())) {
// Trigger event so the plugin can create a cel on-the-fly
App::instance()->BeforePaintEmptyTilemap();
if (!editor->layer() ||
!editor->layer()->cel(editor->frame())) {
return nullptr;
}
// In other case, it looks like PaintEmptyTilemap event created
// the cel...
}
// We need to clear and redraw the brush boundaries after the
// first mouse pressed/point shape if drawn. This is to avoid
// graphical glitches (invalid areas in the ToolLoop's src/dst

View File

@ -1,5 +1,5 @@
// Aseprite
// Copyright (C) 2019-2022 Igara Studio S.A.
// Copyright (C) 2019-2023 Igara Studio S.A.
// Copyright (C) 2001-2018 David Capello
//
// This program is distributed under the terms of
@ -376,6 +376,7 @@ void draw_image_into_new_tilemap_cel(
{
ASSERT(dstLayer->isTilemap());
auto doc = static_cast<Doc*>(dstLayer->sprite()->document());
doc::Tileset* tileset = dstLayer->tileset();
doc::Grid grid = tileset->grid();
grid.origin(gridOrigin);
@ -416,14 +417,15 @@ void draw_image_into_new_tilemap_cel(
cmds->executeAndAdd(addTile);
else {
// TODO a little hacky
addTile->execute(
static_cast<Doc*>(dstLayer->sprite()->document())->context());
addTile->execute(doc->context());
}
tileIndex = addTile->tileIndex();
if (!cmds)
delete addTile;
doc->notifyAfterAddTile(dstLayer, dstCel->frame(), tileIndex);
}
// We were using newTilemap->putPixel() directly but received a
@ -437,8 +439,7 @@ void draw_image_into_new_tilemap_cel(
}
}
static_cast<Doc*>(dstLayer->sprite()->document())
->notifyTilesetChanged(tileset);
doc->notifyTilesetChanged(tileset);
dstCel->data()->setImage(newTilemap, dstLayer);
dstCel->setPosition(grid.tileToCanvas(tilemapBounds.origin()));