diff --git a/include/wayfire/util/duration.hpp b/include/wayfire/util/duration.hpp index 61fecd7..96d3e7b 100644 --- a/include/wayfire/util/duration.hpp +++ b/include/wayfire/util/duration.hpp @@ -11,9 +11,8 @@ namespace animation namespace smoothing { /** - * A smooth function is a function which takes a double in [0, 1] and returns - * another double in R. Both ranges represent percentage of a progress of - * an animation. + * A smooth function is a function which takes a double in [0, 1] and returns another double in R. Both ranges + * represent percentage of a progress of an animation. */ using smooth_function = std::function; @@ -23,6 +22,8 @@ extern smooth_function linear; extern smooth_function circle; /** "sigmoid" smoothing function, i.e x -> 1.0 / (1 + exp(-12 * x + 6)) */ extern smooth_function sigmoid; +/** custom cubic-bezier as in CSS */ +extern smooth_function get_cubic_bezier(double x1, double y1, double x2, double y2); std::vector get_available_smooth_functions(); } @@ -34,10 +35,7 @@ struct animation_description_t animation::smoothing::smooth_function easing; std::string easing_name; - bool operator ==(const animation_description_t& other) const - { - return (length_ms == other.length_ms) && (easing_name == other.easing_name); - } + bool operator ==(const animation_description_t& other) const; }; namespace option_type @@ -66,15 +64,13 @@ struct transition_t }; /** - * duration_t is a class which can be used to track progress over a specific - * time interval. + * duration_t is a class which can be used to track progress over a specific time interval. */ class duration_t { public: /** - * Construct a new duration. - * Initially, the duration is not running and its progress is 1. + * Construct a new duration. Initially, the duration is not running and its progress is 1. * * @param length The length of the duration in milliseconds. * @param smooth The smoothing function for transitions. @@ -95,40 +91,36 @@ class duration_t duration_t& operator =(duration_t&& other) = default; /** - * Start the duration. - * This means that the progress will get reset to 0. + * Start the duration. This means that the progress will get reset to 0. */ void start(); /** - * Get the progress of the duration in percentage. - * The progress will be smoothed using the smoothing function. + * Get the progress of the duration in percentage. The progress will be smoothed using the smoothing + * function. * - * @return The current progress after smoothing. It is guaranteed that when - * the duration starts, progress will be close to 0, and when it is - * finished, it will be close to 1. + * @return The current progress after smoothing. It is guaranteed that when the duration starts, progress + * will be close to 0, and when it is finished, it will be close to 1. */ double progress() const; /** - * Check if the duration is still running. - * Note that even when the duration first finishes, this function will - * still return that the function is running one time. + * Check if the duration is still running. Note that even when the duration first finishes, this function + * will still return that the function is running one time. * * @return Whether the duration still has not elapsed. */ bool running(); /** - * Reverse the duration. The progress will remain the same but the - * direction will reverse toward the opposite start or end point. + * Reverse the duration. The progress will remain the same but the direction will reverse toward the + * opposite start or end point. */ void reverse(); /** * Get duration direction. - * 0: reverse - * 1: forward + * 0: reverse 1: forward */ int get_direction(); @@ -138,17 +130,14 @@ class duration_t }; /** - * A timed transition is a transition between two states which happens - * over a period of time. + * A timed transition is a transition between two states which happens over a period of time. * - * During the transition, the current state is smoothly interpolated between - * start and end. + * During the transition, the current state is smoothly interpolated between start and end. */ struct timed_transition_t : public transition_t { /** - * Construct a new timed transition using the given duration to measure - * progress. + * Construct a new timed transition using the given duration to measure progress. * * @duration The duration to use for time measurement * @start The start state. @@ -204,14 +193,12 @@ class simple_animation_t : public duration_t, public timed_transition_t void animate(double start, double end); /** - * Animate from the current progress to the given end, and start the - * duration. + * Animate from the current progress to the given end, and start the duration. */ void animate(double end); /** - * Animate from the current progress to the current end, and start the - * duration. + * Animate from the current progress to the current end, and start the duration. */ void animate(); }; diff --git a/src/duration.cpp b/src/duration.cpp index fb61430..daaa861 100644 --- a/src/duration.cpp +++ b/src/duration.cpp @@ -4,7 +4,20 @@ #include #include #include +#include #include +#include + +double bezier_helper(double t, double p0, double p1, double p2, double p3) +{ + const double u = 1 - t; + return u * u * u * p0 + 3 * u * u * t * p1 + 3 * u * t * t * p2 + t * t * t * p3; +} + +inline bool epsilon_comparison(double a, double b) +{ + return std::fabs(a - b) <= std::numeric_limits::epsilon() * std::fabs(a + b); +} namespace wf { @@ -20,10 +33,59 @@ smooth_function circle = const double sigmoid_max = 1 + std::exp(-6); smooth_function sigmoid = [] (double x) -> double { return sigmoid_max / (1 + exp(-12 * x + 6)); }; + +smooth_function get_cubic_bezier(double x1, double y1, double x2, double y2) +{ + // https://en.wikipedia.org/wiki/Newton%27s_method + return [=] (double x) + { + double t = x; + for (int i = 0; i < 10; ++i) + { + const double f = bezier_helper(t, 0, x1, x2, 1) - x; + const double df = 3 * (1 - t) * (1 - t) * x1 + 6 * (1 - t) * t * (x2 - x1) + 3 * t * t * (1 - x2); + if (std::abs(f) < 1e-6) + { + break; + } + + t -= f / df; + } + + return bezier_helper(t, 0, y1, y2, 1); + }; +} } } // namespace animation } +bool wf::animation_description_t::operator ==(const animation_description_t & other) const +{ + if (easing_name == other.easing_name) + { + return (length_ms == other.length_ms); + } + + // Cubic-bezier easings need parsing to handle epsilon + std::stringstream easing_a(easing_name); + std::stringstream easing_b(easing_name); + std::string easing_type_a, easing_type_b; + easing_a >> easing_type_a; + easing_b >> easing_type_b; + if ((easing_type_a != "cubic-bezier") || (easing_type_b != "cubic-bezier")) + { + return false; + } + + double x1_a, y1_a, x2_a, y2_a, x1_b, y1_b, x2_b, y2_b; + easing_a >> x1_a >> y1_a >> x2_a >> y2_a; + easing_b >> x1_b >> y1_b >> x2_b >> y2_b; + return epsilon_comparison(x1_a, x1_b) && + epsilon_comparison(y1_a, y1_b) && + epsilon_comparison(x2_a, x2_b) && + epsilon_comparison(y2_b, y2_b); +} + class wf::animation::duration_t::impl { public: @@ -297,7 +359,7 @@ std::optional from_string(cons return animation_description_t{ .length_ms = *val, .easing = animation::smoothing::circle, - .easing_name = "circle", + .easing_name = "circle" }; } @@ -321,7 +383,20 @@ std::optional from_string(cons result.easing_name = "circle"; } - if (!animation::smoothing::easing_map.count(result.easing_name)) + if (animation::smoothing::easing_map.count(result.easing_name)) + { + result.easing = animation::smoothing::easing_map.at(result.easing_name); + } else if (result.easing_name == "cubic-bezier") + { + double x1 = 0, y1 = 0, x2 = 1, y2 = 1; + stream >> x1 >> y1 >> x2 >> y2; + result.easing = animation::smoothing::get_cubic_bezier(x1, y1, x2, y2); + result.easing_name = "cubic-bezier " + + to_string(x1) + + " " + to_string(y1) + + " " + to_string(x2) + + " " + to_string(y2); + } else { return {}; } @@ -333,7 +408,6 @@ std::optional from_string(cons return {}; } - result.easing = animation::smoothing::easing_map.at(result.easing_name); if (suffix == "s") { result.length_ms = N * 1000; diff --git a/test/types_test.cpp b/test/types_test.cpp index 92a322c..b7519da 100644 --- a/test/types_test.cpp +++ b/test/types_test.cpp @@ -536,9 +536,26 @@ TEST_CASE("wf::animation::animation_description_t") }; std::string sigmoid250ms_str = "250ms sigmoid"; + adt custom4s = { + .length_ms = 4000, + .easing = wf::animation::smoothing::get_cubic_bezier(0.25, 0.6, 0.75, 0.4), + .easing_name = "cubic-bezier 0.25 0.6 0.75 0.4", + }; + std::string custom4s_str = "4s cubic-bezier 0.25 0.6 0.75 0.4"; + + adt custom333ms = { + .length_ms = 333, + .easing = wf::animation::smoothing::get_cubic_bezier(0.16, 1, 0.3, 1), + .easing_name = "cubic-bezier 0.1600 1.0000 0.3000 1.0000", + }; + std::string custom333ms_str = "333ms cubic-bezier 0.16 1 0.3 1"; + CHECK(from_string(circle100_str) == circle100); CHECK(from_string(circle100_str_2) == circle100); CHECK(from_string(linear8s_str) == linear8s); CHECK(from_string(sigmoid250ms_str) == sigmoid250ms); + CHECK(from_string(custom4s_str) == custom4s); + CHECK(from_string(custom333ms_str) == custom333ms); + CHECK(from_string(to_string(custom333ms)) == from_string(custom333ms_str)); CHECK(to_string(sigmoid250ms) == sigmoid250ms_str); }