diff --git a/VisualStudio/common.props b/VisualStudio/common.props index 36fa1ae21a6..cb1a838676c 100644 --- a/VisualStudio/common.props +++ b/VisualStudio/common.props @@ -12,7 +12,7 @@ true $(MSBuildThisFileDirectory)..\src\engine;$(MSBuildThisFileDirectory)..\src\fheroes2\gui;$(MSBuildThisFileDirectory)..\src\fheroes2\maps;$(MSBuildThisFileDirectory)..\src\fheroes2\kingdom;$(MSBuildThisFileDirectory)..\src\fheroes2\campaign;$(MSBuildThisFileDirectory)..\src\fheroes2\game;$(MSBuildThisFileDirectory)..\src\fheroes2\dialog;$(MSBuildThisFileDirectory)..\src\fheroes2\system;$(MSBuildThisFileDirectory)..\src\fheroes2\spell;$(MSBuildThisFileDirectory)..\src\fheroes2\monster;$(MSBuildThisFileDirectory)..\src\fheroes2\castle;$(MSBuildThisFileDirectory)..\src\fheroes2\agg;$(MSBuildThisFileDirectory)..\src\fheroes2\heroes;$(MSBuildThisFileDirectory)..\src\fheroes2\resource;$(MSBuildThisFileDirectory)..\src\fheroes2\ai;$(MSBuildThisFileDirectory)..\src\fheroes2\army;$(MSBuildThisFileDirectory)..\src\fheroes2\battle;$(MSBuildThisFileDirectory)..\src\fheroes2\pocketpc;$(MSBuildThisFileDirectory)..\src\fheroes2\objects;$(MSBuildThisFileDirectory)..\src\fheroes2\world;$(MSBuildThisFileDirectory)..\src\fheroes2\image;$(MSBuildThisFileDirectory)..\src\thirdparty\libsmacker;$(MSBuildThisFileDirectory)packages\installed\zlib\include\;%(AdditionalIncludeDirectories) _CRT_SECURE_NO_WARNINGS;_SCL_SECURE_NO_WARNINGS;WITH_ZLIB;%(PreprocessorDefinitions) - 4018;4028;4100;4189;4245;4267;4309;4319;4389;4456;4458;4554;4592;4702;4706;4715;4800 + 4018;4028;4100;4114;4189;4245;4267;4309;4319;4389;4456;4458;4554;4592;4702;4706;4715;4800 $(MSBuildThisFileDirectory)..\src\fheroes2\system;%(AdditionalIncludeDirectories) diff --git a/fheroes2-vs2015.vcxproj b/fheroes2-vs2015.vcxproj index 7b03d631f6a..08cce072f66 100644 --- a/fheroes2-vs2015.vcxproj +++ b/fheroes2-vs2015.vcxproj @@ -311,7 +311,9 @@ + + @@ -478,7 +480,9 @@ + + diff --git a/fheroes2-vs2019.vcxproj b/fheroes2-vs2019.vcxproj index 5a4561906cd..1d7fe4f17c6 100644 --- a/fheroes2-vs2019.vcxproj +++ b/fheroes2-vs2019.vcxproj @@ -312,7 +312,9 @@ + + @@ -479,7 +481,9 @@ + + diff --git a/src/fheroes2/agg/agg_image.cpp b/src/fheroes2/agg/agg_image.cpp index a14d49561b6..1b15f6797f2 100644 --- a/src/fheroes2/agg/agg_image.cpp +++ b/src/fheroes2/agg/agg_image.cpp @@ -18,6 +18,7 @@ * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ +#include #include #include #include @@ -32,8 +33,7 @@ #include "screen.h" #include "text.h" #include "til.h" - -#include "image_tool.h" +#include "ui_text.h" namespace { @@ -1105,5 +1105,68 @@ namespace fheroes2 return height; } + + uint32_t getCharacterLimit( const FontSize fontSize ) + { + switch ( fontSize ) { + case FontSize::SMALL: + return static_cast( GetMaximumICNIndex( ICN::SMALFONT ) ) + 0x20 - 1; + case FontSize::NORMAL: + case FontSize::LARGE: + return static_cast( GetMaximumICNIndex( ICN::FONT ) ) + 0x20 - 1; + default: + assert( 0 ); // Did you add a new font size? Please add implementation. + } + + return 0; + } + + const Sprite & getChar( const uint8_t character, const FontType & fontType ) + { + if ( character < 0x21 ) { + return errorImage; + } + + switch ( fontType.size ) { + case FontSize::SMALL: + switch ( fontType.color ) { + case FontColor::WHITE: + return GetICN( ICN::SMALFONT, character - 0x20 ); + case FontColor::GRAY: + return GetICN( ICN::GRAY_SMALL_FONT, character - 0x20 ); + case FontColor::YELLOW: + return GetICN( ICN::YELLOW_SMALLFONT, character - 0x20 ); + default: + break; + } + break; + case FontSize::NORMAL: + switch ( fontType.color ) { + case FontColor::WHITE: + return GetICN( ICN::FONT, character - 0x20 ); + case FontColor::GRAY: + return GetICN( ICN::GRAY_FONT, character - 0x20 ); + case FontColor::YELLOW: + return GetICN( ICN::YELLOW_FONT, character - 0x20 ); + default: + break; + } + break; + case FontSize::LARGE: + switch ( fontType.color ) { + case FontColor::WHITE: + return GetICN( ICN::WHITE_LARGE_FONT, character - 0x20 ); + default: + break; + } + break; + default: + break; + } + + assert( 0 ); // Did you add a new font size? Please add implementation. + + return errorImage; + } } } diff --git a/src/fheroes2/agg/agg_image.h b/src/fheroes2/agg/agg_image.h index c9baa03b8e3..23e5714429f 100644 --- a/src/fheroes2/agg/agg_image.h +++ b/src/fheroes2/agg/agg_image.h @@ -26,6 +26,8 @@ namespace fheroes2 { class Image; class Sprite; + enum class FontSize : uint8_t; + struct FontType; namespace AGG { @@ -41,5 +43,8 @@ namespace fheroes2 uint32_t ASCIILastSupportedCharacter( const uint32_t fontType ); int32_t GetAbsoluteICNHeight( int icnId ); + + uint32_t getCharacterLimit( const FontSize fontSize ); + const Sprite & getChar( const uint8_t character, const FontType & fontType ); } } diff --git a/src/fheroes2/game/game_mainmenu.cpp b/src/fheroes2/game/game_mainmenu.cpp index bd855750ce6..b62369338f9 100644 --- a/src/fheroes2/game/game_mainmenu.cpp +++ b/src/fheroes2/game/game_mainmenu.cpp @@ -39,6 +39,8 @@ #include "system.h" #include "text.h" #include "ui_button.h" +#include "ui_dialog.h" +#include "ui_text.h" namespace { @@ -176,10 +178,17 @@ fheroes2::GameMode Game::MainMenu( bool isFirstGameRun ) fheroes2::drawMainMenuScreen(); } - Dialog::Message( _( "Please Remember" ), - _( "You can always change game resolution by clicking on the door on the left side of main menu. To switch between windowed " - "and full screen modes press 'F4' key on the keyboard. Enjoy the game!" ), - Font::BIG, Dialog::OK ); + fheroes2::Text header( _( "Please Remember" ), { fheroes2::FontSize::NORMAL, fheroes2::FontColor::YELLOW } ); + + fheroes2::MultiFontText body; + body.add( { _( "You can always change game resolution by clicking on the " ), { fheroes2::FontSize::NORMAL, fheroes2::FontColor::WHITE } } ); + body.add( { _( "door" ), { fheroes2::FontSize::NORMAL, fheroes2::FontColor::YELLOW } } ); + body.add( { _( " on the left side of main menu.\n\nTo switch between windowed and full screen modes\npress " ), + { fheroes2::FontSize::NORMAL, fheroes2::FontColor::WHITE } } ); + body.add( { _( "F4" ), { fheroes2::FontSize::NORMAL, fheroes2::FontColor::YELLOW } } ); + body.add( { _( " key on the keyboard.\n\nEnjoy the game!" ), { fheroes2::FontSize::NORMAL, fheroes2::FontColor::WHITE } } ); + + fheroes2::showMessage( header, body, Dialog::OK ); conf.resetFirstGameRun(); conf.Save( "fheroes2.cfg" ); diff --git a/src/fheroes2/gui/ui_dialog.cpp b/src/fheroes2/gui/ui_dialog.cpp new file mode 100644 index 00000000000..dd3d3e8f1f3 --- /dev/null +++ b/src/fheroes2/gui/ui_dialog.cpp @@ -0,0 +1,67 @@ +/*************************************************************************** + * Free Heroes of Might and Magic II: https://github.com/ihhub/fheroes2 * + * Copyright (C) 2021 * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + ***************************************************************************/ + +#include "ui_dialog.h" +#include "cursor.h" +#include "dialog.h" +#include "localevent.h" +#include "screen.h" +#include "ui_button.h" + +namespace +{ + const int32_t textOffsetY = 10; +} + +namespace fheroes2 +{ + int showMessage( const TextBase & header, const TextBase & body, const int buttons ) + { + // setup cursor + const CursorRestorer cursorRestorer( buttons != 0, ::Cursor::POINTER ); + + const int32_t headerHeight = header.empty() ? 0 : header.height( BOXAREA_WIDTH ) + textOffsetY; + + Dialog::FrameBox box( textOffsetY + headerHeight + body.height( BOXAREA_WIDTH ), buttons != 0 ); + const Rect & pos = box.GetArea(); + + Display & display = Display::instance(); + header.draw( pos.x, pos.y + textOffsetY, BOXAREA_WIDTH, display ); + body.draw( pos.x, pos.y + textOffsetY + headerHeight, BOXAREA_WIDTH, display ); + + ButtonGroup group( pos, buttons ); + group.draw(); + + display.render(); + + int result = Dialog::ZERO; + LocalEvent & le = LocalEvent::Get(); + + while ( result == Dialog::ZERO && le.HandleEvents() ) { + if ( !buttons && !le.MousePressRight() ) { + break; + } + + result = group.processEvents(); + } + + return result; + } +} diff --git a/src/fheroes2/gui/ui_dialog.h b/src/fheroes2/gui/ui_dialog.h new file mode 100644 index 00000000000..8a208c56317 --- /dev/null +++ b/src/fheroes2/gui/ui_dialog.h @@ -0,0 +1,30 @@ +/*************************************************************************** + * Free Heroes of Might and Magic II: https://github.com/ihhub/fheroes2 * + * Copyright (C) 2021 * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + ***************************************************************************/ + +#pragma once + +#include + +#include "ui_text.h" + +namespace fheroes2 +{ + int showMessage( const TextBase & header, const TextBase & body, const int buttons ); +} diff --git a/src/fheroes2/gui/ui_text.cpp b/src/fheroes2/gui/ui_text.cpp new file mode 100644 index 00000000000..8a2e696e254 --- /dev/null +++ b/src/fheroes2/gui/ui_text.cpp @@ -0,0 +1,539 @@ +/*************************************************************************** + * Free Heroes of Might and Magic II: https://github.com/ihhub/fheroes2 * + * Copyright (C) 2021 * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + ***************************************************************************/ + +#include "ui_text.h" +#include "agg_image.h" + +#include +#include + +namespace +{ + const uint8_t lineSeparator = '\n'; + + class CharValidator + { + public: + explicit CharValidator( const fheroes2::FontSize fontSize ) + : _charLimit( fheroes2::AGG::getCharacterLimit( fontSize ) ) + {} + + bool isValid( const uint8_t character ) const + { + return character >= 0x21 && character <= _charLimit; + } + + private: + const uint32_t _charLimit; + }; + + int32_t getInvalidCharWidth( const fheroes2::FontSize fontSize ) + { + switch ( fontSize ) { + case fheroes2::FontSize::SMALL: + return 4; + case fheroes2::FontSize::NORMAL: + return 6; + case fheroes2::FontSize::LARGE: + return 12; + default: + assert( 0 ); // Did you add a new font size? Please add implementation. + } + + return 0; + } + + int32_t getFontHeight( const fheroes2::FontSize fontSize ) + { + switch ( fontSize ) { + case fheroes2::FontSize::SMALL: + return 8 + 2 + 1; + case fheroes2::FontSize::NORMAL: + return 13 + 3 + 1; + case fheroes2::FontSize::LARGE: + return 26 + 6 + 1; + default: + assert( 0 ); // Did you add a new font size? Please add implementation. + } + + return 0; + } + + int32_t getLineWidth( const uint8_t * data, const int32_t size, const fheroes2::FontType & fontType ) + { + assert( data != nullptr && size != 0 ); + + const CharValidator validator( fontType.size ); + + int32_t width = 0; + + const uint8_t * dataEnd = data + size; + while ( data != dataEnd ) { + if ( validator.isValid( *data ) ) { + width += fheroes2::AGG::getChar( *data, fontType ).width(); + } + else { + width += getInvalidCharWidth( fontType.size ); + } + ++data; + } + + return width; + } + + // Ignore all spaces or invalid characters at the end of the line. + int32_t getTruncatedLineWidth( const uint8_t * data, const int32_t size, const fheroes2::FontType & fontType ) + { + assert( data != nullptr && size != 0 ); + + const CharValidator validator( fontType.size ); + + int32_t width = 0; + + int32_t invalidCharWidth = 0; + + const uint8_t * dataEnd = data + size; + while ( data != dataEnd ) { + if ( validator.isValid( *data ) ) { + width += invalidCharWidth; + invalidCharWidth = 0; + width += fheroes2::AGG::getChar( *data, fontType ).width(); + } + else { + invalidCharWidth += getInvalidCharWidth( fontType.size ); + } + ++data; + } + + return width; + } + + void getMultiRowInfo( const uint8_t * data, const int32_t size, const int32_t maxWidth, const fheroes2::FontType & fontType, const int32_t rowHeight, + std::deque & offsets ) + { + assert( data != nullptr && size > 0 && maxWidth > 0 ); + + if ( offsets.empty() ) { + offsets.emplace_back(); + } + + const CharValidator validator( fontType.size ); + + // We need to cut sentences not in the middle of a word but by a space or invalid characters. + const uint8_t * character = data; + const uint8_t * characterEnd = character + size; + + int32_t lineLength = 0; + int32_t lastWordLength = 0; + int32_t lineWidth = 0; + + fheroes2::Point * offset = &offsets.back(); + + while ( character != characterEnd ) { + if ( *character == lineSeparator ) { + // End of line. + if ( lineLength > 0 ) { + const uint8_t * line = character - lineLength; + offset->x += getTruncatedLineWidth( line, lineLength, fontType ); + lineLength = 0; + lastWordLength = 0; + lineWidth = 0; + } + + offsets.emplace_back( 0, offset->y + rowHeight ); + offset = &offsets.back(); + + ++character; + } + else { + ++lineLength; + if ( validator.isValid( *character ) ) { + ++lastWordLength; + lineWidth += fheroes2::AGG::getChar( *character, fontType ).width(); + } + else { + lastWordLength = 0; + lineWidth += getInvalidCharWidth( fontType.size ); + } + + if ( offset->x + lineWidth > maxWidth ) { + const uint8_t * line = character - ( lineLength - 1 ); + if ( lineLength == lastWordLength ) { + offset->x += getTruncatedLineWidth( line, lineLength, fontType ); + ++character; + } + else { + offset->x += getTruncatedLineWidth( line, lineLength - lastWordLength, fontType ); + character -= lastWordLength - 1; + } + + lineLength = 0; + lastWordLength = 0; + lineWidth = 0; + + offsets.emplace_back( 0, offset->y + rowHeight ); + offset = &offsets.back(); + } + else { + ++character; + } + } + } + + offset->x += lineWidth; + } + + int32_t render( const uint8_t * data, const int32_t size, const int32_t x, const int32_t y, fheroes2::Image & output, const fheroes2::FontType & fontType ) + { + assert( data != nullptr && size > 0 && !output.empty() ); + + const CharValidator validator( fontType.size ); + + int32_t offsetX = x; + + const uint8_t * character = data; + const uint8_t * characterEnd = character + size; + + for ( ; character != characterEnd; ++character ) { + if ( !validator.isValid( *character ) ) { + offsetX += getInvalidCharWidth( fontType.size ); + continue; + } + + const fheroes2::Sprite & charSprite = fheroes2::AGG::getChar( *character, fontType ); + assert( !charSprite.empty() ); + + fheroes2::Blit( charSprite, output, offsetX + charSprite.x(), y + charSprite.y() ); + offsetX += charSprite.width() + charSprite.x(); + } + + return offsetX; + } + + void renderLine( 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::FontType & fontType, const bool align ) + { + if ( align ) { + const int32_t correctedLineWidth = getTruncatedLineWidth( data, size, fontType ); + render( data, size, x + ( maxWidth - correctedLineWidth ) / 2, y, output, fontType ); + } + else { + render( data, size, x, y, output, fontType ); + } + } + + void render( 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::FontType & fontType, const int32_t rowHeight, const bool align, std::deque & offsets ) + { + assert( data != nullptr && size > 0 && !output.empty() && maxWidth > 0 ); + + const CharValidator validator( fontType.size ); + + // We need to cut sentences not in the middle of a word but by a space or invalid characters. + const uint8_t * character = data; + const uint8_t * characterEnd = character + size; + + int32_t lineLength = 0; + int32_t lastWordLength = 0; + int32_t lineWidth = 0; + + fheroes2::Point staticOffset; + fheroes2::Point * offset; + + if ( !offsets.empty() ) { + offset = &offsets.front(); + } + else { + offset = &staticOffset; + } + + const int32_t fontHeight = getFontHeight( fontType.size ); + const int32_t yPos = y + ( rowHeight - fontHeight ) / 2; + + while ( character != characterEnd ) { + if ( *character == lineSeparator ) { + // End of line. Render the current line. + if ( lineLength > 0 ) { + renderLine( character - lineLength, lineLength, x + offset->x, yPos + offset->y, maxWidth, output, fontType, align ); + lineLength = 0; + lastWordLength = 0; + lineWidth = 0; + } + + if ( !offsets.empty() ) { + offsets.pop_front(); + assert( !offsets.empty() ); + } + if ( !offsets.empty() ) { + offset = &offsets.front(); + } + else { + offset = &staticOffset; + offset->x = 0; + offset->y += rowHeight; + } + + ++character; + } + else { + ++lineLength; + if ( validator.isValid( *character ) ) { + ++lastWordLength; + lineWidth += fheroes2::AGG::getChar( *character, fontType ).width(); + } + else { + lastWordLength = 0; + lineWidth += getInvalidCharWidth( fontType.size ); + } + + if ( offset->x + lineWidth > maxWidth ) { + const uint8_t * line = character - ( lineLength - 1 ); + if ( lineLength == lastWordLength ) { + // Looks like a word is bigger than line width. + renderLine( line, lineLength, x + offset->x, yPos + offset->y, maxWidth, output, fontType, align ); + ++character; + } + else { + renderLine( line, lineLength - lastWordLength, x + offset->x, yPos + offset->y, maxWidth, output, fontType, align ); + character -= lastWordLength - 1; + } + + lineLength = 0; + lastWordLength = 0; + lineWidth = 0; + + if ( !offsets.empty() ) { + offsets.pop_front(); + assert( !offsets.empty() ); + } + if ( !offsets.empty() ) { + offset = &offsets.front(); + } + else { + offset = &staticOffset; + offset->x = 0; + offset->y += rowHeight; + } + } + else { + ++character; + } + } + } + + if ( lineLength > 0 ) { + renderLine( character - lineLength, lineLength, x + offset->x, yPos + offset->y, maxWidth, output, fontType, align ); + offset->x += lineWidth; + } + } +} + +namespace fheroes2 +{ + TextBase::~TextBase() = default; + + Text::Text( const std::string & text, const FontType fontType ) + : _text( text ) + , _fontType( fontType ) + {} + + Text::~Text() = default; + + int32_t Text::width() const + { + return getLineWidth( reinterpret_cast( _text.data() ), static_cast( _text.size() ), _fontType ); + } + + int32_t Text::height() const + { + return getFontHeight( _fontType.size ); + } + + int32_t Text::height( const int32_t maxWidth ) const + { + const int32_t fontHeight = getFontHeight( _fontType.size ); + + std::deque offsets; + getMultiRowInfo( reinterpret_cast( _text.data() ), static_cast( _text.size() ), maxWidth, _fontType, fontHeight, offsets ); + + return offsets.back().y + fontHeight; + } + + int32_t Text::rows( const int32_t maxWidth ) const + { + const int32_t fontHeight = getFontHeight( _fontType.size ); + + std::deque offsets; + getMultiRowInfo( reinterpret_cast( _text.data() ), static_cast( _text.size() ), maxWidth, _fontType, fontHeight, offsets ); + + return offsets.back().y / fontHeight + 1; + } + + void Text::draw( const int32_t x, const int32_t y, Image & output ) const + { + if ( output.empty() || _text.empty() ) { + // No use to render something on an empty image or if something is empty. + return; + } + + render( reinterpret_cast( _text.data() ), static_cast( _text.size() ), x, y, output, _fontType ); + } + + void Text::draw( const int32_t x, const int32_t y, const int32_t maxWidth, Image & output ) const + { + if ( output.empty() || _text.empty() ) { + // No use to render something on an empty image or if something is empty. + return; + } + + assert( maxWidth > 0 ); // Why is the limit less than 1? + if ( maxWidth <= 0 ) { + draw( x, y, output ); + return; + } + + std::deque offsets; + render( reinterpret_cast( _text.data() ), static_cast( _text.size() ), x, y, maxWidth, output, _fontType, + getFontHeight( _fontType.size ), true, offsets ); + } + + bool Text::empty() const + { + return _text.empty(); + } + + MultiFontText::~MultiFontText() = default; + + void MultiFontText::add( const Text & text ) + { + if ( !text._text.empty() ) { + _texts.emplace_back( text ); + } + } + + void MultiFontText::add( const Text && text ) + { + if ( !text._text.empty() ) { + _texts.emplace_back( text ); + } + } + + int32_t MultiFontText::width() const + { + int32_t totalWidth = 0; + for ( const Text & text : _texts ) { + totalWidth += text.width(); + } + + return totalWidth; + } + + int32_t MultiFontText::height() const + { + int32_t maxHeight = 0; + + for ( const Text & text : _texts ) { + const int32_t height = text.height(); + if ( maxHeight < height ) { + maxHeight = height; + } + } + + return maxHeight; + } + + int32_t MultiFontText::height( const int32_t maxWidth ) const + { + const int32_t maxFontHeight = height(); + + std::deque offsets; + for ( const Text & text : _texts ) { + getMultiRowInfo( reinterpret_cast( text._text.data() ), static_cast( text._text.size() ), maxWidth, text._fontType, maxFontHeight, + offsets ); + } + return offsets.back().y + maxFontHeight; + } + + int32_t MultiFontText::rows( const int32_t maxWidth ) const + { + const int32_t maxFontHeight = height(); + + std::deque offsets; + for ( const Text & text : _texts ) { + getMultiRowInfo( reinterpret_cast( text._text.data() ), static_cast( text._text.size() ), maxWidth, text._fontType, maxFontHeight, + offsets ); + } + + return offsets.back().y / maxFontHeight + 1; + } + + void MultiFontText::draw( const int32_t x, const int32_t y, Image & output ) const + { + if ( output.empty() || _texts.empty() ) { + // No use to render something on an empty image or if something is empty. + return; + } + + const int32_t maxFontHeight = height(); + + int32_t offsetX = x; + for ( const Text & text : _texts ) { + const int32_t fontHeight = getFontHeight( text._fontType.size ); + offsetX = render( reinterpret_cast( text._text.data() ), static_cast( text._text.size() ), offsetX, + y + ( maxFontHeight - fontHeight ) / 2, output, text._fontType ); + } + } + + void MultiFontText::draw( const int32_t x, const int32_t y, const int32_t maxWidth, Image & output ) const + { + if ( output.empty() || _texts.empty() ) { + // No use to render something on an empty image or if something is empty. + return; + } + + assert( maxWidth > 0 ); // Why is the limit less than 1? + if ( maxWidth <= 0 ) { + draw( x, y, output ); + return; + } + + const int32_t maxFontHeight = height(); + + std::deque offsets; + for ( const Text & text : _texts ) { + getMultiRowInfo( reinterpret_cast( text._text.data() ), static_cast( text._text.size() ), maxWidth, text._fontType, maxFontHeight, + offsets ); + } + + for ( Point & point : offsets ) { + point.x = ( maxWidth - point.x ) / 2; + } + + for ( size_t i = 0; i < _texts.size(); ++i ) { + render( reinterpret_cast( _texts[i]._text.data() ), static_cast( _texts[i]._text.size() ), x, y, maxWidth, output, + _texts[i]._fontType, maxFontHeight, false, offsets ); + } + } + + bool MultiFontText::empty() const + { + return _texts.empty(); + } +} diff --git a/src/fheroes2/gui/ui_text.h b/src/fheroes2/gui/ui_text.h new file mode 100644 index 00000000000..a350e62a3bb --- /dev/null +++ b/src/fheroes2/gui/ui_text.h @@ -0,0 +1,136 @@ +/*************************************************************************** + * Free Heroes of Might and Magic II: https://github.com/ihhub/fheroes2 * + * Copyright (C) 2021 * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + ***************************************************************************/ + +#pragma once + +#include "image.h" + +#include +#include + +namespace fheroes2 +{ + // TODO: the old Text classes render text with 2 pixel shift by Y axis. We need to do something to keep the same drawings while replacing old code. + + enum class FontSize : uint8_t + { + SMALL, + NORMAL, + LARGE + }; + + enum class FontColor : uint8_t + { + WHITE, + GRAY, + YELLOW + }; + + struct FontType + { + FontType() = default; + + FontType( const FontSize size_, const FontColor color_ ) + : size( size_ ) + , color( color_ ) + {} + + FontSize size = FontSize::NORMAL; + FontColor color = FontColor::WHITE; + }; + + class TextBase + { + public: + TextBase() = default; + virtual ~TextBase(); + + // Returns width of a text as a single-line text only. + virtual int32_t width() const = 0; + + // Returns height of a text as a single-line text only. + virtual int32_t height() const = 0; + + // Returns height of a text as a multi-line text limited by width of a line. + virtual int32_t height( const int32_t maxWidth ) const = 0; + + // Returns number of multi-line text rows limited by width of a line. + virtual int32_t rows( const int32_t maxWidth ) const = 0; + + // Draw text as a single line text. + virtual void draw( const int32_t x, const int32_t y, Image & output ) const = 0; + + // Draw text as a multi-line limited by width of a line. + virtual void draw( const int32_t x, const int32_t y, const int32_t maxWidth, Image & output ) const = 0; + + // Returns true if here is something to draw. + virtual bool empty() const = 0; + }; + + class Text : public TextBase + { + public: + friend class MultiFontText; + + Text() = default; + Text( const std::string & text, const FontType fontType ); + ~Text() override; + + int32_t width() const override; + int32_t height() const override; + + int32_t height( const int32_t maxWidth ) const override; + int32_t rows( const int32_t maxWidth ) const override; + + void draw( const int32_t x, const int32_t y, Image & output ) const override; + void draw( const int32_t x, const int32_t y, const int32_t maxWidth, Image & output ) const override; + + bool empty() const override; + + private: + std::string _text; + + FontType _fontType; + }; + + class MultiFontText : public TextBase + { + public: + MultiFontText() = default; + ~MultiFontText() override; + + void add( const Text & text ); + void add( const Text && text ); + + int32_t width() const override; + int32_t height() const override; + + int32_t height( const int32_t maxWidth ) const override; + int32_t rows( const int32_t maxWidth ) const override; + + void draw( const int32_t x, const int32_t y, Image & output ) const override; + void draw( const int32_t x, const int32_t y, const int32_t maxWidth, Image & output ) const override; + + bool empty() const override; + + private: + std::vector _texts; + }; +}