yaze 0.3.2
Link to the Past ROM Editor
 
Loading...
Searching...
No Matches
dungeon_editor_v2.cc
Go to the documentation of this file.
1// Related header
2#include "dungeon_editor_v2.h"
3
4// C system headers
5#include <cstdio>
6
7// C++ standard library headers
8#include <iterator>
9#include <memory>
10#include <string>
11#include <utility>
12#include <vector>
13
14// Third-party library headers
15#include "absl/status/status.h"
16#include "absl/strings/str_format.h"
17#include "imgui/imgui.h"
18
19// Project headers
37#include "app/gui/core/icons.h"
38#include "util/log.h"
39#include "util/macro.h"
40#include "core/project.h"
42#include "core/features.h"
46#include "zelda3/dungeon/room.h"
48
49namespace yaze::editor {
50
52 // Clear viewer references in panels BEFORE room_viewers_ is destroyed.
53 // Panels are owned by PanelManager and outlive this editor, so they need
54 // to have their viewer pointers cleared to prevent dangling pointer access.
57 }
60 }
63 }
64}
65
67 renderer_ = renderer;
68 rom_ = rom;
69
70 // Propagate ROM to all rooms
71 if (rom_) {
72 for (auto& room : rooms_) {
73 room.SetRom(rom_);
74 }
75 }
76
77 // Setup docking class for room windows
78 room_window_class_.DockingAllowUnclassed = true;
79 room_window_class_.DockingAlwaysTabBar = true;
80
81 if (!dependencies_.panel_manager) return;
82 auto* panel_manager = dependencies_.panel_manager;
83
84 // Register panels with PanelManager (no boolean flags - visibility is
85 // managed entirely by PanelManager::ShowPanel/HidePanel/IsPanelVisible)
86 panel_manager->RegisterPanel(
87 {.card_id = kControlPanelId,
88 .display_name = "Dungeon Controls",
89 .window_title = " Dungeon Controls",
90 .icon = ICON_MD_CASTLE,
91 .category = "Dungeon",
92 .shortcut_hint = "Ctrl+Shift+D",
93 .visibility_flag = nullptr,
94 .enabled_condition = [this]() { return rom_ && rom_->is_loaded(); },
95 .disabled_tooltip = "Load a ROM to access dungeon controls",
96 .priority = 10});
97
98 panel_manager->RegisterPanel(
99 {.card_id = kRoomSelectorId,
100 .display_name = "Room List",
101 .window_title = " Room List",
102 .icon = ICON_MD_LIST,
103 .category = "Dungeon",
104 .shortcut_hint = "Ctrl+Shift+R",
105 .visibility_flag = nullptr,
106 .enabled_condition = [this]() { return rom_ && rom_->is_loaded(); },
107 .disabled_tooltip = "Load a ROM to browse dungeon rooms",
108 .priority = 20});
109
110 panel_manager->RegisterPanel(
111 {.card_id = kEntranceListId,
112 .display_name = "Entrance List",
113 .window_title = " Entrance List",
114 .icon = ICON_MD_DOOR_FRONT,
115 .category = "Dungeon",
116 .shortcut_hint = "Ctrl+Shift+E",
117 .visibility_flag = nullptr,
118 .enabled_condition = [this]() { return rom_ && rom_->is_loaded(); },
119 .disabled_tooltip = "Load a ROM to browse dungeon entrances",
120 .priority = 25});
121
122 panel_manager->RegisterPanel(
123 {.card_id = "dungeon.entrance_properties",
124 .display_name = "Entrance Properties",
125 .window_title = " Entrance Properties",
126 .icon = ICON_MD_TUNE,
127 .category = "Dungeon",
128 .shortcut_hint = "",
129 .visibility_flag = nullptr,
130 .enabled_condition = [this]() { return rom_ && rom_->is_loaded(); },
131 .disabled_tooltip = "Load a ROM to edit entrance properties",
132 .priority = 26});
133
134 panel_manager->RegisterPanel(
135 {.card_id = kRoomMatrixId,
136 .display_name = "Room Matrix",
137 .window_title = " Room Matrix",
138 .icon = ICON_MD_GRID_VIEW,
139 .category = "Dungeon",
140 .shortcut_hint = "Ctrl+Shift+M",
141 .visibility_flag = nullptr,
142 .enabled_condition = [this]() { return rom_ && rom_->is_loaded(); },
143 .disabled_tooltip = "Load a ROM to view the room matrix",
144 .priority = 30});
145
146 panel_manager->RegisterPanel(
147 {.card_id = kRoomGraphicsId,
148 .display_name = "Room Graphics",
149 .window_title = " Room Graphics",
150 .icon = ICON_MD_IMAGE,
151 .category = "Dungeon",
152 .shortcut_hint = "Ctrl+Shift+G",
153 .visibility_flag = nullptr,
154 .enabled_condition = [this]() { return rom_ && rom_->is_loaded(); },
155 .disabled_tooltip = "Load a ROM to view room graphics",
156 .priority = 50});
157
158 panel_manager->RegisterPanel(
159 {.card_id = kPaletteEditorId,
160 .display_name = "Palette Editor",
161 .window_title = " Palette Editor",
162 .icon = ICON_MD_PALETTE,
163 .category = "Dungeon",
164 .shortcut_hint = "Ctrl+Shift+P",
165 .visibility_flag = nullptr,
166 .enabled_condition = [this]() { return rom_ && rom_->is_loaded(); },
167 .disabled_tooltip = "Load a ROM to edit dungeon palettes",
168 .priority = 70});
169
170 // Show default panels on startup
171 panel_manager->ShowPanel(kControlPanelId);
172 panel_manager->ShowPanel(kRoomSelectorId);
173
174 // Register EditorPanel instances
175 panel_manager->RegisterEditorPanel(std::make_unique<DungeonRoomSelectorPanel>(
176 &room_selector_, [this](int room_id) { OnRoomSelected(room_id); }));
177
178 panel_manager->RegisterEditorPanel(std::make_unique<DungeonEntranceListPanel>(
180 [this](int entrance_id) { OnEntranceSelected(entrance_id); }));
181
182 panel_manager->RegisterEditorPanel(std::make_unique<DungeonRoomMatrixPanel>(
184 [this](int room_id) { OnRoomSelected(room_id); },
185 &rooms_));
186
187 panel_manager->RegisterEditorPanel(std::make_unique<DungeonEntrancesPanel>(
189 [this](int entrance_id) { OnEntranceSelected(entrance_id); }));
190
191 // Note: DungeonRoomGraphicsPanel and DungeonPaletteEditorPanel are registered
192 // in Load() after their dependencies (renderer_, palette_editor_) are initialized
193}
194
196
197absl::Status DungeonEditorV2::Load() {
198 if (!rom_ || !rom_->is_loaded()) {
199 return absl::FailedPreconditionError("ROM not loaded");
200 }
201
202 // Load object dimension table for accurate hit-testing
203 auto& dim_table = zelda3::ObjectDimensionTable::Get();
204 if (!dim_table.IsLoaded()) {
205 RETURN_IF_ERROR(dim_table.LoadFromRom(rom_));
206 }
207
209
210 if (!game_data()) {
211 return absl::FailedPreconditionError("GameData not available");
212 }
213 auto dungeon_main_pal_group = game_data()->palette_groups.dungeon_main;
214 current_palette_ = dungeon_main_pal_group[current_palette_group_id_];
217
222 [this](int room_id) { OnRoomSelected(room_id); });
223
224 // Canvas viewers are lazily created in GetViewerForRoom
225
226 if (!render_service_) {
228 std::make_unique<emu::render::EmulatorRenderService>(rom_);
229 auto status = render_service_->Initialize();
230 if (!status.ok()) {
231 LOG_ERROR("DungeonEditorV2", "Failed to initialize render service: %s",
232 status.message().data());
233 }
234 }
235
236 if (game_data()) {
238 } else {
240 }
241
243
244 // Register panels that depend on initialized state (renderer, palette_editor_)
246 auto graphics_panel = std::make_unique<DungeonRoomGraphicsPanel>(
248 room_graphics_panel_ = graphics_panel.get();
249 dependencies_.panel_manager->RegisterEditorPanel(std::move(graphics_panel));
251 std::make_unique<DungeonPaletteEditorPanel>(&palette_editor_));
252 }
253
254 dungeon_editor_system_ = std::make_unique<zelda3::DungeonEditorSystem>(rom_);
255 (void)dungeon_editor_system_->Initialize();
257
258 // Initialize unified object editor panel
259 // Note: Initially passing nullptr for viewer, will be set on selection
260 auto object_editor = std::make_unique<ObjectEditorPanel>(
261 renderer_, rom_, nullptr, dungeon_editor_system_->GetObjectEditor());
262
263 // Wire up object change callback to trigger room re-rendering
264 dungeon_editor_system_->GetObjectEditor()->SetObjectChangedCallback(
265 [this](size_t /*object_index*/, const zelda3::RoomObject& /*object*/) {
266 if (current_room_id_ >= 0 && current_room_id_ < (int)rooms_.size()) {
267 rooms_[current_room_id_].RenderRoomGraphics();
268 }
269 });
270
271 // Set rooms and initial palette group for correct preview rendering
272 object_editor->SetRooms(&rooms_);
273 object_editor->SetCurrentPaletteGroup(current_palette_group_);
274
275 // Keep raw pointer for later access
276 object_editor_panel_ = object_editor.get();
277
278 // Propagate game_data to the object editor panel if available
279 if (game_data()) {
281 }
282
283 // Register the ObjectEditorPanel directly (it inherits from EditorPanel)
284 // Panel manager takes ownership
286 dependencies_.panel_manager->RegisterEditorPanel(std::move(object_editor));
287
288 // Register sprite and item editor panels with canvas viewer = nullptr
289 // They will get the viewer reference in OnRoomSelected when a room is selected
290 auto sprite_panel = std::make_unique<SpriteEditorPanel>(
291 &current_room_id_, &rooms_, nullptr);
292 sprite_editor_panel_ = sprite_panel.get();
293 dependencies_.panel_manager->RegisterEditorPanel(std::move(sprite_panel));
294
295 auto item_panel = std::make_unique<ItemEditorPanel>(
296 &current_room_id_, &rooms_, nullptr);
297 item_editor_panel_ = item_panel.get();
298 dependencies_.panel_manager->RegisterEditorPanel(std::move(item_panel));
299
300 // Feature Flag: Custom Objects / Minecart Tracks
301 if (core::FeatureFlags::get().kEnableCustomObjects) {
303 auto minecart_panel = std::make_unique<MinecartTrackEditorPanel>();
304 minecart_track_editor_panel_ = minecart_panel.get();
305 dependencies_.panel_manager->RegisterEditorPanel(std::move(minecart_panel));
306 }
307
309 // Update project root for track editor
313 }
314
315 // Initialize custom object manager with project-configured path
319 }
320 }
321 }
322 } else {
323 owned_object_editor_panel_ = std::move(object_editor);
324 }
325
326 palette_editor_.SetOnPaletteChanged([this](int /*palette_id*/) {
327 for (int i = 0; i < active_rooms_.Size; i++) {
328 int room_id = active_rooms_[i];
329 if (room_id >= 0 && room_id < (int)rooms_.size()) {
330 rooms_[room_id].RenderRoomGraphics();
331 }
332 }
333 });
334
335 is_loaded_ = true;
336 return absl::OkStatus();
337}
338
340 const auto& theme = AgentUI::GetTheme();
341 if (room_window_class_.ClassId == 0) {
342 room_window_class_.ClassId = ImGui::GetID("DungeonRoomClass");
343 }
344
345 if (!is_loaded_) {
346 gui::PanelWindow loading_card("Dungeon Editor Loading", ICON_MD_CASTLE);
347 loading_card.SetDefaultSize(400, 200);
348 if (loading_card.Begin()) {
349 ImGui::TextColored(theme.text_secondary_gray, "Loading dungeon data...");
350 ImGui::TextWrapped(
351 "Independent editor cards will appear once ROM data is loaded.");
352 }
353 loading_card.End();
354 return absl::OkStatus();
355 }
356
358
359 if (ImGui::IsKeyPressed(ImGuiKey_Delete)) {
360 // Delegate delete to current room viewer
361 if (auto* viewer = GetViewerForRoom(current_room_id_)) {
362 viewer->DeleteSelectedObjects();
363 }
364 }
365
366 // Process any pending room swaps after all drawing is complete
367 // This prevents ImGui state corruption from modifying collections mid-frame
369
370 return absl::OkStatus();
371}
372
373absl::Status DungeonEditorV2::Undo() {
375 return dungeon_editor_system_->Undo();
376 }
377 return absl::UnimplementedError("Undo not available");
378}
379
380absl::Status DungeonEditorV2::Redo() {
382 return dungeon_editor_system_->Redo();
383 }
384 return absl::UnimplementedError("Redo not available");
385}
386
387absl::Status DungeonEditorV2::Save() {
388 if (!rom_ || !rom_->is_loaded()) {
389 return absl::FailedPreconditionError("ROM not loaded");
390 }
391
392 if (gfx::PaletteManager::Get().HasUnsavedChanges()) {
393 auto status = gfx::PaletteManager::Get().SaveAllToRom();
394 if (!status.ok()) {
395 LOG_ERROR("DungeonEditorV2", "Failed to save palette changes: %s",
396 status.message().data());
397 return status;
398 }
399 LOG_INFO("DungeonEditorV2", "Saved %zu modified colors to ROM",
400 gfx::PaletteManager::Get().GetModifiedColorCount());
401 }
402
403 for (auto& room : rooms_) {
404 auto status = room.SaveObjects();
405 if (!status.ok()) {
406 LOG_ERROR("DungeonEditorV2", "Failed to save room objects: %s",
407 status.message().data());
408 }
409
411 auto sys_status = dungeon_editor_system_->SaveRoom(room.id());
412 if (!sys_status.ok()) {
413 LOG_ERROR("DungeonEditorV2", "Failed to save room system data: %s",
414 sys_status.message().data());
415 }
416 }
417 }
418
420 auto status = dungeon_editor_system_->SaveDungeon();
421 if (!status.ok()) {
422 LOG_ERROR("DungeonEditorV2", "DungeonEditorSystem save failed: %s",
423 status.message().data());
424 return status;
425 }
426 }
427
428 return absl::OkStatus();
429}
430
432 for (int i = 0; i < active_rooms_.Size; i++) {
433 int room_id = active_rooms_[i];
434 std::string card_id = absl::StrFormat("dungeon.room_%d", room_id);
435 bool panel_visible = true;
437 panel_visible = dependencies_.panel_manager->IsPanelVisible(card_id);
438 }
439
440 if (!panel_visible) {
442 room_cards_.erase(room_id);
443 active_rooms_.erase(active_rooms_.Data + i);
444 // Clean up viewer
445 room_viewers_.erase(room_id);
446 i--;
447 continue;
448 }
449
450 bool is_pinned = dependencies_.panel_manager &&
452 std::string active_category =
455 : "";
456
457 if (active_category != "Dungeon" && !is_pinned) {
458 continue;
459 }
460
461 bool open = true;
462
463 // Use unified ResourceLabelProvider for room names
464 std::string base_name = absl::StrFormat("[%03X] %s", room_id,
465 zelda3::GetRoomLabel(room_id).c_str());
466
467 std::string card_name_str = absl::StrFormat(
468 "%s###RoomPanel%d", MakePanelTitle(base_name).c_str(), room_id);
469
470 if (room_cards_.find(room_id) == room_cards_.end()) {
471 room_cards_[room_id] = std::make_shared<gui::PanelWindow>(
472 card_name_str.c_str(), ICON_MD_GRID_ON, &open);
473 room_cards_[room_id]->SetDefaultSize(700, 600);
474 // Note: Room panels use default save settings to preserve docking state
475 }
476
477 auto& room_card = room_cards_[room_id];
478
479 ImGui::SetNextWindowClass(&room_window_class_);
480
481 // Auto-dock room panels together using a shared dock ID
482 // This ensures all room windows tab together in the same dock node
483 if (room_dock_id_ == 0) {
484 // Create a stable dock ID on first use
485 room_dock_id_ = ImGui::GetID("DungeonRoomDock");
486 }
487 ImGui::SetNextWindowDockID(room_dock_id_, ImGuiCond_FirstUseEver);
488
489 if (room_card->Begin(&open)) {
490 // Ensure focused room updates selection context
491 if (ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows)) {
492 OnRoomSelected(room_id, /*request_focus=*/false);
493 }
494 DrawRoomTab(room_id);
495 }
496 room_card->End();
497
498 if (!open) {
501 }
502
503 room_cards_.erase(room_id);
504 active_rooms_.erase(active_rooms_.Data + i);
505 room_viewers_.erase(room_id);
506 i--;
507 }
508 }
509}
510
512 const auto& theme = AgentUI::GetTheme();
513 if (room_id < 0 || room_id >= 0x128) {
514 ImGui::Text("Invalid room ID: %d", room_id);
515 return;
516 }
517
518 auto& room = rooms_[room_id];
519
520 if (!room.IsLoaded()) {
521 auto status = room_loader_.LoadRoom(room_id, room);
522 if (!status.ok()) {
523 ImGui::TextColored(theme.text_error_red, "Failed to load room: %s",
524 status.message().data());
525 return;
526 }
527
529 auto sys_status = dungeon_editor_system_->ReloadRoom(room_id);
530 if (!sys_status.ok()) {
531 LOG_ERROR("DungeonEditorV2", "Failed to load system data: %s",
532 sys_status.message().data());
533 }
534 }
535 }
536
537 if (room.IsLoaded()) {
538 bool needs_render = false;
539
540 // Chronological Step 1: Load Room Data from ROM
541 // This reads the 14-byte room header (blockset, palette, effect, tags)
542 // Reference: kRoomHeaderPointer (0xB5DD)
543 if (room.blocks().empty()) {
544 room.LoadRoomGraphics(room.blockset);
545 needs_render = true;
546 LOG_DEBUG("[DungeonEditorV2]", "Loaded room %d graphics from ROM",
547 room_id);
548 }
549
550 // Chronological Step 2: Load Objects from ROM
551 // This reads the variable-length object stream (subtype 1, 2, 3 objects)
552 // Reference: kRoomObjectPointer (0x874C)
553 // CRITICAL: This step decodes floor1/floor2 bytes which dictate the floor
554 // pattern
555 if (room.GetTileObjects().empty()) {
556 room.LoadObjects();
557 needs_render = true;
558 LOG_DEBUG("[DungeonEditorV2]", "Loaded room %d objects from ROM",
559 room_id);
560 }
561
562 // Chronological Step 3: Render Graphics to Bitmaps
563 // This executes the draw routines (bank_01.asm logic) to populate BG1/BG2
564 // buffers Sequence:
565 // 1. Draw Floor (from floor1/floor2)
566 // 2. Draw Layout (walls/floors from object list)
567 // 3. Draw Objects (subtypes 1, 2, 3)
568 auto& bg1_bitmap = room.bg1_buffer().bitmap();
569 if (needs_render || !bg1_bitmap.is_active() || bg1_bitmap.width() == 0) {
570 room.RenderRoomGraphics();
571 LOG_DEBUG("[DungeonEditorV2]", "Rendered room %d to bitmaps", room_id);
572 }
573 }
574
575 if (room.IsLoaded()) {
576 ImGui::TextColored(theme.text_success_green, ICON_MD_CHECK " Loaded");
577 } else {
578 ImGui::TextColored(theme.text_error_red, ICON_MD_PENDING " Not Loaded");
579 }
580 ImGui::SameLine();
581 ImGui::TextDisabled("Objects: %zu", room.GetTileObjects().size());
582
583 ImGui::Separator();
584
585 // Use per-room viewer
586 if (auto* viewer = GetViewerForRoom(room_id)) {
587 viewer->DrawDungeonCanvas(room_id);
588 }
589}
590
591void DungeonEditorV2::OnRoomSelected(int room_id, bool request_focus) {
592 current_room_id_ = room_id;
593
595 dungeon_editor_system_->SetExternalRoom(&rooms_[room_id]);
596 }
597
598 // Update object editor card with current viewer
601 // IMPORTANT: Update the viewer reference!
603 }
604
605 // Update sprite and item editor panels with current viewer
608 }
609 if (item_editor_panel_) {
611 }
612
613 // Sync palette with current room (must happen before early return for focus changes)
614 if (room_id >= 0 && room_id < (int)rooms_.size()) {
615 auto& room = rooms_[room_id];
616 if (!room.IsLoaded()) {
617 room_loader_.LoadRoom(room_id, room);
618 }
619
620 if (room.IsLoaded()) {
621 current_palette_id_ = room.palette;
623
624 // Update viewer and object editor palette
625 if (auto* viewer = GetViewerForRoom(room_id)) {
626 viewer->SetCurrentPaletteId(current_palette_id_);
627
628 if (game_data()) {
629 auto dungeon_main_pal_group =
631 if (current_palette_id_ < (int)dungeon_main_pal_group.size()) {
632 current_palette_ = dungeon_main_pal_group[current_palette_id_];
633 auto result =
635 if (result.ok()) {
636 current_palette_group_ = result.value();
637 viewer->SetCurrentPaletteGroup(current_palette_group_);
641 }
642 // Sync palette to graphics panel for proper sheet coloring
646 }
647 }
648 }
649 }
650 }
651 }
652 }
653
654 // Check if room is already open
655 for (int i = 0; i < active_rooms_.Size; i++) {
656 if (active_rooms_[i] == room_id) {
657 // Always ensure panel is visible, even if already in active_rooms_
659 std::string card_id = absl::StrFormat("dungeon.room_%d", room_id);
661 }
662 if (request_focus) {
663 FocusRoom(room_id);
664 }
665 return;
666 }
667 }
668
669 active_rooms_.push_back(room_id);
671
673 // Use unified ResourceLabelProvider for room names
674 std::string room_name = absl::StrFormat("[%03X] %s", room_id,
675 zelda3::GetRoomLabel(room_id).c_str());
676
677 std::string base_card_id = absl::StrFormat("dungeon.room_%d", room_id);
678
680 {.card_id = base_card_id,
681 .display_name = room_name,
682 .window_title = ICON_MD_GRID_ON " " + room_name,
683 .icon = ICON_MD_GRID_ON,
684 .category = "Dungeon",
685 .shortcut_hint = "",
686 .visibility_flag = nullptr,
687 .priority = 200 + room_id});
688
690 }
691}
692
694 if (entrance_id < 0 || entrance_id >= static_cast<int>(entrances_.size())) {
695 return;
696 }
697 int room_id = entrances_[entrance_id].room_;
698 OnRoomSelected(room_id);
699}
700
701void DungeonEditorV2::add_room(int room_id) { OnRoomSelected(room_id); }
702
703void DungeonEditorV2::FocusRoom(int room_id) {
704 auto it = room_cards_.find(room_id);
705 if (it != room_cards_.end()) {
706 it->second->Focus();
707 }
708}
709
716
727
731
733 if (current_room_id_ < 0 ||
734 current_room_id_ >= static_cast<int>(rooms_.size())) {
735 LOG_ERROR("DungeonEditorV2", "Cannot place object: Invalid room ID %d",
737 return;
738 }
739
740 auto& room = rooms_[current_room_id_];
741
742 LOG_INFO("DungeonEditorV2",
743 "Placing object ID=0x%02X at position (%d,%d) in room %03X", obj.id_,
744 obj.x_, obj.y_, current_room_id_);
745
746 room.RenderRoomGraphics();
747 LOG_DEBUG("DungeonEditorV2",
748 "Object placed and room re-rendered successfully");
749}
750
751absl::Status DungeonEditorV2::Cut() {
752 if (auto* viewer = GetViewerForRoom(current_room_id_)) {
753 viewer->object_interaction().HandleCopySelected();
754 viewer->object_interaction().HandleDeleteSelected();
755 }
756 return absl::OkStatus();
757}
758
759absl::Status DungeonEditorV2::Copy() {
760 if (auto* viewer = GetViewerForRoom(current_room_id_)) {
761 viewer->object_interaction().HandleCopySelected();
762 }
763 return absl::OkStatus();
764}
765
767 if (auto* viewer = GetViewerForRoom(current_room_id_)) {
768 viewer->object_interaction().HandlePasteObjects();
769 }
770 return absl::OkStatus();
771}
772
774 if (room_id < 0 || room_id >= static_cast<int>(rooms_.size())) return;
775
776 undo_history_[room_id].push_back(rooms_[room_id].GetTileObjects());
777 ClearRedo(room_id);
778}
779
781 int room_id, std::vector<zelda3::RoomObject> snapshot) {
782 if (room_id < 0 || room_id >= static_cast<int>(rooms_.size())) {
783 return absl::InvalidArgumentError("Invalid room ID");
784 }
785
786 auto& room = rooms_[room_id];
787 room.GetTileObjects() = std::move(snapshot);
788 room.RenderRoomGraphics();
789 return absl::OkStatus();
790}
791
792void DungeonEditorV2::ClearRedo(int room_id) { redo_history_[room_id].clear(); }
793
794void DungeonEditorV2::SwapRoomInPanel(int old_room_id, int new_room_id) {
795 // Defer the swap until after the current frame's draw phase completes
796 // This prevents modifying data structures while ImGui is still using them
797 if (new_room_id < 0 || new_room_id >= static_cast<int>(rooms_.size())) {
798 return;
799 }
800 pending_swap_.old_room_id = old_room_id;
801 pending_swap_.new_room_id = new_room_id;
802 pending_swap_.pending = true;
803}
804
806 if (!pending_swap_.pending) {
807 return;
808 }
809
810 int old_room_id = pending_swap_.old_room_id;
811 int new_room_id = pending_swap_.new_room_id;
812 pending_swap_.pending = false;
813
814 // Find the position of old_room in active_rooms_
815 int swap_index = -1;
816 for (int i = 0; i < active_rooms_.Size; i++) {
817 if (active_rooms_[i] == old_room_id) {
818 swap_index = i;
819 break;
820 }
821 }
822
823 if (swap_index < 0) {
824 // Old room not found in active rooms, just select the new one
825 OnRoomSelected(new_room_id);
826 return;
827 }
828
829 // Replace old room with new room in active_rooms_
830 active_rooms_[swap_index] = new_room_id;
832
833 // Unregister old panel
835 std::string old_card_id = absl::StrFormat("dungeon.room_%d", old_room_id);
837 }
838
839 // Clean up old room's card and viewer
840 room_cards_.erase(old_room_id);
841 room_viewers_.erase(old_room_id);
842
843 // Register new panel
845 // Use unified ResourceLabelProvider for room names
846 std::string new_room_name = absl::StrFormat(
847 "[%03X] %s", new_room_id, zelda3::GetRoomLabel(new_room_id).c_str());
848
849 std::string new_card_id = absl::StrFormat("dungeon.room_%d", new_room_id);
850
852 {.card_id = new_card_id,
853 .display_name = new_room_name,
854 .window_title = ICON_MD_GRID_ON " " + new_room_name,
855 .icon = ICON_MD_GRID_ON,
856 .category = "Dungeon",
857 .shortcut_hint = "",
858 .visibility_flag = nullptr,
859 .priority = 200 + new_room_id});
860
862 }
863
864 // Update current selection
865 OnRoomSelected(new_room_id, /*request_focus=*/false);
866}
867
869 auto it = room_viewers_.find(room_id);
870 if (it == room_viewers_.end()) {
871 auto viewer = std::make_unique<DungeonCanvasViewer>(rom_);
872 viewer->SetRooms(&rooms_);
873 viewer->SetRenderer(renderer_);
874 viewer->SetCurrentPaletteGroup(current_palette_group_);
875 viewer->SetCurrentPaletteId(current_palette_id_);
876 viewer->SetGameData(game_data_);
877
878 viewer->object_interaction().SetMutationHook(
879 [this, room_id]() { PushUndoSnapshot(room_id); });
880
881 viewer->object_interaction().SetCacheInvalidationCallback(
882 [this, room_id]() {
883 if (room_id >= 0 && room_id < static_cast<int>(rooms_.size())) {
884 rooms_[room_id].MarkObjectsDirty();
885 rooms_[room_id].RenderRoomGraphics();
886 }
887 });
888
889 viewer->object_interaction().SetObjectPlacedCallback(
890 [this](const zelda3::RoomObject& obj) { HandleObjectPlaced(obj); });
891
893 viewer->SetEditorSystem(dungeon_editor_system_.get());
894 }
895 viewer->SetRoomNavigationCallback(
896 [this](int target_room) {
897 if (target_room >= 0 &&
898 target_room < static_cast<int>(rooms_.size())) {
899 OnRoomSelected(target_room);
900 }
901 });
902 // Swap callback swaps the room in the current panel instead of opening new
903 viewer->SetRoomSwapCallback(
904 [this](int old_room, int new_room) {
905 SwapRoomInPanel(old_room, new_room);
906 });
907 viewer->SetShowObjectPanelCallback([this]() {
909 });
910 viewer->SetShowSpritePanelCallback([this]() {
911 ShowPanel("dungeon.sprite_editor");
912 });
913 viewer->SetShowItemPanelCallback([this]() {
914 ShowPanel("dungeon.item_editor");
915 });
916
917 room_viewers_[room_id] = std::move(viewer);
918 return room_viewers_[room_id].get();
919 }
920 return it->second.get();
921}
922
923} // namespace yaze::editor
The Rom class is used to load, save, and modify Rom data. This is a generic SNES ROM container and do...
Definition rom.h:24
bool is_loaded() const
Definition rom.h:128
static Flags & get()
Definition features.h:92
class MinecartTrackEditorPanel * minecart_track_editor_panel_
static constexpr const char * kControlPanelId
std::array< zelda3::Room, 0x128 > rooms_
class ItemEditorPanel * item_editor_panel_
std::unordered_map< int, std::vector< std::vector< zelda3::RoomObject > > > undo_history_
std::array< zelda3::RoomEntrance, 0x8C > entrances_
gfx::PaletteGroup current_palette_group_
void OnEntranceSelected(int entrance_id)
static constexpr const char * kEntranceListId
class SpriteEditorPanel * sprite_editor_panel_
void HandleObjectPlaced(const zelda3::RoomObject &obj)
std::unique_ptr< ObjectEditorPanel > owned_object_editor_panel_
std::unique_ptr< zelda3::DungeonEditorSystem > dungeon_editor_system_
void OnRoomSelected(int room_id, bool request_focus=true)
DungeonRoomGraphicsPanel * room_graphics_panel_
ObjectEditorPanel * object_editor_panel_
std::map< int, std::unique_ptr< DungeonCanvasViewer > > room_viewers_
gui::PaletteEditorWidget palette_editor_
void ShowPanel(const std::string &card_id)
absl::Status Paste() override
void SwapRoomInPanel(int old_room_id, int new_room_id)
absl::Status RestoreFromSnapshot(int room_id, std::vector< zelda3::RoomObject > snapshot)
static constexpr const char * kObjectToolsId
DungeonCanvasViewer * GetViewerForRoom(int room_id)
absl::Status Update() override
std::unique_ptr< emu::render::EmulatorRenderService > render_service_
std::unordered_map< int, std::vector< std::vector< zelda3::RoomObject > > > redo_history_
std::unordered_map< int, std::shared_ptr< gui::PanelWindow > > room_cards_
static constexpr const char * kRoomGraphicsId
static constexpr const char * kRoomMatrixId
static constexpr const char * kRoomSelectorId
static constexpr const char * kPaletteEditorId
void SetCurrentPaletteGroup(const gfx::PaletteGroup &group)
Set the current palette group for graphics rendering.
absl::Status LoadRoomEntrances(std::array< zelda3::RoomEntrance, 0x8C > &entrances)
absl::Status LoadRoom(int room_id, zelda3::Room &room)
void set_entrances(std::array< zelda3::RoomEntrance, 0x8C > *entrances)
void SetRoomSelectedCallback(std::function< void(int)> callback)
void set_active_rooms(const ImVector< int > &rooms)
void set_rooms(std::array< zelda3::Room, 0x128 > *rooms)
zelda3::GameData * game_data() const
Definition editor.h:228
EditorDependencies dependencies_
Definition editor.h:237
std::string MakePanelTitle(const std::string &base_title) const
Definition editor.h:240
void SetCanvasViewer(DungeonCanvasViewer *viewer)
void SetCanvasViewer(DungeonCanvasViewer *viewer)
void SetCurrentPaletteGroup(const gfx::PaletteGroup &group)
void SetGameData(zelda3::GameData *game_data)
bool ShowPanel(size_t session_id, const std::string &base_card_id)
void RegisterPanel(size_t session_id, const PanelDescriptor &base_info)
std::string GetActiveCategory() const
bool IsPanelVisible(size_t session_id, const std::string &base_card_id) const
void UnregisterPanel(size_t session_id, const std::string &base_card_id)
bool IsPanelPinned(size_t session_id, const std::string &base_card_id) const
void RegisterEditorPanel(std::unique_ptr< EditorPanel > panel)
Register an EditorPanel instance for central drawing.
void SetCanvasViewer(DungeonCanvasViewer *viewer)
void ProcessTextureQueue(IRenderer *renderer)
Definition arena.cc:110
static Arena & Get()
Definition arena.cc:19
Defines an abstract interface for all rendering operations.
Definition irenderer.h:40
void Initialize(zelda3::GameData *game_data)
Initialize the palette manager with GameData.
static PaletteManager & Get()
Get the singleton instance.
absl::Status SaveAllToRom()
Save ALL modified palettes to ROM.
void Initialize(zelda3::GameData *game_data)
void SetOnPaletteChanged(std::function< void(int palette_id)> callback)
Draggable, dockable panel for editor sub-windows.
bool Begin(bool *p_open=nullptr)
void SetDefaultSize(float width, float height)
static CustomObjectManager & Get()
void Initialize(const std::string &custom_objects_folder)
static ObjectDimensionTable & Get()
#define ICON_MD_GRID_VIEW
Definition icons.h:897
#define ICON_MD_CHECK
Definition icons.h:397
#define ICON_MD_TUNE
Definition icons.h:2022
#define ICON_MD_CASTLE
Definition icons.h:380
#define ICON_MD_GRID_ON
Definition icons.h:896
#define ICON_MD_LIST
Definition icons.h:1094
#define ICON_MD_PENDING
Definition icons.h:1398
#define ICON_MD_DOOR_FRONT
Definition icons.h:613
#define ICON_MD_IMAGE
Definition icons.h:982
#define ICON_MD_PALETTE
Definition icons.h:1370
#define LOG_DEBUG(category, format,...)
Definition log.h:103
#define LOG_ERROR(category, format,...)
Definition log.h:109
#define LOG_INFO(category, format,...)
Definition log.h:105
#define ASSIGN_OR_RETURN(type_variable_name, expression)
Definition macro.h:62
const AgentUITheme & GetTheme()
Editors are the view controllers for the application.
Definition agent_chat.cc:23
absl::StatusOr< PaletteGroup > CreatePaletteGroupFromLargePalette(SnesPalette &palette, int num_colors)
Create a PaletteGroup by dividing a large palette into sub-palettes.
std::string GetRoomLabel(int id)
Convenience function to get a room label.
#define RETURN_IF_ERROR(expr)
Definition snes.cc:22
project::YazeProject * project
Definition editor.h:134
std::string custom_objects_folder
Definition project.h:102
std::string code_folder
Definition project.h:97
gfx::PaletteGroupMap palette_groups
Definition game_data.h:89