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"
3
4#include <string>
5#include <vector>
6
7#include "absl/status/status.h"
8#include "absl/strings/str_cat.h"
9#include "absl/strings/str_format.h"
12#include "util/file_util.h"
13#include "app/gfx/core/bitmap.h"
17#include "app/gui/core/style.h"
18#include "app/gui/core/icons.h"
19#include "app/rom.h"
20#include "app/gui/core/input.h"
21#include "imgui.h"
22#include "imgui/misc/cpp/imgui_stdlib.h"
23#include "util/hex.h"
24#include "util/log.h"
25
26namespace yaze {
27namespace editor {
28
29namespace {
30std::string DisplayTextOverflowError(int pos, bool bank) {
31 int space = bank ? kTextDataEnd - kTextData : kTextData2End - kTextData2;
32 std::string bankSTR = bank ? "1st" : "2nd";
33 std::string posSTR =
34 bank ? absl::StrFormat("%X4", pos & 0xFFFF)
35 : absl::StrFormat("%X4", (pos - kTextData2) & 0xFFFF);
36 std::string message = absl::StrFormat(
37 "There is too much text data in the %s block to save.\n"
38 "Available: %X4 | Used: %s",
39 bankSTR, space, posSTR);
40 return message;
41}
42} // namespace
43
44
45using ImGui::BeginChild;
46using ImGui::BeginTable;
47using ImGui::Button;
48using ImGui::EndChild;
49using ImGui::EndTable;
50using ImGui::InputTextMultiline;
51using ImGui::PopID;
52using ImGui::PushID;
53using ImGui::SameLine;
54using ImGui::Separator;
55using ImGui::TableHeadersRow;
56using ImGui::TableNextColumn;
57using ImGui::TableSetupColumn;
58using ImGui::Text;
59using ImGui::TextWrapped;
60
61constexpr ImGuiTableFlags kMessageTableFlags = ImGuiTableFlags_Hideable |
62 ImGuiTableFlags_Borders |
63 ImGuiTableFlags_Resizable;
64
66 // Register cards with EditorCardRegistry (dependency injection)
67 if (!dependencies_.card_registry) return;
68
69 auto* card_registry = dependencies_.card_registry;
70
71 card_registry->RegisterCard({
72 .card_id = MakeCardId("message.message_list"),
73 .display_name = "Message List",
74 .icon = ICON_MD_LIST,
75 .category = "Message",
76 .priority = 10
77 });
78
79 card_registry->RegisterCard({
80 .card_id = MakeCardId("message.message_editor"),
81 .display_name = "Message Editor",
82 .icon = ICON_MD_EDIT,
83 .category = "Message",
84 .priority = 20
85 });
86
87 card_registry->RegisterCard({
88 .card_id = MakeCardId("message.font_atlas"),
89 .display_name = "Font Atlas",
91 .category = "Message",
92 .priority = 30
93 });
94
95 card_registry->RegisterCard({
96 .card_id = MakeCardId("message.dictionary"),
97 .display_name = "Dictionary",
98 .icon = ICON_MD_BOOK,
99 .category = "Message",
100 .priority = 40
101 });
102
103 // Show message list by default
104 card_registry->ShowCard(MakeCardId("message.message_list"));
105
106 for (int i = 0; i < kWidthArraySize; i++) {
108 }
109
111 list_of_texts_ = ReadAllTextData(rom()->mutable_data());
112 font_preview_colors_ = rom()->palette_group().hud.palette(0);
113
114 for (int i = 0; i < 0x4000; i++) {
115 raw_font_gfx_data_[i] = rom()->data()[kGfxFont + i];
116 }
118 gfx::SnesTo8bppSheet(raw_font_gfx_data_, /*bpp=*/2, /*num_sheets=*/2);
119
120 // Create bitmap for font graphics
124
125 // Queue texture creation - will be processed in render loop
128
129 LOG_INFO("MessageEditor", "Font bitmap created and texture queued");
131
132 auto load_font = LoadFontGraphics(*rom());
133 if (load_font.ok()) {
134 message_preview_.font_gfx16_data_2_ = load_font.value().vector();
135 }
141}
142
143absl::Status MessageEditor::Load() {
144 gfx::ScopedTimer timer("MessageEditor::Load");
145 return absl::OkStatus();
146}
147
148absl::Status MessageEditor::Update() {
149 if (!dependencies_.card_registry) return absl::OkStatus();
150
151 auto* card_registry = dependencies_.card_registry;
152
153 // Message List Card - Get visibility flag and pass to Begin() for proper X button
154 bool* list_visible = card_registry->GetVisibilityFlag(MakeCardId("message.message_list"));
155 if (list_visible && *list_visible) {
156 static gui::EditorCard list_card("Message List", ICON_MD_LIST);
157 list_card.SetDefaultSize(400, 600);
158 if (list_card.Begin(list_visible)) {
160 }
161 list_card.End();
162 }
163
164 // Message Editor Card - Get visibility flag and pass to Begin() for proper X button
165 bool* editor_visible = card_registry->GetVisibilityFlag(MakeCardId("message.message_editor"));
166 if (editor_visible && *editor_visible) {
167 static gui::EditorCard editor_card("Message Editor", ICON_MD_EDIT);
168 editor_card.SetDefaultSize(500, 600);
169 if (editor_card.Begin(editor_visible)) {
171 }
172 editor_card.End();
173 }
174
175 // Font Atlas Card - Get visibility flag and pass to Begin() for proper X button
176 bool* font_visible = card_registry->GetVisibilityFlag(MakeCardId("message.font_atlas"));
177 if (font_visible && *font_visible) {
178 static gui::EditorCard font_card("Font Atlas", ICON_MD_FONT_DOWNLOAD);
179 font_card.SetDefaultSize(400, 500);
180 if (font_card.Begin(font_visible)) {
183 }
184 font_card.End();
185 }
186
187 // Dictionary Card - Get visibility flag and pass to Begin() for proper X button
188 bool* dict_visible = card_registry->GetVisibilityFlag(MakeCardId("message.dictionary"));
189 if (dict_visible && *dict_visible) {
190 static gui::EditorCard dict_card("Dictionary", ICON_MD_BOOK);
191 dict_card.SetDefaultSize(400, 500);
192 if (dict_card.Begin(dict_visible)) {
196 }
197 dict_card.End();
198 }
199
200 return absl::OkStatus();
201}
202
205 if (BeginChild("##MessagesList", ImVec2(0, 0), true,
206 ImGuiWindowFlags_AlwaysVerticalScrollbar)) {
208 if (BeginTable("##MessagesTable", 3, kMessageTableFlags)) {
209 TableSetupColumn("ID");
210 TableSetupColumn("Contents");
211 TableSetupColumn("Data");
212
213 TableHeadersRow();
214 for (const auto& message : list_of_texts_) {
215 TableNextColumn();
216 PushID(message.ID);
217 if (Button(util::HexWord(message.ID).c_str())) {
218 current_message_ = message;
221 }
222 PopID();
223 TableNextColumn();
224 TextWrapped("%s", parsed_messages_[message.ID].c_str());
225 TableNextColumn();
226 TextWrapped("%s",
227 util::HexLong(list_of_texts_[message.ID].Address).c_str());
228 }
229 for (const auto& expanded_message : expanded_messages_) {
230 TableNextColumn();
231 PushID(expanded_message.ID + 0x18D);
232 if (Button(util::HexWord(expanded_message.ID + 0x18D).c_str())) {
233 current_message_ = expanded_message;
235 parsed_messages_[expanded_message.ID + 0x18D];
237 }
238 PopID();
239 TableNextColumn();
240 TextWrapped("%s",
241 parsed_messages_[expanded_message.ID + 0x18C].c_str());
242 TableNextColumn();
243 TextWrapped("%s", util::HexLong(expanded_message.Address).c_str());
244 }
245
246 EndTable();
247 }
248 }
249 EndChild();
250}
251
253 Button(absl::StrCat("Message ", current_message_.ID).c_str());
254 if (InputTextMultiline("##MessageEditor", &message_text_box_.text,
255 ImVec2(ImGui::GetContentRegionAvail().x, 0))) {
256 std::string temp = message_text_box_.text;
257 // Strip newline characters.
258 temp.erase(std::remove(temp.begin(), temp.end(), '\n'), temp.end());
261 }
262 Separator();
264
265 ImGui::BeginChild("##MessagePreview", ImVec2(0, 0), true, 1);
266 Text("Message Preview");
267 if (Button("View Palette")) {
268 ImGui::OpenPopup("Palette");
269 }
270 if (ImGui::BeginPopup("Palette")) {
272 ImGui::EndPopup();
273 }
275 BeginChild("CurrentGfxFont", ImVec2(348, 0), true,
276 ImGuiWindowFlags_NoScrollWithMouse);
280
281 // Handle mouse wheel scrolling
282 if (ImGui::IsWindowHovered()) {
283 float wheel = ImGui::GetIO().MouseWheel;
284 if (wheel > 0 && message_preview_.shown_lines > 0) {
286 } else if (wheel < 0 &&
289 }
290 }
291
292 // Draw only the visible portion of the text
294 current_font_gfx16_bitmap_, ImVec2(0, 0), // Destination position
295 ImVec2(340,
296 font_gfx_canvas_.canvas_size().y), // Destination size
297 ImVec2(0, message_preview_.shown_lines * 16), // Source position
298 ImVec2(170,
299 font_gfx_canvas_.canvas_size().y / 2) // Source size
300 );
301
304 EndChild();
305 ImGui::EndChild();
306}
307
314
316 ImGui::BeginChild("##ExpandedMessageSettings", ImVec2(0, 100), true,
317 ImGuiWindowFlags_AlwaysVerticalScrollbar);
318 ImGui::Text("Expanded Messages");
319 static std::string expanded_message_path = "";
320 if (ImGui::Button("Load Expanded Message")) {
321 expanded_message_path = util::FileDialogWrapper::ShowOpenFileDialog();
322 if (!expanded_message_path.empty()) {
323 if (!LoadExpandedMessages(expanded_message_path, parsed_messages_,
326 .ok()) {
327 if (auto* popup_manager = dependencies_.popup_manager) {
328 popup_manager->Show("Error");
329 }
330 }
331 }
332 }
333
334 if (expanded_messages_.size() > 0) {
335 ImGui::Text("Expanded Path: %s", expanded_message_path.c_str());
336 ImGui::Text("Expanded Messages: %lu", expanded_messages_.size());
337 if (ImGui::Button("Add New Message")) {
338 MessageData new_message;
339 new_message.ID = expanded_messages_.back().ID + 1;
340 new_message.Address = expanded_messages_.back().Address +
341 expanded_messages_.back().Data.size();
342 expanded_messages_.push_back(new_message);
343 }
344 if (ImGui::Button("Save Expanded Messages")) {
346 }
347 }
348
349 EndChild();
350}
351
353 ImGui::BeginChild("##TextCommands",
354 ImVec2(0, ImGui::GetContentRegionAvail().y / 2), true,
355 ImGuiWindowFlags_AlwaysVerticalScrollbar);
356 static uint8_t command_parameter = 0;
357 gui::InputHexByte("Command Parameter", &command_parameter);
358 for (const auto& text_element : TextCommands) {
359 if (Button(text_element.GenericToken.c_str())) {
360 message_text_box_.text.append(
361 text_element.GetParamToken(command_parameter));
362 }
363 SameLine();
364 TextWrapped("%s", text_element.Description.c_str());
365 Separator();
366 }
367 EndChild();
368}
369
371 ImGui::BeginChild("##SpecialChars",
372 ImVec2(0, ImGui::GetContentRegionAvail().y / 2), true,
373 ImGuiWindowFlags_AlwaysVerticalScrollbar);
374 for (const auto& text_element : SpecialChars) {
375 if (Button(text_element.GenericToken.c_str())) {
376 message_text_box_.text.append(text_element.GenericToken);
377 }
378 SameLine();
379 TextWrapped("%s", text_element.Description.c_str());
380 Separator();
381 }
382 EndChild();
383}
384
386 if (ImGui::BeginChild("##DictionaryChild",
387 ImVec2(0, ImGui::GetContentRegionAvail().y), true,
388 ImGuiWindowFlags_AlwaysVerticalScrollbar)) {
389 if (BeginTable("##Dictionary", 2, kMessageTableFlags)) {
390 TableSetupColumn("ID");
391 TableSetupColumn("Contents");
392 TableHeadersRow();
393 for (const auto& dictionary : message_preview_.all_dictionaries_) {
394 TableNextColumn();
395 Text("%s", util::HexWord(dictionary.ID).c_str());
396 TableNextColumn();
397 Text("%s", dictionary.Contents.c_str());
398 }
399 EndTable();
400 }
401 }
402 EndChild();
403}
404
406 // Render the message to the preview bitmap
408
409 // Validate preview data before updating
411 LOG_WARN("MessageEditor", "Preview data is empty, skipping bitmap update");
412 return;
413 }
414
416 // CRITICAL: Use set_data() to properly update both data_ AND surface_
417 // mutable_data() returns a reference but doesn't update the surface!
419
420 // Validate surface was updated
422 LOG_ERROR("MessageEditor", "Bitmap surface is null after set_data()");
423 return;
424 }
425
426 // Queue texture update so changes are visible immediately
429
430 LOG_DEBUG("MessageEditor", "Updated message preview bitmap (size: %zu) and queued texture update",
432 } else {
433 // Create bitmap and queue texture creation with 8-bit indexed depth
439
440 LOG_INFO("MessageEditor", "Created message preview bitmap (%dx%d) with 8-bit depth and queued texture creation",
442 }
443}
444
445absl::Status MessageEditor::Save() {
446 std::vector<uint8_t> backup = rom()->vector();
447
448 for (int i = 0; i < kWidthArraySize; i++) {
449 RETURN_IF_ERROR(rom()->WriteByte(kCharactersWidth + i,
451 }
452
453 int pos = kTextData;
454 bool in_second_bank = false;
455
456 for (const auto& message : list_of_texts_) {
457 for (const auto value : message.Data) {
458 RETURN_IF_ERROR(rom()->WriteByte(pos, value));
459
460 if (value == kBlockTerminator) {
461 // Make sure we didn't go over the space available in the first block.
462 // 0x7FFF available.
463 if ((!in_second_bank & pos) > kTextDataEnd) {
464 return absl::InternalError(DisplayTextOverflowError(pos, true));
465 }
466
467 // Switch to the second block.
468 pos = kTextData2 - 1;
469 in_second_bank = true;
470 }
471
472 pos++;
473 }
474
475 RETURN_IF_ERROR(rom()->WriteByte(pos++, kMessageTerminator));
476 }
477
478 // Verify that we didn't go over the space available for the second block.
479 // 0x14BF available.
480 if ((in_second_bank & pos) > kTextData2End) {
481 std::copy(backup.begin(), backup.end(), rom()->mutable_data());
482 return absl::InternalError(DisplayTextOverflowError(pos, false));
483 }
484
485 RETURN_IF_ERROR(rom()->WriteByte(pos, 0xFF));
486
487 return absl::OkStatus();
488}
489
491 for (const auto& expanded_message : expanded_messages_) {
492 std::copy(expanded_message.Data.begin(), expanded_message.Data.end(),
493 expanded_message_bin_.mutable_data() + expanded_message.Address);
494 }
496 expanded_messages_.back().Address + expanded_messages_.back().Data.size(),
497 0xFF));
499 Rom::SaveSettings{.backup = true, .save_new = false, .z3_save = false}));
500 return absl::OkStatus();
501}
502
503absl::Status MessageEditor::Cut() {
504 // Ensure that text is currently selected in the text box.
505 if (!message_text_box_.text.empty()) {
506 // Cut the selected text in the control and paste it into the Clipboard.
508 }
509 return absl::OkStatus();
510}
511
512absl::Status MessageEditor::Paste() {
513 // Determine if there is any text in the Clipboard to paste into the
514 if (ImGui::GetClipboardText() != nullptr) {
515 // Paste the text from the Clipboard into the text box.
517 }
518 return absl::OkStatus();
519}
520
521absl::Status MessageEditor::Copy() {
522 // Ensure that text is selected in the text box.
524 // Copy the selected text to the Clipboard.
526 }
527 return absl::OkStatus();
528}
529
530absl::Status MessageEditor::Undo() {
531 // Determine if last operation can be undone in text box.
533 // Undo the last operation.
535
536 // clear the undo buffer to prevent last action from being redone.
538 }
539 return absl::OkStatus();
540}
541
542absl::Status MessageEditor::Redo() {
543 // Implementation of redo functionality
544 // This would require tracking a redo stack in the TextBox struct
545 return absl::OkStatus();
546}
547
549 // Determine if any text is selected in the TextBox control.
551 // clear all of the text in the textbox.
553 }
554}
555
557 // Determine if any text is selected in the TextBox control.
559 // Select all text in the text box.
561
562 // Move the cursor to the text box.
564 }
565}
566
567absl::Status MessageEditor::Find() {
568 if (ImGui::Begin("Find Text", nullptr, ImGuiWindowFlags_AlwaysAutoResize)) {
569 static char find_text[256] = "";
570 ImGui::InputText("Search", find_text, IM_ARRAYSIZE(find_text));
571
572 if (ImGui::Button("Find Next")) {
573 search_text_ = find_text;
574 }
575
576 ImGui::SameLine();
577 if (ImGui::Button("Find All")) {
578 search_text_ = find_text;
579 }
580
581 ImGui::SameLine();
582 if (ImGui::Button("Replace")) {
583 // TODO: Implement replace functionality
584 }
585
586 ImGui::Checkbox("Case Sensitive", &case_sensitive_);
587 ImGui::SameLine();
588 ImGui::Checkbox("Match Whole Word", &match_whole_word_);
589 }
590 ImGui::End();
591
592 return absl::OkStatus();
593}
594
595} // namespace editor
596} // namespace yaze
auto palette_group() const
Definition rom.h:216
absl::Status WriteByte(int addr, uint8_t value)
Definition rom.cc:728
auto vector() const
Definition rom.h:210
auto mutable_data()
Definition rom.h:207
absl::Status SaveToFile(const SaveSettings &settings)
Definition rom.cc:539
auto data() const
Definition rom.h:206
bool * GetVisibilityFlag(size_t session_id, const std::string &base_card_id)
Get visibility flag pointer for a card.
void RegisterCard(size_t session_id, const CardInfo &base_info)
Register a card for a specific session.
EditorDependencies dependencies_
Definition editor.h:165
std::string MakeCardId(const std::string &base_id) const
Definition editor.h:176
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 Paste() override
absl::Status Undo() override
absl::Status Load() override
std::array< uint8_t, 0x4000 > raw_font_gfx_data_
absl::Status Cut() override
std::vector< MessageData > list_of_texts_
gfx::SnesPalette font_preview_colors_
absl::Status Redo() override
absl::Status Save() override
void QueueTextureCommand(TextureCommandType type, Bitmap *bitmap)
Definition arena.cc:32
static Arena & Get()
Definition arena.cc:15
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:162
bool is_active() const
Definition bitmap.h:293
SnesPalette * mutable_palette()
Definition bitmap.h:278
void set_data(const std::vector< uint8_t > &data)
Definition bitmap.cc:743
void SetPalette(const SnesPalette &palette)
Set the palette for the bitmap.
Definition bitmap.cc:334
SDL_Surface * surface() const
Definition bitmap.h:288
RAII timer for automatic timing management.
void DrawBitmap(Bitmap &bitmap, int border_offset, float scale)
Definition canvas.cc:1004
void DrawContextMenu()
Definition canvas.cc:428
bool DrawTileSelector(int size, int size_y=0)
Definition canvas.cc:862
auto canvas_size() const
Definition canvas.h:298
void DrawBackground(ImVec2 canvas_size=ImVec2(0, 0))
Definition canvas.cc:372
void DrawGrid(float grid_step=64.0f, int tile_id_offset=8)
Definition canvas.cc:1304
Draggable, dockable card for editor sub-windows.
bool Begin(bool *p_open=nullptr)
void SetDefaultSize(float width, float height)
static std::string ShowOpenFileDialog()
ShowOpenFileDialog opens a file dialog and returns the selected filepath. Uses global feature flag to...
#define ICON_MD_FONT_DOWNLOAD
Definition icons.h:816
#define ICON_MD_EDIT
Definition icons.h:643
#define ICON_MD_LIST
Definition icons.h:1092
#define ICON_MD_BOOK
Definition icons.h:281
#define LOG_DEBUG(category, format,...)
Definition log.h:104
#define LOG_ERROR(category, format,...)
Definition log.h:110
#define LOG_WARN(category, format,...)
Definition log.h:108
#define LOG_INFO(category, format,...)
Definition log.h:106
#define PRINT_IF_ERROR(expression)
Definition macro.h:27
#define RETURN_IF_ERROR(expression)
Definition macro.h:53
std::string DisplayTextOverflowError(int pos, bool bank)
constexpr int kCharactersWidth
std::vector< MessageData > ReadAllTextData(uint8_t *rom, int pos)
absl::Status LoadExpandedMessages(std::string &expanded_message_path, std::vector< std::string > &parsed_messages, std::vector< MessageData > &expanded_messages, std::vector< DictionaryEntry > &dictionary)
constexpr int kTextData
constexpr int kCurrentMessageWidth
constexpr int kTextData2
constexpr int kCurrentMessageHeight
constexpr uint8_t kBlockTerminator
constexpr int kGfxFont
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
constexpr int kTextData2End
std::vector< DictionaryEntry > BuildDictionaryEntries(Rom *rom)
std::vector< uint8_t > ParseMessageToData(std::string str)
constexpr uint8_t kWidthArraySize
constexpr ImGuiTableFlags kMessageTableFlags
constexpr int kTextDataEnd
std::vector< uint8_t > SnesTo8bppSheet(std::span< uint8_t > sheet, int bpp, int num_sheets)
Definition snes_tile.cc:129
void EndCanvas(Canvas &canvas)
Definition canvas.cc:1412
void BeginPadding(int i)
Definition style.cc:272
void BeginCanvas(Canvas &canvas, ImVec2 child_size)
Definition canvas.cc:1391
void EndNoPadding()
Definition style.cc:282
void MemoryEditorPopup(const std::string &label, std::span< uint8_t > memory)
Definition input.cc:455
void EndPadding()
Definition style.cc:276
void BeginNoPadding()
Definition style.cc:278
IMGUI_API bool DisplayPalette(gfx::SnesPalette &palette, bool loaded)
Definition color.cc:232
bool InputHexByte(const char *label, uint8_t *data, float input_width, bool no_step)
Definition input.cc:189
std::string HexWord(uint16_t word, HexStringParams params)
Definition hex.cc:41
std::string HexLong(uint32_t dword, HexStringParams params)
Definition hex.cc:52
Main namespace for the application.
Definition controller.cc:20
absl::StatusOr< gfx::Bitmap > LoadFontGraphics(const Rom &rom)
Definition rom.cc:117
PopupManager * popup_manager
Definition editor.h:82
EditorCardRegistry * card_registry
Definition editor.h:80
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< DictionaryEntry > all_dictionaries_
void SelectAll()
Definition style.h:100
std::string text
Definition style.h:55
void clearUndo()
Definition style.h:71
int selection_length
Definition style.h:60