diff options
-rw-r--r-- | .gitignore | 1 | ||||
-rw-r--r-- | .kdev4/dchat.kdev4 | 13 | ||||
-rw-r--r-- | README.md | 3 | ||||
m--------- | depends/odhtdb | 0 | ||||
-rw-r--r-- | include/Cache.hpp | 54 | ||||
-rw-r--r-- | include/Channel.hpp | 23 | ||||
-rw-r--r-- | include/ChannelSidePanel.hpp | 16 | ||||
-rw-r--r-- | include/Message.hpp | 1 | ||||
-rw-r--r-- | include/MessageBoard.hpp | 3 | ||||
-rw-r--r-- | include/MessagePart.hpp | 17 | ||||
-rw-r--r-- | include/ResourceCache.hpp | 12 | ||||
-rw-r--r-- | include/env.hpp | 63 | ||||
-rw-r--r-- | project.conf | 1 | ||||
-rw-r--r-- | src/Cache.cpp | 158 | ||||
-rw-r--r-- | src/Channel.cpp | 79 | ||||
-rw-r--r-- | src/ChannelSidePanel.cpp | 9 | ||||
-rw-r--r-- | src/Message.cpp | 5 | ||||
-rw-r--r-- | src/MessageBoard.cpp | 104 | ||||
-rw-r--r-- | src/MessagePart.cpp | 24 | ||||
-rw-r--r-- | src/ResourceCache.cpp | 25 | ||||
-rw-r--r-- | src/main.cpp | 71 |
21 files changed, 580 insertions, 102 deletions
@@ -1 +1,2 @@ sibs-build/ +dchat.kdev4/ diff --git a/.kdev4/dchat.kdev4 b/.kdev4/dchat.kdev4 index 4709f83..d4eb7c2 100644 --- a/.kdev4/dchat.kdev4 +++ b/.kdev4/dchat.kdev4 @@ -1,5 +1,18 @@ [Buildset] BuildItems=@Variant(\x00\x00\x00\t\x00\x00\x00\x00\x01\x00\x00\x00\x0b\x00\x00\x00\x00\x01\x00\x00\x00\n\x00d\x00c\x00h\x00a\x00t) +[CustomDefinesAndIncludes][ProjectPath0] +Path=. +parseAmbiguousAsCPP=true +parserArguments=-ferror-limit=100 -fspell-checking -Wdocumentation -Wunused-parameter -Wunreachable-code -Wall -std=c++11 +parserArgumentsC=-ferror-limit=100 -fspell-checking -Wdocumentation -Wunused-parameter -Wunreachable-code -Wall -std=c99 + +[CustomDefinesAndIncludes][ProjectPath0][Compiler] +Name=Clang + +[CustomDefinesAndIncludes][ProjectPath0][Includes] +1=/home/dec05eba/.sibs/lib/tiny-process/2.0.0 +2=/home/dec05eba/git/dchat/depends/odhtdb/include + [Project] VersionControlSupport=kdevgit @@ -1,2 +1,3 @@ # DcHaT -Decentralized chat using odhtdb +Decentralized chat using odhtdb. +Max emoji file size is 512kb. diff --git a/depends/odhtdb b/depends/odhtdb -Subproject 6e4d46f8cf911b82a10e8cd25b65fcc421bbc71 +Subproject 59e86b8b22c5ffb925b5a68b43de4ddc92986d5 diff --git a/include/Cache.hpp b/include/Cache.hpp new file mode 100644 index 0000000..89abe2c --- /dev/null +++ b/include/Cache.hpp @@ -0,0 +1,54 @@ +#pragma once + +#include <boost/filesystem/path.hpp> +#include <SFML/Graphics/Texture.hpp> +#include <string> +#include <thread> +#include <mutex> +#include <vector> + +namespace TinyProcessLib +{ + class Process; +} + +namespace dchat +{ + struct ImageByUrlResult + { + enum class Type + { + CACHED, + DOWNLOADING, + FAILED_DOWNLOAD + }; + + // @texture is null if @type is DOWNLOADING or FAILED_DOWNLOAD + const sf::Texture *texture; + Type type; + }; + + class Cache + { + public: + Cache(); + + // Creates directory if it doesn't exist (recursively). Throws boost exception on failure + static boost::filesystem::path getDchatDir(); + + // Get cached image or downloads it. + // Default download file limit is 12MB + // Returns ImageByUrlResult describing texture status. + const ImageByUrlResult getImageByUrl(const std::string &url, int downloadLimitBytes = 12582912); + private: + struct ImageDownloadInfo + { + TinyProcessLib::Process *process; + std::string url; + }; + + std::thread downloadWaitThread; + std::vector<ImageDownloadInfo> imageDownloadProcesses; + std::mutex imageDownloadMutex; + }; +} diff --git a/include/Channel.hpp b/include/Channel.hpp new file mode 100644 index 0000000..fa52a4b --- /dev/null +++ b/include/Channel.hpp @@ -0,0 +1,23 @@ +#pragma once + +#include "MessageBoard.hpp" +#include "Chatbar.hpp" +#include "User.hpp" +#include "Channel.hpp" + +namespace dchat +{ + class Channel + { + public: + Channel(); + ~Channel(); + + void processEvent(const sf::Event &event); + void draw(sf::RenderWindow &window, Cache &cache); + private: + MessageBoard messageBoard; + Chatbar chatbar; + OfflineUser localOfflineUser; + }; +} diff --git a/include/ChannelSidePanel.hpp b/include/ChannelSidePanel.hpp new file mode 100644 index 0000000..604dfcf --- /dev/null +++ b/include/ChannelSidePanel.hpp @@ -0,0 +1,16 @@ +#pragma once + +#include <vector> + +namespace dchat +{ + class Channel; + + class ChannelSidePanel + { + public: + void addChannel(Channel *channel); + private: + std::vector<Channel*> channels; + }; +} diff --git a/include/Message.hpp b/include/Message.hpp index c037eb3..efc1f4c 100644 --- a/include/Message.hpp +++ b/include/Message.hpp @@ -14,6 +14,7 @@ namespace dchat virtual ~Message(); void addText(const std::string &text); + void addImage(const std::string &url); std::vector<MessagePart*>& getParts(); const User *user; diff --git a/include/MessageBoard.hpp b/include/MessageBoard.hpp index f399761..105d675 100644 --- a/include/MessageBoard.hpp +++ b/include/MessageBoard.hpp @@ -1,6 +1,7 @@ #pragma once #include "Message.hpp" +#include "../include/Cache.hpp" #include <SFML/Graphics/RenderTexture.hpp> #include <SFML/Graphics/RenderWindow.hpp> #include <SFML/Window/Event.hpp> @@ -18,7 +19,7 @@ namespace dchat void addMessage(Message *message); void processEvent(const sf::Event &event); - void draw(sf::RenderWindow &window); + void draw(sf::RenderWindow &window, Cache &cache); private: sf::RenderTexture staticContentTexture; bool useStaticContentTexture; diff --git a/include/MessagePart.hpp b/include/MessagePart.hpp index e50852a..cbb0f26 100644 --- a/include/MessagePart.hpp +++ b/include/MessagePart.hpp @@ -1,6 +1,7 @@ #pragma once #include <SFML/Graphics/Text.hpp> +#include <SFML/Graphics/Sprite.hpp> #include <SFML/System/Vector2.hpp> #include <string> @@ -11,7 +12,8 @@ namespace dchat public: enum class Type { - TEXT + TEXT, + EMOJI }; MessagePart(Type _type) : type(_type) {} @@ -35,4 +37,17 @@ namespace dchat sf::Text text; }; + + class MessagePartEmoji : public MessagePart + { + public: + MessagePartEmoji(const std::string &url); + + static float getHeightScaled(); + virtual sf::Vector2f getPosition() const override; + virtual sf::Vector2f getSize() const override; + + sf::Sprite sprite; + std::string url; + }; } diff --git a/include/ResourceCache.hpp b/include/ResourceCache.hpp index d35eb8f..256e5a4 100644 --- a/include/ResourceCache.hpp +++ b/include/ResourceCache.hpp @@ -1,13 +1,25 @@ #pragma once #include <SFML/Graphics/Font.hpp> +#include <SFML/Graphics/Texture.hpp> #include <string> +#include <stdexcept> namespace dchat { + class FailedToLoadResourceException : public std::runtime_error + { + public: + FailedToLoadResourceException(const std::string &errMsg) : std::runtime_error(errMsg) {} + }; + class ResourceCache { public: + // Throws FailedToLoadResourceException on failure static const sf::Font& getFont(const std::string &filepath); + + // Throws FailedToLoadResourceException on failure + static const sf::Texture* getTexture(const std::string &filepath); }; } diff --git a/include/env.hpp b/include/env.hpp new file mode 100644 index 0000000..e0a5b03 --- /dev/null +++ b/include/env.hpp @@ -0,0 +1,63 @@ +#pragma once + +#define OS_FAMILY_WINDOWS 0 +#define OS_FAMILY_POSIX 1 + +#define OS_TYPE_WINDOWS 0 +#define OS_TYPE_LINUX 1 + +#if defined(_WIN32) || defined(_WIN64) + #if defined(_WIN64) + #define OS_ENV_64BIT + #else + #define OS_ENV_32BIT + #endif + #define OS_FAMILY OS_FAMILY_WINDOWS + #define OS_TYPE OS_TYPE_WINDOWS + + #ifndef UNICODE + #define UNICODE + #endif + + #ifndef _UNICODE + #define _UNICODE + #endif + + #ifndef WIN32_LEAN_AND_MEAN + #define WIN32_LEAN_AND_MEAN + #endif + + #include <Windows.h> +#endif + +#if defined(__linux__) || defined(__unix__) || defined(__APPLE__) || defined(_POSIX_VERSION) + #define OS_FAMILY OS_FAMILY_POSIX +#endif + +#ifdef __linux__ + #define OS_TYPE OS_TYPE_LINUX +#endif + +#if defined(__GNUC__) + #if defined(__x86_64__) || defined(__pc64__) + #define OS_ENV_64BIT + #else + #define OS_ENV_32BIT + #endif +#endif + +#if !defined(OS_ENV_32BIT) && !defined(OS_ENV_64BIT) + #error "System is not detected as either 32-bit or 64-bit" +#endif + +#if !defined(OS_FAMILY) + #error "System not supported. Only Windows and Posix systems supported right now" +#endif + +#if !defined(OS_TYPE) + #error "System not supported. Only Windows and linux systems supported right now" +#endif + +#if !defined(DEBUG) && !defined(NDEBUG) +#define DEBUG +#endif diff --git a/project.conf b/project.conf index 58369b9..d8e64f9 100644 --- a/project.conf +++ b/project.conf @@ -9,3 +9,4 @@ sfml-window = "2.4.2" sfml-graphics = "2.4.2" sfml-system = "2.4.2" boost-filesystem = "1.66.0" +tiny-process = "2.0.0" diff --git a/src/Cache.cpp b/src/Cache.cpp new file mode 100644 index 0000000..7e3272a --- /dev/null +++ b/src/Cache.cpp @@ -0,0 +1,158 @@ +#include "../include/Cache.hpp" +#include "../include/env.hpp" +#include "../include/ResourceCache.hpp" +#include <boost/filesystem/convenience.hpp> +#include <unordered_map> +#include <process.hpp> +#include <odhtdb/Hash.hpp> + +#if OS_FAMILY == OS_FAMILY_POSIX +#include <pwd.h> +#else +#include <string> +#endif + +using namespace std; +using namespace TinyProcessLib; + +namespace dchat +{ + unordered_map<string, ImageByUrlResult> imageUrlCache; + + boost::filesystem::path getHomeDir() + { + #if OS_FAMILY == OS_FAMILY_POSIX + const char *homeDir = getenv("HOME"); + if(!homeDir) + { + passwd *pw = getpwuid(getuid()); + homeDir = pw->pw_dir; + } + return boost::filesystem::path(homeDir); + #elif OS_FAMILY == OS_FAMILY_WINDOWS + BOOL ret; + HANDLE hToken; + std::wstring homeDir; + DWORD homeDirLen = MAX_PATH; + homeDir.resize(homeDirLen); + + if (!OpenProcessToken(GetCurrentProcess(), TOKEN_READ, &hToken)) + return Result<FileString>::Err("Failed to open process token"); + + if (!GetUserProfileDirectory(hToken, &homeDir[0], &homeDirLen)) + { + CloseHandle(hToken); + return Result<FileString>::Err("Failed to get home directory"); + } + + CloseHandle(hToken); + homeDir.resize(wcslen(homeDir.c_str())); + return boost::filesystem::path(homeDir); + #endif + } + + boost::filesystem::path Cache::getDchatDir() + { + boost::filesystem::path dchatHomeDir = getHomeDir() / ".dchat"; + boost::filesystem::create_directories(dchatHomeDir); + return dchatHomeDir; + } + + Cache::Cache() + { + downloadWaitThread = thread([this] + { + while(true) + { + imageDownloadMutex.lock(); + for(vector<ImageDownloadInfo>::iterator it = imageDownloadProcesses.begin(); it != imageDownloadProcesses.end();) + { + int exitStatus; + if(it->process->try_get_exit_status(exitStatus)) + { + bool failed = exitStatus != 0; + + if(!failed) + { + boost::filesystem::path filepath = getDchatDir(); + odhtdb::Hash urlHash(it->url.data(), it->url.size()); + filepath /= urlHash.toString(); + + try + { + const sf::Texture *texture = ResourceCache::getTexture(filepath.string()); + ImageByUrlResult &imageByUrlResult = imageUrlCache[it->url]; + imageByUrlResult.texture = texture; + imageByUrlResult.type = ImageByUrlResult::Type::CACHED; + } + catch(FailedToLoadResourceException &e) + { + fprintf(stderr, "%s\n", e.what()); + failed = true; + } + } + + if(failed) + { + imageUrlCache[it->url].type = ImageByUrlResult::Type::FAILED_DOWNLOAD; + fprintf(stderr, "Image download failed for url: %s\n", it->url.c_str()); + } + + it = imageDownloadProcesses.erase(it); + } + else + ++it; + } + imageDownloadMutex.unlock(); + + while(imageDownloadProcesses.empty()) + this_thread::sleep_for(chrono::milliseconds(20)); + } + }); + downloadWaitThread.detach(); + } + + const ImageByUrlResult Cache::getImageByUrl(const string &url, int downloadLimitBytes) + { + auto it = imageUrlCache.find(url); + if(it != imageUrlCache.end()) + return it->second; + + // TODO: Verify hashed url is not too long for filepath on windows + boost::filesystem::path filepath = getDchatDir(); + odhtdb::Hash urlHash(url.data(), url.size()); + filepath /= urlHash.toString(); + + // Check if file exists because we dont want sfml spam with "Failed to load image""... + if(boost::filesystem::exists(filepath)) + { + try + { + const sf::Texture *texture = ResourceCache::getTexture(filepath.string()); + lock_guard<mutex> lock(imageDownloadMutex); + ImageByUrlResult result { texture, ImageByUrlResult::Type::CACHED }; + imageUrlCache[url] = result; + return result; + } + catch(FailedToLoadResourceException &e) + { + + } + } + + lock_guard<mutex> lock(imageDownloadMutex); + ImageByUrlResult result { nullptr, ImageByUrlResult::Type::DOWNLOADING }; + imageUrlCache[url] = result; + + string downloadLimitBytesStr = to_string(downloadLimitBytes); + + Process::string_type cmd = "curl -L --silent -o '"; + cmd += filepath.native(); + cmd += "' --max-filesize " + downloadLimitBytesStr + " --range 0-" + downloadLimitBytesStr + " --url '" + url + "'"; + // certutil.exe -urlcache -split -f "https://url/to/file" path/and/name/to/save/as/file + Process *process = new Process(cmd, "", nullptr, nullptr, false); + ImageDownloadInfo imageDownloadInfo { process, url }; + imageDownloadProcesses.emplace_back(imageDownloadInfo); + return result; + } +} diff --git a/src/Channel.cpp b/src/Channel.cpp new file mode 100644 index 0000000..0fc7ec5 --- /dev/null +++ b/src/Channel.cpp @@ -0,0 +1,79 @@ +#include "../include/Channel.hpp" +#include <cstring> + +using namespace std; + +namespace dchat +{ + Channel::Channel() : + messageBoard(sf::Vector2u(1.0f, 1.0f)), + localOfflineUser("You") + { + { + Message *message = new Message(&localOfflineUser); + message->addText(u8"hello, worldåäö1!"); + message->addImage("https://discordemoji.com/assets/emoji/think_fish.png"); + messageBoard.addMessage(message); + } + + { + Message *message = new Message(&localOfflineUser); + message->addText(u8"hello, world2!"); + messageBoard.addMessage(message); + } + + { + Message *message = new Message(&localOfflineUser); + message->addText(u8"hello, world3!"); + messageBoard.addMessage(message); + } + } + + Channel::~Channel() + { + + } + + void Channel::processEvent(const sf::Event &event) + { + if(event.type == sf::Event::TextEntered) + { + if(event.text.unicode == 8) // backspace + chatbar.removePreviousChar(); + else if(event.text.unicode == 13) // enter + { + Message *message = new Message(&localOfflineUser); + auto chatbarMsgUtf8 = chatbar.getString().toUtf8(); + string msg; + msg.resize(chatbarMsgUtf8.size()); + memcpy(&msg[0], chatbarMsgUtf8.data(), chatbarMsgUtf8.size()); + + message->addText(msg); + messageBoard.addMessage(message); + chatbar.clear(); + } + else if(event.text.unicode == 127) // delete + { + chatbar.removeNextChar(); + } + else + { + chatbar.addChar(event.text.unicode); + } + } + else if(event.type == sf::Event::KeyPressed) + { + if(event.key.code == sf::Keyboard::Left) + chatbar.moveCaretLeft(); + else if(event.key.code == sf::Keyboard::Right) + chatbar.moveCaretRight(); + } + messageBoard.processEvent(event); + } + + void Channel::draw(sf::RenderWindow &window, Cache &cache) + { + messageBoard.draw(window, cache); + chatbar.draw(window); + } +} diff --git a/src/ChannelSidePanel.cpp b/src/ChannelSidePanel.cpp new file mode 100644 index 0000000..23693b3 --- /dev/null +++ b/src/ChannelSidePanel.cpp @@ -0,0 +1,9 @@ +#include "../include/ChannelSidePanel.hpp" + +namespace dchat +{ + void ChannelSidePanel::addChannel(Channel *channel) + { + channels.push_back(channel); + } +} diff --git a/src/Message.cpp b/src/Message.cpp index 44174ec..2740c11 100644 --- a/src/Message.cpp +++ b/src/Message.cpp @@ -23,6 +23,11 @@ namespace dchat messageParts.push_back(new MessagePartText(text)); } + void Message::addImage(const string &url) + { + messageParts.push_back(new MessagePartEmoji(url)); + } + vector<MessagePart*>& Message::getParts() { return messageParts; diff --git a/src/MessageBoard.cpp b/src/MessageBoard.cpp index 2b9115a..acb7be1 100644 --- a/src/MessageBoard.cpp +++ b/src/MessageBoard.cpp @@ -84,7 +84,7 @@ namespace dchat } } - void MessageBoard::draw(sf::RenderWindow &window) + void MessageBoard::draw(sf::RenderWindow &window, Cache &cache) { sf::RenderTarget *renderTarget = nullptr; if(useStaticContentTexture) @@ -99,56 +99,86 @@ namespace dchat dirty = true; } - if(dirty) + auto renderTargetSize = renderTarget->getSize(); + + if(useStaticContentTexture) { - dirty = false; - auto renderTargetSize = renderTarget->getSize(); - - if(useStaticContentTexture) + if(dirty) staticContentTexture.clear(BACKGROUND_COLOR); - else - { - sf::RectangleShape background(sf::Vector2f(renderTargetSize.x, renderTargetSize.y)); - background.setFillColor(BACKGROUND_COLOR); - renderTarget->draw(background); - } - - const sf::Font &usernameFont = ResourceCache::getFont("fonts/Roboto-Regular.ttf"); + } + else + { + sf::RectangleShape background(sf::Vector2f(renderTargetSize.x, renderTargetSize.y)); + background.setFillColor(BACKGROUND_COLOR); + renderTarget->draw(background); + } + + 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) + renderTarget->draw(usernameText); + position.y += usernameText.getCharacterSize() + USERNAME_PADDING_BOTTOM; - sf::Vector2f position; - for(Message *message : messages) + for(MessagePart *messagePart : message->getParts()) { - sf::Text usernameText(message->user->getName(), usernameFont, MessagePartText::getFontSizeScaled() * 1.3f); - usernameText.setFillColor(sf::Color(15, 192, 252)); - usernameText.setPosition(position); - renderTarget->draw(usernameText); - position.y += usernameText.getCharacterSize() + USERNAME_PADDING_BOTTOM; - - for(MessagePart *messagePart : message->getParts()) + switch(messagePart->type) { - switch(messagePart->type) + case MessagePart::Type::TEXT: { - 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)); + 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) renderTarget->draw(messagePartText->text); - position.x += messagePartText->text.getLocalBounds().width; - break; + position.x += messagePartText->text.getLocalBounds().width; + break; + } + case MessagePart::Type::EMOJI: + { + MessagePartEmoji *messagePartEmoji = static_cast<MessagePartEmoji*>(messagePart); + auto imageByUrlResult = cache.getImageByUrl(messagePartEmoji->url, 1024 * 512); + position.x += 5.0f; + 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(dirty) + renderTarget->draw(messagePartEmoji->sprite); + } + else + { + // TODO: Replace this with a loading gif + sf::RectangleShape emojiDownloadRect(sf::Vector2f(MessagePartEmoji::getHeightScaled(), MessagePartEmoji::getHeightScaled())); + emojiDownloadRect.setPosition(floor(position.x), floor(position.y + MessagePart::getSizeScaled() * 0.5f - MessagePartEmoji::getHeightScaled() * 0.5f)); + emojiDownloadRect.setFillColor(sf::Color::White); + if(dirty) + renderTarget->draw(emojiDownloadRect); } + position.x += MessagePartEmoji::getHeightScaled() + 5.0f; + break; } } - position.x = 0.0f; - position.y += MessagePart::getSizeScaled() + MESSAGE_PADDING_BOTTOM; } - - if(useStaticContentTexture) - staticContentTexture.display(); + position.x = 0.0f; + position.y += MessagePart::getSizeScaled() + MESSAGE_PADDING_BOTTOM; } if(useStaticContentTexture) + staticContentTexture.display(); + + dirty = false; + + if(useStaticContentTexture) window.draw(sf::Sprite(staticContentTexture.getTexture())); if(!selectingText) return; diff --git a/src/MessagePart.cpp b/src/MessagePart.cpp index ea8e4a5..dcef3f3 100644 --- a/src/MessagePart.cpp +++ b/src/MessagePart.cpp @@ -34,4 +34,28 @@ namespace dchat { return sf::Vector2f(text.getLocalBounds().width, getFontSizeScaled()); } + + MessagePartEmoji::MessagePartEmoji(const string &_url) : + MessagePart(Type::EMOJI), + url(_url) + { + + } + + 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/ResourceCache.cpp b/src/ResourceCache.cpp index 474360c..4bdd75d 100644 --- a/src/ResourceCache.cpp +++ b/src/ResourceCache.cpp @@ -1,12 +1,12 @@ #include "../include/ResourceCache.hpp" #include <unordered_map> -#include <stdexcept> using namespace std; namespace dchat { unordered_map<string, sf::Font*> fonts; + unordered_map<string, sf::Texture*> textures; const sf::Font& ResourceCache::getFont(const string &filepath) { @@ -20,10 +20,31 @@ namespace dchat delete font; string errMsg = "Failed to load font: "; errMsg += filepath; - throw runtime_error(errMsg); + throw FailedToLoadResourceException(errMsg); } fonts[filepath] = font; return *font; } + + const sf::Texture* ResourceCache::getTexture(const string &filepath) + { + auto it = textures.find(filepath); + if(it != textures.end()) + return it->second; + + sf::Texture *texture = new sf::Texture(); + if(!texture->loadFromFile(filepath)) + { + delete texture; + string errMsg = "Failed to load texture: "; + errMsg += filepath; + throw FailedToLoadResourceException(errMsg); + } + + texture->setSmooth(true); + texture->generateMipmap(); + textures[filepath] = texture; + return texture; + } } diff --git a/src/main.cpp b/src/main.cpp index 481f73a..284df56 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,12 +1,14 @@ -#include "../include/MessageBoard.hpp" -#include "../include/User.hpp" -#include "../include/Chatbar.hpp" +#include "../include/Channel.hpp" +#include "../include/ChannelSidePanel.hpp" +#include "../include/Cache.hpp" #include <string> #include <SFML/Graphics.hpp> #include <cstring> +#include "process.hpp" using namespace std; using namespace dchat; +using namespace TinyProcessLib; int main() { @@ -14,29 +16,11 @@ int main() window.setVerticalSyncEnabled(false); window.setFramerateLimit(60); - OfflineUser *localOfflineUser = new OfflineUser("You"); + Cache cache; - MessageBoard messageBoard(window.getSize()); - - { - Message *message = new Message(localOfflineUser); - message->addText(u8"hello, world!"); - messageBoard.addMessage(message); - } - - { - Message *message = new Message(localOfflineUser); - message->addText(u8"hello, world!"); - messageBoard.addMessage(message); - } - - { - Message *message = new Message(localOfflineUser); - message->addText(u8"hello, world!"); - messageBoard.addMessage(message); - } - - Chatbar chatbar; + Channel channel; + ChannelSidePanel channelSidePanel; + channelSidePanel.addChannel(&channel); while (window.isOpen()) { @@ -50,44 +34,11 @@ int main() sf::View view(sf::FloatRect(0.0f, 0.0f, event.size.width, event.size.height)); window.setView(view); } - else if(event.type == sf::Event::TextEntered) - { - if(event.text.unicode == 8) // backspace - chatbar.removePreviousChar(); - else if(event.text.unicode == 13) // enter - { - Message *message = new Message(localOfflineUser); - auto chatbarMsgUtf8 = chatbar.getString().toUtf8(); - string msg; - msg.resize(chatbarMsgUtf8.size()); - memcpy(&msg[0], chatbarMsgUtf8.data(), chatbarMsgUtf8.size()); - - message->addText(msg); - messageBoard.addMessage(message); - chatbar.clear(); - } - else if(event.text.unicode == 127) // delete - { - chatbar.removeNextChar(); - } - else - { - chatbar.addChar(event.text.unicode); - } - } - else if(event.type == sf::Event::KeyPressed) - { - if(event.key.code == sf::Keyboard::Left) - chatbar.moveCaretLeft(); - else if(event.key.code == sf::Keyboard::Right) - chatbar.moveCaretRight(); - } - messageBoard.processEvent(event); + channel.processEvent(event); } window.clear(); - messageBoard.draw(window); - chatbar.draw(window); + channel.draw(window, cache); window.display(); } |