From 782980b261457bc9a54235bd1a0fc90a6bd1484e Mon Sep 17 00:00:00 2001 From: PrzeBrudny <60609703+PrzeBrudny@users.noreply.github.com> Date: Fri, 20 Nov 2020 02:40:58 +0100 Subject: [PATCH] [EGD-4372] Text addition boundaries added with tests. Removed old bounds checking. Text blocks on newlines split issue fixed. Text widgets cleanups. (#1020) --- changelog.md | 1 + module-gui/gui/widgets/Lines.cpp | 160 ++--- module-gui/gui/widgets/Lines.hpp | 50 +- module-gui/gui/widgets/Text.cpp | 575 ++++++++++++---- module-gui/gui/widgets/Text.hpp | 44 +- module-gui/gui/widgets/TextBlock.cpp | 10 + module-gui/gui/widgets/TextBlock.hpp | 2 + module-gui/gui/widgets/TextBlockCursor.cpp | 7 +- module-gui/gui/widgets/TextBlockCursor.hpp | 5 +- module-gui/gui/widgets/TextConstants.hpp | 27 +- module-gui/gui/widgets/TextCursor.cpp | 38 +- module-gui/gui/widgets/TextCursor.hpp | 2 +- module-gui/gui/widgets/TextFixedSize.cpp | 64 +- module-gui/gui/widgets/TextFixedSize.hpp | 1 - module-gui/gui/widgets/TextLine.cpp | 24 +- module-gui/gui/widgets/TextLine.hpp | 1 - module-gui/gui/widgets/TextParse.cpp | 7 +- .../test/test-catch-text/CMakeLists.txt | 1 - .../test/test-catch-text/test-gui-Text.cpp | 647 ++++++++++++++++++ .../test-catch-text/test-gui-TextBounds.cpp | 84 --- 20 files changed, 1293 insertions(+), 457 deletions(-) delete mode 100644 module-gui/test/test-catch-text/test-gui-TextBounds.cpp diff --git a/changelog.md b/changelog.md index 5b42b5bda8f7263b7f54c935e4ad472cbfa47327..9b88c3154c8f297d697cf8670f6f5b5112207a61 100644 --- a/changelog.md +++ b/changelog.md @@ -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 diff --git a/module-gui/gui/widgets/Lines.cpp b/module-gui/gui/widgets/Lines.cpp index 07c6018519a43573c742071f2f39f97dcb2f69ca..0e4e432ec7b190b475ac6ca21995f9202e72e398 100644 --- a/module-gui/gui/widgets/Lines.cpp +++ b/module-gui/gui/widgets/Lines.cpp @@ -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 diff --git a/module-gui/gui/widgets/Lines.hpp b/module-gui/gui/widgets/Lines.hpp index ee026bb9895db4c4f23b0f4b208bfdbad41d7dce..0fc1bec88eb79189d960918a7e23b992e8a93281 100644 --- a/module-gui/gui/widgets/Lines.hpp +++ b/module-gui/gui/widgets/Lines.hpp @@ -22,21 +22,24 @@ namespace gui class Lines { - Text *parent = nullptr; + Text *text = nullptr; std::list 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); diff --git a/module-gui/gui/widgets/Text.cpp b/module-gui/gui/widgets/Text.cpp index 4372d75224ae7ad9566f6ca654b18f3945b14595..16bc4614835acf2bab15920610e9084678413a15 100644 --- a/module-gui/gui/widgets/Text.cpp +++ b/module-gui/gui/widgets/Text.cpp @@ -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(textToTextBlocks(text, format.getFont(), TextBlock::End::None))); + setText(std::make_unique(textToTextBlocks(text, format))); } void Text::setText(std::unique_ptr &&doc) @@ -196,12 +214,6 @@ namespace gui buildCursor(); } - std::tuple 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(textToTextBlocks(text, format.getFont(), TextBlock::End::None))); + buildDocument(std::make_unique(textToTextBlocks(text, format))); } void Text::buildDocument(std::unique_ptr &&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 + { + auto preDrawLines = std::make_unique(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 + { + auto preDrawLines = std::make_unique(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 + { + 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(*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 + { + 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(*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(*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 + { + 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(*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(*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 + { + std::tuple 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 */ diff --git a/module-gui/gui/widgets/Text.hpp b/module-gui/gui/widgets/Text.hpp index b36b57c4d10c849266c53414c08f6ddd23d71a89..c433413e5a1d7f335455e9a7bf40362a4d0d5cdb 100644 --- a/module-gui/gui/widgets/Text.hpp +++ b/module-gui/gui/widgets/Text.hpp @@ -76,20 +76,39 @@ namespace gui protected: TextType textType = TextType::MULTI_LINE; + std::list limitsList; + /// points to default text font to use - bool underline = false; TextFormat format; - auto moveCursor(const NavigationDirection &direction, std::unique_ptr &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; + auto makePreDrawLines(const TextBlock &textBlock) -> std::unique_ptr; + + auto checkMaxSignsLimit(unsigned int limitVal) -> InputBound; + auto checkMaxSignsLimit(const TextBlock &textBlock, unsigned int limitVal) -> std::tuple; + auto checkMaxSizeLimit(uint32_t utfVal) -> InputBound; + auto checkMaxSizeLimit(const TextBlock &textBlock) -> std::tuple; + auto checkMaxLinesLimit(uint32_t utfVal, unsigned int limitVal) -> InputBound; + auto checkMaxLinesLimit(const TextBlock &textBlock, unsigned int limitVal) -> std::tuple; void preBuildDrawListHookImplementation(std::list &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 &&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; bool addChar(uint32_t utf8); bool removeChar(); - InputBound processBound(InputBound bound, const InputEvent &event); void onTextChanged(); }; diff --git a/module-gui/gui/widgets/TextBlock.cpp b/module-gui/gui/widgets/TextBlock.cpp index b920773d674593161aad11992761596a630a76c8..b29c2b780cf5149bd730540f6518dc09cf03509c 100644 --- a/module-gui/gui/widgets/TextBlock.cpp +++ b/module-gui/gui/widgets/TextBlock.cpp @@ -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(*p.format); + end = p.end; + } + return *this; + } + const UTF8 &TextBlock::getText() const { return text; diff --git a/module-gui/gui/widgets/TextBlock.hpp b/module-gui/gui/widgets/TextBlock.hpp index 9822fba395a7cc721e99eedf4743d26afd5cc5d4..76467ffb4ff76bf8c0a8e5daa2e09421c409a385 100644 --- a/module-gui/gui/widgets/TextBlock.hpp +++ b/module-gui/gui/widgets/TextBlock.hpp @@ -50,5 +50,7 @@ namespace gui void addChar(uint32_t utf_val, unsigned int pos); bool isEmpty() const; + + TextBlock &operator=(const TextBlock &); }; } // namespace gui diff --git a/module-gui/gui/widgets/TextBlockCursor.cpp b/module-gui/gui/widgets/TextBlockCursor.cpp index ea2af69d228339e79011aefba634243edcd06610..86e7916e6fa7dac34bc6e88ade83eb1fe6d26375 100644 --- a/module-gui/gui/widgets/TextBlockCursor.cpp +++ b/module-gui/gui/widgets/TextBlockCursor.cpp @@ -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(); } diff --git a/module-gui/gui/widgets/TextBlockCursor.hpp b/module-gui/gui/widgets/TextBlockCursor.hpp index e1d531f012c3c1f79b244d0a613ba32a42ab9bf3..543206b6380df78ecfe4d48229819f6c93be7855 100644 --- a/module-gui/gui/widgets/TextBlockCursor.hpp +++ b/module-gui/gui/widgets/TextBlockCursor.hpp @@ -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 diff --git a/module-gui/gui/widgets/TextConstants.hpp b/module-gui/gui/widgets/TextConstants.hpp index d167d5dcfe9af949703de5852fde7405cf459ddc..7f17930ea90c582ef3bb744e6f8df4b9eb880a03 100644 --- a/module-gui/gui/widgets/TextConstants.hpp +++ b/module-gui/gui/widgets/TextConstants.hpp @@ -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 diff --git a/module-gui/gui/widgets/TextCursor.cpp b/module-gui/gui/widgets/TextCursor.cpp index 4ca5b527e85ce0bf065a47e00c9654247a22b326..f34fca6f4ddafdcf1acde1c4f52af8d6802afe2e 100644 --- a/module-gui/gui/widgets/TextCursor.cpp +++ b/module-gui/gui/widgets/TextCursor.cpp @@ -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; diff --git a/module-gui/gui/widgets/TextCursor.hpp b/module-gui/gui/widgets/TextCursor.hpp index ffb2ee3b32233a1a38d12d432abfb7cc8b618aa2..a3fa053183be1ae971c4a664503fc41d5c6695ad 100644 --- a/module-gui/gui/widgets/TextCursor.hpp +++ b/module-gui/gui/widgets/TextCursor.hpp @@ -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 diff --git a/module-gui/gui/widgets/TextFixedSize.cpp b/module-gui/gui/widgets/TextFixedSize.cpp index ef97d1b62ea31db3a54d5381a0a55f0d74c151f4..53ebf2434da2ec356d7dce50a68499c0a6f9a8bb 100644 --- a/module-gui/gui/widgets/TextFixedSize.cpp +++ b/module-gui/gui/widgets/TextFixedSize.cpp @@ -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 diff --git a/module-gui/gui/widgets/TextFixedSize.hpp b/module-gui/gui/widgets/TextFixedSize.hpp index 59f0317f6b17497b4ee24f306b2cac114977f53b..1273130f44f0634de51816eba43d73013550e568 100644 --- a/module-gui/gui/widgets/TextFixedSize.hpp +++ b/module-gui/gui/widgets/TextFixedSize.hpp @@ -12,7 +12,6 @@ namespace gui { protected: unsigned int linesCount = style::text::maxTextLines; - Position underlinePadding = 0; void drawLines() override; diff --git a/module-gui/gui/widgets/TextLine.cpp b/module-gui/gui/widgets/TextLine.cpp index 252ac82f560366e559bab1571ec976569d55e0e9..4b7fed7c275e372e727c4ebe0b9ef2ca7255a441 100644 --- a/module-gui/gui/widgets/TextLine.cpp +++ b/module-gui/gui/widgets/TextLine.cpp @@ -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 diff --git a/module-gui/gui/widgets/TextLine.hpp b/module-gui/gui/widgets/TextLine.hpp index d82cd7a70a658f311d3033d4f445cf1658ef4105..b8102cc2bae73cf70d73a8b5b8da2c97cf6cd8fd 100644 --- a/module-gui/gui/widgets/TextLine.hpp +++ b/module-gui/gui/widgets/TextLine.hpp @@ -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 diff --git a/module-gui/gui/widgets/TextParse.cpp b/module-gui/gui/widgets/TextParse.cpp index d443c751ffda80c2f7ed481405d1262cb40fe359..75a90523a7bbe1dbaeb324e9c99ed55d6ccc2103 100644 --- a/module-gui/gui/widgets/TextParse.cpp +++ b/module-gui/gui/widgets/TextParse.cpp @@ -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(format)); + if (ss.good()) { + block.setEnd(TextBlock::End::Newline); } - blocks.emplace_back(TextBlock(line, std::make_unique(format))); + blocks.emplace_back(block); } addEmptyBlockOnNewline(blocks, format); diff --git a/module-gui/test/test-catch-text/CMakeLists.txt b/module-gui/test/test-catch-text/CMakeLists.txt index f37b1aec3160caba0e02b50b5b23f6fc4a88e089..1854257969ce88af9b0f646135e362319d5781ff 100644 --- a/module-gui/test/test-catch-text/CMakeLists.txt +++ b/module-gui/test/test-catch-text/CMakeLists.txt @@ -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/ diff --git a/module-gui/test/test-catch-text/test-gui-Text.cpp b/module-gui/test/test-catch-text/test-gui-Text.cpp index d38811658df050f0645536ed5b99f9258b791884..5839f49f5a579232d64f1be67a4fafaa8ad27814 100644 --- a/module-gui/test/test-catch-text/test-gui-Text.cpp +++ b/module-gui/test/test-catch-text/test-gui-Text.cpp @@ -16,6 +16,8 @@ #include #include #include +#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 = + "TestString1

Test String 2"; + + 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 = + "Test String 1

Test String 2"; + + 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 = + "Test String 1

Test String 2"; + + 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 = + "Test String 1

Test String 2"; + + 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 diff --git a/module-gui/test/test-catch-text/test-gui-TextBounds.cpp b/module-gui/test/test-catch-text/test-gui-TextBounds.cpp deleted file mode 100644 index 10ebea5d280d481b62324fbe4c3f5cf50ea8b6a9..0000000000000000000000000000000000000000 --- a/module-gui/test/test-catch-text/test-gui-TextBounds.cpp +++ /dev/null @@ -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 -#include -#include -#include -#include -#include - -// 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(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); - } -}