[WIP] New tweening-based API for the outro screen

This commit is contained in:
Charles Dang 2024-03-11 17:41:24 -04:00
parent 5b05815d5c
commit f415ca9de1
7 changed files with 584 additions and 48 deletions

View file

@ -401,6 +401,7 @@ utils/irdya_datetime.cpp
utils/markov_generator.cpp
utils/name_generator_factory.cpp
utils/parse_network_address.cpp
utils/tweened_value.cpp
variable.cpp
variable_info.cpp
wesnothd_connection.cpp

View file

@ -32,7 +32,8 @@ namespace
{
// How long text fading should take - currently a hardcoded value.
const unsigned FADE_DURATION_MS = 500;
using namespace std::chrono_literals;
constexpr auto FADE_DURATION = 500ms;
} // end unnamed namespace
@ -42,15 +43,22 @@ REGISTER_DIALOG(outro)
outro::outro(const game_classification& info)
: modal_dialog(window_id())
, alpha_queue_()
, text_()
, current_text_()
, text_index_(0)
, duration_(info.end_text_duration)
, fade_alpha_(0)
, fade_start_(0)
, fading_in_(true)
, timer_id_(0)
{
// 3.5 seconds by default
const auto show_duration = info.end_text_duration > 0 ? std::chrono::milliseconds{info.end_text_duration} : 3500ms;
alpha_queue_ = {
{0, 255, FADE_DURATION, utils::ease_out_cubic},
{255, 255, show_duration, utils::ease_out_cubic},
{255, 0, FADE_DURATION, utils::ease_out_cubic},
};
alpha_queue_.on_complete(std::bind(&outro::advance_text, this));
if(!info.end_text.empty()) {
text_.push_back(info.end_text);
} else {
@ -114,23 +122,6 @@ void outro::update()
return;
}
if(fade_start_ == 0) {
fade_start_ = SDL_GetTicks();
}
// If we've faded fully in...
if(fading_in_ && fade_alpha_ >= 255) {
// Schedule the fadeout after the provided delay.
if(timer_id_ == 0) {
timer_id_ = add_timer(duration_, [this](std::size_t) {
fading_in_ = false;
fade_start_ = 0;
});
}
return;
}
canvas& window_canvas = window::get_canvas(0);
// If we've faded fully out...
@ -141,10 +132,9 @@ void outro::update()
window::close();
return;
}
current_text_ = text_[text_index_];
// ...else show the next bit.
window_canvas.set_variable("outro_text", wfl::variant(current_text_));
window_canvas.set_variable("outro_text", wfl::variant{current_text()});
fading_in_ = true;
@ -156,29 +146,24 @@ void outro::update()
window_canvas.set_variable("fade_alpha", wfl::variant(fade_alpha_));
window_canvas.update_size_variables();
queue_redraw();
}
auto current_ticks = SDL_GetTicks();
void outro::advance_text()
{
// Advance to the next text block, and either display it or close the window if we've shown them all.
text_index_++;
if(fade_start_ > current_ticks) {
// 32-bit ticks counter wraps around after about 49 days, the 64-bit version
// requires SDL 2.0.18+. Just restart the counter in the worst case and let
// the player deal with the sheer ridiculousness of their predicament.
fade_start_ = current_ticks;
}
fade_alpha_ = std::clamp<int>(
std::round(255.0 * double(current_ticks - fade_start_) / double(FADE_DURATION_MS)),
0, 255);
if(!fading_in_) {
fade_alpha_ = 255 - fade_alpha_;
if(text_index_ < text_.size()) {
window::get_canvas(0).set_variable("outro_text", wfl::variant{current_text()});
} else {
window::close();
}
}
void outro::post_show(window& /*window*/)
{
remove_timer(timer_id_);
timer_id_ = 0;
}
} // namespace dialogs

View file

@ -16,6 +16,7 @@
#pragma once
#include "gui/dialogs/modal_dialog.hpp"
#include "utils/tweened_value.hpp"
class game_classification;
@ -48,17 +49,13 @@ private:
virtual void post_show(window& window) override;
void advance_text();
utils::tweened_value_queue alpha_queue_;
std::vector<std::string> text_;
std::string current_text_;
std::size_t text_index_;
unsigned int duration_;
int fade_alpha_;
uint32_t fade_start_;
bool fading_in_;
std::size_t timer_id_;
};
} // namespace dialogs

293
src/utils/easings.cpp Normal file
View file

@ -0,0 +1,293 @@
//
// This is free and unencumbered software released into the public domain.
//
// Anyone is free to copy, modify, publish, use, compile, sell, or
// distribute this software, either in source code form or as a compiled
// binary, for any purpose, commercial or non-commercial, and by any
// means.
//
// In jurisdictions that recognize copyright laws, the author or authors
// of this software dedicate any and all copyright interest in the
// software to the public domain. We make this dedication for the benefit
// of the public at large and to the detriment of our heirs and
// successors. We intend this dedication to be an overt act of
// relinquishment in perpetuity of all present and future rights to this
// software under copyright law.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
// IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
// OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
// OTHER DEALINGS IN THE SOFTWARE.
//
//
// For more information, please refer to <https://unlicense.org>
//
#include "utils/easings.hpp"
#include <boost/math/constants/constants.hpp>
using namespace boost::math::constants;
#include <cmath>
namespace utils::easing
{
// Modeled after the line y = x
double linear_interpolation(double p)
{
return p;
}
// Modeled after the parabola y = x^2
double quadratic_ease_in(double p)
{
return p * p;
}
// Modeled after the parabola y = -x^2 + 2x
double quadratic_ease_out(double p)
{
return -(p * (p - 2));
}
// Modeled after the piecewise quadratic
// y = (1/2)((2x)^2) ; [0, 0.5)
// y = -(1/2)((2x-1)*(2x-3) - 1) ; [0.5, 1]
double quadratic_ease_in_out(double p)
{
if(p < 0.5) {
return 2 * p * p;
} else {
return (-2 * p * p) + (4 * p) - 1;
}
}
// Modeled after the cubic y = x^3
double cubic_ease_in(double p)
{
return p * p * p;
}
// Modeled after the cubic y = (x - 1)^3 + 1
double cubic_ease_out(double p)
{
double f = (p - 1);
return f * f * f + 1;
}
// Modeled after the piecewise cubic
// y = (1/2)((2x)^3) ; [0, 0.5)
// y = (1/2)((2x-2)^3 + 2) ; [0.5, 1]
double cubic_ease_in_out(double p)
{
if(p < 0.5) {
return 4 * p * p * p;
} else {
double f = ((2 * p) - 2);
return 0.5 * f * f * f + 1;
}
}
// Modeled after the quartic x^4
double quartic_ease_in(double p)
{
return p * p * p * p;
}
// Modeled after the quartic y = 1 - (x - 1)^4
double quartic_ease_out(double p)
{
double f = (p - 1);
return f * f * f * (1 - p) + 1;
}
// Modeled after the piecewise quartic
// y = (1/2)((2x)^4) ; [0, 0.5)
// y = -(1/2)((2x-2)^4 - 2) ; [0.5, 1]
double quartic_ease_in_out(double p)
{
if(p < 0.5) {
return 8 * p * p * p * p;
} else {
double f = (p - 1);
return -8 * f * f * f * f + 1;
}
}
// Modeled after the quintic y = x^5
double quintic_ease_in(double p)
{
return p * p * p * p * p;
}
// Modeled after the quintic y = (x - 1)^5 + 1
double quintic_ease_out(double p)
{
double f = (p - 1);
return f * f * f * f * f + 1;
}
// Modeled after the piecewise quintic
// y = (1/2)((2x)^5) ; [0, 0.5)
// y = (1/2)((2x-2)^5 + 2) ; [0.5, 1]
double quintic_ease_in_out(double p)
{
if(p < 0.5) {
return 16 * p * p * p * p * p;
} else {
double f = ((2 * p) - 2);
return 0.5 * f * f * f * f * f + 1;
}
}
// Modeled after quarter-cycle of sine wave
double sine_ease_in(double p)
{
return std::sin((p - 1) * half_pi<double>()) + 1;
}
// Modeled after quarter-cycle of sine wave (different phase)
double sine_ease_out(double p)
{
return std::sin(p * half_pi<double>());
}
// Modeled after half sine wave
double sine_ease_in_out(double p)
{
return 0.5 * (1 - std::cos(p * pi<double>()));
}
// Modeled after shifted quadrant IV of unit circle
double circular_ease_in(double p)
{
return 1 - std::sqrt(1 - (p * p));
}
// Modeled after shifted quadrant II of unit circle
double circular_ease_out(double p)
{
return std::sqrt((2 - p) * p);
}
// Modeled after the piecewise circular function
// y = (1/2)(1 - sqrt(1 - 4x^2)) ; [0, 0.5)
// y = (1/2)(sqrt(-(2x - 3)*(2x - 1)) + 1) ; [0.5, 1]
double circular_ease_in_out(double p)
{
if(p < 0.5) {
return 0.5 * (1 - std::sqrt(1 - 4 * (p * p)));
} else {
return 0.5 * (std::sqrt(-((2 * p) - 3) * ((2 * p) - 1)) + 1);
}
}
// Modeled after the exponential function y = 2^(10(x - 1))
double exponential_ease_in(double p)
{
return (p == 0.0) ? p : std::pow(2, 10 * (p - 1));
}
// Modeled after the exponential function y = -2^(-10x) + 1
double exponential_ease_out(double p)
{
return (p == 1.0) ? p : 1 - std::pow(2, -10 * p);
}
// Modeled after the piecewise exponential
// y = (1/2)2^(10(2x - 1)) ; [0,0.5)
// y = -(1/2)*2^(-10(2x - 1))) + 1 ; [0.5,1]
double exponential_ease_in_out(double p)
{
if(p == 0.0 || p == 1.0)
return p;
if(p < 0.5) {
return 0.5 * std::pow(2, (20 * p) - 10);
} else {
return -0.5 * std::pow(2, (-20 * p) + 10) + 1;
}
}
// Modeled after the damped sine wave y = sin(13pi/2*x)*pow(2, 10 * (x - 1))
double elastic_ease_in(double p)
{
return sin(13 * half_pi<double>() * p) * std::pow(2, 10 * (p - 1));
}
// Modeled after the damped sine wave y = sin(-13pi/2*(x + 1))*pow(2, -10x) + 1
double elastic_ease_out(double p)
{
return std::sin(-13 * half_pi<double>() * (p + 1)) * std::pow(2, -10 * p) + 1;
}
// Modeled after the piecewise exponentially-damped sine wave:
// y = (1/2)*sin(13pi/2*(2*x))*pow(2, 10 * ((2*x) - 1)) ; [0,0.5)
// y = (1/2)*(sin(-13pi/2*((2x-1)+1))*pow(2,-10(2*x-1)) + 2) ; [0.5, 1]
double elastic_ease_in_out(double p)
{
if(p < 0.5) {
return 0.5 * std::sin(13 * half_pi<double>() * (2 * p)) * std::pow(2, 10 * ((2 * p) - 1));
} else {
return 0.5 * (std::sin(-13 * half_pi<double>() * ((2 * p - 1) + 1)) * std::pow(2, -10 * (2 * p - 1)) + 2);
}
}
// Modeled after the overshooting cubic y = x^3-x*sin(x*pi)
double back_ease_in(double p)
{
return p * p * p - p * std::sin(p * pi<double>());
}
// Modeled after overshooting cubic y = 1-((1-x)^3-(1-x)*sin((1-x)*pi))
double back_ease_out(double p)
{
double f = (1 - p);
return 1 - (f * f * f - f * std::sin(f * pi<double>()));
}
// Modeled after the piecewise overshooting cubic function:
// y = (1/2)*((2x)^3-(2x)*sin(2*x*pi)) ; [0, 0.5)
// y = (1/2)*(1-((1-x)^3-(1-x)*sin((1-x)*pi))+1) ; [0.5, 1]
double back_ease_in_out(double p)
{
if(p < 0.5) {
double f = 2 * p;
return 0.5 * (f * f * f - f * std::sin(f * pi<double>()));
} else {
double f = (1 - (2 * p - 1));
return 0.5 * (1 - (f * f * f - f * std::sin(f * pi<double>()))) + 0.5;
}
}
double bounce_ease_in(double p)
{
return 1 - bounce_ease_out(1 - p);
}
double bounce_ease_out(double p)
{
if(p < 4 / 11.0) {
return (121 * p * p) / 16.0;
} else if(p < 8 / 11.0) {
return (363 / 40.0 * p * p) - (99 / 10.0 * p) + 17 / 5.0;
} else if(p < 9 / 10.0) {
return (4356 / 361.0 * p * p) - (35442 / 1805.0 * p) + 16061 / 1805.0;
} else {
return (54 / 5.0 * p * p) - (513 / 25.0 * p) + 268 / 25.0;
}
}
double bounce_ease_in_out(double p)
{
if(p < 0.5) {
return 0.5 * bounce_ease_in(p * 2);
} else {
return 0.5 * bounce_ease_out(p * 2 - 1) + 0.5;
}
}
} // end namespace utils::easing

86
src/utils/easings.hpp Normal file
View file

@ -0,0 +1,86 @@
//
// This is free and unencumbered software released into the public domain.
//
// Anyone is free to copy, modify, publish, use, compile, sell, or
// distribute this software, either in source code form or as a compiled
// binary, for any purpose, commercial or non-commercial, and by any
// means.
//
// In jurisdictions that recognize copyright laws, the author or authors
// of this software dedicate any and all copyright interest in the
// software to the public domain. We make this dedication for the benefit
// of the public at large and to the detriment of our heirs and
// successors. We intend this dedication to be an overt act of
// relinquishment in perpetuity of all present and future rights to this
// software under copyright law.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
// IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
// OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
// OTHER DEALINGS IN THE SOFTWARE.
//
//
// For more information, please refer to <https://unlicense.org>
//
#pragma once
namespace utils::easing
{
// Linear interpolation (no easing)
double linear_interpolation(double p);
// Quadratic easing; p^2
double quadratic_ease_in(double p);
double quadratic_ease_out(double p);
double quadratic_ease_in_out(double p);
// Cubic easing; p^3
double cubic_ease_in(double p);
double cubic_ease_out(double p);
double cubic_ease_in_out(double p);
// Quartic easing; p^4
double quartic_ease_in(double p);
double quartic_ease_out(double p);
double quartic_ease_in_out(double p);
// Quintic easing; p^5
double quintic_ease_in(double p);
double quintic_ease_out(double p);
double quintic_ease_in_out(double p);
// Sine wave easing; sin(p * PI/2)
double sine_ease_in(double p);
double sine_ease_out(double p);
double sine_ease_in_out(double p);
// Circular easing; sqrt(1 - p^2)
double circular_ease_in(double p);
double circular_ease_out(double p);
double circular_ease_in_out(double p);
// Exponential easing, base 2
double exponential_ease_in(double p);
double exponential_ease_out(double p);
double exponential_ease_in_out(double p);
// Exponentially-damped sine wave easing
double elastic_ease_in(double p);
double elastic_ease_out(double p);
double elastic_ease_in_out(double p);
// Overshooting cubic easing;
double back_ease_in(double p);
double back_ease_out(double p);
double back_ease_in_out(double p);
// Exponentially-decaying bounce easing
double bounce_ease_in(double p);
double bounce_ease_out(double p);
double bounce_ease_in_out(double p);
} // end namespace utils::easing

View file

@ -0,0 +1,90 @@
/*
Copyright (C) 2024
Part of the Battle for Wesnoth Project https://www.wesnoth.org/
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY.
See the COPYING file for more details.
*/
#include "utils/tweened_value.hpp"
#include "log.hpp"
#include <algorithm>
namespace utils
{
tweened_value::tweened_value(
int32_t value_start, int32_t value_end, tweened_value::Resolution duration, std::function<double(double)> easing)
: duration{duration}
, value_start{value_start}
, delta{value_end - value_start}
, start_time{std::nullopt}
, easing{easing}
{
}
std::pair<int32_t, bool> tweened_value::value()
{
if(delta == 0) {
// return {value_start, true};
}
if(!start_time) {
start_time = std::chrono::steady_clock::now();
return {value_start, false};
}
const auto elapsed = elapsed_time();
if(elapsed >= duration) {
start_time.reset();
return {value_start + delta, true};
}
const double completed = std::clamp(1.0 * elapsed / duration, 0.0, 1.0);
return {value_start + (delta * easing(completed)), false};
}
tweened_value::Resolution tweened_value::elapsed_time() const
{
if(start_time) {
return std::chrono::duration_cast<Resolution>(std::chrono::steady_clock::now() - *start_time);
} else {
return Resolution{0};
}
}
int32_t tweened_value_queue::value()
{
if(queue.empty()) {
throw std::runtime_error("Attempt to get tweened value from empty queue");
}
const auto& [value, stage_complete] = queue.front().value();
if(stage_complete) {
if(mode == loop_mode::loop) {
loop_queue.push(std::exchange(queue.front(), tweened_value{}));
}
queue.pop();
}
if(queue.empty()) {
if(on_queue_exhausted) {
on_queue_exhausted();
}
if(mode == loop_mode::loop) {
std::swap(loop_queue, queue);
}
}
return value;
}
} // end namespace utils

View file

@ -0,0 +1,84 @@
/*
Copyright (C) 2024
Part of the Battle for Wesnoth Project https://www.wesnoth.org/
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY.
See the COPYING file for more details.
*/
#pragma once
#include "utils/easings.hpp"
#include <chrono>
#include <functional>
#include <initializer_list>
#include <optional>
#include <queue>
#include <tuple>
namespace utils
{
class tweened_value
{
using Resolution = std::chrono::milliseconds;
public:
tweened_value() = default;
tweened_value(int32_t value_start, int32_t value_end, Resolution duration, std::function<double(double)> easing);
std::pair<int32_t, bool> value();
Resolution elapsed_time() const;
private:
Resolution duration;
int32_t value_start;
int32_t delta;
std::optional<std::chrono::steady_clock::time_point> start_time;
std::function<double(double)> easing;
};
class tweened_value_queue
{
public:
enum class loop_mode { once, loop };
tweened_value_queue() = default;
tweened_value_queue(std::initializer_list<tweened_value> list)
: queue{decltype(queue)::container_type{list}}
{
}
void queue_value(tweened_value v)
{
queue.push(std::move(v));
}
void on_complete(std::function<void()> f)
{
on_queue_exhausted = f;
}
int32_t value();
private:
std::queue<tweened_value> queue;
std::queue<tweened_value> loop_queue;
std::function<void()> on_queue_exhausted;
loop_mode mode = loop_mode::loop;
};
} // end namespace utils