#include "../include/MessageBoard.hpp" #include "../include/Settings.hpp" #include "../include/ResourceCache.hpp" #include "../include/Gif.hpp" #include "../include/ChannelSidePanel.hpp" #include "../include/UsersSidePanel.hpp" #include "../include/ChannelTopPanel.hpp" #include "../include/Chatbar.hpp" #include "../include/ColorScheme.hpp" #include "../include/Theme.hpp" #include #include #include #include #include #include using namespace std; namespace dchat { struct LineColor { sf::Color sideColor, centerColor; }; const float USERNAME_PADDING_BOTTOM = 5.0f; const float PADDING_SIDE = 40.0f; const float PADDING_TOP = 0.0f; const float LINE_SIDE_PADDING = 20.0f; const float LINE_HEIGHT = 1.0f; const float USERNAME_TIMESTAMP_SIDE_PADDING = 10.0f; const double SCROLL_MAX_SPEED = 20.0; const LineColor LINE_COLOR { .sideColor = ColorScheme::getBackgroundColor() + sf::Color(10, 10, 10), .centerColor = ColorScheme::getBackgroundColor() + sf::Color(10, 10, 10) }; static void drawGradientLine(const sf::Vector2f &position, const sf::Vector2f &size, const LineColor &color, sf::RenderWindow &window) { sf::Vertex rectangle[] = { sf::Vertex(position, color.sideColor), sf::Vertex(sf::Vector2f(position.x + size.x * 0.5f, position.y), color.centerColor), sf::Vertex(sf::Vector2f(position.x + size.x * 0.5f, position.y + size.y), color.centerColor), sf::Vertex(sf::Vector2f(position.x, position.y + size.y), color.sideColor), sf::Vertex(sf::Vector2f(position.x + size.x * 0.5f, position.y), color.centerColor), sf::Vertex(sf::Vector2f(position.x + size.x, position.y), color.sideColor), sf::Vertex(sf::Vector2f(position.x + size.x, position.y + size.y), color.sideColor), sf::Vertex(sf::Vector2f(position.x + size.x * 0.5f, position.y + size.y), color.centerColor) }; window.draw(rectangle, 8, sf::Quads); } MessageBoard::MessageBoard(const sf::Vector2u &size) : selectingText(false), leftMouseButtonPressed(false), scroll(0.0), scrollSpeed(0.0), totalHeight(0.0), scrollToBottom(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; scrollToBottom = true; } void MessageBoard::drawDefault(sf::RenderWindow &window, Cache &cache) { const float MESSAGE_PADDING_TOP = 25.0f; const float MESSAGE_PADDING_BOTTOM = 30.0f; const sf::Font *usernameFont = ResourceCache::getFont("fonts/Roboto-Regular.ttf"); const int usernameTextCharacterSize = 20 * Settings::getScaling(); const float usernameTextHeight = usernameFont->getLineSpacing(usernameTextCharacterSize); const sf::Font *timestampFont = ResourceCache::getFont("fonts/Roboto-Regular.ttf"); const int timestampTextCharacterSize = 15 * Settings::getScaling(); const float timestampTextHeight = timestampFont->getLineSpacing(timestampTextCharacterSize); sf::RectangleShape lineRect(sf::Vector2f(backgroundSizeWithoutPadding.x - LINE_SIDE_PADDING * Settings::getScaling() * 2.0f, LINE_HEIGHT)); lineRect.setFillColor(ColorScheme::getBackgroundColor() + sf::Color(10, 10, 10)); sf::Vector2 position(ChannelSidePanel::getWidth() + PADDING_SIDE * Settings::getScaling(), ChannelTopPanel::getHeight() + PADDING_TOP); double startHeight = position.y; position.y += scroll; usize numMessages = messages.size(); for(usize i = 0; i < numMessages; ++i) { Message *message = messages[i]; position.y += (MESSAGE_PADDING_TOP * Settings::getScaling()); if(position.y + usernameTextHeight > 0.0f && position.y < backgroundPos.y + backgroundSize.y) { sf::Text usernameText(message->user->getName(), *usernameFont, usernameTextCharacterSize); usernameText.setFillColor(sf::Color(15, 192, 252)); usernameText.setPosition(sf::Vector2f(floor(position.x), floor(position.y))); window.draw(usernameText); if(message->timestampSeconds) { time_t time = (time_t)message->timestampSeconds; struct tm *localTimePtr = localtime(&time); char date[30]; strftime(date, sizeof(date), "%Y-%m-%d at %T", localTimePtr); sf::Text timestamp(date, *timestampFont, timestampTextCharacterSize); timestamp.setFillColor(ColorScheme::getTextRegularColor() * sf::Color(255, 255, 255, 30)); timestamp.setPosition(sf::Vector2f(floor(position.x + usernameText.getLocalBounds().width + USERNAME_TIMESTAMP_SIDE_PADDING * Settings::getScaling()), floor(position.y + 2.0f * Settings::getScaling() + usernameTextHeight * 0.5f - timestampTextHeight * 0.5f))); window.draw(timestamp); } } position.y += usernameTextHeight + USERNAME_PADDING_BOTTOM * Settings::getScaling(); // No need to perform culling here, that is done in @Text draw function message->text.setCharacterSize(18 * Settings::getScaling()); message->text.setMaxWidth(backgroundSize.x); message->text.setPosition(sf::Vector2f(floor(position.x), floor(position.y))); message->text.draw(window, cache); position.y += (message->text.getHeight() + MESSAGE_PADDING_BOTTOM * Settings::getScaling()); if(position.y + LINE_HEIGHT > 0.0f && position.y < backgroundPos.y + backgroundSize.y && i + 1 != numMessages) { lineRect.setPosition(sf::Vector2f(position.x + LINE_SIDE_PADDING * Settings::getScaling() - PADDING_SIDE * Settings::getScaling(), floor(position.y))); window.draw(lineRect); //drawGradientLine(sf::Vector2f(position.x + LINE_SIDE_PADDING, floor(position.y)), sf::Vector2f(backgroundSizeWithoutPadding.x - LINE_SIDE_PADDING * 2.0f, LINE_HEIGHT), LINE_COLOR, window); } position.y += LINE_HEIGHT; } totalHeight = (position.y - scroll) - startHeight; } void MessageBoard::drawSimple(sf::RenderWindow &window, Cache &cache) { const float LINE_SPACING = 20.0f * Settings::getScaling(); const float MESSAGE_PADDING_TOP = 0.0f; const float MESSAGE_PADDING_BOTTOM = 0.0f; const sf::Font *usernameFont = ResourceCache::getFont("fonts/Roboto-Regular.ttf"); const int usernameTextCharacterSize = 20 * Settings::getScaling(); const float usernameTextHeight = usernameFont->getLineSpacing(usernameTextCharacterSize); const float usernameMaxWidth = usernameTextHeight * 5.0f; const sf::Font *timestampFont = ResourceCache::getFont("fonts/Roboto-Regular.ttf"); const int timestampTextCharacterSize = 15 * Settings::getScaling(); const float timestampTextHeight = timestampFont->getLineSpacing(timestampTextCharacterSize); sf::Vector2 position(ChannelSidePanel::getWidth() + PADDING_SIDE * Settings::getScaling() + usernameMaxWidth, ChannelTopPanel::getHeight() + PADDING_TOP); double startHeight = position.y; position.y += scroll; usize numMessages = messages.size(); for(usize i = 0; i < numMessages; ++i) { Message *message = messages[i]; position.y += (MESSAGE_PADDING_TOP * Settings::getScaling()); if(position.y + usernameTextHeight > 0.0f && position.y < backgroundPos.y + backgroundSize.y) { string usernameTextStr = message->user->getName(); usernameTextStr += " - "; sf::Text usernameText(usernameTextStr, *usernameFont, usernameTextCharacterSize); usernameText.setFillColor(sf::Color(15, 192, 252)); usernameText.setPosition(sf::Vector2f(floor(position.x - usernameText.getLocalBounds().width), floor(position.y))); window.draw(usernameText); if(message->timestampSeconds) { time_t time = (time_t)message->timestampSeconds; struct tm *localTimePtr = localtime(&time); char date[30]; strftime(date, sizeof(date), "%Y-%m-%d at %T", localTimePtr); //sf::Text timestamp(date, *timestampFont, timestampTextCharacterSize); //timestamp.setFillColor(ColorScheme::getTextRegularColor() * sf::Color(255, 255, 255, 30)); //timestamp.setPosition(sf::Vector2f(floor(position.x - usernameText.getLocalBounds().width + usernameText.getLocalBounds().width + USERNAME_TIMESTAMP_SIDE_PADDING * Settings::getScaling()), floor(position.y + 2.0f * Settings::getScaling() + usernameTextHeight * 0.5f - timestampTextHeight * 0.5f))); //window.draw(timestamp); } } // No need to perform culling here, that is done in @Text draw function message->text.setCharacterSize(18 * Settings::getScaling()); message->text.setMaxWidth(backgroundSize.x - usernameMaxWidth * 2.0f); message->text.setPosition(sf::Vector2f(floor(position.x), floor(position.y))); message->text.setLineSpacing(LINE_SPACING); message->text.draw(window, cache); position.y += (message->text.getHeight() + MESSAGE_PADDING_BOTTOM * Settings::getScaling()); } totalHeight = (position.y - scroll) - startHeight; } 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; } else if(event.type == sf::Event::MouseWheelScrolled && event.mouseWheelScroll.wheel == sf::Mouse::Wheel::VerticalWheel) { scrollSpeed += (event.mouseWheelScroll.delta * 5.0); if(scrollSpeed > SCROLL_MAX_SPEED) scrollSpeed = SCROLL_MAX_SPEED; else if(scrollSpeed < -SCROLL_MAX_SPEED) scrollSpeed = -SCROLL_MAX_SPEED; } 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(); backgroundSizeWithoutPadding = sf::Vector2f(floor(windowSize.x - ChannelSidePanel::getWidth() - UsersSidePanel::getWidth()), floor(windowSize.y - ChannelTopPanel::getHeight() - Chatbar::getHeight())); backgroundSize = sf::Vector2f(floor(windowSize.x - ChannelSidePanel::getWidth() - UsersSidePanel::getWidth() - PADDING_SIDE * Settings::getScaling() * 2.0f), floor(windowSize.y - ChannelTopPanel::getHeight() - Chatbar::getHeight() - PADDING_TOP)); backgroundPos = sf::Vector2f(ChannelSidePanel::getWidth(), ChannelTopPanel::getHeight()); //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); sf::RectangleShape backgroundRect(backgroundSizeWithoutPadding); backgroundRect.setFillColor(ColorScheme::getBackgroundColor()); backgroundRect.setPosition(ChannelSidePanel::getWidth(), ChannelTopPanel::getHeight()); window.draw(backgroundRect); double deltaTimeMicro = (double)frameTimer.getElapsedTime().asMicroseconds(); frameTimer.restart(); if(dirty) { switch(Theme::getType()) { case Theme::Type::DEFAULT: drawDefault(window, cache); break; case Theme::Type::SIMPLE: drawSimple(window, cache); break; } } scroll += scrollSpeed; double deltaTimeScrollMultiplier = deltaTimeMicro * 0.00004; if(scrollSpeed > 0.0) { scrollSpeed -= deltaTimeScrollMultiplier; } else { scrollSpeed += deltaTimeScrollMultiplier; } if(abs(scrollSpeed - deltaTimeScrollMultiplier) <= deltaTimeScrollMultiplier) scrollSpeed = 0.0; double textOverflow = (double)backgroundSize.y - totalHeight; if(scroll > 0.0 || textOverflow > 0.0) { scroll = 0.0; scrollSpeed = 0.0; } else if(textOverflow < 0.0 && scroll < textOverflow) { scroll = textOverflow; scrollSpeed = 0.0; } if(scrollToBottom) { scrollToBottom = false; if(textOverflow < 0.0) scroll = textOverflow; else scroll = 0.0; } //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); } }