#include "../include/Chatbar.hpp" #include "../include/MessageBoard.hpp" #include "../include/ResourceCache.hpp" #include "../include/Settings.hpp" #include "../include/RoomSidePanel.hpp" #include "../include/UsersSidePanel.hpp" #include "../include/Command.hpp" #include "../include/ColorScheme.hpp" #include #include #include #include #include using namespace std; namespace dchat { const float FONT_SIZE = 18.0f; const float BOX_PADDING_X = 15.0f; const float BOX_PADDING_Y = 5.0f; const int BLINK_TIME_VISIBLE_MS = 500; const int BLINK_TIME_INVISIBLE_MS = 500; const float PADDING_SIDE = 20.0f; const float PADDING_TOP = 30.0f; const float PADDING_BOTTOM = 30.0f; const float LINE_PADDING_SIDE = 20.0f; const float LINE_HEIGHT = 1.0f; static unordered_map binds; Chatbar::Chatbar() : text("", ResourceCache::getFont("fonts/Nunito-Regular.ttf"), FONT_SIZE * Settings::getScaling(), 0), focused(true) { text.setEditable(true); text.setFillColor(sf::Color(240, 240, 240)); background.setFillColor(ColorScheme::getBackgroundColor()); inputBackground.setFillColor(ColorScheme::getBackgroundColor() + sf::Color(10, 10, 10)); } bool Chatbar::isFocused() const { return focused; } class ArgParseException : public std::runtime_error { public: ArgParseException(const string &errMsg) : std::runtime_error(errMsg) {} }; static string stringRemoveQuotes(const StringView &str) { string result; result.reserve(str.size); for(usize i = 0; i < str.size; ++i) { char c = str[i]; if(c != '\'' && c != '"') result += c; } return result; } static vector splitCommandArgs(const StringView &str) { vector result; ssize offset = 0; char quoteChar = '\0'; for(ssize i = 0; i < str.size; ++i) { char c = str[i]; if(c == '\'' || c == '"') { if(c == quoteChar) quoteChar = '\0'; else quoteChar = c; } else if(quoteChar == '\0' && c == ' ') { ssize substrLength = i - offset; if(substrLength > 0) { StringView substrView(str.data + offset, substrLength); string substr = stringRemoveQuotes(substrView); if(!substr.empty()) result.emplace_back(move(substr)); } offset = i + 1; } } if(quoteChar != '\0') { string errMsg = "Reached end of command before end of quote ("; errMsg += quoteChar; errMsg += ")"; throw ArgParseException(errMsg); } ssize strLeft = str.size - offset; if(strLeft > 0) { StringView substrView(str.data + offset, strLeft); string substr = stringRemoveQuotes(substrView); if(!substr.empty()) result.emplace_back(move(substr)); } return result; } void Chatbar::processChatCommand(const StringView &cmd) { vector args; try { args = splitCommandArgs(cmd); } catch(ArgParseException &e) { fprintf(stderr, "Failed to parse command arguments, reason: %s\n", e.what()); return; } if(!args.empty()) { string command = args.front(); args.erase(args.begin()); if(!Command::call(command, args)) fprintf(stderr, "No such command: %s\n", command.c_str()); } } static void findReplaceAll(string &str, const string &substrToReplace, const string &stringToReplaceWith) { size_t findOffset = 0; while(findOffset < (size_t)str.size()) { findOffset = str.find(substrToReplace, findOffset); if(findOffset != string::npos) { string substr = str.replace(findOffset, substrToReplace.size(), stringToReplaceWith); findOffset += stringToReplaceWith.size(); } } } static void replaceBinds(string &msg) { for(auto &it : binds) { findReplaceAll(msg, it.first, it.second); } } void Chatbar::processEvent(const sf::Event &event, Cache *cache, std::shared_ptr room, MessageBoard *messageBoard) { if(!focused) return; text.processEvent(event, cache); if(event.type == sf::Event::TextEntered) { if(event.text.unicode == 13) // enter { if(!text.getString().isEmpty()) { if(sf::Keyboard::isKeyPressed(sf::Keyboard::Key::LShift) || sf::Keyboard::isKeyPressed(sf::Keyboard::Key::RShift)) { //addChar('\n'); } else { auto chatbarMsgUtf8 = text.getString().toUtf8(); string msg; msg.resize(chatbarMsgUtf8.size()); memcpy(&msg[0], chatbarMsgUtf8.data(), chatbarMsgUtf8.size()); if(msg[0] == '/') processChatCommand(StringView(msg.data() + 1, msg.size() - 1)); else { replaceBinds(msg); if(room) room->publishMessage(msg); else messageBoard->addOfflineUserMessage(std::move(msg), false); } text.setString(""); } } } } } void Chatbar::draw(sf::RenderWindow &window, Cache *cache) { auto windowSize = window.getSize(); text.setCharacterSize(FONT_SIZE * Settings::getScaling()); const float fontHeight = text.getFont()->getLineSpacing(text.getCharacterSize()); sf::RectangleShape lineShape(sf::Vector2f(floor(windowSize.x - RoomSidePanel::getWidth() - LINE_PADDING_SIDE * Settings::getScaling() * 2.0f), LINE_HEIGHT)); lineShape.setFillColor(ColorScheme::getBackgroundColor() + sf::Color(10, 10, 10)); lineShape.setPosition(RoomSidePanel::getWidth() + LINE_PADDING_SIDE * Settings::getScaling(), floor(windowSize.y - getHeight())); window.draw(lineShape); sf::Vector2f inputBackgroundSize(floor(windowSize.x - RoomSidePanel::getWidth() - PADDING_SIDE * Settings::getScaling() * 2.0f), floor(fontHeight * 1.7f + BOX_PADDING_Y * Settings::getScaling() * 2.0f)); sf::Vector2f backgroundSize(floor(windowSize.x - RoomSidePanel::getWidth()), floor(getHeight() - LINE_HEIGHT)); background.setSize(backgroundSize); background.setPosition(RoomSidePanel::getWidth(), floor(windowSize.y - backgroundSize.y)); window.draw(background); sf::Vector2f inputBackgroundPos(floor(RoomSidePanel::getWidth() + PADDING_SIDE * Settings::getScaling()), floor(windowSize.y - inputBackgroundSize.y - PADDING_BOTTOM * Settings::getScaling())); inputBackground.setSize(inputBackgroundSize); inputBackground.setPosition(inputBackgroundPos); text.setPosition(floor(inputBackgroundPos.x + BOX_PADDING_X), floor(inputBackgroundPos.y + inputBackgroundSize.y * 0.5f - fontHeight * 0.5f)); text.setMaxWidth(inputBackgroundSize.x - BOX_PADDING_X * 2.0f); window.draw(inputBackground); text.draw(window, cache); } sf::Vector2f Chatbar::getInputPosition(sf::RenderWindow &window) { auto windowSize = window.getSize(); return { floor(RoomSidePanel::getWidth() + PADDING_SIDE * Settings::getScaling()), floor(windowSize.y - getInputSize(window).y - PADDING_BOTTOM * Settings::getScaling()) }; } sf::Vector2f Chatbar::getInputSize(sf::RenderWindow &window) { auto windowSize = window.getSize(); const float fontSize = FONT_SIZE * Settings::getScaling(); const float fontHeight = ResourceCache::getFont("fonts/Nunito-Regular.ttf")->getLineSpacing(fontSize); return { floor(windowSize.x - RoomSidePanel::getWidth() - PADDING_SIDE * Settings::getScaling() * 2.0f), floor(fontHeight * 1.7f + BOX_PADDING_Y * Settings::getScaling() * 2.0f) }; } float Chatbar::getHeight() { const float fontSize = FONT_SIZE * Settings::getScaling(); const float fontHeight = ResourceCache::getFont("fonts/Nunito-Regular.ttf")->getLineSpacing(fontSize); return PADDING_TOP * Settings::getScaling() + floor(fontHeight * 1.7f + BOX_PADDING_Y * Settings::getScaling() * 2.0f) + PADDING_BOTTOM * Settings::getScaling(); } bool Chatbar::addBind(const std::string &key, const std::string &value, bool updateFile) { if(binds.find(key) != binds.end()) return false; binds[key] = value; if(updateFile) replaceBindsInFile(binds); return true; } bool Chatbar::removeBind(const std::string &key, bool updateFile) { auto it = binds.find(key); if(it == binds.end()) return false; binds.erase(it); if(updateFile) replaceBindsInFile(binds); return true; } void Chatbar::loadBindsFromFile() { dchat::loadBindsFromFile([](const std::string &key, const std::string &value) { binds[key] = value; }); } const unordered_map& Chatbar::getBinds() { return binds; } }