joomer-ftxui-file-browser/joomer-ftxui-file-browser.cpp
2025-12-22 18:47:09 -05:00

206 lines
8.2 KiB
C++

#include <ftxui/component/component.hpp>
#include <ftxui/dom/elements.hpp>
#include <ftxui/component/screen_interactive.hpp>
#include <ftxui/screen/screen.hpp>
#include <iostream>
#include <filesystem>
std::string directory_path = ".";
// Note: You must pass the ScreenInteractive object to trigger a redraw.
void ReloadDirectory(ftxui::ScreenInteractive& screen, const std::filesystem::path& new_path, std::vector<std::string>& entries, std::vector<std::string>& clean_names) {
if (!std::filesystem::exists(new_path) || !std::filesystem::is_directory(new_path)) return;
entries.clear();
clean_names.clear();
//std::string directory_path = new_path.string(); // Replace with your directory path //windows
// Check if the path is a valid directory
if (!std::filesystem::exists(new_path) || !std::filesystem::is_directory(new_path)) {
// Handle error case (optional: display a warning in the TUI)
std::cerr << "Error: Not a directory or path does not exist." << std::endl;
std::cerr << "Path not a directory: " + new_path.string();
return;
}
// 2. Read the new directory entries
int i = 1;
clean_names.clear();
entries.clear();
entries.push_back("0. .. (go up one level)"); // Option to go up one level
clean_names.push_back("..");
for (const auto& entry : std::filesystem::directory_iterator(new_path)) {
if (std::filesystem::is_directory(entry.path()) || std::filesystem::is_regular_file(entry.path())) {
std::string name = entry.path().filename().string();
if (!name.empty() && name[0] == '.') // Works in C++11, C++14, and C++17
continue; // Skip hidden files and directories
if (std::filesystem::is_directory(entry.path())) {
name += "/"; // Append slash to directories for clarity
}
entries.push_back(std::to_string(i) + ". " + name);
clean_names.push_back(name);
i++;
}
}
}
int main(int argc, char* argv[]) {
// Define a variable to hold the final selection index
int final_selected_index = -1; // Use -1 to indicate no selection was made
std::vector<std::string> entries;
std::vector<std::string> clean_names; // New vector for logic
std::string current_path = std::filesystem::current_path().string();
std::filesystem::path start_path = std::filesystem::current_path();
if (argc > 1)
{
// Convert relative input (like "../../..") into a full absolute path
start_path = std::filesystem::absolute(argv[1]);
// Convert to string
start_path = start_path.lexically_normal();
std::string path_str = start_path.string();
// Manually remove trailing slash if it exists and isn't the root directory
if (path_str.size() > 1 && (path_str.back() == '/' || path_str.back() == '\\')) {
path_str.pop_back();
}
current_path = path_str;
}
directory_path = current_path; // Update global
// Check if the directory exists
if (!std::filesystem::exists(directory_path) || !std::filesystem::is_directory(directory_path)) {
std::cerr << "Error: Directory '" << directory_path << "' not found or is not a directory." << std::endl;
return 1;
}
auto screen = ftxui::ScreenInteractive::TerminalOutput();
ReloadDirectory(screen, start_path, entries, clean_names);
int selected = 0;
auto menu = ftxui::Menu({
.entries = &entries,
.selected = &selected,
});
std::string input_buffer = ""; // Stores digits as the user types them
// --- The key part: Applying CatchEvent() ---
auto menu_with_event_handler = menu | ftxui::CatchEvent([&](ftxui::Event event) {
// 1. Handle Digit Input (0-9)
if (event.is_character()) {
char c = event.character()[0];
if (std::isdigit(c)) {
input_buffer += c; // Add the digit to our "typing" buffer
return true;
}
}
// 2. Handle Backspace (to correct typing errors)
if (event == ftxui::Event::Backspace && !input_buffer.empty()) {
input_buffer.pop_back();
return true;
}
// Handle the 'Enter' key press
if (event == ftxui::Event::Return) {
if (!input_buffer.empty()) {
try
{
// Convert buffer to integer and check bounds
int requested_index = std::stoi(input_buffer);// -1;
if (requested_index >= 0 && requested_index < (int)clean_names.size()) {
selected = requested_index; // Move the highlight to the typed index
}
} catch (...) {}
input_buffer.clear(); // Clear buffer after use
}
std::string selected_name;
std::filesystem::path new_path;
if (clean_names.size() > 0)
selected_name = clean_names[selected];
// Construct the new path
if (selected_name == "..") {
new_path = std::filesystem::path(current_path).parent_path();
}
else {
new_path = std::filesystem::path(current_path) / selected_name;
}
new_path = std::filesystem::absolute(new_path).lexically_normal();
new_path.make_preferred();
current_path = new_path.string();
directory_path = current_path; // Update global variable
if (std::filesystem::is_regular_file(new_path))
screen.Exit(); // Exit the application
// Call the function to switch and reload
ReloadDirectory(screen, new_path, entries,clean_names);
selected = 0; // Reset selection to the first item
return true; // Event handled
}
// Handle the 'q' key press to quit anytime
if (event == ftxui::Event::Character('q')) {
screen.Exit();
return true; // Event handled
}
// If the event is not one we want to catch, return false
// so the Menu can handle it (like arrow keys)
return false;
});
std::cout << "Initial current_path: " << current_path << std::endl;
// Create a renderer that defines the layout of your application
auto main_renderer = ftxui::Renderer(menu_with_event_handler, [&] {
return ftxui::vbox({
// Header showing the current directory
ftxui::hbox({
ftxui::text(" Current Directory: ") | ftxui::bold,
ftxui::text(current_path) | ftxui::color(ftxui::Color::Cyan),
}),
ftxui::separator(),
// The menu itself, wrapped in a frame to allow scrolling if the list is long
menu_with_event_handler->Render() | ftxui::vscroll_indicator | ftxui::frame,
ftxui::filler(), // Pushes the footer to the bottom
ftxui::separator(),
ftxui::hbox({
ftxui::text("Enter folder #: "),
ftxui::text(input_buffer),
}),
// Footer with instructions
ftxui::hbox({
ftxui::text(" [Enter] Open [q] Quit "),
ftxui::filler(),
ftxui::text(" Items: " + std::to_string(clean_names.size())) | ftxui::dim,
}),
}) | ftxui::border; // Adds a border around the whole app
});
screen.Loop(main_renderer);
// This will print to your terminal AFTER you press 'q' to quit
std::cout << "Final Path on Exit: " << current_path << std::endl;
// ----------------------------------------------------
// --- OUTPUT AFTER THE LOOP HAS CLEANLY EXITED ---
// ----------------------------------------------------
std::cout<< "directory path: " << directory_path << std::endl;
if (final_selected_index >= 0 && final_selected_index < entries.size()) {
std::cout << "Selected Entry: " << entries[final_selected_index] << std::endl;
} else {
std::cout << "Selection cancelled or no valid entry selected." << std::endl;
}
}