summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorlittleblack111 <littleblack11111@gmail.com>2025-04-02 18:49:15 +0800
committerlittleblack111 <littleblack11111@gmail.com>2025-04-07 16:32:07 +0800
commiteff21ee2ce1fdb4328d71d801352ec25f4e457ff (patch)
treef2b3febdbd84ca7e7b799f04a407bf408719c93e /src
parent50de5f9b80f1859b6eaa3b68c10a3f1cfb994737 (diff)
feat: ftxui (#17)
* ready for merge rm sum random ocmments first and https://github.com/ArthurSonzogni/FTXUI/issues/1024 but still, ready for merge since we can't do anything on our end * style: clang-tidy * ci * ci * style: clang-tidy * fix * fix: scroll https://github.com/ArthurSonzogni/git-tui/blob/master/src/scroller.cpp * focus * update todo * style: clang-format * update todo * sticky scroll * update todxo * smol fix need event to update screen. so we just give event when IO updates * style: clang-format --------- Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Diffstat (limited to 'src')
-rw-r--r--src/IOManager.cpp22
-rw-r--r--src/IOManager.hpp30
-rw-r--r--src/TODO.md11
-rw-r--r--src/chatManager.cpp15
-rw-r--r--src/chatManager.hpp5
-rw-r--r--src/chatServer.cpp14
-rw-r--r--src/inputManager.cpp64
-rw-r--r--src/inputManager.hpp14
-rw-r--r--src/log.hpp13
-rw-r--r--src/renderManager.cpp19
-rw-r--r--src/renderManager.hpp21
-rw-r--r--src/scroller.cpp85
-rw-r--r--src/scroller.hpp15
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.