summaryrefslogtreecommitdiff
path: root/src/io
diff options
context:
space:
mode:
Diffstat (limited to 'src/io')
-rw-r--r--src/io/IOManager.cpp34
-rw-r--r--src/io/IOManager.hpp33
-rw-r--r--src/io/inputManager.cpp70
-rw-r--r--src/io/inputManager.hpp25
-rw-r--r--src/io/renderManager.cpp38
-rw-r--r--src/io/renderManager.hpp23
-rw-r--r--src/io/scroller.cpp85
-rw-r--r--src/io/scroller.hpp15
8 files changed, 323 insertions, 0 deletions
diff --git a/src/io/IOManager.cpp b/src/io/IOManager.cpp
new file mode 100644
index 0000000..be29adf
--- /dev/null
+++ b/src/io/IOManager.cpp
@@ -0,0 +1,34 @@
+#include "IOManager.hpp"
+#include "inputManager.hpp"
+#include "../debug/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({.log = log});
+ if (g_pInputManager)
+ g_pInputManager->updateIO();
+}
+
+void CIOManager::addMessage(const CChatManager::SMessage &msg) {
+ m_vIO.push_back({.msg = msg});
+ if (g_pInputManager)
+ g_pInputManager->updateIO();
+}
+
+void CIOManager::addCustom(const std::pair<std::string, std::string> &custom) {
+ m_vIO.push_back({.custom = custom});
+ if (g_pInputManager)
+ g_pInputManager->updateIO();
+}
diff --git a/src/io/IOManager.hpp b/src/io/IOManager.hpp
new file mode 100644
index 0000000..d1d891d
--- /dev/null
+++ b/src/io/IOManager.hpp
@@ -0,0 +1,33 @@
+#pragma once
+
+#include "../core/chatManager.hpp"
+#include "../debug/format.hpp"
+#include <optional>
+
+class CIOManager {
+ public:
+ CIOManager();
+ ~CIOManager();
+
+ struct SLog {
+ eFormatType type;
+ std::string log;
+ };
+ struct SIO {
+ // once a time
+ std::optional<CChatManager::SMessage> msg = std::nullopt;
+ std::optional<SLog> log = std::nullopt;
+ std::optional<std::pair<std::string, std::string>> custom = std::nullopt;
+ };
+
+ const std::vector<SIO> &getIO() const;
+ void addLog(const SLog &log);
+ void addMessage(const CChatManager::SMessage &msg);
+ void addCustom(const std::pair<std::string, std::string> &custom);
+
+ private:
+ std::vector<SIO> m_vIO;
+
+ friend class CChatManager;
+};
+inline std::unique_ptr<CIOManager> g_pIOManager;
diff --git a/src/io/inputManager.cpp b/src/io/inputManager.cpp
new file mode 100644
index 0000000..09a7729
--- /dev/null
+++ b/src/io/inputManager.cpp
@@ -0,0 +1,70 @@
+#include "inputManager.hpp"
+#include "IOManager.hpp"
+#include "../core/chatManager.hpp"
+#include "../command/commandHandler.hpp"
+#include "../debug/log.hpp"
+#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, custom] : 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)));
+ else if (custom)
+ messages.push_back(ftxui::window(ftxui::text(custom->first), ftxui::paragraph(custom->second)));
+ }
+
+ return ftxui::vbox(messages);
+ })) {
+
+ log(LOG, "InputManager: initialized");
+}
+
+CInputManager::~CInputManager() {
+ log(SYS, "InputManager: bye");
+}
+
+void CInputManager::updateIO() {
+ logComponent->OnEvent(ftxui::Event::Custom);
+ g_pRenderManager->screen.PostEvent(ftxui::Event::Custom);
+}
+
+void CInputManager::inputLoop() {
+ container = logComponent |= ftxui::Scroller;
+ container = ftxui::ResizableSplitBottom(inputComponent, container, &bottomSize);
+
+ // 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/io/inputManager.hpp b/src/io/inputManager.hpp
new file mode 100644
index 0000000..a81e39d
--- /dev/null
+++ b/src/io/inputManager.hpp
@@ -0,0 +1,25 @@
+#pragma once
+
+#include <ftxui/component/component.hpp>
+#include <memory>
+
+class CInputManager {
+ public:
+ CInputManager();
+ ~CInputManager();
+
+ 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/io/renderManager.cpp b/src/io/renderManager.cpp
new file mode 100644
index 0000000..a8dff68
--- /dev/null
+++ b/src/io/renderManager.cpp
@@ -0,0 +1,38 @@
+#include "renderManager.hpp"
+#include "../debug/log.hpp"
+
+CRenderManager::CRenderManager()
+ : screen(ftxui::ScreenInteractive::TerminalOutput()) {
+ if (std::atexit([]() {
+ if (g_pRenderManager)
+ g_pRenderManager->exitLoop();
+ }))
+ log(ERR, "Failed to register RenderManager atexit handler, will fallback to quick_exit after hang");
+ log(LOG, "RenderManager: initialized");
+};
+
+CRenderManager::~CRenderManager() {
+ exitLoop();
+ log(SYS, "RenderManager: bye");
+}
+
+void CRenderManager::exitLoop() {
+ if (renderer) {
+ try {
+ screen.ExitLoopClosure()();
+ screen.Exit();
+ } catch (const std::exception &e) {
+ log(WARN, "Exception in exitLoop: {}", e.what());
+ } catch (...) {
+ log(WARN, "Unknown exception in exitLoop");
+ }
+ }
+}
+
+void CRenderManager::enterLoop() {
+ screen.Loop(renderer);
+}
+
+void CRenderManager::setRenderer(ftxui::Component component) {
+ renderer = std::move(component);
+}
diff --git a/src/io/renderManager.hpp b/src/io/renderManager.hpp
new file mode 100644
index 0000000..7a14cc0
--- /dev/null
+++ b/src/io/renderManager.hpp
@@ -0,0 +1,23 @@
+#pragma once
+
+#include <ftxui/component/screen_interactive.hpp>
+#include <memory>
+
+class CRenderManager {
+ public:
+ CRenderManager();
+ ~CRenderManager();
+
+ void enterLoop();
+
+ void setRenderer(ftxui::Component component);
+
+ private:
+ void exitLoop();
+
+ ftxui::ScreenInteractive screen;
+ ftxui::Component renderer;
+
+ friend class CInputManager;
+};
+inline std::unique_ptr<CRenderManager> g_pRenderManager;
diff --git a/src/io/scroller.cpp b/src/io/scroller.cpp
new file mode 100644
index 0000000..c595a39
--- /dev/null
+++ b/src/io/scroller.cpp
@@ -0,0 +1,85 @@
+#include "scroller.hpp"
+
+#include "../debug/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 { // NOLINT
+
+class CScrollerBase : public ComponentBase {
+ public:
+ CScrollerBase(Component child) { Add(child); }
+
+ private:
+ Element OnRender() final { // NOLINT
+ 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 { // NOLINT
+ 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_ - 15;
+ return selected_old != selected_;
+ }
+
+ bool Focusable() const final { return true; } // NOLINT
+
+ bool sticky_select = true;
+ int selected_ = 0;
+ int size_ = 0;
+ Box box_;
+};
+
+Component Scroller(Component child) { // NOLINT
+ return Make<CScrollerBase>(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/io/scroller.hpp b/src/io/scroller.hpp
new file mode 100644
index 0000000..b8b39b1
--- /dev/null
+++ b/src/io/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 { // NOLINT
+Component Scroller(Component child); // NOLINT
+}
+#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.