From e3ca29842f4c4a97acaf5c596ed31376c16ca9fa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mart=C3=ADn=20Lucas=20Golini?= Date: Mon, 17 Nov 2025 00:23:58 -0300 Subject: [PATCH] Rename TextLayouter and move the static functions to TextLayout. Several fixes in AI Assistant chat UI. TextLayout cache is now returned as a shared pointer to avoid copying the struct each time. Some minor changes in FontTrueType and FontManager. ShapedGlyph now stores the TextDirection of each glyph. --- .../assets/textfiles/lorem-ipsum.uext | 1 + include/eepp/graphics/fontmanager.hpp | 2 + include/eepp/graphics/shapedglyph.hpp | 9 +++ include/eepp/graphics/text.hpp | 2 +- include/eepp/graphics/textlayout.hpp | 44 +++++++++++++ include/eepp/graphics/textlayouter.hpp | 41 ------------- include/eepp/network/http.hpp | 20 +++++- src/eepp/graphics/fontmanager.cpp | 15 ++++- src/eepp/graphics/fonttruetype.cpp | 17 +----- src/eepp/graphics/text.cpp | 61 ++++++++++--------- .../{textlayouter.cpp => textlayout.cpp} | 61 +++++++++++-------- src/eepp/network/http.cpp | 20 +++++- src/tests/ui_perf_test/ui_perf_test.cpp | 17 +++--- .../ecode/plugins/aiassistant/chatui.cpp | 22 +++++-- .../aiassistant/llmchatcompletionrequest.cpp | 23 ++++++- .../aiassistant/llmchatcompletionrequest.hpp | 6 ++ 16 files changed, 228 insertions(+), 133 deletions(-) create mode 100644 bin/unit_tests/assets/textfiles/lorem-ipsum.uext create mode 100644 include/eepp/graphics/textlayout.hpp delete mode 100644 include/eepp/graphics/textlayouter.hpp rename src/eepp/graphics/{textlayouter.cpp => textlayout.cpp} (87%) diff --git a/bin/unit_tests/assets/textfiles/lorem-ipsum.uext b/bin/unit_tests/assets/textfiles/lorem-ipsum.uext new file mode 100644 index 000000000..2524e5751 --- /dev/null +++ b/bin/unit_tests/assets/textfiles/lorem-ipsum.uext @@ -0,0 +1 @@ +Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. 😀 \ No newline at end of file diff --git a/include/eepp/graphics/fontmanager.hpp b/include/eepp/graphics/fontmanager.hpp index 74d7a455b..a7cd78250 100644 --- a/include/eepp/graphics/fontmanager.hpp +++ b/include/eepp/graphics/fontmanager.hpp @@ -47,6 +47,8 @@ class EE_API FontManager : public ResourceManager { void setAntialiasing( FontAntialiasing antialiasing ); + Font* getByInternalId( Uint32 internalId ) const; + protected: Font* mColorEmojiFont{ nullptr }; Font* mEmojiFont{ nullptr }; diff --git a/include/eepp/graphics/shapedglyph.hpp b/include/eepp/graphics/shapedglyph.hpp index 4caeae62a..84342d9ec 100644 --- a/include/eepp/graphics/shapedglyph.hpp +++ b/include/eepp/graphics/shapedglyph.hpp @@ -9,12 +9,21 @@ namespace EE::Graphics { class FontTrueType; +enum class TextDirection : Uint8 { + Unspecified = 0, //!< Unspecified + LeftToRight = 4, //!< Left-to-right + RightToLeft, //!< Right-to-left + TopToBottom, //!< Top-to-bottom + BottomToTop //!< Bottom-to-top +}; + struct ShapedGlyph { FontTrueType* font{ nullptr }; Uint32 glyphIndex{ 0 }; Uint32 stringIndex{ 0 }; Vector2f position; Vector2f advance; + TextDirection direction; }; } // namespace EE::Graphics diff --git a/include/eepp/graphics/text.hpp b/include/eepp/graphics/text.hpp index 6d367ea1a..4b1d0244b 100644 --- a/include/eepp/graphics/text.hpp +++ b/include/eepp/graphics/text.hpp @@ -4,7 +4,7 @@ #include #include #include -#include +#include #include #include diff --git a/include/eepp/graphics/textlayout.hpp b/include/eepp/graphics/textlayout.hpp new file mode 100644 index 000000000..d7043a8bc --- /dev/null +++ b/include/eepp/graphics/textlayout.hpp @@ -0,0 +1,44 @@ +#pragma once + +#include +#include +#include + +#include +#include +#include + +namespace EE::Graphics { + +class Font; + +class EE_API TextLayout { + public: + using Cache = std::shared_ptr; + + std::vector shapedGlyphs; + std::vector linesWidth; + Sizef size; + TextDirection direction{ TextDirection::Unspecified }; + + bool isRTL() const { return direction == TextDirection::RightToLeft; } + + static Cache layout( const String& string, Font* font, const Uint32& fontSize, + const Uint32& style, const Uint32& tabWidth = 4, + const Float& outlineThickness = 0.f, std::optional tabOffset = {}, + Uint32 textDrawHints = 0 ); + + static Cache layout( const String::View& string, Font* font, const Uint32& fontSize, + const Uint32& style, const Uint32& tabWidth = 4, + const Float& outlineThickness = 0.f, std::optional tabOffset = {}, + Uint32 textDrawHints = 0 ); + + protected: + template + static Cache layout( const StringType& string, Font* font, const Uint32& fontSize, + const Uint32& style, const Uint32& tabWidth = 4, + const Float& outlineThickness = 0.f, std::optional tabOffset = {}, + Uint32 textDrawHints = 0 ); +}; + +} // namespace EE::Graphics diff --git a/include/eepp/graphics/textlayouter.hpp b/include/eepp/graphics/textlayouter.hpp deleted file mode 100644 index 31e584f07..000000000 --- a/include/eepp/graphics/textlayouter.hpp +++ /dev/null @@ -1,41 +0,0 @@ -#pragma once - -#include -#include -#include - -#include -#include - -namespace EE::Graphics { - -class Font; - -struct TextLayout { - std::vector shapedGlyphs; - std::vector linesWidth; - Sizef size; - bool isRTL{ false }; -}; - -class EE_API TextLayouter { - public: - static TextLayout layout( const String& string, Font* font, const Uint32& fontSize, - const Uint32& style, const Uint32& tabWidth = 4, - const Float& outlineThickness = 0.f, - std::optional tabOffset = {}, Uint32 textDrawHints = 0 ); - - static TextLayout layout( const String::View& string, Font* font, const Uint32& fontSize, - const Uint32& style, const Uint32& tabWidth = 4, - const Float& outlineThickness = 0.f, - std::optional tabOffset = {}, Uint32 textDrawHints = 0 ); - - protected: - template - static TextLayout layout( const StringType& string, Font* font, const Uint32& fontSize, - const Uint32& style, const Uint32& tabWidth = 4, - const Float& outlineThickness = 0.f, - std::optional tabOffset = {}, Uint32 textDrawHints = 0 ); -}; - -} // namespace EE::Graphics diff --git a/include/eepp/network/http.hpp b/include/eepp/network/http.hpp index e449c0eda..7f1356de5 100644 --- a/include/eepp/network/http.hpp +++ b/include/eepp/network/http.hpp @@ -174,7 +174,7 @@ class EE_API Http : NonCopyable { ///< target resource. Patch, ///< The PATCH method is used to apply partial modifications to a resource. Connect ///< The CONNECT method starts two-way communications with the requested - ///< resource. It can be used to open a tunnel. + ///< resource. It can be used to open a tunnel. }; /** @brief Enumerate the available states for a request */ @@ -315,9 +315,22 @@ class EE_API Http : NonCopyable { /** Sets a progress callback */ void setProgressCallback( const ProgressCallback& progressCallback ); + /** Definition of the cancel callback + * @param http The http client + * @param request The http request + */ + typedef std::function + CancelCallback; + + /** Sets a cancel callback */ + void setCancelCallback( const CancelCallback& cancelCb ); + /** Get the progress callback */ const ProgressCallback& getProgressCallback() const; + /** Get the cancel callback */ + const CancelCallback& getCancelCallback() const; + /** Cancels the current request if being processed */ void cancel(); @@ -375,6 +388,7 @@ class EE_API Http : NonCopyable { mutable bool mCancel; ///< Cancel state of current request bool mVerbose{ false }; ///< Enable/Disable verbosity ProgressCallback mProgressCallback; ///< Progress callback + CancelCallback mCancelCallback; ///< Cancel callback unsigned int mMaxRedirections; ///< Maximum number of redirections allowed mutable unsigned int mRedirectionCount; ///< Number of redirections followed by the request URI mProxy; ///< Proxy information @@ -477,7 +491,7 @@ class EE_API Http : NonCopyable { /** @brief Sends the request and creates a new thread, when got the response informs the result ** to the callback. * This function does not lock the caller thread. ** @see sendRequest - ** @return Unique Id of the request added */ + ** @return Unique Id of the request added */ Uint64 sendAsyncRequest( const AsyncResponseCallback& cb, const Http::Request& request, Time timeout = Time::Zero ); @@ -759,6 +773,8 @@ class EE_API Http : NonCopyable { void removeAsyncRequest( AsyncRequest* req ); Request prepareFields( const Http::Request& request ); + + void onCancel( const Http::Request& request ); }; }} // namespace EE::Network diff --git a/src/eepp/graphics/fontmanager.cpp b/src/eepp/graphics/fontmanager.cpp index 0d5a070a2..e29e718a1 100644 --- a/src/eepp/graphics/fontmanager.cpp +++ b/src/eepp/graphics/fontmanager.cpp @@ -9,9 +9,9 @@ FontManager::FontManager() {} FontManager::~FontManager() {} -Graphics::Font* FontManager::add( Graphics::Font* Font ) { - eeASSERT( NULL != Font ); - return ResourceManager::add( Font ); +Graphics::Font* FontManager::add( Graphics::Font* font ) { + eeASSERT( NULL != font ); + return ResourceManager::add( font ); } void FontManager::setColorEmojiFont( Font* font ) { @@ -88,4 +88,13 @@ void FontManager::setAntialiasing( FontAntialiasing antialiasing ) { } } +Font* FontManager::getByInternalId( Uint32 internalId ) const { + for ( auto [_, font] : mResources ) { + if ( font->getType() == FontType::TTF && + static_cast( font )->getFontInternalId() == internalId ) + return font; + } + return nullptr; +} + }} // namespace EE::Graphics diff --git a/src/eepp/graphics/fonttruetype.cpp b/src/eepp/graphics/fonttruetype.cpp index 1caea9324..dd63d8b73 100644 --- a/src/eepp/graphics/fonttruetype.cpp +++ b/src/eepp/graphics/fonttruetype.cpp @@ -1207,21 +1207,8 @@ Glyph FontTrueType::loadGlyphByIndex( Uint32 index, unsigned int characterSize, Uint8* current = pixelPtr; Uint8* end = current + bufferSize; - if ( bitmap.pixel_mode == FT_PIXEL_MODE_LCD ) { - while ( current != end ) { - ( *current++ ) = 0; - ( *current++ ) = 0; - ( *current++ ) = 0; - ( *current++ ) = 0; - } - } else { - while ( current != end ) { - ( *current++ ) = 255; - ( *current++ ) = 255; - ( *current++ ) = 255; - ( *current++ ) = 0; - } - } + std::fill( (Uint32*)pixelPtr, (Uint32*)end, + bitmap.pixel_mode == FT_PIXEL_MODE_LCD ? 0x00000000 : 0x00FFFFFF ); // Extract the glyph's pixels from the bitmap const Uint8* pixels = bitmap.buffer; diff --git a/src/eepp/graphics/text.cpp b/src/eepp/graphics/text.cpp index de7bbf48f..2b8a56b96 100644 --- a/src/eepp/graphics/text.cpp +++ b/src/eepp/graphics/text.cpp @@ -302,10 +302,10 @@ Sizef Text::draw( const StringType& string, const Vector2f& pos, Font* font, Flo !canSkipShaping( textDrawHints ) ) { FontTrueType* rFont = static_cast( font ); - auto layout = TextLayouter::layout( string, rFont, fontSize, style, tabWidth, + auto layout = TextLayout::layout( string, rFont, fontSize, style, tabWidth, outlineThickness, tabOffset, textDrawHints ); - for ( const ShapedGlyph& sg : layout.shapedGlyphs ) { + for ( const ShapedGlyph& sg : layout->shapedGlyphs ) { auto ch = string[sg.stringIndex]; auto gpos( ( sg.position + pos ).trunc() ); @@ -373,7 +373,7 @@ Sizef Text::draw( const StringType& string, const Vector2f& pos, Font* font, Flo BR->drawOpt(); - return layout.size; + return layout->size; } #endif @@ -911,9 +911,9 @@ Float Text::getTextWidth( Font* font, const Uint32& fontSize, const StringType& #ifdef EE_TEXT_SHAPER_ENABLED if ( TextShaperEnabled && font->getType() == FontType::TTF && !canSkipShaping( textDrawHints ) ) { - return TextLayouter::layout( string, static_cast( font ), fontSize, style, + return TextLayout::layout( string, static_cast( font ), fontSize, style, tabWidth, outlineThickness, tabOffset, textDrawHints ) - .size.getWidth(); + ->size.getWidth(); } #endif @@ -956,10 +956,10 @@ std::size_t Text::findLastCharPosWithinLength( Font* font, const Uint32& fontSiz #ifdef EE_TEXT_SHAPER_ENABLED if ( TextShaperEnabled && font->getType() == FontType::TTF && !canSkipShaping( textHints ) ) { auto layout = - TextLayouter::layout( string, static_cast( font ), fontSize, style, + TextLayout::layout( string, static_cast( font ), fontSize, style, tabWidth, outlineThickness, tabOffset /* , textDrawHints */ ); size_t lastStringIndex = 0; - for ( const ShapedGlyph& sg : layout.shapedGlyphs ) { + for ( const ShapedGlyph& sg : layout->shapedGlyphs ) { Glyph metrics = sg.font->getGlyphByIndex( sg.glyphIndex, fontSize, bold, italic, outlineThickness ); if ( sg.position.x + metrics.advance > maxWidth ) @@ -1014,7 +1014,7 @@ Vector2f Text::findCharacterPos( std::size_t index, Font* font, const Uint32& fo #ifdef EE_TEXT_SHAPER_ENABLED if ( TextShaperEnabled && font->getType() == FontType::TTF && !canSkipShaping( textDrawHints ) ) { - auto layout = TextLayouter::layout( string, font, fontSize, style, tabWidth, + auto layout = TextLayout::layout( string, font, fontSize, style, tabWidth, outlineThickness, tabOffset ); Uint32 maxStringIndex = 0; Uint32 closestDist = std::numeric_limits::max(); @@ -1022,7 +1022,7 @@ Vector2f Text::findCharacterPos( std::size_t index, Font* font, const Uint32& fo const ShapedGlyph* msg = nullptr; const ShapedGlyph* csg = nullptr; - for ( const ShapedGlyph& sg : layout.shapedGlyphs ) { + for ( const ShapedGlyph& sg : layout->shapedGlyphs ) { if ( sg.stringIndex >= maxStringIndex ) { maxStringIndex = std::max( maxStringIndex, sg.stringIndex ); msg = &sg; @@ -1035,13 +1035,14 @@ Vector2f Text::findCharacterPos( std::size_t index, Font* font, const Uint32& fo } if ( sg.stringIndex == index ) { - if ( layout.isRTL ) + if ( layout->isRTL() ) return ( sg.position + sg.advance ).trunc(); return sg.position.trunc(); } } - if ( !layout.shapedGlyphs.empty() && !layout.isRTL && index >= maxStringIndex + 1 && msg ) { + if ( !layout->shapedGlyphs.empty() && !layout->isRTL() && index >= maxStringIndex + 1 && + msg ) { Glyph metrics = msg->font->getGlyphByIndex( msg->glyphIndex, fontSize, bold, italic, outlineThickness ); if ( string[msg->stringIndex] == '\t' ) { @@ -1128,18 +1129,18 @@ Int32 Text::findCharacterFromPos( const Vector2i& pos, bool returnNearest, Font* #ifdef EE_TEXT_SHAPER_ENABLED if ( TextShaperEnabled && font->getType() == FontType::TTF && !canSkipShaping( textDrawHints ) ) { - auto layout = TextLayouter::layout( string, font, fontSize, style, tabWidth, + auto layout = TextLayout::layout( string, font, fontSize, style, tabWidth, outlineThickness, tabOffset ); - auto sgs = layout.shapedGlyphs.size(); + auto sgs = layout->shapedGlyphs.size(); if ( sgs == 0 ) return 0; if ( pos.x < 0 ) - return layout.isRTL ? tSize : 0; + return layout->isRTL() ? tSize : 0; for ( size_t i = 0; i < sgs; i++ ) { - const ShapedGlyph* sg = &layout.shapedGlyphs[i]; + const ShapedGlyph* sg = &layout->shapedGlyphs[i]; Float charLeft = sg->position.x; Float charTop = sg->position.y; @@ -1148,31 +1149,31 @@ Int32 Text::findCharacterFromPos( const Vector2i& pos, bool returnNearest, Font* auto curStringIndex = sg->stringIndex; // Expand bounds over the whole cluster (multiple glyphs for one string index) - while ( i + 1 < sgs && layout.shapedGlyphs[i + 1].stringIndex == curStringIndex ) { + while ( i + 1 < sgs && layout->shapedGlyphs[i + 1].stringIndex == curStringIndex ) { i++; - sg = &layout.shapedGlyphs[i]; + sg = &layout->shapedGlyphs[i]; charBottom = sg->position.y + vspace; charRight = sg->position.x + sg->advance.x; }; bool isLastOnLine = i + 1 == sgs || - ( !layout.isRTL && layout.shapedGlyphs[i + 1].position.y != sg->position.y ); + ( !layout->isRTL() && layout->shapedGlyphs[i + 1].position.y != sg->position.y ); if ( fpos.y >= charTop && fpos.y <= charBottom ) { auto findNextInsertionIndex = [&]() -> Int32 { - if ( layout.isRTL ) { + if ( layout->isRTL() ) { if ( i > 0 ) { for ( Int64 j = (Int64)i - 1; j >= 0; j-- ) { - if ( layout.shapedGlyphs[j].stringIndex > sg->stringIndex ) - return layout.shapedGlyphs[j].stringIndex; + if ( layout->shapedGlyphs[j].stringIndex > sg->stringIndex ) + return layout->shapedGlyphs[j].stringIndex; } } return 0; } else { for ( size_t j = i + 1; j < sgs; ++j ) { - if ( layout.shapedGlyphs[j].stringIndex > sg->stringIndex ) - return layout.shapedGlyphs[j].stringIndex; + if ( layout->shapedGlyphs[j].stringIndex > sg->stringIndex ) + return layout->shapedGlyphs[j].stringIndex; } return tSize; } @@ -1310,11 +1311,11 @@ void Text::updateWidthCache() { #ifdef EE_TEXT_SHAPER_ENABLED if ( TextShaperEnabled && mFontStyleConfig.Font->getType() == FontType::TTF && !canSkipShaping( mTextHints ) ) { - auto layout = TextLayouter::layout( mString, mFontStyleConfig.Font, + auto layout = TextLayout::layout( mString, mFontStyleConfig.Font, mFontStyleConfig.CharacterSize, mFontStyleConfig.Style, mTabWidth, mFontStyleConfig.OutlineThickness ); - mLinesWidth = std::move( layout.linesWidth ); - mCachedWidth = layout.size.getWidth(); + mLinesWidth = layout->linesWidth; + mCachedWidth = layout->size.getWidth(); return; } #endif @@ -1627,14 +1628,14 @@ void Text::ensureGeometryUpdate() { if ( TextShaperEnabled && mFontStyleConfig.Font->getType() == FontType::TTF && !canSkipShaping( mTextHints ) ) { FontTrueType* rFont = static_cast( mFontStyleConfig.Font ); - auto layout = TextLayouter::layout( mString, rFont, mFontStyleConfig.CharacterSize, + auto layout = TextLayout::layout( mString, rFont, mFontStyleConfig.CharacterSize, mFontStyleConfig.Style, mTabWidth, mFontStyleConfig.OutlineThickness ); - mLinesWidth = std::move( layout.linesWidth ); - mCachedWidth = layout.size.getWidth(); + mLinesWidth = layout->linesWidth; + mCachedWidth = layout->size.getWidth(); - for ( const ShapedGlyph& sg : layout.shapedGlyphs ) { + for ( const ShapedGlyph& sg : layout->shapedGlyphs ) { if ( mString[sg.stringIndex] == '\t' ) continue; diff --git a/src/eepp/graphics/textlayouter.cpp b/src/eepp/graphics/textlayout.cpp similarity index 87% rename from src/eepp/graphics/textlayouter.cpp rename to src/eepp/graphics/textlayout.cpp index 51e53f630..a2fd51d65 100644 --- a/src/eepp/graphics/textlayouter.cpp +++ b/src/eepp/graphics/textlayout.cpp @@ -1,7 +1,7 @@ #include #include #include -#include +#include #include #ifdef EE_TEXT_SHAPER_ENABLED @@ -13,7 +13,7 @@ namespace EE::Graphics { -using LRULayoutCache = LRUCache<2048, Uint64, TextLayout>; +using LRULayoutCache = LRUCache<2048, Uint64, TextLayout::Cache>; #ifdef EE_TEXT_SHAPER_ENABLED @@ -110,8 +110,10 @@ static void segmentString( TextLayout& result, String::View input, Callable cb ) SBScriptLocatorReset( scriptLocator ); } - if ( runCount > 0 && paragraphOffset == 0 ) - result.isRTL = ( runArray[0].level % 2 ) != 0; + if ( runCount > 0 && paragraphOffset == 0 ) { + result.direction = ( runArray[0].level % 2 ) != 0 ? TextDirection::RightToLeft + : TextDirection::LeftToRight; + } SBLineRelease( line ); SBParagraphRelease( paragraph ); @@ -205,16 +207,16 @@ static inline Uint64 textLayoutHash( const StringType& string, Font* font, } template -TextLayout TextLayouter::layout( const StringType& string, Font* font, const Uint32& characterSize, - const Uint32& style, const Uint32& tabWidth, - const Float& outlineThickness, std::optional tabOffset, - Uint32 textDrawHints ) { +TextLayout::Cache TextLayout::layout( const StringType& string, Font* font, + const Uint32& characterSize, const Uint32& style, + const Uint32& tabWidth, const Float& outlineThickness, + std::optional tabOffset, Uint32 textDrawHints ) { static LRULayoutCache sLayoutCache; - TextLayout result; if ( !font || string.empty() ) { - result.size = { 0.f, font ? (Float)font->getFontHeight( characterSize ) : 0.f }; - return result; + auto layout = std::make_shared(); + layout->size = { 0.f, font ? (Float)font->getFontHeight( characterSize ) : 0.f }; + return layout; } Uint64 hash = 0; @@ -235,6 +237,9 @@ TextLayout TextLayouter::layout( const StringType& string, Font* font, const Uin Vector2f pen; Float maxWidth = 0; + auto resultPtr = std::make_shared(); + TextLayout& result = *resultPtr; + #ifdef EE_TEXT_SHAPER_ENABLED if ( Text::TextShaperEnabled && font->getType() == FontType::TTF && !Text::canSkipShaping( textDrawHints ) ) { @@ -269,6 +274,7 @@ TextLayout TextLayouter::layout( const StringType& string, Font* font, const Uin sg.stringIndex = segment.offset + run.pos() + cluster; sg.position = pen; sg.advance = { advance, 0 }; + sg.direction = (TextDirection)segment.direction; result.shapedGlyphs.emplace_back( std::move( sg ) ); pen.x += advance; @@ -290,6 +296,7 @@ TextLayout TextLayouter::layout( const StringType& string, Font* font, const Uin sg.glyphIndex = glyphInfo[i].codepoint; sg.stringIndex = segment.offset + run.pos() + glyphInfo[i].cluster; sg.advance = { currentGlyph.advance, 0 }; + sg.direction = (TextDirection)segment.direction; sg.position.x = pen.x + ( glyphPos[i].x_offset / 64.f ); sg.position.y = pen.y - ( glyphPos[i].y_offset / 64.f ); result.shapedGlyphs.emplace_back( std::move( sg ) ); @@ -311,6 +318,7 @@ TextLayout TextLayouter::layout( const StringType& string, Font* font, const Uin sg.glyphIndex = glyphInfo[i].codepoint; sg.stringIndex = segment.offset + run.pos() + cluster; sg.advance = { advance, 0 }; + sg.direction = (TextDirection)segment.direction; sg.position = pen; result.shapedGlyphs.emplace_back( std::move( sg ) ); @@ -324,6 +332,7 @@ TextLayout TextLayouter::layout( const StringType& string, Font* font, const Uin sg.glyphIndex = glyphInfo[i].codepoint; sg.stringIndex = segment.offset + run.pos() + glyphInfo[i].cluster; sg.advance = { glyphPos[i].x_advance / 64.f, glyphPos[i].y_advance / 64.f }; + sg.direction = (TextDirection)segment.direction; sg.position.x = std::round( pen.x + ( glyphPos[i].x_offset / 64.f ) ); sg.position.y = std::round( pen.y - ( glyphPos[i].y_offset / 64.f ) ); result.shapedGlyphs.emplace_back( std::move( sg ) ); @@ -378,6 +387,7 @@ TextLayout TextLayouter::layout( const StringType& string, Font* font, const Uin : std::optional{} ) : std::optional{} ), 0 }; + sg.direction = TextDirection::LeftToRight; sg.position = pen; pen.x += sg.advance.x; result.shapedGlyphs.emplace_back( std::move( sg ) ); @@ -391,6 +401,7 @@ TextLayout TextLayouter::layout( const StringType& string, Font* font, const Uin sg.advance = { font->getGlyph( curChar, characterSize, bold, italic, outlineThickness ).advance, 0 }; + sg.direction = TextDirection::LeftToRight; sg.position = pen; pen.x += sg.advance.x; result.shapedGlyphs.emplace_back( std::move( sg ) ); @@ -405,24 +416,24 @@ TextLayout TextLayouter::layout( const StringType& string, Font* font, const Uin maxWidth = eemax( maxWidth, result.linesWidth[result.linesWidth.size() - 1] ); result.size = { maxWidth, std::ceil( pen.y ) }; - sLayoutCache.put( hash, result ); - return result; + sLayoutCache.put( hash, resultPtr ); + return resultPtr; } -TextLayout TextLayouter::layout( const String& string, Font* font, const Uint32& fontSize, - const Uint32& style, const Uint32& tabWidth, - const Float& outlineThickness, std::optional tabOffset, - Uint32 textDrawHints ) { - return TextLayouter::layout( string, font, fontSize, style, tabWidth, outlineThickness, - tabOffset, textDrawHints ); +TextLayout::Cache TextLayout::layout( const String& string, Font* font, const Uint32& fontSize, + const Uint32& style, const Uint32& tabWidth, + const Float& outlineThickness, std::optional tabOffset, + Uint32 textDrawHints ) { + return TextLayout::layout( string, font, fontSize, style, tabWidth, outlineThickness, + tabOffset, textDrawHints ); } -TextLayout TextLayouter::layout( const String::View& string, Font* font, const Uint32& fontSize, - const Uint32& style, const Uint32& tabWidth, - const Float& outlineThickness, std::optional tabOffset, - Uint32 textDrawHints ) { - return TextLayouter::layout( string, font, fontSize, style, tabWidth, - outlineThickness, tabOffset, textDrawHints ); +TextLayout::Cache TextLayout::layout( const String::View& string, Font* font, + const Uint32& fontSize, const Uint32& style, + const Uint32& tabWidth, const Float& outlineThickness, + std::optional tabOffset, Uint32 textDrawHints ) { + return TextLayout::layout( string, font, fontSize, style, tabWidth, + outlineThickness, tabOffset, textDrawHints ); } } // namespace EE::Graphics diff --git a/src/eepp/network/http.cpp b/src/eepp/network/http.cpp index 31a0515d7..0533742e5 100644 --- a/src/eepp/network/http.cpp +++ b/src/eepp/network/http.cpp @@ -173,10 +173,18 @@ void Http::Request::setProgressCallback( const Http::Request::ProgressCallback& mProgressCallback = progressCallback; } +void Http::Request::setCancelCallback( const Http::Request::CancelCallback& cancelCallback ) { + mCancelCallback = cancelCallback; +} + const Http::Request::ProgressCallback& Http::Request::getProgressCallback() const { return mProgressCallback; } +const Http::Request::CancelCallback& Http::Request::getCancelCallback() const { + return mCancelCallback; +} + void Http::Request::cancel() { mCancel = true; setProgressCallback( {} ); @@ -208,6 +216,11 @@ std::string Http::Request::prepareTunnel( const Http& http ) { return out.str(); } +void Http::onCancel( const Http::Request& request ) { + if ( request.getCancelCallback() ) + request.getCancelCallback()( *this, request ); +} + bool Http::Request::isVerbose() const { return mVerbose; } @@ -795,8 +808,10 @@ Http::Response Http::downloadRequest( const Http::Request& request, IOStream& wr // Prepare the response Response received; - if ( request.isCancelled() ) + if ( request.isCancelled() ) { + onCancel( request ); return Response(); + } // If not connected, try to connect to the server if ( mConnection && !mConnection->isConnected() ) { @@ -1128,6 +1143,9 @@ Http::Response Http::downloadRequest( const Http::Request& request, IOStream& wr } } + if ( request.isCancelled() ) + onCancel( request ); + // Close the connection if ( mConnection && !mConnection->isKeepAlive() ) { mConnection->disconnect(); diff --git a/src/tests/ui_perf_test/ui_perf_test.cpp b/src/tests/ui_perf_test/ui_perf_test.cpp index be7cf9893..e1e898c7d 100644 --- a/src/tests/ui_perf_test/ui_perf_test.cpp +++ b/src/tests/ui_perf_test/ui_perf_test.cpp @@ -157,7 +157,7 @@ EE_MAIN_FUNC int main( int, char*[] ) { Text::TextShaperOptimizations = false; UIApplication app( WindowSettings( 1024, 650, "eepp - TextEdit", WindowStyle::Default, WindowBackend::Default, 32, {}, 1, false, true ), - UIApplication::Settings( {}, 1.5f ) ); + UIApplication::Settings( {}, 1.33f ) ); FileSystem::changeWorkingDirectory( Sys::getProcessPath() ); auto ll = UILinearLayout::NewVertical(); ll->setLayoutSizePolicy( SizePolicy::MatchParent, SizePolicy::MatchParent ); @@ -168,19 +168,18 @@ EE_MAIN_FUNC int main( int, char*[] ) { editor->setFontSize( PixelDensity::dpToPx( 12 ) ); FontManager::instance()->addFallbackFont( FontTrueType::New( "arabic", "unit_tests/assets/fonts/NotoNaskhArabic-Regular.ttf" ) ); - // FontManager::instance()->addFallbackFont( FontTrueType::New( - // "NotoSerifBengali-Regular", "unit_tests/assets/fonts/NotoSansBengali-Regular.ttf" ) ); - // editor->setLineWrapMode( LineWrapMode::Word ); - editor->setFont( FontManager::instance()->getByName( "monospace" ) ); + FontManager::instance()->addFallbackFont( FontTrueType::New( + "NotoSerifBengali-Regular", "unit_tests/assets/fonts/NotoSansBengali-Regular.ttf" ) ); + editor->setLineWrapMode( LineWrapMode::Word ); + // editor->setFont( FontManager::instance()->getByName( "monospace" ) ); // editor->loadFromFile( "unit_tests/assets/textfiles/test-arabic-simple.uext" ); - editor->loadFromFile( "unit_tests/assets/textfiles/test-arabic.uext" ); + // editor->loadFromFile( "unit_tests/assets/textfiles/test-arabic.uext" ); // editor->loadFromFile( "unit_tests/assets/textfiles/test-bengali.uext" ); // editor->loadFromFile( "unit_tests/assets/textfiles/test-flags.uext" ); // editor->loadFromFile( "unit_tests/assets/textformat/english.utf8.lf.nobom.txt" ); // editor->loadFromFile( "unit_tests/assets/textfiles/test-arabic-mixed.uext" ); - // editor->getDocument().textInput( "اسمي..." ); - // editor->getDocument().textInput( " হ্যাঁ " ); - // editor->getDocument().textInput( "I'm from...: আমি ... থেকে এসেছি।" ); + // editor->loadFromFile( "unit_tests/assets/textfiles/test-mixed-text.uext" ); + editor->loadFromFile( "unit_tests/assets/textfiles/lorem-ipsum.uext" ); editor->setFont( app.getUI()->getUIThemeManager()->getDefaultFont() ); editor->on( Event::KeyUp, [&]( const Event* event ) { diff --git a/src/tools/ecode/plugins/aiassistant/chatui.cpp b/src/tools/ecode/plugins/aiassistant/chatui.cpp index 22f127ec5..595608c50 100644 --- a/src/tools/ecode/plugins/aiassistant/chatui.cpp +++ b/src/tools/ecode/plugins/aiassistant/chatui.cpp @@ -301,8 +301,11 @@ LLMChatUI::LLMChatUI( PluginManager* manager ) : setCmd( "ai-prompt", [this] { // "ai-prompt-stop" if ( mRequest ) { - mRequest->cancel(); - return; + if ( !mRequest->isCancelled() ) { + mRequest->cancel(); + return; + } else + mRequest.reset(); } auto chats = findAllByClass( "llm_conversation" ); @@ -1021,10 +1024,14 @@ void LLMChatUI::doRequest() { mChatStop->setVisible( true )->setEnabled( true ); UIWidget* chat = addChatUI( LLMChat::Role::Assistant ); - toggleEnableChats( false ); + toggleEnableChats( false ); // editor is not disabled (to allow copy with locked document) auto model = mCurModel; auto* editor = chat->findByClass( "data_ui" ); + + // new editor must be disabled because even on locked document it's possible to move the cursor + editor->setEnabled( false ); + auto* thinking = editor->findByClass( "thinking" ); auto thinkingID = String::hash( String::format( "thinking-%p", thinking ) ); thinking->setVisible( true ); @@ -1052,9 +1059,15 @@ void LLMChatUI::doRequest() { } ); }; - mRequest->cancelCb = [this, thinking, thinkingID]( const LLMChatCompletionRequest& ) { + mRequest->cancelCb = [this, thinking, thinkingID, editor]( const LLMChatCompletionRequest& ) { thinking->removeActionsByTag( thinkingID ); thinking->setVisible( false ); + mChatStop->setVisible( false )->setEnabled( false ); + mChatRun->setVisible( true )->setEnabled( true ); + toggleEnableChats( true ); + editor->setEnabled( true ); + if ( editor->hasFocus() ) + mChatInput->setFocus(); removeLastChat(); }; @@ -1086,6 +1099,7 @@ void LLMChatUI::doRequest() { } mRequest.reset(); toggleEnableChats( true ); + editor->setEnabled( true ); mChatStop->setVisible( false )->setEnabled( false ); mChatRun->setVisible( true )->setEnabled( true ); diff --git a/src/tools/ecode/plugins/aiassistant/llmchatcompletionrequest.cpp b/src/tools/ecode/plugins/aiassistant/llmchatcompletionrequest.cpp index e174f82d6..f4cc5d638 100644 --- a/src/tools/ecode/plugins/aiassistant/llmchatcompletionrequest.cpp +++ b/src/tools/ecode/plugins/aiassistant/llmchatcompletionrequest.cpp @@ -19,13 +19,14 @@ LLMChatCompletionRequest::LLMChatCompletionRequest( const std::string& uri, cons mRequest.setBody( reqBody ); mRequest.setFollowRedirect( true ); mRequest.setMethod( Http::Request::Method::Post ); + mRequest.setCancelCallback( [this]( const Http&, const Http::Request& ) { onCancel(); } ); mRequest.setProgressCallback( [this]( const Http&, const Http::Request&, const Http::Response&, const Http::Request::Status& status, size_t, size_t ) { if ( mCancel ) { - if ( cancelCb ) - cancelCb( *this ); + onCancel(); return false; } + mHadProgress = true; if ( status != Http::Request::ContentReceived ) return true; std::string chunk = @@ -106,12 +107,18 @@ LLMChatCompletionRequest::~LLMChatCompletionRequest() { } void LLMChatCompletionRequest::request() { + mCancel = false; + mCancelled = false; + mHadProgress = false; Http::Response res = mHttp->downloadRequest( mRequest, mStream, Seconds( 5 ) ); if ( doneCb ) doneCb( *this, res ); } void LLMChatCompletionRequest::requestAsync() { + mCancel = false; + mCancelled = false; + mHadProgress = false; mRequestId = mHttp->downloadAsyncRequest( [this]( const Http&, Http::Request&, Http::Response& res ) { if ( doneCb ) @@ -124,10 +131,22 @@ void LLMChatCompletionRequest::cancel() { mCancel = true; if ( mRequestId ) mHttp->setCancelRequest( mRequestId ); + if ( !mHadProgress ) + onCancel(); } const std::string& LLMChatCompletionRequest::getStream() const { return mStream.getStream(); } +bool LLMChatCompletionRequest::isCancelled() const { + return mCancelled; +} + +void LLMChatCompletionRequest::onCancel() { + if ( !mCancelled && cancelCb ) + cancelCb( *this ); + mCancelled = true; +} + } // namespace ecode diff --git a/src/tools/ecode/plugins/aiassistant/llmchatcompletionrequest.hpp b/src/tools/ecode/plugins/aiassistant/llmchatcompletionrequest.hpp index cc182ce9a..ae36e17e3 100644 --- a/src/tools/ecode/plugins/aiassistant/llmchatcompletionrequest.hpp +++ b/src/tools/ecode/plugins/aiassistant/llmchatcompletionrequest.hpp @@ -38,6 +38,8 @@ class LLMChatCompletionRequest { void cancel(); + bool isCancelled() const; + const std::string& getStream() const; const std::string& getResponse() const { return mResponse; } @@ -53,9 +55,13 @@ class LLMChatCompletionRequest { std::string mResponse; size_t mReadBytes{ 0 }; bool mCancel{ false }; + bool mCancelled{ false }; bool mFirstMessage{ true }; bool mReasoning{ false }; + bool mHadProgress{ false }; Uint64 mRequestId{ 0 }; + + void onCancel(); }; } // namespace ecode