diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/IOManager.cpp | 22 | ||||
| -rw-r--r-- | src/IOManager.hpp | 30 | ||||
| -rw-r--r-- | src/TODO.md | 11 | ||||
| -rw-r--r-- | src/chatManager.cpp | 15 | ||||
| -rw-r--r-- | src/chatManager.hpp | 5 | ||||
| -rw-r--r-- | src/chatServer.cpp | 14 | ||||
| -rw-r--r-- | src/inputManager.cpp | 64 | ||||
| -rw-r--r-- | src/inputManager.hpp | 14 | ||||
| -rw-r--r-- | src/log.hpp | 13 | ||||
| -rw-r--r-- | src/renderManager.cpp | 19 | ||||
| -rw-r--r-- | src/renderManager.hpp | 21 | ||||
| -rw-r--r-- | src/scroller.cpp | 85 | ||||
| -rw-r--r-- | src/scroller.hpp | 15 |
13 files changed, 299 insertions, 29 deletions
diff --git a/src/IOManager.cpp b/src/IOManager.cpp new file mode 100644 index 0000000..c5363af --- /dev/null +++ b/src/IOManager.cpp @@ -0,0 +1,22 @@ +#include "IOManager.hpp" +#include "inputManager.hpp" +#include "log.hpp" + +CIOManager::CIOManager() { + log(LOG, "IOManager: initialized"); +} + +CIOManager::~CIOManager() { + m_vIO.clear(); + log(SYS, "IOManager: bye"); +} + +const std::vector<CIOManager::SIO> &CIOManager::getIO() const { + return m_vIO; +} + +void CIOManager::addLog(const SLog &log) { + m_vIO.push_back({.msg = std::nullopt, .log = log}); + if (g_pInputManager) + g_pInputManager->updateIO(); +} diff --git a/src/IOManager.hpp b/src/IOManager.hpp new file mode 100644 index 0000000..ba741ff --- /dev/null +++ b/src/IOManager.hpp @@ -0,0 +1,30 @@ +#pragma once + +#include "chatManager.hpp" +#include "format.hpp" +#include <optional> + +class CIOManager { + public: + CIOManager(); + ~CIOManager(); + + struct SLog { + eFormatType type; + std::string log; + }; + struct SIO { + // cannot have both at once + std::optional<CChatManager::SMessage> msg; + std::optional<SLog> log; + }; + + const std::vector<SIO> &getIO() const; + void addLog(const SLog &log); + + private: + std::vector<SIO> m_vIO; + + friend class CChatManager; +}; +inline std::unique_ptr<CIOManager> g_pIOManager; diff --git a/src/TODO.md b/src/TODO.md new file mode 100644 index 0000000..381095c --- /dev/null +++ b/src/TODO.md @@ -0,0 +1,11 @@ +## General +- [ ] ci unit testing + +## FTXUI + +- [ ] tabs for each user for direct message +- ~[ ] fix: bottom(input) only 20% of screen instead of hardcoded px~ + - [x] fix: bottom(input) only take 1line of screen +- [-] auto focus input // FIXME +- [ ] tabs for logs(log/chat) +- [-] sticky scroll(scroll to bottom) diff --git a/src/chatManager.cpp b/src/chatManager.cpp index 3b8c82d..96c1b0c 100644 --- a/src/chatManager.cpp +++ b/src/chatManager.cpp @@ -1,4 +1,6 @@ #include "chatManager.hpp" +#include "IOManager.hpp" +#include "inputManager.hpp" #include "log.hpp" #include "sessionManager.hpp" @@ -7,7 +9,6 @@ CChatManager::CChatManager() { } CChatManager::~CChatManager() { - m_vMessages.clear(); log(SYS, "ChatManager: bye"); } @@ -19,13 +20,19 @@ void CChatManager::newMessage(const SMessage &msg) { return; } g_pSessionManager->broadcastChat(msg); - m_vMessages.push_back(msg); + g_pIOManager->m_vIO.push_back({.msg = msg, .log = std::nullopt}); + g_pInputManager->updateIO(); } std::string CChatManager::fmtBroadcastMessage(const SMessage &msg) { return NFormatter::fmt(NONE, "{}: {}", msg.username, msg.msg); } -const std::vector<CChatManager::SMessage> &CChatManager::getChat() const { - return m_vMessages; +std::vector<CChatManager::SMessage> CChatManager::getChat() const { + std::vector<SMessage> chat; + + for (const auto &[msg, log] : g_pIOManager->getIO()) + if (msg) + chat.push_back(*msg); + return chat; } diff --git a/src/chatManager.hpp b/src/chatManager.hpp index 084c002..0f423f7 100644 --- a/src/chatManager.hpp +++ b/src/chatManager.hpp @@ -16,9 +16,6 @@ class CChatManager { void newMessage(const SMessage &msg); std::string fmtBroadcastMessage(const SMessage &msg); - const std::vector<SMessage> &getChat() const; - - private: - std::vector<SMessage> m_vMessages; + std::vector<SMessage> getChat() const; }; inline std::unique_ptr<CChatManager> g_pChatManager; diff --git a/src/chatServer.cpp b/src/chatServer.cpp index 56caf4b..7b64e79 100644 --- a/src/chatServer.cpp +++ b/src/chatServer.cpp @@ -1,9 +1,11 @@ #include "chatServer.hpp" +#include "IOManager.hpp" #include "chatManager.hpp" #include "commandHandler.hpp" #include "commands.hpp" #include "inputManager.hpp" #include "log.hpp" +#include "renderManager.hpp" #include "server.hpp" #include "sessionManager.hpp" @@ -28,18 +30,24 @@ void CChatServer::cleanup() { g_pServer.reset(); g_pCommandHandler.reset(); g_pInputManager.reset(); + g_pIOManager.reset(); + g_pRenderManager.reset(); } void CChatServer::initManagers() { log(LOG, "ChatServer: initializing managers"); + log(LOG, "Creating RenderManager"); + g_pRenderManager = std::make_unique<CRenderManager>(); + log(LOG, "Creating InputManager"); + g_pInputManager = std::make_unique<CInputManager>(); log(LOG, "Creating Server"); g_pServer = std::make_unique<CServer>(m_port); + log(LOG, "Creating IOManager"); + g_pIOManager = std::make_unique<CIOManager>(); log(LOG, "Creating SessionManager"); g_pSessionManager = std::make_shared<CSessionManager>(); log(LOG, "Creating ChatManager"); g_pChatManager = std::make_unique<CChatManager>(); - log(LOG, "Creating InputManager"); - g_pInputManager = std::make_unique<CInputManager>(); } void CChatServer::start() { @@ -53,5 +61,5 @@ void CChatServer::start() { m_sessionManagerThread = std::jthread(&CSessionManager::run, g_pSessionManager.get()); m_sessionManagerThread.detach(); - g_pInputManager->run(); + g_pInputManager->inputLoop(); } diff --git a/src/inputManager.cpp b/src/inputManager.cpp index 8de26bd..cc3880b 100644 --- a/src/inputManager.cpp +++ b/src/inputManager.cpp @@ -1,10 +1,43 @@ #include "inputManager.hpp" +#include "IOManager.hpp" #include "chatManager.hpp" #include "commandHandler.hpp" #include "log.hpp" -#include <iostream> +#include "renderManager.hpp" +#include "scroller.hpp" + +CInputManager::CInputManager() + : m_input(ftxui::Input(&m_szInput, "Type here...", {.transform = [](ftxui::InputState state) { + state.element |= ftxui::bgcolor(ftxui::Color::HSVA(0, 0, 0, 0)); + state.element |= ftxui::color(ftxui::Color::White); + return state.element; }, .multiline = false, .on_enter = [&] { + if (m_szInput.empty()) + return; + + g_pCommandHandler->isCommand(m_szInput) ? g_pCommandHandler->handleCommand(m_szInput) : g_pChatManager->newMessage({.msg = m_szInput, .username = "Server"}); + m_szInput.clear(); }}) // NOLINT + ) + , inputComponent(ftxui::Renderer(m_input, [&] { + return ftxui::hbox({ + ftxui::text("> "), + m_input->Render(), + }); + })) + , logComponent(ftxui::Renderer([] { + std::vector<ftxui::Element> messages; + for (const auto &[chat, log] : g_pIOManager->getIO()) { + if (chat && !chat->msg.empty()) { + std::string username = chat->username; + if (username.back() == '\n') + username.pop_back(); + messages.push_back(ftxui::window(ftxui::text(username), ftxui::paragraph(chat->msg))); + } else if (log && !log->log.empty()) + messages.push_back(ftxui::window(ftxui::text(NFormatter::fmt(log->type, "")), ftxui::paragraph(log->log))); + } + + return ftxui::vbox(messages); + })) { -CInputManager::CInputManager() { log(LOG, "InputManager: initialized"); } @@ -12,19 +45,24 @@ CInputManager::~CInputManager() { log(SYS, "InputManager: bye"); } -void CInputManager::run() { - inputLoop(); +void CInputManager::updateIO() { + logComponent->OnEvent(ftxui::Event::Custom); + g_pRenderManager->screen.PostEvent(ftxui::Event::Custom); } void CInputManager::inputLoop() { - while (true) { - std::string input; - std::print("> "); - std::getline(std::cin, input); - - if (input.empty()) - continue; + container = logComponent |= ftxui::Scroller; + container = ftxui::ResizableSplitBottom(inputComponent, container, &bottomSize); - g_pCommandHandler->isCommand(input) ? g_pCommandHandler->handleCommand(input) : g_pChatManager->newMessage({.msg = input, .username = "Server"}); - } + // FIXME: this shit spams TakeFocus(not ideal for power etc reasons) + // consider: custom loop, or just only allow focus to be inputComponent somehow + container |= ftxui::CatchEvent([&](ftxui::Event) { + inputComponent->TakeFocus(); + return false; + }); + g_pRenderManager->setRenderer(ftxui::Renderer(container, [this] { + return container->Render() | ftxui::border; + })); + inputComponent->TakeFocus(); + g_pRenderManager->enterLoop(); } diff --git a/src/inputManager.hpp b/src/inputManager.hpp index ef75b52..a81e39d 100644 --- a/src/inputManager.hpp +++ b/src/inputManager.hpp @@ -1,5 +1,6 @@ #pragma once +#include <ftxui/component/component.hpp> #include <memory> class CInputManager { @@ -7,7 +8,18 @@ class CInputManager { CInputManager(); ~CInputManager(); - void run(); void inputLoop(); + + void updateIO(); + + private: + std::string m_szInput; + + ftxui::Component m_input; + ftxui::Component inputComponent; + ftxui::Component logComponent; + ftxui::Component container; + + int bottomSize = 1; }; inline std::unique_ptr<CInputManager> g_pInputManager; diff --git a/src/log.hpp b/src/log.hpp index 996b6bc..a4654c4 100644 --- a/src/log.hpp +++ b/src/log.hpp @@ -1,16 +1,18 @@ #pragma once +#include "IOManager.hpp" #include "format.hpp" -#include <format> #include <print> #include <string> -#include <unistd.h> inline void log(FILE *_stream, eFormatType type, std::string str) { #ifndef DEBUG if (type == TRACE) return; #endif - std::println(_stream, "{}", NFormatter::fmt(type, str)); + if (g_pIOManager) + g_pIOManager->addLog({.type = type, .log = str}); + else + std::println(_stream, "{}", NFormatter::fmt(type, str)); } template <typename... Args> @@ -19,7 +21,10 @@ void log(FILE *_stream, eFormatType type, std::format_string<Args...> fmt, Args if (type == TRACE) return; #endif - std::println(_stream, "{}", NFormatter::fmt(type, fmt, std::forward<Args>(args)...)); + if (g_pIOManager) + g_pIOManager->addLog({.type = type, .log = NFormatter::fmt(NONEWLINE, fmt, std::forward<Args>(args)...)}); + else + std::println(_stream, "{}", NFormatter::fmt(type, fmt, std::forward<Args>(args)...)); } inline void log(eFormatType type, std::string str) { diff --git a/src/renderManager.cpp b/src/renderManager.cpp new file mode 100644 index 0000000..d3720c7 --- /dev/null +++ b/src/renderManager.cpp @@ -0,0 +1,19 @@ +#include "renderManager.hpp" +#include "log.hpp" + +CRenderManager::CRenderManager() + : screen(ftxui::ScreenInteractive::Fullscreen()) { + log(SYS, "RenderManager: initialized"); +}; + +void CRenderManager::enterLoop() { + screen.Loop(renderer); +} + +void CRenderManager::setRenderer(ftxui::Component component) { + renderer = std::move(component); +} + +CRenderManager::~CRenderManager() { + screen.Exit(); +} diff --git a/src/renderManager.hpp b/src/renderManager.hpp new file mode 100644 index 0000000..4407f66 --- /dev/null +++ b/src/renderManager.hpp @@ -0,0 +1,21 @@ +#pragma once + +#include <ftxui/component/screen_interactive.hpp> +#include <memory> + +class CRenderManager { + public: + CRenderManager(); + ~CRenderManager(); + + void enterLoop(); + + void setRenderer(ftxui::Component component); + + private: + ftxui::ScreenInteractive screen; + ftxui::Component renderer; + + friend class CInputManager; +}; +inline std::unique_ptr<CRenderManager> g_pRenderManager; diff --git a/src/scroller.cpp b/src/scroller.cpp new file mode 100644 index 0000000..bf80770 --- /dev/null +++ b/src/scroller.cpp @@ -0,0 +1,85 @@ +#include "scroller.hpp" + +#include "log.hpp" +#include <algorithm> // for max, min +#include <ftxui/component/component_base.hpp> // for Component, ComponentBase +#include <ftxui/component/event.hpp> // for Event, Event::ArrowDown, Event::ArrowUp, Event::End, Event::Home, Event::PageDown, Event::PageUp +#include <memory> // for shared_ptr, allocator, __shared_ptr_access +#include <utility> // for move + +#include "ftxui/component/component.hpp" // for Make +#include "ftxui/component/mouse.hpp" // for Mouse, Mouse::WheelDown, Mouse::WheelUp +#include "ftxui/dom/deprecated.hpp" // for text +#include "ftxui/dom/elements.hpp" // for operator|, Element, size, vbox, EQUAL, HEIGHT, dbox, reflect, focus, inverted, nothing, select, vscroll_indicator, yflex, yframe +#include "ftxui/dom/node.hpp" // for Node +#include "ftxui/dom/requirement.hpp" // for Requirement +#include "ftxui/screen/box.hpp" // for Box + +namespace ftxui { + +class ScrollerBase : public ComponentBase { + public: + ScrollerBase(Component child) { Add(child); } + + private: + Element OnRender() final { + auto focused = Focused() ? focus : ftxui::select; + auto style = Focused() ? inverted : nothing; + + Element background = ComponentBase::Render(); + background->ComputeRequirement(); + size_ = background->requirement().min_y; + return dbox({ + std::move(background), + vbox({ + text(L"") | size(HEIGHT, EQUAL, selected_), + text(L"") | style | focused, + }), + }) | + vscroll_indicator | yframe | yflex | reflect(box_); + } + + bool OnEvent(Event event) final { + if (event.is_mouse() && box_.Contain(event.mouse().x, event.mouse().y)) + TakeFocus(); + + int selected_old = selected_; + if (event == Event::ArrowUp || event == Event::Character('k') || + (event.is_mouse() && event.mouse().button == Mouse::WheelUp)) + selected_--; + if ((event == Event::ArrowDown || event == Event::Character('j') || + (event.is_mouse() && event.mouse().button == Mouse::WheelDown))) + selected_++; + if (event == Event::PageDown) + selected_ += box_.y_max - box_.y_min; + if (event == Event::PageUp) + selected_ -= box_.y_max - box_.y_min; + if (event == Event::Home) + selected_ = 0; + if (event == Event::End) + selected_ = size_; + + if (event == Event::Custom && sticky_select) + selected_ = size_; + + selected_ = std::max(0, std::min(size_, selected_)); + sticky_select = selected_ == size_; + return selected_old != selected_; + } + + bool Focusable() const final { return true; } + + bool sticky_select = true; + int selected_ = 0; + int size_ = 0; + Box box_; +}; + +Component Scroller(Component child) { + return Make<ScrollerBase>(std::move(child)); +} +} // namespace ftxui + +// Copyright 2021 Arthur Sonzogni. All rights reserved. +// Use of this source code is governed by the MIT license that can be found in +// the LICENSE file. diff --git a/src/scroller.hpp b/src/scroller.hpp new file mode 100644 index 0000000..81a2a6d --- /dev/null +++ b/src/scroller.hpp @@ -0,0 +1,15 @@ +#ifndef SCROLLER_H +#define SCROLLER_H + +#include <ftxui/component/component.hpp> + +#include "ftxui/component/component_base.hpp" // for Component + +namespace ftxui { +Component Scroller(Component child); +} +#endif /* end of include guard: SCROLLER_H */ + +// Copyright 2021 Arthur Sonzogni. All rights reserved. +// Use of this source code is governed by the MIT license that can be found in +// the LICENSE file. |
