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();
+ }
+}