Add Edit > Paste Special > Paste As New (Reference) Layer (fix #672, fix #1748)

This commit is contained in:
David Capello 2019-06-27 15:34:56 -03:00
parent 056eb28670
commit ff6538a68e
8 changed files with 144 additions and 82 deletions

View File

@ -39,6 +39,9 @@
<key command="CopyMerged" shortcut="Ctrl+Shift+C" mac="Cmd+Shift+C" /> <key command="CopyMerged" shortcut="Ctrl+Shift+C" mac="Cmd+Shift+C" />
<key command="Paste" shortcut="Ctrl+V" mac="Cmd+V" /> <key command="Paste" shortcut="Ctrl+V" mac="Cmd+V" />
<key command="Paste" shortcut="Shift+Ins" /> <key command="Paste" shortcut="Shift+Ins" />
<key command="NewLayer" shortcut="Ctrl+Shift+V" mac="Cmd+Shift+V">
<param name="fromClipboard" value="true" />
</key>
<key command="Clear" shortcut="Del" /> <key command="Clear" shortcut="Del" />
<key command="Clear" shortcut="Backspace" /> <key command="Clear" shortcut="Backspace" />
<key command="Fill" shortcut="F" /> <key command="Fill" shortcut="F" />
@ -623,6 +626,15 @@
<item command="Copy" text="@.edit_copy" /> <item command="Copy" text="@.edit_copy" />
<item command="CopyMerged" text="@.edit_copy_merged" /> <item command="CopyMerged" text="@.edit_copy_merged" />
<item command="Paste" text="@.edit_paste" /> <item command="Paste" text="@.edit_paste" />
<menu text="@.edit_paste_special">
<item command="NewLayer" text="@.edit_paste_special_new_layer">
<param name="fromClipboard" value="true" />
</item>
<item command="NewLayer" text="@.edit_paste_special_new_ref_layer">
<param name="reference" value="true" />
<param name="fromClipboard" value="true" />
</item>
</menu>
<item command="Clear" text="@.edit_clear" /> <item command="Clear" text="@.edit_clear" />
<separator /> <separator />
<item command="Fill" text="@.edit_fill" /> <item command="Fill" text="@.edit_fill" />
@ -772,7 +784,7 @@
<separator /> <separator />
<item command="NewLayer" text="@.layer_add_reference_layer"> <item command="NewLayer" text="@.layer_add_reference_layer">
<param name="reference" value="true" /> <param name="reference" value="true" />
<param name="from-file" value="true" /> <param name="fromFile" value="true" />
</item> </item>
</menu> </menu>
<menu text="@.frame"> <menu text="@.frame">

View File

@ -337,10 +337,12 @@ NewFrame_NewEmptyFrame = New Empty Frame
NewFrame_DuplicateCels = Duplicate Linked Cels NewFrame_DuplicateCels = Duplicate Linked Cels
NewFrame_DuplicateCelsBlock = Duplicate Cels NewFrame_DuplicateCelsBlock = Duplicate Cels
NewFrameTag = New Frame Tag NewFrameTag = New Frame Tag
NewLayer = New Layer NewLayer = New {}
NewLayer_BeforeActiveLayer = New Layer Below NewLayer_BeforeActiveLayer = New {} Below
NewLayer_Group = New Group NewLayer_Layer = Layer
NewLayer_ReferenceLayer = New Reference Layer NewLayer_Group = Group
NewLayer_ReferenceLayer = Reference Layer
NewLayer_FromClipboard = {} (From Clipboard)
NewSpriteFromSelection = New Sprite From Selection NewSpriteFromSelection = New Sprite From Selection
OpenBrowser = Open Browser OpenBrowser = Open Browser
OpenFile = Open Sprite OpenFile = Open Sprite
@ -716,6 +718,9 @@ edit_cut = Cu&t
edit_copy = &Copy edit_copy = &Copy
edit_copy_merged = Copy Mer&ged edit_copy_merged = Copy Mer&ged
edit_paste = &Paste edit_paste = &Paste
edit_paste_special = Paste Specia&l
edit_paste_special_new_layer = Paste as New &Layer
edit_paste_special_new_ref_layer = Paste as New &Reference Layer
edit_clear = &Delete edit_clear = &Delete
edit_fill = &Fill edit_fill = &Fill
edit_stroke = Stroke edit_stroke = Stroke

View File

@ -13,6 +13,7 @@
#include "app/cmd/move_layer.h" #include "app/cmd/move_layer.h"
#include "app/commands/command.h" #include "app/commands/command.h"
#include "app/commands/commands.h" #include "app/commands/commands.h"
#include "app/commands/new_params.h"
#include "app/commands/params.h" #include "app/commands/params.h"
#include "app/context_access.h" #include "app/context_access.h"
#include "app/doc_api.h" #include "app/doc_api.h"
@ -24,9 +25,11 @@
#include "app/ui/main_window.h" #include "app/ui/main_window.h"
#include "app/ui/status_bar.h" #include "app/ui/status_bar.h"
#include "app/ui_context.h" #include "app/ui_context.h"
#include "app/util/clipboard.h"
#include "doc/layer.h" #include "doc/layer.h"
#include "doc/primitives.h" #include "doc/primitives.h"
#include "doc/sprite.h" #include "doc/sprite.h"
#include "fmt/format.h"
#include "render/dithering.h" #include "render/dithering.h"
#include "render/ordered_dither.h" #include "render/ordered_dither.h"
#include "render/quantization.h" #include "render/quantization.h"
@ -42,7 +45,18 @@ namespace app {
using namespace ui; using namespace ui;
class NewLayerCommand : public Command { struct NewLayerParams : public NewParams {
Param<std::string> name { this, std::string(), "name" };
Param<bool> group { this, false, "group" };
Param<bool> reference { this, false, "reference" };
Param<bool> ask { this, false, "ask" };
Param<bool> fromFile { this, false, { "fromFile", "from-file" } };
Param<bool> fromClipboard { this, false, "fromClipboard" };
Param<bool> top { this, false, "top" };
Param<bool> before { this, false, "before" };
};
class NewLayerCommand : public CommandWithNewParams<NewLayerParams> {
public: public:
enum class Type { Layer, Group, ReferenceLayer }; enum class Type { Layer, Group, ReferenceLayer };
enum class Place { AfterActiveLayer, BeforeActiveLayer, Top }; enum class Place { AfterActiveLayer, BeforeActiveLayer, Top };
@ -58,46 +72,40 @@ protected:
private: private:
std::string getUniqueLayerName(const Sprite* sprite) const; std::string getUniqueLayerName(const Sprite* sprite) const;
int getMaxLayerNum(const Layer* layer) const; int getMaxLayerNum(const Layer* layer) const;
const char* layerPrefix() const; std::string layerPrefix() const;
std::string m_name;
Type m_type; Type m_type;
Place m_place; Place m_place;
bool m_ask;
bool m_fromFile;
}; };
NewLayerCommand::NewLayerCommand() NewLayerCommand::NewLayerCommand()
: Command(CommandId::NewLayer(), CmdRecordableFlag) : CommandWithNewParams(CommandId::NewLayer(), CmdRecordableFlag)
{ {
m_name = "";
m_type = Type::Layer;
m_place = Place::AfterActiveLayer;
m_ask = false;
} }
void NewLayerCommand::onLoadParams(const Params& params) void NewLayerCommand::onLoadParams(const Params& commandParams)
{ {
m_name = params.get("name"); CommandWithNewParams<NewLayerParams>::onLoadParams(commandParams);
m_type = Type::Layer; m_type = Type::Layer;
if (params.get_as<bool>("group")) if (params().group())
m_type = Type::Group; m_type = Type::Group;
else if (params.get_as<bool>("reference")) else if (params().reference())
m_type = Type::ReferenceLayer; m_type = Type::ReferenceLayer;
m_ask = params.get_as<bool>("ask");
m_fromFile = params.get_as<bool>("from-file");
m_place = Place::AfterActiveLayer; m_place = Place::AfterActiveLayer;
if (params.get_as<bool>("top")) if (params().top())
m_place = Place::Top; m_place = Place::Top;
else if (params.get_as<bool>("before")) else if (params().before())
m_place = Place::BeforeActiveLayer; m_place = Place::BeforeActiveLayer;
} }
bool NewLayerCommand::onEnabled(Context* context) bool NewLayerCommand::onEnabled(Context* context)
{ {
return context->checkFlags(ContextFlags::ActiveDocumentIsWritable | return context->checkFlags(ContextFlags::ActiveDocumentIsWritable |
ContextFlags::HasActiveSprite); ContextFlags::HasActiveSprite)
&& (!params().fromClipboard() ||
(clipboard::get_current_format() == clipboard::ClipboardImage));
} }
namespace { namespace {
@ -126,14 +134,14 @@ void NewLayerCommand::onExecute(Context* context)
} }
}); });
// Default name (m_name is a name specified in params) // Default name
if (!m_name.empty()) if (params().name.isSet())
name = m_name; name = params().name();
else else
name = getUniqueLayerName(sprite); name = getUniqueLayerName(sprite);
// Select a file to copy its content // Select a file to copy its content
if (m_fromFile) { if (params().fromFile()) {
Doc* oldActiveDocument = context->activeDocument(); Doc* oldActiveDocument = context->activeDocument();
Command* openFile = Commands::instance()->byId(CommandId::OpenFile()); Command* openFile = Commands::instance()->byId(CommandId::OpenFile());
Params params; Params params;
@ -154,7 +162,7 @@ void NewLayerCommand::onExecute(Context* context)
#ifdef ENABLE_UI #ifdef ENABLE_UI
// If params specify to ask the user about the name... // If params specify to ask the user about the name...
if (m_ask) { if (params().ask()) {
// We open the window to ask the name // We open the window to ask the name
app::gen::NewLayer window; app::gen::NewLayer window;
window.name()->setText(name.c_str()); window.name()->setText(name.c_str());
@ -186,7 +194,7 @@ void NewLayerCommand::onExecute(Context* context)
{ {
Tx tx( Tx tx(
writer.context(), writer.context(),
std::string("New ") + layerPrefix()); fmt::format(Strings::commands_NewLayer(), layerPrefix()));
DocApi api = document->getApi(tx); DocApi api = document->getApi(tx);
bool afterBackground = false; bool afterBackground = false;
@ -319,6 +327,10 @@ void NewLayerCommand::onExecute(Context* context)
} }
} }
} }
// Paste new layer from clipboard
else if (params().fromClipboard() && layer->isImage()) {
clipboard::paste(context, false);
}
tx.commit(); tx.commit();
} }
@ -330,7 +342,7 @@ void NewLayerCommand::onExecute(Context* context)
StatusBar::instance()->invalidate(); StatusBar::instance()->invalidate();
StatusBar::instance()->showTip( StatusBar::instance()->showTip(
1000, "%s '%s' created", 1000, "%s '%s' created",
layerPrefix(), layerPrefix().c_str(),
name.c_str()); name.c_str());
App::instance()->mainWindow()->popTimeline(); App::instance()->mainWindow()->popTimeline();
@ -341,32 +353,20 @@ void NewLayerCommand::onExecute(Context* context)
std::string NewLayerCommand::onGetFriendlyName() const std::string NewLayerCommand::onGetFriendlyName() const
{ {
std::string text; std::string text;
if (m_place == Place::BeforeActiveLayer)
switch (m_type) { text = fmt::format(Strings::commands_NewLayer_BeforeActiveLayer(), layerPrefix());
case Type::Layer: else
if (m_place == Place::BeforeActiveLayer) text = fmt::format(Strings::commands_NewLayer(), layerPrefix());
text = Strings::commands_NewLayer_BeforeActiveLayer(); if (params().fromClipboard())
else text = fmt::format(Strings::commands_NewLayer_FromClipboard(), text);
text = Strings::commands_NewLayer();
break;
case Type::Group:
text = Strings::commands_NewLayer_Group();
break;
case Type::ReferenceLayer:
text = Strings::commands_NewLayer_ReferenceLayer();
break;
}
return text; return text;
} }
std::string NewLayerCommand::getUniqueLayerName(const Sprite* sprite) const std::string NewLayerCommand::getUniqueLayerName(const Sprite* sprite) const
{ {
char buf[1024]; return fmt::format("{} {}",
std::sprintf(buf, "%s %d", layerPrefix(),
layerPrefix(), getMaxLayerNum(sprite->root())+1);
getMaxLayerNum(sprite->root())+1);
return buf;
} }
int NewLayerCommand::getMaxLayerNum(const Layer* layer) const int NewLayerCommand::getMaxLayerNum(const Layer* layer) const
@ -388,12 +388,12 @@ int NewLayerCommand::getMaxLayerNum(const Layer* layer) const
return max; return max;
} }
const char* NewLayerCommand::layerPrefix() const std::string NewLayerCommand::layerPrefix() const
{ {
switch (m_type) { switch (m_type) {
case Type::Layer: return "Layer"; case Type::Layer: return Strings::commands_NewLayer_Layer();
case Type::Group: return "Group"; case Type::Group: return Strings::commands_NewLayer_Group();
case Type::ReferenceLayer: return "Reference Layer"; case Type::ReferenceLayer: return Strings::commands_NewLayer_ReferenceLayer();
} }
return "Unknown"; return "Unknown";
} }

View File

@ -136,9 +136,8 @@ namespace app {
CommandWithNewParams(Args&&...args) CommandWithNewParams(Args&&...args)
: CommandWithNewParamsBase(std::forward<Args>(args)...) { } : CommandWithNewParamsBase(std::forward<Args>(args)...) { }
T& params() { T& params() { return m_params; }
return m_params; const T& params() const { return m_params; }
}
private: private:
void onResetValues() override { void onResetValues() override {

View File

@ -552,7 +552,7 @@ bool DocView::onCopy(Context* ctx)
bool DocView::onPaste(Context* ctx) bool DocView::onPaste(Context* ctx)
{ {
if (clipboard::get_current_format() == clipboard::ClipboardImage) { if (clipboard::get_current_format() == clipboard::ClipboardImage) {
clipboard::paste(); clipboard::paste(ctx, true);
return true; return true;
} }
else else

View File

@ -4017,7 +4017,7 @@ bool Timeline::onCopy(Context* ctx)
bool Timeline::onPaste(Context* ctx) bool Timeline::onPaste(Context* ctx)
{ {
if (clipboard::get_current_format() == clipboard::ClipboardDocRange) { if (clipboard::get_current_format() == clipboard::ClipboardDocRange) {
clipboard::paste(); clipboard::paste(ctx, true);
return true; return true;
} }
else else

View File

@ -12,6 +12,7 @@
#include "app/app.h" #include "app/app.h"
#include "app/cmd/clear_mask.h" #include "app/cmd/clear_mask.h"
#include "app/cmd/deselect_mask.h" #include "app/cmd/deselect_mask.h"
#include "app/cmd/set_mask.h"
#include "app/cmd/trim_cel.h" #include "app/cmd/trim_cel.h"
#include "app/console.h" #include "app/console.h"
#include "app/context_access.h" #include "app/context_access.h"
@ -324,14 +325,16 @@ void copy_palette(const Palette* palette, const doc::PalettePicks& picks)
clipboard_picks = picks; clipboard_picks = picks;
} }
void paste() void paste(Context* ctx, const bool interactive)
{ {
Editor* editor = current_editor; Site site = ctx->activeSite();
if (editor == NULL) Doc* dstDoc = site.document();
if (!dstDoc)
return; return;
Doc* dstDoc = editor->document(); Sprite* dstSpr = site.sprite();
Sprite* dstSpr = dstDoc->sprite(); if (!dstSpr)
return;
switch (get_current_format()) { switch (get_current_format()) {
@ -350,7 +353,7 @@ void paste()
if (!clipboard_image) if (!clipboard_image)
return; return;
Palette* dst_palette = dstSpr->palette(editor->frame()); Palette* dst_palette = dstSpr->palette(site.frame());
// Source image (clipboard or a converted copy to the destination 'imgtype') // Source image (clipboard or a converted copy to the destination 'imgtype')
ImageRef src_image; ImageRef src_image;
@ -362,7 +365,7 @@ void paste()
src_image = clipboard_image; src_image = clipboard_image;
} }
else { else {
RgbMap* dst_rgbmap = dstSpr->rgbMap(editor->frame()); RgbMap* dst_rgbmap = dstSpr->rgbMap(site.frame());
src_image.reset( src_image.reset(
render::convert_pixel_format( render::convert_pixel_format(
@ -373,9 +376,42 @@ void paste()
0)); 0));
} }
// Change to MovingPixelsState if (current_editor && interactive) {
editor->pasteImage(src_image.get(), // Change to MovingPixelsState
clipboard_mask.get()); current_editor->pasteImage(src_image.get(),
clipboard_mask.get());
}
else {
// Non-interactive version (just copy the image to the cel)
Layer* dstLayer = site.layer();
ASSERT(dstLayer);
if (!dstLayer || !dstLayer->isImage())
return;
Tx tx(ctx, "Paste Image");
DocApi api = dstDoc->getApi(tx);
Cel* dstCel = api.addCel(
static_cast<LayerImage*>(dstLayer), site.frame(),
ImageRef(Image::createCopy(src_image.get())));
// Adjust bounds
if (dstCel) {
if (clipboard_mask) {
if (dstLayer->isReference()) {
dstCel->setBounds(dstSpr->bounds());
Mask emptyMask;
tx(new cmd::SetMask(dstDoc, &emptyMask));
}
else {
dstCel->setBounds(clipboard_mask->bounds());
tx(new cmd::SetMask(dstDoc, clipboard_mask.get()));
}
}
}
tx.commit();
}
break; break;
} }
@ -387,8 +423,12 @@ void paste()
switch (srcRange.type()) { switch (srcRange.type()) {
case DocRange::kCels: { case DocRange::kCels: {
Layer* dstLayer = editor->layer(); Layer* dstLayer = site.layer();
frame_t dstFrameFirst = editor->frame(); ASSERT(dstLayer);
if (!dstLayer)
return;
frame_t dstFrameFirst = site.frame();
DocRange dstRange; DocRange dstRange;
dstRange.startRange(dstLayer, dstFrameFirst, DocRange::kCels); dstRange.startRange(dstLayer, dstFrameFirst, DocRange::kCels);
@ -405,11 +445,12 @@ void paste()
// This is the app::copy_range (not clipboard::copy_range()). // This is the app::copy_range (not clipboard::copy_range()).
if (srcRange.layers() == dstRange.layers()) if (srcRange.layers() == dstRange.layers())
app::copy_range(srcDoc, srcRange, dstRange, kDocRangeBefore); app::copy_range(srcDoc, srcRange, dstRange, kDocRangeBefore);
editor->invalidate(); if (current_editor)
current_editor->invalidate(); // TODO check if this is necessary
return; return;
} }
Tx tx(UIContext::instance(), "Paste Cels"); Tx tx(ctx, "Paste Cels");
DocApi api = dstDoc->getApi(tx); DocApi api = dstDoc->getApi(tx);
// Add extra frames if needed // Add extra frames if needed
@ -484,12 +525,13 @@ void paste()
} }
tx.commit(); tx.commit();
editor->invalidate(); if (current_editor)
current_editor->invalidate(); // TODO check if this is necessary
break; break;
} }
case DocRange::kFrames: { case DocRange::kFrames: {
frame_t dstFrame = editor->frame(); frame_t dstFrame = site.frame();
// We use a DocRange operation to copy frames inside // We use a DocRange operation to copy frames inside
// the same sprite. // the same sprite.
@ -501,7 +543,7 @@ void paste()
break; break;
} }
Tx tx(UIContext::instance(), "Paste Frames"); Tx tx(ctx, "Paste Frames");
DocApi api = dstDoc->getApi(tx); DocApi api = dstDoc->getApi(tx);
auto srcLayers = srcSpr->allBrowsableLayers(); auto srcLayers = srcSpr->allBrowsableLayers();
@ -536,7 +578,8 @@ void paste()
} }
tx.commit(); tx.commit();
editor->invalidate(); if (current_editor)
current_editor->invalidate(); // TODO check if this is necessary
break; break;
} }
@ -544,7 +587,7 @@ void paste()
if (srcDoc->colorMode() != dstDoc->colorMode()) if (srcDoc->colorMode() != dstDoc->colorMode())
throw std::runtime_error("You cannot copy layers of document with different color modes"); throw std::runtime_error("You cannot copy layers of document with different color modes");
Tx tx(UIContext::instance(), "Paste Layers"); Tx tx(ctx, "Paste Layers");
DocApi api = dstDoc->getApi(tx); DocApi api = dstDoc->getApi(tx);
// Remove children if their parent is selected so we only // Remove children if their parent is selected so we only
@ -587,7 +630,8 @@ void paste()
} }
tx.commit(); tx.commit();
editor->invalidate(); if (current_editor)
current_editor->invalidate(); // TODO check if this is necessary
break; break;
} }
} }

View File

@ -1,4 +1,5 @@
// Aseprite // Aseprite
// Copyright (C) 2019 Igara Studio S.A.
// Copyright (C) 2001-2018 David Capello // Copyright (C) 2001-2018 David Capello
// //
// This program is distributed under the terms of // This program is distributed under the terms of
@ -22,6 +23,7 @@ namespace doc {
namespace app { namespace app {
class Doc; class Doc;
class Context;
class ContextReader; class ContextReader;
class ContextWriter; class ContextWriter;
class DocRange; class DocRange;
@ -61,7 +63,7 @@ namespace app {
void copy_range(const ContextReader& context, const DocRange& range); void copy_range(const ContextReader& context, const DocRange& range);
void copy_image(const Image* image, const Mask* mask, const Palette* palette); void copy_image(const Image* image, const Mask* mask, const Palette* palette);
void copy_palette(const Palette* palette, const PalettePicks& picks); void copy_palette(const Palette* palette, const PalettePicks& picks);
void paste(); void paste(Context* ctx, const bool interactive);
// Returns true and fills the specified "size"" with the image's // Returns true and fills the specified "size"" with the image's
// size in the clipboard, or return false in case that the clipboard // size in the clipboard, or return false in case that the clipboard