From 6f7b994684032060f0a2d7af82f4f3572293b8d6 Mon Sep 17 00:00:00 2001 From: littleblack111 Date: Thu, 12 Jun 2025 21:41:41 +0800 Subject: internal!: organized internal structure, using cmake now --- src/io/IOManager.cpp | 34 +++++++++++++++++++ src/io/IOManager.hpp | 33 +++++++++++++++++++ src/io/inputManager.cpp | 70 +++++++++++++++++++++++++++++++++++++++ src/io/inputManager.hpp | 25 ++++++++++++++ src/io/renderManager.cpp | 38 ++++++++++++++++++++++ src/io/renderManager.hpp | 23 +++++++++++++ src/io/scroller.cpp | 85 ++++++++++++++++++++++++++++++++++++++++++++++++ src/io/scroller.hpp | 15 +++++++++ 8 files changed, 323 insertions(+) create mode 100644 src/io/IOManager.cpp create mode 100644 src/io/IOManager.hpp create mode 100644 src/io/inputManager.cpp create mode 100644 src/io/inputManager.hpp create mode 100644 src/io/renderManager.cpp create mode 100644 src/io/renderManager.hpp create mode 100644 src/io/scroller.cpp create mode 100644 src/io/scroller.hpp (limited to 'src/io') 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::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 &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 + +class CIOManager { + public: + CIOManager(); + ~CIOManager(); + + struct SLog { + eFormatType type; + std::string log; + }; + struct SIO { + // once a time + std::optional msg = std::nullopt; + std::optional log = std::nullopt; + std::optional> custom = std::nullopt; + }; + + const std::vector &getIO() const; + void addLog(const SLog &log); + void addMessage(const CChatManager::SMessage &msg); + void addCustom(const std::pair &custom); + + private: + std::vector m_vIO; + + friend class CChatManager; +}; +inline std::unique_ptr 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 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 +#include + +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 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 +#include + +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 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 // for max, min +#include // for Component, ComponentBase +#include // for Event, Event::ArrowDown, Event::ArrowUp, Event::End, Event::Home, Event::PageDown, Event::PageUp +#include // for shared_ptr, allocator, __shared_ptr_access +#include // 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(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 + +#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. -- cgit v1.2.3