From 44437e0d1f2285f82ae76ec7da806a738f4645d1 Mon Sep 17 00:00:00 2001 From: David Capello <david@igarastudio.com> Date: Tue, 23 Feb 2021 11:32:08 -0300 Subject: [PATCH] Add new ConvertLayer command ("Layer > Convert To" menu item) Now we have an easy way to convert between: Background <-> Layers <-> Tilemaps Deprecated (they are kept only for backward compatibility): BackgroundFromLayer LayerFromBackground --- data/gui.xml | 28 +++- data/strings/en.ini | 11 +- src/app/CMakeLists.txt | 3 +- src/app/commands/commands_list.h | 3 +- src/app/commands/convert_layer.cpp | 258 +++++++++++++++++++++++++++++ src/app/ui/app_menuitem.cpp | 10 +- 6 files changed, 299 insertions(+), 14 deletions(-) create mode 100644 src/app/commands/convert_layer.cpp diff --git a/data/gui.xml b/data/gui.xml index 5fcfbc638..3f738b245 100644 --- a/data/gui.xml +++ b/data/gui.xml @@ -840,9 +840,17 @@ </item> </menu> <item command="RemoveLayer" text="@.layer_delete_layer" group="layer_remove" /> - <menu text="@.layer_convert"> - <item command="BackgroundFromLayer" text="@.layer_background_from_layer" /> - <item command="LayerFromBackground" text="@.layer_layer_from_background" group="layer_background" /> + <menu text="@.layer_convert_to"> + <item command="ConvertLayer" text="@.layer_convert_to_background"> + <param name="to" value="background" /> + </item> + <item command="ConvertLayer" text="@.layer_convert_to_layer"> + <param name="to" value="layer" /> + </item> + <separator /> + <item command="ConvertLayer" text="@.layer_convert_to_tilemap"> + <param name="to" value="tilemap" /> + </item> </menu> <separator /> <item command="DuplicateLayer" text="@.layer_duplicate" group="layer_duplicate" /> @@ -1018,9 +1026,17 @@ <param name="group" value="true" /> </item> <item command="RemoveLayer" text="@main_menu.layer_delete_layer" /> - <menu text="@main_menu.layer_convert"> - <item command="BackgroundFromLayer" text="@main_menu.layer_background_from_layer" /> - <item command="LayerFromBackground" text="@main_menu.layer_layer_from_background" group="layer_popup_background" /> + <menu text="@main_menu.layer_convert_to"> + <item command="ConvertLayer" text="@main_menu.layer_convert_to_background"> + <param name="to" value="background" /> + </item> + <item command="ConvertLayer" text="@main_menu.layer_convert_to_layer"> + <param name="to" value="layer" /> + </item> + <separator /> + <item command="ConvertLayer" text="@main_menu.layer_convert_to_tilemap"> + <param name="to" value="tilemap" /> + </item> </menu> <separator /> <item command="DuplicateLayer" text="@main_menu.layer_duplicate" /> diff --git a/data/strings/en.ini b/data/strings/en.ini index d5c182551..00e7039d4 100644 --- a/data/strings/en.ini +++ b/data/strings/en.ini @@ -273,6 +273,10 @@ ClearCel = Clear Cel ClearRecentFiles = Clear Recent Files CloseAllFiles = Close All Files CloseFile = Close File +ConvertLayer = Convert Layer +ConvertLayer_Background = Convert to Background +ConvertLayer_Layer = Convert to Transparent Layer +ConvertLayer_Tilemap = Convert to Tilemap ColorCurve = Color Curve ColorQuantization = Create Palette from Current Sprite (Color Quantization) ContiguousFill = Switch Contiguous Fill @@ -925,9 +929,10 @@ layer_new_layer_via_cut = New Layer via Cu&t layer_new_reference_layer_from_file = New &Reference Layer from File layer_new_tilemap_layer = New Tilemap Layer layer_delete_layer = Delete Laye&r -layer_convert = Conv&ert -layer_background_from_layer = &Background from Layer -layer_layer_from_background = &Layer from Background +layer_convert_to = Conv&ert To... +layer_convert_to_background = &Background +layer_convert_to_layer = &Layer +layer_convert_to_tilemap = &Tilemap layer_duplicate = &Duplicate layer_merge_down = &Merge Down layer_flatten = &Flatten diff --git a/src/app/CMakeLists.txt b/src/app/CMakeLists.txt index 33d981ad2..a97dba6e5 100644 --- a/src/app/CMakeLists.txt +++ b/src/app/CMakeLists.txt @@ -1,5 +1,5 @@ # Aseprite -# Copyright (C) 2018-2020 Igara Studio S.A. +# Copyright (C) 2018-2021 Igara Studio S.A. # Copyright (C) 2001-2018 David Capello # Generate a ui::Widget for each widget in a XML file @@ -535,6 +535,7 @@ add_library(app-lib commands/cmd_undo.cpp commands/command.cpp commands/commands.cpp + commands/convert_layer.cpp commands/filters/cmd_brightness_contrast.cpp commands/filters/cmd_color_curve.cpp commands/filters/cmd_convolution_matrix.cpp diff --git a/src/app/commands/commands_list.h b/src/app/commands/commands_list.h index 8e6556ea4..7725c4769 100644 --- a/src/app/commands/commands_list.h +++ b/src/app/commands/commands_list.h @@ -1,5 +1,5 @@ // Aseprite -// Copyright (C) 2018-2020 Igara Studio S.A. +// Copyright (C) 2018-2021 Igara Studio S.A. // Copyright (C) 2001-2018 David Capello // // This program is distributed under the terms of @@ -14,6 +14,7 @@ FOR_EACH_COMMAND(CelOpacity) FOR_EACH_COMMAND(ChangePixelFormat) FOR_EACH_COMMAND(ColorCurve) FOR_EACH_COMMAND(ColorQuantization) +FOR_EACH_COMMAND(ConvertLayer) FOR_EACH_COMMAND(ConvolutionMatrix) FOR_EACH_COMMAND(CopyColors) FOR_EACH_COMMAND(CopyTiles) diff --git a/src/app/commands/convert_layer.cpp b/src/app/commands/convert_layer.cpp new file mode 100644 index 000000000..922a8ede0 --- /dev/null +++ b/src/app/commands/convert_layer.cpp @@ -0,0 +1,258 @@ +// 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/cmd/add_cel.h" +#include "app/cmd/add_layer.h" +#include "app/cmd/add_tileset.h" +#include "app/cmd/background_from_layer.h" +#include "app/cmd/copy_cel.h" +#include "app/cmd/layer_from_background.h" +#include "app/cmd/remove_layer.h" +#include "app/commands/command.h" +#include "app/commands/new_params.h" +#include "app/context_access.h" +#include "app/i18n/strings.h" +#include "app/modules/gui.h" +#include "app/tx.h" +#include "app/util/cel_ops.h" +#include "doc/grid.h" +#include "doc/layer.h" +#include "doc/layer_tilemap.h" +#include "doc/tileset.h" + +#ifdef ENABLE_SCRIPTING +#include "app/script/luacpp.h" +#endif + +#include <map> + +namespace app { + +enum class ConvertLayerParam { None, Background, Layer, Tilemap }; + +template<> +void Param<ConvertLayerParam>::fromString(const std::string& value) +{ + if (value == "background") + setValue(ConvertLayerParam::Background); + else if (value == "layer") + setValue(ConvertLayerParam::Layer); + else if (value == "tilemap") + setValue(ConvertLayerParam::Tilemap); + else + setValue(ConvertLayerParam::None); +} + +#ifdef ENABLE_SCRIPTING +template<> +void Param<ConvertLayerParam>::fromLua(lua_State* L, int index) +{ + fromString(lua_tostring(L, index)); +} +#endif // ENABLE_SCRIPTING + +struct ConvertLayerParams : public NewParams { + Param<ConvertLayerParam> to { this, ConvertLayerParam::None, "to" }; +}; + +class ConvertLayerCommand : public CommandWithNewParams<ConvertLayerParams> { +public: + ConvertLayerCommand(); + +private: + bool onEnabled(Context* context) override; + void onExecute(Context* context) override; + std::string onGetFriendlyName() const override; + + void copyCels(Tx& tx, + Layer* srcLayer, + Layer* newLayer); +}; + +ConvertLayerCommand::ConvertLayerCommand() + : CommandWithNewParams<ConvertLayerParams>(CommandId::ConvertLayer(), CmdRecordableFlag) +{ +} + +bool ConvertLayerCommand::onEnabled(Context* ctx) +{ + if (!ctx->checkFlags(ContextFlags::ActiveDocumentIsWritable | + ContextFlags::HasActiveSprite | + ContextFlags::HasActiveLayer | + ContextFlags::ActiveLayerIsVisible | + ContextFlags::ActiveLayerIsEditable)) + return false; + + // TODO add support to convert reference layers into regular layers or tilemaps + if (ctx->checkFlags(ContextFlags::ActiveLayerIsReference)) + return false; + + switch (params().to()) { + + case ConvertLayerParam::Background: + return + // Doesn't have a background layer + !ctx->checkFlags(ContextFlags::HasBackgroundLayer) && + // Convert a regular layer or tilemap into background + ctx->checkFlags(ContextFlags::ActiveLayerIsImage) && + // TODO add support for background tliemaps + !ctx->checkFlags(ContextFlags::ActiveLayerIsTilemap); + + case ConvertLayerParam::Layer: + return + // Convert a background layer into a transparent layer + ctx->checkFlags(ContextFlags::ActiveLayerIsImage | + ContextFlags::ActiveLayerIsBackground) || + // or a tilemap into a regular layer + ctx->checkFlags(ContextFlags::ActiveLayerIsTilemap); + + case ConvertLayerParam::Tilemap: + return + ctx->checkFlags(ContextFlags::ActiveLayerIsImage) && + !ctx->checkFlags(ContextFlags::ActiveLayerIsTilemap) && + // TODO add support for background tliemaps + !ctx->checkFlags(ContextFlags::ActiveLayerIsBackground); + + default: + return false; + } +} + +void ConvertLayerCommand::onExecute(Context* ctx) +{ + ContextWriter writer(ctx); + Doc* document(writer.document()); + { + Tx tx(ctx, friendlyName()); + Site site = ctx->activeSite(); + Sprite* sprite = site.sprite(); + Layer* srcLayer = site.layer(); + + switch (params().to()) { + + case ConvertLayerParam::Background: + // Layer -> Background + if (srcLayer->isTransparent()) { + ASSERT(srcLayer->isImage()); + tx(new cmd::BackgroundFromLayer(static_cast<LayerImage*>(srcLayer))); + } + // Tilemap -> Background + else if (srcLayer->isTilemap()) { + auto newLayer = new LayerImage(sprite); + newLayer->configureAsBackground(); + newLayer->setName(Strings::commands_NewFile_BackgroundLayer()); + newLayer->setContinuous(srcLayer->isContinuous()); + tx(new cmd::AddLayer(srcLayer->parent(), newLayer, srcLayer)); + + CelList srcCels; + srcLayer->getCels(srcCels); + for (Cel* srcCel : srcCels) + create_cel_copy(tx, srcCel, sprite, newLayer, srcCel->frame()); + + tx(new cmd::RemoveLayer(srcLayer)); + } + break; + + case ConvertLayerParam::Layer: + // Background -> Layer + if (srcLayer->isBackground()) { + tx(new cmd::LayerFromBackground(srcLayer)); + } + // Background -> Tilemap + else if (srcLayer->isTilemap()) { + auto newLayer = new LayerImage(sprite); + newLayer->setName(srcLayer->name()); + newLayer->setContinuous(srcLayer->isContinuous()); + tx(new cmd::AddLayer(srcLayer->parent(), newLayer, srcLayer)); + + copyCels(tx, srcLayer, newLayer); + + tx(new cmd::RemoveLayer(srcLayer)); + } + break; + + case ConvertLayerParam::Tilemap: + // Background or Transparent Layer -> Tilemap + if (srcLayer->isImage() && + (srcLayer->isBackground() || + srcLayer->isTransparent())) { + auto tileset = new Tileset(sprite, site.grid(), 1); + + auto addTileset = new cmd::AddTileset(sprite, tileset); + tx(addTileset); + tileset_index tsi = addTileset->tilesetIndex(); + + auto newLayer = new LayerTilemap(sprite, tsi); + newLayer->setName(srcLayer->name()); + newLayer->setContinuous(srcLayer->isContinuous()); + tx(new cmd::AddLayer(srcLayer->parent(), newLayer, srcLayer)); + + copyCels(tx, srcLayer, newLayer); + + tx(new cmd::RemoveLayer(srcLayer)); + } + break; + } + + tx.commit(); + } + +#ifdef ENABLE_UI + if (ctx->isUIAvailable()) + update_screen_for_document(document); +#endif +} + +void ConvertLayerCommand::copyCels(Tx& tx, + Layer* srcLayer, + Layer* newLayer) +{ + std::map<doc::ObjectId, doc::Cel*> linkedCels; + + CelList srcCels; + srcLayer->getCels(srcCels); + for (Cel* srcCel : srcCels) { + frame_t frame = srcCel->frame(); + + // Keep linked cels in the new layer + Cel* linkedSrcCel = srcCel->link(); + if (linkedSrcCel) { + auto it = linkedCels.find(linkedSrcCel->id()); + if (it != linkedCels.end()) { + tx(new cmd::CopyCel( + newLayer, linkedSrcCel->frame(), + newLayer, frame, true)); + continue; + } + } + + Cel* newCel = create_cel_copy(tx, srcCel, srcLayer->sprite(), newLayer, frame); + tx(new cmd::AddCel(newLayer, newCel)); + + linkedCels[srcCel->id()] = newCel; + } +} + +std::string ConvertLayerCommand::onGetFriendlyName() const +{ + switch (params().to()) { + case ConvertLayerParam::Background: return Strings::commands_ConvertLayer_Background(); break; + case ConvertLayerParam::Layer: return Strings::commands_ConvertLayer_Layer(); break; + case ConvertLayerParam::Tilemap: return Strings::commands_ConvertLayer_Tilemap(); break; + default: return getBaseFriendlyName(); + } +} + +Command* CommandFactory::createConvertLayerCommand() +{ + return new ConvertLayerCommand; +} + +} // namespace app diff --git a/src/app/ui/app_menuitem.cpp b/src/app/ui/app_menuitem.cpp index 52f8964e6..875e49c38 100644 --- a/src/app/ui/app_menuitem.cpp +++ b/src/app/ui/app_menuitem.cpp @@ -1,5 +1,5 @@ // Aseprite -// Copyright (C) 2019-2020 Igara Studio S.A. +// Copyright (C) 2019-2021 Igara Studio S.A. // Copyright (C) 2001-2017 David Capello // // This program is distributed under the terms of @@ -87,8 +87,12 @@ bool AppMenuItem::onProcessMessage(Message* msg) switch (msg->type()) { case kCloseMessage: - // disable the menu (the keyboard shortcuts are processed by "manager_msg_proc") - setEnabled(false); + // Don't disable items with submenus + if (!hasSubmenu()) { + // Disable the menu item (the keyboard shortcuts are processed + // by "manager_msg_proc") + setEnabled(false); + } break; }