~aleteoryx/muditaos

88a9380ad98982175f5d7b43ede5196ee451f4cf — Przemyslaw Brudny 4 years ago e05ddbf
[EGD-6971] TextLine breaking on space or with dash

Added TextLine breaking on space or with dash.
Tests added.
M module-gui/gui/widgets/TextConstants.hpp => module-gui/gui/widgets/TextConstants.hpp +4 -3
@@ 1,4 1,4 @@
// Copyright (c) 2017-2020, Mudita Sp. z.o.o. All rights reserved.
// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#pragma once


@@ 9,8 9,9 @@ namespace gui
{
    namespace text
    {
        const unsigned int npos = std::numeric_limits<unsigned int>().max();
        const char newline      = '\n';
        constexpr auto npos                     = std::numeric_limits<unsigned int>::max();
        const char newline                      = '\n';
        const unsigned int word_detection_range = 10;
    }; // namespace text

    enum class TextLimitType

M module-gui/gui/widgets/TextLine.cpp => module-gui/gui/widgets/TextLine.cpp +43 -8
@@ 62,11 62,10 @@ namespace gui
                return;
            }

            auto canShow = textFormat->getFont()->getCharCountInSpace(text, maxWidth - widthUsed);
            auto signsCountToShow = calculateSignsToShow(localCursor, text, maxWidth - widthUsed);

            // we can show nothing - this is the end of this line
            if (canShow == 0) {

            if (signsCountToShow == 0) {
                auto item  = buildUITextPart("", textFormat);
                widthUsed  = item->getTextNeedSpace();
                heightUsed = std::max(heightUsed, item->getTextHeight());


@@ 79,15 78,16 @@ namespace gui
            }

            // create item for show and update Line data
            auto item = buildUITextPart(text.substr(0, canShow), textFormat);
            shownLetterCount += canShow;
            auto item =
                buildUITextPart(text.substr(0, signsCountToShow) + (breakLineDashAddition ? "-" : ""), textFormat);
            shownLetterCount += signsCountToShow;
            widthUsed += item->getTextNeedSpace();
            heightUsed = std::max(heightUsed, item->getTextHeight());
            lineContent.emplace_back(item);

            setLineStartConditions(localCursor.getBlockNumber(), localCursor.getPosition());

            localCursor += canShow;
            localCursor += signsCountToShow;

            if (localCursor.checkAndInvalidateBlockChanged() && localCursor.checkPreviousBlockNewLine()) {
                end = TextBlock::End::Newline;


@@ 95,7 95,7 @@ namespace gui
            }

            // not whole text shown, try again for next line if you want
            if (canShow < text.length()) {
            if (signsCountToShow < text.length()) {
                end = TextBlock::End::None;
                return;
            }


@@ 103,6 103,41 @@ namespace gui
        } while (true);
    }

    unsigned int TextLine::calculateSignsToShow(BlockCursor &localCursor, UTF8 &text, unsigned int space)
    {
        auto signsCountToShow = localCursor->getFormat()->getFont()->getCharCountInSpace(text, space);

        // additional one sign to detect potential space as last character in line
        auto searchSubstring = text.substr(0, signsCountToShow + 1);

        auto newlinePos = searchSubstring.findLast("\n", signsCountToShow);
        auto spacePos   = searchSubstring.findLast(" ", signsCountToShow);

        // check if space or newline in word detection range
        if (spacePos <= signsCountToShow - text::word_detection_range) {
            spacePos = UTF8::npos;
        }

        if (newlinePos <= signsCountToShow - text::word_detection_range) {
            newlinePos = UTF8::npos;
        }

        // check if space found and no newline at end
        if (spacePos != UTF8::npos && newlinePos == UTF8::npos) {
            // add one to include space in the line
            signsCountToShow      = spacePos + 1;
            breakLineDashAddition = false;
        }
        // if sings still left in text add dash sign
        else if (signsCountToShow != 0 && signsCountToShow < text.length()) {
            // decrease character shown count by one to fit dash
            signsCountToShow      = signsCountToShow - 1;
            breakLineDashAddition = true;
        }

        return signsCountToShow;
    }

    TextLine::TextLine(TextLine &&from) noexcept
    {
        lineContent            = std::move(from.lineContent);


@@ 117,6 152,7 @@ namespace gui
        lineEnd                = from.lineEnd;
        end                    = from.end;
        maxWidth               = from.maxWidth;
        breakLineDashAddition  = from.breakLineDashAddition;
        lineStartBlockNumber   = from.lineStartBlockNumber;
        lineStartBlockPosition = from.lineStartBlockPosition;
        lineVisible            = from.lineVisible;


@@ 314,5 350,4 @@ namespace gui
            underline->setArea({x, y, getWidth(), height()});
        }
    }

} // namespace gui

M module-gui/gui/widgets/TextLine.hpp => module-gui/gui/widgets/TextLine.hpp +3 -1
@@ 1,4 1,4 @@
// Copyright (c) 2017-2020, Mudita Sp. z.o.o. All rights reserved.
// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#pragma once


@@ 36,9 36,11 @@ namespace gui
        Position storedYOffset              = 0;
        bool lineEnd                        = false;
        bool lineVisible                    = true;
        bool breakLineDashAddition          = false;
        unsigned int lineStartBlockNumber   = text::npos;
        unsigned int lineStartBlockPosition = text::npos;

        unsigned int calculateSignsToShow(BlockCursor &localCursor, UTF8 &text, unsigned int space);
        void createUnderline(unsigned int max_w, unsigned int max_height);
        void updateUnderline(const short &x, const short &y);
        void setLineStartConditions(unsigned int startBlockNumber, unsigned int startBlockPosition);

M module-gui/gui/widgets/TextLineCursor.cpp => module-gui/gui/widgets/TextLineCursor.cpp +9 -5
@@ 182,14 182,19 @@ namespace gui
        }

        /// right - corner case
        if ((checkNpos() || (direction == NavigationDirection::RIGHT && selectedLineNumber == lastVisibleLineNumber))) {
        if ((checkNpos() || (direction == NavigationDirection::RIGHT && selectedLineNumber >= lastVisibleLineNumber))) {

            if (text->lines->stopCondition != LinesDrawStop::OutOfText &&
            auto onLastLine = selectedLineNumber == lastVisibleLineNumber;
            auto onLastLineLastCharacter =
                onLastLine &&
                (selectedLineCursorPosition ==
                 (text->lines->getLine(lastVisibleLineNumber)->length() -
                  (text->lines->getLine(lastVisibleLineNumber)->getEnd() == TextBlock::End::Newline ? 1 : 0)))) {
                  (text->lines->getLine(lastVisibleLineNumber)->getEnd() == TextBlock::End::Newline ? 1 : 0)));

                if (checkNextLineDocumentEnd(selectedLineNumber)) {
            if (text->lines->stopCondition != LinesDrawStop::OutOfText &&
                (onLastLineLastCharacter || (selectedLineNumber > lastVisibleLineNumber))) {

                if (onLastLine && checkNextLineDocumentEnd(selectedLineNumber)) {
                    return TextCursor::Move::End;
                }



@@ 200,7 205,6 @@ namespace gui
                return ret;
            }
            else {

                return TextCursor::moveCursor(direction);
            }
        }

M module-gui/test/test-catch-text/test-gui-Text.cpp => module-gui/test/test-catch-text/test-gui-Text.cpp +66 -0
@@ 1369,3 1369,69 @@ TEST_CASE("Navigating down between input texts")
        REQUIRE(layout.getFocusItemIndex() == 1);
    }
}

TEST_CASE("Text word line breaking tests")
{
    std::string testStringBlock1 = "Test ";
    std::string testStringBlock2 = "String ";
    std::string testStringBlock3 = "LongLongLong";
    std::string testStringBlock4 = testStringBlock1 + testStringBlock3 + testStringBlock2;
    std::string testStringBlock5 = testStringBlock1 + testStringBlock2;
    std::string emptyParagraph   = "<p></p>";

    SECTION("Breaking lines on space on whole words with long words")
    {
        mockup::fontManager();
        using namespace gui;
        auto text = std::make_unique<gui::TestText>();
        text->setMaximumSize(300, 200);

        text->addRichText("<text>" + testStringBlock4 + testStringBlock5 + "</text>");

        REQUIRE(text->linesSize() == 2);
        REQUIRE((*text->lineGet(0)).getText(0) == testStringBlock4);
        REQUIRE((*text->lineGet(1)).getText(0) == testStringBlock5);
    }

    SECTION("Breaking lines on space on whole words with short word")
    {
        mockup::fontManager();
        using namespace gui;
        auto text = std::make_unique<gui::TestText>();
        text->setMaximumSize(250, 200);

        text->addRichText("<text>" + testStringBlock5 + testStringBlock5 + testStringBlock5 + "</text>");

        REQUIRE(text->linesSize() == 2);
        REQUIRE((*text->lineGet(0)).getText(0) == testStringBlock5 + testStringBlock5);
        REQUIRE((*text->lineGet(1)).getText(0) == testStringBlock5);
    }

    SECTION("Breaking lines on newline before breaking it on space")
    {
        mockup::fontManager();
        using namespace gui;
        auto text = std::make_unique<gui::TestText>();
        text->setMaximumSize(250, 200);

        text->addRichText("<text>" + testStringBlock5 + emptyParagraph + testStringBlock5 + "</text>");

        REQUIRE(text->linesSize() == 2);
        REQUIRE((*text->lineGet(0)).getText(0) == testStringBlock5 + "\n");
        REQUIRE((*text->lineGet(1)).getText(0) == testStringBlock5);
    }

    SECTION("Breaking lines by splitting word with dash")
    {
        mockup::fontManager();
        using namespace gui;
        auto text = std::make_unique<gui::TestText>();
        text->setMaximumSize(220, 200);

        text->addRichText("<text>" + testStringBlock4 + "</text>");

        REQUIRE(text->linesSize() == 2);
        REQUIRE((*text->lineGet(0)).getText(0) == testStringBlock1 + testStringBlock3 + "-");
        REQUIRE((*text->lineGet(1)).getText(0) == testStringBlock2);
    }
}

M module-gui/test/test-catch-text/test-gui-TextFixedSize.cpp => module-gui/test/test-catch-text/test-gui-TextFixedSize.cpp +1 -1
@@ 157,7 157,7 @@ TEST_CASE("TextFixedSize remove Char")
    using namespace gui;
    mockup::fontManager();

    std::string testString1 = "Test Line Line Line";
    std::string testString1 = "Test Line Line Line ";
    std::string testString2 = "End";

    std::string testStringLongLine = testString1 + testString2;