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