diff --git a/include/eepp/graphics/image.hpp b/include/eepp/graphics/image.hpp index e667d479f..46351c44d 100644 --- a/include/eepp/graphics/image.hpp +++ b/include/eepp/graphics/image.hpp @@ -111,6 +111,19 @@ class EE_API Image { getInfo( const std::string& path, int* width, int* height, int* channels, const FormatConfiguration& imageFormatConfiguration = FormatConfiguration() ); + /** @return True if success to get the info. + * @param data the pointer containing the image raw data + * @param dataSize the point data size + * @param width the var to store the image width + * @param height the var to store the image height + * @param channels the var to store the image channels count + * @param imageFormatConfiguration The specific image format configuration to use when decoding + * the image. + */ + static bool getInfoFromMemory( + const unsigned char* data, const size_t& dataSize, int* width, int* height, int* channels, + const FormatConfiguration& imageFormatConfiguration = FormatConfiguration() ); + /** @return True if the file is a valid image ( reads the file header to know if the file is an * image file format supported ) * @param path the image path diff --git a/include/eepp/ui/tools/uicodeeditorsplitter.hpp b/include/eepp/ui/tools/uicodeeditorsplitter.hpp index ca759b6aa..74004f1cb 100644 --- a/include/eepp/ui/tools/uicodeeditorsplitter.hpp +++ b/include/eepp/ui/tools/uicodeeditorsplitter.hpp @@ -85,6 +85,11 @@ class EE_API UICodeEditorSplitter { const std::string& tabName, bool focus = true ); + std::vector> getTabFromOwnedWidgetId( const std::string& id ); + + bool removeTabWithOwnedWidgetId( const std::string& id, bool destroyOwnedNode = true, + bool immediateClose = false ); + UICodeEditor* createCodeEditor(); void focusSomeEditor( Node* searchFrom = nullptr ); diff --git a/include/eepp/ui/uiicon.hpp b/include/eepp/ui/uiicon.hpp index 9dcd6db0a..7b56f3ec8 100644 --- a/include/eepp/ui/uiicon.hpp +++ b/include/eepp/ui/uiicon.hpp @@ -3,6 +3,9 @@ #include #include +#include +#include +#include namespace EE { namespace Graphics { class FontTrueType; @@ -43,7 +46,24 @@ class EE_API UIGlyphIcon : public UIIcon { mutable FontTrueType* mFont; Uint32 mCodePoint; - Uint32 mCloseCb{0}; + Uint32 mCloseCb{ 0 }; +}; + +class EE_API UISVGIcon : public UIIcon { + public: + static UIIcon* New( const std::string& name, const std::string& svgXML ); + + virtual ~UISVGIcon(); + + virtual Drawable* getSize( const int& size ) const; + + protected: + UISVGIcon( const std::string& name, const std::string& svgXML ); + + std::string mSVGXml; + mutable std::unordered_map mSVGs; + mutable Sizei mOriSize; + mutable int mOriChannels{ 0 }; }; }} // namespace EE::UI diff --git a/projects/linux/ee.files b/projects/linux/ee.files index 1112374b0..feab45b9d 100644 --- a/projects/linux/ee.files +++ b/projects/linux/ee.files @@ -1194,6 +1194,8 @@ ../../src/tools/ecode/uicodeeditorsplitter.hpp ../../src/tools/ecode/uitreeviewglobalsearch.cpp ../../src/tools/ecode/uitreeviewglobalsearch.hpp +../../src/tools/ecode/uiwelcomescreen.cpp +../../src/tools/ecode/uiwelcomescreen.hpp ../../src/tools/ecode/universallocator.cpp ../../src/tools/ecode/universallocator.hpp ../../src/tools/ecode/version.cpp diff --git a/src/eepp/graphics/image.cpp b/src/eepp/graphics/image.cpp index 7014ab220..cb1c02a8e 100644 --- a/src/eepp/graphics/image.cpp +++ b/src/eepp/graphics/image.cpp @@ -358,6 +358,31 @@ bool Image::getInfo( const std::string& path, int* width, int* height, int* chan return res; } +bool Image::getInfoFromMemory( const unsigned char* data, const size_t& dataSize, int* width, + int* height, int* channels, + const FormatConfiguration& imageFormatConfiguration ) { + bool res = stbi_info_from_memory( data, dataSize, width, height, channels ) != 0; + + if ( !res && svg_test_from_memory( data, dataSize ) ) { + ScopedBuffer sdata( dataSize + 1 ); + memcpy( sdata.get(), data, dataSize ); + sdata[dataSize] = '\0'; + NSVGimage* image = nsvgParse( (char*)sdata.get(), "px", 96.0f ); + + if ( NULL != image ) { + *width = image->width * imageFormatConfiguration.svgScale(); + *height = image->height * imageFormatConfiguration.svgScale(); + *channels = 4; + + nsvgDelete( image ); + + res = true; + } + } + + return res; +} + bool Image::isImage( const std::string& path ) { return STBI_unknown != stbi_test( path.c_str() ) || svg_test( path ); } diff --git a/src/eepp/ui/tools/uicodeeditorsplitter.cpp b/src/eepp/ui/tools/uicodeeditorsplitter.cpp index 266388c9b..5cecc1135 100644 --- a/src/eepp/ui/tools/uicodeeditorsplitter.cpp +++ b/src/eepp/ui/tools/uicodeeditorsplitter.cpp @@ -500,6 +500,33 @@ UICodeEditorSplitter::createWidgetInTabWidget( UITabWidget* tabWidget, UIWidget* return std::make_pair( tab, widget ); } +std::vector> +UICodeEditorSplitter::getTabFromOwnedWidgetId( const std::string& id ) { + std::vector> ret; + forEachTabWidget( [&ret, &id]( UITabWidget* tabWidget ) { + for ( size_t i = 0; i < tabWidget->getTabCount(); ++i ) { + UITab* tab = tabWidget->getTab( i ); + Node* ownedNode = tab->getOwnedWidget(); + if ( ownedNode->isWidget() && ownedNode->asType()->getId() == id ) { + ret.push_back( { tab, tabWidget } ); + } + } + } ); + return ret; +} + +bool UICodeEditorSplitter::removeTabWithOwnedWidgetId( const std::string& id, bool destroyOwnedNode, + bool immediateClose ) { + auto ret = getTabFromOwnedWidgetId( id ); + if ( ret.empty() ) + return false; + + for ( const auto& r : ret ) + r.second->removeTab( r.first, destroyOwnedNode, immediateClose ); + + return true; +} + void UICodeEditorSplitter::removeUnusedTab( UITabWidget* tabWidget, bool destroyOwnedNode, bool immediateClose ) { if ( tabWidget && tabWidget->getTabCount() >= 2 && @@ -1217,7 +1244,14 @@ void UICodeEditorSplitter::onTabClosed( const TabEvent* tabEvent ) { if ( tabWidget->getTabSelectedIndex() >= tabWidget->getTabCount() ) tabWidget->setTabSelected( eemin( tabWidget->getTabCount() - 1, tabEvent->getTabIndex() ) ); + + if ( mCurEditor == widget ) + mCurEditor = nullptr; + + if ( mCurWidget == widget ) + mCurWidget = nullptr; } + if ( tabEvent->getTab()->getOwnedWidget() == mCurEditor ) focusSomeEditor( nullptr ); eeASSERT( curWidgetExists() ); diff --git a/src/eepp/ui/uiicon.cpp b/src/eepp/ui/uiicon.cpp index ab6dccfc1..a72dd3d94 100644 --- a/src/eepp/ui/uiicon.cpp +++ b/src/eepp/ui/uiicon.cpp @@ -1,4 +1,5 @@ #include +#include #include namespace EE { namespace UI { @@ -21,11 +22,11 @@ Drawable* UIIcon::getSize( const int& size ) const { return it->second; int distance = UINT32_MAX; Drawable* closest = nullptr; - for ( auto& it : mSizes ) { - int diff = abs( it.first - size ); + for ( const auto& sit : mSizes ) { + int diff = abs( sit.first - size ); if ( diff < distance ) { distance = diff; - closest = it.second; + closest = sit.second; } } return closest; @@ -67,4 +68,38 @@ UIGlyphIcon::~UIGlyphIcon() { } } +UIIcon* UISVGIcon::New( const std::string& name, const std::string& svgXML ) { + return eeNew( UISVGIcon, ( name, svgXML ) ); +} + +UISVGIcon::~UISVGIcon() {} + +Drawable* UISVGIcon::getSize( const int& size ) const { + auto it = mSVGs.find( size ); + if ( it != mSVGs.end() ) + return it->second; + + Image::FormatConfiguration format; + if ( mOriSize == Sizei::Zero ) { + int w, h, c; + if ( Image::getInfoFromMemory( (const unsigned char*)mSVGXml.data(), mSVGXml.size(), &w, &h, + &c, format ) ) { + mOriSize = { w, h }; + mOriChannels = c; + } else { + return nullptr; + } + } + format.svgScale( size / (Float)eemax( mOriSize.x, mOriSize.y ) ); + Uint32 textId = TextureFactory::instance()->loadFromMemory( + (const unsigned char*)&mSVGXml[0], mSVGXml.size(), false, Texture::ClampMode::ClampToEdge, + false, false, format ); + Texture* texture = TextureFactory::instance()->getTexture( textId ); + mSVGs[size] = texture; + return texture; +} + +UISVGIcon::UISVGIcon( const std::string& name, const std::string& svgXML ) : + UIIcon( name ), mSVGXml( svgXML ) {} + }} // namespace EE::UI diff --git a/src/tools/ecode/ecode.cpp b/src/tools/ecode/ecode.cpp index 482928d33..31c725fbd 100644 --- a/src/tools/ecode/ecode.cpp +++ b/src/tools/ecode/ecode.cpp @@ -5,6 +5,7 @@ #include "plugins/linter/linterplugin.hpp" #include "plugins/lsp/lspclientplugin.hpp" #include "settingsmenu.hpp" +#include "uiwelcomescreen.hpp" #include "version.hpp" #include #include @@ -105,11 +106,12 @@ void App::saveDoc() { if ( mSplitter->getCurEditor()->save() ) { updateEditorState(); } else { - UIMessageBox * msgBox = errorMsgBox( i18n( - "could_not_write_file", "Could not write file to disk.\nPlease check if the file " - "is read-only or if ecode does not have permissions to write to that file.\n" - "File path is: " - ) + mSplitter->getCurEditor()->getDocument().getFilePath() ); + UIMessageBox* msgBox = errorMsgBox( + i18n( "could_not_write_file", + "Could not write file to disk.\nPlease check if the file " + "is read-only or if ecode does not have permissions to write to that file.\n" + "File path is: " ) + + mSplitter->getCurEditor()->getDocument().getFilePath() ); setFocusEditorOnClose( msgBox ); } @@ -1034,9 +1036,13 @@ void App::setFocusEditorOnClose( UIMessageBox* msgBox ) { } Drawable* App::findIcon( const std::string& name ) { + return findIcon( name, mMenuIconSize ); +} + +Drawable* App::findIcon( const std::string& name, const size_t iconSize ) { UIIcon* icon = mUISceneNode->findIcon( name ); if ( icon ) - return icon->getSize( mMenuIconSize ); + return icon->getSize( iconSize ); return nullptr; } @@ -1719,6 +1725,8 @@ bool App::isUnlockedCommand( const std::string& command ) { void App::closeEditors() { mRecentClosedFiles = {}; + mSplitter->removeTabWithOwnedWidgetId( "welcome_ecode" ); + mConfig.saveProject( mCurrentProject, mSplitter, mConfigPath, mProjectDocConfig ); std::vector editors = mSplitter->getAllEditors(); while ( !editors.empty() ) { @@ -1772,6 +1780,7 @@ void App::closeFolder() { mProjectViewEmptyCont->setVisible( true ); mFileSystemModel->setRootPath( "" ); updateOpenRecentFolderBtn(); + UIWelcomeScreen::createWelcomeScreen( this ); } void App::createDocAlert( UICodeEditor* editor ) { @@ -2412,7 +2421,7 @@ void App::newFolder( const FileInfo& file ) { } ); } -void App::createAndShowRecentFolderPopUpMenu() { +void App::createAndShowRecentFolderPopUpMenu( Node* recentFolderBut ) { UIPopUpMenu* menu = UIPopUpMenu::New(); if ( mRecentFolders.empty() ) return; @@ -2424,9 +2433,28 @@ void App::createAndShowRecentFolderPopUpMenu() { const String& txt = event->getNode()->asType()->getText(); loadFolder( txt ); } ); - auto recentFolderBut = mProjectViewEmptyCont->find( "open_recent_folder" ); auto pos = recentFolderBut->getScreenPos(); pos.y += recentFolderBut->getPixelsSize().getHeight(); + UIMenu::findBestMenuPos( pos, menu ); + menu->setPixelsPosition( pos ); + menu->show(); +} + +void App::createAndShowRecentFilesPopUpMenu( Node* recentFilesBut ) { + UIPopUpMenu* menu = UIPopUpMenu::New(); + if ( mRecentFiles.empty() ) + return; + for ( const auto& file : mRecentFiles ) + menu->add( file ); + menu->addEventListener( Event::OnItemClicked, [&]( const Event* event ) { + if ( !event->getNode()->isType( UI_TYPE_MENUITEM ) ) + return; + const String& txt = event->getNode()->asType()->getText(); + loadFileFromPath( txt ); + } ); + auto pos = recentFilesBut->getScreenPos(); + pos.y += recentFilesBut->getPixelsSize().getHeight(); + UIMenu::findBestMenuPos( pos, menu ); menu->setPixelsPosition( pos ); menu->show(); } @@ -2457,7 +2485,8 @@ void App::initProjectTreeView( std::string path ) { mProjectViewEmptyCont->find( "open_recent_folder" ) ->addEventListener( Event::MouseClick, [&]( const Event* event ) { if ( event->asMouseEvent()->getFlags() & EE_BUTTON_LMASK ) - createAndShowRecentFolderPopUpMenu(); + createAndShowRecentFolderPopUpMenu( + mProjectViewEmptyCont->find( "open_recent_folder" ) ); } ); mProjectTreeView = mUISceneNode->find( "project_view" ); mProjectTreeView->setColumnsHidden( @@ -2590,6 +2619,8 @@ void App::initProjectTreeView( std::string path ) { loadFolder( mRecentFolders[0] ); } else { updateOpenRecentFolderBtn(); + + UIWelcomeScreen::createWelcomeScreen( this ); } mProjectTreeView->setAutoExpandOnSingleColumn( true ); @@ -2732,8 +2763,11 @@ void App::cleanUpRecentFolders() { } void App::loadFolder( const std::string& path ) { - if ( !mCurrentProject.empty() ) + if ( !mCurrentProject.empty() ) { closeEditors(); + } else { + mSplitter->removeTabWithOwnedWidgetId( "welcome_ecode" ); + } mProjectViewEmptyCont->setVisible( false ); @@ -3338,6 +3372,20 @@ void App::init( const LogLevel& logLevel, std::string file, const Float& pidelDe iconTheme->add( UIGlyphIcon::New( icon.first, codIconFont, icon.second ) ); } + iconTheme->add( UISVGIcon::New( + "ecode", + "" ) ); + mUISceneNode->getUIIconThemeManager()->setCurrentTheme( iconTheme ); Clock defClock; diff --git a/src/tools/ecode/ecode.hpp b/src/tools/ecode/ecode.hpp index 21c22c52d..1ce0c2f80 100644 --- a/src/tools/ecode/ecode.hpp +++ b/src/tools/ecode/ecode.hpp @@ -125,6 +125,8 @@ class App : public UICodeEditorSplitter::Client { Drawable* findIcon( const std::string& name ); + Drawable* findIcon( const std::string& name, const size_t iconSize ); + std::map getDefaultKeybindings(); std::map getLocalKeybindings(); @@ -244,6 +246,7 @@ class App : public UICodeEditorSplitter::Client { if ( mTerminalManager ) mTerminalManager->configureTerminalShell(); } ); + t.setCommand( "check-for-updates", [&] { checkForUpdates( false ); } ); mSplitter->registerSplitterCommands( t ); } @@ -324,7 +327,9 @@ class App : public UICodeEditorSplitter::Client { void loadFileDelayed(); - const std::vector& getRecentFolders() const; + const std::vector& getRecentFolders() const { return mRecentFolders; }; + + const std::vector& getRecentFiles() const { return mRecentFiles; }; const std::string& getThemesPath() const; @@ -340,6 +345,10 @@ class App : public UICodeEditorSplitter::Client { void loadImageFromMemory( const std::string& content ); + void createAndShowRecentFolderPopUpMenu( Node* recentFoldersBut ); + + void createAndShowRecentFilesPopUpMenu( Node* recentFilesBut ); + protected: std::vector mArgs; EE::Window::Window* mWindow{ nullptr }; @@ -489,8 +498,6 @@ class App : public UICodeEditorSplitter::Client { void cleanUpRecentFiles(); - void createAndShowRecentFolderPopUpMenu(); - void updateOpenRecentFolderBtn(); }; diff --git a/src/tools/ecode/projectdirectorytree.cpp b/src/tools/ecode/projectdirectorytree.cpp index 0bf87e8d9..f5c295ef0 100644 --- a/src/tools/ecode/projectdirectorytree.cpp +++ b/src/tools/ecode/projectdirectorytree.cpp @@ -86,7 +86,7 @@ void ProjectDirectoryTree::scan( const ProjectDirectoryTree::ScanCompleteEvent& #endif #if EE_PLATFORM != EE_PLATFORM_EMSCRIPTEN || defined( __EMSCRIPTEN_PTHREADS__ ) }, - [scanComplete, this] ( const auto& ) { + [scanComplete, this]( const auto& ) { if ( scanComplete ) scanComplete( *this ); } ); @@ -194,6 +194,28 @@ ProjectDirectoryTree::asModel( const size_t& max, return model; } +std::shared_ptr +ProjectDirectoryTree::emptyModel( const std::vector& prependCommands ) { + std::vector files; + std::vector names; + if ( !prependCommands.empty() ) { + int count = 0; + for ( const auto& cmd : prependCommands ) { + names.insert( names.begin() + count, cmd.name ); + files.insert( files.begin() + count, cmd.desc ); + count++; + } + } + auto model = std::make_shared( files, names ); + + if ( !prependCommands.empty() ) { + for ( size_t i = 0; i < prependCommands.size(); ++i ) + model->setIcon( i, prependCommands[i].icon ); + } + + return model; +} + size_t ProjectDirectoryTree::getFilesCount() const { return mFiles.size(); } diff --git a/src/tools/ecode/projectdirectorytree.hpp b/src/tools/ecode/projectdirectorytree.hpp index 6d2ed91c2..c204f1de9 100644 --- a/src/tools/ecode/projectdirectorytree.hpp +++ b/src/tools/ecode/projectdirectorytree.hpp @@ -130,6 +130,9 @@ class ProjectDirectoryTree { std::shared_ptr asModel( const size_t& max, const std::vector& prependCommands = {} ) const; + static std::shared_ptr + emptyModel( const std::vector& prependCommands = {} ); + size_t getFilesCount() const; const std::vector& getFiles() const; diff --git a/src/tools/ecode/uiwelcomescreen.cpp b/src/tools/ecode/uiwelcomescreen.cpp new file mode 100644 index 000000000..cc40548b6 --- /dev/null +++ b/src/tools/ecode/uiwelcomescreen.cpp @@ -0,0 +1,233 @@ +#include "uiwelcomescreen.hpp" +#include "ecode.hpp" +#include +#include + +namespace ecode { + +const char* LAYOUT = R"xml( + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +