From 4c392178dac1de9a299beb78989c4e0f3fecade9 Mon Sep 17 00:00:00 2001 From: dec05eba Date: Sun, 20 May 2018 11:15:15 +0200 Subject: Add image preview and url/image open in browser --- src/Cache.cpp | 24 ++++---- src/Channel.cpp | 6 +- src/Chatbar.cpp | 4 +- src/Gif.cpp | 5 ++ src/ImagePreview.cpp | 168 +++++++++++++++++++++++++++++++++++++++++++++++++++ src/MessageBoard.cpp | 57 +++++++++++------ src/Text.cpp | 85 ++++++++++++++++++++++++-- src/main.cpp | 39 ++++++------ 8 files changed, 328 insertions(+), 60 deletions(-) create mode 100644 src/ImagePreview.cpp (limited to 'src') diff --git a/src/Cache.cpp b/src/Cache.cpp index 074b7bc..e5d910c 100644 --- a/src/Cache.cpp +++ b/src/Cache.cpp @@ -5,6 +5,8 @@ #include "../include/Gif.hpp" #include "../include/Chatbar.hpp" #include "../include/WebPagePreview.hpp" +#include "../include/ImagePreview.hpp" +#include "../include/StringUtils.hpp" #include #include #include @@ -267,11 +269,21 @@ namespace dchat { case ContentByUrlResult::CachedType::TEXTURE: { + if(ImagePreview::getPreviewContentPtr() == it->second.texture) + { + ++it; + continue; + } delete it->second.texture; break; } case ContentByUrlResult::CachedType::GIF: { + if(ImagePreview::getPreviewContentPtr() == it->second.texture) + { + ++it; + continue; + } delete it->second.gif; break; } @@ -311,18 +323,6 @@ namespace dchat } } - static string stringReplaceChar(const string &str, const string &from, const string &to) - { - string result = str; - size_t pos = 0; - while((pos = result.find(from, pos)) != string::npos) - { - result.replace(pos, from.size(), to); - pos += to.size(); - } - return result; - } - const ContentByUrlResult Cache::getContentByUrl(const string &url, int downloadLimitBytes) { lock_guard lock(imageDownloadMutex); diff --git a/src/Channel.cpp b/src/Channel.cpp index f67eb4c..0bacbc3 100644 --- a/src/Channel.cpp +++ b/src/Channel.cpp @@ -196,10 +196,10 @@ namespace dchat } } - void Channel::processEvent(const sf::Event &event) + void Channel::processEvent(const sf::Event &event, Cache &cache) { - chatbar.processEvent(event, this); - messageBoard.processEvent(event); + chatbar.processEvent(event, cache, this); + messageBoard.processEvent(event, cache); } void Channel::draw(sf::RenderWindow &window, Cache &cache) diff --git a/src/Chatbar.cpp b/src/Chatbar.cpp index bd510f7..aab13a0 100644 --- a/src/Chatbar.cpp +++ b/src/Chatbar.cpp @@ -155,11 +155,11 @@ namespace dchat } } - void Chatbar::processEvent(const sf::Event &event, Channel *channel) + void Chatbar::processEvent(const sf::Event &event, Cache &cache, Channel *channel) { if(!focused) return; - text.processEvent(event); + text.processEvent(event, cache); if(event.type == sf::Event::TextEntered) { if(event.text.unicode == 13) // enter diff --git a/src/Gif.cpp b/src/Gif.cpp index afd5b9d..2bd18e8 100644 --- a/src/Gif.cpp +++ b/src/Gif.cpp @@ -144,6 +144,11 @@ namespace dchat sprite.setPosition(position); } + sf::Vector2f Gif::getPosition() const + { + return sprite.getPosition(); + } + void Gif::setScale(const sf::Vector2f &scale) { sprite.setScale(scale); diff --git a/src/ImagePreview.cpp b/src/ImagePreview.cpp new file mode 100644 index 0000000..0b36dbd --- /dev/null +++ b/src/ImagePreview.cpp @@ -0,0 +1,168 @@ +#include "../include/ImagePreview.hpp" +#include "../include/Settings.hpp" +#include "../include/StringUtils.hpp" +#include "../include/Gif.hpp" +#include +#include + +namespace dchat +{ + static const float PADDING_VERTICAL = 40.0f; + static const float PADDING_HORIZONTAL = 40.0f; + + static ImagePreview *instance = nullptr; + static std::string imagePreviewUrl; + + ImagePreview* ImagePreview::getInstance() + { + if(!instance) + instance = new ImagePreview(); + return instance; + } + + void ImagePreview::preview(sf::Texture *texture, const std::string &url) + { + if(texture == getInstance()->texture) return; + getInstance()->texture = texture; + getInstance()->contentType = texture ? ContentType::TEXTURE : ContentType::NONE; + imagePreviewUrl = url; + if(texture) + getInstance()->sprite.setTexture(*texture, true); + else + getInstance()->sprite = sf::Sprite(); + } + + void ImagePreview::preview(Gif *gif, const std::string &url) + { + if(gif == getInstance()->gif) return; + getInstance()->gif = gif; + getInstance()->contentType = gif ? ContentType::GIF : ContentType::NONE; + imagePreviewUrl = url; + getInstance()->sprite = sf::Sprite(); + } + + void* ImagePreview::getPreviewContentPtr() + { + return getInstance()->texture; + } + + sf::Int32 ImagePreview::getTimeSinceLastSeenMs() + { + return getInstance()->lastSeenTimer.getElapsedTime().asMilliseconds(); + } + + void ImagePreview::processEvent(const sf::Event &event) + { + if(getInstance()->contentType == ContentType::NONE) return; + + if(event.mouseButton.button == sf::Mouse::Button::Left) + { + sf::Vector2f imagePos; + switch(getInstance()->contentType) + { + case ContentType::TEXTURE: + imagePos = getInstance()->sprite.getPosition(); + break; + case ContentType::GIF: + imagePos = getInstance()->gif->getPosition(); + break; + } + getInstance()->sprite.getPosition(); + const auto &imageSize = getInstance()->size; + bool mouseInside = false; + if(event.mouseButton.x >= imagePos.x && event.mouseButton.x <= imagePos.x + imageSize.x && + event.mouseButton.y >= imagePos.y && event.mouseButton.y <= imagePos.y + imageSize.y) + { + mouseInside = true; + } + + if(event.type == sf::Event::MouseButtonPressed && mouseInside && !imagePreviewUrl.empty()) + { + // TODO: Implement for other platforms than linux + std::string escapedUrl = stringReplaceChar(imagePreviewUrl, "'", ""); + escapedUrl = stringReplaceChar(escapedUrl, "\\", ""); + std::string cmd = "xdg-open '"; + cmd += escapedUrl; + cmd += "'"; + printf("Clicked on web page preview, opening web page by running command: %s\n", cmd.c_str()); + system(cmd.c_str()); + } + else if(event.type == sf::Event::MouseButtonReleased && !mouseInside) + { + ImagePreview::preview((sf::Texture*)nullptr); + } + } + else if(event.type == sf::Event::KeyPressed && event.key.code == sf::Keyboard::Escape) + { + ImagePreview::preview((sf::Texture*)nullptr); + } + } + + void ImagePreview::draw(sf::RenderWindow &window) + { + if(getInstance()->contentType == ContentType::NONE) return; + + auto windowSize = window.getSize(); + sf::RectangleShape background(sf::Vector2f(windowSize.x, windowSize.y)); + background.setFillColor(sf::Color(0, 0, 0, 200)); + + auto imageSize = getInstance()->calculateImageSize(windowSize); + getInstance()->size = imageSize; + + window.draw(background); + switch(getInstance()->contentType) + { + case ContentType::TEXTURE: + { + auto textureSize = getInstance()->sprite.getTexture()->getSize(); + getInstance()->sprite.setPosition(windowSize.x / 2 - imageSize.x / 2, windowSize.y / 2 - imageSize.y / 2); + getInstance()->sprite.setScale((double)imageSize.x / (double)textureSize.x, (double)imageSize.y / (double)textureSize.y); + window.draw(getInstance()->sprite); + break; + } + case ContentType::GIF: + { + auto textureSize = getInstance()->gif->getSize(); + getInstance()->gif->setPosition(sf::Vector2f(windowSize.x / 2 - imageSize.x / 2, windowSize.y / 2 - imageSize.y / 2)); + getInstance()->gif->setScale(sf::Vector2f((double)imageSize.x / (double)textureSize.x, (double)imageSize.y / (double)textureSize.y)); + getInstance()->gif->draw(window); + break; + } + } + getInstance()->lastSeenTimer.restart(); + } + + sf::Vector2u ImagePreview::calculateImageSize(sf::Vector2u windowSize) const + { + assert(contentType != ContentType::NONE); + sf::Vector2u imageMaxSize(windowSize.x - PADDING_HORIZONTAL * Settings::getScaling() * 2.0f, windowSize.y - PADDING_VERTICAL * Settings::getScaling() * 2.0f); + sf::Vector2u textureSize; + switch(contentType) + { + case ContentType::TEXTURE: + textureSize = texture->getSize(); + break; + case ContentType::GIF: + textureSize = gif->getSize(); + break; + } + auto imageSize = textureSize; + double textureWidthHeightRatio = (double)imageSize.x / (double)imageSize.y; + double textureHeightWidthRatio = (double)imageSize.y / (double)imageSize.x; + + int overflowVertical = (int)imageSize.y - (int)imageMaxSize.y; + if(overflowVertical > 0) + { + imageSize.y = imageMaxSize.y; + imageSize.x -= (overflowVertical * textureWidthHeightRatio); + } + + int overflowHorizontal = (int)imageSize.x - (int)imageMaxSize.x; + if(overflowHorizontal > 0) + { + imageSize.x = imageMaxSize.x; + imageSize.y -= (overflowHorizontal * textureHeightWidthRatio); + } + return imageSize; + } +} diff --git a/src/MessageBoard.cpp b/src/MessageBoard.cpp index 8e1525f..2e92d7f 100644 --- a/src/MessageBoard.cpp +++ b/src/MessageBoard.cpp @@ -46,7 +46,9 @@ namespace dchat scroll(0.0), scrollSpeed(0.0), totalHeight(0.0), - scrollToBottom(false) + scrollToBottom(false), + visibleMessageStartIndex(-1), + visibleMessageEndIndex(-1) { } @@ -81,8 +83,8 @@ namespace dchat { lock_guard lock(messageProcessMutex); messages.insert(messages.begin() + findPositionToInsertMessageByTimestamp(message), message); - if(!id.isEmpty()) - messageIdMap[id] = message; + message->id = id; + messageIdMap[id] = message; dirty = true; scrollToBottom = true; } @@ -102,6 +104,7 @@ namespace dchat } } + // TODO: Instead of deleting message, cover it with black rectangle for(usize i = 0; i < messages.size(); ++i) { Message *message = messages[i]; @@ -137,6 +140,9 @@ namespace dchat sf::Vector2 position(ChannelSidePanel::getWidth() + PADDING_SIDE * Settings::getScaling(), ChannelTopPanel::getHeight() + PADDING_TOP); double startHeight = position.y; position.y += scroll; + + visibleMessageStartIndex = -1; + visibleMessageEndIndex = -1; usize numMessages = messages.size(); for(usize i = 0; i < numMessages; ++i) { @@ -156,11 +162,14 @@ namespace dchat mergeTextWithNext = nextMessage->user == message->user && (nextMessage->timestampSeconds == 0 || nextMessage->timestampSeconds - message->timestampSeconds <= MERGE_TEXT_TIMESTAMP_DIFF_SEC); } + bool visible = false; + if(!mergeTextWithPrev) { position.y += (MESSAGE_PADDING_TOP * Settings::getScaling()); if(position.y + usernameTextHeight > 0.0f && position.y < backgroundPos.y + backgroundSize.y) { + visible = true; sf::Text usernameText(sf::String::fromUtf8(message->user->getName().begin(), message->user->getName().end()), *usernameFont, usernameTextCharacterSize); usernameText.setFillColor(sf::Color(15, 192, 252)); usernameText.setPosition(sf::Vector2f(floor(position.x + (AVATAR_DIAMETER + AVATAR_PADDING_SIDE) * Settings::getScaling()), floor(position.y))); @@ -219,7 +228,9 @@ namespace dchat message->text.setMaxWidth(backgroundSize.x - (AVATAR_DIAMETER + AVATAR_PADDING_SIDE) * Settings::getScaling()); message->text.setPosition(sf::Vector2f(floor(position.x + (AVATAR_DIAMETER + AVATAR_PADDING_SIDE) * Settings::getScaling()), floor(position.y))); message->text.setLineSpacing(LINE_SPACING); - message->text.draw(window, cache); + bool textDrawn = message->text.draw(window, cache); + if(!visible) + visible = textDrawn; position.y += message->text.getHeight(); if(!mergeTextWithNext) { @@ -227,12 +238,20 @@ namespace dchat if(position.y + LINE_HEIGHT > 0.0f && position.y < backgroundPos.y + backgroundSize.y && i + 1 != numMessages) { + visible = true; 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; + + if(visible) + { + if(visibleMessageStartIndex == -1) + visibleMessageStartIndex = i; + visibleMessageEndIndex = i; + } } totalHeight = (position.y - scroll) - startHeight; } @@ -294,40 +313,42 @@ namespace dchat totalHeight = (position.y - scroll) - startHeight; } - void MessageBoard::processEvent(const sf::Event &event) + void MessageBoard::processEvent(const sf::Event &event, Cache &cache) { lock_guard lock(messageProcessMutex); - for(Message *message : messages) - { - message->text.processEvent(event); - } OnlineLocalUser *onlineLocalUser = nullptr; if(channel->getLocalUser()->type == User::Type::ONLINE_LOCAL_USER) onlineLocalUser = static_cast(channel->getLocalUser()); + bool openContextMenu = false; if(onlineLocalUser && event.type == sf::Event::MouseButtonPressed && event.mouseButton.button == sf::Mouse::Button::Right) + openContextMenu = true; + + if(visibleMessageStartIndex != -1) { - for(auto it : messageIdMap) + //printf("visibleMessageStartIndex: %u, visibleMessageEndIndex: %u\n", visibleMessageStartIndex, visibleMessageEndIndex); + for(usize i = visibleMessageStartIndex; i <= visibleMessageEndIndex; ++i) { - it.second->text.processEvent(event); - auto textPos = it.second->text.getPosition(); - if(it.second->user == channel->getLocalUser() && event.mouseButton.x >= textPos.x && event.mouseButton.x <= textPos.x + it.second->text.getMaxWidth() && event.mouseButton.y >= textPos.y && event.mouseButton.y <= textPos.y + it.second->text.getHeight()) + Message *message = messages[i]; + message->text.processEvent(event, cache); + + auto textPos = message->text.getPosition(); + if(openContextMenu && message->user == channel->getLocalUser() && event.mouseButton.x >= textPos.x && event.mouseButton.x <= textPos.x + message->text.getMaxWidth() && event.mouseButton.y >= textPos.y && event.mouseButton.y <= textPos.y + message->text.getHeight()) { auto contextMenu = GlobalContextMenu::getEditMessageContextMenu(); contextMenu->setPosition(sf::Vector2f(event.mouseButton.x, event.mouseButton.y)); contextMenu->setVisible(true); - GlobalContextMenu::setClickDeleteMessageCallbackFunc([this, it, onlineLocalUser](ContextMenuItem *menuItem) + GlobalContextMenu::setClickDeleteMessageCallbackFunc([this, message, onlineLocalUser](ContextMenuItem *menuItem) { - channel->deleteMessage(it.first, onlineLocalUser->getPublicKey()); + channel->deleteMessage(message->id, onlineLocalUser->getPublicKey()); GlobalContextMenu::setClickDeleteMessageCallbackFunc(nullptr); }); - return; } } - GlobalContextMenu::setClickDeleteMessageCallbackFunc(nullptr); } - else if(event.type == sf::Event::MouseWheelScrolled && event.mouseWheelScroll.wheel == sf::Mouse::Wheel::VerticalWheel) + + if(event.type == sf::Event::MouseWheelScrolled && event.mouseWheelScroll.wheel == sf::Mouse::Wheel::VerticalWheel) { scrollSpeed += (event.mouseWheelScroll.delta * 5.0); if(scrollSpeed > SCROLL_MAX_SPEED) diff --git a/src/Text.cpp b/src/Text.cpp index 9688ad1..3aa1574 100644 --- a/src/Text.cpp +++ b/src/Text.cpp @@ -3,6 +3,8 @@ #include "../include/Gif.hpp" #include "../include/WebPagePreview.hpp" #include "../include/ColorScheme.hpp" +#include "../include/ImagePreview.hpp" +#include "../include/StringUtils.hpp" #include #include #include @@ -439,7 +441,6 @@ namespace dchat { // 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) @@ -698,8 +699,81 @@ namespace dchat return result; } - void Text::processEvent(const sf::Event &event) + void Text::onMouseClick(const sf::Event::MouseButtonEvent &event, Cache &cache) { + if(event.button != sf::Mouse::Button::Left) return; + float vspace = font->getLineSpacing(characterSize); + + for(TextElement &textElement : textElements) + { + if(textElement.type == TextElement::Type::URL) + { + sf::Vector2f pos = position; + pos.y += floor(textElement.position.y); + float imageHeight = floor(vspace * IMAGE_HEIGHT_SCALE); + + // 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*)&u8Str; + const ContentByUrlResult contentByUrlResult = cache.getContentByUrl(utf8Str); + if(contentByUrlResult.type == ContentByUrlResult::Type::CACHED) + { + switch(contentByUrlResult.cachedType) + { + case ContentByUrlResult::CachedType::TEXTURE: + { + auto textureSize = contentByUrlResult.texture->getSize(); + float widthToHeightRatio = (float)textureSize.x / (float)textureSize.y; + float imageWidth = fmin(imageHeight * widthToHeightRatio, maxWidth); + if(event.x >= pos.x && event.x <= pos.x + imageWidth && event.y >= pos.y && event.y <= pos.y + imageHeight) + { + ImagePreview::preview(contentByUrlResult.texture, utf8Str); + return; + } + break; + } + case ContentByUrlResult::CachedType::GIF: + { + auto textureSize = contentByUrlResult.gif->getSize(); + float widthToHeightRatio = (float)textureSize.x / (float)textureSize.y; + float imageWidth = fmin(imageHeight * widthToHeightRatio, maxWidth); + if(event.x >= pos.x && event.x <= pos.x + imageWidth && event.y >= pos.y && event.y <= pos.y + imageHeight) + { + ImagePreview::preview(contentByUrlResult.gif, utf8Str); + return; + } + break; + } + case ContentByUrlResult::CachedType::WEB_PAGE_PREVIEW: + { + const float previewWidth = fmin(maxWidth, floor(imageHeight * 1.77f)); + if(event.x >= pos.x && event.x <= pos.x + previewWidth && event.y >= pos.y && event.y <= pos.y + imageHeight) + { + // TODO: Implement for other platforms than linux + std::string escapedUrl = stringReplaceChar(utf8Str, "'", ""); + escapedUrl = stringReplaceChar(escapedUrl, "\\", ""); + std::string cmd = "xdg-open '"; + cmd += escapedUrl; + cmd += "'"; + printf("Clicked on web page preview, opening web page by running command: %s\n", cmd.c_str()); + system(cmd.c_str()); + return; + } + break; + } + } + } + } + } + } + + void Text::processEvent(const sf::Event &event, Cache &cache) + { + if(event.type == sf::Event::MouseButtonReleased) + { + onMouseClick(event.mouseButton, cache); + } + if(!editable) return; bool caretAtEnd = textElements.size() == 0 || textElements[0].text.size == 0 || caretIndex == textElements[0].text.size; @@ -798,7 +872,7 @@ namespace dchat } } - void Text::draw(sf::RenderTarget &target, Cache &cache) + bool Text::draw(sf::RenderTarget &target, Cache &cache) { if(dirtyText) { @@ -831,7 +905,7 @@ namespace dchat //sf::FloatRect textRect(pos.x, pos.y, maxWidth, ) //colRect.contains() //if(pos.x + maxWidth <= 0.0f || pos.x >= maxWidth || pos.y + totalHeight <= 0.0f || pos.y >= target.getSize().y) return; - if(pos.y + getHeight() <= 0.0f || pos.y >= target.getSize().y) return; + if(pos.y + getHeight() <= 0.0f || pos.y >= target.getSize().y) return false; /* if(editable) { @@ -972,7 +1046,7 @@ namespace dchat } } - if(!editable) return; + if(!editable) return true; //float rows = floor(totalHeight / (vspace + lineSpacing)); const float caretRow = getRowByPosition(caretPosition); @@ -981,5 +1055,6 @@ namespace dchat caretRect.setFillColor(sf::Color::White); caretRect.setPosition(sf::Vector2f(floor(pos.x + caretPosition.x), floor(pos.y + caretRow * (vspace + lineSpacing)))); target.draw(caretRect); + return true; } } diff --git a/src/main.cpp b/src/main.cpp index b5ec81e..a66945c 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -9,6 +9,8 @@ #include "../include/Settings.hpp" #include "../include/ColorScheme.hpp" #include "../include/GlobalContextMenu.hpp" +#include "../include/StringUtils.hpp" +#include "../include/ImagePreview.hpp" #include #include #include @@ -19,7 +21,6 @@ #include #include #include -#include #include using namespace std; @@ -46,16 +47,6 @@ static void channelChangeUserNickname(Channel *channel, const StringView data, c // We dont care if there is more data to read (malicious packet), we already got all the data we need } -static void stringReplaceChar(string &str, const string &from, const string &to) -{ - size_t pos = 0; - while((pos = str.find(from, pos)) != string::npos) - { - str.replace(pos, from.size(), to); - pos += to.size(); - } -} - static void channelAddStoredMessage(Channel *channel, const odhtdb::Hash &requestHash, const odhtdb::Signature::PublicKey &creatorPublicKey, const StringView decryptedObject, u64 timestamp) { User *user = channel->getUserByPublicKey(creatorPublicKey); @@ -78,12 +69,11 @@ static void channelAddStoredMessage(Channel *channel, const odhtdb::Hash &reques if(!focused) { stringReplaceChar(msg, "'", ""); - stringReplaceChar(msg, "\"", ""); stringReplaceChar(msg, "\\", ""); string cmd = "notify-send dchat '"; cmd += msg; cmd += "'"; - TinyProcessLib::Process notifySend(cmd, ""); + system(cmd.c_str()); } break; } @@ -681,28 +671,36 @@ int main(int argc, char **argv) else if(event.type == sf::Event::Resized) { sf::FloatRect viewRect(0.0f, 0.0f, event.size.width, event.size.height); + /* // TODO: Use xlib to set window minimum size instead const int minWidth = 800; if(event.size.width < minWidth) { viewRect.width = minWidth; window.setSize(sf::Vector2u(minWidth, event.size.height)); } - + */ sf::View view(viewRect); window.setView(view); } - else if(event.type == sf::Event::GainedFocus) + else if(event.type == sf::Event::MouseEntered) window.setFramerateLimit(FRAMERATE_FOCUSED); - //else if(event.type == sf::Event::LostFocus) + //else if(event.type == sf::Event::MouseLeft) // window.setFramerateLimit(FRAMERATE_NOT_FOCUSED); - if(event.type == sf::Event::GainedFocus) + if(event.type == sf::Event::MouseEntered) focused = true; - else if(event.type == sf::Event::LostFocus) + else if(event.type == sf::Event::MouseLeft) focused = false; - GlobalContextMenu::processEvent(event); - currentChannel->processEvent(event); + if(focused) + { + ImagePreview::processEvent(event); + if(!ImagePreview::getPreviewContentPtr() && ImagePreview::getTimeSinceLastSeenMs() > 250) + { + GlobalContextMenu::processEvent(event); + currentChannel->processEvent(event, cache); + } + } } window.clear(ColorScheme::getBackgroundColor()); @@ -711,6 +709,7 @@ int main(int argc, char **argv) UsersSidePanel::draw(window, cache); ChannelTopPanel::draw(window); GlobalContextMenu::draw(window); + ImagePreview::draw(window); if(waitingToJoin) { -- cgit v1.2.3