yaze 0.2.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"
10#include "app/core/window.h"
11#include "app/gfx/bitmap.h"
13#include "app/gfx/snes_tile.h"
14#include "app/gui/canvas.h"
15#include "app/gui/style.h"
16#include "app/rom.h"
17#include "gui/input.h"
18#include "imgui.h"
19#include "imgui/misc/cpp/imgui_stdlib.h"
20#include "util/hex.h"
21
22namespace yaze {
23namespace editor {
24
25namespace {
26std::string DisplayTextOverflowError(int pos, bool bank) {
27 int space = bank ? kTextDataEnd - kTextData : kTextData2End - kTextData2;
28 std::string bankSTR = bank ? "1st" : "2nd";
29 std::string posSTR =
30 bank ? absl::StrFormat("%X4", pos & 0xFFFF)
31 : absl::StrFormat("%X4", (pos - kTextData2) & 0xFFFF);
32 std::string message = absl::StrFormat(
33 "There is too much text data in the %s block to save.\n"
34 "Available: %X4 | Used: %s",
35 bankSTR, space, posSTR);
36 return message;
37}
38} // namespace
39
40using core::Renderer;
41
42using ImGui::BeginChild;
43using ImGui::BeginTable;
44using ImGui::Button;
45using ImGui::EndChild;
46using ImGui::EndTable;
47using ImGui::InputTextMultiline;
48using ImGui::PopID;
49using ImGui::PushID;
50using ImGui::SameLine;
51using ImGui::Separator;
52using ImGui::TableHeadersRow;
53using ImGui::TableNextColumn;
54using ImGui::TableSetupColumn;
55using ImGui::Text;
56using ImGui::TextWrapped;
57
58constexpr ImGuiTableFlags kMessageTableFlags = ImGuiTableFlags_Hideable |
59 ImGuiTableFlags_Borders |
60 ImGuiTableFlags_Resizable;
61
63 for (int i = 0; i < kWidthArraySize; i++) {
64 message_preview_.width_array[i] = rom()->data()[kCharactersWidth + i];
65 }
66
67 message_preview_.all_dictionaries_ = BuildDictionaryEntries(rom());
68 list_of_texts_ = ReadAllTextData(rom()->mutable_data());
69 font_preview_colors_ = rom()->palette_group().hud.palette(0);
70
71 for (int i = 0; i < 0x4000; i++) {
72 raw_font_gfx_data_[i] = rom()->data()[kGfxFont + i];
73 }
74 message_preview_.font_gfx16_data_ =
75 gfx::SnesTo8bppSheet(raw_font_gfx_data_, /*bpp=*/2, /*num_sheets=*/2);
78 message_preview_.font_gfx16_data_, font_gfx_bitmap_,
80 *font_gfx_bitmap_.mutable_palette() = font_preview_colors_;
82
83 auto load_font = LoadFontGraphics(*rom());
84 if (load_font.ok()) {
85 message_preview_.font_gfx16_data_2_ = load_font.value().vector();
86 }
92}
93
94absl::Status MessageEditor::Load() { return absl::OkStatus(); }
95
96absl::Status MessageEditor::Update() {
97 if (BeginTable("##MessageEditor", 4, kMessageTableFlags)) {
98 TableSetupColumn("List");
99 TableSetupColumn("Contents");
100 TableSetupColumn("Font Atlas");
101 TableSetupColumn("Commands");
102 TableHeadersRow();
103
104 TableNextColumn();
106
107 TableNextColumn();
109
110 TableNextColumn();
113
114 TableNextColumn();
118
119 EndTable();
120 }
121 return absl::OkStatus();
122}
123
126 if (BeginChild("##MessagesList", ImVec2(0, 0), true,
127 ImGuiWindowFlags_AlwaysVerticalScrollbar)) {
129 if (BeginTable("##MessagesTable", 3, kMessageTableFlags)) {
130 TableSetupColumn("ID");
131 TableSetupColumn("Contents");
132 TableSetupColumn("Data");
133
134 TableHeadersRow();
135 for (const auto& message : list_of_texts_) {
136 TableNextColumn();
137 PushID(message.ID);
138 if (Button(util::HexWord(message.ID).c_str())) {
139 current_message_ = message;
140 message_text_box_.text = parsed_messages_[message.ID];
142 }
143 PopID();
144 TableNextColumn();
145 TextWrapped("%s", parsed_messages_[message.ID].c_str());
146 TableNextColumn();
147 TextWrapped("%s",
148 util::HexLong(list_of_texts_[message.ID].Address).c_str());
149 }
150 for (const auto& expanded_message : expanded_messages_) {
151 TableNextColumn();
152 PushID(expanded_message.ID + 0x18D);
153 if (Button(util::HexWord(expanded_message.ID + 0x18D).c_str())) {
154 current_message_ = expanded_message;
155 message_text_box_.text =
156 parsed_messages_[expanded_message.ID + 0x18D];
158 }
159 PopID();
160 TableNextColumn();
161 TextWrapped("%s",
162 parsed_messages_[expanded_message.ID + 0x18C].c_str());
163 TableNextColumn();
164 TextWrapped("%s", util::HexLong(expanded_message.Address).c_str());
165 }
166
167 EndTable();
168 }
169 }
170 EndChild();
171}
172
174 Button(absl::StrCat("Message ", current_message_.ID).c_str());
175 if (InputTextMultiline("##MessageEditor", &message_text_box_.text,
176 ImVec2(ImGui::GetContentRegionAvail().x, 0))) {
177 std::string temp = message_text_box_.text;
178 // Strip newline characters.
179 temp.erase(std::remove(temp.begin(), temp.end(), '\n'), temp.end());
182 }
183 Separator();
184 gui::MemoryEditorPopup("Message Data", current_message_.Data);
185
186 ImGui::BeginChild("##MessagePreview", ImVec2(0, 0), true, 1);
187 Text("Message Preview");
188 if (Button("View Palette")) {
189 ImGui::OpenPopup("Palette");
190 }
191 if (ImGui::BeginPopup("Palette")) {
193 ImGui::EndPopup();
194 }
196 BeginChild("CurrentGfxFont", ImVec2(348, 0), true,
197 ImGuiWindowFlags_NoScrollWithMouse);
198 current_font_gfx16_canvas_.DrawBackground();
200 current_font_gfx16_canvas_.DrawContextMenu();
201
202 // Handle mouse wheel scrolling
203 if (ImGui::IsWindowHovered()) {
204 float wheel = ImGui::GetIO().MouseWheel;
205 if (wheel > 0 && message_preview_.shown_lines > 0) {
206 message_preview_.shown_lines--;
207 } else if (wheel < 0 &&
208 message_preview_.shown_lines < message_preview_.text_line - 2) {
209 message_preview_.shown_lines++;
210 }
211 }
212
213 // Draw only the visible portion of the text
215 current_font_gfx16_bitmap_, ImVec2(0, 0), // Destination position
216 ImVec2(340,
217 font_gfx_canvas_.canvas_size().y), // Destination size
218 ImVec2(0, message_preview_.shown_lines * 16), // Source position
219 ImVec2(170,
220 font_gfx_canvas_.canvas_size().y / 2) // Source size
221 );
222
224 current_font_gfx16_canvas_.DrawOverlay();
225 EndChild();
226 ImGui::EndChild();
227}
228
230 gui::BeginCanvas(font_gfx_canvas_, ImVec2(256, 256));
231 font_gfx_canvas_.DrawBitmap(font_gfx_bitmap_, 0, 0, 2.0f);
232 font_gfx_canvas_.DrawTileSelector(16, 32);
234}
235
237 ImGui::BeginChild("##ExpandedMessageSettings", ImVec2(0, 100), true,
238 ImGuiWindowFlags_AlwaysVerticalScrollbar);
239 ImGui::Text("Expanded Messages");
240 static std::string expanded_message_path = "";
241 if (ImGui::Button("Load Expanded Message")) {
242 expanded_message_path = core::FileDialogWrapper::ShowOpenFileDialog();
243 if (!expanded_message_path.empty()) {
244 if (!LoadExpandedMessages(expanded_message_path, parsed_messages_,
246 message_preview_.all_dictionaries_)
247 .ok()) {
248 context_->popup_manager->Show("Error");
249 }
250 }
251 }
252
253 if (expanded_messages_.size() > 0) {
254 ImGui::Text("Expanded Path: %s", expanded_message_path.c_str());
255 ImGui::Text("Expanded Messages: %lu", expanded_messages_.size());
256 if (ImGui::Button("Add New Message")) {
257 MessageData new_message;
258 new_message.ID = expanded_messages_.back().ID + 1;
259 new_message.Address = expanded_messages_.back().Address +
260 expanded_messages_.back().Data.size();
261 expanded_messages_.push_back(new_message);
262 }
263 if (ImGui::Button("Save Expanded Messages")) {
265 }
266 }
267
268 EndChild();
269}
270
272 ImGui::BeginChild("##TextCommands",
273 ImVec2(0, ImGui::GetContentRegionAvail().y / 2), true,
274 ImGuiWindowFlags_AlwaysVerticalScrollbar);
275 static uint8_t command_parameter = 0;
276 gui::InputHexByte("Command Parameter", &command_parameter);
277 for (const auto& text_element : TextCommands) {
278 if (Button(text_element.GenericToken.c_str())) {
279 message_text_box_.text.append(
280 text_element.GetParamToken(command_parameter));
281 }
282 SameLine();
283 TextWrapped("%s", text_element.Description.c_str());
284 Separator();
285 }
286 EndChild();
287}
288
290 ImGui::BeginChild("##SpecialChars",
291 ImVec2(0, ImGui::GetContentRegionAvail().y / 2), true,
292 ImGuiWindowFlags_AlwaysVerticalScrollbar);
293 for (const auto& text_element : SpecialChars) {
294 if (Button(text_element.GenericToken.c_str())) {
295 message_text_box_.text.append(text_element.GenericToken);
296 }
297 SameLine();
298 TextWrapped("%s", text_element.Description.c_str());
299 Separator();
300 }
301 EndChild();
302}
303
305 if (ImGui::BeginChild("##DictionaryChild",
306 ImVec2(0, ImGui::GetContentRegionAvail().y), true,
307 ImGuiWindowFlags_AlwaysVerticalScrollbar)) {
308 if (BeginTable("##Dictionary", 2, kMessageTableFlags)) {
309 TableSetupColumn("ID");
310 TableSetupColumn("Contents");
311 TableHeadersRow();
312 for (const auto& dictionary : message_preview_.all_dictionaries_) {
313 TableNextColumn();
314 Text("%s", util::HexWord(dictionary.ID).c_str());
315 TableNextColumn();
316 Text("%s", dictionary.Contents.c_str());
317 }
318 EndTable();
319 }
320 }
321 EndChild();
322}
323
337
338absl::Status MessageEditor::Save() {
339 std::vector<uint8_t> backup = rom()->vector();
340
341 for (int i = 0; i < kWidthArraySize; i++) {
342 RETURN_IF_ERROR(rom()->WriteByte(kCharactersWidth + i,
343 message_preview_.width_array[i]));
344 }
345
346 int pos = kTextData;
347 bool in_second_bank = false;
348
349 for (const auto& message : list_of_texts_) {
350 for (const auto value : message.Data) {
351 RETURN_IF_ERROR(rom()->WriteByte(pos, value));
352
353 if (value == kBlockTerminator) {
354 // Make sure we didn't go over the space available in the first block.
355 // 0x7FFF available.
356 if ((!in_second_bank & pos) > kTextDataEnd) {
357 return absl::InternalError(DisplayTextOverflowError(pos, true));
358 }
359
360 // Switch to the second block.
361 pos = kTextData2 - 1;
362 in_second_bank = true;
363 }
364
365 pos++;
366 }
367
368 RETURN_IF_ERROR(rom()->WriteByte(pos++, kMessageTerminator));
369 }
370
371 // Verify that we didn't go over the space available for the second block.
372 // 0x14BF available.
373 if ((in_second_bank & pos) > kTextData2End) {
374 std::copy(backup.begin(), backup.end(), rom()->mutable_data());
375 return absl::InternalError(DisplayTextOverflowError(pos, false));
376 }
377
378 RETURN_IF_ERROR(rom()->WriteByte(pos, 0xFF));
379
380 return absl::OkStatus();
381}
382
384 for (const auto& expanded_message : expanded_messages_) {
385 std::copy(expanded_message.Data.begin(), expanded_message.Data.end(),
386 expanded_message_bin_.mutable_data() + expanded_message.Address);
387 }
389 expanded_messages_.back().Address + expanded_messages_.back().Data.size(),
390 0xFF));
392 Rom::SaveSettings{.backup = true, .save_new = false, .z3_save = false}));
393 return absl::OkStatus();
394}
395
396absl::Status MessageEditor::Cut() {
397 // Ensure that text is currently selected in the text box.
398 if (!message_text_box_.text.empty()) {
399 // Cut the selected text in the control and paste it into the Clipboard.
400 message_text_box_.Cut();
401 }
402 return absl::OkStatus();
403}
404
405absl::Status MessageEditor::Paste() {
406 // Determine if there is any text in the Clipboard to paste into the
407 if (ImGui::GetClipboardText() != nullptr) {
408 // Paste the text from the Clipboard into the text box.
409 message_text_box_.Paste();
410 }
411 return absl::OkStatus();
412}
413
414absl::Status MessageEditor::Copy() {
415 // Ensure that text is selected in the text box.
416 if (message_text_box_.selection_length > 0) {
417 // Copy the selected text to the Clipboard.
418 message_text_box_.Copy();
419 }
420 return absl::OkStatus();
421}
422
423absl::Status MessageEditor::Undo() {
424 // Determine if last operation can be undone in text box.
425 if (message_text_box_.can_undo) {
426 // Undo the last operation.
427 message_text_box_.Undo();
428
429 // clear the undo buffer to prevent last action from being redone.
430 message_text_box_.clearUndo();
431 }
432 return absl::OkStatus();
433}
434
435absl::Status MessageEditor::Redo() {
436 // Implementation of redo functionality
437 // This would require tracking a redo stack in the TextBox struct
438 return absl::OkStatus();
439}
440
442 // Determine if any text is selected in the TextBox control.
443 if (message_text_box_.selection_length == 0) {
444 // clear all of the text in the textbox.
445 message_text_box_.clear();
446 }
447}
448
450 // Determine if any text is selected in the TextBox control.
451 if (message_text_box_.selection_length == 0) {
452 // Select all text in the text box.
453 message_text_box_.SelectAll();
454
455 // Move the cursor to the text box.
456 message_text_box_.Focus();
457 }
458}
459
460absl::Status MessageEditor::Find() {
461 if (ImGui::Begin("Find Text", nullptr, ImGuiWindowFlags_AlwaysAutoResize)) {
462 static char find_text[256] = "";
463 ImGui::InputText("Search", find_text, IM_ARRAYSIZE(find_text));
464
465 if (ImGui::Button("Find Next")) {
466 search_text_ = find_text;
467 }
468
469 ImGui::SameLine();
470 if (ImGui::Button("Find All")) {
471 search_text_ = find_text;
472 }
473
474 ImGui::SameLine();
475 if (ImGui::Button("Replace")) {
476 // TODO: Implement replace functionality
477 }
478
479 ImGui::Checkbox("Case Sensitive", &case_sensitive_);
480 ImGui::SameLine();
481 ImGui::Checkbox("Match Whole Word", &match_whole_word_);
482 }
483 ImGui::End();
484
485 return absl::OkStatus();
486}
487
488} // namespace editor
489} // namespace yaze
auto palette_group() const
Definition rom.h:187
auto vector() const
Definition rom.h:181
auto data() const
Definition rom.h:177
static std::string ShowOpenFileDialog()
ShowOpenFileDialog opens a file dialog and returns the selected filepath.
The Renderer class represents the renderer for the Yaze application.
Definition window.h:35
void CreateAndRenderBitmap(int width, int height, int depth, const std::vector< uint8_t > &data, gfx::Bitmap &bitmap, gfx::SnesPalette &palette)
Definition window.h:64
void UpdateBitmap(gfx::Bitmap *bitmap)
Definition window.h:60
EditorContext * context_
Definition editor.h:91
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
static Renderer & Get()
Definition window.h:37
#define PRINT_IF_ERROR(expression)
Definition macro.h:25
#define RETURN_IF_ERROR(expression)
Definition macro.h:51
std::string DisplayTextOverflowError(int pos, bool bank)
Editors are the view controllers for the application.
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:861
void BeginPadding(int i)
Definition style.cc:372
void BeginCanvas(Canvas &canvas, ImVec2 child_size)
Definition canvas.cc:853
void EndNoPadding()
Definition style.cc:382
void MemoryEditorPopup(const std::string &label, std::span< uint8_t > memory)
Definition input.cc:446
void EndPadding()
Definition style.cc:376
void BeginNoPadding()
Definition style.cc:378
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:176
std::string HexWord(uint16_t word, HexStringParams params)
Definition hex.cc:43
std::string HexLong(uint32_t dword, HexStringParams params)
Definition hex.cc:56
Main namespace for the application.
Definition controller.cc:12
absl::StatusOr< gfx::Bitmap > LoadFontGraphics(const Rom &rom)
Definition rom.cc:77