mirror of
https://github.com/SpartanJ/eepp.git
synced 2026-10-02 03:00:23 +03:00
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:
@@ -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;
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -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
|
||||||
|
|||||||
@@ -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 };
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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 );
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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
@@ -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() {}
|
||||||
|
|||||||
@@ -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 );
|
||||||
|
|||||||
@@ -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 ) :
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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 );
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 ||
|
||||||
|
|||||||
@@ -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 )
|
||||||
|
continue;
|
||||||
bool stateParsingString = false;
|
owned += parameter[i];
|
||||||
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 );
|
funcParameters.emplace_back( std::move( owned ) );
|
||||||
buffer.clear();
|
typeStringData.emplace_back( wasString );
|
||||||
currentParamIsString = false;
|
return true;
|
||||||
};
|
} );
|
||||||
|
return FunctionString( std::string{ view.getName() }, std::move( funcParameters ),
|
||||||
|
std::move( typeStringData ) );
|
||||||
|
}
|
||||||
|
|
||||||
for ( size_t i = 0; i < parametersString.length(); ++i ) {
|
FunctionString::View FunctionString::parseView( std::string_view function ) {
|
||||||
|
const std::size_t open = function.find( '(' );
|
||||||
CharType c = parametersString[i];
|
if ( open == std::string_view::npos )
|
||||||
|
return {};
|
||||||
if ( !stateParsingString ) {
|
const std::size_t close = function.rfind( ')' );
|
||||||
if ( c == '(' ) {
|
if ( close == std::string_view::npos || close < open )
|
||||||
parenDepth++;
|
return {};
|
||||||
buffer += c;
|
std::string_view name = String::trim( function.substr( 0, open ), " \t\n\r\f\v" );
|
||||||
} else if ( c == ')' ) {
|
if ( name.empty() )
|
||||||
if ( parenDepth == 0 )
|
return {};
|
||||||
break;
|
return View{ name, function.substr( open + 1, close - open - 1 ) };
|
||||||
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;
|
|
||||||
else
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add param if we either accumulated characters or actively parsed an empty string parameter.
|
|
||||||
if ( !buffer.empty() || currentParamIsString )
|
|
||||||
pushBufferToParams();
|
|
||||||
|
|
||||||
if constexpr ( std::is_same_v<StringType, std::string> ) {
|
|
||||||
return FunctionString( std::string{ funcName }, funcParameters, typeStringData );
|
|
||||||
} else {
|
|
||||||
return FunctionString( String( funcName ).toUtf8(), funcParameters, typeStringData );
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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,
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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 TimingFunction::Parameters& timingFunctionParameters, const Uint32& propertyIndex,
|
||||||
const std::vector<double> timingFunctionParameters,
|
const bool& isDone ) {
|
||||||
const Uint32& propertyIndex, 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
|
||||||
|
|||||||
@@ -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 ), {} };
|
TimingFunction TimingFunction::parse( std::string_view timingFunction ) {
|
||||||
} else {
|
timingFunction = String::trim( timingFunction, " \t\n\r\f\v" );
|
||||||
TimingFunction tf;
|
SmallVector<char, 64> normalized;
|
||||||
FunctionString func( FunctionString::parse( timingFunction ) );
|
for ( char character : timingFunction )
|
||||||
if ( !func.isEmpty() ) {
|
normalized.emplace_back( character >= 'A' && character <= 'Z' ? character + 32
|
||||||
tf.interpolation = Ease::fromName( timingFunction, Ease::Interpolation::None );
|
: character );
|
||||||
const auto& params = func.getParameters();
|
timingFunction = std::string_view{ normalized.data(), normalized.size() };
|
||||||
for ( const auto& param : params ) {
|
|
||||||
double d;
|
TimingFunction result;
|
||||||
if ( String::fromString( d, param ) )
|
const auto open = timingFunction.find( '(' );
|
||||||
tf.parameters.emplace_back( d );
|
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
|
||||||
|
|||||||
@@ -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();
|
|
||||||
|
|
||||||
transitionDef.property = property;
|
|
||||||
transitionDef.duration = duration;
|
|
||||||
|
|
||||||
if ( splitTransition.size() >= 3 ) {
|
|
||||||
TimingFunction tf( TimingFunction::parse( splitTransition[2] ) );
|
|
||||||
transitionDef.timingFunction = std::move( tf.interpolation );
|
|
||||||
transitionDef.timingFunctionParameters = std::move( tf.parameters );
|
|
||||||
|
|
||||||
if ( transitionDef.timingFunction == Ease::None &&
|
|
||||||
splitTransition.size() == 3 ) {
|
|
||||||
transitionDef.delay =
|
|
||||||
StyleSheetProperty( prop->getName(),
|
|
||||||
String::toLower( splitTransition[2] ) )
|
|
||||||
.asTime();
|
|
||||||
} else if ( splitTransition.size() >= 4 ) {
|
|
||||||
transitionDef.delay =
|
|
||||||
StyleSheetProperty( prop->getName(),
|
|
||||||
String::toLower( splitTransition[3] ) )
|
|
||||||
.asTime();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
transitions[transitionDef.getProperty()] = transitionDef;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return true;
|
transitions[transition.property] = std::move( transition );
|
||||||
},
|
} );
|
||||||
prop->getValue(), ",", ",", "()" );
|
break;
|
||||||
} else if ( propDef->getPropertyId() == PropertyId::TransitionDuration ) {
|
case PropertyId::TransitionDuration:
|
||||||
auto strDurations = String::split( prop->getValue(), ',' );
|
forEachCommaItem( property->getValue(), [&]( std::string_view item ) {
|
||||||
|
durations.emplace_back( parseTime( item ) );
|
||||||
for ( auto dit = strDurations.begin(); dit != strDurations.end(); ++dit ) {
|
} );
|
||||||
std::string duration( String::trim( *dit ) );
|
break;
|
||||||
String::toLowerInPlace( duration );
|
case PropertyId::TransitionDelay:
|
||||||
durations.push_back( StyleSheetProperty( prop->getName(), duration ).asTime() );
|
forEachCommaItem( property->getValue(), [&]( std::string_view item ) {
|
||||||
}
|
delays.emplace_back( parseTime( item ) );
|
||||||
} else if ( propDef->getPropertyId() == PropertyId::TransitionDelay ) {
|
} );
|
||||||
auto strDelays = String::split( prop->getValue(), ',' );
|
break;
|
||||||
|
case PropertyId::TransitionTimingFunction:
|
||||||
for ( auto dit = strDelays.begin(); dit != strDelays.end(); ++dit ) {
|
forEachCommaItem( property->getValue(), [&]( std::string_view item ) {
|
||||||
std::string delay( String::trim( *dit ) );
|
timingFunctions.emplace_back( TimingFunction::parse( item ) );
|
||||||
String::toLowerInPlace( delay );
|
} );
|
||||||
delays.push_back( StyleSheetProperty( prop->getName(), delay ).asTime() );
|
break;
|
||||||
}
|
case PropertyId::TransitionProperty:
|
||||||
} else if ( propDef->getPropertyId() == PropertyId::TransitionTimingFunction ) {
|
forEachCommaItem( property->getValue(), [&]( std::string_view item ) {
|
||||||
auto strTimingFuncs = String::split( prop->getValue(), ",", "", "()" );
|
properties.emplace_back( lowerString( item ) );
|
||||||
|
} );
|
||||||
for ( auto dit = strTimingFuncs.begin(); dit != strTimingFuncs.end(); ++dit ) {
|
break;
|
||||||
TimingFunction tf( TimingFunction::parse( *dit ) );
|
default:
|
||||||
timingFunctions.emplace_back( tf.interpolation );
|
break;
|
||||||
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++ ) {
|
for ( std::size_t i = 0; i < properties.size(); ++i ) {
|
||||||
const std::string& property = properties.at( i );
|
TransitionDefinition transition;
|
||||||
TransitionDefinition transitionDef;
|
transition.property = properties[i];
|
||||||
|
|
||||||
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;
|
||||||
|
|||||||
@@ -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 {};
|
||||||
|
|
||||||
|
|||||||
@@ -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
@@ -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 );
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user