~aleteoryx/muditaos

11f39b1caf15de0d5366b7d2d43567370b8dced6 — PrzeBrudny 5 years ago 608839d
[EGD-4517] Added Text Up/Down navigation with tests. (#1089)

M changelog.md => changelog.md +1 -0
@@ 5,6 5,7 @@
* `[cellular]` Integration with basic flow for SIM card (cellular<>GUI)
* `[file indexer db]` Added UT fot File Indexer DB
* `[cellular]` Added MMI convertible to action messages.
* `[text]` Added Up/Down text navigation (without scrolling)

### Fixed


M module-gui/gui/widgets/Lines.cpp => module-gui/gui/widgets/Lines.cpp +3 -3
@@ 93,13 93,13 @@ namespace gui
        }
    }

    TextLine *Lines::getTextLine(uint32_t line)
    TextLine *Lines::getLine(unsigned int lineNr)
    {
        if (lines.empty() || line >= lines.size()) {
        if (lines.empty() || lineNr >= lines.size()) {
            return nullptr;
        }

        auto it = std::next(lines.begin(), line);
        auto it = std::next(lines.begin(), lineNr);
        return &*it;
    }


M module-gui/gui/widgets/Lines.hpp => module-gui/gui/widgets/Lines.hpp +1 -8
@@ 17,17 17,11 @@

namespace gui
{
    class Text;
    class TextLineCursor;

    class Lines
    {
        Text *text = nullptr;
        std::list<TextLine> lines;

        uint32_t max_lines_count = 4;
        uint32_t scroll_position = 0;

        bool underLine            = false;
        Position underLinePadding = 0;



@@ 122,8 116,7 @@ namespace gui
        auto linesHAlign(Length parentSize) -> void;
        auto linesVAlign(Length parentSize) -> void;

      protected:
        TextLine *getTextLine(uint32_t line);
        TextLine *getLine(unsigned int lineNr);
    };

} // namespace gui

M module-gui/gui/widgets/Text.cpp => module-gui/gui/widgets/Text.cpp +4 -1
@@ 475,7 475,10 @@ namespace gui
            return false;
        }

        if (isMode(EditMode::SCROLL) && (inputEvent.is(KeyCode::KEY_LEFT) || inputEvent.is(KeyCode::KEY_RIGHT))) {
        // Temporary disabled till text scrolling will ba added
        // if (isMode(EditMode::SCROLL) && (inputEvent.is(KeyCode::KEY_LEFT) ||
        // inputEvent.is(KeyCode::KEY_RIGHT))) {
        if (isMode(EditMode::SCROLL)) {
            debug_text("Text in scroll mode ignores left/right navigation");
            return false;
        }

M module-gui/gui/widgets/Text.hpp => module-gui/gui/widgets/Text.hpp +2 -4
@@ 31,8 31,6 @@ namespace gui
        unsigned int cursorPos;
    };

    class Lines;

    ///  @brief Widget that holds multiple lines of text.
    ///
    ///  Can expand horizontally to it's max size if it needs to fit more text in line


@@ 48,7 46,7 @@ namespace gui
    class Text : public Rect
    {
        friend TextCursor;
        friend Lines;
        friend TextLineCursor;

      protected:
        // holds list of labels for displaying currently visible text lines.


@@ 93,7 91,7 @@ namespace gui
        [[nodiscard]] auto getSizeMinusPadding(Axis axis, Area val) -> Length;
        auto applyParentSizeRestrictions() -> void;
        auto calculateAndRequestSize() -> void;
        auto makePreDrawLines(const uint32_t utfVal) -> std::unique_ptr<Lines>;
        auto makePreDrawLines(uint32_t utfVal) -> std::unique_ptr<Lines>;
        auto makePreDrawLines(const TextBlock &textBlock) -> std::unique_ptr<Lines>;

        auto checkMaxSignsLimit(unsigned int limitVal) -> InputBound;

M module-gui/gui/widgets/TextCursor.cpp => module-gui/gui/widgets/TextCursor.cpp +6 -8
@@ 58,15 58,13 @@ namespace gui
            return Move::Error;
        }

        /// left & up - corner case
        if ((checkNpos() || atBegin()) &&
            (direction == NavigationDirection::LEFT || direction == NavigationDirection::UP)) {
        /// left - corner case
        if ((checkNpos() || atBegin()) && (direction == NavigationDirection::LEFT)) {
            return Move::Start;
        }

        /// down & right - corner case
        if ((checkNpos() || atEnd()) &&
            (direction == NavigationDirection::RIGHT || direction == NavigationDirection::DOWN)) {
        /// right - corner case
        if ((checkNpos() || atEnd()) && (direction == NavigationDirection::RIGHT)) {
            return Move::End;
        }



@@ 108,7 106,7 @@ namespace gui
        return Move::Error;
    }

    std::tuple<const TextLine *, unsigned int, unsigned int> TextCursor::getLine()
    std::tuple<const TextLine *, unsigned int, unsigned int> TextCursor::getSelectedLine()
    {
        unsigned int offset_pos = 0;
        unsigned int row        = 0;


@@ 144,7 142,7 @@ namespace gui
            y = getAxisAlignmentValue(Axis::Y, h);
        }
        else if (text != nullptr || text->lines->size() > 0) {
            auto [line, column, row] = getLine();
            auto [line, column, row] = getSelectedLine();
            if (line == nullptr || column == text::npos || row == text::npos) {
                setArea({x, y, w, h});
                return;

M module-gui/gui/widgets/TextCursor.hpp => module-gui/gui/widgets/TextCursor.hpp +3 -3
@@ 19,6 19,7 @@ namespace gui
    /// and position in gui::Text::Lines shown on screen
    class TextCursor : public Rect, public BlockCursor
    {
      protected:
        unsigned int pos_on_screen = 0;
        Text *text                 = nullptr;



@@ 36,7 37,7 @@ namespace gui
            Error, /// error - now not implemented
        };

        TextCursor(gui::Text *parent, unsigned int pos = text::npos, unsigned int block = text::npos);
        explicit TextCursor(gui::Text *parent, unsigned int pos = text::npos, unsigned int block = text::npos);
        TextCursor() = delete;

        /// Up Down - end of line movement like in vi


@@ 47,12 48,11 @@ namespace gui
        /// removeChar)
        virtual Move moveCursor(NavigationDirection direction);
        virtual Move moveCursor(NavigationDirection direction, unsigned int n);
        void reset();

        // TODO note to self - here should be too UTF8 char handling, not in document...
        // cursor can pass processing char directly to TextBlock we are interested in...
        // so this should be in BlockCursor in reality
        auto getLine() -> std::tuple<const TextLine *, unsigned int, unsigned int>;
        auto getSelectedLine() -> std::tuple<const TextLine *, unsigned int, unsigned int>;
        void updateView();

        // TODO this can move our text out of bonds ( and might need calling expand() in Text)

M module-gui/gui/widgets/TextLine.cpp => module-gui/gui/widgets/TextLine.cpp +57 -57
@@ 28,7 28,7 @@ namespace gui

    /// Note - line breaking could be done here with different TextLines to return
    /// or via different block types (i.e. numeric block tyle could be not "breakable"
    TextLine::TextLine(BlockCursor &localCursor, unsigned int max_width) : max_width(max_width)
    TextLine::TextLine(BlockCursor &localCursor, unsigned int maxWidth) : maxWidth(maxWidth)
    {
        do {
            if (!localCursor) { // cursor is faulty


@@ 49,71 49,71 @@ namespace gui
                continue;
            }

            auto text_format = localCursor->getFormat();
            if (text_format->getFont() == nullptr) {
            auto textFormat = localCursor->getFormat();
            if (textFormat->getFont() == nullptr) {
                return;
            }

            // check if max provided width is enought to enter one char at least
            if (max_width < text_format->getFont()->getCharPixelWidth(text[0])) {
            // check if max provided width is enough to enter one char at least
            if (maxWidth < textFormat->getFont()->getCharPixelWidth(text[0])) {
                lineEnd = true;
                return;
            }

            auto can_show = text_format->getFont()->getCharCountInSpace(text, max_width - width_used);
            auto canShow = textFormat->getFont()->getCharCountInSpace(text, maxWidth - widthUsed);

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

                auto item   = buildUITextPart("", text_format);
                width_used  = item->getTextNeedSpace();
                height_used = std::max(height_used, item->getTextHeight());
                elements_to_show_in_line.emplace_back(item);
                auto item  = buildUITextPart("", textFormat);
                widthUsed  = item->getTextNeedSpace();
                heightUsed = std::max(heightUsed, item->getTextHeight());
                lineContent.emplace_back(item);
                end = localCursor->getEnd();

                return;
            }

            // create item for show and update Line data
            auto item = buildUITextPart(text.substr(0, can_show), text_format);
            number_letters_shown += can_show;
            width_used += item->getTextNeedSpace();
            height_used = std::max(height_used, item->getTextHeight());
            elements_to_show_in_line.emplace_back(item);
            auto item = buildUITextPart(text.substr(0, canShow), textFormat);
            shownLetterCount += canShow;
            widthUsed += item->getTextNeedSpace();
            heightUsed = std::max(heightUsed, item->getTextHeight());
            lineContent.emplace_back(item);
            end = localCursor->getEnd();

            localCursor += can_show;
            localCursor += canShow;

            if (localCursor.checkAndInvalidateBlockChanged() && localCursor.checkPreviousBlockNewLine()) {
                return;
            }

            // not whole text shown, try again for next line if you want
            if (can_show < text.length()) {
            if (canShow < text.length()) {
                return;
            }

        } while (true);
    }

    TextLine::TextLine(TextLine &&from)
    TextLine::TextLine(TextLine &&from) noexcept
    {
        elements_to_show_in_line = std::move(from.elements_to_show_in_line);
        number_letters_shown     = from.number_letters_shown;
        width_used               = from.width_used;
        height_used              = from.height_used;
        underline                = from.underline;
        drawUnderline            = from.drawUnderline;
        drawUnderlineMode        = from.drawUnderlineMode;
        underlinePadding         = from.underlinePadding;
        lineEnd                  = from.lineEnd;
        end                      = from.end;
        max_width                = from.max_width;
        lineContent       = std::move(from.lineContent);
        shownLetterCount  = from.shownLetterCount;
        widthUsed         = from.widthUsed;
        heightUsed        = from.heightUsed;
        underline         = from.underline;
        drawUnderline     = from.drawUnderline;
        drawUnderlineMode = from.drawUnderlineMode;
        underlinePadding  = from.underlinePadding;
        lineEnd           = from.lineEnd;
        end               = from.end;
        maxWidth          = from.maxWidth;
    }

    TextLine::~TextLine()
    {
        for (auto &el : elements_to_show_in_line) {
        for (auto &el : lineContent) {
            if (el->parent == nullptr) {
                delete el;
            }


@@ 148,14 148,14 @@ namespace gui

    void TextLine::setPosition(const short &x, const short &y)
    {
        auto line_x_position = x;
        auto lineXPosition = x;

        updateUnderline(x, y);

        for (auto &el : elements_to_show_in_line) {
            auto scoped_disown = ScopedParentDisown(el);
            el->setArea({line_x_position, y - underlinePadding, el->getWidth(), el->getHeight()});
            line_x_position += el->getWidth();
        for (auto &el : lineContent) {
            auto scopedDisown = ScopedParentDisown(el);
            el->setArea({lineXPosition, y - underlinePadding, el->getWidth(), el->getHeight()});
            lineXPosition += el->getWidth();
        }
    }



@@ 167,7 167,7 @@ namespace gui

        parent->addWidget(underline);

        for (auto &el : elements_to_show_in_line) {
        for (auto &el : lineContent) {
            parent->addWidget(el);
        }
    }


@@ 175,7 175,7 @@ namespace gui
    Length TextLine::getWidth() const
    {
        Length width = 0;
        for (auto &line : elements_to_show_in_line) {
        for (auto &line : lineContent) {
            width += line->getWidth();
        }
        return width;


@@ 184,22 184,22 @@ namespace gui
    uint32_t TextLine::getWidthTo(unsigned int pos) const
    {
        uint32_t width  = 0;
        auto curent_pos = 0;
        auto currentPos = 0;
        if (pos == text::npos) {
            return 0;
        }
        for (auto &el : elements_to_show_in_line) {
        for (auto &el : lineContent) {
            if (el->getFont() == nullptr) {
                continue;
            }
            if (curent_pos + el->getTextLength() > pos) {
                width += el->getFont()->getPixelWidth(el->getText(), 0, pos - curent_pos);
            if (currentPos + el->getTextLength() > pos) {
                width += el->getFont()->getPixelWidth(el->getText(), 0, pos - currentPos);
                return width;
            }
            else {
                width += el->getWidth();
            }
            curent_pos += el->getTextLength();
            currentPos += el->getTextLength();
        }
        return width;
    }


@@ 207,7 207,7 @@ namespace gui
    UTF8 TextLine::getText(unsigned int pos) const
    {
        UTF8 text;
        for (auto &label : elements_to_show_in_line) {
        for (auto &label : lineContent) {
            if (label->getFont() == nullptr) {
                continue;
            }


@@ 219,7 219,7 @@ namespace gui

    void TextLine::erase()
    {
        for (auto &el : elements_to_show_in_line) {
        for (auto &el : lineContent) {
            if (el->parent != nullptr) {
                auto p = el->parent;
                p->erase(el);


@@ 234,28 234,28 @@ namespace gui
            p->removeWidget(underline);
        }

        elements_to_show_in_line.clear();
        lineContent.clear();
    }

    void TextLine::alignH(Alignment line_align, Length parent_length) const
    void TextLine::alignH(Alignment lineAlign, Length parentLength) const
    {
        Position xOffset = line_align.calculateHAlignment(parent_length, getWidth());
        Position xOffset = lineAlign.calculateHAlignment(parentLength, getWidth());

        if (xOffset >= 0) {

            if (underline != nullptr && drawUnderlineMode == UnderlineDrawMode::Concurrent)
                underline->setPosition(underline->getPosition(Axis::X) + xOffset, Axis::X);

            for (auto &el : elements_to_show_in_line) {
                auto scoped_disown = ScopedParentDisown(el);
            for (auto &el : lineContent) {
                auto scopedDisown = ScopedParentDisown(el);
                el->setPosition(el->getPosition(Axis::X) + xOffset, Axis::X);
            }
        }
    }

    void TextLine::alignV(Alignment line_align, Length parent_length, Length lines_height)
    void TextLine::alignV(Alignment lineAlign, Length parentLength, Length linesHeight)
    {
        Position yOffset = line_align.calculateVAlignment(parent_length, lines_height);
        Position yOffset = lineAlign.calculateVAlignment(parentLength, linesHeight);

        if (yOffset >= 0 && yOffset != storedYOffset) {



@@ 265,22 265,22 @@ namespace gui
            if (underline != nullptr)
                underline->setPosition(underline->getPosition(Axis::Y) + yOffset, Axis::Y);

            for (auto &el : elements_to_show_in_line) {
                auto scoped_disown = ScopedParentDisown(el);
            for (auto &el : lineContent) {
                auto scopedDisown = ScopedParentDisown(el);
                el->setPosition(el->getPosition(Axis::Y) + yOffset, Axis::Y);
            }
        }
    }

    void TextLine::createUnderline(unsigned int width, unsigned int init_height)
    void TextLine::createUnderline(unsigned int width, unsigned int initHeight)
    {
        if (drawUnderline) {

            underline = new Rect(nullptr, 0, 0, max_width, init_height);
            underline = new Rect(nullptr, 0, 0, maxWidth, initHeight);
            underline->setEdges(RectangleEdge::Bottom);

            if (drawUnderlineMode == UnderlineDrawMode::WholeLine) {
                height_used = std::max(height_used, (Length)init_height);
                heightUsed = std::max(heightUsed, (Length)initHeight);
            }
        }
    }

M module-gui/gui/widgets/TextLine.hpp => module-gui/gui/widgets/TextLine.hpp +15 -17
@@ 12,8 12,6 @@

namespace gui
{
    class TextCursor;

    enum class UnderlineDrawMode
    {
        WholeLine,


@@ 23,11 21,11 @@ namespace gui
    /// interface element for TextDocument->getLine() <-- Text
    class TextLine
    {
        unsigned int number_letters_shown = 0;
        Length width_used                 = 0;
        Length height_used                = 0;
        Length max_width                  = 0;
        std::list<Label *> elements_to_show_in_line;
        unsigned int shownLetterCount = 0;
        Length widthUsed              = 0;
        Length heightUsed             = 0;
        Length maxWidth               = 0;
        std::list<Label *> lineContent;
        Rect *underline                     = nullptr;
        bool drawUnderline                  = false;
        UnderlineDrawMode drawUnderlineMode = UnderlineDrawMode::Concurrent;


@@ 43,7 41,7 @@ namespace gui
        /// creates TextLine with data from text based on TextCursor position filling max_width
        TextLine(BlockCursor &, unsigned int max_width);
        TextLine(TextLine &) = delete;
        TextLine(TextLine &&);
        TextLine(TextLine &&) noexcept;

        TextLine(BlockCursor &cursor,
                 unsigned int max_width,


@@ 65,23 63,23 @@ namespace gui
        /// number of letters in Whole TextLines
        [[nodiscard]] unsigned int length() const
        {
            return number_letters_shown;
            return shownLetterCount;
        }

        /// count of elements in whole TextLine
        [[nodiscard]] unsigned int count() const
        {
            return elements_to_show_in_line.size();
            return lineContent.size();
        }

        [[nodiscard]] Length width() const
        {
            return width_used;
            return widthUsed;
        }

        [[nodiscard]] Length height() const
        {
            return height_used;
            return heightUsed;
        }

        [[nodiscard]] TextBlock::End getEnd() const


@@ 94,10 92,10 @@ namespace gui
            return lineEnd;
        }

        const Item *getElement(unsigned int pos) const
        [[nodiscard]] const Item *getElement(unsigned int pos) const
        {
            unsigned int local_pos = 0;
            for (auto &el : elements_to_show_in_line) {
            for (auto &el : lineContent) {
                local_pos += el->getTextLength();
                if (local_pos >= pos) {
                    return el;


@@ 106,9 104,9 @@ namespace gui
            return nullptr;
        }

        int32_t getX() const
        [[nodiscard]] int32_t getX() const
        {
            return elements_to_show_in_line.front()->area().pos(Axis::X);
            return lineContent.front()->area().pos(Axis::X);
        }

        void setPosition(const short &x, const short &y);


@@ 121,6 119,6 @@ namespace gui
        /// moves Text parts in Text. To not call n times callbacks on resize, call prior to setting parent
        void alignH(Alignment align, Length parent_length) const;
        void alignV(Alignment align, Length parent_length, Length lines_height);
        auto getText(unsigned int pos) const -> UTF8;
        [[nodiscard]] auto getText(unsigned int pos) const -> UTF8;
    };
} // namespace gui

M module-gui/gui/widgets/TextLineCursor.cpp => module-gui/gui/widgets/TextLineCursor.cpp +79 -0
@@ 2,3 2,82 @@
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#include "TextLineCursor.hpp"
#include "Text.hpp"
#include "log/log.hpp"

#define debug_text_cursor(...)
// #define debug_text_cursor(...) LOG_DEBUG(__VA_ARGS__)

gui::TextLineCursor::TextLineCursor(gui::Text *parent, unsigned int pos, unsigned int block)
    : TextCursor(parent, pos, block)
{}

auto gui::TextLineCursor::moveCursor(gui::NavigationDirection direction) -> gui::TextCursor::Move
{
    debug_text_cursor("Before move cursor: screen pos: %d block: %d pos: %d %s",
                      pos_on_screen,
                      getBlockNr(),
                      BlockCursor::getPosition(),
                      atBegin() ? "at begin" : "middle");

    if (!checkDocument()) {
        return Move::Error;
    }

    auto [selectedLine, selectedLineCursorPos, selectedLineNr] = getSelectedLine();

    /// up - corner case
    if ((checkNpos() || (direction == NavigationDirection::UP && selectedLineNr == 0))) {
        return Move::Start;
    }

    /// down - corner case
    if ((checkNpos() || (direction == NavigationDirection::DOWN && selectedLineNr == text->lines->size() - 1))) {
        return Move::End;
    }

    if (direction == NavigationDirection::UP) {

        auto previousLine            = text->lines->getLine(selectedLineNr - 1);
        auto previousLineEndAddition = previousLine->getEnd() == TextBlock::End::Newline ? 1 : 0;

        auto previousLineMoveCount = previousLine->length() > selectedLineCursorPos
                                         ? previousLine->length() - previousLineEndAddition - selectedLineCursorPos
                                         : 0;

        auto moveCount = selectedLineCursorPos + previousLineEndAddition + previousLineMoveCount;

        TextCursor::moveCursor(NavigationDirection::LEFT, moveCount);

        debug_text_cursor("After move cursor: screen pos: %d block: %d pos: %d %s",
                          pos_on_screen,
                          getBlockNr(),
                          BlockCursor::getPosition(),
                          atBegin() ? "at begin" : "middle");

        return Move::Up;
    }

    if (direction == NavigationDirection::DOWN) {

        auto nextLine            = text->lines->getLine(selectedLineNr + 1);
        auto nextLineEndAddition = nextLine->getEnd() == TextBlock::End::Newline ? 1 : 0;

        auto nextLineMoveCount = nextLine->length() > selectedLineCursorPos ? selectedLineCursorPos
                                                                            : nextLine->length() - nextLineEndAddition;

        auto moveCount = (selectedLine->length() - selectedLineCursorPos) + nextLineMoveCount;

        TextCursor::moveCursor(NavigationDirection::RIGHT, moveCount);

        debug_text_cursor("After move cursor: screen pos: %d block: %d pos: %d %s",
                          pos_on_screen,
                          getBlockNr(),
                          BlockCursor::getPosition(),
                          atBegin() ? "at begin" : "middle");

        return Move::Down;
    }

    return TextCursor::moveCursor(direction);
}

M module-gui/gui/widgets/TextLineCursor.hpp => module-gui/gui/widgets/TextLineCursor.hpp +9 -23
@@ 7,31 7,17 @@

namespace gui
{
    class Text;
    class TextDocument;

    class TextLineCursor : public TextCursor
    {
      protected:
      public:
        using TextCursor::TextCursor;

        uint32_t getScreenLine() const
        {
            return line;
        }

        auto moveCursor(NavigationDirection direction) -> Move
        {
            Move movement = TextCursor::moveCursor(direction);
        explicit TextLineCursor(gui::Text *parent, unsigned int pos = text::npos, unsigned int block = text::npos);
        TextLineCursor() = delete;

            if (movement == Move::Down) {
                line++;
            }
            else if (movement == Move::Up) {
                line--;
            }

            return movement;
        }

      private:
        uint32_t line = 0;
        auto moveCursor(NavigationDirection direction) -> Move override;
    };
} // namespace gui
\ No newline at end of file

} // namespace gui

M module-gui/test/test-catch-text/test-gui-Text.cpp => module-gui/test/test-catch-text/test-gui-Text.cpp +2 -2
@@ 49,7 49,7 @@ namespace gui
    class TestText : public Text
    {
      public:
        unsigned int linesSize()
        [[nodiscard]] unsigned int linesSize()
        {
            return lines->get().size();
        }


@@ 60,7 60,7 @@ namespace gui
            return;
        }

        auto &linesGet()
        [[nodiscard]] auto &linesGet()
        {
            return lines->get();
        }

M module-gui/test/test-catch-text/test-gui-TextLineCursor.cpp => module-gui/test/test-catch-text/test-gui-TextLineCursor.cpp +177 -44
@@ 7,66 7,199 @@
#include <mock/buildTextDocument.hpp>
#include <module-gui/gui/widgets/Text.hpp>
#include <mock/multi-line-string.hpp>
#include "Font.hpp"
#include "InitializedFontManager.hpp"

namespace gui
{
    class TestText : public Text
    {
      public:
        [[nodiscard]] unsigned int linesSize()
        {
            return lines->get().size();
        }

        [[nodiscard]] auto &linesGet()
        {
            return lines->get();
        }

        auto moveCursor(NavigationDirection direction, unsigned int n)
        {
            cursor->TextCursor::moveCursor(direction, n);
        }

        [[nodiscard]] auto getSelectedLine()
        {
            return cursor->getSelectedLine();
        }
    };
} // namespace gui

// To be corrected with Text scrolling
TEST_CASE("TextLineCursor", "[.]")
TEST_CASE("TextLineCursor")
{
    using namespace gui;

    SECTION("default")
    std::string testStringShortLine  = "Test Line ";                     // 10 sings
    std::string testStringNormalLine = "Test Normal No Line ";           // 20 sings
    std::string testStringLongLine   = "Test Long Long Long Long Line "; // 30 sings

    SECTION("Default position three lines text")
    {
        auto texts            = mockup::lineStrings(3);
        auto [document, font] = mockup::buildMultilineTestDocument(texts);
        gui::Text text;
        text.setText(std::make_unique<gui::TextDocument>(document));
        auto cursor = new gui::TextLineCursor(&text);
        mockup::fontManager();
        auto text = new gui::TestText();
        text->setMaximumSize(400, 200);

        REQUIRE(cursor->getScreenLine() == 0);
    }
        text->addText(TextBlock(testStringShortLine, Font(27).raw(), TextBlock::End::Newline));
        text->addText(TextBlock(testStringNormalLine, Font(27).raw(), TextBlock::End::Newline));
        text->addText(TextBlock(testStringLongLine, Font(27).raw(), TextBlock::End::None));

    SECTION("move cursor down")
    {
        auto texts            = mockup::lineStrings(3);
        auto [document, font] = mockup::buildMultilineTestDocument(texts);

        gui::Text text;
        text.setText(std::make_unique<gui::TextDocument>(document));
        auto cursor = new gui::TextLineCursor(&text);
        cursor->moveCursor(NavigationDirection::DOWN);
        REQUIRE(cursor->getScreenLine() == 1);
        auto [selectedLine, selectedLineCursorPos, selectedLineNr] = text->getSelectedLine();

        REQUIRE(selectedLine->getText(0).c_str() == testStringLongLine);
        REQUIRE(selectedLineCursorPos == testStringLongLine.length());
        REQUIRE(selectedLineNr == 2);
    }

    SECTION("move cursor right")
    SECTION("Movement three lines text test -> short|normal|long")
    {
        auto texts            = mockup::lineStrings(3);
        auto [document, font] = mockup::buildMultilineTestDocument(texts);
        mockup::fontManager();
        auto text = new gui::TestText();
        text->setMaximumSize(400, 200);

        gui::Text text;
        text.setText(std::make_unique<gui::TextDocument>(document));
        auto cursor = new gui::TextLineCursor(&text);
        cursor->moveCursor(NavigationDirection::RIGHT);
        REQUIRE(cursor->getScreenLine() == 0);
        std::tuple<const TextLine *, unsigned int, unsigned int> selectedLine;

        for (size_t i = 0; i < document.getBlocks().front().length(); i++) {
            cursor->moveCursor(NavigationDirection::RIGHT);
        }
        REQUIRE(cursor->getScreenLine() == 1);
        text->addText(TextBlock(testStringShortLine, Font(27).raw(), TextBlock::End::Newline));
        text->addText(TextBlock(testStringNormalLine, Font(27).raw(), TextBlock::End::Newline));
        text->addText(TextBlock(testStringLongLine, Font(27).raw(), TextBlock::End::None));

        selectedLine = text->getSelectedLine();
        REQUIRE(std::get<0>(selectedLine)->getText(0).c_str() == testStringLongLine);
        REQUIRE(std::get<1>(selectedLine) == testStringLongLine.length());
        REQUIRE(std::get<2>(selectedLine) == 2);

        // Move Down nothing should happen as cursor is at bottom and right end
        text->moveCursor(gui::NavigationDirection::DOWN, 1);

        selectedLine = text->getSelectedLine();
        REQUIRE(std::get<0>(selectedLine)->getText(0).c_str() == testStringLongLine);
        REQUIRE(std::get<1>(selectedLine) == testStringLongLine.length());
        REQUIRE(std::get<2>(selectedLine) == 2);

        // Move Right nothing should happen as cursor is at bottom and right end
        text->moveCursor(gui::NavigationDirection::RIGHT, 1);

        selectedLine = text->getSelectedLine();
        REQUIRE(std::get<0>(selectedLine)->getText(0).c_str() == testStringLongLine);
        REQUIRE(std::get<1>(selectedLine) == testStringLongLine.length());
        REQUIRE(std::get<2>(selectedLine) == 2);

        // Move Left to line center, line should not change but position should
        text->moveCursor(gui::NavigationDirection::LEFT, (testStringLongLine.length() / 2));

        selectedLine = text->getSelectedLine();
        REQUIRE(std::get<0>(selectedLine)->getText(0).c_str() == testStringLongLine);
        REQUIRE(std::get<1>(selectedLine) == (testStringLongLine.length() / 2));
        REQUIRE(std::get<2>(selectedLine) == 2);

        // Move Up line should change and cursor should be at corresponding position as it fits in upper line
        text->moveCursor(gui::NavigationDirection::UP, 1);

        selectedLine = text->getSelectedLine();
        REQUIRE(std::get<0>(selectedLine)->getText(0).c_str() == testStringNormalLine + "\n");
        REQUIRE(std::get<1>(selectedLine) == (testStringLongLine.length() / 2));
        REQUIRE(std::get<2>(selectedLine) == 1);

        // Move Up line should change and cursor should be at its end as corresponding position does not fit
        text->moveCursor(gui::NavigationDirection::UP, 1);

        selectedLine = text->getSelectedLine();
        REQUIRE(std::get<0>(selectedLine)->getText(0).c_str() == testStringShortLine + "\n");
        REQUIRE(std::get<1>(selectedLine) == testStringShortLine.length());
        REQUIRE(std::get<2>(selectedLine) == 0);

        // Move Up line should not change and cursor should not change as it is first line
        text->moveCursor(gui::NavigationDirection::UP, 1);

        selectedLine = text->getSelectedLine();
        REQUIRE(std::get<0>(selectedLine)->getText(0).c_str() == testStringShortLine + "\n");
        REQUIRE(std::get<1>(selectedLine) == testStringShortLine.length());
        REQUIRE(std::get<2>(selectedLine) == 0);

        // Move Left to line and text beginning
        text->moveCursor(gui::NavigationDirection::LEFT, testStringShortLine.length());

        selectedLine = text->getSelectedLine();
        REQUIRE(std::get<0>(selectedLine)->getText(0).c_str() == testStringShortLine + "\n");
        REQUIRE(std::get<1>(selectedLine) == 0);
        REQUIRE(std::get<2>(selectedLine) == 0);

        // Move two times Down line should change and cursor should be at its beginning
        text->moveCursor(gui::NavigationDirection::DOWN, 2);

        selectedLine = text->getSelectedLine();
        REQUIRE(std::get<0>(selectedLine)->getText(0).c_str() == testStringLongLine);
        REQUIRE(std::get<1>(selectedLine) == 0);
        REQUIRE(std::get<2>(selectedLine) == 2);

        // Move one time Left line should change and cursor should be at its end
        text->moveCursor(gui::NavigationDirection::LEFT, 1);

        selectedLine = text->getSelectedLine();
        REQUIRE(std::get<0>(selectedLine)->getText(0).c_str() == testStringNormalLine + "\n");
        REQUIRE(std::get<1>(selectedLine) == testStringNormalLine.length());
        REQUIRE(std::get<2>(selectedLine) == 1);
    }

    SECTION("move cursor to end")
    SECTION("Movement three lines text test -> long|short|normal")
    {
        auto texts            = mockup::lineStrings(3);
        auto [document, font] = mockup::buildMultilineTestDocument(texts);
        mockup::fontManager();
        auto text = new gui::TestText();
        text->setMaximumSize(400, 200);

        gui::Text text;
        text.setText(std::make_unique<gui::TextDocument>(document));
        auto cursor = new gui::TextLineCursor(&text);
        cursor->moveCursor(NavigationDirection::RIGHT);
        REQUIRE(cursor->getScreenLine() == 0);
        std::tuple<const TextLine *, unsigned int, unsigned int> selectedLine;

        for (size_t i = 0; i < document.getText().length(); i++) {
            cursor->moveCursor(NavigationDirection::RIGHT);
        }
        REQUIRE(cursor->getScreenLine() == 2);
        text->addText(TextBlock(testStringLongLine, Font(27).raw(), TextBlock::End::Newline));
        text->addText(TextBlock(testStringShortLine, Font(27).raw(), TextBlock::End::Newline));
        text->addText(TextBlock(testStringNormalLine, Font(27).raw(), TextBlock::End::None));

        selectedLine = text->getSelectedLine();
        REQUIRE(std::get<0>(selectedLine)->getText(0).c_str() == testStringNormalLine);
        REQUIRE(std::get<1>(selectedLine) == testStringNormalLine.length());
        REQUIRE(std::get<2>(selectedLine) == 2);

        // Move Up two times, line should change and cursor should be at short line length position
        text->moveCursor(gui::NavigationDirection::UP, 2);

        selectedLine = text->getSelectedLine();
        REQUIRE(std::get<0>(selectedLine)->getText(0).c_str() == testStringLongLine + "\n");
        REQUIRE(std::get<1>(selectedLine) == testStringShortLine.length());
        REQUIRE(std::get<2>(selectedLine) == 0);

        // Move Right three times, only cursor pos should change
        text->moveCursor(gui::NavigationDirection::RIGHT, 3);

        selectedLine = text->getSelectedLine();
        REQUIRE(std::get<0>(selectedLine)->getText(0).c_str() == testStringLongLine + "\n");
        REQUIRE(std::get<1>(selectedLine) == testStringShortLine.length() + 3);
        REQUIRE(std::get<2>(selectedLine) == 0);

        // Move Down one time, line should change and cursor should be at short line end as corresponding do not fit
        text->moveCursor(gui::NavigationDirection::DOWN, 1);

        selectedLine = text->getSelectedLine();
        REQUIRE(std::get<0>(selectedLine)->getText(0).c_str() == testStringShortLine + "\n");
        REQUIRE(std::get<1>(selectedLine) == testStringShortLine.length());
        REQUIRE(std::get<2>(selectedLine) == 1);

        // Move Right one time, line should change and cursor should be at next line beginning
        text->moveCursor(gui::NavigationDirection::RIGHT, 1);

        selectedLine = text->getSelectedLine();
        REQUIRE(std::get<0>(selectedLine)->getText(0).c_str() == testStringNormalLine);
        REQUIRE(std::get<1>(selectedLine) == 0);
        REQUIRE(std::get<2>(selectedLine) == 2);
    }
}
\ No newline at end of file
}