yaze 0.3.2
Link to the Past ROM Editor
 
Loading...
Searching...
No Matches
message_editor.cc
Go to the documentation of this file.
1#include "message_editor.h"
2
3#include <algorithm>
4#include <string>
5#include <unordered_map>
6#include <vector>
7
8#include "absl/status/status.h"
9#include "absl/strings/str_cat.h"
10#include "absl/strings/str_format.h"
13#include "app/gfx/core/bitmap.h"
19#include "app/gui/core/icons.h"
20#include "app/gui/core/input.h"
21#include "app/gui/core/style.h"
22#include "imgui.h"
23#include "imgui/misc/cpp/imgui_stdlib.h"
24#include "rom/rom.h"
25#include "util/file_util.h"
26#include "util/hex.h"
27#include "util/log.h"
28
29namespace yaze {
30namespace editor {
31
32namespace {
33std::string DisplayTextOverflowError(int pos, bool bank) {
34 int space = bank ? kTextDataEnd - kTextData : kTextData2End - kTextData2;
35 std::string bankSTR = bank ? "1st" : "2nd";
36 std::string posSTR =
37 bank ? absl::StrFormat("%X4", pos & 0xFFFF)
38 : absl::StrFormat("%X4", (pos - kTextData2) & 0xFFFF);
39 std::string message = absl::StrFormat(
40 "There is too much text data in the %s block to save.\n"
41 "Available: %X4 | Used: %s",
42 bankSTR, space, posSTR);
43 return message;
44}
45} // namespace
46
47using ImGui::BeginChild;
48using ImGui::BeginTable;
49using ImGui::Button;
50using ImGui::EndChild;
51using ImGui::EndTable;
52using ImGui::InputTextMultiline;
53using ImGui::PopID;
54using ImGui::PushID;
55using ImGui::SameLine;
56using ImGui::Separator;
57using ImGui::TableHeadersRow;
58using ImGui::TableNextColumn;
59using ImGui::TableSetupColumn;
60using ImGui::Text;
61using ImGui::TextWrapped;
62
63constexpr ImGuiTableFlags kMessageTableFlags = ImGuiTableFlags_Hideable |
64 ImGuiTableFlags_Borders |
65 ImGuiTableFlags_Resizable;
66
69 // Register panels with PanelManager (dependency injection)
71 return;
72
73 auto* panel_manager = dependencies_.panel_manager;
74 const size_t session_id = dependencies_.session_id;
75
76 // Register EditorPanel implementations (they provide both metadata and drawing)
77 panel_manager->RegisterEditorPanel(
78 std::make_unique<MessageListPanel>([this]() { DrawMessageList(); }));
79 panel_manager->RegisterEditorPanel(
80 std::make_unique<MessageEditorPanel>([this]() { DrawCurrentMessage(); }));
81 panel_manager->RegisterEditorPanel(std::make_unique<FontAtlasPanel>([this]() {
84 }));
85 panel_manager->RegisterEditorPanel(
86 std::make_unique<DictionaryPanel>([this]() {
90 }));
91
92 // Show message list by default
93 panel_manager->ShowPanel(session_id, "message.message_list");
94
95 for (int i = 0; i < kWidthArraySize; i++) {
97 }
98
100 list_of_texts_ = ReadAllTextData(rom()->mutable_data());
101 LOG_INFO("MessageEditor", "Loaded %zu messages from ROM",
102 list_of_texts_.size());
103
108 }
111
113
114 if (!list_of_texts_.empty()) {
115 // Default to message 1 if available, otherwise 0
116 size_t default_idx = list_of_texts_.size() > 1 ? 1 : 0;
117 current_message_ = list_of_texts_[default_idx];
122 } else {
123 LOG_ERROR("MessageEditor", "No messages found in ROM!");
124 }
125}
126
127bool MessageEditor::OpenMessageById(int display_id) {
128 const int vanilla_count = static_cast<int>(list_of_texts_.size());
129 const int expanded_base_id = expanded_message_base_id_;
130
131 int expanded_count = static_cast<int>(expanded_messages_.size());
132 auto resolved =
133 ResolveMessageDisplayId(display_id, vanilla_count, expanded_base_id,
134 expanded_count);
135
136 // Convenience: if an expanded ID is requested but we haven't loaded expanded
137 // messages yet, try loading from ROM once.
138 if (!resolved.has_value() && expanded_count == 0 &&
139 display_id >= expanded_base_id && rom_ && rom_->is_loaded()) {
140 const int start = GetExpandedTextDataStart();
141 const int end = GetExpandedTextDataEnd();
142 const size_t rom_size = rom_->size();
143 if (start >= 0 && end >= start &&
144 static_cast<size_t>(end) < rom_size) {
145 const auto status = LoadExpandedMessagesFromRom();
146 if (!status.ok()) {
147 LOG_DEBUG("MessageEditor",
148 "OpenMessageById: expanded load skipped/failed: %s",
149 std::string(status.message()).c_str());
150 }
151 expanded_count = static_cast<int>(expanded_messages_.size());
152 resolved = ResolveMessageDisplayId(display_id, vanilla_count,
153 expanded_base_id, expanded_count);
154 } else {
155 LOG_DEBUG("MessageEditor",
156 "OpenMessageById: expanded region out of bounds (0x%X-0x%X, rom=0x%zX)",
157 start, end, rom_size);
158 }
159 }
160
161 if (!resolved.has_value()) {
162 return false;
163 }
164
166 const size_t session_id = dependencies_.session_id;
167 dependencies_.panel_manager->ShowPanel(session_id, "message.message_list");
168 dependencies_.panel_manager->ShowPanel(session_id, "message.message_editor");
169 }
170
171 if (!resolved->is_expanded) {
172 const int idx = resolved->index;
173 if (idx < 0 || idx >= vanilla_count) {
174 return false;
175 }
176
177 const auto& message = list_of_texts_[idx];
178 current_message_ = message;
179 current_message_index_ = message.ID;
181
182 const int parsed_idx = resolved->display_id;
183 if (parsed_idx >= 0 &&
184 parsed_idx < static_cast<int>(parsed_messages_.size())) {
186 } else {
187 message_text_box_.text.clear();
188 }
189
191 return true;
192 }
193
194 // Expanded message.
195 const int idx = resolved->index;
196 if (idx < 0 || idx >= expanded_count) {
197 return false;
198 }
199
200 const auto& message = expanded_messages_[idx];
201 current_message_ = message;
202 current_message_index_ = message.ID;
204
205 const int parsed_idx = resolved->display_id;
206 if (parsed_idx >= 0 && parsed_idx < static_cast<int>(parsed_messages_.size())) {
208 } else {
209 message_text_box_.text.clear();
210 }
211
213 return true;
214}
215
217 int base_id = static_cast<int>(list_of_texts_.size());
219 const auto& layout = dependencies_.project->hack_manifest.message_layout();
220 if (layout.first_expanded_id != 0) {
221 base_id = static_cast<int>(layout.first_expanded_id);
222 }
223 }
224
225 // Never allow the expanded base to precede the vanilla message count; this
226 // prevents truncating/overlapping IDs when the manifest is missing/mistyped.
227 base_id = std::max(base_id, static_cast<int>(list_of_texts_.size()));
228 return base_id;
229}
230
232 if (game_data() && !game_data()->palette_groups.hud.empty()) {
234 }
235
238 }
239}
240
242 std::vector<gfx::SnesColor> colors;
243 colors.reserve(16);
244 for (int i = 0; i < 16; ++i) {
245 const float value = static_cast<float>(i) / 15.0f;
246 colors.emplace_back(ImVec4(value, value, value, 1.0f));
247 }
248
249 if (!colors.empty()) {
250 colors[0].set_transparent(true);
251 }
252
253 return gfx::SnesPalette(colors);
254}
255
258 if (!rom() || !rom()->is_loaded()) {
259 LOG_WARN("MessageEditor", "ROM not loaded - skipping font graphics load");
260 return;
261 }
262
263 std::fill(raw_font_gfx_data_.begin(), raw_font_gfx_data_.end(), 0);
264
265 const size_t rom_size = rom()->size();
266 if (rom_size > static_cast<size_t>(kGfxFont)) {
267 const size_t available =
268 std::min(raw_font_gfx_data_.size(),
269 rom_size - static_cast<size_t>(kGfxFont));
270 std::copy_n(rom()->data() + kGfxFont, available, raw_font_gfx_data_.begin());
271 if (available < raw_font_gfx_data_.size()) {
272 LOG_WARN("MessageEditor",
273 "Font graphics truncated (ROM size %zu, read %zu bytes)",
274 rom_size, available);
275 }
276 } else {
277 LOG_WARN("MessageEditor",
278 "ROM size %zu too small for font graphics offset 0x%X", rom_size,
279 kGfxFont);
280 }
281
283 gfx::SnesTo8bppSheet(raw_font_gfx_data_, /*bpp=*/2, /*num_sheets=*/2);
284
285 auto load_font = zelda3::LoadFontGraphics(*rom());
286 if (load_font.ok()) {
287 message_preview_.font_gfx16_data_2_ = load_font.value().vector();
288 } else {
289 const std::string error_message(load_font.status().message());
290 LOG_WARN("MessageEditor", "LoadFontGraphics failed: %s",
291 error_message.c_str());
292 }
293
294 const auto& font_data = !message_preview_.font_gfx16_data_.empty()
297 RefreshFontAtlasBitmap(font_data);
299}
300
302 const std::vector<uint8_t>& font_data) {
303 if (font_data.empty()) {
304 LOG_WARN("MessageEditor", "Font graphics data missing - atlas stays empty");
305 return;
306 }
307
308 const int atlas_width = kFontGfxMessageSize;
309 const size_t row_count =
310 (font_data.size() + atlas_width - 1) / atlas_width;
311 const int atlas_height =
312 static_cast<int>(std::max<size_t>(1, row_count));
313
314 const size_t expected_size =
315 static_cast<size_t>(atlas_width) * atlas_height;
316 std::vector<uint8_t> padded(font_data.begin(), font_data.end());
317 if (padded.size() < expected_size) {
318 padded.resize(expected_size, 0);
319 } else if (padded.size() > expected_size) {
320 padded.resize(expected_size);
321 }
322
323 font_gfx_bitmap_.Create(atlas_width, atlas_height, kFontGfxMessageDepth,
324 padded);
327 }
330}
331
332absl::Status MessageEditor::Load() {
333 gfx::ScopedTimer timer("MessageEditor::Load");
334 return absl::OkStatus();
335}
336
337absl::Status MessageEditor::Update() {
338 // Panel drawing is handled centrally by PanelManager::DrawAllVisiblePanels()
339 // via the EditorPanel implementations registered in Initialize().
340 // No local drawing needed here.
341 return absl::OkStatus();
342}
343
348
351
353 return;
354 }
355
356 auto queue_refresh = [](gfx::Bitmap& bitmap) {
357 if (!bitmap.is_active()) {
358 return;
359 }
360 const auto command = bitmap.texture()
363 gfx::Arena::Get().QueueTextureCommand(command, &bitmap);
364 };
365
367 queue_refresh(font_gfx_bitmap_);
368
371 queue_refresh(current_font_gfx16_bitmap_);
372 }
373}
374
392
395 if (BeginChild("##MessagesList", ImVec2(0, 0), true,
396 ImGuiWindowFlags_AlwaysVerticalScrollbar)) {
398 if (ImGui::Button("Import Bundle")) {
400 if (!path.empty()) {
402 }
403 }
404 ImGui::SameLine();
405 if (ImGui::Button("Export Bundle")) {
407 if (!path.empty()) {
408 auto status =
410 if (!status.ok()) {
412 absl::StrFormat("Export failed: %s", status.message());
414 } else {
416 absl::StrFormat("Exported bundle: %s", path);
418 }
419 }
420 }
421 if (!message_bundle_status_.empty()) {
422 ImVec4 color = message_bundle_status_error_
423 ? ImVec4(1.0f, 0.4f, 0.4f, 1.0f)
424 : ImVec4(0.6f, 0.9f, 0.6f, 1.0f);
425 ImGui::TextColored(color, "%s", message_bundle_status_.c_str());
426 }
427 ImGui::Separator();
428 if (BeginTable("##MessagesTable", 4, kMessageTableFlags)) {
429 TableSetupColumn("ID", ImGuiTableColumnFlags_WidthFixed, 50);
430 TableSetupColumn("Type", ImGuiTableColumnFlags_WidthFixed, 80);
431 TableSetupColumn("Contents", ImGuiTableColumnFlags_WidthStretch);
432 TableSetupColumn("Address", ImGuiTableColumnFlags_WidthFixed, 100);
433
434 TableHeadersRow();
435
436 // Calculate total rows for clipper
437 const int vanilla_count = static_cast<int>(list_of_texts_.size());
438 const int expanded_count = static_cast<int>(expanded_messages_.size());
439 const int total_rows = vanilla_count + expanded_count;
440
441 // Use ImGuiListClipper for virtualized rendering
442 ImGuiListClipper clipper;
443 clipper.Begin(total_rows);
444
445 while (clipper.Step()) {
446 for (int row = clipper.DisplayStart; row < clipper.DisplayEnd; ++row) {
447 if (row < vanilla_count) {
448 // Vanilla message
449 const auto& message = list_of_texts_[row];
450 TableNextColumn();
451 PushID(message.ID);
452 if (Button(util::HexWord(message.ID).c_str())) {
454 current_message_ = message;
455 current_message_index_ = message.ID;
459 }
460 PopID();
461
462 TableNextColumn();
463 ImGui::TextColored(ImVec4(0.6f, 0.6f, 1.0f, 1.0f), "Vanilla");
464
465 TableNextColumn();
466 TextWrapped("%s", parsed_messages_[message.ID].c_str());
467
468 TableNextColumn();
469 TextWrapped("%s", util::HexLong(message.Address).c_str());
470 } else {
471 // Expanded message
472 int expanded_idx = row - vanilla_count;
473 const auto& expanded_message = expanded_messages_[expanded_idx];
474 const int display_id =
475 expanded_message_base_id_ + expanded_message.ID;
476 const char* display_text = "Missing text";
477 if (display_id >= 0 &&
478 display_id < static_cast<int>(parsed_messages_.size())) {
479 display_text = parsed_messages_[display_id].c_str();
480 }
481 TableNextColumn();
482 PushID(display_id);
483 if (Button(util::HexWord(display_id).c_str())) {
485 current_message_ = expanded_message;
486 current_message_index_ = expanded_message.ID;
488 message_text_box_.text = display_text;
490 }
491 PopID();
492
493 TableNextColumn();
494 ImGui::TextColored(ImVec4(1.0f, 0.7f, 0.4f, 1.0f), "Expanded");
495
496 TableNextColumn();
497 TextWrapped("%s", display_text);
498
499 TableNextColumn();
500 TextWrapped("%s", util::HexLong(expanded_message.Address).c_str());
501 }
502 }
503 }
504
505 EndTable();
506 }
507 }
508 EndChild();
509}
510
512 Button(absl::StrCat("Message ", current_message_.ID).c_str());
513 if (InputTextMultiline("##MessageEditor", &message_text_box_.text,
514 ImVec2(ImGui::GetContentRegionAvail().x, 0))) {
516 }
517 if (ImGui::IsItemDeactivatedAfterEdit()) {
519 }
521 if (!line_warnings.empty()) {
522 ImGui::TextColored(ImVec4(1.0f, 0.75f, 0.3f, 1.0f),
523 "Line width warnings");
524 for (const auto& warning : line_warnings) {
525 ImGui::BulletText("%s", warning.c_str());
526 }
527 }
528 Separator();
530
531 ImGui::BeginChild("##MessagePreview", ImVec2(0, 0), true);
533 Text("Message Preview");
534 if (Button("View Palette")) {
535 ImGui::OpenPopup("Palette");
536 }
537 if (ImGui::BeginPopup("Palette")) {
539 ImGui::EndPopup();
540 }
542 BeginChild("CurrentGfxFont", ImVec2(348, 0), true,
543 ImGuiWindowFlags_NoScrollWithMouse);
547
548 // Handle mouse wheel scrolling
549 if (ImGui::IsWindowHovered()) {
550 float wheel = ImGui::GetIO().MouseWheel;
551 if (wheel > 0 && message_preview_.shown_lines > 0) {
553 } else if (wheel < 0 &&
556 }
557 }
558
559 // Draw only the visible portion of the text
560 const ImVec2 preview_canvas_size = current_font_gfx16_canvas_.canvas_size();
561 const float dest_width = std::max(0.0f, preview_canvas_size.x - 8.0f);
562 const float dest_height = std::max(0.0f, preview_canvas_size.y - 8.0f);
563 float src_height = 0.0f;
564 if (dest_width > 0.0f && dest_height > 0.0f) {
565 const float src_width = std::min(dest_width * 0.5f,
566 static_cast<float>(kCurrentMessageWidth));
567 src_height = std::min(dest_height * 0.5f,
568 static_cast<float>(kCurrentMessageHeight));
570 current_font_gfx16_bitmap_, ImVec2(0, 0), // Destination position
571 ImVec2(dest_width, dest_height), // Destination size
572 ImVec2(0, message_preview_.shown_lines * 16), // Source position
573 ImVec2(src_width, src_height) // Source size
574 );
575 }
576
577 // Draw scroll break separator lines on the preview canvas
578 {
579 ImDrawList* overlay_draw_list = ImGui::GetWindowDrawList();
580 ImVec2 canvas_p0 = current_font_gfx16_canvas_.zero_point();
581 ImVec2 canvas_sz = current_font_gfx16_canvas_.canvas_size();
582 float line_height = 16.0f;
583 // The bitmap is drawn scaled: dest occupies full canvas, so compute the
584 // vertical scale factor from destination height to source height.
585 float scale_y = 1.0f;
586 if (dest_height > 0.0f && src_height > 0.0f) {
587 scale_y = dest_height / src_height;
588 }
589 for (int marker_line : message_preview_.scroll_marker_lines) {
590 float src_y = (marker_line - message_preview_.shown_lines) * line_height;
591 float y = canvas_p0.y + src_y * scale_y;
592 if (y >= canvas_p0.y && y <= canvas_p0.y + canvas_sz.y) {
593 overlay_draw_list->AddLine(
594 ImVec2(canvas_p0.x, y),
595 ImVec2(canvas_p0.x + canvas_sz.x, y),
596 IM_COL32(100, 180, 255, 180), 1.5f);
597 overlay_draw_list->AddText(
598 ImVec2(canvas_p0.x + canvas_sz.x + 4, y - 6),
599 IM_COL32(100, 180, 255, 200), "[V]");
600 }
601 }
602 }
603
606 EndChild();
607
608 // Message Structure info panel
609 if (ImGui::CollapsingHeader("Message Structure",
610 ImGuiTreeNodeFlags_DefaultOpen)) {
611 ImGui::Text("Lines: %d", message_preview_.text_line + 1);
612
613 int scroll_count = 0;
614 int current_line_chars = 0;
615 int line_num = 0;
616
617 for (size_t i = 0; i < current_message_.Data.size(); i++) {
618 uint8_t byte = current_message_.Data[i];
619 if (byte == kScrollVertical) {
620 scroll_count++;
621 ImGui::TextColored(
622 ImVec4(0.4f, 0.7f, 1.0f, 1.0f),
623 " [V] Scroll at byte %zu (line %d, %d chars)",
624 i, line_num, current_line_chars);
625 current_line_chars = 0;
626 line_num++;
627 } else if (byte == kLine1) {
628 ImGui::TextColored(ImVec4(0.7f, 0.85f, 0.5f, 1.0f),
629 " [1] Line 1 at byte %zu", i);
630 current_line_chars = 0;
631 line_num = 0;
632 } else if (byte == kLine2) {
633 ImGui::TextColored(ImVec4(0.7f, 0.85f, 0.5f, 1.0f),
634 " [2] Line 2 at byte %zu", i);
635 current_line_chars = 0;
636 line_num = 1;
637 } else if (byte == kLine3) {
638 ImGui::TextColored(ImVec4(0.7f, 0.85f, 0.5f, 1.0f),
639 " [3] Line 3 at byte %zu", i);
640 current_line_chars = 0;
641 line_num = 2;
642 } else if (byte < 100) {
643 current_line_chars++;
644 }
645 }
646
647 if (scroll_count == 0) {
648 ImGui::TextDisabled("No scroll breaks in this message");
649 } else {
650 ImGui::Text("Total scroll breaks: %d", scroll_count);
651 }
652
653 // Character width budget
654 ImGui::Separator();
655 ImGui::TextDisabled("Line width budget (max ~170px):");
656 int estimated_line_width = current_line_chars * 8;
657 float width_ratio = static_cast<float>(estimated_line_width) / 170.0f;
658 ImVec4 width_color =
659 (width_ratio > 1.0f)
660 ? ImVec4(0.9f, 0.2f, 0.2f, 1.0f)
661 : (width_ratio > 0.85f)
662 ? ImVec4(0.9f, 0.7f, 0.1f, 1.0f)
663 : ImVec4(0.2f, 0.8f, 0.2f, 1.0f);
664 ImGui::TextColored(width_color, "Last line: ~%dpx / 170px (%d chars)",
665 estimated_line_width, current_line_chars);
666 }
667
668 ImGui::EndChild();
669}
670
678
680 ImGui::BeginChild("##ExpandedMessageSettings", ImVec2(0, 130), true,
681 ImGuiWindowFlags_AlwaysVerticalScrollbar);
682 ImGui::Text("Expanded Messages");
683
684 if (ImGui::Button("Load from ROM")) {
685 auto status = LoadExpandedMessagesFromRom();
686 if (!status.ok()) {
687 LOG_WARN("MessageEditor", "Load from ROM: %s",
688 std::string(status.message()).c_str());
689 }
690 }
691 ImGui::SameLine();
692 if (ImGui::Button("Load from File")) {
694 if (!path.empty()) {
698 expanded_messages_.clear();
699 std::vector<std::string> parsed_expanded;
700 auto status = LoadExpandedMessages(expanded_message_path_, parsed_expanded,
703 if (!status.ok()) {
704 if (auto* popup_manager = dependencies_.popup_manager) {
705 popup_manager->Show("Error");
706 }
707 } else {
708 parsed_messages_.insert(parsed_messages_.end(), parsed_expanded.begin(),
709 parsed_expanded.end());
710 }
711 }
712 }
713
714 if (expanded_messages_.size() > 0) {
715 ImGui::Text("Source: %s", expanded_message_path_.c_str());
716 ImGui::Text("Messages: %lu", expanded_messages_.size());
717
718 // Capacity indicator
719 int capacity = GetExpandedTextDataEnd() - GetExpandedTextDataStart() + 1;
720 int used = CalculateExpandedBankUsage();
721 int remaining = capacity - used;
722 float usage_ratio = static_cast<float>(used) / static_cast<float>(capacity);
723
724 ImVec4 capacity_color;
725 if (usage_ratio < 0.75f) {
726 capacity_color = ImVec4(0.2f, 0.8f, 0.2f, 1.0f); // Green
727 } else if (usage_ratio < 0.90f) {
728 capacity_color = ImVec4(0.9f, 0.7f, 0.1f, 1.0f); // Yellow
729 } else {
730 capacity_color = ImVec4(0.9f, 0.2f, 0.2f, 1.0f); // Red
731 }
732 ImGui::TextColored(capacity_color, "Bank: %d / %d bytes (%d free)",
733 used, capacity, remaining);
734
735 if (ImGui::Button("Add New Message")) {
736 MessageData new_message;
737 new_message.ID = expanded_messages_.back().ID + 1;
738 new_message.Address = expanded_messages_.back().Address +
739 expanded_messages_.back().Data.size();
740 expanded_messages_.push_back(new_message);
741 const int display_id = expanded_message_base_id_ + new_message.ID;
742 if (display_id >= 0 &&
743 static_cast<size_t>(display_id) >= parsed_messages_.size()) {
744 parsed_messages_.resize(display_id + 1);
745 }
746 }
747
748 ImGui::SameLine();
749 if (ImGui::Button("Export to JSON")) {
751 if (!path.empty()) {
753 }
754 }
755 }
756
757 EndChild();
758}
759
761 ImGui::BeginChild("##TextCommands",
762 ImVec2(0, ImGui::GetContentRegionAvail().y / 2), true,
763 ImGuiWindowFlags_AlwaysVerticalScrollbar);
764 static uint8_t command_parameter = 0;
765 gui::InputHexByte("Command Parameter", &command_parameter);
766 for (const auto& text_element : TextCommands) {
767 if (Button(text_element.GenericToken.c_str())) {
768 message_text_box_.text.append(
769 text_element.GetParamToken(command_parameter));
771 }
772 SameLine();
773 TextWrapped("%s", text_element.Description.c_str());
774 Separator();
775 }
776 EndChild();
777}
778
780 ImGui::BeginChild("##SpecialChars",
781 ImVec2(0, ImGui::GetContentRegionAvail().y / 2), true,
782 ImGuiWindowFlags_AlwaysVerticalScrollbar);
783 for (const auto& text_element : SpecialChars) {
784 if (Button(text_element.GenericToken.c_str())) {
785 message_text_box_.text.append(text_element.GenericToken);
787 }
788 SameLine();
789 TextWrapped("%s", text_element.Description.c_str());
790 Separator();
791 }
792 EndChild();
793}
794
796 if (ImGui::BeginChild("##DictionaryChild",
797 ImVec2(0, ImGui::GetContentRegionAvail().y), true,
798 ImGuiWindowFlags_AlwaysVerticalScrollbar)) {
799 if (BeginTable("##Dictionary", 2, kMessageTableFlags)) {
800 TableSetupColumn("ID");
801 TableSetupColumn("Contents");
802 TableHeadersRow();
803
804 // Use ImGuiListClipper for virtualized rendering
805 const int dict_count =
806 static_cast<int>(message_preview_.all_dictionaries_.size());
807 ImGuiListClipper clipper;
808 clipper.Begin(dict_count);
809
810 while (clipper.Step()) {
811 for (int row = clipper.DisplayStart; row < clipper.DisplayEnd; ++row) {
812 const auto& dictionary = message_preview_.all_dictionaries_[row];
813 TableNextColumn();
814 Text("%s", util::HexWord(dictionary.ID).c_str());
815 TableNextColumn();
816 Text("%s", dictionary.Contents.c_str());
817 }
818 }
819
820 EndTable();
821 }
822 }
823 EndChild();
824}
825
826void MessageEditor::UpdateCurrentMessageFromText(const std::string& text) {
828
829 std::string raw_text = text;
830 raw_text.erase(std::remove(raw_text.begin(), raw_text.end(), '\n'),
831 raw_text.end());
832
833 current_message_.RawString = raw_text;
836
837 int parsed_index = current_message_index_;
840 }
841
842 if (parsed_index >= 0) {
843 if (static_cast<size_t>(parsed_index) >= parsed_messages_.size()) {
844 parsed_messages_.resize(parsed_index + 1);
845 }
846 parsed_messages_[parsed_index] = text;
847 }
848
850 if (current_message_index_ >= 0 &&
852 static_cast<int>(expanded_messages_.size())) {
854 }
855 } else {
856 if (current_message_index_ >= 0 &&
857 current_message_index_ < static_cast<int>(list_of_texts_.size())) {
859 }
860 }
861
863}
864
865void MessageEditor::ImportMessageBundleFromFile(const std::string& path) {
868
869 auto entries_or = LoadMessageBundleFromJson(path);
870 if (!entries_or.ok()) {
872 absl::StrFormat("Import failed: %s", entries_or.status().message());
874 return;
875 }
876
877 int applied = 0;
878 int errors = 0;
879 int warnings = 0;
880 int duplicate_errors = 0;
881 int parse_error_entries = 0;
882 int vanilla_updated = 0;
883 int expanded_updated = 0;
884 int expanded_created = 0;
885 bool expanded_modified = false;
886 std::vector<std::string> issue_samples;
887
888 auto add_issue_sample = [&issue_samples](const std::string& issue) {
889 constexpr size_t kMaxIssueSamples = 4;
890 if (issue_samples.size() < kMaxIssueSamples) {
891 issue_samples.push_back(issue);
892 }
893 };
894
895 auto make_entry_key = [](const MessageBundleEntry& entry) {
896 return absl::StrFormat("%s:%d", MessageBankToString(entry.bank), entry.id);
897 };
898
899 std::unordered_map<std::string, int> seen_entries;
900
901 auto entries = entries_or.value();
902 for (const auto& entry : entries) {
903 const std::string entry_key = make_entry_key(entry);
904 if (seen_entries.find(entry_key) != seen_entries.end()) {
905 errors++;
906 duplicate_errors++;
907 add_issue_sample(
908 absl::StrFormat("Duplicate entry for %s", entry_key));
909 continue;
910 }
911 seen_entries.emplace(entry_key, 1);
912
913 auto parse_result = ParseMessageToDataWithDiagnostics(entry.text);
914 auto line_warnings = ValidateMessageLineWidths(entry.text);
915 warnings += static_cast<int>(parse_result.warnings.size());
916 warnings += static_cast<int>(line_warnings.size());
917
918 if (!parse_result.ok()) {
919 errors++;
920 parse_error_entries++;
921 if (!parse_result.errors.empty()) {
922 add_issue_sample(absl::StrFormat(
923 "Parse error for %s: %s", entry_key,
924 parse_result.errors.front()));
925 } else {
926 add_issue_sample(
927 absl::StrFormat("Parse error for %s", entry_key));
928 }
929 continue;
930 }
931
932 if (entry.bank == MessageBank::kVanilla) {
933 if (entry.id < 0 || entry.id >= static_cast<int>(list_of_texts_.size())) {
934 errors++;
935 add_issue_sample(absl::StrFormat(
936 "Vanilla ID out of range: %d", entry.id));
937 continue;
938 }
939 auto& message = list_of_texts_[entry.id];
940 message.RawString = entry.text;
941 message.ContentsParsed = entry.text;
942 message.Data = parse_result.bytes;
943 message.DataParsed = parse_result.bytes;
944 if (entry.id >= 0 && entry.id < static_cast<int>(parsed_messages_.size())) {
945 parsed_messages_[entry.id] = entry.text;
946 }
947 vanilla_updated++;
948 applied++;
949 } else {
950 if (entry.id < 0) {
951 errors++;
952 add_issue_sample(
953 absl::StrFormat("Expanded ID out of range: %d", entry.id));
954 continue;
955 }
956 if (entry.id >= static_cast<int>(expanded_messages_.size())) {
957 const int old_size = static_cast<int>(expanded_messages_.size());
958 const int target_size = entry.id + 1;
959 expanded_messages_.resize(target_size);
960 for (int i = old_size; i < target_size; ++i) {
961 expanded_messages_[i].ID = i;
962 }
963 expanded_created += target_size - old_size;
964 }
965 auto& message = expanded_messages_[entry.id];
966 message.RawString = entry.text;
967 message.ContentsParsed = entry.text;
968 message.Data = parse_result.bytes;
969 message.DataParsed = parse_result.bytes;
970 const int parsed_index = expanded_message_base_id_ + entry.id;
971 if (parsed_index >= 0) {
972 if (static_cast<size_t>(parsed_index) >= parsed_messages_.size()) {
973 parsed_messages_.resize(parsed_index + 1);
974 }
975 parsed_messages_[parsed_index] = entry.text;
976 }
977 expanded_modified = true;
978 expanded_updated++;
979 applied++;
980 }
981 }
982
983 if (expanded_modified) {
984 int pos = GetExpandedTextDataStart();
985 for (auto& message : expanded_messages_) {
986 message.Address = pos;
987 pos += static_cast<int>(message.Data.size()) + 1;
988 }
989 }
990
991 int current_display_id = current_message_index_;
994 }
995 if (current_display_id >= 0) {
996 OpenMessageById(current_display_id);
997 }
998
999 if (errors > 0) {
1000 message_bundle_status_ = absl::StrFormat(
1001 "Import finished with %d errors (%d applied: vanilla %d updated, "
1002 "expanded %d updated/%d created; %d warnings, %d duplicates, %d "
1003 "parse failures).",
1004 errors, applied, vanilla_updated, expanded_updated, expanded_created,
1005 warnings, duplicate_errors, parse_error_entries);
1006 if (!issue_samples.empty()) {
1007 message_bundle_status_ = absl::StrFormat(
1008 "%s Example: %s", message_bundle_status_, issue_samples.front());
1009 }
1011 } else {
1012 message_bundle_status_ = absl::StrFormat(
1013 "Imported %d messages (vanilla %d updated, expanded %d updated/%d "
1014 "created, %d warnings).",
1015 applied, vanilla_updated, expanded_updated, expanded_created, warnings);
1016 }
1017}
1018
1020 // Render the message to the preview bitmap
1022
1023 // Validate preview data before updating
1025 LOG_WARN("MessageEditor", "Preview data is empty, skipping bitmap update");
1026 return;
1027 }
1028
1030 // CRITICAL: Use set_data() to properly update both data_ AND surface_
1031 // mutable_data() returns a reference but doesn't update the surface!
1033
1037 }
1038
1039 // Validate surface was updated
1041 LOG_ERROR("MessageEditor", "Bitmap surface is null after set_data()");
1042 return;
1043 }
1044
1045 // Queue texture update (or create if missing) so changes are visible
1046 const auto command = current_font_gfx16_bitmap_.texture()
1050
1051 LOG_DEBUG(
1052 "MessageEditor",
1053 "Updated message preview bitmap (size: %zu) and queued texture update",
1055 } else {
1056 // Create bitmap and queue texture creation with 8-bit indexed depth
1063
1064 LOG_INFO("MessageEditor",
1065 "Created message preview bitmap (%dx%d) with 8-bit depth and "
1066 "queued texture creation",
1068 }
1069}
1070
1071absl::Status MessageEditor::Save() {
1072 std::vector<uint8_t> backup = rom()->vector();
1073
1074 for (int i = 0; i < kWidthArraySize; i++) {
1075 RETURN_IF_ERROR(rom()->WriteByte(kCharactersWidth + i,
1077 }
1078
1079 int pos = kTextData;
1080 bool in_second_bank = false;
1081
1082 for (const auto& message : list_of_texts_) {
1083 for (const auto value : message.Data) {
1084 RETURN_IF_ERROR(rom()->WriteByte(pos, value));
1085
1086 if (value == kBlockTerminator) {
1087 // Make sure we didn't go over the space available in the first block.
1088 // 0x7FFF available.
1089 if (!in_second_bank && pos > kTextDataEnd) {
1090 return absl::InternalError(DisplayTextOverflowError(pos, true));
1091 }
1092
1093 // Switch to the second block.
1094 pos = kTextData2 - 1;
1095 in_second_bank = true;
1096 }
1097
1098 pos++;
1099 }
1100
1101 RETURN_IF_ERROR(rom()->WriteByte(pos++, kMessageTerminator));
1102 }
1103
1104 // Verify that we didn't go over the space available for the second block.
1105 // 0x14BF available.
1106 if (in_second_bank && pos > kTextData2End) {
1107 std::copy(backup.begin(), backup.end(), rom()->mutable_data());
1108 return absl::InternalError(DisplayTextOverflowError(pos, false));
1109 }
1110
1111 RETURN_IF_ERROR(rom()->WriteByte(pos, 0xFF));
1112
1113 // Also save expanded messages to main ROM if any are loaded
1114 if (!expanded_messages_.empty()) {
1115 auto status = SaveExpandedMessages();
1116 if (!status.ok()) {
1117 std::copy(backup.begin(), backup.end(), rom()->mutable_data());
1118 return status;
1119 }
1120 }
1121
1122 return absl::OkStatus();
1123}
1124
1126 if (expanded_messages_.empty()) {
1127 return absl::OkStatus();
1128 }
1129
1130 if (!rom_ || !rom_->is_loaded()) {
1131 return absl::FailedPreconditionError("ROM not loaded");
1132 }
1133
1134 // Collect all expanded message text strings (mirrors CLI message-write path)
1135 std::vector<std::string> all_texts;
1136 all_texts.reserve(expanded_messages_.size());
1137 for (const auto& msg : expanded_messages_) {
1138 all_texts.push_back(msg.RawString);
1139 }
1140
1141 // Write to main ROM buffer at the expanded text data region
1143 rom_->mutable_data(),
1146 all_texts));
1147
1148 // Recalculate addresses after sequential write
1149 int pos = GetExpandedTextDataStart();
1150 for (auto& msg : expanded_messages_) {
1151 msg.Address = pos;
1152 auto bytes = ParseMessageToData(msg.RawString);
1153 pos += static_cast<int>(bytes.size()) + 1; // +1 for 0x7F terminator
1154 }
1155
1156 return absl::OkStatus();
1157}
1158
1160 if (!rom_ || !rom_->is_loaded()) {
1161 return absl::FailedPreconditionError("ROM not loaded");
1162 }
1163
1166
1167 expanded_messages_.clear();
1170
1171 if (expanded_messages_.empty()) {
1172 return absl::NotFoundError(
1173 "No expanded messages found in ROM at expanded text region");
1174 }
1175
1176 // Parse the expanded messages and append to the unified list
1177 auto parsed_expanded =
1179 for (const auto& msg : expanded_messages_) {
1180 if (msg.ID >= 0 &&
1181 msg.ID < static_cast<int>(parsed_expanded.size())) {
1182 parsed_messages_.push_back(parsed_expanded[msg.ID]);
1183 }
1184 }
1185
1186 expanded_message_path_ = "(ROM)";
1187 return absl::OkStatus();
1188}
1189
1191 if (expanded_messages_.empty()) return 0;
1192 int total = 0;
1193 for (const auto& msg : expanded_messages_) {
1194 total += static_cast<int>(msg.Data.size()) + 1; // +1 for 0x7F
1195 }
1196 total += 1; // +1 for final 0xFF
1197 return total;
1198}
1199
1200absl::Status MessageEditor::Cut() {
1201 // Ensure that text is currently selected in the text box.
1202 if (!message_text_box_.text.empty()) {
1203 // Cut the selected text in the control and paste it into the Clipboard.
1205 }
1206 return absl::OkStatus();
1207}
1208
1209absl::Status MessageEditor::Paste() {
1210 // Determine if there is any text in the Clipboard to paste into the
1211 if (ImGui::GetClipboardText() != nullptr) {
1212 // Paste the text from the Clipboard into the text box.
1214 }
1215 return absl::OkStatus();
1216}
1217
1218absl::Status MessageEditor::Copy() {
1219 // Ensure that text is selected in the text box.
1221 // Copy the selected text to the Clipboard.
1223 }
1224 return absl::OkStatus();
1225}
1226
1228 if (pending_undo_before_.has_value()) {
1229 // If we're still editing the same message, keep the existing "before"
1230 // snapshot so the entire edit session becomes a single undo step.
1231 if (pending_undo_before_->message_index == current_message_index_ &&
1233 return;
1234 }
1236 }
1237
1238 // Capture current state as "before"
1239 int parsed_index = current_message_index_;
1242 }
1243 std::string text;
1244 if (parsed_index >= 0 &&
1245 parsed_index < static_cast<int>(parsed_messages_.size())) {
1246 text = parsed_messages_[parsed_index];
1247 }
1251}
1252
1254 if (!pending_undo_before_.has_value()) return;
1255
1256 // The "after" snapshot must correspond to the same message as the pending
1257 // "before", even if the user navigated to a different message in the UI.
1258 const int message_index = pending_undo_before_->message_index;
1259 const bool is_expanded = pending_undo_before_->is_expanded;
1260
1261 MessageData after_message;
1262 if (is_expanded) {
1263 if (message_index < 0 ||
1264 message_index >= static_cast<int>(expanded_messages_.size())) {
1265 pending_undo_before_.reset();
1266 return;
1267 }
1268 after_message = expanded_messages_[message_index];
1269 } else {
1270 if (message_index < 0 ||
1271 message_index >= static_cast<int>(list_of_texts_.size())) {
1272 pending_undo_before_.reset();
1273 return;
1274 }
1275 after_message = list_of_texts_[message_index];
1276 }
1277
1278 int parsed_index = message_index;
1279 if (is_expanded) {
1280 parsed_index = expanded_message_base_id_ + message_index;
1281 }
1282 std::string text;
1283 if (parsed_index >= 0 &&
1284 parsed_index < static_cast<int>(parsed_messages_.size())) {
1285 text = parsed_messages_[parsed_index];
1286 }
1287 MessageSnapshot after{std::move(after_message), std::move(text), message_index,
1288 is_expanded};
1289
1290 undo_manager_.Push(std::make_unique<MessageEditAction>(
1291 std::move(*pending_undo_before_), std::move(after),
1292 [this](const MessageSnapshot& s) { ApplySnapshot(s); }));
1293 pending_undo_before_.reset();
1294}
1295
1297 current_message_ = snapshot.message;
1301
1302 int parsed_index = snapshot.message_index;
1303 if (snapshot.is_expanded) {
1304 parsed_index = expanded_message_base_id_ + snapshot.message_index;
1305 }
1306 if (parsed_index >= 0 &&
1307 parsed_index < static_cast<int>(parsed_messages_.size())) {
1308 parsed_messages_[parsed_index] = snapshot.parsed_text;
1309 }
1310
1311 if (snapshot.is_expanded) {
1312 if (snapshot.message_index >= 0 &&
1313 snapshot.message_index <
1314 static_cast<int>(expanded_messages_.size())) {
1315 expanded_messages_[snapshot.message_index] = snapshot.message;
1316 }
1317 } else {
1318 if (snapshot.message_index >= 0 &&
1319 snapshot.message_index < static_cast<int>(list_of_texts_.size())) {
1320 list_of_texts_[snapshot.message_index] = snapshot.message;
1321 }
1322 }
1323
1325}
1326
1327absl::Status MessageEditor::Undo() {
1329 return undo_manager_.Undo();
1330}
1331
1332absl::Status MessageEditor::Redo() {
1333 return undo_manager_.Redo();
1334}
1335
1337 // Determine if any text is selected in the TextBox control.
1339 // clear all of the text in the textbox.
1341 }
1342}
1343
1345 // Determine if any text is selected in the TextBox control.
1347 // Select all text in the text box.
1349
1350 // Move the cursor to the text box.
1352 }
1353}
1354
1355absl::Status MessageEditor::Find() {
1356 if (ImGui::Begin("Find Text", nullptr, ImGuiWindowFlags_AlwaysAutoResize)) {
1357 static char find_text[256] = "";
1358 ImGui::InputText("Search", find_text, IM_ARRAYSIZE(find_text));
1359
1360 if (ImGui::Button("Find Next")) {
1361 search_text_ = find_text;
1362 }
1363
1364 ImGui::SameLine();
1365 if (ImGui::Button("Find All")) {
1366 search_text_ = find_text;
1367 }
1368
1369 ImGui::SameLine();
1370 if (ImGui::Button("Replace")) {
1371 // TODO: Implement replace functionality
1372 }
1373
1374 ImGui::Checkbox("Case Sensitive", &case_sensitive_);
1375 ImGui::SameLine();
1376 ImGui::Checkbox("Match Whole Word", &match_whole_word_);
1377 }
1378 ImGui::End();
1379
1380 return absl::OkStatus();
1381}
1382
1383} // namespace editor
1384} // namespace yaze
auto begin()
Definition rom.h:141
auto mutable_data()
Definition rom.h:140
const auto & vector() const
Definition rom.h:143
auto data() const
Definition rom.h:139
auto size() const
Definition rom.h:138
bool is_loaded() const
Definition rom.h:132
const MessageLayout & message_layout() const
bool loaded() const
Check if the manifest has been loaded.
virtual void SetGameData(zelda3::GameData *game_data)
Definition editor.h:246
UndoManager undo_manager_
Definition editor.h:307
zelda3::GameData * game_data() const
Definition editor.h:297
EditorDependencies dependencies_
Definition editor.h:306
std::vector< std::string > parsed_messages_
absl::Status Copy() override
std::vector< MessageData > expanded_messages_
absl::Status Find() override
absl::Status Update() override
absl::Status LoadExpandedMessagesFromRom()
void UpdateCurrentMessageFromText(const std::string &text)
absl::Status Paste() override
void ApplySnapshot(const MessageSnapshot &snapshot)
void RefreshFontAtlasBitmap(const std::vector< uint8_t > &font_data)
absl::Status Undo() override
absl::Status Load() override
std::array< uint8_t, 0x4000 > raw_font_gfx_data_
gfx::SnesPalette BuildFallbackFontPalette() const
absl::Status Cut() override
std::optional< MessageSnapshot > pending_undo_before_
std::vector< MessageData > list_of_texts_
bool OpenMessageById(int display_id)
gfx::SnesPalette font_preview_colors_
absl::Status Redo() override
void ImportMessageBundleFromFile(const std::string &path)
absl::Status Save() override
void SetGameData(zelda3::GameData *game_data) override
bool ShowPanel(size_t session_id, const std::string &base_card_id)
void Push(std::unique_ptr< UndoAction > action)
absl::Status Redo()
Redo the top action. Returns error if stack is empty.
absl::Status Undo()
Undo the top action. Returns error if stack is empty.
void QueueTextureCommand(TextureCommandType type, Bitmap *bitmap)
Definition arena.cc:36
static Arena & Get()
Definition arena.cc:21
Represents a bitmap image optimized for SNES ROM hacking.
Definition bitmap.h:67
const SnesPalette & palette() const
Definition bitmap.h:368
void Create(int width, int height, int depth, std::span< uint8_t > data)
Create a bitmap with the given dimensions and data.
Definition bitmap.cc:201
TextureHandle texture() const
Definition bitmap.h:380
bool is_active() const
Definition bitmap.h:384
SnesPalette * mutable_palette()
Definition bitmap.h:369
void set_data(const std::vector< uint8_t > &data)
Definition bitmap.cc:853
void SetPalette(const SnesPalette &palette)
Set the palette for the bitmap using SNES palette format.
Definition bitmap.cc:384
SDL_Surface * surface() const
Definition bitmap.h:379
RAII timer for automatic timing management.
Represents a palette of colors for the Super Nintendo Entertainment System (SNES).
void DrawBitmap(Bitmap &bitmap, int border_offset, float scale)
Definition canvas.cc:1157
void DrawContextMenu()
Definition canvas.cc:684
bool DrawTileSelector(int size, int size_y=0)
Definition canvas.cc:1093
auto canvas_size() const
Definition canvas.h:451
auto zero_point() const
Definition canvas.h:443
void DrawBackground(ImVec2 canvas_size=ImVec2(0, 0))
Definition canvas.cc:590
void DrawGrid(float grid_step=64.0f, int tile_id_offset=8)
Definition canvas.cc:1480
static std::string ShowSaveFileDialog(const std::string &default_name="", const std::string &default_extension="")
ShowSaveFileDialog opens a save file dialog and returns the selected filepath. Uses global feature fl...
static std::string ShowOpenFileDialog()
ShowOpenFileDialog opens a file dialog and returns the selected filepath. Uses global feature flag to...
#define LOG_DEBUG(category, format,...)
Definition log.h:103
#define LOG_ERROR(category, format,...)
Definition log.h:109
#define LOG_WARN(category, format,...)
Definition log.h:107
#define LOG_INFO(category, format,...)
Definition log.h:105
#define PRINT_IF_ERROR(expression)
Definition macro.h:28
std::string DisplayTextOverflowError(int pos, bool bank)
constexpr int kCharactersWidth
int GetExpandedTextDataStart()
constexpr uint8_t kScrollVertical
std::optional< ResolvedMessageId > ResolveMessageDisplayId(int display_id, int vanilla_count, int expanded_base_id, int expanded_count)
absl::Status LoadExpandedMessages(std::string &expanded_message_path, std::vector< std::string > &parsed_messages, std::vector< MessageData > &expanded_messages, std::vector< DictionaryEntry > &dictionary)
constexpr uint8_t kLine1
constexpr int kTextData
std::string MessageBankToString(MessageBank bank)
constexpr int kCurrentMessageWidth
constexpr int kTextData2
constexpr int kCurrentMessageHeight
absl::Status WriteExpandedTextData(Rom *rom, int start, int end, const std::vector< std::string > &messages)
constexpr uint8_t kBlockTerminator
constexpr uint8_t kLine2
constexpr int kGfxFont
absl::StatusOr< std::vector< MessageBundleEntry > > LoadMessageBundleFromJson(const std::string &path)
constexpr int kFontGfxMessageSize
std::vector< std::string > ParseMessageData(std::vector< MessageData > &message_data, const std::vector< DictionaryEntry > &dictionary_entries)
constexpr uint8_t kMessageTerminator
constexpr int kFontGfxMessageDepth
std::vector< MessageData > ReadAllTextData(uint8_t *rom, int pos, int max_pos)
constexpr int kTextData2End
std::vector< DictionaryEntry > BuildDictionaryEntries(Rom *rom)
std::vector< uint8_t > ParseMessageToData(std::string str)
absl::Status ExportMessagesToJson(const std::string &path, const std::vector< MessageData > &messages)
constexpr uint8_t kWidthArraySize
absl::Status ExportMessageBundleToJson(const std::string &path, const std::vector< MessageData > &vanilla, const std::vector< MessageData > &expanded)
constexpr ImGuiTableFlags kMessageTableFlags
std::vector< MessageData > ReadExpandedTextData(uint8_t *rom, int pos)
MessageParseResult ParseMessageToDataWithDiagnostics(std::string_view str)
int GetExpandedTextDataEnd()
std::vector< std::string > ValidateMessageLineWidths(const std::string &message)
constexpr uint8_t kLine3
constexpr int kTextDataEnd
std::vector< uint8_t > SnesTo8bppSheet(std::span< const uint8_t > sheet, int bpp, int num_sheets)
Definition snes_tile.cc:132
void EndCanvas(Canvas &canvas)
Definition canvas.cc:1591
void BeginPadding(int i)
Definition style.cc:274
void BeginCanvas(Canvas &canvas, ImVec2 child_size)
Definition canvas.cc:1568
void EndNoPadding()
Definition style.cc:286
void MemoryEditorPopup(const std::string &label, std::span< uint8_t > memory)
Definition input.cc:689
void EndPadding()
Definition style.cc:278
void BeginNoPadding()
Definition style.cc:282
IMGUI_API bool DisplayPalette(gfx::SnesPalette &palette, bool loaded)
Definition color.cc:238
bool InputHexByte(const char *label, uint8_t *data, float input_width, bool no_step)
Definition input.cc:370
std::string HexWord(uint16_t word, HexStringParams params)
Definition hex.cc:41
std::string HexLong(uint32_t dword, HexStringParams params)
Definition hex.cc:52
absl::StatusOr< gfx::Bitmap > LoadFontGraphics(const Rom &rom)
Loads font graphics from ROM.
Definition game_data.cc:605
#define RETURN_IF_ERROR(expr)
Definition snes.cc:22
project::YazeProject * project
Definition editor.h:167
std::vector< uint8_t > Data
std::vector< uint8_t > current_preview_data_
void DrawMessagePreview(const MessageData &message)
std::array< uint8_t, kWidthArraySize > width_array
std::vector< uint8_t > font_gfx16_data_2_
std::vector< uint8_t > font_gfx16_data_
std::vector< int > scroll_marker_lines
std::vector< DictionaryEntry > all_dictionaries_
auto palette(int i) const
void SelectAll()
Definition style.h:102
std::string text
Definition style.h:57
int selection_length
Definition style.h:62
core::HackManifest hack_manifest
Definition project.h:160
gfx::PaletteGroupMap palette_groups
Definition game_data.h:89