Reduce allocations in actions and CSS style updates

- allocate Action instances from reusable size-class pools
  - use inline storage for Runnable callbacks, action sequences, spawned actions,
    callback registrations, and main-thread action queues
  - preserve action factory and ownership semantics while recycling completed actions
  - cache computed transitions in ElementDefinition instead of reparsing them on
    every state change
  - parse transition and timing-function values through allocation-light string views
    and inline parameter storage
  - add a non-owning FunctionString view parser with callback-based parameter iteration
  - preserve nested functions, quoted parameters, escaped quotes, and early termination
  - fix transition timing-function lists to cycle independently from delay lists
  - avoid copying variable caches during CSS variable substitution
  - parse drawable sizes and positions without allocating split string vectors
  - reuse animation state and reduce temporary property parsing allocations
  - avoid memory-manager bookkeeping allocations for static source filenames
  - consolidate whitespace trimming through the existing String utilities
  - add regression coverage for action reuse, transition parsing, computed transition
    lookup, timing-list cycling, and non-owning FunctionString parsing
This commit is contained in:
Martín Lucas Golini
2026-08-06 01:16:52 -03:00
parent 74b0454721
commit d90ffe6fea
38 changed files with 1229 additions and 387 deletions
+5 -3
View File
@@ -12,10 +12,12 @@ namespace EE {
class EE_API AllocatedPointer { class EE_API AllocatedPointer {
public: public:
AllocatedPointer( void* data, const std::string& File, int Line, size_t memory, /** file must have static storage duration. All memory-manager entry points pass __FILE__ or a
bool track = false, bool globalAllocation = false ); * string literal, so retaining the pointer avoids allocating bookkeeping strings. */
AllocatedPointer( void* data, const char* file, int line, size_t memory, bool track = false,
bool globalAllocation = false );
std::string mFile; const char* mFile;
int mLine; int mLine;
size_t mMemory; size_t mMemory;
void* mData; void* mData;
+174
View File
@@ -0,0 +1,174 @@
#ifndef EE_CORE_SMALL_FUNCTION_HPP
#define EE_CORE_SMALL_FUNCTION_HPP
#include <cstddef>
#include <functional>
#include <new>
#include <type_traits>
#include <utility>
namespace EE {
/** Copyable type-erased void callable with inline storage and a heap fallback.
*
* This intentionally implements only the signature used by the scene scheduler. Ordinary lambdas
* stay inside the object; unusually large or over-aligned callables retain std::function-like
* fallback behavior.
*/
template <std::size_t InlineSize = 64> class SmallFunction {
public:
SmallFunction() noexcept = default;
SmallFunction( std::nullptr_t ) noexcept {}
SmallFunction( const SmallFunction& other ) {
if ( other.mVTable )
other.mVTable->copy( *this, other );
}
SmallFunction( SmallFunction&& other ) {
if ( other.mVTable )
other.mVTable->move( *this, other );
}
template <typename Callable, typename Decayed = std::decay_t<Callable>,
std::enable_if_t<!std::is_same_v<Decayed, SmallFunction> &&
std::is_invocable_r_v<void, Decayed&>,
int> = 0>
SmallFunction( Callable&& callable ) {
emplace<Decayed>( std::forward<Callable>( callable ) );
}
~SmallFunction() { reset(); }
SmallFunction& operator=( const SmallFunction& other ) {
if ( this != &other ) {
reset();
if ( other.mVTable )
other.mVTable->copy( *this, other );
}
return *this;
}
SmallFunction& operator=( SmallFunction&& other ) {
if ( this != &other ) {
reset();
if ( other.mVTable )
other.mVTable->move( *this, other );
}
return *this;
}
template <typename Callable, typename Decayed = std::decay_t<Callable>,
std::enable_if_t<!std::is_same_v<Decayed, SmallFunction> &&
std::is_invocable_r_v<void, Decayed&>,
int> = 0>
SmallFunction& operator=( Callable&& callable ) {
reset();
emplace<Decayed>( std::forward<Callable>( callable ) );
return *this;
}
SmallFunction& operator=( std::nullptr_t ) noexcept {
reset();
return *this;
}
explicit operator bool() const noexcept { return nullptr != mVTable; }
void operator()() const {
if ( !mVTable )
throw std::bad_function_call();
mVTable->invoke( const_cast<SmallFunction&>( *this ) );
}
void reset() noexcept {
if ( mVTable ) {
mVTable->destroy( *this );
mVTable = nullptr;
}
}
static constexpr std::size_t inlineSize() noexcept { return InlineSize; }
private:
static constexpr std::size_t StorageSize =
InlineSize < sizeof( void* ) ? sizeof( void* ) : InlineSize;
using Storage = std::aligned_storage_t<StorageSize, alignof( std::max_align_t )>;
struct VTable {
void ( *invoke )( SmallFunction& );
void ( *copy )( SmallFunction&, const SmallFunction& );
void ( *move )( SmallFunction&, SmallFunction& );
void ( *destroy )( SmallFunction& ) noexcept;
};
template <typename Callable>
static constexpr bool FitsInline =
sizeof( Callable ) <= StorageSize && alignof( Callable ) <= alignof( Storage );
template <typename Callable> static Callable*& heapPointer( SmallFunction& function ) {
return *reinterpret_cast<Callable**>( &function.mStorage );
}
template <typename Callable> static Callable* inlinePointer( SmallFunction& function ) {
return reinterpret_cast<Callable*>( &function.mStorage );
}
template <typename Callable, typename Value> void emplace( Value&& value ) {
static_assert( std::is_copy_constructible_v<Callable>,
"SmallFunction requires a copy-constructible callable" );
if constexpr ( FitsInline<Callable> ) {
new ( &mStorage ) Callable( std::forward<Value>( value ) );
mVTable = &inlineVTable<Callable>();
} else {
heapPointer<Callable>( *this ) = new Callable( std::forward<Value>( value ) );
mVTable = &heapVTable<Callable>();
}
}
template <typename Callable> static const VTable& inlineVTable() {
static const VTable table{
[]( SmallFunction& function ) { ( *inlinePointer<Callable>( function ) )(); },
[]( SmallFunction& destination, const SmallFunction& source ) {
new ( &destination.mStorage )
Callable( *inlinePointer<Callable>( const_cast<SmallFunction&>( source ) ) );
destination.mVTable = &inlineVTable<Callable>();
},
[]( SmallFunction& destination, SmallFunction& source ) {
new ( &destination.mStorage )
Callable( std::move( *inlinePointer<Callable>( source ) ) );
inlinePointer<Callable>( source )->~Callable();
destination.mVTable = &inlineVTable<Callable>();
source.mVTable = nullptr;
},
[]( SmallFunction& function ) noexcept {
inlinePointer<Callable>( function )->~Callable();
} };
return table;
}
template <typename Callable> static const VTable& heapVTable() {
static const VTable table{
[]( SmallFunction& function ) { ( *heapPointer<Callable>( function ) )(); },
[]( SmallFunction& destination, const SmallFunction& source ) {
heapPointer<Callable>( destination ) =
new Callable( *heapPointer<Callable>( const_cast<SmallFunction&>( source ) ) );
destination.mVTable = &heapVTable<Callable>();
},
[]( SmallFunction& destination, SmallFunction& source ) {
heapPointer<Callable>( destination ) = heapPointer<Callable>( source );
heapPointer<Callable>( source ) = nullptr;
destination.mVTable = &heapVTable<Callable>();
source.mVTable = nullptr;
},
[]( SmallFunction& function ) noexcept { delete heapPointer<Callable>( function ); } };
return table;
}
Storage mStorage;
const VTable* mVTable{ nullptr };
};
} // namespace EE
#endif
+16 -7
View File
@@ -1,8 +1,8 @@
#ifndef EE_MATH_EASE #ifndef EE_MATH_EASE
#define EE_MATH_EASE #define EE_MATH_EASE
#include <eepp/system/functionstring.hpp>
#include <string> #include <string>
#include <string_view>
namespace EE { namespace Math { namespace EE { namespace Math {
@@ -118,7 +118,7 @@ class Ease {
return ""; return "";
} }
static Interpolation fromName( const std::string& name, static Interpolation fromName( std::string_view name,
const Interpolation& defaultInterpolation = Ease::Linear ) { const Interpolation& defaultInterpolation = Ease::Linear ) {
if ( "linear" == name ) if ( "linear" == name )
return Ease::Linear; return Ease::Linear;
@@ -184,13 +184,22 @@ class Ease {
return Ease::ElasticInOut; return Ease::ElasticInOut;
if ( "cubicbezier" == name || "cubic-bezier" == name ) if ( "cubicbezier" == name || "cubic-bezier" == name )
return Ease::CubizBezier; return Ease::CubizBezier;
// Handle the case that the name is a function // Function parameters do not participate in interpolation-name lookup.
if ( name.find_first_of( '(' ) != std::string::npos ) { const auto functionStart = name.find_first_of( '(' );
System::FunctionString func( System::FunctionString::parse( name ) ); if ( functionStart != std::string_view::npos )
return fromName( func.getName(), defaultInterpolation ); return fromName( name.substr( 0, functionStart ), defaultInterpolation );
}
return defaultInterpolation; return defaultInterpolation;
} }
static Interpolation fromName( const std::string& name,
const Interpolation& defaultInterpolation = Ease::Linear ) {
return fromName( std::string_view{ name }, defaultInterpolation );
}
static Interpolation fromName( const char* name,
const Interpolation& defaultInterpolation = Ease::Linear ) {
return fromName( std::string_view{ name }, defaultInterpolation );
}
}; };
}} // namespace EE::Math }} // namespace EE::Math
+19 -1
View File
@@ -1,6 +1,7 @@
#ifndef EE_SCENEACTION_HPP #ifndef EE_SCENEACTION_HPP
#define EE_SCENEACTION_HPP #define EE_SCENEACTION_HPP
#include <cstddef>
#include <eepp/core.hpp> #include <eepp/core.hpp>
#include <eepp/system/time.hpp> #include <eepp/system/time.hpp>
using namespace EE::System; using namespace EE::System;
@@ -21,6 +22,18 @@ class EE_API Action {
virtual ~Action(); virtual ~Action();
/** Allocates actions from a shared size-class pool. Existing Action factories and ownership
* semantics are unchanged; completed slots are recycled by operator delete. */
static void* operator new( std::size_t size );
static void operator delete( void* ptr ) noexcept;
static void operator delete( void* ptr, std::size_t size ) noexcept;
static void* operator new( std::size_t, void* place ) noexcept { return place; }
static void operator delete( void*, void* ) noexcept {}
/** Starts the action. */ /** Starts the action. */
virtual void start() = 0; virtual void start() = 0;
@@ -91,7 +104,12 @@ class EE_API Action {
protected: protected:
friend class Node; friend class Node;
typedef UnorderedMap<ActionType, UnorderedMap<Uint32, ActionCallback>> ActionCallbackMap; struct ActionCallbackEntry {
ActionType type;
Uint32 id;
ActionCallback callback;
};
using ActionCallbackMap = SmallVector<ActionCallbackEntry, 2>;
Node* mNode{ nullptr }; Node* mNode{ nullptr };
Action::UniqueID mId{ 0 }; Action::UniqueID mId{ 0 };
+3
View File
@@ -43,6 +43,9 @@ class EE_API ActionManager {
std::vector<Action*> getActionsByTagFromTarget( Node* target, const Action::UniqueID& tag ); std::vector<Action*> getActionsByTagFromTarget( Node* target, const Action::UniqueID& tag );
void getActionsByTagFromTarget( Node* target, const Action::UniqueID& tag,
SmallVector<Action*, 4>& actions );
void update( const Time& time ); void update( const Time& time );
std::size_t count() const; std::size_t count() const;
+2 -2
View File
@@ -1,15 +1,15 @@
#ifndef EE_SCENE_ACTION_CALLBACK_HPP #ifndef EE_SCENE_ACTION_CALLBACK_HPP
#define EE_SCENE_ACTION_CALLBACK_HPP #define EE_SCENE_ACTION_CALLBACK_HPP
#include <eepp/core/small_function.hpp>
#include <eepp/scene/actions/delay.hpp> #include <eepp/scene/actions/delay.hpp>
#include <eepp/system/mutex.hpp> #include <eepp/system/mutex.hpp>
#include <functional>
namespace EE { namespace Scene { namespace Actions { namespace EE { namespace Scene { namespace Actions {
class EE_API Runnable : public Delay { class EE_API Runnable : public Delay {
public: public:
typedef std::function<void()> RunnableFunc; using RunnableFunc = SmallFunction<48>;
static Runnable* New( RunnableFunc callback, const Time& time = Seconds( 0 ), static Runnable* New( RunnableFunc callback, const Time& time = Seconds( 0 ),
bool loop = false ); bool loop = false );
+4 -1
View File
@@ -42,11 +42,14 @@ class EE_API Sequence : public Action {
virtual ~Sequence(); virtual ~Sequence();
protected: protected:
std::vector<Action*> mSequence; using ActionContainer = SmallVector<Action*, 4>;
ActionContainer mSequence;
Time mDuration; Time mDuration;
Uint32 mCurPos; Uint32 mCurPos;
Sequence( const std::vector<Action*> sequence ); Sequence( const std::vector<Action*> sequence );
Sequence( ActionContainer sequence );
}; };
}}} // namespace EE::Scene::Actions }}} // namespace EE::Scene::Actions
+4 -1
View File
@@ -42,10 +42,13 @@ class EE_API Spawn : public Action {
virtual ~Spawn(); virtual ~Spawn();
protected: protected:
std::vector<Action*> mSpawn; using ActionContainer = SmallVector<Action*, 4>;
ActionContainer mSpawn;
bool mAllDone; bool mAllDone;
Spawn( const std::vector<Action*> spawn ); Spawn( const std::vector<Action*> spawn );
Spawn( ActionContainer spawn );
}; };
}}} // namespace EE::Scene::Actions }}} // namespace EE::Scene::Actions
+3
View File
@@ -1346,6 +1346,9 @@ class EE_API Node : public Transformable {
*/ */
std::vector<Action*> getActionsByTag( const Action::UniqueID& tag ); std::vector<Action*> getActionsByTag( const Action::UniqueID& tag );
/** Appends matching actions to inline-capable storage without allocating in the common case. */
void getActionsByTag( const Action::UniqueID& tag, SmallVector<Action*, 4>& actions );
/** /**
* @brief Removes all actions from this node. * @brief Removes all actions from this node.
* *
+78 -1
View File
@@ -6,6 +6,7 @@
#include <eepp/core/string.hpp> #include <eepp/core/string.hpp>
#include <string> #include <string>
#include <utility>
namespace EE { namespace System { namespace EE { namespace System {
@@ -14,6 +15,82 @@ class EE_API FunctionString {
using Parameters = SmallVector<std::string, 4>; using Parameters = SmallVector<std::string, 4>;
using TypeStringVector = SmallVector<bool, 4>; using TypeStringVector = SmallVector<bool, 4>;
class View {
public:
View() = default;
std::string_view getName() const { return mName; }
bool isEmpty() const { return mName.empty(); }
template <typename Callback> bool forEachParameter( Callback&& callback ) const {
return FunctionString::forEachParameter( mParameters,
std::forward<Callback>( callback ) );
}
private:
friend class FunctionString;
View( std::string_view name, std::string_view parameters ) :
mName( name ), mParameters( parameters ) {}
std::string_view mName;
std::string_view mParameters;
};
static View parseView( std::string_view function );
/** Iterates a comma-separated parameter list without allocating. Nested parentheses and
* quoted strings protect commas. Quoted parameters are reported without their outer quotes;
* escaped quotes remain escaped because the returned views reference the original input.
* The callback receives (parameter, parameterWasString) and returns whether to continue. */
template <typename Callback>
static bool forEachParameter( std::string_view parameters, Callback&& callback ) {
std::size_t start = 0;
int parenthesisDepth = 0;
char quote = 0;
bool escaped = false;
auto emit = [&]( std::size_t end ) {
std::string_view parameter =
String::trim( parameters.substr( start, end - start ), " \t\n\r\f\v" );
bool wasString = parameter.size() >= 2 &&
( parameter.front() == '\'' || parameter.front() == '"' ) &&
parameter.back() == parameter.front();
if ( wasString )
parameter = parameter.substr( 1, parameter.size() - 2 );
if ( parameter.empty() && !wasString )
return true;
return callback( parameter, wasString );
};
for ( std::size_t i = 0; i < parameters.size(); ++i ) {
const char character = parameters[i];
if ( quote ) {
if ( escaped ) {
escaped = false;
} else if ( character == '\\' ) {
escaped = true;
} else if ( character == quote ) {
quote = 0;
}
continue;
}
if ( character == '\'' || character == '"' ) {
quote = character;
} else if ( character == '(' ) {
++parenthesisDepth;
} else if ( character == ')' && parenthesisDepth > 0 ) {
--parenthesisDepth;
} else if ( character == ',' && parenthesisDepth == 0 ) {
if ( !emit( i ) )
return false;
start = i + 1;
}
}
return emit( parameters.size() );
}
static FunctionString parse( const std::string& function ); static FunctionString parse( const std::string& function );
static FunctionString parse( std::string_view function ); static FunctionString parse( std::string_view function );
@@ -41,7 +118,7 @@ class EE_API FunctionString {
Parameters parameters; Parameters parameters;
TypeStringVector typeStringData; TypeStringVector typeStringData;
template <typename StringType> static FunctionString parse( StringType function ); static FunctionString parseOwned( std::string_view function );
}; };
}} // namespace EE::System }} // namespace EE::System
+11 -2
View File
@@ -4,6 +4,7 @@
#include <eepp/math/ease.hpp> #include <eepp/math/ease.hpp>
#include <eepp/system/time.hpp> #include <eepp/system/time.hpp>
#include <eepp/ui/css/stylesheetproperty.hpp> #include <eepp/ui/css/stylesheetproperty.hpp>
#include <eepp/ui/css/timingfunction.hpp>
#include <vector> #include <vector>
using namespace EE::Math; using namespace EE::Math;
@@ -56,8 +57,12 @@ class EE_API AnimationDefinition {
const Ease::Interpolation& getTimingFunction() const; const Ease::Interpolation& getTimingFunction() const;
/** Kept for source compatibility. Internal animation code uses the inline view below so this
* legacy vector is only materialized when an external caller requests it. */
const std::vector<double>& getTimingFunctionParameters() const; const std::vector<double>& getTimingFunctionParameters() const;
const TimingFunction::Parameters& getTimingFunctionParametersInline() const;
const AnimationFillMode& getFillMode() const; const AnimationFillMode& getFillMode() const;
void setName( const std::string& value ); void setName( const std::string& value );
@@ -72,6 +77,8 @@ class EE_API AnimationDefinition {
void setTimingFunctionParameters( const std::vector<double>& timingFunctionParameters ); void setTimingFunctionParameters( const std::vector<double>& timingFunctionParameters );
void setTimingFunctionParameters( const TimingFunction::Parameters& timingFunctionParameters );
void setDirection( const AnimationDirection& value ); void setDirection( const AnimationDirection& value );
void setFillMode( const AnimationFillMode& value ); void setFillMode( const AnimationFillMode& value );
@@ -87,7 +94,9 @@ class EE_API AnimationDefinition {
Time mDuration = Time::Zero; Time mDuration = Time::Zero;
Int32 mIterations = 1; /* -1 == "infinite" */ Int32 mIterations = 1; /* -1 == "infinite" */
Ease::Interpolation mTimingFunction = Ease::Interpolation::Linear; Ease::Interpolation mTimingFunction = Ease::Interpolation::Linear;
std::vector<double> mTimingFunctionParameters{}; TimingFunction::Parameters mTimingFunctionParameters{};
mutable std::vector<double> mTimingFunctionParametersVector;
mutable bool mTimingFunctionParametersVectorValid{ false };
AnimationDirection mDirection = Normal; AnimationDirection mDirection = Normal;
AnimationFillMode mFillMode = None; AnimationFillMode mFillMode = None;
bool mPaused = false; bool mPaused = false;
@@ -98,7 +107,7 @@ inline bool operator==( const AnimationDefinition& a, const AnimationDefinition&
a.getDelay() == b.getDelay() && a.getDirection() == b.getDirection() && a.getDelay() == b.getDelay() && a.getDirection() == b.getDirection() &&
a.isPaused() == b.isPaused() && a.getIterations() == b.getIterations() && a.isPaused() == b.isPaused() && a.getIterations() == b.getIterations() &&
a.getName() == b.getName() && a.getName() == b.getName() &&
a.getTimingFunctionParameters() == b.getTimingFunctionParameters(); a.getTimingFunctionParametersInline() == b.getTimingFunctionParametersInline();
} }
inline bool operator!=( const AnimationDefinition& a, const AnimationDefinition& b ) { inline bool operator!=( const AnimationDefinition& a, const AnimationDefinition& b ) {
@@ -5,6 +5,7 @@
#include <eepp/ui/css/propertyidset.hpp> #include <eepp/ui/css/propertyidset.hpp>
#include <eepp/ui/css/stylesheetproperty.hpp> #include <eepp/ui/css/stylesheetproperty.hpp>
#include <eepp/ui/css/stylesheetstyle.hpp> #include <eepp/ui/css/stylesheetstyle.hpp>
#include <eepp/ui/css/transitiondefinition.hpp>
namespace EE { namespace UI { namespace CSS { namespace EE { namespace UI { namespace CSS {
@@ -22,6 +23,8 @@ class EE_API ElementDefinition : NonCopyable {
const std::vector<const CSS::StyleSheetProperty*>& getTransitionProperties() const; const std::vector<const CSS::StyleSheetProperty*>& getTransitionProperties() const;
const ComputedTransitions& getTransitions() const;
const std::vector<const CSS::StyleSheetProperty*>& getAnimationProperties() const; const std::vector<const CSS::StyleSheetProperty*>& getAnimationProperties() const;
const StyleSheetVariables& getVariables() const; const StyleSheetVariables& getVariables() const;
@@ -38,6 +41,7 @@ class EE_API ElementDefinition : NonCopyable {
StyleSheetVariables mVariables; StyleSheetVariables mVariables;
PropertyIdSet mPropertyIds; PropertyIdSet mPropertyIds;
std::vector<const CSS::StyleSheetProperty*> mTransitionProperties; std::vector<const CSS::StyleSheetProperty*> mTransitionProperties;
ComputedTransitions mTransitions;
std::vector<const CSS::StyleSheetProperty*> mAnimationProperties; std::vector<const CSS::StyleSheetProperty*> mAnimationProperties;
bool mStructurallyVolatile; bool mStructurallyVolatile;
+10 -1
View File
@@ -58,12 +58,21 @@ class EE_API StyleSheetLength {
static StyleSheetLength fromString( const std::string& str, const Float& defaultValue = 0, static StyleSheetLength fromString( const std::string& str, const Float& defaultValue = 0,
bool pxAsDp = false ); bool pxAsDp = false );
static StyleSheetLength fromString( std::string_view str, const Float& defaultValue = 0,
bool pxAsDp = false );
static StyleSheetLength fromString( const char* str, const Float& defaultValue = 0,
bool pxAsDp = false ) {
return fromString( std::string_view{ str }, defaultValue, pxAsDp );
}
StyleSheetLength(); StyleSheetLength();
StyleSheetLength( const Float& val, const Unit& unit ); StyleSheetLength( const Float& val, const Unit& unit );
StyleSheetLength( const std::string& val, const Float& defaultValue = 0 ); StyleSheetLength( const std::string& val, const Float& defaultValue = 0 );
StyleSheetLength( std::string_view val, const Float& defaultValue = 0 );
StyleSheetLength( const char* val, const Float& defaultValue = 0 ) :
StyleSheetLength( std::string_view{ val }, defaultValue ) {}
StyleSheetLength( const StyleSheetLength& val ); StyleSheetLength( const StyleSheetLength& val );
@@ -98,7 +107,7 @@ class EE_API StyleSheetLength {
protected: protected:
static bool isFunctionString( std::string_view str ); static bool isFunctionString( std::string_view str );
static bool parseFunction( const std::string& str, Unit& outUnit, Arguments& outArgs ); static bool parseFunction( std::string_view str, Unit& outUnit, Arguments& outArgs );
Float resolveFunction( const Float& parentSize, const Sizef& viewSize, const Float& displayDpi, Float resolveFunction( const Float& parentSize, const Sizef& viewSize, const Float& displayDpi,
const Float& elFontSize, const Float& globalFontSize, const Float& elFontSize, const Float& globalFontSize,
@@ -26,7 +26,7 @@ class EE_API StyleSheetPropertyAnimation : public Action {
const PropertyDefinition* property, const std::string& startValue, const PropertyDefinition* property, const std::string& startValue,
const std::string& endValue, const std::string& endValue,
const Ease::Interpolation& timingFunction, const Ease::Interpolation& timingFunction,
const std::vector<double> timingFunctionParameters, const TimingFunction::Parameters& timingFunctionParameters,
const Uint32& propertyIndex, const bool& isDone ); const Uint32& propertyIndex, const bool& isDone );
static StyleSheetPropertyAnimation* fromAnimationKeyframes( static StyleSheetPropertyAnimation* fromAnimationKeyframes(
@@ -48,6 +48,13 @@ class EE_API StyleSheetPropertyAnimation : public Action {
const std::vector<double>& timingFunctionParameters, const std::vector<double>& timingFunctionParameters,
const AnimationOrigin& animationOrigin ); const AnimationOrigin& animationOrigin );
static StyleSheetPropertyAnimation*
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 TimingFunction::Parameters& timingFunctionParameters,
const AnimationOrigin& animationOrigin );
void start() override; void start() override;
void stop() override; void stop() override;
@@ -87,8 +94,8 @@ class EE_API StyleSheetPropertyAnimation : public Action {
protected: protected:
AnimationDefinition mAnimation; AnimationDefinition mAnimation;
const PropertyDefinition* mPropertyDef; const PropertyDefinition* mPropertyDef;
std::vector<std::string> mStates; SmallVector<std::string, 4> mStates;
std::vector<Float> mAnimationStepsTime; SmallVector<Float, 4> mAnimationStepsTime;
Time mRealElapsed; Time mRealElapsed;
Time mElapsed; Time mElapsed;
Int32 mPendingIterations; Int32 mPendingIterations;
@@ -103,6 +110,13 @@ class EE_API StyleSheetPropertyAnimation : public Action {
std::vector<Float> animationStepsTime, const Uint32& propertyIndex, std::vector<Float> animationStepsTime, const Uint32& propertyIndex,
const AnimationOrigin& animationOrigin ); const AnimationOrigin& animationOrigin );
StyleSheetPropertyAnimation( const AnimationDefinition& animation,
const PropertyDefinition* propertyDef,
SmallVector<std::string, 4> states,
SmallVector<Float, 4> animationStepsTime,
const Uint32& propertyIndex,
const AnimationOrigin& animationOrigin );
void onStart() override; void onStart() override;
void onUpdate( const Time& time ) override; void onUpdate( const Time& time ) override;
+10 -2
View File
@@ -1,20 +1,28 @@
#ifndef EE_UI_CSS_TIMINGFUNCTION_HPP #ifndef EE_UI_CSS_TIMINGFUNCTION_HPP
#define EE_UI_CSS_TIMINGFUNCTION_HPP #define EE_UI_CSS_TIMINGFUNCTION_HPP
#include <eepp/core/small_vector.hpp>
#include <eepp/core/string.hpp> #include <eepp/core/string.hpp>
#include <eepp/math/ease.hpp> #include <eepp/math/ease.hpp>
using namespace EE;
using namespace EE::Math; using namespace EE::Math;
namespace EE { namespace UI { namespace CSS { namespace EE { namespace UI { namespace CSS {
class EE_API TimingFunction { class EE_API TimingFunction {
public: public:
using Parameters = SmallVector<double, 4>;
static TimingFunction parse( std::string timingFunction ); static TimingFunction parse( std::string timingFunction );
static TimingFunction parse( std::string_view timingFunction );
static TimingFunction parse( const char* timingFunction ) {
return parse( std::string_view{ timingFunction } );
}
Ease::Interpolation interpolation{ Ease::None }; Ease::Interpolation interpolation{ Ease::None };
std::vector<double> parameters; Parameters parameters;
}; };
}}} // namespace EE::UI::CSS }}} // namespace EE::UI::CSS
@@ -1,9 +1,11 @@
#ifndef EE_UI_CSS_TRANSITIONDEFINITION_HPP #ifndef EE_UI_CSS_TRANSITIONDEFINITION_HPP
#define EE_UI_CSS_TRANSITIONDEFINITION_HPP #define EE_UI_CSS_TRANSITIONDEFINITION_HPP
#include <eepp/core/small_vector.hpp>
#include <eepp/math/ease.hpp> #include <eepp/math/ease.hpp>
#include <eepp/system/time.hpp> #include <eepp/system/time.hpp>
#include <eepp/ui/css/stylesheetproperty.hpp> #include <eepp/ui/css/stylesheetproperty.hpp>
#include <eepp/ui/css/timingfunction.hpp>
#include <vector> #include <vector>
using namespace EE::Math; using namespace EE::Math;
@@ -37,6 +39,33 @@ class EE_API TransitionDefinition {
typedef UnorderedMap<std::string, TransitionDefinition> TransitionsMap; typedef UnorderedMap<std::string, TransitionDefinition> TransitionsMap;
/** Compact transition data used by computed element definitions. It owns no strings and keeps
* the complete CSS timing-function payload inline. */
struct ComputedTransitionDefinition {
String::HashType propertyNameHash{ 0 };
Ease::Interpolation timingFunction{ Ease::Linear };
TimingFunction::Parameters timingFunctionParameters;
Time delay{ Time::Zero };
Time duration{ Time::Zero };
};
class EE_API ComputedTransitions {
public:
static ComputedTransitions
parse( const std::vector<const StyleSheetProperty*>& styleSheetProperties );
const ComputedTransitionDefinition* get( String::HashType propertyNameHash ) const;
bool empty() const { return mTransitions.empty(); }
std::size_t size() const { return mTransitions.size(); }
private:
SmallVector<ComputedTransitionDefinition, 4> mTransitions;
void set( const ComputedTransitionDefinition& transition );
};
}}} // namespace EE::UI::CSS }}} // namespace EE::UI::CSS
#endif #endif
+1 -1
View File
@@ -96,7 +96,7 @@ class EE_API UINodeDrawable : public Drawable {
Sizef calcDrawableSize( const std::string& drawableSizeEq ); Sizef calcDrawableSize( const std::string& drawableSizeEq );
Vector2f calcPosition( std::string positionXEq, std::string positionYEq ); Vector2f calcPosition( const std::string& positionXEq, const std::string& positionYEq );
const std::string& getPositionX() const; const std::string& getPositionX() const;
-1
View File
@@ -100,7 +100,6 @@ class EE_API UIStyle : public UIState {
std::shared_ptr<CSS::StyleSheetStyle> mElementStyle; std::shared_ptr<CSS::StyleSheetStyle> mElementStyle;
std::shared_ptr<CSS::ElementDefinition> mGlobalDefinition; std::shared_ptr<CSS::ElementDefinition> mGlobalDefinition;
std::shared_ptr<CSS::ElementDefinition> mDefinition; std::shared_ptr<CSS::ElementDefinition> mDefinition;
CSS::TransitionsMap mTransitions;
CSS::AnimationsMap mAnimations; CSS::AnimationsMap mAnimations;
UnorderedSet<UIWidget*> mRelatedWidgets; UnorderedSet<UIWidget*> mRelatedWidgets;
UnorderedSet<UIWidget*> mSubscribedWidgets; UnorderedSet<UIWidget*> mSubscribedWidgets;
+8 -8
View File
@@ -18,7 +18,7 @@ using namespace EE::Window;
namespace EE { namespace EE {
AllocatedPointer::AllocatedPointer( void* data, const std::string& file, int line, size_t memory, AllocatedPointer::AllocatedPointer( void* data, const char* file, int line, size_t memory,
bool track, bool globalAllocation ) { bool track, bool globalAllocation ) {
mData = data; mData = data;
mFile = file; mFile = file;
@@ -75,7 +75,7 @@ void* MemoryManager::addPointerInPlace( void* place, const AllocatedPointer& aAl
AllocatedPointerMapIt it = state.pointers.find( place ); AllocatedPointerMapIt it = state.pointers.find( place );
if ( it != state.pointers.end() ) { if ( it != state.pointers.end() ) {
removePointer( place, aAllocatedPointer.mFile.c_str(), aAllocatedPointer.mLine ); removePointer( place, aAllocatedPointer.mFile, aAllocatedPointer.mLine );
} }
return addPointer( aAllocatedPointer ); return addPointer( aAllocatedPointer );
@@ -110,7 +110,7 @@ void* MemoryManager::addPointer( const AllocatedPointer& aAllocatedPointer ) {
if ( aAllocatedPointer.mTrack ) if ( aAllocatedPointer.mTrack )
eePRINTL( "Allocating pointer %p at '%s' %d", aAllocatedPointer.mData, eePRINTL( "Allocating pointer %p at '%s' %d", aAllocatedPointer.mData,
aAllocatedPointer.mFile.c_str(), aAllocatedPointer.mLine ); aAllocatedPointer.mFile, aAllocatedPointer.mLine );
return aAllocatedPointer.mData; return aAllocatedPointer.mData;
} }
@@ -123,7 +123,7 @@ void* MemoryManager::reallocPointer( void* data, const AllocatedPointer& aAlloca
AllocatedPointerMapIt it = state.pointers.find( data ); AllocatedPointerMapIt it = state.pointers.find( data );
if ( it != state.pointers.end() && it->second.mTrack ) if ( it != state.pointers.end() && it->second.mTrack )
eePRINTL( "Realloc pointer %p at '%s' %d", data, aAllocatedPointer.mFile.c_str(), eePRINTL( "Realloc pointer %p at '%s' %d", data, aAllocatedPointer.mFile,
aAllocatedPointer.mLine ); aAllocatedPointer.mLine );
if ( it == state.pointers.end() ) if ( it == state.pointers.end() )
@@ -131,10 +131,10 @@ void* MemoryManager::reallocPointer( void* data, const AllocatedPointer& aAlloca
if ( aAllocatedPointer.mTrack ) if ( aAllocatedPointer.mTrack )
eePRINTL( "Reallocating pointer %p at '%s' %d", aAllocatedPointer.mData, eePRINTL( "Reallocating pointer %p at '%s' %d", aAllocatedPointer.mData,
aAllocatedPointer.mFile.c_str(), aAllocatedPointer.mLine ); aAllocatedPointer.mFile, aAllocatedPointer.mLine );
if ( it->first != aAllocatedPointer.mData ) { if ( it->first != aAllocatedPointer.mData ) {
removePointer( data, aAllocatedPointer.mFile.c_str(), aAllocatedPointer.mLine ); removePointer( data, aAllocatedPointer.mFile, aAllocatedPointer.mLine );
addPointer( aAllocatedPointer ); addPointer( aAllocatedPointer );
} else { } else {
state.totalMemoryUsage -= it->second.mMemory; state.totalMemoryUsage -= it->second.mMemory;
@@ -310,7 +310,7 @@ void MemoryManager::showResults() {
char address[2 + sizeof( void* ) * 2 + 1]; char address[2 + sizeof( void* ) * 2 + 1];
snprintf( address, sizeof( address ), "%p", ap.mData ); snprintf( address, sizeof( address ), "%p", ap.mData );
leaks.add_row( { address, ap.mFile, std::to_string( ap.mLine ), leaks.add_row( { address, std::string{ ap.mFile }, std::to_string( ap.mLine ),
FileSystem::sizeToString( static_cast<Int64>( ap.mMemory ) ) } ); FileSystem::sizeToString( static_cast<Int64>( ap.mMemory ) ) } );
} }
@@ -320,7 +320,7 @@ void MemoryManager::showResults() {
auto allocationLocation = []( const AllocatedPointer& allocation ) { auto allocationLocation = []( const AllocatedPointer& allocation ) {
return allocation.mData == nullptr return allocation.mData == nullptr
? std::string( "-" ) ? std::string( "-" )
: allocation.mFile + ":" + std::to_string( allocation.mLine ); : std::string{ allocation.mFile } + ":" + std::to_string( allocation.mLine );
}; };
tabulate::Table summary; tabulate::Table summary;
+142 -11
View File
@@ -1,8 +1,138 @@
#include <array>
#include <cstdlib>
#include <eepp/scene/action.hpp> #include <eepp/scene/action.hpp>
#include <eepp/scene/node.hpp> #include <eepp/scene/node.hpp>
#include <mutex>
#include <new>
namespace EE { namespace Scene { namespace EE { namespace Scene {
namespace {
class ActionPool {
public:
static ActionPool& instance();
void* allocate( std::size_t size ) {
const std::size_t classIndex = findClass( size );
if ( classIndex == NumClasses )
return allocateLarge( size );
std::lock_guard<std::mutex> lock( mMutex );
SizeClass& sizeClass = mClasses[classIndex];
if ( nullptr == sizeClass.freeList )
allocatePage( classIndex );
BlockHeader* block = sizeClass.freeList;
sizeClass.freeList = block->next;
block->classIndex = static_cast<Uint16>( classIndex );
return block + 1;
}
void deallocate( void* ptr ) noexcept {
if ( nullptr == ptr )
return;
BlockHeader* block = static_cast<BlockHeader*>( ptr ) - 1;
if ( block->classIndex == LargeAllocation ) {
std::free( block );
return;
}
std::lock_guard<std::mutex> lock( mMutex );
SizeClass& sizeClass = mClasses[block->classIndex];
block->next = sizeClass.freeList;
sizeClass.freeList = block;
}
private:
static constexpr std::array<std::size_t, 9> ClassSizes = { 64, 96, 128, 192, 256,
384, 512, 768, 1024 };
static constexpr std::size_t NumClasses = ClassSizes.size();
static constexpr Uint16 LargeAllocation = static_cast<Uint16>( -1 );
static constexpr std::size_t BlocksPerPage = 64;
struct alignas( std::max_align_t ) BlockHeader {
BlockHeader* next;
Uint16 classIndex;
};
struct Page {
Page* next;
};
struct SizeClass {
BlockHeader* freeList{ nullptr };
Page* pages{ nullptr };
};
std::array<SizeClass, NumClasses> mClasses;
std::mutex mMutex;
static constexpr std::size_t alignUp( std::size_t size ) {
return ( size + alignof( std::max_align_t ) - 1 ) & ~( alignof( std::max_align_t ) - 1 );
}
static std::size_t findClass( std::size_t size ) {
for ( std::size_t i = 0; i < NumClasses; ++i ) {
if ( size <= ClassSizes[i] )
return i;
}
return NumClasses;
}
void allocatePage( std::size_t classIndex ) {
SizeClass& sizeClass = mClasses[classIndex];
const std::size_t stride = alignUp( sizeof( BlockHeader ) + ClassSizes[classIndex] );
const std::size_t bytes = alignUp( sizeof( Page ) ) + stride * BlocksPerPage;
Page* page = static_cast<Page*>( std::malloc( bytes ) );
if ( nullptr == page )
throw std::bad_alloc();
page->next = sizeClass.pages;
sizeClass.pages = page;
char* storage = reinterpret_cast<char*>( page ) + alignUp( sizeof( Page ) );
for ( std::size_t i = 0; i < BlocksPerPage; ++i ) {
BlockHeader* block = reinterpret_cast<BlockHeader*>( storage + stride * i );
block->classIndex = static_cast<Uint16>( classIndex );
block->next = sizeClass.freeList;
sizeClass.freeList = block;
}
}
static void* allocateLarge( std::size_t size ) {
BlockHeader* block =
static_cast<BlockHeader*>( std::malloc( sizeof( BlockHeader ) + size ) );
if ( nullptr == block )
throw std::bad_alloc();
block->classIndex = LargeAllocation;
block->next = nullptr;
return block + 1;
}
};
ActionPool& ActionPool::instance() {
// Actions can be owned by globals initialized before the first action allocation. Keeping the
// pool alive until process exit avoids a static-destruction-order race with those owners.
alignas( ActionPool ) static unsigned char storage[sizeof( ActionPool )];
static ActionPool* pool = new ( storage ) ActionPool;
return *pool;
}
} // namespace
void* Action::operator new( std::size_t size ) {
return ActionPool::instance().allocate( size );
}
void Action::operator delete( void* ptr ) noexcept {
ActionPool::instance().deallocate( ptr );
}
void Action::operator delete( void* ptr, std::size_t ) noexcept {
ActionPool::instance().deallocate( ptr );
}
Action::~Action() { Action::~Action() {
sendEvent( ActionType::OnDelete ); sendEvent( ActionType::OnDelete );
} }
@@ -53,7 +183,7 @@ Action* Action::reverse() const {
Uint32 Action::addEventListener( const ActionType& actionType, const ActionCallback& callback ) { Uint32 Action::addEventListener( const ActionType& actionType, const ActionCallback& callback ) {
mNumCallBacks++; mNumCallBacks++;
mCallbacks[actionType][mNumCallBacks] = callback; mCallbacks.emplace_back( ActionCallbackEntry{ actionType, mNumCallBacks, callback } );
return mNumCallBacks; return mNumCallBacks;
} }
@@ -65,22 +195,23 @@ Action* Action::on( const Action::ActionType& actionType, const Action::ActionCa
void Action::removeEventListener( const Uint32& callbackId ) { void Action::removeEventListener( const Uint32& callbackId ) {
for ( auto it = mCallbacks.begin(); it != mCallbacks.end(); ++it ) { for ( auto it = mCallbacks.begin(); it != mCallbacks.end(); ++it ) {
UnorderedMap<Uint32, ActionCallback>& event = it->second; if ( it->id == callbackId ) {
mCallbacks.erase( it );
if ( event.erase( callbackId ) )
break; break;
} }
} }
}
void Action::sendEvent( const ActionType& actionType ) { void Action::sendEvent( const ActionType& actionType ) {
if ( 0 != mCallbacks.count( actionType ) ) { // Callbacks are allowed to mutate listener registration. Preserve the previous snapshot
auto event = mCallbacks[actionType]; // semantics while keeping the common callback count in inline storage.
SmallVector<ActionCallback, 4> callbacks;
if ( !event.empty() ) { for ( const auto& callback : mCallbacks ) {
for ( auto it = event.begin(); it != event.end(); ++it ) if ( callback.type == actionType )
it->second( this, actionType ); callbacks.emplace_back( callback.callback );
}
} }
for ( auto& callback : callbacks )
callback( this, actionType );
} }
void Action::onStart() {} void Action::onStart() {}
+11 -2
View File
@@ -81,12 +81,21 @@ std::vector<Action*> ActionManager::getActionsByTagFromTarget( Node* target,
return actions; return actions;
} }
void ActionManager::getActionsByTagFromTarget( Node* target, const Action::UniqueID& tag,
SmallVector<Action*, 4>& actions ) {
Lock l( mMutex );
for ( Action* action : mActions ) {
if ( action->getTarget() == target && action->getTag() == tag )
actions.emplace_back( action );
}
}
bool ActionManager::removeActionByTag( const Action::UniqueID& tag ) { bool ActionManager::removeActionByTag( const Action::UniqueID& tag ) {
return removeAction( getActionByTag( tag ) ); return removeAction( getActionByTag( tag ) );
} }
bool ActionManager::removeActionsByTagFromTarget( Node* target, const Action::UniqueID& tag ) { bool ActionManager::removeActionsByTagFromTarget( Node* target, const Action::UniqueID& tag ) {
std::vector<Action*> removeList; ActionList removeList;
{ {
Lock l( mMutex ); Lock l( mMutex );
@@ -226,7 +235,7 @@ bool ActionManager::removeActions( const std::vector<Action*>& actions ) {
} }
bool ActionManager::removeAllActionsFromTarget( Node* target ) { bool ActionManager::removeAllActionsFromTarget( Node* target ) {
std::vector<Action*> removeList; ActionList removeList;
{ {
Lock l( mMutex ); Lock l( mMutex );
+1 -1
View File
@@ -4,7 +4,7 @@
namespace EE { namespace Scene { namespace Actions { namespace EE { namespace Scene { namespace Actions {
Runnable* Runnable::New( RunnableFunc callback, const Time& time, bool loop ) { Runnable* Runnable::New( RunnableFunc callback, const Time& time, bool loop ) {
return eeNew( Runnable, ( callback, time, loop ) ); return eeNew( Runnable, ( std::move( callback ), time, loop ) );
} }
Runnable::Runnable( Runnable::RunnableFunc callback, const Time& time, bool loop ) : Runnable::Runnable( Runnable::RunnableFunc callback, const Time& time, bool loop ) :
+19 -12
View File
@@ -7,41 +7,45 @@ Sequence* Sequence::New( const std::vector<Action*> sequence ) {
} }
Sequence* Sequence::New( Action* action, Action* action2 ) { Sequence* Sequence::New( Action* action, Action* action2 ) {
return New( {action, action2} ); return eeNew( Sequence, ( ActionContainer{ action, action2 } ) );
} }
Sequence* Sequence::New( Action* action, Action* action2, Action* action3 ) { Sequence* Sequence::New( Action* action, Action* action2, Action* action3 ) {
return New( {action, action2, action3} ); return eeNew( Sequence, ( ActionContainer{ action, action2, action3 } ) );
} }
Sequence* Sequence::New( Action* action, Action* action2, Action* action3, Action* action4 ) { Sequence* Sequence::New( Action* action, Action* action2, Action* action3, Action* action4 ) {
return New( {action, action2, action3, action4} ); return eeNew( Sequence, ( ActionContainer{ action, action2, action3, action4 } ) );
} }
Sequence* Sequence::New( Action* action, Action* action2, Action* action3, Action* action4, Sequence* Sequence::New( Action* action, Action* action2, Action* action3, Action* action4,
Action* action5 ) { Action* action5 ) {
return New( {action, action2, action3, action4, action5} ); return eeNew( Sequence, ( ActionContainer{ action, action2, action3, action4, action5 } ) );
} }
Sequence* Sequence::New( Action* action, Action* action2, Action* action3, Action* action4, Sequence* Sequence::New( Action* action, Action* action2, Action* action3, Action* action4,
Action* action5, Action* action6 ) { Action* action5, Action* action6 ) {
return New( {action, action2, action3, action4, action5, action6} ); return eeNew( Sequence,
( ActionContainer{ action, action2, action3, action4, action5, action6 } ) );
} }
Sequence* Sequence::New( Action* action, Action* action2, Action* action3, Action* action4, Sequence* Sequence::New( Action* action, Action* action2, Action* action3, Action* action4,
Action* action5, Action* action6, Action* action7 ) { Action* action5, Action* action6, Action* action7 ) {
return New( {action, action2, action3, action4, action5, action6, action7} ); return eeNew( Sequence, ( ActionContainer{ action, action2, action3, action4, action5, action6,
action7 } ) );
} }
Sequence* Sequence::New( Action* action, Action* action2, Action* action3, Action* action4, Sequence* Sequence::New( Action* action, Action* action2, Action* action3, Action* action4,
Action* action5, Action* action6, Action* action7, Action* action8 ) { Action* action5, Action* action6, Action* action7, Action* action8 ) {
return New( {action, action2, action3, action4, action5, action6, action7, action8} ); return eeNew( Sequence, ( ActionContainer{ action, action2, action3, action4, action5, action6,
action7, action8 } ) );
} }
Sequence* Sequence::New( Action* action, Action* action2, Action* action3, Action* action4, Sequence* Sequence::New( Action* action, Action* action2, Action* action3, Action* action4,
Action* action5, Action* action6, Action* action7, Action* action8, Action* action5, Action* action6, Action* action7, Action* action8,
Action* action9 ) { Action* action9 ) {
return New( {action, action2, action3, action4, action5, action6, action7, action8, action9} ); return eeNew( Sequence, ( ActionContainer{ action, action2, action3, action4, action5, action6,
action7, action8, action9 } ) );
} }
void Sequence::start() { void Sequence::start() {
@@ -102,17 +106,17 @@ Time Sequence::getTotalTime() {
} }
Action* Sequence::clone() const { Action* Sequence::clone() const {
return Sequence::New( mSequence ); return eeNew( Sequence, ( mSequence ) );
} }
Action* Sequence::reverse() const { Action* Sequence::reverse() const {
std::vector<Action*> reversed; ActionContainer reversed;
for ( auto it = mSequence.rbegin(); it != mSequence.rend(); ++it ) { for ( auto it = mSequence.rbegin(); it != mSequence.rend(); ++it ) {
reversed.push_back( *it ); reversed.push_back( *it );
} }
return Sequence::New( reversed ); return eeNew( Sequence, ( std::move( reversed ) ) );
} }
Sequence::~Sequence() { Sequence::~Sequence() {
@@ -122,6 +126,9 @@ Sequence::~Sequence() {
} }
} }
Sequence::Sequence( const std::vector<Action*> sequence ) : mSequence( sequence ), mCurPos( 0 ) {} Sequence::Sequence( const std::vector<Action*> sequence ) :
mSequence( sequence.begin(), sequence.end() ), mCurPos( 0 ) {}
Sequence::Sequence( ActionContainer sequence ) : mSequence( std::move( sequence ) ), mCurPos( 0 ) {}
}}} // namespace EE::Scene::Actions }}} // namespace EE::Scene::Actions
+19 -12
View File
@@ -7,41 +7,45 @@ Spawn* Spawn::New( const std::vector<Action*> spawn ) {
} }
Spawn* Spawn::New( Action* action, Action* action2 ) { Spawn* Spawn::New( Action* action, Action* action2 ) {
return New( {action, action2} ); return eeNew( Spawn, ( ActionContainer{ action, action2 } ) );
} }
Spawn* Spawn::New( Action* action, Action* action2, Action* action3 ) { Spawn* Spawn::New( Action* action, Action* action2, Action* action3 ) {
return New( {action, action2, action3} ); return eeNew( Spawn, ( ActionContainer{ action, action2, action3 } ) );
} }
Spawn* Spawn::New( Action* action, Action* action2, Action* action3, Action* action4 ) { Spawn* Spawn::New( Action* action, Action* action2, Action* action3, Action* action4 ) {
return New( {action, action2, action3, action4} ); return eeNew( Spawn, ( ActionContainer{ action, action2, action3, action4 } ) );
} }
Spawn* Spawn::New( Action* action, Action* action2, Action* action3, Action* action4, Spawn* Spawn::New( Action* action, Action* action2, Action* action3, Action* action4,
Action* action5 ) { Action* action5 ) {
return New( {action, action2, action3, action4, action5} ); return eeNew( Spawn, ( ActionContainer{ action, action2, action3, action4, action5 } ) );
} }
Spawn* Spawn::New( Action* action, Action* action2, Action* action3, Action* action4, Spawn* Spawn::New( Action* action, Action* action2, Action* action3, Action* action4,
Action* action5, Action* action6 ) { Action* action5, Action* action6 ) {
return New( {action, action2, action3, action4, action5, action6} ); return eeNew( Spawn,
( ActionContainer{ action, action2, action3, action4, action5, action6 } ) );
} }
Spawn* Spawn::New( Action* action, Action* action2, Action* action3, Action* action4, Spawn* Spawn::New( Action* action, Action* action2, Action* action3, Action* action4,
Action* action5, Action* action6, Action* action7 ) { Action* action5, Action* action6, Action* action7 ) {
return New( {action, action2, action3, action4, action5, action6, action7} ); return eeNew( Spawn, ( ActionContainer{ action, action2, action3, action4, action5, action6,
action7 } ) );
} }
Spawn* Spawn::New( Action* action, Action* action2, Action* action3, Action* action4, Spawn* Spawn::New( Action* action, Action* action2, Action* action3, Action* action4,
Action* action5, Action* action6, Action* action7, Action* action8 ) { Action* action5, Action* action6, Action* action7, Action* action8 ) {
return New( {action, action2, action3, action4, action5, action6, action7, action8} ); return eeNew( Spawn, ( ActionContainer{ action, action2, action3, action4, action5, action6,
action7, action8 } ) );
} }
Spawn* Spawn::New( Action* action, Action* action2, Action* action3, Action* action4, Spawn* Spawn::New( Action* action, Action* action2, Action* action3, Action* action4,
Action* action5, Action* action6, Action* action7, Action* action8, Action* action5, Action* action6, Action* action7, Action* action8,
Action* action9 ) { Action* action9 ) {
return New( {action, action2, action3, action4, action5, action6, action7, action8, action9} ); return eeNew( Spawn, ( ActionContainer{ action, action2, action3, action4, action5, action6,
action7, action8, action9 } ) );
} }
void Spawn::start() { void Spawn::start() {
@@ -102,17 +106,17 @@ Time Spawn::getTotalTime() {
} }
Action* Spawn::clone() const { Action* Spawn::clone() const {
return Spawn::New( mSpawn ); return eeNew( Spawn, ( mSpawn ) );
} }
Action* Spawn::reverse() const { Action* Spawn::reverse() const {
std::vector<Action*> reversed; ActionContainer reversed;
for ( auto it = mSpawn.rbegin(); it != mSpawn.rend(); ++it ) { for ( auto it = mSpawn.rbegin(); it != mSpawn.rend(); ++it ) {
reversed.push_back( *it ); reversed.push_back( *it );
} }
return Spawn::New( reversed ); return eeNew( Spawn, ( std::move( reversed ) ) );
} }
Spawn::~Spawn() { Spawn::~Spawn() {
@@ -122,6 +126,9 @@ Spawn::~Spawn() {
} }
} }
Spawn::Spawn( const std::vector<Action*> spawn ) : mSpawn( spawn ), mAllDone( false ) {} Spawn::Spawn( const std::vector<Action*> spawn ) :
mSpawn( spawn.begin(), spawn.end() ), mAllDone( false ) {}
Spawn::Spawn( ActionContainer spawn ) : mSpawn( std::move( spawn ) ), mAllDone( false ) {}
}}} // namespace EE::Scene::Actions }}} // namespace EE::Scene::Actions
+4
View File
@@ -1526,6 +1526,10 @@ std::vector<Action*> Node::getActionsByTag( const Action::UniqueID& tag ) {
return getActionManager()->getActionsByTagFromTarget( this, tag ); return getActionManager()->getActionsByTagFromTarget( this, tag );
} }
void Node::getActionsByTag( const Action::UniqueID& tag, SmallVector<Action*, 4>& actions ) {
getActionManager()->getActionsByTagFromTarget( this, tag, actions );
}
void Node::clearActions() { void Node::clearActions() {
getActionManager()->removeAllActionsFromTarget( this ); getActionManager()->removeAllActionsFromTarget( this );
} }
+2 -8
View File
@@ -106,10 +106,7 @@ static bool tryParseModernRgbRgba( std::string_view paramsStr, Color& outColor )
} }
static bool parseHslHue( Float& out, std::string_view tok ) { static bool parseHslHue( Float& out, std::string_view tok ) {
while ( !tok.empty() && ( tok.front() == ' ' || tok.front() == '\t' ) ) tok = String::trim( tok, " \t" );
tok.remove_prefix( 1 );
while ( !tok.empty() && ( tok.back() == ' ' || tok.back() == '\t' ) )
tok.remove_suffix( 1 );
if ( tok.empty() ) if ( tok.empty() )
return false; return false;
if ( tok.size() == 4 && ( std::strncmp( tok.data(), "none", 4 ) == 0 || if ( tok.size() == 4 && ( std::strncmp( tok.data(), "none", 4 ) == 0 ||
@@ -133,10 +130,7 @@ static bool parseHslHue( Float& out, std::string_view tok ) {
} }
static bool parseHslPercentOrNumber( Float& out, std::string_view tok ) { static bool parseHslPercentOrNumber( Float& out, std::string_view tok ) {
while ( !tok.empty() && ( tok.front() == ' ' || tok.front() == '\t' ) ) tok = String::trim( tok, " \t" );
tok.remove_prefix( 1 );
while ( !tok.empty() && ( tok.back() == ' ' || tok.back() == '\t' ) )
tok.remove_suffix( 1 );
if ( tok.empty() ) if ( tok.empty() )
return false; return false;
if ( tok.size() == 4 && ( std::strncmp( tok.data(), "none", 4 ) == 0 || if ( tok.size() == 4 && ( std::strncmp( tok.data(), "none", 4 ) == 0 ||
+32 -104
View File
@@ -3,128 +3,56 @@
#include <eepp/system/functionstring.hpp> #include <eepp/system/functionstring.hpp>
#include <string> #include <string>
#include <type_traits>
namespace EE { namespace System { namespace EE { namespace System {
template <typename StringType> FunctionString FunctionString::parse( StringType function ) { FunctionString FunctionString::parseOwned( std::string_view function ) {
using CharType = typename StringType::value_type; View view = parseView( function );
if ( view.isEmpty() )
size_t funcSep = function.find( '(' ); return {};
if ( funcSep == StringType::npos )
return FunctionString( "", {}, {} );
auto funcName = String::trim( function.substr( 0, funcSep ) );
Parameters funcParameters; Parameters funcParameters;
TypeStringVector typeStringData; TypeStringVector typeStringData;
view.forEachParameter( [&]( std::string_view parameter, bool wasString ) {
auto parametersString = function.substr( funcSep + 1 ); std::string owned;
size_t paramClose = parametersString.find_last_of( ')' ); owned.reserve( parameter.size() );
if ( paramClose == StringType::npos ) const char quote = wasString ? function[parameter.data() - function.data() - 1] : 0;
return FunctionString( "", {}, {} ); for ( std::size_t i = 0; i < parameter.size(); ++i ) {
if ( wasString && parameter[i] == '\\' && i + 1 < parameter.size() &&
parametersString = parametersString.substr( 0, paramClose ); parameter[i + 1] == quote )
bool stateParsingString = false;
std::basic_string<CharType> buffer;
CharType prevChar = 0;
CharType quoteChar = 0;
bool currentParamIsString = false;
int parenDepth = 0;
auto pushBufferToParams = [&]() {
if constexpr ( std::is_same_v<CharType, char> ) {
if ( !currentParamIsString )
String::trimInPlace( buffer );
funcParameters.push_back( buffer );
} else {
std::string utf8Buffer = String( buffer ).toUtf8();
if ( !currentParamIsString )
String::trimInPlace( utf8Buffer );
funcParameters.push_back( utf8Buffer );
}
typeStringData.push_back( currentParamIsString );
buffer.clear();
currentParamIsString = false;
};
for ( size_t i = 0; i < parametersString.length(); ++i ) {
CharType c = parametersString[i];
if ( !stateParsingString ) {
if ( c == '(' ) {
parenDepth++;
buffer += c;
} else if ( c == ')' ) {
if ( parenDepth == 0 )
break;
if ( parenDepth > 0 )
parenDepth--;
buffer += c;
} else if ( c == ',' ) {
if ( parenDepth == 0 ) {
// Add param if we either accumulated characters or actively parsed an empty
// string parameter.
if ( !buffer.empty() || currentParamIsString ) {
pushBufferToParams();
}
} else {
buffer += c;
}
} else if ( c == '"' || c == '\'' ) {
stateParsingString = true;
quoteChar = c;
if ( parenDepth == 0 && buffer.empty() ) {
currentParamIsString = true;
} else {
buffer += c;
}
} else if ( c == ' ' ) {
if ( buffer.empty() || currentParamIsString )
continue; continue;
else owned += parameter[i];
buffer += c;
} else {
buffer += c;
}
} else {
if ( c == '\\' && i + 1 < parametersString.length() &&
parametersString[i + 1] == quoteChar ) {
} else if ( prevChar != '\\' && c == quoteChar ) {
stateParsingString = false;
if ( !currentParamIsString ) {
buffer += c;
}
} else {
buffer += c;
}
prevChar = c;
} }
funcParameters.emplace_back( std::move( owned ) );
typeStringData.emplace_back( wasString );
return true;
} );
return FunctionString( std::string{ view.getName() }, std::move( funcParameters ),
std::move( typeStringData ) );
} }
// Add param if we either accumulated characters or actively parsed an empty string parameter. FunctionString::View FunctionString::parseView( std::string_view function ) {
if ( !buffer.empty() || currentParamIsString ) const std::size_t open = function.find( '(' );
pushBufferToParams(); if ( open == std::string_view::npos )
return {};
if constexpr ( std::is_same_v<StringType, std::string> ) { const std::size_t close = function.rfind( ')' );
return FunctionString( std::string{ funcName }, funcParameters, typeStringData ); if ( close == std::string_view::npos || close < open )
} else { return {};
return FunctionString( String( funcName ).toUtf8(), funcParameters, typeStringData ); std::string_view name = String::trim( function.substr( 0, open ), " \t\n\r\f\v" );
} if ( name.empty() )
return {};
return View{ name, function.substr( open + 1, close - open - 1 ) };
} }
FunctionString FunctionString::parse( const std::string& function ) { FunctionString FunctionString::parse( const std::string& function ) {
return parse<std::string_view>( std::string_view{ function } ); return parseOwned( function );
} }
FunctionString FunctionString::parse( std::string_view function ) { FunctionString FunctionString::parse( std::string_view function ) {
return parse<std::string_view>( function ); return parseOwned( function );
} }
FunctionString FunctionString::parse( String::View function ) { FunctionString FunctionString::parse( String::View function ) {
return parse<String::View>( function ); return parseOwned( String( function ).toUtf8() );
} }
FunctionString::FunctionString( const std::string& name, const Parameters& parameters, FunctionString::FunctionString( const std::string& name, const Parameters& parameters,
+19 -1
View File
@@ -119,7 +119,8 @@ UnorderedMap<std::string, AnimationDefinition> AnimationDefinition::parseAnimati
case PropertyId::AnimationTimingFunction: { case PropertyId::AnimationTimingFunction: {
TimingFunction tf( TimingFunction::parse( val ) ); TimingFunction tf( TimingFunction::parse( val ) );
timingFunctions.emplace_back( tf.interpolation ); timingFunctions.emplace_back( tf.interpolation );
timingFunctionParameters.emplace_back( tf.parameters ); timingFunctionParameters.emplace_back( tf.parameters.begin(),
tf.parameters.end() );
break; break;
} }
default: default:
@@ -256,12 +257,29 @@ const String::HashType& AnimationDefinition::getId() const {
} }
const std::vector<double>& AnimationDefinition::getTimingFunctionParameters() const { const std::vector<double>& AnimationDefinition::getTimingFunctionParameters() const {
if ( !mTimingFunctionParametersVectorValid ) {
mTimingFunctionParametersVector.assign( mTimingFunctionParameters.begin(),
mTimingFunctionParameters.end() );
mTimingFunctionParametersVectorValid = true;
}
return mTimingFunctionParametersVector;
}
const TimingFunction::Parameters& AnimationDefinition::getTimingFunctionParametersInline() const {
return mTimingFunctionParameters; return mTimingFunctionParameters;
} }
void AnimationDefinition::setTimingFunctionParameters( void AnimationDefinition::setTimingFunctionParameters(
const std::vector<double>& timingFunctionParameters ) { const std::vector<double>& timingFunctionParameters ) {
mTimingFunctionParameters.assign( timingFunctionParameters.begin(),
timingFunctionParameters.end() );
mTimingFunctionParametersVectorValid = false;
}
void AnimationDefinition::setTimingFunctionParameters(
const TimingFunction::Parameters& timingFunctionParameters ) {
mTimingFunctionParameters = timingFunctionParameters; mTimingFunctionParameters = timingFunctionParameters;
mTimingFunctionParametersVectorValid = false;
} }
const AnimationDefinition::AnimationFillMode& AnimationDefinition::getFillMode() const { const AnimationDefinition::AnimationFillMode& AnimationDefinition::getFillMode() const {
+5
View File
@@ -34,6 +34,10 @@ const std::vector<const StyleSheetProperty*>& ElementDefinition::getTransitionPr
return mTransitionProperties; return mTransitionProperties;
} }
const ComputedTransitions& ElementDefinition::getTransitions() const {
return mTransitions;
}
const std::vector<const StyleSheetProperty*>& ElementDefinition::getAnimationProperties() const { const std::vector<const StyleSheetProperty*>& ElementDefinition::getAnimationProperties() const {
return mAnimationProperties; return mAnimationProperties;
} }
@@ -81,6 +85,7 @@ void ElementDefinition::refresh() {
} }
resolveVariables(); resolveVariables();
mTransitions = ComputedTransitions::parse( mTransitionProperties );
for ( auto& property : mProperties ) { for ( auto& property : mProperties ) {
// Only registered properties participate in the dense ID set. Unknown // Only registered properties participate in the dense ID set. Unknown
+11 -4
View File
@@ -232,6 +232,9 @@ StyleSheetLength::StyleSheetLength( const Float& val, const StyleSheetLength::Un
mUnit( unit ), mValue( val ) {} mUnit( unit ), mValue( val ) {}
StyleSheetLength::StyleSheetLength( const std::string& val, const Float& defaultValue ) : StyleSheetLength::StyleSheetLength( const std::string& val, const Float& defaultValue ) :
StyleSheetLength( fromString( std::string_view{ val }, defaultValue ) ) {}
StyleSheetLength::StyleSheetLength( std::string_view val, const Float& defaultValue ) :
StyleSheetLength( fromString( val, defaultValue ) ) {} StyleSheetLength( fromString( val, defaultValue ) ) {}
StyleSheetLength::StyleSheetLength( const StyleSheetLength& val ) { StyleSheetLength::StyleSheetLength( const StyleSheetLength& val ) {
@@ -382,9 +385,14 @@ StyleSheetLength& StyleSheetLength::operator=( const StyleSheetLength& val ) {
StyleSheetLength StyleSheetLength::fromString( const std::string& str, const Float& defaultValue, StyleSheetLength StyleSheetLength::fromString( const std::string& str, const Float& defaultValue,
bool pxAsDp ) { bool pxAsDp ) {
return fromString( std::string_view{ str }, defaultValue, pxAsDp );
}
StyleSheetLength StyleSheetLength::fromString( std::string_view str, const Float& defaultValue,
bool pxAsDp ) {
StyleSheetLength length; StyleSheetLength length;
length.setValue( defaultValue, Unit::Px ); length.setValue( defaultValue, Unit::Px );
const std::string_view value = String::trim( std::string_view( str ) ); const std::string_view value = String::trim( str );
if ( isFunctionString( value ) ) { if ( isFunctionString( value ) ) {
Unit funcUnit = Unit::Px; Unit funcUnit = Unit::Px;
@@ -470,9 +478,8 @@ bool StyleSheetLength::isFunctionString( std::string_view str ) {
String::istartsWith( str, "max(" ) || String::istartsWith( str, "calc(" ); String::istartsWith( str, "max(" ) || String::istartsWith( str, "calc(" );
} }
bool StyleSheetLength::parseFunction( const std::string& str, Unit& outUnit, Arguments& outArgs ) { bool StyleSheetLength::parseFunction( std::string_view str, Unit& outUnit, Arguments& outArgs ) {
std::string_view sv( str ); std::string_view trimmed = String::trim( str );
std::string_view trimmed = String::trim( sv );
if ( String::istartsWith( trimmed, "clamp(" ) ) { if ( String::istartsWith( trimmed, "clamp(" ) ) {
outUnit = Unit::Clamp; outUnit = Unit::Clamp;
+55 -21
View File
@@ -11,7 +11,7 @@ using namespace EE::Math::easing;
namespace EE { namespace UI { namespace CSS { namespace EE { namespace UI { namespace CSS {
inline Float easingFn( const Ease::Interpolation& timingFunction, inline Float easingFn( const Ease::Interpolation& timingFunction,
const std::vector<double>& timingFunctionParameters, const double& t, const TimingFunction::Parameters& timingFunctionParameters, const double& t,
const double& b, const double& c, const double& d ) { const double& b, const double& c, const double& d ) {
if ( timingFunction != Ease::Interpolation::CubizBezier ) if ( timingFunction != Ease::Interpolation::CubizBezier )
return easingCb[timingFunction]( t, b, c, d ); return easingCb[timingFunction]( t, b, c, d );
@@ -24,13 +24,12 @@ inline Float easingFn( const Ease::Interpolation& timingFunction,
return t; return t;
} }
void StyleSheetPropertyAnimation::tweenProperty( UIWidget* widget, const Float& normalizedProgress, void StyleSheetPropertyAnimation::tweenProperty(
const PropertyDefinition* property, UIWidget* widget, const Float& normalizedProgress, const PropertyDefinition* property,
const std::string& startValue, const std::string& startValue, const std::string& endValue,
const std::string& endValue,
const Ease::Interpolation& timingFunction, const Ease::Interpolation& timingFunction,
const std::vector<double> timingFunctionParameters, const TimingFunction::Parameters& timingFunctionParameters, const Uint32& propertyIndex,
const Uint32& propertyIndex, const bool& isDone ) { const bool& isDone ) {
switch ( property->getType() ) { switch ( property->getType() ) {
case PropertyType::NumberFloat: case PropertyType::NumberFloat:
case PropertyType::NumberInt: { case PropertyType::NumberInt: {
@@ -220,13 +219,27 @@ StyleSheetPropertyAnimation* StyleSheetPropertyAnimation::New(
const Uint32& propertyIndex, const Time& duration, const Time& delay, const Uint32& propertyIndex, const Time& duration, const Time& delay,
const Ease::Interpolation& timingFunction, const std::vector<double>& timingFunctionParameters, const Ease::Interpolation& timingFunction, const std::vector<double>& timingFunctionParameters,
const AnimationOrigin& animationOrigin ) { const AnimationOrigin& animationOrigin ) {
TimingFunction::Parameters parameters;
parameters.assign( timingFunctionParameters.begin(), timingFunctionParameters.end() );
return New( property, startValue, endValue, propertyIndex, duration, delay, timingFunction,
parameters, animationOrigin );
}
StyleSheetPropertyAnimation*
StyleSheetPropertyAnimation::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 TimingFunction::Parameters& timingFunctionParameters,
const AnimationOrigin& animationOrigin ) {
AnimationDefinition animation; AnimationDefinition animation;
animation.setDelay( delay ); animation.setDelay( delay );
animation.setDuration( duration ); animation.setDuration( duration );
animation.setTimingFunction( timingFunction ); animation.setTimingFunction( timingFunction );
animation.setTimingFunctionParameters( timingFunctionParameters ); animation.setTimingFunctionParameters( timingFunctionParameters );
return New( animation, property, { startValue, endValue }, { 0, 1 }, propertyIndex, return eeNew( StyleSheetPropertyAnimation,
animationOrigin ); ( animation, property, SmallVector<std::string, 4>{ startValue, endValue },
SmallVector<Float, 4>{ 0, 1 }, propertyIndex, animationOrigin ) );
} }
StyleSheetPropertyAnimation::StyleSheetPropertyAnimation( const AnimationDefinition& animation, StyleSheetPropertyAnimation::StyleSheetPropertyAnimation( const AnimationDefinition& animation,
@@ -237,8 +250,26 @@ StyleSheetPropertyAnimation::StyleSheetPropertyAnimation( const AnimationDefinit
const AnimationOrigin& animationOrigin ) : const AnimationOrigin& animationOrigin ) :
mAnimation( animation ), mAnimation( animation ),
mPropertyDef( propertyDef ), mPropertyDef( propertyDef ),
mStates( states ), mStates( std::make_move_iterator( states.begin() ), std::make_move_iterator( states.end() ) ),
mAnimationStepsTime( animationStepsTime ), mAnimationStepsTime( std::make_move_iterator( animationStepsTime.begin() ),
std::make_move_iterator( animationStepsTime.end() ) ),
mPendingIterations( animation.getIterations() ),
mPropertyIndex( propertyIndex ),
mAnimationOrigin( animationOrigin ),
mPaused( mAnimation.isPaused() ) {
mId = ID;
}
StyleSheetPropertyAnimation::StyleSheetPropertyAnimation( const AnimationDefinition& animation,
const PropertyDefinition* propertyDef,
SmallVector<std::string, 4> states,
SmallVector<Float, 4> animationStepsTime,
const Uint32& propertyIndex,
const AnimationOrigin& animationOrigin ) :
mAnimation( animation ),
mPropertyDef( propertyDef ),
mStates( std::move( states ) ),
mAnimationStepsTime( std::move( animationStepsTime ) ),
mPendingIterations( animation.getIterations() ), mPendingIterations( animation.getIterations() ),
mPropertyIndex( propertyIndex ), mPropertyIndex( propertyIndex ),
mAnimationOrigin( animationOrigin ), mAnimationOrigin( animationOrigin ),
@@ -321,15 +352,17 @@ Time StyleSheetPropertyAnimation::getTotalTime() {
} }
Action* StyleSheetPropertyAnimation::clone() const { Action* StyleSheetPropertyAnimation::clone() const {
return New( mAnimation, mPropertyDef, mStates, mAnimationStepsTime, mPropertyIndex, return eeNew( StyleSheetPropertyAnimation,
mAnimationOrigin ); ( mAnimation, mPropertyDef, mStates, mAnimationStepsTime, mPropertyIndex,
mAnimationOrigin ) );
} }
Action* StyleSheetPropertyAnimation::reverse() const { Action* StyleSheetPropertyAnimation::reverse() const {
std::vector<std::string> vcopy( mStates ); SmallVector<std::string, 4> vcopy( mStates );
std::reverse( vcopy.begin(), vcopy.end() ); std::reverse( vcopy.begin(), vcopy.end() );
return New( mAnimation, mPropertyDef, vcopy, mAnimationStepsTime, mPropertyIndex, return eeNew( StyleSheetPropertyAnimation,
mAnimationOrigin ); ( mAnimation, mPropertyDef, std::move( vcopy ), mAnimationStepsTime,
mPropertyIndex, mAnimationOrigin ) );
} }
const Uint32& StyleSheetPropertyAnimation::getPropertyIndex() const { const Uint32& StyleSheetPropertyAnimation::getPropertyIndex() const {
@@ -371,7 +404,8 @@ void StyleSheetPropertyAnimation::onUpdate( const Time& ) {
Float relativeProgress = curTime / relTime; Float relativeProgress = curTime / relTime;
tweenProperty( widget, relativeProgress, mPropertyDef, mStates[curPos - 1], tweenProperty( widget, relativeProgress, mPropertyDef, mStates[curPos - 1],
mStates[curPos], mAnimation.getTimingFunction(), mStates[curPos], mAnimation.getTimingFunction(),
mAnimation.getTimingFunctionParameters(), mPropertyIndex, isDone() ); mAnimation.getTimingFunctionParametersInline(), mPropertyIndex,
isDone() );
} }
} }
} }
@@ -480,16 +514,16 @@ void StyleSheetPropertyAnimation::prepareDirection() {
} }
void StyleSheetPropertyAnimation::reverseAnimation() { void StyleSheetPropertyAnimation::reverseAnimation() {
std::vector<std::string> reverseCopy( mStates ); SmallVector<std::string, 4> reverseCopy( mStates );
std::reverse( reverseCopy.begin(), reverseCopy.end() ); std::reverse( reverseCopy.begin(), reverseCopy.end() );
mStates = reverseCopy; mStates = std::move( reverseCopy );
std::vector<Float> reverseTimes( mAnimationStepsTime ); SmallVector<Float, 4> reverseTimes( mAnimationStepsTime );
std::reverse( reverseTimes.begin(), reverseTimes.end() ); std::reverse( reverseTimes.begin(), reverseTimes.end() );
for ( size_t i = 0; i < reverseTimes.size(); i++ ) { for ( size_t i = 0; i < reverseTimes.size(); i++ ) {
reverseTimes[i] = 1.f - reverseTimes[i]; reverseTimes[i] = 1.f - reverseTimes[i];
} }
mAnimationStepsTime = reverseTimes; mAnimationStepsTime = std::move( reverseTimes );
} }
}}} // namespace EE::UI::CSS }}} // namespace EE::UI::CSS
+34 -18
View File
@@ -1,29 +1,45 @@
#include <eepp/system/functionstring.hpp> #include <eepp/core/string.hpp>
#include <eepp/ui/css/timingfunction.hpp> #include <eepp/ui/css/timingfunction.hpp>
using namespace EE::System;
namespace EE { namespace UI { namespace CSS { namespace EE { namespace UI { namespace CSS {
TimingFunction TimingFunction::parse( std::string timingFunction ) { TimingFunction TimingFunction::parse( std::string timingFunction ) {
String::trimInPlace( timingFunction ); return parse( std::string_view{ timingFunction } );
String::toLowerInPlace( timingFunction );
if ( timingFunction.find_first_of( '(' ) == std::string::npos ) {
return { Ease::fromName( timingFunction ), {} };
} else {
TimingFunction tf;
FunctionString func( FunctionString::parse( timingFunction ) );
if ( !func.isEmpty() ) {
tf.interpolation = Ease::fromName( timingFunction, Ease::Interpolation::None );
const auto& params = func.getParameters();
for ( const auto& param : params ) {
double d;
if ( String::fromString( d, param ) )
tf.parameters.emplace_back( d );
} }
TimingFunction TimingFunction::parse( std::string_view timingFunction ) {
timingFunction = String::trim( timingFunction, " \t\n\r\f\v" );
SmallVector<char, 64> normalized;
for ( char character : timingFunction )
normalized.emplace_back( character >= 'A' && character <= 'Z' ? character + 32
: character );
timingFunction = std::string_view{ normalized.data(), normalized.size() };
TimingFunction result;
const auto open = timingFunction.find( '(' );
if ( open == std::string_view::npos ) {
result.interpolation = Ease::fromName( timingFunction, Ease::Interpolation::None );
return result;
} }
return tf; const auto close = timingFunction.rfind( ')' );
if ( close == std::string_view::npos || close <= open )
return result;
result.interpolation =
Ease::fromName( timingFunction.substr( 0, open ), Ease::Interpolation::None );
std::string_view parameters = timingFunction.substr( open + 1, close - open - 1 );
while ( !parameters.empty() && result.parameters.size() < 4 ) {
const auto comma = parameters.find( ',' );
std::string_view parameter = String::trim( parameters.substr( 0, comma ), " \t\n\r\f\v" );
double value = 0;
if ( String::fromString( value, parameter ) )
result.parameters.emplace_back( value );
if ( comma == std::string_view::npos )
break;
parameters.remove_prefix( comma + 1 );
} }
return result;
} }
}}} // namespace EE::UI::CSS }}} // namespace EE::UI::CSS
+228 -102
View File
@@ -1,3 +1,4 @@
#include <cctype>
#include <eepp/core/string.hpp> #include <eepp/core/string.hpp>
#include <eepp/system/functionstring.hpp> #include <eepp/system/functionstring.hpp>
#include <eepp/ui/css/propertydefinition.hpp> #include <eepp/ui/css/propertydefinition.hpp>
@@ -6,122 +7,247 @@
namespace EE { namespace UI { namespace CSS { namespace EE { namespace UI { namespace CSS {
namespace {
template <typename Callback> void forEachCommaItem( std::string_view value, Callback&& callback ) {
System::FunctionString::forEachParameter( value, [&]( std::string_view item, bool ) {
callback( item );
return true;
} );
}
SmallVector<std::string_view, 4> splitTransitionTokens( std::string_view value ) {
SmallVector<std::string_view, 4> tokens;
std::size_t start = 0;
int parenthesisDepth = 0;
for ( std::size_t i = 0; i <= value.size(); ++i ) {
if ( i < value.size() ) {
if ( value[i] == '(' )
++parenthesisDepth;
else if ( value[i] == ')' && parenthesisDepth > 0 )
--parenthesisDepth;
}
if ( i == value.size() ||
( std::isspace( static_cast<unsigned char>( value[i] ) ) && parenthesisDepth == 0 ) ) {
if ( i > start )
tokens.emplace_back( value.substr( start, i - start ) );
while ( i + 1 < value.size() &&
std::isspace( static_cast<unsigned char>( value[i + 1] ) ) )
++i;
start = i + 1;
}
}
return tokens;
}
Time parseTime( std::string_view value ) {
value = String::trim( value, " \t\n\r\f\v" );
auto lower = []( char character ) {
return character >= 'A' && character <= 'Z' ? character + ( 'a' - 'A' ) : character;
};
bool milliseconds = value.size() >= 2 && lower( value[value.size() - 2] ) == 'm' &&
lower( value.back() ) == 's';
bool seconds = !milliseconds && !value.empty() && lower( value.back() ) == 's';
bool minutes = !milliseconds && !seconds && !value.empty() && lower( value.back() ) == 'm';
std::size_t numberLength = value.size() - ( milliseconds ? 2 : ( seconds || minutes ? 1 : 0 ) );
double number = 0;
const char* numberStart = value.data();
if ( numberLength > 0 && *numberStart == '+' ) {
++numberStart;
--numberLength;
}
if ( !String::fromString( number, std::string_view{ numberStart, numberLength } ) )
return Time::Zero;
if ( milliseconds )
return Milliseconds( number );
if ( minutes )
return Minutes( number );
return Seconds( number );
}
String::HashType lowerHash( std::string_view value ) {
return String::hashToLower( value.data(), static_cast<Int64>( value.size() ) );
}
std::string lowerString( std::string_view value ) {
std::string result{ value };
String::toLowerInPlace( result );
return result;
}
} // namespace
UnorderedMap<std::string, TransitionDefinition> TransitionDefinition::parseTransitionProperties( UnorderedMap<std::string, TransitionDefinition> TransitionDefinition::parseTransitionProperties(
const std::vector<const StyleSheetProperty*>& styleSheetProperties ) { const std::vector<const StyleSheetProperty*>& styleSheetProperties ) {
std::vector<std::string> properties; SmallVector<std::string, 4> properties;
std::vector<Time> durations; SmallVector<Time, 4> durations;
std::vector<Time> delays; SmallVector<Time, 4> delays;
std::vector<Ease::Interpolation> timingFunctions; SmallVector<TimingFunction, 4> timingFunctions;
std::vector<std::vector<double>> timingFunctionParameters;
TransitionsMap transitions; TransitionsMap transitions;
for ( auto& prop : styleSheetProperties ) { for ( const StyleSheetProperty* property : styleSheetProperties ) {
if ( prop->getPropertyDefinition() == NULL ) if ( nullptr == property || nullptr == property->getPropertyDefinition() )
continue; continue;
const PropertyDefinition* propDef = prop->getPropertyDefinition(); switch ( property->getPropertyDefinition()->getPropertyId() ) {
case PropertyId::Transition:
if ( propDef->getPropertyId() == PropertyId::Transition ) { forEachCommaItem( property->getValue(), [&]( std::string_view item ) {
String::splitCb( auto tokens = splitTransitionTokens( item );
[&transitions, prop]( std::string_view str ) { if ( tokens.size() < 2 )
auto strTransition = String::trim( str ); return;
auto splitTransition = TransitionDefinition transition;
String::split( std::string{ strTransition }, " ", "", "()" ); transition.property = lowerString( tokens[0] );
transition.duration = parseTime( tokens[1] );
if ( !splitTransition.empty() ) { if ( tokens.size() >= 3 ) {
TransitionDefinition transitionDef; TimingFunction timing = TimingFunction::parse( tokens[2] );
transition.timingFunction = timing.interpolation;
if ( splitTransition.size() >= 2 ) { transition.timingFunctionParameters.assign( timing.parameters.begin(),
std::string property = String::trim( splitTransition[0] ); timing.parameters.end() );
String::toLowerInPlace( property ); if ( transition.timingFunction == Ease::None && tokens.size() == 3 )
transition.delay = parseTime( tokens[2] );
Time duration = else if ( tokens.size() >= 4 )
StyleSheetProperty( prop->getName(), transition.delay = parseTime( tokens[3] );
String::toLower( splitTransition[1] ) ) }
.asTime(); transitions[transition.property] = std::move( transition );
} );
transitionDef.property = property; break;
transitionDef.duration = duration; case PropertyId::TransitionDuration:
forEachCommaItem( property->getValue(), [&]( std::string_view item ) {
if ( splitTransition.size() >= 3 ) { durations.emplace_back( parseTime( item ) );
TimingFunction tf( TimingFunction::parse( splitTransition[2] ) ); } );
transitionDef.timingFunction = std::move( tf.interpolation ); break;
transitionDef.timingFunctionParameters = std::move( tf.parameters ); case PropertyId::TransitionDelay:
forEachCommaItem( property->getValue(), [&]( std::string_view item ) {
if ( transitionDef.timingFunction == Ease::None && delays.emplace_back( parseTime( item ) );
splitTransition.size() == 3 ) { } );
transitionDef.delay = break;
StyleSheetProperty( prop->getName(), case PropertyId::TransitionTimingFunction:
String::toLower( splitTransition[2] ) ) forEachCommaItem( property->getValue(), [&]( std::string_view item ) {
.asTime(); timingFunctions.emplace_back( TimingFunction::parse( item ) );
} else if ( splitTransition.size() >= 4 ) { } );
transitionDef.delay = break;
StyleSheetProperty( prop->getName(), case PropertyId::TransitionProperty:
String::toLower( splitTransition[3] ) ) forEachCommaItem( property->getValue(), [&]( std::string_view item ) {
.asTime(); properties.emplace_back( lowerString( item ) );
} );
break;
default:
break;
} }
} }
transitions[transitionDef.getProperty()] = transitionDef; for ( std::size_t i = 0; i < properties.size(); ++i ) {
} TransitionDefinition transition;
} transition.property = properties[i];
return true;
},
prop->getValue(), ",", ",", "()" );
} else if ( propDef->getPropertyId() == PropertyId::TransitionDuration ) {
auto strDurations = String::split( prop->getValue(), ',' );
for ( auto dit = strDurations.begin(); dit != strDurations.end(); ++dit ) {
std::string duration( String::trim( *dit ) );
String::toLowerInPlace( duration );
durations.push_back( StyleSheetProperty( prop->getName(), duration ).asTime() );
}
} else if ( propDef->getPropertyId() == PropertyId::TransitionDelay ) {
auto strDelays = String::split( prop->getValue(), ',' );
for ( auto dit = strDelays.begin(); dit != strDelays.end(); ++dit ) {
std::string delay( String::trim( *dit ) );
String::toLowerInPlace( delay );
delays.push_back( StyleSheetProperty( prop->getName(), delay ).asTime() );
}
} else if ( propDef->getPropertyId() == PropertyId::TransitionTimingFunction ) {
auto strTimingFuncs = String::split( prop->getValue(), ",", "", "()" );
for ( auto dit = strTimingFuncs.begin(); dit != strTimingFuncs.end(); ++dit ) {
TimingFunction tf( TimingFunction::parse( *dit ) );
timingFunctions.emplace_back( tf.interpolation );
timingFunctionParameters.emplace_back( tf.parameters );
}
} else if ( propDef->getPropertyId() == PropertyId::TransitionProperty ) {
auto strProperties = String::split( prop->getValue(), ',' );
for ( auto dit = strProperties.begin(); dit != strProperties.end(); ++dit ) {
std::string property( String::trim( *dit ) );
String::toLowerInPlace( property );
properties.push_back( property );
}
}
}
for ( size_t i = 0; i < properties.size(); i++ ) {
const std::string& property = properties.at( i );
TransitionDefinition transitionDef;
transitionDef.property = property;
if ( !durations.empty() ) if ( !durations.empty() )
transitionDef.duration = durations[i % durations.size()]; transition.duration = durations[i % durations.size()];
if ( !delays.empty() ) if ( !delays.empty() )
transitionDef.delay = delays[i % delays.size()]; transition.delay = delays[i % delays.size()];
if ( !timingFunctions.empty() ) { if ( !timingFunctions.empty() ) {
size_t idx = !delays.empty() ? i % delays.size() : 0; const TimingFunction& timing = timingFunctions[i % timingFunctions.size()];
transitionDef.timingFunction = timingFunctions[idx]; transition.timingFunction = timing.interpolation;
transitionDef.timingFunctionParameters = timingFunctionParameters[idx]; transition.timingFunctionParameters.assign( timing.parameters.begin(),
timing.parameters.end() );
}
transitions[transition.property] = std::move( transition );
} }
transitions[property] = transitionDef; return transitions;
}
void ComputedTransitions::set( const ComputedTransitionDefinition& transition ) {
for ( auto& current : mTransitions ) {
if ( current.propertyNameHash == transition.propertyNameHash ) {
current = transition;
return;
}
}
mTransitions.emplace_back( transition );
}
const ComputedTransitionDefinition*
ComputedTransitions::get( String::HashType propertyNameHash ) const {
const ComputedTransitionDefinition* all = nullptr;
for ( const auto& transition : mTransitions ) {
if ( transition.propertyNameHash == propertyNameHash )
return &transition;
if ( transition.propertyNameHash == String::hash( "all" ) )
all = &transition;
}
return all;
}
ComputedTransitions
ComputedTransitions::parse( const std::vector<const StyleSheetProperty*>& styleSheetProperties ) {
SmallVector<String::HashType, 4> properties;
SmallVector<Time, 4> durations;
SmallVector<Time, 4> delays;
SmallVector<TimingFunction, 4> timingFunctions;
ComputedTransitions transitions;
for ( const StyleSheetProperty* property : styleSheetProperties ) {
if ( nullptr == property || nullptr == property->getPropertyDefinition() )
continue;
switch ( property->getPropertyDefinition()->getPropertyId() ) {
case PropertyId::Transition:
forEachCommaItem( property->getValue(), [&]( std::string_view item ) {
auto tokens = splitTransitionTokens( item );
if ( tokens.size() < 2 )
return;
ComputedTransitionDefinition transition;
transition.propertyNameHash = lowerHash( tokens[0] );
transition.duration = parseTime( tokens[1] );
if ( tokens.size() >= 3 ) {
TimingFunction timing = TimingFunction::parse( tokens[2] );
transition.timingFunction = timing.interpolation;
transition.timingFunctionParameters = std::move( timing.parameters );
if ( transition.timingFunction == Ease::None && tokens.size() == 3 )
transition.delay = parseTime( tokens[2] );
else if ( tokens.size() >= 4 )
transition.delay = parseTime( tokens[3] );
}
transitions.set( transition );
} );
break;
case PropertyId::TransitionDuration:
forEachCommaItem( property->getValue(), [&]( std::string_view item ) {
durations.emplace_back( parseTime( item ) );
} );
break;
case PropertyId::TransitionDelay:
forEachCommaItem( property->getValue(), [&]( std::string_view item ) {
delays.emplace_back( parseTime( item ) );
} );
break;
case PropertyId::TransitionTimingFunction:
forEachCommaItem( property->getValue(), [&]( std::string_view item ) {
timingFunctions.emplace_back( TimingFunction::parse( item ) );
} );
break;
case PropertyId::TransitionProperty:
forEachCommaItem( property->getValue(), [&]( std::string_view item ) {
properties.emplace_back( lowerHash( item ) );
} );
break;
default:
break;
}
}
for ( std::size_t i = 0; i < properties.size(); ++i ) {
ComputedTransitionDefinition transition;
transition.propertyNameHash = properties[i];
if ( !durations.empty() )
transition.duration = durations[i % durations.size()];
if ( !delays.empty() )
transition.delay = delays[i % delays.size()];
if ( !timingFunctions.empty() ) {
const TimingFunction& timing = timingFunctions[i % timingFunctions.size()];
transition.timingFunction = timing.interpolation;
transition.timingFunctionParameters = timing.parameters;
}
transitions.set( transition );
} }
return transitions; return transitions;
+1 -8
View File
@@ -60,14 +60,7 @@ static bool hasDefiniteCSSHeight( UIWidget* widget ) {
static CSSBaselineAlignValue parseBaselineAlign( UIHTMLWidget* widget, static CSSBaselineAlignValue parseBaselineAlign( UIHTMLWidget* widget,
const StyleSheetProperty& property ) { const StyleSheetProperty& property ) {
std::string_view val = property.value(); std::string_view val = String::trim( std::string_view{ property.value() }, " \t\n\r\f\v" );
auto isSpace = []( char c ) {
return c == ' ' || c == '\t' || c == '\n' || c == '\r' || c == '\f';
};
while ( !val.empty() && isSpace( val.front() ) )
val.remove_prefix( 1 );
while ( !val.empty() && isSpace( val.back() ) )
val.remove_suffix( 1 );
if ( val.empty() ) if ( val.empty() )
return {}; return {};
+45 -17
View File
@@ -1,3 +1,5 @@
#include <array>
#include <cctype>
#include <eepp/core/core.hpp> #include <eepp/core/core.hpp>
#include <eepp/graphics/drawableresource.hpp> #include <eepp/graphics/drawableresource.hpp>
#include <eepp/graphics/image.hpp> #include <eepp/graphics/image.hpp>
@@ -19,6 +21,36 @@ using namespace EE::Math::easing;
namespace EE { namespace UI { namespace EE { namespace UI {
namespace {
struct WhitespaceParts {
std::array<std::string_view, 3> values;
std::size_t count{ 0 };
std::size_t size() const { return count; }
bool empty() const { return count == 0; }
std::string_view operator[]( std::size_t index ) const { return values[index]; }
};
WhitespaceParts splitWhitespace( std::string_view value ) {
WhitespaceParts parts;
std::size_t position = 0;
while ( position < value.size() && parts.count < parts.values.size() ) {
while ( position < value.size() &&
std::isspace( static_cast<unsigned char>( value[position] ) ) )
++position;
const std::size_t start = position;
while ( position < value.size() &&
!std::isspace( static_cast<unsigned char>( value[position] ) ) )
++position;
if ( position > start )
parts.values[parts.count++] = value.substr( start, position - start );
}
return parts;
}
} // namespace
void UINodeDrawable::repeatFromText( const std::string& text, RepeatX& repeatX, RepeatY& repeatY ) { void UINodeDrawable::repeatFromText( const std::string& text, RepeatX& repeatX, RepeatY& repeatY ) {
auto parts = String::split( text, ' ' ); auto parts = String::split( text, ' ' );
@@ -796,11 +828,9 @@ Sizef UINodeDrawable::LayerDrawable::calcDrawableSize( const std::string& drawab
Scale1 = eemax( Scale1, Scale2 ); Scale1 = eemax( Scale1, Scale2 );
size = Sizef( drawableSize.getWidth() * Scale1, drawableSize.getHeight() * Scale1 ); size = Sizef( drawableSize.getWidth() * Scale1, drawableSize.getHeight() * Scale1 );
} else { } else {
std::vector<std::string> sizePart = String::split( drawableSizeEq, ' ' ); auto sizePart = splitWhitespace( drawableSizeEq );
if ( sizePart.size() == 1 )
if ( sizePart.size() == 1 ) { sizePart.values[sizePart.count++] = "auto";
sizePart.push_back( "auto" );
}
if ( sizePart.size() == 2 ) { if ( sizePart.size() == 2 ) {
if ( sizePart[0] == "auto" && sizePart[1] == "auto" ) { if ( sizePart[0] == "auto" && sizePart[1] == "auto" ) {
@@ -841,24 +871,22 @@ Sizef UINodeDrawable::LayerDrawable::calcDrawableSize( const std::string& drawab
return size; return size;
} }
Vector2f UINodeDrawable::LayerDrawable::calcPosition( std::string positionXEq, Vector2f UINodeDrawable::LayerDrawable::calcPosition( const std::string& positionXEq,
std::string positionYEq ) { const std::string& positionYEq ) {
Vector2f position( Vector2f::Zero ); Vector2f position( Vector2f::Zero );
if ( positionXEq.empty() ) const std::string_view positionX =
positionXEq = "center"; positionXEq.empty() ? std::string_view{ "center" } : std::string_view{ positionXEq };
const std::string_view positionY =
if ( positionYEq.empty() ) positionYEq.empty() ? std::string_view{ "center" } : std::string_view{ positionYEq };
positionYEq = "center"; auto posX = splitWhitespace( positionX );
auto posY = splitWhitespace( positionY );
auto posX = String::split( positionXEq, ' ' );
auto posY = String::split( positionYEq, ' ' );
if ( posX.empty() || posY.empty() ) if ( posX.empty() || posY.empty() )
return position; return position;
bool needsRoundingX = positionXEq.back() == '%'; bool needsRoundingX = positionX.back() == '%';
bool needsRoundingY = positionYEq.back() == '%'; bool needsRoundingY = positionY.back() == '%';
Sizef ownerSize = mContainer->getOwner()->getPixelsSize(); Sizef ownerSize = mContainer->getOwner()->getPixelsSize();
Float refWidth = ownerSize.getWidth(); Float refWidth = ownerSize.getWidth();
+33 -28
View File
@@ -135,8 +135,8 @@ void UIStyle::setStyleSheetProperties( const CSS::StyleSheetProperties& properti
} }
bool UIStyle::hasTransition( const std::string& propertyName ) { bool UIStyle::hasTransition( const std::string& propertyName ) {
return mTransitions.find( propertyName ) != mTransitions.end() || return mDefinition && mDefinition->getTransitions().get( String::hashToLower(
mTransitions.find( "all" ) != mTransitions.end(); propertyName.c_str(), static_cast<Int64>( propertyName.size() ) ) );
} }
StyleSheetPropertyAnimation* UIStyle::getAnimation( const PropertyDefinition* propertyDef ) { StyleSheetPropertyAnimation* UIStyle::getAnimation( const PropertyDefinition* propertyDef ) {
@@ -160,15 +160,20 @@ bool UIStyle::hasAnimation( const PropertyDefinition* propertyDef ) {
} }
TransitionDefinition UIStyle::getTransition( const std::string& propertyName ) { TransitionDefinition UIStyle::getTransition( const std::string& propertyName ) {
auto propertyTransitionIt = mTransitions.find( propertyName ); TransitionDefinition transition;
if ( !mDefinition )
if ( propertyTransitionIt != mTransitions.end() ) { return transition;
return propertyTransitionIt->second; const auto* computed = mDefinition->getTransitions().get(
} else if ( ( propertyTransitionIt = mTransitions.find( "all" ) ) != mTransitions.end() ) { String::hashToLower( propertyName.c_str(), static_cast<Int64>( propertyName.size() ) ) );
return propertyTransitionIt->second; if ( !computed )
} return transition;
transition.property = propertyName;
return TransitionDefinition(); transition.timingFunction = computed->timingFunction;
transition.timingFunctionParameters.assign( computed->timingFunctionParameters.begin(),
computed->timingFunctionParameters.end() );
transition.delay = computed->delay;
transition.duration = computed->duration;
return transition;
} }
const bool& UIStyle::isChangingState() const { const bool& UIStyle::isChangingState() const {
@@ -339,9 +344,9 @@ void UIStyle::setVariableFromValue( StyleSheetProperty* property, const std::str
for ( int depth = 0; depth < maxDepth; depth++ ) { for ( int depth = 0; depth < maxDepth; depth++ ) {
bool changed = false; bool changed = false;
if ( !property->getVarCache().empty() ) { if ( !property->getVarCache().empty() ) {
auto varCacheCopy = property->getVarCache(); const auto& varCache = property->getVarCache();
for ( auto& var : varCacheCopy ) { for ( const auto& var : varCache ) {
for ( auto& val : var.variableList ) { for ( const auto& val : var.variableList ) {
StyleSheetVariable variable( getVariable( val ) ); StyleSheetVariable variable( getVariable( val ) );
if ( !variable.isEmpty() ) { if ( !variable.isEmpty() ) {
String::replaceAll( newValue, var.definition, variable.getValue() ); String::replaceAll( newValue, var.definition, variable.getValue() );
@@ -432,11 +437,6 @@ void UIStyle::onStateChange() {
mWidget->beginAttributesTransaction(); mWidget->beginAttributesTransaction();
if ( nullptr != mDefinition && !mDefinition->getTransitionProperties().empty() ) {
mTransitions = TransitionDefinition::parseTransitionProperties(
mDefinition->getTransitionProperties() );
}
for ( PropertyId prop : changedProperties ) { for ( PropertyId prop : changedProperties ) {
std::optional<StyleSheetProperty> resolvedProperty; std::optional<StyleSheetProperty> resolvedProperty;
StyleSheetProperty* property = getResolvedLocalProperty( prop, resolvedProperty ); StyleSheetProperty* property = getResolvedLocalProperty( prop, resolvedProperty );
@@ -669,11 +669,17 @@ void UIStyle::applyStyleSheetProperty( const StyleSheetProperty& originalPropert
} }
} }
TransitionDefinition transitionInfo( getTransition( property->getName() ) ); const ComputedTransitionDefinition* transitionInfo =
mDefinition ? mDefinition->getTransitions().get( propertyDefinition->getId() )
: nullptr;
if ( nullptr == transitionInfo ) {
mWidget->applyProperty( *property );
return;
}
std::vector<Action*> previousTransitions = SmallVector<Action*, 4> previousTransitions;
mWidget->getActionsByTag( propertyDefinition->getId() ); mWidget->getActionsByTag( propertyDefinition->getId(), previousTransitions );
std::vector<Action*> removeTransitions; SmallVector<Action*, 4> removeTransitions;
StyleSheetPropertyAnimation* prevTransition = NULL; StyleSheetPropertyAnimation* prevTransition = NULL;
if ( !previousTransitions.empty() ) { if ( !previousTransitions.empty() ) {
@@ -707,9 +713,9 @@ void UIStyle::applyStyleSheetProperty( const StyleSheetProperty& originalPropert
currentProgress = eemin( 1.f, currentProgress ); currentProgress = eemin( 1.f, currentProgress );
if ( 0.f != currentProgress ) { if ( 0.f != currentProgress ) {
elapsed = Milliseconds( ( 1.f - currentProgress ) * elapsed = Milliseconds( ( 1.f - currentProgress ) *
transitionInfo.getDuration().asMilliseconds() ); transitionInfo->duration.asMilliseconds() );
} else { } else {
elapsed = transitionInfo.getDuration(); elapsed = transitionInfo->duration;
} }
startValue = prevTransition->getEndValue(); startValue = prevTransition->getEndValue();
} else if ( startValue == prevTransition->getEndValue() ) { } else if ( startValue == prevTransition->getEndValue() ) {
@@ -723,9 +729,8 @@ void UIStyle::applyStyleSheetProperty( const StyleSheetProperty& originalPropert
StyleSheetPropertyAnimation* newTransition = StyleSheetPropertyAnimation::New( StyleSheetPropertyAnimation* newTransition = StyleSheetPropertyAnimation::New(
propertyDefinition, startValue, property->getValue(), property->getIndex(), propertyDefinition, startValue, property->getValue(), property->getIndex(),
transitionInfo.getDuration(), transitionInfo.getDelay(), transitionInfo->duration, transitionInfo->delay, transitionInfo->timingFunction,
transitionInfo.getTimingFunction(), transitionInfo.getTimingFunctionParameters(), transitionInfo->timingFunctionParameters, AnimationOrigin::Transition );
AnimationOrigin::Transition );
newTransition->setElapsed( elapsed ); newTransition->setElapsed( elapsed );
newTransition->setTag( propertyDefinition->getId() ); newTransition->setTag( propertyDefinition->getId() );
mWidget->runAction( newTransition ); mWidget->runAction( newTransition );
@@ -0,0 +1,125 @@
#include "utest.h"
#include <eepp/scene/actions/delay.hpp>
#include <eepp/scene/actions/runnable.hpp>
#include <eepp/ui/css/stylesheetspecification.hpp>
#include <eepp/ui/css/transitiondefinition.hpp>
using namespace EE;
using namespace EE::Math;
using namespace EE::Scene;
using namespace EE::Scene::Actions;
using namespace EE::System;
using namespace EE::UI::CSS;
UTEST( ActionPool, reusesCompletedActionSlots ) {
Delay* first = Delay::New( Milliseconds( 1 ) );
void* firstAddress = first;
eeDelete( first );
Delay* second = Delay::New( Milliseconds( 2 ) );
EXPECT_EQ( static_cast<void*>( second ), firstAddress );
eeDelete( second );
}
UTEST( ActionCallbacks, listenerMutationUsesEventSnapshot ) {
Delay* action = Delay::New( Time::Zero );
int callbackCount = 0;
Uint32 firstCallback = 0;
Uint32 secondCallback = 0;
firstCallback = action->addEventListener( Action::OnStart, [&]( Action* sender, const auto& ) {
++callbackCount;
sender->removeEventListener( secondCallback );
} );
secondCallback = action->addEventListener( Action::OnStart, [&]( Action* sender, const auto& ) {
++callbackCount;
sender->removeEventListener( firstCallback );
} );
action->start();
EXPECT_EQ( callbackCount, 2 );
eeDelete( action );
}
UTEST( Transitions, compactShorthandParsesWithoutLosingSemantics ) {
auto* specification = StyleSheetSpecification::instance();
StyleSheetProperty shorthand(
specification->getProperty( PropertyId::Transition ),
"opacity 150ms cubic-bezier(0.1, 0.2, 0.3, 0.4) 25ms, width 2s linear" );
std::vector<const StyleSheetProperty*> properties{ &shorthand };
ComputedTransitions transitions = ComputedTransitions::parse( properties );
const auto* opacity = transitions.get( String::hash( "opacity" ) );
const auto* width = transitions.get( String::hash( "width" ) );
ASSERT_TRUE( nullptr != opacity );
ASSERT_TRUE( nullptr != width );
EXPECT_EQ( opacity->duration.asMilliseconds(), 150.0 );
EXPECT_EQ( opacity->delay.asMilliseconds(), 25.0 );
EXPECT_TRUE( opacity->timingFunction == Ease::CubizBezier );
ASSERT_EQ( opacity->timingFunctionParameters.size(), 4u );
EXPECT_EQ( opacity->timingFunctionParameters[2], 0.3 );
EXPECT_EQ( width->duration.asSeconds(), 2.0 );
EXPECT_TRUE( width->timingFunction == Ease::Linear );
}
UTEST( Transitions, longhandListsCycleIndependently ) {
auto* specification = StyleSheetSpecification::instance();
StyleSheetProperty property( specification->getProperty( PropertyId::TransitionProperty ),
"opacity,width,height" );
StyleSheetProperty duration( specification->getProperty( PropertyId::TransitionDuration ),
"1s,2s" );
StyleSheetProperty delay( specification->getProperty( PropertyId::TransitionDelay ),
"10ms,20ms" );
StyleSheetProperty timing( specification->getProperty( PropertyId::TransitionTimingFunction ),
"linear,quadratic-in,bounce-out" );
std::vector<const StyleSheetProperty*> properties{ &property, &duration, &delay, &timing };
ComputedTransitions transitions = ComputedTransitions::parse( properties );
const auto* opacity = transitions.get( String::hash( "opacity" ) );
const auto* width = transitions.get( String::hash( "width" ) );
const auto* height = transitions.get( String::hash( "height" ) );
ASSERT_TRUE( nullptr != opacity );
ASSERT_TRUE( nullptr != width );
ASSERT_TRUE( nullptr != height );
EXPECT_EQ( opacity->duration.asSeconds(), 1.0 );
EXPECT_EQ( width->duration.asSeconds(), 2.0 );
EXPECT_EQ( height->duration.asSeconds(), 1.0 );
EXPECT_EQ( height->delay.asMilliseconds(), 10.0 );
EXPECT_TRUE( opacity->timingFunction == Ease::Linear );
EXPECT_TRUE( width->timingFunction == Ease::QuadraticIn );
EXPECT_TRUE( height->timingFunction == Ease::BounceOut );
TransitionsMap compatibility = TransitionDefinition::parseTransitionProperties( properties );
EXPECT_TRUE( compatibility["height"].getTimingFunction() == Ease::BounceOut );
}
UTEST( Transitions, allIsFallbackAndSpecificPropertyWins ) {
auto* specification = StyleSheetSpecification::instance();
StyleSheetProperty shorthand( specification->getProperty( PropertyId::Transition ),
"all 1s linear, opacity 2s sine-in" );
std::vector<const StyleSheetProperty*> properties{ &shorthand };
ComputedTransitions transitions = ComputedTransitions::parse( properties );
const auto* width = transitions.get( String::hash( "width" ) );
const auto* opacity = transitions.get( String::hash( "opacity" ) );
ASSERT_TRUE( nullptr != width );
ASSERT_TRUE( nullptr != opacity );
EXPECT_EQ( width->duration.asSeconds(), 1.0 );
EXPECT_EQ( opacity->duration.asSeconds(), 2.0 );
EXPECT_TRUE( opacity->timingFunction == Ease::SineIn );
}
UTEST( Transitions, allocationLightParserPreservesAcceptedSpelling ) {
auto* specification = StyleSheetSpecification::instance();
StyleSheetProperty shorthand( specification->getProperty( PropertyId::Transition ),
"opacity +150MS CUBIC-BEZIER(0.1, 0.2, 0.3, 0.4) +25MS" );
std::vector<const StyleSheetProperty*> properties{ &shorthand };
ComputedTransitions transitions = ComputedTransitions::parse( properties );
const auto* opacity = transitions.get( String::hash( "opacity" ) );
ASSERT_TRUE( nullptr != opacity );
EXPECT_EQ( opacity->duration.asMilliseconds(), 150.0 );
EXPECT_EQ( opacity->delay.asMilliseconds(), 25.0 );
EXPECT_TRUE( opacity->timingFunction == Ease::CubizBezier );
EXPECT_EQ( opacity->timingFunctionParameters.size(), 4u );
EXPECT_TRUE( TimingFunction::parse( "cubic-bezier(" ).interpolation == Ease::None );
}
@@ -60,3 +60,44 @@ UTEST( FunctionString, functionString ) {
testCase( R"(test30("str'ing", 'str"ing2'))", "test30", { "str'ing", "str\"ing2" } ); testCase( R"(test30("str'ing", 'str"ing2'))", "test30", { "str'ing", "str\"ing2" } );
testCase( R"(test31('', ''))", "test31", { "", "" } ); testCase( R"(test31('', ''))", "test31", { "", "" } );
} }
UTEST( FunctionString, nonOwningViewParser ) {
constexpr std::string_view input =
R"( compose ( first, nested(1, "two,three"), 'quoted, value', "escaped\"quote", '' ) )";
auto function = FunctionString::parseView( input );
EXPECT_FALSE( function.isEmpty() );
EXPECT_TRUE( "compose"sv == function.getName() );
SmallVector<std::string_view, 5> parameters;
SmallVector<bool, 5> strings;
EXPECT_TRUE( function.forEachParameter( [&]( std::string_view parameter, bool wasString ) {
parameters.emplace_back( parameter );
strings.emplace_back( wasString );
return true;
} ) );
EXPECT_EQ( 5u, parameters.size() );
EXPECT_TRUE( "first"sv == parameters[0] );
EXPECT_TRUE( R"(nested(1, "two,three"))"sv == parameters[1] );
EXPECT_TRUE( "quoted, value"sv == parameters[2] );
EXPECT_TRUE( R"(escaped\"quote)"sv == parameters[3] );
EXPECT_TRUE( ""sv == parameters[4] );
EXPECT_FALSE( strings[0] );
EXPECT_FALSE( strings[1] );
EXPECT_TRUE( strings[2] );
EXPECT_TRUE( strings[3] );
EXPECT_TRUE( strings[4] );
}
UTEST( FunctionString, nonOwningViewParserCanStopEarly ) {
auto function = FunctionString::parseView( "fn(one, two, three)"sv );
int calls = 0;
std::string_view firstParameter;
EXPECT_FALSE( function.forEachParameter( [&]( std::string_view parameter, bool ) {
++calls;
firstParameter = parameter;
return false;
} ) );
EXPECT_EQ( 1, calls );
EXPECT_TRUE( "one"sv == firstParameter );
}