yaze 0.3.2
Link to the Past ROM Editor
 
Loading...
Searching...
No Matches
screen_editor.cc
Go to the documentation of this file.
1#include "screen_editor.h"
3
4#include <fstream>
5#include <iostream>
6#include <string>
7
8#include "absl/strings/str_format.h"
10#include "util/file_util.h"
12#include "app/gfx/core/bitmap.h"
15#include "app/gui/core/color.h"
16#include "app/gui/core/icons.h"
17#include "app/gui/core/input.h"
18#include "imgui/imgui.h"
19#include "util/hex.h"
20#include "util/macro.h"
21
22namespace yaze {
23namespace editor {
24
25
26constexpr uint32_t kRedPen = 0xFF0000FF;
27
29 if (!dependencies_.card_registry) return;
30 auto* card_registry = dependencies_.card_registry;
31
32 card_registry->RegisterCard({.card_id = "screen.dungeon_maps", .display_name = "Dungeon Maps",
33 .icon = ICON_MD_MAP, .category = "Screen",
34 .shortcut_hint = "Alt+1", .priority = 10});
35 card_registry->RegisterCard({.card_id = "screen.inventory_menu", .display_name = "Inventory Menu",
36 .icon = ICON_MD_INVENTORY, .category = "Screen",
37 .shortcut_hint = "Alt+2", .priority = 20});
38 card_registry->RegisterCard({.card_id = "screen.overworld_map", .display_name = "Overworld Map",
39 .icon = ICON_MD_PUBLIC, .category = "Screen",
40 .shortcut_hint = "Alt+3", .priority = 30});
41 card_registry->RegisterCard({.card_id = "screen.title_screen", .display_name = "Title Screen",
42 .icon = ICON_MD_TITLE, .category = "Screen",
43 .shortcut_hint = "Alt+4", .priority = 40});
44 card_registry->RegisterCard({.card_id = "screen.naming_screen", .display_name = "Naming Screen",
45 .icon = ICON_MD_EDIT, .category = "Screen",
46 .shortcut_hint = "Alt+5", .priority = 50});
47
48 // Show title screen by default
49 card_registry->ShowCard("screen.title_screen");
50}
51
52absl::Status ScreenEditor::Load() {
53 gfx::ScopedTimer timer("ScreenEditor::Load");
54
58 tile16_blockset_, *rom(), rom()->graphics_buffer(), false));
59
60 // Load graphics sheets and apply dungeon palette
61 sheets_.try_emplace(0, gfx::Arena::Get().gfx_sheets()[212]);
62 sheets_.try_emplace(1, gfx::Arena::Get().gfx_sheets()[213]);
63 sheets_.try_emplace(2, gfx::Arena::Get().gfx_sheets()[214]);
64 sheets_.try_emplace(3, gfx::Arena::Get().gfx_sheets()[215]);
65
66 // Apply dungeon palette to all sheets
67 for (int i = 0; i < 4; i++) {
68 sheets_[i].SetPalette(*rom()->mutable_dungeon_palette(3));
71 }
72
73 // Create a single tilemap for tile8 graphics with on-demand texture creation
74 // Combine all 4 sheets (128x32 each) into one bitmap (128x128)
75 // This gives us 16 tiles per row × 16 rows = 256 tiles total
76 const int tile8_width = 128;
77 const int tile8_height = 128; // 4 sheets × 32 pixels each
78 std::vector<uint8_t> tile8_data(tile8_width * tile8_height);
79
80 // Copy data from all 4 sheets into the combined bitmap
81 for (int sheet_idx = 0; sheet_idx < 4; sheet_idx++) {
82 const auto& sheet = sheets_[sheet_idx];
83 int dest_y_offset = sheet_idx * 32; // Each sheet is 32 pixels tall
84
85 for (int y = 0; y < 32; y++) {
86 for (int x = 0; x < 128; x++) {
87 int src_index = y * 128 + x;
88 int dest_index = (dest_y_offset + y) * 128 + x;
89
90 if (src_index < sheet.size() && dest_index < tile8_data.size()) {
91 tile8_data[dest_index] = sheet.data()[src_index];
92 }
93 }
94 }
95 }
96
97 // Create tilemap with 8x8 tile size
99 tile8_tilemap_.map_size = {256, 256}; // Logical size for tile count
100 tile8_tilemap_.atlas.Create(tile8_width, tile8_height, 8, tile8_data);
102
103 // Queue single texture creation for the atlas (not individual tiles)
106 return absl::OkStatus();
107}
108
109absl::Status ScreenEditor::Update() {
110 if (!dependencies_.card_registry) return absl::OkStatus();
111 auto* card_registry = dependencies_.card_registry;
112
113 static gui::EditorCard dungeon_maps_card("Dungeon Maps", ICON_MD_MAP);
114 static gui::EditorCard inventory_menu_card("Inventory Menu", ICON_MD_INVENTORY);
115 static gui::EditorCard overworld_map_card("Overworld Map", ICON_MD_PUBLIC);
116 static gui::EditorCard title_screen_card("Title Screen", ICON_MD_TITLE);
117 static gui::EditorCard naming_screen_card("Naming Screen", ICON_MD_EDIT_ATTRIBUTES);
118
119 dungeon_maps_card.SetDefaultSize(800, 600);
120 inventory_menu_card.SetDefaultSize(800, 600);
121 overworld_map_card.SetDefaultSize(600, 500);
122 title_screen_card.SetDefaultSize(600, 500);
123 naming_screen_card.SetDefaultSize(500, 400);
124
125 // Dungeon Maps Card - Check visibility flag exists and is true before rendering
126 bool* dungeon_maps_visible = card_registry->GetVisibilityFlag("screen.dungeon_maps");
127 if (dungeon_maps_visible && *dungeon_maps_visible) {
128 if (dungeon_maps_card.Begin(dungeon_maps_visible)) {
130 }
131 dungeon_maps_card.End();
132 }
133
134 // Inventory Menu Card - Check visibility flag exists and is true before rendering
135 bool* inventory_menu_visible = card_registry->GetVisibilityFlag("screen.inventory_menu");
136 if (inventory_menu_visible && *inventory_menu_visible) {
137 if (inventory_menu_card.Begin(inventory_menu_visible)) {
139 }
140 inventory_menu_card.End();
141 }
142
143 // Overworld Map Card - Check visibility flag exists and is true before rendering
144 bool* overworld_map_visible = card_registry->GetVisibilityFlag("screen.overworld_map");
145 if (overworld_map_visible && *overworld_map_visible) {
146 if (overworld_map_card.Begin(overworld_map_visible)) {
148 }
149 overworld_map_card.End();
150 }
151
152 // Title Screen Card - Check visibility flag exists and is true before rendering
153 bool* title_screen_visible = card_registry->GetVisibilityFlag("screen.title_screen");
154 if (title_screen_visible && *title_screen_visible) {
155 if (title_screen_card.Begin(title_screen_visible)) {
157 }
158 title_screen_card.End();
159 }
160
161 // Naming Screen Card - Check visibility flag exists and is true before rendering
162 bool* naming_screen_visible = card_registry->GetVisibilityFlag("screen.naming_screen");
163 if (naming_screen_visible && *naming_screen_visible) {
164 if (naming_screen_card.Begin(naming_screen_visible)) {
166 }
167 naming_screen_card.End();
168 }
169
170 return status_;
171}
172
174 // Sidebar is now drawn by EditorManager for card-based editors
175 // This method kept for compatibility but sidebar handles card toggles
176}
177
179 static bool create = false;
180 if (!create && rom()->is_loaded()) {
182 if (status_.ok()) {
184 create = true;
185 } else {
186 ImGui::TextColored(ImVec4(1, 0, 0, 1), "Error loading inventory: %s",
187 status_.message().data());
188 return;
189 }
190 }
191
193
194 if (ImGui::BeginTable("InventoryScreen", 4, ImGuiTableFlags_Resizable)) {
195 ImGui::TableSetupColumn("Canvas");
196 ImGui::TableSetupColumn("Tilesheet");
197 ImGui::TableSetupColumn("Item Icons");
198 ImGui::TableSetupColumn("Palette");
199 ImGui::TableHeadersRow();
200
201 ImGui::TableNextColumn();
207
208 ImGui::TableNextColumn();
209 tilesheet_canvas_.DrawBackground(ImVec2(128 * 2 + 2, (192 * 2) + 4));
214
215 ImGui::TableNextColumn();
217
218 ImGui::TableNextColumn();
220
221 ImGui::EndTable();
222 }
223 ImGui::Separator();
224
225 // TODO(scawful): Future Oracle of Secrets menu editor integration
226 // - Full inventory screen layout editor
227 // - Item slot assignment and positioning
228 // - Heart container and magic meter editor
229 // - Equipment display customization
230 // - A/B button equipment quick-select editor
231}
232
234 if (ImGui::BeginTable("InventoryToolset", 8, ImGuiTableFlags_SizingFixedFit,
235 ImVec2(0, 0))) {
236 ImGui::TableSetupColumn("#drawTool");
237 ImGui::TableSetupColumn("#sep1");
238 ImGui::TableSetupColumn("#zoomOut");
239 ImGui::TableSetupColumn("#zoomIN");
240 ImGui::TableSetupColumn("#sep2");
241 ImGui::TableSetupColumn("#bg2Tool");
242 ImGui::TableSetupColumn("#bg3Tool");
243 ImGui::TableSetupColumn("#itemTool");
244
245 ImGui::TableNextColumn();
246 if (ImGui::Button(ICON_MD_UNDO)) {
247 // status_ = inventory_.Undo();
248 }
249 ImGui::TableNextColumn();
250 if (ImGui::Button(ICON_MD_REDO)) {
251 // status_ = inventory_.Redo();
252 }
253 ImGui::TableNextColumn();
254 ImGui::Text(ICON_MD_MORE_VERT);
255 ImGui::TableNextColumn();
256 if (ImGui::Button(ICON_MD_ZOOM_OUT)) {
258 }
259 ImGui::TableNextColumn();
260 if (ImGui::Button(ICON_MD_ZOOM_IN)) {
262 }
263 ImGui::TableNextColumn();
264 ImGui::Text(ICON_MD_MORE_VERT);
265 ImGui::TableNextColumn();
266 if (ImGui::Button(ICON_MD_DRAW)) {
268 }
269 ImGui::TableNextColumn();
270 if (ImGui::Button(ICON_MD_BUILD)) {
271 // current_mode_ = EditingMode::BUILD;
272 }
273
274 ImGui::EndTable();
275 }
276}
277
279 if (ImGui::BeginChild("##ItemIconsList", ImVec2(0, 0), true,
280 ImGuiWindowFlags_HorizontalScrollbar)) {
281 ImGui::Text("Item Icons (2x2 tiles each)");
282 ImGui::Separator();
283
284 auto& icons = inventory_.item_icons();
285 if (icons.empty()) {
286 ImGui::TextWrapped("No item icons loaded. Icons will be loaded when the "
287 "inventory is initialized.");
288 ImGui::EndChild();
289 return;
290 }
291
292 // Display icons in a table format
293 if (ImGui::BeginTable("##IconsTable", 2,
294 ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg)) {
295 ImGui::TableSetupColumn("Icon Name");
296 ImGui::TableSetupColumn("Tile Data");
297 ImGui::TableHeadersRow();
298
299 for (size_t i = 0; i < icons.size(); i++) {
300 const auto& icon = icons[i];
301
302 ImGui::TableNextRow();
303 ImGui::TableNextColumn();
304
305 // Display icon name with selectable row
306 if (ImGui::Selectable(icon.name.c_str(), false,
307 ImGuiSelectableFlags_SpanAllColumns)) {
308 // TODO: Select this icon for editing
309 }
310
311 ImGui::TableNextColumn();
312 // Display tile word data in hex format
313 ImGui::Text("TL:%04X TR:%04X", icon.tile_tl, icon.tile_tr);
314 ImGui::SameLine();
315 ImGui::Text("BL:%04X BR:%04X", icon.tile_bl, icon.tile_br);
316 }
317
318 ImGui::EndTable();
319 }
320
321 ImGui::Separator();
322 ImGui::TextWrapped(
323 "NOTE: Individual icon editing will be implemented in the future "
324 "Oracle of Secrets menu editor. Each icon is composed of 4 tile words "
325 "representing a 2x2 arrangement of 8x8 tiles in SNES tile format "
326 "(vhopppcc cccccccc).");
327 }
328 ImGui::EndChild();
329}
330
332 gfx::ScopedTimer timer("screen_editor_draw_dungeon_map_screen");
333
334 auto& current_dungeon = dungeon_maps_[selected_dungeon];
335
336 floor_number = i;
337 screen_canvas_.DrawBackground(ImVec2(325, 325));
339
340 auto boss_room = current_dungeon.boss_room;
341
342 // Pre-allocate vectors for batch operations
343 std::vector<int> tile_ids_to_render;
344 std::vector<ImVec2> tile_positions;
345 tile_ids_to_render.reserve(zelda3::kNumRooms);
346 tile_positions.reserve(zelda3::kNumRooms);
347
348 for (int j = 0; j < zelda3::kNumRooms; j++) {
349 if (current_dungeon.floor_rooms[floor_number][j] != 0x0F) {
350 int tile16_id = current_dungeon.floor_gfx[floor_number][j];
351 int posX = ((j % 5) * 32);
352 int posY = ((j / 5) * 32);
353
354 // Batch tile rendering
355 tile_ids_to_render.push_back(tile16_id);
356 tile_positions.emplace_back(posX * 2, posY * 2);
357 }
358 }
359
360 // Batch render all tiles
361 for (size_t idx = 0; idx < tile_ids_to_render.size(); ++idx) {
362 int tile16_id = tile_ids_to_render[idx];
363 ImVec2 pos = tile_positions[idx];
364
365 // Extract tile data from the atlas directly
366 const int tiles_per_row = tile16_blockset_.atlas.width() / 16;
367 const int tile_x = (tile16_id % tiles_per_row) * 16;
368 const int tile_y = (tile16_id / tiles_per_row) * 16;
369
370 std::vector<uint8_t> tile_data(16 * 16);
371 int tile_data_offset = 0;
372 tile16_blockset_.atlas.Get16x16Tile(tile_x, tile_y, tile_data, tile_data_offset);
373
374 // Create or update cached tile
375 auto* cached_tile = tile16_blockset_.tile_cache.GetTile(tile16_id);
376 if (!cached_tile) {
377 // Create new cached tile
378 gfx::Bitmap new_tile(16, 16, 8, tile_data);
380 tile16_blockset_.tile_cache.CacheTile(tile16_id, std::move(new_tile));
381 cached_tile = tile16_blockset_.tile_cache.GetTile(tile16_id);
382 } else {
383 // Update existing cached tile data
384 cached_tile->set_data(tile_data);
385 }
386
387 if (cached_tile && cached_tile->is_active()) {
388 // Ensure the cached tile has a valid texture
389 if (!cached_tile->texture()) {
390 // Queue texture creation via Arena's deferred system
393 }
394 screen_canvas_.DrawBitmap(*cached_tile, pos.x, pos.y, 4.0F, 255);
395 }
396 }
397
398 // Draw overlays and labels
399 for (int j = 0; j < zelda3::kNumRooms; j++) {
400 if (current_dungeon.floor_rooms[floor_number][j] != 0x0F) {
401 int posX = ((j % 5) * 32);
402 int posY = ((j / 5) * 32);
403
404 if (current_dungeon.floor_rooms[floor_number][j] == boss_room) {
405 screen_canvas_.DrawOutlineWithColor((posX * 2), (posY * 2), 64, 64,
406 kRedPen);
407 }
408
409 std::string label =
411 screen_canvas_.DrawText(label, (posX * 2), (posY * 2));
412 std::string gfx_id =
413 util::HexByte(current_dungeon.floor_gfx[floor_number][j]);
414 screen_canvas_.DrawText(gfx_id, (posX * 2), (posY * 2) + 16);
415 }
416 }
417
418 screen_canvas_.DrawGrid(64.f, 5);
420
421 if (!screen_canvas_.points().empty()) {
422 int x = screen_canvas_.points().front().x / 64;
423 int y = screen_canvas_.points().front().y / 64;
424 selected_room = x + (y * 5);
425 }
426}
427
429 auto& current_dungeon = dungeon_maps_[selected_dungeon];
430 if (ImGui::BeginTabBar("##DungeonMapTabs")) {
431 auto nbr_floors =
432 current_dungeon.nbr_of_floor + current_dungeon.nbr_of_basement;
433 for (int i = 0; i < nbr_floors; i++) {
434 int basement_num = current_dungeon.nbr_of_basement - i;
435 std::string tab_name = absl::StrFormat("Basement %d", basement_num);
436 if (i >= current_dungeon.nbr_of_basement) {
437 tab_name = absl::StrFormat("Floor %d",
438 i - current_dungeon.nbr_of_basement + 1);
439 }
440 if (ImGui::BeginTabItem(tab_name.data())) {
442 ImGui::EndTabItem();
443 }
444 }
445 ImGui::EndTabBar();
446 }
447
449 "Selected Room",
450 &current_dungeon.floor_rooms[floor_number].at(selected_room));
451
452 gui::InputHexWord("Boss Room", &current_dungeon.boss_room);
453
454 const auto button_size = ImVec2(130, 0);
455
456 if (ImGui::Button("Add Floor", button_size) &&
457 current_dungeon.nbr_of_floor < 8) {
458 current_dungeon.nbr_of_floor++;
460 }
461 ImGui::SameLine();
462 if (ImGui::Button("Remove Floor", button_size) &&
463 current_dungeon.nbr_of_floor > 0) {
464 current_dungeon.nbr_of_floor--;
466 }
467
468 if (ImGui::Button("Add Basement", button_size) &&
469 current_dungeon.nbr_of_basement < 8) {
470 current_dungeon.nbr_of_basement++;
472 }
473 ImGui::SameLine();
474 if (ImGui::Button("Remove Basement", button_size) &&
475 current_dungeon.nbr_of_basement > 0) {
476 current_dungeon.nbr_of_basement--;
478 }
479
480 if (ImGui::Button("Copy Floor", button_size)) {
481 copy_button_pressed = true;
482 }
483 ImGui::SameLine();
484 if (ImGui::Button("Paste Floor", button_size)) {
486 }
487}
488
506 gfx::ScopedTimer timer("screen_editor_draw_dungeon_maps_room_gfx");
507
508 if (ImGui::BeginChild("##DungeonMapTiles", ImVec2(0, 0), true)) {
509 // Enhanced tilesheet canvas with improved tile selection
510 tilesheet_canvas_.DrawBackground(ImVec2((256 * 2) + 2, (192 * 2) + 4));
512
513 // Interactive tile16 selector with grid snapping
515 selected_tile16_ = tilesheet_canvas_.points().front().x / 32 +
516 (tilesheet_canvas_.points().front().y / 32) * 16;
517
518 // Render selected tile16 and cache tile metadata
521 current_tile16_info.begin());
522 }
523 // Use direct bitmap rendering for tilesheet
527
528 if (!tilesheet_canvas_.points().empty() &&
529 !screen_canvas_.points().empty()) {
533 }
534
535 ImGui::Separator();
536 current_tile_canvas_.DrawBackground(); // ImVec2(64 * 2 + 2, 64 * 2 + 4));
538
539 // Get tile8 from cache on-demand (only create texture when needed)
540 if (selected_tile8_ >= 0 && selected_tile8_ < 256) {
541 auto* cached_tile8 = tile8_tilemap_.tile_cache.GetTile(selected_tile8_);
542
543 if (!cached_tile8) {
544 // Extract tile from atlas and cache it
545 const int tiles_per_row = tile8_tilemap_.atlas.width() / 8; // 128 / 8 = 16
546 const int tile_x = (selected_tile8_ % tiles_per_row) * 8;
547 const int tile_y = (selected_tile8_ / tiles_per_row) * 8;
548
549 // Extract 8x8 tile data from atlas
550 std::vector<uint8_t> tile_data(64);
551 for (int py = 0; py < 8; py++) {
552 for (int px = 0; px < 8; px++) {
553 int src_x = tile_x + px;
554 int src_y = tile_y + py;
555 int src_index = src_y * tile8_tilemap_.atlas.width() + src_x;
556 int dst_index = py * 8 + px;
557
558 if (src_index < tile8_tilemap_.atlas.size() && dst_index < 64) {
559 tile_data[dst_index] = tile8_tilemap_.atlas.data()[src_index];
560 }
561 }
562 }
563
564 gfx::Bitmap new_tile8(8, 8, 8, tile_data);
566 tile8_tilemap_.tile_cache.CacheTile(selected_tile8_, std::move(new_tile8));
568 }
569
570 if (cached_tile8 && cached_tile8->is_active()) {
571 // Create texture on-demand only when needed
572 if (!cached_tile8->texture()) {
575 }
576
577 if (current_tile_canvas_.DrawTilePainter(*cached_tile8, 16)) {
578 // Modify the tile16 based on the selected tile and current_tile16_info
579 gfx::ModifyTile16(tile16_blockset_, rom()->graphics_buffer(),
584 }
585 }
586 }
587 // Get selected tile from cache
589 if (selected_tile && selected_tile->is_active()) {
590 // Ensure the selected tile has a valid texture
591 if (!selected_tile->texture()) {
592 // Queue texture creation via Arena's deferred system
595 }
596 current_tile_canvas_.DrawBitmap(*selected_tile, 2, 2, 4.0f, 255);
597 }
600
602 ImGui::SameLine();
605 ImGui::SameLine();
607
608 if (ImGui::Button("Modify Tile16")) {
609 gfx::ModifyTile16(tile16_blockset_, rom()->graphics_buffer(),
614 }
615 }
616 ImGui::EndChild();
617}
618
636 // Enhanced editing mode controls with visual feedback
637 if (ImGui::Button(ICON_MD_DRAW)) {
639 }
640 ImGui::SameLine();
641 if (ImGui::Button(ICON_MD_EDIT)) {
643 }
644 ImGui::SameLine();
645 if (ImGui::Button(ICON_MD_SAVE)) {
647 }
648
649 static std::vector<std::string> dungeon_names = {
650 "Sewers/Sanctuary", "Hyrule Castle", "Eastern Palace",
651 "Desert Palace", "Tower of Hera", "Agahnim's Tower",
652 "Palace of Darkness", "Swamp Palace", "Skull Woods",
653 "Thieves' Town", "Ice Palace", "Misery Mire",
654 "Turtle Rock", "Ganon's Tower"};
655
656 if (ImGui::BeginTable("DungeonMapsTable", 4,
657 ImGuiTableFlags_Resizable |
658 ImGuiTableFlags_Reorderable |
659 ImGuiTableFlags_Hideable)) {
660 ImGui::TableSetupColumn("Dungeon");
661 ImGui::TableSetupColumn("Map");
662 ImGui::TableSetupColumn("Rooms Gfx");
663 ImGui::TableSetupColumn("Tiles Gfx");
664 ImGui::TableHeadersRow();
665
666 ImGui::TableNextColumn();
667 for (int i = 0; i < dungeon_names.size(); i++) {
669 selected_dungeon == i, "Dungeon Names", absl::StrFormat("%d", i),
670 dungeon_names[i]);
671 if (ImGui::IsItemClicked()) {
673 }
674 }
675
676 ImGui::TableNextColumn();
678
679 ImGui::TableNextColumn();
681
682 ImGui::TableNextColumn();
686 // Get the tile8 ID to use for the tile16 drawing above
688 }
692
693 ImGui::Text("Selected tile8: %d", selected_tile8_);
694 ImGui::Separator();
695 ImGui::Text("For use with custom inserted graphics assembly patches.");
696 if (ImGui::Button("Load GFX from BIN file"))
698
699 ImGui::EndTable();
700 }
701}
702
704 std::string bin_file = util::FileDialogWrapper::ShowOpenFileDialog();
705 if (!bin_file.empty()) {
706 std::ifstream file(bin_file, std::ios::binary);
707 if (file.is_open()) {
708 // Read the gfx data into a buffer
709 std::vector<uint8_t> bin_data((std::istreambuf_iterator<char>(file)),
710 std::istreambuf_iterator<char>());
711 if (auto converted_bin = gfx::SnesTo8bppSheet(bin_data, 4, 4);
713 true)
714 .ok()) {
715 sheets_.clear();
716 std::vector<std::vector<uint8_t>> gfx_sheets;
717 for (int i = 0; i < 4; i++) {
718 gfx_sheets.emplace_back(converted_bin.begin() + (i * 0x1000),
719 converted_bin.begin() + ((i + 1) * 0x1000));
720 sheets_.emplace(i, gfx::Bitmap(128, 32, 8, gfx_sheets[i]));
721 sheets_[i].SetPalette(*rom()->mutable_dungeon_palette(3));
722 // Queue texture creation via Arena's deferred system
725 }
726 binary_gfx_loaded_ = true;
727 } else {
728 status_ = absl::InternalError("Failed to load dungeon map tile16");
729 }
730 file.close();
731 }
732 }
733}
734
736 // Initialize title screen on first draw
737 if (!title_screen_loaded_ && rom()->is_loaded()) {
739 if (!status_.ok()) {
740 ImGui::TextColored(ImVec4(1, 0, 0, 1), "Error loading title screen: %s",
741 status_.message().data());
742 return;
743 }
745 }
746
748 ImGui::Text("Title screen not loaded. Ensure ROM is loaded.");
749 return;
750 }
751
752 // Toolbar with mode controls
753 if (ImGui::Button(ICON_MD_DRAW)) {
755 }
756 ImGui::SameLine();
757 if (ImGui::Button(ICON_MD_SAVE)) {
759 if (status_.ok()) {
760 ImGui::OpenPopup("SaveSuccess");
761 }
762 }
763 ImGui::SameLine();
764 ImGui::Text("Selected Tile: %d", selected_title_tile16_);
765
766 // Save success popup
767 if (ImGui::BeginPopup("SaveSuccess")) {
768 ImGui::Text("Title screen saved successfully!");
769 ImGui::EndPopup();
770 }
771
772 // Layer visibility controls
773 bool prev_bg1 = show_title_bg1_;
774 bool prev_bg2 = show_title_bg2_;
775 ImGui::Checkbox("Show BG1", &show_title_bg1_);
776 ImGui::SameLine();
777 ImGui::Checkbox("Show BG2", &show_title_bg2_);
778
779 // Re-render composite if visibility changed
780 if (prev_bg1 != show_title_bg1_ || prev_bg2 != show_title_bg2_) {
782 if (status_.ok()) {
786 }
787 }
788
789 // Layout: 2-column table (composite view + tile selector)
790 if (ImGui::BeginTable("TitleScreenTable", 2,
791 ImGuiTableFlags_Resizable | ImGuiTableFlags_Borders)) {
792 ImGui::TableSetupColumn("Title Screen (Composite)");
793 ImGui::TableSetupColumn("Tile Selector");
794 ImGui::TableHeadersRow();
795
796 // Column 1: Composite Canvas (BG1+BG2 stacked)
797 ImGui::TableNextColumn();
799
800 // Column 2: Blockset Selector
801 ImGui::TableNextColumn();
803
804 ImGui::EndTable();
805 }
806}
807
811
812 // Draw composite tilemap (BG1+BG2 stacked with transparency)
813 auto& composite_bitmap = title_screen_.composite_bitmap();
814 if (composite_bitmap.is_active()) {
815 title_bg1_canvas_.DrawBitmap(composite_bitmap, 0, 0, 2.0f, 255);
816 }
817
818 // Handle tile painting - always paint to BG1 layer
821 if (!title_bg1_canvas_.points().empty()) {
822 auto click_pos = title_bg1_canvas_.points().front();
823 int tile_x = static_cast<int>(click_pos.x) / 8;
824 int tile_y = static_cast<int>(click_pos.y) / 8;
825
826 if (tile_x >= 0 && tile_x < 32 && tile_y >= 0 && tile_y < 32) {
827 int tilemap_index = tile_y * 32 + tile_x;
828
829 // Create tile word: tile_id | (palette << 10) | h_flip | v_flip
830 uint16_t tile_word = selected_title_tile16_ & 0x3FF;
831 tile_word |= (title_palette_ & 0x07) << 10;
832 if (title_h_flip_) tile_word |= 0x4000;
833 if (title_v_flip_) tile_word |= 0x8000;
834
835 // Update BG1 buffer and re-render both layers and composite
836 title_screen_.mutable_bg1_buffer()[tilemap_index] = tile_word;
838 if (status_.ok()) {
839 // Update BG1 texture
843
844 // Re-render and update composite
846 if (status_.ok()) {
848 gfx::Arena::TextureCommandType::UPDATE, &composite_bitmap);
849 }
850 }
851 }
852 }
853 }
854 }
855
858}
859
863
864 // Draw BG1 tilemap
865 auto& bg1_bitmap = title_screen_.bg1_bitmap();
866 if (bg1_bitmap.is_active()) {
867 title_bg1_canvas_.DrawBitmap(bg1_bitmap, 0, 0, 2.0f, 255);
868 }
869
870 // Handle tile painting
873 if (!title_bg1_canvas_.points().empty()) {
874 auto click_pos = title_bg1_canvas_.points().front();
875 int tile_x = static_cast<int>(click_pos.x) / 8;
876 int tile_y = static_cast<int>(click_pos.y) / 8;
877
878 if (tile_x >= 0 && tile_x < 32 && tile_y >= 0 && tile_y < 32) {
879 int tilemap_index = tile_y * 32 + tile_x;
880
881 // Create tile word: tile_id | (palette << 10) | h_flip | v_flip
882 uint16_t tile_word = selected_title_tile16_ & 0x3FF;
883 tile_word |= (title_palette_ & 0x07) << 10;
884 if (title_h_flip_) tile_word |= 0x4000;
885 if (title_v_flip_) tile_word |= 0x8000;
886
887 // Update buffer and re-render
888 title_screen_.mutable_bg1_buffer()[tilemap_index] = tile_word;
890 if (status_.ok()) {
893 }
894 }
895 }
896 }
897 }
898
901}
902
906
907 // Draw BG2 tilemap
908 auto& bg2_bitmap = title_screen_.bg2_bitmap();
909 if (bg2_bitmap.is_active()) {
910 title_bg2_canvas_.DrawBitmap(bg2_bitmap, 0, 0, 2.0f, 255);
911 }
912
913 // Handle tile painting
916 if (!title_bg2_canvas_.points().empty()) {
917 auto click_pos = title_bg2_canvas_.points().front();
918 int tile_x = static_cast<int>(click_pos.x) / 8;
919 int tile_y = static_cast<int>(click_pos.y) / 8;
920
921 if (tile_x >= 0 && tile_x < 32 && tile_y >= 0 && tile_y < 32) {
922 int tilemap_index = tile_y * 32 + tile_x;
923
924 // Create tile word: tile_id | (palette << 10) | h_flip | v_flip
925 uint16_t tile_word = selected_title_tile16_ & 0x3FF;
926 tile_word |= (title_palette_ & 0x07) << 10;
927 if (title_h_flip_) tile_word |= 0x4000;
928 if (title_v_flip_) tile_word |= 0x8000;
929
930 // Update buffer and re-render
931 title_screen_.mutable_bg2_buffer()[tilemap_index] = tile_word;
933 if (status_.ok()) {
936 }
937 }
938 }
939 }
940 }
941
944}
945
949
950 // Draw tile8 bitmap (8x8 tiles used to compose tile16)
951 auto& tiles8_bitmap = title_screen_.tiles8_bitmap();
952 if (tiles8_bitmap.is_active()) {
953 title_blockset_canvas_.DrawBitmap(tiles8_bitmap, 0, 0, 2.0f, 255);
954 }
955
956 // Handle tile selection (8x8 tiles)
958 // Calculate selected tile ID from click position
959 if (!title_blockset_canvas_.points().empty()) {
960 auto click_pos = title_blockset_canvas_.points().front();
961 int tile_x = static_cast<int>(click_pos.x) / 8;
962 int tile_y = static_cast<int>(click_pos.y) / 8;
963 int tiles_per_row = 128 / 8; // 16 tiles per row for 8x8 tiles
964 selected_title_tile16_ = tile_x + (tile_y * tiles_per_row);
965 }
966 }
967
970
971 // Show selected tile preview and controls
972 if (selected_title_tile16_ >= 0) {
973 ImGui::Text("Selected Tile: %d", selected_title_tile16_);
974
975 // Flip controls
976 ImGui::Checkbox("H Flip", &title_h_flip_);
977 ImGui::SameLine();
978 ImGui::Checkbox("V Flip", &title_v_flip_);
979
980 // Palette selector (0-7 for 3BPP graphics)
981 ImGui::SetNextItemWidth(100);
982 ImGui::SliderInt("Palette", &title_palette_, 0, 7);
983 }
984}
985
988
990 // Initialize overworld map on first draw
991 if (!ow_map_loaded_ && rom()->is_loaded()) {
993 if (!status_.ok()) {
994 ImGui::TextColored(ImVec4(1, 0, 0, 1), "Error loading overworld map: %s",
995 status_.message().data());
996 return;
997 }
998 ow_map_loaded_ = true;
999 }
1000
1001 if (!ow_map_loaded_) {
1002 ImGui::Text("Overworld map not loaded. Ensure ROM is loaded.");
1003 return;
1004 }
1005
1006 // Toolbar with mode controls
1007 if (ImGui::Button(ICON_MD_DRAW)) {
1009 }
1010 ImGui::SameLine();
1011 if (ImGui::Button(ICON_MD_SAVE)) {
1013 if (status_.ok()) {
1014 ImGui::OpenPopup("OWSaveSuccess");
1015 }
1016 }
1017 ImGui::SameLine();
1018
1019 // World toggle
1020 if (ImGui::Button(ow_show_dark_world_ ? "Dark World" : "Light World")) {
1022 // Re-render map with new world
1024 if (status_.ok()) {
1027 }
1028 }
1029 ImGui::SameLine();
1030
1031 // Custom map load/save buttons
1032 if (ImGui::Button("Load Custom Map...")) {
1034 if (!path.empty()) {
1036 if (!status_.ok()) {
1037 ImGui::OpenPopup("CustomMapLoadError");
1038 }
1039 }
1040 }
1041 ImGui::SameLine();
1042 if (ImGui::Button("Save Custom Map...")) {
1044 if (!path.empty()) {
1046 if (status_.ok()) {
1047 ImGui::OpenPopup("CustomMapSaveSuccess");
1048 }
1049 }
1050 }
1051
1052 ImGui::SameLine();
1053 ImGui::Text("Selected Tile: %d", selected_ow_tile_);
1054
1055 // Custom map error/success popups
1056 if (ImGui::BeginPopup("CustomMapLoadError")) {
1057 ImGui::Text("Error loading custom map: %s", status_.message().data());
1058 ImGui::EndPopup();
1059 }
1060 if (ImGui::BeginPopup("CustomMapSaveSuccess")) {
1061 ImGui::Text("Custom map saved successfully!");
1062 ImGui::EndPopup();
1063 }
1064
1065 // Save success popup
1066 if (ImGui::BeginPopup("OWSaveSuccess")) {
1067 ImGui::Text("Overworld map saved successfully!");
1068 ImGui::EndPopup();
1069 }
1070
1071 // Layout: 3-column table
1072 if (ImGui::BeginTable("OWMapTable", 3,
1073 ImGuiTableFlags_Resizable | ImGuiTableFlags_Borders)) {
1074 ImGui::TableSetupColumn("Map Canvas");
1075 ImGui::TableSetupColumn("Tileset");
1076 ImGui::TableSetupColumn("Palette");
1077 ImGui::TableHeadersRow();
1078
1079 // Column 1: Map Canvas
1080 ImGui::TableNextColumn();
1083
1084 auto& map_bitmap = ow_map_screen_.map_bitmap();
1085 if (map_bitmap.is_active()) {
1086 ow_map_canvas_.DrawBitmap(map_bitmap, 0, 0, 1.0f, 255);
1087 }
1088
1089 // Handle tile painting
1091 if (ow_map_canvas_.DrawTileSelector(8.0f)) {
1092 if (!ow_map_canvas_.points().empty()) {
1093 auto click_pos = ow_map_canvas_.points().front();
1094 int tile_x = static_cast<int>(click_pos.x) / 8;
1095 int tile_y = static_cast<int>(click_pos.y) / 8;
1096
1097 if (tile_x >= 0 && tile_x < 64 && tile_y >= 0 && tile_y < 64) {
1098 int tile_index = tile_x + (tile_y * 64);
1099
1100 // Update appropriate world's tile data
1101 if (ow_show_dark_world_) {
1103 } else {
1105 }
1106
1107 // Re-render map
1109 if (status_.ok()) {
1112 }
1113 }
1114 }
1115 }
1116 }
1117
1120
1121 // Column 2: Tileset Selector
1122 ImGui::TableNextColumn();
1125
1126 auto& tiles8_bitmap = ow_map_screen_.tiles8_bitmap();
1127 if (tiles8_bitmap.is_active()) {
1128 ow_tileset_canvas_.DrawBitmap(tiles8_bitmap, 0, 0, 2.0f, 255);
1129 }
1130
1131 // Handle tile selection
1133 if (!ow_tileset_canvas_.points().empty()) {
1134 auto click_pos = ow_tileset_canvas_.points().front();
1135 int tile_x = static_cast<int>(click_pos.x) / 8;
1136 int tile_y = static_cast<int>(click_pos.y) / 8;
1137 selected_ow_tile_ = tile_x + (tile_y * 16); // 16 tiles per row
1138 }
1139 }
1140
1143
1144 // Column 3: Palette Display
1145 ImGui::TableNextColumn();
1146 auto& palette = ow_show_dark_world_ ? ow_map_screen_.dw_palette()
1148 // Use inline palette editor for full 128-color palette
1149 gui::InlinePaletteEditor(palette, "Overworld Map Palette");
1150
1151 ImGui::EndTable();
1152 }
1153}
1154
1156 static bool show_bg1 = true;
1157 static bool show_bg2 = true;
1158 static bool show_bg3 = true;
1159
1160 static bool drawing_bg1 = true;
1161 static bool drawing_bg2 = false;
1162 static bool drawing_bg3 = false;
1163
1164 ImGui::Checkbox("Show BG1", &show_bg1);
1165 ImGui::SameLine();
1166 ImGui::Checkbox("Show BG2", &show_bg2);
1167
1168 ImGui::Checkbox("Draw BG1", &drawing_bg1);
1169 ImGui::SameLine();
1170 ImGui::Checkbox("Draw BG2", &drawing_bg2);
1171 ImGui::SameLine();
1172 ImGui::Checkbox("Draw BG3", &drawing_bg3);
1173}
1174
1175} // namespace editor
1176} // namespace yaze
project::ResourceLabelManager * resource_label()
Definition rom.h:223
auto mutable_dungeon_palette(int i)
Definition rom.h:219
void RegisterCard(size_t session_id, const CardInfo &base_info)
Register a card for a specific session.
EditorDependencies dependencies_
Definition editor.h:165
void DrawDungeonMapsRoomGfx()
Draw dungeon room graphics editor with enhanced tile16 editing.
std::array< gfx::TileInfo, 4 > current_tile16_info
absl::Status Load() override
absl::Status Update() override
zelda3::OverworldMapScreen ow_map_screen_
void DrawDungeonMapsEditor()
Draw dungeon maps editor with enhanced ROM hacking features.
zelda3::TitleScreen title_screen_
zelda3::Inventory inventory_
zelda3::DungeonMapLabels dungeon_map_labels_
std::vector< zelda3::DungeonMap > dungeon_maps_
void QueueTextureCommand(TextureCommandType type, Bitmap *bitmap)
Definition arena.cc:32
static Arena & Get()
Definition arena.cc:15
Represents a bitmap image optimized for SNES ROM hacking.
Definition bitmap.h:66
const uint8_t * data() const
Definition bitmap.h:286
const SnesPalette & palette() const
Definition bitmap.h:277
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
auto size() const
Definition bitmap.h:285
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
int width() const
Definition bitmap.h:282
void Get16x16Tile(int tile_x, int tile_y, std::vector< uint8_t > &tile_data, int &tile_data_offset)
Extract a 16x16 tile from the bitmap (SNES metatile size)
Definition bitmap.cc:582
RAII timer for automatic timing management.
void DrawBitmap(Bitmap &bitmap, int border_offset, float scale)
Definition canvas.cc:1004
void DrawOutlineWithColor(int x, int y, int w, int h, ImVec4 color)
Definition canvas.cc:1065
ImVector< ImVec2 > * mutable_points()
Definition canvas.h:291
void DrawContextMenu()
Definition canvas.cc:428
int GetTileIdFromMousePos()
Definition canvas.h:272
bool DrawTileSelector(int size, int size_y=0)
Definition canvas.cc:862
bool DrawTilePainter(const Bitmap &bitmap, int size, float scale=1.0f)
Definition canvas.cc:670
void DrawBitmapTable(const BitmapTable &gfx_bin)
Definition canvas.cc:1047
void DrawBackground(ImVec2 canvas_size=ImVec2(0, 0))
Definition canvas.cc:372
const ImVector< ImVec2 > & points() const
Definition canvas.h:290
void DrawGrid(float grid_step=64.0f, int tile_id_offset=8)
Definition canvas.cc:1304
void DrawText(const std::string &text, int x, int y)
Definition canvas.cc:1252
Draggable, dockable card for editor sub-windows.
bool Begin(bool *p_open=nullptr)
void SetDefaultSize(float width, float height)
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...
absl::Status Create(Rom *rom)
Initialize and load inventory screen data from ROM.
Definition inventory.cc:14
absl::Status SaveCustomMap(const std::string &file_path, bool use_dark_world)
Save map data to external binary file.
absl::Status LoadCustomMap(const std::string &file_path)
Load custom map from external binary file.
absl::Status Save(Rom *rom)
Save changes back to ROM.
absl::Status RenderMapLayer(bool use_dark_world)
Render map tiles into bitmap.
absl::Status Create(Rom *rom)
Initialize and load overworld map data from ROM.
absl::Status RenderCompositeLayer(bool show_bg1, bool show_bg2)
Render composite layer with BG1 on top of BG2 with transparency.
absl::Status Save(Rom *rom)
absl::Status RenderBG2Layer()
Render BG2 tilemap into bitmap pixels Converts tile IDs from tiles_bg2_buffer_ into pixel data.
absl::Status Create(Rom *rom)
Initialize and load title screen data from ROM.
absl::Status RenderBG1Layer()
Render BG1 tilemap into bitmap pixels Converts tile IDs from tiles_bg1_buffer_ into pixel data.
#define ICON_MD_TITLE
Definition icons.h:1988
#define ICON_MD_MORE_VERT
Definition icons.h:1241
#define ICON_MD_EDIT_ATTRIBUTES
Definition icons.h:644
#define ICON_MD_DRAW
Definition icons.h:623
#define ICON_MD_ZOOM_OUT
Definition icons.h:2194
#define ICON_MD_MAP
Definition icons.h:1171
#define ICON_MD_REDO
Definition icons.h:1568
#define ICON_MD_EDIT
Definition icons.h:643
#define ICON_MD_PUBLIC
Definition icons.h:1522
#define ICON_MD_INVENTORY
Definition icons.h:1009
#define ICON_MD_BUILD
Definition icons.h:326
#define ICON_MD_ZOOM_IN
Definition icons.h:2192
#define ICON_MD_SAVE
Definition icons.h:1642
#define ICON_MD_UNDO
Definition icons.h:2037
#define PRINT_IF_ERROR(expression)
Definition macro.h:27
#define RETURN_IF_ERROR(expression)
Definition macro.h:53
#define ASSIGN_OR_RETURN(type_variable_name, expression)
Definition macro.h:61
constexpr uint32_t kRedPen
void RenderTile16(IRenderer *renderer, Tilemap &tilemap, int tile_id)
Definition tilemap.cc:63
void ModifyTile16(Tilemap &tilemap, const std::vector< uint8_t > &data, const TileInfo &top_left, const TileInfo &top_right, const TileInfo &bottom_left, const TileInfo &bottom_right, int sheet_offset, int tile_id)
Definition tilemap.cc:188
void UpdateTile16(IRenderer *renderer, Tilemap &tilemap, int tile_id)
Definition tilemap.cc:90
std::vector< uint8_t > SnesTo8bppSheet(std::span< uint8_t > sheet, int bpp, int num_sheets)
Definition snes_tile.cc:129
bool InputHexWord(const char *label, uint16_t *data, float input_width, bool no_step)
Definition input.cc:175
IMGUI_API absl::Status InlinePaletteEditor(gfx::SnesPalette &palette, const std::string &title, ImGuiColorEditFlags flags)
Full inline palette editor with color picker and copy options.
Definition color.cc:117
bool InputTileInfo(const char *label, gfx::TileInfo *tile_info)
Definition input.cc:325
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 HexByte(uint8_t byte, HexStringParams params)
Definition hex.cc:30
absl::Status LoadDungeonMapTile16(gfx::Tilemap &tile16_blockset, Rom &rom, const std::vector< uint8_t > &gfx_data, bool bin_mode)
Load the dungeon map tile16 from the ROM.
constexpr int kNumRooms
Definition dungeon_map.h:35
absl::Status SaveDungeonMapTile16(gfx::Tilemap &tile16_blockset, Rom &rom)
Save the dungeon map tile16 to the ROM.
absl::StatusOr< std::vector< DungeonMap > > LoadDungeonMaps(Rom &rom, DungeonMapLabels &dungeon_map_labels)
Load the dungeon maps from the ROM.
Main namespace for the application.
Definition controller.cc:20
EditorCardRegistry * card_registry
Definition editor.h:80
void CacheTile(int tile_id, Bitmap &&bitmap)
Cache a tile bitmap.
Definition tilemap.h:58
Bitmap * GetTile(int tile_id)
Get a cached tile by ID.
Definition tilemap.h:42
Pair tile_size
Size of individual tiles (8x8 or 16x16)
Definition tilemap.h:113
TileCache tile_cache
Smart tile cache with LRU eviction.
Definition tilemap.h:111
Pair map_size
Size of tilemap in tiles.
Definition tilemap.h:114
Bitmap atlas
Master bitmap containing all tiles.
Definition tilemap.h:110
std::vector< std::array< gfx::TileInfo, 4 > > tile_info
Tile metadata (4 tiles per 16x16)
Definition tilemap.h:112
void SelectableLabelWithNameEdit(bool selected, const std::string &type, const std::string &key, const std::string &defaultValue)
Definition project.cc:855