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 <string>
4#include <vector>
5
6#include "absl/status/status.h"
7#include "absl/strings/str_cat.h"
8#include "absl/strings/str_format.h"
9#include "app/gfx/arena.h"
11#include "util/file_util.h"
12#include "app/gfx/bitmap.h"
14#include "app/gfx/snes_tile.h"
15#include "app/gui/canvas.h"
16#include "app/gui/style.h"
17#include "app/rom.h"
18#include "app/gui/input.h"
19#include "imgui.h"
20#include "imgui/misc/cpp/imgui_stdlib.h"
21#include "util/hex.h"
22#include "util/log.h"
23
24namespace yaze {
25namespace editor {
26
27namespace {
28std::string DisplayTextOverflowError(int pos, bool bank) {
29 int space = bank ? kTextDataEnd - kTextData : kTextData2End - kTextData2;
30 std::string bankSTR = bank ? "1st" : "2nd";
31 std::string posSTR =
32 bank ? absl::StrFormat("%X4", pos & 0xFFFF)
33 : absl::StrFormat("%X4", (pos - kTextData2) & 0xFFFF);
34 std::string message = absl::StrFormat(
35 "There is too much text data in the %s block to save.\n"
36 "Available: %X4 | Used: %s",
37 bankSTR, space, posSTR);
38 return message;
39}
40} // namespace
41
42
43using ImGui::BeginChild;
44using ImGui::BeginTable;
45using ImGui::Button;
46using ImGui::EndChild;
47using ImGui::EndTable;
48using ImGui::InputTextMultiline;
49using ImGui::PopID;
50using ImGui::PushID;
51using ImGui::SameLine;
52using ImGui::Separator;
53using ImGui::TableHeadersRow;
54using ImGui::TableNextColumn;
55using ImGui::TableSetupColumn;
56using ImGui::Text;
57using ImGui::TextWrapped;
58
59constexpr ImGuiTableFlags kMessageTableFlags = ImGuiTableFlags_Hideable |
60 ImGuiTableFlags_Borders |
61 ImGuiTableFlags_Resizable;
62
64 // Register cards with EditorCardManager
65 auto& card_manager = gui::EditorCardManager::Get();
66
67 card_manager.RegisterCard({
68 .card_id = "message.message_list",
69 .display_name = "Message List",
70 .icon = ICON_MD_LIST,
71 .category = "Message",
72 .visibility_flag = &show_message_list_,
73 .priority = 10
74 });
75
76 card_manager.RegisterCard({
77 .card_id = "message.message_editor",
78 .display_name = "Message Editor",
79 .icon = ICON_MD_EDIT,
80 .category = "Message",
81 .visibility_flag = &show_message_editor_,
82 .priority = 20
83 });
84
85 card_manager.RegisterCard({
86 .card_id = "message.font_atlas",
87 .display_name = "Font Atlas",
89 .category = "Message",
90 .visibility_flag = &show_font_atlas_,
91 .priority = 30
92 });
93
94 card_manager.RegisterCard({
95 .card_id = "message.dictionary",
96 .display_name = "Dictionary",
97 .icon = ICON_MD_BOOK,
98 .category = "Message",
99 .visibility_flag = &show_dictionary_,
100 .priority = 40
101 });
102
103 for (int i = 0; i < kWidthArraySize; i++) {
105 }
106
108 list_of_texts_ = ReadAllTextData(rom()->mutable_data());
109 font_preview_colors_ = rom()->palette_group().hud.palette(0);
110
111 for (int i = 0; i < 0x4000; i++) {
112 raw_font_gfx_data_[i] = rom()->data()[kGfxFont + i];
113 }
115 gfx::SnesTo8bppSheet(raw_font_gfx_data_, /*bpp=*/2, /*num_sheets=*/2);
116
117 // Create bitmap for font graphics
121
122 // Queue texture creation - will be processed in render loop
125
126 LOG_INFO("MessageEditor", "Font bitmap created and texture queued");
128
129 auto load_font = LoadFontGraphics(*rom());
130 if (load_font.ok()) {
131 message_preview_.font_gfx16_data_2_ = load_font.value().vector();
132 }
138}
139
140absl::Status MessageEditor::Load() {
141 gfx::ScopedTimer timer("MessageEditor::Load");
142 return absl::OkStatus();
143}
144
145absl::Status MessageEditor::Update() {
146 if (BeginTable("##MessageEditor", 4, kMessageTableFlags)) {
147 TableSetupColumn("List");
148 TableSetupColumn("Contents");
149 TableSetupColumn("Font Atlas");
150 TableSetupColumn("Commands");
151 TableHeadersRow();
152
153 TableNextColumn();
155
156 TableNextColumn();
158
159 TableNextColumn();
162
163 TableNextColumn();
167
168 EndTable();
169 }
170 return absl::OkStatus();
171}
172
175 if (BeginChild("##MessagesList", ImVec2(0, 0), true,
176 ImGuiWindowFlags_AlwaysVerticalScrollbar)) {
178 if (BeginTable("##MessagesTable", 3, kMessageTableFlags)) {
179 TableSetupColumn("ID");
180 TableSetupColumn("Contents");
181 TableSetupColumn("Data");
182
183 TableHeadersRow();
184 for (const auto& message : list_of_texts_) {
185 TableNextColumn();
186 PushID(message.ID);
187 if (Button(util::HexWord(message.ID).c_str())) {
188 current_message_ = message;
191 }
192 PopID();
193 TableNextColumn();
194 TextWrapped("%s", parsed_messages_[message.ID].c_str());
195 TableNextColumn();
196 TextWrapped("%s",
197 util::HexLong(list_of_texts_[message.ID].Address).c_str());
198 }
199 for (const auto& expanded_message : expanded_messages_) {
200 TableNextColumn();
201 PushID(expanded_message.ID + 0x18D);
202 if (Button(util::HexWord(expanded_message.ID + 0x18D).c_str())) {
203 current_message_ = expanded_message;
205 parsed_messages_[expanded_message.ID + 0x18D];
207 }
208 PopID();
209 TableNextColumn();
210 TextWrapped("%s",
211 parsed_messages_[expanded_message.ID + 0x18C].c_str());
212 TableNextColumn();
213 TextWrapped("%s", util::HexLong(expanded_message.Address).c_str());
214 }
215
216 EndTable();
217 }
218 }
219 EndChild();
220}
221
223 Button(absl::StrCat("Message ", current_message_.ID).c_str());
224 if (InputTextMultiline("##MessageEditor", &message_text_box_.text,
225 ImVec2(ImGui::GetContentRegionAvail().x, 0))) {
226 std::string temp = message_text_box_.text;
227 // Strip newline characters.
228 temp.erase(std::remove(temp.begin(), temp.end(), '\n'), temp.end());
231 }
232 Separator();
234
235 ImGui::BeginChild("##MessagePreview", ImVec2(0, 0), true, 1);
236 Text("Message Preview");
237 if (Button("View Palette")) {
238 ImGui::OpenPopup("Palette");
239 }
240 if (ImGui::BeginPopup("Palette")) {
242 ImGui::EndPopup();
243 }
245 BeginChild("CurrentGfxFont", ImVec2(348, 0), true,
246 ImGuiWindowFlags_NoScrollWithMouse);
250
251 // Handle mouse wheel scrolling
252 if (ImGui::IsWindowHovered()) {
253 float wheel = ImGui::GetIO().MouseWheel;
254 if (wheel > 0 && message_preview_.shown_lines > 0) {
256 } else if (wheel < 0 &&
259 }
260 }
261
262 // Draw only the visible portion of the text
264 current_font_gfx16_bitmap_, ImVec2(0, 0), // Destination position
265 ImVec2(340,
266 font_gfx_canvas_.canvas_size().y), // Destination size
267 ImVec2(0, message_preview_.shown_lines * 16), // Source position
268 ImVec2(170,
269 font_gfx_canvas_.canvas_size().y / 2) // Source size
270 );
271
274 EndChild();
275 ImGui::EndChild();
276}
277
284
286 ImGui::BeginChild("##ExpandedMessageSettings", ImVec2(0, 100), true,
287 ImGuiWindowFlags_AlwaysVerticalScrollbar);
288 ImGui::Text("Expanded Messages");
289 static std::string expanded_message_path = "";
290 if (ImGui::Button("Load Expanded Message")) {
291 expanded_message_path = util::FileDialogWrapper::ShowOpenFileDialog();
292 if (!expanded_message_path.empty()) {
293 if (!LoadExpandedMessages(expanded_message_path, parsed_messages_,
296 .ok()) {
297 context_->popup_manager->Show("Error");
298 }
299 }
300 }
301
302 if (expanded_messages_.size() > 0) {
303 ImGui::Text("Expanded Path: %s", expanded_message_path.c_str());
304 ImGui::Text("Expanded Messages: %lu", expanded_messages_.size());
305 if (ImGui::Button("Add New Message")) {
306 MessageData new_message;
307 new_message.ID = expanded_messages_.back().ID + 1;
308 new_message.Address = expanded_messages_.back().Address +
309 expanded_messages_.back().Data.size();
310 expanded_messages_.push_back(new_message);
311 }
312 if (ImGui::Button("Save Expanded Messages")) {
314 }
315 }
316
317 EndChild();
318}
319
321 ImGui::BeginChild("##TextCommands",
322 ImVec2(0, ImGui::GetContentRegionAvail().y / 2), true,
323 ImGuiWindowFlags_AlwaysVerticalScrollbar);
324 static uint8_t command_parameter = 0;
325 gui::InputHexByte("Command Parameter", &command_parameter);
326 for (const auto& text_element : TextCommands) {
327 if (Button(text_element.GenericToken.c_str())) {
328 message_text_box_.text.append(
329 text_element.GetParamToken(command_parameter));
330 }
331 SameLine();
332 TextWrapped("%s", text_element.Description.c_str());
333 Separator();
334 }
335 EndChild();
336}
337
339 ImGui::BeginChild("##SpecialChars",
340 ImVec2(0, ImGui::GetContentRegionAvail().y / 2), true,
341 ImGuiWindowFlags_AlwaysVerticalScrollbar);
342 for (const auto& text_element : SpecialChars) {
343 if (Button(text_element.GenericToken.c_str())) {
344 message_text_box_.text.append(text_element.GenericToken);
345 }
346 SameLine();
347 TextWrapped("%s", text_element.Description.c_str());
348 Separator();
349 }
350 EndChild();
351}
352
354 if (ImGui::BeginChild("##DictionaryChild",
355 ImVec2(0, ImGui::GetContentRegionAvail().y), true,
356 ImGuiWindowFlags_AlwaysVerticalScrollbar)) {
357 if (BeginTable("##Dictionary", 2, kMessageTableFlags)) {
358 TableSetupColumn("ID");
359 TableSetupColumn("Contents");
360 TableHeadersRow();
361 for (const auto& dictionary : message_preview_.all_dictionaries_) {
362 TableNextColumn();
363 Text("%s", util::HexWord(dictionary.ID).c_str());
364 TableNextColumn();
365 Text("%s", dictionary.Contents.c_str());
366 }
367 EndTable();
368 }
369 }
370 EndChild();
371}
372
374 // Render the message to the preview bitmap
376
377 // Validate preview data before updating
379 LOG_WARN("MessageEditor", "Preview data is empty, skipping bitmap update");
380 return;
381 }
382
384 // CRITICAL: Use set_data() to properly update both data_ AND surface_
385 // mutable_data() returns a reference but doesn't update the surface!
387
388 // Validate surface was updated
390 LOG_ERROR("MessageEditor", "Bitmap surface is null after set_data()");
391 return;
392 }
393
394 // Queue texture update so changes are visible immediately
397
398 LOG_DEBUG("MessageEditor", "Updated message preview bitmap (size: %zu) and queued texture update",
400 } else {
401 // Create bitmap and queue texture creation
407
408 LOG_INFO("MessageEditor", "Created message preview bitmap (%dx%d) and queued texture creation",
410 }
411}
412
413absl::Status MessageEditor::Save() {
414 std::vector<uint8_t> backup = rom()->vector();
415
416 for (int i = 0; i < kWidthArraySize; i++) {
417 RETURN_IF_ERROR(rom()->WriteByte(kCharactersWidth + i,
419 }
420
421 int pos = kTextData;
422 bool in_second_bank = false;
423
424 for (const auto& message : list_of_texts_) {
425 for (const auto value : message.Data) {
426 RETURN_IF_ERROR(rom()->WriteByte(pos, value));
427
428 if (value == kBlockTerminator) {
429 // Make sure we didn't go over the space available in the first block.
430 // 0x7FFF available.
431 if ((!in_second_bank & pos) > kTextDataEnd) {
432 return absl::InternalError(DisplayTextOverflowError(pos, true));
433 }
434
435 // Switch to the second block.
436 pos = kTextData2 - 1;
437 in_second_bank = true;
438 }
439
440 pos++;
441 }
442
443 RETURN_IF_ERROR(rom()->WriteByte(pos++, kMessageTerminator));
444 }
445
446 // Verify that we didn't go over the space available for the second block.
447 // 0x14BF available.
448 if ((in_second_bank & pos) > kTextData2End) {
449 std::copy(backup.begin(), backup.end(), rom()->mutable_data());
450 return absl::InternalError(DisplayTextOverflowError(pos, false));
451 }
452
453 RETURN_IF_ERROR(rom()->WriteByte(pos, 0xFF));
454
455 return absl::OkStatus();
456}
457
459 for (const auto& expanded_message : expanded_messages_) {
460 std::copy(expanded_message.Data.begin(), expanded_message.Data.end(),
461 expanded_message_bin_.mutable_data() + expanded_message.Address);
462 }
464 expanded_messages_.back().Address + expanded_messages_.back().Data.size(),
465 0xFF));
467 Rom::SaveSettings{.backup = true, .save_new = false, .z3_save = false}));
468 return absl::OkStatus();
469}
470
471absl::Status MessageEditor::Cut() {
472 // Ensure that text is currently selected in the text box.
473 if (!message_text_box_.text.empty()) {
474 // Cut the selected text in the control and paste it into the Clipboard.
476 }
477 return absl::OkStatus();
478}
479
480absl::Status MessageEditor::Paste() {
481 // Determine if there is any text in the Clipboard to paste into the
482 if (ImGui::GetClipboardText() != nullptr) {
483 // Paste the text from the Clipboard into the text box.
485 }
486 return absl::OkStatus();
487}
488
489absl::Status MessageEditor::Copy() {
490 // Ensure that text is selected in the text box.
492 // Copy the selected text to the Clipboard.
494 }
495 return absl::OkStatus();
496}
497
498absl::Status MessageEditor::Undo() {
499 // Determine if last operation can be undone in text box.
501 // Undo the last operation.
503
504 // clear the undo buffer to prevent last action from being redone.
506 }
507 return absl::OkStatus();
508}
509
510absl::Status MessageEditor::Redo() {
511 // Implementation of redo functionality
512 // This would require tracking a redo stack in the TextBox struct
513 return absl::OkStatus();
514}
515
517 // Determine if any text is selected in the TextBox control.
519 // clear all of the text in the textbox.
521 }
522}
523
525 // Determine if any text is selected in the TextBox control.
527 // Select all text in the text box.
529
530 // Move the cursor to the text box.
532 }
533}
534
535absl::Status MessageEditor::Find() {
536 if (ImGui::Begin("Find Text", nullptr, ImGuiWindowFlags_AlwaysAutoResize)) {
537 static char find_text[256] = "";
538 ImGui::InputText("Search", find_text, IM_ARRAYSIZE(find_text));
539
540 if (ImGui::Button("Find Next")) {
541 search_text_ = find_text;
542 }
543
544 ImGui::SameLine();
545 if (ImGui::Button("Find All")) {
546 search_text_ = find_text;
547 }
548
549 ImGui::SameLine();
550 if (ImGui::Button("Replace")) {
551 // TODO: Implement replace functionality
552 }
553
554 ImGui::Checkbox("Case Sensitive", &case_sensitive_);
555 ImGui::SameLine();
556 ImGui::Checkbox("Match Whole Word", &match_whole_word_);
557 }
558 ImGui::End();
559
560 return absl::OkStatus();
561}
562
563} // namespace editor
564} // namespace yaze
auto palette_group() const
Definition rom.h:213
absl::Status WriteByte(int addr, uint8_t value)
Definition rom.cc:725
auto vector() const
Definition rom.h:207
auto mutable_data()
Definition rom.h:204
absl::Status SaveToFile(const SaveSettings &settings)
Definition rom.cc:536
auto data() const
Definition rom.h:203
EditorContext * context_
Definition editor.h:124
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 Show(const char *name)
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:264
SnesPalette * mutable_palette()
Definition bitmap.h:252
void set_data(const std::vector< uint8_t > &data)
Definition bitmap.cc:645
void SetPalette(const SnesPalette &palette)
Set the palette for the bitmap.
Definition bitmap.cc:292
SDL_Surface * surface() const
Definition bitmap.h:259
RAII timer for automatic timing management.
void DrawBitmap(Bitmap &bitmap, int border_offset, float scale)
Definition canvas.cc:1062
void DrawContextMenu()
Definition canvas.cc:441
bool DrawTileSelector(int size, int size_y=0)
Definition canvas.cc:920
auto canvas_size() const
Definition canvas.h:314
void DrawBackground(ImVec2 canvas_size=ImVec2(0, 0))
Definition canvas.cc:381
void DrawGrid(float grid_step=64.0f, int tile_id_offset=8)
Definition canvas.cc:1386
static EditorCardManager & Get()
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:1494
void BeginPadding(int i)
Definition style.cc:272
void BeginCanvas(Canvas &canvas, ImVec2 child_size)
Definition canvas.cc:1473
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:50
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.
absl::StatusOr< gfx::Bitmap > LoadFontGraphics(const Rom &rom)
Definition rom.cc:114
PopupManager * popup_manager
Definition editor.h:29
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