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"
2#include "util/i18n/tr.h"
3
4#include <fstream>
5#include <iostream>
6#include <memory>
7#include <string>
8
9#include "absl/strings/str_format.h"
13#include "app/gfx/core/bitmap.h"
18#include "app/gui/core/color.h"
19#include "app/gui/core/icons.h"
20#include "app/gui/core/input.h"
22#include "imgui/imgui.h"
23#include "util/file_util.h"
24#include "util/hex.h"
25#include "util/macro.h"
26
27namespace yaze {
28namespace editor {
29
32 return;
33 auto* window_manager = dependencies_.window_manager;
34
35 window_manager->RegisterPanel(
36 {.card_id = "screen.dungeon_maps",
37 .display_name = "Dungeon Maps",
38 .window_title = " Dungeon Map Editor",
39 .icon = ICON_MD_MAP,
40 .category = "Screen",
41 .shortcut_hint = "Alt+1",
42 .priority = 10,
43 .enabled_condition = [this]() { return rom()->is_loaded(); },
44 .disabled_tooltip = "Load a ROM first"});
45 window_manager->RegisterPanel(
46 {.card_id = "screen.inventory_menu",
47 .display_name = "Inventory Menu",
48 .window_title = " Inventory Menu",
49 .icon = ICON_MD_INVENTORY,
50 .category = "Screen",
51 .shortcut_hint = "Alt+2",
52 .priority = 20,
53 .enabled_condition = [this]() { return rom()->is_loaded(); },
54 .disabled_tooltip = "Load a ROM first"});
55 window_manager->RegisterPanel(
56 {.card_id = "screen.overworld_map",
57 .display_name = "Overworld Map",
58 .window_title = " Overworld Map",
59 .icon = ICON_MD_PUBLIC,
60 .category = "Screen",
61 .shortcut_hint = "Alt+3",
62 .priority = 30,
63 .enabled_condition = [this]() { return rom()->is_loaded(); },
64 .disabled_tooltip = "Load a ROM first"});
65 window_manager->RegisterPanel(
66 {.card_id = "screen.title_screen",
67 .display_name = "Title Screen",
68 .window_title = " Title Screen",
69 .icon = ICON_MD_TITLE,
70 .category = "Screen",
71 .shortcut_hint = "Alt+4",
72 .priority = 40,
73 .enabled_condition = [this]() { return rom()->is_loaded(); },
74 .disabled_tooltip = "Load a ROM first"});
75 window_manager->RegisterPanel(
76 {.card_id = "screen.naming_screen",
77 .display_name = "Naming Screen",
78 .window_title = " Naming Screen",
79 .icon = ICON_MD_EDIT,
80 .category = "Screen",
81 .shortcut_hint = "Alt+5",
82 .priority = 50,
83 .enabled_condition = [this]() { return rom()->is_loaded(); },
84 .disabled_tooltip = "Load a ROM first"});
85
86 // Register WindowContent implementations
87 window_manager->RegisterWindowContent(std::make_unique<DungeonMapsPanel>(
88 [this]() { DrawDungeonMapsEditor(); }));
89 window_manager->RegisterWindowContent(std::make_unique<InventoryMenuPanel>(
90 [this]() { DrawInventoryMenuEditor(); }));
91 window_manager->RegisterWindowContent(
92 std::make_unique<OverworldMapScreenPanel>(
93 [this]() { DrawOverworldMapEditor(); }));
94 window_manager->RegisterWindowContent(std::make_unique<TitleScreenPanel>(
95 [this]() { DrawTitleScreenEditor(); }));
96 window_manager->RegisterWindowContent(std::make_unique<NamingScreenPanel>(
97 [this]() { DrawNamingScreenEditor(); }));
98
99 // Show title screen by default
100 window_manager->OpenWindow("screen.title_screen");
101}
102
103absl::Status ScreenEditor::Load() {
104 gfx::ScopedTimer timer("ScreenEditor::Load");
105 inventory_loaded_ = false;
106
111 game_data()->graphics_buffer, false));
112
113 // Load graphics sheets and apply dungeon palette
114 sheets_[0] =
115 std::make_unique<gfx::Bitmap>(gfx::Arena::Get().gfx_sheets()[212]);
116 sheets_[1] =
117 std::make_unique<gfx::Bitmap>(gfx::Arena::Get().gfx_sheets()[213]);
118 sheets_[2] =
119 std::make_unique<gfx::Bitmap>(gfx::Arena::Get().gfx_sheets()[214]);
120 sheets_[3] =
121 std::make_unique<gfx::Bitmap>(gfx::Arena::Get().gfx_sheets()[215]);
122
123 // Apply dungeon palette to all sheets
124 for (int i = 0; i < 4; i++) {
125 sheets_[i]->SetPalette(
126 *game_data()->palette_groups.dungeon_main.mutable_palette(3));
129 }
130
131 // Create a single tilemap for tile8 graphics with on-demand texture creation
132 // Combine all 4 sheets (128x32 each) into one bitmap (128x128)
133 // This gives us 16 tiles per row × 16 rows = 256 tiles total
134 const int tile8_width = 128;
135 const int tile8_height = 128; // 4 sheets × 32 pixels each
136 std::vector<uint8_t> tile8_data(tile8_width * tile8_height);
137
138 // Copy data from all 4 sheets into the combined bitmap
139 for (int sheet_idx = 0; sheet_idx < 4; sheet_idx++) {
140 const auto& sheet = *sheets_[sheet_idx];
141 int dest_y_offset = sheet_idx * 32; // Each sheet is 32 pixels tall
142
143 for (int y = 0; y < 32; y++) {
144 for (int x = 0; x < 128; x++) {
145 int src_index = y * 128 + x;
146 int dest_index = (dest_y_offset + y) * 128 + x;
147
148 if (src_index < sheet.size() && dest_index < tile8_data.size()) {
149 tile8_data[dest_index] = sheet.data()[src_index];
150 }
151 }
152 }
153 }
154
155 // Create tilemap with 8x8 tile size
156 tile8_tilemap_.tile_size = {8, 8};
157 tile8_tilemap_.map_size = {256, 256}; // Logical size for tile count
158 tile8_tilemap_.atlas.Create(tile8_width, tile8_height, 8, tile8_data);
161
162 // Queue single texture creation for the atlas (not individual tiles)
165 return absl::OkStatus();
166}
167
168absl::Status ScreenEditor::Save() {
169 if (core::FeatureFlags::get().kSaveDungeonMaps) {
171 }
172 // Title screen and overworld maps are currently saved via their respective
173 // 'Save' buttons in the UI, but we could also trigger them here for a full
174 // save.
175 return absl::OkStatus();
176}
177
178absl::Status ScreenEditor::Update() {
179 // Panel drawing is handled centrally by WorkspaceWindowManager::DrawAllVisiblePanels()
180 // via the WindowContent implementations registered in Initialize().
181 // No local drawing needed here - this fixes duplicate panel rendering.
182 return status_;
183}
184
186 // Sidebar is now drawn by EditorManager for card-based editors
187 // This method kept for compatibility but sidebar handles card toggles
188}
189
191 if (!inventory_loaded_ && rom()->is_loaded() && game_data()) {
193 if (status_.ok()) {
195 inventory_loaded_ = true;
196 } else {
197 const auto& theme = AgentUI::GetTheme();
198 ImGui::TextColored(theme.text_error_red,
199 tr("Error loading inventory: %s"),
200 status_.message().data());
201 return;
202 }
203 }
204
206
207 if (ImGui::BeginTable("InventoryScreen", 4, ImGuiTableFlags_Resizable)) {
208 ImGui::TableSetupColumn("Canvas");
209 ImGui::TableSetupColumn("Tilesheet");
210 ImGui::TableSetupColumn("Item Icons");
211 ImGui::TableSetupColumn("Palette");
212 ImGui::TableHeadersRow();
213
214 ImGui::TableNextColumn();
215 {
216 gui::CanvasFrameOptions frame_opts;
217 frame_opts.draw_grid = true;
218 frame_opts.grid_step = 32.0f;
219 frame_opts.render_popups = true;
220 auto runtime = gui::BeginCanvas(screen_canvas_, frame_opts);
221 gui::DrawBitmap(runtime, inventory_.bitmap(), 2,
222 inventory_loaded_ ? 1.0f : 0.0f);
223 gui::EndCanvas(screen_canvas_, runtime, frame_opts);
224 }
225
226 ImGui::TableNextColumn();
227 {
228 gui::CanvasFrameOptions frame_opts;
229 frame_opts.canvas_size = ImVec2(128 * 2 + 2, (192 * 2) + 4);
230 frame_opts.draw_grid = true;
231 frame_opts.grid_step = 16.0f;
232 frame_opts.render_popups = true;
233 auto runtime = gui::BeginCanvas(tilesheet_canvas_, frame_opts);
235 inventory_loaded_ ? 1.0f : 0.0f);
236 gui::EndCanvas(tilesheet_canvas_, runtime, frame_opts);
237 }
238
239 ImGui::TableNextColumn();
241
242 ImGui::TableNextColumn();
244
245 ImGui::EndTable();
246 }
247 ImGui::Separator();
248
249 // TODO(scawful): Future Oracle of Secrets menu editor integration
250 // - Full inventory screen layout editor
251 // - Item slot assignment and positioning
252 // - Heart container and magic meter editor
253 // - Equipment display customization
254 // - A/B button equipment quick-select editor
255}
256
258 if (ImGui::BeginTable("InventoryToolset", 8, ImGuiTableFlags_SizingFixedFit,
259 ImVec2(0, 0))) {
260 ImGui::TableSetupColumn("#drawTool");
261 ImGui::TableSetupColumn("#sep1");
262 ImGui::TableSetupColumn("#zoomOut");
263 ImGui::TableSetupColumn("#zoomIN");
264 ImGui::TableSetupColumn("#sep2");
265 ImGui::TableSetupColumn("#bg2Tool");
266 ImGui::TableSetupColumn("#bg3Tool");
267 ImGui::TableSetupColumn("#itemTool");
268
269 ImGui::TableNextColumn();
270 ImGui::BeginDisabled(!undo_manager_.CanUndo());
272 status_ = Undo();
273 }
274 ImGui::EndDisabled();
275 ImGui::TableNextColumn();
276 ImGui::BeginDisabled(!undo_manager_.CanRedo());
278 status_ = Redo();
279 }
280 ImGui::EndDisabled();
281 ImGui::TableNextColumn();
282 ImGui::Text(ICON_MD_MORE_VERT);
283 ImGui::TableNextColumn();
284 if (gui::ToolbarIconButton(ICON_MD_ZOOM_OUT, "Zoom Out")) {
286 }
287 ImGui::TableNextColumn();
288 if (gui::ToolbarIconButton(ICON_MD_ZOOM_IN, "Zoom In")) {
290 }
291 ImGui::TableNextColumn();
292 ImGui::Text(ICON_MD_MORE_VERT);
293 ImGui::TableNextColumn();
294 if (gui::ToolbarIconButton(ICON_MD_DRAW, "Draw Mode")) {
296 }
297 ImGui::TableNextColumn();
298 if (gui::ToolbarIconButton(ICON_MD_BUILD, "Build Mode")) {
299 // current_mode_ = EditingMode::BUILD;
300 }
301
302 ImGui::EndTable();
303 }
304}
305
307 if (ImGui::BeginChild("##ItemIconsList", ImVec2(0, 0), true,
308 ImGuiWindowFlags_HorizontalScrollbar)) {
309 ImGui::Text(tr("Item Icons (2x2 tiles each)"));
310 ImGui::Separator();
311
312 auto& icons = inventory_.item_icons();
313 if (icons.empty()) {
314 ImGui::TextWrapped(
315 tr("No item icons loaded. Icons will be loaded when the "
316 "inventory is initialized."));
317 ImGui::EndChild();
318 return;
319 }
320
321 // Display icons in a table format
322 if (ImGui::BeginTable("##IconsTable", 2,
323 ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg)) {
324 ImGui::TableSetupColumn("Icon Name");
325 ImGui::TableSetupColumn("Tile Data");
326 ImGui::TableHeadersRow();
327
328 for (size_t i = 0; i < icons.size(); i++) {
329 const auto& icon = icons[i];
330
331 ImGui::TableNextRow();
332 ImGui::TableNextColumn();
333
334 // Display icon name with selectable row
335 if (ImGui::Selectable(icon.name.c_str(), false,
336 ImGuiSelectableFlags_SpanAllColumns)) {
337 // TODO: Select this icon for editing
338 }
339
340 ImGui::TableNextColumn();
341 // Display tile word data in hex format
342 ImGui::Text(tr("TL:%04X TR:%04X"), icon.tile_tl, icon.tile_tr);
343 ImGui::SameLine();
344 ImGui::Text(tr("BL:%04X BR:%04X"), icon.tile_bl, icon.tile_br);
345 }
346
347 ImGui::EndTable();
348 }
349
350 ImGui::Separator();
351 ImGui::TextWrapped(tr(
352 "NOTE: Individual icon editing will be implemented in the future "
353 "Oracle of Secrets menu editor. Each icon is composed of 4 tile words "
354 "representing a 2x2 arrangement of 8x8 tiles in SNES tile format "
355 "(vhopppcc cccccccc)."));
356 }
357 ImGui::EndChild();
358}
359
361 gfx::ScopedTimer timer("screen_editor_draw_dungeon_map_screen");
362
363 const auto& theme = AgentUI::GetTheme();
364 auto& current_dungeon = dungeon_maps_[selected_dungeon];
365
366 floor_number = i;
367 // The dungeon-map-screen canvas is a tile picker: read-only display of
368 // dungeon room placements with click-to-select for tile16 assignment.
370 screen_canvas_.DrawBackground(ImVec2(325, 325));
372
373 auto boss_room = current_dungeon.boss_room;
374
375 // Pre-allocate vectors for batch operations
376 std::vector<int> tile_ids_to_render;
377 std::vector<ImVec2> tile_positions;
378 tile_ids_to_render.reserve(zelda3::kNumRooms);
379 tile_positions.reserve(zelda3::kNumRooms);
380
381 for (int j = 0; j < zelda3::kNumRooms; j++) {
382 if (current_dungeon.floor_rooms[floor_number][j] != 0x0F) {
383 int tile16_id = current_dungeon.floor_gfx[floor_number][j];
384 int posX = ((j % 5) * 32);
385 int posY = ((j / 5) * 32);
386
387 // Batch tile rendering
388 tile_ids_to_render.push_back(tile16_id);
389 tile_positions.emplace_back(posX * 2, posY * 2);
390 }
391 }
392
393 // Batch render all tiles
394 for (size_t idx = 0; idx < tile_ids_to_render.size(); ++idx) {
395 int tile16_id = tile_ids_to_render[idx];
396 ImVec2 pos = tile_positions[idx];
397
398 // Extract tile data from the atlas directly
399 const int tiles_per_row = tile16_blockset_.atlas.width() / 16;
400 const int tile_x = (tile16_id % tiles_per_row) * 16;
401 const int tile_y = (tile16_id / tiles_per_row) * 16;
402
403 std::vector<uint8_t> tile_data(16 * 16);
404 int tile_data_offset = 0;
405 tile16_blockset_.atlas.Get16x16Tile(tile_x, tile_y, tile_data,
406 tile_data_offset);
407
408 // Create or update cached tile
409 auto* cached_tile = tile16_blockset_.tile_cache.GetTile(tile16_id);
410 if (!cached_tile) {
411 // Create new cached tile
412 gfx::Bitmap new_tile(16, 16, 8, tile_data);
414 tile16_blockset_.tile_cache.CacheTile(tile16_id, std::move(new_tile));
415 cached_tile = tile16_blockset_.tile_cache.GetTile(tile16_id);
416 } else {
417 // Update existing cached tile data
418 cached_tile->set_data(tile_data);
419 }
420
421 if (cached_tile && cached_tile->is_active()) {
422 // Ensure the cached tile has a valid texture
423 if (!cached_tile->texture()) {
424 // Queue texture creation via Arena's deferred system
427 }
428 screen_canvas_.DrawBitmap(*cached_tile, pos.x, pos.y, 4.0F, 255);
429 }
430 }
431
432 // Draw overlays and labels
433 for (int j = 0; j < zelda3::kNumRooms; j++) {
434 if (current_dungeon.floor_rooms[floor_number][j] != 0x0F) {
435 int posX = ((j % 5) * 32);
436 int posY = ((j / 5) * 32);
437
438 if (current_dungeon.floor_rooms[floor_number][j] == boss_room) {
439 screen_canvas_.DrawOutlineWithColor((posX * 2), (posY * 2), 64, 64,
440 theme.status_error);
441 }
442
443 std::string label =
445 screen_canvas_.DrawText(label, (posX * 2), (posY * 2));
446 std::string gfx_id =
447 util::HexByte(current_dungeon.floor_gfx[floor_number][j]);
448 screen_canvas_.DrawText(gfx_id, (posX * 2), (posY * 2) + 16);
449 }
450 }
451
452 screen_canvas_.DrawGrid(64.f, 5);
454
455 if (!screen_canvas_.points().empty()) {
456 int x = screen_canvas_.points().front().x / 64;
457 int y = screen_canvas_.points().front().y / 64;
458 selected_room = x + (y * 5);
459 }
460}
461
463 auto& current_dungeon = dungeon_maps_[selected_dungeon];
464 if (gui::BeginThemedTabBar("##DungeonMapTabs")) {
465 auto nbr_floors =
466 current_dungeon.nbr_of_floor + current_dungeon.nbr_of_basement;
467 for (int i = 0; i < nbr_floors; i++) {
468 int basement_num = current_dungeon.nbr_of_basement - i;
469 std::string tab_name = absl::StrFormat("Basement %d", basement_num);
470 if (i >= current_dungeon.nbr_of_basement) {
471 tab_name = absl::StrFormat("Floor %d",
472 i - current_dungeon.nbr_of_basement + 1);
473 }
474 if (ImGui::BeginTabItem(tab_name.data())) {
476 ImGui::EndTabItem();
477 }
478 }
480 }
481
482 {
483 auto room_before = CaptureDungeonMapSnapshot();
485 "Selected Room",
486 &current_dungeon.floor_rooms[floor_number].at(selected_room))) {
487 auto after = CaptureDungeonMapSnapshot();
488 undo_manager_.Push(std::make_unique<ScreenEditAction>(
489 room_before, after,
490 [this](const ScreenSnapshot& s) { RestoreFromSnapshot(s); },
491 "Edit room assignment"));
492 }
493 }
494
495 {
496 auto boss_before = CaptureDungeonMapSnapshot();
497 if (gui::InputHexWord("Boss Room", &current_dungeon.boss_room)) {
498 auto after = CaptureDungeonMapSnapshot();
499 undo_manager_.Push(std::make_unique<ScreenEditAction>(
500 boss_before, after,
501 [this](const ScreenSnapshot& s) { RestoreFromSnapshot(s); },
502 "Edit boss room"));
503 }
504 }
505
506 const auto button_size = ImVec2(130, 0);
507
508 if (ImGui::Button(tr("Add Floor"), button_size) &&
509 current_dungeon.nbr_of_floor < 8) {
510 SaveDungeonMapUndoState("Add floor");
511 current_dungeon.nbr_of_floor++;
514 }
515 ImGui::SameLine();
516 if (ImGui::Button(tr("Remove Floor"), button_size) &&
517 current_dungeon.nbr_of_floor > 0) {
518 SaveDungeonMapUndoState("Remove floor");
519 current_dungeon.nbr_of_floor--;
522 }
523
524 if (ImGui::Button(tr("Add Basement"), button_size) &&
525 current_dungeon.nbr_of_basement < 8) {
526 SaveDungeonMapUndoState("Add basement");
527 current_dungeon.nbr_of_basement++;
530 }
531 ImGui::SameLine();
532 if (ImGui::Button(tr("Remove Basement"), button_size) &&
533 current_dungeon.nbr_of_basement > 0) {
534 SaveDungeonMapUndoState("Remove basement");
535 current_dungeon.nbr_of_basement--;
538 }
539
540 if (ImGui::Button(tr("Copy Floor"), button_size)) {
541 copy_button_pressed = true;
542 }
543 ImGui::SameLine();
544 if (ImGui::Button(tr("Paste Floor"), button_size)) {
546 }
547}
548
566 gfx::ScopedTimer timer("screen_editor_draw_dungeon_maps_room_gfx");
567
568 if (ImGui::BeginChild("##DungeonMapTiles", ImVec2(0, 0), true)) {
569 // Enhanced tilesheet canvas with BeginCanvas/EndCanvas pattern
570 {
571 gui::CanvasFrameOptions tilesheet_opts;
572 tilesheet_opts.canvas_size = ImVec2((256 * 2) + 2, (192 * 2) + 4);
573 tilesheet_opts.draw_grid = true;
574 tilesheet_opts.grid_step = 32.0f;
575 tilesheet_opts.render_popups = true;
576
578 auto tilesheet_rt = gui::BeginCanvas(tilesheet_canvas_, tilesheet_opts);
579
580 // Interactive tile16 selector with grid snapping
581 ImVec2 selected_pos;
582 if (gui::DrawTileSelector(tilesheet_rt, 32, 0, &selected_pos)) {
583 // Double-click detected - handle tile confirmation if needed
584 }
585
586 // Check for single-click selection (legacy compatibility)
588 ImGui::IsMouseClicked(ImGuiMouseButton_Left)) {
589 if (!tilesheet_canvas_.points().empty()) {
590 selected_tile16_ = static_cast<int>(
591 tilesheet_canvas_.points().front().x / 32 +
592 (tilesheet_canvas_.points().front().y / 32) * 16);
593
594 // Render selected tile16 and cache tile metadata
597 current_tile16_info.begin());
598 }
599 }
600
601 // Use stateless bitmap rendering for tilesheet
602 gui::DrawBitmap(tilesheet_rt, tile16_blockset_.atlas, 1, 1, 2.0F, 255);
603
604 gui::EndCanvas(tilesheet_canvas_, tilesheet_rt, tilesheet_opts);
605 }
606
607 if (!tilesheet_canvas_.points().empty() &&
608 !screen_canvas_.points().empty()) {
609 SaveDungeonMapUndoState("Place tile on dungeon map");
614 }
615
616 ImGui::Separator();
617
618 // Current tile canvas with BeginCanvas/EndCanvas pattern
619 {
620 gui::CanvasFrameOptions current_tile_opts;
621 current_tile_opts.draw_grid = true;
622 current_tile_opts.grid_step = 16.0f;
623 current_tile_opts.render_popups = true;
624
626 auto current_tile_rt =
627 gui::BeginCanvas(current_tile_canvas_, current_tile_opts);
628
629 // Get tile8 from cache on-demand (only create texture when needed)
630 if (selected_tile8_ >= 0 && selected_tile8_ < 256) {
631 auto* cached_tile8 = tile8_tilemap_.tile_cache.GetTile(selected_tile8_);
632
633 if (!cached_tile8) {
634 // Extract tile from atlas and cache it
635 const int tiles_per_row =
636 tile8_tilemap_.atlas.width() / 8; // 128 / 8 = 16
637 const int tile_x = (selected_tile8_ % tiles_per_row) * 8;
638 const int tile_y = (selected_tile8_ / tiles_per_row) * 8;
639
640 // Extract 8x8 tile data from atlas
641 std::vector<uint8_t> tile_data(64);
642 for (int py = 0; py < 8; py++) {
643 for (int px = 0; px < 8; px++) {
644 int src_x = tile_x + px;
645 int src_y = tile_y + py;
646 int src_index = src_y * tile8_tilemap_.atlas.width() + src_x;
647 int dst_index = py * 8 + px;
648
649 if (src_index < tile8_tilemap_.atlas.size() && dst_index < 64) {
650 tile_data[dst_index] = tile8_tilemap_.atlas.data()[src_index];
651 }
652 }
653 }
654
655 gfx::Bitmap new_tile8(8, 8, 8, tile_data);
658 std::move(new_tile8));
660 }
661
662 if (cached_tile8 && cached_tile8->is_active()) {
663 // Create texture on-demand only when needed
664 if (!cached_tile8->texture()) {
667 }
668
669 // DrawTilePainter still uses member function (not yet migrated)
670 if (current_tile_canvas_.DrawTilePainter(*cached_tile8, 16)) {
671 // Modify the tile16 based on the selected tile and
672 // current_tile16_info
674 absl::StrFormat("Paint tile16 #%d", selected_tile16_));
675 gfx::ModifyTile16(tile16_blockset_, game_data()->graphics_buffer,
678 212, selected_tile16_);
681 }
682 }
683 }
684
685 // Get selected tile from cache and draw with stateless helper
686 auto* selected_tile =
688 if (selected_tile && selected_tile->is_active()) {
689 // Ensure the selected tile has a valid texture
690 if (!selected_tile->texture()) {
693 }
694 gui::DrawBitmap(current_tile_rt, *selected_tile, 2, 2, 4.0f, 255);
695 }
696
697 gui::EndCanvas(current_tile_canvas_, current_tile_rt, current_tile_opts);
698 }
699
701 ImGui::SameLine();
704 ImGui::SameLine();
706
707 if (ImGui::Button(tr("Modify Tile16"))) {
709 absl::StrFormat("Modify tile16 #%d", selected_tile16_));
710 gfx::ModifyTile16(tile16_blockset_, game_data()->graphics_buffer,
716 }
717 }
718 ImGui::EndChild();
719}
720
738 // Enhanced editing mode controls with visual feedback
739 if (gui::ToolbarIconButton(ICON_MD_DRAW, "Draw Mode")) {
741 }
742 ImGui::SameLine();
743 if (gui::ToolbarIconButton(ICON_MD_EDIT, "Edit Mode")) {
745 }
746 ImGui::SameLine();
747 if (gui::ToolbarIconButton(ICON_MD_SAVE, "Save dungeon map tiles")) {
749 }
750
751 static std::vector<std::string> dungeon_names = {
752 "Sewers/Sanctuary", "Hyrule Castle", "Eastern Palace",
753 "Desert Palace", "Tower of Hera", "Agahnim's Tower",
754 "Palace of Darkness", "Swamp Palace", "Skull Woods",
755 "Thieves' Town", "Ice Palace", "Misery Mire",
756 "Turtle Rock", "Ganon's Tower"};
757
758 if (ImGui::BeginTable("DungeonMapsTable", 4,
759 ImGuiTableFlags_Resizable |
760 ImGuiTableFlags_Reorderable |
761 ImGuiTableFlags_Hideable)) {
762 ImGui::TableSetupColumn("Dungeon");
763 ImGui::TableSetupColumn("Map");
764 ImGui::TableSetupColumn("Rooms Gfx");
765 ImGui::TableSetupColumn("Tiles Gfx");
766 ImGui::TableHeadersRow();
767
768 ImGui::TableNextColumn();
769 for (int i = 0; i < dungeon_names.size(); i++) {
771 selected_dungeon == i, "Dungeon Names", absl::StrFormat("%d", i),
772 dungeon_names[i]);
773 if (ImGui::IsItemClicked()) {
775 }
776 }
777
778 ImGui::TableNextColumn();
780
781 ImGui::TableNextColumn();
783
784 ImGui::TableNextColumn();
789 // Get the tile8 ID to use for the tile16 drawing above
791 }
795
796 ImGui::Text(tr("Selected tile8: %d"), selected_tile8_);
797 ImGui::Separator();
798 ImGui::Text(tr("For use with custom inserted graphics assembly patches."));
799 if (ImGui::Button(tr("Load GFX from BIN file")))
801
802 ImGui::EndTable();
803 }
804}
805
807 std::string bin_file = util::FileDialogWrapper::ShowOpenFileDialog();
808 if (!bin_file.empty()) {
809 std::ifstream file(bin_file, std::ios::binary);
810 if (file.is_open()) {
811 // Read the gfx data into a buffer
812 std::vector<uint8_t> bin_data((std::istreambuf_iterator<char>(file)),
813 std::istreambuf_iterator<char>());
814 if (auto converted_bin = gfx::SnesTo8bppSheet(bin_data, 4, 4);
816 converted_bin, true)
817 .ok()) {
818 sheets_.clear();
819 std::vector<std::vector<uint8_t>> gfx_sheets;
820 for (int i = 0; i < 4; i++) {
821 gfx_sheets.emplace_back(converted_bin.begin() + (i * 0x1000),
822 converted_bin.begin() + ((i + 1) * 0x1000));
823 sheets_[i] = std::make_unique<gfx::Bitmap>(128, 32, 8, gfx_sheets[i]);
824 sheets_[i]->SetPalette(
825 *game_data()->palette_groups.dungeon_main.mutable_palette(3));
826 // Queue texture creation via Arena's deferred system
829 }
830 binary_gfx_loaded_ = true;
831 } else {
832 status_ = absl::InternalError("Failed to load dungeon map tile16");
833 }
834 file.close();
835 }
836 }
837}
838
840 // Initialize title screen on first draw
841 if (!title_screen_loaded_ && rom()->is_loaded() && game_data()) {
843 if (!status_.ok()) {
844 const auto& theme = AgentUI::GetTheme();
845 ImGui::TextColored(theme.text_error_red,
846 tr("Error loading title screen: %s"),
847 status_.message().data());
848 return;
849 }
851 }
852
854 ImGui::Text(tr("Title screen not loaded. Ensure ROM is loaded."));
855 return;
856 }
857
858 // Toolbar with mode controls
859 if (ImGui::Button(ICON_MD_DRAW)) {
861 }
862 ImGui::SameLine();
863 if (ImGui::Button(ICON_MD_SAVE)) {
865 if (status_.ok()) {
866 ImGui::OpenPopup("SaveSuccess");
867 }
868 }
869 ImGui::SameLine();
870 ImGui::Text(tr("Selected Tile: %d"), selected_title_tile16_);
871
872 // Save success popup
873 if (ImGui::BeginPopup("SaveSuccess")) {
874 ImGui::Text(tr("Title screen saved successfully!"));
875 ImGui::EndPopup();
876 }
877
878 // Layer visibility controls
879 bool prev_bg1 = show_title_bg1_;
880 bool prev_bg2 = show_title_bg2_;
881 ImGui::Checkbox(tr("Show BG1"), &show_title_bg1_);
882 ImGui::SameLine();
883 ImGui::Checkbox(tr("Show BG2"), &show_title_bg2_);
884
885 // Re-render composite if visibility changed
886 if (prev_bg1 != show_title_bg1_ || prev_bg2 != show_title_bg2_) {
887 status_ =
889 if (status_.ok()) {
893 }
894 }
895
896 // Layout: 2-column table (composite view + tile selector)
897 if (ImGui::BeginTable("TitleScreenTable", 2,
898 ImGuiTableFlags_Resizable | ImGuiTableFlags_Borders)) {
899 ImGui::TableSetupColumn("Title Screen (Composite)");
900 ImGui::TableSetupColumn("Tile Selector");
901 ImGui::TableHeadersRow();
902
903 // Column 1: Composite Canvas (BG1+BG2 stacked)
904 ImGui::TableNextColumn();
906
907 // Column 2: Blockset Selector
908 ImGui::TableNextColumn();
910
911 ImGui::EndTable();
912 }
913}
914
916 // The title-screen view stacks BG1 + BG2 into one composite bitmap; the
917 // canvas is read-only relative to that output (painting goes to the BG1
918 // tilemap, which then re-renders the composite). Marking both ends lets
919 // tools tell composite outputs apart from editable scratchpads without
920 // consulting caller convention.
924
925 // Draw composite tilemap (BG1+BG2 stacked with transparency).
926 // EnsureCompositeBitmapTextureQueued closes the A4 first-frame race: the
927 // composite has its CREATE queued during TitleScreen::Create(), but if the
928 // canvas draw runs before ProcessTextureQueue, texture() is still null and
929 // canvas_rendering's silent guard would drop the draw. The helper also
930 // pins metadata().purpose so the diagnostic log can name the bitmap.
931 auto& composite_bitmap = title_screen_.composite_bitmap();
933 if (composite_bitmap.is_active()) {
934 title_bg1_canvas_.DrawBitmap(composite_bitmap, 0, 0, 2.0f, 255);
935 }
936
937 // Handle tile painting - always paint to BG1 layer
940 if (!title_bg1_canvas_.points().empty()) {
941 auto click_pos = title_bg1_canvas_.points().front();
942 int tile_x = static_cast<int>(click_pos.x) / 8;
943 int tile_y = static_cast<int>(click_pos.y) / 8;
944
945 if (tile_x >= 0 && tile_x < 32 && tile_y >= 0 && tile_y < 32) {
946 int tilemap_index = tile_y * 32 + tile_x;
947
948 // Create tile word: tile_id | (palette << 10) | h_flip | v_flip
949 uint16_t tile_word = selected_title_tile16_ & 0x3FF;
950 tile_word |= (title_palette_ & 0x07) << 10;
951 if (title_h_flip_)
952 tile_word |= 0x4000;
953 if (title_v_flip_)
954 tile_word |= 0x8000;
955
956 // Update BG1 buffer and re-render both layers and composite
957 title_screen_.mutable_bg1_buffer()[tilemap_index] = tile_word;
959 if (status_.ok()) {
960 // Update BG1 texture
964
965 // Re-render and update composite
968 if (status_.ok()) {
970 gfx::Arena::TextureCommandType::UPDATE, &composite_bitmap);
971 }
972 }
973 }
974 }
975 }
976 }
977
980}
981
985
986 // Draw BG1 tilemap
987 auto& bg1_bitmap = title_screen_.bg1_bitmap();
988 if (bg1_bitmap.is_active()) {
989 title_bg1_canvas_.DrawBitmap(bg1_bitmap, 0, 0, 2.0f, 255);
990 }
991
992 // Handle tile painting
995 if (!title_bg1_canvas_.points().empty()) {
996 auto click_pos = title_bg1_canvas_.points().front();
997 int tile_x = static_cast<int>(click_pos.x) / 8;
998 int tile_y = static_cast<int>(click_pos.y) / 8;
999
1000 if (tile_x >= 0 && tile_x < 32 && tile_y >= 0 && tile_y < 32) {
1001 int tilemap_index = tile_y * 32 + tile_x;
1002
1003 // Create tile word: tile_id | (palette << 10) | h_flip | v_flip
1004 uint16_t tile_word = selected_title_tile16_ & 0x3FF;
1005 tile_word |= (title_palette_ & 0x07) << 10;
1006 if (title_h_flip_)
1007 tile_word |= 0x4000;
1008 if (title_v_flip_)
1009 tile_word |= 0x8000;
1010
1011 // Update buffer and re-render
1012 title_screen_.mutable_bg1_buffer()[tilemap_index] = tile_word;
1014 if (status_.ok()) {
1017 }
1018 }
1019 }
1020 }
1021 }
1022
1025}
1026
1031
1032 // Draw BG2 tilemap
1033 auto& bg2_bitmap = title_screen_.bg2_bitmap();
1034 if (bg2_bitmap.is_active()) {
1035 title_bg2_canvas_.DrawBitmap(bg2_bitmap, 0, 0, 2.0f, 255);
1036 }
1037
1038 // Handle tile painting
1041 if (!title_bg2_canvas_.points().empty()) {
1042 auto click_pos = title_bg2_canvas_.points().front();
1043 int tile_x = static_cast<int>(click_pos.x) / 8;
1044 int tile_y = static_cast<int>(click_pos.y) / 8;
1045
1046 if (tile_x >= 0 && tile_x < 32 && tile_y >= 0 && tile_y < 32) {
1047 int tilemap_index = tile_y * 32 + tile_x;
1048
1049 // Create tile word: tile_id | (palette << 10) | h_flip | v_flip
1050 uint16_t tile_word = selected_title_tile16_ & 0x3FF;
1051 tile_word |= (title_palette_ & 0x07) << 10;
1052 if (title_h_flip_)
1053 tile_word |= 0x4000;
1054 if (title_v_flip_)
1055 tile_word |= 0x8000;
1056
1057 // Update buffer and re-render
1058 title_screen_.mutable_bg2_buffer()[tilemap_index] = tile_word;
1060 if (status_.ok()) {
1063 }
1064 }
1065 }
1066 }
1067 }
1068
1071}
1072
1077
1078 // Draw tile8 bitmap (8x8 tiles used to compose tile16)
1079 auto& tiles8_bitmap = title_screen_.tiles8_bitmap();
1080 if (tiles8_bitmap.is_active()) {
1081 title_blockset_canvas_.DrawBitmap(tiles8_bitmap, 0, 0, 2.0f, 255);
1082 }
1083
1084 // Handle tile selection (8x8 tiles)
1086 // Calculate selected tile ID from click position
1087 if (!title_blockset_canvas_.points().empty()) {
1088 auto click_pos = title_blockset_canvas_.points().front();
1089 int tile_x = static_cast<int>(click_pos.x) / 8;
1090 int tile_y = static_cast<int>(click_pos.y) / 8;
1091 int tiles_per_row = 128 / 8; // 16 tiles per row for 8x8 tiles
1092 selected_title_tile16_ = tile_x + (tile_y * tiles_per_row);
1093 }
1094 }
1095
1098
1099 // Show selected tile preview and controls
1100 if (selected_title_tile16_ >= 0) {
1101 ImGui::Text(tr("Selected Tile: %d"), selected_title_tile16_);
1102
1103 // Flip controls
1104 ImGui::Checkbox(tr("H Flip"), &title_h_flip_);
1105 ImGui::SameLine();
1106 ImGui::Checkbox(tr("V Flip"), &title_v_flip_);
1107
1108 // Palette selector (0-7 for 3BPP graphics)
1109 ImGui::SetNextItemWidth(100);
1110 ImGui::SliderInt(tr("Palette"), &title_palette_, 0, 7);
1111 }
1112}
1113
1115
1117 // Initialize overworld map on first draw
1118 if (!ow_map_loaded_ && rom()->is_loaded()) {
1120 if (!status_.ok()) {
1121 const auto& theme = AgentUI::GetTheme();
1122 ImGui::TextColored(theme.text_error_red,
1123 tr("Error loading overworld map: %s"),
1124 status_.message().data());
1125 return;
1126 }
1127 ow_map_loaded_ = true;
1128 }
1129
1130 if (!ow_map_loaded_) {
1131 ImGui::Text(tr("Overworld map not loaded. Ensure ROM is loaded."));
1132 return;
1133 }
1134
1135 // Toolbar with mode controls. Keep this explicit: beta users confused this
1136 // panel with the main Overworld Editor, but it edits the pause-menu world map
1137 // art.
1138 if (ImGui::Button(tr("Paint Mode"))) {
1140 }
1141 if (ImGui::IsItemHovered()) {
1142 ImGui::SetTooltip(tr(
1143 "Paint the pause-menu world map: choose an 8x8 tile in Tileset, then "
1144 "click Map Canvas."));
1145 }
1146 ImGui::SameLine();
1147 if (ImGui::Button(tr("Save World Map"))) {
1149 if (status_.ok()) {
1150 ImGui::OpenPopup("OWSaveSuccess");
1151 }
1152 }
1153 ImGui::SameLine();
1154
1155 // World toggle
1156 if (ImGui::Button(ow_show_dark_world_ ? "Dark World" : "Light World")) {
1158 // Re-render map with new world
1160 if (status_.ok()) {
1163 }
1164 }
1165 ImGui::SameLine();
1166
1167 // Custom map load/save buttons
1168 if (ImGui::Button(tr("Load Custom Map..."))) {
1170 if (!path.empty()) {
1172 if (!status_.ok()) {
1173 ImGui::OpenPopup("CustomMapLoadError");
1174 }
1175 }
1176 }
1177 ImGui::SameLine();
1178 if (ImGui::Button(tr("Save Custom Map..."))) {
1180 if (!path.empty()) {
1182 if (status_.ok()) {
1183 ImGui::OpenPopup("CustomMapSaveSuccess");
1184 }
1185 }
1186 }
1187
1188 ImGui::SameLine();
1189 ImGui::Text(tr("Selected Tile: %d"), selected_ow_tile_);
1190
1191 ImGui::TextWrapped(tr(
1192 "This edits the pause-menu world map art, not the 160 playable "
1193 "overworld areas. For area-map painting, entrances, exits, items, and "
1194 "sprites, use Overworld Editor. Paint flow here: pick an 8x8 tile from "
1195 "Tileset, then click Map Canvas; use Light/Dark World to choose the "
1196 "target map."));
1197 ImGui::Separator();
1198
1199 // Custom map error/success popups
1200 if (ImGui::BeginPopup("CustomMapLoadError")) {
1201 ImGui::Text(tr("Error loading custom map: %s"), status_.message().data());
1202 ImGui::EndPopup();
1203 }
1204 if (ImGui::BeginPopup("CustomMapSaveSuccess")) {
1205 ImGui::Text(tr("Custom map saved successfully!"));
1206 ImGui::EndPopup();
1207 }
1208
1209 // Save success popup
1210 if (ImGui::BeginPopup("OWSaveSuccess")) {
1211 ImGui::Text(tr("Overworld map saved successfully!"));
1212 ImGui::EndPopup();
1213 }
1214
1215 // Layout: 3-column table
1216 if (ImGui::BeginTable("OWMapTable", 3,
1217 ImGuiTableFlags_Resizable | ImGuiTableFlags_Borders)) {
1218 ImGui::TableSetupColumn("Map Canvas");
1219 ImGui::TableSetupColumn("Tileset");
1220 ImGui::TableSetupColumn("Palette");
1221 ImGui::TableHeadersRow();
1222
1223 // Column 1: Map Canvas
1224 ImGui::TableNextColumn();
1228
1229 auto& map_bitmap = ow_map_screen_.map_bitmap();
1230 if (map_bitmap.is_active()) {
1231 ow_map_canvas_.DrawBitmap(map_bitmap, 0, 0, 1.0f, 255);
1232 }
1233
1234 // Handle tile painting
1236 if (ow_map_canvas_.DrawTileSelector(8.0f)) {
1237 if (!ow_map_canvas_.points().empty()) {
1238 auto click_pos = ow_map_canvas_.points().front();
1239 int tile_x = static_cast<int>(click_pos.x) / 8;
1240 int tile_y = static_cast<int>(click_pos.y) / 8;
1241
1242 if (tile_x >= 0 && tile_x < 64 && tile_y >= 0 && tile_y < 64) {
1243 int tile_index = tile_x + (tile_y * 64);
1244
1245 // Update appropriate world's tile data
1246 if (ow_show_dark_world_) {
1248 } else {
1250 }
1251
1252 // Re-render map
1254 if (status_.ok()) {
1257 }
1258 }
1259 }
1260 }
1261 }
1262
1265
1266 // Column 2: Tileset Selector
1267 ImGui::TableNextColumn();
1271
1272 auto& tiles8_bitmap = ow_map_screen_.tiles8_bitmap();
1273 if (tiles8_bitmap.is_active()) {
1274 ow_tileset_canvas_.DrawBitmap(tiles8_bitmap, 0, 0, 2.0f, 255);
1275 }
1276
1277 // Handle tile selection
1279 if (!ow_tileset_canvas_.points().empty()) {
1280 auto click_pos = ow_tileset_canvas_.points().front();
1281 int tile_x = static_cast<int>(click_pos.x) / 8;
1282 int tile_y = static_cast<int>(click_pos.y) / 8;
1283 selected_ow_tile_ = tile_x + (tile_y * 16); // 16 tiles per row
1284 }
1285 }
1286
1289
1290 // Column 3: Palette Display
1291 ImGui::TableNextColumn();
1294 // Use inline palette editor for full 128-color palette
1295 gui::InlinePaletteEditor(palette, "Overworld Map Palette");
1296
1297 ImGui::EndTable();
1298 }
1299}
1300
1302 static bool show_bg1 = true;
1303 static bool show_bg2 = true;
1304 static bool show_bg3 = true;
1305
1306 static bool drawing_bg1 = true;
1307 static bool drawing_bg2 = false;
1308 static bool drawing_bg3 = false;
1309
1310 ImGui::Checkbox(tr("Show BG1"), &show_bg1);
1311 ImGui::SameLine();
1312 ImGui::Checkbox(tr("Show BG2"), &show_bg2);
1313
1314 ImGui::Checkbox(tr("Draw BG1"), &drawing_bg1);
1315 ImGui::SameLine();
1316 ImGui::Checkbox(tr("Draw BG2"), &drawing_bg2);
1317 ImGui::SameLine();
1318 ImGui::Checkbox(tr("Draw BG3"), &drawing_bg3);
1319}
1320
1321// ---------------------------------------------------------------------------
1322// Undo/redo helpers
1323// ---------------------------------------------------------------------------
1324
1336
1344
1345void ScreenEditor::SaveDungeonMapUndoState(const std::string& description) {
1347 pending_dungeon_desc_ = description;
1349}
1350
1351void ScreenEditor::SaveTile16CompUndoState(const std::string& description) {
1353 pending_tile16_desc_ = description;
1355}
1356
1359 return;
1361
1362 auto after = CaptureDungeonMapSnapshot();
1363 undo_manager_.Push(std::make_unique<ScreenEditAction>(
1365 [this](const ScreenSnapshot& snap) { RestoreFromSnapshot(snap); },
1367}
1368
1371 return;
1373
1374 auto after = CaptureTile16CompSnapshot();
1375 undo_manager_.Push(std::make_unique<ScreenEditAction>(
1377 [this](const ScreenSnapshot& snap) { RestoreFromSnapshot(snap); },
1379}
1380
1382 switch (snapshot.edit_type) {
1384 int idx = snapshot.dungeon_map.dungeon_index;
1385 if (idx >= 0 && idx < static_cast<int>(dungeon_maps_.size())) {
1386 dungeon_maps_[idx] = snapshot.dungeon_map.map_data;
1387 dungeon_map_labels_[idx] = snapshot.dungeon_map.labels;
1388 selected_dungeon = idx;
1389 }
1390 break;
1391 }
1395 // Re-apply tile16 composition to the blockset
1396 if (game_data()) {
1397 gfx::ModifyTile16(tile16_blockset_, game_data()->graphics_buffer,
1402 }
1403 break;
1404 }
1405 }
1406}
1407
1408} // namespace editor
1409} // namespace yaze
project::ResourceLabelManager * resource_label()
Definition rom.h:162
bool is_loaded() const
Definition rom.h:144
static Flags & get()
Definition features.h:119
UndoManager undo_manager_
Definition editor.h:334
zelda3::GameData * game_data() const
Definition editor.h:320
EditorDependencies dependencies_
Definition editor.h:333
void DrawDungeonMapsRoomGfx()
Draw dungeon room graphics editor with enhanced tile16 editing.
absl::Status Undo() override
ScreenSnapshot CaptureTile16CompSnapshot() const
ScreenSnapshot pending_dungeon_before_
std::array< gfx::TileInfo, 4 > current_tile16_info
absl::Status Save() override
absl::Status Load() override
void SaveDungeonMapUndoState(const std::string &description)
absl::Status Update() override
void RestoreFromSnapshot(const ScreenSnapshot &snapshot)
zelda3::OverworldMapScreen ow_map_screen_
ScreenSnapshot pending_tile16_before_
ScreenSnapshot CaptureDungeonMapSnapshot() const
void DrawDungeonMapsEditor()
Draw dungeon maps editor with enhanced ROM hacking features.
absl::Status Redo() override
void SaveTile16CompUndoState(const std::string &description)
zelda3::TitleScreen title_screen_
zelda3::Inventory inventory_
zelda3::DungeonMapLabels dungeon_map_labels_
std::vector< zelda3::DungeonMap > dungeon_maps_
void Push(std::unique_ptr< UndoAction > action)
void RegisterPanel(size_t session_id, const WindowDescriptor &base_info)
void QueueTextureCommand(TextureCommandType type, Bitmap *bitmap)
Definition arena.cc:36
static Arena & Get()
Definition arena.cc:21
Represents a bitmap image optimized for SNES ROM hacking.
Definition bitmap.h:67
const uint8_t * data() const
Definition bitmap.h:398
const SnesPalette & palette() const
Definition bitmap.h:389
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:201
auto size() const
Definition bitmap.h:397
void set_data(const std::vector< uint8_t > &data)
Definition bitmap.cc:853
void SetPalette(const SnesPalette &palette)
Set the palette for the bitmap using SNES palette format.
Definition bitmap.cc:384
int width() const
Definition bitmap.h:394
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:692
RAII timer for automatic timing management.
void DrawBitmap(Bitmap &bitmap, int border_offset, float scale)
Definition canvas.cc:1162
void DrawOutlineWithColor(int x, int y, int w, int h, ImVec4 color)
Definition canvas.cc:1231
ImVector< ImVec2 > * mutable_points()
Definition canvas.h:345
void DrawContextMenu()
Definition canvas.cc:692
int GetTileIdFromMousePos()
Definition canvas.h:317
bool DrawTileSelector(int size, int size_y=0)
Definition canvas.cc:1098
bool DrawTilePainter(const Bitmap &bitmap, int size, float scale=1.0f)
Definition canvas.cc:939
CanvasConfig & GetConfig()
Definition canvas.h:229
bool IsMouseHovering() const
Definition canvas.h:338
void DrawBitmapTable(const BitmapTable &gfx_bin)
Definition canvas.cc:1209
void DrawBackground(ImVec2 canvas_size=ImVec2(0, 0))
Definition canvas.cc:602
const ImVector< ImVec2 > & points() const
Definition canvas.h:344
void DrawGrid(float grid_step=64.0f, int tile_id_offset=8)
Definition canvas.cc:1484
void DrawText(const std::string &text, int x, int y)
Definition canvas.cc:1432
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, GameData *game_data=nullptr)
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 Create(Rom *rom, GameData *game_data=nullptr)
Initialize and load title screen 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 RenderBG1Layer()
Render BG1 tilemap into bitmap pixels Converts tile IDs from tiles_bg1_buffer_ into pixel data.
#define ICON_MD_TITLE
Definition icons.h:1990
#define ICON_MD_MORE_VERT
Definition icons.h:1243
#define ICON_MD_DRAW
Definition icons.h:625
#define ICON_MD_ZOOM_OUT
Definition icons.h:2196
#define ICON_MD_MAP
Definition icons.h:1173
#define ICON_MD_REDO
Definition icons.h:1570
#define ICON_MD_EDIT
Definition icons.h:645
#define ICON_MD_PUBLIC
Definition icons.h:1524
#define ICON_MD_INVENTORY
Definition icons.h:1011
#define ICON_MD_BUILD
Definition icons.h:328
#define ICON_MD_ZOOM_IN
Definition icons.h:2194
#define ICON_MD_SAVE
Definition icons.h:1644
#define ICON_MD_UNDO
Definition icons.h:2039
#define PRINT_IF_ERROR(expression)
Definition macro.h:28
#define ASSIGN_OR_RETURN(type_variable_name, expression)
Definition macro.h:62
const AgentUITheme & GetTheme()
void EnsureCompositeBitmapTextureQueued(gfx::Bitmap &composite)
void RenderTile16(IRenderer *renderer, Tilemap &tilemap, int tile_id)
Definition tilemap.cc:75
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:221
void UpdateTile16(IRenderer *renderer, Tilemap &tilemap, int tile_id)
Definition tilemap.cc:113
std::vector< uint8_t > SnesTo8bppSheet(std::span< const uint8_t > sheet, int bpp, int num_sheets)
Definition snes_tile.cc:132
void EndCanvas(Canvas &canvas)
bool InputHexWord(const char *label, uint16_t *data, float input_width, bool no_step)
Definition input.cc:355
bool DrawTileSelector(const CanvasRuntime &rt, int size, int size_y, ImVec2 *out_selected_pos)
void BeginCanvas(Canvas &canvas, ImVec2 child_size)
void DrawBitmap(const CanvasRuntime &rt, gfx::Bitmap &bitmap, int border_offset=2, float scale=1.0f)
bool BeginThemedTabBar(const char *id, ImGuiTabBarFlags flags)
A stylized tab bar with "Mission Control" branding.
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:122
void EndThemedTabBar()
bool InputTileInfo(const char *label, gfx::TileInfo *tile_info)
Definition input.cc:555
IMGUI_API bool DisplayPalette(gfx::SnesPalette &palette, bool loaded)
Definition color.cc:239
bool ToolbarIconButton(const char *icon, const char *tooltip, bool is_active)
Convenience wrapper for toolbar-sized icon buttons.
bool InputHexByte(const char *label, uint8_t *data, float input_width, bool no_step)
Definition input.cc:376
std::string HexByte(uint8_t byte, HexStringParams params)
Definition hex.cc:30
absl::Status LoadDungeonMapTile16(gfx::Tilemap &tile16_blockset, Rom &rom, GameData *game_data, 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:48
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.
absl::Status SaveDungeonMaps(Rom &rom, std::vector< DungeonMap > &dungeon_maps)
Save the dungeon maps to the ROM.
#define RETURN_IF_ERROR(expr)
Definition snes.cc:22
std::vector< std::array< std::string, zelda3::kNumRooms > > labels
WorkspaceWindowManager * window_manager
Definition editor.h:181
Unified screen editor snapshot.
std::array< gfx::TileInfo, 4 > tile_info
void CacheTile(int tile_id, const Bitmap &bitmap)
Cache a tile bitmap by copying it.
Definition tilemap.h:67
Bitmap * GetTile(int tile_id)
Get a cached tile by ID.
Definition tilemap.h:50
Pair tile_size
Size of individual tiles (8x8 or 16x16)
Definition tilemap.h:123
TileCache tile_cache
Smart tile cache with LRU eviction.
Definition tilemap.h:120
Pair map_size
Size of tilemap in tiles.
Definition tilemap.h:124
Bitmap atlas
Master bitmap containing all tiles.
Definition tilemap.h:119
std::vector< std::array< gfx::TileInfo, 4 > > tile_info
Tile metadata (4 tiles per 16x16)
Definition tilemap.h:122
std::optional< float > grid_step
void SelectableLabelWithNameEdit(bool selected, const std::string &type, const std::string &key, const std::string &defaultValue)
Definition project.cc:2341
gfx::PaletteGroupMap palette_groups
Definition game_data.h:92