2This file is part of the Tweeny library.
4Copyright (c) 2016-2026 Leonardo Guilherme Lucena de Freitas
5Copyright (c) 2016 Guilherme R. Costa
7Permission is hereby granted, free of charge, to any person obtaining a copy of
8this software and associated documentation files (the "Software"), to deal in
9the Software without restriction, including without limitation the rights to
10use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
11the Software, and to permit persons to whom the Software is furnished to do so,
12subject to the following conditions:
14The above copyright notice and this permission notice shall be included in all
15copies or substantial portions of the Software.
17THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
19FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
20COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
21IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
22CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
25#ifndef TWEENY_TWEEN_TCC
26#define TWEENY_TWEEN_TCC
33#include "detail/interpolate.h"
36template <typename FirstValueType, typename... RemainingValueTypes>
37tweeny::tween<FirstValueType, RemainingValueTypes...>::tween(const key_frames_t & key_frames_input) : key_frames(key_frames_input), current_value(render(0)) { }
39template <typename FirstValueType, typename... RemainingValueTypes>
40tweeny::tween<FirstValueType, RemainingValueTypes...>::tween(key_frames_t && key_frames_input) : key_frames(std::move(key_frames_input)), current_value(render(0)) { }
42template <typename FirstValueType, typename... RemainingValueTypes>
43auto tweeny::tween<FirstValueType, RemainingValueTypes...>::find_key_frame_index(uint32_t frame) const -> size_t {
45 while (i + 1 < key_frames.size() && frame >= key_frames[i + 1].position) { ++i; }
50template <typename FirstValueType, typename... RemainingValueTypes>
51auto tweeny::tween<FirstValueType, RemainingValueTypes...>::seek(const uint32_t target_frame) -> tween_value_t {
52 const std::size_t old_keyframe_index = current_keyframe_index;
53 const std::size_t new_keyframe_index = find_key_frame_index(target_frame);
55 current_value = render(target_frame);
56 current_frame = target_frame;
57 current_keyframe_index = new_keyframe_index;
59 invoke_keyframe_listeners(old_keyframe_index, new_keyframe_index);
60 invoke_listeners(seek_listeners);
61 invoke_listeners(update_listeners);
63 if (progress() >= 1.0f) {
64 invoke_listeners(complete_listeners);
70template <typename FirstValueType, typename... RemainingValueTypes>
71auto tweeny::tween<FirstValueType, RemainingValueTypes...>::jump(std::size_t target_key_frame) -> tween_value_t {
72 const std::size_t old_keyframe_index = current_keyframe_index;
73 target_key_frame = std::clamp(target_key_frame, static_cast<size_t>(0), key_frames.size() - 1);
75 const auto target_frame = static_cast<uint32_t>(key_frames[target_key_frame].position);
76 current_value = render(target_frame);
77 current_frame = target_frame;
78 current_keyframe_index = target_key_frame;
80 invoke_keyframe_listeners(old_keyframe_index, target_key_frame);
81 invoke_listeners(jump_listeners);
82 invoke_listeners(update_listeners);
84 if (progress() >= 1.0f) {
85 invoke_listeners(complete_listeners);
91template <typename FirstValueType, typename... RemainingValueTypes>
92auto tweeny::tween<FirstValueType, RemainingValueTypes...>::step(const int32_t frames) -> tween_value_t {
93 const std::size_t old_keyframe_index = current_keyframe_index;
95 uint32_t target_frame = current_frame;
97 const auto dec = static_cast<uint32_t>(-frames);
98 if (dec > target_frame) target_frame = 0;
99 else target_frame -= dec;
101 target_frame += static_cast<uint32_t>(frames);
104 const std::size_t new_keyframe_index = find_key_frame_index(target_frame);
106 current_value = render(target_frame);
107 current_frame = target_frame;
108 current_keyframe_index = new_keyframe_index;
110 invoke_keyframe_listeners(old_keyframe_index, new_keyframe_index);
111 invoke_listeners(step_listeners);
112 invoke_listeners(update_listeners);
114 if (progress() >= 1.0f) {
115 invoke_listeners(complete_listeners);
118 return current_value;
121template <typename FirstValueType, typename ... RemainingValueTypes>
122template <typename Callback>
123auto tweeny::tween<FirstValueType, RemainingValueTypes...>::on(detail::event::step_t, Callback && cb) -> void {
124 using result_t = std::invoke_result_t<Callback, tween&>;
125 static_assert(std::is_same_v<result_t, event::response>,
126 "step callback must return tweeny::event::response");
127 step_listeners.emplace_back(std::forward<Callback>(cb));
130template <typename FirstValueType, typename ... RemainingValueTypes>
131template <typename Callback>
132auto tweeny::tween<FirstValueType, RemainingValueTypes...>::on(detail::event::seek_t, Callback && cb) -> void {
133 using result_t = std::invoke_result_t<Callback, tween&>;
134 static_assert(std::is_same_v<result_t, event::response>,
135 "seek callback must return tweeny::event::response");
136 seek_listeners.emplace_back(std::forward<Callback>(cb));
139template <typename FirstValueType, typename ... RemainingValueTypes>
140template <typename Callback>
141auto tweeny::tween<FirstValueType, RemainingValueTypes...>::on(detail::event::jump_t, Callback && cb) -> void {
142 using result_t = std::invoke_result_t<Callback, tween&>;
143 static_assert(std::is_same_v<result_t, event::response>,
144 "jump callback must return tweeny::event::response");
145 jump_listeners.emplace_back(std::forward<Callback>(cb));
148template <typename FirstValueType, typename ... RemainingValueTypes>
149template <typename Callback>
150auto tweeny::tween<FirstValueType, RemainingValueTypes...>::on(detail::event::complete_t, Callback && cb) -> void {
151 using result_t = std::invoke_result_t<Callback, tween&>;
152 static_assert(std::is_same_v<result_t, event::response>,
153 "complete callback must return tweeny::event::response");
154 complete_listeners.emplace_back(std::forward<Callback>(cb));
157template <typename FirstValueType, typename ... RemainingValueTypes>
158template <typename Callback>
159auto tweeny::tween<FirstValueType, RemainingValueTypes...>::on(detail::event::keyframeEnter_t, Callback && cb) -> void {
160 using result_t = std::invoke_result_t<Callback, tween&, event::keyframeEnter>;
161 static_assert(std::is_same_v<result_t, event::response>,
162 "keyframeEnter callback must return tweeny::event::response");
163 keyframe_enter_listeners.emplace_back(std::forward<Callback>(cb));
166template <typename FirstValueType, typename ... RemainingValueTypes>
167template <typename Callback>
168auto tweeny::tween<FirstValueType, RemainingValueTypes...>::on(detail::event::keyframeLeave_t, Callback && cb) -> void {
169 using result_t = std::invoke_result_t<Callback, tween&, event::keyframeLeave>;
170 static_assert(std::is_same_v<result_t, event::response>,
171 "keyframeLeave callback must return tweeny::event::response");
172 keyframe_leave_listeners.emplace_back(std::forward<Callback>(cb));
175template <typename FirstValueType, typename ... RemainingValueTypes>
176template <typename Callback>
177auto tweeny::tween<FirstValueType, RemainingValueTypes...>::on(detail::event::update_t, Callback && cb) -> void {
178 using result_t = std::invoke_result_t<Callback, tween&>;
179 static_assert(std::is_same_v<result_t, event::response>,
180 "update callback must return tweeny::event::response");
181 update_listeners.emplace_back(std::forward<Callback>(cb));
184template <typename FirstValueType, typename... RemainingValueTypes>
185auto tweeny::tween<FirstValueType, RemainingValueTypes...>::invoke_listeners(std::vector<callback_t>& listeners) -> void {
186 std::vector<std::size_t> to_remove;
187 to_remove.reserve(listeners.size());
189 for (std::size_t i = 0; i < listeners.size(); ++i) {
190 const auto resp = listeners[i](*this);
191 if (resp == event::response::unsubscribe) {
192 to_remove.push_back(i);
196 if (!to_remove.empty()) {
197 using diff_t = typename std::vector<callback_t>::difference_type;
198 for (auto it = to_remove.rbegin(); it != to_remove.rend(); ++it) {
199 listeners.erase(listeners.begin() + static_cast<diff_t>(*it));
204template <typename FirstValueType, typename... RemainingValueTypes>
205auto tweeny::tween<FirstValueType, RemainingValueTypes...>::invoke_keyframe_listeners(const std::size_t old_keyframe_index, const std::size_t new_keyframe_index) -> void {
206 if (old_keyframe_index == new_keyframe_index) return;
210 std::vector<std::size_t> to_remove;
211 const struct event::keyframeLeave evt{old_keyframe_index};
212 for (std::size_t i = 0; i < keyframe_leave_listeners.size(); ++i) {
213 const auto resp = keyframe_leave_listeners[i](*this, evt);
214 if (resp == event::response::unsubscribe) {
215 to_remove.push_back(i);
218 for (auto it = to_remove.rbegin(); it != to_remove.rend(); ++it) {
219 keyframe_leave_listeners.erase(keyframe_leave_listeners.begin() + static_cast<typename std::vector<keyframe_leave_callback_t>::difference_type>(*it));
225 std::vector<std::size_t> to_remove;
226 const struct event::keyframeEnter evt{new_keyframe_index};
227 for (std::size_t i = 0; i < keyframe_enter_listeners.size(); ++i) {
228 const auto resp = keyframe_enter_listeners[i](*this, evt);
229 if (resp == event::response::unsubscribe) {
230 to_remove.push_back(i);
233 for (auto it = to_remove.rbegin(); it != to_remove.rend(); ++it) {
234 keyframe_enter_listeners.erase(keyframe_enter_listeners.begin() + static_cast<typename std::vector<keyframe_enter_callback_t>::difference_type>(*it));
239template <typename FirstValueType, typename... RemainingValueTypes>
240auto tweeny::tween<FirstValueType, RemainingValueTypes...>::render(uint32_t target_frame) const -> tween_value_t {
241 constexpr std::size_t ValuesCount = sizeof...(RemainingValueTypes) + 1;
243 const auto as_return_value = [](const auto & val) -> tween_value_t {
244 if constexpr (ValuesCount == 1) {
245 return std::get<0>(val);
251 if (key_frames.empty()) {
252 if constexpr (ValuesCount == 1) {
253 return FirstValueType{};
255 return typename key_frame_t::values_t{};
259 const auto & first_key_frame = key_frames.front();
260 const auto & last_key_frame = key_frames.back();
262 target_frame = std::clamp(
264 first_key_frame.position,
265 last_key_frame.position
268 if (target_frame <= first_key_frame.position) return as_return_value(first_key_frame.values);
269 if (target_frame >= last_key_frame.position) return as_return_value(last_key_frame.values);
271 std::size_t base_key_key_frame_idx = find_key_frame_index(target_frame);
273 if (base_key_key_frame_idx + 1 >= key_frames.size()) {
274 return as_return_value(last_key_frame.values);
277 const key_frame_t & base_key_frame = key_frames[base_key_key_frame_idx];
278 const key_frame_t & next_key_frame = key_frames[base_key_key_frame_idx + 1];
280 const int64_t base_kf_position = base_key_frame.position;
281 const uint32_t target_kf_position = next_key_frame.position;
282 const int64_t target_frame_i64 = target_frame;
284 float inbetween_progress = 1.0f;
285 if (target_kf_position > base_kf_position) {
286 const auto numerator = static_cast<float>(target_frame_i64 - base_kf_position);
287 const auto denominator = static_cast<float>(target_kf_position - base_kf_position);
288 inbetween_progress = numerator / denominator;
290 inbetween_progress = std::clamp(inbetween_progress, 0.0f, 1.0f);
292 auto values = detail::interpolate_values<FirstValueType, RemainingValueTypes...>(
296 std::make_index_sequence<ValuesCount>{}
299 return as_return_value(values);
302template <typename FirstValueType, typename... RemainingValueTypes>
303auto tweeny::tween<FirstValueType, RemainingValueTypes...>::peek() const -> tween_value_t {
304 return current_value;
307template <typename FirstValueType, typename... RemainingValueTypes>
308auto tweeny::tween<FirstValueType, RemainingValueTypes...>::peek(uint32_t target_frame) const -> tween_value_t {
309 return render(target_frame);
312template <typename FirstValueType, typename... RemainingValueTypes>
313auto tweeny::tween<FirstValueType, RemainingValueTypes...>::progress() const -> float {
314 if (key_frames.empty()) return 0.0f;
316 const auto & first_key_frame = key_frames.front();
317 const auto & last_key_frame = key_frames.back();
319 const uint32_t total_frames = last_key_frame.position - first_key_frame.position;
320 if (total_frames == 0) return 1.0f;
322 const uint32_t current_offset = current_frame - first_key_frame.position;
323 return std::clamp(static_cast<float>(current_offset) / static_cast<float>(total_frames), 0.0f, 1.0f);
326#endif //TWEENY_TWEEN_TCC