Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 22 additions & 35 deletions include/wayfire/util/duration.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<double (double)>;

Expand All @@ -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<std::string> get_available_smooth_functions();
}
Expand All @@ -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
Expand Down Expand Up @@ -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.
Expand All @@ -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();

Expand All @@ -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.
Expand Down Expand Up @@ -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();
};
Expand Down
80 changes: 77 additions & 3 deletions src/duration.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,20 @@
#include <algorithm>
#include <chrono>
#include <cmath>
#include <limits>
#include <map>
#include <sstream>

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<double>::epsilon() * std::fabs(a + b);
}

namespace wf
{
Expand All @@ -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:
Expand Down Expand Up @@ -297,7 +359,7 @@ std::optional<animation_description_t> from_string<animation_description_t>(cons
return animation_description_t{
.length_ms = *val,
.easing = animation::smoothing::circle,
.easing_name = "circle",
.easing_name = "circle"
};
}

Expand All @@ -321,7 +383,20 @@ std::optional<animation_description_t> from_string<animation_description_t>(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 {};
}
Expand All @@ -333,7 +408,6 @@ std::optional<animation_description_t> from_string<animation_description_t>(cons
return {};
}

result.easing = animation::smoothing::easing_map.at(result.easing_name);
if (suffix == "s")
{
result.length_ms = N * 1000;
Expand Down
17 changes: 17 additions & 0 deletions test/types_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<adt>(circle100_str) == circle100);
CHECK(from_string<adt>(circle100_str_2) == circle100);
CHECK(from_string<adt>(linear8s_str) == linear8s);
CHECK(from_string<adt>(sigmoid250ms_str) == sigmoid250ms);
CHECK(from_string<adt>(custom4s_str) == custom4s);
CHECK(from_string<adt>(custom333ms_str) == custom333ms);
CHECK(from_string<adt>(to_string<adt>(custom333ms)) == from_string<adt>(custom333ms_str));
CHECK(to_string<adt>(sigmoid250ms) == sigmoid250ms_str);
}