1 | // Aseprite |
2 | // Copyright (C) 2019-2020 Igara Studio S.A. |
3 | // Copyright (C) 2001-2018 David Capello |
4 | // |
5 | // This program is distributed under the terms of |
6 | // the End-User License Agreement for Aseprite. |
7 | |
8 | // Uncomment this in case you want to debug range ops |
9 | //#define TRACE_RANGE_OPS |
10 | |
11 | #ifdef HAVE_CONFIG_H |
12 | #include "config.h" |
13 | #endif |
14 | |
15 | #include "app/doc_range_ops.h" |
16 | |
17 | #include "app/app.h" |
18 | #include "app/context_access.h" |
19 | #include "app/doc_api.h" |
20 | #include "app/doc_range.h" |
21 | #include "app/transaction.h" |
22 | #include "app/tx.h" |
23 | #include "doc/layer.h" |
24 | #include "doc/sprite.h" |
25 | |
26 | #include <stdexcept> |
27 | |
28 | #ifdef TRACE_RANGE_OPS |
29 | #include <iostream> |
30 | #endif |
31 | |
32 | namespace app { |
33 | |
34 | enum Op { Move, Copy }; |
35 | |
36 | static void move_or_copy_cels( |
37 | DocApi& api, Op op, |
38 | const LayerList& srcLayers, |
39 | const LayerList& dstLayers, |
40 | const SelectedFrames& srcFrames, |
41 | const SelectedFrames& dstFrames) |
42 | { |
43 | ASSERT(srcLayers.size() == dstLayers.size()); |
44 | |
45 | for (layer_t i=0; i<srcLayers.size(); ++i) { |
46 | auto srcFrame = srcFrames.begin(); |
47 | auto dstFrame = dstFrames.begin(); |
48 | auto srcFrameEnd = srcFrames.end(); |
49 | auto dstFrameEnd = dstFrames.end(); |
50 | |
51 | for (; srcFrame != srcFrameEnd && |
52 | dstFrame != dstFrameEnd; ++srcFrame, ++dstFrame) { |
53 | if (i >= 0 && i < srcLayers.size() && srcLayers[i]->isImage()) { |
54 | LayerImage* srcLayer = static_cast<LayerImage*>(srcLayers[i]); |
55 | |
56 | if (i < dstLayers.size() && dstLayers[i]->isImage()) { |
57 | LayerImage* dstLayer = static_cast<LayerImage*>(dstLayers[i]); |
58 | |
59 | #ifdef TRACE_RANGE_OPS |
60 | std::clog << (op == Move ? "Moving" : "Copying" ) |
61 | << " cel " << srcLayer->name() << "[" << *srcFrame << "]" |
62 | << " into " << dstLayer->name() << "[" << *dstFrame << "]\n" ; |
63 | #endif |
64 | |
65 | switch (op) { |
66 | case Move: api.moveCel(srcLayer, *srcFrame, dstLayer, *dstFrame); break; |
67 | case Copy: api.copyCel(srcLayer, *srcFrame, dstLayer, *dstFrame); break; |
68 | } |
69 | } |
70 | // All cels moved from a image layer and dropped in other kind |
71 | // of layer (e.g. a group) will be discarded/deleted. |
72 | else if (op == Move) { |
73 | api.clearCel(srcLayer, *srcFrame); |
74 | } |
75 | } |
76 | } |
77 | } |
78 | } |
79 | |
80 | static DocRange move_or_copy_frames( |
81 | DocApi& api, Op op, |
82 | Sprite* sprite, |
83 | const DocRange& srcRange, |
84 | frame_t dstFrame, |
85 | const DocRangePlace place, |
86 | const TagsHandling tagsHandling) |
87 | { |
88 | const SelectedFrames& srcFrames = srcRange.selectedFrames(); |
89 | |
90 | #ifdef TRACE_RANGE_OPS |
91 | std::clog << "move_or_copy_frames frames[" ; |
92 | for (auto srcFrame : srcFrames) { |
93 | std::clog << srcFrame << ", " ; |
94 | } |
95 | std::clog << "] " |
96 | << (place == kDocRangeBefore ? "before" : |
97 | place == kDocRangeAfter ? "after" : |
98 | "as first child" ) |
99 | << " " << dstFrame << "\n" ; |
100 | #endif |
101 | |
102 | auto srcFrame = srcFrames.begin(); |
103 | auto srcFrameEnd = srcFrames.end(); |
104 | frame_t srcDelta = 0; |
105 | frame_t firstCopiedBlock = 0; |
106 | frame_t dstBeforeFrame = |
107 | (place == kDocRangeBefore ? dstFrame: |
108 | dstFrame+1); |
109 | |
110 | for (; srcFrame != srcFrameEnd; ++srcFrame) { |
111 | frame_t fromFrame = (*srcFrame)+srcDelta; |
112 | |
113 | switch (op) { |
114 | |
115 | case Move: |
116 | if ((*srcFrame) >= dstBeforeFrame) { |
117 | srcDelta = 0; |
118 | fromFrame = *srcFrame; |
119 | } |
120 | break; |
121 | |
122 | case Copy: |
123 | if (fromFrame >= dstBeforeFrame-1 && firstCopiedBlock) { |
124 | srcDelta += firstCopiedBlock; |
125 | fromFrame += firstCopiedBlock; |
126 | firstCopiedBlock = 0; |
127 | } |
128 | break; |
129 | } |
130 | |
131 | #ifdef TRACE_RANGE_OPS |
132 | std::clog << " [" ; |
133 | for (frame_t i=0; i<=sprite->lastFrame(); ++i) { |
134 | std::clog << (sprite->frameDuration(i)-1); |
135 | } |
136 | std::clog << "] => " |
137 | << (op == Move ? "Move" : "Copy" ) |
138 | << " " << (*srcFrame) << "+" << (srcDelta) |
139 | << (place == kDocRangeBefore ? " before " : " after " ) |
140 | << dstFrame << " => " ; |
141 | #endif |
142 | |
143 | switch (op) { |
144 | |
145 | case Move: |
146 | api.moveFrame(sprite, fromFrame, dstFrame, |
147 | (place == kDocRangeBefore ? kDropBeforeFrame: |
148 | kDropAfterFrame), |
149 | tagsHandling); |
150 | |
151 | if (fromFrame < dstBeforeFrame-1) { |
152 | --srcDelta; |
153 | } |
154 | else if (fromFrame > dstBeforeFrame-1) { |
155 | ++dstBeforeFrame; |
156 | ++dstFrame; |
157 | } |
158 | break; |
159 | |
160 | case Copy: |
161 | api.copyFrame(sprite, fromFrame, dstFrame, |
162 | (place == kDocRangeBefore ? kDropBeforeFrame: |
163 | kDropAfterFrame), |
164 | tagsHandling); |
165 | |
166 | if (fromFrame < dstBeforeFrame-1) { |
167 | ++firstCopiedBlock; |
168 | } |
169 | else if (fromFrame >= dstBeforeFrame-1) { |
170 | ++srcDelta; |
171 | } |
172 | ++dstBeforeFrame; |
173 | ++dstFrame; |
174 | break; |
175 | } |
176 | |
177 | #ifdef TRACE_RANGE_OPS |
178 | std::clog << " [" ; |
179 | for (frame_t i=0; i<=sprite->lastFrame(); ++i) { |
180 | std::clog << (sprite->frameDuration(i)-1); |
181 | } |
182 | std::clog << "]\n" ; |
183 | #endif |
184 | } |
185 | |
186 | DocRange result; |
187 | if (!srcRange.selectedLayers().empty()) |
188 | result.selectLayers(srcRange.selectedLayers()); |
189 | result.startRange(nullptr, dstBeforeFrame-srcFrames.size(), DocRange::kFrames); |
190 | result.endRange(nullptr, dstBeforeFrame-1); |
191 | return result; |
192 | } |
193 | |
194 | static bool has_child(LayerGroup* parent, Layer* child) |
195 | { |
196 | for (auto c : parent->layers()) { |
197 | if (c == child) |
198 | return true; |
199 | else if (c->isGroup() && |
200 | has_child(static_cast<LayerGroup*>(c), child)) |
201 | return true; |
202 | } |
203 | return false; |
204 | } |
205 | |
206 | static DocRange drop_range_op( |
207 | Doc* doc, |
208 | const Op op, |
209 | const DocRange& from, |
210 | DocRangePlace place, |
211 | const TagsHandling tagsHandling, |
212 | DocRange to) |
213 | { |
214 | // Convert "first child" operation into a insert after last child. |
215 | LayerGroup* parent = nullptr; |
216 | if (to.type() == DocRange::kLayers && |
217 | !to.selectedLayers().empty()) { |
218 | if (place == kDocRangeFirstChild && |
219 | (*to.selectedLayers().begin())->isGroup()) { |
220 | place = kDocRangeAfter; |
221 | parent = static_cast<LayerGroup*>((*to.selectedLayers().begin())); |
222 | |
223 | to.clearRange(); |
224 | to.startRange(parent->lastLayer(), -1, DocRange::kLayers); |
225 | to.endRange(parent->lastLayer(), -1); |
226 | } |
227 | else { |
228 | parent = (*to.selectedLayers().begin())->parent(); |
229 | } |
230 | |
231 | // Check that we're not moving a group inside itself |
232 | for (auto moveThis : from.selectedLayers()) { |
233 | if (moveThis == parent || |
234 | (moveThis->isGroup() && |
235 | has_child(static_cast<LayerGroup*>(moveThis), parent))) |
236 | return from; |
237 | } |
238 | } |
239 | |
240 | if (place != kDocRangeBefore && |
241 | place != kDocRangeAfter) { |
242 | ASSERT(false); |
243 | throw std::invalid_argument("Invalid 'place' argument" ); |
244 | } |
245 | |
246 | Sprite* sprite = doc->sprite(); |
247 | |
248 | // Check noop/trivial/do nothing cases, i.e., move a range to the same place. |
249 | // Also check invalid cases, like moving a Background layer. |
250 | switch (from.type()) { |
251 | |
252 | case DocRange::kCels: |
253 | if (from == to) |
254 | return from; |
255 | break; |
256 | |
257 | case DocRange::kFrames: |
258 | if (op == Move) { |
259 | // Simple cases with one continuos range of frames that are a |
260 | // no-op. |
261 | if ((from.selectedFrames().ranges() == 1) && |
262 | ((to.firstFrame() >= from.firstFrame() && |
263 | to.lastFrame() <= from.lastFrame()) || |
264 | (place == kDocRangeBefore && to.firstFrame() == from.lastFrame()+1) || |
265 | (place == kDocRangeAfter && to.lastFrame() == from.firstFrame()-1)) && |
266 | // If there are tags, this might not be a no-op |
267 | (sprite->tags().empty() || |
268 | tagsHandling == kDontAdjustTags)) { |
269 | return from; |
270 | } |
271 | } |
272 | break; |
273 | |
274 | case DocRange::kLayers: |
275 | if (op == Move) { |
276 | SelectedLayers srcSelLayers = from.selectedLayers(); |
277 | SelectedLayers dstSelLayers = to.selectedLayers(); |
278 | LayerList srcLayers = srcSelLayers.toBrowsableLayerList(); |
279 | LayerList dstLayers = dstSelLayers.toBrowsableLayerList(); |
280 | ASSERT(!srcLayers.empty()); |
281 | if (srcLayers.empty()) |
282 | return from; |
283 | |
284 | // dstLayers can be nullptr when we insert the first child in |
285 | // a group. |
286 | |
287 | // Check no-ops when we move layers at the same level (all |
288 | // layers with the same parent), all adjacents, and which are |
289 | // moved to the same place. |
290 | if (!dstSelLayers.empty() && |
291 | srcSelLayers.hasSameParent() && |
292 | dstSelLayers.hasSameParent() && |
293 | are_layers_adjacent(srcLayers) && |
294 | are_layers_adjacent(dstLayers)) { |
295 | for (Layer* srcLayer : srcLayers) |
296 | if (dstSelLayers.contains(srcLayer)) |
297 | return from; |
298 | |
299 | if ((place == kDocRangeBefore |
300 | && dstLayers.front() == srcLayers.back()->getNext()) || |
301 | (place == kDocRangeAfter |
302 | && dstLayers.back() == srcLayers.front()->getPrevious())) |
303 | return from; |
304 | } |
305 | |
306 | // We cannot move the background |
307 | for (Layer* layer : srcSelLayers) |
308 | if (layer->isBackground()) |
309 | throw std::runtime_error("The background layer cannot be moved" ); |
310 | } |
311 | |
312 | // Before background |
313 | if (place == kDocRangeBefore) { |
314 | for (Layer* background : to.selectedLayers()) { |
315 | if (background && background->isBackground()) |
316 | throw std::runtime_error("You cannot move or copy something below the background layer" ); |
317 | } |
318 | } |
319 | break; |
320 | } |
321 | |
322 | const char* undoLabel = NULL; |
323 | switch (op) { |
324 | case Move: undoLabel = "Move Range" ; break; |
325 | case Copy: undoLabel = "Copy Range" ; break; |
326 | default: |
327 | ASSERT(false); |
328 | throw std::invalid_argument("Invalid 'op' argument" ); |
329 | } |
330 | DocRange resultRange; |
331 | |
332 | { |
333 | const app::Context* context = static_cast<app::Context*>(doc->context()); |
334 | const ContextReader reader(context); |
335 | ContextWriter writer(reader); |
336 | Tx tx(writer.context(), undoLabel, ModifyDocument); |
337 | DocApi api = doc->getApi(tx); |
338 | |
339 | // TODO Try to add the range with just one call to DocApi |
340 | // methods, to avoid generating a lot of cmd::SetCelFrame (see |
341 | // DocApi::setCelFramePosition() function). |
342 | |
343 | switch (from.type()) { |
344 | |
345 | case DocRange::kCels: { |
346 | LayerList allLayers = sprite->allBrowsableLayers(); |
347 | if (allLayers.empty()) |
348 | break; |
349 | |
350 | LayerList srcLayers = from.selectedLayers().toBrowsableLayerList(); |
351 | LayerList dstLayers = to.selectedLayers().toBrowsableLayerList(); |
352 | if (srcLayers.empty() || |
353 | dstLayers.empty()) |
354 | throw std::invalid_argument("You need to specify a non-empty cels range" ); |
355 | |
356 | if (find_layer_index(allLayers, srcLayers.front()) < |
357 | find_layer_index(allLayers, dstLayers.front())) { |
358 | std::reverse(srcLayers.begin(), srcLayers.end()); |
359 | std::reverse(dstLayers.begin(), dstLayers.end()); |
360 | } |
361 | |
362 | if (from.firstFrame() < to.firstFrame()) { |
363 | auto srcFrames = from.selectedFrames().makeReverse(); |
364 | auto dstFrames = to.selectedFrames().makeReverse(); |
365 | |
366 | move_or_copy_cels(api, op, srcLayers, dstLayers, srcFrames, dstFrames); |
367 | } |
368 | else { |
369 | const auto& srcFrames = from.selectedFrames(); |
370 | const auto& dstFrames = to.selectedFrames(); |
371 | |
372 | move_or_copy_cels(api, op, srcLayers, dstLayers, srcFrames, dstFrames); |
373 | } |
374 | |
375 | resultRange = to; |
376 | break; |
377 | } |
378 | |
379 | case DocRange::kFrames: { |
380 | frame_t dstFrame; |
381 | if (place == kDocRangeBefore) |
382 | dstFrame = to.firstFrame(); |
383 | else |
384 | dstFrame = to.lastFrame(); |
385 | |
386 | resultRange = move_or_copy_frames(api, op, sprite, |
387 | from, dstFrame, |
388 | place, tagsHandling); |
389 | break; |
390 | } |
391 | |
392 | case DocRange::kLayers: { |
393 | LayerList allLayers = sprite->allBrowsableLayers(); |
394 | if (allLayers.empty()) |
395 | break; |
396 | |
397 | LayerList srcLayers = from.selectedLayers().toBrowsableLayerList(); |
398 | LayerList dstLayers = to.selectedLayers().toBrowsableLayerList(); |
399 | ASSERT(!srcLayers.empty()); |
400 | |
401 | switch (op) { |
402 | |
403 | case Move: |
404 | if (place == kDocRangeBefore) { |
405 | Layer* beforeThis = (!dstLayers.empty() ? dstLayers.front(): nullptr); |
406 | Layer* afterThis = nullptr; |
407 | |
408 | for (Layer* srcLayer : srcLayers) { |
409 | if (afterThis) |
410 | api.restackLayerAfter(srcLayer, parent, afterThis); |
411 | else |
412 | api.restackLayerBefore(srcLayer, parent, beforeThis); |
413 | |
414 | afterThis = srcLayer; |
415 | } |
416 | } |
417 | else if (place == kDocRangeAfter) { |
418 | Layer* afterThis = (!dstLayers.empty() ? dstLayers.back(): nullptr); |
419 | for (Layer* srcLayer : srcLayers) { |
420 | api.restackLayerAfter(srcLayer, parent, afterThis); |
421 | afterThis = srcLayer; |
422 | } |
423 | } |
424 | |
425 | // Same set of layers than the "from" range |
426 | resultRange = from; |
427 | break; |
428 | |
429 | case Copy: { |
430 | if (place == kDocRangeBefore) { |
431 | Layer* beforeThis = (!dstLayers.empty() ? dstLayers.front(): nullptr); |
432 | for (Layer* srcLayer : srcLayers) { |
433 | Layer* copiedLayer = api.duplicateLayerBefore( |
434 | srcLayer, parent, beforeThis); |
435 | |
436 | resultRange.startRange(copiedLayer, -1, DocRange::kLayers); |
437 | resultRange.endRange(copiedLayer, -1); |
438 | } |
439 | } |
440 | else if (place == kDocRangeAfter) { |
441 | std::reverse(srcLayers.begin(), srcLayers.end()); |
442 | |
443 | Layer* afterThis = (!dstLayers.empty() ? dstLayers.back(): nullptr); |
444 | for (Layer* srcLayer : srcLayers) { |
445 | Layer* copiedLayer = api.duplicateLayerAfter( |
446 | srcLayer, parent, afterThis); |
447 | |
448 | resultRange.startRange(copiedLayer, -1, DocRange::kLayers); |
449 | resultRange.endRange(copiedLayer, -1); |
450 | } |
451 | } |
452 | break; |
453 | } |
454 | } |
455 | break; |
456 | } |
457 | } |
458 | |
459 | if (resultRange.type() != DocRange::kNone) |
460 | tx.setNewDocRange(resultRange); |
461 | |
462 | tx.commit(); |
463 | } |
464 | |
465 | return resultRange; |
466 | } |
467 | |
468 | DocRange move_range(Doc* doc, |
469 | const DocRange& from, |
470 | const DocRange& to, |
471 | const DocRangePlace place, |
472 | const TagsHandling tagsHandling) |
473 | { |
474 | return drop_range_op(doc, Move, from, place, |
475 | tagsHandling, DocRange(to)); |
476 | } |
477 | |
478 | DocRange copy_range(Doc* doc, |
479 | const DocRange& from, |
480 | const DocRange& to, |
481 | const DocRangePlace place, |
482 | const TagsHandling tagsHandling) |
483 | { |
484 | return drop_range_op(doc, Copy, from, place, |
485 | tagsHandling, DocRange(to)); |
486 | } |
487 | |
488 | void reverse_frames(Doc* doc, const DocRange& range) |
489 | { |
490 | const app::Context* context = static_cast<app::Context*>(doc->context()); |
491 | const ContextReader reader(context); |
492 | ContextWriter writer(reader); |
493 | Tx tx(writer.context(), "Reverse Frames" ); |
494 | DocApi api = doc->getApi(tx); |
495 | Sprite* sprite = doc->sprite(); |
496 | LayerList layers; |
497 | frame_t frameBegin, frameEnd; |
498 | bool moveFrames = false; |
499 | bool swapCels = false; |
500 | |
501 | switch (range.type()) { |
502 | case DocRange::kCels: |
503 | frameBegin = range.firstFrame(); |
504 | frameEnd = range.lastFrame(); |
505 | layers = range.selectedLayers().toBrowsableLayerList(); |
506 | swapCels = true; |
507 | break; |
508 | case DocRange::kFrames: |
509 | frameBegin = range.firstFrame(); |
510 | frameEnd = range.lastFrame(); |
511 | layers = sprite->allLayers(); |
512 | moveFrames = true; |
513 | break; |
514 | case DocRange::kLayers: |
515 | frameBegin = frame_t(0); |
516 | frameEnd = sprite->totalFrames()-1; |
517 | layers = range.selectedLayers().toBrowsableLayerList(); |
518 | swapCels = true; |
519 | break; |
520 | } |
521 | |
522 | if (moveFrames) { |
523 | for (frame_t frameRev = frameEnd+1; |
524 | frameRev > frameBegin; |
525 | --frameRev) { |
526 | api.moveFrame(sprite, frameBegin, frameRev, |
527 | kDropBeforeFrame, |
528 | kDontAdjustTags); |
529 | } |
530 | } |
531 | else if (swapCels) { |
532 | for (Layer* layer : layers) { |
533 | if (!layer->isImage()) |
534 | continue; |
535 | |
536 | for (frame_t frame = frameBegin, |
537 | frameRev = frameEnd; |
538 | frame != (frameBegin+frameEnd)/2+1; |
539 | ++frame, --frameRev) { |
540 | if (frame == frameRev) |
541 | continue; |
542 | |
543 | LayerImage* imageLayer = static_cast<LayerImage*>(layer); |
544 | api.swapCel(imageLayer, frame, frameRev); |
545 | } |
546 | } |
547 | } |
548 | |
549 | tx.setNewDocRange(range); |
550 | tx.commit(); |
551 | } |
552 | |
553 | } // namespace app |
554 | |