yaze 0.3.2
Link to the Past ROM Editor
 
Loading...
Searching...
No Matches
tui.cc
Go to the documentation of this file.
1#include "tui.h"
2
3#include <fstream>
4
5#include <ftxui/component/component.hpp>
6#include <ftxui/component/screen_interactive.hpp>
7#include <ftxui/dom/elements.hpp>
8#include <ftxui/screen/screen.hpp>
9
10#include "absl/strings/str_cat.h"
11#include "absl/strings/str_format.h"
12#include "util/bps.h"
13#include "util/file_util.h"
15#include "cli/cli.h"
17#include "cli/z3ed_ascii_logo.h"
21
22namespace yaze {
23namespace cli {
24
25using namespace ftxui;
26
27namespace {
28void SwitchComponents(ftxui::ScreenInteractive &screen, LayoutID layout) {
29 screen.ExitLoopClosure()();
30 screen.Clear();
31 app_context.current_layout = layout;
32}
33
34bool HandleInput(ftxui::ScreenInteractive &screen, ftxui::Event &event,
35 int &selected) {
36 if (event == Event::ArrowDown || event == Event::Character('j')) {
37 selected++;
38 return true;
39 }
40 if (event == Event::ArrowUp || event == Event::Character('k')) {
41 if (selected != 0) selected--;
42 return true;
43 }
44 if (event == Event::Character('q')) {
46 return true;
47 }
48 return false;
49}
50
51void ReturnIfRomNotLoaded(ftxui::ScreenInteractive &screen) {
52 if (!app_context.rom.is_loaded()) {
53 app_context.error_message = "No ROM loaded.";
55 }
56}
57
58void ApplyBpsPatchComponent(ftxui::ScreenInteractive &screen) {
59 // Text inputs for user to enter file paths (or any relevant data).
60 static std::string patch_file;
61 static std::string base_file;
62
63 auto patch_file_input = Input(&patch_file, "Patch file path");
64 auto base_file_input = Input(&base_file, "Base file path");
65
66 // Button to apply the patch.
67 auto apply_button = Button("Apply Patch", [&] {
68 std::vector<uint8_t> source = app_context.rom.vector();
69 // auto source_contents = util::LoadFile(base_file);
70 // std::copy(source_contents.begin(), source_contents.end(),
71 // std::back_inserter(source));
72 std::vector<uint8_t> patch;
73 auto patch_contents = util::LoadFile(patch_file);
74 std::copy(patch_contents.begin(), patch_contents.end(),
75 std::back_inserter(patch));
76 std::vector<uint8_t> patched;
77
78 try {
79 util::ApplyBpsPatch(source, patch, patched);
80 } catch (const std::runtime_error &e) {
81 app_context.error_message = e.what();
83 return;
84 }
85
86 // Write the patched data to a new file.
87 // Find the . in the base file name and insert _patched before it.
88 auto dot_pos = base_file.find_last_of('.');
89 auto patched_file = base_file.substr(0, dot_pos) + "_patched" +
90 base_file.substr(dot_pos, base_file.size() - dot_pos);
91 std::ofstream file(patched_file, std::ios::binary);
92 if (!file.is_open()) {
93 app_context.error_message = "Could not open file for writing.";
95 return;
96 }
97
98 file.write(reinterpret_cast<const char *>(patched.data()), patched.size());
99
100 // If the patch was applied successfully, return to the main menu.
102 });
103
104 // Button to return to main menu without applying.
105 auto return_button = Button("Back to Main Menu", [&] {
107 });
108
109 // Layout components vertically.
110 auto container = Container::Vertical({
111 patch_file_input,
112 base_file_input,
113 apply_button,
114 return_button,
115 });
116
117 auto renderer = Renderer(container, [&] {
118 return vbox({text("Apply BPS Patch") | center, separator(),
119 text("Enter Patch File:"), patch_file_input->Render(),
120 text("Enter Base File:"), base_file_input->Render(),
121 separator(),
122 hbox({
123 apply_button->Render() | center,
124 separator(),
125 return_button->Render() | center,
126 }) | center}) |
127 center;
128 });
129
130 screen.Loop(renderer);
131}
132
133void GenerateSaveFileComponent(ftxui::ScreenInteractive &screen) {
134 // Produce a list of ftxui::Checkbox for items and values to set
135 // Link to the past items include Bow, Boomerang, etc.
136
137 const static std::vector<std::string> items = {"Bow",
138 "Boomerang",
139 "Hookshot",
140 "Bombs",
141 "Magic Powder",
142 "Fire Rod",
143 "Ice Rod",
144 "Lantern",
145 "Hammer",
146 "Shovel",
147 "Flute",
148 "Bug Net",
149 "Book of Mudora",
150 "Cane of Somaria",
151 "Cane of Byrna",
152 "Magic Cape",
153 "Magic Mirror",
154 "Pegasus Boots",
155 "Flippers",
156 "Moon Pearl",
157 "Bottle 1",
158 "Bottle 2",
159 "Bottle 3",
160 "Bottle 4"};
161
162 constexpr size_t kNumItems = 28;
163 std::array<bool, kNumItems> values = {};
164 auto checkboxes = Container::Vertical({});
165 for (size_t i = 0; i < items.size(); i += 4) {
166 auto row = Container::Horizontal({});
167 for (size_t j = 0; j < 4 && (i + j) < items.size(); ++j) {
168 row->Add(
169 Checkbox(absl::StrCat(items[i + j], " ").data(), &values[i + j]));
170 }
171 checkboxes->Add(row);
172 }
173
174 // border container for sword, shield, armor with radioboxes
175 // to select the current item
176 // sword, shield, armor
177
178 static int sword = 0;
179 static int shield = 0;
180 static int armor = 0;
181
182 const std::vector<std::string> sword_items = {"Fighter", "Master", "Tempered",
183 "Golden"};
184 const std::vector<std::string> shield_items = {"Small", "Fire", "Mirror"};
185 const std::vector<std::string> armor_items = {"Green", "Blue", "Red"};
186
187 auto sword_radiobox = Radiobox(&sword_items, &sword);
188 auto shield_radiobox = Radiobox(&shield_items, &shield);
189 auto armor_radiobox = Radiobox(&armor_items, &armor);
190 auto equipment_container = Container::Vertical({
191 sword_radiobox,
192 shield_radiobox,
193 armor_radiobox,
194 });
195
196 auto save_button = Button("Generate Save File", [&] {
197 // Generate the save file here.
198 // You can use the values vector to determine which items are checked.
199 // After generating the save file, you could either stay here or return to
200 // the main menu.
201 });
202
203 auto back_button =
204 Button("Back", [&] { SwitchComponents(screen, LayoutID::kMainMenu); });
205
206 auto container = Container::Vertical({
207 checkboxes,
208 equipment_container,
209 save_button,
210 back_button,
211 });
212
213 auto renderer = Renderer(container, [&] {
214 return vbox({text("Generate Save File") | center, separator(),
215 text("Select items to include in the save file:"),
216 checkboxes->Render(), separator(),
217 equipment_container->Render(), separator(),
218 hbox({
219 save_button->Render() | center,
220 separator(),
221 back_button->Render() | center,
222 }) | center}) |
223 center;
224 });
225
226 screen.Loop(renderer);
227}
228
229void TodoManagerComponent(ftxui::ScreenInteractive &screen) {
230 static agent::TodoManager manager;
231 static bool initialized = false;
232 if (!initialized) {
233 manager.Initialize();
234 initialized = true;
235 }
236
237 static std::string new_todo_description;
238 static int selected_todo = 0;
239
240 auto refresh_todos = [&]() {
241 auto todos = manager.GetAllTodos();
242 std::vector<std::string> entries;
243 for (const auto& item : todos) {
244 std::string status_emoji;
245 switch (item.status) {
246 case agent::TodoItem::Status::PENDING: status_emoji = "⏳"; break;
247 case agent::TodoItem::Status::IN_PROGRESS: status_emoji = "🔄"; break;
248 case agent::TodoItem::Status::COMPLETED: status_emoji = "✅"; break;
249 case agent::TodoItem::Status::BLOCKED: status_emoji = "🚫"; break;
250 case agent::TodoItem::Status::CANCELLED: status_emoji = "❌"; break;
251 }
252 entries.push_back(absl::StrFormat("%s [%s] %s", status_emoji, item.id, item.description));
253 }
254 return entries;
255 };
256
257 static std::vector<std::string> todo_entries = refresh_todos();
258
259 auto input_field = Input(&new_todo_description, "New TODO description");
260 auto add_button = Button("Add", [&]() {
261 if (!new_todo_description.empty()) {
262 manager.CreateTodo(new_todo_description);
263 new_todo_description.clear();
264 todo_entries = refresh_todos();
265 }
266 });
267
268 auto complete_button = Button("Complete", [&]() {
269 auto todos = manager.GetAllTodos();
270 if (selected_todo < todos.size()) {
271 manager.UpdateStatus(todos[selected_todo].id, agent::TodoItem::Status::COMPLETED);
272 todo_entries = refresh_todos();
273 }
274 });
275
276 auto delete_button = Button("Delete", [&]() {
277 auto todos = manager.GetAllTodos();
278 if (selected_todo < todos.size()) {
279 manager.DeleteTodo(todos[selected_todo].id);
280 if (selected_todo >= todo_entries.size() -1) {
281 selected_todo--;
282 }
283 todo_entries = refresh_todos();
284 }
285 });
286
287 auto back_button = Button("Back", [&] { SwitchComponents(screen, LayoutID::kMainMenu); });
288
289 auto todo_menu = Menu(&todo_entries, &selected_todo);
290
291 auto container = Container::Vertical({
292 Container::Horizontal({input_field, add_button}),
293 todo_menu,
294 Container::Horizontal({complete_button, delete_button, back_button}),
295 });
296
297 auto renderer = Renderer(container, [&] {
298 return vbox({
299 text("📝 TODO Manager") | bold | center,
300 separator(),
301 hbox({text("New: "), input_field->Render(), add_button->Render()}),
302 separator(),
303 todo_menu->Render() | vscroll_indicator | frame | flex,
304 separator(),
305 hbox({complete_button->Render(), delete_button->Render(), back_button->Render()}) | center,
306 }) | border;
307 });
308
309 screen.Loop(renderer);
310}
311
312
313
314void ApplyAsarPatchComponent(ftxui::ScreenInteractive &screen) {
315 ReturnIfRomNotLoaded(screen);
316
317 static std::string asm_file;
318 static std::string output_message;
319 static Color output_color = Color::White;
320
321 auto asm_file_input = Input(&asm_file, "Assembly file (.asm)");
322
323 auto apply_button = Button("Apply Asar Patch", [&] {
324 if (asm_file.empty()) {
325 app_context.error_message = "Please specify an assembly file";
326 SwitchComponents(screen, LayoutID::kError);
327 return;
328 }
329
330 try {
331 // TODO: Use new CommandHandler system for AsarPatch
332 // Reference: src/app/core/asar_wrapper.cc (AsarWrapper class)
333 output_message = "❌ AsarPatch not yet implemented in new CommandHandler system";
334 output_color = Color::Red;
335 } catch (const std::exception& e) {
336 output_message = "Exception: " + std::string(e.what());
337 output_color = Color::Red;
338 }
339 });
340
341 auto back_button = Button("Back to Main Menu", [&] {
342 output_message.clear();
344 });
345
346 auto container = Container::Vertical({
347 asm_file_input,
348 apply_button,
349 back_button,
350 });
351
352 auto renderer = Renderer(container, [&] {
353 std::vector<Element> elements = {
354 text("Apply Asar Patch") | center | bold,
355 separator(),
356 text("Assembly File:"),
357 asm_file_input->Render(),
358 separator(),
359 apply_button->Render() | center,
360 };
361
362 if (!output_message.empty()) {
363 elements.push_back(separator());
364 elements.push_back(text(output_message) | color(output_color));
365 }
366
367 elements.push_back(separator());
368 elements.push_back(back_button->Render() | center);
369
370 return vbox(elements) | center | border;
371 });
372
373 screen.Loop(renderer);
374}
375
376void PaletteEditorComponent(ftxui::ScreenInteractive &screen) {
377 ReturnIfRomNotLoaded(screen);
378
379 auto back_button = Button("Back to Main Menu", [&] {
381 });
382
383 auto renderer = Renderer(back_button, [&] {
384 return vbox({
385 text("Palette Editor") | center | bold,
386 separator(),
387 text("Palette editing functionality coming soon...") | center,
388 separator(),
389 back_button->Render() | center,
390 }) | center | border;
391 });
392
393 screen.Loop(renderer);
394}
395
396void ExtractSymbolsComponent(ftxui::ScreenInteractive &screen) {
397 static std::string asm_file;
398 static std::vector<std::string> symbols_list;
399 static std::string output_message;
400
401 auto asm_file_input = Input(&asm_file, "Assembly file (.asm)");
402
403 auto extract_button = Button("Extract Symbols", [&] {
404 if (asm_file.empty()) {
405 app_context.error_message = "Please specify an assembly file";
406 SwitchComponents(screen, LayoutID::kError);
407 return;
408 }
409
410 try {
411 core::AsarWrapper wrapper;
412 auto init_status = wrapper.Initialize();
413 if (!init_status.ok()) {
414 app_context.error_message = absl::StrCat("Failed to initialize Asar: ", init_status.message());
415 SwitchComponents(screen, LayoutID::kError);
416 return;
417 }
418
419 auto symbols_result = wrapper.ExtractSymbols(asm_file);
420 if (!symbols_result.ok()) {
421 app_context.error_message = absl::StrCat("Symbol extraction failed: ", symbols_result.status().message());
423 return;
424 }
425
426 const auto& symbols = symbols_result.value();
427 output_message = absl::StrFormat("✅ Extracted %d symbols from %s",
428 symbols.size(), asm_file);
429
430 symbols_list.clear();
431 for (const auto& symbol : symbols) {
432 symbols_list.push_back(absl::StrFormat("%-20s @ $%06X",
433 symbol.name, symbol.address));
434 }
435
436 } catch (const std::exception& e) {
437 app_context.error_message = "Exception: " + std::string(e.what());
439 }
440 });
441
442 auto back_button = Button("Back to Main Menu", [&] {
443 output_message.clear();
444 symbols_list.clear();
445 SwitchComponents(screen, LayoutID::kMainMenu);
446 });
447
448 auto container = Container::Vertical({
449 asm_file_input,
450 extract_button,
451 back_button,
452 });
453
454 auto renderer = Renderer(container, [&] {
455 std::vector<Element> elements = {
456 text("Extract Assembly Symbols") | center | bold,
457 separator(),
458 text("Assembly File:"),
459 asm_file_input->Render(),
460 separator(),
461 extract_button->Render() | center,
462 };
463
464 if (!output_message.empty()) {
465 elements.push_back(separator());
466 elements.push_back(text(output_message) | color(Color::Green));
467
468 if (!symbols_list.empty()) {
469 elements.push_back(separator());
470 elements.push_back(text("Symbols:") | bold);
471
472 std::vector<Element> symbol_elements;
473 for (const auto& symbol : symbols_list) {
474 symbol_elements.push_back(text(symbol) | color(Color::Cyan));
475 }
476 elements.push_back(vbox(symbol_elements) | frame | size(HEIGHT, LESS_THAN, 15));
477 }
478 }
479
480 elements.push_back(separator());
481 elements.push_back(back_button->Render() | center);
482
483 return vbox(elements) | center | border;
484 });
485
486 screen.Loop(renderer);
487}
488
489void ValidateAssemblyComponent(ftxui::ScreenInteractive &screen) {
490 static std::string asm_file;
491 static std::string output_message;
492 static Color output_color = Color::White;
493
494 auto asm_file_input = Input(&asm_file, "Assembly file (.asm)");
495
496 auto validate_button = Button("Validate Assembly", [&] {
497 if (asm_file.empty()) {
498 app_context.error_message = "Please specify an assembly file";
499 SwitchComponents(screen, LayoutID::kError);
500 return;
501 }
502
503 try {
504 core::AsarWrapper wrapper;
505 auto init_status = wrapper.Initialize();
506 if (!init_status.ok()) {
507 app_context.error_message = absl::StrCat("Failed to initialize Asar: ", init_status.message());
508 SwitchComponents(screen, LayoutID::kError);
509 return;
510 }
511
512 auto validation_status = wrapper.ValidateAssembly(asm_file);
513 if (validation_status.ok()) {
514 output_message = "✅ Assembly file is valid!";
515 output_color = Color::Green;
516 } else {
517 output_message = absl::StrCat("❌ Validation failed:\n", validation_status.message());
518 output_color = Color::Red;
519 }
520
521 } catch (const std::exception& e) {
522 app_context.error_message = "Exception: " + std::string(e.what());
523 SwitchComponents(screen, LayoutID::kError);
524 }
525 });
526
527 auto back_button = Button("Back to Main Menu", [&] {
528 output_message.clear();
529 SwitchComponents(screen, LayoutID::kMainMenu);
530 });
531
532 auto container = Container::Vertical({
533 asm_file_input,
534 validate_button,
535 back_button,
536 });
537
538 auto renderer = Renderer(container, [&] {
539 std::vector<Element> elements = {
540 text("Validate Assembly File") | center | bold,
541 separator(),
542 text("Assembly File:"),
543 asm_file_input->Render(),
544 separator(),
545 validate_button->Render() | center,
546 };
547
548 if (!output_message.empty()) {
549 elements.push_back(separator());
550 elements.push_back(text(output_message) | color(output_color));
551 }
552
553 elements.push_back(separator());
554 elements.push_back(back_button->Render() | center);
555
556 return vbox(elements) | center | border;
557 });
558
559 screen.Loop(renderer);
560}
561
562void LoadRomComponent(ftxui::ScreenInteractive &screen) {
563 static std::string rom_file;
564 auto rom_file_input = Input(&rom_file, "ROM file path");
565
566 auto load_button = Button("Load ROM", [&] {
567 // Load the ROM file here.
568 auto rom_status = app_context.rom.LoadFromFile(rom_file);
569 if (!rom_status.ok()) {
570 app_context.error_message = std::string(rom_status.message().data(), rom_status.message().size());
571 SwitchComponents(screen, LayoutID::kError);
572 return;
573 }
574 // If the ROM is loaded successfully, switch to the main menu.
575 SwitchComponents(screen, LayoutID::kMainMenu);
576 });
577
578 auto browse_button = Button("Browse...", [&] {
579 // TODO: Implement file dialog
580 // For now, show a placeholder
581 rom_file = "/path/to/your/rom.sfc";
582 });
583
584 auto back_button =
585 Button("Back", [&] { SwitchComponents(screen, LayoutID::kMainMenu); });
586
587 auto container = Container::Vertical({
588 Container::Horizontal({rom_file_input, browse_button}),
589 load_button,
590 back_button,
591 });
592
593 auto renderer = Renderer(container, [&] {
594 return vbox({
595 text("Load ROM") | center | bold,
596 separator(),
597 text("Enter ROM File Path:"),
598 hbox({
599 rom_file_input->Render() | flex,
600 separator(),
601 browse_button->Render(),
602 }),
603 separator(),
604 load_button->Render() | center,
605 separator(),
606 back_button->Render() | center,
607 }) | center | border;
608 });
609
610 screen.Loop(renderer);
611}
612
613Element ColorBox(const Color &color) {
614 return ftxui::text(" ") | ftxui::bgcolor(color);
615}
616
617
618
619void HelpComponent(ftxui::ScreenInteractive &screen) {
620 auto help_text = vbox({
621 // Header
622 text("╔══════════════════════════════════════════════════════════╗") | color(Color::Cyan1) | bold,
623 text("║ Z3ED v0.3.2 - AI-Powered CLI ║") | color(Color::Cyan1) | bold,
624 text("║ The Legend of Zelda: A Link to the Past Editor ║") | color(Color::White),
625 text("╚══════════════════════════════════════════════════════════╝") | color(Color::Cyan1) | bold,
626 text(""),
627 hbox({
628 text("✨ Author: ") | color(Color::Yellow1) | bold,
629 text("scawful") | color(Color::Magenta),
630 text(" │ ") | color(Color::GrayDark),
631 text("🤖 AI: ") | color(Color::Green1) | bold,
632 text("Ollama + Gemini Integration") | color(Color::GreenLight),
633 }) | center,
634 text(""),
635 separator(),
636
637 // AI Agent Commands
638 text("") | center,
639 text("🤖 AI AGENT COMMANDS") | bold | color(Color::Green1) | center,
640 text(" Conversational AI for ROM inspection and modification") | color(Color::GreenLight) | center,
641 separator(),
642 hbox({text(" "), text("💬 Test Chat Mode") | bold | color(Color::Cyan), filler(),
643 text("agent test-conversation") | color(Color::White),
644 text(" [--rom=<file>] [--verbose]") | color(Color::GrayLight)}),
645 hbox({text(" "), text("→ Interactive AI testing with embedded labels") | color(Color::GrayLight)}),
646 text(""),
647 hbox({text(" "), text("📊 Chat with AI") | bold | color(Color::Cyan), filler(),
648 text("agent chat") | color(Color::White),
649 text(" <prompt> [--host] [--port]") | color(Color::GrayLight)}),
650 hbox({text(" "), text("→ Natural language ROM inspection (rooms, sprites, entrances)") | color(Color::GrayLight)}),
651 text(""),
652 hbox({text(" "), text("🎯 Simple Chat") | bold | color(Color::Cyan), filler(),
653 text("agent simple-chat") | color(Color::White),
654 text(" <prompt> [--rom=<file>]") | color(Color::GrayLight)}),
655 hbox({text(" "), text("→ Quick AI queries with automatic ROM loading") | color(Color::GrayLight)}),
656 text(""),
657
658 separator(),
659 text("") | center,
660 text("🎯 ASAR 65816 ASSEMBLER") | bold | color(Color::Yellow1) | center,
661 text(" Assemble and patch with Asar integration") | color(Color::YellowLight) | center,
662 separator(),
663 hbox({text(" "), text("⚡ Apply Patch") | bold | color(Color::Cyan), filler(),
664 text("patch apply-asar") | color(Color::White),
665 text(" <patch.asm> [--rom=<file>]") | color(Color::GrayLight)}),
666 hbox({text(" "), text("🔍 Extract Symbols") | bold | color(Color::Cyan), filler(),
667 text("patch extract-symbols") | color(Color::White),
668 text(" <patch.asm>") | color(Color::GrayLight)}),
669 hbox({text(" "), text("✓ Validate Assembly") | bold | color(Color::Cyan), filler(),
670 text("patch validate") | color(Color::White),
671 text(" <patch.asm>") | color(Color::GrayLight)}),
672 text(""),
673
674 separator(),
675 text("") | center,
676 text("📦 PATCH MANAGEMENT") | bold | color(Color::Blue) | center,
677 separator(),
678 hbox({text(" "), text("Apply BPS Patch") | color(Color::Cyan), filler(),
679 text("patch apply-bps") | color(Color::White),
680 text(" <patch.bps> [--rom=<file>]") | color(Color::GrayLight)}),
681 hbox({text(" "), text("Create BPS Patch") | color(Color::Cyan), filler(),
682 text("patch create") | color(Color::White),
683 text(" <src> <modified>") | color(Color::GrayLight)}),
684 text(""),
685
686 separator(),
687 text("") | center,
688 text("🗃️ ROM OPERATIONS") | bold | color(Color::Magenta) | center,
689 separator(),
690 hbox({text(" "), text("Show ROM Info") | color(Color::Cyan), filler(),
691 text("rom info") | color(Color::White),
692 text(" [--rom=<file>]") | color(Color::GrayLight)}),
693 hbox({text(" "), text("Validate ROM") | color(Color::Cyan), filler(),
694 text("rom validate") | color(Color::White),
695 text(" [--rom=<file>]") | color(Color::GrayLight)}),
696 hbox({text(" "), text("Compare ROMs") | color(Color::Cyan), filler(),
697 text("rom diff") | color(Color::White),
698 text(" <rom_a> <rom_b>") | color(Color::GrayLight)}),
699 hbox({text(" "), text("Backup ROM") | color(Color::Cyan), filler(),
700 text("rom backup") | color(Color::White),
701 text(" <rom_file> [name]") | color(Color::GrayLight)}),
702 hbox({text(" "), text("Expand ROM") | color(Color::Cyan), filler(),
703 text("rom expand") | color(Color::White),
704 text(" <rom_file> <size>") | color(Color::GrayLight)}),
705 text(""),
706
707 separator(),
708 text("") | center,
709 text("🏰 EMBEDDED RESOURCE LABELS") | bold | color(Color::Red1) | center,
710 text(" All Zelda3 names built-in and always available to AI") | color(Color::RedLight) | center,
711 separator(),
712 hbox({text(" 📚 296+ Room Names") | color(Color::GreenLight), text(" │ ") | color(Color::GrayDark),
713 text("256 Sprite Names") | color(Color::GreenLight), text(" │ ") | color(Color::GrayDark),
714 text("133 Entrance Names") | color(Color::GreenLight)}),
715 hbox({text(" 🎨 100 Item Names") | color(Color::GreenLight), text(" │ ") | color(Color::GrayDark),
716 text("160 Overworld Maps") | color(Color::GreenLight), text(" │ ") | color(Color::GrayDark),
717 text("48 Music Tracks") | color(Color::GreenLight)}),
718 hbox({text(" 🔧 60 Tile Types") | color(Color::GreenLight), text(" │ ") | color(Color::GrayDark),
719 text("26 Overlord Names") | color(Color::GreenLight), text(" │ ") | color(Color::GrayDark),
720 text("32 GFX Sheets") | color(Color::GreenLight)}),
721 text(""),
722
723 separator(),
724 text("🌐 GLOBAL FLAGS") | bold | color(Color::White) | center,
725 separator(),
726 hbox({text(" --tui") | color(Color::Cyan), filler(),
727 text("Launch Text User Interface") | color(Color::GrayLight)}),
728 hbox({text(" --rom=<file>") | color(Color::Cyan), filler(),
729 text("Specify ROM file") | color(Color::GrayLight)}),
730 hbox({text(" --output=<file>") | color(Color::Cyan), filler(),
731 text("Specify output file") | color(Color::GrayLight)}),
732 hbox({text(" --verbose") | color(Color::Cyan), filler(),
733 text("Enable verbose output") | color(Color::GrayLight)}),
734 hbox({text(" --dry-run") | color(Color::Cyan), filler(),
735 text("Test without changes") | color(Color::GrayLight)}),
736 text(""),
737 separator(),
738 text("Press 'q' to quit, '/' for command palette, 'h' for help") | center | color(Color::GrayLight),
739 });
740
741 auto back_button =
742 Button("Back", [&] { SwitchComponents(screen, LayoutID::kMainMenu); });
743
744 auto container = Container::Vertical({
745 back_button,
746 });
747
748 auto renderer = Renderer(container, [&] {
749 return vbox({
750 help_text | vscroll_indicator | frame | flex,
751 separator(),
752 back_button->Render() | center,
753 }) |
754 border;
755 });
756
757 screen.Loop(renderer);
758}
759
760
761void DashboardComponent(ftxui::ScreenInteractive &screen) {
762 static int selected = 0;
763 MenuOption option;
764 option.focused_entry = &selected;
765 auto menu = Menu(&kMainMenuEntries, &selected, option);
766
767 auto content_renderer = ftxui::Renderer([&] {
768 return vbox({
769 text(GetColoredLogo()) | center,
770 separator(),
771 text("Welcome to the z3ed Dashboard!") | center,
772 text("Select a tool from the menu to begin.") | center | dim,
773 });
774 });
775
776 auto main_container = Container::Horizontal({
777 menu,
778 content_renderer
779 });
780
781 auto layout = Renderer(main_container, [&] {
782 std::string rom_info = app_context.rom.is_loaded() ? app_context.rom.title() : "No ROM";
783 return vbox({
784 hbox({
785 menu->Render() | size(WIDTH, EQUAL, 30) | border,
786 (content_renderer->Render() | center | flex) | border
787 }),
788 hbox({
789 text(rom_info) | bold,
790 filler(),
791 text("q: Quit | ↑/↓: Navigate | Enter: Select")
792 }) | border
793 });
794 });
795
796 auto event_handler = CatchEvent(layout, [&](const Event& event) {
797 if (event == Event::Character('q')) {
798 SwitchComponents(screen, LayoutID::kExit);
799 return true;
800 }
801 if (event == Event::Return) {
802 // Still use SwitchComponents for now to maintain old behavior
803 switch ((MainMenuEntry)selected) {
804 case MainMenuEntry::kLoadRom: SwitchComponents(screen, LayoutID::kLoadRom); break;
805 case MainMenuEntry::kAIAgentChat: SwitchComponents(screen, LayoutID::kAIAgentChat); break;
806 case MainMenuEntry::kTodoManager: SwitchComponents(screen, LayoutID::kTodoManager); break;
807 case MainMenuEntry::kRomTools: SwitchComponents(screen, LayoutID::kRomTools); break;
808 case MainMenuEntry::kGraphicsTools: SwitchComponents(screen, LayoutID::kGraphicsTools); break;
809 case MainMenuEntry::kTestingTools: SwitchComponents(screen, LayoutID::kTestingTools); break;
810 case MainMenuEntry::kSettings: SwitchComponents(screen, LayoutID::kSettings); break;
811 case MainMenuEntry::kHelp: SwitchComponents(screen, LayoutID::kHelp); break;
812 case MainMenuEntry::kExit: SwitchComponents(screen, LayoutID::kExit); break;
813 }
814 return true;
815 }
816 return false;
817 });
818
819 screen.Loop(event_handler);
820}
821
822void MainMenuComponent(ftxui::ScreenInteractive &screen) {
823 // Tracks which menu item is selected.
824 static int selected = 0;
825 MenuOption option;
826 option.focused_entry = &selected;
827 auto menu = Menu(&kMainMenuEntries, &selected, option);
828 menu = CatchEvent(
829 menu, [&](Event event) { return HandleInput(screen, event, selected); });
830
831 std::string rom_information = "ROM not loaded";
832 if (app_context.rom.is_loaded()) {
833 rom_information = app_context.rom.title();
834 }
835
836 // Create ASCII logo with styling
837 auto logo = vbox({
838 text(" ███████╗██████╗ ███████╗██████╗ ") | color(Color::Cyan1) | bold,
839 text(" ╚══███╔╝╚════██╗██╔════╝██╔══██╗") | color(Color::Cyan1) | bold,
840 text(" ███╔╝ █████╔╝█████╗ ██║ ██║") | color(Color::Cyan1) | bold,
841 text(" ███╔╝ ╚═══██╗██╔══╝ ██║ ██║") | color(Color::Cyan1) | bold,
842 text(" ███████╗██████╔╝███████╗██████╔╝") | color(Color::Cyan1) | bold,
843 text(" ╚══════╝╚═════╝ ╚══════╝╚═════╝ ") | color(Color::Cyan1) | bold,
844 text("") | center,
845 hbox({
846 text(" ▲ ") | color(Color::Yellow1) | bold,
847 text("Zelda 3 Editor") | color(Color::White) | bold,
848 }) | center,
849 hbox({
850 text(" ▲ ▲ ") | color(Color::Yellow1) | bold,
851 text("AI-Powered CLI") | color(Color::GrayLight),
852 }) | center,
853 text(" ▲▲▲▲▲ ") | color(Color::Yellow1) | bold | center,
854 });
855
856 auto title = border(hbox({
857 text("v0.3.2") | bold | color(Color::Green1),
858 separator(),
859 text(rom_information) | bold | color(Color::Red1),
860 }));
861
862 auto renderer = Renderer(menu, [&] {
863 return vbox({
864 separator(),
865 logo | center,
866 separator(),
867 title | center,
868 separator(),
869 menu->Render() | center,
870 });
871 });
872
873 // Catch events like pressing Enter to switch layout or pressing 'q' to exit.
874 auto main_component = CatchEvent(renderer, [&](const Event& event) {
875 if (event == Event::Return) {
876 switch ((MainMenuEntry)selected) {
877 case MainMenuEntry::kLoadRom:
878 SwitchComponents(screen, LayoutID::kLoadRom);
879 return true;
880 case MainMenuEntry::kAIAgentChat:
881 SwitchComponents(screen, LayoutID::kAIAgentChat);
882 return true;
883 case MainMenuEntry::kTodoManager:
884 SwitchComponents(screen, LayoutID::kTodoManager);
885 return true;
886 case MainMenuEntry::kRomTools:
887 SwitchComponents(screen, LayoutID::kRomTools);
888 return true;
889 case MainMenuEntry::kGraphicsTools:
890 SwitchComponents(screen, LayoutID::kGraphicsTools);
891 return true;
892 case MainMenuEntry::kTestingTools:
893 SwitchComponents(screen, LayoutID::kTestingTools);
894 return true;
895 case MainMenuEntry::kSettings:
896 SwitchComponents(screen, LayoutID::kSettings);
897 return true;
898 case MainMenuEntry::kHelp:
899 SwitchComponents(screen, LayoutID::kHelp);
900 return true;
901 case MainMenuEntry::kExit:
902 SwitchComponents(screen, LayoutID::kExit);
903 return true;
904 }
905 }
906
907 if (event == Event::Character('q')) {
908 SwitchComponents(screen, LayoutID::kExit);
909 return true;
910 }
911 return false;
912 });
913
914 screen.Loop(main_component);
915}
916
917} // namespace
918
919void ShowMain() {
920 // Use the new unified layout system
921 UnifiedLayout unified_layout(&app_context.rom);
922
923 // Configure the layout
924 LayoutConfig config;
925 config.left_panel_width = 30;
926 config.right_panel_width = 40;
927 config.bottom_panel_height = 15;
928 config.show_chat = true;
929 config.show_status = true;
930 config.show_tools = true;
931
932 unified_layout.SetLayoutConfig(config);
933
934 // Run the unified layout
935 unified_layout.Run();
936}
937
938} // namespace cli
939} // namespace yaze
void SetLayoutConfig(const LayoutConfig &config)
Manages TODO lists for z3ed agent task execution.
absl::Status Initialize()
Initialize the TODO manager and load persisted data.
std::vector< TodoItem > GetAllTodos() const
Get all TODO items.
Definition cli.h:21
void TodoManagerComponent(ftxui::ScreenInteractive &screen)
Definition tui.cc:229
bool HandleInput(ftxui::ScreenInteractive &screen, ftxui::Event &event, int &selected)
Definition tui.cc:34
void LoadRomComponent(ftxui::ScreenInteractive &screen)
Definition tui.cc:562
Element ColorBox(const Color &color)
Definition tui.cc:613
void ApplyAsarPatchComponent(ftxui::ScreenInteractive &screen)
Definition tui.cc:314
void ValidateAssemblyComponent(ftxui::ScreenInteractive &screen)
Definition tui.cc:489
void ApplyBpsPatchComponent(ftxui::ScreenInteractive &screen)
Definition tui.cc:58
void ExtractSymbolsComponent(ftxui::ScreenInteractive &screen)
Definition tui.cc:396
void MainMenuComponent(ftxui::ScreenInteractive &screen)
Definition tui.cc:822
void GenerateSaveFileComponent(ftxui::ScreenInteractive &screen)
Definition tui.cc:133
void HelpComponent(ftxui::ScreenInteractive &screen)
Definition tui.cc:619
void DashboardComponent(ftxui::ScreenInteractive &screen)
Definition tui.cc:761
void ReturnIfRomNotLoaded(ftxui::ScreenInteractive &screen)
Definition tui.cc:51
void SwitchComponents(ftxui::ScreenInteractive &screen, LayoutID layout)
Definition tui.cc:28
const std::vector< std::string > kMainMenuEntries
Definition tui.h:18
MainMenuEntry
Definition tui.h:30
void ShowMain()
Definition tui.cc:919
LayoutID
Definition tui.h:42
std::string GetColoredLogo()
absl::Status ApplyBpsPatch(const std::vector< uint8_t > &source, const std::vector< uint8_t > &patch, std::vector< uint8_t > &output)
Definition bps.cc:79
std::string LoadFile(const std::string &filename)
Loads the entire contents of a file into a string.
Definition file_util.cc:23
Main namespace for the application.