Files
yaze/src/cli/tui.cc
scawful e7d4f5ea02 Enhance CLI command structure and add new functionalities
- Refactored existing CLI commands to follow a more structured naming convention, improving clarity and usability.
- Introduced new commands for dungeon and graphics handling, including `dungeon export`, `gfx export-sheet`, and `gfx import-sheet`, with placeholder implementations.
- Added palette export and import commands, enhancing the CLI's capabilities for managing graphical assets.
- Updated usage examples and help text to reflect the new command structure and improve user guidance.
- Incremented version number in TUI components to reflect the latest changes.
2025-09-30 21:21:19 -04:00

985 lines
30 KiB
C++

#include "tui.h"
#include <ftxui/component/component.hpp>
#include <ftxui/component/screen_interactive.hpp>
#include <ftxui/dom/elements.hpp>
#include <ftxui/screen/screen.hpp>
#include "absl/strings/str_cat.h"
#include "absl/strings/str_format.h"
#include "absl/strings/str_join.h"
#include "util/bps.h"
#include "app/core/platform/file_dialog.h"
#include "app/core/asar_wrapper.h"
namespace yaze {
namespace cli {
using namespace ftxui;
namespace {
void SwitchComponents(ftxui::ScreenInteractive &screen, LayoutID layout) {
screen.ExitLoopClosure()();
screen.Clear();
app_context.current_layout = layout;
}
bool HandleInput(ftxui::ScreenInteractive &screen, ftxui::Event &event,
int &selected) {
if (event == Event::ArrowDown || event == Event::Character('j')) {
selected++;
return true;
}
if (event == Event::ArrowUp || event == Event::Character('k')) {
if (selected != 0) selected--;
return true;
}
if (event == Event::Character('q')) {
SwitchComponents(screen, LayoutID::kExit);
return true;
}
return false;
}
void ReturnIfRomNotLoaded(ftxui::ScreenInteractive &screen) {
if (!app_context.rom.is_loaded()) {
app_context.error_message = "No ROM loaded.";
SwitchComponents(screen, LayoutID::kError);
}
}
void ApplyBpsPatchComponent(ftxui::ScreenInteractive &screen) {
// Text inputs for user to enter file paths (or any relevant data).
static std::string patch_file;
static std::string base_file;
auto patch_file_input = Input(&patch_file, "Patch file path");
auto base_file_input = Input(&base_file, "Base file path");
// Button to apply the patch.
auto apply_button = Button("Apply Patch", [&] {
std::vector<uint8_t> source = app_context.rom.vector();
// auto source_contents = core::LoadFile(base_file);
// std::copy(source_contents.begin(), source_contents.end(),
// std::back_inserter(source));
std::vector<uint8_t> patch;
auto patch_contents = core::LoadFile(patch_file);
std::copy(patch_contents.begin(), patch_contents.end(),
std::back_inserter(patch));
std::vector<uint8_t> patched;
try {
util::ApplyBpsPatch(source, patch, patched);
} catch (const std::runtime_error &e) {
app_context.error_message = e.what();
SwitchComponents(screen, LayoutID::kError);
return;
}
// Write the patched data to a new file.
// Find the . in the base file name and insert _patched before it.
auto dot_pos = base_file.find_last_of('.');
auto patched_file = base_file.substr(0, dot_pos) + "_patched" +
base_file.substr(dot_pos, base_file.size() - dot_pos);
std::ofstream file(patched_file, std::ios::binary);
if (!file.is_open()) {
app_context.error_message = "Could not open file for writing.";
SwitchComponents(screen, LayoutID::kError);
return;
}
file.write(reinterpret_cast<const char *>(patched.data()), patched.size());
// If the patch was applied successfully, return to the main menu.
SwitchComponents(screen, LayoutID::kMainMenu);
});
// Button to return to main menu without applying.
auto return_button = Button("Back to Main Menu", [&] {
SwitchComponents(screen, LayoutID::kMainMenu);
});
// Layout components vertically.
auto container = Container::Vertical({
patch_file_input,
base_file_input,
apply_button,
return_button,
});
auto renderer = Renderer(container, [&] {
return vbox({text("Apply BPS Patch") | center, separator(),
text("Enter Patch File:"), patch_file_input->Render(),
text("Enter Base File:"), base_file_input->Render(),
separator(),
hbox({
apply_button->Render() | center,
separator(),
return_button->Render() | center,
}) | center}) |
center;
});
screen.Loop(renderer);
}
void GenerateSaveFileComponent(ftxui::ScreenInteractive &screen) {
// Produce a list of ftxui::Checkbox for items and values to set
// Link to the past items include Bow, Boomerang, etc.
const static std::vector<std::string> items = {"Bow",
"Boomerang",
"Hookshot",
"Bombs",
"Magic Powder",
"Fire Rod",
"Ice Rod",
"Lantern",
"Hammer",
"Shovel",
"Flute",
"Bug Net",
"Book of Mudora",
"Cane of Somaria",
"Cane of Byrna",
"Magic Cape",
"Magic Mirror",
"Pegasus Boots",
"Flippers",
"Moon Pearl",
"Bottle 1",
"Bottle 2",
"Bottle 3",
"Bottle 4"};
constexpr size_t kNumItems = 28;
std::array<bool, kNumItems> values = {};
auto checkboxes = Container::Vertical({});
for (size_t i = 0; i < items.size(); i += 4) {
auto row = Container::Horizontal({});
for (size_t j = 0; j < 4 && (i + j) < items.size(); ++j) {
row->Add(
Checkbox(absl::StrCat(items[i + j], " ").data(), &values[i + j]));
}
checkboxes->Add(row);
}
// border container for sword, shield, armor with radioboxes
// to select the current item
// sword, shield, armor
static int sword = 0;
static int shield = 0;
static int armor = 0;
const std::vector<std::string> sword_items = {"Fighter", "Master", "Tempered",
"Golden"};
const std::vector<std::string> shield_items = {"Small", "Fire", "Mirror"};
const std::vector<std::string> armor_items = {"Green", "Blue", "Red"};
auto sword_radiobox = Radiobox(&sword_items, &sword);
auto shield_radiobox = Radiobox(&shield_items, &shield);
auto armor_radiobox = Radiobox(&armor_items, &armor);
auto equipment_container = Container::Vertical({
sword_radiobox,
shield_radiobox,
armor_radiobox,
});
auto save_button = Button("Generate Save File", [&] {
// Generate the save file here.
// You can use the values vector to determine which items are checked.
// After generating the save file, you could either stay here or return to
// the main menu.
});
auto back_button =
Button("Back", [&] { SwitchComponents(screen, LayoutID::kMainMenu); });
auto container = Container::Vertical({
checkboxes,
equipment_container,
save_button,
back_button,
});
auto renderer = Renderer(container, [&] {
return vbox({text("Generate Save File") | center, separator(),
text("Select items to include in the save file:"),
checkboxes->Render(), separator(),
equipment_container->Render(), separator(),
hbox({
save_button->Render() | center,
separator(),
back_button->Render() | center,
}) | center}) |
center;
});
screen.Loop(renderer);
}
void ApplyAsarPatchComponent(ftxui::ScreenInteractive &screen) {
static std::string patch_file;
static std::string output_message;
static std::vector<std::string> symbols_list;
static bool show_symbols = false;
auto patch_file_input = Input(&patch_file, "Assembly patch file (.asm)");
auto apply_button = Button("Apply Asar Patch", [&] {
if (patch_file.empty()) {
app_context.error_message = "Please specify an assembly patch file";
SwitchComponents(screen, LayoutID::kError);
return;
}
if (!app_context.rom.is_loaded()) {
app_context.error_message = "No ROM loaded. Please load a ROM first.";
SwitchComponents(screen, LayoutID::kError);
return;
}
try {
app::core::AsarWrapper wrapper;
auto init_status = wrapper.Initialize();
if (!init_status.ok()) {
app_context.error_message = absl::StrCat("Failed to initialize Asar: ", init_status.message());
SwitchComponents(screen, LayoutID::kError);
return;
}
auto rom_data = app_context.rom.vector();
auto patch_result = wrapper.ApplyPatch(patch_file, rom_data);
if (!patch_result.ok()) {
app_context.error_message = absl::StrCat("Patch failed: ", patch_result.status().message());
SwitchComponents(screen, LayoutID::kError);
return;
}
const auto& result = patch_result.value();
if (!result.success) {
app_context.error_message = absl::StrCat("Patch failed: ", absl::StrJoin(result.errors, "; "));
SwitchComponents(screen, LayoutID::kError);
return;
}
// Update ROM with patched data
// Note: ROM update would need proper implementation
// For now, just indicate success
// Prepare success message
output_message = absl::StrFormat(
"✅ Patch applied successfully!\n"
"📊 ROM size: %d bytes\n"
"🏷️ Symbols found: %d",
result.rom_size, result.symbols.size());
// Prepare symbols list
symbols_list.clear();
for (const auto& symbol : result.symbols) {
symbols_list.push_back(absl::StrFormat("%-20s @ $%06X",
symbol.name, symbol.address));
}
show_symbols = !symbols_list.empty();
} catch (const std::exception& e) {
app_context.error_message = "Exception: " + std::string(e.what());
SwitchComponents(screen, LayoutID::kError);
}
});
auto show_symbols_button = Button("Show Symbols", [&] {
show_symbols = !show_symbols;
});
auto back_button = Button("Back to Main Menu", [&] {
output_message.clear();
symbols_list.clear();
show_symbols = false;
SwitchComponents(screen, LayoutID::kMainMenu);
});
std::vector<Component> container_items = {
patch_file_input,
apply_button,
};
if (!output_message.empty()) {
container_items.push_back(show_symbols_button);
}
container_items.push_back(back_button);
auto container = Container::Vertical(container_items);
auto renderer = Renderer(container, [&] {
std::vector<Element> elements = {
text("Apply Asar Assembly Patch") | center | bold,
separator(),
text("Assembly Patch File:"),
patch_file_input->Render(),
separator(),
apply_button->Render() | center,
};
if (!output_message.empty()) {
elements.push_back(separator());
elements.push_back(text(output_message) | color(Color::Green));
elements.push_back(show_symbols_button->Render() | center);
if (show_symbols && !symbols_list.empty()) {
elements.push_back(separator());
elements.push_back(text("Extracted Symbols:") | bold);
// Show symbols in a scrollable area
std::vector<Element> symbol_elements;
for (size_t i = 0; i < std::min(symbols_list.size(), size_t(10)); ++i) {
symbol_elements.push_back(text(symbols_list[i]) | color(Color::Cyan));
}
if (symbols_list.size() > 10) {
symbol_elements.push_back(text(absl::StrFormat("... and %d more",
symbols_list.size() - 10)) |
color(Color::Yellow));
}
elements.push_back(vbox(symbol_elements) | frame);
}
}
elements.push_back(separator());
elements.push_back(back_button->Render() | center);
return vbox(elements) | center | border;
});
screen.Loop(renderer);
}
void ExtractSymbolsComponent(ftxui::ScreenInteractive &screen) {
static std::string asm_file;
static std::vector<std::string> symbols_list;
static std::string output_message;
auto asm_file_input = Input(&asm_file, "Assembly file (.asm)");
auto extract_button = Button("Extract Symbols", [&] {
if (asm_file.empty()) {
app_context.error_message = "Please specify an assembly file";
SwitchComponents(screen, LayoutID::kError);
return;
}
try {
app::core::AsarWrapper wrapper;
auto init_status = wrapper.Initialize();
if (!init_status.ok()) {
app_context.error_message = absl::StrCat("Failed to initialize Asar: ", init_status.message());
SwitchComponents(screen, LayoutID::kError);
return;
}
auto symbols_result = wrapper.ExtractSymbols(asm_file);
if (!symbols_result.ok()) {
app_context.error_message = absl::StrCat("Symbol extraction failed: ", symbols_result.status().message());
SwitchComponents(screen, LayoutID::kError);
return;
}
const auto& symbols = symbols_result.value();
output_message = absl::StrFormat("✅ Extracted %d symbols from %s",
symbols.size(), asm_file);
symbols_list.clear();
for (const auto& symbol : symbols) {
symbols_list.push_back(absl::StrFormat("%-20s @ $%06X",
symbol.name, symbol.address));
}
} catch (const std::exception& e) {
app_context.error_message = "Exception: " + std::string(e.what());
SwitchComponents(screen, LayoutID::kError);
}
});
auto back_button = Button("Back to Main Menu", [&] {
output_message.clear();
symbols_list.clear();
SwitchComponents(screen, LayoutID::kMainMenu);
});
auto container = Container::Vertical({
asm_file_input,
extract_button,
back_button,
});
auto renderer = Renderer(container, [&] {
std::vector<Element> elements = {
text("Extract Assembly Symbols") | center | bold,
separator(),
text("Assembly File:"),
asm_file_input->Render(),
separator(),
extract_button->Render() | center,
};
if (!output_message.empty()) {
elements.push_back(separator());
elements.push_back(text(output_message) | color(Color::Green));
if (!symbols_list.empty()) {
elements.push_back(separator());
elements.push_back(text("Symbols:") | bold);
std::vector<Element> symbol_elements;
for (const auto& symbol : symbols_list) {
symbol_elements.push_back(text(symbol) | color(Color::Cyan));
}
elements.push_back(vbox(symbol_elements) | frame | size(HEIGHT, LESS_THAN, 15));
}
}
elements.push_back(separator());
elements.push_back(back_button->Render() | center);
return vbox(elements) | center | border;
});
screen.Loop(renderer);
}
void ValidateAssemblyComponent(ftxui::ScreenInteractive &screen) {
static std::string asm_file;
static std::string output_message;
static Color output_color = Color::White;
auto asm_file_input = Input(&asm_file, "Assembly file (.asm)");
auto validate_button = Button("Validate Assembly", [&] {
if (asm_file.empty()) {
app_context.error_message = "Please specify an assembly file";
SwitchComponents(screen, LayoutID::kError);
return;
}
try {
app::core::AsarWrapper wrapper;
auto init_status = wrapper.Initialize();
if (!init_status.ok()) {
app_context.error_message = absl::StrCat("Failed to initialize Asar: ", init_status.message());
SwitchComponents(screen, LayoutID::kError);
return;
}
auto validation_status = wrapper.ValidateAssembly(asm_file);
if (validation_status.ok()) {
output_message = "✅ Assembly file is valid!";
output_color = Color::Green;
} else {
output_message = absl::StrCat("❌ Validation failed:\n", validation_status.message());
output_color = Color::Red;
}
} catch (const std::exception& e) {
app_context.error_message = "Exception: " + std::string(e.what());
SwitchComponents(screen, LayoutID::kError);
}
});
auto back_button = Button("Back to Main Menu", [&] {
output_message.clear();
SwitchComponents(screen, LayoutID::kMainMenu);
});
auto container = Container::Vertical({
asm_file_input,
validate_button,
back_button,
});
auto renderer = Renderer(container, [&] {
std::vector<Element> elements = {
text("Validate Assembly File") | center | bold,
separator(),
text("Assembly File:"),
asm_file_input->Render(),
separator(),
validate_button->Render() | center,
};
if (!output_message.empty()) {
elements.push_back(separator());
elements.push_back(text(output_message) | color(output_color));
}
elements.push_back(separator());
elements.push_back(back_button->Render() | center);
return vbox(elements) | center | border;
});
screen.Loop(renderer);
}
void LoadRomComponent(ftxui::ScreenInteractive &screen) {
static std::string rom_file;
auto rom_file_input = Input(&rom_file, "ROM file path");
auto load_button = Button("Load ROM", [&] {
// Load the ROM file here.
auto rom_status = app_context.rom.LoadFromFile(rom_file);
if (!rom_status.ok()) {
app_context.error_message = std::string(rom_status.message().data(), rom_status.message().size());
SwitchComponents(screen, LayoutID::kError);
return;
}
// If the ROM is loaded successfully, switch to the main menu.
SwitchComponents(screen, LayoutID::kMainMenu);
});
auto browse_button = Button("Browse...", [&] {
// TODO: Implement file dialog
// For now, show a placeholder
rom_file = "/path/to/your/rom.sfc";
});
auto back_button =
Button("Back", [&] { SwitchComponents(screen, LayoutID::kMainMenu); });
auto container = Container::Vertical({
Container::Horizontal({rom_file_input, browse_button}),
load_button,
back_button,
});
auto renderer = Renderer(container, [&] {
return vbox({
text("Load ROM") | center | bold,
separator(),
text("Enter ROM File Path:"),
hbox({
rom_file_input->Render() | flex,
separator(),
browse_button->Render(),
}),
separator(),
load_button->Render() | center,
separator(),
back_button->Render() | center,
}) | center | border;
});
screen.Loop(renderer);
}
Element ColorBox(const Color &color) {
return ftxui::text(" ") | ftxui::bgcolor(color);
}
void PaletteEditorComponent(ftxui::ScreenInteractive &screen) {
ReturnIfRomNotLoaded(screen);
auto back_button =
Button("Back", [&] { SwitchComponents(screen, LayoutID::kMainMenu); });
static auto palette_groups = app_context.rom.palette_group();
static std::vector<gfx::PaletteGroup> ftx_palettes = {
palette_groups.swords,
palette_groups.shields,
palette_groups.armors,
palette_groups.overworld_main,
palette_groups.overworld_aux,
palette_groups.global_sprites,
palette_groups.sprites_aux1,
palette_groups.sprites_aux2,
palette_groups.sprites_aux3,
palette_groups.dungeon_main,
palette_groups.overworld_mini_map,
palette_groups.grass,
palette_groups.object_3d,
};
// Create a list of palette groups to pick from
static int selected_palette_group = 0;
static std::vector<std::string> palette_group_names;
if (palette_group_names.empty()) {
for (size_t i = 0; i < 14; ++i) {
palette_group_names.push_back(gfx::kPaletteCategoryNames[i].data());
}
}
static bool show_palette_editor = false;
static std::vector<std::vector<Element>> palette_elements;
const auto load_palettes_from_current_group = [&]() {
auto palette_group = ftx_palettes[selected_palette_group];
palette_elements.clear();
// Create a list of colors to display in the palette editor.
for (size_t i = 0; i < palette_group.size(); ++i) {
palette_elements.push_back(std::vector<Element>());
for (size_t j = 0; j < palette_group[i].size(); ++j) {
auto color = palette_group[i][j];
palette_elements[i].push_back(
ColorBox(Color::RGB(color.rgb().x, color.rgb().y, color.rgb().z)));
}
}
};
if (show_palette_editor) {
if (palette_elements.empty()) {
load_palettes_from_current_group();
}
auto palette_grid = Container::Vertical({});
for (const auto &element : palette_elements) {
auto row = Container::Horizontal({});
for (const auto &color : element) {
row->Add(Renderer([color] { return color; }));
}
palette_grid->Add(row);
}
// Create a button to save the changes to the palette.
auto save_button = Button("Save Changes", [&] {
// Save the changes to the palette here.
// You can use the current_palette vector to determine the new colors.
// After saving the changes, you could either stay here or return to the
// main menu.
});
auto back_button = Button("Back", [&] {
show_palette_editor = false;
screen.ExitLoopClosure()();
});
auto palette_editor_container = Container::Vertical({
palette_grid,
save_button,
back_button,
});
auto palette_editor_renderer = Renderer(palette_editor_container, [&] {
return vbox({text(gfx::kPaletteCategoryNames[selected_palette_group]
.data()) |
center,
separator(), palette_grid->Render(), separator(),
hbox({
save_button->Render() | center,
separator(),
back_button->Render() | center,
}) | center}) |
center;
});
screen.Loop(palette_editor_renderer);
} else {
auto palette_list = Menu(&palette_group_names, &selected_palette_group);
palette_list = CatchEvent(palette_list, [&](Event event) {
if (event == Event::Return) {
// Load the selected palette group into the palette editor.
// This will be a separate component.
show_palette_editor = true;
screen.ExitLoopClosure()();
load_palettes_from_current_group();
return true;
}
return false;
});
auto container = Container::Vertical({
palette_list,
back_button,
});
auto renderer = Renderer(container, [&] {
return vbox({text("Palette Editor") | center, separator(),
palette_list->Render(), separator(),
back_button->Render() | center}) |
center;
});
screen.Loop(renderer);
}
}
void HelpComponent(ftxui::ScreenInteractive &screen) {
auto help_text = vbox({
text("z3ed v0.3.2") | bold | color(Color::Yellow),
text("by scawful") | color(Color::Magenta),
text("The Legend of Zelda: A Link to the Past Hacking Tool") |
color(Color::Red),
text("Now with Asar 65816 Assembler Integration!") |
color(Color::Green),
separator(),
text("🎯 ASAR COMMANDS") | bold | color(Color::Cyan),
separator(),
hbox({
text("Apply Asar Patch"),
filler(),
text("asar"),
filler(),
text("<patch.asm> [--rom=<file>]"),
}),
hbox({
text("Extract Symbols"),
filler(),
text("extract"),
filler(),
text("<patch.asm>"),
}),
hbox({
text("Validate Assembly"),
filler(),
text("validate"),
filler(),
text("<patch.asm>"),
}),
separator(),
text("📦 PATCH COMMANDS") | bold | color(Color::Blue),
separator(),
hbox({
text("Apply BPS Patch"),
filler(),
text("patch"),
filler(),
text("<patch.bps> [--rom=<file>]"),
}),
hbox({
text("Create BPS Patch"),
filler(),
text("create"),
filler(),
text("<src_file> <modified_file>"),
}),
separator(),
text("🗃️ ROM COMMANDS") | bold | color(Color::Yellow),
separator(),
hbox({
text("Show ROM Info"),
filler(),
text("info"),
filler(),
text("[--rom=<file>]"),
}),
hbox({
text("Backup ROM"),
filler(),
text("backup"),
filler(),
text("<rom_file> [backup_name]"),
}),
hbox({
text("Expand ROM"),
filler(),
text("expand"),
filler(),
text("<rom_file> <size>"),
}),
separator(),
text("🔧 UTILITY COMMANDS") | bold | color(Color::Magenta),
separator(),
hbox({
text("Address Conversion"),
filler(),
text("convert"),
filler(),
text("<address> [--to-pc|--to-snes]"),
}),
hbox({
text("Transfer Tile16"),
filler(),
text("tile16"),
filler(),
text("<src> <dest> <tiles>"),
}),
separator(),
text("🌐 GLOBAL FLAGS") | bold | color(Color::White),
separator(),
hbox({
text("--tui"),
filler(),
text("Launch Text User Interface"),
}),
hbox({
text("--rom=<file>"),
filler(),
text("Specify ROM file"),
}),
hbox({
text("--output=<file>"),
filler(),
text("Specify output file"),
}),
hbox({
text("--verbose"),
filler(),
text("Enable verbose output"),
}),
hbox({
text("--dry-run"),
filler(),
text("Test without changes"),
}),
});
auto help_text_component = Renderer([&] { return help_text; });
auto back_button =
Button("Back", [&] { SwitchComponents(screen, LayoutID::kMainMenu); });
auto container = Container::Vertical({
help_text_component,
back_button,
});
auto renderer = Renderer(container, [&] {
return vbox({
help_text_component->Render() | center,
separator(),
back_button->Render() | center,
}) |
border;
});
screen.Loop(renderer);
}
void MainMenuComponent(ftxui::ScreenInteractive &screen) {
// Tracks which menu item is selected.
static int selected = 0;
MenuOption option;
option.focused_entry = &selected;
auto menu = Menu(&kMainMenuEntries, &selected, option);
menu = CatchEvent(
menu, [&](Event event) { return HandleInput(screen, event, selected); });
std::string rom_information = "ROM not loaded";
if (app_context.rom.is_loaded()) {
rom_information = app_context.rom.title();
}
auto title = border(hbox({
text("z3ed") | bold | color(Color::Blue1),
separator(),
text("v0.3.2") | bold | color(Color::Green1),
separator(),
text(rom_information) | bold | color(Color::Red1),
}));
auto renderer = Renderer(menu, [&] {
return vbox({
separator(),
title | center,
separator(),
menu->Render() | center,
});
});
// Catch events like pressing Enter to switch layout or pressing 'q' to exit.
auto main_component = CatchEvent(renderer, [&](Event event) {
if (event == Event::Return) {
switch ((MainMenuEntry)selected) {
case MainMenuEntry::kLoadRom:
SwitchComponents(screen, LayoutID::kLoadRom);
return true;
case MainMenuEntry::kApplyAsarPatch:
SwitchComponents(screen, LayoutID::kApplyAsarPatch);
return true;
case MainMenuEntry::kApplyBpsPatch:
SwitchComponents(screen, LayoutID::kApplyBpsPatch);
return true;
case MainMenuEntry::kExtractSymbols:
SwitchComponents(screen, LayoutID::kExtractSymbols);
return true;
case MainMenuEntry::kValidateAssembly:
SwitchComponents(screen, LayoutID::kValidateAssembly);
return true;
case MainMenuEntry::kGenerateSaveFile:
SwitchComponents(screen, LayoutID::kGenerateSaveFile);
return true;
case MainMenuEntry::kPaletteEditor:
SwitchComponents(screen, LayoutID::kPaletteEditor);
return true;
case MainMenuEntry::kHelp:
SwitchComponents(screen, LayoutID::kHelp);
return true;
case MainMenuEntry::kExit:
SwitchComponents(screen, LayoutID::kExit);
return true;
}
}
if (event == Event::Character('q')) {
SwitchComponents(screen, LayoutID::kExit);
return true;
}
return false;
});
screen.Loop(main_component);
}
} // namespace
void ShowMain() {
auto screen = ScreenInteractive::TerminalOutput();
while (true) {
switch (app_context.current_layout) {
case LayoutID::kMainMenu: {
MainMenuComponent(screen);
} break;
case LayoutID::kLoadRom: {
LoadRomComponent(screen);
} break;
case LayoutID::kApplyAsarPatch: {
ApplyAsarPatchComponent(screen);
} break;
case LayoutID::kApplyBpsPatch: {
ApplyBpsPatchComponent(screen);
} break;
case LayoutID::kExtractSymbols: {
ExtractSymbolsComponent(screen);
} break;
case LayoutID::kValidateAssembly: {
ValidateAssemblyComponent(screen);
} break;
case LayoutID::kGenerateSaveFile: {
GenerateSaveFileComponent(screen);
} break;
case LayoutID::kPaletteEditor: {
PaletteEditorComponent(screen);
} break;
case LayoutID::kHelp: {
HelpComponent(screen);
} break;
case LayoutID::kError: {
// Display error message and return to main menu.
auto error_button = Button("Back to Main Menu", [&] {
app_context.error_message.clear();
SwitchComponents(screen, LayoutID::kMainMenu);
});
auto error_renderer = Renderer(error_button, [&] {
return vbox({
text("Error") | center | bold | color(Color::Red),
separator(),
text(app_context.error_message) | color(Color::Yellow),
separator(),
error_button->Render() | center
}) | center | border;
});
screen.Loop(error_renderer);
} break;
case LayoutID::kExit:
default:
return; // Exit the application.
}
}
}
} // namespace cli
} // namespace yaze