Skip to content

Commit d89d630

Browse files
authored
support cubic-bezier as easing (#85)
1 parent 76a5897 commit d89d630

File tree

3 files changed

+116
-38
lines changed

3 files changed

+116
-38
lines changed

include/wayfire/util/duration.hpp

Lines changed: 22 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,8 @@ namespace animation
1111
namespace smoothing
1212
{
1313
/**
14-
* A smooth function is a function which takes a double in [0, 1] and returns
15-
* another double in R. Both ranges represent percentage of a progress of
16-
* an animation.
14+
* A smooth function is a function which takes a double in [0, 1] and returns another double in R. Both ranges
15+
* represent percentage of a progress of an animation.
1716
*/
1817
using smooth_function = std::function<double (double)>;
1918

@@ -23,6 +22,8 @@ extern smooth_function linear;
2322
extern smooth_function circle;
2423
/** "sigmoid" smoothing function, i.e x -> 1.0 / (1 + exp(-12 * x + 6)) */
2524
extern smooth_function sigmoid;
25+
/** custom cubic-bezier as in CSS */
26+
extern smooth_function get_cubic_bezier(double x1, double y1, double x2, double y2);
2627

2728
std::vector<std::string> get_available_smooth_functions();
2829
}
@@ -34,10 +35,7 @@ struct animation_description_t
3435
animation::smoothing::smooth_function easing;
3536
std::string easing_name;
3637

37-
bool operator ==(const animation_description_t& other) const
38-
{
39-
return (length_ms == other.length_ms) && (easing_name == other.easing_name);
40-
}
38+
bool operator ==(const animation_description_t& other) const;
4139
};
4240

4341
namespace option_type
@@ -66,15 +64,13 @@ struct transition_t
6664
};
6765

6866
/**
69-
* duration_t is a class which can be used to track progress over a specific
70-
* time interval.
67+
* duration_t is a class which can be used to track progress over a specific time interval.
7168
*/
7269
class duration_t
7370
{
7471
public:
7572
/**
76-
* Construct a new duration.
77-
* Initially, the duration is not running and its progress is 1.
73+
* Construct a new duration. Initially, the duration is not running and its progress is 1.
7874
*
7975
* @param length The length of the duration in milliseconds.
8076
* @param smooth The smoothing function for transitions.
@@ -95,40 +91,36 @@ class duration_t
9591
duration_t& operator =(duration_t&& other) = default;
9692

9793
/**
98-
* Start the duration.
99-
* This means that the progress will get reset to 0.
94+
* Start the duration. This means that the progress will get reset to 0.
10095
*/
10196
void start();
10297

10398
/**
104-
* Get the progress of the duration in percentage.
105-
* The progress will be smoothed using the smoothing function.
99+
* Get the progress of the duration in percentage. The progress will be smoothed using the smoothing
100+
* function.
106101
*
107-
* @return The current progress after smoothing. It is guaranteed that when
108-
* the duration starts, progress will be close to 0, and when it is
109-
* finished, it will be close to 1.
102+
* @return The current progress after smoothing. It is guaranteed that when the duration starts, progress
103+
* will be close to 0, and when it is finished, it will be close to 1.
110104
*/
111105
double progress() const;
112106

113107
/**
114-
* Check if the duration is still running.
115-
* Note that even when the duration first finishes, this function will
116-
* still return that the function is running one time.
108+
* Check if the duration is still running. Note that even when the duration first finishes, this function
109+
* will still return that the function is running one time.
117110
*
118111
* @return Whether the duration still has not elapsed.
119112
*/
120113
bool running();
121114

122115
/**
123-
* Reverse the duration. The progress will remain the same but the
124-
* direction will reverse toward the opposite start or end point.
116+
* Reverse the duration. The progress will remain the same but the direction will reverse toward the
117+
* opposite start or end point.
125118
*/
126119
void reverse();
127120

128121
/**
129122
* Get duration direction.
130-
* 0: reverse
131-
* 1: forward
123+
* 0: reverse 1: forward
132124
*/
133125
int get_direction();
134126

@@ -138,17 +130,14 @@ class duration_t
138130
};
139131

140132
/**
141-
* A timed transition is a transition between two states which happens
142-
* over a period of time.
133+
* A timed transition is a transition between two states which happens over a period of time.
143134
*
144-
* During the transition, the current state is smoothly interpolated between
145-
* start and end.
135+
* During the transition, the current state is smoothly interpolated between start and end.
146136
*/
147137
struct timed_transition_t : public transition_t
148138
{
149139
/**
150-
* Construct a new timed transition using the given duration to measure
151-
* progress.
140+
* Construct a new timed transition using the given duration to measure progress.
152141
*
153142
* @duration The duration to use for time measurement
154143
* @start The start state.
@@ -204,14 +193,12 @@ class simple_animation_t : public duration_t, public timed_transition_t
204193
void animate(double start, double end);
205194

206195
/**
207-
* Animate from the current progress to the given end, and start the
208-
* duration.
196+
* Animate from the current progress to the given end, and start the duration.
209197
*/
210198
void animate(double end);
211199

212200
/**
213-
* Animate from the current progress to the current end, and start the
214-
* duration.
201+
* Animate from the current progress to the current end, and start the duration.
215202
*/
216203
void animate();
217204
};

src/duration.cpp

Lines changed: 77 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,20 @@
44
#include <algorithm>
55
#include <chrono>
66
#include <cmath>
7+
#include <limits>
78
#include <map>
9+
#include <sstream>
10+
11+
double bezier_helper(double t, double p0, double p1, double p2, double p3)
12+
{
13+
const double u = 1 - t;
14+
return u * u * u * p0 + 3 * u * u * t * p1 + 3 * u * t * t * p2 + t * t * t * p3;
15+
}
16+
17+
inline bool epsilon_comparison(double a, double b)
18+
{
19+
return std::fabs(a - b) <= std::numeric_limits<double>::epsilon() * std::fabs(a + b);
20+
}
821

922
namespace wf
1023
{
@@ -20,10 +33,59 @@ smooth_function circle =
2033
const double sigmoid_max = 1 + std::exp(-6);
2134
smooth_function sigmoid =
2235
[] (double x) -> double { return sigmoid_max / (1 + exp(-12 * x + 6)); };
36+
37+
smooth_function get_cubic_bezier(double x1, double y1, double x2, double y2)
38+
{
39+
// https://en.wikipedia.org/wiki/Newton%27s_method
40+
return [=] (double x)
41+
{
42+
double t = x;
43+
for (int i = 0; i < 10; ++i)
44+
{
45+
const double f = bezier_helper(t, 0, x1, x2, 1) - x;
46+
const double df = 3 * (1 - t) * (1 - t) * x1 + 6 * (1 - t) * t * (x2 - x1) + 3 * t * t * (1 - x2);
47+
if (std::abs(f) < 1e-6)
48+
{
49+
break;
50+
}
51+
52+
t -= f / df;
53+
}
54+
55+
return bezier_helper(t, 0, y1, y2, 1);
56+
};
57+
}
2358
}
2459
} // namespace animation
2560
}
2661

62+
bool wf::animation_description_t::operator ==(const animation_description_t & other) const
63+
{
64+
if (easing_name == other.easing_name)
65+
{
66+
return (length_ms == other.length_ms);
67+
}
68+
69+
// Cubic-bezier easings need parsing to handle epsilon
70+
std::stringstream easing_a(easing_name);
71+
std::stringstream easing_b(easing_name);
72+
std::string easing_type_a, easing_type_b;
73+
easing_a >> easing_type_a;
74+
easing_b >> easing_type_b;
75+
if ((easing_type_a != "cubic-bezier") || (easing_type_b != "cubic-bezier"))
76+
{
77+
return false;
78+
}
79+
80+
double x1_a, y1_a, x2_a, y2_a, x1_b, y1_b, x2_b, y2_b;
81+
easing_a >> x1_a >> y1_a >> x2_a >> y2_a;
82+
easing_b >> x1_b >> y1_b >> x2_b >> y2_b;
83+
return epsilon_comparison(x1_a, x1_b) &&
84+
epsilon_comparison(y1_a, y1_b) &&
85+
epsilon_comparison(x2_a, x2_b) &&
86+
epsilon_comparison(y2_b, y2_b);
87+
}
88+
2789
class wf::animation::duration_t::impl
2890
{
2991
public:
@@ -297,7 +359,7 @@ std::optional<animation_description_t> from_string<animation_description_t>(cons
297359
return animation_description_t{
298360
.length_ms = *val,
299361
.easing = animation::smoothing::circle,
300-
.easing_name = "circle",
362+
.easing_name = "circle"
301363
};
302364
}
303365

@@ -321,7 +383,20 @@ std::optional<animation_description_t> from_string<animation_description_t>(cons
321383
result.easing_name = "circle";
322384
}
323385

324-
if (!animation::smoothing::easing_map.count(result.easing_name))
386+
if (animation::smoothing::easing_map.count(result.easing_name))
387+
{
388+
result.easing = animation::smoothing::easing_map.at(result.easing_name);
389+
} else if (result.easing_name == "cubic-bezier")
390+
{
391+
double x1 = 0, y1 = 0, x2 = 1, y2 = 1;
392+
stream >> x1 >> y1 >> x2 >> y2;
393+
result.easing = animation::smoothing::get_cubic_bezier(x1, y1, x2, y2);
394+
result.easing_name = "cubic-bezier " +
395+
to_string(x1) +
396+
" " + to_string(y1) +
397+
" " + to_string(x2) +
398+
" " + to_string(y2);
399+
} else
325400
{
326401
return {};
327402
}
@@ -333,7 +408,6 @@ std::optional<animation_description_t> from_string<animation_description_t>(cons
333408
return {};
334409
}
335410

336-
result.easing = animation::smoothing::easing_map.at(result.easing_name);
337411
if (suffix == "s")
338412
{
339413
result.length_ms = N * 1000;

test/types_test.cpp

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -536,9 +536,26 @@ TEST_CASE("wf::animation::animation_description_t")
536536
};
537537
std::string sigmoid250ms_str = "250ms sigmoid";
538538

539+
adt custom4s = {
540+
.length_ms = 4000,
541+
.easing = wf::animation::smoothing::get_cubic_bezier(0.25, 0.6, 0.75, 0.4),
542+
.easing_name = "cubic-bezier 0.25 0.6 0.75 0.4",
543+
};
544+
std::string custom4s_str = "4s cubic-bezier 0.25 0.6 0.75 0.4";
545+
546+
adt custom333ms = {
547+
.length_ms = 333,
548+
.easing = wf::animation::smoothing::get_cubic_bezier(0.16, 1, 0.3, 1),
549+
.easing_name = "cubic-bezier 0.1600 1.0000 0.3000 1.0000",
550+
};
551+
std::string custom333ms_str = "333ms cubic-bezier 0.16 1 0.3 1";
552+
539553
CHECK(from_string<adt>(circle100_str) == circle100);
540554
CHECK(from_string<adt>(circle100_str_2) == circle100);
541555
CHECK(from_string<adt>(linear8s_str) == linear8s);
542556
CHECK(from_string<adt>(sigmoid250ms_str) == sigmoid250ms);
557+
CHECK(from_string<adt>(custom4s_str) == custom4s);
558+
CHECK(from_string<adt>(custom333ms_str) == custom333ms);
559+
CHECK(from_string<adt>(to_string<adt>(custom333ms)) == from_string<adt>(custom333ms_str));
543560
CHECK(to_string<adt>(sigmoid250ms) == sigmoid250ms_str);
544561
}

0 commit comments

Comments
 (0)