mirror of
https://github.com/aseprite/aseprite.git
synced 2025-03-10 01:13:49 +00:00
New Timeline UI to move frames inside or outside tags (fix #1656)
This commit is contained in:
parent
627ef49716
commit
b321a75b5b
@ -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)
|
||||
|
@ -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;
|
||||
|
@ -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) {
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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();
|
||||
}
|
||||
|
20
src/app/drop_frame_place.h
Normal file
20
src/app/drop_frame_place.h
Normal 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
28
src/app/tags_handling.h
Normal 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
|
@ -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()
|
||||
|
@ -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 {
|
||||
|
Loading…
x
Reference in New Issue
Block a user