aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authordec05eba <dec05eba@protonmail.com>2018-04-21 03:46:58 +0200
committerdec05eba <dec05eba@protonmail.com>2018-04-21 03:50:59 +0200
commit09a8ade6becca2a71f45ff0db5f4bf6d64afb212 (patch)
tree3cc733a5af1323c57f7dc4c18747ae0c7de78be6
parentde059e317e43fa1b94d77fd981be68b86bf6de6e (diff)
Add support for static image emoji
Emoji are downloaded asynchronously using remote program (curl). Need to add support for converting [inline](url) chat message emoji and gifs.
-rw-r--r--.gitignore1
-rw-r--r--.kdev4/dchat.kdev413
-rw-r--r--README.md3
m---------depends/odhtdb0
-rw-r--r--include/Cache.hpp54
-rw-r--r--include/Channel.hpp23
-rw-r--r--include/ChannelSidePanel.hpp16
-rw-r--r--include/Message.hpp1
-rw-r--r--include/MessageBoard.hpp3
-rw-r--r--include/MessagePart.hpp17
-rw-r--r--include/ResourceCache.hpp12
-rw-r--r--include/env.hpp63
-rw-r--r--project.conf1
-rw-r--r--src/Cache.cpp158
-rw-r--r--src/Channel.cpp79
-rw-r--r--src/ChannelSidePanel.cpp9
-rw-r--r--src/Message.cpp5
-rw-r--r--src/MessageBoard.cpp104
-rw-r--r--src/MessagePart.cpp24
-rw-r--r--src/ResourceCache.cpp25
-rw-r--r--src/main.cpp71
21 files changed, 580 insertions, 102 deletions
diff --git a/.gitignore b/.gitignore
index 97420ef..9288f91 100644
--- a/.gitignore
+++ b/.gitignore
@@ -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
diff --git a/README.md b/README.md
index eaf2658..c33de04 100644
--- a/README.md
+++ b/README.md
@@ -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();
}