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