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
21namespace yaze {
22namespace cli {
23
24using namespace ftxui;
25
26namespace {
27void SwitchComponents(ftxui::ScreenInteractive& screen, LayoutID layout) {
28 screen.ExitLoopClosure()();
29 screen.Clear();
30 app_context.current_layout = layout;
31}
32
33bool HandleInput(ftxui::ScreenInteractive& screen, ftxui::Event& event,
34 int& selected) {
35 if (event == Event::ArrowDown || event == Event::Character('j')) {
36 selected++;
37 return true;
38 }
39 if (event == Event::ArrowUp || event == Event::Character('k')) {
40 if (selected != 0)
41 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) {
247 status_emoji = "⏳";
248 break;
250 status_emoji = "🔄";
251 break;
253 status_emoji = "✅";
254 break;
256 status_emoji = "🚫";
257 break;
259 status_emoji = "❌";
260 break;
261 }
262 entries.push_back(absl::StrFormat("%s [%s] %s", status_emoji, item.id,
263 item.description));
264 }
265 return entries;
266 };
267
268 static std::vector<std::string> todo_entries = refresh_todos();
269
270 auto input_field = Input(&new_todo_description, "New TODO description");
271 auto add_button = Button("Add", [&]() {
272 if (!new_todo_description.empty()) {
273 manager.CreateTodo(new_todo_description);
274 new_todo_description.clear();
275 todo_entries = refresh_todos();
276 }
277 });
278
279 auto complete_button = Button("Complete", [&]() {
280 auto todos = manager.GetAllTodos();
281 if (selected_todo < todos.size()) {
282 manager.UpdateStatus(todos[selected_todo].id,
283 agent::TodoItem::Status::COMPLETED);
284 todo_entries = refresh_todos();
285 }
286 });
287
288 auto delete_button = Button("Delete", [&]() {
289 auto todos = manager.GetAllTodos();
290 if (selected_todo < todos.size()) {
291 manager.DeleteTodo(todos[selected_todo].id);
292 if (selected_todo >= todo_entries.size() - 1) {
293 selected_todo--;
294 }
295 todo_entries = refresh_todos();
296 }
297 });
298
299 auto back_button =
300 Button("Back", [&] { SwitchComponents(screen, LayoutID::kMainMenu); });
301
302 auto todo_menu = Menu(&todo_entries, &selected_todo);
303
304 auto container = Container::Vertical({
305 Container::Horizontal({input_field, add_button}),
306 todo_menu,
307 Container::Horizontal({complete_button, delete_button, back_button}),
308 });
309
310 auto renderer = Renderer(container, [&] {
311 return vbox({
312 text("📝 TODO Manager") | bold | center,
313 separator(),
314 hbox({text("New: "), input_field->Render(),
315 add_button->Render()}),
316 separator(),
317 todo_menu->Render() | vscroll_indicator | frame | flex,
318 separator(),
319 hbox({complete_button->Render(), delete_button->Render(),
320 back_button->Render()}) |
321 center,
322 }) |
323 border;
324 });
325
326 screen.Loop(renderer);
327}
328
329void ApplyAsarPatchComponent(ftxui::ScreenInteractive& screen) {
330 ReturnIfRomNotLoaded(screen);
331
332 static std::string asm_file;
333 static std::string output_message;
334 static Color output_color = Color::White;
335
336 auto asm_file_input = Input(&asm_file, "Assembly file (.asm)");
337
338 auto apply_button = Button("Apply Asar Patch", [&] {
339 if (asm_file.empty()) {
340 app_context.error_message = "Please specify an assembly file";
341 SwitchComponents(screen, LayoutID::kError);
342 return;
343 }
344
345 try {
346 // TODO: Use new CommandHandler system for AsarPatch
347 // Reference: src/core/asar_wrapper.cc (AsarWrapper class)
348 output_message =
349 "❌ AsarPatch not yet implemented in new CommandHandler system";
350 output_color = Color::Red;
351 } catch (const std::exception& e) {
352 output_message = "Exception: " + std::string(e.what());
353 output_color = Color::Red;
354 }
355 });
356
357 auto back_button = Button("Back to Main Menu", [&] {
358 output_message.clear();
360 });
361
362 auto container = Container::Vertical({
363 asm_file_input,
364 apply_button,
365 back_button,
366 });
367
368 auto renderer = Renderer(container, [&] {
369 std::vector<Element> elements = {
370 text("Apply Asar Patch") | center | bold,
371 separator(),
372 text("Assembly File:"),
373 asm_file_input->Render(),
374 separator(),
375 apply_button->Render() | center,
376 };
377
378 if (!output_message.empty()) {
379 elements.push_back(separator());
380 elements.push_back(text(output_message) | color(output_color));
381 }
382
383 elements.push_back(separator());
384 elements.push_back(back_button->Render() | center);
385
386 return vbox(elements) | center | border;
387 });
388
389 screen.Loop(renderer);
390}
391
392void PaletteEditorComponent(ftxui::ScreenInteractive& screen) {
393 ReturnIfRomNotLoaded(screen);
394
395 auto back_button = Button("Back to Main Menu", [&] {
397 });
398
399 auto renderer = Renderer(back_button, [&] {
400 return vbox({
401 text("Palette Editor") | center | bold,
402 separator(),
403 text("Palette editing functionality coming soon...") | center,
404 separator(),
405 back_button->Render() | center,
406 }) |
407 center | border;
408 });
409
410 screen.Loop(renderer);
411}
412
413void ExtractSymbolsComponent(ftxui::ScreenInteractive& screen) {
414 static std::string asm_file;
415 static std::vector<std::string> symbols_list;
416 static std::string output_message;
417
418 auto asm_file_input = Input(&asm_file, "Assembly file (.asm)");
419
420 auto extract_button = Button("Extract Symbols", [&] {
421 if (asm_file.empty()) {
422 app_context.error_message = "Please specify an assembly file";
423 SwitchComponents(screen, LayoutID::kError);
424 return;
425 }
426
427 try {
428 core::AsarWrapper wrapper;
429 auto init_status = wrapper.Initialize();
430 if (!init_status.ok()) {
431 app_context.error_message =
432 absl::StrCat("Failed to initialize Asar: ", init_status.message());
433 SwitchComponents(screen, LayoutID::kError);
434 return;
435 }
436
437 auto symbols_result = wrapper.ExtractSymbols(asm_file);
438 if (!symbols_result.ok()) {
439 app_context.error_message = absl::StrCat(
440 "Symbol extraction failed: ", symbols_result.status().message());
442 return;
443 }
444
445 const auto& symbols = symbols_result.value();
446 output_message = absl::StrFormat("✅ Extracted %d symbols from %s",
447 symbols.size(), asm_file);
448
449 symbols_list.clear();
450 for (const auto& symbol : symbols) {
451 symbols_list.push_back(
452 absl::StrFormat("%-20s @ $%06X", symbol.name, symbol.address));
453 }
454
455 } catch (const std::exception& e) {
456 app_context.error_message = "Exception: " + std::string(e.what());
458 }
459 });
460
461 auto back_button = Button("Back to Main Menu", [&] {
462 output_message.clear();
463 symbols_list.clear();
464 SwitchComponents(screen, LayoutID::kMainMenu);
465 });
466
467 auto container = Container::Vertical({
468 asm_file_input,
469 extract_button,
470 back_button,
471 });
472
473 auto renderer = Renderer(container, [&] {
474 std::vector<Element> elements = {
475 text("Extract Assembly Symbols") | center | bold,
476 separator(),
477 text("Assembly File:"),
478 asm_file_input->Render(),
479 separator(),
480 extract_button->Render() | center,
481 };
482
483 if (!output_message.empty()) {
484 elements.push_back(separator());
485 elements.push_back(text(output_message) | color(Color::Green));
486
487 if (!symbols_list.empty()) {
488 elements.push_back(separator());
489 elements.push_back(text("Symbols:") | bold);
490
491 std::vector<Element> symbol_elements;
492 for (const auto& symbol : symbols_list) {
493 symbol_elements.push_back(text(symbol) | color(Color::Cyan));
494 }
495 elements.push_back(vbox(symbol_elements) | frame |
496 size(HEIGHT, LESS_THAN, 15));
497 }
498 }
499
500 elements.push_back(separator());
501 elements.push_back(back_button->Render() | center);
502
503 return vbox(elements) | center | border;
504 });
505
506 screen.Loop(renderer);
507}
508
509void ValidateAssemblyComponent(ftxui::ScreenInteractive& screen) {
510 static std::string asm_file;
511 static std::string output_message;
512 static Color output_color = Color::White;
513
514 auto asm_file_input = Input(&asm_file, "Assembly file (.asm)");
515
516 auto validate_button = Button("Validate Assembly", [&] {
517 if (asm_file.empty()) {
518 app_context.error_message = "Please specify an assembly file";
519 SwitchComponents(screen, LayoutID::kError);
520 return;
521 }
522
523 try {
524 core::AsarWrapper wrapper;
525 auto init_status = wrapper.Initialize();
526 if (!init_status.ok()) {
527 app_context.error_message =
528 absl::StrCat("Failed to initialize Asar: ", init_status.message());
529 SwitchComponents(screen, LayoutID::kError);
530 return;
531 }
532
533 auto validation_status = wrapper.ValidateAssembly(asm_file);
534 if (validation_status.ok()) {
535 output_message = "✅ Assembly file is valid!";
536 output_color = Color::Green;
537 } else {
538 output_message = absl::StrCat("❌ Validation failed:\n",
539 validation_status.message());
540 output_color = Color::Red;
541 }
542
543 } catch (const std::exception& e) {
544 app_context.error_message = "Exception: " + std::string(e.what());
545 SwitchComponents(screen, LayoutID::kError);
546 }
547 });
548
549 auto back_button = Button("Back to Main Menu", [&] {
550 output_message.clear();
551 SwitchComponents(screen, LayoutID::kMainMenu);
552 });
553
554 auto container = Container::Vertical({
555 asm_file_input,
556 validate_button,
557 back_button,
558 });
559
560 auto renderer = Renderer(container, [&] {
561 std::vector<Element> elements = {
562 text("Validate Assembly File") | center | bold,
563 separator(),
564 text("Assembly File:"),
565 asm_file_input->Render(),
566 separator(),
567 validate_button->Render() | center,
568 };
569
570 if (!output_message.empty()) {
571 elements.push_back(separator());
572 elements.push_back(text(output_message) | color(output_color));
573 }
574
575 elements.push_back(separator());
576 elements.push_back(back_button->Render() | center);
577
578 return vbox(elements) | center | border;
579 });
580
581 screen.Loop(renderer);
582}
583
584void LoadRomComponent(ftxui::ScreenInteractive& screen) {
585 static std::string rom_file;
586 auto rom_file_input = Input(&rom_file, "ROM file path");
587
588 auto load_button = Button("Load ROM", [&] {
589 // Load the ROM file here.
590 auto rom_status = app_context.rom.LoadFromFile(rom_file);
591 if (!rom_status.ok()) {
592 app_context.error_message =
593 std::string(rom_status.message().data(), rom_status.message().size());
594 SwitchComponents(screen, LayoutID::kError);
595 return;
596 }
597 // If the ROM is loaded successfully, switch to the main menu.
598 SwitchComponents(screen, LayoutID::kMainMenu);
599 });
600
601 auto browse_button = Button("Browse...", [&] {
602 // TODO: Implement file dialog
603 // For now, show a placeholder
604 rom_file = "/path/to/your/rom.sfc";
605 });
606
607 auto back_button =
608 Button("Back", [&] { SwitchComponents(screen, LayoutID::kMainMenu); });
609
610 auto container = Container::Vertical({
611 Container::Horizontal({rom_file_input, browse_button}),
612 load_button,
613 back_button,
614 });
615
616 auto renderer = Renderer(container, [&] {
617 return vbox({
618 text("Load ROM") | center | bold,
619 separator(),
620 text("Enter ROM File Path:"),
621 hbox({
622 rom_file_input->Render() | flex,
623 separator(),
624 browse_button->Render(),
625 }),
626 separator(),
627 load_button->Render() | center,
628 separator(),
629 back_button->Render() | center,
630 }) |
631 center | border;
632 });
633
634 screen.Loop(renderer);
635}
636
637Element ColorBox(const Color& color) {
638 return ftxui::text(" ") | ftxui::bgcolor(color);
639}
640
641void HelpComponent(ftxui::ScreenInteractive& screen) {
642 auto help_text = vbox({
643 // Header
644 text("╔══════════════════════════════════════════════════════════╗") |
645 color(Color::Cyan1) | bold,
646 text("║ Z3ED v0.3.2 - AI-Powered CLI ║") |
647 color(Color::Cyan1) | bold,
648 text("║ The Legend of Zelda: A Link to the Past Editor ║") |
649 color(Color::White),
650 text("╚══════════════════════════════════════════════════════════╝") |
651 color(Color::Cyan1) | bold,
652 text(""),
653 hbox({
654 text("✨ Author: ") | color(Color::Yellow1) | bold,
655 text("scawful") | color(Color::Magenta),
656 text(" │ ") | color(Color::GrayDark),
657 text("🤖 AI: ") | color(Color::Green1) | bold,
658 text("Ollama + Gemini Integration") | color(Color::GreenLight),
659 }) | center,
660 text(""),
661 separator(),
662
663 // AI Agent Commands
664 text("") | center,
665 text("🤖 AI AGENT COMMANDS") | bold | color(Color::Green1) | center,
666 text(" Conversational AI for ROM inspection and modification") |
667 color(Color::GreenLight) | center,
668 separator(),
669 hbox({text(" "), text("💬 Test Chat Mode") | bold | color(Color::Cyan),
670 filler(), text("agent test-conversation") | color(Color::White),
671 text(" [--rom=<file>] [--verbose]") | color(Color::GrayLight)}),
672 hbox({text(" "),
673 text("→ Interactive AI testing with embedded labels") |
674 color(Color::GrayLight)}),
675 text(""),
676 hbox({text(" "), text("📊 Chat with AI") | bold | color(Color::Cyan),
677 filler(), text("agent chat") | color(Color::White),
678 text(" <prompt> [--host] [--port]") | color(Color::GrayLight)}),
679 hbox({text(" "), text("→ Natural language ROM inspection (rooms, "
680 "sprites, entrances)") |
681 color(Color::GrayLight)}),
682 text(""),
683 hbox({text(" "), text("🎯 Simple Chat") | bold | color(Color::Cyan),
684 filler(), text("agent simple-chat") | color(Color::White),
685 text(" <prompt> [--rom=<file>]") | color(Color::GrayLight)}),
686 hbox({text(" "),
687 text("→ Quick AI queries with automatic ROM loading") |
688 color(Color::GrayLight)}),
689 text(""),
690
691 separator(),
692 text("") | center,
693 text("🎯 ASAR 65816 ASSEMBLER") | bold | color(Color::Yellow1) | center,
694 text(" Assemble and patch with Asar integration") |
695 color(Color::YellowLight) | center,
696 separator(),
697 hbox({text(" "), text("⚡ Apply Patch") | bold | color(Color::Cyan),
698 filler(), text("patch apply-asar") | color(Color::White),
699 text(" <patch.asm> [--rom=<file>]") | color(Color::GrayLight)}),
700 hbox({text(" "), text("🔍 Extract Symbols") | bold | color(Color::Cyan),
701 filler(), text("patch extract-symbols") | color(Color::White),
702 text(" <patch.asm>") | color(Color::GrayLight)}),
703 hbox({text(" "), text("✓ Validate Assembly") | bold | color(Color::Cyan),
704 filler(), text("patch validate") | color(Color::White),
705 text(" <patch.asm>") | color(Color::GrayLight)}),
706 text(""),
707
708 separator(),
709 text("") | center,
710 text("📦 PATCH MANAGEMENT") | bold | color(Color::Blue) | center,
711 separator(),
712 hbox({text(" "), text("Apply BPS Patch") | color(Color::Cyan), filler(),
713 text("patch apply-bps") | color(Color::White),
714 text(" <patch.bps> [--rom=<file>]") | color(Color::GrayLight)}),
715 hbox({text(" "), text("Create BPS Patch") | color(Color::Cyan), filler(),
716 text("patch create") | color(Color::White),
717 text(" <src> <modified>") | color(Color::GrayLight)}),
718 text(""),
719
720 separator(),
721 text("") | center,
722 text("🗃️ ROM OPERATIONS") | bold | color(Color::Magenta) | center,
723 separator(),
724 hbox({text(" "), text("Show ROM Info") | color(Color::Cyan), filler(),
725 text("rom info") | color(Color::White),
726 text(" [--rom=<file>]") | color(Color::GrayLight)}),
727 hbox({text(" "), text("Validate ROM") | color(Color::Cyan), filler(),
728 text("rom validate") | color(Color::White),
729 text(" [--rom=<file>]") | color(Color::GrayLight)}),
730 hbox({text(" "), text("Compare ROMs") | color(Color::Cyan), filler(),
731 text("rom diff") | color(Color::White),
732 text(" <rom_a> <rom_b>") | color(Color::GrayLight)}),
733 hbox({text(" "), text("Backup ROM") | color(Color::Cyan), filler(),
734 text("rom backup") | color(Color::White),
735 text(" <rom_file> [name]") | color(Color::GrayLight)}),
736 hbox({text(" "), text("Expand ROM") | color(Color::Cyan), filler(),
737 text("rom expand") | color(Color::White),
738 text(" <rom_file> <size>") | color(Color::GrayLight)}),
739 text(""),
740
741 separator(),
742 text("") | center,
743 text("🏰 EMBEDDED RESOURCE LABELS") | bold | color(Color::Red1) | center,
744 text(" All Zelda3 names built-in and always available to AI") |
745 color(Color::RedLight) | center,
746 separator(),
747 hbox({text(" 📚 296+ Room Names") | color(Color::GreenLight),
748 text(" │ ") | color(Color::GrayDark),
749 text("256 Sprite Names") | color(Color::GreenLight),
750 text(" │ ") | color(Color::GrayDark),
751 text("133 Entrance Names") | color(Color::GreenLight)}),
752 hbox({text(" 🎨 100 Item Names") | color(Color::GreenLight),
753 text(" │ ") | color(Color::GrayDark),
754 text("160 Overworld Maps") | color(Color::GreenLight),
755 text(" │ ") | color(Color::GrayDark),
756 text("48 Music Tracks") | color(Color::GreenLight)}),
757 hbox({text(" 🔧 60 Tile Types") | color(Color::GreenLight),
758 text(" │ ") | color(Color::GrayDark),
759 text("26 Overlord Names") | color(Color::GreenLight),
760 text(" │ ") | color(Color::GrayDark),
761 text("32 GFX Sheets") | color(Color::GreenLight)}),
762 text(""),
763
764 separator(),
765 text("🌐 GLOBAL FLAGS") | bold | color(Color::White) | center,
766 separator(),
767 hbox({text(" --tui") | color(Color::Cyan), filler(),
768 text("Launch Text User Interface") | color(Color::GrayLight)}),
769 hbox({text(" --rom=<file>") | color(Color::Cyan), filler(),
770 text("Specify ROM file") | color(Color::GrayLight)}),
771 hbox({text(" --output=<file>") | color(Color::Cyan), filler(),
772 text("Specify output file") | color(Color::GrayLight)}),
773 hbox({text(" --verbose") | color(Color::Cyan), filler(),
774 text("Enable verbose output") | color(Color::GrayLight)}),
775 hbox({text(" --dry-run") | color(Color::Cyan), filler(),
776 text("Test without changes") | color(Color::GrayLight)}),
777 text(""),
778 separator(),
779 text("Press 'q' to quit, '/' for command palette, 'h' for help") |
780 center | color(Color::GrayLight),
781 });
782
783 auto back_button =
784 Button("Back", [&] { SwitchComponents(screen, LayoutID::kMainMenu); });
785
786 auto container = Container::Vertical({
787 back_button,
788 });
789
790 auto renderer = Renderer(container, [&] {
791 return vbox({
792 help_text | vscroll_indicator | frame | flex,
793 separator(),
794 back_button->Render() | center,
795 }) |
796 border;
797 });
798
799 screen.Loop(renderer);
800}
801
802void DashboardComponent(ftxui::ScreenInteractive& screen) {
803 static int selected = 0;
804 MenuOption option;
805 option.focused_entry = &selected;
806 auto menu = Menu(&kMainMenuEntries, &selected, option);
807
808 auto content_renderer = ftxui::Renderer([&] {
809 return vbox({
810 text(GetColoredLogo()) | center,
811 separator(),
812 text("Welcome to the z3ed Dashboard!") | center,
813 text("Select a tool from the menu to begin.") | center | dim,
814 });
815 });
816
817 auto main_container = Container::Horizontal({menu, content_renderer});
818
819 auto layout = Renderer(main_container, [&] {
820 std::string rom_info =
821 app_context.rom.is_loaded() ? app_context.rom.title() : "No ROM";
822 return vbox({hbox({menu->Render() | size(WIDTH, EQUAL, 30) | border,
823 (content_renderer->Render() | center | flex) | border}),
824 hbox({text(rom_info) | bold, filler(),
825 text("q: Quit | ↑/↓: Navigate | Enter: Select")}) |
826 border});
827 });
828
829 auto event_handler = CatchEvent(layout, [&](const Event& event) {
830 if (event == Event::Character('q')) {
831 SwitchComponents(screen, LayoutID::kExit);
832 return true;
833 }
834 if (event == Event::Return) {
835 // Still use SwitchComponents for now to maintain old behavior
836 switch ((MainMenuEntry)selected) {
837 case MainMenuEntry::kLoadRom:
838 SwitchComponents(screen, LayoutID::kLoadRom);
839 break;
840 case MainMenuEntry::kAIAgentChat:
841 SwitchComponents(screen, LayoutID::kAIAgentChat);
842 break;
843 case MainMenuEntry::kTodoManager:
844 SwitchComponents(screen, LayoutID::kTodoManager);
845 break;
846 case MainMenuEntry::kRomTools:
847 SwitchComponents(screen, LayoutID::kRomTools);
848 break;
849 case MainMenuEntry::kGraphicsTools:
850 SwitchComponents(screen, LayoutID::kGraphicsTools);
851 break;
852 case MainMenuEntry::kTestingTools:
853 SwitchComponents(screen, LayoutID::kTestingTools);
854 break;
855 case MainMenuEntry::kSettings:
856 SwitchComponents(screen, LayoutID::kSettings);
857 break;
858 case MainMenuEntry::kHelp:
859 SwitchComponents(screen, LayoutID::kHelp);
860 break;
861 case MainMenuEntry::kExit:
862 SwitchComponents(screen, LayoutID::kExit);
863 break;
864 }
865 return true;
866 }
867 return false;
868 });
869
870 screen.Loop(event_handler);
871}
872
873void MainMenuComponent(ftxui::ScreenInteractive& screen) {
874 // Tracks which menu item is selected.
875 static int selected = 0;
876 MenuOption option;
877 option.focused_entry = &selected;
878 auto menu = Menu(&kMainMenuEntries, &selected, option);
879 menu = CatchEvent(
880 menu, [&](Event event) { return HandleInput(screen, event, selected); });
881
882 std::string rom_information = "ROM not loaded";
883 if (app_context.rom.is_loaded()) {
884 rom_information = app_context.rom.title();
885 }
886
887 // Create ASCII logo with styling
888 auto logo = vbox({
889 text(" ███████╗██████╗ ███████╗██████╗ ") | color(Color::Cyan1) | bold,
890 text(" ╚══███╔╝╚════██╗██╔════╝██╔══██╗") | color(Color::Cyan1) | bold,
891 text(" ███╔╝ █████╔╝█████╗ ██║ ██║") | color(Color::Cyan1) | bold,
892 text(" ███╔╝ ╚═══██╗██╔══╝ ██║ ██║") | color(Color::Cyan1) | bold,
893 text(" ███████╗██████╔╝███████╗██████╔╝") | color(Color::Cyan1) | bold,
894 text(" ╚══════╝╚═════╝ ╚══════╝╚═════╝ ") | color(Color::Cyan1) | bold,
895 text("") | center,
896 hbox({
897 text(" ▲ ") | color(Color::Yellow1) | bold,
898 text("Zelda 3 Editor") | color(Color::White) | bold,
899 }) | center,
900 hbox({
901 text(" ▲ ▲ ") | color(Color::Yellow1) | bold,
902 text("AI-Powered CLI") | color(Color::GrayLight),
903 }) | center,
904 text(" ▲▲▲▲▲ ") | color(Color::Yellow1) | bold | center,
905 });
906
907 auto title = border(hbox({
908 text("v0.3.2") | bold | color(Color::Green1),
909 separator(),
910 text(rom_information) | bold | color(Color::Red1),
911 }));
912
913 auto renderer = Renderer(menu, [&] {
914 return vbox({
915 separator(),
916 logo | center,
917 separator(),
918 title | center,
919 separator(),
920 menu->Render() | center,
921 });
922 });
923
924 // Catch events like pressing Enter to switch layout or pressing 'q' to exit.
925 auto main_component = CatchEvent(renderer, [&](const Event& event) {
926 if (event == Event::Return) {
927 switch ((MainMenuEntry)selected) {
928 case MainMenuEntry::kLoadRom:
929 SwitchComponents(screen, LayoutID::kLoadRom);
930 return true;
931 case MainMenuEntry::kAIAgentChat:
932 SwitchComponents(screen, LayoutID::kAIAgentChat);
933 return true;
934 case MainMenuEntry::kTodoManager:
935 SwitchComponents(screen, LayoutID::kTodoManager);
936 return true;
937 case MainMenuEntry::kRomTools:
938 SwitchComponents(screen, LayoutID::kRomTools);
939 return true;
940 case MainMenuEntry::kGraphicsTools:
941 SwitchComponents(screen, LayoutID::kGraphicsTools);
942 return true;
943 case MainMenuEntry::kTestingTools:
944 SwitchComponents(screen, LayoutID::kTestingTools);
945 return true;
946 case MainMenuEntry::kSettings:
947 SwitchComponents(screen, LayoutID::kSettings);
948 return true;
949 case MainMenuEntry::kHelp:
950 SwitchComponents(screen, LayoutID::kHelp);
951 return true;
952 case MainMenuEntry::kExit:
953 SwitchComponents(screen, LayoutID::kExit);
954 return true;
955 }
956 }
957
958 if (event == Event::Character('q')) {
959 SwitchComponents(screen, LayoutID::kExit);
960 return true;
961 }
962 return false;
963 });
964
965 screen.Loop(main_component);
966}
967
968} // namespace
969
970void ShowMain() {
971 // Use the new unified layout system
972 UnifiedLayout unified_layout(&app_context.rom);
973
974 // Configure the layout
975 LayoutConfig config;
976 config.left_panel_width = 30;
977 config.right_panel_width = 40;
978 config.bottom_panel_height = 15;
979 config.show_chat = true;
980 config.show_status = true;
981 config.show_tools = true;
982
983 unified_layout.SetLayoutConfig(config);
984
985 // Run the unified layout
986 unified_layout.Run();
987}
988
989} // namespace cli
990} // 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:17
void TodoManagerComponent(ftxui::ScreenInteractive &screen)
Definition tui.cc:229
bool HandleInput(ftxui::ScreenInteractive &screen, ftxui::Event &event, int &selected)
Definition tui.cc:33
void LoadRomComponent(ftxui::ScreenInteractive &screen)
Definition tui.cc:584
Element ColorBox(const Color &color)
Definition tui.cc:637
void ApplyAsarPatchComponent(ftxui::ScreenInteractive &screen)
Definition tui.cc:329
void ValidateAssemblyComponent(ftxui::ScreenInteractive &screen)
Definition tui.cc:509
void ApplyBpsPatchComponent(ftxui::ScreenInteractive &screen)
Definition tui.cc:58
void ExtractSymbolsComponent(ftxui::ScreenInteractive &screen)
Definition tui.cc:413
void MainMenuComponent(ftxui::ScreenInteractive &screen)
Definition tui.cc:873
void GenerateSaveFileComponent(ftxui::ScreenInteractive &screen)
Definition tui.cc:133
void HelpComponent(ftxui::ScreenInteractive &screen)
Definition tui.cc:641
void DashboardComponent(ftxui::ScreenInteractive &screen)
Definition tui.cc:802
void ReturnIfRomNotLoaded(ftxui::ScreenInteractive &screen)
Definition tui.cc:51
void SwitchComponents(ftxui::ScreenInteractive &screen, LayoutID layout)
Definition tui.cc:27
const std::vector< std::string > kMainMenuEntries
Definition tui.h:21
MainMenuEntry
Definition tui.h:33
void ShowMain()
Definition tui.cc:970
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