| 1 | /**************************************************************************/ |
| 2 | /* tween.cpp */ |
| 3 | /**************************************************************************/ |
| 4 | /* This file is part of: */ |
| 5 | /* GODOT ENGINE */ |
| 6 | /* https://godotengine.org */ |
| 7 | /**************************************************************************/ |
| 8 | /* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ |
| 9 | /* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ |
| 10 | /* */ |
| 11 | /* Permission is hereby granted, free of charge, to any person obtaining */ |
| 12 | /* a copy of this software and associated documentation files (the */ |
| 13 | /* "Software"), to deal in the Software without restriction, including */ |
| 14 | /* without limitation the rights to use, copy, modify, merge, publish, */ |
| 15 | /* distribute, sublicense, and/or sell copies of the Software, and to */ |
| 16 | /* permit persons to whom the Software is furnished to do so, subject to */ |
| 17 | /* the following conditions: */ |
| 18 | /* */ |
| 19 | /* The above copyright notice and this permission notice shall be */ |
| 20 | /* included in all copies or substantial portions of the Software. */ |
| 21 | /* */ |
| 22 | /* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ |
| 23 | /* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ |
| 24 | /* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ |
| 25 | /* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ |
| 26 | /* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ |
| 27 | /* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ |
| 28 | /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ |
| 29 | /**************************************************************************/ |
| 30 | |
| 31 | #include "tween.h" |
| 32 | |
| 33 | #include "scene/animation/easing_equations.h" |
| 34 | #include "scene/main/node.h" |
| 35 | #include "scene/resources/animation.h" |
| 36 | |
| 37 | #define CHECK_VALID() \ |
| 38 | ERR_FAIL_COND_V_MSG(!valid, nullptr, "Tween invalid. Either finished or created outside scene tree."); \ |
| 39 | ERR_FAIL_COND_V_MSG(started, nullptr, "Can't append to a Tween that has started. Use stop() first."); |
| 40 | |
| 41 | Tween::interpolater Tween::interpolaters[Tween::TRANS_MAX][Tween::EASE_MAX] = { |
| 42 | { &linear::in, &linear::in, &linear::in, &linear::in }, // Linear is the same for each easing. |
| 43 | { &sine::in, &sine::out, &sine::in_out, &sine::out_in }, |
| 44 | { &quint::in, &quint::out, &quint::in_out, &quint::out_in }, |
| 45 | { &quart::in, &quart::out, &quart::in_out, &quart::out_in }, |
| 46 | { &quad::in, &quad::out, &quad::in_out, &quad::out_in }, |
| 47 | { &expo::in, &expo::out, &expo::in_out, &expo::out_in }, |
| 48 | { &elastic::in, &elastic::out, &elastic::in_out, &elastic::out_in }, |
| 49 | { &cubic::in, &cubic::out, &cubic::in_out, &cubic::out_in }, |
| 50 | { &circ::in, &circ::out, &circ::in_out, &circ::out_in }, |
| 51 | { &bounce::in, &bounce::out, &bounce::in_out, &bounce::out_in }, |
| 52 | { &back::in, &back::out, &back::in_out, &back::out_in }, |
| 53 | { &spring::in, &spring::out, &spring::in_out, &spring::out_in }, |
| 54 | }; |
| 55 | |
| 56 | void Tweener::set_tween(const Ref<Tween> &p_tween) { |
| 57 | tween = p_tween; |
| 58 | } |
| 59 | |
| 60 | void Tweener::clear_tween() { |
| 61 | tween.unref(); |
| 62 | } |
| 63 | |
| 64 | void Tweener::_bind_methods() { |
| 65 | ADD_SIGNAL(MethodInfo("finished" )); |
| 66 | } |
| 67 | |
| 68 | bool Tween::_validate_type_match(const Variant &p_from, Variant &r_to) { |
| 69 | if (p_from.get_type() != r_to.get_type()) { |
| 70 | // Cast r_to between double and int to avoid minor annoyances. |
| 71 | if (p_from.get_type() == Variant::FLOAT && r_to.get_type() == Variant::INT) { |
| 72 | r_to = double(r_to); |
| 73 | } else if (p_from.get_type() == Variant::INT && r_to.get_type() == Variant::FLOAT) { |
| 74 | r_to = int(r_to); |
| 75 | } else { |
| 76 | ERR_FAIL_V_MSG(false, "Type mismatch between initial and final value: " + Variant::get_type_name(p_from.get_type()) + " and " + Variant::get_type_name(r_to.get_type())); |
| 77 | } |
| 78 | } |
| 79 | return true; |
| 80 | } |
| 81 | |
| 82 | void Tween::_start_tweeners() { |
| 83 | if (tweeners.is_empty()) { |
| 84 | dead = true; |
| 85 | ERR_FAIL_MSG("Tween without commands, aborting." ); |
| 86 | } |
| 87 | |
| 88 | for (Ref<Tweener> &tweener : tweeners.write[current_step]) { |
| 89 | tweener->start(); |
| 90 | } |
| 91 | } |
| 92 | |
| 93 | void Tween::_stop_internal(bool p_reset) { |
| 94 | running = false; |
| 95 | if (p_reset) { |
| 96 | started = false; |
| 97 | dead = false; |
| 98 | total_time = 0; |
| 99 | } |
| 100 | } |
| 101 | |
| 102 | Ref<PropertyTweener> Tween::tween_property(const Object *p_target, const NodePath &p_property, Variant p_to, double p_duration) { |
| 103 | ERR_FAIL_NULL_V(p_target, nullptr); |
| 104 | CHECK_VALID(); |
| 105 | |
| 106 | Vector<StringName> property_subnames = p_property.get_as_property_path().get_subnames(); |
| 107 | if (!_validate_type_match(p_target->get_indexed(property_subnames), p_to)) { |
| 108 | return nullptr; |
| 109 | } |
| 110 | |
| 111 | Ref<PropertyTweener> tweener = memnew(PropertyTweener(p_target, property_subnames, p_to, p_duration)); |
| 112 | append(tweener); |
| 113 | return tweener; |
| 114 | } |
| 115 | |
| 116 | Ref<IntervalTweener> Tween::tween_interval(double p_time) { |
| 117 | CHECK_VALID(); |
| 118 | |
| 119 | Ref<IntervalTweener> tweener = memnew(IntervalTweener(p_time)); |
| 120 | append(tweener); |
| 121 | return tweener; |
| 122 | } |
| 123 | |
| 124 | Ref<CallbackTweener> Tween::tween_callback(const Callable &p_callback) { |
| 125 | CHECK_VALID(); |
| 126 | |
| 127 | Ref<CallbackTweener> tweener = memnew(CallbackTweener(p_callback)); |
| 128 | append(tweener); |
| 129 | return tweener; |
| 130 | } |
| 131 | |
| 132 | Ref<MethodTweener> Tween::tween_method(const Callable &p_callback, const Variant p_from, Variant p_to, double p_duration) { |
| 133 | CHECK_VALID(); |
| 134 | |
| 135 | if (!_validate_type_match(p_from, p_to)) { |
| 136 | return nullptr; |
| 137 | } |
| 138 | |
| 139 | Ref<MethodTweener> tweener = memnew(MethodTweener(p_callback, p_from, p_to, p_duration)); |
| 140 | append(tweener); |
| 141 | return tweener; |
| 142 | } |
| 143 | |
| 144 | void Tween::append(Ref<Tweener> p_tweener) { |
| 145 | p_tweener->set_tween(this); |
| 146 | |
| 147 | if (parallel_enabled) { |
| 148 | current_step = MAX(current_step, 0); |
| 149 | } else { |
| 150 | current_step++; |
| 151 | } |
| 152 | parallel_enabled = default_parallel; |
| 153 | |
| 154 | tweeners.resize(current_step + 1); |
| 155 | tweeners.write[current_step].push_back(p_tweener); |
| 156 | } |
| 157 | |
| 158 | void Tween::stop() { |
| 159 | _stop_internal(true); |
| 160 | } |
| 161 | |
| 162 | void Tween::pause() { |
| 163 | _stop_internal(false); |
| 164 | } |
| 165 | |
| 166 | void Tween::play() { |
| 167 | ERR_FAIL_COND_MSG(!valid, "Tween invalid. Either finished or created outside scene tree." ); |
| 168 | ERR_FAIL_COND_MSG(dead, "Can't play finished Tween, use stop() first to reset its state." ); |
| 169 | running = true; |
| 170 | } |
| 171 | |
| 172 | void Tween::kill() { |
| 173 | running = false; // For the sake of is_running(). |
| 174 | dead = true; |
| 175 | } |
| 176 | |
| 177 | bool Tween::is_running() { |
| 178 | return running; |
| 179 | } |
| 180 | |
| 181 | bool Tween::is_valid() { |
| 182 | return valid; |
| 183 | } |
| 184 | |
| 185 | void Tween::clear() { |
| 186 | valid = false; |
| 187 | |
| 188 | for (List<Ref<Tweener>> &step : tweeners) { |
| 189 | for (Ref<Tweener> &tweener : step) { |
| 190 | tweener->clear_tween(); |
| 191 | } |
| 192 | } |
| 193 | tweeners.clear(); |
| 194 | } |
| 195 | |
| 196 | Ref<Tween> Tween::bind_node(const Node *p_node) { |
| 197 | ERR_FAIL_NULL_V(p_node, this); |
| 198 | |
| 199 | bound_node = p_node->get_instance_id(); |
| 200 | is_bound = true; |
| 201 | return this; |
| 202 | } |
| 203 | |
| 204 | Ref<Tween> Tween::set_process_mode(TweenProcessMode p_mode) { |
| 205 | process_mode = p_mode; |
| 206 | return this; |
| 207 | } |
| 208 | |
| 209 | Tween::TweenProcessMode Tween::get_process_mode() { |
| 210 | return process_mode; |
| 211 | } |
| 212 | |
| 213 | Ref<Tween> Tween::set_pause_mode(TweenPauseMode p_mode) { |
| 214 | pause_mode = p_mode; |
| 215 | return this; |
| 216 | } |
| 217 | |
| 218 | Tween::TweenPauseMode Tween::get_pause_mode() { |
| 219 | return pause_mode; |
| 220 | } |
| 221 | |
| 222 | Ref<Tween> Tween::set_parallel(bool p_parallel) { |
| 223 | default_parallel = p_parallel; |
| 224 | parallel_enabled = p_parallel; |
| 225 | return this; |
| 226 | } |
| 227 | |
| 228 | Ref<Tween> Tween::set_loops(int p_loops) { |
| 229 | loops = p_loops; |
| 230 | return this; |
| 231 | } |
| 232 | |
| 233 | int Tween::get_loops_left() const { |
| 234 | if (loops <= 0) { |
| 235 | return -1; // Infinite loop. |
| 236 | } else { |
| 237 | return loops - loops_done; |
| 238 | } |
| 239 | } |
| 240 | |
| 241 | Ref<Tween> Tween::set_speed_scale(float p_speed) { |
| 242 | speed_scale = p_speed; |
| 243 | return this; |
| 244 | } |
| 245 | |
| 246 | Ref<Tween> Tween::set_trans(TransitionType p_trans) { |
| 247 | default_transition = p_trans; |
| 248 | return this; |
| 249 | } |
| 250 | |
| 251 | Tween::TransitionType Tween::get_trans() { |
| 252 | return default_transition; |
| 253 | } |
| 254 | |
| 255 | Ref<Tween> Tween::set_ease(EaseType p_ease) { |
| 256 | default_ease = p_ease; |
| 257 | return this; |
| 258 | } |
| 259 | |
| 260 | Tween::EaseType Tween::get_ease() { |
| 261 | return default_ease; |
| 262 | } |
| 263 | |
| 264 | Ref<Tween> Tween::parallel() { |
| 265 | parallel_enabled = true; |
| 266 | return this; |
| 267 | } |
| 268 | |
| 269 | Ref<Tween> Tween::chain() { |
| 270 | parallel_enabled = false; |
| 271 | return this; |
| 272 | } |
| 273 | |
| 274 | bool Tween::custom_step(double p_delta) { |
| 275 | bool r = running; |
| 276 | running = true; |
| 277 | bool ret = step(p_delta); |
| 278 | running = running && r; // Running might turn false when Tween finished. |
| 279 | return ret; |
| 280 | } |
| 281 | |
| 282 | bool Tween::step(double p_delta) { |
| 283 | if (dead) { |
| 284 | return false; |
| 285 | } |
| 286 | |
| 287 | if (is_bound) { |
| 288 | Node *node = get_bound_node(); |
| 289 | if (node) { |
| 290 | if (!node->is_inside_tree()) { |
| 291 | return true; |
| 292 | } |
| 293 | } else { |
| 294 | return false; |
| 295 | } |
| 296 | } |
| 297 | |
| 298 | if (!running) { |
| 299 | return true; |
| 300 | } |
| 301 | |
| 302 | if (!started) { |
| 303 | if (tweeners.is_empty()) { |
| 304 | String tween_id; |
| 305 | Node *node = get_bound_node(); |
| 306 | if (node) { |
| 307 | tween_id = vformat("Tween (bound to %s)" , node->is_inside_tree() ? (String)node->get_path() : (String)node->get_name()); |
| 308 | } else { |
| 309 | tween_id = to_string(); |
| 310 | } |
| 311 | ERR_FAIL_V_MSG(false, tween_id + ": started with no Tweeners." ); |
| 312 | } |
| 313 | current_step = 0; |
| 314 | loops_done = 0; |
| 315 | total_time = 0; |
| 316 | _start_tweeners(); |
| 317 | started = true; |
| 318 | } |
| 319 | |
| 320 | double rem_delta = p_delta * speed_scale; |
| 321 | bool step_active = false; |
| 322 | total_time += rem_delta; |
| 323 | |
| 324 | #ifdef DEBUG_ENABLED |
| 325 | double initial_delta = rem_delta; |
| 326 | bool potential_infinite = false; |
| 327 | #endif |
| 328 | |
| 329 | while (rem_delta > 0 && running) { |
| 330 | double step_delta = rem_delta; |
| 331 | step_active = false; |
| 332 | |
| 333 | for (Ref<Tweener> &tweener : tweeners.write[current_step]) { |
| 334 | // Modified inside Tweener.step(). |
| 335 | double temp_delta = rem_delta; |
| 336 | // Turns to true if any Tweener returns true (i.e. is still not finished). |
| 337 | step_active = tweener->step(temp_delta) || step_active; |
| 338 | step_delta = MIN(temp_delta, step_delta); |
| 339 | } |
| 340 | |
| 341 | rem_delta = step_delta; |
| 342 | |
| 343 | if (!step_active) { |
| 344 | emit_signal(SNAME("step_finished" ), current_step); |
| 345 | current_step++; |
| 346 | |
| 347 | if (current_step == tweeners.size()) { |
| 348 | loops_done++; |
| 349 | if (loops_done == loops) { |
| 350 | running = false; |
| 351 | dead = true; |
| 352 | emit_signal(SNAME("finished" )); |
| 353 | break; |
| 354 | } else { |
| 355 | emit_signal(SNAME("loop_finished" ), loops_done); |
| 356 | current_step = 0; |
| 357 | _start_tweeners(); |
| 358 | #ifdef DEBUG_ENABLED |
| 359 | if (loops <= 0 && Math::is_equal_approx(rem_delta, initial_delta)) { |
| 360 | if (!potential_infinite) { |
| 361 | potential_infinite = true; |
| 362 | } else { |
| 363 | // Looped twice without using any time, this is 100% certain infinite loop. |
| 364 | ERR_FAIL_V_MSG(false, "Infinite loop detected. Check set_loops() description for more info." ); |
| 365 | } |
| 366 | } |
| 367 | #endif |
| 368 | } |
| 369 | } else { |
| 370 | _start_tweeners(); |
| 371 | } |
| 372 | } |
| 373 | } |
| 374 | |
| 375 | return true; |
| 376 | } |
| 377 | |
| 378 | bool Tween::can_process(bool p_tree_paused) const { |
| 379 | if (is_bound && pause_mode == TWEEN_PAUSE_BOUND) { |
| 380 | Node *node = get_bound_node(); |
| 381 | if (node) { |
| 382 | return node->is_inside_tree() && node->can_process(); |
| 383 | } |
| 384 | } |
| 385 | |
| 386 | return !p_tree_paused || pause_mode == TWEEN_PAUSE_PROCESS; |
| 387 | } |
| 388 | |
| 389 | Node *Tween::get_bound_node() const { |
| 390 | if (is_bound) { |
| 391 | return Object::cast_to<Node>(ObjectDB::get_instance(bound_node)); |
| 392 | } else { |
| 393 | return nullptr; |
| 394 | } |
| 395 | } |
| 396 | |
| 397 | double Tween::get_total_time() const { |
| 398 | return total_time; |
| 399 | } |
| 400 | |
| 401 | real_t Tween::run_equation(TransitionType p_trans_type, EaseType p_ease_type, real_t p_time, real_t p_initial, real_t p_delta, real_t p_duration) { |
| 402 | if (p_duration == 0) { |
| 403 | // Special case to avoid dividing by 0 in equations. |
| 404 | return p_initial + p_delta; |
| 405 | } |
| 406 | |
| 407 | interpolater func = interpolaters[p_trans_type][p_ease_type]; |
| 408 | return func(p_time, p_initial, p_delta, p_duration); |
| 409 | } |
| 410 | |
| 411 | Variant Tween::interpolate_variant(const Variant &p_initial_val, const Variant &p_delta_val, double p_time, double p_duration, TransitionType p_trans, EaseType p_ease) { |
| 412 | ERR_FAIL_INDEX_V(p_trans, TransitionType::TRANS_MAX, Variant()); |
| 413 | ERR_FAIL_INDEX_V(p_ease, EaseType::EASE_MAX, Variant()); |
| 414 | |
| 415 | // Special case for bool. |
| 416 | if (p_initial_val.get_type() == Variant::BOOL) { |
| 417 | return run_equation(p_trans, p_ease, p_time, p_initial_val, p_delta_val, p_duration) >= 0.5; |
| 418 | } |
| 419 | |
| 420 | Variant ret = Animation::add_variant(p_initial_val, p_delta_val); |
| 421 | ret = Animation::interpolate_variant(p_initial_val, ret, run_equation(p_trans, p_ease, p_time, 0.0, 1.0, p_duration)); |
| 422 | return ret; |
| 423 | } |
| 424 | |
| 425 | String Tween::to_string() { |
| 426 | String ret = Object::to_string(); |
| 427 | Node *node = get_bound_node(); |
| 428 | if (node) { |
| 429 | ret += vformat(" (bound to %s)" , node->get_name()); |
| 430 | } |
| 431 | return ret; |
| 432 | } |
| 433 | |
| 434 | void Tween::_bind_methods() { |
| 435 | ClassDB::bind_method(D_METHOD("tween_property" , "object" , "property" , "final_val" , "duration" ), &Tween::tween_property); |
| 436 | ClassDB::bind_method(D_METHOD("tween_interval" , "time" ), &Tween::tween_interval); |
| 437 | ClassDB::bind_method(D_METHOD("tween_callback" , "callback" ), &Tween::tween_callback); |
| 438 | ClassDB::bind_method(D_METHOD("tween_method" , "method" , "from" , "to" , "duration" ), &Tween::tween_method); |
| 439 | |
| 440 | ClassDB::bind_method(D_METHOD("custom_step" , "delta" ), &Tween::custom_step); |
| 441 | ClassDB::bind_method(D_METHOD("stop" ), &Tween::stop); |
| 442 | ClassDB::bind_method(D_METHOD("pause" ), &Tween::pause); |
| 443 | ClassDB::bind_method(D_METHOD("play" ), &Tween::play); |
| 444 | ClassDB::bind_method(D_METHOD("kill" ), &Tween::kill); |
| 445 | ClassDB::bind_method(D_METHOD("get_total_elapsed_time" ), &Tween::get_total_time); |
| 446 | |
| 447 | ClassDB::bind_method(D_METHOD("is_running" ), &Tween::is_running); |
| 448 | ClassDB::bind_method(D_METHOD("is_valid" ), &Tween::is_valid); |
| 449 | ClassDB::bind_method(D_METHOD("bind_node" , "node" ), &Tween::bind_node); |
| 450 | ClassDB::bind_method(D_METHOD("set_process_mode" , "mode" ), &Tween::set_process_mode); |
| 451 | ClassDB::bind_method(D_METHOD("set_pause_mode" , "mode" ), &Tween::set_pause_mode); |
| 452 | |
| 453 | ClassDB::bind_method(D_METHOD("set_parallel" , "parallel" ), &Tween::set_parallel, DEFVAL(true)); |
| 454 | ClassDB::bind_method(D_METHOD("set_loops" , "loops" ), &Tween::set_loops, DEFVAL(0)); |
| 455 | ClassDB::bind_method(D_METHOD("get_loops_left" ), &Tween::get_loops_left); |
| 456 | ClassDB::bind_method(D_METHOD("set_speed_scale" , "speed" ), &Tween::set_speed_scale); |
| 457 | ClassDB::bind_method(D_METHOD("set_trans" , "trans" ), &Tween::set_trans); |
| 458 | ClassDB::bind_method(D_METHOD("set_ease" , "ease" ), &Tween::set_ease); |
| 459 | |
| 460 | ClassDB::bind_method(D_METHOD("parallel" ), &Tween::parallel); |
| 461 | ClassDB::bind_method(D_METHOD("chain" ), &Tween::chain); |
| 462 | |
| 463 | ClassDB::bind_static_method("Tween" , D_METHOD("interpolate_value" , "initial_value" , "delta_value" , "elapsed_time" , "duration" , "trans_type" , "ease_type" ), &Tween::interpolate_variant); |
| 464 | |
| 465 | ADD_SIGNAL(MethodInfo("step_finished" , PropertyInfo(Variant::INT, "idx" ))); |
| 466 | ADD_SIGNAL(MethodInfo("loop_finished" , PropertyInfo(Variant::INT, "loop_count" ))); |
| 467 | ADD_SIGNAL(MethodInfo("finished" )); |
| 468 | |
| 469 | BIND_ENUM_CONSTANT(TWEEN_PROCESS_PHYSICS); |
| 470 | BIND_ENUM_CONSTANT(TWEEN_PROCESS_IDLE); |
| 471 | |
| 472 | BIND_ENUM_CONSTANT(TWEEN_PAUSE_BOUND); |
| 473 | BIND_ENUM_CONSTANT(TWEEN_PAUSE_STOP); |
| 474 | BIND_ENUM_CONSTANT(TWEEN_PAUSE_PROCESS); |
| 475 | |
| 476 | BIND_ENUM_CONSTANT(TRANS_LINEAR); |
| 477 | BIND_ENUM_CONSTANT(TRANS_SINE); |
| 478 | BIND_ENUM_CONSTANT(TRANS_QUINT); |
| 479 | BIND_ENUM_CONSTANT(TRANS_QUART); |
| 480 | BIND_ENUM_CONSTANT(TRANS_QUAD); |
| 481 | BIND_ENUM_CONSTANT(TRANS_EXPO); |
| 482 | BIND_ENUM_CONSTANT(TRANS_ELASTIC); |
| 483 | BIND_ENUM_CONSTANT(TRANS_CUBIC); |
| 484 | BIND_ENUM_CONSTANT(TRANS_CIRC); |
| 485 | BIND_ENUM_CONSTANT(TRANS_BOUNCE); |
| 486 | BIND_ENUM_CONSTANT(TRANS_BACK); |
| 487 | BIND_ENUM_CONSTANT(TRANS_SPRING); |
| 488 | |
| 489 | BIND_ENUM_CONSTANT(EASE_IN); |
| 490 | BIND_ENUM_CONSTANT(EASE_OUT); |
| 491 | BIND_ENUM_CONSTANT(EASE_IN_OUT); |
| 492 | BIND_ENUM_CONSTANT(EASE_OUT_IN); |
| 493 | } |
| 494 | |
| 495 | Tween::Tween() { |
| 496 | ERR_FAIL_MSG("Tween can't be created directly. Use create_tween() method." ); |
| 497 | } |
| 498 | |
| 499 | Tween::Tween(bool p_valid) { |
| 500 | valid = p_valid; |
| 501 | } |
| 502 | |
| 503 | Ref<PropertyTweener> PropertyTweener::from(const Variant &p_value) { |
| 504 | ERR_FAIL_COND_V(tween.is_null(), nullptr); |
| 505 | if (!tween->_validate_type_match(p_value, final_val)) { |
| 506 | return nullptr; |
| 507 | } |
| 508 | |
| 509 | initial_val = p_value; |
| 510 | do_continue = false; |
| 511 | return this; |
| 512 | } |
| 513 | |
| 514 | Ref<PropertyTweener> PropertyTweener::from_current() { |
| 515 | do_continue = false; |
| 516 | return this; |
| 517 | } |
| 518 | |
| 519 | Ref<PropertyTweener> PropertyTweener::as_relative() { |
| 520 | relative = true; |
| 521 | return this; |
| 522 | } |
| 523 | |
| 524 | Ref<PropertyTweener> PropertyTweener::set_trans(Tween::TransitionType p_trans) { |
| 525 | trans_type = p_trans; |
| 526 | return this; |
| 527 | } |
| 528 | |
| 529 | Ref<PropertyTweener> PropertyTweener::set_ease(Tween::EaseType p_ease) { |
| 530 | ease_type = p_ease; |
| 531 | return this; |
| 532 | } |
| 533 | |
| 534 | Ref<PropertyTweener> PropertyTweener::set_delay(double p_delay) { |
| 535 | delay = p_delay; |
| 536 | return this; |
| 537 | } |
| 538 | |
| 539 | void PropertyTweener::start() { |
| 540 | elapsed_time = 0; |
| 541 | finished = false; |
| 542 | |
| 543 | Object *target_instance = ObjectDB::get_instance(target); |
| 544 | if (!target_instance) { |
| 545 | WARN_PRINT("Target object freed before starting, aborting Tweener." ); |
| 546 | return; |
| 547 | } |
| 548 | |
| 549 | if (do_continue && Math::is_zero_approx(delay)) { |
| 550 | initial_val = target_instance->get_indexed(property); |
| 551 | do_continue = false; |
| 552 | } |
| 553 | |
| 554 | if (relative) { |
| 555 | final_val = Animation::add_variant(initial_val, base_final_val); |
| 556 | } |
| 557 | |
| 558 | delta_val = Animation::subtract_variant(final_val, initial_val); |
| 559 | } |
| 560 | |
| 561 | bool PropertyTweener::step(double &r_delta) { |
| 562 | if (finished) { |
| 563 | // This is needed in case there's a parallel Tweener with longer duration. |
| 564 | return false; |
| 565 | } |
| 566 | |
| 567 | Object *target_instance = ObjectDB::get_instance(target); |
| 568 | if (!target_instance) { |
| 569 | return false; |
| 570 | } |
| 571 | elapsed_time += r_delta; |
| 572 | |
| 573 | if (elapsed_time < delay) { |
| 574 | r_delta = 0; |
| 575 | return true; |
| 576 | } else if (do_continue && !Math::is_zero_approx(delay)) { |
| 577 | initial_val = target_instance->get_indexed(property); |
| 578 | delta_val = Animation::subtract_variant(final_val, initial_val); |
| 579 | do_continue = false; |
| 580 | } |
| 581 | |
| 582 | double time = MIN(elapsed_time - delay, duration); |
| 583 | if (time < duration) { |
| 584 | target_instance->set_indexed(property, tween->interpolate_variant(initial_val, delta_val, time, duration, trans_type, ease_type)); |
| 585 | r_delta = 0; |
| 586 | return true; |
| 587 | } else { |
| 588 | target_instance->set_indexed(property, final_val); |
| 589 | finished = true; |
| 590 | r_delta = elapsed_time - delay - duration; |
| 591 | emit_signal(SNAME("finished" )); |
| 592 | return false; |
| 593 | } |
| 594 | } |
| 595 | |
| 596 | void PropertyTweener::set_tween(const Ref<Tween> &p_tween) { |
| 597 | tween = p_tween; |
| 598 | if (trans_type == Tween::TRANS_MAX) { |
| 599 | trans_type = tween->get_trans(); |
| 600 | } |
| 601 | if (ease_type == Tween::EASE_MAX) { |
| 602 | ease_type = tween->get_ease(); |
| 603 | } |
| 604 | } |
| 605 | |
| 606 | void PropertyTweener::_bind_methods() { |
| 607 | ClassDB::bind_method(D_METHOD("from" , "value" ), &PropertyTweener::from); |
| 608 | ClassDB::bind_method(D_METHOD("from_current" ), &PropertyTweener::from_current); |
| 609 | ClassDB::bind_method(D_METHOD("as_relative" ), &PropertyTweener::as_relative); |
| 610 | ClassDB::bind_method(D_METHOD("set_trans" , "trans" ), &PropertyTweener::set_trans); |
| 611 | ClassDB::bind_method(D_METHOD("set_ease" , "ease" ), &PropertyTweener::set_ease); |
| 612 | ClassDB::bind_method(D_METHOD("set_delay" , "delay" ), &PropertyTweener::set_delay); |
| 613 | } |
| 614 | |
| 615 | PropertyTweener::PropertyTweener(const Object *p_target, const Vector<StringName> &p_property, const Variant &p_to, double p_duration) { |
| 616 | target = p_target->get_instance_id(); |
| 617 | property = p_property; |
| 618 | initial_val = p_target->get_indexed(property); |
| 619 | base_final_val = p_to; |
| 620 | final_val = base_final_val; |
| 621 | duration = p_duration; |
| 622 | |
| 623 | if (p_target->is_ref_counted()) { |
| 624 | ref_copy = p_target; |
| 625 | } |
| 626 | } |
| 627 | |
| 628 | PropertyTweener::PropertyTweener() { |
| 629 | ERR_FAIL_MSG("PropertyTweener can't be created directly. Use the tween_property() method in Tween." ); |
| 630 | } |
| 631 | |
| 632 | void IntervalTweener::start() { |
| 633 | elapsed_time = 0; |
| 634 | finished = false; |
| 635 | } |
| 636 | |
| 637 | bool IntervalTweener::step(double &r_delta) { |
| 638 | if (finished) { |
| 639 | return false; |
| 640 | } |
| 641 | |
| 642 | elapsed_time += r_delta; |
| 643 | |
| 644 | if (elapsed_time < duration) { |
| 645 | r_delta = 0; |
| 646 | return true; |
| 647 | } else { |
| 648 | finished = true; |
| 649 | r_delta = elapsed_time - duration; |
| 650 | emit_signal(SNAME("finished" )); |
| 651 | return false; |
| 652 | } |
| 653 | } |
| 654 | |
| 655 | IntervalTweener::IntervalTweener(double p_time) { |
| 656 | duration = p_time; |
| 657 | } |
| 658 | |
| 659 | IntervalTweener::IntervalTweener() { |
| 660 | ERR_FAIL_MSG("IntervalTweener can't be created directly. Use the tween_interval() method in Tween." ); |
| 661 | } |
| 662 | |
| 663 | Ref<CallbackTweener> CallbackTweener::set_delay(double p_delay) { |
| 664 | delay = p_delay; |
| 665 | return this; |
| 666 | } |
| 667 | |
| 668 | void CallbackTweener::start() { |
| 669 | elapsed_time = 0; |
| 670 | finished = false; |
| 671 | } |
| 672 | |
| 673 | bool CallbackTweener::step(double &r_delta) { |
| 674 | if (finished) { |
| 675 | return false; |
| 676 | } |
| 677 | |
| 678 | if (!callback.get_object()) { |
| 679 | return false; |
| 680 | } |
| 681 | |
| 682 | elapsed_time += r_delta; |
| 683 | if (elapsed_time >= delay) { |
| 684 | Variant result; |
| 685 | Callable::CallError ce; |
| 686 | callback.callp(nullptr, 0, result, ce); |
| 687 | if (ce.error != Callable::CallError::CALL_OK) { |
| 688 | ERR_FAIL_V_MSG(false, "Error calling method from CallbackTweener: " + Variant::get_callable_error_text(callback, nullptr, 0, ce)); |
| 689 | } |
| 690 | |
| 691 | finished = true; |
| 692 | r_delta = elapsed_time - delay; |
| 693 | emit_signal(SNAME("finished" )); |
| 694 | return false; |
| 695 | } |
| 696 | |
| 697 | r_delta = 0; |
| 698 | return true; |
| 699 | } |
| 700 | |
| 701 | void CallbackTweener::_bind_methods() { |
| 702 | ClassDB::bind_method(D_METHOD("set_delay" , "delay" ), &CallbackTweener::set_delay); |
| 703 | } |
| 704 | |
| 705 | CallbackTweener::CallbackTweener(const Callable &p_callback) { |
| 706 | callback = p_callback; |
| 707 | |
| 708 | Object *callback_instance = p_callback.get_object(); |
| 709 | if (callback_instance && callback_instance->is_ref_counted()) { |
| 710 | ref_copy = callback_instance; |
| 711 | } |
| 712 | } |
| 713 | |
| 714 | CallbackTweener::CallbackTweener() { |
| 715 | ERR_FAIL_MSG("CallbackTweener can't be created directly. Use the tween_callback() method in Tween." ); |
| 716 | } |
| 717 | |
| 718 | Ref<MethodTweener> MethodTweener::set_delay(double p_delay) { |
| 719 | delay = p_delay; |
| 720 | return this; |
| 721 | } |
| 722 | |
| 723 | Ref<MethodTweener> MethodTweener::set_trans(Tween::TransitionType p_trans) { |
| 724 | trans_type = p_trans; |
| 725 | return this; |
| 726 | } |
| 727 | |
| 728 | Ref<MethodTweener> MethodTweener::set_ease(Tween::EaseType p_ease) { |
| 729 | ease_type = p_ease; |
| 730 | return this; |
| 731 | } |
| 732 | |
| 733 | void MethodTweener::start() { |
| 734 | elapsed_time = 0; |
| 735 | finished = false; |
| 736 | } |
| 737 | |
| 738 | bool MethodTweener::step(double &r_delta) { |
| 739 | if (finished) { |
| 740 | return false; |
| 741 | } |
| 742 | |
| 743 | if (!callback.get_object()) { |
| 744 | return false; |
| 745 | } |
| 746 | |
| 747 | elapsed_time += r_delta; |
| 748 | |
| 749 | if (elapsed_time < delay) { |
| 750 | r_delta = 0; |
| 751 | return true; |
| 752 | } |
| 753 | |
| 754 | Variant current_val; |
| 755 | double time = MIN(elapsed_time - delay, duration); |
| 756 | if (time < duration) { |
| 757 | current_val = tween->interpolate_variant(initial_val, delta_val, time, duration, trans_type, ease_type); |
| 758 | } else { |
| 759 | current_val = final_val; |
| 760 | } |
| 761 | const Variant **argptr = (const Variant **)alloca(sizeof(Variant *)); |
| 762 | argptr[0] = ¤t_val; |
| 763 | |
| 764 | Variant result; |
| 765 | Callable::CallError ce; |
| 766 | callback.callp(argptr, 1, result, ce); |
| 767 | if (ce.error != Callable::CallError::CALL_OK) { |
| 768 | ERR_FAIL_V_MSG(false, "Error calling method from MethodTweener: " + Variant::get_callable_error_text(callback, argptr, 1, ce)); |
| 769 | } |
| 770 | |
| 771 | if (time < duration) { |
| 772 | r_delta = 0; |
| 773 | return true; |
| 774 | } else { |
| 775 | finished = true; |
| 776 | r_delta = elapsed_time - delay - duration; |
| 777 | emit_signal(SNAME("finished" )); |
| 778 | return false; |
| 779 | } |
| 780 | } |
| 781 | |
| 782 | void MethodTweener::set_tween(const Ref<Tween> &p_tween) { |
| 783 | tween = p_tween; |
| 784 | if (trans_type == Tween::TRANS_MAX) { |
| 785 | trans_type = tween->get_trans(); |
| 786 | } |
| 787 | if (ease_type == Tween::EASE_MAX) { |
| 788 | ease_type = tween->get_ease(); |
| 789 | } |
| 790 | } |
| 791 | |
| 792 | void MethodTweener::_bind_methods() { |
| 793 | ClassDB::bind_method(D_METHOD("set_delay" , "delay" ), &MethodTweener::set_delay); |
| 794 | ClassDB::bind_method(D_METHOD("set_trans" , "trans" ), &MethodTweener::set_trans); |
| 795 | ClassDB::bind_method(D_METHOD("set_ease" , "ease" ), &MethodTweener::set_ease); |
| 796 | } |
| 797 | |
| 798 | MethodTweener::MethodTweener(const Callable &p_callback, const Variant &p_from, const Variant &p_to, double p_duration) { |
| 799 | callback = p_callback; |
| 800 | initial_val = p_from; |
| 801 | delta_val = Animation::subtract_variant(p_to, p_from); |
| 802 | final_val = p_to; |
| 803 | duration = p_duration; |
| 804 | |
| 805 | Object *callback_instance = p_callback.get_object(); |
| 806 | if (callback_instance && callback_instance->is_ref_counted()) { |
| 807 | ref_copy = callback_instance; |
| 808 | } |
| 809 | } |
| 810 | |
| 811 | MethodTweener::MethodTweener() { |
| 812 | ERR_FAIL_MSG("MethodTweener can't be created directly. Use the tween_method() method in Tween." ); |
| 813 | } |
| 814 | |