| 
									
										
										
										
											2022-10-19 23:09:27 +08:00
										 |  |  | // Aseprite Document Library
 | 
					
						
							| 
									
										
										
										
											2024-04-09 06:01:27 +08:00
										 |  |  | // Copyright (C) 2021-2024  Igara Studio S.A.
 | 
					
						
							| 
									
										
										
										
											2022-10-19 23:09:27 +08:00
										 |  |  | //
 | 
					
						
							|  |  |  | // 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"
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-10-21 01:51:42 +08:00
										 |  |  | #include "base/remove_from_container.h"
 | 
					
						
							| 
									
										
										
										
											2022-10-19 23:09:27 +08:00
										 |  |  | #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, | 
					
						
							| 
									
										
										
										
											2023-12-08 04:31:49 +08:00
										 |  |  |                    const Tag* tag, | 
					
						
							|  |  |  |                    const int forward) | 
					
						
							| 
									
										
										
										
											2022-10-19 23:09:27 +08:00
										 |  |  |   : m_sprite(sprite) | 
					
						
							|  |  |  |   , m_tags(tags) | 
					
						
							|  |  |  |   , m_initialFrame(frame) | 
					
						
							|  |  |  |   , m_frame(frame) | 
					
						
							|  |  |  |   , m_playMode(playMode) | 
					
						
							| 
									
										
										
										
											2023-12-08 04:31:49 +08:00
										 |  |  |   , m_forward(forward) | 
					
						
							| 
									
										
										
										
											2022-10-19 23:09:27 +08:00
										 |  |  | { | 
					
						
							|  |  |  |   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); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-12-08 04:31:49 +08:00
										 |  |  |       // Loop the given tag in the constructor infinite times
 | 
					
						
							| 
									
										
										
										
											2022-10-19 23:09:27 +08:00
										 |  |  |       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); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-03-16 02:18:01 +08:00
										 |  |  |     frameDelta -= step; | 
					
						
							| 
									
										
										
										
											2022-10-19 23:09:27 +08:00
										 |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   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); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-10-21 01:51:42 +08:00
										 |  |  | 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; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-10-19 23:09:27 +08:00
										 |  |  | 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); | 
					
						
							| 
									
										
										
										
											2024-04-09 06:01:27 +08:00
										 |  |  |             if (!firstTime) { | 
					
						
							| 
									
										
										
										
											2023-03-16 02:18:01 +08:00
										 |  |  |               goToFirstTagFrame(t); | 
					
						
							| 
									
										
										
										
											2024-04-09 06:01:27 +08:00
										 |  |  |               // 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); | 
					
						
							|  |  |  |             } | 
					
						
							| 
									
										
										
										
											2022-10-19 23:09:27 +08:00
										 |  |  |           } | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |       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: { | 
					
						
							| 
									
										
										
										
											2023-03-16 04:53:12 +08:00
										 |  |  |       auto tag = this->tag(); | 
					
						
							|  |  |  |       if (tag && tag->contains(m_frame)) { | 
					
						
							| 
									
										
										
										
											2022-10-19 23:09:27 +08:00
										 |  |  |         ASSERT(!m_playing.empty()); | 
					
						
							| 
									
										
										
										
											2023-09-21 03:04:50 +08:00
										 |  |  |         [[maybe_unused]] | 
					
						
							| 
									
										
										
										
											2022-10-19 23:09:27 +08:00
										 |  |  |         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) && | 
					
						
							| 
									
										
										
										
											2023-03-16 02:18:01 +08:00
										 |  |  |             (frameDelta > 0 && m_frame == lastTagFrame(tag))) { | 
					
						
							| 
									
										
										
										
											2022-10-19 23:09:27 +08:00
										 |  |  |           decrementRepeat(frameDelta); | 
					
						
							|  |  |  |           return false; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         // Change ping-pong direction
 | 
					
						
							|  |  |  |         else if ((tag->aniDir() == AniDir::PING_PONG || | 
					
						
							|  |  |  |                   tag->aniDir() == AniDir::PING_PONG_REVERSE) && | 
					
						
							| 
									
										
										
										
											2023-03-16 02:18:01 +08:00
										 |  |  |                  m_frame == lastTagFrame(tag)) { | 
					
						
							| 
									
										
										
										
											2022-10-19 23:09:27 +08:00
										 |  |  |           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); | 
					
						
							|  |  |  |         } | 
					
						
							| 
									
										
										
										
											2023-03-16 04:53:12 +08:00
										 |  |  |         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); | 
					
						
							| 
									
										
										
										
											2023-03-16 02:20:53 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-03-16 04:53:12 +08:00
										 |  |  |             m_frame = lastTagFrame(tag); | 
					
						
							|  |  |  |             return false; | 
					
						
							|  |  |  |           } | 
					
						
							|  |  |  |           break; | 
					
						
							| 
									
										
										
										
											2023-03-16 02:20:53 +08:00
										 |  |  |         } | 
					
						
							| 
									
										
										
										
											2024-01-12 23:03:17 +08:00
										 |  |  |         else if (m_playMode == PlayAll) | 
					
						
							|  |  |  |           break; | 
					
						
							| 
									
										
										
										
											2022-10-19 23:09:27 +08:00
										 |  |  |       } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-12-08 04:31:49 +08:00
										 |  |  |       if (frameDelta > 0 && ((m_frame == m_sprite->lastFrame() && m_forward > 0) || | 
					
						
							|  |  |  |                              (m_frame == 0 && m_forward < 0))) { | 
					
						
							| 
									
										
										
										
											2022-10-19 23:09:27 +08:00
										 |  |  |         if (m_playMode == PlayInLoop) { | 
					
						
							| 
									
										
										
										
											2023-12-08 04:31:49 +08:00
										 |  |  |           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(); | 
					
						
							|  |  |  |           } | 
					
						
							| 
									
										
										
										
											2022-10-19 23:09:27 +08:00
										 |  |  |           return false; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         else { | 
					
						
							|  |  |  |           PLAY_TRACE("    Stop animation (PlayAll)"); | 
					
						
							|  |  |  |           stop(); | 
					
						
							|  |  |  |           return false; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |       } | 
					
						
							| 
									
										
										
										
											2023-12-08 04:31:49 +08:00
										 |  |  |       else if (frameDelta < 0 && ((m_frame == 0 && m_forward > 0) || | 
					
						
							|  |  |  |                                   (m_frame == m_sprite->lastFrame() && m_forward < 0))) { | 
					
						
							| 
									
										
										
										
											2022-10-19 23:09:27 +08:00
										 |  |  |         if (m_playMode == PlayInLoop) { | 
					
						
							| 
									
										
										
										
											2023-12-08 04:31:49 +08:00
										 |  |  |           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; | 
					
						
							|  |  |  |           } | 
					
						
							| 
									
										
										
										
											2022-10-19 23:09:27 +08:00
										 |  |  |           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; | 
					
						
							| 
									
										
										
										
											2023-03-16 02:18:01 +08:00
										 |  |  |       goToFirstTagFrame(tag); | 
					
						
							| 
									
										
										
										
											2022-10-19 23:09:27 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  |       PLAY_TRACE("    Repeat tag", | 
					
						
							|  |  |  |                  tag->name(), | 
					
						
							|  |  |  |                  " frame=", | 
					
						
							|  |  |  |                  m_frame, | 
					
						
							|  |  |  |                  "repeat=", | 
					
						
							|  |  |  |                  m_playing.back()->repeat, | 
					
						
							|  |  |  |                  "forward=", | 
					
						
							|  |  |  |                  m_playing.back()->forward); | 
					
						
							| 
									
										
										
										
											2024-02-26 23:43:48 +08:00
										 |  |  |       // Tag has only 1 frame, then don't move the playback cue.
 | 
					
						
							|  |  |  |       return tag->frames() > 1; | 
					
						
							| 
									
										
										
										
											2022-10-19 23:09:27 +08:00
										 |  |  |     } | 
					
						
							|  |  |  |     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
 | 
					
						
							| 
									
										
										
										
											2023-12-08 04:31:49 +08:00
										 |  |  |       int forward = (m_playing.empty() ? m_forward : m_playing.back()->forward); | 
					
						
							| 
									
										
										
										
											2022-10-19 23:09:27 +08:00
										 |  |  |       bool rewind = (m_playing.empty() ? false : m_playing.back()->rewind); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       // New frame outside the tag
 | 
					
						
							|  |  |  |       frame_t newFrame; | 
					
						
							| 
									
										
										
										
											2024-04-09 06:01:27 +08:00
										 |  |  |       if (rewind && !m_playing.empty()) { | 
					
						
							| 
									
										
										
										
											2023-03-16 02:18:01 +08:00
										 |  |  |         newFrame = firstTagFrame(m_playing.back()->tag); | 
					
						
							| 
									
										
										
										
											2022-10-19 23:09:27 +08:00
										 |  |  |       } | 
					
						
							|  |  |  |       else { | 
					
						
							| 
									
										
										
										
											2024-04-09 06:01:27 +08:00
										 |  |  |         // Note that 'tag' means 'the last tag removed from m_playing'
 | 
					
						
							| 
									
										
										
										
											2022-10-19 23:09:27 +08:00
										 |  |  |         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; | 
					
						
							|  |  |  |         } | 
					
						
							| 
									
										
										
										
											2024-04-09 06:01:27 +08:00
										 |  |  |         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; | 
					
						
							|  |  |  |         } | 
					
						
							| 
									
										
										
										
											2022-10-19 23:09:27 +08:00
										 |  |  |       } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       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; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-03-16 02:18:01 +08:00
										 |  |  | frame_t Playback::firstTagFrame(const Tag* tag) | 
					
						
							| 
									
										
										
										
											2022-10-19 23:09:27 +08:00
										 |  |  | { | 
					
						
							|  |  |  |   ASSERT(tag); | 
					
						
							|  |  |  |   ASSERT(!m_playing.empty()); | 
					
						
							|  |  |  |   int forward = m_playing.back()->forward; | 
					
						
							| 
									
										
										
										
											2023-03-16 02:18:01 +08:00
										 |  |  |   return (forward < 0 ? tag->toFrame() : tag->fromFrame()); | 
					
						
							| 
									
										
										
										
											2022-10-19 23:09:27 +08:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-03-16 02:18:01 +08:00
										 |  |  | frame_t Playback::lastTagFrame(const Tag* tag) | 
					
						
							| 
									
										
										
										
											2022-10-19 23:09:27 +08:00
										 |  |  | { | 
					
						
							|  |  |  |   ASSERT(tag); | 
					
						
							| 
									
										
										
										
											2023-03-16 02:18:01 +08:00
										 |  |  |   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); | 
					
						
							| 
									
										
										
										
											2022-10-19 23:09:27 +08:00
										 |  |  |   PLAY_TRACE("    Go to first frame of tag", tag->name(), "frame=", m_frame); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | int Playback::getParentForward() const | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |   if (m_playing.empty()) | 
					
						
							| 
									
										
										
										
											2023-12-08 04:31:49 +08:00
										 |  |  |     return m_forward; | 
					
						
							| 
									
										
										
										
											2022-10-19 23:09:27 +08:00
										 |  |  |   else | 
					
						
							|  |  |  |     return m_playing.back()->forward; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | } // namespace doc
 |