From f7dba042df5242368078801019ab2cac2c4ead9e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mart=C3=ADn=20Lucas=20Golini?= Date: Tue, 21 Jul 2026 23:50:58 -0300 Subject: [PATCH] Replace DrawableSearcher with scoped drawable resolution: Add a scene-owned UI::DrawableResolver for resolving file, data, HTTP, and named drawable references through each UISceneNode's current resource scope. Move generic texture, atlas region, nine-patch, and sprite lookup into Graphics::ResourceScope, preserving drawable resolution for pure Graphics consumers without introducing a UI dependency. Migrate UI images, sprites, menus, CSS parsing, and tests to the new resolver. Resolve CSS icons through the requesting node's scene instead of the global scene, preserving embedded-scene isolation. Remove the obsolete DrawableSearcher API and update platform project manifests, tests, and the shared-resource architecture plan. --- .../resource_shared_ownership_architecture.md | 14 +- include/eepp/graphics.hpp | 1 - include/eepp/graphics/drawablesearcher.hpp | 30 --- include/eepp/graphics/resourcescope.hpp | 3 + include/eepp/ui.hpp | 1 + include/eepp/ui/drawableresolver.hpp | 31 +++ include/eepp/ui/uiscenenode.hpp | 8 + projects/linux/ee.files | 4 +- projects/macos/ee.files | 4 +- projects/windows/ee.files | 4 +- src/eepp/graphics/drawablesearcher.cpp | 244 ------------------ src/eepp/graphics/resourcescope.cpp | 68 +++++ src/eepp/ui/css/drawableimageparser.cpp | 25 +- src/eepp/ui/drawableresolver.cpp | 160 ++++++++++++ src/eepp/ui/uiimage.cpp | 10 +- src/eepp/ui/uimenu.cpp | 24 +- src/eepp/ui/uipushbutton.cpp | 1 - src/eepp/ui/uiscenenode.cpp | 11 +- src/eepp/ui/uisprite.cpp | 14 +- .../resource_prerequisite_tests.cpp | 18 ++ src/tests/unit_tests/uihtml_tests.cpp | 4 +- 21 files changed, 353 insertions(+), 326 deletions(-) delete mode 100644 include/eepp/graphics/drawablesearcher.hpp create mode 100644 include/eepp/ui/drawableresolver.hpp delete mode 100644 src/eepp/graphics/drawablesearcher.cpp create mode 100644 src/eepp/ui/drawableresolver.cpp diff --git a/.agent/plans/resource_shared_ownership_architecture.md b/.agent/plans/resource_shared_ownership_architecture.md index 397009c24..a9f8d69c7 100644 --- a/.agent/plans/resource_shared_ownership_architecture.md +++ b/.agent/plans/resource_shared_ownership_architecture.md @@ -1,6 +1,6 @@ # eepp shared-resource ownership architecture -Status: active implementation baseline; Stage 0 through Stage 4 complete, 2026-07-20. +Status: active implementation baseline; Stage 0 through Stage 5 complete, 2026-07-21. This document freezes the contracts that must be true before the public texture API is changed. The implementation may refine names and small mechanics, but changing an invariant below requires an @@ -727,6 +727,18 @@ Exit criteria: ### Stage 5: layered UI resolution +Status: complete, 2026-07-21. `DrawableSearcher` was removed from Graphics. `ResourceScope` now +provides scoped drawable lookup for textures, atlas regions, nine-patches, and sprites, preserving a +pure Graphics entry point with no UI dependency. Each `UISceneNode` owns an allocation-free +`UI::DrawableResolver` that reads the scene's current scope and referer when resolving file, data, +HTTP, and named drawable references. UI images, sprites, menus, CSS parsing, and tests use the scene +resolver; callers without a scene explicitly construct one over `defaultResourceScope()`. + +CSS icon resolution now uses the requesting node's scene instead of the process-global scene. +Texture names remain isolated by local/imported catalogs and become visible across scenes only when +their scopes or catalogs are shared intentionally. Atlas, nine-patch, and sprite manager migration +to scoped catalogs remains Stage 7 work for those resource families. + Implement UI::DrawableResolver and replace DrawableSearcher. Scene resolvers delegate Graphics work to their explicit scope. Keep CSS/icon/glyph interpretation in UI and cookie/navigation concerns in Web services. diff --git a/include/eepp/graphics.hpp b/include/eepp/graphics.hpp index c3a8a5dc3..624b55d3a 100644 --- a/include/eepp/graphics.hpp +++ b/include/eepp/graphics.hpp @@ -10,7 +10,6 @@ #include #include #include -#include #include #include #include diff --git a/include/eepp/graphics/drawablesearcher.hpp b/include/eepp/graphics/drawablesearcher.hpp deleted file mode 100644 index c95ea9cb4..000000000 --- a/include/eepp/graphics/drawablesearcher.hpp +++ /dev/null @@ -1,30 +0,0 @@ -#ifndef EE_GRAPHICS_DRAWABLEMANAGER_HPP -#define EE_GRAPHICS_DRAWABLEMANAGER_HPP - -#include -#include -#include - -namespace EE { namespace Graphics { - -class ResourceScope; - -class EE_API DrawableSearcher { - public: - static DrawablePtr searchByName( const std::string& name, bool firstSearchSprite = false, - Network::URI referer = "", - ResourceScope* resourceScope = nullptr ); - - static DrawablePtr searchById( const Uint32& id ); - - static void setPrintWarnings( const bool& print ); - - static bool getPrintWarnings(); - - protected: - static bool sPrintWarnings; -}; - -}} // namespace EE::Graphics - -#endif diff --git a/include/eepp/graphics/resourcescope.hpp b/include/eepp/graphics/resourcescope.hpp index 5d6719ae6..60752c466 100644 --- a/include/eepp/graphics/resourcescope.hpp +++ b/include/eepp/graphics/resourcescope.hpp @@ -1,6 +1,7 @@ #ifndef EE_GRAPHICS_RESOURCESCOPE_HPP #define EE_GRAPHICS_RESOURCESCOPE_HPP +#include #include namespace EE { namespace Graphics { @@ -17,6 +18,8 @@ class EE_API ResourceScope { TexturePtr findTexture( const ResourceKey& key ) const; TexturePtr findTexture( const std::string& key ) const; + DrawablePtr findDrawable( const std::string& name, bool firstSearchSprite = false ) const; + DrawablePtr findDrawable( const Uint32& id ) const; void publishLocal( ResourceKey key, TexturePtr texture ); void publishLocal( std::string key, TexturePtr texture ); diff --git a/include/eepp/ui.hpp b/include/eepp/ui.hpp index ee836c590..0765126c1 100644 --- a/include/eepp/ui.hpp +++ b/include/eepp/ui.hpp @@ -47,6 +47,7 @@ #include #include #include +#include #include #include #include diff --git a/include/eepp/ui/drawableresolver.hpp b/include/eepp/ui/drawableresolver.hpp new file mode 100644 index 000000000..463348e43 --- /dev/null +++ b/include/eepp/ui/drawableresolver.hpp @@ -0,0 +1,31 @@ +#ifndef EE_UI_DRAWABLERESOLVER_HPP +#define EE_UI_DRAWABLERESOLVER_HPP + +#include +#include + +namespace EE { namespace UI { + +class UISceneNode; + +/** Resolves named drawables through an explicit UI scene or Graphics resource scope. */ +class EE_API DrawableResolver { + public: + explicit DrawableResolver( UISceneNode& sceneNode ); + explicit DrawableResolver( Graphics::ResourceScope& resourceScope ); + + Graphics::DrawablePtr resolve( const std::string& name, bool firstSearchSprite = false ) const; + Graphics::DrawablePtr resolveById( const Uint32& id ) const; + + void setPrintWarnings( bool printWarnings ); + bool getPrintWarnings() const; + + protected: + UISceneNode* mSceneNode{ nullptr }; + Graphics::ResourceScope* mResourceScope{ nullptr }; + bool mPrintWarnings{ false }; +}; + +}} // namespace EE::UI + +#endif diff --git a/include/eepp/ui/uiscenenode.hpp b/include/eepp/ui/uiscenenode.hpp index de70bd2b0..8aae83e45 100644 --- a/include/eepp/ui/uiscenenode.hpp +++ b/include/eepp/ui/uiscenenode.hpp @@ -10,6 +10,7 @@ #include #include #include +#include #include #include @@ -566,6 +567,12 @@ class EE_API UISceneNode : public SceneNode { */ DrawablePtr findIconDrawable( const std::string& iconName, const size_t& drawableSize ); + /** @return This scene's drawable resolver. */ + DrawableResolver& getDrawableResolver(); + + /** @return This scene's drawable resolver. */ + const DrawableResolver& getDrawableResolver() const; + /** * @brief Gets the keybindings manager. * @@ -910,6 +917,7 @@ class EE_API UISceneNode : public SceneNode { UnorderedMap mFontFaceFamilies; std::shared_ptr mAsyncResourceLoadState; Graphics::ResourceScopePtr mResourceScope; + DrawableResolver mDrawableResolver; KeyBindings mKeyBindings; std::map mKeyBindingCommands; UnorderedSet mDirtyStyle; diff --git a/projects/linux/ee.files b/projects/linux/ee.files index b703fcf55..46d4890f7 100644 --- a/projects/linux/ee.files +++ b/projects/linux/ee.files @@ -57,7 +57,7 @@ ../../include/eepp/graphics/drawablegroup.hpp ../../include/eepp/graphics/drawable.hpp ../../include/eepp/graphics/drawableresource.hpp -../../include/eepp/graphics/drawablesearcher.hpp +../../include/eepp/ui/drawableresolver.hpp ../../include/eepp/graphics/fontbmfont.hpp ../../include/eepp/graphics/font.hpp ../../include/eepp/graphics/fontfamily.hpp @@ -555,7 +555,7 @@ ../../src/eepp/graphics/drawable.cpp ../../src/eepp/graphics/drawablegroup.cpp ../../src/eepp/graphics/drawableresource.cpp -../../src/eepp/graphics/drawablesearcher.cpp +../../src/eepp/ui/drawableresolver.cpp ../../src/eepp/graphics/fontbmfont.cpp ../../src/eepp/graphics/font.cpp ../../src/eepp/graphics/fontfamily.cpp diff --git a/projects/macos/ee.files b/projects/macos/ee.files index 466e77c6b..a320ffa16 100644 --- a/projects/macos/ee.files +++ b/projects/macos/ee.files @@ -57,7 +57,7 @@ ../../include/eepp/graphics/drawablegroup.hpp ../../include/eepp/graphics/drawable.hpp ../../include/eepp/graphics/drawableresource.hpp -../../include/eepp/graphics/drawablesearcher.hpp +../../include/eepp/ui/drawableresolver.hpp ../../include/eepp/graphics/fontbmfont.hpp ../../include/eepp/graphics/font.hpp ../../include/eepp/graphics/fontfamily.hpp @@ -546,7 +546,7 @@ ../../src/eepp/graphics/drawable.cpp ../../src/eepp/graphics/drawablegroup.cpp ../../src/eepp/graphics/drawableresource.cpp -../../src/eepp/graphics/drawablesearcher.cpp +../../src/eepp/ui/drawableresolver.cpp ../../src/eepp/graphics/fontbmfont.cpp ../../src/eepp/graphics/font.cpp ../../src/eepp/graphics/fontfamily.cpp diff --git a/projects/windows/ee.files b/projects/windows/ee.files index 18a331b57..1991069ec 100644 --- a/projects/windows/ee.files +++ b/projects/windows/ee.files @@ -56,7 +56,7 @@ ../../include/eepp/graphics/drawablegroup.hpp ../../include/eepp/graphics/drawable.hpp ../../include/eepp/graphics/drawableresource.hpp -../../include/eepp/graphics/drawablesearcher.hpp +../../include/eepp/ui/drawableresolver.hpp ../../include/eepp/graphics/fontbmfont.hpp ../../include/eepp/graphics/font.hpp ../../include/eepp/graphics/fontmanager.hpp @@ -538,7 +538,7 @@ ../../src/eepp/graphics/drawable.cpp ../../src/eepp/graphics/drawablegroup.cpp ../../src/eepp/graphics/drawableresource.cpp -../../src/eepp/graphics/drawablesearcher.cpp +../../src/eepp/ui/drawableresolver.cpp ../../src/eepp/graphics/fontbmfont.cpp ../../src/eepp/graphics/font.cpp ../../src/eepp/graphics/fontmanager.cpp diff --git a/src/eepp/graphics/drawablesearcher.cpp b/src/eepp/graphics/drawablesearcher.cpp deleted file mode 100644 index 6207820c6..000000000 --- a/src/eepp/graphics/drawablesearcher.cpp +++ /dev/null @@ -1,244 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -using namespace EE::Window; -using namespace EE::Network; - -namespace EE { namespace Graphics { - -bool DrawableSearcher::sPrintWarnings = false; - -static DrawablePtr getSprite( const std::string& sprite ) { - std::vector tTextureRegionVec = - TextureAtlasManager::instance()->getTextureRegionsByPattern( sprite ); - - if ( tTextureRegionVec.size() ) { - SpritePtr tSprite = Sprite::New(); - tSprite->createAnimation(); - tSprite->addFrames( tTextureRegionVec ); - - return tSprite; - } - - return {}; -} - -static DrawablePtr searchByNameInternal( const std::string& name, ResourceScope& resourceScope ) { - String::HashType id = String::hash( name ); - Drawable* source = TextureAtlasManager::instance()->getTextureRegionById( id ); - - if ( NULL == source ) { - source = NinePatchManager::instance()->getById( id ); - } - - if ( source ) { - return source->clone(); - } - - TexturePtr texture = resourceScope.findTexture( name ); - return texture ? texture->clone() : DrawablePtr{}; -} - -static DrawablePtr parseDataURI( const std::string& name, ResourceScope& scope ) { - auto hash = MD5::fromString( name ).toHexString(); - TexturePtr texture = scope.findTexture( hash ); - std::string::size_type formatAndEncSep; - if ( !texture && - ( formatAndEncSep = name.find_first_of( ',' ) ) != std::string::npos ) { - std::string decodingType = "urldecode"; - std::string mediaType = name.substr( 0, formatAndEncSep ); - std::string format; - auto parts = String::split( mediaType, ';' ); - if ( parts.empty() ) - return nullptr; - auto formatNamePos = parts[0].find_first_of( '/' ); - if ( formatNamePos + 1 < mediaType.size() ) - format = parts[0].substr( formatNamePos + 1 ); - if ( parts.size() > 1 ) { - for ( size_t i = 1; i < parts.size(); ++i ) { - if ( "base64" == parts[i] ) { - decodingType = parts[i]; - break; - } - } - } - - TexturePtr tex; - if ( !format.empty() && - ( Image::isImageExtension( "." + format ) || format == "svg+xml" ) ) { - Image::FormatConfiguration format; - format.svgScale( PixelDensity::getPixelDensity() ); - if ( decodingType == "base64" ) { - int fileStart = formatAndEncSep + 1; - std::string_view fileBase64 = std::string_view{ name }.substr( fileStart ); - std::string buffer; - int len = Base64::decode( fileBase64, buffer ); - if ( len > 0 ) { - tex = TextureFactory::instance()->loadFromMemory( - (const unsigned char*)buffer.c_str(), buffer.size(), false, - Texture::ClampMode::ClampToEdge, false, false, format ); - } - } else if ( decodingType == "urldecode" ) { - int fileStart = formatAndEncSep + 1; - std::string decoded( URI::decode( name.substr( fileStart ) ) ); - if ( !decoded.empty() ) { - tex = TextureFactory::instance()->loadFromMemory( - (const unsigned char*)decoded.c_str(), decoded.size(), false, - Texture::ClampMode::ClampToEdge, false, false, format ); - } - } - } - - if ( tex ) { - tex->setName( hash ); - scope.publishLocal( hash, tex ); - texture = std::move( tex ); - } - } - return texture ? texture->clone() : DrawablePtr{}; -} - -DrawablePtr DrawableSearcher::searchByName( const std::string& name, bool firstSearchSprite, - Network::URI referer, - ResourceScope* requestedResourceScope ) { - DrawablePtr drawable; - - if ( name.size() ) { - ResourceScope& resourceScope = - requestedResourceScope ? *requestedResourceScope : defaultResourceScope(); - bool searchedSprite = false; - - if ( firstSearchSprite ) { - if ( String::startsWith( name, "@sprite/" ) ) { - drawable = getSprite( name.substr( 8 ) ); - } else { - drawable = getSprite( name ); - } - - if ( drawable ) { - return drawable; - } - - searchedSprite = true; - } - - if ( name[0] == '@' ) { - if ( String::startsWith( name, "@textureregion/" ) ) { - if ( Drawable* source = - TextureAtlasManager::instance()->getTextureRegionByName( name.substr( 12 ) ) ) - drawable = source->clone(); - } else if ( String::startsWith( name, "@image/" ) ) { - TexturePtr texture = resourceScope.findTexture( name.substr( 7 ) ); - drawable = texture ? texture->clone() : DrawablePtr{}; - } else if ( String::startsWith( name, "@texture/" ) ) { - TexturePtr texture = resourceScope.findTexture( name.substr( 9 ) ); - drawable = texture ? texture->clone() : DrawablePtr{}; - } else if ( String::startsWith( name, "@sprite/" ) && !searchedSprite ) { - drawable = getSprite( name.substr( 8 ) ); - } else if ( String::startsWith( name, "@drawable/" ) ) { - drawable = searchByNameInternal( name.substr( 10 ), resourceScope ); - } else if ( String::startsWith( name, "@9p/" ) ) { - if ( Drawable* source = NinePatchManager::instance()->getByName( name.substr( 4 ) ) ) - drawable = source->clone(); - } else { - drawable = searchByNameInternal( name, resourceScope ); - } - } else if ( String::startsWith( name, "file://" ) ) { - std::string filePath( name.substr( 7 ) ); - -#if EE_PLATFORM == EE_PLATFORM_WIN - if ( filePath.size() >= 3 && filePath[0] == '/' && String::isLetter( filePath[1] ) && - filePath[2] == ':' ) { - filePath = filePath.substr( 1 ); - } -#endif - - FileSystem::filePathRemoveProcessPath( filePath ); - - TexturePtr texture = resourceScope.findTexture( filePath ); - - if ( !texture ) { - TexturePtr tex = TextureFactory::instance()->loadFromFile( filePath ); - - if ( tex ) { - resourceScope.publishLocal( filePath, tex ); - texture = std::move( tex ); - } - } - drawable = texture ? texture->clone() : DrawablePtr{}; - } else if ( String::startsWith( name, "http://" ) || - String::startsWith( name, "https://" ) ) { - TexturePtr texture = resourceScope.findTexture( name ); - - if ( NULL == texture && Engine::instance()->isSharedGLContextEnabled() ) { - texture = TextureFactory::instance()->createEmptyTexture( - 1, 1, 4, Color::Transparent, false, Texture::ClampMode::ClampToEdge, false, - false, name ); - if ( texture ) - resourceScope.publishLocal( name, texture ); - - std::map headers; - if ( !referer.empty() ) - headers["referer"] = referer.toString(); - - Http::getAsync( - [texture, name]( const Http&, Http::Request&, Http::Response& response ) { - if ( response.isOK() && !response.getBody().empty() ) { - Image image( (const Uint8*)&response.getBody()[0], - response.getBody().size() ); - - if ( image.getPixels() != NULL ) - texture->replace( &image ); - } else { - Log::debug( "DrawableSearcher::searchByName: could not download image: " - "%s. Error: %d\n%s", - name, response.getStatus(), response.getBody() ); - } - }, - URI( name ), Seconds( 5 ), {}, headers ); - } - - drawable = texture ? texture->clone() : DrawablePtr{}; - } else if ( String::startsWith( name, "data:image/" ) ) { - drawable = parseDataURI( name, resourceScope ); - } else { - drawable = searchByNameInternal( name, resourceScope ); - } - } - - if ( !drawable && sPrintWarnings ) - Log::warning( "DrawableSearcher::searchByName: \"%s\" not found", name.c_str() ); - - return drawable; -} - -DrawablePtr DrawableSearcher::searchById( const Uint32& id ) { - Drawable* source = TextureAtlasManager::instance()->getTextureRegionById( id ); - DrawablePtr drawable = source ? source->clone() : DrawablePtr{}; - - if ( !drawable && sPrintWarnings ) - Log::warning( "DrawableSearcher::searchById: \"%ld\" not found", id ); - - return drawable; -} - -void DrawableSearcher::setPrintWarnings( const bool& print ) { - sPrintWarnings = print; -} - -bool DrawableSearcher::getPrintWarnings() { - return sPrintWarnings; -} - -}} // namespace EE::Graphics diff --git a/src/eepp/graphics/resourcescope.cpp b/src/eepp/graphics/resourcescope.cpp index 4100b4d5a..8f5d64597 100644 --- a/src/eepp/graphics/resourcescope.cpp +++ b/src/eepp/graphics/resourcescope.cpp @@ -1,5 +1,9 @@ #include +#include +#include #include +#include +#include #include #include @@ -30,6 +34,70 @@ TexturePtr ResourceScope::findTexture( const std::string& key ) const { return {}; } +DrawablePtr ResourceScope::findDrawable( const std::string& name, bool firstSearchSprite ) const { + if ( name.empty() ) + return {}; + + auto findSprite = []( const std::string& pattern ) -> DrawablePtr { + std::vector textureRegions = + TextureAtlasManager::instance()->getTextureRegionsByPattern( pattern ); + if ( textureRegions.empty() ) + return {}; + SpritePtr sprite = Sprite::New(); + sprite->createAnimation(); + sprite->addFrames( textureRegions ); + return sprite; + }; + + bool searchedSprite = false; + if ( firstSearchSprite ) { + DrawablePtr sprite = + findSprite( String::startsWith( name, "@sprite/" ) ? name.substr( 8 ) : name ); + if ( sprite ) + return sprite; + searchedSprite = true; + } + + if ( name[0] == '@' ) { + if ( String::startsWith( name, "@textureregion/" ) ) { + Drawable* source = + TextureAtlasManager::instance()->getTextureRegionByName( name.substr( 12 ) ); + return source ? source->clone() : DrawablePtr{}; + } + if ( String::startsWith( name, "@image/" ) ) { + TexturePtr texture = findTexture( name.substr( 7 ) ); + return texture ? texture->clone() : DrawablePtr{}; + } + if ( String::startsWith( name, "@texture/" ) ) { + TexturePtr texture = findTexture( name.substr( 9 ) ); + return texture ? texture->clone() : DrawablePtr{}; + } + if ( String::startsWith( name, "@sprite/" ) && !searchedSprite ) + return findSprite( name.substr( 8 ) ); + if ( String::startsWith( name, "@drawable/" ) ) + return findDrawable( name.substr( 10 ) ); + if ( String::startsWith( name, "@9p/" ) ) { + Drawable* source = NinePatchManager::instance()->getByName( name.substr( 4 ) ); + return source ? source->clone() : DrawablePtr{}; + } + } + + String::HashType id = String::hash( name ); + Drawable* source = TextureAtlasManager::instance()->getTextureRegionById( id ); + if ( source == nullptr ) + source = NinePatchManager::instance()->getById( id ); + if ( source ) + return source->clone(); + + TexturePtr texture = findTexture( name ); + return texture ? texture->clone() : DrawablePtr{}; +} + +DrawablePtr ResourceScope::findDrawable( const Uint32& id ) const { + Drawable* source = TextureAtlasManager::instance()->getTextureRegionById( id ); + return source ? source->clone() : DrawablePtr{}; +} + void ResourceScope::publishLocal( ResourceKey key, TexturePtr texture ) { publishLocal( key.value(), std::move( texture ) ); } diff --git a/src/eepp/ui/css/drawableimageparser.cpp b/src/eepp/ui/css/drawableimageparser.cpp index 28ce99ebb..2d3771db2 100644 --- a/src/eepp/ui/css/drawableimageparser.cpp +++ b/src/eepp/ui/css/drawableimageparser.cpp @@ -1,11 +1,9 @@ #include #include #include -#include #include #include #include -#include #include #include #include @@ -16,7 +14,6 @@ #include using namespace EE::Graphics; -using namespace EE::Scene; namespace EE { namespace UI { namespace CSS { @@ -163,9 +160,8 @@ DrawablePtr DrawableImageParser::createDrawable( const std::string& value, const if ( !functionType.isEmpty() ) { if ( exists( functionType.getName() ) ) return mFuncs[functionType.getName()]( functionType, size, node ); - } else if ( DrawablePtr drawable = DrawableSearcher::searchByName( - value, false, node->getUISceneNode()->getReferer(), - node->getUISceneNode()->getResourceScope().get() ) ) { + } else if ( DrawablePtr drawable = + node->getUISceneNode()->getDrawableResolver().resolve( value ) ) { return drawable; } @@ -909,10 +905,8 @@ void DrawableImageParser::registerBaseParsers() { const auto& param = functionType.getParameters().at( 0 ); if ( functionType.getName() == "url" && !param.empty() && param[0] != '@' && !String::startsWith( param, "data:image/" ) ) { - DrawablePtr drawable = DrawableSearcher::searchByName( - node->getUISceneNode()->solveRelativePath( param ).toString(), false, - node->getUISceneNode()->getReferer(), - node->getUISceneNode()->getResourceScope().get() ); + DrawablePtr drawable = node->getUISceneNode()->getDrawableResolver().resolve( + node->getUISceneNode()->solveRelativePath( param ).toString() ); return drawable; } else if ( functionType.getParameters().size() > 1 && String::startsWith( param, "data:image/" ) ) { @@ -921,20 +915,17 @@ void DrawableImageParser::registerBaseParsers() { cparam += ','; cparam += functionType.getParameters().at( i ); } - DrawablePtr drawable = DrawableSearcher::searchByName( - cparam, false, node->getUISceneNode()->getReferer(), - node->getUISceneNode()->getResourceScope().get() ); + DrawablePtr drawable = + node->getUISceneNode()->getDrawableResolver().resolve( cparam ); return drawable; } - DrawablePtr drawable = DrawableSearcher::searchByName( - param, false, node->getUISceneNode()->getReferer(), - node->getUISceneNode()->getResourceScope().get() ); + DrawablePtr drawable = node->getUISceneNode()->getDrawableResolver().resolve( param ); return drawable; }; mFuncs["icon"] = []( const FunctionString& functionType, const Sizef& size, UINode* node ) -> DrawablePtr { - auto* uiScene = SceneManager::instance()->getUISceneNode(); + auto* uiScene = node->getUISceneNode(); const auto& params = functionType.getParameters(); if ( params.size() < 2 ) return nullptr; diff --git a/src/eepp/ui/drawableresolver.cpp b/src/eepp/ui/drawableresolver.cpp new file mode 100644 index 000000000..2b5e8d35a --- /dev/null +++ b/src/eepp/ui/drawableresolver.cpp @@ -0,0 +1,160 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace EE::Graphics; +using namespace EE::Network; +using namespace EE::Window; + +namespace EE { namespace UI { + +namespace { + +DrawablePtr parseDataURI( const std::string& name, ResourceScope& resourceScope ) { + std::string hash = MD5::fromString( name ).toHexString(); + TexturePtr texture = resourceScope.findTexture( hash ); + std::string::size_type formatAndEncSep; + if ( !texture && ( formatAndEncSep = name.find_first_of( ',' ) ) != std::string::npos ) { + std::string decodingType = "urldecode"; + std::string mediaType = name.substr( 0, formatAndEncSep ); + std::string formatName; + auto parts = String::split( mediaType, ';' ); + if ( parts.empty() ) + return {}; + auto formatNamePos = parts[0].find_first_of( '/' ); + if ( formatNamePos + 1 < mediaType.size() ) + formatName = parts[0].substr( formatNamePos + 1 ); + for ( size_t i = 1; i < parts.size(); ++i ) { + if ( parts[i] == "base64" ) { + decodingType = parts[i]; + break; + } + } + + TexturePtr decodedTexture; + if ( !formatName.empty() && + ( Image::isImageExtension( "." + formatName ) || formatName == "svg+xml" ) ) { + Image::FormatConfiguration format; + format.svgScale( PixelDensity::getPixelDensity() ); + std::string_view encoded = std::string_view{ name }.substr( formatAndEncSep + 1 ); + if ( decodingType == "base64" ) { + std::string buffer; + if ( Base64::decode( encoded, buffer ) > 0 ) { + decodedTexture = TextureFactory::instance()->loadFromMemory( + reinterpret_cast( buffer.data() ), buffer.size(), + false, Texture::ClampMode::ClampToEdge, false, false, format ); + } + } else { + std::string decoded = URI::decode( encoded ); + if ( !decoded.empty() ) { + decodedTexture = TextureFactory::instance()->loadFromMemory( + reinterpret_cast( decoded.data() ), decoded.size(), + false, Texture::ClampMode::ClampToEdge, false, false, format ); + } + } + } + + if ( decodedTexture ) { + decodedTexture->setName( hash ); + resourceScope.publishLocal( hash, decodedTexture ); + texture = std::move( decodedTexture ); + } + } + + return texture ? texture->clone() : DrawablePtr{}; +} + +} // namespace + +DrawableResolver::DrawableResolver( UISceneNode& sceneNode ) : mSceneNode( &sceneNode ) {} + +DrawableResolver::DrawableResolver( ResourceScope& resourceScope ) : + mResourceScope( &resourceScope ) {} + +DrawablePtr DrawableResolver::resolve( const std::string& name, bool firstSearchSprite ) const { + DrawablePtr drawable; + if ( name.empty() ) + return drawable; + + ResourceScope& resourceScope = mSceneNode ? *mSceneNode->getResourceScope() : *mResourceScope; + if ( String::startsWith( name, "file://" ) ) { + std::string filePath = name.substr( 7 ); +#if EE_PLATFORM == EE_PLATFORM_WIN + if ( filePath.size() >= 3 && filePath[0] == '/' && String::isLetter( filePath[1] ) && + filePath[2] == ':' ) + filePath.erase( 0, 1 ); +#endif + FileSystem::filePathRemoveProcessPath( filePath ); + TexturePtr texture = resourceScope.findTexture( filePath ); + if ( !texture ) { + texture = TextureFactory::instance()->loadFromFile( filePath ); + if ( texture ) + resourceScope.publishLocal( filePath, texture ); + } + drawable = texture ? texture->clone() : DrawablePtr{}; + } else if ( String::startsWith( name, "http://" ) || String::startsWith( name, "https://" ) ) { + TexturePtr texture = resourceScope.findTexture( name ); + if ( !texture && Engine::instance()->isSharedGLContextEnabled() ) { + texture = TextureFactory::instance()->createEmptyTexture( + 1, 1, 4, Color::Transparent, false, Texture::ClampMode::ClampToEdge, false, false, + name ); + if ( texture ) + resourceScope.publishLocal( name, texture ); + + Http::Request::FieldTable headers; + if ( mSceneNode && !mSceneNode->getReferer().empty() ) + headers["referer"] = mSceneNode->getReferer().toString(); + Http::getAsync( + [texture, name]( const Http&, Http::Request&, Http::Response& response ) { + if ( response.isOK() && !response.getBody().empty() ) { + Image image( reinterpret_cast( response.getBody().data() ), + response.getBody().size() ); + if ( image.getPixels() ) + texture->replace( &image ); + } else { + Log::debug( + "DrawableResolver::resolve: could not download image: %s. Error: " + "%d\n%s", + name, response.getStatus(), response.getBody() ); + } + }, + URI( name ), Seconds( 5 ), {}, headers ); + } + drawable = texture ? texture->clone() : DrawablePtr{}; + } else if ( String::startsWith( name, "data:image/" ) ) { + drawable = parseDataURI( name, resourceScope ); + } else { + drawable = resourceScope.findDrawable( name, firstSearchSprite ); + } + + if ( !drawable && mPrintWarnings ) + Log::warning( "DrawableResolver::resolve: \"%s\" not found", name.c_str() ); + return drawable; +} + +DrawablePtr DrawableResolver::resolveById( const Uint32& id ) const { + ResourceScope& resourceScope = mSceneNode ? *mSceneNode->getResourceScope() : *mResourceScope; + DrawablePtr drawable = resourceScope.findDrawable( id ); + if ( !drawable && mPrintWarnings ) + Log::warning( "DrawableResolver::resolveById: \"%ld\" not found", id ); + return drawable; +} + +void DrawableResolver::setPrintWarnings( bool printWarnings ) { + mPrintWarnings = printWarnings; +} + +bool DrawableResolver::getPrintWarnings() const { + return mPrintWarnings; +} + +}} // namespace EE::UI diff --git a/src/eepp/ui/uiimage.cpp b/src/eepp/ui/uiimage.cpp index 2b6baef26..36db9eaef 100644 --- a/src/eepp/ui/uiimage.cpp +++ b/src/eepp/ui/uiimage.cpp @@ -1,5 +1,4 @@ #include -#include #include #include #include @@ -555,9 +554,12 @@ bool UIImage::applyProperty( const StyleSheetProperty& attribute ) { if ( createdDrawable ) { setDrawable( std::move( createdDrawable ) ); } else { - setDrawable( DrawableSearcher::searchByName( - path, false, scene ? scene->getReferer() : URI(), - scene ? scene->getResourceScope().get() : nullptr ) ); + if ( scene ) { + setDrawable( scene->getDrawableResolver().resolve( path ) ); + } else { + DrawableResolver resolver( defaultResourceScope() ); + setDrawable( resolver.resolve( path ) ); + } } break; } diff --git a/src/eepp/ui/uimenu.cpp b/src/eepp/ui/uimenu.cpp index 0edc8ea03..68e5c5831 100644 --- a/src/eepp/ui/uimenu.cpp +++ b/src/eepp/ui/uimenu.cpp @@ -1,4 +1,3 @@ -#include #include #include #include @@ -553,18 +552,23 @@ Uint32 UIMenu::onKeyDown( const KeyEvent& event ) { return UIWidget::onKeyDown( event ); } -static DrawablePtr getIconDrawable( const std::string& name, - UIIconThemeManager* iconThemeManager ) { +static DrawablePtr getIconDrawable( const std::string& name, UISceneNode* sceneNode ) { DrawablePtr iconDrawable; - if ( nullptr != iconThemeManager ) { - UIIcon* icon = iconThemeManager->findIcon( name ); + if ( sceneNode ) { + UIIcon* icon = sceneNode->findIcon( name ); if ( icon ) { // TODO: Fix size iconDrawable = icon->createDrawable( PixelDensity::dpToPx( 16 ) ); } } - if ( nullptr == iconDrawable ) - iconDrawable = DrawableSearcher::searchByName( name ); + if ( !iconDrawable ) { + if ( sceneNode ) { + iconDrawable = sceneNode->getDrawableResolver().resolve( name ); + } else { + DrawableResolver resolver( defaultResourceScope() ); + iconDrawable = resolver.resolve( name ); + } + } return iconDrawable; } @@ -579,8 +583,7 @@ void UIMenu::loadFromXmlNode( const pugi::xml_node& node ) { std::string text( item.attribute( "text" ).as_string() ); std::string icon( item.attribute( "icon" ).as_string() ); if ( nullptr != mSceneNode && mSceneNode->isUISceneNode() ) - add( getTranslatorString( text ), - getIconDrawable( icon, getUISceneNode()->getUIIconThemeManager() ) ); + add( getTranslatorString( text ), getIconDrawable( icon, getUISceneNode() ) ); } else if ( name == "menuseparator" || name == "separator" ) { addSeparator(); } else if ( name == "menucheckbox" || name == "checkbox" ) { @@ -598,8 +601,7 @@ void UIMenu::loadFromXmlNode( const pugi::xml_node& node ) { if ( nullptr != getDrawInvalidator() ) subMenu->setParent( getDrawInvalidator() ); subMenu->loadFromXmlNode( item ); - addSubMenu( getTranslatorString( text ), - getIconDrawable( icon, getUISceneNode()->getUIIconThemeManager() ), + addSubMenu( getTranslatorString( text ), getIconDrawable( icon, getUISceneNode() ), subMenu ); } } diff --git a/src/eepp/ui/uipushbutton.cpp b/src/eepp/ui/uipushbutton.cpp index fd913a2c5..ed85c12a3 100644 --- a/src/eepp/ui/uipushbutton.cpp +++ b/src/eepp/ui/uipushbutton.cpp @@ -1,4 +1,3 @@ -#include #include #include #include diff --git a/src/eepp/ui/uiscenenode.cpp b/src/eepp/ui/uiscenenode.cpp index 450d4e939..03b921e93 100644 --- a/src/eepp/ui/uiscenenode.cpp +++ b/src/eepp/ui/uiscenenode.cpp @@ -142,6 +142,7 @@ UISceneNode::UISceneNode( EE::Window::Window* window ) : mUIIconThemeManager( UIIconThemeManager::New()->setFallbackThemeManager( mUIThemeManager ) ), mAsyncResourceLoadState( std::make_shared() ), mResourceScope( ResourceScope::New() ), + mDrawableResolver( *this ), mKeyBindings( mWindow->getInput() ) { // Reset size since the SceneNode already set it but needs to set the size from zero to emit // the required events to its children. @@ -1348,11 +1349,19 @@ UIIcon* UISceneNode::findIcon( const std::string& iconName ) { } DrawablePtr UISceneNode::findIconDrawable( const std::string& iconName, - const size_t& drawableSize ) { + const size_t& drawableSize ) { UIIcon* icon = findIcon( iconName ); return icon ? icon->createDrawable( drawableSize ) : DrawablePtr{}; } +DrawableResolver& UISceneNode::getDrawableResolver() { + return mDrawableResolver; +} + +const DrawableResolver& UISceneNode::getDrawableResolver() const { + return mDrawableResolver; +} + CSS::MediaFeatures UISceneNode::getMediaFeatures() const { CSS::MediaFeatures media; const Sizef& viewportSize = getViewportPixelsSize(); diff --git a/src/eepp/ui/uisprite.cpp b/src/eepp/ui/uisprite.cpp index 5eb1eb4d5..3b83c2ad1 100644 --- a/src/eepp/ui/uisprite.cpp +++ b/src/eepp/ui/uisprite.cpp @@ -1,4 +1,3 @@ -#include #include #include #include @@ -216,13 +215,14 @@ bool UISprite::applyProperty( const StyleSheetProperty& attribute ) { path = func.getParameters().at( 0 ); } - DrawablePtr res; UISceneNode* scene = getUISceneNode(); - if ( scene ) - res = DrawableSearcher::searchByName( path, true, scene->getReferer(), - scene->getResourceScope().get() ); - else - res = DrawableSearcher::searchByName( path, true ); + DrawablePtr res; + if ( scene ) { + res = scene->getDrawableResolver().resolve( path, true ); + } else { + DrawableResolver resolver( defaultResourceScope() ); + res = resolver.resolve( path, true ); + } if ( res ) { switch ( res->getDrawableType() ) { diff --git a/src/tests/unit_tests/resource_prerequisite_tests.cpp b/src/tests/unit_tests/resource_prerequisite_tests.cpp index 967018e89..7f1573c21 100644 --- a/src/tests/unit_tests/resource_prerequisite_tests.cpp +++ b/src/tests/unit_tests/resource_prerequisite_tests.cpp @@ -372,6 +372,11 @@ UTEST( ResourcePrerequisites, resourceScopesResolveOnlyLocalAndExplicitlyImporte EXPECT_EQ( first.get(), firstScope->findTexture( "same-name" ).get() ); EXPECT_EQ( second.get(), secondScope->findTexture( "same-name" ).get() ); + DrawablePtr scopedDrawable = firstScope->findDrawable( "same-name" ); + ASSERT_TRUE( scopedDrawable != nullptr ); + ASSERT_EQ( scopedDrawable->getDrawableType(), Drawable::TEXTUREDRAWABLE ); + EXPECT_EQ( first.get(), + static_cast( scopedDrawable.get() )->getTexture().get() ); EXPECT_EQ( shared.get(), firstScope->findTexture( "shared-name" ).get() ); EXPECT_TRUE( secondScope->findTexture( "shared-name" ) == nullptr ); EXPECT_TRUE( firstScope->findTexture( "observed-only" ) == nullptr ); @@ -382,6 +387,7 @@ UTEST( ResourcePrerequisites, resourceScopesResolveOnlyLocalAndExplicitlyImporte TextureWeakPtr externallyRetainedWeak = first; TexturePtr externallyRetained = first; + scopedDrawable.reset(); firstScope.reset(); EXPECT_FALSE( externallyRetainedWeak.expired() ); @@ -423,10 +429,22 @@ UTEST( ResourcePrerequisites, uiScenesOwnIsolatedScopesThatCanBeSharedExplicitly firstScene->getResourceScope()->publishLocal( "scene-texture", texture ); EXPECT_TRUE( secondScene->getResourceScope()->findTexture( "scene-texture" ) == nullptr ); + DrawablePtr firstDrawable = firstScene->getDrawableResolver().resolve( "scene-texture" ); + ASSERT_TRUE( firstDrawable != nullptr ); + ASSERT_EQ( firstDrawable->getDrawableType(), Drawable::TEXTUREDRAWABLE ); + EXPECT_EQ( texture.get(), + static_cast( firstDrawable.get() )->getTexture().get() ); + EXPECT_TRUE( secondScene->getDrawableResolver().resolve( "scene-texture" ) == nullptr ); secondScene->setResourceScope( firstScene->getResourceScope() ); EXPECT_EQ( texture.get(), secondScene->getResourceScope()->findTexture( "scene-texture" ).get() ); + DrawablePtr sharedDrawable = secondScene->getDrawableResolver().resolve( "scene-texture" ); + ASSERT_TRUE( sharedDrawable != nullptr ); + EXPECT_EQ( texture.get(), + static_cast( sharedDrawable.get() )->getTexture().get() ); + sharedDrawable.reset(); + firstDrawable.reset(); texture.reset(); eeDelete( secondScene ); eeDelete( firstScene ); diff --git a/src/tests/unit_tests/uihtml_tests.cpp b/src/tests/unit_tests/uihtml_tests.cpp index 581587497..5dfac9521 100644 --- a/src/tests/unit_tests/uihtml_tests.cpp +++ b/src/tests/unit_tests/uihtml_tests.cpp @@ -1,7 +1,6 @@ #include "compareimages.hpp" #include "utest.hpp" -#include #include #include #include @@ -5142,8 +5141,7 @@ UTEST( UIHTML, DeferredFileImageReusesCachedTexture ) { sceneNode->setURI( URI( "file://" + processPath ) ); URI imageURI = sceneNode->solveRelativePath( URI( "../assets/icon/ee.png" ) ); ASSERT_TRUE( FileSystem::fileExists( imageURI.getFSPath() ) ); - DrawablePtr cached = DrawableSearcher::searchByName( - imageURI.toString(), false, sceneNode->getReferer(), sceneNode->getResourceScope().get() ); + DrawablePtr cached = sceneNode->getDrawableResolver().resolve( imageURI.toString() ); ASSERT_TRUE( cached != nullptr ); sceneNode->loadLayoutFromString( HTMLFormatter::HTMLtoXML( R"html(