diff --git a/bin/unit_tests/assets/fontrendering/eepp-ui-text-test.webp b/bin/unit_tests/assets/fontrendering/eepp-ui-text-test.webp new file mode 100644 index 000000000..ce72650c7 Binary files /dev/null and b/bin/unit_tests/assets/fontrendering/eepp-ui-text-test.webp differ diff --git a/bin/unit_tests/assets/layouts/ui_text_test.xml b/bin/unit_tests/assets/layouts/ui_text_test.xml new file mode 100644 index 000000000..f74a33fae --- /dev/null +++ b/bin/unit_tests/assets/layouts/ui_text_test.xml @@ -0,0 +1,65 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/include/eepp/ui/models/modelselection.hpp b/include/eepp/ui/models/modelselection.hpp index 6a9a86a11..2ae6f38ef 100644 --- a/include/eepp/ui/models/modelselection.hpp +++ b/include/eepp/ui/models/modelselection.hpp @@ -2,6 +2,8 @@ #define EE_UI_MODEL_MODELSELECTION_HPP #include +#include +#include #include #include @@ -17,12 +19,20 @@ class EE_API ModelSelection { public: ModelSelection( UIAbstractView* view ) : mView( view ) {} - int size() const { return mIndexes.size(); } - bool isEmpty() const { return mIndexes.empty(); } + int size() const { + Lock l( mMutex ); + return mIndexes.size(); + } + bool isEmpty() const { + Lock l( mMutex ); + return mIndexes.empty(); + } bool contains( const ModelIndex& index ) const { + Lock l( mMutex ); return std::find( mIndexes.begin(), mIndexes.end(), index ) != mIndexes.end(); } bool containsRow( int row ) const { + Lock l( mMutex ); for ( auto& index : mIndexes ) { if ( index.row() == row ) return true; @@ -48,13 +58,16 @@ class EE_API ModelSelection { } std::vector indexes() const { + Lock l( mMutex ); std::vector indexes; + indexes.reserve( mIndexes.size() ); for ( auto& index : mIndexes ) indexes.push_back( index ); return indexes; } ModelIndex first() const { + Lock l( mMutex ); if ( mIndexes.empty() ) return {}; return *mIndexes.begin(); @@ -79,6 +92,8 @@ class EE_API ModelSelection { bool mDisableNotify{ false }; bool mNotifyPending{ false }; void notifySelectionChanged(); + + mutable Mutex mMutex; }; }}} // namespace EE::UI::Models diff --git a/src/eepp/ui/abstract/uiabstractview.cpp b/src/eepp/ui/abstract/uiabstractview.cpp index b68c0f2ad..3cfe3cb0b 100644 --- a/src/eepp/ui/abstract/uiabstractview.cpp +++ b/src/eepp/ui/abstract/uiabstractview.cpp @@ -151,6 +151,12 @@ void UIAbstractView::onModelSelectionChange() { } void UIAbstractView::notifySelectionChange() { + if ( !Engine::isMainThread() ) { + debounce( [this] { notifySelectionChange(); }, Time::Zero, + String::hash( "notifySelectionChange" ) ); + return; + } + onModelSelectionChange(); sendCommonEvent( Event::OnSelectionChanged ); if ( mOnSelectionChange ) diff --git a/src/eepp/ui/models/modelselection.cpp b/src/eepp/ui/models/modelselection.cpp index ff6b81071..d93081497 100644 --- a/src/eepp/ui/models/modelselection.cpp +++ b/src/eepp/ui/models/modelselection.cpp @@ -5,6 +5,7 @@ namespace EE { namespace UI { namespace Models { void ModelSelection::removeAllMatching( std::function filter ) { + Lock l( mMutex ); std::vector notMatching; for ( auto& index : mIndexes ) { if ( !filter( index ) ) @@ -18,6 +19,7 @@ void ModelSelection::removeAllMatching( std::function void ModelSelection::set( const ModelIndex& index ) { eeASSERT( index.isValid() ); + Lock l( mMutex ); if ( mIndexes.size() == 1 && contains( index ) ) return; mIndexes.clear(); @@ -30,6 +32,7 @@ void ModelSelection::set( const std::vector& indexes, bool notify ) for ( auto& index : indexes ) eeASSERT( index.isValid() ); #endif + Lock l( mMutex ); mIndexes.clear(); mIndexes = indexes; if ( notify ) @@ -38,6 +41,7 @@ void ModelSelection::set( const std::vector& indexes, bool notify ) void ModelSelection::add( const ModelIndex& index ) { eeASSERT( index.isValid() ); + Lock l( mMutex ); auto contains = std::find( mIndexes.begin(), mIndexes.end(), index ); if ( contains == mIndexes.end() ) return; @@ -47,6 +51,7 @@ void ModelSelection::add( const ModelIndex& index ) { void ModelSelection::toggle( const ModelIndex& index ) { eeASSERT( index.isValid() ); + Lock l( mMutex ); auto contains = std::find( mIndexes.begin(), mIndexes.end(), index ); if ( contains != mIndexes.end() ) mIndexes.erase( contains ); @@ -57,6 +62,7 @@ void ModelSelection::toggle( const ModelIndex& index ) { bool ModelSelection::remove( const ModelIndex& index ) { eeASSERT( index.isValid() ); + Lock l( mMutex ); auto contains = std::find( mIndexes.begin(), mIndexes.end(), index ); if ( contains == mIndexes.end() ) return false; @@ -66,6 +72,7 @@ bool ModelSelection::remove( const ModelIndex& index ) { } void ModelSelection::clear( bool notify ) { + Lock l( mMutex ); if ( mIndexes.empty() ) return; mIndexes.clear(); @@ -74,6 +81,7 @@ void ModelSelection::clear( bool notify ) { } void ModelSelection::notifySelectionChanged() { + Lock l( mMutex ); if ( !mDisableNotify ) { mView->notifySelectionChange(); mNotifyPending = false; diff --git a/src/tests/unit_tests/fontrendering.cpp b/src/tests/unit_tests/fontrendering.cpp index ba92fcf60..e11ef2feb 100644 --- a/src/tests/unit_tests/fontrendering.cpp +++ b/src/tests/unit_tests/fontrendering.cpp @@ -795,3 +795,33 @@ UTEST( FontRendering, textSetFillColor ) { Engine::destroySingleton(); } + +UTEST( FontRendering, UITextTest ) { + const auto runTest = [&]() { + UIApplication app( + WindowSettings( 1024, 650, "eepp - UI Text Test", WindowStyle::Default, + WindowBackend::Default, 32, {}, 1, false, true ), + UIApplication::Settings( Sys::getProcessPath() + ".." + FileSystem::getOSSlash(), 1 ) ); + FileSystem::changeWorkingDirectory( Sys::getProcessPath() ); + app.getUI()->loadLayoutFromFile( "assets/layouts/ui_text_test.xml" ); + SceneManager::instance()->update(); + SceneManager::instance()->draw(); + compareImages( utest_state, utest_result, app.getWindow(), "eepp-ui-text-test" ); + }; + + UTEST_PRINT_STEP( "Text Shaper disabled" ); + { + BoolScopedOp op( Text::TextShaperEnabled, false ); + runTest(); + } + + UTEST_PRINT_STEP( "Text Shaper enabled" ); + { + BoolScopedOp op( Text::TextShaperEnabled, true ); + runTest(); + + UTEST_PRINT_STEP( "Text Shaper enabled w/o optimizations" ); + BoolScopedOp op2( Text::TextShaperOptimizations, false ); + runTest(); + } +}