Tweeny 4.1.0
A Tweening library for modern C++
Loading...
Searching...
No Matches
tween.tcc
1/*
2This file is part of the Tweeny library.
3
4Copyright (c) 2016-2026 Leonardo Guilherme Lucena de Freitas
5Copyright (c) 2016 Guilherme R. Costa
6
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:
13
14The above copyright notice and this permission notice shall be included in all
15copies or substantial portions of the Software.
16
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.
23*/
24
25#ifndef TWEENY_TWEEN_TCC
26#define TWEENY_TWEEN_TCC
27
28#include <algorithm>
29#include <cstdint>
30#include <tuple>
31#include <utility>
32
33#include "detail/interpolate.h"
34#include "easing.h"
35
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)) { }
38
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)) { }
41
42template <typename FirstValueType, typename... RemainingValueTypes>
43auto tweeny::tween<FirstValueType, RemainingValueTypes...>::find_key_frame_index(uint32_t frame) const -> size_t {
44 std::size_t i = 0;
45 while (i + 1 < key_frames.size() && frame >= key_frames[i + 1].position) { ++i; }
46 return i;
47}
48
49
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);
54
55 current_value = render(target_frame);
56 current_frame = target_frame;
57 current_keyframe_index = new_keyframe_index;
58
59 invoke_keyframe_listeners(old_keyframe_index, new_keyframe_index);
60 invoke_listeners(seek_listeners);
61 invoke_listeners(update_listeners);
62
63 if (progress() >= 1.0f) {
64 invoke_listeners(complete_listeners);
65 }
66
67 return current_value;
68}
69
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);
74
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;
79
80 invoke_keyframe_listeners(old_keyframe_index, target_key_frame);
81 invoke_listeners(jump_listeners);
82 invoke_listeners(update_listeners);
83
84 if (progress() >= 1.0f) {
85 invoke_listeners(complete_listeners);
86 }
87
88 return current_value;
89}
90
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;
94
95 uint32_t target_frame = current_frame;
96 if (frames < 0) {
97 const auto dec = static_cast<uint32_t>(-frames);
98 if (dec > target_frame) target_frame = 0;
99 else target_frame -= dec;
100 } else {
101 target_frame += static_cast<uint32_t>(frames);
102 }
103
104 const std::size_t new_keyframe_index = find_key_frame_index(target_frame);
105
106 current_value = render(target_frame);
107 current_frame = target_frame;
108 current_keyframe_index = new_keyframe_index;
109
110 invoke_keyframe_listeners(old_keyframe_index, new_keyframe_index);
111 invoke_listeners(step_listeners);
112 invoke_listeners(update_listeners);
113
114 if (progress() >= 1.0f) {
115 invoke_listeners(complete_listeners);
116 }
117
118 return current_value;
119}
120
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));
128}
129
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));
137}
138
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));
146}
147
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));
155}
156
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));
164}
165
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));
173}
174
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));
182}
183
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());
188
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);
193 }
194 }
195
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));
200 }
201 }
202}
203
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;
207
208 // Leave listeners
209 {
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);
216 }
217 }
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));
220 }
221 }
222
223 // Enter listeners
224 {
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);
231 }
232 }
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));
235 }
236 }
237}
238
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;
242
243 const auto as_return_value = [](const auto & val) -> tween_value_t {
244 if constexpr (ValuesCount == 1) {
245 return std::get<0>(val);
246 } else {
247 return val;
248 }
249 };
250
251 if (key_frames.empty()) {
252 if constexpr (ValuesCount == 1) {
253 return FirstValueType{};
254 } else {
255 return typename key_frame_t::values_t{};
256 }
257 }
258
259 const auto & first_key_frame = key_frames.front();
260 const auto & last_key_frame = key_frames.back();
261
262 target_frame = std::clamp(
263 target_frame,
264 first_key_frame.position,
265 last_key_frame.position
266 );
267
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);
270
271 std::size_t base_key_key_frame_idx = find_key_frame_index(target_frame);
272
273 if (base_key_key_frame_idx + 1 >= key_frames.size()) {
274 return as_return_value(last_key_frame.values);
275 }
276
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];
279
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;
283
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;
289 }
290 inbetween_progress = std::clamp(inbetween_progress, 0.0f, 1.0f);
291
292 auto values = detail::interpolate_values<FirstValueType, RemainingValueTypes...>(
293 inbetween_progress,
294 base_key_frame,
295 next_key_frame,
296 std::make_index_sequence<ValuesCount>{}
297 );
298
299 return as_return_value(values);
300}
301
302template <typename FirstValueType, typename... RemainingValueTypes>
303auto tweeny::tween<FirstValueType, RemainingValueTypes...>::peek() const -> tween_value_t {
304 return current_value;
305}
306
307template <typename FirstValueType, typename... RemainingValueTypes>
308auto tweeny::tween<FirstValueType, RemainingValueTypes...>::peek(uint32_t target_frame) const -> tween_value_t {
309 return render(target_frame);
310}
311
312template <typename FirstValueType, typename... RemainingValueTypes>
313auto tweeny::tween<FirstValueType, RemainingValueTypes...>::progress() const -> float {
314 if (key_frames.empty()) return 0.0f;
315
316 const auto & first_key_frame = key_frames.front();
317 const auto & last_key_frame = key_frames.back();
318
319 const uint32_t total_frames = last_key_frame.position - first_key_frame.position;
320 if (total_frames == 0) return 1.0f;
321
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);
324}
325
326#endif //TWEENY_TWEEN_TCC