Initial commit.

This commit is contained in:
Yohan Boujon 2025-05-06 23:01:28 +02:00
commit 9c0343d6e7
15 changed files with 620 additions and 0 deletions

1
.gitignore vendored Normal file
View file

@ -0,0 +1 @@
**/build/**

43
CMakeLists.txt Normal file
View file

@ -0,0 +1,43 @@
cmake_minimum_required(VERSION 3.18)
project(
ftxui_template
DESCRIPTION "FTXUI Template Project"
HOMEPAGE_URL "https://www.etheryo.fr/"
LANGUAGES CXX C
)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_C_STANDARD 99)
set(CMAKE_C_STANDARD_REQUIRED ON)
# Dependencies
include(dependencies.cmake)
set(TARGET rmlui_test)
set(SRC "${CMAKE_CURRENT_SOURCE_DIR}/src")
add_executable(${TARGET}
${SRC}/ui.cpp
${SRC}/logger.cpp
${SRC}/event.cpp
${SRC}/main.cpp
)
# Include folders
target_include_directories(${TARGET} PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/include")
target_include_directories(${TARGET} PRIVATE "${FTXUI_INCLUDE_DIRS}")
# Libraries
target_link_libraries(
${TARGET} PRIVATE
component
dom
screen
)
# Output folder
set_target_properties(${TARGET}
PROPERTIES
ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}"
LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}"
RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}"
)

17
dependencies.cmake Normal file
View file

@ -0,0 +1,17 @@
cmake_minimum_required(VERSION 3.22) #FTXUI asks for CMake 3.22 and C++ 17
include(FetchContent)
FetchContent_Declare(ftxui
GIT_REPOSITORY https://github.com/ArthurSonzogni/ftxui
GIT_TAG v6.0.2
)
FetchContent_GetProperties(ftxui)
if(NOT ftxui_POPULATED)
FetchContent_Populate(ftxui)
add_subdirectory(${ftxui_SOURCE_DIR} ${ftxui_BINARY_DIR} EXCLUDE_FROM_ALL)
endif()
set(FTXUI_INCLUDE_DIRS
"${ftxui_SOURCE_DIR}/include"
)

53
include/event.h Normal file
View file

@ -0,0 +1,53 @@
#ifndef HEADER_EVENT_FTXUI
#define HEADER_EVENT_FTXUI
#include <deque>
#include <any>
#include <functional>
#include <mutex>
enum class EventType : uint8_t
{
SWITCH_SCREEN,
SEND_COMMAND,
LAUNCH_INSTANCE,
STOP,
};
struct EventPayload
{
EventType type;
std::any data;
};
class EventHandler
{
public:
EventHandler();
void set_handler(std::function<void(EventHandler &, const EventPayload &)> function);
void loop();
void stop();
protected:
void add_event(const EventPayload &payload);
friend class Event;
private:
bool _is_running;
std::function<void(EventHandler &, const EventPayload &)> _handler;
std::deque<EventPayload> _event_buffer;
std::mutex _mutex;
};
class Event
{
public:
Event(EventHandler &eventhandler);
void send_event(const EventPayload &payload);
void send_event(const EventType &type, std::any data = {});
private:
EventHandler &_event_handler;
};
#endif // HEADER_EVENT_FTXUI

46
include/logger.h Normal file
View file

@ -0,0 +1,46 @@
#ifndef HEADER_LOGGER_FTXUI
#define HEADER_LOGGER_FTXUI
#include <string>
#include <vector>
#include <ostream>
enum class LoggerType : uint8_t
{
_UNDEFINED,
PRINT,
COMMAND,
// You can add more here
_COUNT
};
struct LoggerData
{
std::string str;
uint64_t epoch;
LoggerType type;
};
class Logger
{
public:
static Logger &GetInstance()
{
static Logger instance;
return instance;
}
void set_max_size(int size);
void push_back(std::string str, LoggerType type = LoggerType::_UNDEFINED);
const std::vector<LoggerData> &get_buffer(void) noexcept;
private:
Logger();
~Logger();
void update_view();
size_t _maxSize;
std::vector<LoggerData> _buffer;
std::vector<LoggerData> _view;
};
#endif // HEADER_LOGGER_FTXUI

24
include/pages/page.h Normal file
View file

@ -0,0 +1,24 @@
#ifndef HEADER_PAGE_FTXUI
#define HEADER_PAGE_FTXUI
#include <ftxui/component/component_base.hpp>
#include "event.h"
class ScreenPage : public Event
{
public:
ScreenPage(EventHandler &handler)
: Event(handler), _page()
{
}
ftxui::Component *getPage()
{
return &_page;
}
protected:
ftxui::Component _page;
};
#endif // HEADER_PAGE_FTXUI

0
include/pages/page1.h Normal file
View file

0
include/pages/page2.h Normal file
View file

39
include/ui.h Normal file
View file

@ -0,0 +1,39 @@
#ifndef HEADER_UI_FTXUI
#define HEADER_UI_FTXUI
#include "pages/page.h"
#include <vector>
#include <ftxui/component/component_base.hpp>
#include <ftxui/component/screen_interactive.hpp>
#include <ftxui/component/component.hpp>
class UserInterface
{
public:
static UserInterface &GetInstance()
{
static UserInterface instance;
return instance;
}
ftxui::ScreenInteractive *get_screen();
void add_screen(ScreenPage *p);
void select_screen(size_t index);
void select_screen(const ScreenPage *p);
void start();
void update();
private:
UserInterface();
~UserInterface();
ftxui::ScreenInteractive _screen;
std::vector<ScreenPage *> _pages;
ftxui::Component _page_container;
ftxui::Component _main_container;
ftxui::Component _main_renderer;
int _page_index;
};
#endif // HEADER_RENDER_FTXUI

63
src/event.cpp Normal file
View file

@ -0,0 +1,63 @@
#include "event.h"
#include <thread>
#include <chrono>
#include <iostream>
constexpr int64_t EVENT_LOOP_WAIT_MS = 100;
// Event
Event::Event(EventHandler &eventhandler)
: _event_handler(eventhandler)
{
}
void Event::send_event(const EventPayload &payload)
{
_event_handler.add_event(payload);
}
void Event::send_event(const EventType &type, std::any data)
{
_event_handler.add_event({type, data});
}
// EventHandler
EventHandler::EventHandler()
: _is_running(false), _event_buffer({})
{
}
void EventHandler::set_handler(std::function<void(EventHandler &, const EventPayload &)> function)
{
_handler = function;
}
void EventHandler::add_event(const EventPayload &payload)
{
std::lock_guard<std::mutex> lock(_mutex);
_event_buffer.push_back(payload);
}
void EventHandler::loop()
{
_is_running = true;
while (_is_running)
{
if (!_event_buffer.empty())
{
std::lock_guard<std::mutex> lock(_mutex);
EventPayload evt = _event_buffer.front();
_event_buffer.pop_front();
_handler(*this, evt);
}
std::this_thread::sleep_for(std::chrono::milliseconds(EVENT_LOOP_WAIT_MS));
}
}
void EventHandler::stop()
{
_is_running = false;
}

41
src/logger.cpp Normal file
View file

@ -0,0 +1,41 @@
#include "logger.h"
#include "ui.h"
#include <algorithm>
#include <chrono>
constexpr int MAX_SIZE = 99;
Logger::Logger()
: _maxSize(MAX_SIZE), _buffer({})
{
}
void Logger::set_max_size(int size)
{
if (size == _maxSize)
return;
_maxSize = size;
update_view();
}
void Logger::push_back(std::string str, LoggerType type)
{
const uint64_t now = static_cast<uint64_t>(std::chrono::system_clock::now().time_since_epoch() / std::chrono::milliseconds(1));
_buffer.push_back({str, now, type});
update_view();
UserInterface::GetInstance().update();
}
void Logger::update_view()
{
typename std::vector<LoggerData>::iterator itBegin = _buffer.begin();
if (_buffer.size() >= _maxSize)
itBegin = _buffer.end() - _maxSize;
_view.clear();
std::copy(itBegin, _buffer.end(), std::back_inserter(_view));
}
// Getters
const std::vector<LoggerData> &Logger::get_buffer(void) noexcept { return _view; }

43
src/main.cpp Normal file
View file

@ -0,0 +1,43 @@
#include <iostream>
#include <string>
#include <thread>
#include "ui.h"
#include "event.h"
using namespace ftxui;
std::thread t_instance;
static inline void handle(EventHandler &handler, const EventPayload &payload)
{
UserInterface &ui = UserInterface::GetInstance();
if (payload.type == EventType::STOP)
handler.stop();
else if (payload.type == EventType::SWITCH_SCREEN)
ui.select_screen(std::any_cast<size_t>(payload.data));
}
int main(int argc, char **argv)
{
// Event/Screen
EventHandler handler;
UserInterface &ui = UserInterface::GetInstance();
// Page declaration
// ScreenPageDebugger debugger(handler, screen);
// screen.add_screen(&launcher);
// Event handler function
handler.set_handler(handle);
// Main logic
std::thread t_event(&EventHandler::loop, &handler);
ui.start();
// Finish
handler.stop();
t_event.join();
return 0;
}

130
src/pages/page1.cpp Normal file
View file

@ -0,0 +1,130 @@
#include "pages/debugger.h"
#include "logger.h"
#include "toolBox.h"
#include "ftxui/component/component.hpp"
#include "ftxui/component/component_base.hpp"
#include "ftxui/component/event.hpp"
#include "ftxui/component/screen_interactive.hpp"
#include "ftxui/dom/elements.hpp"
using namespace ftxui;
constexpr uint8_t LOGGER_OFFSET = 7;
inline ftxui::Element ScreenPageDebugger::transform(ftxui::InputState state)
{
if (state.is_placeholder)
state.element |= dim;
if (state.focused)
{
state.element |= color(Color::White);
input_selected = true;
}
else
{
input_selected = false;
}
return state.element;
}
inline bool ScreenPageDebugger::catch_event(Event event)
{
if (event.is_character())
{
// Checking if it is a command...
}
const int size = _render.get_screen()->dimy();
log_buffer.set_max_size(size - LOGGER_OFFSET);
if (event == Event::Return)
{
send_event(EventType::SEND_COMMAND, cmd_str);
}
return (event == Event::Return);
}
ftxui::Element ScreenPageDebugger::render_input()
{
Element arrow = text("> ");
if (input_selected)
arrow |= bgcolor(Color::White) | color(Color::Black);
else
arrow |= color(Color::Default) | bgcolor(Color::Default);
return vbox({separatorEmpty(),
text("Send an event"),
hbox({arrow,
input_event->Render()})});
}
ftxui::Element ScreenPageDebugger::render_log()
{
Elements log_lines;
for (const auto &logger : log_buffer.get_buffer())
{
if(logger.type == LoggerType::STUB) {
log_lines.push_back(hbox({
text(get_time_str(logger.epoch)) | dim,
text(logger.str)
}));
} else {
log_lines.push_back(hbox({
text(logger.str) | italic | dim
}));
}
}
auto log_content = vbox(std::move(log_lines)) | yframe;
return window(
text("log") | hcenter | bold,
log_content | vscroll_indicator | frame) |
flex;
}
ftxui::Element ScreenPageDebugger::render_status()
{
return window(text("status") | hcenter | bold, text("content") | center | dim, BorderStyle::EMPTY) | flex | size(WIDTH, GREATER_THAN, 30);
}
ScreenPageDebugger::ScreenPageDebugger(EventHandler &handler, ScreenRender &sr)
: ScreenPage(handler), _render(sr), input_selected(false)
{
input_option.transform = [&](const InputState state)
{
return this->transform(state);
};
input_event = Input(&cmd_str, "Press 'enter' to send the event. Type '/help' for commands.", input_option);
Component input = Renderer(input_event, [&]()
{ return render_input(); });
input |= CatchEvent(
[&](const Event &event)
{
return this->catch_event(event);
});
Component log = Renderer([&]()
{ return render_log(); });
Component status = Renderer([&]()
{ return render_status(); });
_page = Container::Vertical({
Container::Horizontal({log,
status}) |
yflex,
input,
});
}
Logger *ScreenPageDebugger::get_logger()
{
return &log_buffer;
}

57
src/pages/page2.cpp Normal file
View file

@ -0,0 +1,57 @@
#include "pages/launcher.h"
#include "loader.h"
#include "ftxui/component/component.hpp"
#include "ftxui/component/component_base.hpp"
#include "ftxui/component/event.hpp"
#include "ftxui/component/screen_interactive.hpp"
#include "ftxui/dom/elements.hpp"
using namespace ftxui;
static const std::vector<std::string> _launcher_entries = {
"Legacy",
"Debug"};
static const std::vector<std::string> _library_entries = {
"Reply",
"UDP_IP",
"Emit",
"BOUCHON",
"Push"};
inline void ScreenPageLauncher::on_launch()
{
this->send_event(EventType::SWITCH_SCREEN, static_cast<size_t>(1));
// const std::string name = get_simple_name(_library_entries[_library_selected]);
this->send_event(EventType::LAUNCH_INSTANCE, _library_entries[_library_selected]);
}
ScreenPageLauncher::ScreenPageLauncher(EventHandler &handler)
: ScreenPage(handler)
{
// Will be used later: for now crashes the launcher because it instanciate some values...
// _library_entries = get_libraries();
_library_selected = 0;
_library = Radiobox(&_library_entries, &_library_selected);
_launcher_selected = 0;
_launcher = Radiobox(&_launcher_entries, &_launcher_selected);
Component library_selection = Renderer(_library, [&]
{ return vbox({window(text("Select Library"),
_library->Render() | vscroll_indicator | frame) |
center | flex}) |
xflex_grow; });
Component launcher_selection = Renderer(_launcher, [&]
{ return vbox({window(text("Select launcher"),
_launcher->Render() | vscroll_indicator | frame) |
center | flex}); });
_launch_button = Container::Vertical({Button("Launch", [&]
{ return on_launch(); })});
Component launch = Renderer(_launch_button, [&]
{ return vbox({_launch_button->Render()}) | center | xflex; });
_page = Container::Vertical({Container::Horizontal({library_selection, launcher_selection, launch}) | flex});
}

63
src/ui.cpp Normal file
View file

@ -0,0 +1,63 @@
#include "ui.h"
#include <algorithm>
#include "ftxui/component/component.hpp"
using namespace ftxui;
UserInterface::UserInterface()
: _screen(ScreenInteractive::Fullscreen()), _pages({}), _page_container(), _main_container(), _main_renderer(), _page_index(0)
{
}
UserInterface::~UserInterface()
{
_screen.Exit();
}
ftxui::ScreenInteractive *UserInterface::get_screen()
{
return &_screen;
}
void UserInterface::add_screen(ScreenPage *p)
{
_pages.push_back(p);
}
void UserInterface::select_screen(size_t index)
{
_page_index = index;
}
void UserInterface::select_screen(const ScreenPage *p)
{
const typename std::vector<ScreenPage *>::iterator it = std::find(_pages.begin(), _pages.end(), p);
const size_t index = it - _pages.begin();
select_screen(index);
}
void UserInterface::start()
{
Components pages_component;
for (const auto &p : _pages)
pages_component.push_back(*(p->getPage()));
_page_container = Container::Tab(pages_component, &_page_index);
_main_container = Container::Vertical({
_page_container | flex,
});
_main_renderer = Renderer(_main_container, [&]
{ return vbox({
text("FTXUI Template") | inverted | hcenter,
separatorEmpty(),
_main_container->Render() | yflex_grow,
}); });
_screen.Loop(_main_renderer);
}
void UserInterface::update()
{
_screen.PostEvent(ftxui::Event::Custom);
}