New Timeline UI to move frames inside or outside tags (fix #1656)

This commit is contained in:
David Capello 2018-06-12 17:11:10 -03:00
parent 627ef49716
commit b321a75b5b
9 changed files with 540 additions and 64 deletions

View File

@ -1,5 +1,5 @@
// Aseprite
// Copyright (C) 2001-2017 David Capello
// Copyright (C) 2001-2018 David Capello
//
// This program is distributed under the terms of
// the End-User License Agreement for Aseprite.
@ -59,6 +59,8 @@
#include <set>
#define TRACE_DOCAPI(...)
namespace app {
DocumentApi::DocumentApi(Document* document, Transaction& transaction)
@ -181,13 +183,17 @@ void DocumentApi::trimSprite(Sprite* sprite)
void DocumentApi::addFrame(Sprite* sprite, frame_t newFrame)
{
copyFrame(sprite, newFrame-1, newFrame);
copyFrame(sprite, newFrame-1, newFrame,
kDropBeforeFrame,
kDefaultTagsAdjustment);
}
void DocumentApi::addEmptyFrame(Sprite* sprite, frame_t newFrame)
{
m_transaction.execute(new cmd::AddFrame(sprite, newFrame));
adjustFrameTags(sprite, newFrame, +1, false);
adjustFrameTags(sprite, newFrame, +1,
kDropBeforeFrame,
kDefaultTagsAdjustment);
}
void DocumentApi::addEmptyFramesTo(Sprite* sprite, frame_t newFrame)
@ -196,18 +202,31 @@ void DocumentApi::addEmptyFramesTo(Sprite* sprite, frame_t newFrame)
addEmptyFrame(sprite, sprite->totalFrames());
}
void DocumentApi::copyFrame(Sprite* sprite, frame_t fromFrame, frame_t newFrame)
void DocumentApi::copyFrame(Sprite* sprite,
const frame_t fromFrame,
const frame_t newFrame,
const DropFramePlace dropFramePlace,
const TagsHandling tagsHandling)
{
ASSERT(sprite);
m_transaction.execute(new cmd::CopyFrame(sprite, fromFrame, newFrame));
adjustFrameTags(sprite, newFrame, +1, false);
m_transaction.execute(
new cmd::CopyFrame(
sprite, fromFrame,
(dropFramePlace == kDropBeforeFrame ? newFrame:
newFrame+1)));
adjustFrameTags(sprite, newFrame, +1,
dropFramePlace,
tagsHandling);
}
void DocumentApi::removeFrame(Sprite* sprite, frame_t frame)
{
ASSERT(frame >= 0);
m_transaction.execute(new cmd::RemoveFrame(sprite, frame));
adjustFrameTags(sprite, frame, -1, false);
adjustFrameTags(sprite, frame, -1,
kDropBeforeFrame,
kDefaultTagsAdjustment);
}
void DocumentApi::setTotalFrames(Sprite* sprite, frame_t frames)
@ -231,13 +250,20 @@ void DocumentApi::setFrameRangeDuration(Sprite* sprite, frame_t from, frame_t to
m_transaction.execute(new cmd::SetFrameDuration(sprite, fr, msecs));
}
void DocumentApi::moveFrame(Sprite* sprite, frame_t frame, frame_t beforeFrame)
void DocumentApi::moveFrame(Sprite* sprite,
const frame_t frame,
frame_t targetFrame,
const DropFramePlace dropFramePlace,
const TagsHandling tagsHandling)
{
if (frame != beforeFrame &&
frame >= 0 &&
frame <= sprite->lastFrame() &&
beforeFrame >= 0 &&
beforeFrame <= sprite->lastFrame()+1) {
const frame_t beforeFrame =
(dropFramePlace == kDropBeforeFrame ? targetFrame: targetFrame+1);
if (frame >= 0 && frame <= sprite->lastFrame() &&
beforeFrame >= 0 && beforeFrame <= sprite->lastFrame()+1 &&
((frame != beforeFrame) ||
(!sprite->frameTags().empty() &&
tagsHandling != kDontAdjustTags))) {
// Change the frame-lengths.
int frlen_aux = sprite->frameDuration(frame);
@ -254,11 +280,16 @@ void DocumentApi::moveFrame(Sprite* sprite, frame_t frame, frame_t beforeFrame)
setFrameDuration(sprite, beforeFrame, frlen_aux);
}
adjustFrameTags(sprite, frame, -1, true);
adjustFrameTags(sprite, beforeFrame, +1, true);
if (tagsHandling != kDontAdjustTags) {
adjustFrameTags(sprite, frame, -1, dropFramePlace, tagsHandling);
if (targetFrame >= frame)
--targetFrame;
adjustFrameTags(sprite, targetFrame, +1, dropFramePlace, tagsHandling);
}
// Change cel positions.
moveFrameLayer(sprite->root(), frame, beforeFrame);
if (frame != beforeFrame)
moveFrameLayer(sprite->root(), frame, beforeFrame);
}
}
@ -567,8 +598,21 @@ void DocumentApi::setPalette(Sprite* sprite, frame_t frame, const Palette* newPa
}
}
void DocumentApi::adjustFrameTags(Sprite* sprite, frame_t frame, frame_t delta, bool between)
void DocumentApi::adjustFrameTags(Sprite* sprite,
const frame_t frame,
const frame_t delta,
const DropFramePlace dropFramePlace,
const TagsHandling tagsHandling)
{
TRACE_DOCAPI(
"\n adjustFrameTags %s frame %d delta=%d tags=%s:\n",
(dropFramePlace == kDropBeforeFrame ? "before": "after"),
frame, delta,
(tagsHandling == kDefaultTagsAdjustment ? "default":
tagsHandling == kFitInsideTags ? "fit-inside":
"fit-outside"));
ASSERT(tagsHandling != kDontAdjustTags);
// As FrameTag::setFrameRange() changes m_frameTags, we need to use
// a copy of this collection
std::vector<FrameTag*> tags(sprite->frameTags().begin(), sprite->frameTags().end());
@ -577,15 +621,40 @@ void DocumentApi::adjustFrameTags(Sprite* sprite, frame_t frame, frame_t delta,
frame_t from = tag->fromFrame();
frame_t to = tag->toFrame();
TRACE_DOCAPI(" - [from to]=[%d %d] ->", from, to);
// When delta = +1, frame = beforeFrame
if (delta == +1) {
if (frame <= from) { ++from; }
if (frame <= to+1) { ++to; }
switch (tagsHandling) {
case kDefaultTagsAdjustment:
if (frame <= from) { ++from; }
if (frame <= to+1) { ++to; }
break;
case kFitInsideTags:
if (frame < from) { ++from; }
if (frame <= to) { ++to; }
break;
case kFitOutsideTags:
if ((frame < from) ||
(frame == from &&
dropFramePlace == kDropBeforeFrame)) {
++from;
}
if ((frame < to) ||
(frame == to &&
dropFramePlace == kDropBeforeFrame)) {
++to;
}
break;
}
}
else if (delta == -1) {
if (frame < from) { --from; }
if (frame <= to) { --to; }
}
TRACE_DOCAPI(" [%d %d]\n", from, to);
if (from != tag->fromFrame() ||
to != tag->toFrame()) {
if (from > to)

View File

@ -1,5 +1,5 @@
// Aseprite
// Copyright (C) 2001-2017 David Capello
// Copyright (C) 2001-2018 David Capello
//
// This program is distributed under the terms of
// the End-User License Agreement for Aseprite.
@ -8,6 +8,8 @@
#define APP_DOCUMENT_API_H_INCLUDED
#pragma once
#include "app/drop_frame_place.h"
#include "app/tags_handling.h"
#include "doc/algorithm/flip_type.h"
#include "doc/color.h"
#include "doc/frame.h"
@ -49,12 +51,20 @@ namespace app {
void addFrame(Sprite* sprite, frame_t newFrame);
void addEmptyFrame(Sprite* sprite, frame_t newFrame);
void addEmptyFramesTo(Sprite* sprite, frame_t newFrame);
void copyFrame(Sprite* sprite, frame_t fromFrame, frame_t newFrame);
void copyFrame(Sprite* sprite,
const frame_t fromFrame,
const frame_t newFrame,
const DropFramePlace dropFramePlace,
const TagsHandling tagsHandling);
void removeFrame(Sprite* sprite, frame_t frame);
void setTotalFrames(Sprite* sprite, frame_t frames);
void setFrameDuration(Sprite* sprite, frame_t frame, int msecs);
void setFrameRangeDuration(Sprite* sprite, frame_t from, frame_t to, int msecs);
void moveFrame(Sprite* sprite, frame_t frame, frame_t beforeFrame);
void moveFrame(Sprite* sprite,
const frame_t frame,
frame_t targetFrame,
const DropFramePlace dropFramePlace,
const TagsHandling tagsHandling);
// Cels API
void addCel(LayerImage* layer, Cel* cel);
@ -104,7 +114,11 @@ namespace app {
private:
void setCelFramePosition(Cel* cel, frame_t frame);
void moveFrameLayer(Layer* layer, frame_t frame, frame_t beforeFrame);
void adjustFrameTags(Sprite* sprite, frame_t frame, frame_t delta, bool between);
void adjustFrameTags(Sprite* sprite,
const frame_t frame,
const frame_t delta,
const DropFramePlace dropFramePlace,
const TagsHandling tagsHandling);
Document* m_document;
Transaction& m_transaction;

View File

@ -79,7 +79,9 @@ static DocumentRange move_or_copy_frames(
DocumentApi& api, Op op,
Sprite* sprite,
const DocumentRange& srcRange,
frame_t dstFrame)
frame_t dstFrame,
const DocumentRangePlace place,
const TagsHandling tagsHandling)
{
const SelectedFrames& srcFrames = srcRange.selectedFrames();
@ -88,13 +90,20 @@ static DocumentRange move_or_copy_frames(
for (auto srcFrame : srcFrames) {
std::clog << srcFrame << ", ";
}
std::clog << "] => " << dstFrame << "\n";
std::clog << "] "
<< (place == kDocumentRangeBefore ? "before":
place == kDocumentRangeAfter ? "after":
"as first child")
<< " " << dstFrame << "\n";
#endif
auto srcFrame = srcFrames.begin();
auto srcFrameEnd = srcFrames.end();
frame_t srcDelta = 0;
frame_t firstCopiedBlock = 0;
frame_t dstBeforeFrame =
(place == kDocumentRangeBefore ? dstFrame:
dstFrame+1);
for (; srcFrame != srcFrameEnd; ++srcFrame) {
frame_t fromFrame = (*srcFrame)+srcDelta;
@ -102,14 +111,14 @@ static DocumentRange move_or_copy_frames(
switch (op) {
case Move:
if ((*srcFrame) >= dstFrame) {
if ((*srcFrame) >= dstBeforeFrame) {
srcDelta = 0;
fromFrame = *srcFrame;
}
break;
case Copy:
if (fromFrame >= dstFrame-1 && firstCopiedBlock) {
if (fromFrame >= dstBeforeFrame-1 && firstCopiedBlock) {
srcDelta += firstCopiedBlock;
fromFrame += firstCopiedBlock;
firstCopiedBlock = 0;
@ -124,31 +133,41 @@ static DocumentRange move_or_copy_frames(
}
std::clog << "] => "
<< (op == Move ? "Move": "Copy")
<< " " << (*srcFrame) << "+" << (srcDelta) << " -> " << dstFrame << " => ";
<< " " << (*srcFrame) << "+" << (srcDelta)
<< (place == kDocumentRangeBefore ? " before ": " after ")
<< dstFrame << " => ";
#endif
switch (op) {
case Move:
api.moveFrame(sprite, fromFrame, dstFrame);
api.moveFrame(sprite, fromFrame, dstFrame,
(place == kDocumentRangeBefore ? kDropBeforeFrame:
kDropAfterFrame),
tagsHandling);
if (fromFrame < dstFrame-1) {
if (fromFrame < dstBeforeFrame-1) {
--srcDelta;
}
else if (fromFrame > dstFrame-1) {
else if (fromFrame > dstBeforeFrame-1) {
++dstBeforeFrame;
++dstFrame;
}
break;
case Copy:
api.copyFrame(sprite, fromFrame, dstFrame);
api.copyFrame(sprite, fromFrame, dstFrame,
(place == kDocumentRangeBefore ? kDropBeforeFrame:
kDropAfterFrame),
tagsHandling);
if (fromFrame < dstFrame-1) {
if (fromFrame < dstBeforeFrame-1) {
++firstCopiedBlock;
}
else if (fromFrame >= dstFrame-1) {
else if (fromFrame >= dstBeforeFrame-1) {
++srcDelta;
}
++dstBeforeFrame;
++dstFrame;
break;
}
@ -165,8 +184,8 @@ static DocumentRange move_or_copy_frames(
DocumentRange result;
if (!srcRange.selectedLayers().empty())
result.selectLayers(srcRange.selectedLayers());
result.startRange(nullptr, dstFrame-srcFrames.size(), DocumentRange::kFrames);
result.endRange(nullptr, dstFrame-1);
result.startRange(nullptr, dstBeforeFrame-srcFrames.size(), DocumentRange::kFrames);
result.endRange(nullptr, dstBeforeFrame-1);
return result;
}
@ -183,8 +202,12 @@ static bool has_child(LayerGroup* parent, Layer* child)
}
static DocumentRange drop_range_op(
Document* doc, Op op, const DocumentRange& from,
DocumentRangePlace place, DocumentRange to)
Document* doc,
const Op op,
const DocumentRange& from,
DocumentRangePlace place,
const TagsHandling tagsHandling,
DocumentRange to)
{
// Convert "first child" operation into a insert after last child.
LayerGroup* parent = nullptr;
@ -237,8 +260,12 @@ static DocumentRange drop_range_op(
((to.firstFrame() >= from.firstFrame() &&
to.lastFrame() <= from.lastFrame()) ||
(place == kDocumentRangeBefore && to.firstFrame() == from.lastFrame()+1) ||
(place == kDocumentRangeAfter && to.lastFrame() == from.firstFrame()-1)))
(place == kDocumentRangeAfter && to.lastFrame() == from.firstFrame()-1)) &&
// If there are tags, this might not be a no-op
(sprite->frameTags().empty() ||
tagsHandling == kDontAdjustTags)) {
return from;
}
}
break;
@ -352,9 +379,11 @@ static DocumentRange drop_range_op(
if (place == kDocumentRangeBefore)
dstFrame = to.firstFrame();
else
dstFrame = to.lastFrame()+1;
dstFrame = to.lastFrame();
resultRange = move_or_copy_frames(api, op, sprite, from, dstFrame);
resultRange = move_or_copy_frames(api, op, sprite,
from, dstFrame,
place, tagsHandling);
break;
}
@ -434,14 +463,24 @@ static DocumentRange drop_range_op(
return resultRange;
}
DocumentRange move_range(Document* doc, const DocumentRange& from, const DocumentRange& to, DocumentRangePlace place)
DocumentRange move_range(Document* doc,
const DocumentRange& from,
const DocumentRange& to,
const DocumentRangePlace place,
const TagsHandling tagsHandling)
{
return drop_range_op(doc, Move, from, place, DocumentRange(to));
return drop_range_op(doc, Move, from, place,
tagsHandling, DocumentRange(to));
}
DocumentRange copy_range(Document* doc, const DocumentRange& from, const DocumentRange& to, DocumentRangePlace place)
DocumentRange copy_range(Document* doc,
const DocumentRange& from,
const DocumentRange& to,
const DocumentRangePlace place,
const TagsHandling tagsHandling)
{
return drop_range_op(doc, Copy, from, place, DocumentRange(to));
return drop_range_op(doc, Copy, from, place,
tagsHandling, DocumentRange(to));
}
void reverse_frames(Document* doc, const DocumentRange& range)
@ -482,7 +521,9 @@ void reverse_frames(Document* doc, const DocumentRange& range)
for (frame_t frameRev = frameEnd+1;
frameRev > frameBegin;
--frameRev) {
api.moveFrame(sprite, frameBegin, frameRev);
api.moveFrame(sprite, frameBegin, frameRev,
kDropBeforeFrame,
kDontAdjustTags);
}
}
else if (swapCels) {

View File

@ -1,5 +1,5 @@
// Aseprite
// Copyright (C) 2001-2016 David Capello
// Copyright (C) 2001-2018 David Capello
//
// This program is distributed under the terms of
// the End-User License Agreement for Aseprite.
@ -8,6 +8,8 @@
#define APP_DOCUMENT_RANGE_OPS_H_INCLUDED
#pragma once
#include "app/tags_handling.h"
#include <vector>
namespace app {
@ -23,8 +25,16 @@ namespace app {
// These functions returns the new location of the "from" range or
// throws an std::runtime_error() in case that the operation cannot
// be done. (E.g. the background layer cannot be moved.)
DocumentRange move_range(Document* doc, const DocumentRange& from, const DocumentRange& to, DocumentRangePlace place);
DocumentRange copy_range(Document* doc, const DocumentRange& from, const DocumentRange& to, DocumentRangePlace place);
DocumentRange move_range(Document* doc,
const DocumentRange& from,
const DocumentRange& to,
const DocumentRangePlace place,
const TagsHandling tagsHandling = kDefaultTagsAdjustment);
DocumentRange copy_range(Document* doc,
const DocumentRange& from,
const DocumentRange& to,
const DocumentRangePlace place,
const TagsHandling tagsHandling = kDefaultTagsAdjustment);
void reverse_frames(Document* doc, const DocumentRange& range);

View File

@ -1284,3 +1284,242 @@ TEST(DocRangeOps2, DropInsideBugs) {
doc->close();
}
TEST_F(DocRangeOps, MoveRangeWithTags) {
FrameTag* a = new FrameTag(0, 2);
FrameTag* b = new FrameTag(3, 5);
sprite->frameTags().add(a);
sprite->frameTags().add(b);
EXPECT_EQ(0, a->fromFrame());
EXPECT_EQ(2, a->toFrame());
EXPECT_EQ(3, b->fromFrame());
EXPECT_EQ(5, b->toFrame());
// Move before tag "a" (outside the tag)
EXPECT_EQ(frames_range(0, 1),
move_range(doc,
frames_range(1, 2),
frames_range(0),
kDocumentRangeBefore,
kFitOutsideTags));
EXPECT_FRAME_ORDER6(1, 2, 0, 3, 4, 5);
EXPECT_EQ(2, a->fromFrame());
EXPECT_EQ(2, a->toFrame());
EXPECT_EQ(3, b->fromFrame());
EXPECT_EQ(5, b->toFrame());
// Check that undo for frame tags does work.
doc->undoHistory()->undo();
EXPECT_EQ(0, a->fromFrame());
EXPECT_EQ(2, a->toFrame());
EXPECT_EQ(3, b->fromFrame());
EXPECT_EQ(5, b->toFrame());
// Move before tag "a" (inside the tag)
EXPECT_EQ(frames_range(0, 1),
move_range(doc,
frames_range(1, 2),
frames_range(0),
kDocumentRangeBefore,
kFitInsideTags));
EXPECT_FRAME_ORDER6(1, 2, 0, 3, 4, 5);
EXPECT_EQ(0, a->fromFrame());
EXPECT_EQ(2, a->toFrame());
EXPECT_EQ(3, b->fromFrame());
EXPECT_EQ(5, b->toFrame());
EXPECT_TRUE(doc->undoHistory()->canUndo());
doc->undoHistory()->undo();
// Move after (and inside) tag "a"
EXPECT_EQ(frames_range(1, 2),
move_range(doc,
frames_range(0, 1),
frames_range(2),
kDocumentRangeAfter,
kFitInsideTags));
EXPECT_FRAME_ORDER6(2, 0, 1, 3, 4, 5);
EXPECT_EQ(0, a->fromFrame());
EXPECT_EQ(2, a->toFrame());
EXPECT_EQ(3, b->fromFrame());
EXPECT_EQ(5, b->toFrame());
EXPECT_TRUE(doc->undoHistory()->canUndo());
doc->undoHistory()->undo();
// Move between tag "a" and "b" (outside both tags)
EXPECT_EQ(frames_range(1, 2),
move_range(doc,
frames_range(0, 1),
frames_range(2),
kDocumentRangeAfter,
kFitOutsideTags));
EXPECT_FRAME_ORDER6(2, 0, 1, 3, 4, 5);
EXPECT_EQ(0, a->fromFrame());
EXPECT_EQ(0, a->toFrame());
EXPECT_EQ(3, b->fromFrame());
EXPECT_EQ(5, b->toFrame());
EXPECT_TRUE(doc->undoHistory()->canUndo());
doc->undoHistory()->undo();
EXPECT_EQ(frames_range(1, 2),
move_range(doc,
frames_range(0, 1),
frames_range(3),
kDocumentRangeBefore,
kFitOutsideTags));
EXPECT_FRAME_ORDER6(2, 0, 1, 3, 4, 5);
EXPECT_EQ(0, a->fromFrame());
EXPECT_EQ(0, a->toFrame());
EXPECT_EQ(3, b->fromFrame());
EXPECT_EQ(5, b->toFrame());
EXPECT_TRUE(doc->undoHistory()->canUndo());
doc->undoHistory()->undo();
EXPECT_EQ(frames_range(2, 3),
move_range(doc,
frames_range(2, 3),
frames_range(2),
kDocumentRangeAfter,
kFitOutsideTags));
EXPECT_FRAME_ORDER6(0, 1, 2, 3, 4, 5);
EXPECT_EQ(0, a->fromFrame());
EXPECT_EQ(1, a->toFrame());
EXPECT_EQ(4, b->fromFrame());
EXPECT_EQ(5, b->toFrame());
EXPECT_TRUE(doc->undoHistory()->canUndo());
doc->undoHistory()->undo();
EXPECT_EQ(frames_range(2, 3),
move_range(doc,
frames_range(2, 3),
frames_range(3),
kDocumentRangeBefore,
kFitOutsideTags));
EXPECT_FRAME_ORDER6(0, 1, 2, 3, 4, 5);
EXPECT_EQ(0, a->fromFrame());
EXPECT_EQ(1, a->toFrame());
EXPECT_EQ(4, b->fromFrame());
EXPECT_EQ(5, b->toFrame());
EXPECT_TRUE(doc->undoHistory()->canUndo());
doc->undoHistory()->undo();
// Move after tag "b" (inside tag)
EXPECT_EQ(frames_range(4, 5),
move_range(doc,
frames_range(0, 1),
frames_range(5),
kDocumentRangeAfter,
kFitInsideTags));
EXPECT_FRAME_ORDER6(2, 3, 4, 5, 0, 1);
EXPECT_EQ(0, a->fromFrame());
EXPECT_EQ(0, a->toFrame());
EXPECT_EQ(1, b->fromFrame());
EXPECT_EQ(5, b->toFrame());
EXPECT_TRUE(doc->undoHistory()->canUndo());
doc->undoHistory()->undo();
// Move after tag "b" (outside tag)
EXPECT_EQ(frames_range(4, 5),
move_range(doc,
frames_range(0, 1),
frames_range(5),
kDocumentRangeAfter,
kFitOutsideTags));
EXPECT_FRAME_ORDER6(2, 3, 4, 5, 0, 1);
EXPECT_EQ(0, a->fromFrame());
EXPECT_EQ(0, a->toFrame());
EXPECT_EQ(1, b->fromFrame());
EXPECT_EQ(3, b->toFrame());
EXPECT_TRUE(doc->undoHistory()->canUndo());
doc->undoHistory()->undo();
// Put frame 1 and 4 in the middle of both tags (outside)
DocumentRange from;
from.startRange(nullptr, 1, DocumentRange::kFrames); from.endRange(nullptr, 1);
from.startRange(nullptr, 4, DocumentRange::kFrames); from.endRange(nullptr, 4);
EXPECT_EQ(frames_range(2, 3),
move_range(doc,
from,
frames_range(2),
kDocumentRangeAfter,
kFitOutsideTags));
EXPECT_FRAME_ORDER6(0, 2, 1, 4, 3, 5);
EXPECT_EQ(0, a->fromFrame());
EXPECT_EQ(1, a->toFrame());
EXPECT_EQ(4, b->fromFrame());
EXPECT_EQ(5, b->toFrame());
EXPECT_TRUE(doc->undoHistory()->canUndo());
doc->undoHistory()->undo();
// Put frame 1 and 4 before tag "b" (inside)
EXPECT_EQ(frames_range(2, 3),
move_range(doc,
from,
frames_range(3),
kDocumentRangeBefore,
kFitInsideTags));
EXPECT_FRAME_ORDER6(0, 2, 1, 4, 3, 5);
EXPECT_EQ(0, a->fromFrame());
EXPECT_EQ(1, a->toFrame());
EXPECT_EQ(2, b->fromFrame());
EXPECT_EQ(5, b->toFrame());
EXPECT_TRUE(doc->undoHistory()->canUndo());
doc->undoHistory()->undo();
}
TEST_F(DocRangeOps, CopyRangeWithTags) {
FrameTag* a = new FrameTag(0, 2);
FrameTag* b = new FrameTag(3, 5);
sprite->frameTags().add(a);
sprite->frameTags().add(b);
EXPECT_EQ(0, a->fromFrame());
EXPECT_EQ(2, a->toFrame());
EXPECT_EQ(3, b->fromFrame());
EXPECT_EQ(5, b->toFrame());
// Copy before tag "a" (outside the tag)
EXPECT_EQ(frames_range(0),
copy_range(doc,
frames_range(1),
frames_range(0),
kDocumentRangeBefore,
kFitOutsideTags));
EXPECT_FRAME_COPY3(1, 0, 1, 2, 3, 4, 5);
EXPECT_EQ(1, a->fromFrame());
EXPECT_EQ(3, a->toFrame());
EXPECT_EQ(4, b->fromFrame());
EXPECT_EQ(6, b->toFrame());
EXPECT_TRUE(doc->undoHistory()->canUndo());
doc->undoHistory()->undo();
// Copy before tag "a" (inside the tag)
EXPECT_EQ(frames_range(0),
copy_range(doc,
frames_range(1),
frames_range(0),
kDocumentRangeBefore,
kFitInsideTags));
EXPECT_FRAME_COPY3(1, 0, 1, 2, 3, 4, 5);
EXPECT_EQ(0, a->fromFrame());
EXPECT_EQ(3, a->toFrame());
EXPECT_EQ(4, b->fromFrame());
EXPECT_EQ(6, b->toFrame());
EXPECT_TRUE(doc->undoHistory()->canUndo());
doc->undoHistory()->undo();
// Copy after tag "a" (outside the tag)
EXPECT_EQ(frames_range(3),
copy_range(doc,
frames_range(1),
frames_range(2),
kDocumentRangeAfter,
kFitOutsideTags));
EXPECT_FRAME_COPY3(0, 1, 2, 1, 3, 4, 5);
EXPECT_EQ(0, a->fromFrame());
EXPECT_EQ(2, a->toFrame());
EXPECT_EQ(4, b->fromFrame());
EXPECT_EQ(6, b->toFrame());
EXPECT_TRUE(doc->undoHistory()->canUndo());
doc->undoHistory()->undo();
}

View File

@ -0,0 +1,20 @@
// Aseprite
// Copyright (C) 2018 David Capello
//
// This program is distributed under the terms of
// the End-User License Agreement for Aseprite.
#ifndef APP_DROP_FRAME_PLACE_H_INCLUDED
#define APP_DROP_FRAME_PLACE_H_INCLUDED
#pragma once
namespace app {
enum DropFramePlace {
kDropBeforeFrame,
kDropAfterFrame,
};
} // namespace app
#endif

28
src/app/tags_handling.h Normal file
View File

@ -0,0 +1,28 @@
// Aseprite
// Copyright (C) 2018 David Capello
//
// This program is distributed under the terms of
// the End-User License Agreement for Aseprite.
#ifndef APP_TAGS_HANDLING_H_INCLUDED
#define APP_TAGS_HANDLING_H_INCLUDED
#pragma once
namespace app {
// How to adjust tags when we move a frame in the border of a tag.
enum TagsHandling {
// Do not move tags.
kDontAdjustTags,
// Move tags "in the best possible way" when the user doesn't
// specify what to do about them.
kDefaultTagsAdjustment,
// Put frames inside tags.
kFitInsideTags,
// Put frames outside tags.
kFitOutsideTags,
};
} // namespace app
#endif

View File

@ -191,6 +191,14 @@ Timeline::DropTarget::DropTarget()
{
hhit = HNone;
vhit = VNone;
outside = false;
}
Timeline::DropTarget::DropTarget(const DropTarget& o)
: hhit(o.hhit)
, vhit(o.vhit)
, outside(o.outside)
{
}
Timeline::Row::Row()
@ -2370,6 +2378,35 @@ void Timeline::drawFrameTags(ui::Graphics* g)
bounds.h = bounds.y2() - frameTagBounds.y2();
bounds.y = frameTagBounds.y2();
int dx = 0, dw = 0;
if (m_dropTarget.outside &&
m_dropTarget.hhit != DropTarget::HNone &&
m_dropRange.type() == DocumentRange::kFrames) {
switch (m_dropTarget.hhit) {
case DropTarget::Before:
if (m_dropRange.firstFrame() == frameTag->fromFrame()) {
dx = +frameBoxWidth()/4;
dw = -frameBoxWidth()/4;
}
else if (m_dropRange.firstFrame()-1 == frameTag->toFrame()) {
dw = -frameBoxWidth()/4;
}
break;
case DropTarget::After:
if (m_dropRange.lastFrame() == frameTag->toFrame()) {
dw = -frameBoxWidth()/4;
}
else if (m_dropRange.lastFrame()+1 == frameTag->fromFrame()) {
dx = +frameBoxWidth()/4;
dw = -frameBoxWidth()/4;
}
break;
}
}
bounds.x += dx;
bounds.w += dw;
frameTagBounds.x += dx;
gfx::Color bg =
(m_tagFocusBand < 0 || pass == 1) ?
frameTag->color(): theme->colors.timelineBandBg();
@ -3573,6 +3610,7 @@ void Timeline::dropRange(DropOp op)
Range newFromRange;
DocumentRangePlace place = kDocumentRangeAfter;
Range dropRange = m_dropRange;
bool outside = m_dropTarget.outside;
switch (m_range.type()) {
@ -3605,10 +3643,16 @@ void Timeline::dropRange(DropOp op)
prepareToMoveRange();
try {
TagsHandling tagsHandling = (outside ? kFitOutsideTags:
kFitInsideTags);
invalidateRange();
if (copy)
newFromRange = copy_range(m_document, m_range, dropRange, place);
newFromRange = copy_range(m_document, m_range, dropRange,
place, tagsHandling);
else
newFromRange = move_range(m_document, m_range, dropRange, place);
newFromRange = move_range(m_document, m_range, dropRange,
place, tagsHandling);
// If we drop a cel in the same frame (but in another layer),
// document views are not updated, so we are forcing the updating of
@ -3616,6 +3660,14 @@ void Timeline::dropRange(DropOp op)
m_document->notifyGeneralUpdate();
moveRange(newFromRange);
invalidateRange();
if (m_range.type() == Range::kFrames &&
m_sprite &&
!m_sprite->frameTags().empty()) {
invalidateRect(getFrameHeadersBounds().offset(origin()));
}
}
catch (const std::exception& ex) {
Console::showException(ex);
@ -3675,10 +3727,10 @@ void Timeline::unlockRange()
void Timeline::updateDropRange(const gfx::Point& pt)
{
DropTarget::HHit oldHHit = m_dropTarget.hhit;
DropTarget::VHit oldVHit = m_dropTarget.vhit;
const DropTarget oldDropTarget = m_dropTarget;
m_dropTarget.hhit = DropTarget::HNone;
m_dropTarget.vhit = DropTarget::VNone;
m_dropTarget.outside = false;
if (m_state != STATE_MOVING_RANGE) {
m_dropRange.clearRange();
@ -3703,10 +3755,14 @@ void Timeline::updateDropRange(const gfx::Point& pt)
gfx::Rect bounds = getRangeBounds(m_dropRange);
if (pt.x < bounds.x + bounds.w/2)
if (pt.x < bounds.x + bounds.w/2) {
m_dropTarget.hhit = DropTarget::Before;
else
m_dropTarget.outside = (pt.x < bounds.x+2);
}
else {
m_dropTarget.hhit = DropTarget::After;
m_dropTarget.outside = (pt.x >= bounds.x2()-2);
}
if (m_hot.veryBottom)
m_dropTarget.vhit = DropTarget::VeryBottom;
@ -3724,10 +3780,8 @@ void Timeline::updateDropRange(const gfx::Point& pt)
m_dropTarget.vhit = DropTarget::Bottom;
}
if (oldHHit != m_dropTarget.hhit ||
oldVHit != m_dropTarget.vhit) {
if (oldDropTarget != m_dropTarget)
invalidate();
}
}
void Timeline::clearClipboardRange()

View File

@ -206,15 +206,16 @@ namespace app {
FirstChild,
VeryBottom
};
DropTarget();
DropTarget(const DropTarget& o);
bool operator!=(const DropTarget& o) const {
return (hhit != o.hhit ||
vhit != o.vhit ||
outside != o.outside);
}
HHit hhit;
VHit vhit;
Layer* layer;
ObjectId layerId;
frame_t frame;
int xpos, ypos;
bool outside;
};
struct Row {