aboutsummaryrefslogtreecommitdiff
path: root/src/Text.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/Text.cpp')
-rw-r--r--src/Text.cpp325
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);
}
}