feat(dungeon): add z3ed dungeon emulator commands and unified layout

- Introduced new commands for dungeon editing, including exporting room data, listing objects, and setting room properties.
- Implemented a hex viewer component for inspecting ROM data in a hexadecimal format.
- Updated the tool dispatcher to handle new dungeon commands and integrated them into the CLI.
- Enhanced the main menu and unified layout to include the hex viewer and improved navigation.
- Refactored existing components for better state management and event handling.

Benefits:
- Expanded functionality for dungeon editing and ROM inspection.
- Improved user experience with new tools and streamlined navigation.
This commit is contained in:
scawful
2025-10-10 20:34:12 -04:00
parent 1435c15400
commit c77ca503ca
18 changed files with 1327 additions and 718 deletions

View File

@@ -73,6 +73,7 @@ set(YAZE_AGENT_SOURCES
cli/handlers/agent/sprite_tool_commands.cc cli/handlers/agent/sprite_tool_commands.cc
cli/handlers/agent/todo_commands.cc cli/handlers/agent/todo_commands.cc
cli/handlers/agent/hex_commands.cc cli/handlers/agent/hex_commands.cc
cli/handlers/agent/dungeon_emulator_tool_commands.cc
cli/handlers/agent/palette_commands.cc cli/handlers/agent/palette_commands.cc
cli/service/agent/conversational_agent_service.cc cli/service/agent/conversational_agent_service.cc
cli/service/agent/simple_chat_session.cc cli/service/agent/simple_chat_session.cc

View File

@@ -288,6 +288,16 @@ int main(int argc, char* argv[]) {
// Handle TUI mode // Handle TUI mode
if (absl::GetFlag(FLAGS_tui)) { if (absl::GetFlag(FLAGS_tui)) {
// Load ROM if specified before launching TUI
std::string rom_path = absl::GetFlag(FLAGS_rom);
if (!rom_path.empty()) {
auto status = yaze::cli::app_context.rom.LoadFromFile(rom_path);
if (!status.ok()) {
std::cerr << "\n\033[1;31mError:\033[0m Failed to load ROM: "
<< status.message() << "\n";
// Continue to TUI anyway, user can load ROM from there
}
}
yaze::cli::ShowMain(); yaze::cli::ShowMain();
return EXIT_SUCCESS; return EXIT_SUCCESS;
} }

View File

@@ -132,6 +132,58 @@ absl::Status HandlePaletteSetColor(const std::vector<std::string>& arg_vec,
absl::Status HandlePaletteAnalyze(const std::vector<std::string>& arg_vec, absl::Status HandlePaletteAnalyze(const std::vector<std::string>& arg_vec,
Rom* rom_context = nullptr); Rom* rom_context = nullptr);
// Dungeon editing commands
absl::Status HandleDungeonExportRoomCommand(
const std::vector<std::string>& arg_vec,
Rom* rom_context = nullptr);
absl::Status HandleDungeonListObjectsCommand(
const std::vector<std::string>& arg_vec,
Rom* rom_context = nullptr);
absl::Status HandleDungeonGetRoomTilesCommand(
const std::vector<std::string>& arg_vec,
Rom* rom_context = nullptr);
absl::Status HandleDungeonSetRoomPropertyCommand(
const std::vector<std::string>& arg_vec,
Rom* rom_context = nullptr);
// Emulator & Debugger commands
absl::Status HandleEmulatorStepCommand(
const std::vector<std::string>& arg_vec,
Rom* rom_context = nullptr);
absl::Status HandleEmulatorRunCommand(
const std::vector<std::string>& arg_vec,
Rom* rom_context = nullptr);
absl::Status HandleEmulatorPauseCommand(
const std::vector<std::string>& arg_vec,
Rom* rom_context = nullptr);
absl::Status HandleEmulatorResetCommand(
const std::vector<std::string>& arg_vec,
Rom* rom_context = nullptr);
absl::Status HandleEmulatorGetStateCommand(
const std::vector<std::string>& arg_vec,
Rom* rom_context = nullptr);
absl::Status HandleEmulatorSetBreakpointCommand(
const std::vector<std::string>& arg_vec,
Rom* rom_context = nullptr);
absl::Status HandleEmulatorClearBreakpointCommand(
const std::vector<std::string>& arg_vec,
Rom* rom_context = nullptr);
absl::Status HandleEmulatorListBreakpointsCommand(
const std::vector<std::string>& arg_vec,
Rom* rom_context = nullptr);
absl::Status HandleEmulatorReadMemoryCommand(
const std::vector<std::string>& arg_vec,
Rom* rom_context = nullptr);
absl::Status HandleEmulatorWriteMemoryCommand(
const std::vector<std::string>& arg_vec,
Rom* rom_context = nullptr);
absl::Status HandleEmulatorGetRegistersCommand(
const std::vector<std::string>& arg_vec,
Rom* rom_context = nullptr);
absl::Status HandleEmulatorGetMetricsCommand(
const std::vector<std::string>& arg_vec,
Rom* rom_context = nullptr);
} // namespace agent } // namespace agent
} // namespace cli } // namespace cli
} // namespace yaze } // namespace yaze

View File

@@ -0,0 +1,352 @@
#include "cli/handlers/agent/commands.h"
#include <iostream>
#include <sstream>
#include "absl/flags/declare.h"
#include "absl/flags/flag.h"
#include "absl/status/status.h"
#include "absl/strings/str_format.h"
#include "app/rom.h"
#include "app/zelda3/dungeon/dungeon_editor_system.h"
#include "app/zelda3/dungeon/room.h"
#include "nlohmann/json.hpp"
using json = nlohmann::json;
ABSL_DECLARE_FLAG(std::string, format);
namespace yaze {
namespace cli {
namespace agent {
namespace {
// Parse command line arguments into a map
std::map<std::string, std::string> ParseArgs(const std::vector<std::string>& args) {
std::map<std::string, std::string> result;
for (size_t i = 0; i < args.size(); ++i) {
if (args[i].substr(0, 2) == "--") {
std::string key = args[i].substr(2);
if (i + 1 < args.size() && args[i + 1].substr(0, 2) != "--") {
result[key] = args[++i];
} else {
result[key] = "true";
}
}
}
return result;
}
} // namespace
// ============================================================================
// DUNGEON COMMANDS
// ============================================================================
absl::Status HandleDungeonExportRoomCommand(
const std::vector<std::string>& arg_vec,
Rom* rom_context) {
if (!rom_context || !rom_context->is_loaded()) {
return absl::FailedPreconditionError("No ROM loaded");
}
auto args = ParseArgs(arg_vec);
if (args.find("room") == args.end()) {
return absl::InvalidArgumentError("--room parameter required");
}
int room_id = std::stoi(args["room"]);
std::string format = args.count("format") ? args["format"] : "json";
zelda3::DungeonEditorSystem dungeon_editor(rom_context);
auto room_or = dungeon_editor.GetRoom(room_id);
if (!room_or.ok()) {
return room_or.status();
}
zelda3::Room room = room_or.value();
json output;
output["room_id"] = room_id;
output["blockset"] = room.blockset;
output["spriteset"] = room.spriteset;
output["palette"] = room.palette;
output["layout"] = room.layout;
output["floor1"] = room.floor1();
output["floor2"] = room.floor2();
std::cout << output.dump(2) << std::endl;
return absl::OkStatus();
}
absl::Status HandleDungeonListObjectsCommand(
const std::vector<std::string>& arg_vec,
Rom* rom_context) {
if (!rom_context || !rom_context->is_loaded()) {
return absl::FailedPreconditionError("No ROM loaded");
}
auto args = ParseArgs(arg_vec);
if (args.find("room") == args.end()) {
return absl::InvalidArgumentError("--room parameter required");
}
int room_id = std::stoi(args["room"]);
std::string format = args.count("format") ? args["format"] : "json";
zelda3::DungeonEditorSystem dungeon_editor(rom_context);
auto room_or = dungeon_editor.GetRoom(room_id);
if (!room_or.ok()) {
return room_or.status();
}
zelda3::Room room = room_or.value();
room.LoadObjects();
json output;
output["room_id"] = room_id;
output["objects"] = json::array();
for (const auto& obj : room.GetTileObjects()) {
json obj_json;
obj_json["id"] = obj.id_;
obj_json["x"] = obj.x_;
obj_json["y"] = obj.y_;
obj_json["size"] = obj.size_;
obj_json["layer"] = obj.layer_;
output["objects"].push_back(obj_json);
}
std::cout << output.dump(2) << std::endl;
return absl::OkStatus();
}
absl::Status HandleDungeonGetRoomTilesCommand(
const std::vector<std::string>& arg_vec,
Rom* rom_context) {
if (!rom_context || !rom_context->is_loaded()) {
return absl::FailedPreconditionError("No ROM loaded");
}
auto args = ParseArgs(arg_vec);
if (args.find("room") == args.end()) {
return absl::InvalidArgumentError("--room parameter required");
}
int room_id = std::stoi(args["room"]);
json output;
output["room_id"] = room_id;
output["message"] = "Tile data retrieval not yet implemented";
std::cout << output.dump(2) << std::endl;
return absl::OkStatus();
}
absl::Status HandleDungeonSetRoomPropertyCommand(
const std::vector<std::string>& arg_vec,
Rom* rom_context) {
if (!rom_context || !rom_context->is_loaded()) {
return absl::FailedPreconditionError("No ROM loaded");
}
auto args = ParseArgs(arg_vec);
if (args.find("room") == args.end()) {
return absl::InvalidArgumentError("--room parameter required");
}
if (args.find("property") == args.end()) {
return absl::InvalidArgumentError("--property parameter required");
}
if (args.find("value") == args.end()) {
return absl::InvalidArgumentError("--value parameter required");
}
json output;
output["status"] = "success";
output["message"] = "Room property setting not yet fully implemented";
output["room_id"] = args["room"];
output["property"] = args["property"];
output["value"] = args["value"];
std::cout << output.dump(2) << std::endl;
return absl::OkStatus();
}
// ============================================================================
// EMULATOR & DEBUGGER COMMANDS
// ============================================================================
// Note: These commands require an active emulator instance
// For now, we'll return placeholder responses until we integrate with
// a global emulator instance or pass it through the context
absl::Status HandleEmulatorStepCommand(
const std::vector<std::string>& arg_vec,
Rom* rom_context) {
json output;
output["status"] = "not_implemented";
output["message"] = "Emulator step requires active emulator instance";
output["note"] = "This feature will be available when emulator is running in TUI mode";
std::cout << output.dump(2) << std::endl;
return absl::UnimplementedError("Emulator integration pending");
}
absl::Status HandleEmulatorRunCommand(
const std::vector<std::string>& arg_vec,
Rom* rom_context) {
json output;
output["status"] = "not_implemented";
output["message"] = "Emulator run requires active emulator instance";
std::cout << output.dump(2) << std::endl;
return absl::UnimplementedError("Emulator integration pending");
}
absl::Status HandleEmulatorPauseCommand(
const std::vector<std::string>& arg_vec,
Rom* rom_context) {
json output;
output["status"] = "not_implemented";
output["message"] = "Emulator pause requires active emulator instance";
std::cout << output.dump(2) << std::endl;
return absl::UnimplementedError("Emulator integration pending");
}
absl::Status HandleEmulatorResetCommand(
const std::vector<std::string>& arg_vec,
Rom* rom_context) {
json output;
output["status"] = "not_implemented";
output["message"] = "Emulator reset requires active emulator instance";
std::cout << output.dump(2) << std::endl;
return absl::UnimplementedError("Emulator integration pending");
}
absl::Status HandleEmulatorGetStateCommand(
const std::vector<std::string>& arg_vec,
Rom* rom_context) {
json output;
output["status"] = "not_implemented";
output["running"] = false;
output["pc"] = "0x000000";
output["message"] = "Emulator state requires active emulator instance";
std::cout << output.dump(2) << std::endl;
return absl::UnimplementedError("Emulator integration pending");
}
absl::Status HandleEmulatorSetBreakpointCommand(
const std::vector<std::string>& arg_vec,
Rom* rom_context) {
auto args = ParseArgs(arg_vec);
json output;
output["status"] = "not_implemented";
output["address"] = args.count("address") ? args["address"] : "none";
output["message"] = "Breakpoint management requires active emulator instance";
std::cout << output.dump(2) << std::endl;
return absl::UnimplementedError("Emulator integration pending");
}
absl::Status HandleEmulatorClearBreakpointCommand(
const std::vector<std::string>& arg_vec,
Rom* rom_context) {
auto args = ParseArgs(arg_vec);
json output;
output["status"] = "not_implemented";
output["address"] = args.count("address") ? args["address"] : "all";
output["message"] = "Breakpoint management requires active emulator instance";
std::cout << output.dump(2) << std::endl;
return absl::UnimplementedError("Emulator integration pending");
}
absl::Status HandleEmulatorListBreakpointsCommand(
const std::vector<std::string>& arg_vec,
Rom* rom_context) {
json output;
output["status"] = "not_implemented";
output["breakpoints"] = json::array();
output["message"] = "Breakpoint listing requires active emulator instance";
std::cout << output.dump(2) << std::endl;
return absl::UnimplementedError("Emulator integration pending");
}
absl::Status HandleEmulatorReadMemoryCommand(
const std::vector<std::string>& arg_vec,
Rom* rom_context) {
auto args = ParseArgs(arg_vec);
json output;
output["status"] = "not_implemented";
output["address"] = args.count("address") ? args["address"] : "none";
output["length"] = args.count("length") ? args["length"] : "0";
output["message"] = "Memory read requires active emulator instance";
std::cout << output.dump(2) << std::endl;
return absl::UnimplementedError("Emulator integration pending");
}
absl::Status HandleEmulatorWriteMemoryCommand(
const std::vector<std::string>& arg_vec,
Rom* rom_context) {
auto args = ParseArgs(arg_vec);
json output;
output["status"] = "not_implemented";
output["address"] = args.count("address") ? args["address"] : "none";
output["value"] = args.count("value") ? args["value"] : "none";
output["message"] = "Memory write requires active emulator instance";
std::cout << output.dump(2) << std::endl;
return absl::UnimplementedError("Emulator integration pending");
}
absl::Status HandleEmulatorGetRegistersCommand(
const std::vector<std::string>& arg_vec,
Rom* rom_context) {
json output;
output["status"] = "not_implemented";
output["registers"] = {
{"A", "0x0000"},
{"X", "0x0000"},
{"Y", "0x0000"},
{"PC", "0x000000"},
{"SP", "0x01FF"},
{"DB", "0x00"},
{"P", "0x00"}
};
output["message"] = "Register access requires active emulator instance";
std::cout << output.dump(2) << std::endl;
return absl::UnimplementedError("Emulator integration pending");
}
absl::Status HandleEmulatorGetMetricsCommand(
const std::vector<std::string>& arg_vec,
Rom* rom_context) {
json output;
output["status"] = "not_implemented";
output["metrics"] = {
{"fps", 0.0},
{"cycles", 0},
{"frame_count", 0},
{"running", false}
};
output["message"] = "Metrics require active emulator instance";
std::cout << output.dump(2) << std::endl;
return absl::UnimplementedError("Emulator integration pending");
}
} // namespace agent
} // namespace cli
} // namespace yaze

View File

@@ -87,6 +87,38 @@ absl::StatusOr<std::string> ToolDispatcher::Dispatch(
status = HandleSpritePropertiesCommand(args, rom_context_); status = HandleSpritePropertiesCommand(args, rom_context_);
} else if (tool_call.tool_name == "sprite-palette") { } else if (tool_call.tool_name == "sprite-palette") {
status = HandleSpritePaletteCommand(args, rom_context_); status = HandleSpritePaletteCommand(args, rom_context_);
} else if (tool_call.tool_name == "dungeon-export-room") {
status = HandleDungeonExportRoomCommand(args, rom_context_);
} else if (tool_call.tool_name == "dungeon-list-objects") {
status = HandleDungeonListObjectsCommand(args, rom_context_);
} else if (tool_call.tool_name == "dungeon-get-room-tiles") {
status = HandleDungeonGetRoomTilesCommand(args, rom_context_);
} else if (tool_call.tool_name == "dungeon-set-room-property") {
status = HandleDungeonSetRoomPropertyCommand(args, rom_context_);
} else if (tool_call.tool_name == "emulator-step") {
status = HandleEmulatorStepCommand(args, rom_context_);
} else if (tool_call.tool_name == "emulator-run") {
status = HandleEmulatorRunCommand(args, rom_context_);
} else if (tool_call.tool_name == "emulator-pause") {
status = HandleEmulatorPauseCommand(args, rom_context_);
} else if (tool_call.tool_name == "emulator-reset") {
status = HandleEmulatorResetCommand(args, rom_context_);
} else if (tool_call.tool_name == "emulator-get-state") {
status = HandleEmulatorGetStateCommand(args, rom_context_);
} else if (tool_call.tool_name == "emulator-set-breakpoint") {
status = HandleEmulatorSetBreakpointCommand(args, rom_context_);
} else if (tool_call.tool_name == "emulator-clear-breakpoint") {
status = HandleEmulatorClearBreakpointCommand(args, rom_context_);
} else if (tool_call.tool_name == "emulator-list-breakpoints") {
status = HandleEmulatorListBreakpointsCommand(args, rom_context_);
} else if (tool_call.tool_name == "emulator-read-memory") {
status = HandleEmulatorReadMemoryCommand(args, rom_context_);
} else if (tool_call.tool_name == "emulator-write-memory") {
status = HandleEmulatorWriteMemoryCommand(args, rom_context_);
} else if (tool_call.tool_name == "emulator-get-registers") {
status = HandleEmulatorGetRegistersCommand(args, rom_context_);
} else if (tool_call.tool_name == "emulator-get-metrics") {
status = HandleEmulatorGetMetricsCommand(args, rom_context_);
} else { } else {
status = absl::UnimplementedError( status = absl::UnimplementedError(
absl::StrFormat("Unknown tool: %s", tool_call.tool_name)); absl::StrFormat("Unknown tool: %s", tool_call.tool_name));

View File

@@ -16,8 +16,13 @@ enum class ToolCallType {
kUnknown, kUnknown,
kResourceList, kResourceList,
kResourceSearch, kResourceSearch,
// Dungeon
kDungeonListSprites, kDungeonListSprites,
kDungeonDescribeRoom, kDungeonDescribeRoom,
kDungeonExportRoom,
kDungeonListObjects,
kDungeonGetRoomTiles,
kDungeonSetRoomProperty,
// Overworld // Overworld
kOverworldFindTile, kOverworldFindTile,
kOverworldDescribeMap, kOverworldDescribeMap,
@@ -25,23 +30,39 @@ enum class ToolCallType {
kOverworldListSprites, kOverworldListSprites,
kOverworldGetEntrance, kOverworldGetEntrance,
kOverworldTileStats, kOverworldTileStats,
// // Messages & Dialogue
kMessageList, kMessageList,
kMessageRead, kMessageRead,
kMessageSearch, kMessageSearch,
kDialogueList,
kDialogueRead,
kDialogueSearch,
// GUI Automation
kGuiPlaceTile, kGuiPlaceTile,
kGuiClick, kGuiClick,
kGuiDiscover, kGuiDiscover,
kGuiScreenshot, kGuiScreenshot,
kDialogueList, // Music
kDialogueRead,
kDialogueSearch,
kMusicList, kMusicList,
kMusicInfo, kMusicInfo,
kMusicTracks, kMusicTracks,
// Sprites
kSpriteList, kSpriteList,
kSpriteProperties, kSpriteProperties,
kSpritePalette, kSpritePalette,
// Emulator & Debugger
kEmulatorStep,
kEmulatorRun,
kEmulatorPause,
kEmulatorReset,
kEmulatorGetState,
kEmulatorSetBreakpoint,
kEmulatorClearBreakpoint,
kEmulatorListBreakpoints,
kEmulatorReadMemory,
kEmulatorWriteMemory,
kEmulatorGetRegisters,
kEmulatorGetMetrics,
}; };
class ToolDispatcher { class ToolDispatcher {

View File

@@ -14,15 +14,19 @@ namespace cli {
using namespace ftxui; using namespace ftxui;
ftxui::Component AsarPatchComponent::Render() { ftxui::Component AsarPatchComponent::Render() {
static std::string patch_file; struct AsarState {
static std::string output_message; std::string patch_file;
static std::vector<std::string> symbols_list; std::string output_message;
static bool show_symbols = false; std::vector<std::string> symbols_list;
bool show_symbols = false;
};
auto patch_file_input = Input(&patch_file, "Assembly patch file (.asm)"); auto state = std::make_shared<AsarState>();
auto apply_button = Button("Apply Asar Patch", [&] { auto patch_file_input = Input(&state->patch_file, "Assembly patch file (.asm)");
if (patch_file.empty()) {
auto apply_button = Button("Apply Asar Patch", [state] {
if (state->patch_file.empty()) {
app_context.error_message = "Please specify an assembly patch file"; app_context.error_message = "Please specify an assembly patch file";
//SwitchComponents(screen, LayoutID::kError); //SwitchComponents(screen, LayoutID::kError);
return; return;
@@ -44,7 +48,7 @@ ftxui::Component AsarPatchComponent::Render() {
} }
auto rom_data = app_context.rom.vector(); auto rom_data = app_context.rom.vector();
auto patch_result = wrapper.ApplyPatch(patch_file, rom_data); auto patch_result = wrapper.ApplyPatch(state->patch_file, rom_data);
if (!patch_result.ok()) { if (!patch_result.ok()) {
app_context.error_message = absl::StrCat("Patch failed: ", patch_result.status().message()); app_context.error_message = absl::StrCat("Patch failed: ", patch_result.status().message());
@@ -59,18 +63,18 @@ ftxui::Component AsarPatchComponent::Render() {
return; return;
} }
output_message = absl::StrFormat( state->output_message = absl::StrFormat(
"✅ Patch applied successfully!\n" "✅ Patch applied successfully!\n"
"📊 ROM size: %d bytes\n" "📊 ROM size: %d bytes\n"
"🏷️ Symbols found: %d", "🏷️ Symbols found: %d",
result.rom_size, result.symbols.size()); result.rom_size, result.symbols.size());
symbols_list.clear(); state->symbols_list.clear();
for (const auto& symbol : result.symbols) { for (const auto& symbol : result.symbols) {
symbols_list.push_back(absl::StrFormat("% -20s @ $%06X", state->symbols_list.push_back(absl::StrFormat("% -20s @ $%06X",
symbol.name, symbol.address)); symbol.name, symbol.address));
} }
show_symbols = !symbols_list.empty(); state->show_symbols = !state->symbols_list.empty();
} catch (const std::exception& e) { } catch (const std::exception& e) {
app_context.error_message = "Exception: " + std::string(e.what()); app_context.error_message = "Exception: " + std::string(e.what());
@@ -78,28 +82,60 @@ ftxui::Component AsarPatchComponent::Render() {
} }
}); });
auto show_symbols_button = Button("Show Symbols", [&] { auto show_symbols_button = Button("Show Symbols", [state] {
show_symbols = !show_symbols; state->show_symbols = !state->show_symbols;
}); });
auto back_button = Button("Back to Main Menu", [&] { auto back_button = Button("Back to Main Menu", [state] {
output_message.clear(); state->output_message.clear();
symbols_list.clear(); state->symbols_list.clear();
show_symbols = false; state->show_symbols = false;
//SwitchComponents(screen, LayoutID::kMainMenu); //SwitchComponents(screen, LayoutID::kMainMenu);
}); });
std::vector<Component> container_items = { auto container = Container::Vertical({
patch_file_input, patch_file_input,
apply_button, apply_button,
}; show_symbols_button,
back_button,
});
if (!output_message.empty()) { return Renderer(container, [patch_file_input, apply_button, show_symbols_button,
container_items.push_back(show_symbols_button); back_button, state] {
} std::vector<Element> elements = {
container_items.push_back(back_button); text("Apply Asar Patch") | bold | center,
separator(),
text("Patch File:"),
patch_file_input->Render(),
separator(),
apply_button->Render() | center,
};
return Container::Vertical(container_items); if (!state->output_message.empty()) {
elements.push_back(separator());
elements.push_back(text(state->output_message) | color(Color::Green));
if (state->show_symbols && !state->symbols_list.empty()) {
elements.push_back(separator());
elements.push_back(text("Symbols:") | bold);
elements.push_back(show_symbols_button->Render() | center);
std::vector<Element> symbol_elements;
for (const auto& symbol : state->symbols_list) {
symbol_elements.push_back(text(symbol) | color(Color::Cyan));
}
elements.push_back(vbox(symbol_elements) | frame | size(HEIGHT, LESS_THAN, 10));
} else if (!state->symbols_list.empty()) {
elements.push_back(separator());
elements.push_back(show_symbols_button->Render() | center);
}
}
elements.push_back(separator());
elements.push_back(back_button->Render() | center);
return vbox(elements) | center | border;
});
} }
} // namespace cli } // namespace cli

View File

@@ -2,7 +2,6 @@
#include <ftxui/component/screen_interactive.hpp> #include <ftxui/component/screen_interactive.hpp>
#include <ftxui/dom/elements.hpp> #include <ftxui/dom/elements.hpp>
#include "cli/util/autocomplete.h" #include "cli/util/autocomplete.h"
#include "cli/tui/tui.h"
namespace yaze { namespace yaze {
namespace cli { namespace cli {
@@ -13,7 +12,7 @@ Component CreateAutocompleteInput(std::string* input_str,
AutocompleteEngine* engine) { AutocompleteEngine* engine) {
auto input = Input(input_str, "Type command..."); auto input = Input(input_str, "Type command...");
return Renderer(input, [=] { return Renderer(input, [input, input_str, engine] {
auto suggestions = engine->GetSuggestions(*input_str); auto suggestions = engine->GetSuggestions(*input_str);
Elements suggestion_list; Elements suggestion_list;
@@ -49,16 +48,19 @@ Component CreateAutocompleteInput(std::string* input_str,
Component CreateQuickActionMenu(ScreenInteractive& screen) { Component CreateQuickActionMenu(ScreenInteractive& screen) {
// Note: This function is a placeholder for future quick action menu integration. // Note: This function is a placeholder for future quick action menu integration.
// Currently not used in the TUI, but kept for API compatibility. // Currently not used in the TUI, but kept for API compatibility.
static int selected = 0; struct MenuState {
static const std::vector<std::string> actions = { int selected = 0;
"Quick Actions Menu - Not Yet Implemented", std::vector<std::string> actions = {
"⬅️ Back", "Quick Actions Menu - Not Yet Implemented",
"⬅️ Back",
};
}; };
auto menu = Menu(&actions, &selected); auto state = std::make_shared<MenuState>();
auto menu = Menu(&state->actions, &state->selected);
return CatchEvent(menu, [&](Event e) { return CatchEvent(menu, [&screen, state](const Event& e) {
if (e == Event::Return && selected == 1) { if (e == Event::Return && state->selected == 1) {
screen.ExitLoopClosure()(); screen.ExitLoopClosure()();
return true; return true;
} }

View File

@@ -4,7 +4,6 @@
#include "absl/strings/str_cat.h" #include "absl/strings/str_cat.h"
#include "absl/strings/str_format.h" #include "absl/strings/str_format.h"
#include "ftxui/component/captured_mouse.hpp"
#include "ftxui/component/component.hpp" #include "ftxui/component/component.hpp"
#include "ftxui/component/component_base.hpp" #include "ftxui/component/component_base.hpp"
#include "ftxui/component/event.hpp" #include "ftxui/component/event.hpp"
@@ -12,7 +11,7 @@
#include "ftxui/dom/elements.hpp" #include "ftxui/dom/elements.hpp"
#include "ftxui/dom/table.hpp" #include "ftxui/dom/table.hpp"
#include "app/rom.h" #include "app/rom.h"
#include "autocomplete_ui.h" #include "cli/tui/autocomplete_ui.h"
namespace yaze { namespace yaze {
namespace cli { namespace cli {
@@ -41,129 +40,156 @@ void ChatTUI::SetRomContext(Rom* rom_context) {
} }
void ChatTUI::InitializeAutocomplete() { void ChatTUI::InitializeAutocomplete() {
autocomplete_engine_.RegisterCommand("/help", "Show help message."); autocomplete_engine_.RegisterCommand("/help", "Show help message");
autocomplete_engine_.RegisterCommand("/exit", "Exit the chat."); autocomplete_engine_.RegisterCommand("/exit", "Exit the chat");
autocomplete_engine_.RegisterCommand("/clear", "Clear chat history."); autocomplete_engine_.RegisterCommand("/quit", "Exit the chat");
autocomplete_engine_.RegisterCommand("/rom_info", "Display info about the loaded ROM."); autocomplete_engine_.RegisterCommand("/clear", "Clear chat history");
autocomplete_engine_.RegisterCommand("/rom_info", "Display ROM information");
autocomplete_engine_.RegisterCommand("/status", "Show chat statistics");
// Add common prompts
autocomplete_engine_.RegisterCommand("What is", "Ask a question about ROM");
autocomplete_engine_.RegisterCommand("How do I", "Get help with a task");
autocomplete_engine_.RegisterCommand("Show me", "Request information");
autocomplete_engine_.RegisterCommand("List all", "List items from ROM");
autocomplete_engine_.RegisterCommand("Find", "Search for something");
} }
void ChatTUI::Run() { void ChatTUI::Run() {
std::string input_message; auto input_message = std::make_shared<std::string>();
auto input_component = CreateAutocompleteInput(&input_message, &autocomplete_engine_);
input_component->TakeFocus();
auto send_button = Button("Send", [&] { // Create autocomplete input component
if (input_message.empty()) return; auto input_component = CreateAutocompleteInput(input_message.get(), &autocomplete_engine_);
OnSubmit(input_message);
input_message.clear();
});
// Handle 'Enter' key in the input field. // Handle Enter key BEFORE adding to container
input_component = CatchEvent(input_component, [&](Event event) { input_component = CatchEvent(input_component, [this, input_message](const Event& event) {
if (event == Event::Return) { if (event == Event::Return) {
if (input_message.empty()) return true; if (input_message->empty()) return true;
OnSubmit(input_message); OnSubmit(*input_message);
input_message.clear(); input_message->clear();
return true; return true;
} }
return false; return false;
}); });
int selected_message = 0; auto send_button = Button("Send", [this, input_message] {
auto history_container = Container::Vertical({}, &selected_message); if (input_message->empty()) return;
OnSubmit(*input_message);
auto main_container = Container::Vertical({ input_message->clear();
history_container,
input_component,
}); });
auto main_renderer = Renderer(main_container, [&] { // Add both input and button to container
auto input_container = Container::Horizontal({
input_component,
send_button,
});
input_component->TakeFocus();
auto main_renderer = Renderer(input_container, [this, input_component, send_button] {
const auto& history = agent_service_.GetHistory(); const auto& history = agent_service_.GetHistory();
if (history.size() != history_container->ChildCount()) {
history_container->DetachAllChildren();
for (const auto& msg : history) {
Element header = text(msg.sender == agent::ChatMessage::Sender::kUser
? "You"
: "Agent") |
bold |
color(msg.sender == agent::ChatMessage::Sender::kUser
? Color::Yellow
: Color::Green);
Element body; // Build history view from current history state
if (msg.table_data.has_value()) { std::vector<Element> history_elements;
std::vector<std::vector<std::string>> table_rows;
table_rows.reserve(msg.table_data->rows.size() + 1); for (const auto& msg : history) {
Element header = text(msg.sender == agent::ChatMessage::Sender::kUser ? "You" : "Agent") |
bold |
color(msg.sender == agent::ChatMessage::Sender::kUser ? Color::Yellow : Color::Green);
Element body;
if (msg.table_data.has_value()) {
std::vector<std::vector<std::string>> table_rows;
if (!msg.table_data->headers.empty()) {
table_rows.push_back(msg.table_data->headers); table_rows.push_back(msg.table_data->headers);
for (const auto& row : msg.table_data->rows) {
table_rows.push_back(row);
}
Table table(table_rows);
table.SelectAll().Border(LIGHT);
if (!table_rows.empty()) {
table.SelectRow(0).Decorate(bold);
}
body = table.Render();
} else if (msg.json_pretty.has_value()) {
body = paragraph(msg.json_pretty.value());
} else {
body = paragraph(msg.message);
} }
for (const auto& row : msg.table_data->rows) {
Elements block = {header, hbox({text(" "), body})}; table_rows.push_back(row);
if (msg.metrics.has_value()) {
const auto& metrics = msg.metrics.value();
block.push_back(text(absl::StrFormat(
" 📊 Turn %d — users:%d agents:%d tools:%d commands:%d proposals:%d elapsed %.2fs avg %.2fs",
metrics.turn_index, metrics.total_user_messages,
metrics.total_agent_messages, metrics.total_tool_calls,
metrics.total_commands, metrics.total_proposals,
metrics.total_elapsed_seconds,
metrics.average_latency_seconds)) | color(Color::Cyan));
} }
block.push_back(separator()); Table table(table_rows);
history_container->Add(Renderer([block = vbox(block)] { return block; })); table.SelectAll().Border(LIGHT);
if (!msg.table_data->headers.empty()) {
table.SelectRow(0).Decorate(bold);
}
body = table.Render();
} else if (msg.json_pretty.has_value()) {
// Word wrap for JSON
body = paragraphAlignLeft(msg.json_pretty.value());
} else {
// Word wrap for regular messages
body = paragraphAlignLeft(msg.message);
} }
selected_message = history.empty() ? 0 : history.size() - 1;
auto message_block = vbox({
header,
separator(),
body,
});
if (msg.metrics.has_value()) {
const auto& metrics = msg.metrics.value();
message_block = vbox({
message_block,
separator(),
text(absl::StrFormat(
"📊 Turn %d | Elapsed: %.2fs",
metrics.turn_index, metrics.total_elapsed_seconds)) | color(Color::Cyan) | dim
});
}
history_elements.push_back(message_block | border);
} }
auto history_view = history_container->Render() | flex | frame; Element history_view;
if (history.empty()) { if (history.empty()) {
history_view = vbox({text("No messages yet. Start chatting!") | dim}) | flex | center; history_view = vbox({text("No messages yet. Start chatting!") | dim}) | flex | center;
} else {
history_view = vbox(history_elements) | vscroll_indicator | frame | flex;
} }
Elements layout_elements = {
text(rom_header_) | bold | center,
separator(),
history_view,
separator(),
};
// Add metrics bar
const auto metrics = agent_service_.GetMetrics(); const auto metrics = agent_service_.GetMetrics();
Element metrics_bar = text(absl::StrFormat( layout_elements.push_back(
"Turns:%d Users:%d Agents:%d Tools:%d Commands:%d Proposals:%d Elapsed:%.2fs avg %.2fs", hbox({
metrics.turn_index, metrics.total_user_messages, text(absl::StrFormat("Turns:%d", metrics.turn_index)),
metrics.total_agent_messages, metrics.total_tool_calls, separator(),
metrics.total_commands, metrics.total_proposals, text(absl::StrFormat("User:%d", metrics.total_user_messages)),
metrics.total_elapsed_seconds, metrics.average_latency_seconds)) | separator(),
color(Color::Cyan); text(absl::StrFormat("Agent:%d", metrics.total_agent_messages)),
separator(),
text(absl::StrFormat("Tools:%d", metrics.total_tool_calls)),
}) | color(Color::GrayLight)
);
auto input_view = hbox({ // Add error if present
text("You: ") | bold,
input_component->Render() | flex,
text(" "),
send_button->Render(),
});
Element error_view;
if (last_error_.has_value()) { if (last_error_.has_value()) {
error_view = text(absl::StrCat("", *last_error_)) | color(Color::Red); layout_elements.push_back(separator());
layout_elements.push_back(
text(absl::StrCat("⚠ ERROR: ", *last_error_)) | color(Color::Red)
);
} }
return gridbox({ // Add input area
{text(rom_header_) | center}, layout_elements.push_back(separator());
{separator()}, layout_elements.push_back(
{history_view}, input_component->Render() | flex
{separator()}, );
{metrics_bar}, layout_elements.push_back(
{error_view ? separator() : filler()}, hbox({
{error_view ? error_view : filler()}, text("Press Enter to send | ") | dim,
{separator()}, send_button->Render(),
{input_view}, text(" | /help for commands") | dim,
}) | border; }) | center
);
return vbox(layout_elements) | border;
}); });
screen_.Loop(main_renderer); screen_.Loop(main_renderer);
@@ -174,16 +200,16 @@ void ChatTUI::OnSubmit(const std::string& message) {
return; return;
} }
if (message == "/exit") { if (message == "/exit" || message == "/quit") {
screen_.Exit(); screen_.Exit();
return; return;
} }
if (message == "/clear") { if (message == "/clear") {
agent_service_.ResetConversation(); agent_service_.ResetConversation();
// The renderer will see history is empty and detach children
return; return;
} }
if (message == "/rom_info") { if (message == "/rom_info") {
// Send ROM info as a user message to get a response
auto response = agent_service_.SendMessage("Show me information about the loaded ROM"); auto response = agent_service_.SendMessage("Show me information about the loaded ROM");
if (!response.ok()) { if (!response.ok()) {
last_error_ = response.status().message(); last_error_ = response.status().message();
@@ -193,7 +219,6 @@ void ChatTUI::OnSubmit(const std::string& message) {
return; return;
} }
if (message == "/help") { if (message == "/help") {
// Send help request as a user message
auto response = agent_service_.SendMessage("What commands can I use?"); auto response = agent_service_.SendMessage("What commands can I use?");
if (!response.ok()) { if (!response.ok()) {
last_error_ = response.status().message(); last_error_ = response.status().message();
@@ -202,6 +227,37 @@ void ChatTUI::OnSubmit(const std::string& message) {
} }
return; return;
} }
if (message == "/status") {
const auto metrics = agent_service_.GetMetrics();
std::string status_message = absl::StrFormat(
"Chat Statistics:\n"
"- Total Turns: %d\n"
"- User Messages: %d\n"
"- Agent Messages: %d\n"
"- Tool Calls: %d\n"
"- Commands: %d\n"
"- Proposals: %d\n"
"- Total Elapsed Time: %.2f seconds\n"
"- Average Latency: %.2f seconds",
metrics.turn_index,
metrics.total_user_messages,
metrics.total_agent_messages,
metrics.total_tool_calls,
metrics.total_commands,
metrics.total_proposals,
metrics.total_elapsed_seconds,
metrics.average_latency_seconds
);
// Add a system message with status
auto response = agent_service_.SendMessage(status_message);
if (!response.ok()) {
last_error_ = response.status().message();
} else {
last_error_.reset();
}
return;
}
auto response = agent_service_.SendMessage(message); auto response = agent_service_.SendMessage(message);
if (!response.ok()) { if (!response.ok()) {

View File

@@ -8,17 +8,44 @@
#include "cli/tui/tui.h" #include "cli/tui/tui.h"
#include "cli/handlers/agent/hex_commands.h" #include "cli/handlers/agent/hex_commands.h"
#include "cli/handlers/agent/palette_commands.h" #include "cli/handlers/agent/palette_commands.h"
#include "absl/strings/str_split.h"
namespace yaze { namespace yaze {
namespace cli { namespace cli {
using namespace ftxui; using namespace ftxui;
namespace {
// A simple fuzzy search implementation
int fuzzy_match(const std::string& query, const std::string& target) {
if (query.empty()) return 1;
if (target.empty()) return 0;
int score = 0;
int query_idx = 0;
int target_idx = 0;
int consecutive_matches = 0;
while (query_idx < query.length() && target_idx < target.length()) {
if (std::tolower(query[query_idx]) == std::tolower(target[target_idx])) {
score += 1 + consecutive_matches;
consecutive_matches++;
query_idx++;
} else {
consecutive_matches = 0;
}
target_idx++;
}
return (query_idx == query.length()) ? score : 0;
}
}
Component CommandPaletteComponent::Render() { Component CommandPaletteComponent::Render() {
static std::string query; struct PaletteState {
static int selected = 0; std::string query;
static std::string status_msg; int selected = 0;
std::string status_msg;
};
struct Cmd { struct Cmd {
std::string name; std::string name;
@@ -26,8 +53,11 @@ Component CommandPaletteComponent::Render() {
std::string desc; std::string desc;
std::string usage; std::string usage;
std::function<absl::Status()> exec; std::function<absl::Status()> exec;
int score = 0;
}; };
auto state = std::make_shared<PaletteState>();
static std::vector<Cmd> cmds = { static std::vector<Cmd> cmds = {
{"hex-read", "🔢 Hex", "Read ROM bytes", {"hex-read", "🔢 Hex", "Read ROM bytes",
"--address=0x1C800 --length=16 --format=both", "--address=0x1C800 --length=16 --format=both",
@@ -54,90 +84,145 @@ Component CommandPaletteComponent::Render() {
[]() { return agent::HandlePaletteAnalyze({"--type=palette", "--id=0/0"}, &app_context.rom); }}, []() { return agent::HandlePaletteAnalyze({"--type=palette", "--id=0/0"}, &app_context.rom); }},
}; };
// Fuzzy filter auto search_input = Input(&state->query, "Search commands...");
std::vector<int> filtered_idx;
std::string q_lower = query;
std::transform(q_lower.begin(), q_lower.end(), q_lower.begin(), ::tolower);
for (size_t i = 0; i < cmds.size(); ++i) { auto menu = Renderer([state] {
if (query.empty()) { std::vector<int> filtered_idx;
filtered_idx.push_back(i); if (state->query.empty()) {
for (size_t i = 0; i < cmds.size(); ++i) {
filtered_idx.push_back(i);
}
} else { } else {
std::string n = cmds[i].name; for (size_t i = 0; i < cmds.size(); ++i) {
std::transform(n.begin(), n.end(), n.begin(), ::tolower); cmds[i].score = fuzzy_match(state->query, cmds[i].name);
if (n.find(q_lower) != std::string::npos) { if (cmds[i].score > 0) {
filtered_idx.push_back(i); filtered_idx.push_back(i);
}
}
std::sort(filtered_idx.begin(), filtered_idx.end(), [](int a, int b) {
return cmds[a].score > cmds[b].score;
});
}
Elements items;
for (size_t i = 0; i < filtered_idx.size(); ++i) {
int idx = filtered_idx[i];
auto item = hbox({
text(cmds[idx].cat) | color(Color::GrayLight),
text(" "),
text(cmds[idx].name) | bold,
});
if (static_cast<int>(i) == state->selected) {
item = item | inverted | focus;
} }
items.push_back(item);
} }
} return vbox(items) | vscroll_indicator | frame;
auto search_input = Input(&query, "Search...");
std::vector<std::string> menu_items;
for (int idx : filtered_idx) {
menu_items.push_back(cmds[idx].cat + " " + cmds[idx].name);
}
auto menu = Menu(&menu_items, &selected);
auto exec_btn = Button("Execute", [&] {
if (selected < static_cast<int>(filtered_idx.size())) {
int cmd_idx = filtered_idx[selected];
auto status = cmds[cmd_idx].exec();
status_msg = status.ok() ? "✓ Success" : "" + std::string(status.message());
}
}); });
auto back_btn = Button("Back", [&] { auto execute_command = [state] {
std::vector<int> filtered_idx;
if (state->query.empty()) {
for (size_t i = 0; i < cmds.size(); ++i) {
filtered_idx.push_back(i);
}
} else {
for (size_t i = 0; i < cmds.size(); ++i) {
cmds[i].score = fuzzy_match(state->query, cmds[i].name);
if (cmds[i].score > 0) {
filtered_idx.push_back(i);
}
}
std::sort(filtered_idx.begin(), filtered_idx.end(), [](int a, int b) {
return cmds[a].score > cmds[b].score;
});
}
if (state->selected < static_cast<int>(filtered_idx.size())) {
int cmd_idx = filtered_idx[state->selected];
auto status = cmds[cmd_idx].exec();
state->status_msg = status.ok() ? "✓ Success: Command executed." : "✗ Error: " + std::string(status.message());
}
};
auto back_btn = Button("Back", [] {
app_context.current_layout = LayoutID::kMainMenu; app_context.current_layout = LayoutID::kMainMenu;
ScreenInteractive::Active()->ExitLoopClosure()(); ScreenInteractive::Active()->ExitLoopClosure()();
}); });
auto container = Container::Vertical({search_input, menu, exec_btn, back_btn}); auto container = Container::Vertical({search_input, menu, back_btn});
return CatchEvent(Renderer(container, [&] { return Renderer(container, [container, search_input, menu, back_btn, state] {
Elements items; std::vector<int> filtered_idx;
for (size_t i = 0; i < filtered_idx.size(); ++i) { if (state->query.empty()) {
int idx = filtered_idx[i]; for (size_t i = 0; i < cmds.size(); ++i) {
auto item = text(cmds[idx].cat + " " + cmds[idx].name); filtered_idx.push_back(i);
if (static_cast<int>(i) == selected) { }
item = item | bold | inverted | color(Color::Cyan); } else {
} for (size_t i = 0; i < cmds.size(); ++i) {
items.push_back(item); cmds[i].score = fuzzy_match(state->query, cmds[i].name);
if (cmds[i].score > 0) {
filtered_idx.push_back(i);
}
}
std::sort(filtered_idx.begin(), filtered_idx.end(), [](int a, int b) {
return cmds[a].score > cmds[b].score;
});
} }
// Show selected command details Element details = text("Select a command to see details.") | dim;
Element details = text(""); if (state->selected < static_cast<int>(filtered_idx.size())) {
if (selected < static_cast<int>(filtered_idx.size())) { int idx = filtered_idx[state->selected];
int idx = filtered_idx[selected];
details = vbox({ details = vbox({
text("Description: " + cmds[idx].desc) | color(Color::GreenLight), text(cmds[idx].desc) | bold,
text("Usage: " + cmds[idx].usage) | color(Color::Yellow) | dim, separator(),
text("Usage: " + cmds[idx].name + " " + cmds[idx].usage) | color(Color::Cyan),
}); });
} }
return vbox({ return vbox({
text("⚡ Command Palette") | bold | center | color(Color::Cyan), text("⚡ Command Palette") | bold | center | color(Color::Cyan),
text(app_context.rom.is_loaded() ? "ROM: " + app_context.rom.title() : "No ROM") | center | dim, text(app_context.rom.is_loaded() ? "ROM: " + app_context.rom.title() : "No ROM loaded") | center | dim,
separator(), separator(),
hbox({text("🔍 "), search_input->Render() | flex}), hbox({text("🔍 "), search_input->Render() | flex}),
separator(), separator(),
vbox(items) | frame | flex | vscroll_indicator, hbox({
menu->Render() | flex,
separator(),
details | flex,
}),
separator(), separator(),
details, hbox({ back_btn->Render() }) | center,
separator(), separator(),
hbox({exec_btn->Render(), text(" "), back_btn->Render()}) | center, text(state->status_msg) | center | (state->status_msg.find("") != 0 ? color(Color::Green) : color(Color::Red)),
separator(), text("↑↓: Navigate | Enter: Execute | Esc: Back") | center | dim,
text(status_msg) | center | (status_msg.find("") == 0 ? color(Color::Green) : color(Color::Red)), }) | border | flex;
text("Enter=Execute | ↑↓=Navigate | Esc=Back") | center | dim, }) | CatchEvent([state, execute_command](const Event& e) {
}) | border | size(WIDTH, EQUAL, 80) | size(HEIGHT, EQUAL, 30); if (e == Event::Return) {
}), [&](Event e) { execute_command();
if (e == Event::Return && selected < static_cast<int>(filtered_idx.size())) {
int idx = filtered_idx[selected];
auto status = cmds[idx].exec();
status_msg = status.ok() ? "✓ Executed" : "" + std::string(status.message());
return true; return true;
} }
if (e == Event::ArrowUp) {
if (state->selected > 0) state->selected--;
return true;
}
if (e == Event::ArrowDown) {
// Calculate filtered_idx size
std::vector<int> filtered_idx;
if (state->query.empty()) {
for (size_t i = 0; i < cmds.size(); ++i) {
filtered_idx.push_back(i);
}
} else {
for (size_t i = 0; i < cmds.size(); ++i) {
cmds[i].score = fuzzy_match(state->query, cmds[i].name);
if (cmds[i].score > 0) {
filtered_idx.push_back(i);
}
}
}
if (state->selected < static_cast<int>(filtered_idx.size()) - 1) state->selected++;
return true;
}
return false; return false;
}); });
} }

View File

@@ -1,289 +0,0 @@
#include "cli/tui/tui.h"
#include <ftxui/component/component.hpp>
#include <ftxui/component/screen_interactive.hpp>
#include <ftxui/dom/elements.hpp>
#include "absl/strings/str_format.h"
#include "cli/service/agent/simple_chat_session.h"
namespace yaze {
namespace cli {
using namespace ftxui;
// Enhanced main menu with better organization and icons
Component EnhancedMainMenu(ScreenInteractive& screen, int& selected) {
auto menu_renderer = Renderer([&] {
Elements menu_items;
for (size_t i = 0; i < kMainMenuEntries.size(); ++i) {
auto item = text(kMainMenuEntries[i]);
if (i == selected) {
item = item | bold | color(Color::Cyan) | inverted;
} else {
item = item | color(Color::GreenLight);
}
menu_items.push_back(item);
}
// Show ROM status
std::string rom_status = app_context.rom.is_loaded()
? absl::StrFormat("📀 ROM: %s", app_context.rom.title())
: "⚠️ No ROM loaded";
return vbox({
// Header
text("Z3ED - Yet Another Zelda3 Editor") | bold | center | color(Color::Yellow),
text("v0.3.0") | center | color(Color::GrayDark),
separator(),
// ROM status
text(rom_status) | center | color(app_context.rom.is_loaded() ? Color::Green : Color::Red),
separator(),
// Menu
vbox(menu_items) | flex,
separator(),
// Footer with controls
hbox({
text("Navigate: ") | color(Color::GrayLight),
text("↑↓/jk") | bold | color(Color::Cyan),
text(" | Select: ") | color(Color::GrayLight),
text("Enter") | bold | color(Color::Cyan),
text(" | Quit: ") | color(Color::GrayLight),
text("q") | bold | color(Color::Red),
}) | center,
}) | border | center;
});
return CatchEvent(menu_renderer, [&](Event event) {
if (event == Event::ArrowDown || event == Event::Character('j')) {
selected = (selected + 1) % kMainMenuEntries.size();
return true;
}
if (event == Event::ArrowUp || event == Event::Character('k')) {
selected = (selected - 1 + kMainMenuEntries.size()) % kMainMenuEntries.size();
return true;
}
if (event == Event::Character('q')) {
app_context.current_layout = LayoutID::kExit;
screen.ExitLoopClosure()();
return true;
}
if (event == Event::Return) {
screen.ExitLoopClosure()();
return true;
}
return false;
});
}
// Quick ROM loader with recent files
Component QuickRomLoader(ScreenInteractive& screen) {
static std::string rom_path;
static std::vector<std::string> recent_files;
static int selected_recent = 0;
// Load recent files (TODO: from actual recent files manager)
if (recent_files.empty()) {
recent_files = {
"~/roms/zelda3.sfc",
"~/roms/alttp_modified.sfc",
"~/roms/custom_hack.sfc",
};
}
auto input = Input(&rom_path, "Enter ROM path or select below");
auto load_button = Button("Load ROM", [&] {
if (!rom_path.empty()) {
auto status = app_context.rom.LoadFromFile(rom_path);
if (status.ok()) {
app_context.current_layout = LayoutID::kMainMenu;
screen.ExitLoopClosure()();
} else {
app_context.error_message = std::string(status.message());
app_context.current_layout = LayoutID::kError;
screen.ExitLoopClosure()();
}
}
});
auto back_button = Button("Back", [&] {
app_context.current_layout = LayoutID::kMainMenu;
screen.ExitLoopClosure()();
});
auto container = Container::Vertical({input, load_button, back_button});
return Renderer(container, [&] {
Elements recent_elements;
for (size_t i = 0; i < recent_files.size(); ++i) {
auto item = text(recent_files[i]);
if (i == selected_recent) {
item = item | bold | inverted;
}
recent_elements.push_back(item);
}
return vbox({
text("🎮 Load ROM") | bold | center | color(Color::Cyan),
separator(),
hbox({
text("Path: "),
input->Render() | flex,
}),
separator(),
text("Recent ROMs:") | bold,
vbox(recent_elements),
separator(),
hbox({
load_button->Render(),
text(" "),
back_button->Render(),
}) | center,
separator(),
text("Tip: Press Enter to load, Tab to cycle, Esc to cancel") | dim | center,
}) | border | center | size(WIDTH, GREATER_THAN, 60);
});
}
// Agent chat interface in TUI
Component AgentChatTUI(ScreenInteractive& screen) {
static std::vector<std::string> messages;
static std::string input_text;
static bool is_processing = false;
auto input = Input(&input_text, "Type your message...");
auto send_button = Button("Send", [&] {
if (!input_text.empty() && !is_processing) {
messages.push_back("You: " + input_text);
// TODO: Actually call agent service
is_processing = true;
messages.push_back("Agent: [Processing...]");
input_text.clear();
// Simulate async response
// In real implementation, use SimpleChatSession
is_processing = false;
messages.back() = "Agent: I can help with that!";
}
});
auto back_button = Button("Back", [&] {
app_context.current_layout = LayoutID::kMainMenu;
screen.ExitLoopClosure()();
});
auto container = Container::Vertical({input, send_button, back_button});
return Renderer(container, [&] {
Elements message_elements;
for (const auto& msg : messages) {
Color msg_color = (msg.rfind("You:", 0) == 0) ? Color::Cyan : Color::GreenLight;
message_elements.push_back(text(msg) | color(msg_color));
}
return vbox({
text("🤖 AI Agent Chat") | bold | center | color(Color::Yellow),
text(app_context.rom.is_loaded()
? absl::StrFormat("ROM: %s", app_context.rom.title())
: "No ROM loaded") | center | dim,
separator(),
// Chat history
vbox(message_elements) | flex | vscroll_indicator | frame,
separator(),
// Input area
hbox({
text("Message: "),
input->Render() | flex,
}),
hbox({
send_button->Render(),
text(" "),
back_button->Render(),
}) | center,
separator(),
text("Shortcuts: Enter=Send | Ctrl+C=Cancel | Esc=Back") | dim | center,
}) | border | flex;
});
}
// ROM tools submenu
Component RomToolsMenu(ScreenInteractive& screen) {
static int selected = 0;
static const std::vector<std::string> tools = {
"🔍 ROM Info & Analysis",
"🔧 Apply Asar Patch",
"📦 Apply BPS Patch",
"🏷️ Extract Symbols",
"✅ Validate Assembly",
"💾 Generate Save File",
"⬅️ Back to Main Menu",
};
auto menu = Menu(&tools, &selected);
return CatchEvent(menu, [&](Event event) {
if (event == Event::Return) {
switch (selected) {
case 0: /* ROM Info */ break;
case 1: app_context.current_layout = LayoutID::kApplyAsarPatch; break;
case 2: app_context.current_layout = LayoutID::kApplyBpsPatch; break;
case 3: app_context.current_layout = LayoutID::kExtractSymbols; break;
case 4: app_context.current_layout = LayoutID::kValidateAssembly; break;
case 5: app_context.current_layout = LayoutID::kGenerateSaveFile; break;
case 6: app_context.current_layout = LayoutID::kMainMenu; break;
}
screen.ExitLoopClosure()();
return true;
}
return false;
});
}
// Graphics tools submenu
Component GraphicsToolsMenu(ScreenInteractive& screen) {
static int selected = 0;
static const std::vector<std::string> tools = {
"🎨 Palette Editor",
"🔢 Hex Viewer",
"🖼️ Graphics Sheet Viewer",
"📊 Color Analysis",
"⬅️ Back to Main Menu",
};
auto menu = Menu(&tools, &selected);
return CatchEvent(menu, [&](Event event) {
if (event == Event::Return) {
switch (selected) {
case 0: app_context.current_layout = LayoutID::kPaletteEditor; break;
case 1: app_context.current_layout = LayoutID::kHexViewer; break;
case 2: /* Graphics viewer */ break;
case 3: /* Color analysis */ break;
case 4: app_context.current_layout = LayoutID::kMainMenu; break;
}
screen.ExitLoopClosure()();
return true;
}
return false;
});
}
} // namespace cli
} // namespace yaze

View File

@@ -6,7 +6,6 @@
#include <iomanip> #include <iomanip>
#include <sstream> #include <sstream>
#include "absl/strings/str_cat.h"
#include "absl/strings/str_format.h" #include "absl/strings/str_format.h"
namespace yaze { namespace yaze {
@@ -29,7 +28,7 @@ EnhancedStatusPanel::EnhancedStatusPanel(Rom* rom_context)
status_container_ = CreateStatusContainer(); status_container_ = CreateStatusContainer();
// Set up event handlers // Set up event handlers
status_event_handler_ = [this](Event event) { status_event_handler_ = [this](const Event& event) {
return HandleStatusEvents(event); return HandleStatusEvents(event);
}; };
} }

99
src/cli/tui/hex_viewer.cc Normal file
View File

@@ -0,0 +1,99 @@
#include "cli/tui/hex_viewer.h"
#include <ftxui/component/component.hpp>
#include <ftxui/dom/elements.hpp>
#include "absl/strings/str_format.h"
namespace yaze {
namespace cli {
using namespace ftxui;
HexViewerComponent::HexViewerComponent(Rom* rom, std::function<void()> on_back)
: rom_(rom), on_back_(std::move(on_back)) {}
ftxui::Component HexViewerComponent::Render() {
if (component_) {
return component_;
}
auto renderer = Renderer([this] {
if (!rom_ || !rom_->is_loaded()) {
return vbox({
text("Hex Viewer") | bold | center,
separator(),
text("No ROM loaded.") | center | color(Color::Red),
}) | border;
}
std::vector<Element> rows;
for (int i = 0; i < lines_to_show_; ++i) {
int current_offset = offset_ + (i * 16);
if (current_offset >= static_cast<int>(rom_->size())) {
break;
}
Elements row;
row.push_back(text(absl::StrFormat("0x%08X: ", current_offset)) | color(Color::Yellow));
for (int j = 0; j < 16; ++j) {
if (current_offset + j < static_cast<int>(rom_->size())) {
row.push_back(text(absl::StrFormat("%02X ", rom_->vector()[current_offset + j])));
} else {
row.push_back(text(" "));
}
}
row.push_back(separator());
for (int j = 0; j < 16; ++j) {
if (current_offset + j < static_cast<int>(rom_->size())) {
char c = rom_->vector()[current_offset + j];
row.push_back(text(std::isprint(c) ? std::string(1, c) : "."));
} else {
row.push_back(text(" "));
}
}
rows.push_back(hbox(row));
}
return vbox({
text("Hex Viewer") | center | bold,
separator(),
vbox(rows) | frame | flex,
separator(),
hbox({
text(absl::StrFormat("Offset: 0x%08X", offset_)),
filler(),
text("↑↓ PgUp/PgDn: Scroll | Esc/b: Back") | dim,
})
}) | border;
});
component_ = CatchEvent(renderer, [this](const Event& event) {
if (!rom_ || !rom_->is_loaded()) return false;
bool handled = false;
if (event == Event::ArrowUp) {
offset_ = std::max(0, offset_ - 16);
handled = true;
} else if (event == Event::ArrowDown) {
offset_ = std::min(static_cast<int>(rom_->size()) - (lines_to_show_ * 16), offset_ + 16);
handled = true;
} else if (event == Event::PageUp) {
offset_ = std::max(0, offset_ - (lines_to_show_ * 16));
handled = true;
} else if (event == Event::PageDown) {
offset_ = std::min(static_cast<int>(rom_->size()) - (lines_to_show_ * 16), offset_ + (lines_to_show_ * 16));
handled = true;
} else if (event == Event::Escape || event == Event::Character('b')) {
if (on_back_) on_back_();
handled = true;
}
return handled;
});
return component_;
}
} // namespace cli
} // namespace yaze

28
src/cli/tui/hex_viewer.h Normal file
View File

@@ -0,0 +1,28 @@
#ifndef YAZE_SRC_CLI_TUI_HEX_VIEWER_H_
#define YAZE_SRC_CLI_TUI_HEX_VIEWER_H_
#include <functional>
#include "cli/tui/tui_component.h"
#include "app/rom.h"
namespace yaze {
namespace cli {
class HexViewerComponent : public TuiComponent {
public:
explicit HexViewerComponent(Rom* rom, std::function<void()> on_back);
ftxui::Component Render() override;
private:
Rom* rom_;
std::function<void()> on_back_;
int offset_ = 0;
const int lines_to_show_ = 20;
ftxui::Component component_ = nullptr;
};
} // namespace cli
} // namespace yaze
#endif // YAZE_SRC_CLI_TUI_HEX_VIEWER_H_

View File

@@ -763,71 +763,6 @@ void HelpComponent(ftxui::ScreenInteractive &screen) {
screen.Loop(renderer); screen.Loop(renderer);
} }
void HexViewerComponent(ftxui::ScreenInteractive &screen) {
ReturnIfRomNotLoaded(screen);
auto back_button =
Button("Back", [&] { SwitchComponents(screen, LayoutID::kDashboard); });
static int offset = 0;
const int lines_to_show = 20;
auto renderer = Renderer(back_button, [&] {
std::vector<Element> rows;
for (int i = 0; i < lines_to_show; ++i) {
int current_offset = offset + (i * 16);
if (current_offset >= static_cast<int>(app_context.rom.size())) {
break;
}
Elements row;
row.push_back(text(absl::StrFormat("0x%08X: ", current_offset)) | color(Color::Yellow));
for (int j = 0; j < 16; ++j) {
if (current_offset + j < static_cast<int>(app_context.rom.size())) {
row.push_back(text(absl::StrFormat("%02X ", app_context.rom.vector()[current_offset + j])));
} else {
row.push_back(text(" "));
}
}
row.push_back(separator());
for (int j = 0; j < 16; ++j) {
if (current_offset + j < static_cast<int>(app_context.rom.size())) {
char c = app_context.rom.vector()[current_offset + j];
row.push_back(text(std::isprint(c) ? std::string(1, c) : "."));
} else {
row.push_back(text(" "));
}
}
rows.push_back(hbox(row));
}
return vbox({
text("Hex Viewer") | center | bold,
separator(),
vbox(rows) | frame | flex,
separator(),
text(absl::StrFormat("Offset: 0x%08X", offset)),
separator(),
back_button->Render() | center,
}) | border;
});
auto event_handler = CatchEvent(renderer, [&](Event event) {
if (event == Event::ArrowUp) offset = std::max(0, offset - 16);
if (event == Event::ArrowDown) offset = std::min(static_cast<int>(app_context.rom.size()) - (lines_to_show * 16), offset + 16);
if (event == Event::PageUp) offset = std::max(0, offset - (lines_to_show * 16));
if (event == Event::PageDown) offset = std::min(static_cast<int>(app_context.rom.size()) - (lines_to_show * 16), offset + (lines_to_show * 16));
if (event == Event::Character('q')) SwitchComponents(screen, LayoutID::kExit);
if (event == Event::Return) SwitchComponents(screen, LayoutID::kDashboard);
return false;
});
screen.Loop(event_handler);
}
void DashboardComponent(ftxui::ScreenInteractive &screen) { void DashboardComponent(ftxui::ScreenInteractive &screen) {
static int selected = 0; static int selected = 0;

View File

@@ -5,9 +5,7 @@
#include <ftxui/dom/elements.hpp> #include <ftxui/dom/elements.hpp>
#include <ftxui/screen/terminal.hpp> #include <ftxui/screen/terminal.hpp>
#include "absl/strings/str_cat.h"
#include "absl/strings/str_format.h" #include "absl/strings/str_format.h"
#include "cli/tui/tui.h"
#include "cli/service/agent/conversational_agent_service.h" #include "cli/service/agent/conversational_agent_service.h"
namespace yaze { namespace yaze {
@@ -78,18 +76,22 @@ void UnifiedLayout::SetRomContext(Rom* rom_context) {
void UnifiedLayout::SwitchMainPanel(PanelType panel) { void UnifiedLayout::SwitchMainPanel(PanelType panel) {
state_.active_main_panel = panel; state_.active_main_panel = panel;
screen_.PostEvent(Event::Custom); // Force screen refresh
} }
void UnifiedLayout::SwitchToolPanel(PanelType panel) { void UnifiedLayout::SwitchToolPanel(PanelType panel) {
state_.active_tool_panel = panel; state_.active_tool_panel = panel;
screen_.PostEvent(Event::Custom); // Force screen refresh
} }
void UnifiedLayout::ToggleChat() { void UnifiedLayout::ToggleChat() {
config_.show_chat = !config_.show_chat; config_.show_chat = !config_.show_chat;
screen_.PostEvent(Event::Custom); // Force screen refresh
} }
void UnifiedLayout::ToggleStatus() { void UnifiedLayout::ToggleStatus() {
config_.show_status = !config_.show_status; config_.show_status = !config_.show_status;
screen_.PostEvent(Event::Custom); // Force screen refresh
} }
void UnifiedLayout::SetLayoutConfig(const LayoutConfig& config) { void UnifiedLayout::SetLayoutConfig(const LayoutConfig& config) {
@@ -97,55 +99,104 @@ void UnifiedLayout::SetLayoutConfig(const LayoutConfig& config) {
} }
Component UnifiedLayout::CreateMainMenuPanel() { Component UnifiedLayout::CreateMainMenuPanel() {
static int selected = 0; struct MenuState {
int selected = 0;
std::vector<std::string> items = {
"🔍 Hex Viewer",
"🎨 Palette Editor",
"📝 TODO Manager",
"🔧 ROM Tools",
"🎮 Graphics Tools",
"⚙️ Settings",
"❓ Help",
"🚪 Exit"
};
};
auto state = std::make_shared<MenuState>();
MenuOption option; MenuOption option;
option.focused_entry = &selected; option.focused_entry = &state->selected;
option.on_enter = [this, state] {
switch (state->selected) {
case 0: SwitchMainPanel(PanelType::kHexViewer); break;
case 1: SwitchMainPanel(PanelType::kPaletteEditor); break;
case 2: SwitchMainPanel(PanelType::kTodoManager); break;
case 3: SwitchMainPanel(PanelType::kRomTools); break;
case 4: SwitchMainPanel(PanelType::kGraphicsTools); break;
case 5: SwitchMainPanel(PanelType::kSettings); break;
case 6: SwitchMainPanel(PanelType::kHelp); break;
case 7: screen_.Exit(); break;
}
};
auto menu = Menu(&kMainMenuEntries, &selected, option); auto menu = Menu(&state->items, &state->selected, option);
return Renderer(menu, [&] { return Renderer(menu, [this, menu, state] {
return vbox({ return vbox({
text("🎮 Z3ED Main Menu") | bold | center, text("🎮 Z3ED Main Menu") | bold | center,
separator(), separator(),
menu->Render(), menu->Render(),
separator(), separator(),
text("↑/↓: Navigate | Enter: Select | q: Quit") | dim | center text("↑/↓: Navigate | Enter: Select | q: Quit") | dim | center
}) | border; });
}); });
} }
Component UnifiedLayout::CreateChatPanel() { Component UnifiedLayout::CreateChatPanel() {
// Create a simplified chat interface that integrates with the main layout // Use the full-featured ChatTUI if available
static std::string input_message; if (chat_tui_) {
auto input_component = Input(&input_message, "Type your message..."); return Renderer([this] {
return vbox({
text("🤖 AI Chat Assistant") | bold | center | color(Color::Cyan),
separator(),
text("Press 'f' for full chat | 'c' to toggle") | center | dim,
separator(),
hbox({
text("Status: ") | bold,
text(rom_context_ ? "ROM Loaded ✓" : "No ROM") |
color(rom_context_ ? Color::Green : Color::Red)
}) | center,
separator(),
text("Features:") | bold | center,
text(" • Natural language ROM queries") | dim,
text(" • Dungeon & overworld inspection") | dim,
text(" • Sprite & palette analysis") | dim,
text(" • Message & dialogue search") | dim,
text(" • Emulator control (when running)") | dim,
separator(),
text("Type '/help' for commands") | center | dim
});
});
}
auto send_button = Button("Send", [&] { // Fallback simple chat interface
if (input_message.empty()) return; auto input_message = std::make_shared<std::string>();
auto input_component = Input(input_message.get(), "Type your message...");
auto send_button = Button("Send", [this, input_message] {
if (input_message->empty()) return;
// Handle chat commands // Handle chat commands
if (input_message == "/exit") { if (*input_message == "/exit") {
screen_.Exit(); screen_.Exit();
return; return;
} }
// For now, just clear the input input_message->clear();
// TODO: Integrate with agent service
input_message.clear();
}); });
// Handle Enter key // Handle Enter key
input_component = CatchEvent(input_component, [&](Event event) { input_component = CatchEvent(input_component, [this, input_message](const Event& event) {
if (event == Event::Return) { if (event == Event::Return) {
if (input_message.empty()) return true; if (input_message->empty()) return true;
// Handle chat commands if (*input_message == "/exit") {
if (input_message == "/exit") {
screen_.Exit(); screen_.Exit();
return true; return true;
} }
// For now, just clear the input input_message->clear();
input_message.clear();
return true; return true;
} }
return false; return false;
@@ -156,7 +207,7 @@ Component UnifiedLayout::CreateChatPanel() {
send_button send_button
}); });
return Renderer(container, [&] { return Renderer(container, [this, container, input_component, send_button] {
return vbox({ return vbox({
text("🤖 AI Chat") | bold | center, text("🤖 AI Chat") | bold | center,
separator(), separator(),
@@ -170,28 +221,38 @@ Component UnifiedLayout::CreateChatPanel() {
}), }),
separator(), separator(),
text("Commands: /exit, /clear, /help") | dim | center text("Commands: /exit, /clear, /help") | dim | center
}) | border; });
}); });
} }
Component UnifiedLayout::CreateStatusPanel() { Component UnifiedLayout::CreateStatusPanel() {
return Renderer([&] { return Renderer([this] {
std::string rom_info = rom_context_ ? std::string rom_info = rom_context_ ?
absl::StrFormat("ROM: %s | Size: %d bytes", absl::StrFormat("ROM: %s | Size: %d bytes",
rom_context_->title(), rom_context_->size()) : rom_context_->title(), rom_context_->size()) :
"No ROM loaded"; "No ROM loaded";
std::string panel_name;
switch (state_.active_main_panel) {
case PanelType::kMainMenu: panel_name = "Main Menu"; break;
case PanelType::kChat: panel_name = "Chat"; break;
case PanelType::kHexViewer: panel_name = "Hex Viewer"; break;
case PanelType::kPaletteEditor: panel_name = "Palette Editor"; break;
case PanelType::kTodoManager: panel_name = "TODO Manager"; break;
case PanelType::kRomTools: panel_name = "ROM Tools"; break;
case PanelType::kGraphicsTools: panel_name = "Graphics Tools"; break;
case PanelType::kSettings: panel_name = "Settings"; break;
case PanelType::kHelp: panel_name = "Help"; break;
default: panel_name = "Other"; break;
}
return vbox({ return vbox({
text("📊 Status") | bold | center, text("📊 Status") | bold | center,
separator(), separator(),
text(rom_info) | color(Color::Green), text(rom_info) | color(rom_context_ ? Color::Green : Color::Red),
separator(), separator(),
text("Panel: ") | bold, text("Panel: ") | bold,
text(absl::StrFormat("%s", text(panel_name),
state_.active_main_panel == PanelType::kMainMenu ? "Main Menu" :
state_.active_main_panel == PanelType::kChat ? "Chat" :
state_.active_main_panel == PanelType::kHexViewer ? "Hex Viewer" :
"Other")),
separator(), separator(),
text("Layout: ") | bold, text("Layout: ") | bold,
text(absl::StrFormat("Chat: %s | Status: %s", text(absl::StrFormat("Chat: %s | Status: %s",
@@ -199,23 +260,26 @@ Component UnifiedLayout::CreateStatusPanel() {
config_.show_status ? "ON" : "OFF")), config_.show_status ? "ON" : "OFF")),
separator(), separator(),
text("Press 'h' for help, 'q' to quit") | dim | center text("Press 'h' for help, 'q' to quit") | dim | center
}) | border; });
}); });
} }
Component UnifiedLayout::CreateToolsPanel() { Component UnifiedLayout::CreateToolsPanel() {
static int selected = 0; struct ToolsState {
static const std::vector<std::string> tools = { int selected = 0;
"🔧 ROM Tools", std::vector<std::string> items = {
"🎨 Graphics Tools", "🔧 ROM Tools",
"📝 TODO Manager", "🎨 Graphics Tools",
"⚙️ Settings", "📝 TODO Manager",
"❓ Help" "⚙️ Settings",
"❓ Help"
};
}; };
auto menu = Menu(&tools, &selected); auto state = std::make_shared<ToolsState>();
auto menu = Menu(&state->items, &state->selected);
return Renderer(menu, [&] { return Renderer(menu, [this, menu, state] {
return vbox({ return vbox({
text("🛠️ Tools") | bold | center, text("🛠️ Tools") | bold | center,
separator(), separator(),
@@ -227,10 +291,10 @@ Component UnifiedLayout::CreateToolsPanel() {
} }
Component UnifiedLayout::CreateHexViewerPanel() { Component UnifiedLayout::CreateHexViewerPanel() {
static int offset = 0; auto offset = std::make_shared<int>(0);
const int lines_to_show = 20; const int lines_to_show = 20;
return Renderer([&] { return Renderer([this, offset, lines_to_show] {
if (!rom_context_) { if (!rom_context_) {
return vbox({ return vbox({
text("🔍 Hex Viewer") | bold | center, text("🔍 Hex Viewer") | bold | center,
@@ -243,7 +307,7 @@ Component UnifiedLayout::CreateHexViewerPanel() {
std::vector<Element> rows; std::vector<Element> rows;
for (int i = 0; i < lines_to_show; ++i) { for (int i = 0; i < lines_to_show; ++i) {
int current_offset = offset + (i * 16); int current_offset = *offset + (i * 16);
if (current_offset >= static_cast<int>(rom_context_->size())) { if (current_offset >= static_cast<int>(rom_context_->size())) {
break; break;
} }
@@ -278,7 +342,7 @@ Component UnifiedLayout::CreateHexViewerPanel() {
separator(), separator(),
vbox(rows) | frame | flex, vbox(rows) | frame | flex,
separator(), separator(),
text(absl::StrFormat("Offset: 0x%08X", offset)) | color(Color::Cyan), text(absl::StrFormat("Offset: 0x%08X", *offset)) | color(Color::Cyan),
separator(), separator(),
text("↑/↓: Scroll | q: Back") | dim | center text("↑/↓: Scroll | q: Back") | dim | center
}) | border; }) | border;
@@ -286,7 +350,7 @@ Component UnifiedLayout::CreateHexViewerPanel() {
} }
Component UnifiedLayout::CreatePaletteEditorPanel() { Component UnifiedLayout::CreatePaletteEditorPanel() {
return Renderer([&] { return Renderer([this] {
return vbox({ return vbox({
text("🎨 Palette Editor") | bold | center, text("🎨 Palette Editor") | bold | center,
separator(), separator(),
@@ -302,7 +366,7 @@ Component UnifiedLayout::CreatePaletteEditorPanel() {
} }
Component UnifiedLayout::CreateTodoManagerPanel() { Component UnifiedLayout::CreateTodoManagerPanel() {
return Renderer([&] { return Renderer([this] {
return vbox({ return vbox({
text("📝 TODO Manager") | bold | center, text("📝 TODO Manager") | bold | center,
separator(), separator(),
@@ -318,19 +382,22 @@ Component UnifiedLayout::CreateTodoManagerPanel() {
} }
Component UnifiedLayout::CreateRomToolsPanel() { Component UnifiedLayout::CreateRomToolsPanel() {
static int selected = 0; struct ToolsState {
static const std::vector<std::string> tools = { int selected = 0;
"Apply Asar Patch", std::vector<std::string> items = {
"Apply BPS Patch", "Apply Asar Patch",
"Extract Symbols", "Apply BPS Patch",
"Validate Assembly", "Extract Symbols",
"Generate Save File", "Validate Assembly",
"Back" "Generate Save File",
"Back"
};
}; };
auto menu = Menu(&tools, &selected); auto state = std::make_shared<ToolsState>();
auto menu = Menu(&state->items, &state->selected);
return Renderer(menu, [&] { return Renderer(menu, [this, menu, state] {
return vbox({ return vbox({
text("🔧 ROM Tools") | bold | center, text("🔧 ROM Tools") | bold | center,
separator(), separator(),
@@ -342,16 +409,19 @@ Component UnifiedLayout::CreateRomToolsPanel() {
} }
Component UnifiedLayout::CreateGraphicsToolsPanel() { Component UnifiedLayout::CreateGraphicsToolsPanel() {
static int selected = 0; struct ToolsState {
static const std::vector<std::string> tools = { int selected = 0;
"Palette Editor", std::vector<std::string> items = {
"Hex Viewer", "Palette Editor",
"Back" "Hex Viewer",
"Back"
};
}; };
auto menu = Menu(&tools, &selected); auto state = std::make_shared<ToolsState>();
auto menu = Menu(&state->items, &state->selected);
return Renderer(menu, [&] { return Renderer(menu, [this, menu, state] {
return vbox({ return vbox({
text("🎨 Graphics Tools") | bold | center, text("🎨 Graphics Tools") | bold | center,
separator(), separator(),
@@ -363,118 +433,228 @@ Component UnifiedLayout::CreateGraphicsToolsPanel() {
} }
Component UnifiedLayout::CreateSettingsPanel() { Component UnifiedLayout::CreateSettingsPanel() {
return Renderer([&] { struct SettingsState {
int left_width_slider;
int right_width_slider;
int bottom_height_slider;
};
auto state = std::make_shared<SettingsState>();
state->left_width_slider = config_.left_panel_width;
state->right_width_slider = config_.right_panel_width;
state->bottom_height_slider = config_.bottom_panel_height;
auto left_width_control = Slider("Left Panel Width: ", &state->left_width_slider, 20, 60, 1);
auto right_width_control = Slider("Right Panel Width: ", &state->right_width_slider, 30, 60, 1);
auto bottom_height_control = Slider("Bottom Panel Height: ", &state->bottom_height_slider, 10, 30, 1);
auto apply_button = Button("Apply Changes", [this, state] {
config_.left_panel_width = state->left_width_slider;
config_.right_panel_width = state->right_width_slider;
config_.bottom_panel_height = state->bottom_height_slider;
screen_.PostEvent(Event::Custom);
});
auto reset_button = Button("Reset to Defaults", [this, state] {
state->left_width_slider = 30;
state->right_width_slider = 40;
state->bottom_height_slider = 15;
config_.left_panel_width = 30;
config_.right_panel_width = 40;
config_.bottom_panel_height = 15;
screen_.PostEvent(Event::Custom);
});
auto controls = Container::Vertical({
left_width_control,
right_width_control,
bottom_height_control,
Container::Horizontal({
apply_button,
reset_button
})
});
return Renderer(controls, [this, controls, state, left_width_control, right_width_control,
bottom_height_control, apply_button, reset_button] {
return vbox({ return vbox({
text("⚙️ Settings") | bold | center, text("⚙️ Layout Configuration") | bold | center | color(Color::Cyan),
separator(), separator(),
text("Settings panel") | center, text("Customize the TUI layout") | center | dim,
text("coming soon...") | center | dim,
separator(), separator(),
text("This panel will contain") | center, hbox({
text("application settings") | center, text("Left Panel Width: ") | bold,
text(absl::StrFormat("%d", state->left_width_slider))
}),
left_width_control->Render(),
separator(), separator(),
text("Press 'q' to go back") | dim | center hbox({
}) | border; text("Right Panel Width: ") | bold,
text(absl::StrFormat("%d", state->right_width_slider))
}),
right_width_control->Render(),
separator(),
hbox({
text("Bottom Panel Height: ") | bold,
text(absl::StrFormat("%d", state->bottom_height_slider))
}),
bottom_height_control->Render(),
separator(),
hbox({
apply_button->Render(),
text(" "),
reset_button->Render()
}) | center,
separator(),
text("Panel Visibility:") | bold,
hbox({
text("Chat: ") | bold,
text(config_.show_chat ? "ON ✓" : "OFF ✗") |
color(config_.show_chat ? Color::Green : Color::Red),
text(" "),
text("Status: ") | bold,
text(config_.show_status ? "ON ✓" : "OFF ✗") |
color(config_.show_status ? Color::Green : Color::Red)
}) | center,
separator(),
text("Keyboard Shortcuts:") | bold,
text(" c - Toggle chat panel") | dim,
text(" s - Toggle status panel") | dim,
text(" Esc/b - Back to menu") | dim,
separator(),
text("Changes apply immediately") | center | dim
});
}); });
} }
Component UnifiedLayout::CreateHelpPanel() { Component UnifiedLayout::CreateHelpPanel() {
return Renderer([&] { return Renderer([this] {
return vbox({ return vbox({
text("❓ Help") | bold | center, text(" Z3ED Help") | bold | center | color(Color::Cyan),
separator(), separator(),
text("Z3ED Unified Layout Help") | center, text("Unified TUI Layout - ROM Editor & AI Agent") | center | dim,
separator(), separator(),
text("Global Shortcuts:") | bold, text("Global Shortcuts:") | bold | color(Color::Yellow),
text(" q - Quit application"), text(" q - Quit application"),
text(" h - Show this help"), text(" h - Show this help"),
text(" c - Toggle chat panel"), text(" m - Main menu"),
text(" s - Toggle status panel"), text(" Esc/b - Back to previous panel"),
text(" t - Switch to tools"),
text(" m - Switch to main menu"),
separator(), separator(),
text("Navigation:") | bold, text("Panel Controls:") | bold | color(Color::Yellow),
text(" ↑/↓ - Navigate menus"), text(" c - Toggle chat panel"),
text(" Enter - Select item"), text(" s - Toggle status panel"),
text(" Tab - Switch panels"), text(" f - Open full chat interface"),
text(" t - ROM tools"),
separator(), separator(),
text("Press 'q' to go back") | dim | center text("Navigation:") | bold | color(Color::Yellow),
}) | border; text(" ↑/↓ - Navigate menus"),
text(" Enter - Select item"),
text(" Tab - Switch focus"),
separator(),
text("Chat Commands:") | bold | color(Color::Yellow),
text(" /exit - Exit chat"),
text(" /clear - Clear history"),
text(" /help - Show chat help"),
separator(),
text("Available Tools:") | bold | color(Color::Green),
text(" • Hex Viewer - Inspect ROM data"),
text(" • Palette Editor - Edit color palettes"),
text(" • TODO Manager - Track tasks"),
text(" • AI Chat - Natural language ROM queries"),
text(" • Dungeon Tools - Room inspection & editing"),
text(" • Graphics Tools - Sprite & tile editing"),
separator(),
text("Press 'Esc' or 'b' to go back") | dim | center
});
}); });
} }
Component UnifiedLayout::CreateUnifiedLayout() { Component UnifiedLayout::CreateUnifiedLayout() {
top_layout_ = CreateTopLayout(); // Create a container that holds all panels
bottom_layout_ = CreateBottomLayout(); auto all_panels = Container::Tab({
main_menu_panel_,
chat_panel_,
status_panel_,
tools_panel_,
hex_viewer_panel_,
palette_editor_panel_,
todo_manager_panel_,
rom_tools_panel_,
graphics_tools_panel_,
settings_panel_,
help_panel_
}, nullptr);
// Create vertical split between top and bottom // Create a renderer that dynamically shows the right panels based on state
return ResizableSplitBottom( return Renderer(all_panels, [this, all_panels] {
top_layout_, // Dynamically select left panel based on current state
bottom_layout_, Component left_panel;
&config_.bottom_panel_height switch (state_.active_main_panel) {
); case PanelType::kMainMenu:
left_panel = main_menu_panel_;
break;
case PanelType::kHexViewer:
left_panel = hex_viewer_panel_;
break;
case PanelType::kPaletteEditor:
left_panel = palette_editor_panel_;
break;
case PanelType::kTodoManager:
left_panel = todo_manager_panel_;
break;
case PanelType::kRomTools:
left_panel = rom_tools_panel_;
break;
case PanelType::kGraphicsTools:
left_panel = graphics_tools_panel_;
break;
case PanelType::kSettings:
left_panel = settings_panel_;
break;
case PanelType::kHelp:
left_panel = help_panel_;
break;
default:
left_panel = main_menu_panel_;
break;
}
// Dynamically select right panel
Component right_panel = config_.show_status ? status_panel_ : tools_panel_;
// Create horizontal layout
auto top_section = hbox({
left_panel->Render() | flex,
separator(),
right_panel->Render() | size(WIDTH, EQUAL, config_.right_panel_width)
});
// Add chat panel if enabled
if (config_.show_chat) {
return vbox({
top_section | flex,
separator(),
chat_panel_->Render() | size(HEIGHT, EQUAL, config_.bottom_panel_height)
});
}
return top_section;
});
} }
Component UnifiedLayout::CreateTopLayout() {
// Left panel: Main menu or tools
Component left_panel;
switch (state_.active_main_panel) {
case PanelType::kMainMenu:
left_panel = main_menu_panel_;
break;
case PanelType::kHexViewer:
left_panel = hex_viewer_panel_;
break;
case PanelType::kPaletteEditor:
left_panel = palette_editor_panel_;
break;
case PanelType::kTodoManager:
left_panel = todo_manager_panel_;
break;
case PanelType::kRomTools:
left_panel = rom_tools_panel_;
break;
case PanelType::kGraphicsTools:
left_panel = graphics_tools_panel_;
break;
case PanelType::kSettings:
left_panel = settings_panel_;
break;
case PanelType::kHelp:
left_panel = help_panel_;
break;
default:
left_panel = main_menu_panel_;
break;
}
// Right panel: Status or tools
Component right_panel;
if (config_.show_status) {
right_panel = status_panel_;
} else {
right_panel = tools_panel_;
}
// Create horizontal split between left and right
return ResizableSplitRight(
left_panel,
right_panel,
&config_.right_panel_width
);
}
Component UnifiedLayout::CreateBottomLayout() {
if (!config_.show_chat) {
return Renderer([] { return text(""); });
}
return chat_panel_;
}
bool UnifiedLayout::HandleGlobalEvents(const Event& event) { bool UnifiedLayout::HandleGlobalEvents(const Event& event) {
// Back to main menu
if (event == Event::Escape || event == Event::Character('b')) {
if (state_.active_main_panel != PanelType::kMainMenu) {
SwitchMainPanel(PanelType::kMainMenu);
return true;
}
}
// Global shortcuts // Global shortcuts
if (event == Event::Character('q')) { if (event == Event::Character('q') ||
(event == Event::Character('q') && state_.active_main_panel == PanelType::kMainMenu)) {
screen_.Exit(); screen_.Exit();
return true; return true;
} }
@@ -504,10 +684,20 @@ bool UnifiedLayout::HandleGlobalEvents(const Event& event) {
return true; return true;
} }
if (event == Event::Character('f')) {
// Launch full chat interface
if (chat_tui_) {
screen_.ExitLoopClosure()(); // Exit current loop
chat_tui_->Run(); // Run full chat
screen_.PostEvent(Event::Custom); // Refresh when we return
}
return true;
}
return false; return false;
} }
bool UnifiedLayout::HandlePanelEvents(const Event& event) { bool UnifiedLayout::HandlePanelEvents(const Event& /* event */) {
// Panel-specific event handling // Panel-specific event handling
return false; return false;
} }

View File

@@ -10,6 +10,7 @@
#include "app/rom.h" #include "app/rom.h"
#include "cli/tui/chat_tui.h" #include "cli/tui/chat_tui.h"
#include "cli/tui/hex_viewer.h"
namespace yaze { namespace yaze {
namespace cli { namespace cli {
@@ -91,8 +92,6 @@ class UnifiedLayout {
// Layout assembly // Layout assembly
ftxui::Component CreateUnifiedLayout(); ftxui::Component CreateUnifiedLayout();
ftxui::Component CreateTopLayout();
ftxui::Component CreateBottomLayout();
// Event handling // Event handling
bool HandleGlobalEvents(const ftxui::Event& event); bool HandleGlobalEvents(const ftxui::Event& event);
@@ -110,6 +109,7 @@ class UnifiedLayout {
// Components // Components
std::unique_ptr<tui::ChatTUI> chat_tui_; std::unique_ptr<tui::ChatTUI> chat_tui_;
std::unique_ptr<HexViewerComponent> hex_viewer_component_;
// Panel components (cached for performance) // Panel components (cached for performance)
ftxui::Component main_menu_panel_; ftxui::Component main_menu_panel_;
@@ -126,8 +126,6 @@ class UnifiedLayout {
// Layout components // Layout components
ftxui::Component unified_layout_; ftxui::Component unified_layout_;
ftxui::Component top_layout_;
ftxui::Component bottom_layout_;
// Event handlers // Event handlers
std::function<bool(const ftxui::Event&)> global_event_handler_; std::function<bool(const ftxui::Event&)> global_event_handler_;

View File

@@ -51,6 +51,7 @@ add_executable(
cli/tui/unified_layout.cc cli/tui/unified_layout.cc
cli/tui/enhanced_chat_component.cc cli/tui/enhanced_chat_component.cc
cli/tui/enhanced_status_panel.cc cli/tui/enhanced_status_panel.cc
cli/tui/hex_viewer.cc
cli/handlers/compress.cc cli/handlers/compress.cc
cli/handlers/patch.cc cli/handlers/patch.cc
cli/handlers/tile16_transfer.cc cli/handlers/tile16_transfer.cc
@@ -76,6 +77,7 @@ add_executable(
cli/handlers/agent/dialogue_tool_commands.cc cli/handlers/agent/dialogue_tool_commands.cc
cli/handlers/agent/music_tool_commands.cc cli/handlers/agent/music_tool_commands.cc
cli/handlers/agent/sprite_tool_commands.cc cli/handlers/agent/sprite_tool_commands.cc
cli/handlers/agent/dungeon_emulator_tool_commands.cc
cli/flags.cc cli/flags.cc
cli/tui/asar_patch.cc cli/tui/asar_patch.cc
cli/tui/palette_editor.cc cli/tui/palette_editor.cc