From 13508c20d1322058386870bb38baab08b8a6d1bb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mart=C3=ADn=20Lucas=20Golini?= Date: Fri, 31 Jul 2026 01:25:41 -0300 Subject: [PATCH] UIImageViewer expanded capabilities. ecode image viewing will not auto-scale images to the pixel density. eeiv: Modernizing eeiv to use eepp GUI, already working just like before, there are probably a few things to fix. --- include/eepp/ui/tools/uiimageviewer.hpp | 22 + src/eepp/ui/iconmanager.cpp | 1 + src/eepp/ui/tools/uidiffview.cpp | 1 + src/eepp/ui/tools/uiimageviewer.cpp | 129 ++- src/tools/ecode/ecode.cpp | 1 + src/tools/eeiv/app.cpp | 1242 ----------------------- src/tools/eeiv/app.hpp | 193 ---- src/tools/eeiv/consolecommands.cpp | 32 +- src/tools/eeiv/eeiv.cpp | 518 +++++++++- src/tools/eeiv/eeiv.hpp | 89 ++ 10 files changed, 734 insertions(+), 1494 deletions(-) delete mode 100644 src/tools/eeiv/app.cpp delete mode 100755 src/tools/eeiv/app.hpp mode change 100755 => 100644 src/tools/eeiv/eeiv.cpp create mode 100644 src/tools/eeiv/eeiv.hpp diff --git a/include/eepp/ui/tools/uiimageviewer.hpp b/include/eepp/ui/tools/uiimageviewer.hpp index 0bbec2580..59720f4c3 100644 --- a/include/eepp/ui/tools/uiimageviewer.hpp +++ b/include/eepp/ui/tools/uiimageviewer.hpp @@ -76,6 +76,23 @@ class EE_API UIImageViewer : public UIWidget { void resetImageView(); + /** Recalculates the displayed image size and position when the viewer size changes. */ + void setAutoFitOnSizeChange( bool enabled ) { mAutoFitOnSizeChange = enabled; } + + bool getAutoFitOnSizeChange() const { return mAutoFitOnSizeChange; } + + /** + * Uses the image's native pixel dimensions when it fits in the viewer, independently of the + * UI pixel density. Images larger than the viewer are still scaled down to fit. + */ + void setUseNativeImageSize( bool enabled ) { mUseNativeImageSize = enabled; } + + bool getUseNativeImageSize() const { return mUseNativeImageSize; } + + void setPreserveImageView( bool preserve ) { mPreserveImageView = preserve; } + + bool getPreserveImageView() const { return mPreserveImageView; } + bool isLoading() const { return mLoading; } bool hasImage() const; @@ -100,6 +117,9 @@ class EE_API UIImageViewer : public UIWidget { Int64 mCurFileSize{ 0 }; Image::Format mCurFileType; Uint32 mDisplayOptions{ 0 }; + bool mPreserveImageView{ false }; + bool mAutoFitOnSizeChange{ false }; + bool mUseNativeImageSize{ false }; UIImageViewer(); @@ -109,6 +129,8 @@ class EE_API UIImageViewer : public UIWidget { virtual Uint32 onKeyDown( const KeyEvent& event ) override; + void updateImageLayout(); + void updateTextDisplay(); }; diff --git a/src/eepp/ui/iconmanager.cpp b/src/eepp/ui/iconmanager.cpp index 7368d3e81..aa908f093 100644 --- a/src/eepp/ui/iconmanager.cpp +++ b/src/eepp/ui/iconmanager.cpp @@ -98,6 +98,7 @@ ResourcePtr IconManager::init( const std::string& iconThemeName, { "filetype-pic", 0xF3C5 }, { "filetype-pvr", 0xF3C5 }, { "filetype-pkm", 0xF3C5 }, + { "filetype-webp", 0xF3C5 }, { "filetype-mp3", 0xEF83 }, { "filetype-ogg", 0xEF83 }, { "filetype-wav", 0xEF83 }, diff --git a/src/eepp/ui/tools/uidiffview.cpp b/src/eepp/ui/tools/uidiffview.cpp index e58637438..2ea55a82c 100644 --- a/src/eepp/ui/tools/uidiffview.cpp +++ b/src/eepp/ui/tools/uidiffview.cpp @@ -456,6 +456,7 @@ void UIDiffView::createImageViewers() { iv->setVisible( false ); iv->setLayoutSizePolicy( SizePolicy::Fixed, SizePolicy::Fixed ); iv->setDisplayOptions( UIImageViewer::DisplayDimensions ); + iv->setUseNativeImageSize( true ); return iv; }; mLeftImageViewer = initImageView(); diff --git a/src/eepp/ui/tools/uiimageviewer.cpp b/src/eepp/ui/tools/uiimageviewer.cpp index 0845ed089..c2e53e4d8 100644 --- a/src/eepp/ui/tools/uiimageviewer.cpp +++ b/src/eepp/ui/tools/uiimageviewer.cpp @@ -3,6 +3,7 @@ #include #include #include +#include #include #include #include @@ -68,7 +69,9 @@ UIImageViewer::UIImageViewer() : UIWidget( "imageviewer" ) { mImage->setVisible( false ); mImage->setDragEnabled( true ); mImage->setLayoutSizePolicy( SizePolicy::Fixed, SizePolicy::Fixed ); - mImage->setScaleType( UIScaleType::FitInside ); + // UIImageViewer calculates an aspect-preserving destination size. Expand makes UIImage render + // into that exact box, including the optional density-aware upscale. + mImage->setScaleType( UIScaleType::Expand ); mLoader = UILoader::New(); mLoader->setParent( this ); mLoader->setIndeterminate( true ); @@ -152,59 +155,109 @@ void UIImageViewer::loadImageAsync( std::string_view path, bool isContents, bool path.size() ) : Image::getFormat( path ); - if ( format == Image::Format::Unknown ) + if ( format == Image::Format::Unknown ) { + Log::error( "UIImageViewer: unsupported or invalid image: %s", + isContents ? "" : path.c_str() ); + runOnMainThread( [this] { mLoader->setVisible( false ); } ); return; - - DrawablePtr image; - - if ( mClosing ) - return; - - if ( format != Image::Format::GIF ) { - auto tex = isContents ? TextureFactory::instance()->loadFromMemory( - reinterpret_cast( path.c_str() ), - path.size() ) - : TextureFactory::instance()->loadFromFile( path ); - SpritePtr sprite = Sprite::New(); - sprite->createStatic( tex ); - image = std::move( sprite ); - } else { - IOStream* stream = isContents - ? (IOStream*)new IOStreamMemory( path.c_str(), path.size() ) - : (IOStream*)new IOStreamFile( path ); - SpritePtr sprite = Sprite::fromGif( *stream ); - sprite->setAutoAnimate( false ); - image = std::move( sprite ); - delete stream; } if ( mClosing ) return; - if ( image ) { - mCurFileSize = isContents ? path.size() : FileSystem::fileSize( path ); - mCurFileType = format; + auto decodedImages = std::make_shared>(); + int gifDelay = 0; + if ( format == Image::Format::GIF ) { + if ( isContents ) { + IOStreamMemory stream( path.c_str(), path.size() ); + auto gif = Image::loadGif( stream ); + *decodedImages = std::move( gif.first ); + gifDelay = gif.second; + } else { + IOStreamFile stream( path ); + auto gif = Image::loadGif( stream ); + *decodedImages = std::move( gif.first ); + gifDelay = gif.second; + } + } else { + Image decoded = + isContents ? Image( reinterpret_cast( path.data() ), path.size() ) + : Image( path ); + if ( decoded.getPixelsPtr() ) + decodedImages->emplace_back( std::move( decoded ) ); + } + if ( decodedImages->empty() ) + Log::error( "UIImageViewer: failed to decode image: %s", + isContents ? "" : path.c_str() ); - runOnMainThread( [this, image] { + const Int64 fileSize = isContents ? path.size() : FileSystem::fileSize( path ); + std::string filePath( isContents ? std::string{} : path ); + + SpritePtr sprite = Sprite::New(); + for ( const Image& decoded : *decodedImages ) { + auto texture = TextureFactory::instance()->loadFromPixels( + decoded.getPixelsPtr(), decoded.getWidth(), decoded.getHeight(), + decoded.getChannels(), false, Texture::ClampMode::ClampToEdge, false, false, + filePath ); + if ( texture ) + sprite->addFrame( std::move( texture ) ); + else + Log::error( "UIImageViewer: texture upload failed for a %ux%u image", + decoded.getWidth(), decoded.getHeight() ); + } + + runOnMainThread( [this, decodedImages, format, fileSize, gifDelay, + filePath = std::move( filePath ), sprite = std::move( sprite )] { + DrawablePtr image; + if ( sprite->getNumFrames() ) { + const size_t frameCount = sprite->getNumFrames(); + if ( format == Image::Format::GIF ) { + sprite->setAnimationSpeed( 1000.f / static_cast( gifDelay ) ); + sprite->setAutoAnimate( false ); + } + image = std::move( sprite ); + mCurFileSize = fileSize; + mCurFileType = format; mImage->setDrawable( image ); updateTextDisplay(); auto s( image->getPixelsSize() ); - auto scale( s.x > mSize.x || s.y > mSize.y - ? eemin( mSize.x / s.getWidth(), mSize.y / s.getHeight() ) - : 1.f ); - mImage->setPixelsSize( eefloor( image->getPixelsSize().x * scale ), - eefloor( image->getPixelsSize().y * scale ) ); - resetImageView(); + updateImageLayout(); + if ( !mPreserveImageView ) + resetImageView(); + else + mImage->setVisible( true ); + Log::info( "UIImageViewer: loaded %s (%dx%d, %d frame%s)", + Image::formatToString( format ).c_str(), (int)s.x, (int)s.y, + (int)frameCount, frameCount == 1 ? "" : "s" ); sendCommonEvent( Event::OnResourceLoaded ); - } ); - } - - runOnMainThread( [this] { mLoader->setVisible( false ); } ); + } + mLoader->setVisible( false ); + } ); } ); } void UIImageViewer::onSizeChange() { mLoader->center(); + if ( mAutoFitOnSizeChange ) { + mImage->setScale( 1.f ); + updateImageLayout(); + } +} + +void UIImageViewer::updateImageLayout() { + if ( !mImage || !mImage->getDrawable() ) + return; + + Sizef imageSize( mImage->getDrawable()->getPixelsSize() ); + if ( mUseNativeImageSize ) + imageSize /= PixelDensity::getPixelDensity(); + + const Float scale = + imageSize.x > mSize.x || imageSize.y > mSize.y + ? eemin( mSize.x / imageSize.getWidth(), mSize.y / imageSize.getHeight() ) + : 1.f; + mImage->setPixelsSize( eefloor( imageSize.x * scale ), eefloor( imageSize.y * scale ) ); + mImage->center(); } void UIImageViewer::reset() { diff --git a/src/tools/ecode/ecode.cpp b/src/tools/ecode/ecode.cpp index 3966d1f4a..7b9707242 100644 --- a/src/tools/ecode/ecode.cpp +++ b/src/tools/ecode/ecode.cpp @@ -2543,6 +2543,7 @@ void App::loadImageFromMedium( const std::string& path, bool isMemory, bool forc } } else { UIImageViewer* imageView = UIImageViewer::New(); + imageView->setUseNativeImageSize( true ); auto [tab, iv] = mSplitter->createWidget( imageView, i18n( "image_viewer", "Image Viewer" ) ); diff --git a/src/tools/eeiv/app.cpp b/src/tools/eeiv/app.cpp deleted file mode 100644 index 2976f8068..000000000 --- a/src/tools/eeiv/app.cpp +++ /dev/null @@ -1,1242 +0,0 @@ -#include -#include - -using namespace std::literals; - -// This application is not meant to be used as an example of beautiful code, -// it's just old code that works fine and looks ugly. It was made exclusively -// for my personal use, and still manages to satisfy my very basic image viewing needs. -// Some day i'll make this look good. - -#if EE_PLATFORM == EE_PLATFORM_WIN -#include -#ifndef WIN32_LEAN_AND_MEAN -#define WIN32_LEAN_AND_MEAN -#endif -#include -static std::string getWindowsPath() { -#ifdef UNICODE - wchar_t Buffer[1024]; - GetWindowsDirectory( Buffer, 1024 ); - return EE::String( Buffer ).toUtf8(); -#else - char Buffer[1024]; - GetWindowsDirectory( Buffer, 1024 ); - return std::string( Buffer ); -#endif -} -#undef RGB -#undef KEY_EXECUTE -#endif - -#include "app.hpp" - -bool App::isRawImage( const std::string& path ) { - std::string Ext = FileSystem::fileExtension( path ); - return Ext == "uint8" || Ext == "float32"; -} - -bool App::isImage( const std::string& _path ) { - std::string path = _path; - - if ( path.size() >= 7 && path.substr( 0, 7 ) == "file://" ) - path = path.substr( 7 ); - - if ( !FileSystem::isDirectory( path ) && FileSystem::fileSize( path ) ) { - if ( Image::isImage( path ) ) { - return true; - } else { - std::string Ext = FileSystem::fileExtension( path ); - if ( Ext == "uint8" || Ext == "float32" ) { - return true; - } - return Image::isImage( path ); - } - } - - return false; -} - -bool App::isHttpUrl( const std::string& path ) { - return path.substr( 0, 7 ) == "http://" || path.substr( 0, 8 ) == "https://"; -} - -App::App( int argc, char* argv[] ) : - Fon( NULL ), - Mon( NULL ), - mCurImg( 0 ), - mFading( false ), - mAlpha( 255 ), - mCurAlpha( 255 ), - mLaterLoad( false ), - mCursor( true ), - mMouseLeftPressing( false ), - mMouseMiddlePressing( false ), - mImgRT( RENDER_NORMAL ), - mFilter( Texture::Filter::Linear ), - mShowHelp( false ), - mFirstLoad( false ), - mUsedTempDir( false ), - mLockZoomAndPosition( false ), - mHelpCache( NULL ), - mSlideShow( false ), - mSlideTime( 4000 ), - mSlideTicks( Sys::getTicks() ) { - mStorePath = Sys::getConfigPath( "eeiv" ) + FileSystem::getOSSlash(); - mTmpPath = mStorePath + "tmp" + FileSystem::getOSSlash(); - - std::string nstr; - - if ( argc > 1 ) - nstr.assign( argv[1] ); - else - nstr.assign( argv[0] ); - - loadDir( nstr, false ); -} - -App::~App() { - clearTempDir(); -} - -void App::loadConfigValues() { - mConfig.Width = Ini.getValueI( "window", "width", 1024 ); - mConfig.Height = Ini.getValueI( "window", "height", 768 ); - mConfig.BitColor = Ini.getValueI( "window", "bitcolor", 32 ); - mConfig.Windowed = Ini.getValueB( "window", "windowed", true ); - mConfig.Resizeable = Ini.getValueB( "window", "resizeable", true ); - mConfig.VSync = Ini.getValueB( "window", "vsync", true ); - mConfig.DoubleBuffering = Ini.getValueB( "window", "doublebuffering", true ); - mConfig.UseDesktopResolution = Ini.getValueB( "window", "usedesktopresolution", false ); - mConfig.NoFrame = Ini.getValueB( "window", "borderless", false ); - mConfig.MaximizeAtStart = Ini.getValueB( "window", "maximizeatstart", true ); - mConfig.FrameLimit = Ini.getValueI( "window", "framelimit", 0 ); - mConfig.Fade = Ini.getValueB( "viewer", "fade", true ); - mConfig.LateLoading = Ini.getValueB( "viewer", "lateloading", true ); - mConfig.BlockWheelSpeed = Ini.getValueB( "viewer", "blockwheelspeed", true ); - mConfig.ShowInfo = Ini.getValueB( "viewer", "showinfo", true ); - mConfig.TransitionTime = Ini.getValueI( "viewer", "transitiontime", 200 ); - mConfig.ConsoleFontSize = Ini.getValueI( "viewer", "consolefontsize", 12 ); - mConfig.AppFontSize = Ini.getValueI( "viewer", "appfontsize", 12 ); - mConfig.DefaultImageZoom = Ini.getValueF( "viewer", "defaultimagezoom", 1 ); - mConfig.WheelBlockTime = Ini.getValueI( "viewer", "wheelblocktime", 200 ); -} - -void App::loadConfig() { - std::string tPath = mStorePath + "eeiv.ini"; - Ini.loadFromFile( tPath ); - - if ( FileSystem::fileExists( tPath ) ) { - Ini.readFile(); - loadConfigValues(); - } else { - Ini.setValueI( "window", "width", 1024 ); - Ini.setValueI( "window", "height", 768 ); - Ini.setValueI( "window", "bitcolor", 32 ); - Ini.setValueI( "window", "windowed", 1 ); - Ini.setValueI( "window", "resizeable", 1 ); - Ini.setValueI( "window", "vsync", 1 ); - Ini.setValueI( "window", "doublebuffering", 1 ); - Ini.setValueI( "window", "usedesktopresolution", 0 ); - Ini.setValueI( "window", "borderless", 0 ); - Ini.setValueI( "window", "maximizeatstart", 1 ); - Ini.setValueI( "window", "framelimit", 0 ); - Ini.setValueI( "viewer", "fade", 1 ); - Ini.setValueI( "viewer", "lateloading", 1 ); - Ini.setValueI( "viewer", "blockwheelspeed", 1 ); - Ini.setValueI( "viewer", "showinfo", 1 ); - Ini.setValueI( "viewer", "transitiontime", 200 ); - Ini.setValueI( "viewer", "consolefontsize", 12 ); - Ini.setValueI( "viewer", "appfontsize", 12 ); - Ini.setValueI( "viewer", "defaultimagezoom", 1 ); - Ini.setValueI( "viewer", "wheelblocktime", 200 ); - - if ( !FileSystem::isDirectory( mStorePath ) ) - FileSystem::makeDir( mStorePath ); - - Ini.writeFile(); - loadConfigValues(); - } -} - -bool App::init() { - loadConfig(); - - EE = Engine::instance(); - MyPath = Sys::getProcessPath(); - - std::string iconp( MyPath + "assets/eeiv.png" ); - - if ( !FileSystem::fileExists( iconp ) ) { - iconp = MyPath + "assets/icon/ee.png"; - } - - WindowSettings WinSettings = EE->createWindowSettings( &Ini, "window" ); - ContextSettings ConSettings = EE->createContextSettings( &Ini, "window" ); - - WinSettings.Icon = iconp; - WinSettings.Title = "eeiv"; - - mWindow = EE->createWindow( WinSettings, ConSettings ); - - if ( mWindow->isOpen() ) { - Display* currentDisplay = Engine::instance()->getDisplayManager()->getDisplayIndex( - mWindow->getCurrentDisplayIndex() ); - - PixelDensity::setPixelDensity( PixelDensity::toFloat( currentDisplay->getPixelDensity() ) ); - - formatConfiguration.svgScale( 2 /*PixelDensity::getPixelDensity()*/ ); - - if ( mConfig.FrameLimit ) - mWindow->setFrameRateLimit( 60 ); - - mWindow->setCloseRequestCallback( [&]( EE::Window::Window* ) -> bool { - updateConfig(); - return true; - } ); - - TF = TextureFactory::instance(); - Log = Log::instance(); - KM = mWindow->getInput(); - - if ( mConfig.MaximizeAtStart ) - mWindow->maximize(); - - Clock TE; - - std::string MyFontPath = MyPath + "assets/fonts" + FileSystem::getOSSlash(); - - TTF = FontTrueType::New( "DejaVuSans" ); - TTFMon = FontTrueType::New( "DejaVuSansMono" ); - -#if EE_PLATFORM == EE_PLATFORM_WIN - std::string fontsPath( getWindowsPath() + "\\Fonts\\" ); -#else - std::string fontsPath( "/usr/share/fonts/truetype/" ); -#endif - - if ( FileSystem::fileExists( fontsPath + "DejaVuSans.ttf" ) && - FileSystem::fileExists( fontsPath + "DejaVuSansMono.ttf" ) ) { - TTF->loadFromFile( fontsPath + "DejaVuSans.ttf" ); - TTFMon->loadFromFile( fontsPath + "DejaVuSansMono.ttf" ); - } else if ( FileSystem::fileExists( MyFontPath + "DejaVuSans.ttf" ) && - FileSystem::fileExists( MyFontPath + "DejaVuSansMono.ttf" ) ) { - TTF->loadFromFile( MyFontPath + "DejaVuSans.ttf" ); - TTFMon->loadFromFile( MyFontPath + "DejaVuSansMono.ttf" ); - } else if ( FileSystem::fileExists( fontsPath + "Arial.ttf" ) && - FileSystem::fileExists( fontsPath + "cour.ttf" ) ) { - TTF->loadFromFile( fontsPath + "Arial.ttf" ); - TTFMon->loadFromFile( fontsPath + "cour.ttf" ); - } else { - Log::instance()->writef( "Fonts not found... closing." ); - return false; - } - - Fon = TTF.get(); - Mon = TTFMon.get(); - - FonCache.setFont( Fon ); - FonCache.setFontSize( mConfig.AppFontSize ); - FonCache.setOutlineThickness( PixelDensity::dpToPxI( 1 ) ); - - Log::instance()->writef( "Fonts loading time: %f ms", - TE.getElapsedTimeAndReset().asMilliseconds() ); - - if ( !Fon && !Mon ) - return false; - - UISceneNode* uiSceneNode = UISceneNode::New(); - uiSceneNode->getUIThemeManager()->setDefaultFont( Fon ); - SceneManager::instance()->add( uiSceneNode ); - - Con = UIConsole::NewOpt( Mon, true, true, 1024000 ); - Con->setQuakeMode( true ); - Con->setVisible( false ); - Con->setFontSize( mConfig.ConsoleFontSize ); - - mConCmds = std::make_unique( this, Con ); - - Con->addCommand( "loaddir", [this]( auto event ) { mConCmds->cmdLoadDir( event ); } ); - Con->addCommand( "loadimg", [this]( auto event ) { mConCmds->cmdLoadImg( event ); } ); - Con->addCommand( "setbackcolor", - [this]( auto event ) { mConCmds->cmdSetBackColor( event ); } ); - Con->addCommand( "setimgfade", [this]( auto event ) { mConCmds->cmdSetImgFade( event ); } ); - Con->addCommand( "setlateloading", - [this]( auto event ) { mConCmds->cmdSetLateLoading( event ); } ); - Con->addCommand( "setblockwheel", - [this]( auto event ) { mConCmds->cmdSetBlockWheel( event ); } ); - Con->addCommand( "moveto", [this]( auto event ) { mConCmds->cmdMoveTo( event ); } ); - Con->addCommand( "batchimgscale", - [this]( auto event ) { mConCmds->cmdBatchImgScale( event ); } ); - Con->addCommand( "batchimgchangeformat", - [this]( auto event ) { mConCmds->cmdBatchImgChangeFormat( event ); } ); - Con->addCommand( "batchimgthumbnail", - [this]( auto event ) { mConCmds->cmdBatchImgThumbnail( event ); } ); - Con->addCommand( "imgchangeformat", - [this]( auto event ) { mConCmds->cmdImgChangeFormat( event ); } ); - Con->addCommand( "imgresize", [this]( auto event ) { mConCmds->cmdImgResize( event ); } ); - Con->addCommand( "imgscale", [this]( auto event ) { mConCmds->cmdImgScale( event ); } ); - Con->addCommand( "imgthumbnail", - [this]( auto event ) { mConCmds->cmdImgThumbnail( event ); } ); - Con->addCommand( "imgcentercrop", - [this]( auto event ) { mConCmds->cmdImgCenterCrop( event ); } ); - Con->addCommand( "slideshow", [this]( auto event ) { mConCmds->cmdSlideShow( event ); } ); - Con->addCommand( "setzoom", [this]( auto event ) { mConCmds->cmdSetZoom( event ); } ); - - setWindowCaption(); - - getImages(); - - if ( mFile != "" ) { - fastLoadImage( curImagePos( mFile ) ); - } else { - if ( mFiles.size() ) - fastLoadImage( 0 ); - } - - if ( 0 == mFiles.size() && 0 == mFile.length() ) { - Con->toggle(); - } - - return true; - } - - return false; -} - -void App::process() { - if ( init() ) { - do { - ET = mWindow->getElapsed().asMilliseconds(); - - input(); - - TEP.restart(); - - if ( mWindow->isVisible() ) { - render(); - - if ( KM->isKeyUp( KEY_F12 ) ) - mWindow->takeScreenshot(); - - mWindow->display( true ); - } else { - Sys::sleep( Milliseconds( 16 ) ); - } - - RET = TEP.getElapsedTimeAndReset().asMilliseconds(); - - if ( mConfig.LateLoading && mLaterLoad ) { - if ( Sys::getTicks() - mLastLaterTick > mConfig.TransitionTime ) { - updateImages(); - mLaterLoad = false; - } - } - - if ( mFirstLoad ) { - updateImages(); - mFirstLoad = false; - } - } while ( mWindow->isRunning() ); - } - - end(); -} - -void App::loadDir( const std::string& path, const bool& getimages ) { - std::string tmpFile; - - if ( !FileSystem::isDirectory( path ) ) { - if ( path.substr( 0, 7 ) == "file://" ) { - mFilePath = path.substr( 7 ); - mFilePath = mFilePath.substr( 0, mFilePath.find_last_of( FileSystem::getOSSlash() ) ); - tmpFile = path.substr( path.find_last_of( FileSystem::getOSSlash() ) + 1 ); - } else if ( isHttpUrl( path ) ) { - mUsedTempDir = true; - - if ( !FileSystem::isDirectory( mTmpPath ) ) - FileSystem::makeDir( mTmpPath ); - - URI uri( path ); - Http http( uri.getHost(), uri.getPort() ); - Http::Request request( uri.getPathAndQuery() ); - Http::Response response = http.sendRequest( request ); - - if ( response.getStatus() == Http::Response::Ok ) { - if ( !FileSystem::fileWrite( - mTmpPath + "tmpfile", - reinterpret_cast( &response.getBody()[0] ), - response.getBody().size() ) ) { - Con->pushText( "Couldn't write the downloaded image to disk." ); - - return; - } - } else { - Con->pushText( "Couldn't download the image from network." ); - - return; - } - - mFilePath = mTmpPath; - tmpFile = "tmpfile"; - } else { - mFilePath = path.substr( 0, path.find_last_of( FileSystem::getOSSlash() ) ); - tmpFile = path.substr( path.find_last_of( FileSystem::getOSSlash() ) + 1 ); - } - - String::replaceAll( mFilePath, "%20", " " ); - - if ( mFilePath == "" ) { -#if EE_PLATFORM == EE_PLATFORM_WIN - mFilePath = "C:\\"; -#else - mFilePath = FileSystem::getOSSlash(); -#endif - } - - FileSystem::dirAddSlashAtEnd( mFilePath ); - - if ( isImage( mFilePath + tmpFile ) ) - mFile = tmpFile; - else - return; - } else { - mFilePath = path; - - FileSystem::dirAddSlashAtEnd( mFilePath ); - } - - mCurImg = 0; - - if ( getimages ) - getImages(); - - if ( mFiles.size() ) { - if ( mFile.size() ) - mCurImg = curImagePos( mFile ); - - if ( mWindow->isRunning() ) - updateImages(); - } -} - -void App::updateConfig() { - mConfig.Width = EE->getCurrentWindow()->getWidth(); - mConfig.Height = EE->getCurrentWindow()->getHeight(); - mConfig.MaximizeAtStart = EE->getCurrentWindow()->isMaximized(); -} - -void App::clearTempDir() { - if ( mUsedTempDir ) { - getImages(); - - for ( Uint32 i = 0; i < mFiles.size(); i++ ) { - std::string Delfile = mFilePath + mFiles[i].Path; - remove( Delfile.c_str() ); - } - } -} - -void App::getImages() { - Clock TE; - - Uint32 i; - std::vector tStr; - mFiles.clear(); - - std::vector tmpFiles = FileSystem::filesGetInPath( mFilePath, true ); - for ( i = 0; i < tmpFiles.size(); i++ ) - if ( isImage( mFilePath + tmpFiles[i] ) ) - tStr.push_back( tmpFiles[i] ); - - for ( i = 0; i < tStr.size(); i++ ) { - ImageData tmpI; - tmpI.Path = tStr[i]; - tmpI.Tex.clear(); - - mFiles.push_back( tmpI ); - } - - Con->pushText( "Image list loaded in %f ms.", TE.getElapsedTimeAndReset().asMilliseconds() ); - - Con->pushText( "Directory: \"" + String::fromUtf8( mFilePath ) + "\"" ); - for ( Uint32 i = 0; i < mFiles.size(); i++ ) - Con->pushText( " " + String::fromUtf8( mFiles[i].Path ) ); -} - -Uint32 App::curImagePos( const std::string& path ) { - for ( Uint32 i = 0; i < mFiles.size(); i++ ) { - if ( mFiles[i].Path == path ) - return i; - } - return 0; -} - -void App::fastLoadImage( const Uint32& ImgNum ) { - mCurImg = ImgNum; - auto res = loadImage( mFiles[mCurImg].Path, true ); - mFiles[mCurImg].Tex = std::move( res.first ); - mFiles[mCurImg].animFps = res.second; - mFirstLoad = true; -} - -void App::setImage( const std::vector& Tex, const std::string& path, Float animFps ) { - if ( !Tex.empty() ) { - mFiles[mCurImg].Tex = Tex; - - mImgRT = RENDER_NORMAL; - - Vector2f scale( mImg.getScale() ); - if ( Tex.size() == 1 ) { - mImg.createStatic( Tex[0] ); - } else { - mImg.reset(); - for ( const TexturePtr& texture : Tex ) - mImg.addFrame( texture ); - mImg.setAnimationSpeed( animFps ); - } - mImg.setRenderMode( mImgRT ); - mImg.setScale( scale ); - - if ( !mLockZoomAndPosition ) { - mImg.setScale( mConfig.DefaultImageZoom ); - mImg.setPosition( Vector2f::Zero ); - } - - if ( path != mFiles[mCurImg].Path ) - mCurImg = curImagePos( path ); - - mFile = mFiles[mCurImg].Path; - - if ( !mLockZoomAndPosition ) - scaleToScreen(); - - Texture* pTex = nullptr; - for ( const TexturePtr& texture : Tex ) { - pTex = texture.get(); - if ( pTex ) - pTex->setFilter( mFilter ); - } - - if ( NULL != pTex ) { - FonCache.setString( "File: " + String::fromUtf8( mFile ) + - "\nWidth: " + String::toString( pTex->getWidth() ) + - "\nHeight: " + String::toString( pTex->getHeight() ) + "\n" + - String::toString( mCurImg + 1 ) + "/" + - String::toString( (Uint64)mFiles.size() ) ); - } - } else { - FonCache.setString( "File: " + String::fromUtf8( path ) + - " failed to load. \nReason: " + Image::getLastFailureReason() ); - } - - setWindowCaption(); -} - -static Sizei imageSizeFromName( const std::string& path ) { - Sizei size( Sizei::Zero ); - size_t xPos = std::string::npos; - for ( size_t i = 0; i < path.size(); i++ ) { - if ( i > 0 && i < path.size() - 1 && path[i] == 'x' && String::isNumber( path[i - 1] ) && - String::isNumber( path[i + 1] ) ) { - xPos = i; - break; - } - } - if ( xPos != std::string::npos ) { - std::string width; - std::string height; - for ( size_t i = xPos - 1; i >= 0; i-- ) { - if ( String::isNumber( path[i] ) ) { - width = path[i] + width; - } else { - break; - } - } - for ( size_t i = xPos + 1; i < path.size(); i++ ) { - if ( String::isNumber( path[i] ) ) { - height += path[i]; - } else { - break; - } - } - int w; - int h; - if ( String::fromString( w, width ) && String::fromString( h, height ) ) { - return Sizei( w, h ); - } - } - return size; -} - -std::pair, Float> App::loadImage( const std::string& path, - const bool& setAsCurrent ) { - std::vector textures; - std::string filePath( mFilePath + path ); - Float animFps = 60; - - if ( isRawImage( filePath ) ) { - ScopedBuffer buffer; - if ( FileSystem::fileGet( filePath, buffer ) ) { - Sizei size( imageSizeFromName( path ) ); - int channels = 0; - for ( size_t c = 1; c <= 4; c++ ) { - if ( buffer.size() - 8 == size.getWidth() * size.getHeight() * c ) { - channels = c; - break; - } - } - if ( channels > 0 ) { - TexturePtr tex = TF->loadFromPixels( buffer.get() + 8, size.getWidth(), - size.getHeight(), channels ); - if ( tex ) - textures.push_back( std::move( tex ) ); - } - } - } else if ( Image::getFormat( filePath ) == Image::Format::GIF ) { - IOStreamFile stream( filePath ); - auto [gif, delay] = Texture::loadGif( stream ); - textures = std::move( gif ); - delay = delay ? delay : 100; - animFps = 1000.f / delay; - } else { - TexturePtr tex = TF->loadFromFile( filePath, false, Texture::ClampMode::ClampToEdge, false, - false, formatConfiguration ); - if ( tex ) - textures.push_back( std::move( tex ) ); - } - - if ( setAsCurrent ) - setImage( textures, path, animFps ); - - return { textures, animFps }; -} - -void App::updateImages() { - for ( Int32 i = 0; i < (Int32)mFiles.size(); i++ ) { - if ( !( i == ( mCurImg - 1 ) || i == mCurImg || i == ( mCurImg + 1 ) ) ) { - unloadImage( i ); - } - - if ( i == ( mCurImg - 1 ) || i == ( mCurImg + 1 ) ) { - if ( mFiles[i].Tex.empty() ) { - auto res = loadImage( mFiles[i].Path ); - mFiles[i].Tex = std::move( res.first ); - mFiles[i].animFps = res.second; - } - } - - if ( i == mCurImg ) { - if ( mFiles[i].Tex.empty() ) { - auto res = loadImage( mFiles[i].Path, true ); - mFiles[i].Tex = std::move( res.first ); - mFiles[i].animFps = res.second; - } else - setImage( mFiles[i].Tex, mFiles[i].Path, mFiles[i].animFps ); - } - } -} - -void App::unloadImage( const Uint32& img ) { - mFiles[img].Tex.clear(); -} - -void App::optUpdate() { - Vector2f scale( mImg.getScale() ); - if ( mFiles[mCurImg].Tex.size() == 1 ) - mImg.createStatic( mFiles[mCurImg].Tex[0] ); - else { - mImg.reset(); - for ( const TexturePtr& texture : mFiles[mCurImg].Tex ) - mImg.addFrame( texture ); - } - mImg.setScale( scale ); - - if ( !mLockZoomAndPosition ) { - mImg.setScale( mConfig.DefaultImageZoom ); - mImg.setPosition( Vector2f::Zero ); - scaleToScreen(); - } - - if ( mConfig.LateLoading ) { - mLaterLoad = true; - mLastLaterTick = Sys::getTicks(); - - if ( !mFiles[mCurImg].Tex.empty() ) { - Texture* Tex = mFiles[mCurImg].Tex[0].get(); - - if ( Tex ) { - FonCache.setString( "File: " + String::fromUtf8( mFiles[mCurImg].Path ) + - "\nWidth: " + String::toString( Tex->getWidth() ) + - "\nHeight: " + String::toString( Tex->getHeight() ) + "\n" + - String::toString( mCurImg + 1 ) + "/" + - String::toString( (Uint64)mFiles.size() ) ); - } - } - } else - updateImages(); -} - -void App::loadFirstImage() { - if ( mCurImg != 0 ) - fastLoadImage( 0 ); -} - -void App::loadLastImage() { - if ( mCurImg != (Int32)( mFiles.size() - 1 ) ) - fastLoadImage( mFiles.size() - 1 ); -} - -void App::loadNextImage() { - if ( ( mCurImg + 1 ) < (Int32)mFiles.size() ) { - createFade(); - mCurImg++; - optUpdate(); - } -} - -void App::loadPrevImage() { - if ( ( mCurImg - 1 ) >= 0 ) { - createFade(); - mCurImg--; - optUpdate(); - } -} - -void App::switchFade() { - if ( mConfig.Fade ) { - mAlpha = 255.0f; - mCurAlpha = 255; - mFading = false; - } - - mConfig.Fade = !mConfig.Fade; - mConfig.LateLoading = !mConfig.LateLoading; - mConfig.BlockWheelSpeed = !mConfig.BlockWheelSpeed; -} - -void App::input() { - KM->update(); - Mouse = KM->getMousePos(); - - if ( KM->isKeyDown( KEY_TAB ) && KM->isAltPressed() ) { - mWindow->minimize(); - } - - if ( KM->isKeyDown( KEY_ESCAPE ) || ( KM->isKeyDown( KEY_Q ) && !Con->isActive() ) ) { - updateConfig(); - mWindow->close(); - } - - if ( ( KM->isAltPressed() && KM->isKeyUp( KEY_RETURN ) ) || - ( KM->isKeyUp( KEY_F ) && !Con->isActive() ) ) { - mWindow->toggleFullscreen(); - - prepareFrame(); - scaleToScreen(); - } - - if ( KM->isKeyUp( KEY_F6 ) ) { - switchFade(); - } - - if ( KM->isKeyUp( KEY_F3 ) ) { - Con->toggle(); - } - - if ( ( KM->isKeyUp( KEY_S ) && !Con->isActive() ) || KM->isKeyUp( KEY_F4 ) ) { - mCursor = !mCursor; - mWindow->getCursorManager()->setVisible( mCursor ); - } - - if ( KM->isKeyUp( KEY_H ) && !Con->isActive() ) { - mShowHelp = !mShowHelp; - } - - if ( ( ( KM->isKeyUp( KEY_V ) && KM->isControlPressed() ) || - ( KM->isKeyUp( KEY_INSERT ) && KM->isShiftPressed() ) ) && - !Con->isActive() ) { - std::string tPath = mWindow->getClipboard()->getText(); - - if ( ( tPath.size() && isImage( tPath ) ) || FileSystem::isDirectory( tPath ) ) { - loadDir( tPath ); - } - } - - if ( !Con->isActive() ) { - if ( KM->mouseWheelScrolledUp() || KM->isKeyUp( KEY_PAGEUP ) ) { - if ( !mConfig.BlockWheelSpeed || - Sys::getTicks() - mLastWheelUse > mConfig.WheelBlockTime ) { - mLastWheelUse = Sys::getTicks(); - loadPrevImage(); - disableSlideShow(); - } - } - - if ( KM->mouseWheelScrolledDown() || KM->isKeyUp( KEY_PAGEDOWN ) ) { - if ( !mConfig.BlockWheelSpeed || - Sys::getTicks() - mLastWheelUse > mConfig.WheelBlockTime ) { - mLastWheelUse = Sys::getTicks(); - loadNextImage(); - disableSlideShow(); - } - } - - if ( KM->isKeyUp( KEY_I ) ) { - mConfig.ShowInfo = !mConfig.ShowInfo; - } - } - - if ( mFiles.size() && !mFiles[mCurImg].Tex.empty() && !Con->isActive() ) { - if ( KM->isKeyUp( KEY_HOME ) ) { - loadFirstImage(); - disableSlideShow(); - } - - if ( KM->isKeyUp( KEY_END ) ) { - loadLastImage(); - disableSlideShow(); - } - - if ( KM->isKeyUp( KEY_KP_MULTIPLY ) ) { - scaleToScreen(); - } - - if ( KM->isKeyUp( KEY_KP_DIVIDE ) ) { - mImg.setScale( mConfig.DefaultImageZoom ); - } - - if ( KM->isKeyUp( KEY_Z ) ) { - zoomImage(); - } - - if ( KM->isKeyUp( KEY_N ) ) { - if ( mWindow->getSize().getWidth() != (Int32)mImg.getSize().getWidth() || - mWindow->getSize().getHeight() != (Int32)mImg.getSize().getHeight() ) { - mWindow->setSize( mImg.getSize().getWidth(), mImg.getSize().getHeight() ); - } - } - - if ( Sys::getTicks() - mZoomTicks >= 15 ) { - mZoomTicks = Sys::getTicks(); - - if ( KM->isKeyDown( KEY_KP_MINUS ) ) - mImg.setScale( mImg.getScale() - 0.02f ); - - if ( KM->isKeyDown( KEY_KP_PLUS ) ) - mImg.setScale( mImg.getScale() + 0.02f ); - - if ( mImg.getScale().x < 0.0125f ) - mImg.setScale( 0.0125f ); - - if ( mImg.getScale().x > 50.0f ) - mImg.setScale( 50.0f ); - } - - if ( KM->isKeyDown( KEY_LEFT ) ) { - Vector2f nPos( - (Float)( (Int32)( mImg.getPosition().x + - ( ( mWindow->getElapsed().asMilliseconds() * 0.4f ) ) ) ), - mImg.getPosition().y ); - mImg.setPosition( nPos ); - } - - if ( KM->isKeyDown( KEY_RIGHT ) ) { - Vector2f nPos( - (Float)( (Int32)( mImg.getPosition().x + - ( -( mWindow->getElapsed().asMilliseconds() * 0.4f ) ) ) ), - mImg.getPosition().y ); - mImg.setPosition( nPos ); - } - - if ( KM->isKeyDown( KEY_UP ) ) { - Vector2f nPos( - mImg.getPosition().x, - (Float)( (Int32)( mImg.getPosition().y + - ( ( mWindow->getElapsed().asMilliseconds() * 0.4f ) ) ) ) ); - mImg.setPosition( nPos ); - } - - if ( KM->isKeyDown( KEY_DOWN ) ) { - Vector2f nPos( - mImg.getPosition().x, - (Float)( (Int32)( mImg.getPosition().y + - ( -( mWindow->getElapsed().asMilliseconds() * 0.4f ) ) ) ) ); - mImg.setPosition( nPos ); - } - - if ( KM->mouseLeftClicked() ) { - mMouseLeftPressing = false; - } - - if ( KM->isMouseLeftPressed() ) { - Vector2f mNewPos; - if ( !mMouseLeftPressing ) { - mMouseLeftStartClick = Mouse; - mMouseLeftPressing = true; - } else { - mMouseLeftClick = Mouse; - - mNewPos.x = static_cast( mMouseLeftClick.x ) - - static_cast( mMouseLeftStartClick.x ); - mNewPos.y = static_cast( mMouseLeftClick.y ) - - static_cast( mMouseLeftStartClick.y ); - - if ( mNewPos.x != 0 || mNewPos.y != 0 ) { - mMouseLeftStartClick = Mouse; - mImg.setPosition( Vector2f( mImg.getPosition().x + mNewPos.x, - mImg.getPosition().y + mNewPos.y ) ); - } - } - } - - if ( KM->mouseMiddleClicked() ) { - mMouseMiddlePressing = false; - } - - if ( KM->isMouseMiddlePressed() ) { - if ( !mMouseMiddlePressing ) { - mMouseMiddleStartClick = Mouse; - mMouseMiddlePressing = true; - } else { - mMouseMiddleClick = Mouse; - - Vector2f v1( (Float)mMouseMiddleStartClick.x, (Float)mMouseMiddleStartClick.y ); - Vector2f v2( Vector2f( (Float)mMouseMiddleClick.x, (Float)mMouseMiddleClick.y ) ); - Line2f l1( v1, v2 ); - Float Dist = v1.distance( v2 ) * 0.01f; - Float Ang = l1.getAngle(); - - if ( Dist ) { - mMouseMiddleStartClick = Mouse; - if ( Ang >= 0.0f && Ang <= 180.0f ) { - mImg.setScale( mImg.getScale() - Dist ); - if ( mImg.getScale().x < 0.0125f ) - mImg.setScale( 0.0125f ); - } else { - mImg.setScale( mImg.getScale() + Dist ); - } - } - } - } - - if ( KM->isMouseRightPressed() ) { - Line2f line( Vector2f( Mouse.x, Mouse.y ), Vector2f( HWidth, HHeight ) ); - mImg.setRotation( line.getAngle() ); - } - - if ( KM->isKeyUp( KEY_X ) ) { - if ( mImgRT == RENDER_NORMAL ) - mImgRT = RENDER_FLIPPED; - else if ( mImgRT == RENDER_MIRROR ) - mImgRT = RENDER_FLIPPED_MIRRORED; - else if ( mImgRT == RENDER_FLIPPED_MIRRORED ) - mImgRT = RENDER_MIRROR; - else - mImgRT = RENDER_NORMAL; - - mImg.setRenderMode( mImgRT ); - } - - if ( KM->isKeyUp( KEY_C ) ) { - if ( mImgRT == RENDER_NORMAL ) - mImgRT = RENDER_MIRROR; - else if ( mImgRT == RENDER_FLIPPED ) - mImgRT = RENDER_FLIPPED_MIRRORED; - else if ( mImgRT == RENDER_FLIPPED_MIRRORED ) - mImgRT = RENDER_FLIPPED; - else - mImgRT = RENDER_NORMAL; - - mImg.setRenderMode( mImgRT ); - } - - if ( KM->isKeyUp( KEY_R ) ) { - mImg.setRotation( mImg.getRotation() + 90.0f ); - scaleToScreen(); - } - - if ( KM->isKeyUp( KEY_A ) ) { - mFilter = mFilter == Texture::Filter::Linear ? Texture::Filter::Nearest - : Texture::Filter::Linear; - size_t numFrames = mImg.getNumFrames(); - for ( size_t i = 0; i < numFrames; i++ ) { - Texture* tex = mImg.getTextureRegion( i )->getTexture().get(); - if ( tex ) - tex->setFilter( mFilter ); - } - } - - if ( KM->isKeyUp( KEY_M ) ) { - mImg.setPosition( Vector2f::Zero ); - mImg.setScale( mConfig.DefaultImageZoom ); - mImg.setRotation( 0.f ); - scaleToScreen(); - - if ( EE->getCurrentWindow()->isMaximized() ) { - EE->getCurrentWindow()->setSize( mImg.getSize().getWidth(), - mImg.getSize().getHeight() ); - } - } - - if ( KM->isKeyUp( KEY_T ) ) { - mImg.setPosition( Vector2f::Zero ); - mImg.setScale( mConfig.DefaultImageZoom ); - mImg.setRotation( 0.f ); - scaleToScreen(); - } - - if ( KM->isKeyUp( KEY_E ) ) { - createSlideShow( mSlideTime ); - } - - if ( KM->isKeyUp( KEY_D ) ) { - disableSlideShow(); - } - - if ( KM->isKeyUp( KEY_K ) ) { - Texture* curTex; - - if ( NULL != mImg.getCurrentTextureRegion() && - NULL != ( curTex = mImg.getCurrentTextureRegion()->getTexture().get() ) ) { - curTex->setMipmap( !curTex->getMipmap() ); - curTex->reload(); - } - } - - if ( KM->isKeyUp( KEY_L ) ) { - mLockZoomAndPosition = !mLockZoomAndPosition; - } - - if ( KM->isKeyUp( KEY_F5 ) ) { - Texture* curTex; - - if ( NULL != mImg.getCurrentTextureRegion() && - NULL != ( curTex = mImg.getCurrentTextureRegion()->getTexture().get() ) ) { - Image img( curTex->getFilepath(), 0, formatConfiguration ); - curTex->replace( &img ); - } - } - } -} - -void App::createSlideShow( Uint32 time ) { - if ( time < 250 ) - time = 250; - - mSlideShow = true; - mSlideTime = time; - mSlideTicks = Sys::getTicks(); -} - -void App::disableSlideShow() { - mSlideShow = false; -} - -void App::doSlideShow() { - if ( mSlideShow ) { - if ( Sys::getTicks() - mSlideTicks >= mSlideTime ) { - mSlideTicks = Sys::getTicks(); - - if ( (Uint32)( mCurImg + 1 ) < mFiles.size() ) { - loadNextImage(); - } else { - disableSlideShow(); - } - } - } -} - -void App::scaleToScreen( const bool& force ) { - if ( mFiles.size() && !mFiles[mCurImg].Tex.empty() ) { - Texture* Tex = mFiles[mCurImg].Tex[0].get(); - - if ( NULL == Tex ) - return; - - if ( Tex->getImageWidth() * mConfig.DefaultImageZoom >= Width || - Tex->getImageHeight() * mConfig.DefaultImageZoom >= Height ) { - zoomImage(); - } else if ( force ) { - mImg.setScale( mConfig.DefaultImageZoom ); - } - } -} - -void App::zoomImage() { - if ( mFiles.size() && !mFiles[mCurImg].Tex.empty() ) { - Texture* Tex = mFiles[mCurImg].Tex[0].get(); - - if ( NULL == Tex ) - return; - - Sizef boxSize = mImg.getSize(); - - mImg.setScale( eemin( Width / boxSize.getWidth(), Height / boxSize.getHeight() ) ); - } -} - -void App::setWindowCaption() { - if ( mFiles.size() ) - mInfo = "EEiv - " + mFiles[mCurImg].Path; - else - mInfo = "EEiv"; - - if ( mInfo != mWindow->getTitle() ) - mWindow->setTitle( mInfo ); -} - -void App::prepareFrame() { - Width = mWindow->getWidth(); - Height = mWindow->getHeight(); - HWidth = Width * 0.5f; - HHeight = Height * 0.5f; -} - -void App::render() { - prepareFrame(); - - doSlideShow(); - - if ( mFiles.size() && !mFiles[mCurImg].Tex.empty() ) { - doFade(); - - Texture* Tex = mImg.getCurrentTextureRegion()->getTexture().get(); - - if ( Tex ) { - Float X = static_cast( - static_cast( HWidth - mImg.getSize().getWidth() * 0.5f ) ); - Float Y = static_cast( - static_cast( HHeight - mImg.getSize().getHeight() * 0.5f ) ); - - mImg.setAutoAnimate( false ); - mImg.update(); - mImg.setOffset( Vector2i( X, Y ) ); - mImg.setAlpha( mCurAlpha ); - mImg.draw(); - } - } - - if ( mConfig.ShowInfo ) - FonCache.draw( 0, 0 ); - - printHelp(); - - SceneManager::instance()->update(); - SceneManager::instance()->draw(); -} - -void App::createFade() { - if ( mConfig.Fade ) { - mAlpha = 0.0f; - mCurAlpha = 0; - mFading = true; - mOldImg = mImg; - } -} - -void App::doFade() { - if ( mConfig.Fade && mFading ) { - mAlpha += ( 255 * RET ) / mConfig.TransitionTime; - mCurAlpha = static_cast( mAlpha ); - - if ( mAlpha >= 255.0f ) { - mAlpha = 255.0f; - mCurAlpha = 255; - mFading = false; - } - - Texture* Tex = NULL; - - if ( NULL != mOldImg.getCurrentTextureRegion() && - ( Tex = mOldImg.getCurrentTextureRegion()->getTexture().get() ) ) { - Float X = static_cast( - static_cast( HWidth - mOldImg.getSize().getWidth() * 0.5f ) ); - Float Y = static_cast( - static_cast( HHeight - mOldImg.getSize().getHeight() * 0.5f ) ); - - mOldImg.setOffset( Vector2i( X, Y ) ); - mOldImg.setAlpha( 255 - mCurAlpha ); - mOldImg.draw(); - } - } -} - -void App::setImgScale( Float scale ) { - mImg.setScale( scale ); -} - -void App::saveConfig() { - Ini.setValueI( "window", "width", mConfig.Width ); - Ini.setValueI( "window", "height", mConfig.Height ); - Ini.setValueI( "window", "bitcolor", mConfig.BitColor ); - Ini.setValueI( "window", "windowed", mConfig.Windowed ); - Ini.setValueI( "window", "resizeable", mConfig.Resizeable ); - Ini.setValueI( "window", "vsync", mConfig.VSync ); - Ini.setValueI( "window", "doublebuffering", mConfig.DoubleBuffering ); - Ini.setValueI( "window", "usedesktopresolution", mConfig.UseDesktopResolution ); - Ini.setValueI( "window", "noframe", mConfig.NoFrame ); - Ini.setValueI( "window", "maximizeatstart", mConfig.MaximizeAtStart ); - Ini.setValueI( "window", "framelimit", mConfig.FrameLimit ); - Ini.setValueI( "viewer", "fade", mConfig.Fade ); - Ini.setValueI( "viewer", "lateloading", mConfig.LateLoading ); - Ini.setValueI( "viewer", "blockwheelspeed", mConfig.BlockWheelSpeed ); - Ini.setValueI( "viewer", "showinfo", mConfig.ShowInfo ); - Ini.setValueI( "viewer", "transitiontime", mConfig.TransitionTime ); - Ini.setValueI( "viewer", "consolefontsize", mConfig.ConsoleFontSize ); - Ini.setValueI( "viewer", "appfontsize", mConfig.AppFontSize ); - Ini.setValueF( "viewer", "defaultimagezoom", mConfig.DefaultImageZoom ); - Ini.setValueI( "viewer", "wheelblocktime", mConfig.WheelBlockTime ); - - Ini.writeFile(); -} - -void App::end() { - saveConfig(); - TTF.reset(); - TTFMon.reset(); - Engine::destroySingleton(); -} - -void App::printHelp() { - if ( mShowHelp ) { - Uint32 Top = 6; - Uint32 Left = 6; - - if ( NULL == mHelpCache ) { - String HT = "Keys List:\n"; - HT += "Escape: Quit from EEiv\n"; - HT += "ALT + RETURN or F: Toogle Fullscreen - Windowed\n"; - HT += String::fromUtf8( "F3 or º: Toggle Console\n"sv ); - HT += "F4 or S: Show/Hide Cursor\n"; - HT += "Mouse Wheel Up or PageUp: Go to Previous Image\n"; - HT += "Mouse Wheel Down or PageDown: Go to Next Image\n"; - HT += "Key I: Show/Hide Image Info\n"; - HT += "Key *: Scale image to screen\n"; - HT += "Key /: Reset Scale to 1\n"; - HT += "Key Z: Fit Image to Screen\n"; - HT += "Key + and -, or mouse middle press up or down: Zoom in and Zoom out image\n"; - HT += "Key X: Flip Image\n"; - HT += "Key C: Mirror Image\n"; - HT += "Key R: Rotate 90º Image and scale it to screen\n"; - HT += "Key T: Reset the position and the scale of the image\n"; - HT += "Key A: Change the texture filter\n"; - HT += "Key M: Change the screen size to the size of the current image.\n"; - HT += "Key E: Play SlideShow\n"; - HT += "Key D: Pause/Disable SlideShow\n"; - HT += - "Key K: Reload the image switching the mipmap state ( with or without mipmaps )\n"; - HT += "Key L: Lock zoom and image position when switching images\n"; - HT += "Key Left - Right - Top - Down or left mouse press: Move the image\n"; - HT += "Key F5: Reload the image\n"; - HT += "Key F6: Switch fade\n"; - HT += "Key F12: Take a screenshot\n"; - HT += "Key HOME: Go to the first screenshot on the folder\n"; - HT += "Key END: Go to the last screenshot on the folder"; - - mHelpCache = eeNew( Text, ( HT, Fon, mConfig.AppFontSize ) ); - } - - mHelpCache->draw( Left, Height - Top - mHelpCache->getTextHeight() ); - } -} diff --git a/src/tools/eeiv/app.hpp b/src/tools/eeiv/app.hpp deleted file mode 100755 index 726cf0432..000000000 --- a/src/tools/eeiv/app.hpp +++ /dev/null @@ -1,193 +0,0 @@ -#ifndef CAPP_H -#define CAPP_H - -#include - -#include "consolecommands.hpp" - -class App { - public: - struct ImageData { - std::string Path; - std::vector Tex; - Float animFps{ 60 }; - }; - - struct Config { - Uint32 Width; - Uint32 Height; - Uint8 BitColor; - bool Windowed; - bool Resizeable; - bool VSync; - bool DoubleBuffering; - bool UseDesktopResolution; - bool NoFrame; - bool MaximizeAtStart; - bool Fade; - bool LateLoading; - bool BlockWheelSpeed; - bool ShowInfo; - Uint32 FrameLimit; - float TransitionTime; - int ConsoleFontSize; - int AppFontSize; - float DefaultImageZoom; - Uint32 WheelBlockTime; - }; - - static bool isRawImage( const std::string& path ); - - static bool isImage( const std::string& _path ); - - static bool isHttpUrl( const std::string& path ); - - App( int argc, char* argv[] ); - - ~App(); - - void process(); - - void loadDir( const std::string& path, const bool& getimages = true ); - - const std::string& getFileDirectoryPath() const { return mFilePath; } - - const std::string& getFile() const { return mFile; } - - std::string getFilePath() const { return mFilePath + mFile; } - - bool getSlideShow() const { return mSlideShow; } - - void setSlideShow( bool set ) { mSlideShow = set; } - - void createSlideShow( Uint32 time ); - - void fastLoadImage( const Uint32& ImgNum ); - - void setImgScale( Float scale ); - - const std::vector getFiles() const { return mFiles; } - - EE::Window::Window* getWindow() const { return mWindow; } - - Config& getConfig() { return mConfig; } - - protected: - void updateConfig(); - bool init(); - void input(); - void render(); - void end(); - void prepareFrame(); - void scaleToScreen( const bool& force = false ); - void getImages(); - Uint32 curImagePos( const std::string& path ); - std::pair, Float> loadImage( const std::string& path, - const bool& SetAsCurrent = false ); - void loadNextImage(); - void loadPrevImage(); - void zoomImage(); - void unloadImage( const Uint32& img ); - void updateImages(); - void setImage( const std::vector& Tex, const std::string& path, - Float animFps = 60 ); - void doFade(); - void createFade(); - void optUpdate(); - void printHelp(); - void loadFirstImage(); - void loadLastImage(); - void loadConfigValues(); - void loadConfig(); - void saveConfig(); - void clearTempDir(); - void videoResize(); - void restoreMouse(); - void setWindowCaption(); - void switchFade(); - void doSlideShow(); - void disableSlideShow(); - - Config mConfig; - - Engine* EE; - TextureFactory* TF; - System::Log* Log; - EE::Window::Window* mWindow{ nullptr }; - EE::Window::Input* KM; - - std::string MyPath; - - Font* Fon; //! Default App Font - Font* Mon; //! Console App Font - Text FonCache; - - FontTrueTypePtr TTF, TTFMon; - - UIConsole* Con; //! Console Instance - - Vector2i Mouse; //! Mouse Position on Screen - double ET; //! Elapsed Time Between Frames - double RET; //! Relative Elapsed Time ( skip time in Input() ) - - Float Width, Height; //! Width and Height of the Window - Float HWidth, HHeight; //! Half Width and Height of the Window - - std::string SLASH; //! Default SLASH string - - std::vector mFiles; //! Directory image file list - - Int32 mLastTicks, mZoomTicks; - std::string mInfo; //! Window caption string - - std::string mFilePath; //! Directory path of files - std::string mFile; //! Last used file name - - Int32 mCurImg; //! Current Image Selected from mFile - Sprite mImg, mOldImg; - - bool mFading; - Float mAlpha; - Uint8 mCurAlpha; - - bool mLaterLoad; - Int32 mLastLaterTick; - - Clock TEP; - - bool mCursor; - - bool mMouseLeftPressing; - Vector2i mMouseLeftStartClick, mMouseLeftClick; - - bool mMouseMiddlePressing; - Vector2i mMouseMiddleStartClick, mMouseMiddleClick; - - RenderMode mImgRT; - Texture::Filter mFilter; - - Int32 mLastWheelUse; - bool mShowHelp; - - bool mFirstLoad; - - std::string mStorePath; - std::string mTmpPath; - bool mUsedTempDir; - bool mLockZoomAndPosition; - - Clock TE; - - Text* mHelpCache; - - bool mSlideShow; - Uint32 mSlideTime; - Uint32 mSlideTicks; - - IniFile Ini; - - Image::FormatConfiguration formatConfiguration; - std::unique_ptr mConCmds; -}; - -#endif diff --git a/src/tools/eeiv/consolecommands.cpp b/src/tools/eeiv/consolecommands.cpp index 7eef9f58a..04e3ea3b1 100644 --- a/src/tools/eeiv/consolecommands.cpp +++ b/src/tools/eeiv/consolecommands.cpp @@ -1,5 +1,5 @@ #include "consolecommands.hpp" -#include "app.hpp" +#include "eeiv.hpp" static std::string createSavePath( const std::string& oriPath, Uint32 width, Uint32 height, Image::SaveType saveType ) { @@ -507,7 +507,7 @@ void ConsoleCommands::cmdBatchImgChangeFormat( const std::vector& params } void ConsoleCommands::cmdMoveTo( const std::vector& params ) { - if ( params.size() >= 2 && mApp->getFiles().size() > 0 ) { + if ( params.size() >= 2 && mApp->getFileCount() > 0 ) { Int32 tInt = 0; bool Res = String::fromString( tInt, params[1] ); @@ -515,12 +515,12 @@ void ConsoleCommands::cmdMoveTo( const std::vector& params ) { if ( tInt ) tInt--; - if ( Res && tInt >= 0 && tInt < (Int32)mApp->getFiles().size() ) { + if ( Res && tInt >= 0 && tInt < (Int32)mApp->getFileCount() ) { Con->pushText( "moveto: moving to image number " + String::toString( tInt + 1 ) ); mApp->fastLoadImage( tInt ); } else if ( params[1] == "last" ) { Con->pushText( "moveto: moving to last" ); - mApp->fastLoadImage( mApp->getFiles().size() - 1 ); + mApp->fastLoadImage( mApp->getFileCount() - 1 ); } else if ( params[1] == "first" ) { Con->pushText( "moveto: moving to first" ); mApp->fastLoadImage( 0 ); @@ -578,18 +578,30 @@ void ConsoleCommands::cmdSetImgFade( const std::vector& params ) { } void ConsoleCommands::cmdSetBackColor( const std::vector& params ) { - String Error( + const String error( "Usage example: setbackcolor 255 255 255 (RGB Color, numbers between 0 and 255)" ); - if ( params.size() >= 2 ) { - if ( params.size() >= 2 ) { - mApp->getWindow()->setClearColor( Color::fromString( params[1].toUtf8() ).toRGB() ); + if ( params.size() == 2 ) { + mApp->setBackgroundColor( Color::fromString( params[1].toUtf8() ) ); + Con->pushText( "setbackcolor applied" ); + return; + } + + if ( params.size() >= 4 ) { + Int32 red; + Int32 green; + Int32 blue; + if ( String::fromString( red, params[1] ) && String::fromString( green, params[2] ) && + String::fromString( blue, params[3] ) && red >= 0 && red <= 255 && green >= 0 && + green <= 255 && blue >= 0 && blue <= 255 ) { + mApp->setBackgroundColor( Color( static_cast( red ), static_cast( green ), + static_cast( blue ) ) ); Con->pushText( "setbackcolor applied" ); return; } - - Con->pushText( Error ); } + + Con->pushText( error ); } void ConsoleCommands::cmdLoadImg( const std::vector& params ) { diff --git a/src/tools/eeiv/eeiv.cpp b/src/tools/eeiv/eeiv.cpp old mode 100755 new mode 100644 index f232608db..191af69bb --- a/src/tools/eeiv/eeiv.cpp +++ b/src/tools/eeiv/eeiv.cpp @@ -1,15 +1,511 @@ -#include "app.hpp" +#include "eeiv.hpp" -EE_MAIN_FUNC int main(int argc, char *argv[]) { - App * MyApp = eeNew( App, ( argc, argv ) ); +#include - MyApp->process(); +using namespace EE::Network; +using namespace EE::Window; - eeDelete( MyApp ); - - Engine::destroySingleton(); - - EE::MemoryManager::showResults(); - - return 0; +bool App::isImage( const std::string& inputPath ) { + std::string path( inputPath ); + if ( String::startsWith( path, "file://" ) ) + path.erase( 0, 7 ); + return !FileSystem::isDirectory( path ) && FileSystem::fileSize( path ) && + Image::isImage( path ); +} + +bool App::isHttpUrl( const std::string& path ) { + return String::startsWith( path, "http://" ) || String::startsWith( path, "https://" ); +} + +App::App( int argc, char* argv[] ) : + mStorePath( Sys::getConfigPath( "eeiv" ) + FileSystem::getOSSlash() ), + mTmpPath( mStorePath + "tmp" + FileSystem::getOSSlash() ) { + if ( argc > 1 ) { + mInitialPath = argv[1]; + if ( FileSystem::isRelativePath( mInitialPath ) && !isHttpUrl( mInitialPath ) ) { + std::string cwd( FileSystem::getCurrentWorkingDirectory() ); + FileSystem::dirAddSlashAtEnd( cwd ); + mInitialPath.insert( 0, cwd ); + } + } +} + +App::~App() { + if ( mUIApplication ) { + saveConfig(); + Http::Pool::getGlobal().clear(); + Http::setThreadPool( nullptr ); + } +} + +EE::Window::Window* App::getWindow() const { + return mUIApplication ? mUIApplication->getWindow() : nullptr; +} + +void App::setBackgroundColor( const Color& color ) { + if ( getWindow() ) + getWindow()->setClearColor( color.toRGB() ); + if ( mMainLayout ) + mMainLayout->setBackgroundColor( color ); +} + +void App::loadConfig() { + const std::string configPath( mStorePath + "eeiv.ini" ); + mIni.loadFromFile( configPath ); + if ( FileSystem::fileExists( configPath ) ) + mIni.readFile(); + else + FileSystem::makeDir( mStorePath ); + + mConfig.Fade = mIni.getValueB( "viewer", "fade", true ); + mConfig.LateLoading = mIni.getValueB( "viewer", "lateloading", true ); + mConfig.BlockWheelSpeed = mIni.getValueB( "viewer", "blockwheelspeed", true ); + mConfig.ShowInfo = mIni.getValueB( "viewer", "showinfo", true ); + mConfig.MaximizeAtStart = mIni.getValueB( "window", "maximizeatstart", true ); + mConfig.VSync = mIni.getValueB( "window", "vsync", false ); + const std::string frameRateLimit( mIni.getValue( "window", "frameratelimit" ) ); + mConfig.FrameLimit = frameRateLimit.empty() + ? mIni.getValueI( "window", "framelimit", + ContextSettings::FrameRateLimitScreenRefreshRate ) + : mIni.getValueI( "window", "frameratelimit", + ContextSettings::FrameRateLimitScreenRefreshRate ); + mConfig.TransitionTime = mIni.getValueF( "viewer", "transitiontime", 200 ); + mConfig.ConsoleFontSize = mIni.getValueI( "viewer", "consolefontsize", 12 ); + mConfig.AppFontSize = mIni.getValueI( "viewer", "appfontsize", 12 ); + mConfig.DefaultImageZoom = mIni.getValueF( "viewer", "defaultimagezoom", 1 ); + mConfig.WheelBlockTime = mIni.getValueI( "viewer", "wheelblocktime", 200 ); +} + +void App::updateConfig() { + if ( !getWindow() || !getWindow()->isOpen() ) + return; + mIni.setValueI( "window", "width", getWindow()->getWidth() ); + mIni.setValueI( "window", "height", getWindow()->getHeight() ); + mConfig.MaximizeAtStart = getWindow()->isMaximized(); +} + +void App::saveConfig() { + updateConfig(); + mIni.setValueI( "viewer", "fade", mConfig.Fade ); + mIni.setValueI( "viewer", "lateloading", mConfig.LateLoading ); + mIni.setValueI( "viewer", "blockwheelspeed", mConfig.BlockWheelSpeed ); + mIni.setValueI( "viewer", "showinfo", mConfig.ShowInfo ); + mIni.setValueI( "window", "vsync", mConfig.VSync ); + mIni.setValueI( "window", "frameratelimit", mConfig.FrameLimit ); + mIni.setValueI( "window", "maximizeatstart", mConfig.MaximizeAtStart ); + mIni.setValueF( "viewer", "transitiontime", mConfig.TransitionTime ); + mIni.setValueI( "viewer", "consolefontsize", mConfig.ConsoleFontSize ); + mIni.setValueI( "viewer", "appfontsize", mConfig.AppFontSize ); + mIni.setValueF( "viewer", "defaultimagezoom", mConfig.DefaultImageZoom ); + mIni.setValueI( "viewer", "wheelblocktime", mConfig.WheelBlockTime ); + mIni.writeFile(); +} + +bool App::init() { + loadConfig(); + WindowSettings windowSettings = Engine::instance()->createWindowSettings( &mIni, "window" ); + ContextSettings contextSettings = + Engine::instance()->createContextSettings( &mIni, "window", false ); + contextSettings.VSync = mConfig.VSync; + contextSettings.FrameRateLimit = mConfig.FrameLimit; + contextSettings.SharedGLContext = true; + windowSettings.Title = "eeiv"; + windowSettings.Icon = Sys::getProcessPath() + "assets/eeiv.png"; + if ( !FileSystem::fileExists( windowSettings.Icon ) ) + windowSettings.Icon = Sys::getProcessPath() + "assets/icon/ee.png"; + + mUIApplication = std::make_unique( windowSettings, UIApplication::Settings(), + contextSettings ); + if ( !getWindow() || !getWindow()->isOpen() ) + return false; + setBackgroundColor( Color::Black ); + + mThreadPool = ThreadPool::createShared( 2 ); + mUIApplication->getUI()->setThreadPool( mThreadPool ); + Http::setThreadPool( mThreadPool ); + if ( mConfig.MaximizeAtStart ) + getWindow()->maximize(); + getWindow()->setCloseRequestCallback( [this]( EE::Window::Window* ) { + saveConfig(); + return true; + } ); + + mMainLayout = mUIApplication->getUI()->loadLayoutFromString( R"xml( + + + + + )xml" ); + if ( !mMainLayout ) { + Log::error( "eeiv: failed to create the main UI layout" ); + return false; + } + mImageViewer = mMainLayout->find( "image_viewer" ); + mHelp = mMainLayout->find( "key_help" ); + if ( !mImageViewer || !mHelp ) { + Log::error( "eeiv: one or more widgets are missing from the main UI layout" ); + return false; + } + + mImageViewer->setMinScale( 0.0125f ); + mImageViewer->setMaxScale( 50.f ); + mImageViewer->setAutoFitOnSizeChange( true ); + mImageViewer->setUseNativeImageSize( true ); + mImageViewer->setDisplayOptions( mConfig.ShowInfo ? UIImageViewer::DisplayName | + UIImageViewer::DisplayDimensions | + UIImageViewer::DisplayGalleryPosition + : 0 ); + mImageViewer->on( Event::OnResourceLoaded, [this]( auto ) { syncLoadedImage(); } ); + + mHelp->setText( "H: Toggle this help\n" + "F3: Toggle console\n" + "F11: Open UI inspector\n" + "F12: Take screenshot\n" + "Mouse wheel: Previous / next image\n" + "Page Up: Previous image\n" + "Page Down: Next image\n" + "Home: First image\n" + "End: Last image\n" + "Arrow keys: Move image\n" + "Keypad + / -: Zoom image\n" + "Z: Fit image\n" + "Keypad *: Fit image\n" + "Keypad /: Reset zoom\n" + "X: Flip image\n" + "C: Mirror image\n" + "R: Rotate image\n" + "T: Reset view\n" + "A: Toggle filtering\n" + "E: Start slideshow\n" + "D: Stop slideshow\n" + "L: Lock view\n" + "F5: Reload image\n" + "F: Toggle fullscreen\n" + "S: Toggle cursor\n" + "I: Toggle image info\n" + "Q: Quit\n" + "Escape: Quit" ); + + // Quake-mode consoles are root overlays by design. Keeping it outside the content layout also + // preserves UIConsole's native show/hide animation. + mConsole = UIConsole::NewOpt( nullptr, true, true, 1024000 ); + mConsole->setParent( mUIApplication->getUI()->getRoot() ); + mConsole->setQuakeMode( true ); + mConsole->setVisible( false ); + mConsole->setMaxLogLines( 1024000 ); + mConsole->setFontSize( mConfig.ConsoleFontSize ); + mConsoleCommands = std::make_unique( this, mConsole ); + registerConsoleCommands(); + registerKeyBindings(); + + if ( !mInitialPath.empty() ) + loadDir( mInitialPath, true ); + if ( mFiles.empty() ) + mConsole->toggle(); + return true; +} + +void App::registerConsoleCommands() { + const auto add = [this]( const char* name, auto command ) { + mConsole->addCommand( name, [this, command]( const auto& event ) { + ( mConsoleCommands.get()->*command )( event ); + } ); + }; + add( "loaddir", &ConsoleCommands::cmdLoadDir ); + add( "loadimg", &ConsoleCommands::cmdLoadImg ); + add( "setbackcolor", &ConsoleCommands::cmdSetBackColor ); + add( "setimgfade", &ConsoleCommands::cmdSetImgFade ); + add( "setlateloading", &ConsoleCommands::cmdSetLateLoading ); + add( "setblockwheel", &ConsoleCommands::cmdSetBlockWheel ); + add( "moveto", &ConsoleCommands::cmdMoveTo ); + add( "batchimgscale", &ConsoleCommands::cmdBatchImgScale ); + add( "batchimgchangeformat", &ConsoleCommands::cmdBatchImgChangeFormat ); + add( "batchimgthumbnail", &ConsoleCommands::cmdBatchImgThumbnail ); + add( "imgchangeformat", &ConsoleCommands::cmdImgChangeFormat ); + add( "imgresize", &ConsoleCommands::cmdImgResize ); + add( "imgscale", &ConsoleCommands::cmdImgScale ); + add( "imgthumbnail", &ConsoleCommands::cmdImgThumbnail ); + add( "imgcentercrop", &ConsoleCommands::cmdImgCenterCrop ); + add( "slideshow", &ConsoleCommands::cmdSlideShow ); + add( "setzoom", &ConsoleCommands::cmdSetZoom ); +} + +void App::registerKeyBindings() { + mUIApplication->getUI()->on( Event::KeyUp, [this]( const Event* event ) { + const auto* keyEvent = event->asKeyEvent(); + const Keycode key = keyEvent->getKeyCode(); + if ( key == KEY_F11 ) { + UIWidgetInspector::create( mUIApplication->getUI() ); + return; + } + if ( key == KEY_F3 ) { + mHelp->setVisible( false ); + mConsole->toggle(); + return; + } + if ( mConsole->isActive() ) + return; + if ( key == KEY_ESCAPE || key == KEY_Q ) + getWindow()->close(); + else if ( key == KEY_TAB && keyEvent->getMod() & KEYMOD_ALT ) + getWindow()->minimize(); + else if ( key == KEY_F || ( key == KEY_RETURN && keyEvent->getMod() & KEYMOD_ALT ) ) + getWindow()->toggleFullscreen(); + else if ( key == KEY_F12 ) + getWindow()->takeScreenshot(); + else if ( key == KEY_H ) + toggleHelp(); + else if ( key == KEY_S || key == KEY_F4 ) { + mCursorVisible = !mCursorVisible; + getWindow()->getCursorManager()->setVisible( mCursorVisible ); + } else if ( ( key == KEY_V && keyEvent->getMod() & KEYMOD_CTRL ) || + ( key == KEY_INSERT && keyEvent->getMod() & KEYMOD_SHIFT ) ) { + const std::string path( getWindow()->getClipboard()->getText() ); + if ( isImage( path ) || isHttpUrl( path ) || FileSystem::isDirectory( path ) ) + loadDir( path ); + } else if ( key == KEY_HOME && !mFiles.empty() ) + fastLoadImage( 0 ); + else if ( key == KEY_END && !mFiles.empty() ) + fastLoadImage( mFiles.size() - 1 ); + else if ( key == KEY_PAGEUP && mCurImg > 0 ) { + mSlideShow = false; + fastLoadImage( mCurImg - 1 ); + } else if ( key == KEY_PAGEDOWN && mCurImg + 1 < static_cast( mFiles.size() ) ) { + mSlideShow = false; + fastLoadImage( mCurImg + 1 ); + } else if ( key == KEY_I ) { + mConfig.ShowInfo = !mConfig.ShowInfo; + mImageViewer->setDisplayOptions( + mConfig.ShowInfo ? UIImageViewer::DisplayName | UIImageViewer::DisplayDimensions | + UIImageViewer::DisplayGalleryPosition + : 0 ); + } else if ( key == KEY_KP_MULTIPLY || key == KEY_Z ) { + fitImage(); + } else if ( key == KEY_KP_DIVIDE ) { + setImgScale( mConfig.DefaultImageZoom ); + } else if ( key == KEY_N && mImageViewer->getImage()->getDrawable() ) { + const Sizef size( mImageViewer->getImage()->getDrawable()->getPixelsSize() ); + getWindow()->setSize( size.getWidth(), size.getHeight() ); + } else if ( key == KEY_A ) { + if ( Sprite* sprite = getImageSprite() ) { + mTextureFilter = mTextureFilter == Texture::Filter::Linear + ? Texture::Filter::Nearest + : Texture::Filter::Linear; + for ( size_t i = 0; i < sprite->getNumFrames(); ++i ) { + Texture* texture = sprite->getTextureRegion( i )->getTexture().get(); + if ( texture ) + texture->setFilter( mTextureFilter ); + } + } + } else if ( key == KEY_L ) { + mImageViewer->setPreserveImageView( !mImageViewer->getPreserveImageView() ); + } else if ( key == KEY_M ) { + fitImage(); + if ( getWindow()->isMaximized() && mImageViewer->getImage()->getDrawable() ) { + const Sizef size( mImageViewer->getImage()->getDrawable()->getPixelsSize() ); + getWindow()->setSize( size.getWidth(), size.getHeight() ); + } + } else if ( key == KEY_K ) { + if ( Sprite* sprite = getImageSprite() ) { + if ( TextureRegion* region = sprite->getCurrentTextureRegion() ) { + if ( Texture* texture = region->getTexture().get() ) { + texture->setMipmap( !texture->getMipmap() ); + texture->reload(); + } + } + } + } else if ( key == KEY_F5 && !getFilePath().empty() ) { + loadImagePath( getFilePath() ); + } else if ( key == KEY_E ) { + createSlideShow( mSlideTime ); + } else if ( key == KEY_D ) { + mSlideShow = false; + } + } ); + mUIApplication->getUI()->on( Event::KeyDown, [this]( const Event* event ) { + if ( mConsole->isActive() || !mImageViewer->hasImage() ) + return; + const Keycode key = event->asKeyEvent()->getKeyCode(); + Vector2f position( mImageViewer->getImage()->getPixelsPosition() ); + if ( key == KEY_LEFT ) + position.x += 20.f; + else if ( key == KEY_RIGHT ) + position.x -= 20.f; + else if ( key == KEY_UP ) + position.y += 20.f; + else if ( key == KEY_DOWN ) + position.y -= 20.f; + else + return; + mImageViewer->getImage()->setPixelsPosition( position ); + } ); +} + +void App::toggleHelp() { + mHelp->setVisible( !mHelp->isVisible() ); + if ( mHelp->isVisible() ) + mHelp->toFront(); +} + +void App::fitImage() { + if ( mImageViewer && mImageViewer->hasImage() ) + mImageViewer->resetImageView(); +} + +Sprite* App::getImageSprite() const { + if ( !mImageViewer || !mImageViewer->getImage() || !mImageViewer->getImage()->getDrawable() ) + return nullptr; + return static_cast( mImageViewer->getImage()->getDrawable().get() ); +} + +void App::getImages() { + mFiles.clear(); + if ( !FileSystem::isDirectory( mFilePath ) ) + return; + for ( const std::string& file : FileSystem::filesGetInPath( mFilePath, true ) ) { + if ( isImage( mFilePath + file ) ) + mFiles.push_back( file ); + } +} + +void App::loadDir( const std::string& inputPath, const bool& getImagesList ) { + std::string path( inputPath ); + String::replaceAll( path, "%20", " " ); + if ( String::startsWith( path, "file://" ) ) + path.erase( 0, 7 ); + + if ( isHttpUrl( path ) ) { + loadImageUrl( path ); + return; + } + + if ( FileSystem::isDirectory( path ) ) { + mFilePath = path; + FileSystem::dirAddSlashAtEnd( mFilePath ); + mFile.clear(); + } else { + if ( !isImage( path ) ) + return; + mFilePath = FileSystem::fileRemoveFileName( path ); + FileSystem::dirAddSlashAtEnd( mFilePath ); + mFile = FileSystem::fileNameFromPath( path ); + } + + if ( getImagesList ) + getImages(); + if ( mFiles.empty() ) + return; + + mCurImg = 0; + if ( !mFile.empty() ) { + auto it = std::find( mFiles.begin(), mFiles.end(), mFile ); + if ( it != mFiles.end() ) + mCurImg = std::distance( mFiles.begin(), it ); + } + fastLoadImage( mCurImg ); +} + +void App::loadImagePath( const std::string& path, bool loadGallery ) { + Log::info( "eeiv: loading image: %s", path.c_str() ); + mImageViewer->loadImageAsync( path, false, loadGallery ); +} + +void App::loadImageUrl( const std::string& url ) { + FileSystem::makeDir( mTmpPath ); + const std::string outputPath( mTmpPath + "download" ); + mConsole->pushText( "Downloading \"" + url + "\"..." ); + Http::getAsync( + [this, outputPath]( const Http&, Http::Request&, Http::Response& response ) { + if ( !response.isOK() ) { + mConsole->runOnMainThread( [this, status = response.getStatus()] { + mConsole->pushText( "Couldn't download the image (HTTP %d).", status ); + } ); + return; + } + const std::string& body = response.getBody(); + if ( !FileSystem::fileWrite( outputPath, reinterpret_cast( body.data() ), + body.size() ) ) { + mConsole->runOnMainThread( [this] { + mConsole->pushText( "Couldn't write the downloaded image to disk." ); + } ); + return; + } + mConsole->runOnMainThread( [this, outputPath] { loadDir( outputPath ); } ); + }, + URI( url ) ); +} + +void App::fastLoadImage( const Uint32& imageNum ) { + if ( imageNum >= mFiles.size() ) + return; + mCurImg = imageNum; + mFile = mFiles[mCurImg]; + loadImagePath( mFilePath + mFile ); +} + +void App::syncLoadedImage() { + const std::string path( mImageViewer->getImagePath() ); + if ( path.empty() ) + return; + mFilePath = FileSystem::fileRemoveFileName( path ); + FileSystem::dirAddSlashAtEnd( mFilePath ); + mFile = FileSystem::fileNameFromPath( path ); + auto it = std::find( mFiles.begin(), mFiles.end(), mFile ); + if ( it != mFiles.end() ) + mCurImg = std::distance( mFiles.begin(), it ); + if ( Sprite* sprite = getImageSprite() ) { + for ( size_t i = 0; i < sprite->getNumFrames(); ++i ) { + if ( Texture* texture = sprite->getTextureRegion( i )->getTexture().get() ) + texture->setFilter( mTextureFilter ); + } + } + getWindow()->setTitle( "eeiv - " + mFile ); + mImageViewer->getImage()->setFocus(); + Log::info( "eeiv: displaying image %d/%d: %s", mCurImg + 1, mFiles.size(), path.c_str() ); +} + +void App::setImgScale( Float scale ) { + if ( mImageViewer && mImageViewer->getImage() ) + mImageViewer->getImage()->setScale( scale ); +} + +void App::createSlideShow( Uint32 time ) { + mSlideTime = eemax( 250, time ); + mSlideTicks = Sys::getTicks(); + mSlideShow = true; +} + +void App::updateSlideShow() { + if ( !mSlideShow || Sys::getTicks() - mSlideTicks < mSlideTime ) + return; + mSlideTicks = Sys::getTicks(); + if ( mCurImg + 1 < static_cast( mFiles.size() ) ) + fastLoadImage( mCurImg + 1 ); + else + mSlideShow = false; +} + +void App::process() { + if ( !init() ) + return; + getWindow()->runMainLoop( [this] { + getWindow()->getInput()->update(); + updateSlideShow(); + SceneManager::instance()->update(); + if ( mUIApplication->getUI()->invalidated() ) { + getWindow()->clear(); + SceneManager::instance()->draw(); + getWindow()->display(); + } else { + getWindow()->getInput()->waitEvent( + Milliseconds( getWindow()->hasFocus() ? 16 : 100 ) ); + } + } ); +} + +EE_MAIN_FUNC int main( int argc, char* argv[] ) { + App app( argc, argv ); + app.process(); + return EXIT_SUCCESS; } diff --git a/src/tools/eeiv/eeiv.hpp b/src/tools/eeiv/eeiv.hpp new file mode 100644 index 000000000..47ddaf1c1 --- /dev/null +++ b/src/tools/eeiv/eeiv.hpp @@ -0,0 +1,89 @@ +#pragma once + +#include + +#include "consolecommands.hpp" + +using namespace EE; +using namespace EE::Graphics; +using namespace EE::System; +using namespace EE::UI; +using namespace EE::UI::Tools; + +class App { + public: + struct Config { + bool Fade{ true }; + bool LateLoading{ true }; + bool BlockWheelSpeed{ true }; + bool ShowInfo{ true }; + bool MaximizeAtStart{ true }; + bool VSync{ false }; + Uint32 FrameLimit{ ContextSettings::FrameRateLimitScreenRefreshRate }; + float TransitionTime{ 200 }; + int ConsoleFontSize{ 12 }; + int AppFontSize{ 12 }; + float DefaultImageZoom{ 1 }; + Uint32 WheelBlockTime{ 200 }; + }; + + static bool isImage( const std::string& path ); + static bool isHttpUrl( const std::string& path ); + + App( int argc, char* argv[] ); + ~App(); + + void process(); + void loadDir( const std::string& path, const bool& getImages = true ); + void fastLoadImage( const Uint32& imageNum ); + void setImgScale( Float scale ); + void createSlideShow( Uint32 time ); + void setBackgroundColor( const Color& color ); + + const std::string& getFileDirectoryPath() const { return mFilePath; } + const std::string& getFile() const { return mFile; } + std::string getFilePath() const { return mFilePath + mFile; } + bool getSlideShow() const { return mSlideShow; } + void setSlideShow( bool enabled ) { mSlideShow = enabled; } + size_t getFileCount() const { return mFiles.size(); } + EE::Window::Window* getWindow() const; + Config& getConfig() { return mConfig; } + + private: + bool init(); + void loadConfig(); + void saveConfig(); + void updateConfig(); + void getImages(); + void registerConsoleCommands(); + void registerKeyBindings(); + void loadImagePath( const std::string& path, bool loadGallery = true ); + void loadImageUrl( const std::string& url ); + void syncLoadedImage(); + void updateSlideShow(); + void toggleHelp(); + void fitImage(); + Sprite* getImageSprite() const; + + std::string mInitialPath; + Config mConfig; + IniFile mIni; + std::string mStorePath; + std::string mTmpPath; + std::string mFilePath; + std::string mFile; + std::vector mFiles; + Int32 mCurImg{ 0 }; + bool mSlideShow{ false }; + Uint32 mSlideTime{ 4000 }; + Uint32 mSlideTicks{ 0 }; + std::shared_ptr mThreadPool; + std::unique_ptr mUIApplication; + UIWidget* mMainLayout{ nullptr }; + UIImageViewer* mImageViewer{ nullptr }; + UITextView* mHelp{ nullptr }; + UIConsole* mConsole{ nullptr }; + bool mCursorVisible{ true }; + Texture::Filter mTextureFilter{ Texture::Filter::Linear }; + std::unique_ptr mConsoleCommands; +};