From de8a31046c5718271d25f9a94998c1cb8d87993c Mon Sep 17 00:00:00 2001 From: Vlad Date: Thu, 2 Oct 2025 17:05:54 +0300 Subject: [PATCH 1/8] support cubic-bezier as easing --- src/duration.cpp | 35 +++++++++++++++++++++++++++++++++-- 1 file changed, 33 insertions(+), 2 deletions(-) diff --git a/src/duration.cpp b/src/duration.cpp index fb61430..934ec47 100644 --- a/src/duration.cpp +++ b/src/duration.cpp @@ -6,6 +6,11 @@ #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; +} + namespace wf { namespace animation @@ -20,6 +25,22 @@ 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 } @@ -321,7 +342,17 @@ 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); + } + else { return {}; } @@ -333,7 +364,7 @@ std::optional from_string(cons return {}; } - result.easing = animation::smoothing::easing_map.at(result.easing_name); + if (suffix == "s") { result.length_ms = N * 1000; From b318c9fe6a1ba02decf16aa7bd6c4adce1c3ecf8 Mon Sep 17 00:00:00 2001 From: Vlad Date: Thu, 2 Oct 2025 17:09:40 +0300 Subject: [PATCH 2/8] fix bracket placement --- src/duration.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/duration.cpp b/src/duration.cpp index 934ec47..62c5326 100644 --- a/src/duration.cpp +++ b/src/duration.cpp @@ -6,7 +6,8 @@ #include #include -double bezier_helper(double t, double p0, double p1, double p2, double p3) { +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; } From 5d1ebb3263fbcb711d77b889d06d80d52e932559 Mon Sep 17 00:00:00 2001 From: Vlad Date: Sat, 4 Oct 2025 16:48:30 +0300 Subject: [PATCH 3/8] uncrustify --- src/duration.cpp | 25 ++++++++++++++----------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/src/duration.cpp b/src/duration.cpp index 62c5326..421fe36 100644 --- a/src/duration.cpp +++ b/src/duration.cpp @@ -9,7 +9,7 @@ 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; + return u * u * u * p0 + 3 * u * u * t * p1 + 3 * u * t * t * p2 + t * t * t * p3; } namespace wf @@ -27,18 +27,24 @@ 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) { +smooth_function get_cubic_bezier(double x1, double y1, double x2, double y2) +{ // https://en.wikipedia.org/wiki/Newton%27s_method - return [=](double x) { + return [=] (double x) + { double t = x; - for(int i=0; i<10; ++i) { + 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) { + 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); }; } @@ -346,14 +352,12 @@ std::optional from_string(cons 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") + } 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); - } - else + } else { return {}; } @@ -365,7 +369,6 @@ std::optional from_string(cons return {}; } - if (suffix == "s") { result.length_ms = N * 1000; From c14d89d39b3d684e27ba7e627b2fefb922bfb522 Mon Sep 17 00:00:00 2001 From: Vlad Date: Sat, 18 Oct 2025 23:19:16 +0300 Subject: [PATCH 4/8] add to_string and comparison support for cubic-bezier --- include/wayfire/util/duration.hpp | 13 ++++++++++++- src/duration.cpp | 15 ++++++++++++++- 2 files changed, 26 insertions(+), 2 deletions(-) diff --git a/include/wayfire/util/duration.hpp b/include/wayfire/util/duration.hpp index 61fecd7..032dedb 100644 --- a/include/wayfire/util/duration.hpp +++ b/include/wayfire/util/duration.hpp @@ -33,10 +33,21 @@ struct animation_description_t int length_ms; animation::smoothing::smooth_function easing; std::string easing_name; + double x1, y1, x2, y2; bool operator ==(const animation_description_t& other) const { - return (length_ms == other.length_ms) && (easing_name == other.easing_name); + return + (length_ms == other.length_ms) + && ( + (easing_name == other.easing_name) + || ( + (x1 == other.x1) + && (y1 == other.y1) + && (x2 == other.x2) + && (y2 == other.y2) + ) + ); } }; diff --git a/src/duration.cpp b/src/duration.cpp index 421fe36..e2d6676 100644 --- a/src/duration.cpp +++ b/src/duration.cpp @@ -326,6 +326,7 @@ std::optional from_string(cons .length_ms = *val, .easing = animation::smoothing::circle, .easing_name = "circle", + .x1 = 0, .y1 = 0, .x2 = 0, .y2 = 0 }; } @@ -357,6 +358,10 @@ std::optional from_string(cons 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.x1 = x1; + result.y1 = y1; + result.x2 = x2; + result.y2 = y2; } else { return {}; @@ -383,7 +388,15 @@ std::optional from_string(cons template<> std::string to_string(const animation_description_t& value) { - return to_string(value.length_ms) + "ms " + to_string(value.easing_name); + if (!value.easing_name.empty()) { + return to_string(value.length_ms) + "ms " + to_string(value.easing_name); + } else { + return to_string(value.length_ms) + + "ms cubic-bezier " + to_string(value.x1) + + " " + to_string(value.y1) + + " " + to_string(value.x2) + + " " + to_string(value.y2); + } } } } From c74088bb5acf06b1130fa940d38b58016e443e22 Mon Sep 17 00:00:00 2001 From: Vlad Date: Sat, 18 Oct 2025 23:28:17 +0300 Subject: [PATCH 5/8] fix headers; add unit test --- include/wayfire/util/duration.hpp | 2 ++ src/duration.cpp | 7 +++++-- test/types_test.cpp | 10 ++++++++++ 3 files changed, 17 insertions(+), 2 deletions(-) diff --git a/include/wayfire/util/duration.hpp b/include/wayfire/util/duration.hpp index 032dedb..3301338 100644 --- a/include/wayfire/util/duration.hpp +++ b/include/wayfire/util/duration.hpp @@ -23,6 +23,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(); } diff --git a/src/duration.cpp b/src/duration.cpp index e2d6676..bf50c54 100644 --- a/src/duration.cpp +++ b/src/duration.cpp @@ -388,9 +388,12 @@ std::optional from_string(cons template<> std::string to_string(const animation_description_t& value) { - if (!value.easing_name.empty()) { + if (!value.easing_name.empty()) + { return to_string(value.length_ms) + "ms " + to_string(value.easing_name); - } else { + } + else + { return to_string(value.length_ms) + "ms cubic-bezier " + to_string(value.x1) + " " + to_string(value.y1) diff --git a/test/types_test.cpp b/test/types_test.cpp index 92a322c..46cffaf 100644 --- a/test/types_test.cpp +++ b/test/types_test.cpp @@ -536,9 +536,19 @@ 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 = "", + .x1 = 0.25, .y1 = 0.6, .x2 = 0.75, .y2 = 0.4 + }; + std::string custom4s_str = "4s cubic-bezier 0.25 0.6 0.75 0.4"; + 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(to_string(sigmoid250ms) == sigmoid250ms_str); + CHECK(to_string(custom4s) == custom4s_str); } From 853d5e9fe61ea59b504201fd1ed9a5e0f6a0419a Mon Sep 17 00:00:00 2001 From: Vlad Date: Sat, 18 Oct 2025 23:56:58 +0300 Subject: [PATCH 6/8] make it simpler --- include/wayfire/util/duration.hpp | 13 +------------ src/duration.cpp | 25 +++++++------------------ test/types_test.cpp | 3 +-- 3 files changed, 9 insertions(+), 32 deletions(-) diff --git a/include/wayfire/util/duration.hpp b/include/wayfire/util/duration.hpp index 3301338..2e0479d 100644 --- a/include/wayfire/util/duration.hpp +++ b/include/wayfire/util/duration.hpp @@ -35,21 +35,10 @@ struct animation_description_t int length_ms; animation::smoothing::smooth_function easing; std::string easing_name; - double x1, y1, x2, y2; bool operator ==(const animation_description_t& other) const { - return - (length_ms == other.length_ms) - && ( - (easing_name == other.easing_name) - || ( - (x1 == other.x1) - && (y1 == other.y1) - && (x2 == other.x2) - && (y2 == other.y2) - ) - ); + return (length_ms == other.length_ms) && (easing_name == other.easing_name); } }; diff --git a/src/duration.cpp b/src/duration.cpp index bf50c54..e820ea3 100644 --- a/src/duration.cpp +++ b/src/duration.cpp @@ -325,8 +325,7 @@ std::optional from_string(cons return animation_description_t{ .length_ms = *val, .easing = animation::smoothing::circle, - .easing_name = "circle", - .x1 = 0, .y1 = 0, .x2 = 0, .y2 = 0 + .easing_name = "circle" }; } @@ -358,10 +357,11 @@ std::optional from_string(cons 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.x1 = x1; - result.y1 = y1; - result.x2 = x2; - result.y2 = y2; + result.easing_name = "cubic-bezier " + + to_string(x1) + + " " + to_string(y1) + + " " + to_string(x2) + + " " + to_string(y2); } else { return {}; @@ -388,18 +388,7 @@ std::optional from_string(cons template<> std::string to_string(const animation_description_t& value) { - if (!value.easing_name.empty()) - { - return to_string(value.length_ms) + "ms " + to_string(value.easing_name); - } - else - { - return to_string(value.length_ms) - + "ms cubic-bezier " + to_string(value.x1) - + " " + to_string(value.y1) - + " " + to_string(value.x2) - + " " + to_string(value.y2); - } + return to_string(value.length_ms) + "ms " + to_string(value.easing_name); } } } diff --git a/test/types_test.cpp b/test/types_test.cpp index 46cffaf..a10e6a1 100644 --- a/test/types_test.cpp +++ b/test/types_test.cpp @@ -539,8 +539,7 @@ TEST_CASE("wf::animation::animation_description_t") adt custom4s = { .length_ms = 4000, .easing = wf::animation::smoothing::get_cubic_bezier(0.25, 0.6, 0.75, 0.4), - .easing_name = "", - .x1 = 0.25, .y1 = 0.6, .x2 = 0.75, .y2 = 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"; From fab0caa4449342249a720cc00a694448979ca429 Mon Sep 17 00:00:00 2001 From: Vlad Date: Sun, 19 Oct 2025 11:27:53 +0300 Subject: [PATCH 7/8] fix comparison and tests --- include/wayfire/util/duration.hpp | 5 +---- src/duration.cpp | 32 +++++++++++++++++++++++++++++++ test/types_test.cpp | 10 +++++++++- 3 files changed, 42 insertions(+), 5 deletions(-) diff --git a/include/wayfire/util/duration.hpp b/include/wayfire/util/duration.hpp index 2e0479d..664fbd8 100644 --- a/include/wayfire/util/duration.hpp +++ b/include/wayfire/util/duration.hpp @@ -36,10 +36,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 diff --git a/src/duration.cpp b/src/duration.cpp index e820ea3..c4662fd 100644 --- a/src/duration.cpp +++ b/src/duration.cpp @@ -4,7 +4,9 @@ #include #include #include +#include #include +#include double bezier_helper(double t, double p0, double p1, double p2, double p3) { @@ -12,6 +14,11 @@ double bezier_helper(double t, double p0, double p1, double p2, double p3) 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 { namespace animation @@ -52,6 +59,31 @@ smooth_function get_cubic_bezier(double x1, double y1, double x2, double y2) } // 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: diff --git a/test/types_test.cpp b/test/types_test.cpp index a10e6a1..b7519da 100644 --- a/test/types_test.cpp +++ b/test/types_test.cpp @@ -543,11 +543,19 @@ TEST_CASE("wf::animation::animation_description_t") }; 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); - CHECK(to_string(custom4s) == custom4s_str); } From da3d389e53ea765d3b0f2b4572cce55bad63c8bf Mon Sep 17 00:00:00 2001 From: Vlad Date: Tue, 21 Oct 2025 20:20:14 +0300 Subject: [PATCH 8/8] =?UTF-8?q?uncrustify=20=E2=9C=A8=F0=9F=92=85=E2=9C=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- include/wayfire/util/duration.hpp | 50 ++++++++++++------------------- src/duration.cpp | 24 ++++++++------- 2 files changed, 32 insertions(+), 42 deletions(-) diff --git a/include/wayfire/util/duration.hpp b/include/wayfire/util/duration.hpp index 664fbd8..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; @@ -65,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. @@ -94,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(); @@ -137,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. @@ -203,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 c4662fd..daaa861 100644 --- a/src/duration.cpp +++ b/src/duration.cpp @@ -59,29 +59,31 @@ smooth_function get_cubic_bezier(double x1, double y1, double x2, double y2) } // namespace animation } -bool wf::animation_description_t::operator==(const animation_description_t &other) const +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") + 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); + 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 @@ -389,11 +391,11 @@ std::optional from_string(cons 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); + result.easing_name = "cubic-bezier " + + to_string(x1) + + " " + to_string(y1) + + " " + to_string(x2) + + " " + to_string(y2); } else { return {};