mirror of
https://github.com/SpartanJ/eepp.git
synced 2026-05-28 17:16:29 +03:00
Several fixes in soft-wrap implementation.
Added soft-wrap support in UITextView and UITooltip. Added more soft-wrap tests, now testing also selection.
This commit is contained in:
Binary file not shown.
|
After Width: | Height: | Size: 8.8 KiB |
@@ -368,6 +368,11 @@ class EE_API Text {
|
||||
/** Finds the visual line index that contains the given character index. */
|
||||
size_t findVisualLineFromCharIndex( size_t charIndex );
|
||||
|
||||
/** @return A list of rectangles that cover the selection of the string, each rectangle
|
||||
* has the line spacing height and covers the width of the selection.
|
||||
*/
|
||||
std::vector<Rectf> getSelectionRects( size_t selectionStartIndex, size_t selectionEndIndex );
|
||||
|
||||
protected:
|
||||
struct VertexCoords {
|
||||
Vector2f texCoords;
|
||||
@@ -383,7 +388,7 @@ class EE_API Text {
|
||||
mutable bool mColorsNeedUpdate : 1 { false };
|
||||
mutable bool mContainsColorEmoji : 1 { false };
|
||||
mutable bool mVisualLinesNeedUpdate : 1 { true };
|
||||
mutable bool mCachedWidthNeedUpdate: 1 { true };
|
||||
mutable bool mCachedWidthNeedUpdate : 1 { true };
|
||||
bool mTabStops : 1 { false };
|
||||
bool mLineWrapKeepIndentation : 1 { false };
|
||||
|
||||
|
||||
@@ -124,6 +124,10 @@ class EE_API UITextView : public UIWidget {
|
||||
|
||||
bool isWordWrap() const;
|
||||
|
||||
std::pair<int, int> getSelection() const;
|
||||
|
||||
void setSelection( std::pair<int, int> sel );
|
||||
|
||||
protected:
|
||||
Text* mTextCache;
|
||||
String mString;
|
||||
@@ -132,13 +136,7 @@ class EE_API UITextView : public UIWidget {
|
||||
Int32 mSelCurInit;
|
||||
Int32 mSelCurEnd;
|
||||
Uint32 mTextDrawHints{ 0 };
|
||||
struct SelPosCache {
|
||||
SelPosCache( Vector2f ip, Vector2f ep ) : initPos( ip ), endPos( ep ) {}
|
||||
|
||||
Vector2f initPos;
|
||||
Vector2f endPos;
|
||||
};
|
||||
std::vector<SelPosCache> mSelPosCache;
|
||||
std::vector<Rectf> mSelRectsCache;
|
||||
Int32 mLastSelCurInit;
|
||||
Int32 mLastSelCurEnd;
|
||||
bool mSelecting;
|
||||
|
||||
@@ -74,7 +74,7 @@ LineWrap::computeLineBreaksInternal( const String::View& string, Font* font, Uin
|
||||
LineWrapType info;
|
||||
info.wraps.push_back( 0 );
|
||||
|
||||
if ( string.empty() || nullptr == font || mode == LineWrapMode::NoWrap || maxWidth == 0 ) {
|
||||
if ( string.empty() || nullptr == font ) {
|
||||
if constexpr ( std::is_same_v<LineWrapType, LineWrapInfoEx> ) {
|
||||
info.wrapsWidth.push_back( 0 );
|
||||
}
|
||||
@@ -143,6 +143,7 @@ LineWrap::computeLineBreaksInternal( const String::View& string, Font* font, Uin
|
||||
size_t lastSpace = 0;
|
||||
Uint32 prevChar = 0;
|
||||
size_t idx = 0;
|
||||
bool hasWrap = maxWidth > 0 && mode != LineWrapMode::NoWrap;
|
||||
|
||||
for ( const auto& curChar : string ) {
|
||||
if ( curChar == '\n' ) {
|
||||
@@ -175,7 +176,7 @@ LineWrap::computeLineBreaksInternal( const String::View& string, Font* font, Uin
|
||||
|
||||
xoffset += w;
|
||||
|
||||
if ( xoffset > maxWidth ) {
|
||||
if ( hasWrap && xoffset > maxWidth ) {
|
||||
if ( mode == LineWrapMode::Word && lastSpace ) {
|
||||
if constexpr ( std::is_same_v<LineWrapType, LineWrapInfoEx> ) {
|
||||
info.wrapsWidth.push_back( std::ceil( lastWordWrapWidth ) );
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
#include <eepp/graphics/renderer/opengl.hpp>
|
||||
#include <eepp/graphics/renderer/renderer.hpp>
|
||||
#include <eepp/graphics/text.hpp>
|
||||
#include <eepp/graphics/textlayout.hpp>
|
||||
#include <eepp/graphics/texture.hpp>
|
||||
#include <eepp/graphics/texturefactory.hpp>
|
||||
#include <limits>
|
||||
@@ -1938,21 +1939,11 @@ void Text::ensureGeometryUpdate() {
|
||||
}
|
||||
}
|
||||
|
||||
// Helper lambda to check if index starts a soft-wrapped line (not a real newline)
|
||||
auto isSoftWrapLineStart = [this, &useSoftWrap, ¤tVisualLine]( Int64 idx ) -> bool {
|
||||
if ( !useSoftWrap || currentVisualLine + 1 >= mVisualLines.size() )
|
||||
return false;
|
||||
// Check if this is the start of the next visual line
|
||||
if ( idx == mVisualLines[currentVisualLine + 1] ) {
|
||||
// It's a soft wrap if the previous char wasn't a newline
|
||||
if ( idx > 0 && mString[idx - 1] != '\n' ) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
return !( !useSoftWrap || currentVisualLine + 1 >= mVisualLines.size() ) &&
|
||||
idx == mVisualLines[currentVisualLine + 1] && idx > 0 && mString[idx] != '\n';
|
||||
};
|
||||
|
||||
// Helper to update alignment for current visual line
|
||||
auto updateAlignmentForLine = [this, ¢erDiffX, &line]() {
|
||||
switch ( Font::getHorizontalAlign( mAlign ) ) {
|
||||
case TEXT_ALIGN_CENTER:
|
||||
@@ -2179,6 +2170,8 @@ void Text::ensureGeometryUpdate() {
|
||||
// For soft wrap, the width cache was already handled by ensureVisualLinesUpdate
|
||||
if ( useSoftWrap && mCachedWidthNeedUpdate ) {
|
||||
mCachedWidthNeedUpdate = false;
|
||||
if ( !mLinesWidth.empty() )
|
||||
mCachedWidth = *std::max_element( mLinesWidth.begin(), mLinesWidth.end() );
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2785,4 +2778,72 @@ size_t Text::findVisualLineFromCharIndex( size_t charIndex ) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
std::vector<Rectf> Text::getSelectionRects( size_t selectionStartIndex, size_t selectionEndIndex ) {
|
||||
std::vector<Rectf> rects;
|
||||
|
||||
if ( selectionStartIndex == selectionEndIndex || !mFontStyleConfig.Font )
|
||||
return rects;
|
||||
|
||||
if ( selectionStartIndex > selectionEndIndex )
|
||||
std::swap( selectionStartIndex, selectionEndIndex );
|
||||
|
||||
ensureVisualLinesUpdate();
|
||||
cacheWidth();
|
||||
|
||||
size_t startLine = findVisualLineFromCharIndex( selectionStartIndex );
|
||||
size_t endLine = findVisualLineFromCharIndex( selectionEndIndex );
|
||||
Float hspace =
|
||||
mFontStyleConfig.Font
|
||||
->getGlyph( ' ', mFontStyleConfig.CharacterSize, mFontStyleConfig.Style & Text::Bold,
|
||||
mFontStyleConfig.Style & Text::Italic )
|
||||
.advance;
|
||||
Float vspace = static_cast<Float>(
|
||||
mFontStyleConfig.Font->getLineSpacing( mFontStyleConfig.CharacterSize ) );
|
||||
|
||||
for ( size_t i = startLine; i <= endLine; ++i ) {
|
||||
Float top = i * vspace;
|
||||
Float bottom = top + vspace;
|
||||
Float left = 0;
|
||||
Float right = 0;
|
||||
Float centerDiffX = 0;
|
||||
|
||||
if ( i < mLinesWidth.size() ) {
|
||||
switch ( Font::getHorizontalAlign( mAlign ) ) {
|
||||
case TEXT_ALIGN_CENTER:
|
||||
centerDiffX = std::trunc( ( mCachedWidth - mLinesWidth[i] ) * 0.5f );
|
||||
break;
|
||||
case TEXT_ALIGN_RIGHT:
|
||||
centerDiffX = mCachedWidth - mLinesWidth[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Calculate Left
|
||||
if ( i == startLine ) {
|
||||
left = findCharacterPos( selectionStartIndex ).x;
|
||||
} else {
|
||||
left = centerDiffX;
|
||||
}
|
||||
|
||||
// Calculate Right
|
||||
if ( i == endLine ) {
|
||||
// If it's a newline character, we select a small chunk to indicate the newline
|
||||
// selection
|
||||
if ( selectionEndIndex < mString.size() && mString[selectionEndIndex] == '\n' ) {
|
||||
right = findCharacterPos( selectionEndIndex ).x + hspace;
|
||||
} else {
|
||||
right = findCharacterPos( selectionEndIndex ).x;
|
||||
}
|
||||
} else {
|
||||
right = centerDiffX + ( i < mLinesWidth.size() ? mLinesWidth[i] : 0 );
|
||||
}
|
||||
|
||||
if ( left != right ) {
|
||||
rects.push_back( Rectf( left, top, right, bottom ) );
|
||||
}
|
||||
}
|
||||
|
||||
return rects;
|
||||
}
|
||||
|
||||
}} // namespace EE::Graphics
|
||||
|
||||
@@ -545,6 +545,8 @@ void TextLayout::wrapLayout( const String::View& string, TextLayout& result,
|
||||
const Float& outlineThickness, Float hspace ) {
|
||||
std::size_t paragraphCount = result.paragraphs.size();
|
||||
|
||||
result.size = Sizef::Zero;
|
||||
|
||||
for ( std::size_t paragraphIdx = 0; paragraphIdx < paragraphCount; paragraphIdx++ ) {
|
||||
ShapedTextParagraph& sp = result.paragraphs[paragraphIdx];
|
||||
std::size_t shapedGlyphCount = sp.shapedGlyphs.size();
|
||||
@@ -614,6 +616,9 @@ void TextLayout::wrapLayout( const String::View& string, TextLayout& result,
|
||||
}
|
||||
|
||||
if ( sp.wrapInfo.wrapsWidth.empty() ) {
|
||||
if ( shapedGlyphCount )
|
||||
maxSize = sp.shapedGlyphs.back().position + sp.shapedGlyphs.back().advance;
|
||||
|
||||
// Restore the original wraps which are the paragraph wraps (no wrapping occurred)
|
||||
sp.wrapInfo.wrapsWidth = std::move( wrapsWidth );
|
||||
} else if ( !sp.shapedGlyphs.empty() ) {
|
||||
@@ -622,9 +627,10 @@ void TextLayout::wrapLayout( const String::View& string, TextLayout& result,
|
||||
}
|
||||
|
||||
sp.size = maxSize;
|
||||
}
|
||||
|
||||
result.size = result.paragraphs[result.paragraphs.size() - 1].size;
|
||||
result.size = { std::max( sp.size.x, result.size.x ),
|
||||
std::max( sp.size.y, result.size.y ) };
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace EE::Graphics
|
||||
|
||||
@@ -272,7 +272,8 @@ TextRange DocumentView::getVisibleIndexRange( VisibleIndex visibleIndex ) const
|
||||
Int64 idx = static_cast<Int64>( visibleIndex );
|
||||
auto start = getVisibleIndexPosition( visibleIndex );
|
||||
auto end = start;
|
||||
if ( idx + 1 < static_cast<Int64>( mVisibleLines.size() ) &&
|
||||
eeASSERT( visibleIndex >= static_cast<VisibleIndex>( 0 ) );
|
||||
if ( idx >= 0 && idx + 1 < static_cast<Int64>( mVisibleLines.size() ) &&
|
||||
mVisibleLines[idx + 1].line() == start.line() ) {
|
||||
end.setColumn( mVisibleLines[idx + 1].column() );
|
||||
} else {
|
||||
|
||||
@@ -4701,7 +4701,7 @@ String UICodeEditor::checkMouseOverLink( const Vector2i& position, bool checkMod
|
||||
if ( !mInteractiveLinks || ( checkModifiers && !getInput()->isKeyModPressed() ) )
|
||||
return resetLinkOver( position );
|
||||
|
||||
TextPosition pos( resolveScreenPosition( position.asFloat(), false ) );
|
||||
TextPosition pos( resolveScreenPosition( position.asFloat() ) );
|
||||
if ( pos.line() >= (Int64)mDoc->linesCount() )
|
||||
return resetLinkOver( position );
|
||||
|
||||
|
||||
@@ -323,6 +323,9 @@ UITextView* UITextView::setSelectionBackColor( const Color& color ) {
|
||||
}
|
||||
|
||||
void UITextView::autoWrap() {
|
||||
mTextCache->setLineWrapMode( mFlags & UI_WORD_WRAP ? LineWrapMode::Word
|
||||
: LineWrapMode::NoWrap );
|
||||
|
||||
if ( mFlags & UI_WORD_WRAP ) {
|
||||
wrapText( mSize.getWidth() - mPaddingPx.Left - mPaddingPx.Right );
|
||||
}
|
||||
@@ -333,7 +336,8 @@ void UITextView::wrapText( const Uint32& maxWidth ) {
|
||||
mTextCache->setString( mString );
|
||||
}
|
||||
|
||||
mTextCache->hardWrapText( maxWidth );
|
||||
mTextCache->setLineWrapMode( LineWrapMode::Word );
|
||||
mTextCache->setMaxWrapWidth( maxWidth );
|
||||
invalidateDraw();
|
||||
}
|
||||
|
||||
@@ -564,46 +568,26 @@ void UITextView::drawSelection( Text* textCache ) {
|
||||
return;
|
||||
}
|
||||
|
||||
Int32 lastEnd;
|
||||
Vector2f initPos, endPos;
|
||||
|
||||
if ( mLastSelCurInit != selCurInit() || mLastSelCurEnd != selCurEnd() ) {
|
||||
mSelPosCache.clear();
|
||||
mSelRectsCache.clear();
|
||||
mLastSelCurInit = selCurInit();
|
||||
mLastSelCurEnd = selCurEnd();
|
||||
|
||||
do {
|
||||
initPos = textCache->findCharacterPos( init );
|
||||
lastEnd = textCache->getString().find_first_of( '\n', init );
|
||||
|
||||
if ( lastEnd < end && -1 != lastEnd ) {
|
||||
endPos = textCache->findCharacterPos( lastEnd );
|
||||
init = lastEnd + 1;
|
||||
} else {
|
||||
endPos = textCache->findCharacterPos( end );
|
||||
lastEnd = end;
|
||||
}
|
||||
|
||||
mSelPosCache.push_back( SelPosCache( initPos, endPos ) );
|
||||
} while ( end != lastEnd );
|
||||
mSelRectsCache = mTextCache->getSelectionRects( selCurInit(), selCurEnd() );
|
||||
}
|
||||
|
||||
if ( !mSelPosCache.empty() ) {
|
||||
if ( !mSelRectsCache.empty() ) {
|
||||
Vector2f initPos, endPos;
|
||||
Primitives P;
|
||||
P.setColor( mFontStyleConfig.FontSelectionBackColor );
|
||||
Float vspace = textCache->getFont()->getLineSpacing( mTextCache->getCharacterSize() );
|
||||
Float height = mSize.y - mPaddingPx.Top - mPaddingPx.Bottom;
|
||||
Float offsetY = eefloor( ( height - mTextCache->getTextHeight() ) * 0.5f );
|
||||
for ( size_t i = 0; i < mSelRectsCache.size(); i++ ) {
|
||||
initPos = mSelRectsCache[i].getPosition();
|
||||
endPos = mSelRectsCache[i].getPosition() + mSelRectsCache[i].getSize();
|
||||
|
||||
for ( size_t i = 0; i < mSelPosCache.size(); i++ ) {
|
||||
initPos = mSelPosCache[i].initPos;
|
||||
endPos = mSelPosCache[i].endPos;
|
||||
|
||||
P.drawRectangle(
|
||||
Rectf( mScreenPos.x + initPos.x + mRealAlignOffset.x + mPaddingPx.Left,
|
||||
mScreenPos.y + initPos.y + offsetY + mPaddingPx.Top,
|
||||
mScreenPos.x + endPos.x + mRealAlignOffset.x + mPaddingPx.Left,
|
||||
mScreenPos.y + endPos.y + offsetY + mPaddingPx.Top + vspace ) );
|
||||
P.drawRectangle( Rectf(
|
||||
mScreenPos.x + initPos.x + mRealAlignOffset.x + mPaddingPx.Left,
|
||||
mScreenPos.y + initPos.y + mRealAlignOffset.y + mPaddingPx.Top,
|
||||
mScreenPos.x + endPos.x + mRealAlignOffset.x + mPaddingPx.Left,
|
||||
mScreenPos.y + endPos.y + mRealAlignOffset.y + mPaddingPx.Top ) );
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -637,6 +621,15 @@ void UITextView::setFontStyleConfig( const UIFontStyleConfig& fontStyleConfig )
|
||||
onFontStyleChanged();
|
||||
}
|
||||
|
||||
std::pair<int, int> UITextView::getSelection() const {
|
||||
return { mSelCurInit, mSelCurEnd };
|
||||
}
|
||||
|
||||
void UITextView::setSelection( std::pair<int, int> sel ) {
|
||||
selCurInit( std::clamp( sel.first, 0, (Int32)mString.size() ) );
|
||||
selCurEnd( std::clamp( sel.second, 0, (Int32)mString.size() ) );
|
||||
}
|
||||
|
||||
void UITextView::selCurInit( const Int32& init ) {
|
||||
if ( mSelCurInit != init ) {
|
||||
mSelCurInit = init;
|
||||
|
||||
@@ -159,7 +159,7 @@ void UITooltip::draw() {
|
||||
UINode::draw();
|
||||
|
||||
if ( mTextCache->getTextWidth() ) {
|
||||
mTextCache->setAlign( getFlags() );
|
||||
mTextCache->setAlign( getHorizontalAlign() | getVerticalAlign() );
|
||||
mTextCache->draw( std::trunc( mScreenPos.x ) + (int)mAlignOffset.x,
|
||||
std::trunc( mScreenPos.y ) + (int)mAlignOffset.y, Vector2f::One, 0.f,
|
||||
getBlendMode() );
|
||||
@@ -615,6 +615,9 @@ void UITooltip::onAlphaChange() {
|
||||
}
|
||||
|
||||
void UITooltip::autoWrap() {
|
||||
mTextCache->setLineWrapMode( mFlags & UI_WORD_WRAP ? LineWrapMode::Word
|
||||
: LineWrapMode::NoWrap );
|
||||
|
||||
if ( mFlags & UI_WORD_WRAP && !mMaxWidthEq.empty() ) {
|
||||
Float length =
|
||||
lengthFromValue( mMaxWidthEq, CSS::PropertyRelativeTarget::ContainingBlockWidth );
|
||||
@@ -627,7 +630,8 @@ void UITooltip::wrapText( const Uint32& maxWidth ) {
|
||||
mTextCache->setString( mStringBuffer );
|
||||
}
|
||||
|
||||
mTextCache->hardWrapText( maxWidth );
|
||||
mTextCache->setLineWrapMode( LineWrapMode::Word );
|
||||
mTextCache->setMaxWrapWidth( maxWidth );
|
||||
invalidateDraw();
|
||||
}
|
||||
|
||||
|
||||
@@ -161,8 +161,11 @@ EE_MAIN_FUNC int main( int, char*[] ) {
|
||||
FileSystem::changeWorkingDirectory( Sys::getProcessPath() );
|
||||
auto ll = UILinearLayout::NewVertical();
|
||||
ll->setLayoutSizePolicy( SizePolicy::MatchParent, SizePolicy::MatchParent );
|
||||
auto editor = UITextView::New();
|
||||
/*
|
||||
auto editor = UITextEdit::New();
|
||||
editor->setShowLineNumber( false );
|
||||
*/
|
||||
editor->setLayoutSizePolicy( SizePolicy::MatchParent, SizePolicy::MatchParent );
|
||||
editor->setParent( ll );
|
||||
editor->setFontSize( PixelDensity::dpToPx( 12 ) );
|
||||
@@ -170,7 +173,8 @@ EE_MAIN_FUNC int main( int, char*[] ) {
|
||||
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->setWordWrap( true );
|
||||
// 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" );
|
||||
@@ -179,7 +183,11 @@ EE_MAIN_FUNC int main( int, char*[] ) {
|
||||
// editor->loadFromFile( "unit_tests/assets/textformat/english.utf8.lf.nobom.txt" );
|
||||
// editor->loadFromFile( "unit_tests/assets/textfiles/test-arabic-mixed.uext" );
|
||||
// editor->loadFromFile( "unit_tests/assets/textfiles/test-mixed-text.uext" );
|
||||
editor->loadFromFile( "unit_tests/assets/textfiles/lorem-ipsum.uext" );
|
||||
// editor->loadFromFile( "unit_tests/assets/textfiles/lorem-ipsum.uext" );
|
||||
std::string buffer;
|
||||
FileSystem::fileGet( "unit_tests/assets/textfiles/lorem-ipsum.uext", buffer );
|
||||
editor->setText( buffer );
|
||||
editor->setTextSelection( true );
|
||||
|
||||
editor->setFont( app.getUI()->getUIThemeManager()->getDefaultFont() );
|
||||
editor->on( Event::KeyUp, [&]( const Event* event ) {
|
||||
|
||||
@@ -1089,6 +1089,46 @@ UTEST( FontRendering, TextHardWrap ) {
|
||||
}
|
||||
}
|
||||
|
||||
UTEST( FontRendering, UITextViewWrappedSelection ) {
|
||||
const auto runTest = [&]() {
|
||||
UIApplication app(
|
||||
WindowSettings( 1024, 650, "eepp - TextView Wrapped Selection", WindowStyle::Default,
|
||||
WindowBackend::Default, 32, {}, 1, false, true ),
|
||||
UIApplication::Settings( Sys::getProcessPath() + ".." + FileSystem::getOSSlash(),
|
||||
1.5f ) );
|
||||
FileSystem::changeWorkingDirectory( Sys::getProcessPath() );
|
||||
std::string buffer;
|
||||
FileSystem::fileGet( "assets/textfiles/lorem-ipsum.uext", buffer );
|
||||
auto textView = UITextView::New();
|
||||
textView->setLayoutSizePolicy( SizePolicy::Fixed, SizePolicy::WrapContent );
|
||||
textView->setPixelsSize( app.getUI()->getPixelsSize() );
|
||||
textView->setText( buffer );
|
||||
textView->setWordWrap( true );
|
||||
textView->setTextSelection( true );
|
||||
textView->setSelection( { 51, 286 } );
|
||||
SceneManager::instance()->update();
|
||||
SceneManager::instance()->draw();
|
||||
compareImages( utest_state, utest_result, app.getWindow(),
|
||||
"eepp-textview-wrapped-selection" );
|
||||
};
|
||||
|
||||
UTEST_PRINT_STEP( "Text Shaper disabled" );
|
||||
{
|
||||
BoolScopedOp op( Text::TextShaperEnabled, false );
|
||||
runTest();
|
||||
}
|
||||
|
||||
UTEST_PRINT_STEP( "Text Shaper enabled" );
|
||||
{
|
||||
BoolScopedOp op( Text::TextShaperEnabled, true );
|
||||
runTest();
|
||||
|
||||
UTEST_PRINT_STEP( "Text Shaper enabled w/o optimizations" );
|
||||
BoolScopedOp op2( Text::TextShaperOptimizations, false );
|
||||
runTest();
|
||||
}
|
||||
}
|
||||
|
||||
UTEST( FontRendering, TextSoftWrapPos ) {
|
||||
const auto runTest = [&]() {
|
||||
UIApplication app(
|
||||
@@ -1109,16 +1149,16 @@ UTEST( FontRendering, TextSoftWrapPos ) {
|
||||
text.setMaxWrapWidth( 200.f );
|
||||
|
||||
Vector2f pos = text.findCharacterPos( 30 );
|
||||
EXPECT_GT( pos.y, 0 );
|
||||
EXPECT_GT( pos.y, 0.f );
|
||||
|
||||
Float vspace = text.getFont()->getLineSpacing( text.getCharacterSize() );
|
||||
Vector2i queryPos( 10, (int)vspace + 5 );
|
||||
Int32 foundIndex = text.findCharacterFromPos( queryPos );
|
||||
|
||||
EXPECT_GT( foundIndex, 14 );
|
||||
EXPECT_GT( foundIndex, (Int32)14 );
|
||||
|
||||
Vector2f foundPos = text.findCharacterPos( foundIndex );
|
||||
EXPECT_GT( foundPos.y, 0 );
|
||||
EXPECT_GT( foundPos.y, 0.f );
|
||||
};
|
||||
|
||||
UTEST_PRINT_STEP( "Text Shaper disabled" );
|
||||
@@ -1137,3 +1177,82 @@ UTEST( FontRendering, TextSoftWrapPos ) {
|
||||
runTest();
|
||||
}
|
||||
}
|
||||
|
||||
UTEST( FontRendering, TextSelection ) {
|
||||
auto win = Engine::instance()->createWindow(
|
||||
WindowSettings( 1024, 650, "eepp - Text Selection", WindowStyle::Default,
|
||||
WindowBackend::Default, 32, {}, 1, false, true ) );
|
||||
ASSERT_TRUE_MSG( win->isOpen(), "Failed to create Window" );
|
||||
FileSystem::changeWorkingDirectory( Sys::getProcessPath() );
|
||||
|
||||
Text::TextShaperEnabled = false;
|
||||
|
||||
FontTrueType* font = FontTrueType::New( "NotoSans-Regular" );
|
||||
bool loaded = font->loadFromFile( "../assets/fonts/NotoSans-Regular.ttf" );
|
||||
ASSERT_TRUE( loaded );
|
||||
FontFamily::loadFromRegular( font );
|
||||
|
||||
FontStyleConfig config;
|
||||
config.Font = font;
|
||||
config.CharacterSize = 20;
|
||||
config.FontColor = Color::Black;
|
||||
config.Style = Text::Regular;
|
||||
|
||||
String txt( "Line 1\nLine 2 is longer\nLine 3" );
|
||||
|
||||
Text text;
|
||||
text.setStyleConfig( config );
|
||||
text.setString( txt );
|
||||
|
||||
// Test 1: Single line selection (Line 1)
|
||||
{
|
||||
std::vector<Rectf> rects = text.getSelectionRects( 0, 4 ); // "Line"
|
||||
EXPECT_EQ( 1ul, rects.size() );
|
||||
if ( !rects.empty() ) {
|
||||
EXPECT_EQ( 0, rects[0].Top );
|
||||
EXPECT_GT( rects[0].getWidth(), 0 );
|
||||
EXPECT_EQ( text.findCharacterPos( 0 ).x, rects[0].Left );
|
||||
EXPECT_EQ( text.findCharacterPos( 4 ).x, rects[0].Right );
|
||||
}
|
||||
}
|
||||
|
||||
// Test 2: Multi-line selection (Line 1 to Line 2)
|
||||
{
|
||||
// "Line 1\nLine 2" -> Indices: "Line 1" (0-5), "\n" (6), "Line 2" (7-12)
|
||||
// Select from index 2 ("n" in "Line 1") to index 9 ("i" in "Line 2")
|
||||
std::vector<Rectf> rects = text.getSelectionRects( 2, 9 );
|
||||
EXPECT_EQ( 2ul, rects.size() );
|
||||
if ( rects.size() >= 2 ) {
|
||||
// First line rect: From index 2 to end of line 1
|
||||
EXPECT_EQ( text.findCharacterPos( 2 ).x, rects[0].Left );
|
||||
EXPECT_GT( rects[0].Right, rects[0].Left );
|
||||
|
||||
// Second line rect: From start of line 2 to index 9
|
||||
EXPECT_EQ( 0, rects[1].Left ); // Left aligned
|
||||
EXPECT_EQ( text.findCharacterPos( 9 ).x, rects[1].Right );
|
||||
}
|
||||
}
|
||||
|
||||
// Test 3: Full selection
|
||||
{
|
||||
std::vector<Rectf> rects = text.getSelectionRects( 0, txt.size() );
|
||||
EXPECT_EQ( 3ul, rects.size() );
|
||||
}
|
||||
|
||||
// Test 4: Soft wrap
|
||||
{
|
||||
text.setLineWrapMode( LineWrapMode::Word );
|
||||
text.setMaxWrapWidth( 50 ); // Force wrap
|
||||
|
||||
text.setString( "This is a very long string that should wrap multiple times." );
|
||||
// Ensure layout is updated
|
||||
text.getVisualLineCount();
|
||||
|
||||
EXPECT_GT( text.getVisualLineCount(), (Uint32)1 );
|
||||
|
||||
std::vector<Rectf> rects = text.getSelectionRects( 0, text.getString().size() );
|
||||
EXPECT_EQ( (size_t)text.getVisualLineCount(), rects.size() );
|
||||
}
|
||||
|
||||
Engine::destroySingleton();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user