~aleteoryx/muditaos

782980b261457bc9a54235bd1a0fc90a6bd1484e — PrzeBrudny 5 years ago b481486
[EGD-4372] Text addition boundaries added with tests. Removed old bounds checking. Text blocks on newlines split issue fixed. Text widgets cleanups. (#1020)

M changelog.md => changelog.md +1 -0
@@ 10,6 10,7 @@
* `[cellular]` Added change PIN functionality
* `[cellular]` Added possibility of unlock SIM card (no PIN on start) functionality
* `[testing]` Added test harness with message sending case
* `[text]` Text input boundaries support added (max signs or max widget area).

### Changed


M module-gui/gui/widgets/Lines.cpp => module-gui/gui/widgets/Lines.cpp +63 -97
@@ 4,129 4,95 @@
#include "Lines.hpp"
#include "TextLineCursor.hpp"
#include "Text.hpp"
#include "RawFont.hpp"

#if DEBUG_GUI_TEXT_LINES == 1
#define debug_text_lines(...) LOG_DEBUG(__VA_ARGS__)
#else
#define debug_text_lines(...)
#endif

namespace gui
{
    // LEFT/RIGHT/UP/DOWN
    auto Lines::checkNavigationBounds(TextLineCursor &cursor, InputEvent event) -> gui::InputBound
    {
        auto dir = inputToNavigation(event);
        if (dir == NavigationDirection::NONE) {
            return InputBound::UNDEFINED;
        }

        auto screen_bound = scroll_position + max_lines_count - 1;
        auto lines_bound  = lines.size() - 1;

        if (dir == NavigationDirection::UP || dir == NavigationDirection::LEFT) {
            screen_bound = 0;
            lines_bound  = 0;
        }

        if (dir == NavigationDirection::LEFT && cursor.getPosOnScreen() > 0) {
            return InputBound::CAN_MOVE;
        }

        unsigned int pos = cursor.BlockCursor::getPosition();
        auto textLine    = getTextLine(cursor.getScreenLine());

        if (textLine == nullptr) {
            return InputBound::NO_DATA;
        }

        size_t lineLength = textLine->length();

        if (dir == NavigationDirection::RIGHT && pos < lineLength) {
            return InputBound::CAN_MOVE;
        }

        if (cursor.getScreenLine() >= lines_bound) {
            return InputBound::NO_DATA;
    auto Lines::linesVAlign(Length parentSize) -> void
    {
        for (auto &line : lines) {
            line.alignV(text->getAlignment(Axis::Y), parentSize, linesHeight());
        }
    }

        if (cursor.getScreenLine() >= screen_bound) {
            return InputBound::HIT_BOUND;
    auto Lines::linesHAlign(Length parentSize) -> void
    {
        for (auto &line : lines) {
            line.alignH(text->getAlignment(Axis::X), parentSize);
        }

        return InputBound::CAN_MOVE;
    }

    auto Lines::checkAdditionBounds(TextLineCursor &cursor, InputEvent event) -> gui::InputBound
    auto Lines::draw(BlockCursor &drawCursor, Length w, Length h, Position lineYPosition, Position lineXPosition)
        -> void
    {
        auto keymap = parent->mode != nullptr ? parent->mode->get() : "";
        auto code   = gui::Profiles::get(keymap).get(event.key.key_code, 0);
        while (true) {
            auto textLine = gui::TextLine(drawCursor, w);

        auto format        = cursor->getFormat();
        uint32_t line      = cursor.getScreenLine();
        TextLine *textLine = getTextLine(line);
            if (textLine.length() == 0 && textLine.getLineEnd()) {
                debug_text_lines("cant show more text from this document");
                break;
            }

        if (textLine == nullptr || !cursor) {
            return InputBound::CAN_ADD;
        }
            if (lineYPosition + textLine.height() > h) { // no more space for next line
                debug_text_lines("no more space for next text_line: %d + %" PRIu32 " > %" PRIu32,
                                 lineYPosition,
                                 textLine.height(),
                                 h);
                break;
            }

        auto bound = textLine->checkBounds(cursor, code, format);
        if (bound == InputBound::CANT_PROCESS && line == scroll_position) {
            // TODO -> to be corrected in next PR
            return InputBound::CAN_ADD;
        }
            emplace(std::move(textLine));
            auto &line = last();

        return InputBound::CAN_ADD;
    }
            line.setPosition(lineXPosition, lineYPosition);
            line.setParent(text);

    auto Lines::checkRemovalBounds(TextLineCursor &cursor, InputEvent event) -> gui::InputBound
    {
        if (lines.empty()) {
            return InputBound::CANT_PROCESS;
            lineYPosition += line.height();
        }
    }

        // TODO -> to be corrected in next PR
        return InputBound::CAN_REMOVE;
    auto Lines::draw(BlockCursor &drawCursor,
                     Length w,
                     Length h,
                     Position lineYPosition,
                     Position lineXPosition,
                     unsigned int linesCount) -> void
    {
        Length initHeight = text->getTextFormat().getFont()->info.line_height;

        uint32_t line = cursor.getScreenLine();
        uint32_t pos  = cursor.getPosOnScreen();
        while (true) {
            auto textLine =
                gui::TextLine(drawCursor, w, initHeight, underLine, UnderlineDrawMode::WholeLine, underLinePadding);

        if (pos == 0) {
            if (line == scroll_position + max_lines_count) {
                return InputBound::HIT_BOUND;
            }
            if (line == 0) {
                return InputBound::CANT_PROCESS;
            if (textLine.height() > 0 && initHeight != textLine.height()) {
                initHeight = textLine.height();
            }
        }

        return InputBound::CAN_REMOVE;
    }

    void Lines::updateScrollPosition(NavigationDirection dir, unsigned int lines_to_scroll)
    {
        if (dir == NavigationDirection::UP) {
            scroll_position -= lines_to_scroll;
        }
            if (lineYPosition + initHeight > h) {
                break;
            }

        if (dir == NavigationDirection::DOWN) {
            scroll_position += lines_to_scroll;
        }
    }
            if (lines.size() >= linesCount) {
                break;
            }

    void Lines::linesVAlign(Length parentSize)
    {
        for (auto &line : lines) {
            line.alignV(parent->getAlignment(Axis::Y), parentSize, linesHeight());
        }
    }
            emplace(std::move(textLine));
            auto &line = last();
            line.setPosition(lineXPosition, lineYPosition);
            line.setParent(text);

    void Lines::linesHAlign(Length parentSize)
    {
        for (auto &line : lines) {
            line.alignH(parent->getAlignment(Axis::X), parentSize);
            lineYPosition += line.height();
        }
    }

    void Lines::draw(TextCursor &cursor)
    {
        parent->drawLines();
    }

    TextLine *Lines::getTextLine(uint32_t line)
    {
        if (lines.empty() || line >= lines.size()) {


@@ 135,6 101,6 @@ namespace gui

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

} // namespace gui
\ No newline at end of file
} // namespace gui

M module-gui/gui/widgets/Lines.hpp => module-gui/gui/widgets/Lines.hpp +40 -10
@@ 22,21 22,24 @@ namespace gui

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

        uint32_t max_lines_count = 4;
        uint32_t scroll_position = 0;

        bool underLine            = false;
        Position underLinePadding = 0;

      public:
        Lines(Text *parent) : parent(parent)
        Lines(Text *text) : text(text)
        {}

        ~Lines() = default;

        void erase()
        {
            if (parent != nullptr) {
            if (text != nullptr) {
                for (auto &line : lines) {
                    line.erase();
                }


@@ 64,6 67,11 @@ namespace gui
            return lines.size();
        }

        [[nodiscard]] auto empty() const noexcept -> bool
        {
            return lines.empty();
        }

        auto maxWidth()
        {
            unsigned int w = 0;


@@ 83,14 91,36 @@ namespace gui
            return h;
        }

        void draw(TextCursor &cursor);
        auto setUnderLine(bool val) -> void
        {
            underLine = val;
        }

        auto getUnderLine() -> bool
        {
            return underLine;
        }

        auto setUnderLinePadding(Position val) -> void
        {
            underLinePadding = val;
        }

        auto getUnderLinePadding() -> Position
        {
            return underLinePadding;
        }

        auto draw(BlockCursor &drawCursor, Length w, Length h, Position lineYPosition, Position lineXPosition) -> void;
        auto draw(BlockCursor &drawCursor,
                  Length w,
                  Length h,
                  Position lineYPosition,
                  Position lineXPosition,
                  unsigned int linesCount) -> void;

        void linesHAlign(Length parentSize);
        void linesVAlign(Length parentSize);
        auto checkNavigationBounds(TextLineCursor &cursor, InputEvent event) -> InputBound;
        auto checkAdditionBounds(TextLineCursor &cursor, InputEvent event) -> InputBound;
        auto checkRemovalBounds(TextLineCursor &cursor, InputEvent event) -> InputBound;
        void updateScrollPosition(NavigationDirection scroll, unsigned int lines_to_scroll = 1);
        auto linesHAlign(Length parentSize) -> void;
        auto linesVAlign(Length parentSize) -> void;

      protected:
        TextLine *getTextLine(uint32_t line);

M module-gui/gui/widgets/Text.cpp => module-gui/gui/widgets/Text.cpp +426 -149
@@ 95,10 95,29 @@ namespace gui
    void Text::setTextType(TextType type)
    {}

    void Text::setTextLimitType(TextLimitType limitType, unsigned int val)
    {
        auto it = std::find_if(limitsList.begin(), limitsList.end(), [&limitType](const TextLimit &limitOnList) {
            return limitOnList.limitType == limitType;
        });

        if (it != limitsList.end()) {
            (*it).limitValue = val;
        }
        else {
            limitsList.emplace_back(TextLimit{limitType, val});
        }
    }

    void Text::clearTextLimits()
    {
        limitsList.clear();
    }

    void Text::setUnderline(const bool val)
    {
        if (underline != val) {
            underline = val;
        if (lines->getUnderLine() != val) {
            lines->setUnderLine(val);
            drawLines();
        }
    }


@@ 106,8 125,7 @@ namespace gui
    void Text::setText(const UTF8 &text)
    {
        debug_text("setText: %s", text.c_str());
        /// TODO here should be format passed
        setText(std::make_unique<TextDocument>(textToTextBlocks(text, format.getFont(), TextBlock::End::None)));
        setText(std::make_unique<TextDocument>(textToTextBlocks(text, format)));
    }

    void Text::setText(std::unique_ptr<TextDocument> &&doc)


@@ 196,12 214,6 @@ namespace gui
        buildCursor();
    }

    std::tuple<NavigationDirection, uint32_t> scrollView(const TextCursor &cursor)
    {
        uint32_t scrolledLines = 1;
        return {NavigationDirection::UP, scrolledLines};
    }

    bool Text::onInput(const InputEvent &evt)
    {
        if (Rect::onInput(evt)) {


@@ 228,7 240,7 @@ namespace gui
            debug_text("handleNavigation");
            return true;
        }
        if (handleBackspace(evt)) {
        if (handleRemovalChar(evt)) {
            debug_text("handleBackspace");
            return true;
        }


@@ 287,69 299,21 @@ namespace gui
        return true;
    }

    bool Text::handleNavigation(const InputEvent &inputEvent)
    auto Text::getSizeMinusPadding(Axis axis, Area val) -> Length
    {
        if (!inputEvent.isShortPress()) {
            return false;
        }
        auto size = area(val).size(axis);

        if (lines->checkNavigationBounds(*cursor, inputEvent) == InputBound::CAN_MOVE) {

            if (isMode(EditMode::SCROLL) && (inputEvent.is(KeyCode::KEY_LEFT) || inputEvent.is(KeyCode::KEY_RIGHT))) {
                debug_text("Text in scroll mode ignores left/right navigation");
                return false;
            }
            auto ret = cursor->moveCursor(inputToNavigation(inputEvent));
            debug_text("moveCursor: %s", c_str(ret));
            if (ret == TextCursor::Move::Start || ret == TextCursor::Move::End) {
                debug_text("scrolling needs implementing");
                return false;
            }
            if (ret != TextCursor::Move::Error) {
                return true;
            }
        if (size <= padding.getSumInAxis(axis)) {
            size = 0;
        }

        return false;
    }

    bool Text::handleEnter()
    {
        return false;
    }

    bool Text::addChar(uint32_t utf_value)
    {
        cursor->addChar(utf_value);
        auto block = document->getBlock(cursor);

        if (block == nullptr) {
            return false;
        else {
            size -= padding.getSumInAxis(axis);
        }
        debug_text("value: %d, block len: %d", utf_value, block->length());
        return true;
        return size;
    }

    void Text::drawCursor()
    auto Text::applyParentSizeRestrictions() -> void
    {
        cursor->updateView();
    }

    void Text::drawLines()
    {
        lines->erase();

        auto sizeMinusPadding = [&](Axis axis, Area val) {
            auto size = area(val).size(axis);
            if (size <= padding.getSumInAxis(axis)) {
                size = 0;
            }
            else {
                size -= padding.getSumInAxis(axis);
            }
            return size;
        };

        // Check if Parent does not have max size restrictions.
        if (this->parent != nullptr) {
            area(Area::Max).w = parent->area(Area::Max).w != 0 ? std::min(parent->area(Area::Max).w, area(Area::Max).w)


@@ 357,71 321,50 @@ namespace gui
            area(Area::Max).h = parent->area(Area::Max).h != 0 ? std::min(parent->area(Area::Max).h, area(Area::Max).h)
                                                               : area(Area::Max).h;
        }
    }

        Length w = sizeMinusPadding(Axis::X, Area::Max);
        Length h = sizeMinusPadding(Axis::Y, Area::Max);
    auto Text::drawCursor() -> void
    {
        cursor->updateView();
    }

    auto Text::drawLines() -> void
    {
        lines->erase();

        auto lineYPosition = padding.top;
        applyParentSizeRestrictions();

        BlockCursor drawCursor(cursor->getDocument(), 0, 0, getTextFormat().getFont());

        debug_text("--> START drawLines: {%" PRIu32 ", %" PRIu32 "}", w, h);

        auto lineXPosition = padding.left;
        do {
            auto textLine = gui::TextLine(drawCursor, w);
        lines->draw(drawCursor,
                    getSizeMinusPadding(Axis::X, Area::Max),
                    getSizeMinusPadding(Axis::Y, Area::Max),
                    padding.top,
                    padding.left);

            if (textLine.length() == 0 && textLine.getLineEnd()) {
                debug_text("cant show more text from this document");
                break;
            }
        calculateAndRequestSize();

            if (lineYPosition + textLine.height() > h) { // no more space for next line
                debug_text("no more space for next text_line: %d + %" PRIu32 " > %" PRIu32,
                           lineYPosition,
                           textLine.height(),
                           h);
                lineYPosition += textLine.height();
                break;
            }
        lines->linesHAlign(getSizeMinusPadding(Axis::X, Area::Normal));
        lines->linesVAlign(getSizeMinusPadding(Axis::Y, Area::Normal));

            // for each different text which fits in line, addWidget last - to not trigger callbacks to parent
            // on resizes while not needed, after detach there can be no `break` othervise there will be leak - hence
            // detach
            lines->emplace(std::move(textLine));
            auto &line = lines->last();

            line.setPosition(lineXPosition, lineYPosition);
            line.setParent(this);

            lineYPosition += line.height();

            debug_text(
                "debug text drawing: \n cursor pos: %d line length: %d : document length "
                "%d \n x: %d, y: %d \n%s \n, lines count %lu",
                drawCursor.getPosition(),
                lines->last().length(),
                document->getText().length(),
                lineXPosition,
                lineYPosition,
                [&]() -> std::string {
                    std::string text = document->getText();
                    return text;
                }()
                             .c_str(),
                lines->size());
        } while (true);
        debug_text("<- END\n");
    }

    auto Text::calculateAndRequestSize() -> void
    {
        // silly case resize - there request space and all is nice
        // need to at least erase last line if it wont fit
        // should be done on each loop
        {
            Length hUsed = lineYPosition + padding.bottom;
            Length hUsed = padding.top + lines->linesHeight() + padding.bottom;
            Length wUsed = lines->maxWidth() + padding.getSumInAxis(Axis::X);

            if (lines->size() == 0) {
                debug_text("No lines to show, try to at least fit in cursor");
                if (format.getFont() != nullptr && lineYPosition < format.getFont()->info.line_height) {
                if (format.getFont() != nullptr &&
                    padding.top + lines->linesHeight() < format.getFont()->info.line_height) {
                    hUsed = format.getFont()->info.line_height;
                    wUsed = TextCursor::default_width;
                    debug_text("empty line height: %d", hUsed);


@@ 436,11 379,6 @@ namespace gui
                    debug_text("No free height for text!");
                }
            }

            lines->linesHAlign(sizeMinusPadding(Axis::X, Area::Normal));
            lines->linesVAlign(sizeMinusPadding(Axis::Y, Area::Normal));

            debug_text("<- END\n");
        }
    }



@@ 466,14 404,22 @@ namespace gui

    void Text::buildDocument(const UTF8 &text)
    {
        buildDocument(std::make_unique<TextDocument>(textToTextBlocks(text, format.getFont(), TextBlock::End::None)));
        buildDocument(std::make_unique<TextDocument>(textToTextBlocks(text, format)));
    }

    void Text::buildDocument(std::unique_ptr<TextDocument> &&document_moved)
    {
        document = std::move(document_moved);
        debug_text("document text: %s", document->getText().c_str());
        if (document_moved == nullptr) {
            debug_text("Passed invalid document, returning without setting it");
            return;
        }
        document->destroy();
        buildCursor();

        for (const auto &block : document_moved->getBlocks()) {
            *cursor << block;
        }
        debug_text("document text: %s", document->getText().c_str());
        drawLines();
    }



@@ 491,7 437,7 @@ namespace gui
        cursor->setVisible(focus && isMode(EditMode::EDIT));
    }

    bool Text::handleRotateInputMode(const InputEvent &inputEvent)
    auto Text::handleRotateInputMode(const InputEvent &inputEvent) -> bool
    {
        if (mode != nullptr && inputEvent.isShortPress() && inputEvent.keyCode == gui::KeyCode::KEY_AST) {
            mode->next();


@@ 500,7 446,7 @@ namespace gui
        return false;
    }

    bool Text::handleRestoreInputModeUI(const InputEvent &inputEvent)
    auto Text::handleRestoreInputModeUI(const InputEvent &inputEvent) -> bool
    {
        if (mode != nullptr && inputEvent.isKeyRelease()) {
            mode->show_restore();


@@ 508,7 454,7 @@ namespace gui
        return false;
    }

    bool Text::handleSelectSpecialChar(const InputEvent &inputEvent)
    auto Text::handleSelectSpecialChar(const InputEvent &inputEvent) -> bool
    {

        if (mode != nullptr && inputEvent.isLongPress() && inputEvent.keyCode == gui::KeyCode::KEY_AST) {


@@ 518,53 464,73 @@ namespace gui
        return false;
    }

    bool Text::handleActivation(const InputEvent &inputEvent)
    auto Text::handleActivation(const InputEvent &inputEvent) -> bool
    {
        return inputEvent.isShortPress() && inputEvent.is(KeyCode::KEY_AST) && Rect::onActivated(nullptr);
    }

    bool Text::handleBackspace(const InputEvent &inputEvent)
    auto Text::handleNavigation(const InputEvent &inputEvent) -> bool
    {
        if (!inputEvent.isShortPress()) {
            return false;
        }

        if (isMode(EditMode::SCROLL) && (inputEvent.is(KeyCode::KEY_LEFT) || inputEvent.is(KeyCode::KEY_RIGHT))) {
            debug_text("Text in scroll mode ignores left/right navigation");
            return false;
        }
        auto ret = cursor->moveCursor(inputToNavigation(inputEvent));
        debug_text("moveCursor: %s", c_str(ret));
        if (ret == TextCursor::Move::Start || ret == TextCursor::Move::End) {
            debug_text("scrolling needs implementing");
            return false;
        }
        if (ret != TextCursor::Move::Error) {
            return true;
        }

        return false;
    }

    bool Text::handleRemovalChar(const InputEvent &inputEvent)
    {
        if (!isMode(EditMode::EDIT)) {
            return false;
        }
        if (lines->checkRemovalBounds(*cursor, inputEvent) == InputBound::CAN_REMOVE) {
            if (inputEvent.isShortPress() && inputEvent.is(key_signs_remove)) {
                if (!document->isEmpty() && removeChar()) {
                    onTextChanged();
                    drawLines();
                }
                return true;
        if (inputEvent.isShortPress() && inputEvent.is(key_signs_remove)) {
            if (!document->isEmpty() && removeChar()) {
                onTextChanged();
                drawLines();
            }
            return true;
        }
        return false;
    }

    bool Text::handleAddChar(const InputEvent &inputEvent)
    {
        if (inputEvent.isShortPress() == false || !isMode(EditMode::EDIT)) {
        if (!inputEvent.isShortPress() || !isMode(EditMode::EDIT)) {
            return false;
        }

        if (lines->checkAdditionBounds(*cursor, inputEvent) == InputBound::CAN_ADD) {
        auto code = translator.handle(inputEvent.key, mode ? mode->get() : "");

        if (code != KeyProfile::none_key && checkAdditionBounds(code) == InputBound::CAN_ADD) {

            auto code = translator.handle(inputEvent.key, mode ? mode->get() : "");
            debug_text("handleAddChar %d -> Begin", code);
            debug_text("%s times: %" PRIu32, inputEvent.str().c_str(), translator.getTimes());

            if (code != KeyProfile::none_key) {
                /// if we have multi press in non digit mode - we need to replace char and put next char from translator
                if (!(mode->is(InputMode::digit) || (mode->is(InputMode::phone))) && translator.getTimes() > 0) {
                    removeChar();
                }
                addChar(code);
                onTextChanged();
                drawLines();
            /// if we have multi press in non digit mode - we need to replace char and put next char from translator
            if (!(mode->is(InputMode::digit) || (mode->is(InputMode::phone))) && translator.getTimes() > 0) {
                removeChar();
            }
            addChar(code);
            onTextChanged();
            drawLines();

                debug_text("handleAddChar -> End(true)");
            debug_text("handleAddChar -> End(true)");

                return true;
            }
            return true;
        }

        debug_text("handleAdChar -> End(false)");


@@ 576,16 542,327 @@ namespace gui
        if (!inputEvent.isLongPress()) {
            return false;
        }

        auto val = toNumeric(inputEvent.keyCode);
        if (val != InvalidNumericKeyCode) {

        if (val != InvalidNumericKeyCode && checkAdditionBounds(val) == InputBound::CAN_ADD) {

            addChar(intToAscii(val));
            onTextChanged();
            drawLines();
            return true;
        }

        return false;
    }

    auto Text::makePreDrawLines(const uint32_t utfVal) -> std::unique_ptr<Lines>
    {
        auto preDrawLines = std::make_unique<Lines>(this);
        auto documentCopy = *cursor->getDocument();
        auto formatCopy   = getTextFormat().getFont();

        applyParentSizeRestrictions();

        BlockCursor preDrawCursor(&documentCopy, 0, 0, formatCopy);
        preDrawCursor.addChar(utfVal);

        preDrawLines->draw(
            preDrawCursor, getSizeMinusPadding(Axis::X, Area::Max), text::npos, padding.top, padding.left);

        return preDrawLines;
    }

    auto Text::makePreDrawLines(const TextBlock &textBlock) -> std::unique_ptr<Lines>
    {
        auto preDrawLines     = std::make_unique<Lines>(this);
        auto documentCopy     = *cursor->getDocument();
        auto formatCopy       = getTextFormat().getFont();
        auto preDrawTextBlock = TextBlock(textBlock);
        auto preDrawTextEnd   = textBlock.getEnd();

        applyParentSizeRestrictions();

        BlockCursor preDrawCursor(&documentCopy, 0, 0, formatCopy);

        applyParentSizeRestrictions();

        preDrawCursor.addTextBlock(std::move(preDrawTextBlock));

        if (preDrawTextEnd == TextBlock::End::Newline) {
            documentCopy.append(TextBlock("", formatCopy, TextBlock::End::None));
        }

        preDrawLines->draw(
            preDrawCursor, getSizeMinusPadding(Axis::X, Area::Max), text::npos, padding.top, padding.left);

        return preDrawLines;
    }

    auto Text::checkMaxSignsLimit(unsigned int limitVal) -> InputBound
    {
        if (getText().length() >= limitVal) {
            debug_text("Text at max signs count can't add more");
            return InputBound::HIT_BOUND;
        }
        else {
            return InputBound::CAN_ADD;
        }
    }

    auto Text::checkMaxSignsLimit(const TextBlock &textBlock, unsigned int limitVal)
        -> std::tuple<InputBound, TextBlock>
    {
        if (getText().length() >= limitVal) {
            debug_text("Text at max signs count can't add more.");
            return {InputBound::HIT_BOUND, textBlock};
        }
        else if (getText().length() + textBlock.length() >= limitVal) {

            // Split existing block into smaller one that can still fit and return it
            auto availableSpace = limitVal - getText().length();
            auto partBlockText  = textBlock.getText().substr(0, availableSpace);
            auto blockFormat    = textBlock.getFormat();

            debug_text("Text at max sign count adding part of block. Original: %s, Fit part %s",
                       textBlock.getText().c_str(),
                       partBlockText.c_str());

            return {InputBound::CAN_ADD_PART, TextBlock(partBlockText, std::make_unique<TextFormat>(*blockFormat))};
        }
        else {
            return {InputBound::CAN_ADD, textBlock};
        }
    }

    auto Text::checkMaxSizeLimit(uint32_t utfVal) -> InputBound
    {
        auto returnValue = InputBound::CAN_ADD;

        auto preDrawLines = makePreDrawLines(utfVal);

        debug_text("Text lines height: %d, preDraw height: %d, Widget max h: %d",
                   lines->linesHeight(),
                   preDrawLines->linesHeight(),
                   area(Area::Max).h);

        if (preDrawLines->maxWidth() == 0 ||
            preDrawLines->linesHeight() + getPadding().getSumInAxis(Axis::Y) > area(Area::Max).h) {

            debug_text("Text at max size can't add more");
            preDrawLines->erase();
            returnValue = InputBound::HIT_BOUND;
        }

        preDrawLines->erase();
        return returnValue;
    }

    auto Text::checkMaxSizeLimit(const TextBlock &textBlock) -> std::tuple<InputBound, TextBlock>
    {
        auto preDrawLines = makePreDrawLines(textBlock);

        debug_text("Text lines height: %d, preDraw height: %d, Widget max h: %d",
                   lines->linesHeight(),
                   preDrawLines->linesHeight(),
                   area(Area::Max).h);

        if (preDrawLines->maxWidth() == 0) {
            preDrawLines->erase();
            return {InputBound::HIT_BOUND, textBlock};
        }

        if (preDrawLines->linesHeight() + getPadding().getSumInAxis(Axis::Y) > area(Area::Max).h) {

            debug_text("Text at max size can't add whole bock, try to split");

            for (unsigned int signCounter = textBlock.length(); signCounter != 0; signCounter--) {

                preDrawLines->erase();

                auto partBlockText = textBlock.getText().substr(0, signCounter);
                auto blockFormat   = textBlock.getFormat();

                preDrawLines = makePreDrawLines(TextBlock(partBlockText, std::make_unique<TextFormat>(*blockFormat)));

                debug_text("Text lines height: %d, preDraw height: %d, Widget max h: %d",
                           lines->linesHeight(),
                           preDrawLines->linesHeight(),
                           area(Area::Max).h);

                if (preDrawLines->linesHeight() + getPadding().getSumInAxis(Axis::Y) <= area(Area::Max).h) {

                    preDrawLines->erase();

                    debug_text("Text at max size adding part of block. Original: %s, Fit part %s",
                               textBlock.getText().c_str(),
                               partBlockText.c_str());

                    return {InputBound::CAN_ADD_PART,
                            TextBlock(partBlockText, std::make_unique<TextFormat>(*blockFormat))};
                }
            }

            preDrawLines->erase();
            // If not a part of block can fit return hit bound.
            return {InputBound::HIT_BOUND, textBlock};
        }

        preDrawLines->erase();
        return {InputBound::CAN_ADD, textBlock};
    }

    auto Text::checkMaxLinesLimit(uint32_t utfVal, unsigned int limitVal) -> InputBound
    {
        auto returnValue = InputBound::CAN_ADD;

        auto preDrawLines = makePreDrawLines(utfVal);

        debug_text("Text lines height: %d, preDraw height: %d, Widget max h: %d",
                   lines->linesHeight(),
                   preDrawLines->linesHeight(),
                   area(Area::Max).h);

        if (preDrawLines->size() > limitVal) {

            debug_text("Text at max size can't add more");
            preDrawLines->erase();
            returnValue = InputBound::HIT_BOUND;
        }

        preDrawLines->erase();
        return returnValue;
    }

    auto Text::checkMaxLinesLimit(const TextBlock &textBlock, unsigned int limitVal)
        -> std::tuple<InputBound, TextBlock>
    {
        auto preDrawLines = makePreDrawLines(textBlock);

        debug_text("Text lines height: %d, preDraw height: %d, Widget max h: %d",
                   lines->linesHeight(),
                   preDrawLines->linesHeight(),
                   area(Area::Max).h);

        if (preDrawLines->maxWidth() == 0) {
            preDrawLines->erase();
            return {InputBound::HIT_BOUND, textBlock};
        }

        if (preDrawLines->size() > limitVal) {

            debug_text("Text at max line size can't add whole bock, try to split");

            for (unsigned int signCounter = textBlock.length(); signCounter != 0; signCounter--) {

                preDrawLines->erase();

                auto partBlockText = textBlock.getText().substr(0, signCounter);
                auto blockFormat   = textBlock.getFormat();

                preDrawLines = makePreDrawLines(TextBlock(partBlockText, std::make_unique<TextFormat>(*blockFormat)));

                debug_text("Text lines height: %d, preDraw height: %d, Widget max h: %d",
                           lines->linesHeight(),
                           preDrawLines->linesHeight(),
                           area(Area::Max).h);

                if (preDrawLines->size() <= limitVal) {

                    preDrawLines->erase();

                    debug_text("Text at max line size adding part of block. Original: %s, Fit part %s",
                               textBlock.getText().c_str(),
                               partBlockText.c_str());

                    return {InputBound::CAN_ADD_PART,
                            TextBlock(partBlockText, std::make_unique<TextFormat>(*blockFormat))};
                }
            }

            preDrawLines->erase();
            // If not a part of block can fit return hit bound.
            return {InputBound::HIT_BOUND, textBlock};
        }

        preDrawLines->erase();
        return {InputBound::CAN_ADD, textBlock};
    }

    auto Text::checkAdditionBounds(const uint32_t utfVal) -> InputBound
    {
        auto returnValue = InputBound::CAN_ADD;

        for (auto limit : limitsList) {

            switch (limit.limitType) {
            case TextLimitType::MAX_SIGNS_COUNT:
                returnValue = checkMaxSignsLimit(limit.limitValue);
                break;
            case TextLimitType::MAX_LINES:
                returnValue = checkMaxLinesLimit(utfVal, limit.limitValue);
                break;
            case TextLimitType::MAX_SIZE:
                returnValue = checkMaxSizeLimit(utfVal);
                break;
            default:
                break;
            }

            if (returnValue == InputBound::HIT_BOUND) {
                return returnValue;
            }
        }
        return returnValue;
    }

    auto Text::checkAdditionBounds(const TextBlock &textBlock) -> std::tuple<InputBound, TextBlock>
    {
        std::tuple<InputBound, TextBlock> returnValue = {InputBound::CAN_ADD, textBlock};
        auto shortestProcessedBlock                   = textBlock;

        for (auto limit : limitsList) {

            switch (limit.limitType) {
            case TextLimitType::MAX_SIGNS_COUNT:
                returnValue = checkMaxSignsLimit(textBlock, limit.limitValue);
                break;
            case TextLimitType::MAX_LINES:
                returnValue = checkMaxLinesLimit(textBlock, limit.limitValue);
                break;
            case TextLimitType::MAX_SIZE:
                returnValue = checkMaxSizeLimit(textBlock);
                break;
            default:
                break;
            }

            if (std::get<0>(returnValue) == InputBound::CAN_ADD_PART) {
                if (std::get<1>(returnValue).length() < shortestProcessedBlock.length()) {
                    shortestProcessedBlock = std::get<1>(returnValue);
                }
            }

            if (std::get<0>(returnValue) == InputBound::HIT_BOUND) {
                return returnValue;
            }
        }

        return {std::get<0>(returnValue), shortestProcessedBlock};
    }

    bool Text::addChar(uint32_t utf_value)
    {
        cursor->addChar(utf_value);
        auto block = document->getBlock(cursor);

        if (block == nullptr) {
            return false;
        }
        debug_text("value: %d, block len: %d", utf_value, block->length());
        return true;
    }

    bool Text::removeChar()
    {
        if (!document->isEmpty()) {


@@ 619,6 896,6 @@ namespace gui
            // Move cursor to backup position from end of document.
            cursor->TextCursor::moveCursor(NavigationDirection::LEFT, cursorPosDiff);
        }
    };
    }

} /* namespace gui */

M module-gui/gui/widgets/Text.hpp => module-gui/gui/widgets/Text.hpp +29 -15
@@ 76,20 76,39 @@ namespace gui

      protected:
        TextType textType = TextType::MULTI_LINE;
        std::list<TextLimit> limitsList;

        /// points to default text font to use
        bool underline = false;
        TextFormat format;

        auto moveCursor(const NavigationDirection &direction, std::unique_ptr<TextDocument> &document) -> bool;
        auto handleRotateInputMode(const InputEvent &inputEvent) -> bool;
        auto handleRestoreInputModeUI(const InputEvent &inputEvent) -> bool;
        auto handleSelectSpecialChar(const InputEvent &inputEvent) -> bool;
        auto handleActivation(const InputEvent &inputEvent) -> bool;
        auto handleNavigation(const InputEvent &inputEvent) -> bool;
        auto handleEnter() -> bool;
        auto handleRemovalChar(const InputEvent &inputEvent) -> bool;
        auto handleDigitLongPress(const InputEvent &inputEvent) -> bool;
        auto handleAddChar(const InputEvent &inputEvent) -> bool;

        [[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(const TextBlock &textBlock) -> std::unique_ptr<Lines>;

        auto checkMaxSignsLimit(unsigned int limitVal) -> InputBound;
        auto checkMaxSignsLimit(const TextBlock &textBlock, unsigned int limitVal) -> std::tuple<InputBound, TextBlock>;
        auto checkMaxSizeLimit(uint32_t utfVal) -> InputBound;
        auto checkMaxSizeLimit(const TextBlock &textBlock) -> std::tuple<InputBound, TextBlock>;
        auto checkMaxLinesLimit(uint32_t utfVal, unsigned int limitVal) -> InputBound;
        auto checkMaxLinesLimit(const TextBlock &textBlock, unsigned int limitVal) -> std::tuple<InputBound, TextBlock>;

        void preBuildDrawListHookImplementation(std::list<Command> &commands);
        /// redrawing lines
        /// it redraws visible lines on screen and if needed requests resize in parent
        virtual void drawLines();
        virtual auto drawLines() -> void;
        /// redrawing cursor
        void drawCursor();
        auto drawCursor() -> void;

      public:
        /// Callback when text changed


@@ 110,7 129,9 @@ namespace gui

        void setEditMode(EditMode mode);
        void setTextType(TextType type);
        void setUnderline(const bool val);
        void setTextLimitType(TextLimitType limitType, unsigned int val = 0);
        void clearTextLimits();
        void setUnderline(bool val);
        virtual void setText(const UTF8 &text);
        void setText(std::unique_ptr<TextDocument> &&document);



@@ 162,17 183,10 @@ namespace gui
      public:
        TextChangedCallback textChangedCallback;

        bool handleRotateInputMode(const InputEvent &inputEvent);
        bool handleRestoreInputModeUI(const InputEvent &inputEvent);
        bool handleSelectSpecialChar(const InputEvent &inputEvent);
        bool handleActivation(const InputEvent &inputEvent);
        bool handleBackspace(const InputEvent &inputEvent);
        bool handleDigitLongPress(const InputEvent &inputEvent);
        bool handleAddChar(const InputEvent &inputEvent);

        auto checkAdditionBounds(uint32_t utfVal) -> InputBound;
        auto checkAdditionBounds(const TextBlock &textBlock) -> std::tuple<InputBound, TextBlock>;
        bool addChar(uint32_t utf8);
        bool removeChar();
        InputBound processBound(InputBound bound, const InputEvent &event);
        void onTextChanged();
    };


M module-gui/gui/widgets/TextBlock.cpp => module-gui/gui/widgets/TextBlock.cpp +10 -0
@@ 30,6 30,16 @@ namespace gui
        end    = p.end;
    }

    TextBlock &TextBlock::operator=(const TextBlock &p)
    {
        if (this != &p) {
            text   = p.text;
            format = std::make_unique<TextFormat>(*p.format);
            end    = p.end;
        }
        return *this;
    }

    const UTF8 &TextBlock::getText() const
    {
        return text;

M module-gui/gui/widgets/TextBlock.hpp => module-gui/gui/widgets/TextBlock.hpp +2 -0
@@ 50,5 50,7 @@ namespace gui

        void addChar(uint32_t utf_val, unsigned int pos);
        bool isEmpty() const;

        TextBlock &operator=(const TextBlock &);
    };
} // namespace gui

M module-gui/gui/widgets/TextBlockCursor.cpp => module-gui/gui/widgets/TextBlockCursor.cpp +6 -1
@@ 210,9 210,14 @@ namespace gui

    void BlockCursor::addTextBlock(TextBlock &&textblock)
    {
        if (textblock.length() == 0) {

        if (textblock.length() == 0 && document->blocks.empty() && textblock.getEnd() != TextBlock::End::Newline) {
            return;
        }
        else if (textblock.length() == 0 && ((--blocksEnd())->getEnd() != TextBlock::End::Newline)) {
            return;
        }

        if (document->isEmpty()) {
            resetNpos();
        }

M module-gui/gui/widgets/TextBlockCursor.hpp => module-gui/gui/widgets/TextBlockCursor.hpp +3 -2
@@ 85,9 85,10 @@ namespace gui
        [[nodiscard]] auto checkAndInvalidateBlockChanged() -> bool
        {
            if (blockChanged) {
                blockChanged = true;
                blockChanged = false;
                return true;
            }
            return blockChanged;
            return false;
        }

        [[nodiscard]] auto checkLastNewLine() -> bool

M module-gui/gui/widgets/TextConstants.hpp => module-gui/gui/widgets/TextConstants.hpp +17 -10
@@ 13,11 13,24 @@ namespace gui
        const char newline      = '\n';
    }; // namespace text

    enum class TextLimitType
    {
        MAX_SIZE,
        MAX_LINES,
        MAX_SIGNS_COUNT
    };

    struct TextLimit
    {
        TextLimitType limitType;
        unsigned int limitValue;
    };

    enum class ExpandMode
    {
        EXPAND_UP,
        EXPAND_DOWN,
        EXPAND_NONE // defult
        EXPAND_NONE // default
    };

    enum class EditMode


@@ 35,15 48,9 @@ namespace gui

    enum class InputBound
    {
        UNDEFINED = 0x000,

        CANT_PROCESS = 0x100,
        NO_DATA      = 0x101,
        HIT_BOUND    = 0x110,

        CAN_ADD    = 0x1000,
        CAN_MOVE   = 0x10000,
        CAN_REMOVE = 0x100000
        CAN_ADD,
        CAN_ADD_PART,
        HIT_BOUND
    };

} // namespace gui

M module-gui/gui/widgets/TextCursor.cpp => module-gui/gui/widgets/TextCursor.cpp +23 -15
@@ 164,30 164,38 @@ namespace gui
        moveCursor(NavigationDirection::RIGHT);
    }

    TextCursor &TextCursor::operator<<(const UTF8 &text)
    TextCursor &TextCursor::operator<<(const UTF8 &textString)
    {
        for (unsigned int i = 0; i < text.length(); ++i) {
            addChar(text[i]);
        for (unsigned int i = 0; i < textString.length(); ++i) {
            if (text->checkAdditionBounds(textString[i]) == InputBound::CAN_ADD) {
                addChar(textString[i]);
            }
            else {
                break;
            }
        }
        return *this;
    }

    TextCursor &TextCursor::operator<<(TextBlock textblock)
    TextCursor &TextCursor::operator<<(const TextBlock &textBlock)
    {
        auto len = textblock.length();
        auto end = textblock.getEnd();
        auto [addBoundResult, processedTextBlock] = text->checkAdditionBounds(textBlock);

        BlockCursor::addTextBlock(std::move(textblock));
        if (addBoundResult == InputBound::CAN_ADD || addBoundResult == InputBound::CAN_ADD_PART) {

        // +1 is for block barrier
        for (unsigned int i = 0; i < len + 1; ++i) {
            moveCursor(NavigationDirection::RIGHT);
        }
            auto len = processedTextBlock.length();
            auto end = processedTextBlock.getEnd();

        // If new added block ends with newline split it for additional empty block at end
        if (end == TextBlock::End::Newline) {
            document->addNewline(*this, TextBlock::End::Newline);
            moveCursor(NavigationDirection::RIGHT);
            BlockCursor::addTextBlock(std::move(processedTextBlock));

            // +1 is for block barrier
            moveCursor(NavigationDirection::RIGHT, len + 1);

            // If new added block ends with newline split it for additional empty block at end
            if (end == TextBlock::End::Newline) {
                document->addNewline(*this, TextBlock::End::Newline);
                moveCursor(NavigationDirection::RIGHT);
            }
        }

        return *this;

M module-gui/gui/widgets/TextCursor.hpp => module-gui/gui/widgets/TextCursor.hpp +1 -1
@@ 58,7 58,7 @@ namespace gui
        // TODO this can move our text out of bonds ( and might need calling expand() in Text)
        void addChar(uint32_t utf_val);
        TextCursor &operator<<(const UTF8 &);
        TextCursor &operator<<(TextBlock);
        TextCursor &operator<<(const TextBlock &);
        void removeChar();

        [[nodiscard]] auto getPosOnScreen() const

M module-gui/gui/widgets/TextFixedSize.cpp => module-gui/gui/widgets/TextFixedSize.cpp +13 -51
@@ 1,3 1,4 @@

// Copyright (c) 2017-2020, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md



@@ 9,7 10,7 @@ namespace gui
    TextFixedSize::TextFixedSize(Item *parent, Position x, Position y, Length w, Length h) : Text(parent, x, y, w, h)
    {
        setEditMode(EditMode::EDIT);
        underline = true;
        lines->setUnderLine(true);
    }

    void TextFixedSize::setLines(const unsigned int val)


@@ 22,8 23,8 @@ namespace gui

    void TextFixedSize::setUnderlinePadding(Position val)
    {
        if (underlinePadding != val) {
            underlinePadding = val;
        if (lines->getUnderLinePadding() != val) {
            lines->setUnderLinePadding(val);
            drawLines();
        }
    }


@@ 32,55 33,16 @@ namespace gui
    {
        lines->erase();

        auto sizeMinusPadding = [&](Axis axis, Area val) {
            auto size = area(val).size(axis);
            if (size <= padding.getSumInAxis(axis)) {
                size = 0;
            }
            else {
                size -= padding.getSumInAxis(axis);
            }
            return size;
        };

        uint32_t w           = sizeMinusPadding(Axis::X, Area::Normal);
        uint32_t h           = sizeMinusPadding(Axis::Y, Area::Normal);
        auto line_y_position = padding.top;

        BlockCursor draw_cursor(cursor->getDocument(), 0, 0, getTextFormat().getFont());

        unsigned int currentLine = 0;
        unsigned int lineHeight  = format.getFont()->info.line_height + underlinePadding;

        auto line_x_position = padding.left;
        do {
            auto text_line =
                gui::TextLine(draw_cursor, w, lineHeight, underline, UnderlineDrawMode::WholeLine, underlinePadding);

            if (text_line.height() > 0 && lineHeight != text_line.height()) {
                lineHeight = text_line.height();
            }

            if (line_y_position + lineHeight > h) {
                break;
            }

            if (currentLine >= linesCount) {
                break;
            }

            lines->emplace(std::move(text_line));
            auto &line = lines->last();
            line.setPosition(line_x_position, line_y_position);
            line.setParent(this);

            line_y_position += lineHeight;

            currentLine++;
        BlockCursor drawCursor(cursor->getDocument(), 0, 0, getTextFormat().getFont());

        } while (true);
        lines->draw(drawCursor,
                    getSizeMinusPadding(Axis::X, Area::Normal),
                    getSizeMinusPadding(Axis::Y, Area::Normal),
                    padding.top,
                    padding.left,
                    linesCount);

        lines->linesHAlign(sizeMinusPadding(Axis::X, Area::Normal));
        lines->linesVAlign(sizeMinusPadding(Axis::Y, Area::Normal));
        lines->linesHAlign(getSizeMinusPadding(Axis::X, Area::Normal));
        lines->linesVAlign(getSizeMinusPadding(Axis::Y, Area::Normal));
    }
} // namespace gui

M module-gui/gui/widgets/TextFixedSize.hpp => module-gui/gui/widgets/TextFixedSize.hpp +0 -1
@@ 12,7 12,6 @@ namespace gui
    {
      protected:
        unsigned int linesCount   = style::text::maxTextLines;
        Position underlinePadding = 0;

        void drawLines() override;


M module-gui/gui/widgets/TextLine.cpp => module-gui/gui/widgets/TextLine.cpp +8 -16
@@ 54,18 54,22 @@ namespace gui
                return;
            }

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

            auto can_show = text_format->getFont()->getCharCountInSpace(text, max_width - width_used);

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

                auto item = buildUITextPart(" ", text_format);
                width_used += item->getTextNeedSpace();
                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);
                end = localCursor->getEnd();
                ++localCursor;

                return;
            }


@@ 291,16 295,4 @@ namespace gui
        }
    }

    auto TextLine::checkBounds(TextLineCursor &cursor, uint32_t utf_value, const TextFormat *format) -> InputBound
    {
        auto font = format->getFont();
        auto text = getText(0);
        text.insertCode(utf_value);

        if ((font->getPixelWidth(text)) <= max_width) {
            return InputBound::CAN_ADD;
        }

        return InputBound::CANT_PROCESS;
    }
} // namespace gui

M module-gui/gui/widgets/TextLine.hpp => module-gui/gui/widgets/TextLine.hpp +0 -1
@@ 122,6 122,5 @@ namespace gui
        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;
        auto checkBounds(TextLineCursor &cursor, uint32_t utf_value, const TextFormat *format) -> InputBound;
    };
} // namespace gui

M module-gui/gui/widgets/TextParse.cpp => module-gui/gui/widgets/TextParse.cpp +4 -3
@@ 25,10 25,11 @@ namespace gui
        std::stringstream ss(text.c_str());
        std::string line;
        while (std::getline(ss, line)) {
            if (!blocks.empty()) {
                blocks.back().setEnd(TextBlock::End::Newline);
            auto block = TextBlock(line, std::make_unique<TextFormat>(format));
            if (ss.good()) {
                block.setEnd(TextBlock::End::Newline);
            }
            blocks.emplace_back(TextBlock(line, std::make_unique<TextFormat>(format)));
            blocks.emplace_back(block);
        }

        addEmptyBlockOnNewline(blocks, format);

M module-gui/test/test-catch-text/CMakeLists.txt => module-gui/test/test-catch-text/CMakeLists.txt +0 -1
@@ 15,7 15,6 @@ add_catch2_executable(
                test-gui-TextParse.cpp
                test-gui-Font.cpp
                test-gui-TextLineCursor.cpp
                test-gui-TextBounds.cpp
        INCLUDE
                ..
                ../mock/

M module-gui/test/test-catch-text/test-gui-Text.cpp => module-gui/test/test-catch-text/test-gui-Text.cpp +647 -0
@@ 16,6 16,8 @@
#include <algorithm>
#include <mock/BlockFactory.hpp>
#include <RawFont.hpp>
#include "Font.hpp"
#include "RichTextParser.hpp"

TEST_CASE("Text ctor")
{


@@ 310,3 312,648 @@ TEST_CASE("Text backup and restore tests")
        REQUIRE(text->getCursorPos() == testStringTwoLines.length() - cursorMoveN);
    }
}

TEST_CASE("Text addition bounds - text sings count restricted")
{
    std::string testStringOneLine  = "Test String 1";
    std::string testStringTwoLines = "Test String 1\n Test String 2";

    std::string richTextTwoLines =
        "<text font='gt_pressura' color='12' size='30'>Test</text><text size='25'>String</text><text size='20' "
        "weight='bold'>1</text><br></br><text>Test String 2</text>";

    SECTION("Adding text to max signs count set to 0")
    {
        mockup::fontManager();
        using namespace gui;
        auto text = new gui::TestText();
        text->setTextLimitType(gui::TextLimitType::MAX_SIGNS_COUNT, 0);

        text->addText(testStringOneLine);

        REQUIRE(text->getText().empty());
    }

    SECTION("Adding text to max signs count set to 1")
    {
        mockup::fontManager();
        using namespace gui;
        auto text = new gui::TestText();
        text->setTextLimitType(gui::TextLimitType::MAX_SIGNS_COUNT, 1);

        text->addText(testStringOneLine);

        REQUIRE(text->getText().length() == 1);
    }

    SECTION("Adding two lines text to max signs count set to one line sings count")
    {
        mockup::fontManager();
        using namespace gui;
        auto text = new gui::TestText();
        text->setTextLimitType(gui::TextLimitType::MAX_SIGNS_COUNT, testStringOneLine.length());

        text->addText(testStringTwoLines);

        REQUIRE(text->getText().length() == testStringOneLine.length());
    }

    SECTION("Adding two lines text to max signs count set to two line sings count")
    {
        mockup::fontManager();
        using namespace gui;
        auto text = new gui::TestText();
        text->setTextLimitType(gui::TextLimitType::MAX_SIGNS_COUNT, testStringTwoLines.length());

        text->addText(testStringTwoLines);

        REQUIRE(text->getText().length() == testStringTwoLines.length());
    }

    SECTION("Adding TextBlock with max sign count lower than block size")
    {
        mockup::fontManager();
        using namespace gui;
        auto text = new gui::TestText();
        text->setTextLimitType(gui::TextLimitType::MAX_SIGNS_COUNT, testStringOneLine.length());

        text->addText(TextBlock(testStringTwoLines, Font(27).raw(), TextBlock::End::None));

        REQUIRE(text->getText().length() == testStringOneLine.length());
        REQUIRE(text->getText().length() != testStringTwoLines.length());
    }

    SECTION("Adding TextBlock with max sign count greater than block and adding more text after")
    {
        auto additionalSpace = 5;
        auto textLimit       = testStringOneLine.length() + additionalSpace;

        mockup::fontManager();
        using namespace gui;
        auto text = new gui::TestText();
        text->setTextLimitType(gui::TextLimitType::MAX_SIGNS_COUNT, textLimit);

        text->addText(TextBlock(testStringOneLine, Font(27).raw(), TextBlock::End::None));

        REQUIRE(text->getText().length() == testStringOneLine.length());

        text->addText(testStringOneLine);

        REQUIRE(text->getText().length() == testStringOneLine.length() + additionalSpace);
    }

    SECTION("Adding RichText with max sign count lower than block size")
    {
        unsigned int signCountRestricted = 5;

        mockup::fontManager();
        using namespace gui;
        auto text = new gui::TestText();

        text->setTextLimitType(gui::TextLimitType::MAX_SIGNS_COUNT, signCountRestricted);

        text->addRichText(richTextTwoLines);

        REQUIRE(text->getText().length() == signCountRestricted);
    }

    SECTION("Adding RichText with max sign count greater than block and adding more text after")
    {
        auto additionalSpace = 5;

        mockup::fontManager();
        using namespace gui;
        auto text = new gui::TestText();

        auto format         = text->getTextFormat();
        auto parsedRichText = gui::text::RichTextParser().parse(richTextTwoLines, &format);
        auto textLimit      = parsedRichText->getText().length() + additionalSpace;

        text->setTextLimitType(gui::TextLimitType::MAX_SIGNS_COUNT, textLimit);

        text->addRichText(richTextTwoLines);

        REQUIRE(text->getText().length() == parsedRichText->getText().length());

        text->addText(testStringOneLine);

        REQUIRE(text->getText().length() == parsedRichText->getText().length() + additionalSpace);
    }
}

TEST_CASE("Text addition bounds - text widget size restricted")
{
    std::string testStringOneLine  = "Test String 1";
    std::string testStringTwoLines = "Test String 1\nTest String 2";

    std::string testStringFirstLine  = "Test String 1";
    std::string testStringSecondLine = "Test String 2";

    std::string richTextTwoLines =
        "<text font='gt_pressura' color='12' size='30'>Test </text><text size='25'>String </text><text size='20' "
        "weight='bold'>1</text><br></br><text>Test String 2</text>";

    SECTION("Adding text to 0 size text and no parent to grant size")
    {
        mockup::fontManager();
        using namespace gui;
        auto text = new gui::TestText();
        text->setTextLimitType(gui::TextLimitType::MAX_SIZE);
        text->setMaximumSize(0, 0);

        text->addText(testStringOneLine);

        REQUIRE(text->getText().empty());
    }

    SECTION("Adding text to text with parent max size set to not fit")
    {
        mockup::fontManager();
        using namespace gui;
        auto text = new gui::TestText();
        text->setTextLimitType(gui::TextLimitType::MAX_SIZE);
        BoxLayout layout = BoxLayout(nullptr, 0, 0, 0, 0);
        layout.setMaximumSize(10, 10);
        layout.addWidget(text);

        text->addText(testStringOneLine);

        REQUIRE(text->getText().empty());
    }

    SECTION("Adding two lines text to text with no parent and size set to fit one line")
    {
        mockup::fontManager();
        using namespace gui;
        auto text = new gui::TestText();
        text->setTextLimitType(gui::TextLimitType::MAX_SIZE);
        text->setMaximumSize(200, 30);

        text->addText(testStringTwoLines);

        REQUIRE(text->linesSize() == 1);
        REQUIRE(text->getText() == testStringOneLine);
        REQUIRE(text->getText() != testStringTwoLines);
    }

    SECTION("Adding two lines text to text with no parent and size set to fit two line")
    {
        mockup::fontManager();
        using namespace gui;
        auto text = new gui::TestText();
        text->setTextLimitType(gui::TextLimitType::MAX_SIZE);
        text->setMaximumSize(200, 60);

        text->addText(testStringTwoLines);

        REQUIRE(text->linesSize() == 2);
        REQUIRE(text->getText() != testStringOneLine);
        REQUIRE(text->getText() == testStringTwoLines);
    }

    SECTION("Adding two lines text to text with space for two lines and parent size set to fit one line")
    {
        mockup::fontManager();
        using namespace gui;
        auto text = new gui::TestText();
        text->setTextLimitType(gui::TextLimitType::MAX_SIZE);
        text->setMaximumSize(200, 60);
        BoxLayout layout = BoxLayout(nullptr, 0, 0, 0, 0);
        layout.setMaximumSize(200, 30);
        layout.addWidget(text);

        text->addText(testStringTwoLines);

        REQUIRE(text->linesSize() == 1);
        REQUIRE(text->getText() == testStringOneLine);
        REQUIRE(text->getText() != testStringTwoLines);
    }

    SECTION("Adding text block to 0 size text and no parent to grant size")
    {
        mockup::fontManager();
        using namespace gui;
        auto text = new gui::TestText();
        text->setTextLimitType(gui::TextLimitType::MAX_SIZE);
        text->setMaximumSize(0, 0);

        text->addText(TextBlock(testStringOneLine, Font(27).raw(), TextBlock::End::None));

        REQUIRE(text->getText().empty());
    }

    SECTION("Adding text block to text with parent max size set to not fit")
    {
        mockup::fontManager();
        using namespace gui;
        auto text = new gui::TestText();
        text->setTextLimitType(gui::TextLimitType::MAX_SIZE);
        BoxLayout layout = BoxLayout(nullptr, 0, 0, 0, 0);
        layout.setMaximumSize(10, 10);
        layout.addWidget(text);

        text->addText(TextBlock(testStringOneLine, Font(27).raw(), TextBlock::End::None));

        REQUIRE(text->getText().empty());
    }

    SECTION("Adding two lines text block to text with no parent and size set to fit one line")
    {
        mockup::fontManager();
        using namespace gui;
        auto text = new gui::TestText();
        text->setTextLimitType(gui::TextLimitType::MAX_SIZE);
        text->setMaximumSize(150, 30);

        text->addText(TextBlock(testStringFirstLine, Font(27).raw(), TextBlock::End::Newline));
        text->addText(TextBlock(testStringSecondLine, Font(27).raw(), TextBlock::End::None));

        REQUIRE(text->linesSize() == 1);
        REQUIRE(text->getText() == testStringFirstLine);
        REQUIRE(text->getText() != testStringTwoLines);
    }

    SECTION("Adding two lines text block to text with no parent and size set to fit two line")
    {
        mockup::fontManager();
        using namespace gui;
        auto text = new gui::TestText();
        text->setTextLimitType(gui::TextLimitType::MAX_SIZE);
        text->setMaximumSize(200, 60);

        text->addText(TextBlock(testStringFirstLine, Font(27).raw(), TextBlock::End::Newline));
        text->addText(TextBlock(testStringSecondLine, Font(27).raw(), TextBlock::End::None));

        REQUIRE(text->linesSize() == 2);
        REQUIRE(text->getText() != testStringFirstLine);
        REQUIRE(text->getText() == testStringTwoLines);
    }

    SECTION("Adding two lines text block to text with space for two lines and parent size set to fit one line")
    {
        mockup::fontManager();
        using namespace gui;
        auto text = new gui::TestText();
        text->setTextLimitType(gui::TextLimitType::MAX_SIZE);
        text->setMaximumSize(200, 60);
        BoxLayout layout = BoxLayout(nullptr, 0, 0, 0, 0);
        layout.setMaximumSize(150, 30);
        layout.addWidget(text);

        text->addText(TextBlock(testStringFirstLine, Font(27).raw(), TextBlock::End::Newline));
        text->addText(TextBlock(testStringSecondLine, Font(27).raw(), TextBlock::End::None));

        REQUIRE(text->linesSize() == 1);
        REQUIRE(text->getText() == testStringFirstLine);
        REQUIRE(text->getText() != testStringTwoLines);
    }

    SECTION("Adding RichText to 0 size text and no parent to grant size")
    {
        mockup::fontManager();
        using namespace gui;
        auto text = new gui::TestText();
        text->setTextLimitType(gui::TextLimitType::MAX_SIZE);
        text->setMaximumSize(0, 0);

        text->addRichText(richTextTwoLines);

        REQUIRE(text->getText().empty());
    }

    SECTION("Adding RichText to text with parent max size set to not fit")
    {
        mockup::fontManager();
        using namespace gui;
        auto text = new gui::TestText();
        text->setTextLimitType(gui::TextLimitType::MAX_SIZE);
        BoxLayout layout = BoxLayout(nullptr, 0, 0, 0, 0);
        layout.setMaximumSize(10, 10);
        layout.addWidget(text);

        text->addRichText(richTextTwoLines);

        REQUIRE(text->getText().empty());
    }

    SECTION("Adding two lines RichText to text with no parent and size set to fit one line")
    {
        mockup::fontManager();
        using namespace gui;
        auto text = new gui::TestText();
        text->setTextLimitType(gui::TextLimitType::MAX_SIZE);
        text->setMaximumSize(160, 40);

        text->addRichText(richTextTwoLines);

        REQUIRE(text->linesSize() == 1);
        REQUIRE(text->getText() == testStringFirstLine);
        REQUIRE(text->getText() != testStringTwoLines);
    }

    SECTION("Adding two lines RichText to text with no parent and size set to fit two line")
    {
        mockup::fontManager();
        using namespace gui;
        auto text = new gui::TestText();
        text->setTextLimitType(gui::TextLimitType::MAX_SIZE);
        text->setMaximumSize(200, 80);

        text->addRichText(richTextTwoLines);

        REQUIRE(text->linesSize() == 2);
        REQUIRE(text->getText() != testStringFirstLine);
        REQUIRE(text->getText() == testStringTwoLines);
    }

    SECTION("Adding two lines RichText to text with space for two lines and parent size set to fit one line")
    {
        mockup::fontManager();
        using namespace gui;
        auto text = new gui::TestText();
        text->setTextLimitType(gui::TextLimitType::MAX_SIZE);
        text->setMaximumSize(200, 80);
        BoxLayout layout = BoxLayout(nullptr, 0, 0, 0, 0);
        layout.setMaximumSize(160, 40);
        layout.addWidget(text);

        text->addRichText(richTextTwoLines);

        REQUIRE(text->linesSize() == 1);
        REQUIRE(text->getText() == testStringFirstLine);
        REQUIRE(text->getText() != testStringTwoLines);
    }
}

TEST_CASE("Text addition bounds - text widget line size restricted")
{
    std::string testStringOneLine  = "Test String 1";
    std::string testStringTwoLines = "Test String 1\nTest String 2";

    std::string testStringFirstLine  = "Test String 1";
    std::string testStringSecondLine = "Test String 2";

    std::string richTextTwoLines =
        "<text font='gt_pressura' color='12' size='30'>Test </text><text size='25'>String </text><text size='20' "
        "weight='bold'>1</text><br></br><text>Test String 2</text>";

    SECTION("Adding text to 0 line size text")
    {
        mockup::fontManager();
        using namespace gui;
        auto text = new gui::TestText();
        text->setTextLimitType(gui::TextLimitType::MAX_LINES, 0);
        text->setMaximumSize(150, 100);

        text->addText(testStringOneLine);

        REQUIRE(text->getText().empty());
    }

    SECTION("Adding two lines text to 1 line size text")
    {
        mockup::fontManager();
        using namespace gui;
        auto text = new gui::TestText();
        text->setTextLimitType(gui::TextLimitType::MAX_LINES, 1);
        text->setMaximumSize(150, 100);

        text->addText(testStringTwoLines);

        REQUIRE(text->linesSize() == 1);
        REQUIRE(text->getText() == testStringOneLine);
        REQUIRE(text->getText() != testStringTwoLines);
    }

    SECTION("Adding two lines text to 2 line size text")
    {
        mockup::fontManager();
        using namespace gui;
        auto text = new gui::TestText();
        text->setTextLimitType(gui::TextLimitType::MAX_LINES, 2);
        text->setMaximumSize(150, 100);

        text->addText(testStringTwoLines);

        REQUIRE(text->linesSize() == 2);
        REQUIRE(text->getText() == testStringTwoLines);
        REQUIRE(text->getText() != testStringOneLine);
    }

    SECTION("Adding text block to 0 line size text")
    {
        mockup::fontManager();
        using namespace gui;
        auto text = new gui::TestText();
        text->setTextLimitType(gui::TextLimitType::MAX_LINES, 0);
        text->setMaximumSize(150, 100);

        text->addText(TextBlock(testStringOneLine, Font(27).raw(), TextBlock::End::None));

        REQUIRE(text->getText().empty());
    }

    SECTION("Adding two block lines text to 1 line size text")
    {
        mockup::fontManager();
        using namespace gui;
        auto text = new gui::TestText();
        text->setTextLimitType(gui::TextLimitType::MAX_LINES, 1);
        text->setMaximumSize(150, 100);

        text->addText(TextBlock(testStringFirstLine, Font(27).raw(), TextBlock::End::Newline));
        text->addText(TextBlock(testStringSecondLine, Font(27).raw(), TextBlock::End::None));

        REQUIRE(text->linesSize() == 1);
        REQUIRE(text->getText() == testStringOneLine);
        REQUIRE(text->getText() != testStringTwoLines);
    }

    SECTION("Adding two lines text to 2 line size text")
    {
        mockup::fontManager();
        using namespace gui;
        auto text = new gui::TestText();
        text->setTextLimitType(gui::TextLimitType::MAX_LINES, 2);
        text->setMaximumSize(150, 100);

        text->addText(TextBlock(testStringFirstLine, Font(27).raw(), TextBlock::End::Newline));
        text->addText(TextBlock(testStringSecondLine, Font(27).raw(), TextBlock::End::None));

        REQUIRE(text->linesSize() == 2);
        REQUIRE(text->getText() == testStringTwoLines);
        REQUIRE(text->getText() != testStringOneLine);
    }

    SECTION("Adding RichText to 0 line size text")
    {
        mockup::fontManager();
        using namespace gui;
        auto text = new gui::TestText();
        text->setTextLimitType(gui::TextLimitType::MAX_LINES, 0);
        text->setMaximumSize(150, 100);

        text->addRichText(richTextTwoLines);

        REQUIRE(text->getText().empty());
    }

    SECTION("Adding two lines RichText to 1 line size text")
    {
        mockup::fontManager();
        using namespace gui;
        auto text = new gui::TestText();
        text->setTextLimitType(gui::TextLimitType::MAX_LINES, 1);
        text->setMaximumSize(160, 100);

        text->addRichText(richTextTwoLines);

        REQUIRE(text->linesSize() == 1);
        REQUIRE(text->getText() == testStringFirstLine);
        REQUIRE(text->getText() != testStringTwoLines);
    }

    SECTION("Adding two lines RichText to 2 line size text")
    {
        mockup::fontManager();
        using namespace gui;
        auto text = new gui::TestText();
        text->setTextLimitType(gui::TextLimitType::MAX_LINES, 2);
        text->setMaximumSize(160, 100);

        text->addRichText(richTextTwoLines);

        REQUIRE(text->linesSize() == 2);
        REQUIRE(text->getText() == testStringTwoLines);
        REQUIRE(text->getText() != testStringFirstLine);
    }
}

TEST_CASE("Text addition bounds - multiple limits tests")
{
    std::string testStringOneLine  = "Test String 1";
    std::string testStringTwoLines = "Test String 1\nTest String 2";

    std::string richTextTwoLines =
        "<text font='gt_pressura' color='12' size='30'>Test </text><text size='25'>String </text><text size='20' "
        "weight='bold'>1</text><br></br><text>Test String 2</text>";

    SECTION("Adding text to lower limit set to signs count and size and lines on higher limit")
    {
        mockup::fontManager();
        using namespace gui;
        auto text = new gui::TestText();

        text->setTextLimitType(gui::TextLimitType::MAX_LINES, 2);
        text->setTextLimitType(gui::TextLimitType::MAX_SIGNS_COUNT, testStringOneLine.length());
        text->setTextLimitType(gui::TextLimitType::MAX_SIZE);
        text->setMaximumSize(150, 100);

        text->addText(testStringTwoLines);

        REQUIRE(text->linesSize() == 1);
        REQUIRE(text->getText() == testStringOneLine);
        REQUIRE(text->getText().length() == testStringOneLine.length());
    }

    SECTION("Adding text to lower limit set to lines count and size and signs count on higher limit")
    {
        mockup::fontManager();
        using namespace gui;
        auto text = new gui::TestText();

        unsigned int signsLimit = 100;

        text->setTextLimitType(gui::TextLimitType::MAX_LINES, 1);
        text->setTextLimitType(gui::TextLimitType::MAX_SIGNS_COUNT, signsLimit);
        text->setTextLimitType(gui::TextLimitType::MAX_SIZE);
        text->setMaximumSize(150, 100);

        text->addText(testStringOneLine);
        text->addText(testStringOneLine);
        text->addText(testStringOneLine);

        REQUIRE(text->linesSize() == 1);
        REQUIRE(text->getText().length() != signsLimit);
    }

    SECTION("Adding text to lower limit set to size and lines size and signs count on higher limit")
    {
        mockup::fontManager();
        using namespace gui;
        auto text = new gui::TestText();

        unsigned int signsLimit = 100;

        text->setTextLimitType(gui::TextLimitType::MAX_LINES, 3);
        text->setTextLimitType(gui::TextLimitType::MAX_SIGNS_COUNT, signsLimit);
        text->setTextLimitType(gui::TextLimitType::MAX_SIZE);
        text->setMaximumSize(140, 30);

        text->addText(testStringOneLine);
        text->addText(testStringOneLine);
        text->addText(testStringOneLine);

        REQUIRE(text->linesSize() == 1);
        REQUIRE(text->getText() == testStringOneLine);
        REQUIRE(text->getText().length() != signsLimit);
    }

    SECTION("Adding RichText to lower limit set to signs count and size and lines on higher limit")
    {
        mockup::fontManager();
        using namespace gui;
        auto text = new gui::TestText();

        auto format         = text->getTextFormat();
        auto parsedRichText = gui::text::RichTextParser().parse(richTextTwoLines, &format);

        text->setTextLimitType(gui::TextLimitType::MAX_LINES, 4);
        text->setTextLimitType(gui::TextLimitType::MAX_SIGNS_COUNT, parsedRichText->getText().length());
        text->setTextLimitType(gui::TextLimitType::MAX_SIZE);
        text->setMaximumSize(300, 100);

        text->addRichText(richTextTwoLines);

        REQUIRE(text->linesSize() == 2);
        REQUIRE(text->getText().length() == parsedRichText->getText().length());
    }

    SECTION("Adding RichText to lower limit set to lines count and size and signs count on higher limit")
    {
        mockup::fontManager();
        using namespace gui;
        auto text = new gui::TestText();

        unsigned int signsLimit = 100;

        text->setTextLimitType(gui::TextLimitType::MAX_LINES, 1);
        text->setTextLimitType(gui::TextLimitType::MAX_SIGNS_COUNT, signsLimit);
        text->setTextLimitType(gui::TextLimitType::MAX_SIZE);
        text->setMaximumSize(300, 100);

        text->addRichText(richTextTwoLines);

        REQUIRE(text->linesSize() == 1);
        REQUIRE(text->getText().length() != signsLimit);
    }

    SECTION("Adding RichText to lower limit set to size and lines size and signs count on higher limit")
    {
        mockup::fontManager();
        using namespace gui;
        auto text = new gui::TestText();

        unsigned int signsLimit = 100;

        text->setTextLimitType(gui::TextLimitType::MAX_LINES, 3);
        text->setTextLimitType(gui::TextLimitType::MAX_SIGNS_COUNT, signsLimit);
        text->setTextLimitType(gui::TextLimitType::MAX_SIZE);
        text->setMaximumSize(140, 30);

        text->addRichText(richTextTwoLines);

        REQUIRE(text->linesSize() == 1);
        REQUIRE(text->getText().length() != signsLimit);
    }
}
\ No newline at end of file

D module-gui/test/test-catch-text/test-gui-TextBounds.cpp => module-gui/test/test-catch-text/test-gui-TextBounds.cpp +0 -84
@@ 1,84 0,0 @@
// Copyright (c) 2017-2020, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#include <catch2/catch.hpp>
#include <limits>
#include <module-gui/gui/widgets/TextBlock.hpp>
#include <mock/buildTextDocument.hpp>
#include <module-gui/gui/widgets/Text.hpp>
#include <mock/multi-line-string.hpp>

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

    auto key_up = gui::InputEvent(
        {
            RawKey::State::Released,
            bsp::KeyCodes::Undefined,
        },
        gui::InputEvent::State::keyReleasedShort,
        gui::KeyCode::KEY_UP);

    auto key_left = gui::InputEvent(
        {
            RawKey::State::Released,
            bsp::KeyCodes::Undefined,
        },
        gui::InputEvent::State::keyReleasedShort,
        gui::KeyCode::KEY_LEFT);

    auto key_down = gui::InputEvent(
        {
            RawKey::State::Released,
            bsp::KeyCodes::Undefined,
        },
        gui::InputEvent::State::keyReleasedShort,
        gui::KeyCode::KEY_DOWN);

    auto texts            = mockup::lineStrings(6);
    auto [document, font] = mockup::buildMultilineTestDocument(texts);

    auto text = gui::Text();
    text.setText(std::make_unique<gui::TextDocument>(document));
    text.setFont(font);

    SECTION("moving cursor - hit bound")
    {
        auto lines  = Lines(&text);
        auto cursor = new gui::TextLineCursor(&text);

        lines.emplace(TextLine(*cursor, 1000));
        lines.emplace(TextLine(*cursor, 1000));
        lines.emplace(TextLine(*cursor, 1000));
        lines.emplace(TextLine(*cursor, 1000));
        lines.emplace(TextLine(*cursor, 1000));

        cursor->moveCursor(NavigationDirection::DOWN);
        cursor->moveCursor(NavigationDirection::DOWN);
        cursor->moveCursor(NavigationDirection::DOWN);

        auto bound = lines.checkNavigationBounds(*cursor, key_down);

        REQUIRE(bound == InputBound::HIT_BOUND);
    }

    SECTION("moving cursor - reach data end")
    {
        auto lines  = Lines(&text);
        auto cursor = new gui::TextLineCursor(&text);

        lines.emplace(TextLine(*cursor, 100));
        auto bound = lines.checkNavigationBounds(*cursor, key_up);
        REQUIRE(bound == InputBound::NO_DATA);

        cursor->moveCursor(NavigationDirection::RIGHT);
        bound = lines.checkNavigationBounds(*cursor, key_left);
        REQUIRE(bound == InputBound::CAN_MOVE);

        cursor->moveCursor(NavigationDirection::DOWN);
        bound = lines.checkNavigationBounds(*cursor, key_up);
        REQUIRE(bound == InputBound::NO_DATA);
    }
}