19#include "absl/status/status.h"
20#include "absl/strings/str_format.h"
21#include "absl/types/span.h"
22#include "imgui/imgui.h"
85 const absl::Status status =
88 LOG_WARN(
"DungeonEditorV2",
"Minecart project rebind failed: %s",
103 [
this, editor_manager, session_id](
104 const std::vector<zelda3::TrackCollisionResult>& preview,
106 if (editor_manager !=
nullptr &&
107 !editor_manager->IsCurrentProjectContextOwnedBySession(
109 return absl::FailedPreconditionError(
110 "Minecart collision apply requires its project context to be "
115 if (editor_manager ==
nullptr) {
123 [editor_manager, session_id](
125 if (!editor_manager->IsCurrentProjectContextOwnedBySession(
127 return absl::FailedPreconditionError(
128 "Minecart overlay edit requires its project context to be "
131 editor_manager->GetCurrentProject()->dungeon_overlay = overlay;
132 editor_manager->MarkCurrentProjectDirty();
133 return absl::OkStatus();
136 [editor_manager, session_id]() -> absl::Status {
137 if (!editor_manager->IsCurrentProjectContextOwnedBySession(
139 return absl::FailedPreconditionError(
140 "Minecart overlay draft requires its project context to be "
143 editor_manager->MarkCurrentProjectDirty();
144 return absl::OkStatus();
147 [editor_manager, session_id]() -> absl::Status {
148 if (!editor_manager->IsCurrentProjectContextOwnedBySession(
150 return absl::FailedPreconditionError(
151 "Minecart project save requires its project context to be "
154 return editor_manager->SaveProject();
160 return absl::OkStatus();
163 return absl::FailedPreconditionError(
164 "Enable Custom Dungeon Objects before opening Minecart Tracks");
169 return absl::FailedPreconditionError(
170 "Minecart Tracks can only be registered for the active project "
174 auto minecart_panel = std::make_unique<MinecartTrackEditorPanel>();
176 minecart_panel->SetRooms(&
rooms_);
177 minecart_panel->SetRoomNavigationCallback(
184 std::move(minecart_panel));
190 [
this](
int, std::unique_ptr<DungeonCanvasViewer>& viewer) {
191 if (viewer !=
nullptr) {
208 return absl::OkStatus();
212 const bool custom_objects_enabled =
214 const bool custom_objects_state_changed =
216 const uint64_t generation =
219 !custom_objects_state_changed) {
223 if (custom_objects_state_changed) {
232 if (viewer !=
nullptr) {
233 viewer->InvalidateExternalSpriteResources();
237 [&](
int,
auto& viewer) { invalidate_sprite_resources(viewer.get()); });
251 constexpr const char* kPrefix =
"dungeon.room_";
252 constexpr size_t kPrefixLength = 13;
253 if (card_id.rfind(kPrefix, 0) != 0 || card_id.size() <= kPrefixLength) {
256 return std::all_of(card_id.begin() + kPrefixLength, card_id.end(),
257 [](
unsigned char ch) { return std::isdigit(ch) != 0; });
261 constexpr std::array<const char*, 3> kWorkbenchNavigationPanelIds = {
268 std::any_of(kWorkbenchNavigationPanelIds.begin(),
269 kWorkbenchNavigationPanelIds.end(),
270 [&card_id](
const char* navigation_id) {
271 return card_id == navigation_id;
278 : rom_(rom), room_loader_(rom), room_selector_(rom), rooms_(rom) {
341 return absl::FailedPreconditionError(
"ROM not loaded");
347 std::array<zelda3::RoomEntrance, zelda3::kNumDungeonEntranceSlots>
349 std::array<zelda3::DungeonSpawnPoint, zelda3::kNumDungeonSpawnPoints>
350 refreshed_spawn_points;
354 struct RefreshedRoom {
358 std::vector<std::pair<int, bool>> materialized_rooms;
360 [&materialized_rooms](
int room_id,
const zelda3::Room& room) {
361 materialized_rooms.emplace_back(room_id, room.
IsLoaded());
364 std::vector<RefreshedRoom> refreshed_rooms;
365 refreshed_rooms.reserve(materialized_rooms.size());
366 for (
const auto& [room_id, was_loaded] : materialized_rooms) {
374 refreshed_rooms.push_back({room_id, std::move(refreshed_room)});
392 [&prepare_viewer](
int room_id,
393 std::unique_ptr<DungeonCanvasViewer>& viewer) {
394 prepare_viewer(viewer.get(), room_id);
397 const int compare_room_id =
407 (void)object_editor->ClearSelection();
408 object_editor->ClearHistory();
425 for (
auto& refreshed : refreshed_rooms) {
428 *room = std::move(refreshed.room);
455 const int resolved_palette_id = current_room->ResolveDungeonPaletteId();
457 resolved_palette_id >= 0 &&
458 resolved_palette_id <
461 ?
static_cast<uint64_t
>(resolved_palette_id)
490 [&bind_viewer](
int room_id,
491 std::unique_ptr<DungeonCanvasViewer>& viewer) {
492 bind_viewer(viewer.get(), room_id);
523 current_viewer = viewer->get();
525 if (current_viewer) {
529 return absl::OkStatus();
538 bool legacy_imported =
false;
539 const bool water_fill_save_enabled =
541 std::vector<zelda3::WaterFillZoneEntry> zones;
545 zones = std::move(zones_or.value());
547 LOG_WARN(
"DungeonEditorV2",
"WaterFillTable parse failed: %s",
548 zones_or.status().message().data());
551 absl::StrFormat(
"WaterFill table parse failed: %s",
552 zones_or.status().message()),
558 std::string sym_path;
565 if (legacy_or.ok()) {
566 zones = std::move(legacy_or.value());
567 legacy_imported = !zones.empty();
569 LOG_WARN(
"DungeonEditorV2",
"Legacy water gate import failed: %s",
570 legacy_or.status().message().data());
574 for (
const auto& z : zones) {
575 if (z.room_id < 0 || z.room_id >=
static_cast<int>(
rooms_.
size())) {
578 auto& room =
rooms_[z.room_id];
580 for (uint16_t off : z.fill_offsets) {
581 const int x =
static_cast<int>(off % 64);
582 const int y =
static_cast<int>(off / 64);
589 if (!legacy_imported || !water_fill_save_enabled) {
596 water_fill_save_enabled
597 ?
"Imported legacy water gate zones (save to write new table)"
598 :
"Loaded legacy water gate zones read-only (WaterFill saving is "
632 window_manager->RegisterPanelAlias(
"dungeon.entrances",
633 "dungeon.entrance_properties");
639 window_manager->RegisterPanel(
640 {.card_id =
"dungeon.workbench",
641 .display_name =
"Dungeon Workbench",
642 .window_title =
" Dungeon Workbench",
644 .category =
"Dungeon",
645 .workflow_group =
"Core",
647 .visibility_flag =
nullptr,
650 .disabled_tooltip =
"Load a ROM to edit dungeon rooms"});
652 window_manager->RegisterPanel(
654 .display_name =
"Room List",
655 .window_title =
" Room List",
657 .category =
"Dungeon",
658 .workflow_group =
"Core",
659 .shortcut_hint =
"Ctrl+Shift+R",
660 .visibility_flag =
nullptr,
663 .disabled_tooltip =
"Load a ROM to browse dungeon rooms"});
665 window_manager->RegisterPanel(
667 .display_name =
"Entrance List",
668 .window_title =
" Entrance List",
670 .category =
"Dungeon",
671 .workflow_group =
"Core",
672 .shortcut_hint =
"Ctrl+Shift+E",
673 .visibility_flag =
nullptr,
676 .disabled_tooltip =
"Load a ROM to browse dungeon entrances"});
678 window_manager->RegisterPanel(
679 {.card_id =
"dungeon.entrance_properties",
680 .display_name =
"Entrance Properties",
681 .window_title =
" Entrance Properties",
683 .category =
"Dungeon",
684 .workflow_group =
"Core",
686 .visibility_flag =
nullptr,
689 .disabled_tooltip =
"Load a ROM to edit entrance properties"});
691 window_manager->RegisterPanel(
693 .display_name =
"Room Matrix",
694 .window_title =
" Room Matrix",
696 .category =
"Dungeon",
697 .workflow_group =
"Core",
698 .shortcut_hint =
"Ctrl+Shift+M",
699 .visibility_flag =
nullptr,
702 .disabled_tooltip =
"Load a ROM to view the room matrix"});
704 window_manager->RegisterPanel(
706 .display_name =
"Room Graphics",
707 .window_title =
" Room Graphics",
709 .category =
"Dungeon",
710 .workflow_group =
"Editors",
711 .shortcut_hint =
"Ctrl+Shift+G",
712 .visibility_flag =
nullptr,
715 .disabled_tooltip =
"Load a ROM to view room graphics"});
717 window_manager->RegisterPanel(
719 .display_name =
"Object Selector",
720 .window_title =
" Object Selector",
722 .category =
"Dungeon",
723 .workflow_group =
"Editors",
725 .visibility_flag =
nullptr,
728 .disabled_tooltip =
"Load a ROM to browse dungeon objects"});
730 window_manager->RegisterPanel(
732 .display_name =
"Door Editor",
733 .window_title =
" Door Editor",
735 .category =
"Dungeon",
736 .workflow_group =
"Editors",
738 .visibility_flag =
nullptr,
741 .disabled_tooltip =
"Load a ROM to edit dungeon doors"});
743 window_manager->RegisterPanel(
745 .display_name =
"Palette Editor",
746 .window_title =
" Palette Editor",
748 .category =
"Dungeon",
749 .workflow_group =
"Editors",
751 .shortcut_hint =
"Ctrl+Shift+Alt+P",
752 .visibility_flag =
nullptr,
755 .disabled_tooltip =
"Load a ROM to edit dungeon palettes"});
770 window_manager->RegisterWindowContent(std::make_unique<RoomBrowserContent>(
773 window_manager->RegisterWindowContent(
774 std::make_unique<DungeonEntranceListPanel>(
779 auto matrix_panel = std::make_unique<RoomMatrixContent>(
782 [
this](
int old_room,
int new_room) {
786 matrix_panel->SetRoomIntentCallback(
790 window_manager->RegisterWindowContent(std::move(matrix_panel));
794 auto workbench = std::make_unique<DungeonWorkbenchContent>(
800 [
this](
int room_id) {
803 LOG_ERROR(
"DungeonEditorV2",
"Apply Room failed: %s",
804 status.message().data());
807 absl::StrFormat(
"Apply Room failed: %s", status.message()),
819 [
this]() ->
const std::deque<int>& {
return recent_rooms_; },
820 [
this](
int room_id) {
834 [
this]() {
Undo().IgnoreError(); }, [
this]() {
Redo().IgnoreError(); },
839 [window_manager](
const std::string& window_id) {
840 const size_t session_id = window_manager->GetActiveSessionId();
841 return window_manager->IsWindowOpen(session_id, window_id);
843 [
this, window_manager](
const std::string& window_id) {
844 if (window_id.empty()) {
848 window_manager->GetActiveSessionId();
854 [window_manager]() { window_manager->TriggerShowShortcuts(); });
865 window_manager->RegisterWindowContent(std::move(workbench));
868 window_manager->RegisterWindowContent(std::make_unique<DungeonEntrancesPanel>(
878 return absl::FailedPreconditionError(
"ROM not loaded");
884 if (!dim_table.IsLoaded()) {
892 return absl::FailedPreconditionError(
"GameData not available");
925 auto graphics_panel = std::make_unique<RoomGraphicsContent>(
929 std::move(graphics_panel));
934 std::move(palette_panel));
942 auto object_selector = std::make_unique<ObjectSelectorContent>(
945 auto object_editor = std::make_unique<ObjectEditorContent>(
952 rooms_[current_room_id_].RenderRoomGraphics();
957 object_selector->SetRooms(&
rooms_);
960 auto open_workbench_selection_inspector = [
this]() {
967 object_selector->SetOpenObjectEditorCallback(
968 open_workbench_selection_inspector);
974 auto jump_to_reciprocal_door = [
this](
int neighbor_room_id,
976 if (neighbor_room_id < 0 ||
977 neighbor_room_id >=
static_cast<int>(
rooms_.
size())) {
982 viewer->object_interaction()
983 .entity_coordinator()
985 .SelectDoor(door_index);
988 object_editor->SetJumpToReciprocalDoorCallback(jump_to_reciprocal_door);
991 auto door_editor = std::make_unique<DoorEditorContent>();
992 door_editor->SetRooms(&
rooms_);
993 door_editor->SetOpenSelectionInspectorCallback(
994 open_workbench_selection_inspector);
995 door_editor->SetJumpToReciprocalDoorCallback(jump_to_reciprocal_door);
1007 auto custom_collision_panel =
1008 std::make_unique<CustomCollisionPanel>(
nullptr,
nullptr);
1011 auto water_fill_panel = std::make_unique<WaterFillPanel>(
nullptr,
nullptr);
1014 auto room_tag_panel = std::make_unique<RoomTagEditorPanel>();
1016 room_tag_panel->SetRooms(&
rooms_);
1024 std::move(object_selector));
1031 sprite_panel->SetOpenSelectionInspectorCallback(
1032 open_workbench_selection_inspector);
1035 std::move(sprite_panel));
1039 item_panel->SetOpenSelectionInspectorCallback(
1040 open_workbench_selection_inspector);
1045 std::move(custom_collision_panel));
1047 std::move(water_fill_panel));
1049 std::move(room_tag_panel));
1052 auto tile_editor_panel =
1055 tile_editor_panel->SetStandardWritePreflightCallback(
1056 [
this](
const std::vector<std::pair<uint32_t, uint32_t>>& ranges) {
1059 return absl::OkStatus();
1064 "dungeon object tile data",
"ObjectTileEditorPanel",
1067 tile_editor_panel->SetTilesAppliedCallback([
this]() {
1075 std::move(tile_editor_panel));
1101 auto overlay_panel = std::make_unique<OverlayManagerPanel>();
1104 std::move(overlay_panel));
1138 return absl::OkStatus();
1147 [
this](
const std::string& group_name,
int palette_index) {
1153 int palette_index) {
1158 if (group_name ==
"hud") {
1161 }
else if (group_name ==
"dungeon_main") {
1171 std::array<bool, zelda3::kNumberOfRooms> rendered_rooms{};
1172 auto render_once = [
this, &rendered_rooms](
int room_id) {
1173 if (room_id < 0 || room_id >=
static_cast<int>(
rooms_.
size()) ||
1174 room_id >=
static_cast<int>(rendered_rooms.size()) ||
1175 rendered_rooms[room_id]) {
1178 rooms_[room_id].RenderRoomGraphics();
1179 rendered_rooms[room_id] =
true;
1190 bool force_shared_palette_refresh) {
1197 force_shared_palette_refresh &&
1203 const bool force_shared_palette_refresh =
1207 static_cast<uint64_t
>(dungeon_main_pal_group.size())) {
1218 &dungeon_main_pal_group, &render_once,
1219 force_shared_palette_refresh](
1221 std::unique_ptr<DungeonCanvasViewer>& viewer) {
1222 if (room_id < 0 || room_id >=
static_cast<int>(
rooms_.
size())) {
1225 const int palette_id =
rooms_[room_id].ResolveDungeonPaletteId();
1226 if (palette_id < 0 ||
1227 palette_id >=
static_cast<int>(dungeon_main_pal_group.size())) {
1234 const auto room_palette_group =
1236 dungeon_main_pal_group.palette_ref(palette_id),
game_data());
1237 if (viewer && viewer->object_interaction().IsObjectLoaded()) {
1238 render_once(room_id);
1240 apply_palette(viewer.get(), palette_id, room_palette_group,
1241 force_shared_palette_refresh);
1265 if (tile_palette_id < 0 ||
1266 tile_palette_id >=
static_cast<int>(dungeon_palettes.size()) ||
1274 dungeon_palettes.palette_ref(tile_palette_id),
game_data()));
1292 room.MarkGraphicsDirty();
1318 if (loading_card.
Begin()) {
1319 ImGui::TextColored(theme.text_secondary_gray,
1320 tr(
"Loading dungeon data..."));
1322 tr(
"Independent editor cards will appear once ROM data is loaded."));
1325 return absl::OkStatus();
1333 if (!ImGui::GetIO().WantTextInput) {
1335 if (ImGui::IsKeyPressed(ImGuiKey_Tab) && ImGui::GetIO().KeyCtrl) {
1338 int current_idx = -1;
1339 for (
int i = 0; i < static_cast<int>(
recent_rooms_.size()); ++i) {
1345 if (current_idx != -1) {
1347 if (ImGui::GetIO().KeyShift) {
1352 (current_idx - 1 +
static_cast<int>(
recent_rooms_.size())) %
1359 int current_idx = -1;
1367 if (current_idx != -1) {
1369 if (ImGui::GetIO().KeyShift) {
1381 if (ImGui::GetIO().KeyCtrl) {
1383 const int kCols = 16;
1385 if (ImGui::IsKeyPressed(ImGuiKey_UpArrow)) {
1388 }
else if (ImGui::IsKeyPressed(ImGuiKey_DownArrow)) {
1391 }
else if (ImGui::IsKeyPressed(ImGuiKey_LeftArrow)) {
1394 }
else if (ImGui::IsKeyPressed(ImGuiKey_RightArrow)) {
1400 if (next_room != -1) {
1414 return absl::OkStatus();
1424 room_opts.
tooltip =
"Click to refocus viewer on this room";
1425 room_opts.
on_click = [
this, room_id]() {
1432 std::move(room_opts));
1440 absl::StrFormat(
"%d rooms loaded of %d total", loaded, total);
1442 absl::StrFormat(
"%d/%d", loaded, total),
1443 std::move(rooms_opts));
1449 "This room has pending editor changes that are not yet applied to the "
1452 std::move(current_room_opts));
1456 mode_opts.
tooltip =
"Click to toggle Workbench / Standalone workflow";
1463 std::move(mode_opts));
1498 return entrance.dirty();
1504 return spawn.dirty();
1528 return room->HasUnsavedChanges();
1545 if (!window_manager) {
1553 window_manager->OpenWindow(session_id,
"dungeon.workbench");
1562 for (
const auto& descriptor :
1563 window_manager->GetWindowsInCategory(session_id,
"Dungeon")) {
1564 const std::string& card_id = descriptor.card_id;
1565 if (!IsWorkbenchNavigationWindowId(card_id) ||
1566 !window_manager->IsWindowOpen(session_id, card_id) ||
1567 window_manager->IsWindowPinned(session_id, card_id)) {
1571 window_manager->CloseWindow(session_id, card_id);
1575 window_manager->CloseWindow(session_id,
"dungeon.workbench");
1581 for (
const std::string& card_id :
1583 window_manager->OpenWindow(session_id, card_id);
1590 enabled ?
"Dungeon workflow: Workbench"
1591 :
"Dungeon workflow: Standalone Panels",
1610 std::string card_id = absl::StrFormat(
"dungeon.room_%d", room_id);
1611 bool panel_visible =
true;
1616 if (!panel_visible) {
1629 std::string active_category =
1634 if (active_category !=
"Dungeon" && !is_pinned) {
1650 ImGui::SetNextWindowDockID(
room_dock_id_, ImGuiCond_FirstUseEver);
1652 if (room_card->Begin(&open)) {
1653 if (ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows)) {
1690 if (room_id < 0 || room_id >= 0x128) {
1691 ImGui::Text(tr(
"Invalid room ID: %d"), room_id);
1695 auto& room =
rooms_[room_id];
1697 if (!room.IsLoaded()) {
1700 ImGui::TextColored(theme.text_error_red, tr(
"Failed to load room: %s"),
1701 status.message().data());
1707 if (!sys_status.ok()) {
1708 LOG_ERROR(
"DungeonEditorV2",
"Failed to load system data: %s",
1709 sys_status.message().data());
1714 if (room.IsLoaded()) {
1715 bool needs_render =
false;
1717 if (room.blocks().empty()) {
1719 room.LoadRoomGraphics();
1720 needs_render =
true;
1723 if (!room.AreObjectsLoaded()) {
1725 needs_render =
true;
1728 auto& bg1_bitmap = room.bg1_buffer().bitmap();
1729 if (needs_render || !bg1_bitmap.is_active() || bg1_bitmap.width() == 0) {
1730 room.RenderRoomGraphics();
1734 if (room.IsLoaded()) {
1735 ImGui::TextColored(theme.text_success_green,
ICON_MD_CHECK " Loaded");
1737 ImGui::TextColored(theme.text_error_red,
ICON_MD_PENDING " Not Loaded");
1740 ImGui::TextDisabled(tr(
"Objects: %zu"), room.GetTileObjects().size());
1745 const std::string return_label = absl::StrFormat(
1747 if (ImGui::SmallButton(return_label.c_str())) {
1750 if (ImGui::IsItemHovered()) {
1752 tr(
"Switch back to the integrated Dungeon Workbench workflow."));
1760 bool connected = client && client->IsConnected();
1762 ImGui::BeginDisabled();
1764 std::string warp_label =
1766 if (ImGui::SmallButton(warp_label.c_str())) {
1767 auto status = client->WriteWord(0x7E00A0,
static_cast<uint16_t
>(room_id));
1771 absl::StrFormat(
"Warped to room 0x%03X", room_id),
1777 absl::StrFormat(
"Warp failed: %s", status.message()),
1783 ImGui::EndDisabled();
1784 if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled)) {
1785 ImGui::SetTooltip(tr(
"Connect to Mesen2 first"));
1788 if (ImGui::IsItemHovered()) {
1789 ImGui::SetTooltip(tr(
"Warp to room 0x%03X in Mesen2"), room_id);
1800 const float status_bar_reserved =
1801 std::max(ImGui::GetFontSize() + ImGui::GetStyle().FramePadding.y * 2.0f,
1803 ImGui::GetStyle().ItemSpacing.y;
1804 if (ImGui::BeginChild(
"##StandaloneRoomBody",
1805 ImVec2(0.0f, -status_bar_reserved),
false)) {
1806 viewer->DrawDungeonCanvas(room_id);
1811 bool room_dirty =
false;
1812 if (room_id >= 0 && room_id <
static_cast<int>(
rooms_.
size())) {
1813 room_dirty =
rooms_[room_id].HasUnsavedChanges();
1815 const char* tool_mode =
1816 viewer->object_interaction().mode_manager().GetModeName();
1819 status.workflow_primary =
false;
1823 status.on_undo = [
this]() {
1824 Undo().IgnoreError();
1826 status.on_redo = [
this]() {
1827 Redo().IgnoreError();
1834 if (ImGui::GetCurrentContext() ==
nullptr) {
1845 if (ImGui::GetCurrentContext() ==
nullptr ||
1855 ImGui::GetIO().WantTextInput || ImGui::IsAnyItemActive() ||
1872 if (room_id < 0 || room_id >=
static_cast<int>(
rooms_.
size())) {
1891 if (room_id < 0 || room_id >=
static_cast<int>(
rooms_.
size())) {
1892 LOG_WARN(
"DungeonEditorV2",
"Ignoring invalid room selection: %d", room_id);
1920 if (room_id >= 0 && room_id < (
int)
rooms_.
size()) {
1921 auto& room =
rooms_[room_id];
1922 if (!room.IsLoaded()) {
1926 if (room.IsLoaded()) {
1938 auto dungeon_main_pal_group =
1983 std::string card_id = absl::StrFormat(
"dungeon.room_%d", room_id);
1986 if (request_focus) {
1998 std::string room_name = absl::StrFormat(
2001 std::string base_card_id = absl::StrFormat(
"dungeon.room_%d", room_id);
2004 {.card_id = base_card_id,
2005 .display_name = room_name,
2008 .category =
"Dungeon",
2009 .workflow_group =
"Rooms",
2010 .shortcut_hint =
"",
2011 .visibility_flag =
nullptr,
2012 .priority = 200 + room_id});
2019 if (entrance_id < 0 || entrance_id >=
static_cast<int>(
entrances_.size())) {
2026 if (room_id < 0 || room_id >=
static_cast<int>(
rooms_.
size())) {
2033 if (entrance_id < 0 || entrance_id >=
static_cast<int>(
entrances_.size())) {
2038 return spawn.spawn_id() == entrance_id ? spawn.room_id : -1;
2044 int room_id)
const {
2045 if (room_id < 0 || room_id >=
static_cast<int>(
rooms_.
size())) {
2057 return spawn.main_gfx;
2061 if (entrance.room_ != room_id) {
2064 return entrance.blockset_;
2068 if (room_id < 0 || room_id >=
static_cast<int>(
rooms_.
size())) {
2071 auto& room =
rooms_[room_id];
2072 if (!room.IsLoaded()) {
2075 room.SetRenderEntranceBlockset(
2081 if (viewer ==
nullptr) {
2084 const uint8_t entrance_blockset =
2086 if (entrance_blockset == 0xFF) {
2100 it->second->Focus();
2140 LOG_ERROR(
"DungeonEditorV2",
"Cannot place object: Invalid room ID %d",
2142 const std::string message = absl::StrFormat(
2155 "Placing object ID=0x%02X at position (%d,%d) in room %03X", obj.
id_,
2158 room.RenderRoomGraphics();
2160 "Object placed and room re-rendered successfully");
2165 absl::StrFormat(
"Placed 0x%02X in room %03X", obj.
id_,
2173 if (room_id < 0 || room_id >=
static_cast<int>(
rooms_.
size())) {
2174 LOG_WARN(
"DungeonEditorV2",
"Edit Graphics ignored (invalid room id %d)",
2180 if (!editor_manager) {
2182 "Edit Graphics ignored (editor manager unavailable)");
2186 auto& room =
rooms_[room_id];
2188 room.LoadRoomGraphics();
2190 uint16_t sheet_id = 0;
2191 uint16_t tile_index = 0;
2192 bool resolved_sheet =
false;
2193 if (
auto tiles_or =
object.GetTiles();
2194 tiles_or.ok() && !tiles_or.value().empty()) {
2195 const uint16_t tile_id = tiles_or.value().front().id_;
2196 const size_t block_index =
static_cast<size_t>(tile_id / 64);
2197 const auto blocks = room.blocks();
2198 if (block_index < blocks.size()) {
2199 sheet_id = blocks[block_index];
2200 resolved_sheet =
true;
2204 const int tiles_per_sheet = tiles_per_row * tiles_per_col;
2205 if (tiles_per_sheet > 0) {
2206 tile_index =
static_cast<uint16_t
>(tile_id % tiles_per_sheet);
2211 if (
auto* editor_set = editor_manager->GetCurrentEditorSet()) {
2212 if (
auto* graphics = editor_set->GetGraphicsEditor()) {
2213 if (resolved_sheet) {
2214 graphics->SelectSheet(sheet_id);
2215 graphics->HighlightTile(sheet_id, tile_index,
2216 absl::StrFormat(
"Object 0x%02X",
object.id_));
2229 return absl::FailedPreconditionError(
2230 "Object Tile Editor panel is unavailable");
2233 return absl::FailedPreconditionError(
2234 "Workspace window manager is unavailable");
2237 return absl::FailedPreconditionError(
"ROM not loaded");
2239 if (room_id < 0 || room_id >=
static_cast<int>(
rooms_.
size())) {
2240 return absl::OutOfRangeError(
"Dungeon room id is outside the room store");
2243 return absl::UnimplementedError(
2244 "Object tile editing is supported only for audited objects 0x11F and "
2249 return absl::FailedPreconditionError(
"Dungeon palette data is unavailable");
2252 auto& room =
rooms_[room_id];
2253 if (!room.IsLoaded()) {
2257 room.PrepareForRender();
2259 const int palette_id = room.ResolveDungeonPaletteId();
2261 if (palette_id < 0 ||
2262 palette_id >=
static_cast<int>(dungeon_palettes.size())) {
2263 return absl::OutOfRangeError(
2264 "Room resolves to an unavailable dungeon palette");
2266 auto room_palette = dungeon_palettes[palette_id];
2271 object.id_, room_id, &
rooms_, palette_group);
2272 if (!open_status.ok()) {
2285 return absl::NotFoundError(
2286 "Object Tile Editor window is not registered in this session");
2288 return absl::OkStatus();
2294 if (new_room_id < 0 || new_room_id >=
static_cast<int>(
rooms_.
size())) {
2312 int swap_index = -1;
2320 if (swap_index < 0) {
2340 slot_id = it->second;
2357 std::string old_card_id = absl::StrFormat(
"dungeon.room_%d", old_room_id);
2358 const bool old_pinned =
2364 std::string new_room_name = absl::StrFormat(
2367 std::string new_card_id = absl::StrFormat(
"dungeon.room_%d", new_room_id);
2370 {.card_id = new_card_id,
2371 .display_name = new_room_name,
2374 .category =
"Dungeon",
2375 .workflow_group =
"Rooms",
2376 .shortcut_hint =
"",
2377 .visibility_flag =
nullptr,
2378 .priority = 200 + new_room_id});
2414 if (!window_manager) {
2422 if (ImGui::GetCurrentContext() !=
nullptr) {
2425 if (!window_name.empty()) {
2426 ImGui::SetWindowFocus(window_name.c_str());
2517 const absl::Status status =
2522 LOG_ERROR(
"DungeonEditorV2",
"Edit Object Tiles failed: %s",
2523 status.message().data());
2526 absl::StrFormat(
"Edit Object Tiles failed: %s", status.message()),
2531 auto status =
SaveRoom(target_room_id);
2533 LOG_ERROR(
"DungeonEditorV2",
"Apply Room failed: %s",
2534 status.message().data());
2537 absl::StrFormat(
"Apply Room failed: %s", status.message()),
2559 room_viewers_.SetEvictionPredicate([
this](
const int& candidate) {
2569 auto* viewer_ptr = existing->get();
2575 std::string card_id = absl::StrFormat(
"dungeon.room_%d", room_id);
2578 viewer_ptr->
SetPinCallback([
this, card_id, room_id](
bool pinned) {
2580 dependencies_.window_manager->SetWindowPinned(card_id, pinned);
2581 if (auto* v = GetViewerForRoom(room_id)) {
2582 v->SetPinned(pinned);
2593 auto viewer = std::make_unique<DungeonCanvasViewer>(rom_);
2594 viewer->SetCompactHeaderMode(
false);
2595 viewer->SetRoomDetailsExpanded(
true);
2598 viewer->SetRenderer(renderer_);
2599 viewer->SetCurrentPaletteGroup(current_palette_group_);
2600 viewer->SetCurrentPaletteId(current_palette_id_);
2601 viewer->SetGameData(game_data_);
2602 ConfigureViewerRenderContext(viewer_ptr, room_id);
2603 ApplyEntranceRenderContext(room_id);
2609 viewer->object_interaction().SetMutationCallback([
this, viewer_ptr]() {
2611 if (rid >= 0 && rid <
static_cast<int>(rooms_.size())) {
2615 BeginUndoSnapshot(rid);
2617 BeginCollisionUndoSnapshot(rid);
2619 BeginWaterFillUndoSnapshot(rid);
2624 viewer->object_interaction().SetCacheInvalidationCallback([
this,
2627 if (rid >= 0 && rid <
static_cast<int>(rooms_.size())) {
2631 rooms_[rid].MarkObjectsDirty();
2632 rooms_[rid].RenderRoomGraphics();
2638 FinalizeUndoAction(rid);
2648 FinalizeCollisionUndoAction(rid);
2657 FinalizeWaterFillUndoAction(rid);
2662 viewer->object_interaction().SetObjectPlacedCallback(
2663 [
this](
const zelda3::RoomObject& obj) { HandleObjectPlaced(obj); });
2665 if (dungeon_editor_system_) {
2666 viewer->SetEditorSystem(dungeon_editor_system_.get());
2668 viewer->SetRoomNavigationCallback([
this](
int target_room) {
2669 if (target_room >= 0 && target_room <
static_cast<int>(rooms_.size())) {
2670 OnRoomSelected(target_room);
2674 viewer->SetRoomSwapCallback([
this](
int old_room,
int new_room) {
2675 SwapRoomInPanel(old_room, new_room);
2677 WireViewerPanelCallbacks(viewer.get());
2680 if (dependencies_.window_manager) {
2681 std::string card_id = absl::StrFormat(
"dungeon.room_%d", room_id);
2682 viewer->SetPinned(dependencies_.window_manager->IsWindowPinned(card_id));
2683 viewer->SetPinCallback([
this, card_id, room_id](
bool pinned) {
2684 if (dependencies_.window_manager) {
2685 dependencies_.window_manager->SetWindowPinned(card_id, pinned);
2687 if (auto* v = GetViewerForRoom(room_id)) {
2688 v->SetPinned(pinned);
2694 auto* stored = room_viewers_.Insert(room_id, std::move(viewer));
2695 return stored->get();
2699void DungeonEditorV2::RefreshWorkbenchViewerRuntimeContext(
2705 if (viewer->
rom() != rom_) {
2708 if (viewer->
rooms() != &rooms_) {
2711 if (viewer->
game_data() != game_data_) {
2717 ConfigureViewerRenderContext(viewer, room_id);
2720 if (viewer->
project() != dependencies_.project) {
2726 if (!workbench_viewer_) {
2727 workbench_viewer_ = std::make_unique<DungeonCanvasViewer>(rom_);
2728 auto* viewer = workbench_viewer_.get();
2729 viewer->SetCompactHeaderMode(
true);
2730 viewer->SetRoomDetailsExpanded(
false);
2731 viewer->SetHeaderVisible(
false);
2732 viewer->SetHeaderHiddenMetadataHudVisible(
false);
2733 viewer->SetRooms(&rooms_);
2734 viewer->SetRenderer(renderer_);
2735 viewer->SetCurrentPaletteGroup(current_palette_group_);
2736 viewer->SetCurrentPaletteId(current_palette_id_);
2737 viewer->SetGameData(game_data_);
2741 viewer->object_interaction().SetMutationCallback([
this, viewer]() {
2742 const int rid = viewer ? viewer->current_room_id() : -1;
2743 if (rid >= 0 && rid <
static_cast<int>(rooms_.size())) {
2744 const auto domain = viewer->object_interaction().last_mutation_domain();
2745 if (domain == MutationDomain::kTileObjects) {
2746 BeginUndoSnapshot(rid);
2747 }
else if (domain == MutationDomain::kCustomCollision) {
2748 BeginCollisionUndoSnapshot(rid);
2749 }
else if (domain == MutationDomain::kWaterFill) {
2750 BeginWaterFillUndoSnapshot(rid);
2754 viewer->object_interaction().SetCacheInvalidationCallback([
this, viewer]() {
2755 const int rid = viewer ? viewer->current_room_id() : -1;
2756 if (rid >= 0 && rid <
static_cast<int>(rooms_.size())) {
2758 viewer->object_interaction().last_invalidation_domain();
2759 if (domain == MutationDomain::kTileObjects) {
2760 rooms_[rid].MarkObjectsDirty();
2761 rooms_[rid].RenderRoomGraphics();
2763 viewer->object_interaction().mode_manager().GetMode();
2764 if (mode != InteractionMode::DraggingObjects) {
2765 FinalizeUndoAction(rid);
2767 }
else if (domain == MutationDomain::kCustomCollision) {
2769 viewer->object_interaction().mode_manager().GetMode();
2771 viewer->object_interaction().mode_manager().GetModeState();
2772 if (mode == InteractionMode::PaintCollision && st.is_painting) {
2775 FinalizeCollisionUndoAction(rid);
2776 }
else if (domain == MutationDomain::kWaterFill) {
2778 viewer->object_interaction().mode_manager().GetMode();
2780 viewer->object_interaction().mode_manager().GetModeState();
2781 if (mode == InteractionMode::PaintWaterFill && st.is_painting) {
2784 FinalizeWaterFillUndoAction(rid);
2789 viewer->object_interaction().SetObjectPlacedCallback(
2792 if (dungeon_editor_system_) {
2793 viewer->SetEditorSystem(dungeon_editor_system_.get());
2798 viewer->SetRoomSwapCallback([
this](
int ,
int new_room) {
2799 OnRoomSelected(new_room,
false);
2801 viewer->SetRoomNavigationCallback([
this](
int target_room) {
2802 OnRoomSelected(target_room,
false);
2805 WireViewerPanelCallbacks(viewer);
2808 auto* viewer = workbench_viewer_.get();
2809 RefreshWorkbenchViewerRuntimeContext(viewer, current_room_id_);
2814 if (!workbench_compare_viewer_) {
2815 workbench_compare_viewer_ = std::make_unique<DungeonCanvasViewer>(rom_);
2816 auto* viewer = workbench_compare_viewer_.get();
2817 viewer->SetCompactHeaderMode(
true);
2818 viewer->SetRoomDetailsExpanded(
false);
2819 viewer->SetHeaderHiddenMetadataHudVisible(
false);
2820 viewer->SetRooms(&rooms_);
2821 viewer->SetRenderer(renderer_);
2822 viewer->SetCurrentPaletteGroup(current_palette_group_);
2823 viewer->SetCurrentPaletteId(current_palette_id_);
2824 viewer->SetGameData(game_data_);
2828 viewer->SetObjectInteractionEnabled(
false);
2829 viewer->SetHeaderReadOnly(
true);
2830 viewer->SetHeaderVisible(
false);
2832 if (dungeon_editor_system_) {
2835 viewer->SetEditorSystem(dungeon_editor_system_.get());
2838 viewer->SetMinecartTrackPanel(minecart_track_editor_panel_);
2839 viewer->SetProject(dependencies_.project);
2842 auto* viewer = workbench_compare_viewer_.get();
2843 RefreshWorkbenchViewerRuntimeContext(viewer, room_id);
2844 viewer->SetObjectInteractionEnabled(
false);
2845 viewer->SetHeaderReadOnly(
true);
2846 viewer->SetHeaderVisible(
false);
2850void DungeonEditorV2::SyncPanelsToRoom(
int room_id) {
2852 if (object_selector_panel_) {
2853 object_selector_panel_->SetCanvasViewerProvider(
2854 [
this]() {
return GetViewerForRoom(current_room_id_); });
2855 object_selector_panel_->SetCurrentRoom(room_id);
2856 object_selector_panel_->SetCanvasViewer(GetViewerForRoom(room_id));
2858 if (object_editor_content_) {
2859 object_editor_content_->SetCanvasViewerProvider(
2860 [
this]() {
return GetViewerForRoom(current_room_id_); });
2861 object_editor_content_->SetCurrentRoom(room_id);
2862 object_editor_content_->SetCanvasViewer(GetViewerForRoom(room_id));
2864 if (door_editor_panel_) {
2865 door_editor_panel_->SetCanvasViewerProvider(
2866 [
this]() {
return GetViewerForRoom(current_room_id_); });
2867 door_editor_panel_->SetCurrentRoom(room_id);
2868 door_editor_panel_->SetCanvasViewer(GetViewerForRoom(room_id));
2872 if (sprite_editor_panel_) {
2873 sprite_editor_panel_->SetCanvasViewer(GetViewerForRoom(room_id));
2875 if (item_editor_panel_) {
2876 item_editor_panel_->SetCanvasViewer(GetViewerForRoom(room_id));
2878 if (custom_collision_panel_) {
2879 auto* viewer = GetViewerForRoom(room_id);
2880 custom_collision_panel_->SetCanvasViewer(viewer);
2882 custom_collision_panel_->SetInteraction(&viewer->object_interaction());
2885 if (water_fill_panel_) {
2886 auto* viewer = GetViewerForRoom(room_id);
2887 water_fill_panel_->SetCanvasViewer(viewer);
2889 water_fill_panel_->SetInteraction(&viewer->object_interaction());
2893 if (room_tag_editor_panel_) {
2894 room_tag_editor_panel_->SetCurrentRoomId(room_id);
2897 if (overlay_manager_panel_) {
2898 auto* viewer = GetViewerForRoom(room_id);
2901 overlay_state.
show_grid = viewer->mutable_show_grid();
2904 viewer->mutable_show_coordinate_overlay();
2906 viewer->mutable_show_room_debug_info();
2910 viewer->mutable_show_minecart_tracks();
2912 viewer->mutable_show_custom_collision_overlay();
2914 viewer->mutable_show_track_collision_overlay();
2916 viewer->mutable_show_camera_quadrant_overlay();
2918 viewer->mutable_show_minecart_sprite_overlay();
2920 viewer->mutable_show_track_collision_legend();
2921 overlay_manager_panel_->SetState(overlay_state);
2926void DungeonEditorV2::ShowRoomPanel(
int room_id) {
2927 if (room_id < 0 || room_id >=
static_cast<int>(rooms_.size())) {
2931 bool already_active =
false;
2932 for (
int i = 0; i < active_rooms_.Size; ++i) {
2933 if (active_rooms_[i] == room_id) {
2934 already_active =
true;
2938 if (!already_active) {
2939 active_rooms_.push_back(room_id);
2940 room_selector_.set_active_rooms(active_rooms_);
2943 std::string card_id = absl::StrFormat(
"dungeon.room_%d", room_id);
2945 if (dependencies_.window_manager) {
2946 if (!dependencies_.window_manager->GetWindowDescriptor(
2947 dependencies_.window_manager->GetActiveSessionId(), card_id)) {
2948 std::string room_name = absl::StrFormat(
2950 dependencies_.window_manager->RegisterWindow(
2951 {.card_id = card_id,
2952 .display_name = room_name,
2955 .category =
"Dungeon",
2956 .workflow_group =
"Rooms",
2957 .shortcut_hint =
"",
2958 .visibility_flag =
nullptr,
2959 .priority = 200 + room_id});
2961 dependencies_.window_manager->OpenWindow(card_id);
2965 if (room_cards_.find(room_id) == room_cards_.end()) {
2966 std::string base_name = absl::StrFormat(
2968 const int slot_id = GetOrCreateRoomPanelSlotId(room_id);
2969 std::string card_name_str = absl::StrFormat(
2970 "%s###RoomPanelSlot%d",
MakePanelTitle(base_name).c_str(), slot_id);
2972 auto card = std::make_shared<gui::PanelWindow>(card_name_str.c_str(),
2974 card->SetDefaultSize(620, 700);
2975 room_cards_[room_id] = card;
void Publish(const T &event)
The Rom class is used to load, save, and modify Rom data. This is a generic SNES ROM container and do...
bool loaded() const
Check if the manifest has been loaded.
void SetCanvasViewer(DungeonCanvasViewer *viewer)
void SetInteraction(DungeonObjectInteraction *interaction)
void SetCanvasViewer(DungeonCanvasViewer *viewer)
void SetCurrentPaletteId(uint64_t id)
void SetShowEntranceListCallback(std::function< void()> callback)
void ClearEntranceRenderContext()
DungeonRoomStore * rooms() const
void SetEditorSystem(zelda3::DungeonEditorSystem *system)
void SetMinecartTrackPanel(MinecartTrackEditorPanel *panel)
int current_room_id() const
void SetShowItemPanelCallback(std::function< void()> callback)
void SetPinned(bool pinned)
void SetShowRoomGraphicsCallback(std::function< void()> callback)
void SetProject(const project::YazeProject *project)
void SetRenderer(gfx::IRenderer *renderer)
void SetEditGraphicsCallback(std::function< void(int, const zelda3::RoomObject &)> callback)
zelda3::GameData * game_data() const
void SetShowRoomListCallback(std::function< void()> callback)
void RefreshRomBackedState(Rom *rom, zelda3::GameData *game_data, DungeonRoomStore *rooms, int room_id)
DungeonObjectInteraction & object_interaction()
void SetShowRoomMatrixCallback(std::function< void()> callback)
void SetShowSpritePanelCallback(std::function< void()> callback)
void SetShowObjectPanelCallback(std::function< void()> callback)
void SetEntranceRenderContext(int entrance_id, uint8_t entrance_blockset)
const project::YazeProject * project() const
void SetEditObjectTilesCallback(std::function< void(int, const zelda3::RoomObject &)> callback)
void SetSaveRoomCallback(std::function< void(int)> callback)
void SetCurrentPaletteGroup(const gfx::PaletteGroup &group, bool force_refresh=false)
void SetShowDungeonSettingsCallback(std::function< void()> callback)
void DeleteSelectedObjects()
void SetRooms(DungeonRoomStore *rooms)
bool CanHandleRoomCanvasShortcut() const
void SetGameData(zelda3::GameData *game_data)
void SetShowDoorEditorCallback(std::function< void()> callback)
void SetPinCallback(std::function< void(bool)> callback)
class MinecartTrackEditorPanel * minecart_track_editor_panel_
int GetOrCreateRoomPanelSlotId(int room_id)
void ConfigureMinecartProjectCallbacks()
std::deque< int > recent_rooms_
absl::Status SaveRoom(int room_id)
void SetAgentMode(bool enabled)
std::unique_ptr< CustomCollisionPanel > owned_custom_collision_panel_
class CustomCollisionPanel * custom_collision_panel_
std::optional< int > room_canvas_delete_shortcut_frame_
bool HasPendingRoomChanges() const
bool observed_custom_objects_enabled_
uint64_t current_palette_group_id_
void ToggleWorkbenchWorkflowMode(bool show_toast=true)
void add_room(int room_id)
void ContributeStatus(StatusBar *status_bar) override
void OpenGraphicsEditorForObject(int room_id, const zelda3::RoomObject &object)
absl::Status OpenObjectTileEditorForObject(int room_id, const zelda3::RoomObject &object)
absl::Status EnsureMinecartTrackEditorPanel()
class ItemEditorPanel * item_editor_panel_
PendingUndo pending_undo_
std::unique_ptr< RoomTagEditorPanel > owned_room_tag_editor_panel_
void ShowRoomPanel(int room_id)
int next_room_panel_slot_id_
void SetDependencies(const EditorDependencies &deps) override
util::LruCache< int, std::unique_ptr< DungeonCanvasViewer > > room_viewers_
gfx::PaletteGroup current_palette_group_
void OnEntranceSelected(int entrance_id)
static constexpr const char * kEntranceListId
gfx::IRenderer * renderer_
void TouchViewerLru(int room_id)
PendingStandaloneToolWindow pending_standalone_tool_window_
class SpriteEditorPanel * sprite_editor_panel_
void FocusRoom(int room_id)
void HandleObjectPlaced(const zelda3::RoomObject &obj)
std::unique_ptr< zelda3::DungeonEditorSystem > dungeon_editor_system_
std::array< zelda3::RoomEntrance, zelda3::kNumDungeonEntranceSlots > entrances_
void OpenWindow(const std::string &window_id)
void SyncPanelsToRoom(int room_id)
DoorEditorContent * door_editor_panel_
void Initialize() override
std::unordered_map< int, int > room_panel_slot_ids_
class DungeonWorkbenchContent * workbench_panel_
int ResolveEntranceRoomId(int entrance_id) const
void SynchronizeCustomObjectAssets()
bool IsWorkbenchWorkflowEnabled() const
void OnRoomSelected(int room_id, bool request_focus=true)
~DungeonEditorV2() override
PendingCollisionUndo pending_collision_undo_
ObjectTileEditorPanel * object_tile_editor_panel_
ObjectSelectorContent * object_selector_panel_
OverlayManagerPanel * overlay_manager_panel_
absl::Status PrepareProjectSave()
void ProcessDeferredTextures()
uint8_t ResolveSelectedEntranceBlocksetForRoom(int room_id) const
DungeonCanvasViewer * GetWorkbenchCompareViewer(int room_id)
void WireViewerPanelCallbacks(DungeonCanvasViewer *viewer)
gfx::SnesPalette current_palette_
ImVector< int > active_rooms_
std::unique_ptr< DungeonCanvasViewer > workbench_compare_viewer_
PendingWaterFillUndo pending_water_fill_undo_
std::unique_ptr< MinecartTrackEditorPanel > owned_minecart_track_editor_panel_
uint64_t observed_custom_object_generation_
gui::PaletteEditorWidget palette_editor_
std::unique_ptr< ObjectEditorContent > owned_object_editor_content_
std::unique_ptr< DoorEditorContent > owned_door_editor_panel_
int PendingRoomCount() const
bool ConsumeRoomCanvasDeleteShortcut(DungeonCanvasViewer &viewer)
static bool IsValidRoomId(int room_id)
void SwapRoomInPanel(int old_room_id, int new_room_id)
RoomGraphicsContent * room_graphics_panel_
void ExpireStaleRoomCanvasDeleteShortcut()
static constexpr size_t kMaxRecentRooms
void RegisterPaletteListener()
void DrawRoomTab(int room_id)
std::array< zelda3::DungeonSpawnPoint, zelda3::kNumDungeonSpawnPoints > spawn_points_
void SetWorkbenchWorkflowMode(bool enabled, bool show_toast=true)
class RoomTagEditorPanel * room_tag_editor_panel_
void ProcessPendingSwap()
std::optional< SaveTransactionSnapshot > save_transaction_snapshot_
std::unique_ptr< ObjectSelectorContent > owned_object_selector_panel_
PendingSwap pending_swap_
static constexpr const char * kDoorEditorId
void ProcessPendingWorkflowMode()
DungeonCanvasViewer * GetViewerForRoom(int room_id)
absl::Status Update() override
class WaterFillPanel * water_fill_panel_
absl::Status Undo() override
int LoadedRoomCount() const
void ReloadWaterFillZones()
ImGuiWindowClass room_window_class_
void SelectObject(int obj_id)
DungeonRoomLoader room_loader_
DungeonCanvasViewer * GetWorkbenchViewer()
std::unordered_map< int, std::shared_ptr< gui::PanelWindow > > room_cards_
absl::Status Redo() override
void ApplyEntranceRenderContext(int room_id)
bool HasPendingProjectDraftChanges() const
void RemoveViewerFromLru(int room_id)
static constexpr const char * kObjectSelectorId
void ConfigureViewerRenderContext(DungeonCanvasViewer *viewer, int room_id)
void PrepareForSessionTeardown() override
static constexpr const char * kRoomGraphicsId
void QueueRoomCanvasDeleteShortcut()
void QueueWorkbenchWorkflowMode(bool enabled, bool show_toast=true)
ObjectEditorContent * object_editor_content_
static constexpr const char * kRoomMatrixId
void HandleDungeonPaletteChanged(gui::DungeonPaletteChange change)
static constexpr const char * kRoomSelectorId
DungeonRoomSelector room_selector_
zelda3::GameData * game_data_
PaletteEditorContent * palette_editor_panel_
bool HasPendingDungeonChanges() const
std::unique_ptr< DungeonCanvasViewer > workbench_viewer_
bool CurrentRoomHasPendingChanges() const
absl::Status RefreshRomBackedState()
bool undo_restore_triggered_ping_
void ReleaseRoomPanelSlotId(int room_id)
absl::Status Load() override
int current_room_id() const
int TotalRoomCount() const
std::unique_ptr< WaterFillPanel > owned_water_fill_panel_
absl::Status ApplyMinecartCollisionBatch(const std::vector< zelda3::TrackCollisionResult > &preview, const zelda3::GeneratorOptions &options)
void ProcessPendingStandaloneToolWindow()
static constexpr const char * kPaletteEditorId
DungeonEditorV2(Rom *rom=nullptr)
PendingWorkflowMode pending_workflow_mode_
void InvalidateDungeonPaletteUsers(gui::DungeonPaletteChange change)
void HandleArenaPaletteChanged(const std::string &group_name, int palette_index)
uint64_t current_palette_id_
std::vector< std::string > workbench_suspended_navigation_window_ids_
MutationDomain last_mutation_domain() const
InteractionModeManager & mode_manager()
MutationDomain last_invalidation_domain() const
bool IsObjectLoaded() const
void SetOpenMinecartEditorWindowCallback(std::function< bool()> callback)
void InvalidatePreviewCache()
void SetOpenTileEditorWindowCallback(std::function< bool()> callback)
void SetTileEditorPanel(ObjectTileEditorPanel *panel)
void SynchronizeCustomObjectGeneration()
absl::Status LoadRoom(int room_id, zelda3::Room &room)
absl::Status LoadRoomEntrances(std::array< zelda3::RoomEntrance, zelda3::kNumDungeonEntranceSlots > &entrances)
absl::Status LoadDungeonSpawnPoints(std::array< zelda3::DungeonSpawnPoint, zelda3::kNumDungeonSpawnPoints > &spawn_points)
void SetGameData(zelda3::GameData *game_data)
void SetRoomSelectedWithIntentCallback(std::function< void(int, RoomSelectionIntent)> callback)
void SetEntranceSelectedCallback(std::function< void(int)> callback)
void set_current_room_id(uint16_t room_id)
void set_entrances(std::array< zelda3::RoomEntrance, zelda3::kNumDungeonEntranceSlots > *entrances)
void set_current_entrance_id(int entrance_id)
void set_rooms(DungeonRoomStore *rooms)
void SetRoomSelectedCallback(std::function< void(int)> callback)
void set_spawn_points(std::array< zelda3::DungeonSpawnPoint, zelda3::kNumDungeonSpawnPoints > *spawn_points)
void SetProject(const project::YazeProject *project)
void set_active_rooms(const ImVector< int > &rooms)
zelda3::Room * GetIfMaterialized(int room_id)
zelda3::Room * GetIfLoaded(int room_id)
void SetGameData(zelda3::GameData *game_data)
void ForEachMaterialized(Fn &&fn)
static void Draw(const DungeonStatusBarState &state)
static DungeonStatusBarState BuildState(const DungeonCanvasViewer &viewer, const char *tool_mode, bool room_dirty)
void SetOpenKeyboardShortcutsCallback(std::function< void()> callback)
void FocusEntranceBrowser()
void FocusSelectionInspector()
void SetUndoRedoProvider(std::function< bool()> can_undo, std::function< bool()> can_redo, std::function< void()> on_undo, std::function< void()> on_redo, std::function< std::string()> undo_desc, std::function< std::string()> redo_desc, std::function< int()> undo_depth)
void SetPrimaryCanvasDrawnCallback(std::function< void(DungeonCanvasViewer &)> cb)
void SetOnInspectorSideChanged(std::function< void(bool)> cb)
void OpenRoomGraphicsTool()
void FocusRoomInspector()
void SetStandaloneToolCallbacks(std::function< bool(const std::string &)> is_window_open, std::function< bool(const std::string &)> open_and_focus_window)
void SetEmbeddedEditorPanels(WindowContent *object_selector, WindowContent *door_editor, WindowContent *sprite_editor, WindowContent *item_editor, WindowContent *room_graphics, WindowContent *palette_editor)
void SetEmbeddedToolPanels(RoomTagEditorPanel *room_tags, CustomCollisionPanel *custom_collision, WaterFillPanel *water_fill, MinecartTrackEditorPanel *minecart_tracks)
void NotifyRoomChanged(int previous_room_id)
Called by the editor when the current room changes.
void OpenObjectSelectorTool()
void ShowConnectedGraph()
void SetInspectorOnLeft(bool on_left)
void SetPitDamageTableProvider(std::function< zelda3::PitDamageTable *()> provider)
The EditorManager controls the main editor window and manages the various editor classes.
UndoManager undo_manager_
virtual void SetDependencies(const EditorDependencies &deps)
zelda3::GameData * game_data() const
EditorDependencies dependencies_
InteractionMode GetMode() const
Get current interaction mode.
ModeState & GetModeState()
Get mutable reference to mode state.
void SetCanvasViewer(DungeonCanvasViewer *viewer)
void SetRooms(DungeonRoomStore *rooms)
std::string GetId() const override
Unique identifier for this panel.
void SetProjectChangedCallback(ProjectChangedCallback callback)
void DetachRuntimeContext()
absl::Status RebindProjectContext(project::YazeProject *project)
bool HasUnpublishedChanges() const
void SetCollisionBatchApplyCallback(CollisionBatchApplyCallback callback)
absl::Status PrepareProjectSave()
void SetProjectDraftChangedCallback(ProjectDraftChangedCallback callback)
void SetProjectSaveCallback(ProjectSaveCallback callback)
bool HasPendingProjectDraftChanges() const
void SetCanvasViewer(DungeonCanvasViewer *viewer)
void SetPlacementError(const std::string &message)
void SetContext(EditorContext ctx)
void SetCurrentRoom(int room_id)
void SetRooms(DungeonRoomStore *rooms)
void SetGameData(zelda3::GameData *game_data)
DungeonObjectSelector & object_selector()
void DetachRuntimeContext()
void SetCurrentPaletteGroup(const gfx::PaletteGroup &group)
void SelectObject(int obj_id)
void SetAgentOptimizedLayout(bool enabled)
void SetStandardWritePreflightCallback(StandardWritePreflightCallback callback)
void SetCurrentPaletteGroup(const gfx::PaletteGroup &group)
void SetCurrentPaletteGroupForRoom(int room_id, const gfx::PaletteGroup &group)
void SetTilesAppliedCallback(TilesAppliedCallback callback)
int current_room_id() const
absl::Status OpenForObject(int16_t object_id, int room_id, DungeonRoomStore *rooms)
bool HasUnappliedChanges() const
std::string GetId() const override
Unique identifier for this panel.
void SetCurrentPaletteGroup(const gfx::PaletteGroup &group)
Set the current palette group for graphics rendering.
void SetRooms(DungeonRoomStore *rooms)
void SetCanvasViewer(DungeonCanvasViewer *viewer)
A session-aware status bar displayed at the bottom of the application.
void SetCustomSegment(const std::string &key, const std::string &value)
Set a custom segment with key-value pair.
void SetEditorMode(const std::string &mode)
Set the current editor mode or tool.
void Show(const std::string &message, ToastType type=ToastType::kInfo, float ttl_seconds=3.0f)
std::string GetRedoDescription() const
Description of the action that would be redone (for UI)
std::string GetUndoDescription() const
Description of the action that would be undone (for UI)
size_t UndoStackSize() const
std::string GetDungeonInspectorSide() const
void SetDungeonInspectorSide(std::string side)
void SetCanvasViewer(DungeonCanvasViewer *viewer)
void SetInteraction(DungeonObjectInteraction *interaction)
std::string GetWorkspaceWindowName(size_t session_id, const std::string &base_window_id) const
Resolve the exact ImGui window name for a panel by base ID.
size_t GetActiveSessionId() const
void SetWindowPinned(size_t session_id, const std::string &base_window_id, bool pinned)
std::string GetActiveCategory() const
void RegisterWindowContent(std::unique_ptr< WindowContent > window)
Register a WindowContent instance for central drawing.
void RegisterWindow(size_t session_id, const WindowDescriptor &descriptor)
bool IsWindowOpen(size_t session_id, const std::string &base_window_id) const
bool OpenWindow(size_t session_id, const std::string &base_window_id)
bool IsWindowPinned(size_t session_id, const std::string &base_window_id) const
void ShowAllWindowsInCategory(size_t session_id, const std::string &category)
void UnregisterWindow(size_t session_id, const std::string &base_window_id)
void RegisterPanelAlias(const std::string &legacy_base_id, const std::string &canonical_base_id)
Register a legacy panel ID alias that resolves to a canonical ID.
static std::shared_ptr< MesenSocketClient > & GetClient()
int RegisterPaletteListener(PaletteChangeCallback callback)
Register a callback for palette change notifications.
void ProcessTextureQueue(IRenderer *renderer)
void UnregisterPaletteListener(int listener_id)
Unregister a palette change listener.
void Initialize(zelda3::GameData *game_data)
Initialize the palette manager with GameData.
static PaletteManager & Get()
Get the singleton instance.
Draggable, dockable panel for editor sub-windows.
bool Begin(bool *p_open=nullptr)
void SetDefaultSize(float width, float height)
uint64_t asset_generation() const
static CustomObjectManager & Get()
void RefreshFeatureFlagMappings()
static DrawRoutineRegistry & Get()
static ObjectDimensionTable & Get()
static bool IsEditableStandardObject(int16_t object_id)
Dungeon Room Entrance or Spawn Point.
void set_water_fill_sram_bit_mask(uint8_t mask)
bool HasUnsavedChanges() const
void ClearWaterFillZone()
void ClearWaterFillDirty()
int ResolveDungeonPaletteId() const
void SetGameData(GameData *data)
void SetWaterFillTile(int x, int y, bool filled)
#define ICON_MD_ROCKET_LAUNCH
#define ICON_MD_GRID_VIEW
#define ICON_MD_DOOR_FRONT
#define ICON_MD_WORKSPACES
#define LOG_DEBUG(category, format,...)
#define LOG_ERROR(category, format,...)
#define LOG_WARN(category, format,...)
#define LOG_INFO(category, format,...)
const AgentUITheme & GetTheme()
bool IsTransientDungeonRoomWindowId(const std::string &card_id)
bool IsWorkbenchNavigationWindowId(const std::string &card_id)
constexpr const char * kStandalone
constexpr const char * kWorkbench
Editors are the view controllers for the application.
absl::Status ValidateHackManifestSaveConflicts(const core::HackManifest &manifest, project::RomWritePolicy write_policy, const std::vector< std::pair< uint32_t, uint32_t > > &ranges, absl::string_view save_scope, const char *log_tag, ToastManager *toast_manager)
RoomSelectionIntent
Intent for room selection in the dungeon editor.
constexpr int kTilesheetHeight
constexpr int kTilesheetWidth
std::string MakePanelTitle(const std::string &title)
Room LoadRoomHeaderFromRom(Rom *rom, int room_id)
std::string GetRoomLabel(int id)
Convenience function to get a room label.
absl::StatusOr< std::vector< WaterFillZoneEntry > > LoadLegacyWaterGateZones(Rom *rom, const std::string &symbol_path)
gfx::PaletteGroup BuildDungeonRenderPaletteGroupFromGameData(const gfx::SnesPalette &dungeon_palette, const GameData *game_data)
constexpr int kNumDungeonSpawnPoints
std::unique_ptr< DungeonEditorSystem > CreateDungeonEditorSystem(Rom *rom, GameData *game_data)
Factory function to create dungeon editor system.
absl::StatusOr< std::vector< WaterFillZoneEntry > > LoadWaterFillTable(Rom *rom)
#define RETURN_IF_ERROR(expr)
bool kEnableCustomObjects
struct yaze::core::FeatureFlags::Flags::Dungeon dungeon
Unified dependency container for all editor types.
project::YazeProject * project
GlobalEditorContext * global_context
ToastManager * toast_manager
gfx::IRenderer * renderer
UserSettings * user_settings
WorkspaceWindowManager * window_manager
static JumpToRoomRequestEvent Create(int room, size_t session=0)
bool * show_room_debug_info
bool * show_coordinate_overlay
bool * show_camera_quadrants
bool * show_custom_collision
bool * show_minecart_sprites
bool * show_minecart_tracks
bool * show_texture_debug
bool * show_collision_legend
bool * show_track_collision
bool * show_object_bounds
Optional behavior for an interactive status bar segment.
std::function< void()> on_click
PaletteGroup dungeon_main
Represents a group of palettes.
DungeonRenderPaletteSource source
static constexpr float kStatusBarHeight
Dungeon overlay configuration (per-project).
core::HackManifest hack_manifest
std::string GetAbsolutePath(const std::string &relative_path) const
std::string symbols_filename
gfx::PaletteGroupMap palette_groups
PitDamageTable pit_damage_table