diff options
Diffstat (limited to 'src/Text.cpp')
-rw-r--r-- | src/Text.cpp | 325 |
1 files changed, 299 insertions, 26 deletions
diff --git a/src/Text.cpp b/src/Text.cpp index bc23235..be147db 100644 --- a/src/Text.cpp +++ b/src/Text.cpp @@ -22,8 +22,13 @@ namespace dchat color(sf::Color::White), urlColor(URL_COLOR), dirty(false), + dirtyText(false), + dirtyCaret(false), plainText(false), - totalHeight(0.0f) + editable(false), + caretMoveDirection(CaretMoveDirection::NONE), + totalHeight(0.0f), + caretIndex(0) { } @@ -36,32 +41,28 @@ namespace dchat color(sf::Color::White), urlColor(URL_COLOR), dirty(true), + dirtyText(false), + dirtyCaret(false), plainText(_plainText), + editable(false), + caretMoveDirection(CaretMoveDirection::NONE), totalHeight(0.0f), - lineSpacing(0.0f) + lineSpacing(0.0f), + caretIndex(0) { setString(_str); } + void Text::setString(const sf::String &str) { if(str != this->str) { this->str = str; dirty = true; - textElements.clear(); - stringSplitElements(this->str, 0); + dirtyText = true; } } - void Text::appendStringNewLine(const sf::String &str) - { - usize prevSize = this->str.getSize(); - this->str += '\n'; - this->str += str; - dirty = true; - stringSplitElements(this->str, prevSize); - } - void Text::setPosition(float x, float y) { position.x = x; @@ -109,6 +110,20 @@ namespace dchat } } + void Text::setEditable(bool editable) + { + if(editable != this->editable) + { + this->editable = editable; + if(!plainText) + { + dirty = true; + dirtyText = true; + } + dirtyCaret = true; + } + } + float Text::getHeight() const { return totalHeight; @@ -142,7 +157,7 @@ namespace dchat { StringViewUtf32 wholeStr(&stringToSplit[startIndex], stringToSplit.getSize() - startIndex); textElements.push_back({ wholeStr, TextElement::Type::TEXT }); - if(plainText) + if(plainText || editable) return; const char *httpStrRaw = "http://"; @@ -159,9 +174,9 @@ namespace dchat static_assert(sizeof(*parentheseStr.getData()) == sizeof(u32), "sf::String size has changed..."); std::vector<TextElement> newTextElements; - for(size_t i = 0; i < textElements.size(); ++i) + // Split emoji + for(const TextElement &textElement : textElements) { - TextElement textElement = textElements[i]; if(textElement.type != TextElement::Type::TEXT) { newTextElements.push_back(textElement); @@ -199,10 +214,10 @@ namespace dchat } textElements = newTextElements; + // Split http newTextElements.clear(); - for(size_t i = 0; i < textElements.size(); ++i) + for(const TextElement &textElement : textElements) { - TextElement textElement = textElements[i]; if(textElement.type != TextElement::Type::TEXT) { newTextElements.push_back(textElement); @@ -225,10 +240,10 @@ namespace dchat } textElements = newTextElements; + // Split https newTextElements.clear(); - for(size_t i = 0; i < textElements.size(); ++i) + for(const TextElement &textElement : textElements) { - TextElement textElement = textElements[i]; if(textElement.type != TextElement::Type::TEXT) { newTextElements.push_back(textElement); @@ -333,10 +348,13 @@ namespace dchat prevCodePoint = codePoint; glyphPos.x += kerning; + usize vertexStart = vertexOffset + i * 4; + switch(codePoint) { case ' ': { + vertices[vertexStart].position = sf::Vector2f(glyphPos.x, glyphPos.y - vspace); glyphPos.x += hspace; if(glyphPos.x > maxWidth * 0.5f) { @@ -347,6 +365,7 @@ namespace dchat } case '\t': { + vertices[vertexStart].position = sf::Vector2f(glyphPos.x, glyphPos.y - vspace); glyphPos.x += (hspace * TAB_WIDTH); if(glyphPos.x > maxWidth * 0.5f) { @@ -357,12 +376,14 @@ namespace dchat } case '\n': { + vertices[vertexStart].position = sf::Vector2f(glyphPos.x, glyphPos.y - vspace); glyphPos.x = 0.0f; glyphPos.y += vspace + lineSpacing; continue; } case '\v': { + vertices[vertexStart].position = sf::Vector2f(glyphPos.x, glyphPos.y - vspace); glyphPos.y += (vspace * TAB_WIDTH) + lineSpacing; continue; } @@ -408,10 +429,10 @@ namespace dchat sf::Color fontColor = (textElement.type == TextElement::Type::TEXT ? color : urlColor); - vertices[vertexOffset + i * 4 + 0] = { vertexTopLeft, fontColor, textureTopLeft }; - vertices[vertexOffset + i * 4 + 1] = { vertexTopRight, fontColor, textureTopRight }; - vertices[vertexOffset + i * 4 + 2] = { vertexBottomRight, fontColor, textureBottomRight }; - vertices[vertexOffset + i * 4 + 3] = { vertexBottomLeft, fontColor, textureBottomLeft }; + vertices[vertexStart + 0] = { vertexTopLeft, fontColor, textureTopLeft }; + vertices[vertexStart + 1] = { vertexTopRight, fontColor, textureTopRight }; + vertices[vertexStart + 2] = { vertexBottomRight, fontColor, textureBottomRight }; + vertices[vertexStart + 3] = { vertexBottomLeft, fontColor, textureBottomLeft }; glyphPos.x += glyph.advance; } @@ -433,14 +454,256 @@ namespace dchat totalHeight = glyphPos.y + vspace + lineSpacing; } + void Text::updateCaret() + { + assert(!dirty && !dirtyText); + switch(caretMoveDirection) + { + case CaretMoveDirection::UP: + { + caretIndex = getPreviousLineClosestPosition(caretIndex); + break; + } + case CaretMoveDirection::DOWN: + { + caretIndex = getNextLineClosestPosition(caretIndex); + break; + } + case CaretMoveDirection::HOME: + { + caretIndex = getStartOfLine(caretIndex); + break; + } + case CaretMoveDirection::END: + { + caretIndex = getEndOfLine(caretIndex); + break; + } + default: + // Ignore... + break; + } + + usize vertexIndex = caretIndex * 4; + const sf::Vertex &topLeftVertex = vertices[vertexIndex]; + caretPosition = topLeftVertex.position; + } + + bool Text::isCaretAtEnd() const + { + assert(!dirty && !dirtyText); + return textElements[0].text.size == 0 || caretIndex == textElements[0].text.size; + } + + // TODO: This can be optimized by using step algorithm (jump to middle of vertices list and check if it's at next row, if not then divide by 2 again, and do this recursively) + int Text::getStartOfLine(int startIndex) const + { + assert(!dirty && !dirtyText); + int numVertices = vertices.getVertexCount(); + if(numVertices < 4) return 0; + + usize vertexIndex = caretIndex * 4; + const sf::Vertex &startTopLeftVertex = vertices[vertexIndex]; + int startRow = getRowByPosition(startTopLeftVertex.position); + for(int i = startIndex * 4; i >= 0; i -= 4) + { + const sf::Vertex &topLeftVertex = vertices[i]; + int row = getRowByPosition(topLeftVertex.position); + if(row != startRow) + { + return std::max(0, i / 4 + 1); + } + } + return 0; + } + + // TODO: This can be optimized by using step algorithm (jump to middle of vertices list and check if it's at next row, if not then divide by 2 again, and do this recursively) + int Text::getEndOfLine(int startIndex) const + { + assert(!dirty && !dirtyText); + int numVertices = vertices.getVertexCount(); + if(numVertices < 4) return 0; + + usize vertexIndex = caretIndex * 4; + const sf::Vertex &startTopLeftVertex = vertices[vertexIndex]; + int startRow = getRowByPosition(startTopLeftVertex.position); + for(int i = startIndex * 4; i < numVertices; i += 4) + { + const sf::Vertex &topLeftVertex = vertices[i]; + int row = getRowByPosition(topLeftVertex.position); + if(row != startRow) + { + return std::max(0, i / 4 - 1); + } + } + return numVertices / 4; + } + + // TODO: This can be optimized by using step algorithm (jump to middle of vertices list and check if it's at next row, if not then divide by 2 again, and do this recursively) + int Text::getPreviousLineClosestPosition(int startIndex) const + { + assert(!dirty && !dirtyText); + int numVertices = vertices.getVertexCount(); + if(numVertices < 4) return 0; + + usize vertexIndex = caretIndex * 4; + const sf::Vertex &startTopLeftVertex = vertices[vertexIndex]; + int startRow = getRowByPosition(startTopLeftVertex.position); + int closestIndex = -1; + float closestAbsoluteDiffX = 0.0f; + for(int i = startIndex * 4; i >= 0; i -= 4) + { + const sf::Vertex &topLeftVertex = vertices[i]; + int row = getRowByPosition(topLeftVertex.position); + float absoluteDiffX = fabs(topLeftVertex.position.x - startTopLeftVertex.position.x); + int rowDiff = abs(row - startRow); + if(rowDiff > 1) + break; + + if(rowDiff == 1 && (closestIndex == -1 || absoluteDiffX < closestAbsoluteDiffX)) + { + closestIndex = i; + closestAbsoluteDiffX = absoluteDiffX; + } + } + + if(closestIndex != -1) + return closestIndex / 4; + + return 0; + } + + // TODO: This can be optimized by using step algorithm (jump to middle of vertices list and check if it's at next row, if not then divide by 2 again, and do this recursively) + int Text::getNextLineClosestPosition(int startIndex) const + { + assert(!dirty && !dirtyText); + int numVertices = vertices.getVertexCount(); + if(numVertices < 4) return 0; + + usize vertexIndex = caretIndex * 4; + const sf::Vertex &startTopLeftVertex = vertices[vertexIndex]; + int startRow = getRowByPosition(startTopLeftVertex.position); + int closestIndex = -1; + float closestAbsoluteDiffX = 0.0f; + for(int i = startIndex * 4; i < numVertices; i += 4) + { + const sf::Vertex &topLeftVertex = vertices[i]; + int row = getRowByPosition(topLeftVertex.position); + float absoluteDiffX = fabs(topLeftVertex.position.x - startTopLeftVertex.position.x); + int rowDiff = abs(row - startRow); + if(rowDiff > 1) + break; + + if(rowDiff == 1 && (closestIndex == -1 || absoluteDiffX < closestAbsoluteDiffX)) + { + closestIndex = i; + closestAbsoluteDiffX = absoluteDiffX; + } + } + + if(closestIndex != -1) + return closestIndex / 4; + + return numVertices / 4; + } + + int Text::getRowByPosition(const sf::Vector2f &position) const + { + assert(!dirty && !dirtyText); + const float vspace = font->getLineSpacing(characterSize); + return static_cast<int>(1.0f + position.y / (vspace + lineSpacing)); + } + + void Text::processEvent(const sf::Event &event) + { + if(!editable) return; + + if(event.type == sf::Event::KeyPressed) + { + if(event.key.code == sf::Keyboard::Left && caretIndex > 0) + { + --caretIndex; + dirtyCaret = true; + } + else if(event.key.code == sf::Keyboard::Right && !isCaretAtEnd()) + { + ++caretIndex; + dirtyCaret = true; + } + else if(event.key.code == sf::Keyboard::BackSpace && caretIndex > 0) + { + auto strBefore = str.substring(0, caretIndex - 1); + auto strAfter = str.substring(caretIndex); + setString(strBefore + strAfter); + --caretIndex; + dirtyCaret = true; + } + else if(event.key.code == sf::Keyboard::Delete && !isCaretAtEnd()) + { + auto strBefore = str.substring(0, caretIndex); + auto strAfter = str.substring(caretIndex + 1); + setString(strBefore + strAfter); + } + else if(event.key.code == sf::Keyboard::Up) + { + caretMoveDirection = CaretMoveDirection::UP; + } + else if(event.key.code == sf::Keyboard::Down) + { + caretMoveDirection = CaretMoveDirection::DOWN; + } + else if(event.key.code == sf::Keyboard::Home) + { + caretMoveDirection = CaretMoveDirection::HOME; + } + else if(event.key.code == sf::Keyboard::End) + { + caretMoveDirection = CaretMoveDirection::END; + } + } + else if(event.type == sf::Event::TextEntered) + { + if(event.text.unicode == 8 || event.text.unicode == 127) // backspace, del + return; + + if(isCaretAtEnd()) + str += event.text.unicode; + else + { + auto strBefore = str.substring(0, caretIndex); + auto strAfter = str.substring(caretIndex); + str = strBefore + event.text.unicode + strAfter; + } + + ++caretIndex; + dirty = true; + dirtyText = true; + dirtyCaret = true; + } + } + void Text::draw(sf::RenderTarget &target, Cache &cache) { + if(dirtyText) + { + textElements.clear(); + stringSplitElements(this->str, 0); + dirtyText = false; + } + if(dirty) { updateGeometry(); dirty = false; } + if(dirtyCaret || caretMoveDirection != CaretMoveDirection::NONE) + { + updateCaret(); + dirtyCaret = false; + caretMoveDirection = CaretMoveDirection::NONE; + } + float vspace = font->getLineSpacing(characterSize); sf::RenderStates states; @@ -458,6 +721,7 @@ namespace dchat states.texture = &font->getTexture(characterSize); target.draw(vertices, states); + pos.y -= floor(vspace); for(TextElement &textElement : textElements) { if(textElement.type == TextElement::Type::EMOJI) @@ -551,9 +815,8 @@ namespace dchat } case ContentByUrlResult::CachedType::WEB_PAGE_PREVIEW: { - const float previewWidth = floor(imageHeight * 1.77f); + const float previewWidth = fmin(maxWidth, floor(imageHeight * 1.77f)); - // No need to perform culling here, that is done in @Text draw function contentByUrlResult.webPagePreview->title.setCharacterSize(characterSize); contentByUrlResult.webPagePreview->title.setMaxWidth(previewWidth); contentByUrlResult.webPagePreview->title.setPosition(pos); @@ -573,5 +836,15 @@ namespace dchat } } } + + if(!editable) return; + + //float rows = floor(totalHeight / (vspace + lineSpacing)); + const float caretRow = getRowByPosition(caretPosition); + + sf::RectangleShape caretRect(sf::Vector2f(2.0f, floor(vspace))); + caretRect.setFillColor(sf::Color::White); + caretRect.setPosition(sf::Vector2f(floor(pos.x + caretPosition.x), floor(pos.y + caretRow * (vspace + lineSpacing)))); + target.draw(caretRect); } } |