Add property to disable the standard tilemap UI

Added a Sprite.tileManagementPlugin property for plugins that want to
replace the standard tilemap/tileset interface. This includes a new
external file field in .aseprite files to specify that the sprite
tiles are controlled by a specific plugin.

Once this property is set, the standard tilemap/tileset modes
selectors will disappear and the only way to make then available will
be setting this property to nil/empty string again.

Fix https://github.com/aseprite/Attachment-System/issues/21
This commit is contained in:
David Capello 2023-02-15 18:49:36 -03:00
parent 9475ff67d4
commit 64ce25fae2
15 changed files with 319 additions and 45 deletions

View File

@ -275,8 +275,9 @@ reference external palettes, tilesets, or extensions that make use of extended p
0 - External palette
1 - External tileset
2 - Extension name for properties
3 - Extension name for tile management (can exist one per sprite)
BYTE[7] Reserved (set to zero)
STRING External file name or extension ID
STRING External file name or extension ID (see NOTE.4)
### Mask Chunk (0x2016) DEPRECATED
@ -515,6 +516,15 @@ Details about the ZLIB and DEFLATE compression methods:
* Some extra notes that might help you to decode the data:
http://george.chiramattel.com/blog/2007/09/deflatestream-block-length-does-not-match.html
#### NOTE.4
The extension ID must be a string like `publisher/ExtensionName`, for
example, the [Aseprite Attachment System](https://github.com/aseprite/Attachment-System)
uses `aseprite/Attachment-System`.
This string will be used in a future to automatically link to the
extension URL in the [Aseprite Store](https://github.com/aseprite/aseprite/issues/1928).
## File Format Changes
1. The first change from the first release of the new .ase format,

View File

@ -520,6 +520,7 @@ add_library(app-lib
cmd/set_slice_key.cpp
cmd/set_slice_name.cpp
cmd/set_sprite_size.cpp
cmd/set_sprite_tile_management_plugin.cpp
cmd/set_tag_anidir.cpp
cmd/set_tag_color.cpp
cmd/set_tag_name.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/set_sprite_tile_management_plugin.h"
#include "app/doc.h"
#include "app/doc_event.h"
#include "doc/sprite.h"
namespace app {
namespace cmd {
SetSpriteTileManagementPlugin::SetSpriteTileManagementPlugin(
Sprite* sprite,
const std::string& value)
: WithSprite(sprite)
, m_oldValue(sprite->tileManagementPlugin())
, m_newValue(value)
{
}
void SetSpriteTileManagementPlugin::onExecute()
{
Sprite* spr = sprite();
spr->setTileManagementPlugin(m_newValue);
spr->incrementVersion();
}
void SetSpriteTileManagementPlugin::onUndo()
{
Sprite* spr = sprite();
spr->setTileManagementPlugin(m_oldValue);
spr->incrementVersion();
}
void SetSpriteTileManagementPlugin::onFireNotifications()
{
Sprite* spr = sprite();
Doc* doc = static_cast<Doc*>(spr->document());
DocEvent ev(doc);
ev.sprite(spr);
doc->notify_observers<DocEvent&>(&DocObserver::onTileManagementPluginChange, ev);
}
} // namespace cmd
} // namespace app

View File

@ -0,0 +1,42 @@
// 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_SPRITE_TILE_MANAGEMENT_PLUGIN_H_INCLUDED
#define APP_CMD_SET_SPRITE_TILE_MANAGEMENT_PLUGIN_H_INCLUDED
#pragma once
#include "app/cmd.h"
#include "app/cmd/with_sprite.h"
#include <string>
namespace app {
namespace cmd {
using namespace doc;
class SetSpriteTileManagementPlugin : public Cmd
, public WithSprite {
public:
SetSpriteTileManagementPlugin(Sprite* sprite,
const std::string& value);
protected:
void onExecute() override;
void onUndo() override;
void onFireNotifications() override;
size_t onMemSize() const override {
return sizeof(*this) + m_oldValue.size() + m_newValue.size();
}
private:
std::string m_oldValue;
std::string m_newValue;
};
} // namespace cmd
} // namespace app
#endif

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
//
// This program is distributed under the terms of
@ -99,6 +99,9 @@ namespace app {
// The tileset was remapped (e.g. when tiles are re-ordered).
virtual void onRemapTileset(DocEvent& ev, const doc::Remap& remap) { }
// When the tile management plugin property is changed.
virtual void onTileManagementPluginChange(DocEvent& ev) { }
};
} // namespace app

View File

@ -1375,6 +1375,12 @@ static void ase_file_write_external_files_chunk(
putExtentionIds(slice->userData().propertiesMaps(), ext_files);
}
// Tile management plugin
if (sprite->hasTileManagementPlugin()) {
ext_files.insert(ASE_EXTERNAL_FILE_TILE_MANAGEMENT,
sprite->tileManagementPlugin());
}
// No external files to write
if (ext_files.items().empty())
return;

View File

@ -29,6 +29,7 @@
#include "app/cmd/set_mask.h"
#include "app/cmd/set_pixel_ratio.h"
#include "app/cmd/set_sprite_size.h"
#include "app/cmd/set_sprite_tile_management_plugin.h"
#include "app/cmd/set_transparent_color.h"
#include "app/color_spaces.h"
#include "app/commands/commands.h"
@ -978,6 +979,31 @@ int Sprite_set_pixelRatio(lua_State* L)
return 0;
}
int Sprite_get_tileManagementPlugin(lua_State* L)
{
const auto sprite = get_docobj<Sprite>(L, 1);
if (sprite->hasTileManagementPlugin())
lua_pushstring(L, sprite->tileManagementPlugin().c_str());
else
lua_pushnil(L);
return 1;
}
int Sprite_set_tileManagementPlugin(lua_State* L)
{
auto sprite = get_docobj<Sprite>(L, 1);
std::string value;
if (const char* p = lua_tostring(L, 2))
value = p;
if (sprite->tileManagementPlugin() != value) {
Tx tx;
tx(new cmd::SetSpriteTileManagementPlugin(sprite, value));
tx.commit();
}
return 0;
}
const luaL_Reg Sprite_methods[] = {
{ "__eq", Sprite_eq },
{ "resize", Sprite_resize },
@ -1041,6 +1067,7 @@ const Property Sprite_properties[] = {
{ "properties", UserData_get_properties<Sprite>, UserData_set_properties<Sprite> },
{ "pixelRatio", Sprite_get_pixelRatio, Sprite_set_pixelRatio },
{ "events", Sprite_get_events, nullptr },
{ "tileManagementPlugin", Sprite_get_tileManagementPlugin, Sprite_set_tileManagementPlugin },
{ nullptr, nullptr, nullptr }
};

View File

@ -539,6 +539,12 @@ TilemapMode ColorBar::tilemapMode() const
void ColorBar::setTilemapMode(TilemapMode mode)
{
// With sprites that has a custom tile management plugin, we support
// only editing pixels in manual mode.
if (customTileManagement()) {
mode = TilemapMode::Pixels;
}
if (m_tilemapMode != mode) {
m_tilemapMode = mode;
updateFromTilemapMode();
@ -610,8 +616,14 @@ TilesetMode ColorBar::tilesetMode() const
return TilesetMode::Manual;
}
void ColorBar::setTilesetMode(const TilesetMode mode)
void ColorBar::setTilesetMode(TilesetMode mode)
{
// With sprites that has a custom tile management plugin, we support
// only the manual mode.
if (customTileManagement()) {
mode = TilesetMode::Manual;
}
m_tilesetMode = mode;
for (int i=0; i<3; ++i) {
@ -652,9 +664,16 @@ void ColorBar::onActiveSiteChange(const Site& site)
}
bool isTilemap = false;
if (site.layer())
if (site.layer()) {
isTilemap = site.layer()->isTilemap();
if (isTilemap && customTileManagement()) {
isTilemap = false;
m_tilesetMode = TilesetMode::Manual;
m_tilemapMode = TilemapMode::Pixels;
}
}
if (m_tilesHBox.isVisible() != isTilemap) {
m_tilesHBox.setVisible(isTilemap);
updateFromTilemapMode();
@ -692,6 +711,12 @@ void ColorBar::onTilesetChanged(DocEvent& ev)
m_tilesView.deselect();
}
void ColorBar::onTileManagementPluginChange(DocEvent& ev)
{
// Same update process as in onActiveSiteChange()
onActiveSiteChange(UIContext::instance()->activeSite());
}
void ColorBar::onAppPaletteChange()
{
COLOR_BAR_TRACE("ColorBar::onAppPaletteChange()\n");
@ -2008,7 +2033,15 @@ bool ColorBar::canEditTiles() const
{
const Site site = UIContext::instance()->activeSite();
return (site.layer() &&
site.layer()->isTilemap());
site.layer()->isTilemap() &&
!customTileManagement());
}
bool ColorBar::customTileManagement() const
{
return (m_lastDocument &&
m_lastDocument->sprite() &&
m_lastDocument->sprite()->hasTileManagementPlugin());
}
} // namespace app

View File

@ -1,5 +1,5 @@
// Aseprite
// Copyright (C) 2018-2021 Igara Studio S.A.
// Copyright (C) 2018-2023 Igara Studio S.A.
// Copyright (C) 2001-2018 David Capello
//
// This program is distributed under the terms of
@ -94,7 +94,7 @@ namespace app {
void setTilemapMode(TilemapMode mode);
TilesetMode tilesetMode() const;
void setTilesetMode(const TilesetMode mode);
void setTilesetMode(TilesetMode mode);
ColorButton* fgColorButton() { return &m_fgColor; }
ColorButton* bgColorButton() { return &m_bgColor; }
@ -105,6 +105,7 @@ namespace app {
// DocObserver impl
void onGeneralUpdate(DocEvent& ev) override;
void onTilesetChanged(DocEvent& ev) override;
void onTileManagementPluginChange(DocEvent& ev) override;
// InputChainElement impl
void onNewInputPriority(InputChainElement* element,
@ -190,6 +191,7 @@ namespace app {
void showPalettePresets();
void showPaletteOptions();
bool canEditTiles() const;
bool customTileManagement() const;
void updateFromTilemapMode();
static void fixColorIndex(ColorButton& color);

View File

@ -1,8 +1,9 @@
# Aseprite Document IO Library
# Copyright (c) 2022 Igara Studio S.A.
# Copyright (c) 2022-2023 Igara Studio S.A.
# Copyright (c) 2016-2018 David Capello
add_library(dio-lib
aseprite_common.cpp
aseprite_decoder.cpp
decode_file.cpp
decoder.cpp

View File

@ -0,0 +1,70 @@
// Aseprite Document IO Library
// Copyright (c) 2018-2023 Igara Studio S.A.
// Copyright (c) 2001-2018 David Capello
//
// This file is released under the terms of the MIT license.
// Read LICENSE.txt for more information.
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "dio/aseprite_common.h"
namespace dio {
uint32_t AsepriteExternalFiles::insert(const uint8_t type,
const std::string& filename)
{
auto it = m_toID[type].find(filename);
if (it != m_toID[type].end())
return it->second;
else {
insert(++m_lastid, type, filename);
return m_lastid;
}
}
void AsepriteExternalFiles::insert(uint32_t id,
const uint8_t type,
const std::string& filename)
{
ASSERT(type >= 0 && type < ASE_EXTERNAL_FILE_TYPES);
m_items[id] = Item{ filename, type };
m_toID[type][filename] = id;
}
bool AsepriteExternalFiles::getIDByFilename(const uint8_t type,
const std::string& fn,
uint32_t& id) const
{
ASSERT(type >= 0 && type < ASE_EXTERNAL_FILE_TYPES);
auto it = m_toID[type].find(fn);
if (it == m_toID[type].end())
return false;
id = it->second;
return true;
}
bool AsepriteExternalFiles::getFilenameByID(uint32_t id, std::string& fn) const
{
auto it = m_items.find(id);
if (it == m_items.end())
return false;
fn = it->second.fn;
return true;
}
std::string AsepriteExternalFiles::tileManagementPlugin() const
{
constexpr uint8_t type = ASE_EXTERNAL_FILE_TILE_MANAGEMENT;
auto it = m_toID[type].begin();
if (it != m_toID[type].end())
return it->first;
else
return std::string();
}
} // namespace dio

View File

@ -1,5 +1,5 @@
// Aseprite Document IO Library
// Copyright (c) 2018-2020 Igara Studio S.A.
// Copyright (c) 2018-2023 Igara Studio S.A.
// Copyright (c) 2001-2018 David Capello
//
// This file is released under the terms of the MIT license.
@ -66,7 +66,8 @@
#define ASE_EXTERNAL_FILE_PALETTE 0
#define ASE_EXTERNAL_FILE_TILESET 1
#define ASE_EXTERNAL_FILE_EXTENSION 2
#define ASE_EXTERNAL_FILE_TYPES 3
#define ASE_EXTERNAL_FILE_TILE_MANAGEMENT 3
#define ASE_EXTERNAL_FILE_TYPES 4
namespace dio {
@ -115,53 +116,27 @@ public:
using Items = std::map<uint32_t, Item>;
const Items& items() const {
return m_items;
}
bool empty() const { return m_items.empty(); }
const Items& items() const { return m_items; }
// Adds the external filename with the next autogenerated ID and specified type.
uint32_t insert(const uint8_t type,
const std::string& filename) {
auto it = m_toID[type].find(filename);
if (it != m_toID[type].end())
return it->second;
else {
insert(++m_lastid, type, filename);
return m_lastid;
}
}
const std::string& filename);
// Adds the external filename using the specified ID and type.
void insert(uint32_t id,
const uint8_t type,
const std::string& filename) {
ASSERT(type >= 0 && type < ASE_EXTERNAL_FILE_TYPES);
m_items[id] = Item{ filename, type };
m_toID[type][filename] = id;
}
const std::string& filename);
// Returns true if the given filename exists in the external files
// chunk, and assign its ID in "id"
bool getIDByFilename(const uint8_t type,
const std::string& fn,
uint32_t& id) const {
ASSERT(type >= 0 && type < ASE_EXTERNAL_FILE_TYPES);
uint32_t& id) const;
auto it = m_toID[type].find(fn);
if (it == m_toID[type].end())
return false;
id = it->second;
return true;
}
bool getFilenameByID(uint32_t id, std::string& fn) const;
bool getFilenameByID(uint32_t id, std::string& fn) const {
auto it = m_items.find(id);
if (it == m_items.end())
return false;
fn = it->second.fn;
return true;
}
std::string tileManagementPlugin() const;
private:
uint32_t m_lastid = 0; // ID used to add new items

View File

@ -187,6 +187,13 @@ bool AsepriteDecoder::decode()
case ASE_FILE_CHUNK_EXTERNAL_FILE:
readExternalFiles(extFiles);
// Tile management plugin
if (!extFiles.empty()) {
std::string fn = extFiles.tileManagementPlugin();
if (!fn.empty())
sprite->setTileManagementPlugin(fn);
}
break;
case ASE_FILE_CHUNK_MASK: {

View File

@ -1,5 +1,5 @@
// Aseprite Document Library
// Copyright (C) 2018-2021 Igara Studio S.A.
// Copyright (C) 2018-2023 Igara Studio S.A.
// Copyright (C) 2001-2018 David Capello
//
// This file is released under the terms of the MIT license.
@ -29,6 +29,7 @@
#include "gfx/rect.h"
#include <memory>
#include <string>
#include <vector>
#define DOC_SPRITE_MAX_WIDTH 65535
@ -222,6 +223,18 @@ namespace doc {
bool hasTilesets() const { return m_tilesets != nullptr; }
Tilesets* tilesets() const;
const std::string& tileManagementPlugin() const {
return m_tileManagementPlugin;
}
bool hasTileManagementPlugin() const {
return !m_tileManagementPlugin.empty();
}
void setTileManagementPlugin(const std::string& plugin) {
m_tileManagementPlugin = plugin;
}
private:
Document* m_document;
ImageSpec m_spec;
@ -242,6 +255,15 @@ namespace doc {
// Tilesets
mutable Tilesets* m_tilesets;
// Custom tile management plugin. This can be an ID that specifies
// a custom plugin that will be used to handle tilesets and
// tilemaps for this specific sprite. This property is saved
// inside .aseprite files (ASE_EXTERNAL_FILE_TILE_MANAGEMENT), and
// it's used by the UI to disable the standard tileset/tilemap UX
// (e.g. drag & drop tiles, or TilesetMode::Auto mode, etc.),
// giving the possibility to handle tiles exclusively to a plugin.
std::string m_tileManagementPlugin;
// Disable default constructor and copying
Sprite();
DISABLE_COPYING(Sprite);

View File

@ -1,4 +1,4 @@
-- Copyright (C) 2019-2022 Igara Studio S.A.
-- Copyright (C) 2019-2023 Igara Studio S.A.
-- Copyright (C) 2018 David Capello
--
-- This file is released under the terms of the MIT license.
@ -206,3 +206,25 @@ do
assert(a == a)
assert(a ~= b) -- Compares IDs, not sprite size
end
-- Tile management plugin
do
local fn = "_test_sprite_tileManagementPlugin.aseprite"
local a = Sprite(1, 1)
assert(a.tileManagementPlugin == nil)
a.tileManagementPlugin = "test"
app.undo()
assert(a.tileManagementPlugin == nil)
app.redo()
assert(a.tileManagementPlugin == "test")
a:saveAs(fn)
b = app.open(fn)
assert(b.tileManagementPlugin == "test")
b.tileManagementPlugin = nil
b:saveAs(fn)
c = app.open(fn)
assert(c.tileManagementPlugin == nil)
end