mirror of
https://github.com/aseprite/aseprite.git
synced 2024-09-02 21:19:10 +00:00
Preserve cel links when we copy/move a range of cels/frames
This is a common solution in the DocApi wrapper that takes cares of the duplicated cels that we're copying with copyCel() and that are linked in the source, so then it maps the links into the destination. Solving this in DocApi we fixed the problem on the timeline drag-and-drop (doc_range_ops), copy/paste clipboard ranges, and merged the code in NewFrame to duplicate (linked) cels. We've also added 3 variants of Duplicate Cels with this change: - Duplicate Cels: Copies the whole cel block without linking to previous cels. - Duplicate Linked Cels: Copies the whole cel block linking all cels to previous cels. - Duplicate Cels w/Layer Mode: Depending on the layer mode (continuous or not) the cels will be linked or not (this is how "duplicate linked cels" was working before, and was added just in case for backward compatibility). Fixes: http://steamcommunity.com/app/431730/discussions/1/142261352649813598/
This commit is contained in:
parent
4ff77a4d55
commit
75636afd64
17
data/gui.xml
17
data/gui.xml
@ -92,13 +92,13 @@
|
||||
<param name="content" value="empty" />
|
||||
</key>
|
||||
<key command="NewFrame" shortcut="Alt+M">
|
||||
<param name="content" value="cel" />
|
||||
<param name="content" value="cellinked" />
|
||||
</key>
|
||||
<key command="NewFrame" shortcut="Alt+D">
|
||||
<param name="content" value="celblock" />
|
||||
<param name="content" value="celcopies" />
|
||||
</key>
|
||||
<key command="NewFrame" shortcut="Alt+Shift+D">
|
||||
<param name="content" value="cel" />
|
||||
<param name="content" value="cellinked" />
|
||||
</key>
|
||||
<key command="RemoveFrame" shortcut="Alt+C" />
|
||||
<key command="FrameProperties" shortcut="P">
|
||||
@ -496,6 +496,9 @@
|
||||
<key command="AutocropSprite">
|
||||
<param name="byGrid" value="true" />
|
||||
</key>
|
||||
<key command="NewFrame">
|
||||
<param name="content" value="cel" />
|
||||
</key>
|
||||
</commands>
|
||||
|
||||
<!-- Keyboard shortcuts to select tools -->
|
||||
@ -816,10 +819,10 @@
|
||||
<param name="content" value="empty" />
|
||||
</item>
|
||||
<item command="NewFrame" text="@.frame_duplicate_cels">
|
||||
<param name="content" value="celblock" />
|
||||
<param name="content" value="celcopies" />
|
||||
</item>
|
||||
<item command="NewFrame" text="@.frame_duplicate_linked_cels">
|
||||
<param name="content" value="cel" />
|
||||
<param name="content" value="cellinked" />
|
||||
</item>
|
||||
<item command="RemoveFrame" text="@.frame_delete_frame" />
|
||||
<separator />
|
||||
@ -1005,10 +1008,10 @@
|
||||
<item command="LinkCels" text="@.link_cels" />
|
||||
<separator />
|
||||
<item command="NewFrame" text="@main_menu.frame_duplicate_cels">
|
||||
<param name="content" value="celblock" />
|
||||
<param name="content" value="celcopies" />
|
||||
</item>
|
||||
<item command="NewFrame" text="@main_menu.frame_duplicate_linked_cels">
|
||||
<param name="content" value="cel" />
|
||||
<param name="content" value="cellinked" />
|
||||
</item>
|
||||
</menu>
|
||||
|
||||
|
@ -337,8 +337,9 @@ NewFile = New File
|
||||
NewFile_FromClipboard = New File from Clipboard
|
||||
NewFrame = New Frame
|
||||
NewFrame_NewEmptyFrame = New Empty Frame
|
||||
NewFrame_DuplicateCels = Duplicate Linked Cels
|
||||
NewFrame_DuplicateCelsBlock = Duplicate Cels
|
||||
NewFrame_DuplicateCels = Duplicate Cels w/Layer Mode
|
||||
NewFrame_DuplicateCelsCopies = Duplicate Cels
|
||||
NewFrame_DuplicateCelsLinked = Duplicate Linked Cels
|
||||
NewFrameTag = New Frame Tag
|
||||
NewLayer = New {}
|
||||
NewLayer_BeforeActiveLayer = New {} Below
|
||||
|
@ -1,4 +1,5 @@
|
||||
// Aseprite
|
||||
// Copyright (C) 2019 Igara Studio S.A.
|
||||
// Copyright (C) 2001-2016 David Capello
|
||||
//
|
||||
// This program is distributed under the terms of
|
||||
@ -37,16 +38,7 @@ void CopyFrame::onExecute()
|
||||
executeAndAdd(new cmd::AddFrame(sprite, m_newFrame));
|
||||
executeAndAdd(new cmd::SetFrameDuration(sprite, m_newFrame, msecs));
|
||||
|
||||
if (fromFrame >= m_newFrame)
|
||||
++fromFrame;
|
||||
|
||||
for (Layer* layer : sprite->allLayers()) {
|
||||
if (layer->isImage()) {
|
||||
executeAndAdd(new cmd::CopyCel(
|
||||
static_cast<LayerImage*>(layer), fromFrame,
|
||||
static_cast<LayerImage*>(layer), m_newFrame, layer->isContinuous()));
|
||||
}
|
||||
}
|
||||
// Do not copy cels (cmd::CopyCel must be called from outside)
|
||||
}
|
||||
|
||||
} // namespace cmd
|
||||
|
@ -1,5 +1,5 @@
|
||||
// Aseprite
|
||||
// Copyright (C) 2018 Igara Studio S.A.
|
||||
// Copyright (C) 2018-2019 Igara Studio S.A.
|
||||
// Copyright (C) 2001-2018 David Capello
|
||||
//
|
||||
// This program is distributed under the terms of
|
||||
@ -42,7 +42,8 @@ public:
|
||||
DUPLICATE_FRAME,
|
||||
NEW_EMPTY_FRAME,
|
||||
DUPLICATE_CELS,
|
||||
DUPLICATE_CELS_BLOCK,
|
||||
DUPLICATE_CELS_COPIES,
|
||||
DUPLICATE_CELS_LINKED,
|
||||
};
|
||||
|
||||
NewFrameCommand();
|
||||
@ -74,8 +75,11 @@ void NewFrameCommand::onLoadParams(const Params& params)
|
||||
m_content = Content::NEW_EMPTY_FRAME;
|
||||
else if (content == "cel")
|
||||
m_content = Content::DUPLICATE_CELS;
|
||||
else if (content == "celblock")
|
||||
m_content = Content::DUPLICATE_CELS_BLOCK;
|
||||
else if (content == "celblock" ||
|
||||
content == "celcopies")
|
||||
m_content = Content::DUPLICATE_CELS_COPIES;
|
||||
else if (content == "cellinked")
|
||||
m_content = Content::DUPLICATE_CELS_LINKED;
|
||||
}
|
||||
|
||||
bool NewFrameCommand::onEnabled(Context* context)
|
||||
@ -104,13 +108,12 @@ void NewFrameCommand::onExecute(Context* context)
|
||||
break;
|
||||
|
||||
case Content::DUPLICATE_CELS:
|
||||
case Content::DUPLICATE_CELS_BLOCK: {
|
||||
case Content::DUPLICATE_CELS_LINKED:
|
||||
case Content::DUPLICATE_CELS_COPIES: {
|
||||
const Site* site = writer.site();
|
||||
if (site->inTimeline() &&
|
||||
!site->selectedLayers().empty() &&
|
||||
!site->selectedFrames().empty()) {
|
||||
std::map<CelData*, Cel*> relatedCels;
|
||||
|
||||
#if ENABLE_UI
|
||||
auto timeline = App::instance()->timeline();
|
||||
timeline->prepareToMoveRange();
|
||||
@ -129,36 +132,19 @@ void NewFrameCommand::onExecute(Context* context)
|
||||
(site->selectedFrames().lastFrame() -
|
||||
site->selectedFrames().firstFrame() + 1);
|
||||
|
||||
std::unique_ptr<bool> continuous = nullptr;
|
||||
switch (m_content) {
|
||||
case Content::DUPLICATE_CELS_COPIES: continuous.reset(new bool(false)); break;
|
||||
case Content::DUPLICATE_CELS_LINKED: continuous.reset(new bool(true)); break;
|
||||
}
|
||||
|
||||
for (Layer* layer : selLayers) {
|
||||
if (layer->isImage()) {
|
||||
for (frame_t srcFrame : site->selectedFrames().reversed()) {
|
||||
frame_t dstFrame = srcFrame+frameRange;
|
||||
bool continuous;
|
||||
CelData* srcCelData = nullptr;
|
||||
|
||||
if (m_content == Content::DUPLICATE_CELS_BLOCK) {
|
||||
continuous = false;
|
||||
|
||||
Cel* srcCel = static_cast<LayerImage*>(layer)->cel(srcFrame);
|
||||
if (srcCel) {
|
||||
srcCelData = srcCel->data();
|
||||
|
||||
auto it = relatedCels.find(srcCelData);
|
||||
if (it != relatedCels.end()) {
|
||||
srcFrame = it->second->frame();
|
||||
continuous = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
continuous = layer->isContinuous();
|
||||
|
||||
api.copyCel(
|
||||
static_cast<LayerImage*>(layer), srcFrame,
|
||||
static_cast<LayerImage*>(layer), dstFrame, continuous);
|
||||
|
||||
if (srcCelData && !relatedCels[srcCelData])
|
||||
relatedCels[srcCelData] = layer->cel(dstFrame);
|
||||
static_cast<LayerImage*>(layer), dstFrame, continuous.get());
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -168,10 +154,10 @@ void NewFrameCommand::onExecute(Context* context)
|
||||
timeline->moveRange(range);
|
||||
#endif
|
||||
}
|
||||
else {
|
||||
else if (auto layer = static_cast<LayerImage*>(writer.layer())) {
|
||||
api.copyCel(
|
||||
static_cast<LayerImage*>(writer.layer()), writer.frame(),
|
||||
static_cast<LayerImage*>(writer.layer()), writer.frame()+1);
|
||||
layer, writer.frame(),
|
||||
layer, writer.frame()+1);
|
||||
|
||||
#ifdef ENABLE_UI // TODO the active frame should be part of the Site
|
||||
// TODO should we use DocObserver?
|
||||
@ -216,8 +202,11 @@ std::string NewFrameCommand::onGetFriendlyName() const
|
||||
case Content::DUPLICATE_CELS:
|
||||
text = Strings::commands_NewFrame_DuplicateCels();
|
||||
break;
|
||||
case Content::DUPLICATE_CELS_BLOCK:
|
||||
text = Strings::commands_NewFrame_DuplicateCelsBlock();
|
||||
case Content::DUPLICATE_CELS_COPIES:
|
||||
text = Strings::commands_NewFrame_DuplicateCelsCopies();
|
||||
break;
|
||||
case Content::DUPLICATE_CELS_LINKED:
|
||||
text = Strings::commands_NewFrame_DuplicateCelsLinked();
|
||||
break;
|
||||
}
|
||||
|
||||
|
@ -337,19 +337,33 @@ void DocApi::addEmptyFramesTo(Sprite* sprite, frame_t newFrame)
|
||||
}
|
||||
|
||||
void DocApi::copyFrame(Sprite* sprite,
|
||||
const frame_t fromFrame,
|
||||
const frame_t newFrame,
|
||||
const DropFramePlace dropFramePlace,
|
||||
const TagsHandling tagsHandling)
|
||||
frame_t fromFrame,
|
||||
const frame_t newFrame0,
|
||||
const DropFramePlace dropFramePlace,
|
||||
const TagsHandling tagsHandling)
|
||||
{
|
||||
ASSERT(sprite);
|
||||
|
||||
frame_t newFrame =
|
||||
(dropFramePlace == kDropBeforeFrame ? newFrame0:
|
||||
newFrame0+1);
|
||||
|
||||
m_transaction.execute(
|
||||
new cmd::CopyFrame(
|
||||
sprite, fromFrame,
|
||||
(dropFramePlace == kDropBeforeFrame ? newFrame:
|
||||
newFrame+1)));
|
||||
sprite, fromFrame, newFrame));
|
||||
|
||||
adjustFrameTags(sprite, newFrame, +1,
|
||||
if (fromFrame >= newFrame)
|
||||
++fromFrame;
|
||||
|
||||
for (Layer* layer : sprite->allLayers()) {
|
||||
if (layer->isImage()) {
|
||||
copyCel(
|
||||
static_cast<LayerImage*>(layer), fromFrame,
|
||||
static_cast<LayerImage*>(layer), newFrame);
|
||||
}
|
||||
}
|
||||
|
||||
adjustFrameTags(sprite, newFrame0, +1,
|
||||
dropFramePlace,
|
||||
tagsHandling);
|
||||
}
|
||||
@ -553,26 +567,41 @@ void DocApi::moveCel(
|
||||
|
||||
void DocApi::copyCel(
|
||||
LayerImage* srcLayer, frame_t srcFrame,
|
||||
LayerImage* dstLayer, frame_t dstFrame)
|
||||
{
|
||||
copyCel(
|
||||
srcLayer, srcFrame,
|
||||
dstLayer, dstFrame, dstLayer->isContinuous());
|
||||
}
|
||||
|
||||
void DocApi::copyCel(
|
||||
LayerImage* srcLayer, frame_t srcFrame,
|
||||
LayerImage* dstLayer, frame_t dstFrame, bool continuous)
|
||||
LayerImage* dstLayer, frame_t dstFrame,
|
||||
const bool* forceContinuous)
|
||||
{
|
||||
ASSERT(srcLayer != dstLayer || srcFrame != dstFrame);
|
||||
|
||||
if (srcLayer == dstLayer && srcFrame == dstFrame)
|
||||
return; // Nothing to be done
|
||||
|
||||
Cel* srcCel = srcLayer->cel(srcFrame);
|
||||
if (srcCel) {
|
||||
auto it = m_linkedCels.find(srcCel->data());
|
||||
if (it != m_linkedCels.end()) {
|
||||
Cel* dstRelated = it->second;
|
||||
if (dstRelated && dstRelated->layer() == dstLayer) {
|
||||
// Create a link
|
||||
m_transaction.execute(
|
||||
new cmd::CopyCel(
|
||||
dstRelated->layer(), dstRelated->frame(),
|
||||
dstLayer, dstFrame, true));
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
m_transaction.execute(
|
||||
new cmd::CopyCel(
|
||||
srcLayer, srcFrame,
|
||||
dstLayer, dstFrame, continuous));
|
||||
dstLayer, dstFrame,
|
||||
(forceContinuous ? *forceContinuous:
|
||||
dstLayer->isContinuous())));
|
||||
|
||||
if (srcCel && srcCel->links()) {
|
||||
if (Cel* dstCel = dstLayer->cel(dstFrame))
|
||||
m_linkedCels[srcCel->data()] = dstCel;
|
||||
}
|
||||
}
|
||||
|
||||
void DocApi::swapCel(
|
||||
|
@ -17,8 +17,11 @@
|
||||
#include "doc/image_ref.h"
|
||||
#include "gfx/rect.h"
|
||||
|
||||
#include <map>
|
||||
|
||||
namespace doc {
|
||||
class Cel;
|
||||
class CelData;
|
||||
class Image;
|
||||
class Layer;
|
||||
class LayerGroup;
|
||||
@ -55,7 +58,7 @@ namespace app {
|
||||
void addEmptyFrame(Sprite* sprite, frame_t newFrame);
|
||||
void addEmptyFramesTo(Sprite* sprite, frame_t newFrame);
|
||||
void copyFrame(Sprite* sprite,
|
||||
const frame_t fromFrame,
|
||||
frame_t fromFrame,
|
||||
const frame_t newFrame,
|
||||
const DropFramePlace dropFramePlace,
|
||||
const TagsHandling tagsHandling);
|
||||
@ -82,10 +85,8 @@ namespace app {
|
||||
LayerImage* dstLayer, frame_t dstFrame);
|
||||
void copyCel(
|
||||
LayerImage* srcLayer, frame_t srcFrame,
|
||||
LayerImage* dstLayer, frame_t dstFrame);
|
||||
void copyCel(
|
||||
LayerImage* srcLayer, frame_t srcFrame,
|
||||
LayerImage* dstLayer, frame_t dstFrame, bool continuous);
|
||||
LayerImage* dstLayer, frame_t dstFrame,
|
||||
const bool* forceContinuous = nullptr);
|
||||
void swapCel(
|
||||
LayerImage* layer, frame_t frame1, frame_t frame2);
|
||||
|
||||
@ -133,6 +134,10 @@ namespace app {
|
||||
|
||||
Doc* m_document;
|
||||
Transaction& m_transaction;
|
||||
|
||||
// Map used in copyCel() to re-create the original set of linked
|
||||
// cels from the src layers when we copy a block of cels.
|
||||
std::map<CelData*, Cel*> m_linkedCels;
|
||||
};
|
||||
|
||||
} // namespace app
|
||||
|
@ -54,7 +54,6 @@ static void move_or_copy_cels(
|
||||
LayerImage* srcLayer = static_cast<LayerImage*>(srcLayers[i]);
|
||||
|
||||
if (i < dstLayers.size() && dstLayers[i]->isImage()) {
|
||||
LayerImage* srcLayer = static_cast<LayerImage*>(srcLayers[i]);
|
||||
LayerImage* dstLayer = static_cast<LayerImage*>(dstLayers[i]);
|
||||
|
||||
#ifdef TRACE_RANGE_OPS
|
||||
@ -68,6 +67,8 @@ static void move_or_copy_cels(
|
||||
case Copy: api.copyCel(srcLayer, *srcFrame, dstLayer, *dstFrame); break;
|
||||
}
|
||||
}
|
||||
// All cels moved from a image layer and dropped in other kind
|
||||
// of layer (e.g. a group) will be discarded/deleted.
|
||||
else if (op == Move) {
|
||||
api.clearCel(srcLayer, *srcFrame);
|
||||
}
|
||||
|
@ -496,50 +496,17 @@ void paste(Context* ctx, const bool interactive)
|
||||
!dstLayer->isImage())
|
||||
continue;
|
||||
|
||||
// Maps a linked Cel in the original sprite with its
|
||||
// corresponding copy in the new sprite. In this way
|
||||
// we can.
|
||||
std::map<Cel*, Cel*> relatedCels;
|
||||
|
||||
frame_t dstFrame = dstFrameFirst;
|
||||
for (frame_t srcFrame : srcRange.selectedFrames()) {
|
||||
Cel* srcCel = srcLayer->cel(srcFrame);
|
||||
Cel* srcLink = nullptr;
|
||||
|
||||
if (srcCel && srcCel->image()) {
|
||||
bool createCopy = true;
|
||||
|
||||
if (dstLayer->isContinuous() &&
|
||||
srcCel->links()) {
|
||||
srcLink = srcCel->link();
|
||||
if (!srcLink)
|
||||
srcLink = srcCel;
|
||||
|
||||
if (srcLink) {
|
||||
Cel* dstRelated = relatedCels[srcLink];
|
||||
if (dstRelated) {
|
||||
createCopy = false;
|
||||
|
||||
// Create a link from dstRelated
|
||||
api.copyCel(
|
||||
static_cast<LayerImage*>(dstLayer), dstRelated->frame(),
|
||||
static_cast<LayerImage*>(dstLayer), dstFrame);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (createCopy) {
|
||||
api.copyCel(
|
||||
static_cast<LayerImage*>(srcLayer), srcFrame,
|
||||
static_cast<LayerImage*>(dstLayer), dstFrame);
|
||||
|
||||
if (srcLink)
|
||||
relatedCels[srcLink] = dstLayer->cel(dstFrame);
|
||||
}
|
||||
api.copyCel(
|
||||
static_cast<LayerImage*>(srcLayer), srcFrame,
|
||||
static_cast<LayerImage*>(dstLayer), dstFrame);
|
||||
}
|
||||
else {
|
||||
Cel* dstCel = dstLayer->cel(dstFrame);
|
||||
if (dstCel)
|
||||
if (Cel* dstCel = dstLayer->cel(dstFrame))
|
||||
api.clearCel(dstCel);
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user