mirror of
https://github.com/mudita/MuditaOS.git
synced 2026-04-24 09:03:41 -04:00
Updated widgets margins. Updated used fonts. Updated focus change bold on font, cursor position, underline bold. Added line spacing support and cleaned underline properties.
390 lines
12 KiB
C++
390 lines
12 KiB
C++
// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
|
|
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md
|
|
|
|
#include "TextLine.hpp"
|
|
#include "Common.hpp"
|
|
#include "Label.hpp"
|
|
#include "TextBlock.hpp"
|
|
#include "TextDocument.hpp"
|
|
#include "TextCursor.hpp"
|
|
#include <cstdio>
|
|
#include <RawFont.hpp>
|
|
#include <numeric>
|
|
|
|
namespace gui
|
|
{
|
|
|
|
/// helper function to get our text representation
|
|
Label *buildUITextPart(const UTF8 &text, const TextFormat *format)
|
|
{
|
|
auto item = new gui::Label(nullptr);
|
|
item->setText(text);
|
|
item->setFont(format->getFont());
|
|
item->setTextColor(format->getColor());
|
|
item->setSize(item->getTextNeedSpace(), item->getTextHeight());
|
|
item->setEdges(RectangleEdge::None);
|
|
return item;
|
|
}
|
|
|
|
/// Note - line breaking could be done here with different TextLines to return
|
|
/// or via different block types (i.e. numeric block tyle could be not "breakable"
|
|
TextLine::TextLine(BlockCursor &localCursor, unsigned int maxWidth) : maxWidth(maxWidth)
|
|
{
|
|
do {
|
|
if (!localCursor) { // cursor is faulty
|
|
lineEnd = true;
|
|
return;
|
|
}
|
|
|
|
if (localCursor.atEnd() && !localCursor.checkLastNewLine()) {
|
|
lineEnd = true;
|
|
return;
|
|
}
|
|
|
|
// take text we want to show
|
|
auto text = localCursor.getUTF8Text();
|
|
|
|
if (text.length() == 0 && localCursor.checkCurrentBlockNoNewLine() && !localCursor.atEnd()) {
|
|
setLineStartConditions(localCursor.getBlockNumber(), localCursor.getPosition());
|
|
localCursor.goToNextBlock();
|
|
continue;
|
|
}
|
|
|
|
auto textFormat = localCursor->getFormat();
|
|
if (textFormat->getFont() == nullptr) {
|
|
lineEnd = true;
|
|
return;
|
|
}
|
|
|
|
// check if max provided width is enough to enter one char at least
|
|
if (maxWidth < textFormat->getFont()->getCharPixelWidth(text[0])) {
|
|
lineEnd = true;
|
|
return;
|
|
}
|
|
|
|
auto signsCountToShow = calculateSignsToShow(localCursor, text, maxWidth - widthUsed);
|
|
|
|
// we can show nothing - this is the end of this line
|
|
if (signsCountToShow == 0) {
|
|
auto item = buildUITextPart("", textFormat);
|
|
widthUsed = item->getTextNeedSpace();
|
|
heightUsed = std::max(heightUsed, item->getTextHeight());
|
|
lineContent.emplace_back(item);
|
|
end = TextBlock::End::None;
|
|
|
|
setLineStartConditions(localCursor.getBlockNumber(), localCursor.getPosition());
|
|
|
|
return;
|
|
}
|
|
|
|
// create item for show and update Line data
|
|
auto item = buildUITextPart(textToPrint(signsCountToShow, text), textFormat);
|
|
shownLetterCount += signsCountToShow;
|
|
widthUsed += item->getTextNeedSpace();
|
|
heightUsed = std::max(heightUsed, item->getTextHeight());
|
|
lineContent.emplace_back(item);
|
|
|
|
setLineStartConditions(localCursor.getBlockNumber(), localCursor.getPosition());
|
|
|
|
localCursor += signsCountToShow;
|
|
|
|
if (removeTrailingSpace) {
|
|
end = TextBlock::End::Newline;
|
|
return;
|
|
}
|
|
|
|
if (localCursor.checkAndInvalidateBlockChanged() && localCursor.checkPreviousBlockNewLine()) {
|
|
end = TextBlock::End::Newline;
|
|
return;
|
|
}
|
|
|
|
// not whole text shown, try again for next line if you want
|
|
if (signsCountToShow < text.length()) {
|
|
end = TextBlock::End::None;
|
|
return;
|
|
}
|
|
|
|
} while (true);
|
|
}
|
|
|
|
unsigned int TextLine::calculateSignsToShow(BlockCursor &localCursor, UTF8 &text, unsigned int space)
|
|
{
|
|
auto signsCountToShow = localCursor->getFormat()->getFont()->getCharCountInSpace(text, space);
|
|
|
|
// additional one sign to detect potential space as last character in line
|
|
auto searchSubstring = text.substr(0, signsCountToShow + 1);
|
|
|
|
auto newlinePos = searchSubstring.findLast("\n", signsCountToShow);
|
|
auto spacePos = searchSubstring.findLast(" ", signsCountToShow);
|
|
|
|
// check if space or newline in word detection range
|
|
if (spacePos <= signsCountToShow - text::word_detection_range) {
|
|
spacePos = UTF8::npos;
|
|
}
|
|
|
|
if (newlinePos <= signsCountToShow - text::word_detection_range) {
|
|
newlinePos = UTF8::npos;
|
|
}
|
|
|
|
// check if space found and no newline at end
|
|
if (spacePos != UTF8::npos && newlinePos == UTF8::npos) {
|
|
// if line ends on space remove it when drawing
|
|
if (spacePos >= signsCountToShow) {
|
|
removeTrailingSpace = true;
|
|
}
|
|
|
|
// add one to include space in the line
|
|
signsCountToShow = spacePos + 1;
|
|
|
|
breakLineDashAddition = false;
|
|
}
|
|
// if sings still left in text add dash sign
|
|
else if (signsCountToShow != 0 && signsCountToShow < text.length()) {
|
|
// decrease character shown count by one to fit dash
|
|
signsCountToShow = signsCountToShow - 1;
|
|
breakLineDashAddition = true;
|
|
}
|
|
|
|
return signsCountToShow;
|
|
}
|
|
|
|
UTF8 TextLine::textToPrint(unsigned int signsCountToShow, UTF8 &text)
|
|
{
|
|
auto textToPrint = text.substr(0, signsCountToShow);
|
|
|
|
if (removeTrailingSpace) {
|
|
textToPrint = text.substr(0, signsCountToShow - 1);
|
|
}
|
|
|
|
if (breakLineDashAddition) {
|
|
textToPrint = textToPrint + "-";
|
|
}
|
|
|
|
return textToPrint;
|
|
}
|
|
|
|
TextLine::TextLine(TextLine &&from) noexcept
|
|
{
|
|
lineContent = std::move(from.lineContent);
|
|
shownLetterCount = from.shownLetterCount;
|
|
widthUsed = from.widthUsed;
|
|
heightUsed = from.heightUsed;
|
|
underline = from.underline;
|
|
underLineProperties = from.underLineProperties;
|
|
lineEnd = from.lineEnd;
|
|
end = from.end;
|
|
maxWidth = from.maxWidth;
|
|
breakLineDashAddition = from.breakLineDashAddition;
|
|
removeTrailingSpace = from.removeTrailingSpace;
|
|
lineStartBlockNumber = from.lineStartBlockNumber;
|
|
lineStartBlockPosition = from.lineStartBlockPosition;
|
|
lineVisible = from.lineVisible;
|
|
}
|
|
|
|
TextLine::~TextLine()
|
|
{
|
|
for (auto &el : lineContent) {
|
|
if (el->parent == nullptr) {
|
|
delete el;
|
|
}
|
|
}
|
|
|
|
if (underline != nullptr && underline->parent == nullptr) {
|
|
delete underline;
|
|
}
|
|
}
|
|
|
|
/// class to disown Item temporary to ignore callback
|
|
class ScopedParentDisown
|
|
{
|
|
Item *parent = nullptr;
|
|
Item *item = nullptr;
|
|
|
|
public:
|
|
ScopedParentDisown(Item *it) : item(it)
|
|
{
|
|
if (item != nullptr) {
|
|
parent = item->parent;
|
|
}
|
|
}
|
|
|
|
~ScopedParentDisown()
|
|
{
|
|
if (item != nullptr) {
|
|
item->parent = parent;
|
|
}
|
|
}
|
|
};
|
|
|
|
void TextLine::setPosition(const short &x, const short &y)
|
|
{
|
|
auto lineXPosition = x;
|
|
|
|
updateUnderline(x, y);
|
|
|
|
for (auto &el : lineContent) {
|
|
auto scopedDisown = ScopedParentDisown(el);
|
|
el->setArea({lineXPosition, y - underLineProperties.underLinePadding, el->getWidth(), el->getHeight()});
|
|
lineXPosition += el->getWidth();
|
|
}
|
|
}
|
|
|
|
void TextLine::setLineStartConditions(unsigned int startBlockNumber, unsigned int startBlockPosition)
|
|
{
|
|
if (lineStartBlockNumber == text::npos && lineStartBlockPosition == text::npos) {
|
|
lineStartBlockNumber = startBlockNumber;
|
|
lineStartBlockPosition = startBlockPosition;
|
|
}
|
|
}
|
|
|
|
void TextLine::setParent(Item *parent)
|
|
{
|
|
if (parent == nullptr) {
|
|
return;
|
|
}
|
|
|
|
createUnderline(maxWidth, underLineProperties.underlineHeight);
|
|
parent->addWidget(underline);
|
|
|
|
for (auto &el : lineContent) {
|
|
parent->addWidget(el);
|
|
}
|
|
}
|
|
|
|
Length TextLine::getWidth() const
|
|
{
|
|
Length width = 0;
|
|
for (auto &line : lineContent) {
|
|
width += line->getWidth();
|
|
}
|
|
return width;
|
|
}
|
|
|
|
uint32_t TextLine::getWidthTo(unsigned int pos) const
|
|
{
|
|
uint32_t width = 0;
|
|
auto currentPos = 0;
|
|
if (pos == text::npos) {
|
|
return 0;
|
|
}
|
|
for (auto &el : lineContent) {
|
|
if (el->getFont() == nullptr) {
|
|
continue;
|
|
}
|
|
if (currentPos + el->getTextLength() > pos) {
|
|
width += el->getFont()->getPixelWidth(el->getText(), 0, pos - currentPos);
|
|
return width;
|
|
}
|
|
else {
|
|
width += el->getWidth();
|
|
}
|
|
currentPos += el->getTextLength();
|
|
}
|
|
return width;
|
|
}
|
|
|
|
const Item *TextLine::getElement(unsigned int pos) const noexcept
|
|
{
|
|
unsigned int local_pos = 0;
|
|
for (auto &el : lineContent) {
|
|
local_pos += el->getTextLength();
|
|
if (local_pos >= pos) {
|
|
return el;
|
|
}
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
UTF8 TextLine::getText(unsigned int pos) const
|
|
{
|
|
UTF8 text;
|
|
for (auto &label : lineContent) {
|
|
if (label->getFont() == nullptr) {
|
|
continue;
|
|
}
|
|
text += label->getText();
|
|
}
|
|
|
|
return text;
|
|
}
|
|
|
|
void TextLine::erase()
|
|
{
|
|
for (auto &el : lineContent) {
|
|
if (el->parent != nullptr) {
|
|
auto p = el->parent;
|
|
p->erase(el);
|
|
}
|
|
else {
|
|
delete el;
|
|
}
|
|
}
|
|
|
|
if (underline != nullptr && underline->parent != nullptr) {
|
|
auto p = underline->parent;
|
|
p->removeWidget(underline);
|
|
}
|
|
|
|
lineContent.clear();
|
|
}
|
|
|
|
void TextLine::alignH(Alignment lineAlign, Length parentLength) const
|
|
{
|
|
Position xOffset = lineAlign.calculateHAlignment(parentLength, getWidth());
|
|
|
|
if (xOffset >= 0) {
|
|
|
|
if (underline != nullptr && underLineProperties.drawUnderlineMode == UnderlineDrawMode::Concurrent)
|
|
underline->setPosition(underline->getPosition(Axis::X) + xOffset, Axis::X);
|
|
|
|
for (auto &el : lineContent) {
|
|
auto scopedDisown = ScopedParentDisown(el);
|
|
el->setPosition(el->getPosition(Axis::X) + xOffset, Axis::X);
|
|
}
|
|
}
|
|
}
|
|
|
|
void TextLine::alignV(Alignment lineAlign, Length parentLength, Length linesHeight)
|
|
{
|
|
Position yOffset = lineAlign.calculateVAlignment(parentLength, linesHeight);
|
|
|
|
if (yOffset >= 0 && yOffset != storedYOffset) {
|
|
|
|
// Refactor - workaround for multiple offset addition.
|
|
storedYOffset = yOffset;
|
|
|
|
if (underline != nullptr)
|
|
underline->setPosition(underline->getPosition(Axis::Y) + yOffset, Axis::Y);
|
|
|
|
for (auto &el : lineContent) {
|
|
auto scopedDisown = ScopedParentDisown(el);
|
|
el->setPosition(el->getPosition(Axis::Y) + yOffset, Axis::Y);
|
|
}
|
|
}
|
|
}
|
|
|
|
void TextLine::createUnderline(unsigned int width, unsigned int initHeight)
|
|
{
|
|
if (underLineProperties.draw) {
|
|
|
|
underline = new Rect(nullptr, 0, 0, maxWidth, initHeight);
|
|
underline->setPenWidth(underLineProperties.thickness);
|
|
underline->setEdges(RectangleEdge::Bottom);
|
|
|
|
if (underLineProperties.drawUnderlineMode == UnderlineDrawMode::WholeLine) {
|
|
heightUsed = std::max(heightUsed, (Length)initHeight);
|
|
}
|
|
}
|
|
}
|
|
|
|
void TextLine::updateUnderline(const short &x, const short &y)
|
|
{
|
|
if (underline != nullptr && underLineProperties.drawUnderlineMode == UnderlineDrawMode::WholeLine) {
|
|
underline->setArea({x, y, underline->widgetArea.w, height()});
|
|
}
|
|
else if (underline != nullptr && underLineProperties.drawUnderlineMode == UnderlineDrawMode::Concurrent) {
|
|
underline->setArea({x, y, getWidth(), height()});
|
|
}
|
|
}
|
|
} // namespace gui
|