diff options
author | dec05eba <dec05eba@protonmail.com> | 2018-04-23 09:53:31 +0200 |
---|---|---|
committer | dec05eba <dec05eba@protonmail.com> | 2018-04-23 09:55:12 +0200 |
commit | ddff0f1b7ea84f6a1321b8eb8a4d47317873d955 (patch) | |
tree | 28565c3a3d336559fcf149e1552ae237cc3d855d | |
parent | 1e0e68f9cda51c881b32a54d9eece71c1428f7ac (diff) |
Add word wrap for message board & TODO
TODO: Message board is now redrawn every frame.
Text should be modified to render on static & dynamic texture ->
text & static images on static texture, gif & video on dynamic texture
-rw-r--r-- | include/Gif.hpp | 4 | ||||
-rw-r--r-- | include/Message.hpp | 14 | ||||
-rw-r--r-- | include/MessageBoard.hpp | 1 | ||||
-rw-r--r-- | include/MessagePart.hpp | 55 | ||||
-rw-r--r-- | include/StringView.hpp | 26 | ||||
-rw-r--r-- | include/Text.hpp | 62 | ||||
-rw-r--r-- | src/Channel.cpp | 16 | ||||
-rw-r--r-- | src/Chatbar.cpp | 2 | ||||
-rw-r--r-- | src/Gif.cpp | 4 | ||||
-rw-r--r-- | src/Message.cpp | 62 | ||||
-rw-r--r-- | src/MessageBoard.cpp | 170 | ||||
-rw-r--r-- | src/MessagePart.cpp | 62 | ||||
-rw-r--r-- | src/Text.cpp | 308 | ||||
-rw-r--r-- | src/main.cpp | 11 |
14 files changed, 434 insertions, 363 deletions
diff --git a/include/Gif.hpp b/include/Gif.hpp index 1a69a52..87e6956 100644 --- a/include/Gif.hpp +++ b/include/Gif.hpp @@ -1,7 +1,7 @@ #pragma once #include "StringView.hpp" -#include <SFML/Graphics/RenderWindow.hpp> +#include <SFML/Graphics/RenderTarget.hpp> #include <SFML/Graphics/Texture.hpp> #include <SFML/Graphics/Sprite.hpp> #include <SFML/System/Clock.hpp> @@ -30,7 +30,7 @@ namespace dchat void setPosition(const sf::Vector2f &position); void setSize(const sf::Vector2f &size); - void draw(sf::RenderWindow &window); + void draw(sf::RenderTarget &target); static bool isDataGif(const StringView &data); private: diff --git a/include/Message.hpp b/include/Message.hpp index 7cd7fdf..a6edddf 100644 --- a/include/Message.hpp +++ b/include/Message.hpp @@ -1,7 +1,7 @@ #pragma once -#include "MessagePart.hpp" #include "User.hpp" +#include "Text.hpp" #include <string> #include <vector> @@ -10,17 +10,9 @@ namespace dchat class Message { public: - Message(User *user); - virtual ~Message(); - - void addText(const std::string &text, bool newLine = true); - void addEmoji(const std::string &url, bool newLine = true); - std::vector<MessagePart*>& getParts(); - - static Message* buildFromString(User *user, const std::string &str); + Message(User *user, const std::string &text); const User *user; - private: - std::vector<MessagePart*> messageParts; + Text text; }; } diff --git a/include/MessageBoard.hpp b/include/MessageBoard.hpp index c510164..06a7cd1 100644 --- a/include/MessageBoard.hpp +++ b/include/MessageBoard.hpp @@ -1,6 +1,7 @@ #pragma once #include "Message.hpp" +#include "types.hpp" #include "../include/Cache.hpp" #include <SFML/Graphics/RenderTexture.hpp> #include <SFML/Graphics/RenderWindow.hpp> diff --git a/include/MessagePart.hpp b/include/MessagePart.hpp deleted file mode 100644 index 00544fb..0000000 --- a/include/MessagePart.hpp +++ /dev/null @@ -1,55 +0,0 @@ -#pragma once - -#include <SFML/Graphics/Text.hpp> -#include <SFML/Graphics/Sprite.hpp> -#include <SFML/System/Vector2.hpp> -#include <string> - -namespace dchat -{ - class MessagePart - { - public: - enum class Type - { - TEXT, - EMOJI - }; - - MessagePart(Type _type, bool _newLine) : type(_type), newLine(_newLine) {} - virtual ~MessagePart(){} - - static float getSizeScaled(); - virtual sf::Vector2f getPosition() const = 0; - virtual sf::Vector2f getSize() const = 0; - - const Type type; - bool newLine; - }; - - class MessagePartText : public MessagePart - { - public: - MessagePartText(const std::string &text, bool newLine); - - static float getFontSizeScaled(); - virtual sf::Vector2f getPosition() const override; - virtual sf::Vector2f getSize() const override; - - sf::Text text; - }; - - class MessagePartEmoji : public MessagePart - { - public: - MessagePartEmoji(const std::string &url, bool newLine); - - static float getHeightScaled(); - virtual sf::Vector2f getPosition() const override; - virtual sf::Vector2f getSize() const override; - - sf::Sprite sprite; - std::string url; - bool dirty; - }; -} diff --git a/include/StringView.hpp b/include/StringView.hpp index 3293358..4e9066b 100644 --- a/include/StringView.hpp +++ b/include/StringView.hpp @@ -6,36 +6,37 @@ namespace dchat { - class StringView + template <typename CharType> + class BasicStringView { public: - StringView() : data(nullptr), size(0) + BasicStringView() : data(nullptr), size(0) { } - StringView(const StringView &other) : data(other.data), size(other.size) + BasicStringView(const BasicStringView<CharType> &other) : data(other.data), size(other.size) { } - StringView(const char *_data) : data(_data), size(strlen(_data)) + BasicStringView(const CharType *_data) : data(_data), size(strlen(_data)) { } - StringView(const char *_data, usize _size) : data(_data), size(_size) + BasicStringView(const CharType *_data, usize _size) : data(_data), size(_size) { } - StringView operator = (const StringView &other) + BasicStringView<CharType> operator = (const BasicStringView<CharType> &other) { - StringView result(other.data, other.size); + BasicStringView<CharType> result(other.data, other.size); return result; } - StringView(StringView &&other) + BasicStringView( BasicStringView<CharType> &&other) { data = other.data; size = other.size; @@ -44,19 +45,22 @@ namespace dchat other.size = 0; } - bool equals(const StringView &other) const + bool equals(const BasicStringView<CharType> &other) const { if(size != other.size) return false; return memcmp(data, other.data, size) == 0; } - char operator [] (usize index) const + CharType operator [] (usize index) const { assert(index < size); return data[index]; } - const char *data; + const CharType *data; usize size; }; + + using StringView = BasicStringView<char>; + using StringViewUtf32 = BasicStringView<u32>; } diff --git a/include/Text.hpp b/include/Text.hpp new file mode 100644 index 0000000..53db93b --- /dev/null +++ b/include/Text.hpp @@ -0,0 +1,62 @@ +#pragma once + +#include "StringView.hpp" +#include "Cache.hpp" +#include <SFML/Graphics/VertexArray.hpp> +#include <SFML/Graphics/Font.hpp> +#include <SFML/Graphics/RenderTarget.hpp> +#include <SFML/System/String.hpp> +#include <vector> + +namespace dchat +{ + class Text + { + public: + Text(const sf::Font &font); + Text(const sf::String &str, const sf::Font &font, unsigned int characterSize, float maxWidth, bool plainText = true); + + void setString(const sf::String &str); + void setPosition(float x, float y); + void setPosition(const sf::Vector2f &position); + void setMaxWidth(float maxWidth); + void setCharacterSize(unsigned int characterSize); + void setFillColor(sf::Color color); + + // Warning: won't update until @draw is called + float getHeight() const; + + void draw(sf::RenderTarget &target, Cache &cache); + private: + void stringSplitElements(); + void updateGeometry(); + private: + struct TextElement + { + enum class Type + { + TEXT, + EMOJI + }; + + TextElement() {} + TextElement(const StringViewUtf32 &_text, Type _type) : text(_text), type(_type) {} + + StringViewUtf32 text; + sf::Vector2f position; + Type type; + }; + + sf::String str; + sf::Font font; + unsigned int characterSize; + sf::VertexArray vertices; + float maxWidth; + sf::Vector2f position; + sf::Color color; + bool dirty; + bool plainText; + float totalHeight; + std::vector<TextElement> textElements; + }; +} diff --git a/src/Channel.cpp b/src/Channel.cpp index 5e81f37..f240085 100644 --- a/src/Channel.cpp +++ b/src/Channel.cpp @@ -10,22 +10,22 @@ namespace dchat localOfflineUser("You") { { - Message *message = new Message(&localOfflineUser); - message->addText(u8"hello, worldåäö1!", false); - message->addEmoji("https://discordemoji.com/assets/emoji/playtime.png"); + Message *message = new Message(&localOfflineUser, u8"hello, worldåäö1![emoji](https://discordemoji.com/assets/emoji/playtime.png)"); messageBoard.addMessage(message); } { - Message *message = new Message(&localOfflineUser); - message->addText(u8"hello, world2!", false); - message->addEmoji("https://discordemoji.com/assets/emoji/Feels3DMan.gif"); + Message *message = new Message(&localOfflineUser, u8"hello, world2![emoji](https://discordemoji.com/assets/emoji/Feels3DMan.gif)"); messageBoard.addMessage(message); } { - Message *message = new Message(&localOfflineUser); - message->addText(u8"hello, world3!"); + Message *message = new Message(&localOfflineUser, u8"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."); + messageBoard.addMessage(message); + } + + { + Message *message = new Message(&localOfflineUser, u8"xddd"); messageBoard.addMessage(message); } } diff --git a/src/Chatbar.cpp b/src/Chatbar.cpp index bdfc75d..d775db4 100644 --- a/src/Chatbar.cpp +++ b/src/Chatbar.cpp @@ -114,7 +114,7 @@ namespace dchat string msg; msg.resize(chatbarMsgUtf8.size()); memcpy(&msg[0], chatbarMsgUtf8.data(), chatbarMsgUtf8.size()); - channel->getMessageBoard().addMessage(Message::buildFromString(channel->getLocalUser(), msg)); + channel->getMessageBoard().addMessage(new Message(channel->getLocalUser(), msg)); clear(); } } diff --git a/src/Gif.cpp b/src/Gif.cpp index 3f7216e..b1f4fd3 100644 --- a/src/Gif.cpp +++ b/src/Gif.cpp @@ -127,7 +127,7 @@ namespace dchat sprite.setScale(size.x / (float)textureSize.x, size.y / (float)textureSize.y); } - void Gif::draw(sf::RenderWindow &window) + void Gif::draw(sf::RenderTarget &target) { double frameDeltaCs = (double)frameTimer.getElapsedTime().asMilliseconds() * 0.1; // Centisecond frameTimer.restart(); @@ -159,7 +159,7 @@ namespace dchat texture.update(image); sprite.setTexture(texture, true); } - window.draw(sprite); + target.draw(sprite); } bool Gif::isDataGif(const StringView &data) diff --git a/src/Message.cpp b/src/Message.cpp index 19630b3..285a722 100644 --- a/src/Message.cpp +++ b/src/Message.cpp @@ -1,67 +1,15 @@ #include "../include/Message.hpp" -#include "../include/StringView.hpp" +#include "../include/ResourceCache.hpp" +#include "../include/Settings.hpp" using namespace std; namespace dchat { - Message::Message(User *_user) : - user(_user) + 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) { } - - Message::~Message() - { - for(MessagePart *messagePart : messageParts) - { - delete messagePart; - } - } - - void Message::addText(const string &text, bool newLine) - { - messageParts.push_back(new MessagePartText(text, newLine)); - } - - void Message::addEmoji(const string &url, bool newLine) - { - messageParts.push_back(new MessagePartEmoji(url, newLine)); - } - - vector<MessagePart*>& Message::getParts() - { - return messageParts; - } - - StringView getNextNewLine(const StringView &str) - { - for(usize i = 0; i < str.size; ++i) - { - if(str[i] == '\n') - return StringView(str.data, i); - } - return StringView(); - } - - Message* Message::buildFromString(User *user, const std::string &str) - { - Message *message = new Message(user); - usize strOffset = 0; - while(strOffset < str.size()) - { - usize foundIndex = str.find('\n', strOffset); - usize lineEnd = foundIndex; - if(foundIndex == string::npos) - lineEnd = str.size(); - - message->addText(str.substr(strOffset, lineEnd - strOffset), foundIndex != string::npos); - - if(foundIndex == string::npos) - break; - else - strOffset = lineEnd + 1; - } - return message; - } } diff --git a/src/MessageBoard.cpp b/src/MessageBoard.cpp index d39e8be..fd41f02 100644 --- a/src/MessageBoard.cpp +++ b/src/MessageBoard.cpp @@ -6,6 +6,7 @@ #include <SFML/Graphics/Sprite.hpp> #include <SFML/Window/Mouse.hpp> #include <SFML/Graphics/Rect.hpp> +#include <SFML/Graphics/Text.hpp> #include <cmath> using namespace std; @@ -13,7 +14,7 @@ using namespace std; namespace dchat { const sf::Color BACKGROUND_COLOR(40, 40, 40); - const float USERNAME_PADDING_BOTTOM = 5.0f; + const float USERNAME_PADDING_BOTTOM = 0.0f; const float MESSAGE_PADDING_BOTTOM = 20.0f; MessageBoard::MessageBoard(const sf::Vector2u &size) : @@ -95,95 +96,29 @@ namespace dchat 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(sf::Color::Transparent); + staticContentTexture.clear(BACKGROUND_COLOR); const sf::Font &usernameFont = ResourceCache::getFont("fonts/Roboto-Regular.ttf"); - sf::Vector2f position; - for(Message *message : messages) + if(dirty) { - sf::Text usernameText(message->user->getName(), usernameFont, MessagePartText::getFontSizeScaled() * 1.3f); - usernameText.setFillColor(sf::Color(15, 192, 252)); - usernameText.setPosition(position); - if(dirty) - staticContentTexture.draw(usernameText); - position.y += usernameText.getCharacterSize() + USERNAME_PADDING_BOTTOM; - - int index = 0; - int numParts = message->getParts().size(); - for(MessagePart *messagePart : message->getParts()) + sf::Vector2f position; + for(Message *message : messages) { - switch(messagePart->type) - { - case MessagePart::Type::TEXT: - { - MessagePartText *messagePartText = static_cast<MessagePartText*>(messagePart); - messagePartText->text.setFillColor(sf::Color(240, 240, 240)); - messagePartText->text.setCharacterSize(MessagePartText::getFontSizeScaled()); - messagePartText->text.setPosition(floor(position.x), floor(position.y + MessagePart::getSizeScaled() * 0.5f - MessagePartText::getFontSizeScaled() * 0.5f)); - if(dirty) - staticContentTexture.draw(messagePartText->text); - position.x += messagePartText->text.getLocalBounds().width; - break; - } - case MessagePart::Type::EMOJI: - { - MessagePartEmoji *messagePartEmoji = static_cast<MessagePartEmoji*>(messagePart); - position.x += 5.0f; - auto imageByUrlResult = cache.getImageByUrl(messagePartEmoji->url, 1024 * 512); - bool imageDrawn = false; - if(imageByUrlResult.isGif && imageByUrlResult.gif) - { - sf::Vector2f pos(backgroundPos.x + floor(position.x), backgroundPos.y + floor(position.y + MessagePart::getSizeScaled() * 0.5f - MessagePartEmoji::getHeightScaled() * 0.5f)); - imageByUrlResult.gif->setPosition(pos); - imageByUrlResult.gif->setSize(sf::Vector2f(MessagePartEmoji::getHeightScaled(), MessagePartEmoji::getHeightScaled())); - imageByUrlResult.gif->draw(window); - imageDrawn = true; - } - else - { - // Emoji is dirty when it's created, but render target can become dirty after emoji has been added, so we need to set emoji as dirty then - if(dirty) - messagePartEmoji->dirty = true; - if(imageByUrlResult.texture) - { - // TODO: Verify this doesn't cause lag - messagePartEmoji->sprite.setTexture(*imageByUrlResult.texture, true); - sf::Vector2f spriteSize(MessagePartEmoji::getHeightScaled(), MessagePartEmoji::getHeightScaled()); - messagePartEmoji->sprite.setScale(spriteSize.x / (float)imageByUrlResult.texture->getSize().x, spriteSize.y / (float)imageByUrlResult.texture->getSize().y); - messagePartEmoji->sprite.setPosition(floor(position.x), floor(position.y + MessagePart::getSizeScaled() * 0.5f - MessagePartEmoji::getHeightScaled() * 0.5f)); - if(messagePartEmoji->dirty) - { - messagePartEmoji->dirty = false; - staticContentTexture.draw(messagePartEmoji->sprite); - } - imageDrawn = true; - } - } - - if(!imageDrawn) - { - // TODO: Replace this with a loading gif - sf::RectangleShape emojiDownloadRect(sf::Vector2f(MessagePartEmoji::getHeightScaled(), MessagePartEmoji::getHeightScaled())); - emojiDownloadRect.setPosition(backgroundPos.x + floor(position.x), backgroundPos.y + floor(position.y + MessagePart::getSizeScaled() * 0.5f - MessagePartEmoji::getHeightScaled() * 0.5f)); - emojiDownloadRect.setFillColor(sf::Color::White); - window.draw(emojiDownloadRect); - } - position.x += MessagePartEmoji::getHeightScaled() + 5.0f; - break; - } - } + 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; - if(index < numParts - 1 && messagePart->newLine) - { - position.x = 0.0f; - position.y += MessagePart::getSizeScaled(); - } - ++index; + message->text.setMaxWidth(backgroundSize.x); + message->text.setPosition(position); + message->text.draw(staticContentTexture, cache); + position.y += message->text.getHeight() + MESSAGE_PADDING_BOTTOM; } - position.x = 0.0f; - position.y += MessagePart::getSizeScaled() + MESSAGE_PADDING_BOTTOM; } staticContentTexture.display(); @@ -199,76 +134,5 @@ namespace dchat sf::Vector2f selectionRectStart(min((float)mousePos.x, selectingTextStart.x), min((float)mousePos.y, selectingTextStart.y)); sf::Vector2f selectionRectEnd(max((float)mousePos.x, selectingTextStart.x), max((float)mousePos.y, selectingTextStart.y)); sf::FloatRect selectionRect(selectionRectStart, selectionRectEnd - selectionRectStart); -#if 0 - // TODO: Remove this, put logic in render loop above - for(Message *message : messages) - { - float messagePartStartX = -999.0f; - float messagePartEndX = 0.0f; - float messagePartX = 0.0f; - float messagePartStartY = 0.0f; - - for(MessagePart *messagePart : message->getParts()) - { - sf::Vector2f position = messagePart->getPosition(); - sf::Vector2f size = messagePart->getSize(); - sf::FloatRect messagePartRect(position, size); - if(!selectionRect.intersects(messagePartRect)) continue; - - switch(messagePart->type) - { - case MessagePart::Type::TEXT: - { - MessagePartText *messagePartText = static_cast<MessagePartText*>(messagePart); - messagePartStartY = position.y; - sf::Uint32 prevCodePoint = -1; - for(int i = 0; i < messagePartText->text.getString().getSize(); ++i) - { - sf::Uint32 codePoint = messagePartText->text.getString()[i]; - const sf::Glyph &glyph = messagePartText->text.getFont()->getGlyph(codePoint, messagePartText->text.getCharacterSize(), false); - float glyphWidth = glyph.advance; - if(prevCodePoint != -1) - glyphWidth += messagePartText->text.getFont()->getKerning(prevCodePoint, codePoint, messagePartText->text.getCharacterSize()); - - if(selectionRect.left < messagePartX + glyph.advance * 0.5f) - { - if(messagePartStartX < 0.0f) - { - messagePartStartX = messagePartX; - if(mousePos.y > messagePartStartY + MessagePart::getSizeScaled()) - { - messagePartEndX = position.x + size.x; - goto nextMessagePart; - } - } - } - - if(selectionRect.left + selectionRect.width > messagePartX + glyph.advance * 0.5f) - { - messagePartEndX = messagePartX + glyphWidth; - } - else - break; - - messagePartX += glyphWidth; - prevCodePoint = codePoint; - } - break; - } - } - - nextMessagePart: - ; - } - - if(messagePartStartX >= 0.0f) - { - sf::RectangleShape selectionShape(sf::Vector2f(floor(messagePartEndX - messagePartStartX), floor(MessagePart::getSizeScaled()))); - selectionShape.setPosition(messagePartStartX, messagePartStartY); - selectionShape.setFillColor(sf::Color(100, 100, 255, 100)); - window.draw(selectionShape); - } - } -#endif } } diff --git a/src/MessagePart.cpp b/src/MessagePart.cpp deleted file mode 100644 index 215c239..0000000 --- a/src/MessagePart.cpp +++ /dev/null @@ -1,62 +0,0 @@ -#include "../include/MessagePart.hpp" -#include "../include/ResourceCache.hpp" -#include "../include/Settings.hpp" - -using namespace std; - -namespace dchat -{ - const float MESSAGE_PART_SIZE = 27; - - float MessagePart::getSizeScaled() - { - return MESSAGE_PART_SIZE * Settings::getScaling(); - } - - MessagePartText::MessagePartText(const string &_text, bool _newLine) : - MessagePart(Type::TEXT, _newLine), - text("", ResourceCache::getFont("fonts/Roboto-Regular.ttf"), MessagePartText::getFontSizeScaled()) - { - text.setString(sf::String::fromUtf8(_text.begin(), _text.end())); - } - - float MessagePartText::getFontSizeScaled() - { - return MessagePart::getSizeScaled() * 0.8f; - } - - sf::Vector2f MessagePartText::getPosition() const - { - return text.getPosition(); - } - - sf::Vector2f MessagePartText::getSize() const - { - return sf::Vector2f(text.getLocalBounds().width, getFontSizeScaled()); - } - - MessagePartEmoji::MessagePartEmoji(const string &_url, bool _newLine) : - MessagePart(Type::EMOJI, _newLine), - url(_url), - dirty(true) - { - - } - - float MessagePartEmoji::getHeightScaled() - { - return MessagePart::getSizeScaled() * 1.0f; - } - - sf::Vector2f MessagePartEmoji::getPosition() const - { - return sprite.getPosition(); - } - - sf::Vector2f MessagePartEmoji::getSize() const - { - auto spriteScale = sprite.getScale(); - auto textureSize = sprite.getTexture()->getSize(); - return { (float)textureSize.x * spriteScale.x, (float)textureSize.y * spriteScale.y }; - } -} diff --git a/src/Text.cpp b/src/Text.cpp new file mode 100644 index 0000000..41eb4e3 --- /dev/null +++ b/src/Text.cpp @@ -0,0 +1,308 @@ +#include "../include/Text.hpp" +#include "../include/Cache.hpp" +#include "../include/Gif.hpp" +#include <SFML/Graphics/RectangleShape.hpp> + +namespace dchat +{ + const float TAB_WIDTH = 4.0f; + const float EMOJI_PADDING = 5.0f; + + Text::Text(const sf::Font &_font) : + font(_font), + characterSize(0), + maxWidth(0.0f), + color(sf::Color::White), + dirty(false), + plainText(false), + totalHeight(0.0f) + { + + } + + Text::Text(const sf::String &_str, const sf::Font &_font, unsigned int _characterSize, float _maxWidth, bool _plainText) : + font(_font), + characterSize(_characterSize), + vertices(sf::PrimitiveType::Quads), + maxWidth(_maxWidth), + color(sf::Color::White), + dirty(true), + plainText(_plainText), + totalHeight(0.0f) + { + setString(_str); + } + + void Text::setString(const sf::String &str) + { + if(str != this->str) + { + this->str = str; + dirty = true; + stringSplitElements(); + } + } + + void Text::setPosition(float x, float y) + { + position.x = x; + position.y = y; + } + + void Text::setPosition(const sf::Vector2f &position) + { + this->position = position; + } + + void Text::setMaxWidth(float maxWidth) + { + if(maxWidth != this->maxWidth) + { + this->maxWidth = maxWidth; + dirty = true; + } + } + + void Text::setCharacterSize(unsigned int characterSize) + { + if(characterSize != this->characterSize) + { + this->characterSize = characterSize; + dirty = true; + } + } + + void Text::setFillColor(sf::Color color) + { + if(color != this->color) + { + this->color = color; + dirty = true; + } + } + + float Text::getHeight() const + { + return totalHeight; + } + + void Text::stringSplitElements() + { + textElements.clear(); + if(plainText) + { + StringViewUtf32 wholeStr(&str[0], str.getSize()); + textElements.push_back({ wholeStr, TextElement::Type::TEXT }); + return; + } + + size_t offset = 0; + while(offset < str.getSize()) + { + size_t stringStart = offset; + size_t foundStartIndex = str.find("[emoji](", offset); + size_t foundEndIndex = -1; + if(foundStartIndex != -1) + { + offset += (foundStartIndex + 8); + foundEndIndex = str.find(")", offset); + } + + if(foundEndIndex != -1) + { + StringViewUtf32 beforeEmojiStr(&str[stringStart], foundStartIndex - stringStart); + textElements.push_back({ beforeEmojiStr, TextElement::Type::TEXT }); + + StringViewUtf32 url(&str[offset], foundEndIndex - offset); + textElements.push_back({ url, TextElement::Type::EMOJI }); + offset = foundEndIndex + 1; + } + else + { + StringViewUtf32 strToEnd(&str[stringStart], str.getSize() - stringStart); + textElements.push_back({ strToEnd, TextElement::Type::TEXT }); + offset = str.getSize(); + } + } + } + + // Logic loosely based on https://github.com/SFML/SFML/wiki/Source:-CurvedText + void Text::updateGeometry() + { + 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) + { + if(textElement.type == TextElement::Type::EMOJI) + { + glyphPos.x += EMOJI_PADDING; + textElement.position.x = glyphPos.x; + 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; + } + + usize vertexOffset = vertices.getVertexCount(); + vertices.resize(vertices.getVertexCount() + (4 * textElement.text.size)); + textElement.position = glyphPos; + for(size_t i = 0; i < textElement.text.size; ++i) + { + sf::Uint32 codePoint = textElement.text[i]; + float kerning = font.getKerning(prevCodePoint, codePoint, characterSize); + prevCodePoint = codePoint; + glyphPos.x += kerning; + + switch(codePoint) + { + case ' ': + { + glyphPos.x += hspace; + if(glyphPos.x > maxWidth * 0.5f) + { + lastSpacingWordWrapIndex = i; + lastSpacingAccumulatedOffset = glyphPos.x; + } + continue; + } + case '\t': + { + glyphPos.x += (hspace * TAB_WIDTH); + if(glyphPos.x > maxWidth * 0.5f) + { + lastSpacingWordWrapIndex = i; + lastSpacingAccumulatedOffset = glyphPos.x; + } + continue; + } + case '\n': + { + glyphPos.x = 0.0f; + glyphPos.y += vspace; + continue; + } + case '\v': + { + glyphPos.y += (vspace * TAB_WIDTH); + continue; + } + } + + const sf::Glyph &glyph = font.getGlyph(codePoint, characterSize, false); + if(glyphPos.x + glyph.advance > maxWidth) + { + // If there was a space in the text and text width is too long, then we need to word wrap at space index instead, + // which means we need to change the position of all vertices after the space to the current vertex + //printf("last spacing word wrap index: %zu\n", lastSpacingWordWrapIndex); + if(lastSpacingWordWrapIndex != -1) + { + for(size_t j = lastSpacingWordWrapIndex; j < i; ++j) + { + for(size_t k = 0; k < 4; ++k) + { + sf::Vector2f &vertexPos = vertices[vertexOffset + j * 4 + k].position; + vertexPos.x -= lastSpacingAccumulatedOffset; + vertexPos.y += vspace; + } + } + + glyphPos.x -= lastSpacingAccumulatedOffset; + lastSpacingWordWrapIndex = -1; + lastSpacingAccumulatedOffset = 0.0f; + } + else + glyphPos.x = 0.0f; + + glyphPos.y += vspace; + } + + sf::Vector2f vertexTopLeft(glyphPos.x + glyph.bounds.left, glyphPos.y + glyph.bounds.top); + sf::Vector2f vertexTopRight(glyphPos.x + glyph.bounds.left + glyph.bounds.width, glyphPos.y + glyph.bounds.top); + sf::Vector2f vertexBottomLeft(glyphPos.x + glyph.bounds.left, glyphPos.y + glyph.bounds.top + glyph.bounds.height); + sf::Vector2f vertexBottomRight(glyphPos.x + glyph.bounds.left + glyph.bounds.width, glyphPos.y + glyph.bounds.top + glyph.bounds.height); + + sf::Vector2f textureTopLeft(glyph.textureRect.left, glyph.textureRect.top); + sf::Vector2f textureTopRight(glyph.textureRect.left + glyph.textureRect.width, glyph.textureRect.top); + sf::Vector2f textureBottomLeft(glyph.textureRect.left, glyph.textureRect.top + glyph.textureRect.height); + sf::Vector2f textureBottomRight(glyph.textureRect.left + glyph.textureRect.width, glyph.textureRect.top + glyph.textureRect.height); + + vertices[vertexOffset + i * 4 + 0] = { vertexTopLeft, color, textureTopLeft }; + vertices[vertexOffset + i * 4 + 1] = { vertexTopRight, color, textureTopRight }; + vertices[vertexOffset + i * 4 + 2] = { vertexBottomRight, color, textureBottomRight }; + vertices[vertexOffset + i * 4 + 3] = { vertexBottomLeft, color, textureBottomLeft }; + + glyphPos.x += glyph.advance; + } + } + totalHeight = glyphPos.y + vspace; + } + + void Text::draw(sf::RenderTarget &target, Cache &cache) + { + if(dirty) + { + updateGeometry(); + dirty = false; + } + + 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 + states.transform.translate(pos); + states.texture = &font.getTexture(characterSize); + target.draw(vertices, states); + + for(TextElement &textElement : textElements) + { + if(textElement.type == TextElement::Type::EMOJI) + { + sf::Vector2f pos = position; + pos += textElement.position; + sf::Vector2f size(emojiSize, emojiSize); + + // TODO: Optimize this (add unordered_map that takes StringViewUtf32 as key) + auto u8Str = sf::String::fromUtf32(textElement.text.data, textElement.text.data + textElement.text.size).toUtf8(); + const std::string &utf8Str = *(std::basic_string<char>*)&u8Str; + const ImageByUrlResult imageByUrlResult = cache.getImageByUrl(utf8Str); + if(imageByUrlResult.type == ImageByUrlResult::Type::CACHED) + { + if(imageByUrlResult.isGif) + { + imageByUrlResult.gif->setPosition(pos); + imageByUrlResult.gif->setSize(size); + imageByUrlResult.gif->draw(target); + } + else + { + // TODO: Store this sprite somewhere, might not be efficient to create a new sprite object every frame + sf::Sprite sprite(*imageByUrlResult.texture); + sprite.setPosition(pos); + sprite.setScale(size.x / (float)imageByUrlResult.texture->getSize().x, size.y / (float)imageByUrlResult.texture->getSize().y); + target.draw(sprite); + } + } + else + { + sf::RectangleShape rect(size); + rect.setFillColor(sf::Color::White); + rect.setPosition(pos); + target.draw(rect); + } + } + } + } +} diff --git a/src/main.cpp b/src/main.cpp index 3ca986f..df5c558 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,17 +1,26 @@ #include "../include/Channel.hpp" #include "../include/ChannelSidePanel.hpp" #include "../include/Cache.hpp" +#include "../include/ResourceCache.hpp" #include <string> #include <SFML/Graphics.hpp> #include <cstring> #include <X11/Xlib.h> +#include <boost/filesystem/path.hpp> using namespace std; using namespace dchat; using namespace TinyProcessLib; -int main() +int main(int argc, char **argv) { + /* + boost::filesystem::path programPath(argv[0]); + auto parentPath = programPath.parent_path(); + printf("parent path: %s\n", parentPath.string().c_str()); + boost::filesystem::current_path(parentPath); // Ensures loading of resources works no matter which path we run this executable from + */ + XInitThreads(); sf::RenderWindow window(sf::VideoMode(1920, 1080), "dchat"); window.setVerticalSyncEnabled(false); |