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.
This commit is contained in:
Martín Lucas Golini
2026-07-31 01:25:41 -03:00
parent 51ceff9b08
commit 13508c20d1
10 changed files with 734 additions and 1494 deletions
+1
View File
@@ -98,6 +98,7 @@ ResourcePtr<UIIconTheme> 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 },
+1
View File
@@ -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();
+91 -38
View File
@@ -3,6 +3,7 @@
#include <eepp/system/filesystem.hpp>
#include <eepp/system/iostreamfile.hpp>
#include <eepp/system/iostreammemory.hpp>
#include <eepp/system/log.hpp>
#include <eepp/system/scopedop.hpp>
#include <eepp/ui/tools/uiimageviewer.hpp>
#include <eepp/ui/uiimage.hpp>
@@ -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 ? "<memory>" : 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<const unsigned char*>( 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<std::vector<Image>>();
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<const Uint8*>( 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 ? "<memory>" : 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<Float>( 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() {
+1
View File
@@ -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" ) );
File diff suppressed because it is too large Load Diff
-193
View File
@@ -1,193 +0,0 @@
#ifndef CAPP_H
#define CAPP_H
#include <eepp/ee.hpp>
#include "consolecommands.hpp"
class App {
public:
struct ImageData {
std::string Path;
std::vector<TexturePtr> 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<ImageData> 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<std::vector<TexturePtr>, 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<TexturePtr>& 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<ImageData> 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<ConsoleCommands> mConCmds;
};
#endif
+22 -10
View File
@@ -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<String>& params
}
void ConsoleCommands::cmdMoveTo( const std::vector<String>& 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<String>& 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<String>& params ) {
}
void ConsoleCommands::cmdSetBackColor( const std::vector<String>& 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<Uint8>( red ), static_cast<Uint8>( green ),
static_cast<Uint8>( blue ) ) );
Con->pushText( "setbackcolor applied" );
return;
}
Con->pushText( Error );
}
Con->pushText( error );
}
void ConsoleCommands::cmdLoadImg( const std::vector<String>& params ) {
Executable → Regular
+507 -11
View File
@@ -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 <eepp/network/http.hpp>
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<UIApplication>( 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(
<RelativeLayout id="eeiv_layout" lw="mp" lh="mp" background-color="#000">
<ImageViewer id="image_viewer" lw="mp" lh="mp" />
<TextView id="key_help" lw="wc" lh="wc" lg="bottom|left" margin="8dp"
padding="8dp" background-color="#141414DC" visible="false" enabled="false" />
</RelativeLayout>
)xml" );
if ( !mMainLayout ) {
Log::error( "eeiv: failed to create the main UI layout" );
return false;
}
mImageViewer = mMainLayout->find<UIImageViewer>( "image_viewer" );
mHelp = mMainLayout->find<UITextView>( "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<ConsoleCommands>( 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<Int32>( 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<Sprite*>( 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<const Uint8*>( 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<Uint32>( 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<Int32>( 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;
}
+89
View File
@@ -0,0 +1,89 @@
#pragma once
#include <eepp/ee.hpp>
#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<std::string> mFiles;
Int32 mCurImg{ 0 };
bool mSlideShow{ false };
Uint32 mSlideTime{ 4000 };
Uint32 mSlideTicks{ 0 };
std::shared_ptr<ThreadPool> mThreadPool;
std::unique_ptr<UIApplication> 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<ConsoleCommands> mConsoleCommands;
};