mirror of https://github.com/aseprite/aseprite.git
				
				
				
			
		
			
				
	
	
		
			631 lines
		
	
	
		
			19 KiB
		
	
	
	
		
			C++
		
	
	
	
			
		
		
	
	
			631 lines
		
	
	
		
			19 KiB
		
	
	
	
		
			C++
		
	
	
	
| // Aseprite Document Library
 | |
| // Copyright (C) 2021-2024  Igara Studio S.A.
 | |
| //
 | |
| // This file is released under the terms of the MIT license.
 | |
| // Read LICENSE.txt for more information.
 | |
| 
 | |
| #ifdef HAVE_CONFIG_H
 | |
|   #include "config.h"
 | |
| #endif
 | |
| 
 | |
| #include "doc/playback.h"
 | |
| 
 | |
| #include "base/remove_from_container.h"
 | |
| #include "doc/frame.h"
 | |
| #include "doc/sprite.h"
 | |
| #include "doc/tag.h"
 | |
| 
 | |
| #include <limits>
 | |
| 
 | |
| #define PLAY_TRACE(...) // TRACEARGS
 | |
| 
 | |
| namespace doc {
 | |
| 
 | |
| [[maybe_unused]]
 | |
| static const char* mode_to_string(Playback::Mode mode)
 | |
| {
 | |
|   switch (mode) {
 | |
|     case Playback::PlayAll:               return "PlayAll";
 | |
|     case Playback::PlayInLoop:            return "PlayInLoop";
 | |
|     case Playback::PlayWithoutTagsInLoop: return "PlayWithoutTagsInLoop";
 | |
|     case Playback::PlayOnce:              return "PlayOnce";
 | |
|     case Playback::Stopped:               return "Stopped";
 | |
|   }
 | |
|   return "";
 | |
| }
 | |
| 
 | |
| Playback::PlayTag::PlayTag(const Tag* tag, int parentForward)
 | |
|   : tag(tag)
 | |
|   , forward(parentForward *
 | |
|             (tag->aniDir() == AniDir::FORWARD || tag->aniDir() == AniDir::PING_PONG ? 1 : -1))
 | |
| {
 | |
|   if (tag->repeat() > 0) {
 | |
|     repeat = tag->repeat();
 | |
|   }
 | |
|   // Repeat=0 is a "infinite repeat", but we'll play the tag just
 | |
|   // once.
 | |
|   else {
 | |
|     if (tag->aniDir() == AniDir::PING_PONG || tag->aniDir() == AniDir::PING_PONG_REVERSE) {
 | |
|       repeat = 2;
 | |
|     }
 | |
|     else {
 | |
|       repeat = 1;
 | |
|     }
 | |
|   }
 | |
| }
 | |
| 
 | |
| Playback::Playback(const Sprite* sprite,
 | |
|                    const TagsList& tags,
 | |
|                    const frame_t frame,
 | |
|                    const Mode playMode,
 | |
|                    const Tag* tag,
 | |
|                    const int forward)
 | |
|   : m_sprite(sprite)
 | |
|   , m_tags(tags)
 | |
|   , m_initialFrame(frame)
 | |
|   , m_frame(frame)
 | |
|   , m_playMode(playMode)
 | |
|   , m_forward(forward)
 | |
| {
 | |
|   PLAY_TRACE("--Playback-- tag=", (tag ? tag->name() : ""), "mode=", mode_to_string(m_playMode));
 | |
| 
 | |
|   // Go to the first frame of the animation or active frame tag
 | |
|   if (playMode == Mode::PlayOnce) {
 | |
|     if (tag) {
 | |
|       m_frame = (tag->aniDir() == AniDir::REVERSE || tag->aniDir() == AniDir::PING_PONG_REVERSE ?
 | |
|                    tag->toFrame() :
 | |
|                    tag->fromFrame());
 | |
| 
 | |
|       addTag(tag, false, 1);
 | |
|     }
 | |
|     else {
 | |
|       m_frame = 0;
 | |
|     }
 | |
|   }
 | |
|   else if (playMode == Mode::PlayInLoop) {
 | |
|     if (tag) {
 | |
|       addTag(tag, false, 1);
 | |
| 
 | |
|       // Loop the given tag in the constructor infinite times
 | |
|       m_playing.back()->repeat = std::numeric_limits<int>::max();
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   if (m_sprite)
 | |
|     handleEnterFrame(frame, true);
 | |
| }
 | |
| 
 | |
| Playback::Playback(const Sprite* sprite, const frame_t frame, const Mode playMode, const Tag* tag)
 | |
|   : Playback(sprite, (sprite ? sprite->tags().getInternalList() : TagsList()), frame, playMode, tag)
 | |
| {
 | |
| }
 | |
| 
 | |
| frame_t Playback::nextFrame(frame_t frameDelta)
 | |
| {
 | |
|   PLAY_TRACE("  Playback::nextFrame { frame=", m_frame, "+", frameDelta);
 | |
| 
 | |
|   int step = (frameDelta > 0 ? +1 : -1);
 | |
| 
 | |
|   while (frameDelta != 0 && m_playMode != Stopped) {
 | |
|     bool move = handleExitFrame(step);
 | |
|     if (move)
 | |
|       handleMoveFrame(step);
 | |
|     handleEnterFrame(step, false);
 | |
| 
 | |
|     frameDelta -= step;
 | |
|   }
 | |
| 
 | |
|   PLAY_TRACE("  } =",
 | |
|              m_frame,
 | |
|              "(tag=",
 | |
|              (tag() ? tag()->name() : "nullptr"),
 | |
|              ", repeat=",
 | |
|              (!m_playing.empty() ? m_playing.back()->repeat : -1),
 | |
|              ")");
 | |
|   return m_frame;
 | |
| }
 | |
| 
 | |
| void Playback::stop()
 | |
| {
 | |
|   if (m_playMode == Mode::PlayAll || m_playMode == Mode::PlayOnce) {
 | |
|     m_frame = m_initialFrame;
 | |
|   }
 | |
|   m_playMode = Mode::Stopped;
 | |
| }
 | |
| 
 | |
| Tag* Playback::tag() const
 | |
| {
 | |
|   return (!m_playing.empty() ? const_cast<Tag*>(m_playing.back()->tag) : nullptr);
 | |
| }
 | |
| 
 | |
| void Playback::removeReferencesToTag(Tag* tag)
 | |
| {
 | |
|   base::remove_from_container(m_tags, tag);
 | |
|   base::remove_from_container(m_played, tag);
 | |
| 
 | |
|   for (auto it = m_playing.begin(); it != m_playing.end();) {
 | |
|     std::unique_ptr<PlayTag>& playTag = *it;
 | |
|     if (playTag->tag == tag)
 | |
|       it = m_playing.erase(it);
 | |
|     else
 | |
|       ++it;
 | |
|   }
 | |
| }
 | |
| 
 | |
| void Playback::handleEnterFrame(const frame_t frameDelta, const bool firstTime)
 | |
| {
 | |
|   PLAY_TRACE("    handleEnterFrame", m_frame, "+", frameDelta);
 | |
| 
 | |
|   switch (m_playMode) {
 | |
|     case PlayAll:
 | |
|     case PlayInLoop: {
 | |
|       const Tag* tag = this->tag();
 | |
|       const frame_t frame = m_frame;
 | |
|       const int forward = getParentForward();
 | |
| 
 | |
|       for (const Tag* t : m_tags) {
 | |
|         if (t->contains(frame)) {
 | |
|           // Ignored tags that were played
 | |
|           if (m_played.find(t) != m_played.end()) {
 | |
|             continue;
 | |
|           }
 | |
| 
 | |
|           if (tag && (tag->toFrame() < t->toFrame() || tag->fromFrame() > t->fromFrame())) {
 | |
|             // Cascade
 | |
|             addTag(t, true, 1);
 | |
|           }
 | |
|           else {
 | |
|             addTag(t, false, forward);
 | |
|             if (!firstTime) {
 | |
|               goToFirstTagFrame(t);
 | |
|               // Handle cases where inner tags will jump to different
 | |
|               // frames several times recursively (e.g. one reverse
 | |
|               // inside other reverse).
 | |
|               //
 | |
|               // Consideration for tests:
 | |
|               // Playback.OnePingPongInsidePingPongReverse
 | |
|               // Playback.OneReverseInsidePingPongReverse
 | |
|               // Playback.OnePingPongReverseInsideReverse
 | |
|               if (frame != m_frame)
 | |
|                 handleEnterFrame(frameDelta, false);
 | |
|             }
 | |
|           }
 | |
|         }
 | |
|       }
 | |
|       break;
 | |
|     }
 | |
| 
 | |
|     case PlayWithoutTagsInLoop:
 | |
|     case PlayOnce:
 | |
|       // Do nothing
 | |
|       break;
 | |
|   }
 | |
| }
 | |
| 
 | |
| bool Playback::handleExitFrame(const frame_t frameDelta)
 | |
| {
 | |
|   PLAY_TRACE("    handleExitFrame", m_frame, "+", frameDelta);
 | |
| 
 | |
|   switch (m_playMode) {
 | |
|     case PlayAll:
 | |
|     case PlayInLoop: {
 | |
|       auto tag = this->tag();
 | |
|       if (tag && tag->contains(m_frame)) {
 | |
|         ASSERT(!m_playing.empty());
 | |
|         [[maybe_unused]]
 | |
|         int forward = m_playing.back()->forward;
 | |
| 
 | |
|         PLAY_TRACE("tag aniDir=",
 | |
|                    (int)tag->aniDir(),
 | |
|                    "range=",
 | |
|                    (int)tag->fromFrame(),
 | |
|                    (int)tag->toFrame(),
 | |
|                    "forward=",
 | |
|                    forward);
 | |
| 
 | |
|         if ((tag->aniDir() == AniDir::FORWARD || tag->aniDir() == AniDir::REVERSE) &&
 | |
|             (frameDelta > 0 && m_frame == lastTagFrame(tag))) {
 | |
|           decrementRepeat(frameDelta);
 | |
|           return false;
 | |
|         }
 | |
|         // Change ping-pong direction
 | |
|         else if ((tag->aniDir() == AniDir::PING_PONG ||
 | |
|                   tag->aniDir() == AniDir::PING_PONG_REVERSE) &&
 | |
|                  m_frame == lastTagFrame(tag)) {
 | |
|           PLAY_TRACE("    Changing direction frame=", m_frame, " forward=", forward, "->", -forward);
 | |
| 
 | |
|           // Changing the direction of the ping-pong animation
 | |
|           m_playing.back()->invertForward();
 | |
|           return decrementRepeat(frameDelta);
 | |
|         }
 | |
|         else if (m_playMode == PlayInLoop) {
 | |
|           if (frameDelta < 0 && m_frame == firstTagFrame(tag)) {
 | |
|             PLAY_TRACE("    Going to last frame=",
 | |
|                        lastTagFrame(tag),
 | |
|                        " (PlayInLoop) frame=",
 | |
|                        m_frame,
 | |
|                        " forward=",
 | |
|                        forward);
 | |
| 
 | |
|             m_frame = lastTagFrame(tag);
 | |
|             return false;
 | |
|           }
 | |
|           break;
 | |
|         }
 | |
|         else if (m_playMode == PlayAll)
 | |
|           break;
 | |
|       }
 | |
| 
 | |
|       if (frameDelta > 0 && ((m_frame == m_sprite->lastFrame() && m_forward > 0) ||
 | |
|                              (m_frame == 0 && m_forward < 0))) {
 | |
|         if (m_playMode == PlayInLoop) {
 | |
|           if (m_forward > 0) {
 | |
|             PLAY_TRACE("    Going back to frame=0 (PlayInLoop)", m_frame, m_sprite->lastFrame());
 | |
|             m_frame = 0;
 | |
|           }
 | |
|           else {
 | |
|             PLAY_TRACE("    Going back to frame=last frame (PlayInLoop)");
 | |
|             m_frame = m_sprite->lastFrame();
 | |
|           }
 | |
|           return false;
 | |
|         }
 | |
|         else {
 | |
|           PLAY_TRACE("    Stop animation (PlayAll)");
 | |
|           stop();
 | |
|           return false;
 | |
|         }
 | |
|       }
 | |
|       else if (frameDelta < 0 && ((m_frame == 0 && m_forward > 0) ||
 | |
|                                   (m_frame == m_sprite->lastFrame() && m_forward < 0))) {
 | |
|         if (m_playMode == PlayInLoop) {
 | |
|           if (m_forward > 0) {
 | |
|             PLAY_TRACE("    Going back to frame=last frame (PlayInLoop)");
 | |
|             m_frame = m_sprite->lastFrame();
 | |
|           }
 | |
|           else {
 | |
|             PLAY_TRACE("    Going back to frame=0 (PlayInLoop)");
 | |
|             m_frame = 0;
 | |
|           }
 | |
|           return false;
 | |
|         }
 | |
|         else {
 | |
|           PLAY_TRACE("    Stop animation in first frame (PlayAll)");
 | |
|           stop();
 | |
|           return false;
 | |
|         }
 | |
|       }
 | |
|       break;
 | |
|     }
 | |
| 
 | |
|     case PlayWithoutTagsInLoop:
 | |
|       // Do nothing
 | |
|       break;
 | |
| 
 | |
|     case PlayOnce: {
 | |
|       if (auto tag = this->tag()) {
 | |
|         ASSERT(m_playing.size() == 1);
 | |
|         int forward = m_playing.back()->forward;
 | |
| 
 | |
|         if ((tag->aniDir() == AniDir::FORWARD && m_frame == tag->toFrame()) ||
 | |
|             (tag->aniDir() == AniDir::REVERSE && m_frame == tag->fromFrame()) ||
 | |
|             (tag->aniDir() == AniDir::PING_PONG && m_frame == tag->fromFrame() && forward < 0) ||
 | |
|             (tag->aniDir() == AniDir::PING_PONG_REVERSE && m_frame == tag->toFrame() &&
 | |
|              forward > 0)) {
 | |
|           stop();
 | |
|           return false;
 | |
|         }
 | |
|         else if ((tag->aniDir() == AniDir::PING_PONG && m_frame == tag->toFrame() && forward > 0) ||
 | |
|                  (tag->aniDir() == AniDir::PING_PONG_REVERSE && m_frame == tag->fromFrame() &&
 | |
|                   forward < 0)) {
 | |
|           PLAY_TRACE("    Changing direction frame=", m_frame, " forward=", forward, "->", -forward);
 | |
| 
 | |
|           // Changing the direction of the ping-pong animation
 | |
|           m_playing.back()->invertForward();
 | |
|         }
 | |
|       }
 | |
|       else if ((frameDelta > 0 && m_frame == m_sprite->lastFrame()) ||
 | |
|                (frameDelta < 0 && m_frame == 0)) {
 | |
|         stop();
 | |
|         return false;
 | |
|       }
 | |
|       break;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   return true;
 | |
| }
 | |
| 
 | |
| void Playback::handleMoveFrame(const frame_t frameDelta)
 | |
| {
 | |
|   PLAY_TRACE("    handleMoveFrame", m_frame, "+", frameDelta);
 | |
| 
 | |
|   switch (m_playMode) {
 | |
|     case PlayWithoutTagsInLoop: {
 | |
|       ASSERT(m_playing.empty());
 | |
| 
 | |
|       frame_t first = 0;
 | |
|       frame_t last = m_sprite->lastFrame();
 | |
|       m_frame += frameDelta;
 | |
|       if (m_frame < 0)
 | |
|         m_frame = last;
 | |
|       if (m_frame > last)
 | |
|         m_frame = first;
 | |
|       break;
 | |
|     }
 | |
| 
 | |
|     case PlayAll:
 | |
|     case PlayInLoop:
 | |
|     case PlayOnce:   {
 | |
|       m_frame += frameDelta * getParentForward();
 | |
|       break;
 | |
|     }
 | |
|   }
 | |
| }
 | |
| 
 | |
| void Playback::addTag(const Tag* tag, const bool rewind, const int forward)
 | |
| {
 | |
|   auto playTag = std::make_unique<PlayTag>(tag, forward);
 | |
| 
 | |
|   PLAY_TRACE("    addTag", tag->name(), "rewind", rewind, "new playTag forward", playTag->forward);
 | |
| 
 | |
|   if (rewind) {
 | |
|     playTag->rewind = true;
 | |
| 
 | |
|     // Delay the deletion of currentPlayTag to this new tag
 | |
|     PlayTag* currentPlayTag = m_playing.back().get();
 | |
|     PlayTag* delayed = currentPlayTag;
 | |
| 
 | |
|     while (delayed->delayedDelete)
 | |
|       delayed = delayed->delayedDelete;
 | |
|     delayed->delayedDelete = playTag.get();
 | |
|     for (const Tag* otherTag : delayed->removeThese)
 | |
|       playTag->removeThese.push_back(otherTag);
 | |
|     playTag->removeThese.push_back(delayed->tag);
 | |
|     delayed->removeThese.clear();
 | |
| 
 | |
|     auto it = m_playing.end(), begin = m_playing.begin();
 | |
|     --it;
 | |
|     ASSERT(it->get() == currentPlayTag);
 | |
|     while (it != begin) {
 | |
|       if ((*it)->tag == delayed->tag)
 | |
|         break;
 | |
|       --it;
 | |
|     }
 | |
| 
 | |
|     m_playing.insert(it, std::move(playTag));
 | |
|   }
 | |
|   else {
 | |
|     m_playing.push_back(std::move(playTag));
 | |
|   }
 | |
|   m_played.insert(tag);
 | |
| }
 | |
| 
 | |
| void Playback::removeLastTagFromPlayed()
 | |
| {
 | |
|   PlayTag* playTag = m_playing.back().get();
 | |
| 
 | |
|   for (auto otherTag : playTag->removeThese) {
 | |
|     auto it = m_played.find(otherTag);
 | |
|     ASSERT(it != m_played.end());
 | |
|     if (it != m_played.end())
 | |
|       m_played.erase(it);
 | |
|   }
 | |
| 
 | |
|   auto it = m_played.find(playTag->tag);
 | |
|   ASSERT(it != m_played.end());
 | |
|   if (it != m_played.end())
 | |
|     m_played.erase(it);
 | |
| }
 | |
| 
 | |
| bool Playback::decrementRepeat(const frame_t frameDelta)
 | |
| {
 | |
|   while (true) {
 | |
|     Tag* tag = this->tag();
 | |
|     PLAY_TRACE("    Decrement tag", tag->name(), "repeat", m_playing.back()->repeat, "-1");
 | |
| 
 | |
|     if (m_playing.back()->repeat > 1) {
 | |
|       --m_playing.back()->repeat;
 | |
|       goToFirstTagFrame(tag);
 | |
| 
 | |
|       PLAY_TRACE("    Repeat tag",
 | |
|                  tag->name(),
 | |
|                  " frame=",
 | |
|                  m_frame,
 | |
|                  "repeat=",
 | |
|                  m_playing.back()->repeat,
 | |
|                  "forward=",
 | |
|                  m_playing.back()->forward);
 | |
|       // Tag has only 1 frame, then don't move the playback cue.
 | |
|       return tag->frames() > 1;
 | |
|     }
 | |
|     else {
 | |
|       // Remove tag from played
 | |
|       if (!m_playing.back()->delayedDelete) {
 | |
|         PLAY_TRACE("    Removing played tag", tag->name());
 | |
|         removeLastTagFromPlayed();
 | |
|       }
 | |
|       else {
 | |
|         PLAY_TRACE("    Delaying the removal of played tag", tag->name());
 | |
|       }
 | |
| 
 | |
|       // Delete and remove PlayTag
 | |
|       m_playing.pop_back();
 | |
| 
 | |
|       // Forward direction of the parent tag
 | |
|       int forward = (m_playing.empty() ? m_forward : m_playing.back()->forward);
 | |
|       bool rewind = (m_playing.empty() ? false : m_playing.back()->rewind);
 | |
| 
 | |
|       // New frame outside the tag
 | |
|       frame_t newFrame;
 | |
|       if (rewind && !m_playing.empty()) {
 | |
|         newFrame = firstTagFrame(m_playing.back()->tag);
 | |
|       }
 | |
|       else {
 | |
|         // Note that 'tag' means 'the last tag removed from m_playing'
 | |
|         newFrame = (frameDelta * forward < 0 ? tag->fromFrame() - 1 : tag->toFrame() + 1);
 | |
|       }
 | |
| 
 | |
|       PLAY_TRACE("    After tag", tag->name(), "possible new frame=", newFrame, "forward", forward);
 | |
| 
 | |
|       if (newFrame < 0 || newFrame > m_sprite->lastFrame()) {
 | |
|         if (m_playMode == PlayAll) {
 | |
|           stop();
 | |
|           return false;
 | |
|         }
 | |
|         if (newFrame < 0) {
 | |
|           // m_playing.empty() should never happen, because the only
 | |
|           // way to have "newFrame < 0" is if we have a tag on
 | |
|           // m_playing in REVERSE or PING_PONG_REVERSE which frame 0
 | |
|           // is contained into that tag.
 | |
|           ASSERT(!m_playing.empty());
 | |
|           if (m_playing.empty()) {
 | |
|             newFrame = m_sprite->lastFrame();
 | |
|           }
 | |
|           else {
 | |
|             // Special cases arise with PING_PONG_REVERSE aniDir and when
 | |
|             // the begining of the tag range matches with the first frame of
 | |
|             // the sprite.
 | |
|             // Consideration for tests inside:
 | |
|             // Playback.OnePingPongInsideOther
 | |
|             //     A            A
 | |
|             // >-------<    >-------<
 | |
|             //   B            B
 | |
|             // <--->        >---<
 | |
|             // 0 1 2 3 4    0 1 2 3 4
 | |
|             PlayTag* parentPlaying = m_playing.back().get();
 | |
|             // When parentPlaying is PING_PONG_REVERSE
 | |
|             // the next frame will be defined according:
 | |
|             // 1. The playloop has more repetitions to decrement
 | |
|             //    --> go to the next frame of the 'tag'
 | |
|             // 2. The playloop has no more repetitions to decrement
 | |
|             //    --> Start all the playloop again.
 | |
|             if (parentPlaying->repeat > 1) {
 | |
|               if (parentPlaying->tag->aniDir() == AniDir::PING_PONG_REVERSE)
 | |
|                 parentPlaying->invertForward();
 | |
|               --parentPlaying->repeat;
 | |
|               newFrame = tag->toFrame() + 1;
 | |
|             }
 | |
|             else
 | |
|               continue;
 | |
|           }
 | |
|         }
 | |
|         else if (newFrame > m_sprite->lastFrame()) {
 | |
|           // If all the tags were played and
 | |
|           // the 'tag' range  ==  timeline range and
 | |
|           // the 'tag' is PING_PONG_REVERSE -->
 | |
|           // The playloop has to start on the last frame of
 | |
|           // the timeline, or the first frame of the most nested
 | |
|           // tag (on reverse direction).
 | |
|           // Consideration for tests:
 | |
|           // Playback.WithTagRepetitions
 | |
|           // Playback.OnePingPongInsideOther
 | |
|           // Playback.OnePingPongInsideOther14, 15, 18 and 19
 | |
|           //    A      <-- last tag removed from 'm_playing', i.e. 'tag'
 | |
|           // >-----<
 | |
|           //    B      <-- most nested tag
 | |
|           // ***-***
 | |
|           // 0 1 2 3
 | |
|           if (m_playing.empty() && tag->aniDir() == AniDir::PING_PONG_REVERSE &&
 | |
|               tag->fromFrame() == 0 && tag->toFrame() == m_sprite->lastFrame()) {
 | |
|             m_frame = m_sprite->lastFrame();
 | |
|             handleEnterFrame(frameDelta, false);
 | |
|             if (m_playing.size() > 1) {
 | |
|               m_playing.back()->invertForward();
 | |
|               goToFirstTagFrame(m_playing.back()->tag);
 | |
|             }
 | |
|             return false;
 | |
|           }
 | |
| 
 | |
|           // 'tag' is contained by other tag and the last frame of each tag
 | |
|           // matches in the last frame of the sprite
 | |
|           if (!m_playing.empty() && tag->toFrame() == m_playing.back()->tag->toFrame()) {
 | |
|             PlayTag* parentPlaying = m_playing.back().get();
 | |
|             // The parentPlaying has no more repetitions to decrement
 | |
|             //    --> continue to remove the 'parentTag'
 | |
|             if (parentPlaying->repeat <= 1)
 | |
|               continue;
 | |
|             // Consideration for test:
 | |
|             // Playback.OnePingPongInsideOther
 | |
|             if (parentPlaying->tag->aniDir() == AniDir::PING_PONG ||
 | |
|                 parentPlaying->tag->aniDir() == AniDir::PING_PONG_REVERSE) {
 | |
|               parentPlaying->invertForward();
 | |
|               newFrame = tag->fromFrame() - 1;
 | |
|             }
 | |
|             // Consideration for test:
 | |
|             // Playback.OnePingPongInsideForward2
 | |
|             else if (parentPlaying->tag->aniDir() == AniDir::FORWARD) {
 | |
|               --parentPlaying->repeat;
 | |
|               newFrame = parentPlaying->tag->fromFrame();
 | |
|             }
 | |
|             else
 | |
|               newFrame = 0;
 | |
|           }
 | |
|           else
 | |
|             newFrame = 0;
 | |
|         }
 | |
|       }
 | |
| 
 | |
|       m_frame = newFrame;
 | |
| 
 | |
|       if (auto newTag = this->tag()) {
 | |
|         if (newTag->contains(m_frame)) {
 | |
|           PLAY_TRACE("    Back to tag", newTag->name(), "frame=", m_frame);
 | |
|           return false;
 | |
|         }
 | |
|         else {
 | |
|           // Now we try to decrement this tag repeat counter...
 | |
|         }
 | |
|       }
 | |
|       else {
 | |
|         // Special case where a ping-pong animation ends in the 1st
 | |
|         // frame and we are playing in loop mode, so starting the
 | |
|         // animation again should continue in the 2nd frame
 | |
|         if (m_playing.empty() && m_playMode == PlayInLoop &&
 | |
|             (tag->aniDir() == AniDir::PING_PONG || tag->aniDir() == AniDir::PING_PONG_REVERSE) &&
 | |
|             tag->fromFrame() == 0 && tag->toFrame() == m_sprite->lastFrame()) {
 | |
|           PLAY_TRACE("    Re-adding ping-pong tag", tag->name(), "frame=", m_frame);
 | |
|           addTag(tag, false, getParentForward());
 | |
|           return false;
 | |
|         }
 | |
|         else {
 | |
|           PLAY_TRACE("    Going outside the tag", tag->name(), "frame=", m_frame);
 | |
|           return false;
 | |
|         }
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| }
 | |
| 
 | |
| frame_t Playback::firstTagFrame(const Tag* tag)
 | |
| {
 | |
|   ASSERT(tag);
 | |
|   ASSERT(!m_playing.empty());
 | |
|   int forward = m_playing.back()->forward;
 | |
|   return (forward < 0 ? tag->toFrame() : tag->fromFrame());
 | |
| }
 | |
| 
 | |
| frame_t Playback::lastTagFrame(const Tag* tag)
 | |
| {
 | |
|   ASSERT(tag);
 | |
|   ASSERT(!m_playing.empty());
 | |
|   int forward = m_playing.back()->forward;
 | |
|   return (forward > 0 ? tag->toFrame() : tag->fromFrame());
 | |
| }
 | |
| 
 | |
| void Playback::goToFirstTagFrame(const Tag* tag)
 | |
| {
 | |
|   ASSERT(tag);
 | |
|   m_frame = firstTagFrame(tag);
 | |
|   PLAY_TRACE("    Go to first frame of tag", tag->name(), "frame=", m_frame);
 | |
| }
 | |
| 
 | |
| int Playback::getParentForward() const
 | |
| {
 | |
|   if (m_playing.empty())
 | |
|     return m_forward;
 | |
|   else
 | |
|     return m_playing.back()->forward;
 | |
| }
 | |
| 
 | |
| } // namespace doc
 |