yaze 0.3.2
Link to the Past ROM Editor
 
Loading...
Searching...
No Matches
map_properties.cc
Go to the documentation of this file.
2#include "util/i18n/tr.h"
3
4#include <algorithm>
5#include <array>
6#include <cstring>
7#include <vector>
8
9#include "absl/status/statusor.h"
10#include "absl/strings/str_format.h"
16#include "app/gui/core/color.h"
17#include "app/gui/core/icons.h"
18#include "app/gui/core/input.h"
24#include "imgui/imgui.h"
25#include "util/macro.h"
28
29namespace yaze {
30namespace editor {
31
32using ImGui::BeginTable;
33// HOVER_HINT is defined in util/macro.h
34using ImGui::Separator;
35using ImGui::TableNextColumn;
36using ImGui::Text;
37
38// Using centralized UI constants
39namespace {
40
41bool IsValidMapId(int map_id) {
42 return map_id >= 0 && map_id < zelda3::kNumOverworldMaps;
43}
44
45int EffectivePropertyMapId(zelda3::Overworld* overworld, int map_id) {
46 if (!overworld || !IsValidMapId(map_id)) {
47 return map_id;
48 }
49 auto* map = overworld->mutable_overworld_map(map_id);
50 if (!map) {
51 return map_id;
52 }
53 const int parent = map->parent();
54 if (!IsValidMapId(parent)) {
55 return map_id;
56 }
57 return parent;
58}
59
60absl::Status CheckFieldSupported(const Rom* rom,
61 const OverworldPropertyEdit& edit) {
62 if (!rom || !rom->is_loaded()) {
63 return absl::FailedPreconditionError("ROM is not loaded");
64 }
65 const auto version = zelda3::OverworldVersionHelper::GetVersion(*rom);
66 switch (edit.field) {
68 if ((edit.value == static_cast<int>(zelda3::AreaSizeEnum::WideArea) ||
69 edit.value == static_cast<int>(zelda3::AreaSizeEnum::TallArea)) &&
71 return absl::FailedPreconditionError(
72 "Wide and Tall areas require ZSCustomOverworld v3+");
73 }
74 return absl::OkStatus();
79 return absl::FailedPreconditionError(
80 "This field requires ZSCustomOverworld v2+");
81 }
82 return absl::OkStatus();
85 return absl::FailedPreconditionError(
86 "Animated GFX requires ZSCustomOverworld v3+");
87 }
88 return absl::OkStatus();
91 return absl::FailedPreconditionError(
92 "Custom tile graphics require ZSCustomOverworld v1+");
93 }
94 return absl::OkStatus();
97 return absl::FailedPreconditionError(
98 "Visual effect editing requires ZSCustomOverworld v1+");
99 }
100 return absl::OkStatus();
101 default:
102 return absl::OkStatus();
103 }
104}
105
106int MusicRomAddressForMapState(int map_id, int state) {
107 if (map_id >= 0 && map_id < zelda3::kDarkWorldMapIdStart) {
108 switch (state) {
109 case 0:
110 return zelda3::kOverworldMusicBeginning + map_id;
111 case 1:
112 return zelda3::kOverworldMusicZelda + map_id;
113 case 2:
115 case 3:
116 return zelda3::kOverworldMusicAgahnim + map_id;
117 default:
118 return -1;
119 }
120 }
121 if (map_id >= zelda3::kDarkWorldMapIdStart &&
122 map_id < zelda3::kSpecialWorldMapIdStart && state == 0) {
125 }
126 return -1;
127}
128
129bool WriteRomByteIfValid(Rom* rom, int address, uint8_t value) {
130 if (!rom || address < 0 || static_cast<size_t>(address) >= rom->size()) {
131 return false;
132 }
133 (*rom)[address] = value;
134 return true;
135}
136
137void WriteRomWordIfValid(Rom* rom, int address, uint16_t value) {
138 if (!rom || address < 0 || static_cast<size_t>(address + 1) >= rom->size()) {
139 return;
140 }
141 (*rom)[address] = value & 0xFF;
142 (*rom)[address + 1] = (value >> 8) & 0xFF;
143}
144
146 const zelda3::Overworld& overworld, int map_id) {
148 if (!IsValidMapId(map_id)) {
149 return clipboard;
150 }
151
152 const auto* selected_map = overworld.overworld_map(map_id);
153 if (!selected_map) {
154 return clipboard;
155 }
156
157 int source_map_id = map_id;
158 if (IsValidMapId(selected_map->parent())) {
159 source_map_id = selected_map->parent();
160 }
161 const auto* map = overworld.overworld_map(source_map_id);
162 if (!map) {
163 return clipboard;
164 }
165
166 clipboard.valid = true;
167 clipboard.source_map_id = source_map_id;
168 clipboard.area_size = static_cast<int>(map->area_size());
169 clipboard.area_graphics = map->area_graphics();
170 clipboard.area_palette = map->area_palette();
171 clipboard.main_palette = map->main_palette();
172 clipboard.animated_graphics = map->animated_gfx();
173 clipboard.message_id = map->message_id();
174 clipboard.subscreen_overlay = map->subscreen_overlay();
175 clipboard.area_specific_bg_color = map->area_specific_bg_color();
176 clipboard.mosaic = *const_cast<zelda3::OverworldMap*>(map)->mutable_mosaic();
177 clipboard.mosaic_expanded = map->mosaic_expanded();
178 for (int i = 0; i < 3; ++i) {
179 clipboard.sprite_graphics[i] = map->sprite_graphics(i);
180 clipboard.sprite_palette[i] = map->sprite_palette(i);
181 }
182 for (int i = 0; i < 4; ++i) {
183 clipboard.music[i] = map->area_music(i);
184 }
185 for (int i = 0; i < 8; ++i) {
186 clipboard.custom_tilesets[i] = map->custom_tileset(i);
187 }
188 return clipboard;
189}
190
191} // namespace
192
196
198 return std::clamp(game_state_ ? *game_state_ : fallback, 0, 2);
199}
200
202 const int clamped_state = std::clamp(game_state, 0, 2);
203 local_game_state_ = clamped_state;
204 if (game_state_) {
205 *game_state_ = clamped_state;
206 }
207}
208
210 if (!overworld_ || map_index < 0 || map_index >= zelda3::kNumOverworldMaps) {
211 return;
212 }
213 auto* map = overworld_->mutable_overworld_map(map_index);
214 if (!map) {
215 return;
216 }
217 map->set_game_state(CurrentGameState(map->game_state()));
218}
219
221 int& current_world, int& current_map, bool& current_map_lock,
222 bool& show_map_properties_panel, bool& show_custom_bg_color_editor,
223 bool& show_overlay_editor, bool& show_overlay_preview, int& game_state,
224 EditingMode& current_mode, EntityEditMode& entity_edit_mode) {
225 (void)show_overlay_editor; // Reserved for future use
226 (void)show_custom_bg_color_editor; // Now handled by sidebar
227 (void)game_state; // Now handled by sidebar
228 (void)show_overlay_preview; // Reserved
229
230 // Simplified canvas toolbar - Navigation and Mode controls
231 if (BeginTable("MapPropertiesCanvasToolbar", 7,
232 ImGuiTableFlags_Borders | ImGuiTableFlags_SizingFixedFit,
233 ImVec2(0, 0), -1)) {
234 ImGui::TableSetupColumn("World", ImGuiTableColumnFlags_WidthFixed,
236 ImGui::TableSetupColumn("Map", ImGuiTableColumnFlags_WidthFixed,
238 ImGui::TableSetupColumn("Area Size", ImGuiTableColumnFlags_WidthFixed,
240 ImGui::TableSetupColumn("Lock", ImGuiTableColumnFlags_WidthFixed,
242 ImGui::TableSetupColumn("Mode", ImGuiTableColumnFlags_WidthFixed,
243 80.0f); // Mouse/Paint
244 ImGui::TableSetupColumn(
245 "Entity", ImGuiTableColumnFlags_WidthStretch); // Entity status
246 ImGui::TableSetupColumn("Sidebar", ImGuiTableColumnFlags_WidthFixed, 40.0f);
247
248 TableNextColumn();
249 const int clamped_world = std::clamp(current_world, 0, 2);
250 ImGui::TextDisabled("%s", kWorldNames[clamped_world]);
251
252 TableNextColumn();
253 ImGui::Text(tr("%d (0x%02X)"), current_map, current_map);
254
255 TableNextColumn();
256 // Use centralized version detection
258
259 // ALL ROMs support Small/Large. Only v3+ supports Wide/Tall.
260 int current_area_size =
261 static_cast<int>(overworld_->overworld_map(current_map)->area_size());
262 ImGui::SetNextItemWidth(kComboAreaSizeWidth);
263
265 // v3+ ROM: Show all 4 area size options
266 if (ImGui::Combo("##AreaSize", &current_area_size, kAreaSizeNames, 4)) {
268 current_area_size});
269 }
270 } else {
271 // Vanilla/v1/v2 ROM: Show only Small/Large (first 2 options)
272 const char* limited_names[] = {"Small (1x1)", "Large (2x2)"};
273 int limited_size = (current_area_size == 0 || current_area_size == 1)
274 ? current_area_size
275 : 0;
276
277 if (ImGui::Combo("##AreaSize", &limited_size, limited_names, 2)) {
279 {current_map, OverworldPropertyField::kAreaSize, 0, limited_size});
280 }
281
282 if (rom_version == zelda3::OverworldVersion::kVanilla ||
284 HOVER_HINT("Small (1x1) and Large (2x2) maps. Wide/Tall require v3+");
285 }
286 }
287
288 TableNextColumn();
289 if (ImGui::Button(current_map_lock ? ICON_MD_LOCK : ICON_MD_LOCK_OPEN,
290 ImVec2(40, 0))) {
291 current_map_lock = !current_map_lock;
292 }
293 HOVER_HINT(current_map_lock ? "Unlock Map" : "Lock Map");
294
295 TableNextColumn();
296 // Mode Controls
297 {
298 gui::StyleVarGuard spacing_guard(ImGuiStyleVar_ItemSpacing, ImVec2(0, 0));
300 ImVec2(30, 0))) {
301 current_mode = EditingMode::MOUSE;
303 }
304 HOVER_HINT("Mouse Mode (1)\nNavigate, pan, and manage entities");
305
306 ImGui::SameLine();
308 current_mode == EditingMode::DRAW_TILE,
309 ImVec2(30, 0))) {
310 current_mode = EditingMode::DRAW_TILE;
312 }
313 HOVER_HINT("Tile Paint Mode (2)\nDraw tiles on the map");
314 }
315
316 TableNextColumn();
317 // Entity Status
318 if (entity_edit_mode != EntityEditMode::NONE) {
319 const char* entity_icon = "";
320 const char* entity_label = "";
321 switch (entity_edit_mode) {
323 entity_icon = ICON_MD_DOOR_FRONT;
324 entity_label = "Entrances";
325 break;
327 entity_icon = ICON_MD_DOOR_BACK;
328 entity_label = "Exits";
329 break;
331 entity_icon = ICON_MD_GRASS;
332 entity_label = "Items";
333 break;
335 entity_icon = ICON_MD_PEST_CONTROL_RODENT;
336 entity_label = "Sprites";
337 break;
339 entity_icon = ICON_MD_ADD_LOCATION;
340 entity_label = "Transports";
341 break;
343 entity_icon = ICON_MD_MUSIC_NOTE;
344 entity_label = "Music";
345 break;
346 default:
347 break;
348 }
349 ImGui::TextColored(ImVec4(0.4f, 0.8f, 1.0f, 1.0f), "%s %s", entity_icon,
350 entity_label);
351 }
352
353 TableNextColumn();
354 // Sidebar Toggle
355 if (ImGui::Button(ICON_MD_TUNE, ImVec2(40, 0))) {
356 show_map_properties_panel = !show_map_properties_panel;
357 }
358 HOVER_HINT("Toggle Map Properties Sidebar");
359
360 ImGui::EndTable();
361 }
362}
363
365 int current_map, bool& show_map_properties_panel) {
366 (void)show_map_properties_panel; // Used by caller for window state
367 if (!overworld_->is_loaded()) {
368 Text(tr("No overworld loaded"));
369 return;
370 }
371
372 // Header with map info and lock status
373 ImGui::BeginGroup();
374 Text(tr("Current Map: %d (0x%02X)"), current_map, current_map);
375 ImGui::EndGroup();
376
377 Separator();
378
379 // Create tabs for different property categories
380 if (gui::BeginThemedTabBar("MapPropertiesTabs",
381 ImGuiTabBarFlags_FittingPolicyScroll)) {
382 // Basic Properties Tab
383 if (ImGui::BeginTabItem(tr("Basic Properties"))) {
384 DrawBasicPropertiesTab(current_map);
385 ImGui::EndTabItem();
386 }
387
388 // Sprite Properties Tab
389 if (ImGui::BeginTabItem(tr("Sprite Properties"))) {
390 DrawSpritePropertiesTab(current_map);
391 ImGui::EndTabItem();
392 }
393
394 // Custom Overworld Features Tab
396 if (rom_version != zelda3::OverworldVersion::kVanilla &&
397 ImGui::BeginTabItem(tr("Custom Features"))) {
398 DrawCustomFeaturesTab(current_map);
399 ImGui::EndTabItem();
400 }
401
402 // Tile Graphics Tab
403 if (ImGui::BeginTabItem(tr("Tile Graphics"))) {
404 DrawTileGraphicsTab(current_map);
405 ImGui::EndTabItem();
406 }
407
408 // Music Tab
409 if (ImGui::BeginTabItem(tr("Music"))) {
410 DrawMusicTab(current_map);
411 ImGui::EndTabItem();
412 }
413
415 }
416}
417
419 int current_map, bool& show_custom_bg_color_editor) {
420 (void)show_custom_bg_color_editor; // Used by caller for window state
421 if (!overworld_->is_loaded()) {
422 Text(tr("No overworld loaded"));
423 return;
424 }
425
428 Text(tr("Custom background colors require ZSCustomOverworld v2+"));
429 return;
430 }
431
432 Text(tr("Custom Background Color Editor"));
433 Separator();
434
435 // Read enable flag from ROM (not static - must reflect current ROM state)
436 bool use_area_specific_bg_color =
438 if (ImGui::Checkbox(tr("Use Area-Specific Background Color"),
439 &use_area_specific_bg_color)) {
440 // Update ROM data when checkbox is toggled
442 use_area_specific_bg_color ? 0x01 : 0x00;
443 }
444
445 if (use_area_specific_bg_color) {
446 // Get current color
447 uint16_t current_color =
448 overworld_->overworld_map(current_map)->area_specific_bg_color();
449 gfx::SnesColor snes_color(current_color);
450
451 // Convert to ImVec4 for color picker
452 ImVec4 color_vec = gui::ConvertSnesColorToImVec4(snes_color);
453
454 if (ImGui::ColorPicker4(
455 "Background Color", (float*)&color_vec,
456 ImGuiColorEditFlags_DisplayRGB | ImGuiColorEditFlags_DisplayHex)) {
457 // Convert back to SNES color and update
458 gfx::SnesColor new_snes_color = gui::ConvertImVec4ToSnesColor(color_vec);
459 ApplyPropertyEdit({current_map,
461 new_snes_color.snes()});
462 }
463
464 Text(tr("SNES Color: 0x%04X"), current_color);
465 }
466}
467
469 bool& show_overlay_editor) {
470 (void)show_overlay_editor; // Used by caller for window state
471 if (!overworld_->is_loaded()) {
472 Text(tr("No overworld loaded"));
473 return;
474 }
475
477
478 ImGui::TextColored(ImVec4(0.4f, 0.8f, 1.0f, 1.0f),
479 ICON_MD_LAYERS " Visual Effects Configuration");
480 ImGui::Text(tr("Map: 0x%02X"), current_map);
481 Separator();
482
483 if (rom_version == zelda3::OverworldVersion::kVanilla) {
484 ImGui::TextColored(
485 ImVec4(1.0f, 0.8f, 0.4f, 1.0f), ICON_MD_INFO
486 " Enhanced overlay editing requires ZSCustomOverworld v1+");
487 ImGui::Separator();
488 ImGui::TextWrapped(tr(
489 "Subscreen overlays are a vanilla feature used for atmospheric effects "
490 "like fog, rain, and forest canopy. ZSCustomOverworld expands this by "
491 "allowing per-area overlay configuration and additional "
492 "customization."));
493 return;
494 }
495
496 // Help section
497 if (ImGui::CollapsingHeader(ICON_MD_HELP_OUTLINE " What are Visual Effects?",
498 ImGuiTreeNodeFlags_DefaultOpen)) {
499 ImGui::Indent();
500 ImGui::TextWrapped(tr(
501 "Visual effects (subscreen overlays) are semi-transparent layers drawn "
502 "on top of or behind your map. They reference special area maps "
503 "(0x80-0x9F) "
504 "for their tile16 graphics data."));
505 ImGui::Spacing();
506 ImGui::Text(tr("Common uses:"));
507 ImGui::BulletText(tr("Fog effects (Lost Woods, Skull Woods)"));
508 ImGui::BulletText(tr("Rain (Misery Mire)"));
509 ImGui::BulletText(tr("Forest canopy (Lost Woods)"));
510 ImGui::BulletText(tr("Sky backgrounds (Death Mountain)"));
511 ImGui::BulletText(tr("Under bridge views"));
512 ImGui::Unindent();
513 ImGui::Separator();
514 }
515
516 // Read enable flag from ROM (not static - must reflect current ROM state)
517 bool use_subscreen_overlay =
519 if (ImGui::Checkbox(ICON_MD_VISIBILITY " Enable Visual Effect for This Area",
520 &use_subscreen_overlay)) {
521 // Update ROM data when checkbox is toggled
523 use_subscreen_overlay ? 0x01 : 0x00;
524 }
525 if (ImGui::IsItemHovered()) {
526 ImGui::SetTooltip(
527 tr("Enable/disable visual effect overlay for this map area"));
528 }
529
530 if (use_subscreen_overlay) {
531 ImGui::Spacing();
532 uint16_t current_overlay =
533 overworld_->overworld_map(current_map)->subscreen_overlay();
534 if (gui::InputHexWord(ICON_MD_PHOTO " Visual Effect Map ID",
535 &current_overlay, kInputFieldSize + 30)) {
537 0, current_overlay});
538 }
539 if (ImGui::IsItemHovered()) {
540 ImGui::SetTooltip(
541 tr("ID of the special area map (0x80-0x9F) to use for\n"
542 "visual effects. That map's tile16 data will be drawn\n"
543 "as a semi-transparent layer on this area."));
544 }
545
546 // Show description
547 std::string overlay_desc = GetOverlayDescription(current_overlay);
548 ImGui::TextColored(ImVec4(0.4f, 1.0f, 0.4f, 1.0f), ICON_MD_INFO " %s",
549 overlay_desc.c_str());
550
551 ImGui::Separator();
552 if (ImGui::CollapsingHeader(ICON_MD_LIGHTBULB
553 " Common Visual Effect IDs")) {
554 ImGui::Indent();
555 ImGui::BulletText(tr("0x0093 - Triforce Room Curtain"));
556 ImGui::BulletText(tr("0x0094 - Under the Bridge"));
557 ImGui::BulletText(tr("0x0095 - Sky Background (LW Death Mountain)"));
558 ImGui::BulletText(tr("0x0096 - Pyramid Background"));
559 ImGui::BulletText(tr("0x0097 - Fog Overlay (Master Sword Area)"));
560 ImGui::BulletText(tr("0x009C - Lava Background (DW Death Mountain)"));
561 ImGui::BulletText(tr("0x009D - Fog Overlay (Lost/Skull Woods)"));
562 ImGui::BulletText(tr("0x009E - Tree Canopy (Forest)"));
563 ImGui::BulletText(tr("0x009F - Rain Effect (Misery Mire)"));
564 ImGui::BulletText(tr("0x00FF - No Overlay (Disabled)"));
565 ImGui::Unindent();
566 }
567 } else {
568 ImGui::Spacing();
569 ImGui::TextColored(ImVec4(0.6f, 0.6f, 0.6f, 1.0f), ICON_MD_BLOCK
570 " No visual effects enabled for this area");
571 }
572}
573
575 gui::Canvas& canvas, int current_map, bool& current_map_lock,
576 bool& show_map_properties_panel, bool& show_custom_bg_color_editor,
577 bool& show_overlay_editor, int current_mode, project::YazeProject* project,
578 SharedClipboard* shared_clipboard) {
579 // Clear any existing context menu items
580 canvas.ClearContextMenuItems();
581
582 if (overworld_ && overworld_->is_loaded() && current_map >= 0 &&
583 current_map < zelda3::kNumOverworldMaps &&
584 overworld_->overworld_map(current_map) != nullptr) {
585 const auto metadata = BuildOverworldMapMetadata(
586 *overworld_, rom_, project, current_map, CurrentGameState());
587
588 gui::CanvasMenuItem header_item =
589 gui::CanvasMenuItem::Disabled(ICON_MD_MAP " " + metadata.map_title);
590 header_item.separator_after = true;
591 canvas.AddContextMenuItem(header_item);
592
593 gui::CanvasMenuItem metadata_menu;
594 metadata_menu.label = ICON_MD_INFO " Map Metadata";
595 metadata_menu.subitems.push_back(
596 gui::CanvasMenuItem::Disabled(metadata.version_label));
597 metadata_menu.subitems.push_back(
598 gui::CanvasMenuItem::Disabled(metadata.area_size_label));
599 metadata_menu.subitems.push_back(
600 gui::CanvasMenuItem::Disabled(metadata.parent_label));
601 metadata_menu.subitems.push_back(
602 gui::CanvasMenuItem::Disabled(metadata.area_gfx_label));
603 metadata_menu.subitems.push_back(
604 gui::CanvasMenuItem::Disabled(metadata.area_palette_label));
605 metadata_menu.subitems.push_back(
606 gui::CanvasMenuItem::Disabled(metadata.sprite_gfx_label));
607 metadata_menu.subitems.push_back(
608 gui::CanvasMenuItem::Disabled(metadata.sprite_palette_label));
609 metadata_menu.subitems.push_back(
610 gui::CanvasMenuItem::Disabled(metadata.music_label));
611 metadata_menu.subitems.push_back(
612 gui::CanvasMenuItem::Disabled(metadata.message_label));
613 metadata_menu.separator_after = true;
614 canvas.AddContextMenuItem(metadata_menu);
615
616 if (const auto* current_map_ptr = overworld_->overworld_map(current_map);
617 current_map_ptr) {
618 const int parent = IsValidMapId(current_map_ptr->parent())
619 ? current_map_ptr->parent()
620 : current_map;
621 gui::CanvasMenuItem related_maps_menu;
622 related_maps_menu.label = ICON_MD_ACCOUNT_TREE " Related Maps";
623
624 if (parent != current_map) {
625 gui::CanvasMenuItem select_parent_item;
626 select_parent_item.label =
627 absl::StrFormat("Select Parent Map 0x%02X", parent);
628 select_parent_item.callback = [this, parent]() {
630 map_selection_callback_(parent, false);
631 }
632 };
633 related_maps_menu.subitems.push_back(select_parent_item);
634 }
635
636 for (int map_id = 0; map_id < zelda3::kNumOverworldMaps; ++map_id) {
637 if (map_id == current_map ||
638 (map_id == parent && parent != current_map)) {
639 continue;
640 }
641 const auto* sibling = overworld_->overworld_map(map_id);
642 if (!sibling || sibling->parent() != parent) {
643 continue;
644 }
645
646 gui::CanvasMenuItem sibling_item;
647 sibling_item.label =
648 map_id == parent
649 ? absl::StrFormat("Select Parent Map 0x%02X", map_id)
650 : absl::StrFormat("Select Sibling Map 0x%02X", map_id);
651 sibling_item.callback = [this, map_id]() {
653 map_selection_callback_(map_id, false);
654 }
655 };
656 related_maps_menu.subitems.push_back(sibling_item);
657 }
658
659 if (!related_maps_menu.subitems.empty()) {
660 canvas.AddContextMenuItem(related_maps_menu);
661 }
662 }
663
664 if (shared_clipboard) {
665 gui::CanvasMenuItem metadata_actions_menu;
666 metadata_actions_menu.label =
667 ICON_MD_CONTENT_COPY " Copy / Paste Metadata";
668 if (shared_clipboard->has_overworld_map_metadata &&
669 shared_clipboard->overworld_map_metadata.valid) {
670 metadata_actions_menu.subitems.push_back(gui::CanvasMenuItem::Disabled(
673 shared_clipboard->overworld_map_metadata)));
674 }
675
676 auto add_metadata_actions = [&](OverworldMapMetadataClipboardScope scope,
677 const char* copy_label,
678 const char* paste_label) {
679 gui::CanvasMenuItem copy_item;
680 copy_item.label = copy_label;
681 copy_item.callback = [this, current_map, shared_clipboard, scope]() {
682 shared_clipboard->overworld_map_metadata =
683 CaptureMapMetadataClipboard(*overworld_, current_map);
684 shared_clipboard->overworld_map_metadata.scope = scope;
685 shared_clipboard->has_overworld_map_metadata =
686 shared_clipboard->overworld_map_metadata.valid;
687 };
688 metadata_actions_menu.subitems.push_back(copy_item);
689
690 gui::CanvasMenuItem paste_item;
691 paste_item.label = paste_label;
692 paste_item.enabled_condition = [shared_clipboard, scope]() {
693 return shared_clipboard->has_overworld_map_metadata &&
695 shared_clipboard->overworld_map_metadata, scope);
696 };
697 paste_item.callback = [this, current_map, shared_clipboard, scope]() {
698 const auto edits = BuildOverworldMetadataPasteEdits(
699 current_map, shared_clipboard->overworld_map_metadata, scope);
700 (void)ApplyPropertyEdits(
701 edits,
702 absl::StrFormat(
703 "Paste %s from 0x%02X",
705 shared_clipboard->overworld_map_metadata.source_map_id));
706 };
707 paste_item.separator_after =
709 metadata_actions_menu.subitems.push_back(paste_item);
710 };
711
712 add_metadata_actions(OverworldMapMetadataClipboardScope::kAll,
713 ICON_MD_CONTENT_COPY " Copy Map Metadata",
714 ICON_MD_CONTENT_PASTE " Paste Map Metadata");
716 ICON_MD_IMAGE " Copy Graphics Metadata",
717 ICON_MD_CONTENT_PASTE " Paste Graphics Metadata");
719 ICON_MD_PALETTE " Copy Palette Metadata",
720 ICON_MD_CONTENT_PASTE " Paste Palette Metadata");
722 ICON_MD_MUSIC_NOTE " Copy Music/Message Metadata",
724 " Paste Music/Message Metadata");
725
726 canvas.AddContextMenuItem(metadata_actions_menu);
727 }
728
729 if (project) {
730 const std::string popup_id =
731 absl::StrFormat("RenameOverworldMapLabelContext%02X", current_map);
733 ICON_MD_EDIT " Rename Map Label", popup_id,
734 [this, project, current_map, initial_label = metadata.map_name,
735 popup_id]() {
736 static std::array<char, 128> label_buffer{};
737 static int active_map = -1;
738 static std::string error_message;
739 if (active_map != current_map) {
740 active_map = current_map;
741 error_message.clear();
742 std::strncpy(label_buffer.data(), initial_label.c_str(),
743 label_buffer.size() - 1);
744 label_buffer[label_buffer.size() - 1] = '\0';
745 }
746
747 if (ImGui::BeginPopup(popup_id.c_str())) {
748 ImGui::Text(tr("%s Map Label"), ICON_MD_LABEL);
749 ImGui::TextDisabled(tr("Map 0x%02X"), current_map);
750 ImGui::SetNextItemWidth(260.0f);
751 ImGui::InputText("##OverworldContextMapLabel",
752 label_buffer.data(), label_buffer.size());
753
754 if (ImGui::Button(ICON_MD_CHECK " Apply")) {
755 auto status =
758 "overworld_map", current_map, label_buffer.data())
759 : RenameProjectResourceLabel(project, "overworld_map",
760 current_map,
761 label_buffer.data());
762 if (status.ok()) {
763 active_map = -1;
764 ImGui::CloseCurrentPopup();
765 } else {
766 error_message = std::string(status.message());
767 }
768 }
769 ImGui::SameLine();
770 if (ImGui::Button(ICON_MD_CLEAR " Clear")) {
771 auto status =
773 ? resource_label_edit_callback_("overworld_map",
774 current_map, "")
775 : RenameProjectResourceLabel(project, "overworld_map",
776 current_map, "");
777 if (status.ok()) {
778 active_map = -1;
779 ImGui::CloseCurrentPopup();
780 } else {
781 error_message = std::string(status.message());
782 }
783 }
784 if (!error_message.empty()) {
785 ImGui::TextWrapped("%s", error_message.c_str());
786 }
787 ImGui::EndPopup();
788 }
789 });
790 canvas.AddContextMenuItem(rename_item);
791 }
792 }
793
794 gui::CanvasMenuItem select_item;
795 select_item.label = ICON_MD_CHECK " Select This Map";
796 select_item.callback = [this, current_map]() {
797 if (map_selection_callback_) {
798 map_selection_callback_(current_map, false);
799 }
800 };
801 canvas.AddContextMenuItem(select_item);
802
803 // Add entity insertion submenu (only in MOUSE mode)
804 if (current_mode == 0 && entity_insert_callback_) { // 0 = EditingMode::MOUSE
805 gui::CanvasMenuItem entity_menu;
806 entity_menu.label = ICON_MD_ADD_LOCATION " Insert Entity";
807
808 // Entrance submenu item
809 gui::CanvasMenuItem entrance_item;
810 entrance_item.label = ICON_MD_DOOR_FRONT " Entrance";
811 entrance_item.callback = [this]() {
812 if (entity_insert_callback_) {
813 entity_insert_callback_("entrance");
814 }
815 };
816 entity_menu.subitems.push_back(entrance_item);
817
818 // Hole submenu item
819 gui::CanvasMenuItem hole_item;
820 hole_item.label = ICON_MD_CYCLONE " Hole";
821 hole_item.callback = [this]() {
822 if (entity_insert_callback_) {
823 entity_insert_callback_("hole");
824 }
825 };
826 entity_menu.subitems.push_back(hole_item);
827
828 // Exit submenu item
829 gui::CanvasMenuItem exit_item;
830 exit_item.label = ICON_MD_DOOR_BACK " Exit";
831 exit_item.callback = [this]() {
832 if (entity_insert_callback_) {
833 entity_insert_callback_("exit");
834 }
835 };
836 entity_menu.subitems.push_back(exit_item);
837
838 // Item submenu item
839 gui::CanvasMenuItem item_item;
840 item_item.label = ICON_MD_GRASS " Item";
841 item_item.callback = [this]() {
842 if (entity_insert_callback_) {
843 entity_insert_callback_("item");
844 }
845 };
846 entity_menu.subitems.push_back(item_item);
847
848 // Sprite submenu item
849 gui::CanvasMenuItem sprite_item;
850 sprite_item.label = ICON_MD_PEST_CONTROL_RODENT " Sprite";
851 sprite_item.callback = [this]() {
852 if (entity_insert_callback_) {
853 entity_insert_callback_("sprite");
854 }
855 };
856 entity_menu.subitems.push_back(sprite_item);
857
858 canvas.AddContextMenuItem(entity_menu);
859
860 if (sample_tile16_callback_) {
861 gui::CanvasMenuItem tile16_sample_item;
862 tile16_sample_item.label = ICON_MD_COLORIZE " Sample Tile16";
863 tile16_sample_item.callback = [this]() {
864 if (sample_tile16_callback_) {
865 (void)sample_tile16_callback_();
866 }
867 };
868 canvas.AddContextMenuItem(tile16_sample_item);
869 }
870
871 if (edit_tile16_callback_) {
872 gui::CanvasMenuItem tile16_edit_item;
873 tile16_edit_item.label = ICON_MD_GRID_VIEW " Edit Tile16";
874 tile16_edit_item.callback = [this]() {
875 if (edit_tile16_callback_) {
876 edit_tile16_callback_();
877 }
878 };
879 canvas.AddContextMenuItem(tile16_edit_item);
880 }
881 }
882
883 // Add overworld-specific context menu items
884 gui::CanvasMenuItem lock_item;
885 lock_item.label = current_map_lock ? ICON_MD_LOCK_OPEN " Unpin Map"
886 : ICON_MD_PUSH_PIN " Pin This Map";
887 lock_item.callback = [this, current_map, &current_map_lock]() {
888 if (current_map_lock) {
889 current_map_lock = false;
890 return;
891 }
892 if (map_selection_callback_) {
893 map_selection_callback_(current_map, false);
894 }
895 current_map_lock = true;
896 };
897 canvas.AddContextMenuItem(lock_item);
898
899 // Area Configuration
900 gui::CanvasMenuItem properties_item;
901 properties_item.label = ICON_MD_TUNE " Area Configuration";
902 properties_item.callback = [&show_map_properties_panel]() {
903 show_map_properties_panel = true;
904 };
905 canvas.AddContextMenuItem(properties_item);
906
907 // Custom overworld features (only show if v3+)
908 auto rom_version = zelda3::OverworldVersionHelper::GetVersion(*rom_);
910 // Custom Background Color
911 gui::CanvasMenuItem bg_color_item;
912 bg_color_item.label = ICON_MD_FORMAT_COLOR_FILL " Custom Background Color";
913 bg_color_item.callback = [&show_custom_bg_color_editor]() {
914 show_custom_bg_color_editor = true;
915 };
916 canvas.AddContextMenuItem(bg_color_item);
917
918 // Visual Effects Editor
919 gui::CanvasMenuItem overlay_item;
920 overlay_item.label = ICON_MD_LAYERS " Visual Effects";
921 overlay_item.callback = [&show_overlay_editor]() {
922 show_overlay_editor = true;
923 };
924 canvas.AddContextMenuItem(overlay_item);
925 }
926
927 // Canvas controls
928 gui::CanvasMenuItem reset_view_item;
929 reset_view_item.label = ICON_MD_RESTORE " Reset View";
930 reset_view_item.callback = [&canvas]() {
931 canvas.set_global_scale(1.0f);
932 canvas.set_scrolling(ImVec2(0, 0));
933 };
934 canvas.AddContextMenuItem(reset_view_item);
935
936 gui::CanvasMenuItem zoom_in_item;
937 zoom_in_item.label = ICON_MD_ZOOM_IN " Zoom In";
938 zoom_in_item.callback = [&canvas]() {
939 float scale =
940 std::min(kOverworldMaxZoom, canvas.global_scale() + kOverworldZoomStep);
941 canvas.set_global_scale(scale);
942 };
943 canvas.AddContextMenuItem(zoom_in_item);
944
945 gui::CanvasMenuItem zoom_out_item;
946 zoom_out_item.label = ICON_MD_ZOOM_OUT " Zoom Out";
947 zoom_out_item.callback = [&canvas]() {
948 float scale =
949 std::max(kOverworldMinZoom, canvas.global_scale() - kOverworldZoomStep);
950 canvas.set_global_scale(scale);
951 };
952 canvas.AddContextMenuItem(zoom_out_item);
953}
954
955// Private method implementations
956void MapPropertiesSystem::DrawGraphicsPopup(int current_map, int game_state) {
957 const int current_game_state = CurrentGameState(game_state);
960 .c_str())) {
961 ImGui::PushID("GraphicsPopup"); // Fix ImGui duplicate ID warnings
962
963 // Use theme-aware spacing instead of hardcoded constants
965 float padding = gui::LayoutHelpers::GetButtonPadding();
966 gui::StyleVarGuard popup_style_guard(
967 {{ImGuiStyleVar_ItemSpacing, ImVec2(spacing, spacing)},
968 {ImGuiStyleVar_FramePadding, ImVec2(padding, padding)}});
969
970 ImGui::Text(tr("Graphics Settings"));
971 ImGui::Separator();
972
973 // Area Graphics
974 uint8_t area_gfx = overworld_->overworld_map(current_map)->area_graphics();
975 if (gui::InputHexByte(ICON_MD_IMAGE " Area Graphics", &area_gfx,
977 ApplyPropertyEdit(
978 {current_map, OverworldPropertyField::kAreaGraphics, 0, area_gfx});
979 }
980 HOVER_HINT("Main tileset graphics for this map area");
981
982 // Sprite Graphics
983 uint8_t sprite_gfx = overworld_->overworld_map(current_map)
984 ->sprite_graphics(current_game_state);
985 if (gui::InputHexByte(absl::StrFormat(ICON_MD_PETS " Sprite GFX (%s)",
986 kGameStateNames[current_game_state])
987 .c_str(),
988 &sprite_gfx, kHexByteInputWidth)) {
989 ApplyPropertyEdit({current_map, OverworldPropertyField::kSpriteGraphics,
990 current_game_state, sprite_gfx});
991 }
992 HOVER_HINT("Sprite graphics sheet for current game state");
993
994 auto rom_version_gfx = zelda3::OverworldVersionHelper::GetVersion(*rom_);
996 uint8_t animated_gfx =
997 overworld_->overworld_map(current_map)->animated_gfx();
998 if (gui::InputHexByte(ICON_MD_ANIMATION " Animated GFX", &animated_gfx,
1000 ApplyPropertyEdit({current_map,
1001 OverworldPropertyField::kAnimatedGraphics, 0,
1002 animated_gfx});
1003 }
1004 HOVER_HINT("Animated tile graphics (water, lava, etc.)");
1005 }
1006
1007 // Custom Tile Graphics - Only available for v1+ ROMs
1009 rom_version_gfx)) {
1010 ImGui::Separator();
1011 ImGui::Text(ICON_MD_GRID_VIEW " Custom Tile Graphics");
1012 ImGui::Separator();
1013
1014 // Show the 8 custom graphics IDs in a 2-column layout for density
1015 if (BeginTable("CustomTileGraphics", 2, ImGuiTableFlags_SizingFixedFit)) {
1016 for (int i = 0; i < 8; i++) {
1017 TableNextColumn();
1018 std::string label = absl::StrFormat(ICON_MD_LAYERS " Sheet %d", i);
1019 uint8_t custom_gfx =
1020 overworld_->overworld_map(current_map)->custom_tileset(i);
1021 if (gui::InputHexByte(label.c_str(), &custom_gfx, 90.f)) {
1022 ApplyPropertyEdit({current_map,
1023 OverworldPropertyField::kCustomTileset, i,
1024 custom_gfx});
1025 }
1026 if (ImGui::IsItemHovered()) {
1027 ImGui::SetTooltip(tr("Custom graphics sheet %d (0x00-0xFF)"), i);
1028 }
1029 }
1030 ImGui::EndTable();
1031 }
1032 } else if (rom_version_gfx == zelda3::OverworldVersion::kVanilla) {
1033 ImGui::Separator();
1034 ImGui::TextColored(ImVec4(0.6f, 0.6f, 0.6f, 1.0f),
1035 ICON_MD_INFO " Custom Tile Graphics");
1036 ImGui::TextWrapped(
1037 tr("Custom tile graphics require ZSCustomOverworld v1+.\n"
1038 "Upgrade your ROM to access 8 customizable graphics sheets."));
1039 }
1040 ImGui::PopID(); // Pop GraphicsPopup ID scope
1041 ImGui::EndPopup();
1042 }
1043}
1044
1045void MapPropertiesSystem::DrawPalettesPopup(int current_map, int game_state,
1046 bool& show_custom_bg_color_editor) {
1047 const int current_game_state = CurrentGameState(game_state);
1048 if (ImGui::BeginPopup(gui::MakePopupId(gui::EditorNames::kOverworld,
1050 .c_str())) {
1051 ImGui::PushID("PalettesPopup"); // Fix ImGui duplicate ID warnings
1052
1053 // Use theme-aware spacing instead of hardcoded constants
1055 float padding = gui::LayoutHelpers::GetButtonPadding();
1056 gui::StyleVarGuard popup_style_guard(
1057 {{ImGuiStyleVar_ItemSpacing, ImVec2(spacing, spacing)},
1058 {ImGuiStyleVar_FramePadding, ImVec2(padding, padding)}});
1059
1060 ImGui::Text(tr("Palette Settings"));
1061 ImGui::Separator();
1062
1063 // Area Palette
1064 uint8_t area_palette =
1065 overworld_->overworld_map(current_map)->area_palette();
1066 if (gui::InputHexByte(ICON_MD_PALETTE " Area Palette", &area_palette,
1068 ApplyPropertyEdit(
1069 {current_map, OverworldPropertyField::kAreaPalette, 0, area_palette});
1070 }
1071 HOVER_HINT("Main color palette for background tiles");
1072
1073 // Read fresh to reflect ROM upgrades
1074 auto rom_version_pal = zelda3::OverworldVersionHelper::GetVersion(*rom_);
1076 rom_version_pal)) {
1077 uint8_t main_palette =
1078 overworld_->overworld_map(current_map)->main_palette();
1079 if (gui::InputHexByte(ICON_MD_COLOR_LENS " Main Palette", &main_palette,
1081 ApplyPropertyEdit({current_map, OverworldPropertyField::kMainPalette, 0,
1082 main_palette});
1083 }
1084 HOVER_HINT("Extended main palette (ZSCustomOverworld v2+)");
1085 }
1086
1087 // Sprite Palette
1088 uint8_t sprite_palette = overworld_->overworld_map(current_map)
1089 ->sprite_palette(current_game_state);
1090 if (gui::InputHexByte(absl::StrFormat(ICON_MD_COLORIZE " Sprite Pal (%s)",
1091 kGameStateNames[current_game_state])
1092 .c_str(),
1093 &sprite_palette, kHexByteInputWidth)) {
1094 ApplyPropertyEdit({current_map, OverworldPropertyField::kSpritePalette,
1095 current_game_state, sprite_palette});
1096 }
1097 HOVER_HINT("Color palette for sprites in current game state");
1098
1099 ImGui::Separator();
1100 if (ImGui::Button(ICON_MD_FORMAT_COLOR_FILL " Custom Background Color",
1101 ImVec2(-1, 0))) {
1102 show_custom_bg_color_editor = !show_custom_bg_color_editor;
1103 }
1104 HOVER_HINT("Open custom background color editor (v2+)");
1105 ImGui::PopID(); // Pop PalettesPopup ID scope
1106 ImGui::EndPopup();
1107 }
1108}
1109
1110void MapPropertiesSystem::DrawPropertiesPopup(int current_map,
1111 bool& show_map_properties_panel,
1112 bool& show_overlay_preview,
1113 int& game_state) {
1114 if (ImGui::BeginPopup(gui::MakePopupId(gui::EditorNames::kOverworld,
1116 .c_str())) {
1117 ImGui::PushID("ConfigPopup"); // Fix ImGui duplicate ID warnings
1118
1119 // Use theme-aware spacing instead of hardcoded constants
1121 float padding = gui::LayoutHelpers::GetButtonPadding();
1122 gui::StyleVarGuard popup_style_guard(
1123 {{ImGuiStyleVar_ItemSpacing, ImVec2(spacing, spacing)},
1124 {ImGuiStyleVar_FramePadding, ImVec2(padding, padding)}});
1125
1126 ImGui::Text(ICON_MD_TUNE " Area Configuration");
1127 ImGui::Separator();
1128
1129 // Basic Properties in 2-column layout for density
1130 if (BeginTable("BasicProps", 2, ImGuiTableFlags_SizingFixedFit)) {
1131 // Message ID
1132 TableNextColumn();
1133 ImGui::Text(ICON_MD_MESSAGE " Message");
1134 TableNextColumn();
1135 uint16_t message_id =
1136 overworld_->overworld_map(current_map)->message_id();
1137 if (gui::InputHexWordCustom("##MsgId", &message_id, kHexWordInputWidth)) {
1138 ApplyPropertyEdit(
1139 {current_map, OverworldPropertyField::kMessageId, 0, message_id});
1140 }
1141 if (ImGui::IsItemHovered()) {
1142 ImGui::SetTooltip(tr("Message ID shown when entering this area"));
1143 }
1144
1145 // Game State
1146 TableNextColumn();
1147 ImGui::Text(ICON_MD_GAMEPAD " Game State");
1148 TableNextColumn();
1149 ImGui::SetNextItemWidth(kComboGameStateWidth);
1150 int current_game_state = CurrentGameState(game_state);
1151 if (ImGui::Combo("##GameState", &current_game_state, kGameStateNames,
1152 3)) {
1153 game_state = current_game_state;
1154 SetCurrentGameState(current_game_state);
1155 RefreshMapProperties();
1156 RefreshOverworldMap();
1157 }
1158 if (ImGui::IsItemHovered()) {
1159 ImGui::SetTooltip(
1160 tr("Affects sprite graphics/palettes based on story progress"));
1161 }
1162
1163 ImGui::EndTable();
1164 }
1165
1166 // Area Configuration Section
1167 ImGui::Separator();
1168 ImGui::Text(ICON_MD_ASPECT_RATIO " Area Configuration");
1169 ImGui::Separator();
1170
1171 // ALL ROMs support Small/Large. Only v3+ supports Wide/Tall.
1172 uint8_t asm_version = (*rom_)[zelda3::OverworldCustomASMHasBeenApplied];
1173
1174 int current_area_size =
1175 static_cast<int>(overworld_->overworld_map(current_map)->area_size());
1176 ImGui::SetNextItemWidth(kComboAreaSizeWidth);
1177
1178 if (asm_version >= 3 && asm_version != 0xFF) {
1179 // v3+ ROM: Show all 4 area size options
1180 if (ImGui::Combo(ICON_MD_PHOTO_SIZE_SELECT_LARGE " Size",
1181 &current_area_size, kAreaSizeNames, 4)) {
1182 ApplyPropertyEdit({current_map, OverworldPropertyField::kAreaSize, 0,
1183 current_area_size});
1184 }
1185 HOVER_HINT("Map area size (1x1, 2x2, 2x1, 1x2 screens)");
1186 } else {
1187 // Vanilla/v1/v2 ROM: Show only Small/Large
1188 const char* limited_names[] = {"Small (1x1)", "Large (2x2)"};
1189 int limited_size = (current_area_size == 0 || current_area_size == 1)
1190 ? current_area_size
1191 : 0;
1192
1193 if (ImGui::Combo(ICON_MD_PHOTO_SIZE_SELECT_LARGE " Size", &limited_size,
1194 limited_names, 2)) {
1195 ApplyPropertyEdit(
1196 {current_map, OverworldPropertyField::kAreaSize, 0, limited_size});
1197 }
1198 HOVER_HINT("Small (1x1) and Large (2x2) maps. Wide/Tall require v3+");
1199 }
1200
1201 // Visual Effects Section
1202 ImGui::Separator();
1203 ImGui::Text(ICON_MD_AUTO_FIX_HIGH " Visual Effects");
1204 ImGui::Separator();
1205
1206 DrawMosaicControls(current_map);
1207 DrawOverlayControls(current_map, show_overlay_preview);
1208
1209 // Advanced Options Section
1210 ImGui::Separator();
1211 if (ImGui::Button(ICON_MD_OPEN_IN_NEW " Full Configuration Panel",
1212 ImVec2(-1, 0))) {
1213 show_map_properties_panel = true;
1214 ImGui::CloseCurrentPopup();
1215 }
1216 HOVER_HINT("Open detailed area configuration with all settings tabs");
1217
1218 ImGui::PopID(); // Pop ConfigPopup ID scope
1219 ImGui::EndPopup();
1220 }
1221}
1222
1223void MapPropertiesSystem::DrawBasicPropertiesTab(int current_map) {
1224 if (BeginTable("BasicProperties", 2,
1225 ImGuiTableFlags_Borders | ImGuiTableFlags_SizingFixedFit)) {
1226 ImGui::TableSetupColumn("Property", ImGuiTableColumnFlags_WidthFixed, 180);
1227 ImGui::TableSetupColumn("Value", ImGuiTableColumnFlags_WidthStretch);
1228
1229 TableNextColumn();
1230 ImGui::Text(ICON_MD_IMAGE " Area Graphics");
1231 TableNextColumn();
1232 uint8_t area_gfx = overworld_->overworld_map(current_map)->area_graphics();
1233 if (gui::InputHexByte("##AreaGfx", &area_gfx, kInputFieldSize)) {
1234 ApplyPropertyEdit(
1235 {current_map, OverworldPropertyField::kAreaGraphics, 0, area_gfx});
1236 }
1237 if (ImGui::IsItemHovered()) {
1238 ImGui::SetTooltip(tr("Main tileset graphics for this map area"));
1239 }
1240
1241 TableNextColumn();
1242 ImGui::Text(ICON_MD_PALETTE " Area Palette");
1243 TableNextColumn();
1244 uint8_t area_pal = overworld_->overworld_map(current_map)->area_palette();
1245 if (gui::InputHexByte("##AreaPal", &area_pal, kInputFieldSize)) {
1246 ApplyPropertyEdit(
1247 {current_map, OverworldPropertyField::kAreaPalette, 0, area_pal});
1248 }
1249 if (ImGui::IsItemHovered()) {
1250 ImGui::SetTooltip(tr("Color palette for background tiles"));
1251 }
1252
1253 TableNextColumn();
1254 ImGui::Text(ICON_MD_MESSAGE " Message ID");
1255 TableNextColumn();
1256 uint16_t message_id = overworld_->overworld_map(current_map)->message_id();
1257 if (gui::InputHexWord("##MsgId", &message_id, kInputFieldSize + 20)) {
1258 ApplyPropertyEdit(
1259 {current_map, OverworldPropertyField::kMessageId, 0, message_id});
1260 }
1261 if (ImGui::IsItemHovered()) {
1262 ImGui::SetTooltip(tr("Message displayed when entering this area"));
1263 }
1264
1265 TableNextColumn();
1266 ImGui::Text(ICON_MD_BLUR_ON " Mosaic Effect");
1267 TableNextColumn();
1268 bool mosaic =
1269 *overworld_->mutable_overworld_map(current_map)->mutable_mosaic();
1270 if (ImGui::Checkbox("##mosaic", &mosaic)) {
1271 ApplyPropertyEdit(
1272 {current_map, OverworldPropertyField::kMosaic, 0, mosaic ? 1 : 0});
1273 }
1274 if (ImGui::IsItemHovered()) {
1275 ImGui::SetTooltip(tr("Enable pixelated mosaic transition effect"));
1276 }
1277
1278 // Add music editing controls with icons
1279 TableNextColumn();
1280 ImGui::Text(ICON_MD_MUSIC_NOTE " Music (Beginning)");
1281 TableNextColumn();
1282 uint8_t music0 = overworld_->overworld_map(current_map)->area_music(0);
1283 if (gui::InputHexByte("##Music0", &music0, kInputFieldSize)) {
1284 ApplyPropertyEdit(
1285 {current_map, OverworldPropertyField::kMusic, 0, music0});
1286 }
1287 if (ImGui::IsItemHovered()) {
1288 ImGui::SetTooltip(tr("Music track before rescuing Zelda"));
1289 }
1290
1291 TableNextColumn();
1292 ImGui::Text(ICON_MD_MUSIC_NOTE " Music (Zelda)");
1293 TableNextColumn();
1294 uint8_t music1 = overworld_->overworld_map(current_map)->area_music(1);
1295 if (gui::InputHexByte("##Music1", &music1, kInputFieldSize)) {
1296 ApplyPropertyEdit(
1297 {current_map, OverworldPropertyField::kMusic, 1, music1});
1298 }
1299 if (ImGui::IsItemHovered()) {
1300 ImGui::SetTooltip(tr("Music track after rescuing Zelda"));
1301 }
1302
1303 TableNextColumn();
1304 ImGui::Text(ICON_MD_MUSIC_NOTE " Music (Master Sword)");
1305 TableNextColumn();
1306 uint8_t music2 = overworld_->overworld_map(current_map)->area_music(2);
1307 if (gui::InputHexByte("##Music2", &music2, kInputFieldSize)) {
1308 ApplyPropertyEdit(
1309 {current_map, OverworldPropertyField::kMusic, 2, music2});
1310 }
1311 if (ImGui::IsItemHovered()) {
1312 ImGui::SetTooltip(tr("Music track after obtaining Master Sword"));
1313 }
1314
1315 TableNextColumn();
1316 ImGui::Text(ICON_MD_MUSIC_NOTE " Music (Agahnim)");
1317 TableNextColumn();
1318 uint8_t music3 = overworld_->overworld_map(current_map)->area_music(3);
1319 if (gui::InputHexByte("##Music3", &music3, kInputFieldSize)) {
1320 ApplyPropertyEdit(
1321 {current_map, OverworldPropertyField::kMusic, 3, music3});
1322 }
1323 if (ImGui::IsItemHovered()) {
1324 ImGui::SetTooltip(tr("Music track after defeating Agahnim (Dark World)"));
1325 }
1326
1327 ImGui::EndTable();
1328 }
1329}
1330
1331void MapPropertiesSystem::DrawSpritePropertiesTab(int current_map) {
1332 if (BeginTable("SpriteProperties", 2,
1333 ImGuiTableFlags_Borders | ImGuiTableFlags_SizingFixedFit)) {
1334 ImGui::TableSetupColumn("Property", ImGuiTableColumnFlags_WidthFixed, 180);
1335 ImGui::TableSetupColumn("Value", ImGuiTableColumnFlags_WidthStretch);
1336
1337 TableNextColumn();
1338 ImGui::Text(ICON_MD_GAMEPAD " Game State");
1339 TableNextColumn();
1340 int game_state = CurrentGameState();
1341 ImGui::SetNextItemWidth(120.f);
1342 if (ImGui::Combo("##GameState", &game_state, kGameStateNames, 3)) {
1343 SetCurrentGameState(game_state);
1344 RefreshMapProperties();
1345 RefreshOverworldMap();
1346 }
1347 if (ImGui::IsItemHovered()) {
1348 ImGui::SetTooltip(tr("Affects which sprite graphics/palettes are used"));
1349 }
1350
1351 TableNextColumn();
1352 ImGui::Text(ICON_MD_PETS " Sprite Graphics 1");
1353 TableNextColumn();
1354 uint8_t sprite_gfx1 =
1355 overworld_->overworld_map(current_map)->sprite_graphics(1);
1356 if (gui::InputHexByte("##SprGfx1", &sprite_gfx1, kInputFieldSize)) {
1357 ApplyPropertyEdit({current_map, OverworldPropertyField::kSpriteGraphics,
1358 1, sprite_gfx1});
1359 }
1360 if (ImGui::IsItemHovered()) {
1361 ImGui::SetTooltip(
1362 tr("First sprite graphics sheet for Zelda rescued state"));
1363 }
1364
1365 TableNextColumn();
1366 ImGui::Text(ICON_MD_PETS " Sprite Graphics 2");
1367 TableNextColumn();
1368 uint8_t sprite_gfx2 =
1369 overworld_->overworld_map(current_map)->sprite_graphics(2);
1370 if (gui::InputHexByte("##SprGfx2", &sprite_gfx2, kInputFieldSize)) {
1371 ApplyPropertyEdit({current_map, OverworldPropertyField::kSpriteGraphics,
1372 2, sprite_gfx2});
1373 }
1374 if (ImGui::IsItemHovered()) {
1375 ImGui::SetTooltip(
1376 tr("Second sprite graphics sheet for Master Sword obtained state"));
1377 }
1378
1379 TableNextColumn();
1380 ImGui::Text(ICON_MD_COLORIZE " Sprite Palette 1");
1381 TableNextColumn();
1382 uint8_t sprite_pal1 =
1383 overworld_->overworld_map(current_map)->sprite_palette(1);
1384 if (gui::InputHexByte("##SprPal1", &sprite_pal1, kInputFieldSize)) {
1385 ApplyPropertyEdit({current_map, OverworldPropertyField::kSpritePalette, 1,
1386 sprite_pal1});
1387 }
1388 if (ImGui::IsItemHovered()) {
1389 ImGui::SetTooltip(tr("Color palette for sprites - Zelda rescued state"));
1390 }
1391
1392 TableNextColumn();
1393 ImGui::Text(ICON_MD_COLORIZE " Sprite Palette 2");
1394 TableNextColumn();
1395 uint8_t sprite_pal2 =
1396 overworld_->overworld_map(current_map)->sprite_palette(2);
1397 if (gui::InputHexByte("##SprPal2", &sprite_pal2, kInputFieldSize)) {
1398 ApplyPropertyEdit({current_map, OverworldPropertyField::kSpritePalette, 2,
1399 sprite_pal2});
1400 }
1401 if (ImGui::IsItemHovered()) {
1402 ImGui::SetTooltip(
1403 tr("Color palette for sprites - Master Sword obtained state"));
1404 }
1405
1406 ImGui::EndTable();
1407 }
1408}
1409
1410void MapPropertiesSystem::DrawCustomFeaturesTab(int current_map) {
1411 if (BeginTable("CustomFeatures", 2,
1412 ImGuiTableFlags_Borders | ImGuiTableFlags_SizingFixedFit)) {
1413 ImGui::TableSetupColumn("Property", ImGuiTableColumnFlags_WidthFixed, 180);
1414 ImGui::TableSetupColumn("Value", ImGuiTableColumnFlags_WidthStretch);
1415
1416 TableNextColumn();
1417 ImGui::Text(ICON_MD_PHOTO_SIZE_SELECT_LARGE " Area Size");
1418 TableNextColumn();
1419 // ALL ROMs support Small/Large. Only v3+ supports Wide/Tall.
1420 auto rom_version_basic = zelda3::OverworldVersionHelper::GetVersion(*rom_);
1421
1422 int current_area_size =
1423 static_cast<int>(overworld_->overworld_map(current_map)->area_size());
1424 ImGui::SetNextItemWidth(130.f);
1425
1426 if (zelda3::OverworldVersionHelper::SupportsAreaEnum(rom_version_basic)) {
1427 // v3+ ROM: Show all 4 area size options
1428 static const char* all_sizes[] = {"Small (1x1)", "Large (2x2)",
1429 "Wide (2x1)", "Tall (1x2)"};
1430 if (ImGui::Combo("##AreaSize", &current_area_size, all_sizes, 4)) {
1431 ApplyPropertyEdit({current_map, OverworldPropertyField::kAreaSize, 0,
1432 current_area_size});
1433 }
1434 if (ImGui::IsItemHovered()) {
1435 ImGui::SetTooltip(
1436 tr("Map size: Small (1x1), Large (2x2), Wide (2x1), Tall (1x2)"));
1437 }
1438 } else {
1439 // Vanilla/v1/v2 ROM: Show only Small/Large
1440 static const char* limited_sizes[] = {"Small (1x1)", "Large (2x2)"};
1441 int limited_size = (current_area_size == 0 || current_area_size == 1)
1442 ? current_area_size
1443 : 0;
1444
1445 if (ImGui::Combo("##AreaSize", &limited_size, limited_sizes, 2)) {
1446 auto size = (limited_size == 1) ? zelda3::AreaSizeEnum::LargeArea
1448 ApplyPropertyEdit({current_map, OverworldPropertyField::kAreaSize, 0,
1449 static_cast<int>(size)});
1450 }
1451 if (ImGui::IsItemHovered()) {
1452 ImGui::SetTooltip(
1453 tr("Map size: Small (1x1), Large (2x2). Wide/Tall require v3+"));
1454 }
1455 }
1456
1458 rom_version_basic)) {
1459 TableNextColumn();
1460 ImGui::Text(ICON_MD_COLOR_LENS " Main Palette");
1461 TableNextColumn();
1462 uint8_t main_palette =
1463 overworld_->overworld_map(current_map)->main_palette();
1464 if (gui::InputHexByte("##MainPal", &main_palette, kInputFieldSize)) {
1465 ApplyPropertyEdit({current_map, OverworldPropertyField::kMainPalette, 0,
1466 main_palette});
1467 }
1468 if (ImGui::IsItemHovered()) {
1469 ImGui::SetTooltip(tr("Extended main palette (ZSCustomOverworld v2+)"));
1470 }
1471 }
1472
1474 rom_version_basic)) {
1475 TableNextColumn();
1476 ImGui::Text(ICON_MD_ANIMATION " Animated GFX");
1477 TableNextColumn();
1478 uint8_t animated_gfx =
1479 overworld_->overworld_map(current_map)->animated_gfx();
1480 if (gui::InputHexByte("##AnimGfx", &animated_gfx, kInputFieldSize)) {
1481 ApplyPropertyEdit({current_map,
1482 OverworldPropertyField::kAnimatedGraphics, 0,
1483 animated_gfx});
1484 }
1485 if (ImGui::IsItemHovered()) {
1486 ImGui::SetTooltip(tr("Animated tile graphics ID (water, lava, etc.)"));
1487 }
1488
1489 TableNextColumn();
1490 ImGui::Text(ICON_MD_LAYERS " Subscreen Overlay");
1491 TableNextColumn();
1492 uint16_t subscreen_overlay =
1493 overworld_->overworld_map(current_map)->subscreen_overlay();
1494 if (gui::InputHexWord("##SubOverlay", &subscreen_overlay,
1495 kInputFieldSize + 20)) {
1496 ApplyPropertyEdit({current_map,
1497 OverworldPropertyField::kSubscreenOverlay, 0,
1498 subscreen_overlay});
1499 }
1500 if (ImGui::IsItemHovered()) {
1501 ImGui::SetTooltip(
1502 tr("Visual effects overlay ID (fog, rain, backgrounds)"));
1503 }
1504 }
1505
1506 ImGui::EndTable();
1507 }
1508}
1509
1510void MapPropertiesSystem::DrawTileGraphicsTab(int current_map) {
1511 auto rom_version = zelda3::OverworldVersionHelper::GetVersion(*rom_);
1512
1513 // Only show custom tile graphics for v1+ ROMs
1515 ImGui::Text(ICON_MD_GRID_VIEW " Custom Tile Graphics (8 sheets)");
1516 Separator();
1517
1518 if (BeginTable("TileGraphics", 2,
1519 ImGuiTableFlags_Borders | ImGuiTableFlags_SizingFixedFit)) {
1520 ImGui::TableSetupColumn("Property", ImGuiTableColumnFlags_WidthFixed,
1521 180);
1522 ImGui::TableSetupColumn("Value", ImGuiTableColumnFlags_WidthStretch);
1523
1524 for (int i = 0; i < 8; i++) {
1525 TableNextColumn();
1526 ImGui::Text(ICON_MD_LAYERS " Sheet %d", i);
1527 TableNextColumn();
1528 uint8_t custom_tileset =
1529 overworld_->overworld_map(current_map)->custom_tileset(i);
1530 if (gui::InputHexByte(absl::StrFormat("##TileGfx%d", i).c_str(),
1531 &custom_tileset, kInputFieldSize)) {
1532 ApplyPropertyEdit({current_map,
1533 OverworldPropertyField::kCustomTileset, i,
1534 custom_tileset});
1535 }
1536 if (ImGui::IsItemHovered()) {
1537 ImGui::SetTooltip(tr("Custom graphics sheet %d (0x00-0xFF)"), i);
1538 }
1539 }
1540
1541 ImGui::EndTable();
1542 }
1543
1544 Separator();
1545 ImGui::TextWrapped(
1546 tr("These 8 sheets allow custom tile graphics per map. "
1547 "Each sheet references a graphics ID loaded into VRAM."));
1548 } else {
1549 // Vanilla ROM - show info message
1550 ImGui::TextColored(ImVec4(0.6f, 0.6f, 0.6f, 1.0f),
1551 ICON_MD_INFO " Custom Tile Graphics");
1552 ImGui::Separator();
1553 ImGui::TextWrapped(
1554 tr("Custom tile graphics are not available in vanilla ROMs.\n\n"
1555 "To enable this feature, upgrade your ROM to ZSCustomOverworld v1+, "
1556 "which provides 8 customizable graphics sheets per map for advanced "
1557 "tileset customization."));
1558 }
1559}
1560
1561void MapPropertiesSystem::DrawMusicTab(int current_map) {
1562 ImGui::Text(ICON_MD_MUSIC_NOTE " Music Settings for Game States");
1563 Separator();
1564
1565 if (BeginTable("MusicSettings", 2,
1566 ImGuiTableFlags_Borders | ImGuiTableFlags_SizingFixedFit)) {
1567 ImGui::TableSetupColumn("Game State", ImGuiTableColumnFlags_WidthFixed,
1568 220);
1569 ImGui::TableSetupColumn("Music Track ID",
1570 ImGuiTableColumnFlags_WidthStretch);
1571
1572 const char* music_state_names[] = {
1573 ICON_MD_PLAY_ARROW " Beginning (Pre-Zelda)",
1574 ICON_MD_FAVORITE " Zelda Rescued",
1575 ICON_MD_OFFLINE_BOLT " Master Sword Obtained",
1576 ICON_MD_CASTLE " Agahnim Defeated"};
1577
1578 const char* music_descriptions[] = {
1579 "Music before rescuing Zelda from the castle",
1580 "Music after rescuing Zelda from Hyrule Castle",
1581 "Music after obtaining the Master Sword from the Lost Woods",
1582 "Music after defeating Agahnim (Dark World music)"};
1583
1584 for (int i = 0; i < 4; i++) {
1585 TableNextColumn();
1586 ImGui::Text("%s", music_state_names[i]);
1587
1588 TableNextColumn();
1589 uint8_t music = overworld_->overworld_map(current_map)->area_music(i);
1590 if (gui::InputHexByte(absl::StrFormat("##Music%d", i).c_str(), &music,
1591 kInputFieldSize)) {
1592 ApplyPropertyEdit(
1593 {current_map, OverworldPropertyField::kMusic, i, music});
1594 }
1595 if (ImGui::IsItemHovered()) {
1596 ImGui::SetTooltip("%s", music_descriptions[i]);
1597 }
1598 }
1599
1600 ImGui::EndTable();
1601 }
1602
1603 Separator();
1604 ImGui::TextWrapped(
1605 tr("Music tracks control the background music for different "
1606 "game progression states on this overworld map."));
1607
1608 // Show common music track IDs for reference in a collapsing section
1609 Separator();
1610 if (ImGui::CollapsingHeader(ICON_MD_HELP_OUTLINE " Common Music Track IDs",
1611 ImGuiTreeNodeFlags_DefaultOpen)) {
1612 ImGui::Indent();
1613 ImGui::BulletText(tr("0x02 - Overworld Theme"));
1614 ImGui::BulletText(tr("0x05 - Kakariko Village"));
1615 ImGui::BulletText(tr("0x07 - Lost Woods"));
1616 ImGui::BulletText(tr("0x09 - Dark World Theme"));
1617 ImGui::BulletText(tr("0x0F - Ganon's Tower"));
1618 ImGui::BulletText(tr("0x11 - Death Mountain"));
1619 ImGui::Unindent();
1620 }
1621}
1622
1623absl::Status MapPropertiesSystem::ApplyPropertyEdit(
1624 const OverworldPropertyEdit& edit) {
1625 if (property_edit_callback_) {
1626 return property_edit_callback_(edit);
1627 }
1628 return ApplyPropertyEditDirect(edit);
1629}
1630
1631absl::Status MapPropertiesSystem::ApplyPropertyEdits(
1632 const std::vector<OverworldPropertyEdit>& edits,
1633 const std::string& description) {
1634 if (property_edit_batch_callback_) {
1635 return property_edit_batch_callback_(edits, description);
1636 }
1637 for (const auto& edit : edits) {
1638 if (!CheckPropertyEditSupported(edit).ok()) {
1639 continue;
1640 }
1641 RETURN_IF_ERROR(ApplyPropertyEdit(edit));
1642 }
1643 return absl::OkStatus();
1644}
1645
1646absl::Status MapPropertiesSystem::CheckPropertyEditSupported(
1647 const OverworldPropertyEdit& edit) const {
1648 return CheckFieldSupported(rom_, edit);
1649}
1650
1651absl::StatusOr<int> MapPropertiesSystem::ReadPropertyValue(
1652 const OverworldPropertyEdit& edit) const {
1653 if (!overworld_) {
1654 return absl::FailedPreconditionError("Overworld is not available");
1655 }
1656 const int map_id = EffectivePropertyMapId(
1657 const_cast<zelda3::Overworld*>(overworld_), edit.map_id);
1658 if (!IsValidMapId(map_id)) {
1659 return absl::InvalidArgumentError(
1660 absl::StrFormat("Invalid overworld map: %d", edit.map_id));
1661 }
1662 const auto* map = overworld_->overworld_map(map_id);
1663 if (!map) {
1664 return absl::NotFoundError(
1665 absl::StrFormat("Overworld map 0x%02X is unavailable", map_id));
1666 }
1667
1668 switch (edit.field) {
1669 case OverworldPropertyField::kAreaSize:
1670 return static_cast<int>(map->area_size());
1671 case OverworldPropertyField::kAreaGraphics:
1672 return map->area_graphics();
1673 case OverworldPropertyField::kAreaPalette:
1674 return map->area_palette();
1675 case OverworldPropertyField::kMainPalette:
1676 return map->main_palette();
1677 case OverworldPropertyField::kSpriteGraphics:
1678 return map->sprite_graphics(std::clamp(edit.index, 0, 2));
1679 case OverworldPropertyField::kSpritePalette:
1680 return map->sprite_palette(std::clamp(edit.index, 0, 2));
1681 case OverworldPropertyField::kAnimatedGraphics:
1682 return map->animated_gfx();
1683 case OverworldPropertyField::kCustomTileset:
1684 return map->custom_tileset(std::clamp(edit.index, 0, 7));
1685 case OverworldPropertyField::kMessageId:
1686 return map->message_id();
1687 case OverworldPropertyField::kMusic:
1688 return map->area_music(std::clamp(edit.index, 0, 3));
1689 case OverworldPropertyField::kMosaic:
1690 return *const_cast<zelda3::OverworldMap*>(map)->mutable_mosaic() ? 1 : 0;
1691 case OverworldPropertyField::kMosaicExpanded:
1692 return map->mosaic_expanded()[std::clamp(edit.index, 0, 3)] ? 1 : 0;
1693 case OverworldPropertyField::kAreaSpecificBgColor:
1694 return map->area_specific_bg_color();
1695 case OverworldPropertyField::kSubscreenOverlay:
1696 return map->subscreen_overlay();
1697 }
1698 return absl::InvalidArgumentError("Unknown overworld property field");
1699}
1700
1701absl::Status MapPropertiesSystem::ApplyPropertyEditDirect(
1702 const OverworldPropertyEdit& edit) {
1703 if (!overworld_ || !rom_) {
1704 return absl::FailedPreconditionError(
1705 "Overworld property editing requires loaded overworld data and ROM");
1706 }
1707 const int map_id = EffectivePropertyMapId(overworld_, edit.map_id);
1708 if (!IsValidMapId(map_id)) {
1709 return absl::InvalidArgumentError(
1710 absl::StrFormat("Invalid overworld map: %d", edit.map_id));
1711 }
1712 auto* map = overworld_->mutable_overworld_map(map_id);
1713 if (!map) {
1714 return absl::NotFoundError(
1715 absl::StrFormat("Overworld map 0x%02X is unavailable", map_id));
1716 }
1717
1718 const absl::Status supported = CheckFieldSupported(rom_, edit);
1719 if (!supported.ok()) {
1720 return supported;
1721 }
1722
1723 const int game_state = std::clamp(edit.index, 0, 2);
1724 switch (edit.field) {
1725 case OverworldPropertyField::kAreaSize: {
1726 const auto size = static_cast<zelda3::AreaSizeEnum>(edit.value);
1727 auto status = overworld_->ConfigureMultiAreaMap(map_id, size);
1728 if (!status.ok()) {
1729 return status;
1730 }
1731 RefreshSiblingMapGraphics(map_id, true);
1732 RefreshOverworldMap();
1733 break;
1734 }
1735 case OverworldPropertyField::kAreaGraphics:
1736 map->set_area_graphics(static_cast<uint8_t>(edit.value));
1737 RefreshMapProperties();
1738 if (maps_bmp_) {
1739 (*maps_bmp_)[map_id].set_modified(true);
1740 }
1741 PrepareMapForGraphicsRefresh(map_id);
1742 map->LoadAreaGraphics();
1743 RefreshSiblingMapGraphics(map_id);
1744 RefreshTile16Blockset();
1745 RefreshOverworldMap();
1746 break;
1747 case OverworldPropertyField::kAreaPalette:
1748 map->set_area_palette(static_cast<uint8_t>(edit.value));
1749 RefreshMapProperties();
1750 RefreshMapPalette();
1751 RefreshOverworldMap();
1752 break;
1753 case OverworldPropertyField::kMainPalette:
1754 map->set_main_palette(static_cast<uint8_t>(edit.value));
1755 RefreshMapProperties();
1756 RefreshMapPalette();
1757 RefreshOverworldMap();
1758 break;
1759 case OverworldPropertyField::kSpriteGraphics:
1760 map->set_sprite_graphics(game_state, static_cast<uint8_t>(edit.value));
1761 ForceRefreshGraphics(map_id);
1762 RefreshMapProperties();
1763 RefreshOverworldMap();
1764 break;
1765 case OverworldPropertyField::kSpritePalette:
1766 map->set_sprite_palette(game_state, static_cast<uint8_t>(edit.value));
1767 RefreshMapProperties();
1768 RefreshOverworldMap();
1769 break;
1770 case OverworldPropertyField::kAnimatedGraphics:
1771 map->set_animated_gfx(static_cast<uint8_t>(edit.value));
1772 ForceRefreshGraphics(map_id);
1773 RefreshMapProperties();
1774 RefreshTile16Blockset();
1775 RefreshOverworldMap();
1776 break;
1777 case OverworldPropertyField::kCustomTileset: {
1778 const int slot = std::clamp(edit.index, 0, 7);
1779 map->set_custom_tileset(slot, static_cast<uint8_t>(edit.value));
1780 PrepareMapForGraphicsRefresh(map_id);
1781 map->LoadAreaGraphics();
1782 ForceRefreshGraphics(map_id);
1783 RefreshSiblingMapGraphics(map_id);
1784 RefreshMapProperties();
1785 RefreshTile16Blockset();
1786 RefreshOverworldMap();
1787 break;
1788 }
1789 case OverworldPropertyField::kMessageId:
1790 map->set_message_id(static_cast<uint16_t>(edit.value));
1791 RefreshMapProperties();
1792 RefreshOverworldMap();
1793 break;
1794 case OverworldPropertyField::kMusic: {
1795 const int music_state = std::clamp(edit.index, 0, 3);
1796 *map->mutable_area_music(music_state) = static_cast<uint8_t>(edit.value);
1797 WriteRomByteIfValid(rom_, MusicRomAddressForMapState(map_id, music_state),
1798 static_cast<uint8_t>(edit.value));
1799 RefreshMapProperties();
1800 break;
1801 }
1802 case OverworldPropertyField::kMosaic:
1803 *map->mutable_mosaic() = edit.value != 0;
1804 RefreshMapProperties();
1805 RefreshOverworldMap();
1806 break;
1807 case OverworldPropertyField::kMosaicExpanded:
1808 map->set_mosaic_expanded(std::clamp(edit.index, 0, 3), edit.value != 0);
1809 RefreshMapProperties();
1810 RefreshOverworldMap();
1811 break;
1812 case OverworldPropertyField::kAreaSpecificBgColor:
1813 map->set_area_specific_bg_color(static_cast<uint16_t>(edit.value));
1814 WriteRomWordIfValid(
1816 static_cast<uint16_t>(edit.value));
1817 RefreshMapProperties();
1818 RefreshOverworldMap();
1819 break;
1820 case OverworldPropertyField::kSubscreenOverlay:
1821 map->set_subscreen_overlay(static_cast<uint16_t>(edit.value));
1822 WriteRomWordIfValid(
1824 static_cast<uint16_t>(edit.value));
1825 RefreshMapProperties();
1826 RefreshOverworldMap();
1827 break;
1828 }
1829
1830 rom_->set_dirty(true);
1831 return absl::OkStatus();
1832}
1833
1834void MapPropertiesSystem::RefreshMapProperties() {
1835 if (refresh_map_properties_) {
1836 refresh_map_properties_();
1837 }
1838}
1839
1840void MapPropertiesSystem::RefreshOverworldMap() {
1841 if (refresh_overworld_map_) {
1842 refresh_overworld_map_();
1843 }
1844}
1845
1846absl::Status MapPropertiesSystem::RefreshMapPalette() {
1847 if (refresh_map_palette_) {
1848 return refresh_map_palette_();
1849 }
1850 return absl::OkStatus();
1851}
1852
1853absl::Status MapPropertiesSystem::RefreshTile16Blockset() {
1854 if (refresh_tile16_blockset_) {
1855 return refresh_tile16_blockset_();
1856 }
1857 return absl::OkStatus();
1858}
1859
1860void MapPropertiesSystem::ForceRefreshGraphics(int map_index) {
1861 if (force_refresh_graphics_) {
1862 force_refresh_graphics_(map_index);
1863 }
1864}
1865
1866void MapPropertiesSystem::RefreshSiblingMapGraphics(int map_index,
1867 bool include_self) {
1868 if (!overworld_ || !maps_bmp_ || map_index < 0 ||
1869 map_index >= zelda3::kNumOverworldMaps) {
1870 return;
1871 }
1872
1873 auto* map = overworld_->mutable_overworld_map(map_index);
1874 if (map->area_size() == zelda3::AreaSizeEnum::SmallArea) {
1875 return; // No siblings for small areas
1876 }
1877
1878 int parent_id = map->parent();
1879 std::vector<int> siblings;
1880
1881 switch (map->area_size()) {
1883 siblings = {parent_id, parent_id + 1, parent_id + 8, parent_id + 9};
1884 break;
1886 siblings = {parent_id, parent_id + 1};
1887 break;
1889 siblings = {parent_id, parent_id + 8};
1890 break;
1891 default:
1892 return;
1893 }
1894
1895 for (int sibling : siblings) {
1896 if (sibling >= 0 && sibling < zelda3::kNumOverworldMaps) {
1897 // Skip self unless include_self is true
1898 if (sibling == map_index && !include_self) {
1899 continue;
1900 }
1901
1902 // Mark as modified FIRST
1903 (*maps_bmp_)[sibling].set_modified(true);
1904
1905 // Load graphics from ROM
1906 PrepareMapForGraphicsRefresh(sibling);
1907 overworld_->mutable_overworld_map(sibling)->LoadAreaGraphics();
1908
1909 // CRITICAL FIX: Force immediate refresh on the sibling
1910 // This will trigger the callback to OverworldEditor's
1911 // RefreshChildMapOnDemand
1912 ForceRefreshGraphics(sibling);
1913 }
1914 }
1915
1916 // After marking all siblings, trigger a refresh
1917 // This ensures all marked maps get processed
1918 RefreshOverworldMap();
1919}
1920
1921void MapPropertiesSystem::DrawMosaicControls(int current_map) {
1922 auto rom_version = zelda3::OverworldVersionHelper::GetVersion(*rom_);
1924 ImGui::Separator();
1925 ImGui::Text(tr("Mosaic Effects (per direction):"));
1926
1927 auto* current_map_ptr = overworld_->mutable_overworld_map(current_map);
1928 std::array<bool, 4> mosaic_expanded = current_map_ptr->mosaic_expanded();
1929 const char* direction_names[] = {"North", "South", "East", "West"};
1930
1931 for (int i = 0; i < 4; i++) {
1932 if (ImGui::Checkbox(direction_names[i], &mosaic_expanded[i])) {
1933 ApplyPropertyEdit({current_map, OverworldPropertyField::kMosaicExpanded,
1934 i, mosaic_expanded[i] ? 1 : 0});
1935 }
1936 }
1937 } else {
1938 bool mosaic =
1939 *overworld_->mutable_overworld_map(current_map)->mutable_mosaic();
1940 if (ImGui::Checkbox(tr("Mosaic Effect"), &mosaic)) {
1941 ApplyPropertyEdit(
1942 {current_map, OverworldPropertyField::kMosaic, 0, mosaic ? 1 : 0});
1943 }
1944 }
1945}
1946
1947void MapPropertiesSystem::DrawOverlayControls(int current_map,
1948 bool& show_overlay_preview) {
1949 auto rom_version = zelda3::OverworldVersionHelper::GetVersion(*rom_);
1950
1951 // Determine if this is a special overworld map (0x80-0x9F)
1952 bool is_special_overworld_map = (current_map >= 0x80 && current_map < 0xA0);
1953
1954 if (is_special_overworld_map) {
1955 // Special overworld maps (0x80-0x9F) serve as visual effect sources
1956 ImGui::TextColored(ImVec4(0.4f, 0.8f, 1.0f, 1.0f),
1957 ICON_MD_INFO " Special Area Map (0x%02X)", current_map);
1958 ImGui::Separator();
1959 ImGui::TextWrapped(tr(
1960 "This is a special area map (0x80-0x9F) used as a visual effect "
1961 "source. These maps provide the graphics data for subscreen overlays "
1962 "like fog, rain, forest canopy, and sky backgrounds that appear on "
1963 "normal maps (0x00-0x7F)."));
1964 ImGui::Spacing();
1965 ImGui::TextWrapped(tr(
1966 "You can edit the tile16 data here to customize how the visual effects "
1967 "appear when referenced by other maps."));
1968 } else {
1969 // Light World (0x00-0x3F) and Dark World (0x40-0x7F) maps support subscreen
1970 // overlays
1971
1972 // Comprehensive help section
1973 ImGui::TextColored(ImVec4(0.4f, 0.8f, 1.0f, 1.0f),
1974 ICON_MD_HELP_OUTLINE " Visual Effects Overview");
1975 ImGui::SameLine();
1976 if (ImGui::Button(ICON_MD_INFO "##HelpButton")) {
1979 .c_str());
1980 }
1981
1982 if (ImGui::BeginPopup(gui::MakePopupId(gui::EditorNames::kOverworld,
1984 .c_str())) {
1985 ImGui::Text(ICON_MD_HELP " Understanding Overlay Types");
1986 ImGui::Separator();
1987
1988 ImGui::TextColored(ImVec4(0.4f, 1.0f, 0.4f, 1.0f), ICON_MD_LAYERS
1989 " 1. Subscreen Overlays (Visual Effects)");
1990 ImGui::Indent();
1991 ImGui::BulletText(tr("Displayed as semi-transparent layers"));
1992 ImGui::BulletText(tr("Reference special area maps (0x80-0x9F)"));
1993 ImGui::BulletText(tr("Examples: fog, rain, forest canopy, sky"));
1994 ImGui::BulletText(tr("Purely visual - don't affect collision"));
1995 ImGui::Unindent();
1996
1997 ImGui::Spacing();
1998 ImGui::TextColored(ImVec4(1.0f, 0.8f, 0.4f, 1.0f),
1999 ICON_MD_EDIT_NOTE " 2. Map Overlays (Interactive)");
2000 ImGui::Indent();
2001 ImGui::BulletText(tr("Dynamic tile16 changes on the map"));
2002 ImGui::BulletText(tr("Used for bridges appearing, holes opening"));
2003 ImGui::BulletText(tr("Stored as tile16 ID arrays"));
2004 ImGui::BulletText(tr("Affect collision and interaction"));
2005 ImGui::BulletText(tr("Triggered by game events/progression"));
2006 ImGui::Unindent();
2007
2008 ImGui::Separator();
2009 ImGui::TextWrapped(
2010 tr("Note: Subscreen overlays are what you configure here. "
2011 "Map overlays are event-driven and edited separately."));
2012
2013 ImGui::EndPopup();
2014 }
2015 ImGui::Separator();
2016
2017 // Subscreen Overlay Section
2018 ImGui::Text(ICON_MD_LAYERS " Subscreen Overlay (Visual Effects)");
2019
2020 uint16_t current_overlay =
2021 overworld_->overworld_map(current_map)->subscreen_overlay();
2022 if (gui::InputHexWord("Visual Effect Map ID", &current_overlay,
2023 kInputFieldSize + 20)) {
2024 ApplyPropertyEdit({current_map, OverworldPropertyField::kSubscreenOverlay,
2025 0, current_overlay});
2026 }
2027 if (ImGui::IsItemHovered()) {
2028 ImGui::SetTooltip(
2029 tr("References a special area map (0x80-0x9F) for visual effects.\n"
2030 "The referenced map's tile16 data is drawn as a semi-transparent\n"
2031 "layer on top of or behind this area for atmospheric effects."));
2032 }
2033
2034 // Show subscreen overlay description with color coding
2035 std::string overlay_desc = GetOverlayDescription(current_overlay);
2036 if (current_overlay == 0x00FF) {
2037 ImGui::TextColored(ImVec4(0.6f, 0.6f, 0.6f, 1.0f), ICON_MD_CHECK " %s",
2038 overlay_desc.c_str());
2039 } else if (current_overlay >= 0x80 && current_overlay < 0xA0) {
2040 ImGui::TextColored(ImVec4(0.4f, 1.0f, 0.4f, 1.0f),
2041 ICON_MD_VISIBILITY " %s", overlay_desc.c_str());
2042 } else {
2043 ImGui::TextColored(ImVec4(1.0f, 0.8f, 0.4f, 1.0f),
2044 ICON_MD_HELP_OUTLINE " %s", overlay_desc.c_str());
2045 }
2046
2047 // Preview checkbox with better labeling
2048 ImGui::Spacing();
2049 if (ImGui::Checkbox(ICON_MD_PREVIEW " Preview Visual Effect on Map",
2050 &show_overlay_preview)) {
2051 // Toggle subscreen overlay preview
2052 }
2053 if (ImGui::IsItemHovered()) {
2054 ImGui::SetTooltip(tr(
2055 "Shows a semi-transparent preview of the visual effect overlay\n"
2056 "drawn on top of the current map in the editor canvas.\n\n"
2057 "This preview shows how the subscreen overlay will appear in-game."));
2058 }
2059
2060 ImGui::Separator();
2061
2062 // Interactive/Dynamic Map Overlay Section (for vanilla ROMs)
2063 if (rom_version == zelda3::OverworldVersion::kVanilla) {
2064 ImGui::TextColored(ImVec4(1.0f, 0.8f, 0.4f, 1.0f),
2065 ICON_MD_EDIT_NOTE " Map Overlay (Interactive)");
2066 ImGui::SameLine();
2067 if (ImGui::Button(ICON_MD_INFO "##MapOverlayHelp")) {
2068 ImGui::OpenPopup(
2071 .c_str());
2072 }
2073 if (ImGui::BeginPopup(
2076 .c_str())) {
2077 ImGui::Text(ICON_MD_HELP " Map Overlays (Interactive Tile Changes)");
2078 ImGui::Separator();
2079 ImGui::TextWrapped(
2080 tr("Map overlays are different from visual effect overlays. "
2081 "They contain tile16 data that dynamically replaces tiles on "
2082 "the map based on game events or progression."));
2083 ImGui::Spacing();
2084 ImGui::Text(tr("Common uses:"));
2085 ImGui::BulletText(tr("Bridges appearing over water"));
2086 ImGui::BulletText(tr("Holes revealing secret passages"));
2087 ImGui::BulletText(tr("Rocks/bushes being moved"));
2088 ImGui::BulletText(tr("Environmental changes from story events"));
2089 ImGui::Spacing();
2090 ImGui::TextWrapped(
2091 tr("These are triggered by game code and stored as separate "
2092 "tile data arrays in the ROM. ZSCustomOverworld v3+ provides "
2093 "extended control over these features."));
2094 ImGui::EndPopup();
2095 }
2096
2097 auto* current_map_ptr = overworld_->overworld_map(current_map);
2098 ImGui::Spacing();
2099 if (current_map_ptr->has_overlay()) {
2100 ImGui::TextColored(ImVec4(0.4f, 1.0f, 0.4f, 1.0f),
2101 ICON_MD_CHECK " Overlay ID: 0x%04X",
2102 current_map_ptr->overlay_id());
2103 ImGui::Text(ICON_MD_STORAGE " Data Size: %d bytes",
2104 static_cast<int>(current_map_ptr->overlay_data().size()));
2105 ImGui::TextColored(ImVec4(0.8f, 0.8f, 0.4f, 1.0f),
2106 ICON_MD_INFO " Read-only in vanilla ROM");
2107 } else {
2108 ImGui::TextColored(ImVec4(0.6f, 0.6f, 0.6f, 1.0f),
2109 ICON_MD_BLOCK " No map overlay data for this area");
2110 }
2111 }
2112
2113 // Show version and capability info
2114 ImGui::Separator();
2115 if (rom_version == zelda3::OverworldVersion::kVanilla) {
2116 ImGui::TextColored(ImVec4(0.7f, 0.7f, 0.7f, 1.0f),
2117 ICON_MD_INFO " Vanilla ROM");
2118 ImGui::BulletText(tr("Visual effects use maps 0x80-0x9F"));
2119 ImGui::BulletText(tr("Map overlays are read-only"));
2120 } else {
2121 const char* version_name =
2123 ImGui::TextColored(ImVec4(0.4f, 1.0f, 0.4f, 1.0f), ICON_MD_UPGRADE " %s",
2124 version_name);
2125 ImGui::BulletText(tr("Enhanced visual effect control"));
2127 ImGui::BulletText(tr("Extended overlay system"));
2128 ImGui::BulletText(tr("Custom area sizes support"));
2129 }
2130 }
2131 }
2132}
2133
2134std::string MapPropertiesSystem::GetOverlayDescription(uint16_t overlay_id) {
2135 if (overlay_id == 0x0093) {
2136 return "Triforce Room Curtain";
2137 } else if (overlay_id == 0x0094) {
2138 return "Under the Bridge";
2139 } else if (overlay_id == 0x0095) {
2140 return "Sky Background (LW Death Mountain)";
2141 } else if (overlay_id == 0x0096) {
2142 return "Pyramid Background";
2143 } else if (overlay_id == 0x0097) {
2144 return "First Fog Overlay (Master Sword Area)";
2145 } else if (overlay_id == 0x009C) {
2146 return "Lava Background (DW Death Mountain)";
2147 } else if (overlay_id == 0x009D) {
2148 return "Second Fog Overlay (Lost Woods/Skull Woods)";
2149 } else if (overlay_id == 0x009E) {
2150 return "Tree Canopy (Forest)";
2151 } else if (overlay_id == 0x009F) {
2152 return "Rain Effect (Misery Mire)";
2153 } else if (overlay_id == 0x00FF) {
2154 return "No Overlay";
2155 } else {
2156 return "Custom overlay";
2157 }
2158}
2159
2160void MapPropertiesSystem::DrawOverlayPreviewOnMap(int current_map,
2161 int current_world,
2162 bool show_overlay_preview) {
2163 gfx::ScopedTimer timer("map_properties_draw_overlay_preview");
2164
2165 if (!show_overlay_preview || !maps_bmp_ || !canvas_)
2166 return;
2167
2168 // Get subscreen overlay information based on ROM version and map type
2169 uint16_t overlay_id = 0x00FF;
2170 bool has_subscreen_overlay = false;
2171
2172 bool is_special_overworld_map = (current_map >= 0x80 && current_map < 0xA0);
2173
2174 if (is_special_overworld_map) {
2175 // Special overworld maps (0x80-0x9F) do not support subscreen overlays
2176 return;
2177 }
2178
2179 // Light World (0x00-0x3F) and Dark World (0x40-0x7F) maps support subscreen
2180 // overlays for all versions
2181 overlay_id = overworld_->overworld_map(current_map)->subscreen_overlay();
2182 has_subscreen_overlay = (overlay_id != 0x00FF);
2183
2184 if (!has_subscreen_overlay)
2185 return;
2186
2187 // Map subscreen overlay ID to special area map for bitmap
2188 int overlay_map_index = -1;
2189 if (overlay_id >= 0x80 && overlay_id < 0xA0) {
2190 overlay_map_index = overlay_id;
2191 }
2192
2193 if (overlay_map_index < 0 || overlay_map_index >= zelda3::kNumOverworldMaps)
2194 return;
2195
2196 // Get the subscreen overlay map's bitmap
2197 const auto& overlay_bitmap = (*maps_bmp_)[overlay_map_index];
2198 if (!overlay_bitmap.is_active() || !overlay_bitmap.texture())
2199 return;
2200
2201 // Calculate position for subscreen overlay preview on the current map
2202 int current_map_x = current_map % 8;
2203 int current_map_y = current_map / 8;
2204 if (current_world == 1) {
2205 current_map_x = (current_map - 0x40) % 8;
2206 current_map_y = (current_map - 0x40) / 8;
2207 } else if (current_world == 2) {
2208 current_map_x = (current_map - 0x80) % 8;
2209 current_map_y = (current_map - 0x80) / 8;
2210 }
2211
2212 int scale = static_cast<int>(canvas_->global_scale());
2213 int map_x = current_map_x * kOverworldMapSize * scale;
2214 int map_y = current_map_y * kOverworldMapSize * scale;
2215
2216 // Determine if this is a background or foreground subscreen overlay
2217 bool is_background_overlay =
2218 (overlay_id == 0x0095 || overlay_id == 0x0096 || overlay_id == 0x009C);
2219
2220 // Set alpha for semi-transparent preview
2221 ImU32 overlay_color =
2222 is_background_overlay ? IM_COL32(255, 255, 255, 128)
2223 : // Background subscreen overlays - lighter
2224 IM_COL32(255, 255, 255,
2225 180); // Foreground subscreen overlays - more opaque
2226
2227 // Draw the subscreen overlay bitmap with semi-transparency
2228 canvas_->draw_list()->AddImage(
2229 (ImTextureID)(intptr_t)overlay_bitmap.texture(), ImVec2(map_x, map_y),
2230 ImVec2(map_x + kOverworldMapSize * scale,
2231 map_y + kOverworldMapSize * scale),
2232 ImVec2(0, 0), ImVec2(1, 1), overlay_color);
2233}
2234
2235void MapPropertiesSystem::DrawViewPopup() {
2236 if (ImGui::BeginPopup(gui::MakePopupId(gui::EditorNames::kOverworld,
2238 .c_str())) {
2239 ImGui::PushID("ViewPopup"); // Fix ImGui duplicate ID warnings
2240
2241 // Use theme-aware spacing instead of hardcoded constants
2243 float padding = gui::LayoutHelpers::GetButtonPadding();
2244 gui::StyleVarGuard popup_style_guard(
2245 {{ImGuiStyleVar_ItemSpacing, ImVec2(spacing, spacing)},
2246 {ImGuiStyleVar_FramePadding, ImVec2(padding, padding)}});
2247
2248 ImGui::Text(tr("View Controls"));
2249 ImGui::Separator();
2250
2251 // Horizontal layout for view controls
2252 if (ImGui::Button(ICON_MD_ZOOM_OUT, ImVec2(kIconButtonWidth, 0))) {
2253 float new_scale = std::max(kOverworldMinZoom,
2254 canvas_->global_scale() - kOverworldZoomStep);
2255 canvas_->set_global_scale(new_scale);
2256 }
2257 HOVER_HINT("Zoom out on the canvas");
2258 ImGui::SameLine();
2259 if (ImGui::Button(ICON_MD_ZOOM_IN, ImVec2(kIconButtonWidth, 0))) {
2260 float new_scale = std::min(kOverworldMaxZoom,
2261 canvas_->global_scale() + kOverworldZoomStep);
2262 canvas_->set_global_scale(new_scale);
2263 }
2264 HOVER_HINT("Zoom in on the canvas");
2265 ImGui::SameLine();
2266 if (ImGui::Button(ICON_MD_OPEN_IN_FULL, ImVec2(kIconButtonWidth, 0))) {
2267 canvas_->set_global_scale(1.0f);
2268 }
2269 HOVER_HINT("Reset zoom to 100%");
2270
2271 ImGui::PopID(); // Pop ViewPopup ID scope
2272 ImGui::EndPopup();
2273 }
2274}
2275
2276void MapPropertiesSystem::DrawQuickAccessPopup() {
2277 if (ImGui::BeginPopup(gui::MakePopupId(gui::EditorNames::kOverworld,
2279 .c_str())) {
2280 ImGui::PushID("QuickPopup"); // Fix ImGui duplicate ID warnings
2281
2282 // Use theme-aware spacing instead of hardcoded constants
2284 float padding = gui::LayoutHelpers::GetButtonPadding();
2285 gui::StyleVarGuard popup_style_guard(
2286 {{ImGuiStyleVar_ItemSpacing, ImVec2(spacing, spacing)},
2287 {ImGuiStyleVar_FramePadding, ImVec2(padding, padding)}});
2288
2289 ImGui::Text(tr("Quick Access"));
2290 ImGui::Separator();
2291
2292 // Horizontal layout for quick access buttons
2293 if (ImGui::Button(ICON_MD_GRID_VIEW, ImVec2(kIconButtonWidth, 0))) {
2294 // This would need to be connected to the Tile16 editor toggle
2295 // For now, just show the option
2296 }
2297 HOVER_HINT("Open Tile16 Editor (Ctrl+T)");
2298 ImGui::SameLine();
2299
2300 if (ImGui::Button(ICON_MD_CONTENT_COPY, ImVec2(kIconButtonWidth, 0))) {
2301 // This would need to be connected to the copy map function
2302 // For now, just show the option
2303 }
2304 HOVER_HINT("Copy current map to clipboard");
2305 ImGui::SameLine();
2306
2307 if (ImGui::Button(ICON_MD_LOCK, ImVec2(kIconButtonWidth, 0))) {
2308 // This would need to be connected to the map lock toggle
2309 // For now, just show the option
2310 }
2311 HOVER_HINT("Lock/unlock current map (Ctrl+L)");
2312
2313 ImGui::PopID(); // Pop QuickPopup ID scope
2314 ImGui::EndPopup();
2315 }
2316}
2317
2318} // namespace editor
2319} // namespace yaze
The Rom class is used to load, save, and modify Rom data. This is a generic SNES ROM container and do...
Definition rom.h:28
auto size() const
Definition rom.h:150
bool is_loaded() const
Definition rom.h:144
ResourceLabelEditCallback resource_label_edit_callback_
void PrepareMapForGraphicsRefresh(int map_index)
absl::Status ApplyPropertyEdit(const OverworldPropertyEdit &edit)
void DrawOverlayEditor(int current_map, bool &show_overlay_editor)
std::function< void(int, bool)> map_selection_callback_
absl::Status ApplyPropertyEdits(const std::vector< OverworldPropertyEdit > &edits, const std::string &description={})
void DrawMapPropertiesPanel(int current_map, bool &show_map_properties_panel)
void DrawSpritePropertiesTab(int current_map)
void DrawBasicPropertiesTab(int current_map)
void DrawCustomFeaturesTab(int current_map)
void SetCurrentGameState(int game_state)
void SetupCanvasContextMenu(gui::Canvas &canvas, int current_map, bool &current_map_lock, bool &show_map_properties_panel, bool &show_custom_bg_color_editor, bool &show_overlay_editor, int current_mode=0, project::YazeProject *project=nullptr, SharedClipboard *shared_clipboard=nullptr)
void DrawCustomBackgroundColorEditor(int current_map, bool &show_custom_bg_color_editor)
void DrawCanvasToolbar(int &current_world, int &current_map, bool &current_map_lock, bool &show_map_properties_panel, bool &show_custom_bg_color_editor, bool &show_overlay_editor, bool &show_overlay_preview, int &game_state, EditingMode &current_mode, EntityEditMode &entity_edit_mode)
void DrawTileGraphicsTab(int current_map)
std::string GetOverlayDescription(uint16_t overlay_id)
RAII timer for automatic timing management.
SNES Color container.
Definition snes_color.h:110
constexpr uint16_t snes() const
Get SNES 15-bit color.
Definition snes_color.h:193
Modern, robust canvas for drawing and manipulating graphics.
Definition canvas.h:64
void SetUsageMode(CanvasUsage usage)
Definition canvas.cc:290
void ClearContextMenuItems()
Definition canvas.cc:863
void AddContextMenuItem(const gui::CanvasMenuItem &item)
Definition canvas.cc:840
static float GetButtonPadding()
static float GetStandardSpacing()
RAII guard for ImGui style vars.
Definition style_guard.h:68
Represents a single Overworld map screen.
const std::array< bool, 4 > & mosaic_expanded() const
static bool SupportsCustomBGColors(OverworldVersion version)
Check if ROM supports custom background colors per area (v2+)
static OverworldVersion GetVersion(const Rom &rom)
Detect ROM version from ASM marker byte.
static bool SupportsAreaEnum(OverworldVersion version)
Check if ROM supports area enum system (v3+ only)
static bool SupportsAnimatedGFX(OverworldVersion version)
Check if ROM supports animated GFX selection (v3+)
static bool SupportsExpandedSpace(OverworldVersion version)
Check if ROM uses expanded ROM space for overworld data.
static const char * GetVersionName(OverworldVersion version)
Get human-readable version name for display/logging.
Represents the full Overworld data, light and dark world.
Definition overworld.h:389
auto is_loaded() const
Definition overworld.h:728
auto overworld_map(int i) const
Definition overworld.h:662
auto mutable_overworld_map(int i)
Definition overworld.h:668
#define ICON_MD_BLOCK
Definition icons.h:269
#define ICON_MD_GRID_VIEW
Definition icons.h:897
#define ICON_MD_COLORIZE
Definition icons.h:441
#define ICON_MD_ACCOUNT_TREE
Definition icons.h:83
#define ICON_MD_INFO
Definition icons.h:993
#define ICON_MD_STORAGE
Definition icons.h:1865
#define ICON_MD_PETS
Definition icons.h:1431
#define ICON_MD_UPGRADE
Definition icons.h:2047
#define ICON_MD_LOCK_OPEN
Definition icons.h:1142
#define ICON_MD_LOCK
Definition icons.h:1140
#define ICON_MD_LIGHTBULB
Definition icons.h:1083
#define ICON_MD_PHOTO_SIZE_SELECT_LARGE
Definition icons.h:1459
#define ICON_MD_OFFLINE_BOLT
Definition icons.h:1344
#define ICON_MD_PLAY_ARROW
Definition icons.h:1479
#define ICON_MD_CHECK
Definition icons.h:397
#define ICON_MD_DRAW
Definition icons.h:625
#define ICON_MD_TUNE
Definition icons.h:2022
#define ICON_MD_ZOOM_OUT
Definition icons.h:2196
#define ICON_MD_OPEN_IN_FULL
Definition icons.h:1353
#define ICON_MD_MAP
Definition icons.h:1173
#define ICON_MD_LABEL
Definition icons.h:1053
#define ICON_MD_MESSAGE
Definition icons.h:1201
#define ICON_MD_VISIBILITY
Definition icons.h:2101
#define ICON_MD_GRASS
Definition icons.h:891
#define ICON_MD_FORMAT_COLOR_FILL
Definition icons.h:830
#define ICON_MD_EDIT
Definition icons.h:645
#define ICON_MD_DOOR_BACK
Definition icons.h:612
#define ICON_MD_CASTLE
Definition icons.h:380
#define ICON_MD_AUTO_FIX_HIGH
Definition icons.h:218
#define ICON_MD_MUSIC_NOTE
Definition icons.h:1264
#define ICON_MD_RESTORE
Definition icons.h:1605
#define ICON_MD_CONTENT_PASTE
Definition icons.h:467
#define ICON_MD_ASPECT_RATIO
Definition icons.h:192
#define ICON_MD_LAYERS
Definition icons.h:1068
#define ICON_MD_ANIMATION
Definition icons.h:157
#define ICON_MD_DOOR_FRONT
Definition icons.h:613
#define ICON_MD_BLUR_ON
Definition icons.h:281
#define ICON_MD_IMAGE
Definition icons.h:982
#define ICON_MD_CLEAR
Definition icons.h:416
#define ICON_MD_PREVIEW
Definition icons.h:1512
#define ICON_MD_FAVORITE
Definition icons.h:727
#define ICON_MD_HELP_OUTLINE
Definition icons.h:935
#define ICON_MD_ADD_LOCATION
Definition icons.h:100
#define ICON_MD_ZOOM_IN
Definition icons.h:2194
#define ICON_MD_MOUSE
Definition icons.h:1251
#define ICON_MD_EDIT_NOTE
Definition icons.h:650
#define ICON_MD_PALETTE
Definition icons.h:1370
#define ICON_MD_OPEN_IN_NEW
Definition icons.h:1354
#define ICON_MD_PEST_CONTROL_RODENT
Definition icons.h:1430
#define ICON_MD_CONTENT_COPY
Definition icons.h:465
#define ICON_MD_PUSH_PIN
Definition icons.h:1529
#define ICON_MD_COLOR_LENS
Definition icons.h:440
#define ICON_MD_PHOTO
Definition icons.h:1451
#define ICON_MD_CYCLONE
Definition icons.h:514
#define ICON_MD_GAMEPAD
Definition icons.h:866
#define ICON_MD_HELP
Definition icons.h:933
#define HOVER_HINT(string)
Definition macro.h:24
void WriteRomWordIfValid(Rom *rom, int address, uint16_t value)
int EffectivePropertyMapId(zelda3::Overworld *overworld, int map_id)
absl::Status CheckFieldSupported(const Rom *rom, const OverworldPropertyEdit &edit)
OverworldMapMetadataClipboard CaptureMapMetadataClipboard(const zelda3::Overworld &overworld, int map_id)
bool WriteRomByteIfValid(Rom *rom, int address, uint8_t value)
std::vector< OverworldPropertyEdit > BuildOverworldMetadataPasteEdits(int target_map_id, const OverworldMapMetadataClipboard &clipboard, OverworldMapMetadataClipboardScope requested_scope)
absl::Status RenameProjectResourceLabel(project::YazeProject *project, const std::string &type, int id, const std::string &label)
constexpr const char * kAreaSizeNames[]
bool CanPasteOverworldMapMetadata(const OverworldMapMetadataClipboard &clipboard, OverworldMapMetadataClipboardScope requested_scope)
constexpr unsigned int kOverworldMapSize
constexpr float kOverworldMaxZoom
constexpr float kTableColumnWorld
constexpr float kOverworldMinZoom
constexpr const char * kWorldNames[]
constexpr float kHexByteInputWidth
const char * OverworldMapMetadataClipboardScopeName(OverworldMapMetadataClipboardScope scope)
constexpr float kTableColumnMap
OverworldMapMetadata BuildOverworldMapMetadata(const zelda3::Overworld &overworld, const Rom *rom, const project::YazeProject *project, int map_id, int game_state)
constexpr float kTableColumnAreaSize
constexpr float kOverworldZoomStep
constexpr float kComboGameStateWidth
constexpr float kInputFieldSize
Definition entity.cc:28
constexpr const char * kGameStateNames[]
Definition ui_constants.h:8
constexpr float kIconButtonWidth
std::string DescribeOverworldMapMetadataClipboard(const OverworldMapMetadataClipboard &clipboard)
constexpr float kHexWordInputWidth
constexpr float kComboAreaSizeWidth
constexpr float kTableColumnLock
constexpr const char * kOverworld
Definition popup_id.h:53
constexpr const char * kPalettesPopup
Definition popup_id.h:75
constexpr const char * kGraphicsPopup
Definition popup_id.h:74
constexpr const char * kOverlayTypesHelp
Definition popup_id.h:79
constexpr const char * kInteractiveOverlayHelp
Definition popup_id.h:80
constexpr const char * kViewPopup
Definition popup_id.h:77
constexpr const char * kQuickPopup
Definition popup_id.h:78
constexpr const char * kConfigPopup
Definition popup_id.h:76
bool InputHexWord(const char *label, uint16_t *data, float input_width, bool no_step)
Definition input.cc:355
bool BeginThemedTabBar(const char *id, ImGuiTabBarFlags flags)
A stylized tab bar with "Mission Control" branding.
void EndThemedTabBar()
bool ToggleButton(const char *label, bool active, const ImVec2 &size)
ImVec4 ConvertSnesColorToImVec4(const gfx::SnesColor &color)
Convert SnesColor to standard ImVec4 for display.
Definition color.cc:23
bool InputHexWordCustom(const char *label, uint16_t *data, float input_width)
Definition input.cc:723
std::string MakePopupId(size_t session_id, const std::string &editor_name, const std::string &popup_name)
Generate session-aware popup IDs to prevent conflicts in multi-editor layouts.
Definition popup_id.h:23
gfx::SnesColor ConvertImVec4ToSnesColor(const ImVec4 &color)
Convert standard ImVec4 to SnesColor.
Definition color.cc:36
bool InputHexByte(const char *label, uint8_t *data, float input_width, bool no_step)
Definition input.cc:376
constexpr int OverworldCustomAreaSpecificBGEnabled
constexpr int kSpecialWorldMapIdStart
constexpr int kNumOverworldMaps
Definition common.h:85
constexpr int kOverworldMusicBeginning
Definition overworld.h:125
AreaSizeEnum
Area size enumeration for v3+ ROMs.
constexpr int kOverworldMusicDarkWorld
Definition overworld.h:129
constexpr int OverworldCustomASMHasBeenApplied
Definition common.h:89
constexpr int kOverworldMusicAgahnim
Definition overworld.h:128
@ kVanilla
0xFF in ROM, no ZScream ASM applied
constexpr int kOverworldMusicMasterSword
Definition overworld.h:127
constexpr int kOverworldMusicZelda
Definition overworld.h:126
constexpr int kDarkWorldMapIdStart
constexpr int OverworldCustomSubscreenOverlayEnabled
constexpr int OverworldCustomAreaSpecificBGPalette
constexpr int OverworldCustomSubscreenOverlayArray
#define RETURN_IF_ERROR(expr)
Definition snes.cc:22
SNES color in 15-bit RGB format (BGR555)
OverworldMapMetadataClipboard overworld_map_metadata
Definition editor.h:123
Declarative menu item definition.
Definition canvas_menu.h:64
std::vector< CanvasMenuItem > subitems
Definition canvas_menu.h:94
std::function< bool()> enabled_condition
Definition canvas_menu.h:81
std::function< void()> callback
Definition canvas_menu.h:75
static CanvasMenuItem WithPopup(const std::string &lbl, const std::string &popup_id, std::function< void()> render_callback)
static CanvasMenuItem Disabled(const std::string &lbl)
Modern project structure with comprehensive settings consolidation.
Definition project.h:172
struct snes_color snes_color
SNES color in 15-bit RGB format (BGR555)