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.
This commit is contained in:
Martín Lucas Golini
2026-07-21 23:50:58 -03:00
parent b5efd6d709
commit f7dba042df
21 changed files with 353 additions and 326 deletions
@@ -1,6 +1,6 @@
# eepp shared-resource ownership architecture # 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 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 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 ### 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 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 to their explicit scope. Keep CSS/icon/glyph interpretation in UI and cookie/navigation concerns in
Web services. Web services.
-1
View File
@@ -10,7 +10,6 @@
#include <eepp/graphics/drawable.hpp> #include <eepp/graphics/drawable.hpp>
#include <eepp/graphics/drawablegroup.hpp> #include <eepp/graphics/drawablegroup.hpp>
#include <eepp/graphics/drawableresource.hpp> #include <eepp/graphics/drawableresource.hpp>
#include <eepp/graphics/drawablesearcher.hpp>
#include <eepp/graphics/font.hpp> #include <eepp/graphics/font.hpp>
#include <eepp/graphics/fontbmfont.hpp> #include <eepp/graphics/fontbmfont.hpp>
#include <eepp/graphics/fontfamily.hpp> #include <eepp/graphics/fontfamily.hpp>
@@ -1,30 +0,0 @@
#ifndef EE_GRAPHICS_DRAWABLEMANAGER_HPP
#define EE_GRAPHICS_DRAWABLEMANAGER_HPP
#include <eepp/core/core.hpp>
#include <eepp/graphics/drawable.hpp>
#include <eepp/network/uri.hpp>
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
+3
View File
@@ -1,6 +1,7 @@
#ifndef EE_GRAPHICS_RESOURCESCOPE_HPP #ifndef EE_GRAPHICS_RESOURCESCOPE_HPP
#define EE_GRAPHICS_RESOURCESCOPE_HPP #define EE_GRAPHICS_RESOURCESCOPE_HPP
#include <eepp/graphics/drawable.hpp>
#include <eepp/graphics/resourcecatalog.hpp> #include <eepp/graphics/resourcecatalog.hpp>
namespace EE { namespace Graphics { namespace EE { namespace Graphics {
@@ -17,6 +18,8 @@ class EE_API ResourceScope {
TexturePtr findTexture( const ResourceKey& key ) const; TexturePtr findTexture( const ResourceKey& key ) const;
TexturePtr findTexture( const std::string& 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( ResourceKey key, TexturePtr texture );
void publishLocal( std::string key, TexturePtr texture ); void publishLocal( std::string key, TexturePtr texture );
+1
View File
@@ -47,6 +47,7 @@
#include <eepp/ui/doc/textposition.hpp> #include <eepp/ui/doc/textposition.hpp>
#include <eepp/ui/doc/textrange.hpp> #include <eepp/ui/doc/textrange.hpp>
#include <eepp/ui/doc/textundostack.hpp> #include <eepp/ui/doc/textundostack.hpp>
#include <eepp/ui/drawableresolver.hpp>
#include <eepp/ui/iconmanager.hpp> #include <eepp/ui/iconmanager.hpp>
#include <eepp/ui/inlinelayouter.hpp> #include <eepp/ui/inlinelayouter.hpp>
#include <eepp/ui/keyboardshortcut.hpp> #include <eepp/ui/keyboardshortcut.hpp>
+31
View File
@@ -0,0 +1,31 @@
#ifndef EE_UI_DRAWABLERESOLVER_HPP
#define EE_UI_DRAWABLERESOLVER_HPP
#include <eepp/graphics/drawable.hpp>
#include <eepp/graphics/resourcescope.hpp>
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
+8
View File
@@ -10,6 +10,7 @@
#include <eepp/system/translator.hpp> #include <eepp/system/translator.hpp>
#include <eepp/ui/colorschemepreferences.hpp> #include <eepp/ui/colorschemepreferences.hpp>
#include <eepp/ui/css/stylesheet.hpp> #include <eepp/ui/css/stylesheet.hpp>
#include <eepp/ui/drawableresolver.hpp>
#include <eepp/ui/keyboardshortcut.hpp> #include <eepp/ui/keyboardshortcut.hpp>
#include <eepp/ui/layoutinvalidation.hpp> #include <eepp/ui/layoutinvalidation.hpp>
@@ -566,6 +567,12 @@ class EE_API UISceneNode : public SceneNode {
*/ */
DrawablePtr findIconDrawable( const std::string& iconName, const size_t& drawableSize ); 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. * @brief Gets the keybindings manager.
* *
@@ -910,6 +917,7 @@ class EE_API UISceneNode : public SceneNode {
UnorderedMap<Font*, std::string> mFontFaceFamilies; UnorderedMap<Font*, std::string> mFontFaceFamilies;
std::shared_ptr<AsyncResourceLoadState> mAsyncResourceLoadState; std::shared_ptr<AsyncResourceLoadState> mAsyncResourceLoadState;
Graphics::ResourceScopePtr mResourceScope; Graphics::ResourceScopePtr mResourceScope;
DrawableResolver mDrawableResolver;
KeyBindings mKeyBindings; KeyBindings mKeyBindings;
std::map<std::string, KeyBindingCommand> mKeyBindingCommands; std::map<std::string, KeyBindingCommand> mKeyBindingCommands;
UnorderedSet<UIWidget*> mDirtyStyle; UnorderedSet<UIWidget*> mDirtyStyle;
+2 -2
View File
@@ -57,7 +57,7 @@
../../include/eepp/graphics/drawablegroup.hpp ../../include/eepp/graphics/drawablegroup.hpp
../../include/eepp/graphics/drawable.hpp ../../include/eepp/graphics/drawable.hpp
../../include/eepp/graphics/drawableresource.hpp ../../include/eepp/graphics/drawableresource.hpp
../../include/eepp/graphics/drawablesearcher.hpp ../../include/eepp/ui/drawableresolver.hpp
../../include/eepp/graphics/fontbmfont.hpp ../../include/eepp/graphics/fontbmfont.hpp
../../include/eepp/graphics/font.hpp ../../include/eepp/graphics/font.hpp
../../include/eepp/graphics/fontfamily.hpp ../../include/eepp/graphics/fontfamily.hpp
@@ -555,7 +555,7 @@
../../src/eepp/graphics/drawable.cpp ../../src/eepp/graphics/drawable.cpp
../../src/eepp/graphics/drawablegroup.cpp ../../src/eepp/graphics/drawablegroup.cpp
../../src/eepp/graphics/drawableresource.cpp ../../src/eepp/graphics/drawableresource.cpp
../../src/eepp/graphics/drawablesearcher.cpp ../../src/eepp/ui/drawableresolver.cpp
../../src/eepp/graphics/fontbmfont.cpp ../../src/eepp/graphics/fontbmfont.cpp
../../src/eepp/graphics/font.cpp ../../src/eepp/graphics/font.cpp
../../src/eepp/graphics/fontfamily.cpp ../../src/eepp/graphics/fontfamily.cpp
+2 -2
View File
@@ -57,7 +57,7 @@
../../include/eepp/graphics/drawablegroup.hpp ../../include/eepp/graphics/drawablegroup.hpp
../../include/eepp/graphics/drawable.hpp ../../include/eepp/graphics/drawable.hpp
../../include/eepp/graphics/drawableresource.hpp ../../include/eepp/graphics/drawableresource.hpp
../../include/eepp/graphics/drawablesearcher.hpp ../../include/eepp/ui/drawableresolver.hpp
../../include/eepp/graphics/fontbmfont.hpp ../../include/eepp/graphics/fontbmfont.hpp
../../include/eepp/graphics/font.hpp ../../include/eepp/graphics/font.hpp
../../include/eepp/graphics/fontfamily.hpp ../../include/eepp/graphics/fontfamily.hpp
@@ -546,7 +546,7 @@
../../src/eepp/graphics/drawable.cpp ../../src/eepp/graphics/drawable.cpp
../../src/eepp/graphics/drawablegroup.cpp ../../src/eepp/graphics/drawablegroup.cpp
../../src/eepp/graphics/drawableresource.cpp ../../src/eepp/graphics/drawableresource.cpp
../../src/eepp/graphics/drawablesearcher.cpp ../../src/eepp/ui/drawableresolver.cpp
../../src/eepp/graphics/fontbmfont.cpp ../../src/eepp/graphics/fontbmfont.cpp
../../src/eepp/graphics/font.cpp ../../src/eepp/graphics/font.cpp
../../src/eepp/graphics/fontfamily.cpp ../../src/eepp/graphics/fontfamily.cpp
+2 -2
View File
@@ -56,7 +56,7 @@
../../include/eepp/graphics/drawablegroup.hpp ../../include/eepp/graphics/drawablegroup.hpp
../../include/eepp/graphics/drawable.hpp ../../include/eepp/graphics/drawable.hpp
../../include/eepp/graphics/drawableresource.hpp ../../include/eepp/graphics/drawableresource.hpp
../../include/eepp/graphics/drawablesearcher.hpp ../../include/eepp/ui/drawableresolver.hpp
../../include/eepp/graphics/fontbmfont.hpp ../../include/eepp/graphics/fontbmfont.hpp
../../include/eepp/graphics/font.hpp ../../include/eepp/graphics/font.hpp
../../include/eepp/graphics/fontmanager.hpp ../../include/eepp/graphics/fontmanager.hpp
@@ -538,7 +538,7 @@
../../src/eepp/graphics/drawable.cpp ../../src/eepp/graphics/drawable.cpp
../../src/eepp/graphics/drawablegroup.cpp ../../src/eepp/graphics/drawablegroup.cpp
../../src/eepp/graphics/drawableresource.cpp ../../src/eepp/graphics/drawableresource.cpp
../../src/eepp/graphics/drawablesearcher.cpp ../../src/eepp/ui/drawableresolver.cpp
../../src/eepp/graphics/fontbmfont.cpp ../../src/eepp/graphics/fontbmfont.cpp
../../src/eepp/graphics/font.cpp ../../src/eepp/graphics/font.cpp
../../src/eepp/graphics/fontmanager.cpp ../../src/eepp/graphics/fontmanager.cpp
-244
View File
@@ -1,244 +0,0 @@
#include <eepp/graphics/drawablesearcher.hpp>
#include <eepp/graphics/globaltextureatlas.hpp>
#include <eepp/graphics/ninepatchmanager.hpp>
#include <eepp/graphics/resourcescope.hpp>
#include <eepp/graphics/sprite.hpp>
#include <eepp/graphics/textureatlasmanager.hpp>
#include <eepp/graphics/texturefactory.hpp>
#include <eepp/system/base64.hpp>
#include <eepp/system/filesystem.hpp>
#include <eepp/system/md5.hpp>
#include <eepp/network/http.hpp>
#include <eepp/network/uri.hpp>
#include <eepp/window/engine.hpp>
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<TextureRegion*> 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<std::string, std::string> 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
+68
View File
@@ -1,5 +1,9 @@
#include <algorithm> #include <algorithm>
#include <eepp/core/string.hpp>
#include <eepp/graphics/ninepatchmanager.hpp>
#include <eepp/graphics/resourcescope.hpp> #include <eepp/graphics/resourcescope.hpp>
#include <eepp/graphics/sprite.hpp>
#include <eepp/graphics/textureatlasmanager.hpp>
#include <eepp/system/lock.hpp> #include <eepp/system/lock.hpp>
#include <eepp/window/engine.hpp> #include <eepp/window/engine.hpp>
@@ -30,6 +34,70 @@ TexturePtr ResourceScope::findTexture( const std::string& key ) const {
return {}; return {};
} }
DrawablePtr ResourceScope::findDrawable( const std::string& name, bool firstSearchSprite ) const {
if ( name.empty() )
return {};
auto findSprite = []( const std::string& pattern ) -> DrawablePtr {
std::vector<TextureRegion*> 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 ) { void ResourceScope::publishLocal( ResourceKey key, TexturePtr texture ) {
publishLocal( key.value(), std::move( texture ) ); publishLocal( key.value(), std::move( texture ) );
} }
+8 -17
View File
@@ -1,11 +1,9 @@
#include <eepp/graphics/circledrawable.hpp> #include <eepp/graphics/circledrawable.hpp>
#include <eepp/graphics/convexshapedrawable.hpp> #include <eepp/graphics/convexshapedrawable.hpp>
#include <eepp/graphics/drawable.hpp> #include <eepp/graphics/drawable.hpp>
#include <eepp/graphics/drawablesearcher.hpp>
#include <eepp/graphics/fontmanager.hpp> #include <eepp/graphics/fontmanager.hpp>
#include <eepp/graphics/rectangledrawable.hpp> #include <eepp/graphics/rectangledrawable.hpp>
#include <eepp/graphics/triangledrawable.hpp> #include <eepp/graphics/triangledrawable.hpp>
#include <eepp/scene/scenemanager.hpp>
#include <eepp/system/log.hpp> #include <eepp/system/log.hpp>
#include <eepp/system/luapattern.hpp> #include <eepp/system/luapattern.hpp>
#include <eepp/ui/css/drawableimageparser.hpp> #include <eepp/ui/css/drawableimageparser.hpp>
@@ -16,7 +14,6 @@
#include <eepp/ui/uiscenenode.hpp> #include <eepp/ui/uiscenenode.hpp>
using namespace EE::Graphics; using namespace EE::Graphics;
using namespace EE::Scene;
namespace EE { namespace UI { namespace CSS { namespace EE { namespace UI { namespace CSS {
@@ -163,9 +160,8 @@ DrawablePtr DrawableImageParser::createDrawable( const std::string& value, const
if ( !functionType.isEmpty() ) { if ( !functionType.isEmpty() ) {
if ( exists( functionType.getName() ) ) if ( exists( functionType.getName() ) )
return mFuncs[functionType.getName()]( functionType, size, node ); return mFuncs[functionType.getName()]( functionType, size, node );
} else if ( DrawablePtr drawable = DrawableSearcher::searchByName( } else if ( DrawablePtr drawable =
value, false, node->getUISceneNode()->getReferer(), node->getUISceneNode()->getDrawableResolver().resolve( value ) ) {
node->getUISceneNode()->getResourceScope().get() ) ) {
return drawable; return drawable;
} }
@@ -909,10 +905,8 @@ void DrawableImageParser::registerBaseParsers() {
const auto& param = functionType.getParameters().at( 0 ); const auto& param = functionType.getParameters().at( 0 );
if ( functionType.getName() == "url" && !param.empty() && param[0] != '@' && if ( functionType.getName() == "url" && !param.empty() && param[0] != '@' &&
!String::startsWith( param, "data:image/" ) ) { !String::startsWith( param, "data:image/" ) ) {
DrawablePtr drawable = DrawableSearcher::searchByName( DrawablePtr drawable = node->getUISceneNode()->getDrawableResolver().resolve(
node->getUISceneNode()->solveRelativePath( param ).toString(), false, node->getUISceneNode()->solveRelativePath( param ).toString() );
node->getUISceneNode()->getReferer(),
node->getUISceneNode()->getResourceScope().get() );
return drawable; return drawable;
} else if ( functionType.getParameters().size() > 1 && } else if ( functionType.getParameters().size() > 1 &&
String::startsWith( param, "data:image/" ) ) { String::startsWith( param, "data:image/" ) ) {
@@ -921,20 +915,17 @@ void DrawableImageParser::registerBaseParsers() {
cparam += ','; cparam += ',';
cparam += functionType.getParameters().at( i ); cparam += functionType.getParameters().at( i );
} }
DrawablePtr drawable = DrawableSearcher::searchByName( DrawablePtr drawable =
cparam, false, node->getUISceneNode()->getReferer(), node->getUISceneNode()->getDrawableResolver().resolve( cparam );
node->getUISceneNode()->getResourceScope().get() );
return drawable; return drawable;
} }
DrawablePtr drawable = DrawableSearcher::searchByName( DrawablePtr drawable = node->getUISceneNode()->getDrawableResolver().resolve( param );
param, false, node->getUISceneNode()->getReferer(),
node->getUISceneNode()->getResourceScope().get() );
return drawable; return drawable;
}; };
mFuncs["icon"] = []( const FunctionString& functionType, const Sizef& size, mFuncs["icon"] = []( const FunctionString& functionType, const Sizef& size,
UINode* node ) -> DrawablePtr { UINode* node ) -> DrawablePtr {
auto* uiScene = SceneManager::instance()->getUISceneNode(); auto* uiScene = node->getUISceneNode();
const auto& params = functionType.getParameters(); const auto& params = functionType.getParameters();
if ( params.size() < 2 ) if ( params.size() < 2 )
return nullptr; return nullptr;
+160
View File
@@ -0,0 +1,160 @@
#include <eepp/graphics/image.hpp>
#include <eepp/graphics/pixeldensity.hpp>
#include <eepp/graphics/texturefactory.hpp>
#include <eepp/network/http.hpp>
#include <eepp/network/uri.hpp>
#include <eepp/system/base64.hpp>
#include <eepp/system/filesystem.hpp>
#include <eepp/system/log.hpp>
#include <eepp/system/md5.hpp>
#include <eepp/ui/drawableresolver.hpp>
#include <eepp/ui/uiscenenode.hpp>
#include <eepp/window/engine.hpp>
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<const unsigned char*>( 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<const unsigned char*>( 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<const Uint8*>( 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
+6 -4
View File
@@ -1,5 +1,4 @@
#include <eepp/graphics/drawable.hpp> #include <eepp/graphics/drawable.hpp>
#include <eepp/graphics/drawablesearcher.hpp>
#include <eepp/graphics/image.hpp> #include <eepp/graphics/image.hpp>
#include <eepp/graphics/sprite.hpp> #include <eepp/graphics/sprite.hpp>
#include <eepp/graphics/texture.hpp> #include <eepp/graphics/texture.hpp>
@@ -555,9 +554,12 @@ bool UIImage::applyProperty( const StyleSheetProperty& attribute ) {
if ( createdDrawable ) { if ( createdDrawable ) {
setDrawable( std::move( createdDrawable ) ); setDrawable( std::move( createdDrawable ) );
} else { } else {
setDrawable( DrawableSearcher::searchByName( if ( scene ) {
path, false, scene ? scene->getReferer() : URI(), setDrawable( scene->getDrawableResolver().resolve( path ) );
scene ? scene->getResourceScope().get() : nullptr ) ); } else {
DrawableResolver resolver( defaultResourceScope() );
setDrawable( resolver.resolve( path ) );
}
} }
break; break;
} }
+13 -11
View File
@@ -1,4 +1,3 @@
#include <eepp/graphics/drawablesearcher.hpp>
#include <eepp/graphics/font.hpp> #include <eepp/graphics/font.hpp>
#include <eepp/ui/css/propertydefinition.hpp> #include <eepp/ui/css/propertydefinition.hpp>
#include <eepp/ui/uiiconthememanager.hpp> #include <eepp/ui/uiiconthememanager.hpp>
@@ -553,18 +552,23 @@ Uint32 UIMenu::onKeyDown( const KeyEvent& event ) {
return UIWidget::onKeyDown( event ); return UIWidget::onKeyDown( event );
} }
static DrawablePtr getIconDrawable( const std::string& name, static DrawablePtr getIconDrawable( const std::string& name, UISceneNode* sceneNode ) {
UIIconThemeManager* iconThemeManager ) {
DrawablePtr iconDrawable; DrawablePtr iconDrawable;
if ( nullptr != iconThemeManager ) { if ( sceneNode ) {
UIIcon* icon = iconThemeManager->findIcon( name ); UIIcon* icon = sceneNode->findIcon( name );
if ( icon ) { if ( icon ) {
// TODO: Fix size // TODO: Fix size
iconDrawable = icon->createDrawable( PixelDensity::dpToPx( 16 ) ); iconDrawable = icon->createDrawable( PixelDensity::dpToPx( 16 ) );
} }
} }
if ( nullptr == iconDrawable ) if ( !iconDrawable ) {
iconDrawable = DrawableSearcher::searchByName( name ); if ( sceneNode ) {
iconDrawable = sceneNode->getDrawableResolver().resolve( name );
} else {
DrawableResolver resolver( defaultResourceScope() );
iconDrawable = resolver.resolve( name );
}
}
return iconDrawable; return iconDrawable;
} }
@@ -579,8 +583,7 @@ void UIMenu::loadFromXmlNode( const pugi::xml_node& node ) {
std::string text( item.attribute( "text" ).as_string() ); std::string text( item.attribute( "text" ).as_string() );
std::string icon( item.attribute( "icon" ).as_string() ); std::string icon( item.attribute( "icon" ).as_string() );
if ( nullptr != mSceneNode && mSceneNode->isUISceneNode() ) if ( nullptr != mSceneNode && mSceneNode->isUISceneNode() )
add( getTranslatorString( text ), add( getTranslatorString( text ), getIconDrawable( icon, getUISceneNode() ) );
getIconDrawable( icon, getUISceneNode()->getUIIconThemeManager() ) );
} else if ( name == "menuseparator" || name == "separator" ) { } else if ( name == "menuseparator" || name == "separator" ) {
addSeparator(); addSeparator();
} else if ( name == "menucheckbox" || name == "checkbox" ) { } else if ( name == "menucheckbox" || name == "checkbox" ) {
@@ -598,8 +601,7 @@ void UIMenu::loadFromXmlNode( const pugi::xml_node& node ) {
if ( nullptr != getDrawInvalidator() ) if ( nullptr != getDrawInvalidator() )
subMenu->setParent( getDrawInvalidator() ); subMenu->setParent( getDrawInvalidator() );
subMenu->loadFromXmlNode( item ); subMenu->loadFromXmlNode( item );
addSubMenu( getTranslatorString( text ), addSubMenu( getTranslatorString( text ), getIconDrawable( icon, getUISceneNode() ),
getIconDrawable( icon, getUISceneNode()->getUIIconThemeManager() ),
subMenu ); subMenu );
} }
} }
-1
View File
@@ -1,4 +1,3 @@
#include <eepp/graphics/drawablesearcher.hpp>
#include <eepp/graphics/text.hpp> #include <eepp/graphics/text.hpp>
#include <eepp/ui/css/propertydefinition.hpp> #include <eepp/ui/css/propertydefinition.hpp>
#include <eepp/ui/uiicon.hpp> #include <eepp/ui/uiicon.hpp>
+10 -1
View File
@@ -142,6 +142,7 @@ UISceneNode::UISceneNode( EE::Window::Window* window ) :
mUIIconThemeManager( UIIconThemeManager::New()->setFallbackThemeManager( mUIThemeManager ) ), mUIIconThemeManager( UIIconThemeManager::New()->setFallbackThemeManager( mUIThemeManager ) ),
mAsyncResourceLoadState( std::make_shared<AsyncResourceLoadState>() ), mAsyncResourceLoadState( std::make_shared<AsyncResourceLoadState>() ),
mResourceScope( ResourceScope::New() ), mResourceScope( ResourceScope::New() ),
mDrawableResolver( *this ),
mKeyBindings( mWindow->getInput() ) { mKeyBindings( mWindow->getInput() ) {
// Reset size since the SceneNode already set it but needs to set the size from zero to emit // Reset size since the SceneNode already set it but needs to set the size from zero to emit
// the required events to its children. // the required events to its children.
@@ -1348,11 +1349,19 @@ UIIcon* UISceneNode::findIcon( const std::string& iconName ) {
} }
DrawablePtr UISceneNode::findIconDrawable( const std::string& iconName, DrawablePtr UISceneNode::findIconDrawable( const std::string& iconName,
const size_t& drawableSize ) { const size_t& drawableSize ) {
UIIcon* icon = findIcon( iconName ); UIIcon* icon = findIcon( iconName );
return icon ? icon->createDrawable( drawableSize ) : DrawablePtr{}; 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 UISceneNode::getMediaFeatures() const {
CSS::MediaFeatures media; CSS::MediaFeatures media;
const Sizef& viewportSize = getViewportPixelsSize(); const Sizef& viewportSize = getViewportPixelsSize();
+7 -7
View File
@@ -1,4 +1,3 @@
#include <eepp/graphics/drawablesearcher.hpp>
#include <eepp/graphics/globaltextureatlas.hpp> #include <eepp/graphics/globaltextureatlas.hpp>
#include <eepp/graphics/sprite.hpp> #include <eepp/graphics/sprite.hpp>
#include <eepp/graphics/texturedrawable.hpp> #include <eepp/graphics/texturedrawable.hpp>
@@ -216,13 +215,14 @@ bool UISprite::applyProperty( const StyleSheetProperty& attribute ) {
path = func.getParameters().at( 0 ); path = func.getParameters().at( 0 );
} }
DrawablePtr res;
UISceneNode* scene = getUISceneNode(); UISceneNode* scene = getUISceneNode();
if ( scene ) DrawablePtr res;
res = DrawableSearcher::searchByName( path, true, scene->getReferer(), if ( scene ) {
scene->getResourceScope().get() ); res = scene->getDrawableResolver().resolve( path, true );
else } else {
res = DrawableSearcher::searchByName( path, true ); DrawableResolver resolver( defaultResourceScope() );
res = resolver.resolve( path, true );
}
if ( res ) { if ( res ) {
switch ( res->getDrawableType() ) { switch ( res->getDrawableType() ) {
@@ -372,6 +372,11 @@ UTEST( ResourcePrerequisites, resourceScopesResolveOnlyLocalAndExplicitlyImporte
EXPECT_EQ( first.get(), firstScope->findTexture( "same-name" ).get() ); EXPECT_EQ( first.get(), firstScope->findTexture( "same-name" ).get() );
EXPECT_EQ( second.get(), secondScope->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<TextureDrawable*>( scopedDrawable.get() )->getTexture().get() );
EXPECT_EQ( shared.get(), firstScope->findTexture( "shared-name" ).get() ); EXPECT_EQ( shared.get(), firstScope->findTexture( "shared-name" ).get() );
EXPECT_TRUE( secondScope->findTexture( "shared-name" ) == nullptr ); EXPECT_TRUE( secondScope->findTexture( "shared-name" ) == nullptr );
EXPECT_TRUE( firstScope->findTexture( "observed-only" ) == nullptr ); EXPECT_TRUE( firstScope->findTexture( "observed-only" ) == nullptr );
@@ -382,6 +387,7 @@ UTEST( ResourcePrerequisites, resourceScopesResolveOnlyLocalAndExplicitlyImporte
TextureWeakPtr externallyRetainedWeak = first; TextureWeakPtr externallyRetainedWeak = first;
TexturePtr externallyRetained = first; TexturePtr externallyRetained = first;
scopedDrawable.reset();
firstScope.reset(); firstScope.reset();
EXPECT_FALSE( externallyRetainedWeak.expired() ); EXPECT_FALSE( externallyRetainedWeak.expired() );
@@ -423,10 +429,22 @@ UTEST( ResourcePrerequisites, uiScenesOwnIsolatedScopesThatCanBeSharedExplicitly
firstScene->getResourceScope()->publishLocal( "scene-texture", texture ); firstScene->getResourceScope()->publishLocal( "scene-texture", texture );
EXPECT_TRUE( secondScene->getResourceScope()->findTexture( "scene-texture" ) == nullptr ); 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<TextureDrawable*>( firstDrawable.get() )->getTexture().get() );
EXPECT_TRUE( secondScene->getDrawableResolver().resolve( "scene-texture" ) == nullptr );
secondScene->setResourceScope( firstScene->getResourceScope() ); secondScene->setResourceScope( firstScene->getResourceScope() );
EXPECT_EQ( texture.get(), EXPECT_EQ( texture.get(),
secondScene->getResourceScope()->findTexture( "scene-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<TextureDrawable*>( sharedDrawable.get() )->getTexture().get() );
sharedDrawable.reset();
firstDrawable.reset();
texture.reset(); texture.reset();
eeDelete( secondScene ); eeDelete( secondScene );
eeDelete( firstScene ); eeDelete( firstScene );
+1 -3
View File
@@ -1,7 +1,6 @@
#include "compareimages.hpp" #include "compareimages.hpp"
#include "utest.hpp" #include "utest.hpp"
#include <eepp/graphics/drawablesearcher.hpp>
#include <eepp/graphics/fontfamily.hpp> #include <eepp/graphics/fontfamily.hpp>
#include <eepp/graphics/fontmanager.hpp> #include <eepp/graphics/fontmanager.hpp>
#include <eepp/graphics/fonttruetype.hpp> #include <eepp/graphics/fonttruetype.hpp>
@@ -5142,8 +5141,7 @@ UTEST( UIHTML, DeferredFileImageReusesCachedTexture ) {
sceneNode->setURI( URI( "file://" + processPath ) ); sceneNode->setURI( URI( "file://" + processPath ) );
URI imageURI = sceneNode->solveRelativePath( URI( "../assets/icon/ee.png" ) ); URI imageURI = sceneNode->solveRelativePath( URI( "../assets/icon/ee.png" ) );
ASSERT_TRUE( FileSystem::fileExists( imageURI.getFSPath() ) ); ASSERT_TRUE( FileSystem::fileExists( imageURI.getFSPath() ) );
DrawablePtr cached = DrawableSearcher::searchByName( DrawablePtr cached = sceneNode->getDrawableResolver().resolve( imageURI.toString() );
imageURI.toString(), false, sceneNode->getReferer(), sceneNode->getResourceScope().get() );
ASSERT_TRUE( cached != nullptr ); ASSERT_TRUE( cached != nullptr );
sceneNode->loadLayoutFromString( HTMLFormatter::HTMLtoXML( R"html( sceneNode->loadLayoutFromString( HTMLFormatter::HTMLtoXML( R"html(