mirror of
https://github.com/SpartanJ/eepp.git
synced 2026-10-01 02:30:25 +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:
@@ -18,7 +18,7 @@ using namespace EE::Window;
|
||||
|
||||
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 ) {
|
||||
mData = data;
|
||||
mFile = file;
|
||||
@@ -75,7 +75,7 @@ void* MemoryManager::addPointerInPlace( void* place, const AllocatedPointer& aAl
|
||||
AllocatedPointerMapIt it = state.pointers.find( place );
|
||||
|
||||
if ( it != state.pointers.end() ) {
|
||||
removePointer( place, aAllocatedPointer.mFile.c_str(), aAllocatedPointer.mLine );
|
||||
removePointer( place, aAllocatedPointer.mFile, aAllocatedPointer.mLine );
|
||||
}
|
||||
|
||||
return addPointer( aAllocatedPointer );
|
||||
@@ -110,7 +110,7 @@ void* MemoryManager::addPointer( const AllocatedPointer& aAllocatedPointer ) {
|
||||
|
||||
if ( aAllocatedPointer.mTrack )
|
||||
eePRINTL( "Allocating pointer %p at '%s' %d", aAllocatedPointer.mData,
|
||||
aAllocatedPointer.mFile.c_str(), aAllocatedPointer.mLine );
|
||||
aAllocatedPointer.mFile, aAllocatedPointer.mLine );
|
||||
|
||||
return aAllocatedPointer.mData;
|
||||
}
|
||||
@@ -123,7 +123,7 @@ void* MemoryManager::reallocPointer( void* data, const AllocatedPointer& aAlloca
|
||||
AllocatedPointerMapIt it = state.pointers.find( data );
|
||||
|
||||
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 );
|
||||
|
||||
if ( it == state.pointers.end() )
|
||||
@@ -131,10 +131,10 @@ void* MemoryManager::reallocPointer( void* data, const AllocatedPointer& aAlloca
|
||||
|
||||
if ( aAllocatedPointer.mTrack )
|
||||
eePRINTL( "Reallocating pointer %p at '%s' %d", aAllocatedPointer.mData,
|
||||
aAllocatedPointer.mFile.c_str(), aAllocatedPointer.mLine );
|
||||
aAllocatedPointer.mFile, aAllocatedPointer.mLine );
|
||||
|
||||
if ( it->first != aAllocatedPointer.mData ) {
|
||||
removePointer( data, aAllocatedPointer.mFile.c_str(), aAllocatedPointer.mLine );
|
||||
removePointer( data, aAllocatedPointer.mFile, aAllocatedPointer.mLine );
|
||||
addPointer( aAllocatedPointer );
|
||||
} else {
|
||||
state.totalMemoryUsage -= it->second.mMemory;
|
||||
@@ -310,7 +310,7 @@ void MemoryManager::showResults() {
|
||||
|
||||
char address[2 + sizeof( void* ) * 2 + 1];
|
||||
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 ) ) } );
|
||||
}
|
||||
|
||||
@@ -320,7 +320,7 @@ void MemoryManager::showResults() {
|
||||
auto allocationLocation = []( const AllocatedPointer& allocation ) {
|
||||
return allocation.mData == nullptr
|
||||
? std::string( "-" )
|
||||
: allocation.mFile + ":" + std::to_string( allocation.mLine );
|
||||
: std::string{ allocation.mFile } + ":" + std::to_string( allocation.mLine );
|
||||
};
|
||||
|
||||
tabulate::Table summary;
|
||||
|
||||
+142
-11
@@ -1,8 +1,138 @@
|
||||
#include <array>
|
||||
#include <cstdlib>
|
||||
#include <eepp/scene/action.hpp>
|
||||
#include <eepp/scene/node.hpp>
|
||||
#include <mutex>
|
||||
#include <new>
|
||||
|
||||
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() {
|
||||
sendEvent( ActionType::OnDelete );
|
||||
}
|
||||
@@ -53,7 +183,7 @@ Action* Action::reverse() const {
|
||||
Uint32 Action::addEventListener( const ActionType& actionType, const ActionCallback& callback ) {
|
||||
mNumCallBacks++;
|
||||
|
||||
mCallbacks[actionType][mNumCallBacks] = callback;
|
||||
mCallbacks.emplace_back( ActionCallbackEntry{ actionType, mNumCallBacks, callback } );
|
||||
|
||||
return mNumCallBacks;
|
||||
}
|
||||
@@ -65,22 +195,23 @@ Action* Action::on( const Action::ActionType& actionType, const Action::ActionCa
|
||||
|
||||
void Action::removeEventListener( const Uint32& callbackId ) {
|
||||
for ( auto it = mCallbacks.begin(); it != mCallbacks.end(); ++it ) {
|
||||
UnorderedMap<Uint32, ActionCallback>& event = it->second;
|
||||
|
||||
if ( event.erase( callbackId ) )
|
||||
if ( it->id == callbackId ) {
|
||||
mCallbacks.erase( it );
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Action::sendEvent( const ActionType& actionType ) {
|
||||
if ( 0 != mCallbacks.count( actionType ) ) {
|
||||
auto event = mCallbacks[actionType];
|
||||
|
||||
if ( !event.empty() ) {
|
||||
for ( auto it = event.begin(); it != event.end(); ++it )
|
||||
it->second( this, actionType );
|
||||
}
|
||||
// Callbacks are allowed to mutate listener registration. Preserve the previous snapshot
|
||||
// semantics while keeping the common callback count in inline storage.
|
||||
SmallVector<ActionCallback, 4> callbacks;
|
||||
for ( const auto& callback : mCallbacks ) {
|
||||
if ( callback.type == actionType )
|
||||
callbacks.emplace_back( callback.callback );
|
||||
}
|
||||
for ( auto& callback : callbacks )
|
||||
callback( this, actionType );
|
||||
}
|
||||
|
||||
void Action::onStart() {}
|
||||
|
||||
@@ -81,12 +81,21 @@ std::vector<Action*> ActionManager::getActionsByTagFromTarget( Node* target,
|
||||
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 ) {
|
||||
return removeAction( getActionByTag( tag ) );
|
||||
}
|
||||
|
||||
bool ActionManager::removeActionsByTagFromTarget( Node* target, const Action::UniqueID& tag ) {
|
||||
std::vector<Action*> removeList;
|
||||
ActionList removeList;
|
||||
|
||||
{
|
||||
Lock l( mMutex );
|
||||
@@ -226,7 +235,7 @@ bool ActionManager::removeActions( const std::vector<Action*>& actions ) {
|
||||
}
|
||||
|
||||
bool ActionManager::removeAllActionsFromTarget( Node* target ) {
|
||||
std::vector<Action*> removeList;
|
||||
ActionList removeList;
|
||||
|
||||
{
|
||||
Lock l( mMutex );
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
namespace EE { namespace Scene { namespace Actions {
|
||||
|
||||
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 ) :
|
||||
|
||||
@@ -7,41 +7,45 @@ Sequence* Sequence::New( const std::vector<Action*> sequence ) {
|
||||
}
|
||||
|
||||
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 ) {
|
||||
return New( {action, action2, action3} );
|
||||
return eeNew( Sequence, ( ActionContainer{ action, action2, action3 } ) );
|
||||
}
|
||||
|
||||
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,
|
||||
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,
|
||||
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,
|
||||
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,
|
||||
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,
|
||||
Action* action5, Action* action6, Action* action7, Action* action8,
|
||||
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() {
|
||||
@@ -102,17 +106,17 @@ Time Sequence::getTotalTime() {
|
||||
}
|
||||
|
||||
Action* Sequence::clone() const {
|
||||
return Sequence::New( mSequence );
|
||||
return eeNew( Sequence, ( mSequence ) );
|
||||
}
|
||||
|
||||
Action* Sequence::reverse() const {
|
||||
std::vector<Action*> reversed;
|
||||
ActionContainer reversed;
|
||||
|
||||
for ( auto it = mSequence.rbegin(); it != mSequence.rend(); ++it ) {
|
||||
reversed.push_back( *it );
|
||||
}
|
||||
|
||||
return Sequence::New( reversed );
|
||||
return eeNew( Sequence, ( std::move( reversed ) ) );
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
@@ -7,41 +7,45 @@ Spawn* Spawn::New( const std::vector<Action*> spawn ) {
|
||||
}
|
||||
|
||||
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 ) {
|
||||
return New( {action, action2, action3} );
|
||||
return eeNew( Spawn, ( ActionContainer{ action, action2, action3 } ) );
|
||||
}
|
||||
|
||||
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,
|
||||
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,
|
||||
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,
|
||||
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,
|
||||
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,
|
||||
Action* action5, Action* action6, Action* action7, Action* action8,
|
||||
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() {
|
||||
@@ -102,17 +106,17 @@ Time Spawn::getTotalTime() {
|
||||
}
|
||||
|
||||
Action* Spawn::clone() const {
|
||||
return Spawn::New( mSpawn );
|
||||
return eeNew( Spawn, ( mSpawn ) );
|
||||
}
|
||||
|
||||
Action* Spawn::reverse() const {
|
||||
std::vector<Action*> reversed;
|
||||
ActionContainer reversed;
|
||||
|
||||
for ( auto it = mSpawn.rbegin(); it != mSpawn.rend(); ++it ) {
|
||||
reversed.push_back( *it );
|
||||
}
|
||||
|
||||
return Spawn::New( reversed );
|
||||
return eeNew( Spawn, ( std::move( reversed ) ) );
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
@@ -1526,6 +1526,10 @@ std::vector<Action*> Node::getActionsByTag( const Action::UniqueID& 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() {
|
||||
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 ) {
|
||||
while ( !tok.empty() && ( tok.front() == ' ' || tok.front() == '\t' ) )
|
||||
tok.remove_prefix( 1 );
|
||||
while ( !tok.empty() && ( tok.back() == ' ' || tok.back() == '\t' ) )
|
||||
tok.remove_suffix( 1 );
|
||||
tok = String::trim( tok, " \t" );
|
||||
if ( tok.empty() )
|
||||
return false;
|
||||
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 ) {
|
||||
while ( !tok.empty() && ( tok.front() == ' ' || tok.front() == '\t' ) )
|
||||
tok.remove_prefix( 1 );
|
||||
while ( !tok.empty() && ( tok.back() == ' ' || tok.back() == '\t' ) )
|
||||
tok.remove_suffix( 1 );
|
||||
tok = String::trim( tok, " \t" );
|
||||
if ( tok.empty() )
|
||||
return false;
|
||||
if ( tok.size() == 4 && ( std::strncmp( tok.data(), "none", 4 ) == 0 ||
|
||||
|
||||
@@ -3,128 +3,56 @@
|
||||
#include <eepp/system/functionstring.hpp>
|
||||
|
||||
#include <string>
|
||||
#include <type_traits>
|
||||
|
||||
namespace EE { namespace System {
|
||||
|
||||
template <typename StringType> FunctionString FunctionString::parse( StringType function ) {
|
||||
using CharType = typename StringType::value_type;
|
||||
|
||||
size_t funcSep = function.find( '(' );
|
||||
if ( funcSep == StringType::npos )
|
||||
return FunctionString( "", {}, {} );
|
||||
|
||||
auto funcName = String::trim( function.substr( 0, funcSep ) );
|
||||
FunctionString FunctionString::parseOwned( std::string_view function ) {
|
||||
View view = parseView( function );
|
||||
if ( view.isEmpty() )
|
||||
return {};
|
||||
Parameters funcParameters;
|
||||
TypeStringVector typeStringData;
|
||||
|
||||
auto parametersString = function.substr( funcSep + 1 );
|
||||
size_t paramClose = parametersString.find_last_of( ')' );
|
||||
if ( paramClose == StringType::npos )
|
||||
return FunctionString( "", {}, {} );
|
||||
|
||||
parametersString = parametersString.substr( 0, paramClose );
|
||||
|
||||
bool stateParsingString = false;
|
||||
std::basic_string<CharType> buffer;
|
||||
CharType prevChar = 0;
|
||||
CharType quoteChar = 0;
|
||||
|
||||
bool currentParamIsString = false;
|
||||
int parenDepth = 0;
|
||||
|
||||
auto pushBufferToParams = [&]() {
|
||||
if constexpr ( std::is_same_v<CharType, char> ) {
|
||||
if ( !currentParamIsString )
|
||||
String::trimInPlace( buffer );
|
||||
funcParameters.push_back( buffer );
|
||||
} else {
|
||||
std::string utf8Buffer = String( buffer ).toUtf8();
|
||||
if ( !currentParamIsString )
|
||||
String::trimInPlace( utf8Buffer );
|
||||
funcParameters.push_back( utf8Buffer );
|
||||
view.forEachParameter( [&]( std::string_view parameter, bool wasString ) {
|
||||
std::string owned;
|
||||
owned.reserve( parameter.size() );
|
||||
const char quote = wasString ? function[parameter.data() - function.data() - 1] : 0;
|
||||
for ( std::size_t i = 0; i < parameter.size(); ++i ) {
|
||||
if ( wasString && parameter[i] == '\\' && i + 1 < parameter.size() &&
|
||||
parameter[i + 1] == quote )
|
||||
continue;
|
||||
owned += parameter[i];
|
||||
}
|
||||
typeStringData.push_back( currentParamIsString );
|
||||
buffer.clear();
|
||||
currentParamIsString = false;
|
||||
};
|
||||
funcParameters.emplace_back( std::move( owned ) );
|
||||
typeStringData.emplace_back( wasString );
|
||||
return true;
|
||||
} );
|
||||
return FunctionString( std::string{ view.getName() }, std::move( funcParameters ),
|
||||
std::move( typeStringData ) );
|
||||
}
|
||||
|
||||
for ( size_t i = 0; i < parametersString.length(); ++i ) {
|
||||
|
||||
CharType c = parametersString[i];
|
||||
|
||||
if ( !stateParsingString ) {
|
||||
if ( c == '(' ) {
|
||||
parenDepth++;
|
||||
buffer += c;
|
||||
} else if ( c == ')' ) {
|
||||
if ( parenDepth == 0 )
|
||||
break;
|
||||
if ( parenDepth > 0 )
|
||||
parenDepth--;
|
||||
buffer += c;
|
||||
} else if ( c == ',' ) {
|
||||
if ( parenDepth == 0 ) {
|
||||
// Add param if we either accumulated characters or actively parsed an empty
|
||||
// string parameter.
|
||||
if ( !buffer.empty() || currentParamIsString ) {
|
||||
pushBufferToParams();
|
||||
}
|
||||
} else {
|
||||
buffer += c;
|
||||
}
|
||||
} else if ( c == '"' || c == '\'' ) {
|
||||
stateParsingString = true;
|
||||
quoteChar = c;
|
||||
if ( parenDepth == 0 && buffer.empty() ) {
|
||||
currentParamIsString = true;
|
||||
} else {
|
||||
buffer += c;
|
||||
}
|
||||
} else if ( c == ' ' ) {
|
||||
if ( buffer.empty() || currentParamIsString )
|
||||
continue;
|
||||
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::View FunctionString::parseView( std::string_view function ) {
|
||||
const std::size_t open = function.find( '(' );
|
||||
if ( open == std::string_view::npos )
|
||||
return {};
|
||||
const std::size_t close = function.rfind( ')' );
|
||||
if ( close == std::string_view::npos || close < open )
|
||||
return {};
|
||||
std::string_view name = String::trim( function.substr( 0, open ), " \t\n\r\f\v" );
|
||||
if ( name.empty() )
|
||||
return {};
|
||||
return View{ name, function.substr( open + 1, close - open - 1 ) };
|
||||
}
|
||||
|
||||
FunctionString FunctionString::parse( const std::string& function ) {
|
||||
return parse<std::string_view>( std::string_view{ function } );
|
||||
return parseOwned( function );
|
||||
}
|
||||
|
||||
FunctionString FunctionString::parse( std::string_view function ) {
|
||||
return parse<std::string_view>( function );
|
||||
return parseOwned( 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,
|
||||
|
||||
@@ -119,7 +119,8 @@ UnorderedMap<std::string, AnimationDefinition> AnimationDefinition::parseAnimati
|
||||
case PropertyId::AnimationTimingFunction: {
|
||||
TimingFunction tf( TimingFunction::parse( val ) );
|
||||
timingFunctions.emplace_back( tf.interpolation );
|
||||
timingFunctionParameters.emplace_back( tf.parameters );
|
||||
timingFunctionParameters.emplace_back( tf.parameters.begin(),
|
||||
tf.parameters.end() );
|
||||
break;
|
||||
}
|
||||
default:
|
||||
@@ -256,12 +257,29 @@ const String::HashType& AnimationDefinition::getId() 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;
|
||||
}
|
||||
|
||||
void AnimationDefinition::setTimingFunctionParameters(
|
||||
const std::vector<double>& timingFunctionParameters ) {
|
||||
mTimingFunctionParameters.assign( timingFunctionParameters.begin(),
|
||||
timingFunctionParameters.end() );
|
||||
mTimingFunctionParametersVectorValid = false;
|
||||
}
|
||||
|
||||
void AnimationDefinition::setTimingFunctionParameters(
|
||||
const TimingFunction::Parameters& timingFunctionParameters ) {
|
||||
mTimingFunctionParameters = timingFunctionParameters;
|
||||
mTimingFunctionParametersVectorValid = false;
|
||||
}
|
||||
|
||||
const AnimationDefinition::AnimationFillMode& AnimationDefinition::getFillMode() const {
|
||||
|
||||
@@ -34,6 +34,10 @@ const std::vector<const StyleSheetProperty*>& ElementDefinition::getTransitionPr
|
||||
return mTransitionProperties;
|
||||
}
|
||||
|
||||
const ComputedTransitions& ElementDefinition::getTransitions() const {
|
||||
return mTransitions;
|
||||
}
|
||||
|
||||
const std::vector<const StyleSheetProperty*>& ElementDefinition::getAnimationProperties() const {
|
||||
return mAnimationProperties;
|
||||
}
|
||||
@@ -81,6 +85,7 @@ void ElementDefinition::refresh() {
|
||||
}
|
||||
|
||||
resolveVariables();
|
||||
mTransitions = ComputedTransitions::parse( mTransitionProperties );
|
||||
|
||||
for ( auto& property : mProperties ) {
|
||||
// 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 ) {}
|
||||
|
||||
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::StyleSheetLength( const StyleSheetLength& val ) {
|
||||
@@ -382,9 +385,14 @@ StyleSheetLength& StyleSheetLength::operator=( const StyleSheetLength& val ) {
|
||||
|
||||
StyleSheetLength StyleSheetLength::fromString( const std::string& str, const Float& defaultValue,
|
||||
bool pxAsDp ) {
|
||||
return fromString( std::string_view{ str }, defaultValue, pxAsDp );
|
||||
}
|
||||
|
||||
StyleSheetLength StyleSheetLength::fromString( std::string_view str, const Float& defaultValue,
|
||||
bool pxAsDp ) {
|
||||
StyleSheetLength length;
|
||||
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 ) ) {
|
||||
Unit funcUnit = Unit::Px;
|
||||
@@ -470,9 +478,8 @@ bool StyleSheetLength::isFunctionString( std::string_view str ) {
|
||||
String::istartsWith( str, "max(" ) || String::istartsWith( str, "calc(" );
|
||||
}
|
||||
|
||||
bool StyleSheetLength::parseFunction( const std::string& str, Unit& outUnit, Arguments& outArgs ) {
|
||||
std::string_view sv( str );
|
||||
std::string_view trimmed = String::trim( sv );
|
||||
bool StyleSheetLength::parseFunction( std::string_view str, Unit& outUnit, Arguments& outArgs ) {
|
||||
std::string_view trimmed = String::trim( str );
|
||||
|
||||
if ( String::istartsWith( trimmed, "clamp(" ) ) {
|
||||
outUnit = Unit::Clamp;
|
||||
|
||||
@@ -11,7 +11,7 @@ using namespace EE::Math::easing;
|
||||
namespace EE { namespace UI { namespace CSS {
|
||||
|
||||
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 ) {
|
||||
if ( timingFunction != Ease::Interpolation::CubizBezier )
|
||||
return easingCb[timingFunction]( t, b, c, d );
|
||||
@@ -24,13 +24,12 @@ inline Float easingFn( const Ease::Interpolation& timingFunction,
|
||||
return t;
|
||||
}
|
||||
|
||||
void StyleSheetPropertyAnimation::tweenProperty( UIWidget* widget, const Float& normalizedProgress,
|
||||
const PropertyDefinition* property,
|
||||
const std::string& startValue,
|
||||
const std::string& endValue,
|
||||
const Ease::Interpolation& timingFunction,
|
||||
const std::vector<double> timingFunctionParameters,
|
||||
const Uint32& propertyIndex, const bool& isDone ) {
|
||||
void StyleSheetPropertyAnimation::tweenProperty(
|
||||
UIWidget* widget, const Float& normalizedProgress, const PropertyDefinition* property,
|
||||
const std::string& startValue, const std::string& endValue,
|
||||
const Ease::Interpolation& timingFunction,
|
||||
const TimingFunction::Parameters& timingFunctionParameters, const Uint32& propertyIndex,
|
||||
const bool& isDone ) {
|
||||
switch ( property->getType() ) {
|
||||
case PropertyType::NumberFloat:
|
||||
case PropertyType::NumberInt: {
|
||||
@@ -220,13 +219,27 @@ StyleSheetPropertyAnimation* StyleSheetPropertyAnimation::New(
|
||||
const Uint32& propertyIndex, const Time& duration, const Time& delay,
|
||||
const Ease::Interpolation& timingFunction, const std::vector<double>& timingFunctionParameters,
|
||||
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;
|
||||
animation.setDelay( delay );
|
||||
animation.setDuration( duration );
|
||||
animation.setTimingFunction( timingFunction );
|
||||
animation.setTimingFunctionParameters( timingFunctionParameters );
|
||||
return New( animation, property, { startValue, endValue }, { 0, 1 }, propertyIndex,
|
||||
animationOrigin );
|
||||
return eeNew( StyleSheetPropertyAnimation,
|
||||
( animation, property, SmallVector<std::string, 4>{ startValue, endValue },
|
||||
SmallVector<Float, 4>{ 0, 1 }, propertyIndex, animationOrigin ) );
|
||||
}
|
||||
|
||||
StyleSheetPropertyAnimation::StyleSheetPropertyAnimation( const AnimationDefinition& animation,
|
||||
@@ -237,8 +250,26 @@ StyleSheetPropertyAnimation::StyleSheetPropertyAnimation( const AnimationDefinit
|
||||
const AnimationOrigin& animationOrigin ) :
|
||||
mAnimation( animation ),
|
||||
mPropertyDef( propertyDef ),
|
||||
mStates( states ),
|
||||
mAnimationStepsTime( animationStepsTime ),
|
||||
mStates( std::make_move_iterator( states.begin() ), std::make_move_iterator( states.end() ) ),
|
||||
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() ),
|
||||
mPropertyIndex( propertyIndex ),
|
||||
mAnimationOrigin( animationOrigin ),
|
||||
@@ -321,15 +352,17 @@ Time StyleSheetPropertyAnimation::getTotalTime() {
|
||||
}
|
||||
|
||||
Action* StyleSheetPropertyAnimation::clone() const {
|
||||
return New( mAnimation, mPropertyDef, mStates, mAnimationStepsTime, mPropertyIndex,
|
||||
mAnimationOrigin );
|
||||
return eeNew( StyleSheetPropertyAnimation,
|
||||
( mAnimation, mPropertyDef, mStates, mAnimationStepsTime, mPropertyIndex,
|
||||
mAnimationOrigin ) );
|
||||
}
|
||||
|
||||
Action* StyleSheetPropertyAnimation::reverse() const {
|
||||
std::vector<std::string> vcopy( mStates );
|
||||
SmallVector<std::string, 4> vcopy( mStates );
|
||||
std::reverse( vcopy.begin(), vcopy.end() );
|
||||
return New( mAnimation, mPropertyDef, vcopy, mAnimationStepsTime, mPropertyIndex,
|
||||
mAnimationOrigin );
|
||||
return eeNew( StyleSheetPropertyAnimation,
|
||||
( mAnimation, mPropertyDef, std::move( vcopy ), mAnimationStepsTime,
|
||||
mPropertyIndex, mAnimationOrigin ) );
|
||||
}
|
||||
|
||||
const Uint32& StyleSheetPropertyAnimation::getPropertyIndex() const {
|
||||
@@ -371,7 +404,8 @@ void StyleSheetPropertyAnimation::onUpdate( const Time& ) {
|
||||
Float relativeProgress = curTime / relTime;
|
||||
tweenProperty( widget, relativeProgress, mPropertyDef, mStates[curPos - 1],
|
||||
mStates[curPos], mAnimation.getTimingFunction(),
|
||||
mAnimation.getTimingFunctionParameters(), mPropertyIndex, isDone() );
|
||||
mAnimation.getTimingFunctionParametersInline(), mPropertyIndex,
|
||||
isDone() );
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -480,16 +514,16 @@ void StyleSheetPropertyAnimation::prepareDirection() {
|
||||
}
|
||||
|
||||
void StyleSheetPropertyAnimation::reverseAnimation() {
|
||||
std::vector<std::string> reverseCopy( mStates );
|
||||
SmallVector<std::string, 4> reverseCopy( mStates );
|
||||
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() );
|
||||
for ( size_t i = 0; i < reverseTimes.size(); i++ ) {
|
||||
reverseTimes[i] = 1.f - reverseTimes[i];
|
||||
}
|
||||
mAnimationStepsTime = reverseTimes;
|
||||
mAnimationStepsTime = std::move( reverseTimes );
|
||||
}
|
||||
|
||||
}}} // namespace EE::UI::CSS
|
||||
|
||||
@@ -1,29 +1,45 @@
|
||||
#include <eepp/system/functionstring.hpp>
|
||||
#include <eepp/core/string.hpp>
|
||||
#include <eepp/ui/css/timingfunction.hpp>
|
||||
|
||||
using namespace EE::System;
|
||||
|
||||
namespace EE { namespace UI { namespace CSS {
|
||||
|
||||
TimingFunction TimingFunction::parse( std::string timingFunction ) {
|
||||
String::trimInPlace( timingFunction );
|
||||
String::toLowerInPlace( timingFunction );
|
||||
if ( timingFunction.find_first_of( '(' ) == std::string::npos ) {
|
||||
return { Ease::fromName( timingFunction ), {} };
|
||||
} else {
|
||||
TimingFunction tf;
|
||||
FunctionString func( FunctionString::parse( timingFunction ) );
|
||||
if ( !func.isEmpty() ) {
|
||||
tf.interpolation = Ease::fromName( timingFunction, Ease::Interpolation::None );
|
||||
const auto& params = func.getParameters();
|
||||
for ( const auto& param : params ) {
|
||||
double d;
|
||||
if ( String::fromString( d, param ) )
|
||||
tf.parameters.emplace_back( d );
|
||||
}
|
||||
}
|
||||
return tf;
|
||||
return parse( std::string_view{ timingFunction } );
|
||||
}
|
||||
|
||||
TimingFunction TimingFunction::parse( std::string_view timingFunction ) {
|
||||
timingFunction = String::trim( timingFunction, " \t\n\r\f\v" );
|
||||
SmallVector<char, 64> normalized;
|
||||
for ( char character : timingFunction )
|
||||
normalized.emplace_back( character >= 'A' && character <= 'Z' ? character + 32
|
||||
: character );
|
||||
timingFunction = std::string_view{ normalized.data(), normalized.size() };
|
||||
|
||||
TimingFunction result;
|
||||
const auto open = timingFunction.find( '(' );
|
||||
if ( open == std::string_view::npos ) {
|
||||
result.interpolation = Ease::fromName( timingFunction, Ease::Interpolation::None );
|
||||
return result;
|
||||
}
|
||||
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
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
#include <cctype>
|
||||
#include <eepp/core/string.hpp>
|
||||
#include <eepp/system/functionstring.hpp>
|
||||
#include <eepp/ui/css/propertydefinition.hpp>
|
||||
@@ -6,122 +7,247 @@
|
||||
|
||||
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(
|
||||
const std::vector<const StyleSheetProperty*>& styleSheetProperties ) {
|
||||
std::vector<std::string> properties;
|
||||
std::vector<Time> durations;
|
||||
std::vector<Time> delays;
|
||||
std::vector<Ease::Interpolation> timingFunctions;
|
||||
std::vector<std::vector<double>> timingFunctionParameters;
|
||||
SmallVector<std::string, 4> properties;
|
||||
SmallVector<Time, 4> durations;
|
||||
SmallVector<Time, 4> delays;
|
||||
SmallVector<TimingFunction, 4> timingFunctions;
|
||||
TransitionsMap transitions;
|
||||
|
||||
for ( auto& prop : styleSheetProperties ) {
|
||||
if ( prop->getPropertyDefinition() == NULL )
|
||||
for ( const StyleSheetProperty* property : styleSheetProperties ) {
|
||||
if ( nullptr == property || nullptr == property->getPropertyDefinition() )
|
||||
continue;
|
||||
|
||||
const PropertyDefinition* propDef = prop->getPropertyDefinition();
|
||||
|
||||
if ( propDef->getPropertyId() == PropertyId::Transition ) {
|
||||
String::splitCb(
|
||||
[&transitions, prop]( std::string_view str ) {
|
||||
auto strTransition = String::trim( str );
|
||||
auto splitTransition =
|
||||
String::split( std::string{ strTransition }, " ", "", "()" );
|
||||
|
||||
if ( !splitTransition.empty() ) {
|
||||
TransitionDefinition transitionDef;
|
||||
|
||||
if ( splitTransition.size() >= 2 ) {
|
||||
std::string property = String::trim( splitTransition[0] );
|
||||
String::toLowerInPlace( property );
|
||||
|
||||
Time duration =
|
||||
StyleSheetProperty( prop->getName(),
|
||||
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;
|
||||
}
|
||||
switch ( property->getPropertyDefinition()->getPropertyId() ) {
|
||||
case PropertyId::Transition:
|
||||
forEachCommaItem( property->getValue(), [&]( std::string_view item ) {
|
||||
auto tokens = splitTransitionTokens( item );
|
||||
if ( tokens.size() < 2 )
|
||||
return;
|
||||
TransitionDefinition transition;
|
||||
transition.property = lowerString( tokens[0] );
|
||||
transition.duration = parseTime( tokens[1] );
|
||||
if ( tokens.size() >= 3 ) {
|
||||
TimingFunction timing = TimingFunction::parse( tokens[2] );
|
||||
transition.timingFunction = timing.interpolation;
|
||||
transition.timingFunctionParameters.assign( timing.parameters.begin(),
|
||||
timing.parameters.end() );
|
||||
if ( transition.timingFunction == Ease::None && tokens.size() == 3 )
|
||||
transition.delay = parseTime( tokens[2] );
|
||||
else if ( tokens.size() >= 4 )
|
||||
transition.delay = parseTime( tokens[3] );
|
||||
}
|
||||
return true;
|
||||
},
|
||||
prop->getValue(), ",", ",", "()" );
|
||||
} else if ( propDef->getPropertyId() == PropertyId::TransitionDuration ) {
|
||||
auto strDurations = String::split( prop->getValue(), ',' );
|
||||
|
||||
for ( auto dit = strDurations.begin(); dit != strDurations.end(); ++dit ) {
|
||||
std::string duration( String::trim( *dit ) );
|
||||
String::toLowerInPlace( duration );
|
||||
durations.push_back( StyleSheetProperty( prop->getName(), duration ).asTime() );
|
||||
}
|
||||
} else if ( propDef->getPropertyId() == PropertyId::TransitionDelay ) {
|
||||
auto strDelays = String::split( prop->getValue(), ',' );
|
||||
|
||||
for ( auto dit = strDelays.begin(); dit != strDelays.end(); ++dit ) {
|
||||
std::string delay( String::trim( *dit ) );
|
||||
String::toLowerInPlace( delay );
|
||||
delays.push_back( StyleSheetProperty( prop->getName(), delay ).asTime() );
|
||||
}
|
||||
} else if ( propDef->getPropertyId() == PropertyId::TransitionTimingFunction ) {
|
||||
auto strTimingFuncs = String::split( prop->getValue(), ",", "", "()" );
|
||||
|
||||
for ( auto dit = strTimingFuncs.begin(); dit != strTimingFuncs.end(); ++dit ) {
|
||||
TimingFunction tf( TimingFunction::parse( *dit ) );
|
||||
timingFunctions.emplace_back( tf.interpolation );
|
||||
timingFunctionParameters.emplace_back( tf.parameters );
|
||||
}
|
||||
} else if ( propDef->getPropertyId() == PropertyId::TransitionProperty ) {
|
||||
auto strProperties = String::split( prop->getValue(), ',' );
|
||||
|
||||
for ( auto dit = strProperties.begin(); dit != strProperties.end(); ++dit ) {
|
||||
std::string property( String::trim( *dit ) );
|
||||
String::toLowerInPlace( property );
|
||||
properties.push_back( property );
|
||||
}
|
||||
transitions[transition.property] = std::move( 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( lowerString( item ) );
|
||||
} );
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
for ( size_t i = 0; i < properties.size(); i++ ) {
|
||||
const std::string& property = properties.at( i );
|
||||
TransitionDefinition transitionDef;
|
||||
|
||||
transitionDef.property = property;
|
||||
|
||||
for ( std::size_t i = 0; i < properties.size(); ++i ) {
|
||||
TransitionDefinition transition;
|
||||
transition.property = properties[i];
|
||||
if ( !durations.empty() )
|
||||
transitionDef.duration = durations[i % durations.size()];
|
||||
|
||||
transition.duration = durations[i % durations.size()];
|
||||
if ( !delays.empty() )
|
||||
transitionDef.delay = delays[i % delays.size()];
|
||||
|
||||
transition.delay = delays[i % delays.size()];
|
||||
if ( !timingFunctions.empty() ) {
|
||||
size_t idx = !delays.empty() ? i % delays.size() : 0;
|
||||
transitionDef.timingFunction = timingFunctions[idx];
|
||||
transitionDef.timingFunctionParameters = timingFunctionParameters[idx];
|
||||
const TimingFunction& timing = timingFunctions[i % timingFunctions.size()];
|
||||
transition.timingFunction = timing.interpolation;
|
||||
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;
|
||||
|
||||
@@ -60,14 +60,7 @@ static bool hasDefiniteCSSHeight( UIWidget* widget ) {
|
||||
|
||||
static CSSBaselineAlignValue parseBaselineAlign( UIHTMLWidget* widget,
|
||||
const StyleSheetProperty& property ) {
|
||||
std::string_view val = property.value();
|
||||
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 );
|
||||
std::string_view val = String::trim( std::string_view{ property.value() }, " \t\n\r\f\v" );
|
||||
if ( val.empty() )
|
||||
return {};
|
||||
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
#include <array>
|
||||
#include <cctype>
|
||||
#include <eepp/core/core.hpp>
|
||||
#include <eepp/graphics/drawableresource.hpp>
|
||||
#include <eepp/graphics/image.hpp>
|
||||
@@ -19,6 +21,36 @@ using namespace EE::Math::easing;
|
||||
|
||||
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 ) {
|
||||
auto parts = String::split( text, ' ' );
|
||||
|
||||
@@ -796,11 +828,9 @@ Sizef UINodeDrawable::LayerDrawable::calcDrawableSize( const std::string& drawab
|
||||
Scale1 = eemax( Scale1, Scale2 );
|
||||
size = Sizef( drawableSize.getWidth() * Scale1, drawableSize.getHeight() * Scale1 );
|
||||
} else {
|
||||
std::vector<std::string> sizePart = String::split( drawableSizeEq, ' ' );
|
||||
|
||||
if ( sizePart.size() == 1 ) {
|
||||
sizePart.push_back( "auto" );
|
||||
}
|
||||
auto sizePart = splitWhitespace( drawableSizeEq );
|
||||
if ( sizePart.size() == 1 )
|
||||
sizePart.values[sizePart.count++] = "auto";
|
||||
|
||||
if ( sizePart.size() == 2 ) {
|
||||
if ( sizePart[0] == "auto" && sizePart[1] == "auto" ) {
|
||||
@@ -841,24 +871,22 @@ Sizef UINodeDrawable::LayerDrawable::calcDrawableSize( const std::string& drawab
|
||||
return size;
|
||||
}
|
||||
|
||||
Vector2f UINodeDrawable::LayerDrawable::calcPosition( std::string positionXEq,
|
||||
std::string positionYEq ) {
|
||||
Vector2f UINodeDrawable::LayerDrawable::calcPosition( const std::string& positionXEq,
|
||||
const std::string& positionYEq ) {
|
||||
Vector2f position( Vector2f::Zero );
|
||||
|
||||
if ( positionXEq.empty() )
|
||||
positionXEq = "center";
|
||||
|
||||
if ( positionYEq.empty() )
|
||||
positionYEq = "center";
|
||||
|
||||
auto posX = String::split( positionXEq, ' ' );
|
||||
auto posY = String::split( positionYEq, ' ' );
|
||||
const std::string_view positionX =
|
||||
positionXEq.empty() ? std::string_view{ "center" } : std::string_view{ positionXEq };
|
||||
const std::string_view positionY =
|
||||
positionYEq.empty() ? std::string_view{ "center" } : std::string_view{ positionYEq };
|
||||
auto posX = splitWhitespace( positionX );
|
||||
auto posY = splitWhitespace( positionY );
|
||||
|
||||
if ( posX.empty() || posY.empty() )
|
||||
return position;
|
||||
|
||||
bool needsRoundingX = positionXEq.back() == '%';
|
||||
bool needsRoundingY = positionYEq.back() == '%';
|
||||
bool needsRoundingX = positionX.back() == '%';
|
||||
bool needsRoundingY = positionY.back() == '%';
|
||||
|
||||
Sizef ownerSize = mContainer->getOwner()->getPixelsSize();
|
||||
Float refWidth = ownerSize.getWidth();
|
||||
|
||||
+33
-28
@@ -135,8 +135,8 @@ void UIStyle::setStyleSheetProperties( const CSS::StyleSheetProperties& properti
|
||||
}
|
||||
|
||||
bool UIStyle::hasTransition( const std::string& propertyName ) {
|
||||
return mTransitions.find( propertyName ) != mTransitions.end() ||
|
||||
mTransitions.find( "all" ) != mTransitions.end();
|
||||
return mDefinition && mDefinition->getTransitions().get( String::hashToLower(
|
||||
propertyName.c_str(), static_cast<Int64>( propertyName.size() ) ) );
|
||||
}
|
||||
|
||||
StyleSheetPropertyAnimation* UIStyle::getAnimation( const PropertyDefinition* propertyDef ) {
|
||||
@@ -160,15 +160,20 @@ bool UIStyle::hasAnimation( const PropertyDefinition* propertyDef ) {
|
||||
}
|
||||
|
||||
TransitionDefinition UIStyle::getTransition( const std::string& propertyName ) {
|
||||
auto propertyTransitionIt = mTransitions.find( propertyName );
|
||||
|
||||
if ( propertyTransitionIt != mTransitions.end() ) {
|
||||
return propertyTransitionIt->second;
|
||||
} else if ( ( propertyTransitionIt = mTransitions.find( "all" ) ) != mTransitions.end() ) {
|
||||
return propertyTransitionIt->second;
|
||||
}
|
||||
|
||||
return TransitionDefinition();
|
||||
TransitionDefinition transition;
|
||||
if ( !mDefinition )
|
||||
return transition;
|
||||
const auto* computed = mDefinition->getTransitions().get(
|
||||
String::hashToLower( propertyName.c_str(), static_cast<Int64>( propertyName.size() ) ) );
|
||||
if ( !computed )
|
||||
return transition;
|
||||
transition.property = propertyName;
|
||||
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 {
|
||||
@@ -339,9 +344,9 @@ void UIStyle::setVariableFromValue( StyleSheetProperty* property, const std::str
|
||||
for ( int depth = 0; depth < maxDepth; depth++ ) {
|
||||
bool changed = false;
|
||||
if ( !property->getVarCache().empty() ) {
|
||||
auto varCacheCopy = property->getVarCache();
|
||||
for ( auto& var : varCacheCopy ) {
|
||||
for ( auto& val : var.variableList ) {
|
||||
const auto& varCache = property->getVarCache();
|
||||
for ( const auto& var : varCache ) {
|
||||
for ( const auto& val : var.variableList ) {
|
||||
StyleSheetVariable variable( getVariable( val ) );
|
||||
if ( !variable.isEmpty() ) {
|
||||
String::replaceAll( newValue, var.definition, variable.getValue() );
|
||||
@@ -432,11 +437,6 @@ void UIStyle::onStateChange() {
|
||||
|
||||
mWidget->beginAttributesTransaction();
|
||||
|
||||
if ( nullptr != mDefinition && !mDefinition->getTransitionProperties().empty() ) {
|
||||
mTransitions = TransitionDefinition::parseTransitionProperties(
|
||||
mDefinition->getTransitionProperties() );
|
||||
}
|
||||
|
||||
for ( PropertyId prop : changedProperties ) {
|
||||
std::optional<StyleSheetProperty> 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 =
|
||||
mWidget->getActionsByTag( propertyDefinition->getId() );
|
||||
std::vector<Action*> removeTransitions;
|
||||
SmallVector<Action*, 4> previousTransitions;
|
||||
mWidget->getActionsByTag( propertyDefinition->getId(), previousTransitions );
|
||||
SmallVector<Action*, 4> removeTransitions;
|
||||
StyleSheetPropertyAnimation* prevTransition = NULL;
|
||||
|
||||
if ( !previousTransitions.empty() ) {
|
||||
@@ -707,9 +713,9 @@ void UIStyle::applyStyleSheetProperty( const StyleSheetProperty& originalPropert
|
||||
currentProgress = eemin( 1.f, currentProgress );
|
||||
if ( 0.f != currentProgress ) {
|
||||
elapsed = Milliseconds( ( 1.f - currentProgress ) *
|
||||
transitionInfo.getDuration().asMilliseconds() );
|
||||
transitionInfo->duration.asMilliseconds() );
|
||||
} else {
|
||||
elapsed = transitionInfo.getDuration();
|
||||
elapsed = transitionInfo->duration;
|
||||
}
|
||||
startValue = prevTransition->getEndValue();
|
||||
} else if ( startValue == prevTransition->getEndValue() ) {
|
||||
@@ -723,9 +729,8 @@ void UIStyle::applyStyleSheetProperty( const StyleSheetProperty& originalPropert
|
||||
|
||||
StyleSheetPropertyAnimation* newTransition = StyleSheetPropertyAnimation::New(
|
||||
propertyDefinition, startValue, property->getValue(), property->getIndex(),
|
||||
transitionInfo.getDuration(), transitionInfo.getDelay(),
|
||||
transitionInfo.getTimingFunction(), transitionInfo.getTimingFunctionParameters(),
|
||||
AnimationOrigin::Transition );
|
||||
transitionInfo->duration, transitionInfo->delay, transitionInfo->timingFunction,
|
||||
transitionInfo->timingFunctionParameters, AnimationOrigin::Transition );
|
||||
newTransition->setElapsed( elapsed );
|
||||
newTransition->setTag( propertyDefinition->getId() );
|
||||
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"(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