diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/Cache.cpp | 42 | ||||
-rw-r--r-- | src/Channel.cpp | 1 | ||||
-rw-r--r-- | src/ChannelSidePanel.cpp | 4 | ||||
-rw-r--r-- | src/MessageBoard.cpp | 5 | ||||
-rw-r--r-- | src/Text.cpp | 325 | ||||
-rw-r--r-- | src/UsersSidePanel.cpp | 4 |
6 files changed, 346 insertions, 35 deletions
diff --git a/src/Cache.cpp b/src/Cache.cpp index cba346b..c795595 100644 --- a/src/Cache.cpp +++ b/src/Cache.cpp @@ -196,22 +196,32 @@ namespace dchat int exitStatus; if(it->process->try_get_exit_status(exitStatus)) { + ContentByUrlResult contentByUrlResult; bool failed = exitStatus != 0; - if(!failed) + if(failed) + { + contentByUrlResult = { (sf::Texture*)nullptr, ContentByUrlResult::Type::FAILED_DOWNLOAD }; + } + else { boost::filesystem::path filepath = getImagesDir(); odhtdb::Hash urlHash(it->url.data(), it->url.size()); filepath /= urlHash.toString(); - ContentByUrlResult contentByUrlResult = loadImageFromFile(filepath); - imageDownloadMutex.lock(); - contentUrlCache[it->url] = contentByUrlResult; - imageDownloadMutex.unlock(); + contentByUrlResult = loadImageFromFile(filepath); if(contentByUrlResult.type == ContentByUrlResult::Type::CACHED) { printf("Download content from url: %s\n", it->url.c_str()); } } + + imageDownloadMutex.lock(); + contentUrlCache[it->url] = contentByUrlResult; + boost::filesystem::path downloadingFilepath = getImagesDir() / ".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); + imageDownloadMutex.unlock(); it = imageDownloadProcesses.erase(it); } else @@ -243,6 +253,18 @@ namespace dchat downloadWaitThread.join(); } + void replaceFileIgnoreError(const boost::filesystem::path &path) + { + try + { + fileReplace(path, StringView()); + } + catch(FileException &e) + { + fprintf(stderr, "Failed to replace file: %s, reason: %s\n", path.string().c_str(), e.what()); + } + } + const ContentByUrlResult Cache::getContentByUrl(const string &url, int downloadLimitBytes) { lock_guard<mutex> lock(imageDownloadMutex); @@ -255,6 +277,15 @@ namespace dchat odhtdb::Hash urlHash(url.data(), url.size()); filepath /= urlHash.toString(); + boost::filesystem::path downloadingFilepath = getImagesDir() / ".downloading"; + if(boost::filesystem::exists(downloadingFilepath)) + { + // Intentionally ignore failure, program should not crash if we fail to remove these files... + boost::system::error_code err; + boost::filesystem::remove(filepath, err); + boost::filesystem::remove(downloadingFilepath, err); + } + ContentByUrlResult contentByUrlResult = loadImageFromFile(filepath); if(contentByUrlResult.type == ContentByUrlResult::Type::CACHED) { @@ -268,6 +299,7 @@ namespace dchat return contentByUrlResult; } + replaceFileIgnoreError(downloadingFilepath); ContentByUrlResult result((sf::Texture*)nullptr, ContentByUrlResult::Type::DOWNLOADING); contentUrlCache[url] = result; diff --git a/src/Channel.cpp b/src/Channel.cpp index f8e79c6..2bd1b0b 100644 --- a/src/Channel.cpp +++ b/src/Channel.cpp @@ -31,6 +31,7 @@ namespace dchat { Message *message = new Message(&systemUser, u8"pepedab https://discordemoji.com/assets/emoji/PepeDab.gif coggers https://discordemoji.com/assets/emoji/COGGERS.gif check out this url http://www.grandtournation.com/6808/start-date-of-the-grand-tour-season-3-confirmed-mark-your-calendars/ owo"); + message->text.setEditable(true); messageBoard.addMessage(message); } diff --git a/src/ChannelSidePanel.cpp b/src/ChannelSidePanel.cpp index 8d0b714..89a550a 100644 --- a/src/ChannelSidePanel.cpp +++ b/src/ChannelSidePanel.cpp @@ -33,7 +33,7 @@ namespace dchat { float posY = ChannelTopPanel::getHeight(); auto windowSize = window.getSize(); - sf::RectangleShape rect(sf::Vector2f(getWidth(), windowSize.y)); + sf::RectangleShape rect(sf::Vector2f(getWidth(), windowSize.y - ChannelTopPanel::getHeight())); rect.setPosition(0.0f, posY); rect.setFillColor(ColorScheme::getPanelColor()); window.draw(rect); @@ -68,6 +68,6 @@ namespace dchat float ChannelSidePanel::getWidth() { - return WIDTH * Settings::getScaling(); + return floor(WIDTH * Settings::getScaling()); } } diff --git a/src/MessageBoard.cpp b/src/MessageBoard.cpp index cee3e50..d73dc84 100644 --- a/src/MessageBoard.cpp +++ b/src/MessageBoard.cpp @@ -238,6 +238,11 @@ namespace dchat selectingTextStart.x = mousePos.x; selectingTextStart.y = mousePos.y; } + + for(Message *message : messages) + { + message->text.processEvent(event); + } } void MessageBoard::draw(sf::RenderWindow &window, Cache &cache) 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); } } diff --git a/src/UsersSidePanel.cpp b/src/UsersSidePanel.cpp index f665d1f..9c04062 100644 --- a/src/UsersSidePanel.cpp +++ b/src/UsersSidePanel.cpp @@ -20,7 +20,7 @@ namespace dchat { float posY = ChannelTopPanel::getHeight(); auto windowSize = window.getSize(); - sf::RectangleShape rect(sf::Vector2f(getWidth(), windowSize.y)); + sf::RectangleShape rect(sf::Vector2f(getWidth(), windowSize.y - ChannelTopPanel::getHeight())); rect.setFillColor(ColorScheme::getPanelColor()); rect.setPosition(windowSize.x - getWidth(), posY); window.draw(rect); @@ -45,6 +45,6 @@ namespace dchat float UsersSidePanel::getWidth() { - return WIDTH * Settings::getScaling(); + return floor(WIDTH * Settings::getScaling()); } } |