#include "../include/MessageBoard.hpp" #include "../include/Settings.hpp" #include "../include/ResourceCache.hpp" #include "../include/Gif.hpp" #include #include #include #include #include using namespace std; namespace dchat { const sf::Color BACKGROUND_COLOR(40, 40, 40); const float USERNAME_PADDING_BOTTOM = 5.0f; const float MESSAGE_PADDING_BOTTOM = 20.0f; MessageBoard::MessageBoard(const sf::Vector2u &size) : selectingText(false), leftMouseButtonPressed(false) { updateStaticContentTexture(size); } MessageBoard::~MessageBoard() { for(Message *message : messages) { delete message; } } void MessageBoard::updateStaticContentTexture(const sf::Vector2u &newSize) { if(!staticContentTexture.create(newSize.x, newSize.y)) throw std::runtime_error("Failed to create render target for message board!"); dirty = true; } void MessageBoard::addMessage(Message *message) { messages.push_back(message); dirty = true; } void MessageBoard::processEvent(const sf::Event &event) { if(event.type == sf::Event::MouseButtonPressed) { if(event.mouseButton.button == sf::Mouse::Button::Left) { leftMouseButtonPressed = true; mousePos.x = event.mouseButton.x; mousePos.y = event.mouseButton.y; } } else if(event.type == sf::Event::MouseButtonReleased) { if(event.mouseButton.button == sf::Mouse::Button::Left) { leftMouseButtonPressed = false; mousePos.x = event.mouseButton.x; mousePos.y = event.mouseButton.y; } } else if(event.type == sf::Event::LostFocus) { leftMouseButtonPressed = false; } else if(event.type == sf::Event::MouseMoved) { mousePos.x = event.mouseMove.x; mousePos.y = event.mouseMove.y; } if(selectingText && !leftMouseButtonPressed) { selectingText = false; } else if(!selectingText && leftMouseButtonPressed) { selectingText = true; selectingTextStart.x = mousePos.x; selectingTextStart.y = mousePos.y; } } void MessageBoard::draw(sf::RenderWindow &window, Cache &cache) { auto windowSize = window.getSize(); 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(dirty) staticContentTexture.clear(sf::Color::Transparent); const sf::Font &usernameFont = ResourceCache::getFont("fonts/Roboto-Regular.ttf"); sf::Vector2f position; for(Message *message : messages) { 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()) { switch(messagePart->type) { case MessagePart::Type::TEXT: { MessagePartText *messagePartText = static_cast(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(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; } } if(index < numParts - 1 && messagePart->newLine) { position.x = 0.0f; position.y += MessagePart::getSizeScaled(); } ++index; } position.x = 0.0f; position.y += MessagePart::getSizeScaled() + MESSAGE_PADDING_BOTTOM; } staticContentTexture.display(); dirty = false; // TODO: Save this, expensive to create on fly? sf::Sprite textureSprite(staticContentTexture.getTexture()); textureSprite.setPosition(backgroundPos); window.draw(textureSprite); if(!selectingText) return; 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(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 } }