mirror of
https://github.com/yoboujon/ftxui_template.git
synced 2025-06-08 13:00:50 +02:00
Initial commit.
This commit is contained in:
commit
9c0343d6e7
15 changed files with 620 additions and 0 deletions
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
**/build/**
|
43
CMakeLists.txt
Normal file
43
CMakeLists.txt
Normal 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
17
dependencies.cmake
Normal 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
53
include/event.h
Normal 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
46
include/logger.h
Normal 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
24
include/pages/page.h
Normal 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
0
include/pages/page1.h
Normal file
0
include/pages/page2.h
Normal file
0
include/pages/page2.h
Normal file
39
include/ui.h
Normal file
39
include/ui.h
Normal 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
63
src/event.cpp
Normal 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
41
src/logger.cpp
Normal 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
43
src/main.cpp
Normal 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
130
src/pages/page1.cpp
Normal 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
57
src/pages/page2.cpp
Normal 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
63
src/ui.cpp
Normal 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);
|
||||
}
|
Loading…
Add table
Reference in a new issue