diff --git a/src/fheroes2/dialog/dialog_selectcount.cpp b/src/fheroes2/dialog/dialog_selectcount.cpp index b74ad9f2f2a..604c253e7df 100644 --- a/src/fheroes2/dialog/dialog_selectcount.cpp +++ b/src/fheroes2/dialog/dialog_selectcount.cpp @@ -210,8 +210,9 @@ bool Dialog::inputString( const fheroes2::TextBase & title, const fheroes2::Text bool isCursorVisible = true; const fheroes2::FontType fontType( fheroes2::FontType::normalWhite() ); fheroes2::Text text( insertCharToString( result, charInsertPos, isCursorVisible ? '_' : '\x7F' ), fontType, textLanguage ); + text.keepLineTrailingSpaces(); if ( !isMultiLine ) { - text.fitToOneRow( textInputArea.width, false ); + text.fitToOneRow( textInputArea.width ); } text.drawInRoi( textInputArea.x, textInputArea.y + 2, textInputArea.width, display, textInputArea ); @@ -343,7 +344,7 @@ bool Dialog::inputString( const fheroes2::TextBase & title, const fheroes2::Text text.set( insertCharToString( result, charInsertPos, isCursorVisible ? '_' : '\x7F' ), fontType, textLanguage ); if ( !isMultiLine ) { - text.fitToOneRow( textInputArea.width, false ); + text.fitToOneRow( textInputArea.width ); } textBackground.restore(); diff --git a/src/fheroes2/dialog/dialog_selectfile.cpp b/src/fheroes2/dialog/dialog_selectfile.cpp index 221e98a807b..79f7594a23b 100644 --- a/src/fheroes2/dialog/dialog_selectfile.cpp +++ b/src/fheroes2/dialog/dialog_selectfile.cpp @@ -112,7 +112,8 @@ namespace fheroes2::Text currentFilename( filename, isEditing ? fheroes2::FontType::normalWhite() : fheroes2::FontType::normalYellow() ); // Do not ignore spaces at the end. - currentFilename.fitToOneRow( maxFileNameWidth, false ); + currentFilename.keepLineTrailingSpaces(); + currentFilename.fitToOneRow( maxFileNameWidth ); currentFilename.draw( field.x + 4 + ( maxFileNameWidth - currentFilename.width() ) / 2, field.y + 4, display ); } @@ -198,7 +199,8 @@ namespace dsty += 2; fheroes2::Text text{ std::move( savname ), font }; - text.fitToOneRow( maxFileNameWidth, false ); + text.keepLineTrailingSpaces(); + text.fitToOneRow( maxFileNameWidth ); text.draw( dstx + 4 + ( maxFileNameWidth - text.width() ) / 2, dsty, display ); redrawDateTime( display, info.timestamp, dstx + maxFileNameWidth + 9, dsty, font ); diff --git a/src/fheroes2/gui/ui_text.cpp b/src/fheroes2/gui/ui_text.cpp index f20258749f1..6c6241d79a2 100644 --- a/src/fheroes2/gui/ui_text.cpp +++ b/src/fheroes2/gui/ui_text.cpp @@ -22,6 +22,7 @@ #include #include +#include #include #include "agg_image.h" @@ -45,70 +46,25 @@ namespace return ( character == ' ' ); } - struct TextLineInfo - { - TextLineInfo() = default; - - TextLineInfo( const int32_t offsetX_, const int32_t offsetY_, const int32_t lineWidth_, const int32_t count ) - : offsetX( offsetX_ ) - , offsetY( offsetY_ ) - , lineWidth( lineWidth_ ) - , characterCount( count ) - { - // Do nothing. - } - - int32_t offsetX{ 0 }; - int32_t offsetY{ 0 }; - int32_t lineWidth{ 0 }; - int32_t characterCount{ 0 }; - }; - - int32_t getLineWidth( const uint8_t * data, const int32_t size, const fheroes2::FontCharHandler & charHandler ) + int32_t getLineWidth( const uint8_t * data, const int32_t size, const fheroes2::FontCharHandler & charHandler, const bool keepTrailingSpaces ) { assert( data != nullptr && size > 0 ); int32_t width = 0; - const uint8_t * dataEnd = data + size; - while ( data != dataEnd ) { - width += charHandler.getWidth( *data ); - - ++data; - } - - return width; - } - - int32_t getMaxCharacterCount( const uint8_t * data, const int32_t size, const fheroes2::FontCharHandler & charHandler, const int32_t maxWidth ) - { - assert( data != nullptr && size > 0 && maxWidth > 0 ); - - int32_t width = 0; - for ( int32_t characterCount = 0; characterCount < size; ++characterCount, ++data ) { - width += charHandler.getWidth( *data ); - - if ( width > maxWidth ) { - return characterCount; + if ( keepTrailingSpaces ) { + for ( ; data != dataEnd; ++data ) { + width += charHandler.getWidth( *data ); } - } - - return size; - } - // Ignore spaces at the end of the line. This function must be used only at the time of final rendering. - int32_t getTruncatedLineWidth( const uint8_t * data, const int32_t size, const fheroes2::FontCharHandler & charHandler ) - { - assert( data != nullptr && size > 0 ); + return width; + } - int32_t width = 0; int32_t spaceWidth = 0; - const int32_t spaceCharWidth = charHandler.getSpaceCharWidth(); - const uint8_t * dataEnd = data + size; - while ( data != dataEnd ) { + for ( ; data != dataEnd; ++data ) { if ( isSpaceChar( *data ) ) { spaceWidth += spaceCharWidth; } @@ -117,122 +73,26 @@ namespace spaceWidth = 0; } - - ++data; } return width; } - // Returns text lines parameters (in pixels) in 'offsets': x - horizontal line shift, y - vertical line shift. - // And in 'characterCount' - the number of characters on the line, in 'lineWidth' the width including the `offsetX` value. - void getTextLineInfos( const uint8_t * data, const int32_t size, const int32_t maxWidth, const int32_t firstLineOffsetX, const fheroes2::FontType fontType, - const int32_t rowHeight, std::vector & textLineInfos ) + int32_t getMaxCharacterCount( const uint8_t * data, const int32_t size, const fheroes2::FontCharHandler & charHandler, const int32_t maxWidth ) { - assert( data != nullptr && size > 0 ); - - int32_t lineWidth = firstLineOffsetX; - int32_t offsetY = textLineInfos.empty() ? 0 : textLineInfos.back().offsetY; - - const fheroes2::FontCharHandler charHandler( fontType ); - - if ( maxWidth < 1 ) { - // The text will be displayed in a single line. - - lineWidth += getLineWidth( data, size, charHandler ); - textLineInfos.emplace_back( firstLineOffsetX, offsetY, lineWidth, size ); - - return; - } - - int32_t offsetX = firstLineOffsetX; - int32_t lineCharCount = 0; - int32_t lastWordCharCount = 0; - - const uint8_t * dataEnd = data + size; - - while ( data != dataEnd ) { - if ( isLineSeparator( *data ) ) { - textLineInfos.emplace_back( offsetX, offsetY, lineWidth, lineCharCount + 1 ); - - offsetX = 0; - offsetY += rowHeight; - lineCharCount = 0; - lastWordCharCount = 0; - lineWidth = 0; - - ++data; - } - else { - // This is another character in the line. Get its width. - - const int32_t charWidth = charHandler.getWidth( *data ); - - if ( lineWidth + charWidth > maxWidth ) { - // Current character has exceeded the maximum line width. - - if ( isSpaceChar( *data ) ) { - // Current character could be a space character then current line is over. - // For the characters count we take this space into the account. - ++lineCharCount; - - // Skip this space character. - ++data; - } - else if ( lineCharCount == lastWordCharCount ) { - // This is the only word in the line. - // Search for '-' symbol to avoid truncating the word in the middle. - const uint8_t * hyphenPos = data - lineCharCount; - for ( ; hyphenPos != data; ++hyphenPos ) { - if ( *hyphenPos == hyphenChar ) { - break; - } - } - - if ( hyphenPos != data ) { - // The '-' symbol has been found. In this case we consider everything after it as a separate word. - lineCharCount -= static_cast( data - hyphenPos ) - 1; - lineWidth = getLineWidth( data - lineCharCount, lineCharCount, charHandler ); - - data = hyphenPos; - ++data; - } - else if ( firstLineOffsetX > 0 && ( textLineInfos.empty() || textLineInfos.back().offsetY == offsetY ) ) { - // This word was not the first in the line so we can move it to the next line. - // It can happen in the case of the multi-font text. - data -= lastWordCharCount; - - lineCharCount = 0; - lineWidth = firstLineOffsetX; - } - } - else if ( lastWordCharCount > 0 ) { - // Exclude last word from this line. - data -= lastWordCharCount; - - lineCharCount -= lastWordCharCount; - lineWidth -= getLineWidth( data, lastWordCharCount, charHandler ); - } + assert( data != nullptr && size > 0 && maxWidth > 0 ); - textLineInfos.emplace_back( offsetX, offsetY, lineWidth, lineCharCount ); + int32_t width = 0; - offsetX = 0; - offsetY += rowHeight; - lineCharCount = 0; - lastWordCharCount = 0; - lineWidth = 0; - } - else { - lastWordCharCount = isSpaceChar( *data ) ? 0 : ( lastWordCharCount + 1 ); + for ( int32_t characterCount = 0; characterCount < size; ++characterCount, ++data ) { + width += charHandler.getWidth( *data ); - ++data; - ++lineCharCount; - lineWidth += charWidth; - } + if ( width > maxWidth ) { + return characterCount; } } - textLineInfos.emplace_back( offsetX, offsetY, lineWidth, lineCharCount ); + return size; } int32_t renderSingleLine( const uint8_t * data, const int32_t size, const int32_t x, const int32_t y, fheroes2::Image & output, const fheroes2::Rect & imageRoi, @@ -274,22 +134,6 @@ namespace return offsetX; } - void renderCenterAlignedLine( const uint8_t * data, const int32_t size, const int32_t x, const int32_t y, const int32_t maxWidth, fheroes2::Image & output, - const fheroes2::Rect & imageRoi, const fheroes2::FontType fontType ) - { - const fheroes2::FontCharHandler charHandler( fontType ); - - const int32_t correctedLineWidth = getTruncatedLineWidth( data, size, charHandler ); - - assert( correctedLineWidth <= maxWidth ); - // For button font single letters in a row we add 1 extra pixel to the width to more properly center odd-width letters. - const int32_t extraOffsetX - = ( size == 1 && ( maxWidth % 2 == 0 ) && ( fontType.size == fheroes2::FontSize::BUTTON_RELEASED || fontType.size == fheroes2::FontSize::BUTTON_PRESSED ) ) - ? 1 - : 0; - renderSingleLine( data, size, x + ( maxWidth - correctedLineWidth + extraOffsetX ) / 2, y, output, imageRoi, charHandler ); - } - int32_t getMaxWordWidth( const uint8_t * data, const int32_t size, const fheroes2::FontType fontType ) { assert( data != nullptr && size > 0 ); @@ -367,7 +211,7 @@ namespace fheroes2 const auto langugeSwitcher = getLanguageSwitcher( *this ); const fheroes2::FontCharHandler charHandler( _fontType ); - return getLineWidth( reinterpret_cast( _text.data() ), static_cast( _text.size() ), charHandler ); + return getLineWidth( reinterpret_cast( _text.data() ), static_cast( _text.size() ), charHandler, _keepLineTrailingSpaces ); } // TODO: Properly handle strings with many text lines ('\n'). Now their heights are counted as if they're one line. @@ -387,7 +231,7 @@ namespace fheroes2 const int32_t fontHeight = height(); std::vector lineInfos; - getTextLineInfos( reinterpret_cast( _text.data() ), static_cast( _text.size() ), maxWidth, 0, _fontType, fontHeight, lineInfos ); + getTextLineInfos( lineInfos, maxWidth, fontHeight, false ); if ( lineInfos.size() == 1 ) { // This is a single-line message. @@ -407,8 +251,7 @@ namespace fheroes2 while ( startWidth + 1 < endWidth ) { const int32_t currentWidth = ( endWidth + startWidth ) / 2; std::vector tempLineInfos; - getTextLineInfos( reinterpret_cast( _text.data() ), static_cast( _text.size() ), currentWidth, 0, _fontType, fontHeight, - tempLineInfos ); + getTextLineInfos( tempLineInfos, currentWidth, fontHeight, false ); if ( tempLineInfos.size() > lineInfos.size() ) { startWidth = currentWidth; @@ -431,7 +274,7 @@ namespace fheroes2 const int32_t fontHeight = height(); std::vector lineInfos; - getTextLineInfos( reinterpret_cast( _text.data() ), static_cast( _text.size() ), maxWidth, 0, _fontType, fontHeight, lineInfos ); + getTextLineInfos( lineInfos, maxWidth, fontHeight, false ); return lineInfos.back().offsetY + fontHeight; } @@ -444,7 +287,7 @@ namespace fheroes2 const auto langugeSwitcher = getLanguageSwitcher( *this ); std::vector lineInfos; - getTextLineInfos( reinterpret_cast( _text.data() ), static_cast( _text.size() ), maxWidth, 0, _fontType, height(), lineInfos ); + getTextLineInfos( lineInfos, maxWidth, height(), false ); return static_cast( lineInfos.size() ); } @@ -475,21 +318,27 @@ namespace fheroes2 } const auto langugeSwitcher = getLanguageSwitcher( *this ); - const uint8_t * data = reinterpret_cast( _text.data() ); std::vector lineInfos; - getTextLineInfos( data, static_cast( _text.size() ), maxWidth, 0, _fontType, height(), lineInfos ); + getTextLineInfos( lineInfos, maxWidth, height(), false ); + + const uint8_t * data = reinterpret_cast( _text.data() ); + const fheroes2::FontCharHandler charHandler( _fontType ); for ( const TextLineInfo & info : lineInfos ) { if ( info.characterCount > 0 ) { - renderCenterAlignedLine( data, info.characterCount, x + info.offsetX, y + info.offsetY, maxWidth, output, imageRoi, _fontType ); + // Center the text line when rendering multi-line texts. + // TODO: Implement text alignment setting to allow multi-line left aligned text for editor's warning messages. + const int32_t offsetX = info.offsetX + ( maxWidth - info.lineWidth ) / 2; + + renderSingleLine( data, info.characterCount, x + offsetX, y + info.offsetY, output, imageRoi, charHandler ); } data += info.characterCount; } } - void Text::fitToOneRow( const int32_t maxWidth, const bool ignoreSpacesAtTextEnd /* = true */ ) + void Text::fitToOneRow( const int32_t maxWidth ) { assert( maxWidth > 0 ); // Why is the limit less than 1? if ( maxWidth <= 0 ) { @@ -505,8 +354,7 @@ namespace fheroes2 const fheroes2::FontCharHandler charHandler( _fontType ); const int32_t originalTextWidth - = ignoreSpacesAtTextEnd ? getTruncatedLineWidth( reinterpret_cast( _text.data() ), static_cast( _text.size() ), charHandler ) - : getLineWidth( reinterpret_cast( _text.data() ), static_cast( _text.size() ), charHandler ); + = getLineWidth( reinterpret_cast( _text.data() ), static_cast( _text.size() ), charHandler, _keepLineTrailingSpaces ); if ( originalTextWidth <= maxWidth ) { // Nothing to do. The text is not longer than the provided maximum width. return; @@ -514,7 +362,7 @@ namespace fheroes2 const std::string truncatedEnding( "..." ); const int32_t truncationSymbolWidth - = getLineWidth( reinterpret_cast( truncatedEnding.data() ), static_cast( truncatedEnding.size() ), charHandler ); + = getLineWidth( reinterpret_cast( truncatedEnding.data() ), static_cast( truncatedEnding.size() ), charHandler, true ); const int32_t maxCharacterCount = getMaxCharacterCount( reinterpret_cast( _text.data() ), static_cast( _text.size() ), charHandler, maxWidth - truncationSymbolWidth ); @@ -523,6 +371,142 @@ namespace fheroes2 _text += truncatedEnding; } + void Text::getTextLineInfos( std::vector & textLineInfos, const int32_t maxWidth, const int32_t rowHeight, const bool keepTextTrailingSpaces ) const + { + assert( !_text.empty() ); + + const uint8_t * data = reinterpret_cast( _text.data() ); + + const int32_t firstLineOffsetX = textLineInfos.empty() ? 0 : textLineInfos.back().lineWidth; + int32_t lineWidth = firstLineOffsetX; + int32_t offsetY = textLineInfos.empty() ? 0 : textLineInfos.back().offsetY; + + const fheroes2::FontCharHandler charHandler( _fontType ); + + if ( maxWidth < 1 ) { + // The text will be displayed in a single line. + + const int32_t size = static_cast( _text.size() ); + lineWidth += getLineWidth( data, size, charHandler, _keepLineTrailingSpaces || keepTextTrailingSpaces ); + + textLineInfos.emplace_back( firstLineOffsetX, offsetY, lineWidth, size ); + return; + } + + int32_t offsetX = firstLineOffsetX; + int32_t lineCharCount = 0; + int32_t lastWordCharCount = 0; + int32_t spaceCharCount = 0; + + const uint8_t * dataEnd = data + _text.size(); + + while ( data != dataEnd ) { + if ( isLineSeparator( *data ) ) { + if ( !_keepLineTrailingSpaces ) { + lineWidth -= spaceCharCount * charHandler.getSpaceCharWidth(); + } + + textLineInfos.emplace_back( offsetX, offsetY, lineWidth, lineCharCount + 1 ); + + spaceCharCount = 0; + offsetX = 0; + offsetY += rowHeight; + lineCharCount = 0; + lastWordCharCount = 0; + lineWidth = 0; + + ++data; + } + else { + // This is another character in the line. Get its width. + + const int32_t charWidth = charHandler.getWidth( *data ); + + if ( lineWidth + charWidth > maxWidth ) { + // Current character has exceeded the maximum line width. + + if ( !_keepLineTrailingSpaces && isSpaceChar( *data ) ) { + // Current character could be a space character then current line is over. + // For the characters count we take this space into the account. + ++lineCharCount; + + // Skip this space character. + ++data; + } + else if ( lineCharCount == lastWordCharCount ) { + // This is the only word in the line. + // Search for '-' symbol to avoid truncating the word in the middle. + const uint8_t * hyphenPos = data - lineCharCount; + for ( ; hyphenPos != data; ++hyphenPos ) { + if ( *hyphenPos == hyphenChar ) { + break; + } + } + + if ( hyphenPos != data ) { + // The '-' symbol has been found. In this case we consider everything after it as a separate word. + const int32_t postHyphenCharCount = static_cast( data - hyphenPos ) - 1; + + lineCharCount -= postHyphenCharCount; + lineWidth -= getLineWidth( data - postHyphenCharCount, postHyphenCharCount, charHandler, true ); + + data = hyphenPos; + ++data; + } + else if ( firstLineOffsetX > 0 && ( textLineInfos.empty() || textLineInfos.back().offsetY == offsetY ) ) { + // This word was not the first in the line so we can move it to the next line. + // It can happen in the case of the multi-font text. + data -= lastWordCharCount; + + lineCharCount = 0; + lineWidth = firstLineOffsetX; + } + } + else if ( lastWordCharCount > 0 ) { + // Exclude last word from this line. + data -= lastWordCharCount; + + lineCharCount -= lastWordCharCount; + lineWidth -= getLineWidth( data, lastWordCharCount, charHandler, true ); + } + + if ( !_keepLineTrailingSpaces ) { + lineWidth -= spaceCharCount * charHandler.getSpaceCharWidth(); + } + + textLineInfos.emplace_back( offsetX, offsetY, lineWidth, lineCharCount ); + + spaceCharCount = 0; + offsetX = 0; + offsetY += rowHeight; + lineCharCount = 0; + lastWordCharCount = 0; + lineWidth = 0; + } + else { + if ( isSpaceChar( *data ) ) { + lastWordCharCount = 0; + ++spaceCharCount; + } + else { + ++lastWordCharCount; + spaceCharCount = 0; + } + + ++data; + ++lineCharCount; + lineWidth += charWidth; + } + } + } + + if ( !_keepLineTrailingSpaces && !keepTextTrailingSpaces ) { + lineWidth -= spaceCharCount * charHandler.getSpaceCharWidth(); + } + + textLineInfos.emplace_back( offsetX, offsetY, lineWidth, lineCharCount ); + } + MultiFontText::~MultiFontText() = default; void MultiFontText::add( Text text ) @@ -558,11 +542,7 @@ namespace fheroes2 const int32_t maxFontHeight = height(); std::vector lineInfos; - for ( const Text & text : _texts ) { - const auto langugeSwitcher = getLanguageSwitcher( text ); - getTextLineInfos( reinterpret_cast( text._text.data() ), static_cast( text._text.size() ), maxWidth, - lineInfos.empty() ? 0 : lineInfos.back().lineWidth, text._fontType, maxFontHeight, lineInfos ); - } + _getMultiFontTextLineInfos( lineInfos, maxWidth, maxFontHeight ); int32_t maxRowWidth = lineInfos.front().lineWidth; for ( const TextLineInfo & lineInfo : lineInfos ) { @@ -577,11 +557,8 @@ namespace fheroes2 const int32_t maxFontHeight = height(); std::vector lineInfos; - for ( const Text & text : _texts ) { - const auto langugeSwitcher = getLanguageSwitcher( text ); - getTextLineInfos( reinterpret_cast( text._text.data() ), static_cast( text._text.size() ), maxWidth, - lineInfos.empty() ? 0 : lineInfos.back().lineWidth, text._fontType, maxFontHeight, lineInfos ); - } + _getMultiFontTextLineInfos( lineInfos, maxWidth, maxFontHeight ); + return lineInfos.back().offsetY + maxFontHeight; } @@ -594,15 +571,7 @@ namespace fheroes2 const int32_t maxFontHeight = height(); std::vector lineInfos; - for ( const Text & text : _texts ) { - if ( text._text.empty() ) { - continue; - } - - const auto langugeSwitcher = getLanguageSwitcher( text ); - getTextLineInfos( reinterpret_cast( text._text.data() ), static_cast( text._text.size() ), - lineInfos.empty() ? 0 : lineInfos.back().lineWidth, maxWidth, text._fontType, maxFontHeight, lineInfos ); - } + _getMultiFontTextLineInfos( lineInfos, maxWidth, maxFontHeight ); if ( lineInfos.empty() ) { return 0; @@ -641,11 +610,7 @@ namespace fheroes2 const int32_t maxFontHeight = height(); std::vector lineInfos; - for ( const Text & text : _texts ) { - const auto langugeSwitcher = getLanguageSwitcher( text ); - getTextLineInfos( reinterpret_cast( text._text.data() ), static_cast( text._text.size() ), maxWidth, - lineInfos.empty() ? 0 : lineInfos.back().lineWidth, text._fontType, maxFontHeight, lineInfos ); - } + _getMultiFontTextLineInfos( lineInfos, maxWidth, maxFontHeight ); if ( lineInfos.empty() ) { return; @@ -706,6 +671,19 @@ namespace fheroes2 return output; } + void MultiFontText::_getMultiFontTextLineInfos( std::vector & textLineInfos, const int32_t maxWidth, const int32_t rowHeight ) const + { + const size_t textsCount = _texts.size(); + for ( size_t i = 0; i < textsCount; ++i ) { + const auto langugeSwitcher = getLanguageSwitcher( _texts[i] ); + + // To properly render a multi-font text we must not ignore spaces at the end of a text entry which is not the last one. + const bool isNotLastTextEntry = ( i != textsCount - 1 ); + + _texts[i].getTextLineInfos( textLineInfos, maxWidth, rowHeight, isNotLastTextEntry ); + } + } + FontCharHandler::FontCharHandler( const FontType fontType ) : _fontType( fontType ) , _charLimit( AGG::getCharacterLimit( fontType.size ) ) @@ -763,73 +741,6 @@ namespace fheroes2 return 0; } - size_t getTextInputCursorPosition( const Text & text, const size_t currentTextCursorPosition, const Point & pointerCursorOffset, const Rect & textRoi ) - { - // TODO: expose `Text` helper functions used in this method and convert it to a function in 'ui_tools.cpp' - - if ( text.empty() ) { - // The text is empty. - return 0; - } - - const FontType fontType = text.getFontType(); - const int32_t fontHeight = getFontHeight( fontType.size ); - const int32_t pointerLine = ( pointerCursorOffset.y - textRoi.y ) / fontHeight; - - if ( pointerLine < 0 ) { - // Pointer is upper than the first text line. - return 0; - } - - const int32_t textWidth = text.width( textRoi.width ); - const std::string & textString = text.text(); - const size_t textSize = textString.size(); - - std::vector lineInfos; - getTextLineInfos( reinterpret_cast( textString.data() ), static_cast( textSize ), textWidth, 0, fontType, fontHeight, lineInfos ); - - if ( pointerLine >= static_cast( lineInfos.size() ) ) { - // Pointer is lower than the last text line. - // Reduce textSize by 1 because the cursor character ('_') was added to the line. - return textSize - 1; - } - - size_t cursorPosition = 0; - for ( int32_t i = 0; i < pointerLine; ++i ) { - cursorPosition += lineInfos[i].characterCount; - } - - int32_t positionOffsetX = 0; - const int32_t maxOffsetX = pointerCursorOffset.x - textRoi.x - ( textRoi.width - lineInfos[pointerLine].lineWidth ) / 2; - - if ( maxOffsetX <= 0 ) { - // Pointer is to the left of the text line. - return ( cursorPosition > currentTextCursorPosition ) ? cursorPosition - 1 : cursorPosition; - } - - if ( maxOffsetX > lineInfos[pointerLine].lineWidth ) { - // Pointer is to the right of the text line. - cursorPosition += lineInfos[pointerLine].characterCount; - - return ( cursorPosition > currentTextCursorPosition ) ? cursorPosition - 1 : cursorPosition; - } - - const FontCharHandler charHandler( fontType ); - - for ( size_t i = cursorPosition; i < textSize; ++i ) { - const int32_t charWidth = charHandler.getWidth( static_cast( textString[i] ) ); - positionOffsetX += charWidth; - - if ( positionOffsetX > maxOffsetX ) { - // Take into account that the cursor character ('_') was added to the line. - return ( i > currentTextCursorPosition ) ? i - 1 : i; - } - } - - // Reduce textSize by 1 because the cursor character ('_') was added to the line. - return textSize - 1; - } - bool isFontAvailable( const std::string_view text, const FontType fontType ) { if ( text.empty() ) { diff --git a/src/fheroes2/gui/ui_text.h b/src/fheroes2/gui/ui_text.h index fcdbe9fbf7e..9a4dde2e2e2 100644 --- a/src/fheroes2/gui/ui_text.h +++ b/src/fheroes2/gui/ui_text.h @@ -20,7 +20,6 @@ #pragma once -#include #include #include #include @@ -94,6 +93,25 @@ namespace fheroes2 } }; + struct TextLineInfo + { + TextLineInfo() = default; + + TextLineInfo( const int32_t offsetX_, const int32_t offsetY_, const int32_t lineWidth_, const int32_t count ) + : offsetX( offsetX_ ) + , offsetY( offsetY_ ) + , lineWidth( lineWidth_ ) + , characterCount( count ) + { + // Do nothing. + } + + int32_t offsetX{ 0 }; + int32_t offsetY{ 0 }; + int32_t lineWidth{ 0 }; + int32_t characterCount{ 0 }; + }; + int32_t getFontHeight( const FontSize fontSize ); class TextBase @@ -152,9 +170,9 @@ namespace fheroes2 } protected: - bool _isUniformedVerticalAlignment{ true }; - std::optional _language; + + bool _isUniformedVerticalAlignment{ true }; }; class Text final : public TextBase @@ -216,8 +234,7 @@ namespace fheroes2 } // This method modifies the underlying text and ends it with '...' if it is longer than the provided width. - // By default it ignores spaces at the end of the text phrase. - void fitToOneRow( const int32_t maxWidth, const bool ignoreSpacesAtTextEnd = true ); + void fitToOneRow( const int32_t maxWidth ); std::string text() const override { @@ -229,10 +246,23 @@ namespace fheroes2 return _fontType; } + // Sets to keep trailing spaces at each text line end including the end of the text. + void keepLineTrailingSpaces() + { + _keepLineTrailingSpaces = true; + } + + // Returns text lines parameters (in pixels) in 'offsets': x - horizontal line shift, y - vertical line shift. + // And in 'characterCount' - the number of characters on the line, in 'lineWidth' the width including the `offsetX` value. + // The 'keepTextTrailingSpaces' is used to take into account all the spaces at the text end in example when you want to join multiple texts in multi-font texts. + void getTextLineInfos( std::vector & textLineInfos, const int32_t maxWidth, const int32_t rowHeight, const bool keepTextTrailingSpaces ) const; + private: std::string _text; FontType _fontType; + + bool _keepLineTrailingSpaces{ false }; }; class MultiFontText final : public TextBase @@ -262,6 +292,8 @@ namespace fheroes2 std::string text() const override; private: + void _getMultiFontTextLineInfos( std::vector & textLineInfos, const int32_t maxWidth, const int32_t rowHeight ) const; + std::vector _texts; }; @@ -293,9 +325,6 @@ namespace fheroes2 const int32_t _spaceCharWidth; }; - // Returns the character position number in the text. - size_t getTextInputCursorPosition( const Text & text, const size_t currentTextCursorPosition, const Point & pointerCursorOffset, const Rect & textRoi ); - // This function is usually useful for text generation on buttons as button font is a separate set of sprites. bool isFontAvailable( const std::string_view text, const FontType fontType ); } diff --git a/src/fheroes2/gui/ui_tool.cpp b/src/fheroes2/gui/ui_tool.cpp index 59c43a28bf3..e038207b088 100644 --- a/src/fheroes2/gui/ui_tool.cpp +++ b/src/fheroes2/gui/ui_tool.cpp @@ -687,6 +687,69 @@ namespace fheroes2 return textSize; } + size_t getTextInputCursorPosition( const Text & text, const size_t currentTextCursorPosition, const Point & pointerCursorOffset, const Rect & textRoi ) + { + if ( text.empty() ) { + // The text is empty. + return 0; + } + + const FontType fontType = text.getFontType(); + const int32_t fontHeight = getFontHeight( fontType.size ); + const int32_t pointerLine = ( pointerCursorOffset.y - textRoi.y ) / fontHeight; + + if ( pointerLine < 0 ) { + // Pointer is upper than the first text line. + return 0; + } + + std::vector lineInfos; + text.getTextLineInfos( lineInfos, textRoi.width, fontHeight, true ); + + if ( pointerLine >= static_cast( lineInfos.size() ) ) { + // Pointer is lower than the last text line. + // Reduce textSize by 1 because the cursor character ('_') was added to the line. + return text.text().size() - 1; + } + + size_t cursorPosition = 0; + for ( int32_t i = 0; i < pointerLine; ++i ) { + cursorPosition += lineInfos[i].characterCount; + } + + int32_t positionOffsetX = 0; + const int32_t maxOffsetX = pointerCursorOffset.x - textRoi.x - ( textRoi.width - lineInfos[pointerLine].lineWidth ) / 2; + + if ( maxOffsetX <= 0 ) { + // Pointer is to the left of the text line. + return ( cursorPosition > currentTextCursorPosition ) ? cursorPosition - 1 : cursorPosition; + } + + if ( maxOffsetX > lineInfos[pointerLine].lineWidth ) { + // Pointer is to the right of the text line. + cursorPosition += lineInfos[pointerLine].characterCount; + + return ( cursorPosition > currentTextCursorPosition ) ? cursorPosition - 1 : cursorPosition; + } + + const FontCharHandler charHandler( fontType ); + const std::string & textString = text.text(); + const size_t textSize = textString.size(); + + for ( size_t i = cursorPosition; i < textSize; ++i ) { + const int32_t charWidth = charHandler.getWidth( static_cast( textString[i] ) ); + positionOffsetX += charWidth; + + if ( positionOffsetX > maxOffsetX ) { + // Take into account that the cursor character ('_') was added to the line. + return ( i > currentTextCursorPosition ) ? i - 1 : i; + } + } + + // Reduce textSize by 1 because the cursor character ('_') was added to the line. + return textSize - 1; + } + void InvertedFadeWithPalette( Image & image, const Rect & roi, const Rect & excludedRoi, const uint8_t paletteId, const int32_t fadeTimeMs, const int32_t frameCount ) { Display & display = Display::instance(); diff --git a/src/fheroes2/gui/ui_tool.h b/src/fheroes2/gui/ui_tool.h index 23731f6dbb5..86fa78f31a0 100644 --- a/src/fheroes2/gui/ui_tool.h +++ b/src/fheroes2/gui/ui_tool.h @@ -217,6 +217,9 @@ namespace fheroes2 size_t getTextInputCursorPosition( const std::string & text, const FontType fontType, const size_t currentTextCursorPosition, const int32_t pointerCursorXOffset, const int32_t textStartXOffset ); + // Returns the character position number in the text. + size_t getTextInputCursorPosition( const Text & text, const size_t currentTextCursorPosition, const Point & pointerCursorOffset, const Rect & textRoi ); + void InvertedShadow( Image & image, const Rect & roi, const Rect & excludedRoi, const uint8_t paletteId, const int32_t paletteCount ); bool processIntegerValueTyping( const int32_t minimum, const int32_t maximum, int32_t & value );