19#include "absl/status/status.h"
20#include "absl/strings/str_format.h"
21#include "absl/types/span.h"
22#include "imgui/imgui.h"
84 const absl::Status status =
87 LOG_WARN(
"DungeonEditorV2",
"Minecart project rebind failed: %s",
100 if (editor_manager ==
nullptr) {
109 [editor_manager, session_id](
111 if (!editor_manager->IsCurrentProjectContextOwnedBySession(
113 return absl::FailedPreconditionError(
114 "Minecart overlay edit requires its project context to be "
117 editor_manager->GetCurrentProject()->dungeon_overlay = overlay;
118 editor_manager->MarkCurrentProjectDirty();
119 return absl::OkStatus();
122 [editor_manager, session_id]() -> absl::Status {
123 if (!editor_manager->IsCurrentProjectContextOwnedBySession(
125 return absl::FailedPreconditionError(
126 "Minecart overlay draft requires its project context to be "
129 editor_manager->MarkCurrentProjectDirty();
130 return absl::OkStatus();
133 [editor_manager, session_id]() -> absl::Status {
134 if (!editor_manager->IsCurrentProjectContextOwnedBySession(
136 return absl::FailedPreconditionError(
137 "Minecart project save requires its project context to be "
140 return editor_manager->SaveProject();
147 constexpr const char* kPrefix =
"dungeon.room_";
148 constexpr size_t kPrefixLength = 13;
149 if (card_id.rfind(kPrefix, 0) != 0 || card_id.size() <= kPrefixLength) {
152 return std::all_of(card_id.begin() + kPrefixLength, card_id.end(),
153 [](
unsigned char ch) { return std::isdigit(ch) != 0; });
159 : rom_(rom), room_loader_(rom), room_selector_(rom), rooms_(rom) {
202 return absl::FailedPreconditionError(
"ROM not loaded");
208 std::array<zelda3::RoomEntrance, zelda3::kNumDungeonEntranceSlots>
210 std::array<zelda3::DungeonSpawnPoint, zelda3::kNumDungeonSpawnPoints>
211 refreshed_spawn_points;
215 struct RefreshedRoom {
219 std::vector<std::pair<int, bool>> materialized_rooms;
221 [&materialized_rooms](
int room_id,
const zelda3::Room& room) {
222 materialized_rooms.emplace_back(room_id, room.
IsLoaded());
225 std::vector<RefreshedRoom> refreshed_rooms;
226 refreshed_rooms.reserve(materialized_rooms.size());
227 for (
const auto& [room_id, was_loaded] : materialized_rooms) {
235 refreshed_rooms.push_back({room_id, std::move(refreshed_room)});
253 [&prepare_viewer](
int room_id,
254 std::unique_ptr<DungeonCanvasViewer>& viewer) {
255 prepare_viewer(viewer.get(), room_id);
258 const int compare_room_id =
268 (void)object_editor->ClearSelection();
269 object_editor->ClearHistory();
285 for (
auto& refreshed : refreshed_rooms) {
288 *room = std::move(refreshed.room);
315 const int resolved_palette_id = current_room->ResolveDungeonPaletteId();
317 resolved_palette_id >= 0 &&
318 resolved_palette_id <
321 ?
static_cast<uint64_t
>(resolved_palette_id)
350 [&bind_viewer](
int room_id,
351 std::unique_ptr<DungeonCanvasViewer>& viewer) {
352 bind_viewer(viewer.get(), room_id);
384 current_viewer = viewer->get();
386 if (current_viewer) {
390 return absl::OkStatus();
399 bool legacy_imported =
false;
400 const bool water_fill_save_enabled =
402 std::vector<zelda3::WaterFillZoneEntry> zones;
406 zones = std::move(zones_or.value());
408 LOG_WARN(
"DungeonEditorV2",
"WaterFillTable parse failed: %s",
409 zones_or.status().message().data());
412 absl::StrFormat(
"WaterFill table parse failed: %s",
413 zones_or.status().message()),
419 std::string sym_path;
426 if (legacy_or.ok()) {
427 zones = std::move(legacy_or.value());
428 legacy_imported = !zones.empty();
430 LOG_WARN(
"DungeonEditorV2",
"Legacy water gate import failed: %s",
431 legacy_or.status().message().data());
435 for (
const auto& z : zones) {
436 if (z.room_id < 0 || z.room_id >=
static_cast<int>(
rooms_.
size())) {
439 auto& room =
rooms_[z.room_id];
441 for (uint16_t off : z.fill_offsets) {
442 const int x =
static_cast<int>(off % 64);
443 const int y =
static_cast<int>(off / 64);
450 if (!legacy_imported || !water_fill_save_enabled) {
457 water_fill_save_enabled
458 ?
"Imported legacy water gate zones (save to write new table)"
459 :
"Loaded legacy water gate zones read-only (WaterFill saving is "
493 window_manager->RegisterPanelAlias(
"dungeon.entrances",
494 "dungeon.entrance_properties");
498 window_manager->RegisterPanel(
499 {.card_id =
"dungeon.workbench",
500 .display_name =
"Dungeon Workbench",
501 .window_title =
" Dungeon Workbench",
503 .category =
"Dungeon",
504 .shortcut_hint =
"Ctrl+Shift+W",
505 .visibility_flag =
nullptr,
508 .disabled_tooltip =
"Load a ROM to edit dungeon rooms"});
510 window_manager->RegisterPanel(
512 .display_name =
"Room List",
513 .window_title =
" Room List",
515 .category =
"Dungeon",
516 .shortcut_hint =
"Ctrl+Shift+R",
517 .visibility_flag =
nullptr,
520 .disabled_tooltip =
"Load a ROM to browse dungeon rooms"});
522 window_manager->RegisterPanel(
524 .display_name =
"Entrance List",
525 .window_title =
" Entrance List",
527 .category =
"Dungeon",
528 .shortcut_hint =
"Ctrl+Shift+E",
529 .visibility_flag =
nullptr,
532 .disabled_tooltip =
"Load a ROM to browse dungeon entrances"});
534 window_manager->RegisterPanel(
535 {.card_id =
"dungeon.entrance_properties",
536 .display_name =
"Entrance Properties",
537 .window_title =
" Entrance Properties",
539 .category =
"Dungeon",
541 .visibility_flag =
nullptr,
544 .disabled_tooltip =
"Load a ROM to edit entrance properties"});
546 window_manager->RegisterPanel(
548 .display_name =
"Room Matrix",
549 .window_title =
" Room Matrix",
551 .category =
"Dungeon",
552 .shortcut_hint =
"Ctrl+Shift+M",
553 .visibility_flag =
nullptr,
556 .disabled_tooltip =
"Load a ROM to view the room matrix"});
558 window_manager->RegisterPanel(
560 .display_name =
"Room Graphics",
561 .window_title =
" Room Graphics",
563 .category =
"Dungeon",
564 .shortcut_hint =
"Ctrl+Shift+G",
565 .visibility_flag =
nullptr,
568 .disabled_tooltip =
"Load a ROM to view room graphics"});
570 window_manager->RegisterPanel(
572 .display_name =
"Object Selector",
573 .window_title =
" Object Selector",
575 .category =
"Dungeon",
577 .visibility_flag =
nullptr,
580 .disabled_tooltip =
"Load a ROM to browse dungeon objects"});
582 window_manager->RegisterPanel(
584 .display_name =
"Door Editor",
585 .window_title =
" Door Editor",
587 .category =
"Dungeon",
589 .visibility_flag =
nullptr,
592 .disabled_tooltip =
"Load a ROM to edit dungeon doors"});
594 window_manager->RegisterPanel(
596 .display_name =
"Palette Editor",
597 .window_title =
" Palette Editor",
599 .category =
"Dungeon",
601 .shortcut_hint =
"Ctrl+Shift+Alt+P",
602 .visibility_flag =
nullptr,
605 .disabled_tooltip =
"Load a ROM to edit dungeon palettes"});
620 window_manager->RegisterWindowContent(std::make_unique<RoomBrowserContent>(
623 window_manager->RegisterWindowContent(
624 std::make_unique<DungeonEntranceListPanel>(
629 auto matrix_panel = std::make_unique<RoomMatrixContent>(
632 [
this](
int old_room,
int new_room) {
636 matrix_panel->SetRoomIntentCallback(
640 window_manager->RegisterWindowContent(std::move(matrix_panel));
644 auto workbench = std::make_unique<DungeonWorkbenchContent>(
650 [
this](
int room_id) {
653 LOG_ERROR(
"DungeonEditorV2",
"Apply Room failed: %s",
654 status.message().data());
657 absl::StrFormat(
"Apply Room failed: %s", status.message()),
669 [
this]() ->
const std::deque<int>& {
return recent_rooms_; },
670 [
this](
int room_id) {
680 [
this]() {
Undo().IgnoreError(); }, [
this]() {
Redo().IgnoreError(); },
694 window_manager->RegisterWindowContent(std::move(workbench));
697 window_manager->RegisterWindowContent(std::make_unique<DungeonEntrancesPanel>(
707 return absl::FailedPreconditionError(
"ROM not loaded");
713 if (!dim_table.IsLoaded()) {
721 return absl::FailedPreconditionError(
"GameData not available");
754 auto graphics_panel = std::make_unique<RoomGraphicsContent>(
758 std::move(graphics_panel));
763 std::move(palette_panel));
771 auto object_selector = std::make_unique<ObjectSelectorContent>(
774 auto object_editor = std::make_unique<ObjectEditorContent>(
781 rooms_[current_room_id_].RenderRoomGraphics();
786 object_selector->SetRooms(&
rooms_);
789 auto open_workbench_selection_inspector = [
this]() {
796 object_selector->SetOpenObjectEditorCallback(
797 open_workbench_selection_inspector);
803 auto jump_to_reciprocal_door = [
this](
int neighbor_room_id,
805 if (neighbor_room_id < 0 ||
806 neighbor_room_id >=
static_cast<int>(
rooms_.
size())) {
811 viewer->object_interaction()
812 .entity_coordinator()
814 .SelectDoor(door_index);
817 object_editor->SetJumpToReciprocalDoorCallback(jump_to_reciprocal_door);
820 auto door_editor = std::make_unique<DoorEditorContent>();
821 door_editor->SetRooms(&
rooms_);
822 door_editor->SetOpenSelectionInspectorCallback(
823 open_workbench_selection_inspector);
824 door_editor->SetJumpToReciprocalDoorCallback(jump_to_reciprocal_door);
841 auto custom_collision_panel =
842 std::make_unique<CustomCollisionPanel>(
nullptr,
nullptr);
845 auto water_fill_panel = std::make_unique<WaterFillPanel>(
nullptr,
nullptr);
848 auto room_tag_panel = std::make_unique<RoomTagEditorPanel>();
850 room_tag_panel->SetRooms(&
rooms_);
854 std::unique_ptr<MinecartTrackEditorPanel> minecart_panel;
856 minecart_panel = std::make_unique<MinecartTrackEditorPanel>();
888 std::move(object_selector));
895 sprite_panel->SetOpenSelectionInspectorCallback(
896 open_workbench_selection_inspector);
899 std::move(sprite_panel));
903 item_panel->SetOpenSelectionInspectorCallback(
904 open_workbench_selection_inspector);
909 std::move(custom_collision_panel));
911 std::move(water_fill_panel));
913 std::move(room_tag_panel));
914 if (minecart_panel) {
916 std::move(minecart_panel));
921 auto tile_editor_panel =
924 tile_editor_panel->SetStandardWritePreflightCallback(
925 [
this](
const std::vector<std::pair<uint32_t, uint32_t>>& ranges) {
928 return absl::OkStatus();
933 "dungeon object tile data",
"ObjectTileEditorPanel",
936 tile_editor_panel->SetStandardTilesAppliedCallback([
this]() {
944 tile_editor_panel->SetObjectCreatedCallback(
945 [
this](
int object_id,
const std::string& filename) {
961 std::move(tile_editor_panel));
983 auto overlay_panel = std::make_unique<OverlayManagerPanel>();
986 std::move(overlay_panel));
1017 return absl::OkStatus();
1026 [
this](
const std::string& group_name,
int palette_index) {
1032 int palette_index) {
1037 if (group_name ==
"hud") {
1040 }
else if (group_name ==
"dungeon_main") {
1050 std::array<bool, zelda3::kNumberOfRooms> rendered_rooms{};
1051 auto render_once = [
this, &rendered_rooms](
int room_id) {
1052 if (room_id < 0 || room_id >=
static_cast<int>(
rooms_.
size()) ||
1053 room_id >=
static_cast<int>(rendered_rooms.size()) ||
1054 rendered_rooms[room_id]) {
1057 rooms_[room_id].RenderRoomGraphics();
1058 rendered_rooms[room_id] =
true;
1069 bool force_shared_palette_refresh) {
1076 force_shared_palette_refresh &&
1082 const bool force_shared_palette_refresh =
1086 static_cast<uint64_t
>(dungeon_main_pal_group.size())) {
1089 if (
auto pal_group =
1098 [
this, change, &apply_palette, &dungeon_main_pal_group,
1099 &render_once, force_shared_palette_refresh](
1100 int room_id, std::unique_ptr<DungeonCanvasViewer>& viewer) {
1101 if (room_id < 0 || room_id >=
static_cast<int>(
rooms_.
size())) {
1104 const int palette_id =
rooms_[room_id].ResolveDungeonPaletteId();
1105 if (palette_id < 0 ||
1107 static_cast<int>(dungeon_main_pal_group.size())) {
1116 dungeon_main_pal_group.palette_ref(palette_id));
1117 if (room_palette_group.ok()) {
1118 if (viewer && viewer->object_interaction().IsObjectLoaded()) {
1119 render_once(room_id);
1121 apply_palette(viewer.get(), palette_id,
1122 room_palette_group.value(),
1123 force_shared_palette_refresh);
1157 room.MarkGraphicsDirty();
1180 if (loading_card.
Begin()) {
1181 ImGui::TextColored(theme.text_secondary_gray,
1182 tr(
"Loading dungeon data..."));
1184 tr(
"Independent editor cards will appear once ROM data is loaded."));
1187 return absl::OkStatus();
1195 if (!ImGui::GetIO().WantTextInput) {
1196 if (ImGui::IsKeyPressed(ImGuiKey_Delete)) {
1199 viewer->DeleteSelectedObjects();
1203 if (ImGui::GetIO().KeyCtrl && ImGui::GetIO().KeyShift &&
1204 ImGui::IsKeyPressed(ImGuiKey_W,
false)) {
1209 if (ImGui::IsKeyPressed(ImGuiKey_Tab) && ImGui::GetIO().KeyCtrl) {
1212 int current_idx = -1;
1213 for (
int i = 0; i < static_cast<int>(
recent_rooms_.size()); ++i) {
1219 if (current_idx != -1) {
1221 if (ImGui::GetIO().KeyShift) {
1226 (current_idx - 1 +
static_cast<int>(
recent_rooms_.size())) %
1233 int current_idx = -1;
1241 if (current_idx != -1) {
1243 if (ImGui::GetIO().KeyShift) {
1255 if (ImGui::GetIO().KeyCtrl) {
1257 const int kCols = 16;
1259 if (ImGui::IsKeyPressed(ImGuiKey_UpArrow)) {
1262 }
else if (ImGui::IsKeyPressed(ImGuiKey_DownArrow)) {
1265 }
else if (ImGui::IsKeyPressed(ImGuiKey_LeftArrow)) {
1268 }
else if (ImGui::IsKeyPressed(ImGuiKey_RightArrow)) {
1274 if (next_room != -1) {
1288 return absl::OkStatus();
1298 room_opts.
tooltip =
"Click to refocus viewer on this room";
1299 room_opts.
on_click = [
this, room_id]() {
1306 std::move(room_opts));
1314 absl::StrFormat(
"%d rooms loaded of %d total", loaded, total);
1316 absl::StrFormat(
"%d/%d", loaded, total),
1317 std::move(rooms_opts));
1323 "This room has pending editor changes that are not yet applied to the "
1326 std::move(current_room_opts));
1330 mode_opts.
tooltip =
"Click to toggle Workbench / Standalone workflow";
1337 std::move(mode_opts));
1372 return entrance.dirty();
1378 return spawn.dirty();
1402 return room->HasUnsavedChanges();
1419 if (!window_manager) {
1427 window_manager->OpenWindow(session_id,
"dungeon.workbench");
1435 for (
const auto& descriptor :
1436 window_manager->GetWindowsInCategory(session_id,
"Dungeon")) {
1437 const std::string& card_id = descriptor.card_id;
1438 if (card_id ==
"dungeon.workbench") {
1441 if (window_manager->IsWindowPinned(session_id, card_id)) {
1444 const bool is_room_window = IsTransientDungeonRoomWindowId(card_id);
1447 window_manager->CloseWindow(session_id, card_id);
1451 window_manager->CloseWindow(session_id,
"dungeon.workbench");
1461 enabled ?
"Dungeon workflow: Workbench"
1462 :
"Dungeon workflow: Standalone Panels",
1481 std::string card_id = absl::StrFormat(
"dungeon.room_%d", room_id);
1482 bool panel_visible =
true;
1487 if (!panel_visible) {
1500 std::string active_category =
1505 if (active_category !=
"Dungeon" && !is_pinned) {
1521 ImGui::SetNextWindowDockID(
room_dock_id_, ImGuiCond_FirstUseEver);
1523 if (room_card->Begin(&open)) {
1524 if (ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows)) {
1561 if (room_id < 0 || room_id >= 0x128) {
1562 ImGui::Text(tr(
"Invalid room ID: %d"), room_id);
1566 auto& room =
rooms_[room_id];
1568 if (!room.IsLoaded()) {
1571 ImGui::TextColored(theme.text_error_red, tr(
"Failed to load room: %s"),
1572 status.message().data());
1578 if (!sys_status.ok()) {
1579 LOG_ERROR(
"DungeonEditorV2",
"Failed to load system data: %s",
1580 sys_status.message().data());
1585 if (room.IsLoaded()) {
1586 bool needs_render =
false;
1588 if (room.blocks().empty()) {
1590 room.LoadRoomGraphics();
1591 needs_render =
true;
1594 if (!room.AreObjectsLoaded()) {
1596 needs_render =
true;
1599 auto& bg1_bitmap = room.bg1_buffer().bitmap();
1600 if (needs_render || !bg1_bitmap.is_active() || bg1_bitmap.width() == 0) {
1601 room.RenderRoomGraphics();
1605 if (room.IsLoaded()) {
1606 ImGui::TextColored(theme.text_success_green,
ICON_MD_CHECK " Loaded");
1608 ImGui::TextColored(theme.text_error_red,
ICON_MD_PENDING " Not Loaded");
1611 ImGui::TextDisabled(tr(
"Objects: %zu"), room.GetTileObjects().size());
1616 const std::string return_label = absl::StrFormat(
1618 if (ImGui::SmallButton(return_label.c_str())) {
1621 if (ImGui::IsItemHovered()) {
1623 tr(
"Switch back to the integrated Dungeon Workbench workflow "
1624 "(Ctrl+Shift+W)."));
1632 bool connected = client && client->IsConnected();
1634 ImGui::BeginDisabled();
1636 std::string warp_label =
1638 if (ImGui::SmallButton(warp_label.c_str())) {
1639 auto status = client->WriteWord(0x7E00A0,
static_cast<uint16_t
>(room_id));
1643 absl::StrFormat(
"Warped to room 0x%03X", room_id),
1649 absl::StrFormat(
"Warp failed: %s", status.message()),
1655 ImGui::EndDisabled();
1656 if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled)) {
1657 ImGui::SetTooltip(tr(
"Connect to Mesen2 first"));
1660 if (ImGui::IsItemHovered()) {
1661 ImGui::SetTooltip(tr(
"Warp to room 0x%03X in Mesen2"), room_id);
1672 const float status_bar_reserved =
1673 std::max(ImGui::GetFontSize() + ImGui::GetStyle().FramePadding.y * 2.0f,
1675 ImGui::GetStyle().ItemSpacing.y;
1676 if (ImGui::BeginChild(
"##StandaloneRoomBody",
1677 ImVec2(0.0f, -status_bar_reserved),
false)) {
1678 viewer->DrawDungeonCanvas(room_id);
1682 bool room_dirty =
false;
1683 if (room_id >= 0 && room_id <
static_cast<int>(
rooms_.
size())) {
1684 room_dirty =
rooms_[room_id].HasUnsavedChanges();
1686 const char* tool_mode =
1687 viewer->object_interaction().mode_manager().GetModeName();
1690 status.workflow_primary =
false;
1694 status.on_undo = [
this]() {
1695 Undo().IgnoreError();
1697 status.on_redo = [
this]() {
1698 Redo().IgnoreError();
1711 if (room_id < 0 || room_id >=
static_cast<int>(
rooms_.
size())) {
1730 if (room_id < 0 || room_id >=
static_cast<int>(
rooms_.
size())) {
1731 LOG_WARN(
"DungeonEditorV2",
"Ignoring invalid room selection: %d", room_id);
1759 if (room_id >= 0 && room_id < (
int)
rooms_.
size()) {
1760 auto& room =
rooms_[room_id];
1761 if (!room.IsLoaded()) {
1765 if (room.IsLoaded()) {
1777 auto dungeon_main_pal_group =
1824 std::string card_id = absl::StrFormat(
"dungeon.room_%d", room_id);
1827 if (request_focus) {
1839 std::string room_name = absl::StrFormat(
1842 std::string base_card_id = absl::StrFormat(
"dungeon.room_%d", room_id);
1845 {.card_id = base_card_id,
1846 .display_name = room_name,
1849 .category =
"Dungeon",
1850 .shortcut_hint =
"",
1851 .visibility_flag =
nullptr,
1852 .priority = 200 + room_id});
1859 if (entrance_id < 0 || entrance_id >=
static_cast<int>(
entrances_.size())) {
1866 if (room_id < 0 || room_id >=
static_cast<int>(
rooms_.
size())) {
1873 if (entrance_id < 0 || entrance_id >=
static_cast<int>(
entrances_.size())) {
1878 return spawn.spawn_id() == entrance_id ? spawn.room_id : -1;
1884 int room_id)
const {
1885 if (room_id < 0 || room_id >=
static_cast<int>(
rooms_.
size())) {
1897 return spawn.main_gfx;
1901 if (entrance.room_ != room_id) {
1904 return entrance.blockset_;
1908 if (room_id < 0 || room_id >=
static_cast<int>(
rooms_.
size())) {
1911 auto& room =
rooms_[room_id];
1912 if (!room.IsLoaded()) {
1915 room.SetRenderEntranceBlockset(
1921 if (viewer ==
nullptr) {
1924 const uint8_t entrance_blockset =
1926 if (entrance_blockset == 0xFF) {
1940 it->second->Focus();
1980 LOG_ERROR(
"DungeonEditorV2",
"Cannot place object: Invalid room ID %d",
1982 const std::string message = absl::StrFormat(
1995 "Placing object ID=0x%02X at position (%d,%d) in room %03X", obj.
id_,
1998 room.RenderRoomGraphics();
2000 "Object placed and room re-rendered successfully");
2005 absl::StrFormat(
"Placed 0x%02X in room %03X", obj.
id_,
2013 if (room_id < 0 || room_id >=
static_cast<int>(
rooms_.
size())) {
2014 LOG_WARN(
"DungeonEditorV2",
"Edit Graphics ignored (invalid room id %d)",
2020 if (!editor_manager) {
2022 "Edit Graphics ignored (editor manager unavailable)");
2026 auto& room =
rooms_[room_id];
2028 room.LoadRoomGraphics();
2030 uint16_t sheet_id = 0;
2031 uint16_t tile_index = 0;
2032 bool resolved_sheet =
false;
2033 if (
auto tiles_or =
object.GetTiles();
2034 tiles_or.ok() && !tiles_or.value().empty()) {
2035 const uint16_t tile_id = tiles_or.value().front().id_;
2036 const size_t block_index =
static_cast<size_t>(tile_id / 64);
2037 const auto blocks = room.blocks();
2038 if (block_index < blocks.size()) {
2039 sheet_id = blocks[block_index];
2040 resolved_sheet =
true;
2044 const int tiles_per_sheet = tiles_per_row * tiles_per_col;
2045 if (tiles_per_sheet > 0) {
2046 tile_index =
static_cast<uint16_t
>(tile_id % tiles_per_sheet);
2051 if (
auto* editor_set = editor_manager->GetCurrentEditorSet()) {
2052 if (
auto* graphics = editor_set->GetGraphicsEditor()) {
2053 if (resolved_sheet) {
2054 graphics->SelectSheet(sheet_id);
2055 graphics->HighlightTile(sheet_id, tile_index,
2056 absl::StrFormat(
"Object 0x%02X",
object.id_));
2069 return absl::FailedPreconditionError(
2070 "Object Tile Editor panel is unavailable");
2073 return absl::FailedPreconditionError(
2074 "Workspace window manager is unavailable");
2077 return absl::FailedPreconditionError(
"ROM not loaded");
2079 if (room_id < 0 || room_id >=
static_cast<int>(
rooms_.
size())) {
2080 return absl::OutOfRangeError(
"Dungeon room id is outside the room store");
2083 return absl::UnimplementedError(
2084 "Object tile editing is supported only for audited objects 0x11F and "
2089 return absl::FailedPreconditionError(
"Dungeon palette data is unavailable");
2092 auto& room =
rooms_[room_id];
2093 if (!room.IsLoaded()) {
2097 room.PrepareForRender();
2099 const int palette_id = room.ResolveDungeonPaletteId();
2101 if (palette_id < 0 ||
2102 palette_id >=
static_cast<int>(dungeon_palettes.size())) {
2103 return absl::OutOfRangeError(
2104 "Room resolves to an unavailable dungeon palette");
2106 auto room_palette = dungeon_palettes[palette_id];
2111 object.id_, room_id, &
rooms_, palette_group);
2112 if (!open_status.ok()) {
2125 return absl::NotFoundError(
2126 "Object Tile Editor window is not registered in this session");
2128 return absl::OkStatus();
2134 if (new_room_id < 0 || new_room_id >=
static_cast<int>(
rooms_.
size())) {
2152 int swap_index = -1;
2160 if (swap_index < 0) {
2180 slot_id = it->second;
2197 std::string old_card_id = absl::StrFormat(
"dungeon.room_%d", old_room_id);
2198 const bool old_pinned =
2204 std::string new_room_name = absl::StrFormat(
2207 std::string new_card_id = absl::StrFormat(
"dungeon.room_%d", new_room_id);
2210 {.card_id = new_card_id,
2211 .display_name = new_room_name,
2214 .category =
"Dungeon",
2215 .shortcut_hint =
"",
2216 .visibility_flag =
nullptr,
2217 .priority = 200 + new_room_id});
2329 const absl::Status status =
2334 LOG_ERROR(
"DungeonEditorV2",
"Edit Object Tiles failed: %s",
2335 status.message().data());
2338 absl::StrFormat(
"Edit Object Tiles failed: %s", status.message()),
2343 auto status =
SaveRoom(target_room_id);
2345 LOG_ERROR(
"DungeonEditorV2",
"Apply Room failed: %s",
2346 status.message().data());
2349 absl::StrFormat(
"Apply Room failed: %s", status.message()),
2371 room_viewers_.SetEvictionPredicate([
this](
const int& candidate) {
2381 auto* viewer_ptr = existing->get();
2387 std::string card_id = absl::StrFormat(
"dungeon.room_%d", room_id);
2390 viewer_ptr->
SetPinCallback([
this, card_id, room_id](
bool pinned) {
2392 dependencies_.window_manager->SetWindowPinned(card_id, pinned);
2393 if (auto* v = GetViewerForRoom(room_id)) {
2394 v->SetPinned(pinned);
2405 auto viewer = std::make_unique<DungeonCanvasViewer>(rom_);
2406 viewer->SetCompactHeaderMode(
false);
2407 viewer->SetRoomDetailsExpanded(
true);
2410 viewer->SetRenderer(renderer_);
2411 viewer->SetCurrentPaletteGroup(current_palette_group_);
2412 viewer->SetCurrentPaletteId(current_palette_id_);
2413 viewer->SetGameData(game_data_);
2414 ConfigureViewerRenderContext(viewer_ptr, room_id);
2415 ApplyEntranceRenderContext(room_id);
2421 viewer->object_interaction().SetMutationCallback([
this, viewer_ptr]() {
2423 if (rid >= 0 && rid <
static_cast<int>(rooms_.size())) {
2427 BeginUndoSnapshot(rid);
2429 BeginCollisionUndoSnapshot(rid);
2431 BeginWaterFillUndoSnapshot(rid);
2436 viewer->object_interaction().SetCacheInvalidationCallback([
this,
2439 if (rid >= 0 && rid <
static_cast<int>(rooms_.size())) {
2443 rooms_[rid].MarkObjectsDirty();
2444 rooms_[rid].RenderRoomGraphics();
2450 FinalizeUndoAction(rid);
2460 FinalizeCollisionUndoAction(rid);
2469 FinalizeWaterFillUndoAction(rid);
2474 viewer->object_interaction().SetObjectPlacedCallback(
2475 [
this](
const zelda3::RoomObject& obj) { HandleObjectPlaced(obj); });
2477 if (dungeon_editor_system_) {
2478 viewer->SetEditorSystem(dungeon_editor_system_.get());
2480 viewer->SetRoomNavigationCallback([
this](
int target_room) {
2481 if (target_room >= 0 && target_room <
static_cast<int>(rooms_.size())) {
2482 OnRoomSelected(target_room);
2486 viewer->SetRoomSwapCallback([
this](
int old_room,
int new_room) {
2487 SwapRoomInPanel(old_room, new_room);
2489 WireViewerPanelCallbacks(viewer.get());
2492 if (dependencies_.window_manager) {
2493 std::string card_id = absl::StrFormat(
"dungeon.room_%d", room_id);
2494 viewer->SetPinned(dependencies_.window_manager->IsWindowPinned(card_id));
2495 viewer->SetPinCallback([
this, card_id, room_id](
bool pinned) {
2496 if (dependencies_.window_manager) {
2497 dependencies_.window_manager->SetWindowPinned(card_id, pinned);
2499 if (auto* v = GetViewerForRoom(room_id)) {
2500 v->SetPinned(pinned);
2506 auto* stored = room_viewers_.Insert(room_id, std::move(viewer));
2507 return stored->get();
2511void DungeonEditorV2::RefreshWorkbenchViewerRuntimeContext(
2517 if (viewer->
rom() != rom_) {
2520 if (viewer->
rooms() != &rooms_) {
2523 if (viewer->
game_data() != game_data_) {
2529 ConfigureViewerRenderContext(viewer, room_id);
2532 if (viewer->
project() != dependencies_.project) {
2538 if (!workbench_viewer_) {
2539 workbench_viewer_ = std::make_unique<DungeonCanvasViewer>(rom_);
2540 auto* viewer = workbench_viewer_.get();
2541 viewer->SetCompactHeaderMode(
true);
2542 viewer->SetRoomDetailsExpanded(
false);
2543 viewer->SetHeaderVisible(
false);
2544 viewer->SetHeaderHiddenMetadataHudVisible(
false);
2545 viewer->SetRooms(&rooms_);
2546 viewer->SetRenderer(renderer_);
2547 viewer->SetCurrentPaletteGroup(current_palette_group_);
2548 viewer->SetCurrentPaletteId(current_palette_id_);
2549 viewer->SetGameData(game_data_);
2553 viewer->object_interaction().SetMutationCallback([
this, viewer]() {
2554 const int rid = viewer ? viewer->current_room_id() : -1;
2555 if (rid >= 0 && rid <
static_cast<int>(rooms_.size())) {
2556 const auto domain = viewer->object_interaction().last_mutation_domain();
2557 if (domain == MutationDomain::kTileObjects) {
2558 BeginUndoSnapshot(rid);
2559 }
else if (domain == MutationDomain::kCustomCollision) {
2560 BeginCollisionUndoSnapshot(rid);
2561 }
else if (domain == MutationDomain::kWaterFill) {
2562 BeginWaterFillUndoSnapshot(rid);
2566 viewer->object_interaction().SetCacheInvalidationCallback([
this, viewer]() {
2567 const int rid = viewer ? viewer->current_room_id() : -1;
2568 if (rid >= 0 && rid <
static_cast<int>(rooms_.size())) {
2570 viewer->object_interaction().last_invalidation_domain();
2571 if (domain == MutationDomain::kTileObjects) {
2572 rooms_[rid].MarkObjectsDirty();
2573 rooms_[rid].RenderRoomGraphics();
2575 viewer->object_interaction().mode_manager().GetMode();
2576 if (mode != InteractionMode::DraggingObjects) {
2577 FinalizeUndoAction(rid);
2579 }
else if (domain == MutationDomain::kCustomCollision) {
2581 viewer->object_interaction().mode_manager().GetMode();
2583 viewer->object_interaction().mode_manager().GetModeState();
2584 if (mode == InteractionMode::PaintCollision && st.is_painting) {
2587 FinalizeCollisionUndoAction(rid);
2588 }
else if (domain == MutationDomain::kWaterFill) {
2590 viewer->object_interaction().mode_manager().GetMode();
2592 viewer->object_interaction().mode_manager().GetModeState();
2593 if (mode == InteractionMode::PaintWaterFill && st.is_painting) {
2596 FinalizeWaterFillUndoAction(rid);
2601 viewer->object_interaction().SetObjectPlacedCallback(
2604 if (dungeon_editor_system_) {
2605 viewer->SetEditorSystem(dungeon_editor_system_.get());
2610 viewer->SetRoomSwapCallback([
this](
int ,
int new_room) {
2611 OnRoomSelected(new_room,
false);
2613 viewer->SetRoomNavigationCallback([
this](
int target_room) {
2614 OnRoomSelected(target_room,
false);
2617 WireViewerPanelCallbacks(viewer);
2620 auto* viewer = workbench_viewer_.get();
2621 RefreshWorkbenchViewerRuntimeContext(viewer, current_room_id_);
2626 if (!workbench_compare_viewer_) {
2627 workbench_compare_viewer_ = std::make_unique<DungeonCanvasViewer>(rom_);
2628 auto* viewer = workbench_compare_viewer_.get();
2629 viewer->SetCompactHeaderMode(
true);
2630 viewer->SetRoomDetailsExpanded(
false);
2631 viewer->SetHeaderHiddenMetadataHudVisible(
false);
2632 viewer->SetRooms(&rooms_);
2633 viewer->SetRenderer(renderer_);
2634 viewer->SetCurrentPaletteGroup(current_palette_group_);
2635 viewer->SetCurrentPaletteId(current_palette_id_);
2636 viewer->SetGameData(game_data_);
2640 viewer->SetObjectInteractionEnabled(
false);
2641 viewer->SetHeaderReadOnly(
true);
2642 viewer->SetHeaderVisible(
false);
2644 if (dungeon_editor_system_) {
2647 viewer->SetEditorSystem(dungeon_editor_system_.get());
2650 viewer->SetMinecartTrackPanel(minecart_track_editor_panel_);
2651 viewer->SetProject(dependencies_.project);
2654 auto* viewer = workbench_compare_viewer_.get();
2655 RefreshWorkbenchViewerRuntimeContext(viewer, room_id);
2656 viewer->SetObjectInteractionEnabled(
false);
2657 viewer->SetHeaderReadOnly(
true);
2658 viewer->SetHeaderVisible(
false);
2662void DungeonEditorV2::SyncPanelsToRoom(
int room_id) {
2664 if (object_selector_panel_) {
2665 object_selector_panel_->SetCanvasViewerProvider(
2666 [
this]() {
return GetViewerForRoom(current_room_id_); });
2667 object_selector_panel_->SetCurrentRoom(room_id);
2668 object_selector_panel_->SetCanvasViewer(GetViewerForRoom(room_id));
2670 if (object_editor_content_) {
2671 object_editor_content_->SetCanvasViewerProvider(
2672 [
this]() {
return GetViewerForRoom(current_room_id_); });
2673 object_editor_content_->SetCurrentRoom(room_id);
2674 object_editor_content_->SetCanvasViewer(GetViewerForRoom(room_id));
2676 if (door_editor_panel_) {
2677 door_editor_panel_->SetCanvasViewerProvider(
2678 [
this]() {
return GetViewerForRoom(current_room_id_); });
2679 door_editor_panel_->SetCurrentRoom(room_id);
2680 door_editor_panel_->SetCanvasViewer(GetViewerForRoom(room_id));
2684 if (sprite_editor_panel_) {
2685 sprite_editor_panel_->SetCanvasViewer(GetViewerForRoom(room_id));
2687 if (item_editor_panel_) {
2688 item_editor_panel_->SetCanvasViewer(GetViewerForRoom(room_id));
2690 if (custom_collision_panel_) {
2691 auto* viewer = GetViewerForRoom(room_id);
2692 custom_collision_panel_->SetCanvasViewer(viewer);
2694 custom_collision_panel_->SetInteraction(&viewer->object_interaction());
2697 if (water_fill_panel_) {
2698 auto* viewer = GetViewerForRoom(room_id);
2699 water_fill_panel_->SetCanvasViewer(viewer);
2701 water_fill_panel_->SetInteraction(&viewer->object_interaction());
2705 if (room_tag_editor_panel_) {
2706 room_tag_editor_panel_->SetCurrentRoomId(room_id);
2709 if (overlay_manager_panel_) {
2710 auto* viewer = GetViewerForRoom(room_id);
2713 overlay_state.
show_grid = viewer->mutable_show_grid();
2716 viewer->mutable_show_coordinate_overlay();
2718 viewer->mutable_show_room_debug_info();
2722 viewer->mutable_show_minecart_tracks();
2724 viewer->mutable_show_custom_collision_overlay();
2726 viewer->mutable_show_track_collision_overlay();
2728 viewer->mutable_show_camera_quadrant_overlay();
2730 viewer->mutable_show_minecart_sprite_overlay();
2732 viewer->mutable_show_track_collision_legend();
2733 overlay_manager_panel_->SetState(overlay_state);
2738void DungeonEditorV2::ShowRoomPanel(
int room_id) {
2739 if (room_id < 0 || room_id >=
static_cast<int>(rooms_.size())) {
2743 bool already_active =
false;
2744 for (
int i = 0; i < active_rooms_.Size; ++i) {
2745 if (active_rooms_[i] == room_id) {
2746 already_active =
true;
2750 if (!already_active) {
2751 active_rooms_.push_back(room_id);
2752 room_selector_.set_active_rooms(active_rooms_);
2755 std::string card_id = absl::StrFormat(
"dungeon.room_%d", room_id);
2757 if (dependencies_.window_manager) {
2758 if (!dependencies_.window_manager->GetWindowDescriptor(
2759 dependencies_.window_manager->GetActiveSessionId(), card_id)) {
2760 std::string room_name = absl::StrFormat(
2762 dependencies_.window_manager->RegisterWindow(
2763 {.card_id = card_id,
2764 .display_name = room_name,
2767 .category =
"Dungeon",
2768 .shortcut_hint =
"",
2769 .visibility_flag =
nullptr,
2770 .priority = 200 + room_id});
2772 dependencies_.window_manager->OpenWindow(card_id);
2776 if (room_cards_.find(room_id) == room_cards_.end()) {
2777 std::string base_name = absl::StrFormat(
2779 const int slot_id = GetOrCreateRoomPanelSlotId(room_id);
2780 std::string card_name_str = absl::StrFormat(
2781 "%s###RoomPanelSlot%d",
MakePanelTitle(base_name).c_str(), slot_id);
2783 auto card = std::make_shared<gui::PanelWindow>(card_name_str.c_str(),
2785 card->SetDefaultSize(620, 700);
2786 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 SetRooms(DungeonRoomStore *rooms)
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_
bool HasPendingRoomChanges() const
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)
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)
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
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_
gui::PaletteEditorWidget palette_editor_
std::unique_ptr< ObjectEditorContent > owned_object_editor_content_
std::unique_ptr< DoorEditorContent > owned_door_editor_panel_
int PendingRoomCount() const
static bool IsValidRoomId(int room_id)
void SwapRoomInPanel(int old_room_id, int new_room_id)
RoomGraphicsContent * room_graphics_panel_
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)
static constexpr const char * kRoomGraphicsId
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_
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_
MutationDomain last_mutation_domain() const
InteractionModeManager & mode_manager()
MutationDomain last_invalidation_domain() const
bool IsObjectLoaded() const
void SetCustomObjectsFolder(const std::string &folder)
void SetProject(project::YazeProject *project)
void InvalidatePreviewCache()
void SetOpenTileEditorWindowCallback(std::function< bool()> callback)
void SetTileEditorPanel(ObjectTileEditorPanel *panel)
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)
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 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 SetOnInspectorSideChanged(std::function< void(bool)> cb)
void OpenRoomGraphicsTool()
void FocusRoomInspector()
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)
void SetProjectChangedCallback(ProjectChangedCallback callback)
void SetRoomNavigationCallback(RoomNavigationCallback callback)
absl::Status RebindProjectContext(project::YazeProject *project)
bool HasUnpublishedChanges() const
absl::Status SetProject(project::YazeProject *project)
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 SetCanvasViewer(DungeonCanvasViewer *viewer)
void SetContext(EditorContext ctx)
void SetCurrentRoom(int room_id)
void SetRooms(DungeonRoomStore *rooms)
void SetGameData(zelda3::GameData *game_data)
DungeonObjectSelector & object_selector()
void SetCurrentPaletteGroup(const gfx::PaletteGroup &group)
void SelectObject(int obj_id)
void SetAgentOptimizedLayout(bool enabled)
void SetCurrentPaletteGroup(const gfx::PaletteGroup &group)
void SetCurrentPaletteGroupForRoom(int room_id, const gfx::PaletteGroup &group)
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)
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)
static CustomObjectManager & Get()
void AddObjectFile(int object_id, const std::string &filename)
void Initialize(const std::string &custom_objects_folder)
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,...)
#define ASSIGN_OR_RETURN(type_variable_name, expression)
const AgentUITheme & GetTheme()
bool IsTransientDungeonRoomWindowId(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.
absl::StatusOr< PaletteGroup > CreatePaletteGroupFromLargePalette(SnesPalette &palette, int num_colors)
Create a PaletteGroup by dividing a large palette into sub-palettes.
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)
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)
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).
std::unordered_map< int, std::vector< std::string > > custom_object_files
std::string custom_objects_folder
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