From db57f39ae3b10c0049a6430c0ce6461bd856f523 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mart=C3=ADn=20Lucas=20Golini?= Date: Thu, 8 Sep 2022 20:14:07 -0300 Subject: [PATCH] Added support to "cubic-bezier" timing function. --- TODO.md | 2 - bin/assets/ui/breeze.css | 2 +- docs/articles/cssspecification.md | 6 +- include/eepp/math/ease.hpp | 11 ++ include/eepp/math/easing.hpp | 37 +++-- include/eepp/ui/css/animationdefinition.hpp | 8 +- .../ui/css/stylesheetpropertyanimation.hpp | 2 + include/eepp/ui/css/timingfunction.hpp | 22 +++ include/eepp/ui/css/transitiondefinition.hpp | 3 + projects/linux/ee.files | 2 + src/eepp/math/easing.cpp | 143 +++++++++++++++++- src/eepp/ui/css/animationdefinition.cpp | 30 +++- .../ui/css/stylesheetpropertyanimation.cpp | 56 +++++-- src/eepp/ui/css/timingfunction.cpp | 29 ++++ src/eepp/ui/css/transitiondefinition.cpp | 31 ++-- src/eepp/ui/uistyle.cpp | 3 +- src/eepp/ui/uiviewpager.cpp | 5 +- 17 files changed, 332 insertions(+), 60 deletions(-) create mode 100644 include/eepp/ui/css/timingfunction.hpp create mode 100644 src/eepp/ui/css/timingfunction.cpp diff --git a/TODO.md b/TODO.md index 881545c1d..b9e3b78f5 100644 --- a/TODO.md +++ b/TODO.md @@ -23,8 +23,6 @@ ### CSS -* Add `cubic-bezier` timing function support. - * Add support for [calc](https://developer.mozilla.org/en-US/docs/Web/CSS/calc). ### UICodeEditor diff --git a/bin/assets/ui/breeze.css b/bin/assets/ui/breeze.css index 6cc7ca0a8..46715cfb9 100644 --- a/bin/assets/ui/breeze.css +++ b/bin/assets/ui/breeze.css @@ -141,7 +141,7 @@ DropDownList { border-color: var(--button-border); border-radius: var(--button-radius); border-width: var(--border-width); - transition: all 0.125s; + transition: all 0.125s cubic-bezier(0.77, 0.77, 0.38, 0.38); } DropDownList, diff --git a/docs/articles/cssspecification.md b/docs/articles/cssspecification.md index 961659c2e..96f2e107d 100644 --- a/docs/articles/cssspecification.md +++ b/docs/articles/cssspecification.md @@ -1847,12 +1847,12 @@ Read [transition-property](https://developer.mozilla.org/en-US/docs/Web/CSS/tran Read [transition-timing-function](https://developer.mozilla.org/en-US/docs/Web/CSS/transition-timing-function) documentation. -Timing function names are custom, and not the same as the standard, but `cubic-bezier` will be added -soon. Current timing functions supported: linear, quadratic-in, quadratic-out, quadratic-in-out, +Timing function names are custom, and not the same as the standard, but `cubic-bezier` is supported. +Current timing functions supported: linear, quadratic-in, quadratic-out, quadratic-in-out, sine-in, sine-out, sine-in-out, exponential-in, exponential-out, exponential-in-out, quartic-in, quartic-out, quartic-in-out, circular-in, circular-out, circular-in-out, cubic-in, cubic-out, cubic-in-out, back-in, back-out, back-in-out, bounce-in, bounce-out, bounce-in-out, elastic-in, -elastic-out, elastic-in-out, none. +elastic-out, elastic-in-out, cubic-bezier, none. ### value diff --git a/include/eepp/math/ease.hpp b/include/eepp/math/ease.hpp index 489dedb1c..2371aeca7 100644 --- a/include/eepp/math/ease.hpp +++ b/include/eepp/math/ease.hpp @@ -1,6 +1,7 @@ #ifndef EE_MATH_EASE #define EE_MATH_EASE +#include #include namespace EE { namespace Math { @@ -41,6 +42,7 @@ class Ease { ElasticIn, ElasticOut, ElasticInOut, + CubizBezier, None }; @@ -108,6 +110,8 @@ class Ease { return "elastic-out"; case Ease::ElasticInOut: return "elastic-in-out"; + case Ease::CubizBezier: + return "cubic-bezier"; case Ease::None: return "none"; } @@ -178,6 +182,13 @@ class Ease { return Ease::ElasticOut; if ( "elasticinout" == name || "elastic-in-out" == name ) return Ease::ElasticInOut; + if ( "cubicbezier" == name || "cubic-bezier" == name ) + return Ease::CubizBezier; + // Handle the case that the name is a function + if ( name.find_first_of( '(' ) != std::string::npos ) { + System::FunctionString func( System::FunctionString::parse( name ) ); + return fromName( func.getName(), defaultInterpolation ); + } return defaultInterpolation; } }; diff --git a/include/eepp/math/easing.hpp b/include/eepp/math/easing.hpp index 7ff9a256d..52e3b92b0 100644 --- a/include/eepp/math/easing.hpp +++ b/include/eepp/math/easing.hpp @@ -10,6 +10,25 @@ typedef double ( *easingCbFunc )( double, double, double, double ); extern EE_API easingCbFunc easingCb[]; +/** + * Keeps the initial value until the current time equals the duration. + * + * @param t Specifies the current time, between 0 and duration inclusive. + * @param b Specifies the initial value of the animation property. + * @param c Specifies the total change in the animation property. + * @param d Specifies the duration of the motion. + * @return The value of the interpolated property at the specified time. + */ +inline double noneInterpolation( double t, double b, double c, double d ) { + return t == 0 ? b : ( t == d ? c : b ); +} + +double EE_API cubicBezierInterpolation( double x1, double y1, double x2, double y2, double t ); + +inline double cubicBezierNoParams( double t, double, double, double ) { + return cubicBezierInterpolation( 0, 0, 1, 1, t ); +} + /** * Calculate the position of a point from a linear interpolation. * @@ -24,7 +43,7 @@ inline double linearInterpolation( double t, double b, double c, double d ) { } /** - * The QuadraticIn() method starts motion from a zero velocity + * The quadraticIn() method starts motion from a zero velocity * and then accelerates motion as it executes. * @param t Specifies the current time, between 0 and duration inclusive. * @param b Specifies the initial value of the animation property. @@ -39,7 +58,7 @@ inline double quadraticIn( double t, double b, double c, double d ) { } /** - * The QuadraticOut() method starts motion fast + * The quadraticOut() method starts motion fast * and then decelerates motion to a zero velocity as it executes. * * @param t Specifies the current time, between 0 and duration inclusive. @@ -55,8 +74,8 @@ inline double quadraticOut( double t, double b, double c, double d ) { } /** - * The QuadraticInOut() method combines the motion - * of the QuadraticIn() and QuadraticOut() methods + * The quadraticInOut() method combines the motion + * of the quadraticIn() and QuadraticOut() methods * to start the motion from a zero velocity, * accelerate motion, then decelerate to a zero velocity. * @@ -78,7 +97,7 @@ inline double quadraticInOut( double t, double b, double c, double d ) { } /** - * The SineIn() method starts motion from zero velocity + * The sineIn() method starts motion from zero velocity * and then accelerates motion as it executes. * * @param t Specifies the current time, between 0 and duration inclusive. @@ -122,7 +141,7 @@ inline double sineInOut( double t, double b, double c, double d ) { } /** - * The ExponentialIn() method starts motion slowly + * The exponentialIn() method starts motion slowly * and then accelerates motion as it executes. * * @param t Specifies the current time, between 0 and duration inclusive. @@ -136,7 +155,7 @@ inline double exponentialIn( double t, double b, double c, double d ) { } /** - * The ExponentialOut() method starts motion fast + * The exponentialOut() method starts motion fast * and then decelerates motion to a zero velocity as it executes. * * @param t Specifies the current time, between 0 and duration inclusive. @@ -150,8 +169,8 @@ inline double exponentialOut( double t, double b, double c, double d ) { } /** - * The ExponentialInOut() method combines the motion - * of the ExponentialIn() and ExponentialOut() methods + * The exponentialInOut() method combines the motion + * of the exponentialIn() and ExponentialOut() methods * to start the motion from a zero velocity, accelerate motion, * then decelerate to a zero velocity. * diff --git a/include/eepp/ui/css/animationdefinition.hpp b/include/eepp/ui/css/animationdefinition.hpp index a1f867320..81c586275 100644 --- a/include/eepp/ui/css/animationdefinition.hpp +++ b/include/eepp/ui/css/animationdefinition.hpp @@ -57,6 +57,8 @@ class EE_API AnimationDefinition { const Ease::Interpolation& getTimingFunction() const; + const std::vector& getTimingFunctionParameters() const; + const AnimationFillMode& getFillMode() const; void setName( const std::string& value ); @@ -69,6 +71,8 @@ class EE_API AnimationDefinition { void setTimingFunction( const Ease::Interpolation& value ); + void setTimingFunctionParameters( const std::vector& timingFunctionParameters ); + void setDirection( const AnimationDirection& value ); void setFillMode( const AnimationFillMode& value ); @@ -84,6 +88,7 @@ class EE_API AnimationDefinition { Time mDuration = Time::Zero; Int32 mIterations = 1; /* -1 == "infinite" */ Ease::Interpolation mTimingFunction = Ease::Interpolation::Linear; + std::vector mTimingFunctionParameters{}; AnimationDirection mDirection = Normal; AnimationFillMode mFillMode = None; bool mPaused = false; @@ -93,7 +98,8 @@ inline bool operator==( const AnimationDefinition& a, const AnimationDefinition& return a.getDuration() == b.getDuration() && a.getTimingFunction() == b.getTimingFunction() && a.getDelay() == b.getDelay() && a.getDirection() == b.getDirection() && a.isPaused() == b.isPaused() && a.getIterations() == b.getIterations() && - a.getName() == b.getName(); + a.getName() == b.getName() && + a.getTimingFunctionParameters() == b.getTimingFunctionParameters(); } inline bool operator!=( const AnimationDefinition& a, const AnimationDefinition& b ) { diff --git a/include/eepp/ui/css/stylesheetpropertyanimation.hpp b/include/eepp/ui/css/stylesheetpropertyanimation.hpp index caef4a928..20873f942 100644 --- a/include/eepp/ui/css/stylesheetpropertyanimation.hpp +++ b/include/eepp/ui/css/stylesheetpropertyanimation.hpp @@ -26,6 +26,7 @@ class EE_API StyleSheetPropertyAnimation : public Action { const PropertyDefinition* property, const std::string& startValue, const std::string& endValue, const Ease::Interpolation& timingFunction, + const std::vector timingFunctionParameters, const Uint32& propertyIndex, const bool& isDone ); static StyleSheetPropertyAnimation* fromAnimationKeyframes( @@ -44,6 +45,7 @@ class EE_API StyleSheetPropertyAnimation : public Action { New( const PropertyDefinition* property, const std::string& startValue, const std::string& endValue, const Uint32& propertyIndex, const Time& duration, const Time& delay, const Ease::Interpolation& timingFunction, + const std::vector& timingFunctionParameters, const AnimationOrigin& animationOrigin ); void start() override; diff --git a/include/eepp/ui/css/timingfunction.hpp b/include/eepp/ui/css/timingfunction.hpp new file mode 100644 index 000000000..62cf4419c --- /dev/null +++ b/include/eepp/ui/css/timingfunction.hpp @@ -0,0 +1,22 @@ +#ifndef EE_UI_CSS_TIMINGFUNCTION_HPP +#define EE_UI_CSS_TIMINGFUNCTION_HPP + +#include +#include + +using namespace EE; +using namespace EE::Math; + +namespace EE { namespace UI { namespace CSS { + +class EE_API TimingFunction { + public: + static TimingFunction parse( std::string timingFunction ); + + Ease::Interpolation interpolation{ Ease::None }; + std::vector parameters; +}; + +}}} // namespace EE::UI::CSS + +#endif // EE_UI_CSS_TIMINGFUNCTION_HPP diff --git a/include/eepp/ui/css/transitiondefinition.hpp b/include/eepp/ui/css/transitiondefinition.hpp index 9962831a0..3c74a2e63 100644 --- a/include/eepp/ui/css/transitiondefinition.hpp +++ b/include/eepp/ui/css/transitiondefinition.hpp @@ -23,12 +23,15 @@ class EE_API TransitionDefinition { Ease::Interpolation getTimingFunction() const { return timingFunction; } + std::vector getTimingFunctionParameters() const { return timingFunctionParameters; } + const Time& getDelay() const { return delay; } const Time& getDuration() const { return duration; } std::string property; Ease::Interpolation timingFunction = Ease::Interpolation::Linear; + std::vector timingFunctionParameters{}; Time delay = Time::Zero; Time duration = Time::Zero; }; diff --git a/projects/linux/ee.files b/projects/linux/ee.files index f1f4ea65b..fa388cd5a 100644 --- a/projects/linux/ee.files +++ b/projects/linux/ee.files @@ -327,6 +327,7 @@ ../../include/eepp/ui/css/stylesheetspecification.hpp ../../include/eepp/ui/css/stylesheetstyle.hpp ../../include/eepp/ui/css/stylesheetvariable.hpp +../../include/eepp/ui/css/timingfunction.hpp ../../include/eepp/ui/css/transitiondefinition.hpp ../../include/eepp/ui/doc/syntaxcolorscheme.hpp ../../include/eepp/ui/doc/syntaxdefinition.hpp @@ -814,6 +815,7 @@ ../../src/eepp/ui/css/stylesheetspecification.cpp ../../src/eepp/ui/css/stylesheetstyle.cpp ../../src/eepp/ui/css/stylesheetvariable.cpp +../../src/eepp/ui/css/timingfunction.cpp ../../src/eepp/ui/css/transitiondefinition.cpp ../../src/eepp/ui/doc/syntaxcolorscheme.cpp ../../src/eepp/ui/doc/syntaxdefinition.cpp diff --git a/src/eepp/math/easing.cpp b/src/eepp/math/easing.cpp index 6a31b0283..edb944b97 100644 --- a/src/eepp/math/easing.cpp +++ b/src/eepp/math/easing.cpp @@ -2,12 +2,141 @@ namespace EE { namespace Math { namespace easing { -easingCbFunc easingCb[] = { - linearInterpolation, quadraticIn, quadraticOut, quadraticInOut, sineIn, sineOut, - sineInOut, exponentialIn, exponentialOut, exponentialInOut, quarticIn, quarticOut, - quarticInOut, quinticIn, quinticOut, quinticInOut, circularIn, circularOut, - circularInOut, cubicIn, cubicOut, cubicInOut, backIn, backOut, - backInOut, bounceIn, bounceOut, bounceInOut, elasticIn, elasticOut, - elasticInOut}; +easingCbFunc easingCb[] = { linearInterpolation, + quadraticIn, + quadraticOut, + quadraticInOut, + sineIn, + sineOut, + sineInOut, + exponentialIn, + exponentialOut, + exponentialInOut, + quarticIn, + quarticOut, + quarticInOut, + quinticIn, + quinticOut, + quinticInOut, + circularIn, + circularOut, + circularInOut, + cubicIn, + cubicOut, + cubicInOut, + backIn, + backOut, + backInOut, + bounceIn, + bounceOut, + bounceInOut, + elasticIn, + elasticOut, + elasticInOut, + cubicBezierNoParams, + noneInterpolation }; + +/** + * https://github.com/gre/bezier-easing + * BezierEasing - use bezier curve for transition easing function + * by Gaëtan Renaudeau 2014 - 2015 – MIT License + */ +#define NEWTON_ITERATIONS 4 +#define NEWTON_MIN_SLOPE 0.001 +#define SUBDIVISION_PRECISION 0.0000001 +#define SUBDIVISION_MAX_ITERATIONS 10 +#define kSplineTableSize 11 +#define kSampleStepSize ( 1.0 / ( kSplineTableSize - 1.0 ) ) + +double A( double aA1, double aA2 ) { + return 1.0 - 3.0 * aA2 + 3.0 * aA1; +} +double B( double aA1, double aA2 ) { + return 3.0 * aA2 - 6.0 * aA1; +} +double C( double aA1 ) { + return 3.0 * aA1; +} + +// Returns x(t) given t, x1, and x2, or y(t) given t, y1, and y2. +double calcBezier( double aT, double aA1, double aA2 ) { + return ( ( A( aA1, aA2 ) * aT + B( aA1, aA2 ) ) * aT + C( aA1 ) ) * aT; +} + +// Returns dx/dt given t, x1, and x2, or dy/dt given t, y1, and y2. +double getSlope( double aT, double aA1, double aA2 ) { + return 3.0 * A( aA1, aA2 ) * aT * aT + 2.0 * B( aA1, aA2 ) * aT + C( aA1 ); +} + +double binarySubdivide( double aX, double aA, double aB, double mX1, double mX2 ) { + double currentX, currentT; + size_t i = 0; + do { + currentT = aA + ( aB - aA ) / 2.0; + currentX = calcBezier( currentT, mX1, mX2 ) - aX; + if ( currentX > 0.0 ) { + aB = currentT; + } else { + aA = currentT; + } + } while ( eeabs( currentX ) > SUBDIVISION_PRECISION && ++i < SUBDIVISION_MAX_ITERATIONS ); + return currentT; +} + +double newtonRaphsonIterate( double aX, double aGuessT, double mX1, double mX2 ) { + for ( size_t i = 0; i < NEWTON_ITERATIONS; ++i ) { + double currentSlope = getSlope( aGuessT, mX1, mX2 ); + if ( currentSlope == 0.0 ) { + return aGuessT; + } + double currentX = calcBezier( aGuessT, mX1, mX2 ) - aX; + aGuessT -= currentX / currentSlope; + } + return aGuessT; +} + +double cubicBezierInterpolation( double x1, double y1, double x2, double y2, double t ) { + if ( !( 0 <= x1 && x1 <= 1 && 0 <= x2 && x2 <= 1 ) ) + return t; // 'bezier x values must be in [0, 1] range' + + if ( x1 == y1 && x2 == y2 ) + return t; + + // Precompute samples table + double sampleValues[kSplineTableSize]; + for ( size_t i = 0; i < kSplineTableSize; ++i ) + sampleValues[i] = calcBezier( i * kSampleStepSize, x1, x2 ); + + auto getTForX = [&sampleValues, x1, x2]( double aX ) { + double intervalStart = 0.0; + size_t currentSample = 1; + double lastSample = kSplineTableSize - 1; + + for ( ; currentSample != lastSample && sampleValues[currentSample] <= aX; + ++currentSample ) { + intervalStart += kSampleStepSize; + } + --currentSample; + + // Interpolate to provide an initial guess for t + double dist = ( aX - sampleValues[currentSample] ) / + ( sampleValues[currentSample + 1] - sampleValues[currentSample] ); + double guessForT = intervalStart + dist * kSampleStepSize; + + double initialSlope = getSlope( guessForT, x1, x2 ); + if ( initialSlope >= NEWTON_MIN_SLOPE ) { + return newtonRaphsonIterate( aX, guessForT, x1, x2 ); + } else if ( initialSlope == 0.0 ) { + return guessForT; + } else { + return binarySubdivide( aX, intervalStart, intervalStart + kSampleStepSize, x1, x2 ); + } + }; + + // Because JavaScript number are imprecise, we should guarantee the extremes are right. + if ( t == 0 || t == 1 ) + return t; + return calcBezier( getTForX( t ), y1, y2 ); +} }}} // namespace EE::Math::easing diff --git a/src/eepp/ui/css/animationdefinition.cpp b/src/eepp/ui/css/animationdefinition.cpp index b111f9c16..b13dd5e84 100644 --- a/src/eepp/ui/css/animationdefinition.cpp +++ b/src/eepp/ui/css/animationdefinition.cpp @@ -1,9 +1,11 @@ +#include #include #include +#include namespace EE { namespace UI { namespace CSS { -bool isTimingFunction( const std::string& str ) { +inline bool isTimingFunction( const std::string& str ) { return Ease::Interpolation::None != Ease::fromName( str, Ease::Interpolation::None ); } @@ -15,6 +17,7 @@ std::unordered_map AnimationDefinition::parseA std::vector