diff --git a/bin/assets/fonts/NotoColorEmoji.ttf b/bin/assets/fonts/NotoColorEmoji.ttf new file mode 100644 index 000000000..2c1f10435 Binary files /dev/null and b/bin/assets/fonts/NotoColorEmoji.ttf differ diff --git a/include/eepp/graphics/fonttruetype.hpp b/include/eepp/graphics/fonttruetype.hpp index 7bf4b7fe5..92759a973 100644 --- a/include/eepp/graphics/fonttruetype.hpp +++ b/include/eepp/graphics/fonttruetype.hpp @@ -113,6 +113,7 @@ class EE_API FontTrueType : public Font { mutable std::vector mPixelBuffer; ///< Pixel buffer holding a glyph's pixels before being written to the texture bool mBoldAdvanceSameAsRegular; + bool mIsColorEmojiFont{ false }; mutable std::map mClosestCharacterSize; }; diff --git a/include/eepp/ui/models/modeleditingdelegate.hpp b/include/eepp/ui/models/modeleditingdelegate.hpp index d15ad5e48..530f588e5 100644 --- a/include/eepp/ui/models/modeleditingdelegate.hpp +++ b/include/eepp/ui/models/modeleditingdelegate.hpp @@ -9,6 +9,11 @@ namespace EE { namespace UI { namespace Models { class EE_API ModelEditingDelegate { public: + enum SelectionBehavior { + DoNotSelect, + SelectAll, + }; + virtual ~ModelEditingDelegate() {} void bind( std::shared_ptr model, const ModelIndex& index ) { @@ -23,9 +28,12 @@ class EE_API ModelEditingDelegate { UIWidget* getWidget() const { return mWidget; } std::function onCommit; + std::function onRollback; + std::function onChange; virtual Variant getValue() const = 0; - virtual void setValue( const Variant& ) = 0; + virtual void setValue( const Variant&, + SelectionBehavior selectionBehavior = SelectionBehavior::SelectAll ) = 0; virtual void willBeginEditing() {} protected: @@ -38,6 +46,16 @@ class EE_API ModelEditingDelegate { onCommit(); } + void rollback() { + if ( onRollback ) + onRollback(); + } + void change() { + if ( onChange ) + onChange(); + } + + private: std::shared_ptr mModel; ModelIndex mIndex; UIWidget* mWidget{ nullptr }; diff --git a/projects/linux/ee.creator.user b/projects/linux/ee.creator.user index bf9d6a0c9..07d90362d 100644 --- a/projects/linux/ee.creator.user +++ b/projects/linux/ee.creator.user @@ -1,6 +1,6 @@ - + EnvironmentId @@ -193,7 +193,7 @@ true - gmake + --with-dynamic-freetype gmake premake4 %{buildDir}../../../ ProjectExplorer.ProcessStep @@ -234,7 +234,7 @@ true - gmake + --with-dynamic-freetype gmake premake5 %{buildDir}../../../ ProjectExplorer.ProcessStep @@ -590,7 +590,7 @@ true - gmake + --with-dynamic-freetype gmake premake5 %{buildDir}../../../ ProjectExplorer.ProcessStep diff --git a/src/eepp/core/debug.cpp b/src/eepp/core/debug.cpp index df3eaeede..8fc829c66 100644 --- a/src/eepp/core/debug.cpp +++ b/src/eepp/core/debug.cpp @@ -33,7 +33,7 @@ void eeREPORT_ASSERT( const char* File, int Line, const char* Exp ) { printf( "ASSERT: %s file:%s line:%d", Exp, File, Line ); } -#if defined( EE_COMPILER_GCC ) && !defined( EE_ARM ) +#if defined( EE_COMPILER_GCC ) && !defined( EE_ARM ) && EE_PLATFORM != EE_PLATFORM_EMSCRIPTEN asm( "int3" ); #else assert( false ); diff --git a/src/eepp/graphics/fonttruetype.cpp b/src/eepp/graphics/fonttruetype.cpp index 6937dac06..30d92d43d 100644 --- a/src/eepp/graphics/fonttruetype.cpp +++ b/src/eepp/graphics/fonttruetype.cpp @@ -12,6 +12,7 @@ #include FT_OUTLINE_H #include FT_BITMAP_H #include FT_STROKER_H +#include FT_TRUETYPE_TABLES_H #include #include @@ -73,6 +74,13 @@ FontTrueType::~FontTrueType() { cleanup(); } +bool isColorEmojiFont( const FT_Face& face ) { + static const uint32_t tag = FT_MAKE_TAG( 'C', 'B', 'D', 'T' ); + unsigned long length = 0; + FT_Load_Sfnt_Table( face, tag, 0, nullptr, &length ); + return length > 0; +} + bool FontTrueType::loadFromFile( const std::string& filename ) { if ( !FileSystem::fileExists( filename ) && PackManager::instance()->isFallbackToPacksActive() ) { @@ -109,27 +117,33 @@ bool FontTrueType::loadFromFile( const std::string& filename ) { return false; } - // Load the stroker that will be used to outline the font - FT_Stroker stroker; - if ( FT_Stroker_New( static_cast( mLibrary ), &stroker ) != 0 ) { - Log::error( "Failed to load font \"%s\" (failed to create the stroker)", filename.c_str() ); - FT_Done_Face( face ); - return false; + mFace = face; + mIsColorEmojiFont = isColorEmojiFont( static_cast( mFace ) ); + + FT_Stroker stroker = nullptr; + if ( !mIsColorEmojiFont ) { + // Load the stroker that will be used to outline the font + if ( FT_Stroker_New( static_cast( mLibrary ), &stroker ) != 0 ) { + Log::error( "Failed to load font \"%s\" (failed to create the stroker)", + filename.c_str() ); + FT_Done_Face( face ); + return false; + } + + // Store the loaded font in our ugly void* :) + mStroker = stroker; } // Select the unicode character map if ( FT_Select_Charmap( face, FT_ENCODING_UNICODE ) != 0 ) { Log::error( "Failed to load font \"%s\" (failed to set the Unicode character set)", filename.c_str() ); - FT_Stroker_Done( stroker ); + if ( stroker ) + FT_Stroker_Done( stroker ); FT_Done_Face( face ); return false; } - // Store the loaded font in our ugly void* :) - mStroker = stroker; - mFace = face; - // Store the font information mInfo.family = face->family_name ? face->family_name : std::string(); @@ -168,25 +182,30 @@ bool FontTrueType::loadFromMemory( const void* data, std::size_t sizeInBytes, bo return false; } + mFace = face; + mIsColorEmojiFont = isColorEmojiFont( static_cast( mFace ) ); + // Load the stroker that will be used to outline the font - FT_Stroker stroker; - if ( FT_Stroker_New( static_cast( mLibrary ), &stroker ) != 0 ) { - Log::error( "Failed to load font from memory (failed to create the stroker)" ); - FT_Done_Face( face ); - return false; + FT_Stroker stroker = nullptr; + if ( !mIsColorEmojiFont ) { + if ( FT_Stroker_New( static_cast( mLibrary ), &stroker ) != 0 ) { + Log::error( "Failed to load font from memory (failed to create the stroker)" ); + FT_Done_Face( face ); + return false; + } } // Select the Unicode character map if ( FT_Select_Charmap( face, FT_ENCODING_UNICODE ) != 0 ) { Log::error( "Failed to load font from memory (failed to set the Unicode character set)" ); - FT_Stroker_Done( stroker ); + if ( stroker ) + FT_Stroker_Done( stroker ); FT_Done_Face( face ); return false; } // Store the loaded font in our ugly void* :) mStroker = stroker; - mFace = face; // Store the font information mInfo.family = face->family_name ? face->family_name : std::string(); @@ -236,19 +255,25 @@ bool FontTrueType::loadFromStream( IOStream& stream ) { return false; } - // Load the stroker that will be used to outline the font - FT_Stroker stroker; - if ( FT_Stroker_New( static_cast( mLibrary ), &stroker ) != 0 ) { - Log::error( "Failed to load font from stream (failed to create the stroker)" ); - FT_Done_Face( face ); - delete rec; - return false; + mFace = face; + mIsColorEmojiFont = isColorEmojiFont( static_cast( mFace ) ); + FT_Stroker stroker = nullptr; + + if ( !mIsColorEmojiFont ) { + // Load the stroker that will be used to outline the font + if ( FT_Stroker_New( static_cast( mLibrary ), &stroker ) != 0 ) { + Log::error( "Failed to load font from stream (failed to create the stroker)" ); + FT_Done_Face( face ); + delete rec; + return false; + } } // Select the Unicode character map if ( FT_Select_Charmap( face, FT_ENCODING_UNICODE ) != 0 ) { Log::error( "Failed to load font from stream (failed to set the Unicode character set)" ); - FT_Stroker_Done( stroker ); + if ( stroker ) + FT_Stroker_Done( stroker ); FT_Done_Face( face ); delete rec; return false; @@ -256,7 +281,6 @@ bool FontTrueType::loadFromStream( IOStream& stream ) { // Store the loaded font in our ugly void* :) mStroker = stroker; - mFace = face; mStreamRec = rec; // Store the font information @@ -514,7 +538,7 @@ Glyph FontTrueType::loadGlyph( Uint32 codePoint, unsigned int characterSize, boo FT_Error err = 0; // Load the glyph corresponding to the code point - FT_Int32 flags = FT_LOAD_TARGET_NORMAL | FT_LOAD_FORCE_AUTOHINT; + FT_Int32 flags = FT_LOAD_TARGET_NORMAL | FT_LOAD_FORCE_AUTOHINT | FT_LOAD_COLOR; if ( outlineThickness != 0 ) flags |= FT_LOAD_NO_BITMAP; if ( ( err = FT_Load_Char( face, codePoint, flags ) ) != 0 ) { @@ -578,14 +602,28 @@ Glyph FontTrueType::loadGlyph( Uint32 codePoint, unsigned int characterSize, boo // pollute them with pixels from neighbors const int padding = 2; + Float scale = + mIsColorEmojiFont ? (Float)characterSize / (Float)eemax( width, height ) : 1.f; + + int destWidth = width; + int destHeight = height; + + if ( mIsColorEmojiFont ) { + destWidth *= scale; + destHeight *= scale; + glyph.advance *= scale; + } + width += 2 * padding; height += 2 * padding; + destWidth += 2 * padding; + destHeight += 2 * padding; // Get the glyphs page corresponding to the character size Page& page = mPages[characterSize]; // Find a good position for the new glyph into the texture - glyph.textureRect = findGlyphRect( page, width, height ); + glyph.textureRect = findGlyphRect( page, destWidth, destHeight ); // Make sure the texture data is positioned in the center // of the allocated texture rectangle @@ -594,6 +632,12 @@ Glyph FontTrueType::loadGlyph( Uint32 codePoint, unsigned int characterSize, boo glyph.textureRect.Right -= 2 * padding; glyph.textureRect.Bottom -= 2 * padding; + // Write the pixels to the texture + unsigned int x = glyph.textureRect.Left - padding; + unsigned int y = glyph.textureRect.Top - padding; + unsigned int w = glyph.textureRect.Right + 2 * padding; + unsigned int h = glyph.textureRect.Bottom + 2 * padding; + // Compute the glyph's bounding box glyph.bounds.Left = static_cast( face->glyph->metrics.horiBearingX ) / static_cast( 1 << 6 ); @@ -609,14 +653,17 @@ Glyph FontTrueType::loadGlyph( Uint32 codePoint, unsigned int characterSize, boo // Resize the pixel buffer to the new size and fill it with transparent white pixels mPixelBuffer.resize( width * height * 4 ); - Uint8* current = &mPixelBuffer[0]; + Uint8* pixelPtr = &mPixelBuffer[0]; + Uint8* current = pixelPtr; Uint8* end = current + width * height * 4; - while ( current != end ) { - ( *current++ ) = 255; - ( *current++ ) = 255; - ( *current++ ) = 255; - ( *current++ ) = 0; + if ( bitmap.pixel_mode != FT_PIXEL_MODE_BGRA ) { + while ( current != end ) { + ( *current++ ) = 255; + ( *current++ ) = 255; + ( *current++ ) = 255; + ( *current++ ) = 0; + } } // Extract the glyph's pixels from the bitmap @@ -636,6 +683,30 @@ Glyph FontTrueType::loadGlyph( Uint32 codePoint, unsigned int characterSize, boo } pixels += bitmap.pitch; } + } else if ( bitmap.pixel_mode == FT_PIXEL_MODE_BGRA ) { + Image source( (Uint8*)pixels, bitmap.width, bitmap.rows, 4 ); + Image dest( &mPixelBuffer[0], width, height, 4 ); + source.avoidFreeImage( true ); + dest.avoidFreeImage( true ); + for ( size_t y = 0; y < bitmap.rows; ++y ) { + for ( size_t x = 0; x < bitmap.width; ++x ) { + Color col = source.getPixel( x, y ); + dest.setPixel( padding + x, padding + y, Color( col.b, col.g, col.r, col.a ) ); + } + } + + if ( scale < 1.f ) { + dest.scale( scale ); + pixelPtr = dest.getPixels(); + dest.saveToFile( String::format( "/home/downloads/%uld.png", codePoint ), + Image::SAVE_TYPE_PNG ); + glyph.bounds.Left *= scale; + glyph.bounds.Right *= scale; + glyph.bounds.Top *= scale; + glyph.bounds.Bottom *= scale; + w = dest.getWidth(); + h = dest.getHeight(); + } } else { // Pixels are 8 bits gray levels for ( int y = padding; y < height - padding; ++y ) { @@ -648,12 +719,7 @@ Glyph FontTrueType::loadGlyph( Uint32 codePoint, unsigned int characterSize, boo } } - // Write the pixels to the texture - unsigned int x = glyph.textureRect.Left - padding; - unsigned int y = glyph.textureRect.Top - padding; - unsigned int w = glyph.textureRect.Right + 2 * padding; - unsigned int h = glyph.textureRect.Bottom + 2 * padding; - page.texture->update( &mPixelBuffer[0], w, h, x, y ); + page.texture->update( pixelPtr, w, h, x, y ); } // Delete the FT glyph @@ -735,7 +801,21 @@ bool FontTrueType::setCurrentSize( unsigned int characterSize ) const { FT_UShort currentSize = face->size->metrics.x_ppem; if ( currentSize != characterSize ) { - FT_Error result = FT_Set_Pixel_Sizes( face, 0, characterSize ); + if ( mIsColorEmojiFont ) { + int bestMatch = 0; + int diff = eeabs( characterSize - face->available_sizes[0].width ); + for ( int i = 1; i < face->num_fixed_sizes; ++i ) { + int ndiff = eeabs( characterSize - face->available_sizes[i].width ); + if ( ndiff < diff ) { + bestMatch = i; + diff = ndiff; + } + } + characterSize = bestMatch; + } + + FT_Error result = mIsColorEmojiFont ? FT_Select_Size( face, characterSize ) + : FT_Set_Pixel_Sizes( face, 0, characterSize ); if ( result == FT_Err_Invalid_Pixel_Size ) { // In the case of bitmap fonts, resizing can @@ -767,7 +847,7 @@ bool FontTrueType::setCurrentSize( unsigned int characterSize ) const { } else { return false; } - } else { + } else if ( characterSize != currentSize && face->size->metrics.x_ppem > 0 ) { return setCurrentSize( it->second ); } } diff --git a/src/eepp/graphics/image.cpp b/src/eepp/graphics/image.cpp index b225a00c7..523e55943 100644 --- a/src/eepp/graphics/image.cpp +++ b/src/eepp/graphics/image.cpp @@ -855,10 +855,10 @@ void Image::scale( const Float& scale, ResamplerFilter filter ) { if ( 1.f == scale ) return; - Int32 new_width = (Int32)( (Float)mWidth * scale ); - Int32 new_height = (Int32)( (Float)mHeight * scale ); + Int32 newWidth = (Int32)( (Float)mWidth * scale ); + Int32 newHeight = (Int32)( (Float)mHeight * scale ); - resize( new_width, new_height, filter ); + resize( newWidth, newHeight, filter ); } Graphics::Image* Image::thumbnail( const Uint32& maxWidth, const Uint32& maxHeight, diff --git a/src/examples/fonts/fonts.cpp b/src/examples/fonts/fonts.cpp index ed88eaba2..5a16a2284 100644 --- a/src/examples/fonts/fonts.cpp +++ b/src/examples/fonts/fonts.cpp @@ -3,6 +3,7 @@ EE::Window::Window* win = NULL; FontTrueType* fontTest; FontTrueType* fontTest2; +FontTrueType* fontEmoji; FontBMFont* fontBMFont; FontSprite* fontSprite; Text text; @@ -10,6 +11,7 @@ Text text2; Text text3; Text text4; Text text5; +Text text6; void mainLoop() { // Clear the screen buffer @@ -38,6 +40,8 @@ void mainLoop() { text5.draw( ( win->getWidth() - text5.getTextWidth() ) * 0.5f, 640 ); + text6.draw( ( win->getWidth() - text6.getTextWidth() ) * 0.5f, 690 ); + // Draw frame win->display(); } @@ -113,6 +117,13 @@ EE_MAIN_FUNC int main( int argc, char* argv[] ) { text5.setString( "Lorem ipsum dolor sit amet, consectetur adipisicing elit." ); text5.setFontSize( 38 ); + fontEmoji = FontTrueType::New( "NotoColorEmoji" ); + fontEmoji->loadFromFile( "assets/fonts/NotoColorEmoji.ttf" ); + + text6.setFont( fontEmoji ); + text6.setFontSize( 64 ); + text6.setString( "👽 😀 💩 😃 👻" ); + // Application loop win->runMainLoop( &mainLoop ); } diff --git a/src/tools/codeeditor/codeeditor.cpp b/src/tools/codeeditor/codeeditor.cpp index 60e77b0d3..1a7b956b4 100644 --- a/src/tools/codeeditor/codeeditor.cpp +++ b/src/tools/codeeditor/codeeditor.cpp @@ -1056,7 +1056,9 @@ void App::syncProjectTreeWithEditor( UICodeEditor* editor ) { std::string path = editor->getDocument().getFilePath(); if ( path.size() >= mCurrentProject.size() ) { path = path.substr( mCurrentProject.size() ); + mProjectTreeView->setFocusOnSelection( false ); mProjectTreeView->selectRowWithPath( path ); + mProjectTreeView->setFocusOnSelection( true ); } } } diff --git a/src/tools/codeeditor/docsearchcontroller.cpp b/src/tools/codeeditor/docsearchcontroller.cpp index f3bf94ef4..689c3bcf1 100644 --- a/src/tools/codeeditor/docsearchcontroller.cpp +++ b/src/tools/codeeditor/docsearchcontroller.cpp @@ -261,7 +261,6 @@ void DocSearchController::hideSearchBar() { } void DocSearchController::onCodeEditorFocusChange( UICodeEditor* editor ) { - if ( mSearchState.editor && mSearchState.editor != editor ) { String word = mSearchState.editor->getHighlightWord(); mSearchState.editor->setHighlightWord( "" ); diff --git a/src/tools/codeeditor/filelocator.cpp b/src/tools/codeeditor/filelocator.cpp index 71977c43d..a08abc2a5 100644 --- a/src/tools/codeeditor/filelocator.cpp +++ b/src/tools/codeeditor/filelocator.cpp @@ -24,7 +24,7 @@ void FileLocator::updateLocateTable() { } ); #else mLocateTable->setModel( - mDirTree->fuzzyMatchTree( mLocateInput->getText(), LOCATEBAR_MAX_RESULTS ) ); + mApp->getDirTree()->fuzzyMatchTree( mLocateInput->getText(), LOCATEBAR_MAX_RESULTS ) ); mLocateTable->getSelection().set( mLocateTable->getModel()->index( 0 ) ); #endif } else { diff --git a/src/tools/codeeditor/formattermodule.cpp b/src/tools/codeeditor/formattermodule.cpp index e009ec322..2c9b62790 100644 --- a/src/tools/codeeditor/formattermodule.cpp +++ b/src/tools/codeeditor/formattermodule.cpp @@ -149,10 +149,8 @@ void FormatterModule::runFormatter( UICodeEditor* editor, const Formatter& forma strings.push_back( cmdArr[i].c_str() ); strings.push_back( NULL ); struct subprocess_s subprocess; - int result = subprocess_create( strings.data(), - subprocess_option_inherit_environment | - subprocess_option_combined_stdout_stderr, - &subprocess ); + int result = + subprocess_create( strings.data(), subprocess_option_inherit_environment, &subprocess ); if ( 0 == result ) { std::string buffer( 1024, '\0' ); std::string data;