#include #include #include #include #include #include #include "bella_sdk/bella_engine.h" // Core rendering engine #include "bella_sdk/bella_scene.h" // Scene management #include "dl_core/dl_logging.h" // Logging utilities #include // For std::this_thread (C++ standard library) #include // For timing operations (C++ standard library) std::string directory_path = "."; int selected_index = -1; // Global variable to hold the selected index dl::String currentRender; void CleanTrailingSlash(std::string& path) { // Only strip if it's longer than a root path (e.g., "/" or "C:\") #if defined(_WIN32) if (path.size() > 3 && (path.back() == '/' || path.back() == '\\')) path.pop_back(); #else if (path.size() > 1 && path.back() == '/') path.pop_back(); #endif } std::string FormatSize(uintmax_t size) { if (size == 0) return "0 B"; const char* units[] = { "B", "KB", "MB", "GB", "TB" }; int i = 0; double d_size = static_cast(size); while (d_size >= 1024 && i < 4) { d_size /= 1024; i++; } std::stringstream ss; ss << std::fixed << std::setprecision(1) << d_size << " " << units[i]; return ss.str(); } /* * MyEngineObserver Class * This class receives callbacks from the Bella rendering engine to track rendering progress. * It implements the EngineObserver interface and provides methods to: * - Handle render start/stop events * - Track rendering progress * - Handle error conditions * - Store and retrieve the current progress state */ struct MyEngineObserver : public dl::bella_sdk::EngineObserver { public: // Called when a rendering pass starts void onStarted(dl::String pass) override { std::cout << "Started pass " << pass.buf() << std::endl; dl::logInfo("Started pass %s", pass.buf()); } // Called to update the current status of rendering //void onStatus(String pass, String status) override //{ // logInfo("%s [%s]", status.buf(), pass.buf()); //} // Called to update rendering progress (percentage, time remaining, etc) void onProgress(dl::String pass, dl::bella_sdk::Progress progress) override { std::cout << progress.toString().buf() << std::endl; setString(new std::string(progress.toString().buf())); dl::logInfo("%s [%s]", progress.toString().buf(), pass.buf()); } //void onImage(String pass, Image image) override //{ // logInfo("We got an image %d x %d.", (int)image.width(), (int)image.height()); //} // Called when an error occurs during rendering void onError(dl::String pass, dl::String msg) override { dl::logError("%s [%s]", msg.buf(), pass.buf()); } // Called when a rendering pass completes void onStopped(dl::String pass) override { dl::logInfo("Stopped %s", pass.buf()); std::atomic_bool active_render = false; } // Returns the current progress as a string std::string getProgress() const { std::string* currentProgress = progressPtr.load(); if (currentProgress) { return *currentProgress; } else { return ""; } } // Cleanup resources in destructor ~MyEngineObserver() { setString(nullptr); } private: // Thread-safe pointer to current progress string std::atomic progressPtr{ nullptr }; // Helper function to safely update the progress string void setString(std::string* newStatus) { std::string* oldStatus = progressPtr.exchange(newStatus); delete oldStatus; // Clean up old string if it exists } }; // Note: You must pass the ScreenInteractive object to trigger a redraw. void ReloadDirectory(ftxui::ScreenInteractive& screen, const std::filesystem::path& new_path, std::vector& entries, std::vector& 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(".."); if (std::filesystem::is_directory(new_path)) { // Append slash to directories for clarity 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 } if (std::filesystem::is_regular_file(entry.path()) && i == selected_index) { // Optionally, append file size auto size = std::filesystem::file_size(entry.path()); name += " (" + FormatSize(size) + ")"; } entries.push_back(std::to_string(i) + ". " + name); clean_names.push_back(name); i++; } } } } // Replace this line in renderImage(): // engine.render(); void renderSynchronous(dl::bella_sdk::Engine& engine, MyEngineObserver& engineObserver, std::string selected_name) { dl::String belPath = dl::String(directory_path.c_str()) + "\\"; engine.loadScene(belPath + dl::String(selected_name.c_str())); if (!engine.scene().read(dl::String(selected_name.c_str()))) return; dl::bella_sdk::Node bPass = engine.scene().beautyPass(); // Create the mandatory output node type dl::bella_sdk::Node pathNode = engine.scene().createNode("outputImagePath"); pathNode["dir"] = dl::String(directory_path.c_str()); pathNode["name"] = "render_result"; pathNode["ext"] = ".png"; // Link the node to the beauty pass bPass["overridePath"] = pathNode; bPass["timeLimit"] = 30.0f; // This call will now "freeze" the UI until finished engine.start(); // Explicitly wait until the engine is done to ensure the file is written while (engine.rendering()) { std::this_thread::sleep_for(std::chrono::milliseconds(100)); } } 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 entries; std::vector 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; } ftxui::ScreenInteractive screen = ftxui::ScreenInteractive::TerminalOutput(); ReloadDirectory(screen, start_path, entries, clean_names); int selected = 0; auto menu = ftxui::Menu(&entries,&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(); CleanTrailingSlash(current_path); // std::string normalized = std::filesystem::path(current_path).generic_string(); directory_path = current_path; // Update global variable if (std::filesystem::is_regular_file(new_path)) { selected_index = selected; // Update global selected index final_selected_index = selected; // Store the final selection index if (selected_name.substr(selected_name.length() - 3) == "bsz") { dl::bella_sdk::Engine engine; // The renderer itself MyEngineObserver engineObserver; engine.subscribe(&engineObserver); renderSynchronous(engine, engineObserver, selected_name); } } else { current_path = new_path.string(); } // Call the function to switch and reload ReloadDirectory(screen, current_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; }); // Create a renderer that defines the layout of your application ftxui::Component 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; } }