diff --git a/include/eepp/system/resourcemanager.hpp b/include/eepp/system/resourcemanager.hpp index 43dd7b639..cd72f795d 100644 --- a/include/eepp/system/resourcemanager.hpp +++ b/include/eepp/system/resourcemanager.hpp @@ -3,6 +3,8 @@ #include #include +#include +#include #include #include @@ -81,16 +83,14 @@ template class ResourceManager { pred( res ); } - template - T* findIf( Predicate pred ) const { + template T* findIf( Predicate pred ) const { for ( const auto& res : mResources ) if ( pred( res ) ) return res.second; return nullptr; } - template - T* findIf( Predicate pred ) { + template T* findIf( Predicate pred ) { for ( auto& res : mResources ) if ( pred( res ) ) return res.second; @@ -98,6 +98,7 @@ template class ResourceManager { } protected: + Mutex mMutex; UnorderedMap mResources; bool mIsDestroying; }; @@ -115,16 +116,19 @@ template ResourceManager::~ResourceManager() { template void ResourceManager::destroy() { mIsDestroying = true; - for ( auto& it : mResources ) { - T* res = it.second; - eeSAFE_DELETE( res ); + { + Lock l( mMutex ); + for ( auto& it : mResources ) { + T* res = it.second; + eeSAFE_DELETE( res ); + } + mResources.clear(); } - mResources.clear(); - mIsDestroying = false; } +// This is not thread safe template UnorderedMap& ResourceManager::getResources() { return mResources; } @@ -132,6 +136,7 @@ template UnorderedMap& ResourceManager::getRe template T* ResourceManager::add( T* resource ) { if ( NULL != resource ) { if ( !existsId( resource->getId() ) ) { + Lock l( mMutex ); mResources[resource->getId()] = resource; return resource; @@ -147,6 +152,7 @@ template T* ResourceManager::add( T* resource ) { return add( resource ); } + Lock l( mMutex ); mResources[resource->getId()] = resource; return resource; } @@ -155,8 +161,11 @@ template T* ResourceManager::add( T* resource ) { template bool ResourceManager::remove( T* resource, bool remove ) { if ( NULL != resource ) { - mResources.erase( resource->getId() ); + { + Lock l( mMutex ); + mResources.erase( resource->getId() ); + } if ( remove ) eeSAFE_DELETE( resource ); @@ -179,6 +188,7 @@ template bool ResourceManager::exists( const std::string& name ) { } template bool ResourceManager::existsId( const String::HashType& id ) { + Lock l( mMutex ); return mResources.find( id ) != mResources.end(); } @@ -187,17 +197,20 @@ template T* ResourceManager::getByName( const std::string& name ) { } template T* ResourceManager::getById( const String::HashType& id ) { + Lock l( mMutex ); auto it = mResources.find( id ); return it != mResources.end() ? it->second : nullptr; } template void ResourceManager::printNames() { + Lock l( mMutex ); for ( auto& it : mResources ) { eePRINTL( "'%s'", it.second->getName().c_str() ); } } template Uint32 ResourceManager::getCount() { + Lock l( mMutex ); return (Uint32)mResources.size(); } @@ -274,6 +287,7 @@ template class ResourceManagerMulti { const bool& isDestroying() const; protected: + Mutex mMutex; std::unordered_multimap mResources; bool mIsDestroying; }; @@ -291,12 +305,15 @@ template ResourceManagerMulti::~ResourceManagerMulti() { template void ResourceManagerMulti::destroy() { mIsDestroying = true; - for ( auto& it : mResources ) { - T* res = it.second; - eeSAFE_DELETE( res ); - } + { + Lock l( mMutex ); + for ( auto& it : mResources ) { + T* res = it.second; + eeSAFE_DELETE( res ); + } - mResources.clear(); + mResources.clear(); + } mIsDestroying = false; } @@ -308,6 +325,7 @@ std::unordered_multimap& ResourceManagerMulti::getResou template T* ResourceManagerMulti::add( T* resource ) { if ( NULL != resource ) { + Lock l( mMutex ); mResources.insert( std::pair( resource->getId(), resource ) ); return resource; } @@ -316,14 +334,17 @@ template T* ResourceManagerMulti::add( T* resource ) { template bool ResourceManagerMulti::remove( T* resource, bool remove ) { if ( NULL != resource ) { - auto range = mResources.equal_range( resource->getId() ); - auto it = range.first; - while ( it != range.second ) { - if ( it->second == resource ) { - mResources.erase( it ); - break; + { + Lock l( mMutex ); + auto range = mResources.equal_range( resource->getId() ); + auto it = range.first; + while ( it != range.second ) { + if ( it->second == resource ) { + mResources.erase( it ); + break; + } + it++; } - it++; } if ( remove ) @@ -350,6 +371,7 @@ template bool ResourceManagerMulti::exists( const std::string& name } template bool ResourceManagerMulti::existsId( const String::HashType& id ) { + Lock l( mMutex ); return mResources.find( id ) != mResources.end(); } @@ -358,21 +380,25 @@ template T* ResourceManagerMulti::getByName( const std::string& nam } template T* ResourceManagerMulti::getById( const String::HashType& id ) { + Lock l( mMutex ); auto it = mResources.find( id ); return it != mResources.end() ? it->second : nullptr; } template void ResourceManagerMulti::printNames() { + Lock l( mMutex ); for ( auto& it : mResources ) { eePRINTL( "'%s'", it.second->getName().c_str() ); } } template Uint32 ResourceManagerMulti::getCount() { + Lock l( mMutex ); return (Uint32)mResources.size(); } template Uint32 ResourceManagerMulti::getCount( const String::HashType& id ) { + Lock l( mMutex ); return mResources.count( id ); } diff --git a/src/eepp/ui/css/stylesheetspecification.cpp b/src/eepp/ui/css/stylesheetspecification.cpp index f47aaca6b..2929d881c 100644 --- a/src/eepp/ui/css/stylesheetspecification.cpp +++ b/src/eepp/ui/css/stylesheetspecification.cpp @@ -981,7 +981,7 @@ void StyleSheetSpecification::registerDefaultShorthandParsers() { String::isNumber( tok[0] ) || tok[0] == '-' || tok[0] == '.' || tok[0] == '+' ) { positionStr += tok + " "; - } else if ( Color::isColorString( tok ) ) { + } else { int pos = getIndexEndingWith( propNames, "-color" ); if ( pos != -1 ) properties.emplace_back( StyleSheetProperty( propNames[pos], value ) ); diff --git a/src/eepp/ui/uidropdownlist.cpp b/src/eepp/ui/uidropdownlist.cpp index eb0678405..e5f424e84 100644 --- a/src/eepp/ui/uidropdownlist.cpp +++ b/src/eepp/ui/uidropdownlist.cpp @@ -21,6 +21,8 @@ UIDropDownList* UIDropDownList::New() { UIDropDownList::UIDropDownList( const std::string& tag ) : UIDropDown( tag ), mListBox( NULL ) { applyDefaultTheme(); + mFlags |= UI_LOADS_ITS_CHILDREN; + mListBox = UIListBox::NewWithTag( mTag + "::listbox" ); mListBox->setSize( getSize().getWidth(), mStyleConfig.MaxNumVisibleItems * getSize().getHeight() ); diff --git a/src/eepp/ui/uilinearlayout.cpp b/src/eepp/ui/uilinearlayout.cpp index 684fc87c6..7381626a5 100644 --- a/src/eepp/ui/uilinearlayout.cpp +++ b/src/eepp/ui/uilinearlayout.cpp @@ -259,15 +259,18 @@ void UILinearLayout::packVertical() { setInternalPixelsHeight( h ); } - if ( getLayoutWidthPolicy() == SizePolicy::WrapContent && getPixelsSize().getWidth() != maxX ) { - if ( !( 0 != getLayoutWeight() && getParent()->isType( UI_TYPE_LINEAR_LAYOUT ) && - getParent()->asType()->getOrientation() == - UIOrientation::Horizontal ) ) { - if ( mMinWidthEq.empty() || PixelDensity::dpToPx( mMinSize.getWidth() ) < maxX ) { - setInternalPixelsWidth( maxX ); - mPacking = false; - packVertical(); - notifyLayoutAttrChangeParent(); + if ( getLayoutWidthPolicy() == SizePolicy::WrapContent ) { + Float w = fitMinMaxSizePx( Sizef( maxX, 0 ) ).getWidth(); + if ( getPixelsSize().getWidth() != w ) { + if ( !( 0 != getLayoutWeight() && getParent()->isType( UI_TYPE_LINEAR_LAYOUT ) && + getParent()->asType()->getOrientation() == + UIOrientation::Horizontal ) ) { + if ( mMinWidthEq.empty() || PixelDensity::dpToPx( mMinSize.getWidth() ) < maxX ) { + setInternalPixelsWidth( maxX ); + mPacking = false; + packVertical(); + notifyLayoutAttrChangeParent(); + } } } } @@ -383,16 +386,18 @@ void UILinearLayout::packHorizontal() { setInternalPixelsWidth( w ); } - if ( getLayoutHeightPolicy() == SizePolicy::WrapContent && - getPixelsSize().getHeight() != maxY ) { - if ( !( 0 != getLayoutWeight() && getParent()->isType( UI_TYPE_LINEAR_LAYOUT ) && - getParent()->asType()->getOrientation() == - UIOrientation::Vertical ) ) { - if ( mMinHeightEq.empty() || PixelDensity::dpToPx( mMinSize.getHeight() ) < maxY ) { - setInternalPixelsHeight( maxY ); - mPacking = false; - packHorizontal(); - notifyLayoutAttrChangeParent(); + if ( getLayoutHeightPolicy() == SizePolicy::WrapContent ) { + Float h = fitMinMaxSizePx( Sizef( 0, maxY ) ).getHeight(); + if ( getPixelsSize().getHeight() != h ) { + if ( !( 0 != getLayoutWeight() && getParent()->isType( UI_TYPE_LINEAR_LAYOUT ) && + getParent()->asType()->getOrientation() == + UIOrientation::Vertical ) ) { + if ( mMinHeightEq.empty() || PixelDensity::dpToPx( mMinSize.getHeight() ) < maxY ) { + setInternalPixelsHeight( maxY ); + mPacking = false; + packHorizontal(); + notifyLayoutAttrChangeParent(); + } } } } diff --git a/src/eepp/ui/uinode.cpp b/src/eepp/ui/uinode.cpp index f2dea8b90..795c7200b 100644 --- a/src/eepp/ui/uinode.cpp +++ b/src/eepp/ui/uinode.cpp @@ -138,16 +138,10 @@ const Vector2f& UINode::getPixelsPosition() const { } void UINode::setInternalSize( const Sizef& size ) { - Sizef s( size ); - - if ( s.x < mMinSize.x ) - s.x = mMinSize.x; - - if ( s.y < mMinSize.y ) - s.y = mMinSize.y; + Sizef s( fitMinMaxSizeDp( size ) ); if ( s != mDpSize ) { - mDpSize = size; + mDpSize = s; mSize = PixelDensity::dpToPx( s ); mNodeFlags |= NODE_FLAG_POLYGON_DIRTY; updateCenter(); @@ -158,14 +152,7 @@ void UINode::setInternalSize( const Sizef& size ) { } void UINode::setInternalPixelsSize( const Sizef& size ) { - Sizef s( size ); - Sizef pMinSize( PixelDensity::dpToPx( mMinSize ) ); - - if ( s.x < pMinSize.x ) - s.x = pMinSize.x; - - if ( s.y < pMinSize.y ) - s.y = pMinSize.y; + Sizef s( fitMinMaxSizePx( size ) ); if ( s != mSize ) { mDpSize = PixelDensity::pxToDp( s ).ceil(); diff --git a/src/eepp/ui/uirichtext.cpp b/src/eepp/ui/uirichtext.cpp index f08b9f777..785570bed 100644 --- a/src/eepp/ui/uirichtext.cpp +++ b/src/eepp/ui/uirichtext.cpp @@ -475,14 +475,28 @@ void UIRichText::onAlphaChange() { void UIRichText::rebuildRichText( RichText& richText, IntrinsicMode mode ) { richText.clear(); - // Calculate maximum layout width for the RichText block Float maxWidth = mSize.getWidth() - mPaddingPx.Left - mPaddingPx.Right; if ( maxWidth < 0 ) maxWidth = 0; + + Float mw = 0.f; + if ( !mMaxWidthEq.empty() ) { + mw = getMaxSizePx().getWidth() - mPaddingPx.Left - mPaddingPx.Right; + if ( mw < 0 ) mw = 0.f; + } + if ( mWidthPolicy == SizePolicy::WrapContent || mode != IntrinsicMode::None ) { - richText.setMaxWidth( 0.f ); // Let it grow unbounded to query text bounds later + if ( mode == IntrinsicMode::None && !mMaxWidthEq.empty() ) { + richText.setMaxWidth( mw ); + } else { + richText.setMaxWidth( 0.f ); // Let it grow unbounded to query text bounds later + } } else { - richText.setMaxWidth( maxWidth ); + if ( !mMaxWidthEq.empty() && mw < maxWidth ) { + richText.setMaxWidth( mw ); + } else { + richText.setMaxWidth( maxWidth ); + } } auto processWidget = [&]( UIWidget* widget, auto& processWidgetRef ) -> void { @@ -706,16 +720,26 @@ void UIRichText::updateLayout() { positionChildren(); + Float totW = mSize.getWidth(); if ( mWidthPolicy == SizePolicy::WrapContent ) { - setInternalPixelsWidth( mRichText.getSize().getWidth() + mPaddingPx.Left + - mPaddingPx.Right ); + totW = mRichText.getSize().getWidth() + mPaddingPx.Left + mPaddingPx.Right; + if ( !mMaxWidthEq.empty() && totW > getMaxSizePx().getWidth() ) + setClipType( ClipType::ContentBox ); } + if ( totW != mSize.getWidth() || mWidthPolicy == SizePolicy::WrapContent ) + setInternalPixelsWidth( totW ); + + Float totH = mSize.getHeight(); if ( mHeightPolicy == SizePolicy::WrapContent ) { - setInternalPixelsHeight( mRichText.getSize().getHeight() + mPaddingPx.Top + - mPaddingPx.Bottom ); + totH = mRichText.getSize().getHeight() + mPaddingPx.Top + mPaddingPx.Bottom; + if ( !mMaxHeightEq.empty() && totH > getMaxSizePx().getHeight() ) + setClipType( ClipType::ContentBox ); } + if ( totH != mSize.getHeight() || mHeightPolicy == SizePolicy::WrapContent ) + setInternalPixelsHeight( totH ); + if ( mResizedCount ) positionChildren(); @@ -737,7 +761,13 @@ Float UIRichText::getMinIntrinsicWidth() const { mMaxIntrinsicWidth = richText.getMaxIntrinsicWidth() + mPaddingPx.Left + mPaddingPx.Right; mIntrinsicWidthsDirty = false; } - return mMinIntrinsicWidth; + + Float minWidth = mMinIntrinsicWidth; + if ( !mMinWidthEq.empty() ) + minWidth = eemax( minWidth, getMinSizePx().getWidth() ); + if ( !mMaxWidthEq.empty() ) + minWidth = eemin( minWidth, getMaxSizePx().getWidth() ); + return minWidth; } Float UIRichText::getMaxIntrinsicWidth() const { @@ -753,7 +783,13 @@ Float UIRichText::getMaxIntrinsicWidth() const { mMaxIntrinsicWidth = richText.getMaxIntrinsicWidth() + mPaddingPx.Left + mPaddingPx.Right; mIntrinsicWidthsDirty = false; } - return mMaxIntrinsicWidth; + + Float maxWidth = mMaxIntrinsicWidth; + if ( !mMinWidthEq.empty() ) + maxWidth = eemax( maxWidth, getMinSizePx().getWidth() ); + if ( !mMaxWidthEq.empty() ) + maxWidth = eemin( maxWidth, getMaxSizePx().getWidth() ); + return maxWidth; } Uint32 UIRichText::onMessage( const NodeMessage* Msg ) { diff --git a/src/tests/unit_tests/richtext.cpp b/src/tests/unit_tests/richtext.cpp index f846c1f2a..b9da69ab6 100644 --- a/src/tests/unit_tests/richtext.cpp +++ b/src/tests/unit_tests/richtext.cpp @@ -1023,3 +1023,134 @@ UTEST( UIRichText, CustomBRHeight ) { eeDelete( sceneNode ); Engine::destroySingleton(); } + +UTEST( UIRichText, MinMaxWidth ) { + Engine::instance()->createWindow( WindowSettings( 800, 600, "RichText Min/Max Width Test", + WindowStyle::Default, WindowBackend::Default, + 32, {}, 1, false, true ) ); + FileSystem::changeWorkingDirectory( Sys::getProcessPath() ); + + FontTrueType* font = FontTrueType::New( "NotoSans-Regular" ); + font->loadFromFile( "../assets/fonts/NotoSans-Regular.ttf" ); + ASSERT_TRUE( font->loaded() ); + FontFamily::loadFromRegular( font ); + + UI::UISceneNode* sceneNode = UI::UISceneNode::New(); + UI::UIThemeManager* themeManager = sceneNode->getUIThemeManager(); + themeManager->setDefaultFont( font ); + + String xml = R"xml( + + Short + This is a very long text that should definitely wrap because of the max-width property being set to 100dp. + This is another very long text with fixed width policy. + + )xml"; + + sceneNode->loadLayoutFromString( xml ); + UI::UIRichText* rtMin = sceneNode->find( "rt_min" ); + UI::UIRichText* rtMax = sceneNode->find( "rt_max" ); + UI::UIRichText* rtMaxFixed = sceneNode->find( "rt_max_fixed" ); + ASSERT_TRUE( rtMin != nullptr ); + ASSERT_TRUE( rtMax != nullptr ); + ASSERT_TRUE( rtMaxFixed != nullptr ); + + sceneNode->update( Time::Zero ); + + EXPECT_EQ( rtMin->getSize().getWidth(), PixelDensity::dpToPx( 200 ) ); + EXPECT_LE( rtMax->getSize().getWidth(), PixelDensity::dpToPx( 100 ) ); + EXPECT_GT( rtMax->getSize().getHeight(), PixelDensity::dpToPx( 30 ) ); // should wrap to multiple lines + EXPECT_LE( rtMaxFixed->getSize().getWidth(), PixelDensity::dpToPx( 100 ) ); + EXPECT_GT( rtMaxFixed->getSize().getHeight(), PixelDensity::dpToPx( 30 ) ); // should wrap to multiple lines + + eeDelete( sceneNode ); + Engine::destroySingleton(); +} + +UTEST( UIRichText, MinMaxWidthChildren ) { + Engine::instance()->createWindow( WindowSettings( 800, 600, "RichText Min/Max Width Children Test", + WindowStyle::Default, WindowBackend::Default, + 32, {}, 1, false, true ) ); + FileSystem::changeWorkingDirectory( Sys::getProcessPath() ); + + FontTrueType* font = FontTrueType::New( "NotoSans-Regular" ); + font->loadFromFile( "../assets/fonts/NotoSans-Regular.ttf" ); + ASSERT_TRUE( font->loaded() ); + FontFamily::loadFromRegular( font ); + + UI::UISceneNode* sceneNode = UI::UISceneNode::New(); + UI::UIThemeManager* themeManager = sceneNode->getUIThemeManager(); + themeManager->setDefaultFont( font ); + + String xml = R"xml( + + + This is a long text that expands the RichText so its max-width is reached. + + + + )xml"; + + sceneNode->loadLayoutFromString( xml ); + UI::UIRichText* rtParent = sceneNode->find( "rt_parent" ); + UI::UIWidget* childWidget = sceneNode->find( "child_widget" ); + ASSERT_TRUE( rtParent != nullptr ); + ASSERT_TRUE( childWidget != nullptr ); + + sceneNode->update( Time::Zero ); + sceneNode->update( Time::Zero ); // Run a second pass to allow MatchParent to resolve against the new clamped parent size + sceneNode->update( Time::Zero ); + + EXPECT_LE( rtParent->getSize().getWidth(), PixelDensity::dpToPx( 100 ) ); + EXPECT_GT( rtParent->getSize().getWidth(), 0 ); // Assert it's not 0 + EXPECT_EQ( childWidget->getSize().getWidth(), rtParent->getSize().getWidth() ); + EXPECT_LE( childWidget->getSize().getWidth(), PixelDensity::dpToPx( 100 ) ); + EXPECT_GT( childWidget->getSize().getWidth(), 0 ); // Assert it's not 0 + + eeDelete( sceneNode ); + Engine::destroySingleton(); +} + +UTEST( UILayout, MinMaxWidthChildren ) { + Engine::instance()->createWindow( WindowSettings( 800, 600, "Layout Min/Max Width Children Test", + WindowStyle::Default, WindowBackend::Default, + 32, {}, 1, false, true ) ); + FileSystem::changeWorkingDirectory( Sys::getProcessPath() ); + + FontTrueType* font = FontTrueType::New( "NotoSans-Regular" ); + font->loadFromFile( "../assets/fonts/NotoSans-Regular.ttf" ); + ASSERT_TRUE( font->loaded() ); + FontFamily::loadFromRegular( font ); + + UI::UISceneNode* sceneNode = UI::UISceneNode::New(); + UI::UIThemeManager* themeManager = sceneNode->getUIThemeManager(); + themeManager->setDefaultFont( font ); + + String xml = R"xml( + + + + + + + )xml"; + + sceneNode->loadLayoutFromString( xml ); + UI::UIWidget* llParent = sceneNode->find( "ll_parent" ); + UI::UIWidget* childWidget2 = sceneNode->find( "child_widget2" ); + ASSERT_TRUE( llParent != nullptr ); + ASSERT_TRUE( childWidget2 != nullptr ); + + sceneNode->update( Time::Zero ); + sceneNode->update( Time::Zero ); + sceneNode->update( Time::Zero ); + + EXPECT_LE( llParent->getSize().getWidth(), PixelDensity::dpToPx( 150 ) ); + EXPECT_GT( llParent->getSize().getWidth(), 0 ); // Assert it's not 0 + EXPECT_EQ( childWidget2->getSize().getWidth(), llParent->getSize().getWidth() ); + EXPECT_LE( childWidget2->getSize().getWidth(), PixelDensity::dpToPx( 150 ) ); + EXPECT_GT( childWidget2->getSize().getWidth(), 0 ); // Assert it's not 0 + + eeDelete( sceneNode ); + Engine::destroySingleton(); +}