From 82494cc4254677d3b20730dcba37c412a6ea211d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mart=C3=ADn=20Lucas=20Golini?= Date: Sun, 24 Nov 2024 01:06:24 -0300 Subject: [PATCH] Added animated gif support. --- include/eepp/graphics/image.hpp | 41 +++- include/eepp/graphics/sprite.hpp | 39 +++- include/eepp/graphics/texturefactory.hpp | 3 + include/eepp/ui/uiimage.hpp | 2 + src/eepp/graphics/image.cpp | 61 +++++- src/eepp/graphics/sprite.cpp | 232 ++++++++++++++++------- src/eepp/graphics/texturefactory.cpp | 5 + src/eepp/ui/tools/uitextureviewer.hpp | 3 +- src/eepp/ui/uiimage.cpp | 37 +++- src/eepp/ui/uiscrollview.cpp | 16 +- src/thirdparty/SOIL2 | 2 +- src/tools/ecode/ecode.cpp | 48 +++-- 12 files changed, 375 insertions(+), 114 deletions(-) diff --git a/include/eepp/graphics/image.hpp b/include/eepp/graphics/image.hpp index 46351c44d..2680bd693 100644 --- a/include/eepp/graphics/image.hpp +++ b/include/eepp/graphics/image.hpp @@ -24,6 +24,24 @@ namespace EE { namespace Graphics { /** @brief A simple image class to manipulate them. */ class EE_API Image { public: + enum class Format { + Unknown = 0, + JPEG = 1, + PNG = 2, + BMP = 3, + GIF = 4, + TGA = 5, + PSD = 6, + PIC = 7, + PNM = 8, + DDS = 9, + PVR = 10, + PKM = 11, + HDR = 12, + QOI = 13, + SVG = 14, + }; + /** @enum PixelFormat Format Pixel formats to write into a texture image. */ enum PixelFormat { PIXEL_FORMAT_RED, @@ -89,6 +107,9 @@ class EE_API Image { Uint32 mJpegSaveQuality; }; + /* @return an array of images and the delay of the first frame */ + static std::pair, int> loadGif( IOStream& stream ); + /** @return The File Extension of a Save Type */ static std::string saveTypeToExtension( const Int32& Format ); @@ -130,6 +151,22 @@ class EE_API Image { */ static bool isImage( const std::string& path ); + /** @return True if the file is a valid image ( reads the file header to know if the file is an + * image file format supported ) + * @param path the image path + */ + static bool isImage( const unsigned char* data, const size_t& dataSize ); + + /** @return The image format if valid + * @param path the image path + */ + static Image::Format getFormat( const std::string& path ); + + /** @return The image format if valid + * @param path the image path + */ + static Image::Format getFormat( const unsigned char* data, const size_t& dataSize ); + /** @return If the path or file name has a supported image file extension * @param path the image path or file name */ @@ -248,6 +285,8 @@ class EE_API Image { /** @return A pointer to the first pixel of the image. */ virtual const Uint8* getPixelsPtr(); + virtual const Uint8* getPixelsPtr() const; + /** Return the pointer to the array containing the image */ Uint8* getPixels() const; @@ -279,7 +318,7 @@ class EE_API Image { Sizei getSize(); /** Save the Image to a new File in a specific format */ - virtual bool saveToFile( const std::string& filepath, const SaveType& Format ); + virtual bool saveToFile( const std::string& filepath, const SaveType& Format ) const; /** Create an Alpha mask from a Color */ virtual void createMaskFromColor( const Color& ColorKey, Uint8 Alpha ); diff --git a/include/eepp/graphics/sprite.hpp b/include/eepp/graphics/sprite.hpp index 49bc6cda3..bf1f71302 100644 --- a/include/eepp/graphics/sprite.hpp +++ b/include/eepp/graphics/sprite.hpp @@ -22,6 +22,7 @@ class EE_API Sprite : public Drawable { SPRITE_EVENT_LAST_FRAME, SPRITE_EVENT_FIRST_FRAME, SPRITE_EVENT_END_ANIM_TO, + SPRITE_EVENT_NEW_FRAME, SPRITE_EVENT_USER // User vents }; @@ -36,6 +37,8 @@ class EE_API Sprite : public Drawable { const Vector2i& offset = Vector2i( 0, 0 ), const Rect& TexSector = Rect( 0, 0, 0, 0 ) ); + static Sprite* fromGif( IOStream& gif ); + /** Instanciate an empty sprite */ Sprite(); @@ -366,10 +369,15 @@ class EE_API Sprite : public Drawable { void animToFrameAndStop( Uint32 GoTo ); /** Set the sprite events callback */ - void setEventsCallback( const SpriteCallback& Cb, void* UserData = NULL ); + void setEventsCallback( const SpriteCallback& Cb, void* UserData = nullptr ); - /** Removes the current callback */ - void clearCallback(); + /** Push a new event callback. + * @return The Callback Id + */ + Uint32 pushEventsCallback( const SpriteCallback& cb, void* UserData = nullptr ); + + /** Pop the event callback id indicated. */ + bool popEventsCallback( const Uint32& callbackId ); /** Creates a copy of the current sprite and returns it */ Sprite clone(); @@ -383,13 +391,23 @@ class EE_API Sprite : public Drawable { /** Fire a User Event in the sprite */ void fireEvent( const Uint32& Event ); + Sprite& setAsTextureRegionOwner( bool set ); + + bool isTextureRegionOwner() const; + + Sprite& setAsTextureOwner( bool set ); + + bool isTextureOwner() const; + protected: enum SpriteFlags { SPRITE_FLAG_AUTO_ANIM = ( 1 << 0 ), SPRITE_FLAG_REVERSE_ANIM = ( 1 << 1 ), SPRITE_FLAG_ANIM_PAUSED = ( 1 << 2 ), SPRITE_FLAG_ANIM_TO_FRAME_AND_STOP = ( 1 << 3 ), - SPRITE_FLAG_EVENTS_ENABLED = ( 1 << 4 ) + SPRITE_FLAG_EVENTS_ENABLED = ( 1 << 4 ), + SPRITE_FLAG_TEXTURE_OWNER = ( 1 << 5 ), + SPRITE_FLAG_TEXTURE_REGION_OWNER = ( 1 << 6 ), }; Uint32 mFlags{ SPRITE_FLAG_AUTO_ANIM | SPRITE_FLAG_EVENTS_ENABLED }; @@ -397,6 +415,7 @@ class EE_API Sprite : public Drawable { Float mRotation{ 0.f }; Vector2f mScale{ 1.f, 1.f }; Float mAnimSpeed{ 16.f }; + Uint32 mNumCallBacks{ 0 }; Color* mVertexColors{ nullptr }; @@ -412,11 +431,13 @@ class EE_API Sprite : public Drawable { unsigned int mSubFrames{ 1 }; unsigned int mAnimTo{ 0 }; - SpriteCallback mCb; - void* mUserData{ nullptr }; + struct SpriteCbData { + SpriteCallback cb; + void* userData{ nullptr }; + }; + UnorderedMap mCallbacks; - class Frame { - public: + struct Frame { std::vector Spr; }; std::vector mFrames; @@ -425,6 +446,8 @@ class EE_API Sprite : public Drawable { void clearFrame(); + void cleanUpResources(); + unsigned int getFrame( const unsigned int& FrameNum ); unsigned int getSubFrame( const unsigned int& SubFrame ); diff --git a/include/eepp/graphics/texturefactory.hpp b/include/eepp/graphics/texturefactory.hpp index 6c867404c..29043b196 100644 --- a/include/eepp/graphics/texturefactory.hpp +++ b/include/eepp/graphics/texturefactory.hpp @@ -199,6 +199,9 @@ class EE_API TextureFactory : protected Mutex { /** Determine if the TextureId passed exists */ bool existsId( const Uint32& TexId ); + /** Determine if the TextureId passed exists */ + bool exists( const Texture* tex ); + /** @return A pointer to the Texture */ Texture* getTexture( const Uint32& TexId ); diff --git a/include/eepp/ui/uiimage.hpp b/include/eepp/ui/uiimage.hpp index 01d1916d4..d7954b6d1 100644 --- a/include/eepp/ui/uiimage.hpp +++ b/include/eepp/ui/uiimage.hpp @@ -40,6 +40,8 @@ class EE_API UIImage : public UIWidget { virtual std::string getPropertyString( const PropertyDefinition* propertyDef, const Uint32& propertyIndex = 0 ) const; + virtual void scheduledUpdate( const Time& time ); + virtual std::vector getPropertiesImplemented() const; const UIScaleType& getScaleType() const; diff --git a/src/eepp/graphics/image.cpp b/src/eepp/graphics/image.cpp index cb1c02a8e..24ca53296 100644 --- a/src/eepp/graphics/image.cpp +++ b/src/eepp/graphics/image.cpp @@ -387,6 +387,25 @@ bool Image::isImage( const std::string& path ) { return STBI_unknown != stbi_test( path.c_str() ) || svg_test( path ); } +bool Image::isImage( const unsigned char* data, const size_t& dataSize ) { + return STBI_unknown != stbi_test_from_memory( data, dataSize ) || + svg_test_from_memory( data, dataSize ); +} + +Image::Format Image::getFormat( const std::string& path ) { + auto format = stbi_test( path.c_str() ); + if ( format == STBI_unknown ) + return svg_test( path ) ? Image::Format::SVG : Image::Format::Unknown; + return static_cast( format ); +} + +Image::Format Image::getFormat( const unsigned char* data, const size_t& dataSize ) { + auto format = stbi_test_from_memory( data, dataSize ); + if ( format == STBI_unknown ) + return svg_test_from_memory( data, dataSize ) ? Image::Format::SVG : Image::Format::Unknown; + return static_cast( format ); +} + bool Image::isImageExtension( const std::string& path ) { const std::string ext( FileSystem::fileExtension( path ) ); return ( ext == "png" || ext == "tga" || ext == "bmp" || ext == "jpg" || ext == "gif" || @@ -670,6 +689,10 @@ const Uint8* Image::getPixelsPtr() { return reinterpret_cast( &mPixels[0] ); } +const Uint8* Image::getPixelsPtr() const { + return reinterpret_cast( &mPixels[0] ); +} + Color Image::getPixel( const unsigned int& x, const unsigned int& y ) { eeASSERT( !( mPixels == NULL || x > mWidth || y > mHeight ) ); Color dst; @@ -747,7 +770,7 @@ unsigned int Image::getChannels() const { return mChannels; } -bool Image::saveToFile( const std::string& filepath, const SaveType& Format ) { +bool Image::saveToFile( const std::string& filepath, const SaveType& Format ) const { bool Res = false; std::string fpath( FileSystem::fileRemoveFileName( filepath ) ); @@ -1000,4 +1023,40 @@ const Image::FormatConfiguration& Image::getImageFormatConfiguration() const { return mFormatConfiguration; } +std::pair, int> Image::loadGif( IOStream& stream ) { + stbi_io_callbacks callbacks; + callbacks.read = &IOCb::read; + callbacks.skip = &IOCb::skip; + callbacks.eof = &IOCb::eof; + stream.seek( 0 ); + auto type = stbi_test_from_callbacks( &callbacks, &stream ); + if ( type != STBI_gif ) + return {}; + stream.seek( 0 ); + std::vector gif; + ScopedBuffer buf( stream.getSize() ); + stream.read( (char*)buf.get(), buf.size() ); + int width, height, frames, comp; + int* delays = NULL; + unsigned char* data = stbi_load_gif_from_memory( buf.get(), buf.size(), &delays, &width, + &height, &frames, &comp, 0 ); + + if ( data == nullptr ) + return {}; + + gif.reserve( frames ); + + unsigned char* start = data; + size_t frame_size = width * height * sizeof( unsigned char ) * comp; + for ( int i = 0; i < frames; ++i ) { + gif.emplace_back( (const Uint8*)start, width, height, comp ); + start += frame_size; + } + + auto delay = delays[0]; + free( data ); + free( delays ); + return { std::move( gif ), delay }; +} + }} // namespace EE::Graphics diff --git a/src/eepp/graphics/sprite.cpp b/src/eepp/graphics/sprite.cpp index eebdf724e..5a6ce0cef 100644 --- a/src/eepp/graphics/sprite.cpp +++ b/src/eepp/graphics/sprite.cpp @@ -28,6 +28,22 @@ Sprite* Sprite::New( const Uint32& TexId, const Sizef& DestSize, const Vector2i& return eeNew( Sprite, ( TexId, DestSize, offset, TexSector ) ); } +Sprite* Sprite::fromGif( IOStream& stream ) { + auto [gif, delay] = Image::loadGif( stream ); + Sprite* sprite = Sprite::New(); + + for ( const auto& i : gif ) { + auto texId = TextureFactory::instance()->loadFromPixels( i.getPixels(), i.getWidth(), + i.getHeight(), i.getChannels() ); + sprite->addFrame( texId ); + } + + sprite->setAnimationSpeed( 1000.f / (float)delay ); + sprite->setAsTextureRegionOwner( true ); + sprite->setAsTextureOwner( true ); + return sprite; +} + Sprite::Sprite() : Drawable( Drawable::SPRITE ) {} Sprite::Sprite( const std::string& name, const std::string& extension, @@ -47,6 +63,7 @@ Sprite::Sprite( const Uint32& TexId, const Sizef& DestSize, const Vector2i& Offs } Sprite::~Sprite() { + cleanUpResources(); eeSAFE_DELETE_ARRAY( mVertexColors ); } @@ -67,7 +84,8 @@ Sprite& Sprite::operator=( const Sprite& Other ) { mCurrentSubFrame = Other.mCurrentSubFrame; mSubFrames = Other.mSubFrames; mAnimTo = Other.mAnimTo; - mCb = Other.mCb; + mCallbacks = Other.mCallbacks; + mNumCallBacks = Other.mNumCallBacks; if ( NULL != Other.mVertexColors ) { mVertexColors = eeNewArray( Color, 4 ); @@ -101,8 +119,8 @@ Sprite Sprite::clone() { Spr.mCurrentSubFrame = mCurrentSubFrame; Spr.mSubFrames = mSubFrames; Spr.mAnimTo = mAnimTo; - Spr.mCb = mCb; - Spr.mUserData = mUserData; + Spr.mCallbacks = mCallbacks; + Spr.mNumCallBacks = mNumCallBacks; if ( NULL != mVertexColors ) { Spr.mVertexColors = eeNewArray( Color, 4 ); @@ -124,7 +142,29 @@ void Sprite::clearFrame() { mFrames.clear(); } +void Sprite::cleanUpResources() { + if ( isTextureRegionOwner() || isTextureOwner() ) { + size_t frames = getNumFrames(); + + for ( size_t i = 0; i < frames; i++ ) { + for ( size_t f = 0; f < mFrames[i].Spr.size(); f++ ) { + TextureRegion* region = mFrames[i].Spr[f]; + Texture* texture = region->getTexture(); + + if ( isTextureOwner() && texture && + TextureFactory::instance()->exists( texture ) ) { + eeSAFE_DELETE( texture ); + } + + if ( isTextureRegionOwner() ) + GlobalTextureAtlas::instance()->remove( region ); + } + } + } +} + void Sprite::reset() { + cleanUpResources(); clearFrame(); mFlags = SPRITE_FLAG_AUTO_ANIM | SPRITE_FLAG_EVENTS_ENABLED; @@ -146,12 +186,16 @@ void Sprite::reset() { mAnimTo = 0; disableVertexColors(); + + fireEvent( SPRITE_EVENT_NEW_FRAME ); } void Sprite::setCurrentFrame( unsigned int CurFrame ) { if ( CurFrame ) CurFrame--; + bool changed = mCurrentFrame != CurFrame; + mfCurrentFrame = CurFrame; mCurrentFrame = (unsigned int)CurFrame; @@ -164,11 +208,16 @@ void Sprite::setCurrentFrame( unsigned int CurFrame ) { mfCurrentFrame = 0.0f; mCurrentFrame = 0; } + + if ( changed ) + fireEvent( SPRITE_EVENT_NEW_FRAME ); } void Sprite::setCurrentSubFrame( const unsigned int& CurSubFrame ) { - if ( CurSubFrame < mSubFrames ) + if ( CurSubFrame < mSubFrames && mCurrentSubFrame != CurSubFrame ) { mCurrentSubFrame = CurSubFrame; + fireEvent( SPRITE_EVENT_NEW_FRAME ); + } } Quad2f Sprite::getQuad() { @@ -465,79 +514,85 @@ void Sprite::update() { } void Sprite::update( const Time& ElapsedTime ) { - if ( mFrames.size() > 1 && !SPR_FGET( SPRITE_FLAG_ANIM_PAUSED ) && Time::Zero != ElapsedTime ) { - unsigned int Size = (unsigned int)mFrames.size() - 1; + if ( !( mFrames.size() > 1 && !SPR_FGET( SPRITE_FLAG_ANIM_PAUSED ) && + Time::Zero != ElapsedTime ) ) + return; + + unsigned int Size = (unsigned int)mFrames.size() - 1; + + if ( mRepetitions == 0 ) + return; + + if ( !SPR_FGET( SPRITE_FLAG_REVERSE_ANIM ) ) + mfCurrentFrame += mAnimSpeed * ElapsedTime.asSeconds(); + else + mfCurrentFrame -= mAnimSpeed * ElapsedTime.asSeconds(); + + auto lastFrame = mCurrentFrame; + mCurrentFrame = (unsigned int)mfCurrentFrame; + + if ( SPR_FGET( SPRITE_FLAG_ANIM_TO_FRAME_AND_STOP ) ) { + if ( mAnimTo == mCurrentFrame ) { + mFlags &= ~SPRITE_FLAG_ANIM_TO_FRAME_AND_STOP; + + goToAndStop( mAnimTo ); + + fireEvent( SPRITE_EVENT_END_ANIM_TO ); - if ( mRepetitions == 0 ) return; - - if ( !SPR_FGET( SPRITE_FLAG_REVERSE_ANIM ) ) - mfCurrentFrame += mAnimSpeed * ElapsedTime.asSeconds(); - else - mfCurrentFrame -= mAnimSpeed * ElapsedTime.asSeconds(); - - mCurrentFrame = (unsigned int)mfCurrentFrame; - - if ( SPR_FGET( SPRITE_FLAG_ANIM_TO_FRAME_AND_STOP ) ) { - if ( mAnimTo == mCurrentFrame ) { - mFlags &= ~SPRITE_FLAG_ANIM_TO_FRAME_AND_STOP; - - goToAndStop( mAnimTo ); - - fireEvent( SPRITE_EVENT_END_ANIM_TO ); - - return; - } } + } - if ( !SPR_FGET( SPRITE_FLAG_REVERSE_ANIM ) && mfCurrentFrame >= Size + 1.0f ) { - if ( mRepetitions < 0 ) { - mfCurrentFrame = 0.0f; - mCurrentFrame = 0; - fireEvent( SPRITE_EVENT_FIRST_FRAME ); - } else { - if ( mRepetitions == 0 ) { - mfCurrentFrame = (Float)Size; - mCurrentFrame = Size; - fireEvent( SPRITE_EVENT_LAST_FRAME ); - } else { - mfCurrentFrame = 0.0f; - mCurrentFrame = 0; - mRepetitions--; - fireEvent( SPRITE_EVENT_FIRST_FRAME ); - } - } - } else if ( SPR_FGET( SPRITE_FLAG_REVERSE_ANIM ) && mfCurrentFrame < 0.0f ) { - if ( mRepetitions < 0 ) { - mfCurrentFrame = Size + 1.0f; + if ( !SPR_FGET( SPRITE_FLAG_REVERSE_ANIM ) && mfCurrentFrame >= Size + 1.0f ) { + if ( mRepetitions < 0 ) { + mfCurrentFrame = 0.0f; + mCurrentFrame = 0; + fireEvent( SPRITE_EVENT_FIRST_FRAME ); + } else { + if ( mRepetitions == 0 ) { + mfCurrentFrame = (Float)Size; mCurrentFrame = Size; fireEvent( SPRITE_EVENT_LAST_FRAME ); } else { - if ( mRepetitions == 0 ) { - mfCurrentFrame = 0.0f; - mCurrentFrame = 0; - fireEvent( SPRITE_EVENT_FIRST_FRAME ); - } else { - mfCurrentFrame = Size + 1.0f; - mCurrentFrame = Size; - mRepetitions--; - fireEvent( SPRITE_EVENT_LAST_FRAME ); - } + mfCurrentFrame = 0.0f; + mCurrentFrame = 0; + mRepetitions--; + fireEvent( SPRITE_EVENT_FIRST_FRAME ); } } - - if ( mfCurrentFrame < 0.0f ) { - if ( SPR_FGET( SPRITE_FLAG_REVERSE_ANIM ) ) { + } else if ( SPR_FGET( SPRITE_FLAG_REVERSE_ANIM ) && mfCurrentFrame < 0.0f ) { + if ( mRepetitions < 0 ) { + mfCurrentFrame = Size + 1.0f; + mCurrentFrame = Size; + fireEvent( SPRITE_EVENT_LAST_FRAME ); + } else { + if ( mRepetitions == 0 ) { mfCurrentFrame = 0.0f; mCurrentFrame = 0; fireEvent( SPRITE_EVENT_FIRST_FRAME ); } else { - mfCurrentFrame = (Float)Size; + mfCurrentFrame = Size + 1.0f; mCurrentFrame = Size; + mRepetitions--; fireEvent( SPRITE_EVENT_LAST_FRAME ); } } } + + if ( mfCurrentFrame < 0.0f ) { + if ( SPR_FGET( SPRITE_FLAG_REVERSE_ANIM ) ) { + mfCurrentFrame = 0.0f; + mCurrentFrame = 0; + fireEvent( SPRITE_EVENT_FIRST_FRAME ); + } else { + mfCurrentFrame = (Float)Size; + mCurrentFrame = Size; + fireEvent( SPRITE_EVENT_LAST_FRAME ); + } + } + + if ( lastFrame != mCurrentFrame ) + fireEvent( SPRITE_EVENT_NEW_FRAME ); } unsigned int Sprite::getEndFrame() { @@ -554,8 +609,12 @@ void Sprite::setReverseFromStart() { unsigned int Size = (unsigned int)mFrames.size() - 1; - mfCurrentFrame = (Float)Size; - mCurrentFrame = Size; + if ( mCurrentFrame != Size ) { + mfCurrentFrame = (Float)Size; + mCurrentFrame = Size; + + fireEvent( SPRITE_EVENT_NEW_FRAME ); + } } void Sprite::draw( const BlendMode& Blend, const RenderMode& Effect ) { @@ -804,8 +863,11 @@ void Sprite::goToAndPlay( Uint32 GoTo ) { GoTo--; if ( GoTo < mFrames.size() ) { - mCurrentFrame = GoTo; - mfCurrentFrame = (Float)GoTo; + if ( mCurrentFrame != GoTo ) { + mCurrentFrame = GoTo; + mfCurrentFrame = (Float)GoTo; + fireEvent( SPRITE_EVENT_NEW_FRAME ); + } setAnimationPaused( false ); } @@ -829,21 +891,51 @@ void Sprite::animToFrameAndStop( Uint32 GoTo ) { } } -void Sprite::setEventsCallback( const SpriteCallback& Cb, void* UserData ) { - mCb = Cb; - mUserData = UserData; +Uint32 Sprite::pushEventsCallback( const SpriteCallback& cb, void* UserData ) { + mCallbacks[++mNumCallBacks] = SpriteCbData{ cb, UserData }; + return mNumCallBacks; } -void Sprite::clearCallback() { - mCb = nullptr; +bool Sprite::popEventsCallback( const Uint32& callbackId ) { + return mCallbacks.erase( callbackId ) > 0; +} + +void Sprite::setEventsCallback( const SpriteCallback& cb, void* UserData ) { + pushEventsCallback( cb, UserData ); } void Sprite::fireEvent( const Uint32& Event ) { - if ( SPR_FGET( SPRITE_FLAG_EVENTS_ENABLED ) && mCb ) { - mCb( Event, this, mUserData ); + if ( SPR_FGET( SPRITE_FLAG_EVENTS_ENABLED ) ) { + for ( const auto& cb : mCallbacks ) { + cb.second.cb( Event, this, cb.second.userData ); + } } } +Sprite& Sprite::setAsTextureRegionOwner( bool set ) { + if ( set ) + mFlags |= SPRITE_FLAG_TEXTURE_REGION_OWNER; + else + mFlags &= ~SPRITE_FLAG_TEXTURE_REGION_OWNER; + return *this; +} + +bool Sprite::isTextureRegionOwner() const { + return mFlags & SPRITE_FLAG_TEXTURE_REGION_OWNER; +} + +Sprite& Sprite::setAsTextureOwner( bool set ) { + if ( set ) + mFlags |= SPRITE_FLAG_TEXTURE_OWNER; + else + mFlags &= ~SPRITE_FLAG_TEXTURE_OWNER; + return *this; +} + +bool Sprite::isTextureOwner() const { + return mFlags & SPRITE_FLAG_TEXTURE_OWNER; +} + void Sprite::setOrigin( const OriginPoint& origin ) { mOrigin = origin; } diff --git a/src/eepp/graphics/texturefactory.cpp b/src/eepp/graphics/texturefactory.cpp index d11e04946..b62fc87b0 100644 --- a/src/eepp/graphics/texturefactory.cpp +++ b/src/eepp/graphics/texturefactory.cpp @@ -262,6 +262,7 @@ void TextureFactory::setCurrentTexture( const int& TexId, const Uint32& TextureU std::vector TextureFactory::getTextures() { std::vector textures; + textures.reserve( mTextures.size() ); for ( Uint32 i = 1; i < mTextures.size(); i++ ) { Texture* Tex = getTexture( i ); @@ -322,6 +323,10 @@ bool TextureFactory::existsId( const Uint32& TexId ) { return ( TexId < mTextures.size() && TexId > 0 && NULL != mTextures[TexId] ); } +bool TextureFactory::exists( const Texture* tex ) { + return std::find( mTextures.begin(), mTextures.end(), tex ) != mTextures.end(); +} + Texture* TextureFactory::getTexture( const Uint32& TexId ) { return mTextures[TexId]; } diff --git a/src/eepp/ui/tools/uitextureviewer.hpp b/src/eepp/ui/tools/uitextureviewer.hpp index ae7265387..fed9e0d48 100644 --- a/src/eepp/ui/tools/uitextureviewer.hpp +++ b/src/eepp/ui/tools/uitextureviewer.hpp @@ -2,13 +2,12 @@ #define EE_UITEXTUREVIEWER_HPP #include -#include using namespace EE::UI; namespace EE { namespace UI { -class UIGridLayout;; +class UIGridLayout; namespace Tools { diff --git a/src/eepp/ui/uiimage.cpp b/src/eepp/ui/uiimage.cpp index 7ffeabe02..f7b8a6dc1 100644 --- a/src/eepp/ui/uiimage.cpp +++ b/src/eepp/ui/uiimage.cpp @@ -54,11 +54,27 @@ UIImage* UIImage::setDrawable( Drawable* drawable, bool ownIt ) { mDrawableOwner = ownIt; sendCommonEvent( Event::OnResourceChange ); - if ( NULL != mDrawable && mDrawable->isDrawableResource() ) { - mResourceChangeCb = static_cast( mDrawable ) - ->pushResourceChangeCallback( [this]( auto, auto event, auto res ) { - onDrawableResourceEvent( event, res ); - } ); + if ( mDrawable ) { + if ( mDrawable->getDrawableType() == Drawable::SPRITE ) { + if ( !isSubscribedForScheduledUpdate() ) + subscribeScheduledUpdate(); + + mResourceChangeCb = + static_cast( mDrawable )->pushEventsCallback( [this]( auto, auto, auto ) { + invalidateDraw(); + } ); + } else { + if ( mDrawable->isDrawableResource() ) { + mResourceChangeCb = + static_cast( mDrawable ) + ->pushResourceChangeCallback( [this]( auto, auto event, auto res ) { + onDrawableResourceEvent( event, res ); + } ); + } + + if ( isSubscribedForScheduledUpdate() ) + unsubscribeScheduledUpdate(); + } } onAutoSize(); @@ -176,12 +192,14 @@ void UIImage::autoAlign() { } void UIImage::safeDeleteDrawable() { - if ( NULL != mDrawable && mDrawable->isDrawableResource() ) { + if ( mDrawable && mDrawable->getDrawableType() == Drawable::SPRITE ) { + static_cast( mDrawable )->popEventsCallback( mResourceChangeCb ); + } else if ( mDrawable && mDrawable->isDrawableResource() ) { static_cast( mDrawable )->popResourceChangeCallback( mResourceChangeCb ); mResourceChangeCb = 0; } - if ( NULL != mDrawable && mDrawableOwner ) { + if ( mDrawable && mDrawableOwner ) { eeSAFE_DELETE( mDrawable ); mDrawableOwner = false; @@ -235,6 +253,11 @@ std::string UIImage::getPropertyString( const PropertyDefinition* propertyDef, } } +void UIImage::scheduledUpdate( const Time& time ) { + if ( mDrawable && mDrawable->getDrawableType() == Drawable::SPRITE ) + static_cast( mDrawable )->update( time ); +} + std::vector UIImage::getPropertiesImplemented() const { auto props = UIWidget::getPropertiesImplemented(); auto local = { PropertyId::ScaleType, PropertyId::Tint }; diff --git a/src/eepp/ui/uiscrollview.cpp b/src/eepp/ui/uiscrollview.cpp index f6868ca21..dfe8ee4b3 100644 --- a/src/eepp/ui/uiscrollview.cpp +++ b/src/eepp/ui/uiscrollview.cpp @@ -29,10 +29,9 @@ UIScrollView::UIScrollView() : mContainer->setFlags( UI_OWNS_CHILDS_POSITION ); mContainer->enableReportSizeChangeToChilds(); - mVScroll->addEventListener( Event::OnValueChange, - [this] ( auto event ) { onValueChangeCb( event ); } ); - mHScroll->addEventListener( Event::OnValueChange, - [this] ( auto event ) { onValueChangeCb( event ); } ); + mContainer->on( Event::OnSizeChange, [this]( auto ) { containerUpdate(); } ); + mVScroll->on( Event::OnValueChange, [this]( auto event ) { onValueChangeCb( event ); } ); + mHScroll->on( Event::OnValueChange, [this]( auto event ) { onValueChangeCb( event ); } ); applyDefaultTheme(); } @@ -75,10 +74,11 @@ void UIScrollView::onChildCountChange( Node* child, const bool& removed ) { mScrollView = child; mScrollView->setParent( mContainer ); - mSizeChangeCb = mScrollView->addEventListener( - Event::OnSizeChange, [this] ( auto event ) { onScrollViewSizeChange( event ); } ); - mPosChangeCb = mScrollView->addEventListener( - Event::OnPositionChange, [this] ( auto event ) { onScrollViewPositionChange( event ); } ); + mSizeChangeCb = mScrollView->on( + Event::OnSizeChange, [this]( auto event ) { onScrollViewSizeChange( event ); } ); + mPosChangeCb = mScrollView->on( Event::OnPositionChange, [this]( auto event ) { + onScrollViewPositionChange( event ); + } ); containerUpdate(); } diff --git a/src/thirdparty/SOIL2 b/src/thirdparty/SOIL2 index 6f7781907..46208c59a 160000 --- a/src/thirdparty/SOIL2 +++ b/src/thirdparty/SOIL2 @@ -1 +1 @@ -Subproject commit 6f778190777dcc614ec57b4c8747ab068c503f31 +Subproject commit 46208c59af900f89afc96466be5e8532f71493da diff --git a/src/tools/ecode/ecode.cpp b/src/tools/ecode/ecode.cpp index baf9ef3a3..120898885 100644 --- a/src/tools/ecode/ecode.cpp +++ b/src/tools/ecode/ecode.cpp @@ -17,6 +17,7 @@ #include #include #include +#include #include #include #include @@ -2118,10 +2119,29 @@ void App::loadImageFromMedium( const std::string& path, bool isMemory ) { #if EE_PLATFORM != EE_PLATFORM_EMSCRIPTEN || defined( __EMSCRIPTEN_PTHREADS__ ) mThreadPool->run( [this, imageView, loaderView, path, isMemory]() { #endif - Texture* image = - isMemory ? TextureFactory::instance()->loadFromMemory( - reinterpret_cast( path.c_str() ), path.size() ) - : TextureFactory::instance()->loadFromFile( path ); + Image::Format format = + isMemory ? Image::getFormat( reinterpret_cast( path.c_str() ), + path.size() ) + : Image::getFormat( path ); + + if ( format == Image::Format::Unknown ) + return; + + Drawable* image = nullptr; + + if ( format != Image::Format::GIF ) { + image = isMemory ? TextureFactory::instance()->loadFromMemory( + reinterpret_cast( path.c_str() ), + path.size() ) + : TextureFactory::instance()->loadFromFile( path ); + } else { + IOStream* stream = isMemory + ? (IOStream*)new IOStreamMemory( path.c_str(), path.size() ) + : (IOStream*)new IOStreamFile( path ); + image = Sprite::fromGif( *stream ); + delete stream; + } + if ( mImageLayout->isVisible() ) { imageView->runOnMainThread( [this, imageView, loaderView, image]() { mImageLayout->setFocus(); @@ -2129,7 +2149,7 @@ void App::loadImageFromMedium( const std::string& path, bool isMemory ) { loaderView->setVisible( false ); } ); } else { - TextureFactory::instance()->remove( image ); + eeSAFE_DELETE( image ); imageView->setDrawable( nullptr ); loaderView->setVisible( false ); } @@ -2980,18 +3000,14 @@ void App::initProjectTreeView( std::string path, bool openClean ) { } void App::initImageView() { - mImageLayout->on( Event::MouseClick, [this]( const Event* ) { + const auto onCloseImage = [this]( const Event* ) { mImageLayout->findByType( UI_TYPE_IMAGE )->setDrawable( nullptr ); mImageLayout->setEnabled( false )->setVisible( false ); - } ); - mImageLayout->on( Event::KeyDown, [this]( const Event* event ) { - if ( event->asKeyEvent()->getKeyCode() == KEY_ESCAPE ) { - mImageLayout->findByType( UI_TYPE_IMAGE )->setDrawable( nullptr ); - mImageLayout->setEnabled( false )->setVisible( false ); - if ( mSplitter->getCurWidget() ) - mSplitter->getCurWidget()->setFocus(); - } - } ); + if ( mSplitter->getCurWidget() ) + mSplitter->getCurWidget()->setFocus(); + }; + mImageLayout->on( Event::MouseClick, onCloseImage ); + mImageLayout->on( Event::KeyDown, onCloseImage ); } bool App::isFileVisibleInTreeView( const std::string& filePath ) { @@ -3670,7 +3686,7 @@ void App::init( const LogLevel& logLevel, std::string file, const Float& pidelDe mSplitter->setHideTabBarOnSingleTab( mConfig.editor.hideTabBarOnSingleTab ); mSplitter->setOnTabWidgetCreateCb( [this]( UITabWidget* tabWidget ) { tabWidget->getTabBar()->onDoubleClick( - [this]( const MouseEvent* event ) { mSplitter->createEditorInNewTab(); } ); + [this]( const MouseEvent* ) { mSplitter->createEditorInNewTab(); } ); } ); mPluginManager->setSplitter( mSplitter );