From 2b7d030551a357272f9cc39e347855bd2e3faba2 Mon Sep 17 00:00:00 2001 From: dec05eba Date: Mon, 7 May 2018 23:00:21 +0200 Subject: Improve text editing, use Text object for chatbar Improve text rendering by not using floating point position --- include/Chatbar.hpp | 20 ++----- include/Text.hpp | 6 +++ src/Cache.cpp | 12 ++--- src/Channel.cpp | 2 +- src/ChannelTopPanel.cpp | 1 + src/Chatbar.cpp | 140 ++++-------------------------------------------- src/Text.cpp | 140 +++++++++++++++++++++++++++++++++++++++--------- 7 files changed, 144 insertions(+), 177 deletions(-) diff --git a/include/Chatbar.hpp b/include/Chatbar.hpp index 27b57f9..5d69448 100644 --- a/include/Chatbar.hpp +++ b/include/Chatbar.hpp @@ -1,7 +1,8 @@ #pragma once #include "StringView.hpp" -#include +#include "Text.hpp" +#include "Cache.hpp" #include #include #include @@ -18,20 +19,10 @@ namespace dchat public: Chatbar(); - void addChar(sf::Uint32 codePoint); - void addString(const std::string &strToAdd); - const sf::String& getString() const; - void removePreviousChar(); - void removeNextChar(); - void clear(); - - void moveCaretLeft(); - void moveCaretRight(); - bool isFocused() const; void processEvent(const sf::Event &event, Channel *channel); - void draw(sf::RenderWindow &window); + void draw(sf::RenderWindow &window, Cache &cache); static float getHeight(); @@ -41,12 +32,9 @@ namespace dchat private: void processChatCommand(const StringView &cmd); private: - sf::Text text; + Text text; sf::RectangleShape background; sf::RectangleShape inputBackground; - int caretIndex; - sf::Vector2f caretOffset; - sf::Clock blinkTimer; bool focused; }; } diff --git a/include/Text.hpp b/include/Text.hpp index 0bc3ced..d2809de 100644 --- a/include/Text.hpp +++ b/include/Text.hpp @@ -36,11 +36,17 @@ namespace dchat Text(const sf::String &str, const sf::Font *font, unsigned int characterSize, float maxWidth, bool plainText = true); void setString(const sf::String &str); + const sf::String& getString() const; void setPosition(float x, float y); void setPosition(const sf::Vector2f &position); void setMaxWidth(float maxWidth); + void setCharacterSize(unsigned int characterSize); + unsigned int getCharacterSize() const; + + const sf::Font* getFont() const; + void setFillColor(sf::Color color); void setLineSpacing(float lineSpacing); void setEditable(bool editable); diff --git a/src/Cache.cpp b/src/Cache.cpp index c795595..0b4bfbf 100644 --- a/src/Cache.cpp +++ b/src/Cache.cpp @@ -196,6 +196,10 @@ namespace dchat int exitStatus; if(it->process->try_get_exit_status(exitStatus)) { + boost::filesystem::path filepath = getImagesDir(); + odhtdb::Hash urlHash(it->url.data(), it->url.size()); + filepath /= urlHash.toString(); + ContentByUrlResult contentByUrlResult; bool failed = exitStatus != 0; if(failed) @@ -204,10 +208,6 @@ namespace dchat } else { - boost::filesystem::path filepath = getImagesDir(); - odhtdb::Hash urlHash(it->url.data(), it->url.size()); - filepath /= urlHash.toString(); - contentByUrlResult = loadImageFromFile(filepath); if(contentByUrlResult.type == ContentByUrlResult::Type::CACHED) { @@ -217,7 +217,7 @@ namespace dchat imageDownloadMutex.lock(); contentUrlCache[it->url] = contentByUrlResult; - boost::filesystem::path downloadingFilepath = getImagesDir() / ".downloading"; + boost::filesystem::path downloadingFilepath = filepath / ".downloading"; // Intentionally ignore failure, program should not crash if we fail to remove these files... boost::system::error_code err; boost::filesystem::remove(downloadingFilepath, err); @@ -277,7 +277,7 @@ namespace dchat odhtdb::Hash urlHash(url.data(), url.size()); filepath /= urlHash.toString(); - boost::filesystem::path downloadingFilepath = getImagesDir() / ".downloading"; + boost::filesystem::path downloadingFilepath = filepath / ".downloading"; if(boost::filesystem::exists(downloadingFilepath)) { // Intentionally ignore failure, program should not crash if we fail to remove these files... diff --git a/src/Channel.cpp b/src/Channel.cpp index 2426981..e433aee 100644 --- a/src/Channel.cpp +++ b/src/Channel.cpp @@ -185,7 +185,7 @@ namespace dchat void Channel::draw(sf::RenderWindow &window, Cache &cache) { messageBoard.draw(window, cache); - chatbar.draw(window); + chatbar.draw(window, cache); } void Channel::setCurrent(Channel *channel) diff --git a/src/ChannelTopPanel.cpp b/src/ChannelTopPanel.cpp index 1781e74..44ce007 100644 --- a/src/ChannelTopPanel.cpp +++ b/src/ChannelTopPanel.cpp @@ -6,6 +6,7 @@ #include "../include/ChannelSidePanel.hpp" #include "../include/UsersSidePanel.hpp" #include +#include #include namespace dchat diff --git a/src/Chatbar.cpp b/src/Chatbar.cpp index 55fd764..5f17c28 100644 --- a/src/Chatbar.cpp +++ b/src/Chatbar.cpp @@ -29,89 +29,15 @@ namespace dchat unordered_map binds; Chatbar::Chatbar() : - text("", *ResourceCache::getFont("fonts/Roboto-Regular.ttf"), FONT_SIZE * Settings::getScaling()), - caretIndex(0), + text("", ResourceCache::getFont("fonts/Roboto-Regular.ttf"), FONT_SIZE * Settings::getScaling(), 0), focused(true) { + text.setEditable(true); text.setFillColor(sf::Color(240, 240, 240)); background.setFillColor(ColorScheme::getBackgroundColor()); inputBackground.setFillColor(ColorScheme::getBackgroundColor() + sf::Color(10, 10, 10)); } - void Chatbar::addChar(sf::Uint32 codePoint) - { - auto str = text.getString(); - str.insert(caretIndex, codePoint); - text.setString(str); - ++caretIndex; - caretOffset = text.findCharacterPos(caretIndex) - text.getPosition(); - blinkTimer.restart(); - } - - void Chatbar::addString(const string &strToAdd) - { - if(strToAdd.empty()) return; - auto str = text.getString(); - str.insert(caretIndex, sf::String::fromUtf8(strToAdd.begin(), strToAdd.end())); - text.setString(str); - caretIndex += strToAdd.size(); - caretOffset = text.findCharacterPos(caretIndex) - text.getPosition(); - blinkTimer.restart(); - } - - const sf::String& Chatbar::getString() const - { - return text.getString(); - } - - void Chatbar::removePreviousChar() - { - if(caretIndex > 0) - { - auto str = text.getString(); - str.erase(caretIndex - 1); - text.setString(str); - --caretIndex; - caretOffset = text.findCharacterPos(caretIndex) - text.getPosition(); - } - blinkTimer.restart(); - } - - void Chatbar::removeNextChar() - { - if(caretIndex < text.getString().getSize()) - { - auto str = text.getString(); - str.erase(caretIndex); - text.setString(str); - } - blinkTimer.restart(); - } - - void Chatbar::clear() - { - text.setString(""); - caretIndex = 0; - caretOffset.x = 0.0f; - caretOffset.y = 0.0f; - blinkTimer.restart(); - } - - void Chatbar::moveCaretLeft() - { - caretIndex = max(0, caretIndex - 1); - // TODO: Use glyph size to optimize this, no need to iterate all glyphs - caretOffset = text.findCharacterPos(caretIndex) - text.getPosition(); - blinkTimer.restart(); - } - - void Chatbar::moveCaretRight() - { - caretIndex = min((int)text.getString().getSize(), caretIndex + 1); - caretOffset = text.findCharacterPos(caretIndex) - text.getPosition(); - blinkTimer.restart(); - } - bool Chatbar::isFocused() const { return focused; @@ -207,18 +133,6 @@ namespace dchat } } - static string getClipboard() - { - string result; - TinyProcessLib::Process process("xsel -o -b", "", [&result](const char *bytes, size_t n) - { - result.append(bytes, n); - }); - if(process.get_exit_status() != 0) - fprintf(stderr, "Failed to get clipboard content\n"); - return result; - } - static void findReplaceAll(string &str, const string &substrToReplace, const string &stringToReplaceWith) { size_t findOffset = 0; @@ -245,21 +159,20 @@ namespace dchat { if(!focused) return; + text.processEvent(event); if(event.type == sf::Event::TextEntered) { - if(event.text.unicode == 8) // backspace - removePreviousChar(); - else if(event.text.unicode == 13) // enter + if(event.text.unicode == 13) // enter { - if(!getString().isEmpty()) + if(!text.getString().isEmpty()) { if(sf::Keyboard::isKeyPressed(sf::Keyboard::Key::LShift) || sf::Keyboard::isKeyPressed(sf::Keyboard::Key::RShift)) { - addChar('\n'); + //addChar('\n'); } else { - auto chatbarMsgUtf8 = getString().toUtf8(); + auto chatbarMsgUtf8 = text.getString().toUtf8(); string msg; msg.resize(chatbarMsgUtf8.size()); memcpy(&msg[0], chatbarMsgUtf8.data(), chatbarMsgUtf8.size()); @@ -270,35 +183,14 @@ namespace dchat replaceBinds(msg); channel->addMessage(msg); } - clear(); + text.setString(""); } } } - else if(event.text.unicode == 127) // delete - { - removeNextChar(); - } - else if(event.text.unicode == 22) // ctrl+v - { - // TODO: Instead of calling external xsel, use sfml clipboard functionality (in new sfml version) - string clipboard = getClipboard(); - addString(clipboard); - } - else - { - addChar(event.text.unicode); - } - } - else if(event.type == sf::Event::KeyPressed) - { - if(event.key.code == sf::Keyboard::Left) - moveCaretLeft(); - else if(event.key.code == sf::Keyboard::Right) - moveCaretRight(); } } - void Chatbar::draw(sf::RenderWindow &window) + void Chatbar::draw(sf::RenderWindow &window, Cache &cache) { auto windowSize = window.getSize(); @@ -320,20 +212,10 @@ namespace dchat inputBackground.setSize(inputBackgroundSize); inputBackground.setPosition(inputBackgroundPos); text.setPosition(floor(inputBackgroundPos.x + BOX_PADDING_X), floor(inputBackgroundPos.y + inputBackgroundSize.y * 0.5f - fontHeight * 0.5f)); + text.setMaxWidth(inputBackgroundSize.x - BOX_PADDING_X * 2.0f); window.draw(inputBackground); - window.draw(text); - - int blinkElapsedTime = blinkTimer.getElapsedTime().asMilliseconds(); - if(focused && blinkElapsedTime <= BLINK_TIME_VISIBLE_MS) - { - sf::RectangleShape caretShape(sf::Vector2f(2.0f, inputBackgroundSize.y - BOX_PADDING_Y * Settings::getScaling() * 2.0f)); - caretShape.setPosition(floor(text.getPosition().x + caretOffset.x), floor(caretOffset.y + inputBackgroundPos.y + BOX_PADDING_Y * Settings::getScaling())); - window.draw(caretShape); - } - - if(blinkElapsedTime > BLINK_TIME_VISIBLE_MS + BLINK_TIME_INVISIBLE_MS) - blinkTimer.restart(); + text.draw(window, cache); } float Chatbar::getHeight() diff --git a/src/Text.cpp b/src/Text.cpp index f78c6b7..6fa1fcc 100644 --- a/src/Text.cpp +++ b/src/Text.cpp @@ -5,6 +5,7 @@ #include "../include/ColorScheme.hpp" #include #include +#include namespace dchat { @@ -59,9 +60,19 @@ namespace dchat this->str = str; dirty = true; dirtyText = true; + if(str.getSize() < caretIndex) + { + caretIndex = str.getSize(); + dirtyCaret = true; + } } } + const sf::String& Text::getString() const + { + return str; + } + void Text::setPosition(float x, float y) { position.x = x; @@ -91,6 +102,16 @@ namespace dchat } } + unsigned int Text::getCharacterSize() const + { + return characterSize; + } + + const sf::Font* Text::getFont() const + { + return font; + } + void Text::setFillColor(sf::Color color) { if(color != this->color) @@ -323,7 +344,7 @@ namespace dchat { textElement.position.y = glyphPos.y; // TODO: Find a better way to do this, @totalHeight is wrong because we add emojiSize and then vspace - glyphPos.y += emojiSize - vspace; + glyphPos.y += floor(emojiSize - vspace); } else { @@ -333,7 +354,7 @@ namespace dchat if(glyphPos.x > maxWidth) { glyphPos.x = 0.0f; - glyphPos.y += vspace + lineSpacing; + glyphPos.y += floor(vspace + lineSpacing); } boundingBox.width = std::max(boundingBox.width, glyphPos.x); @@ -341,7 +362,7 @@ namespace dchat } usize vertexOffset = vertices.getVertexCount(); - vertices.resize(vertices.getVertexCount() + (4 * textElement.text.size)); + vertices.resize(vertices.getVertexCount() + 4 * (textElement.text.size + 1)); textElement.position = glyphPos; for(size_t i = 0; i < textElement.text.size; ++i) { @@ -356,7 +377,10 @@ namespace dchat { case ' ': { - vertices[vertexStart].position = sf::Vector2f(glyphPos.x, glyphPos.y - vspace); + vertices[vertexStart + 0] = { sf::Vector2f(glyphPos.x, glyphPos.y - vspace), sf::Color::Transparent, sf::Vector2f() }; + vertices[vertexStart + 1] = { sf::Vector2f(glyphPos.x + hspace, glyphPos.y - vspace), sf::Color::Transparent, sf::Vector2f() }; + vertices[vertexStart + 2] = { sf::Vector2f(glyphPos.x + hspace, glyphPos.y), sf::Color::Transparent, sf::Vector2f() }; + vertices[vertexStart + 3] = { sf::Vector2f(glyphPos.x, glyphPos.y), sf::Color::Transparent, sf::Vector2f() }; glyphPos.x += hspace; if(glyphPos.x > maxWidth * 0.5f) { @@ -367,7 +391,10 @@ namespace dchat } case '\t': { - vertices[vertexStart].position = sf::Vector2f(glyphPos.x, glyphPos.y - vspace); + vertices[vertexStart + 0] = { sf::Vector2f(glyphPos.x, glyphPos.y - vspace), sf::Color::Transparent, sf::Vector2f() }; + vertices[vertexStart + 1] = { sf::Vector2f(glyphPos.x + hspace * TAB_WIDTH, glyphPos.y - vspace), sf::Color::Transparent, sf::Vector2f() }; + vertices[vertexStart + 2] = { sf::Vector2f(glyphPos.x + hspace * TAB_WIDTH, glyphPos.y), sf::Color::Transparent, sf::Vector2f() }; + vertices[vertexStart + 3] = { sf::Vector2f(glyphPos.x, glyphPos.y), sf::Color::Transparent, sf::Vector2f() }; glyphPos.x += (hspace * TAB_WIDTH); if(glyphPos.x > maxWidth * 0.5f) { @@ -378,15 +405,21 @@ namespace dchat } case '\n': { - vertices[vertexStart].position = sf::Vector2f(glyphPos.x, glyphPos.y - vspace); + vertices[vertexStart + 0] = { sf::Vector2f(glyphPos.x, glyphPos.y - vspace), sf::Color::Transparent, sf::Vector2f() }; + vertices[vertexStart + 1] = { sf::Vector2f(0.0f, glyphPos.y), sf::Color::Transparent, sf::Vector2f() }; + vertices[vertexStart + 2] = { sf::Vector2f(0.0f, glyphPos.y), sf::Color::Transparent, sf::Vector2f() }; + vertices[vertexStart + 3] = { sf::Vector2f(0.0f, glyphPos.y), sf::Color::Transparent, sf::Vector2f() }; glyphPos.x = 0.0f; - glyphPos.y += vspace + lineSpacing; + glyphPos.y += floor(vspace + lineSpacing); continue; } case '\v': { - vertices[vertexStart].position = sf::Vector2f(glyphPos.x, glyphPos.y - vspace); - glyphPos.y += (vspace * TAB_WIDTH) + lineSpacing; + vertices[vertexStart + 0] = { sf::Vector2f(glyphPos.x, glyphPos.y - vspace), sf::Color::Transparent, sf::Vector2f() }; + vertices[vertexStart + 1] = { sf::Vector2f(0.0f, glyphPos.y), sf::Color::Transparent, sf::Vector2f() }; + vertices[vertexStart + 2] = { sf::Vector2f(0.0f, glyphPos.y), sf::Color::Transparent, sf::Vector2f() }; + vertices[vertexStart + 3] = { sf::Vector2f(0.0f, glyphPos.y), sf::Color::Transparent, sf::Vector2f() }; + glyphPos.y += floor(vspace * TAB_WIDTH + lineSpacing); continue; } } @@ -405,7 +438,7 @@ namespace dchat { sf::Vector2f &vertexPos = vertices[vertexOffset + j * 4 + k].position; vertexPos.x -= lastSpacingAccumulatedOffset; - vertexPos.y += vspace + lineSpacing; + vertexPos.y += floor(vspace + lineSpacing); } } @@ -416,7 +449,7 @@ namespace dchat else glyphPos.x = 0.0f; - glyphPos.y += vspace + lineSpacing; + glyphPos.y += floor(vspace + lineSpacing); } sf::Vector2f vertexTopLeft(glyphPos.x + glyph.bounds.left, glyphPos.y + glyph.bounds.top); @@ -439,6 +472,11 @@ namespace dchat glyphPos.x += glyph.advance; } + vertices[vertices.getVertexCount() - 4] = { sf::Vector2f(glyphPos.x, glyphPos.y - vspace), sf::Color::Transparent, sf::Vector2f() }; + vertices[vertices.getVertexCount() - 3] = { sf::Vector2f(glyphPos.x, glyphPos.y - vspace), sf::Color::Transparent, sf::Vector2f() }; + vertices[vertices.getVertexCount() - 2] = { sf::Vector2f(glyphPos.x, glyphPos.y - vspace), sf::Color::Transparent, sf::Vector2f() }; + vertices[vertices.getVertexCount() - 1] = { sf::Vector2f(glyphPos.x, glyphPos.y - vspace), sf::Color::Transparent, sf::Vector2f() }; + if(textElement.type != TextElement::Type::TEXT) { prevCodePoint = 0; @@ -494,8 +532,16 @@ namespace dchat } usize vertexIndex = caretIndex * 4; - const sf::Vertex &topLeftVertex = vertices[vertexIndex]; - caretPosition = topLeftVertex.position; + if(vertexIndex == 0) + { + float vspace = font->getLineSpacing(characterSize); + caretPosition = sf::Vector2f(0.0f, -vspace); + } + else + { + const sf::Vertex &topLeftVertex = vertices[vertexIndex]; + caretPosition = topLeftVertex.position; + } } bool Text::isCaretAtEnd() const @@ -511,15 +557,16 @@ namespace dchat int numVertices = vertices.getVertexCount(); if(numVertices < 4) return 0; - usize vertexIndex = caretIndex * 4; + usize vertexIndex = startIndex * 4; const sf::Vertex &startTopLeftVertex = vertices[vertexIndex]; int startRow = getRowByPosition(startTopLeftVertex.position); - for(int i = startIndex * 4; i >= 0; i -= 4) + for(int i = startIndex * 4; i > 0; i -= 4) { const sf::Vertex &topLeftVertex = vertices[i]; int row = getRowByPosition(topLeftVertex.position); if(row != startRow) { + printf("start of line %u, startIndex: %u\n", i, startIndex * 4); return std::max(0, i / 4 + 1); } } @@ -533,7 +580,7 @@ namespace dchat int numVertices = vertices.getVertexCount(); if(numVertices < 4) return 0; - usize vertexIndex = caretIndex * 4; + usize vertexIndex = startIndex * 4; const sf::Vertex &startTopLeftVertex = vertices[vertexIndex]; int startRow = getRowByPosition(startTopLeftVertex.position); for(int i = startIndex * 4; i < numVertices; i += 4) @@ -555,7 +602,7 @@ namespace dchat int numVertices = vertices.getVertexCount(); if(numVertices < 4) return 0; - usize vertexIndex = caretIndex * 4; + usize vertexIndex = startIndex * 4; const sf::Vertex &startTopLeftVertex = vertices[vertexIndex]; int startRow = getRowByPosition(startTopLeftVertex.position); int closestIndex = -1; @@ -589,7 +636,7 @@ namespace dchat int numVertices = vertices.getVertexCount(); if(numVertices < 4) return 0; - usize vertexIndex = caretIndex * 4; + usize vertexIndex = startIndex * 4; const sf::Vertex &startTopLeftVertex = vertices[vertexIndex]; int startRow = getRowByPosition(startTopLeftVertex.position); int closestIndex = -1; @@ -623,11 +670,23 @@ namespace dchat return static_cast(1.0f + position.y / (vspace + lineSpacing)); } + static std::string getClipboard() + { + std::string result; + TinyProcessLib::Process process("xsel -o -b", "", [&result](const char *bytes, size_t n) + { + result.append(bytes, n); + }); + if(process.get_exit_status() != 0) + fprintf(stderr, "Failed to get clipboard content\n"); + return result; + } + void Text::processEvent(const sf::Event &event) { - if(!editable || textElements.size() == 0) return; + if(!editable) return; - bool caretAtEnd = textElements[0].text.size == 0 || caretIndex == textElements[0].text.size; + bool caretAtEnd = textElements.size() == 0 || textElements[0].text.size == 0 || caretIndex == textElements[0].text.size; if(event.type == sf::Event::KeyPressed) { @@ -645,8 +704,8 @@ namespace dchat { auto strBefore = str.substring(0, caretIndex - 1); auto strAfter = str.substring(caretIndex); - setString(strBefore + strAfter); --caretIndex; + setString(strBefore + strAfter); dirtyCaret = true; } else if(event.key.code == sf::Keyboard::Delete && !caretAtEnd) @@ -671,22 +730,52 @@ namespace dchat { caretMoveDirection = CaretMoveDirection::END; } + else if(event.key.code == sf::Keyboard::Return) + { + if(sf::Keyboard::isKeyPressed(sf::Keyboard::LShift) || sf::Keyboard::isKeyPressed(sf::Keyboard::RShift)) + { + if(caretAtEnd) + str += '\n'; + else + { + auto strBefore = str.substring(0, caretIndex); + auto strAfter = str.substring(caretIndex); + str = strBefore + '\n' + strAfter; + } + + ++caretIndex; + dirty = true; + dirtyText = true; + dirtyCaret = true; + } + } } else if(event.type == sf::Event::TextEntered) { if(event.text.unicode == 8 || event.text.unicode == 127) // backspace, del return; + sf::String stringToAdd; + if(event.text.unicode == 22) // ctrl+v + { + auto clipboardString = getClipboard(); + stringToAdd = sf::String::fromUtf8(clipboardString.begin(), clipboardString.end()); + } + else if(event.text.unicode >= 32 || event.text.unicode == 9) // 9 == tab + stringToAdd = event.text.unicode; + else + return; + if(caretAtEnd) - str += event.text.unicode; + str += stringToAdd; else { auto strBefore = str.substring(0, caretIndex); auto strAfter = str.substring(caretIndex); - str = strBefore + event.text.unicode + strAfter; + str = strBefore + stringToAdd + strAfter; } - ++caretIndex; + caretIndex += stringToAdd.getSize(); dirty = true; dirtyText = true; dirtyCaret = true; @@ -727,7 +816,7 @@ namespace dchat //colRect.contains() //if(pos.x + maxWidth <= 0.0f || pos.x >= maxWidth || pos.y + totalHeight <= 0.0f || pos.y >= target.getSize().y) return; if(pos.y + getHeight() <= 0.0f || pos.y >= target.getSize().y) return; - + /* if(editable) { sf::RectangleShape editBox(sf::Vector2f(std::max(maxWidth, boundingBox.width), boundingBox.height)); @@ -735,6 +824,7 @@ namespace dchat editBox.setFillColor(ColorScheme::getBackgroundColor() + sf::Color(10, 10, 10)); target.draw(editBox); } + */ states.transform.translate(pos); states.texture = &font->getTexture(characterSize); -- cgit v1.2.3