From 3ab4127ae3fc3b837f5350509c78db03467500cd Mon Sep 17 00:00:00 2001 From: dec05eba Date: Mon, 23 Apr 2018 13:30:03 +0200 Subject: Add support for big emoji if it's the only thing on a line TODO: Currently message board renders directly to window, it should render to render target for optimization purpose --- .vscode/launch.json | 27 ++++++++++++++ include/MessageBoard.hpp | 3 ++ include/StringView.hpp | 15 ++++---- include/Text.hpp | 8 +++- src/Channel.cpp | 10 +++++ src/Message.cpp | 2 +- src/MessageBoard.cpp | 52 +++++++++++++++++--------- src/Text.cpp | 95 +++++++++++++++++++++++++++++++++++++++--------- src/main.cpp | 6 +-- 9 files changed, 171 insertions(+), 47 deletions(-) create mode 100644 .vscode/launch.json diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..d49b13b --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,27 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "name": "(gdb) Launch", + "type": "cppdbg", + "request": "launch", + "program": "${workspaceFolder}/sibs-build/debug/dchat", + "args": [], + "stopAtEntry": false, + "cwd": "${workspaceFolder}", + "environment": [], + "externalConsole": true, + "MIMode": "gdb", + "setupCommands": [ + { + "description": "Enable pretty-printing for gdb", + "text": "-enable-pretty-printing", + "ignoreFailures": true + } + ] + } + ] +} \ No newline at end of file diff --git a/include/MessageBoard.hpp b/include/MessageBoard.hpp index 06a7cd1..ca1405f 100644 --- a/include/MessageBoard.hpp +++ b/include/MessageBoard.hpp @@ -29,5 +29,8 @@ namespace dchat sf::Vector2f mousePos; sf::Vector2f selectingTextStart; std::vector messages; + double scroll; + double scrollSpeed; + sf::Clock frameTimer; }; } diff --git a/include/StringView.hpp b/include/StringView.hpp index 4e9066b..9eea387 100644 --- a/include/StringView.hpp +++ b/include/StringView.hpp @@ -15,7 +15,7 @@ namespace dchat } - BasicStringView(const BasicStringView &other) : data(other.data), size(other.size) + BasicStringView(const BasicStringView &other) : data(other.data), size(other.size) { } @@ -30,13 +30,14 @@ namespace dchat } - BasicStringView operator = (const BasicStringView &other) + BasicStringView& operator = (const BasicStringView &other) { - BasicStringView result(other.data, other.size); - return result; + data = other.data; + size = other.size; + return *this; } - BasicStringView( BasicStringView &&other) + BasicStringView(BasicStringView &&other) { data = other.data; size = other.size; @@ -45,10 +46,10 @@ namespace dchat other.size = 0; } - bool equals(const BasicStringView &other) const + bool equals(const BasicStringView &other) const { if(size != other.size) return false; - return memcmp(data, other.data, size) == 0; + return memcmp(data, other.data, size * sizeof(CharType)) == 0; } CharType operator [] (usize index) const diff --git a/include/Text.hpp b/include/Text.hpp index 53db93b..93ec0c1 100644 --- a/include/Text.hpp +++ b/include/Text.hpp @@ -17,6 +17,8 @@ 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); + void appendStringNewLine(const sf::String &str); + void setPosition(float x, float y); void setPosition(const sf::Vector2f &position); void setMaxWidth(float maxWidth); @@ -26,9 +28,10 @@ namespace dchat // Warning: won't update until @draw is called float getHeight() const; + // Performs culling. @updateGeometry is called even if text is not visible if text is dirty, because updateGeometry might change the dimension of the text and make is visible void draw(sf::RenderTarget &target, Cache &cache); private: - void stringSplitElements(); + void stringSplitElements(sf::String &stringToSplit, usize startIndex); void updateGeometry(); private: struct TextElement @@ -40,11 +43,12 @@ namespace dchat }; TextElement() {} - TextElement(const StringViewUtf32 &_text, Type _type) : text(_text), type(_type) {} + TextElement(const StringViewUtf32 &_text, Type _type) : text(_text), type(_type), ownLine(false) {} StringViewUtf32 text; sf::Vector2f position; Type type; + bool ownLine; // Currently only used for emoji, to make emoji bigger when it's the only thing on a line }; sf::String str; diff --git a/src/Channel.cpp b/src/Channel.cpp index f240085..e16b25f 100644 --- a/src/Channel.cpp +++ b/src/Channel.cpp @@ -28,6 +28,16 @@ namespace dchat Message *message = new Message(&localOfflineUser, u8"xddd"); messageBoard.addMessage(message); } + + { + Message *message = new Message(&localOfflineUser, u8"[emoji](https://discordemoji.com/assets/emoji/SlowbroDumb.png)"); + messageBoard.addMessage(message); + } + + { + Message *message = new Message(&localOfflineUser, u8"Message after big emoji"); + messageBoard.addMessage(message); + } } Channel::~Channel() diff --git a/src/Message.cpp b/src/Message.cpp index 285a722..b72eabe 100644 --- a/src/Message.cpp +++ b/src/Message.cpp @@ -8,7 +8,7 @@ namespace dchat { Message::Message(User *_user, const std::string &_text) : user(_user), - text(sf::String::fromUtf8(_text.begin(), _text.end()), ResourceCache::getFont("fonts/Roboto-Regular.ttf"), 17 * Settings::getScaling(), 0.0f, false) + text(sf::String::fromUtf8(_text.begin(), _text.end()), ResourceCache::getFont("fonts/Roboto-Regular.ttf"), 20 * Settings::getScaling(), 0.0f, false) { } diff --git a/src/MessageBoard.cpp b/src/MessageBoard.cpp index fd41f02..0b0e2fe 100644 --- a/src/MessageBoard.cpp +++ b/src/MessageBoard.cpp @@ -19,7 +19,9 @@ namespace dchat MessageBoard::MessageBoard(const sf::Vector2u &size) : selectingText(false), - leftMouseButtonPressed(false) + leftMouseButtonPressed(false), + scroll(0.0), + scrollSpeed(0.0) { updateStaticContentTexture(size); } @@ -74,6 +76,10 @@ namespace dchat mousePos.x = event.mouseMove.x; mousePos.y = event.mouseMove.y; } + else if(event.type == sf::Event::MouseWheelScrolled && event.mouseWheelScroll.wheel == sf::Mouse::Wheel::VerticalWheel) + { + scrollSpeed += (event.mouseWheelScroll.delta * 30.0); + } if(selectingText && !leftMouseButtonPressed) { @@ -93,41 +99,53 @@ namespace dchat sf::Vector2u backgroundSize(floor(windowSize.x * 0.7f), floor(windowSize.y)); sf::Vector2f backgroundPos(floor(windowSize.x * 0.5f - backgroundSize.x * 0.5f), 0.0f); - if(backgroundSize != staticContentTexture.getSize()) - updateStaticContentTexture(backgroundSize); + //if(backgroundSize != staticContentTexture.getSize()) + // updateStaticContentTexture(backgroundSize); // TODO: Remove this when dchat::Text can render to static and dynamic render target dirty = true; - if(dirty) - staticContentTexture.clear(BACKGROUND_COLOR); + //if(dirty) + // staticContentTexture.clear(BACKGROUND_COLOR); const sf::Font &usernameFont = ResourceCache::getFont("fonts/Roboto-Regular.ttf"); + double deltaTimeMicro = (double)frameTimer.getElapsedTime().asMicroseconds(); + frameTimer.restart(); + + scroll += scrollSpeed; + scrollSpeed /= (deltaTimeMicro * 0.0001); + if(dirty) { - sf::Vector2f position; + sf::Vector2f position = backgroundPos; + position.y += scroll; for(Message *message : messages) { - sf::Text usernameText(message->user->getName(), usernameFont, 20 * Settings::getScaling()); - usernameText.setFillColor(sf::Color(15, 192, 252)); - usernameText.setPosition(position); - staticContentTexture.draw(usernameText); - position.y += usernameText.getFont()->getLineSpacing(usernameText.getCharacterSize()) + USERNAME_PADDING_BOTTOM; + sf::Text usernameText(message->user->getName(), usernameFont, 24 * Settings::getScaling()); + float usernameTextHeight = usernameText.getFont()->getLineSpacing(usernameText.getCharacterSize()); + if(position.y + usernameTextHeight > 0.0f && position.y < backgroundSize.y) + { + usernameText.setFillColor(sf::Color(15, 192, 252)); + usernameText.setPosition(sf::Vector2f(floor(position.x), floor(position.y))); + window.draw(usernameText); + } + position.y += usernameTextHeight + USERNAME_PADDING_BOTTOM; + // No need to perform culling here, that is done in @Text draw function message->text.setMaxWidth(backgroundSize.x); - message->text.setPosition(position); - message->text.draw(staticContentTexture, cache); + message->text.setPosition(sf::Vector2f(floor(position.x), floor(position.y))); + message->text.draw(window, cache); position.y += message->text.getHeight() + MESSAGE_PADDING_BOTTOM; } } - staticContentTexture.display(); + //staticContentTexture.display(); dirty = false; // TODO: Save this, expensive to create on fly? - sf::Sprite textureSprite(staticContentTexture.getTexture()); - textureSprite.setPosition(backgroundPos); - window.draw(textureSprite); + //sf::Sprite textureSprite(staticContentTexture.getTexture()); + //textureSprite.setPosition(backgroundPos); + //window.draw(textureSprite); if(!selectingText) return; diff --git a/src/Text.cpp b/src/Text.cpp index 41eb4e3..fc3effe 100644 --- a/src/Text.cpp +++ b/src/Text.cpp @@ -2,11 +2,14 @@ #include "../include/Cache.hpp" #include "../include/Gif.hpp" #include +#include namespace dchat { const float TAB_WIDTH = 4.0f; const float EMOJI_PADDING = 5.0f; + const float EMOJI_SCALE_WITH_TEXT = 1.0f; + const float EMOJI_SCALE_STANDALONE = 5.0f; Text::Text(const sf::Font &_font) : font(_font), @@ -39,10 +42,20 @@ namespace dchat { this->str = str; dirty = true; - stringSplitElements(); + textElements.clear(); + stringSplitElements(this->str, 0); } } + 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; @@ -86,44 +99,51 @@ namespace dchat return totalHeight; } - void Text::stringSplitElements() + void Text::stringSplitElements(sf::String &stringToSplit, usize startIndex) { - textElements.clear(); if(plainText) { - StringViewUtf32 wholeStr(&str[0], str.getSize()); + StringViewUtf32 wholeStr(&stringToSplit[startIndex], stringToSplit.getSize() - startIndex); textElements.push_back({ wholeStr, TextElement::Type::TEXT }); return; } - size_t offset = 0; - while(offset < str.getSize()) + size_t offset = startIndex; + while(offset < stringToSplit.getSize()) { size_t stringStart = offset; - size_t foundStartIndex = str.find("[emoji](", offset); + size_t foundStartIndex = stringToSplit.find("[emoji](", offset); size_t foundEndIndex = -1; if(foundStartIndex != -1) { offset += (foundStartIndex + 8); - foundEndIndex = str.find(")", offset); + foundEndIndex = stringToSplit.find(")", offset); } if(foundEndIndex != -1) { - StringViewUtf32 beforeEmojiStr(&str[stringStart], foundStartIndex - stringStart); + StringViewUtf32 beforeEmojiStr(&stringToSplit[stringStart], foundStartIndex - stringStart); textElements.push_back({ beforeEmojiStr, TextElement::Type::TEXT }); - StringViewUtf32 url(&str[offset], foundEndIndex - offset); + StringViewUtf32 url(&stringToSplit[offset], foundEndIndex - offset); textElements.push_back({ url, TextElement::Type::EMOJI }); offset = foundEndIndex + 1; } else { - StringViewUtf32 strToEnd(&str[stringStart], str.getSize() - stringStart); + StringViewUtf32 strToEnd(&stringToSplit[stringStart], stringToSplit.getSize() - stringStart); textElements.push_back({ strToEnd, TextElement::Type::TEXT }); - offset = str.getSize(); + offset = stringToSplit.getSize(); } } + + for(std::vector::iterator it = textElements.begin(); it != textElements.end();) + { + if(it->text.size == 0) + it = textElements.erase(it); + else + ++it; + } } // Logic loosely based on https://github.com/SFML/SFML/wiki/Source:-CurvedText @@ -132,25 +152,60 @@ namespace dchat vertices.clear(); float hspace = font.getGlyph(' ', characterSize, false).advance; float vspace = font.getLineSpacing(characterSize); - float emojiSize = vspace * 1.0; sf::Vector2f glyphPos; sf::Uint32 prevCodePoint = 0; size_t lastSpacingWordWrapIndex = -1; float lastSpacingAccumulatedOffset = 0.0f; - for(TextElement &textElement : textElements) + for(usize textElementIndex = 0; textElementIndex < textElements.size(); ++textElementIndex) { + TextElement &textElement = textElements[textElementIndex]; if(textElement.type == TextElement::Type::EMOJI) { + bool ownLineLeft = false; + if(textElementIndex == 0) + ownLineLeft = true; + else + { + TextElement &prevElement = textElements[textElementIndex - 1]; + if(prevElement.text[prevElement.text.size - 1] == '\n') + ownLineLeft = true; + } + + bool ownLineRight = false; + if(textElementIndex == textElements.size() - 1) + ownLineRight = true; + else + { + TextElement &nextElement = textElements[textElementIndex + 1]; + if(nextElement.text[0] == '\n') + ownLineRight = true; + } + + if(ownLineLeft && ownLineRight) + textElement.ownLine = true; + + float emojiSize = vspace * (textElement.ownLine ? EMOJI_SCALE_STANDALONE : EMOJI_SCALE_WITH_TEXT); + glyphPos.x += EMOJI_PADDING; textElement.position.x = glyphPos.x; - textElement.position.y = glyphPos.y + vspace * 0.5f - emojiSize * 0.5f; + if(textElement.ownLine) + { + 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; + } + else + { + textElement.position.y = glyphPos.y + vspace * 0.5f - emojiSize * 0.5f; + } glyphPos.x += emojiSize + EMOJI_PADDING; if(glyphPos.x > maxWidth) { glyphPos.x = 0.0f; glyphPos.y += vspace; } + continue; } @@ -257,11 +312,14 @@ namespace dchat } float vspace = font.getLineSpacing(characterSize); - float emojiSize = vspace * 1.0; sf::RenderStates states; sf::Vector2f pos = position; - pos.y += vspace; // Origin is at bottom left, we want it to be at top left + pos.y += floor(vspace); // Origin is at bottom left, we want it to be at top left + + // TODO: Do not use maxWidth here. Max width might be set to 99999 and actual text width might be 200. Text width should be calculated instead + if(pos.x + maxWidth <= 0.0f || pos.x >= maxWidth || pos.y + totalHeight <= 0.0f || pos.y >= target.getSize().y) return; + states.transform.translate(pos); states.texture = &font.getTexture(characterSize); target.draw(vertices, states); @@ -272,6 +330,9 @@ namespace dchat { sf::Vector2f pos = position; pos += textElement.position; + pos.x = floor(pos.x); + pos.y = floor(pos.y); + float emojiSize = vspace * (textElement.ownLine ? EMOJI_SCALE_STANDALONE : EMOJI_SCALE_WITH_TEXT); sf::Vector2f size(emojiSize, emojiSize); // TODO: Optimize this (add unordered_map that takes StringViewUtf32 as key) diff --git a/src/main.cpp b/src/main.cpp index df5c558..6d718e5 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -31,12 +31,12 @@ int main(int argc, char **argv) Cache cache; Channel channel; - ChannelSidePanel channelSidePanel; - channelSidePanel.addChannel(&channel); + //ChannelSidePanel channelSidePanel; + //channelSidePanel.addChannel(&channel); + sf::Event event; while (window.isOpen()) { - sf::Event event; while (window.pollEvent(event)) { if (event.type == sf::Event::Closed) -- cgit v1.2.3