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
38#include "app/gui/core/icons.h"
39#include "core/features.h"
40#include "core/project.h"
41#include "util/log.h"
42#include "util/macro.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
82 return;
83 auto* panel_manager = dependencies_.panel_manager;
84
85 // Register panels with PanelManager (no boolean flags - visibility is
86 // managed entirely by PanelManager::ShowPanel/HidePanel/IsPanelVisible)
87 panel_manager->RegisterPanel(
88 {.card_id = kControlPanelId,
89 .display_name = "Dungeon Controls",
90 .window_title = " Dungeon Controls",
91 .icon = ICON_MD_CASTLE,
92 .category = "Dungeon",
93 .shortcut_hint = "Ctrl+Shift+D",
94 .visibility_flag = nullptr,
95 .priority = 10,
96 .enabled_condition = [this]() { return rom_ && rom_->is_loaded(); },
97 .disabled_tooltip = "Load a ROM to access dungeon controls"});
98
99 panel_manager->RegisterPanel(
100 {.card_id = kRoomSelectorId,
101 .display_name = "Room List",
102 .window_title = " Room List",
103 .icon = ICON_MD_LIST,
104 .category = "Dungeon",
105 .shortcut_hint = "Ctrl+Shift+R",
106 .visibility_flag = nullptr,
107 .priority = 20,
108 .enabled_condition = [this]() { return rom_ && rom_->is_loaded(); },
109 .disabled_tooltip = "Load a ROM to browse dungeon rooms"});
110
111 panel_manager->RegisterPanel(
112 {.card_id = kEntranceListId,
113 .display_name = "Entrance List",
114 .window_title = " Entrance List",
115 .icon = ICON_MD_DOOR_FRONT,
116 .category = "Dungeon",
117 .shortcut_hint = "Ctrl+Shift+E",
118 .visibility_flag = nullptr,
119 .priority = 25,
120 .enabled_condition = [this]() { return rom_ && rom_->is_loaded(); },
121 .disabled_tooltip = "Load a ROM to browse dungeon entrances"});
122
123 panel_manager->RegisterPanel(
124 {.card_id = "dungeon.entrance_properties",
125 .display_name = "Entrance Properties",
126 .window_title = " Entrance Properties",
127 .icon = ICON_MD_TUNE,
128 .category = "Dungeon",
129 .shortcut_hint = "",
130 .visibility_flag = nullptr,
131 .priority = 26,
132 .enabled_condition = [this]() { return rom_ && rom_->is_loaded(); },
133 .disabled_tooltip = "Load a ROM to edit entrance properties"});
134
135 panel_manager->RegisterPanel(
136 {.card_id = kRoomMatrixId,
137 .display_name = "Room Matrix",
138 .window_title = " Room Matrix",
139 .icon = ICON_MD_GRID_VIEW,
140 .category = "Dungeon",
141 .shortcut_hint = "Ctrl+Shift+M",
142 .visibility_flag = nullptr,
143 .priority = 30,
144 .enabled_condition = [this]() { return rom_ && rom_->is_loaded(); },
145 .disabled_tooltip = "Load a ROM to view the room matrix"});
146
147 panel_manager->RegisterPanel(
148 {.card_id = kRoomGraphicsId,
149 .display_name = "Room Graphics",
150 .window_title = " Room Graphics",
151 .icon = ICON_MD_IMAGE,
152 .category = "Dungeon",
153 .shortcut_hint = "Ctrl+Shift+G",
154 .visibility_flag = nullptr,
155 .priority = 50,
156 .enabled_condition = [this]() { return rom_ && rom_->is_loaded(); },
157 .disabled_tooltip = "Load a ROM to view room graphics"});
158
159 panel_manager->RegisterPanel(
160 {.card_id = kPaletteEditorId,
161 .display_name = "Palette Editor",
162 .window_title = " Palette Editor",
163 .icon = ICON_MD_PALETTE,
164 .category = "Dungeon",
165 .shortcut_hint = "Ctrl+Shift+P",
166 .visibility_flag = nullptr,
167 .priority = 70,
168 .enabled_condition = [this]() { return rom_ && rom_->is_loaded(); },
169 .disabled_tooltip = "Load a ROM to edit dungeon palettes"});
170
171 // Show default panels on startup
172 panel_manager->ShowPanel(kControlPanelId);
173 panel_manager->ShowPanel(kRoomSelectorId);
174
175 // Register EditorPanel instances
176 panel_manager->RegisterEditorPanel(std::make_unique<DungeonRoomSelectorPanel>(
177 &room_selector_, [this](int room_id) { OnRoomSelected(room_id); }));
178
179 panel_manager->RegisterEditorPanel(std::make_unique<DungeonEntranceListPanel>(
181 [this](int entrance_id) { OnEntranceSelected(entrance_id); }));
182
183 panel_manager->RegisterEditorPanel(std::make_unique<DungeonRoomMatrixPanel>(
185 [this](int room_id) { OnRoomSelected(room_id); }, &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>(&current_room_id_,
291 &rooms_, nullptr);
292 sprite_editor_panel_ = sprite_panel.get();
293 dependencies_.panel_manager->RegisterEditorPanel(std::move(sprite_panel));
294
295 auto item_panel =
296 std::make_unique<ItemEditorPanel>(&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();
306 std::move(minecart_panel));
307 }
308
310 // Update project root for track editor
314 }
315
316 // Initialize custom object manager with project-configured path
320 }
321 }
322 }
323 } else {
324 owned_object_editor_panel_ = std::move(object_editor);
325 }
326
327 palette_editor_.SetOnPaletteChanged([this](int /*palette_id*/) {
328 for (int i = 0; i < active_rooms_.Size; i++) {
329 int room_id = active_rooms_[i];
330 if (room_id >= 0 && room_id < (int)rooms_.size()) {
331 rooms_[room_id].RenderRoomGraphics();
332 }
333 }
334 });
335
336 is_loaded_ = true;
337 return absl::OkStatus();
338}
339
341 const auto& theme = AgentUI::GetTheme();
342 if (room_window_class_.ClassId == 0) {
343 room_window_class_.ClassId = ImGui::GetID("DungeonRoomClass");
344 }
345
346 if (!is_loaded_) {
347 gui::PanelWindow loading_card("Dungeon Editor Loading", ICON_MD_CASTLE);
348 loading_card.SetDefaultSize(400, 200);
349 if (loading_card.Begin()) {
350 ImGui::TextColored(theme.text_secondary_gray, "Loading dungeon data...");
351 ImGui::TextWrapped(
352 "Independent editor cards will appear once ROM data is loaded.");
353 }
354 loading_card.End();
355 return absl::OkStatus();
356 }
357
359
360 if (ImGui::IsKeyPressed(ImGuiKey_Delete)) {
361 // Delegate delete to current room viewer
362 if (auto* viewer = GetViewerForRoom(current_room_id_)) {
363 viewer->DeleteSelectedObjects();
364 }
365 }
366
367 // Process any pending room swaps after all drawing is complete
368 // This prevents ImGui state corruption from modifying collections mid-frame
370
371 return absl::OkStatus();
372}
373
374absl::Status DungeonEditorV2::Save() {
375 if (!rom_ || !rom_->is_loaded()) {
376 return absl::FailedPreconditionError("ROM not loaded");
377 }
378
379 if (gfx::PaletteManager::Get().HasUnsavedChanges()) {
380 auto status = gfx::PaletteManager::Get().SaveAllToRom();
381 if (!status.ok()) {
382 LOG_ERROR("DungeonEditorV2", "Failed to save palette changes: %s",
383 status.message().data());
384 return status;
385 }
386 LOG_INFO("DungeonEditorV2", "Saved %zu modified colors to ROM",
387 gfx::PaletteManager::Get().GetModifiedColorCount());
388 }
389
390 for (auto& room : rooms_) {
391 auto status = room.SaveObjects();
392 if (!status.ok()) {
393 LOG_ERROR("DungeonEditorV2", "Failed to save room objects: %s",
394 status.message().data());
395 }
396
398 auto sys_status = dungeon_editor_system_->SaveRoom(room.id());
399 if (!sys_status.ok()) {
400 LOG_ERROR("DungeonEditorV2", "Failed to save room system data: %s",
401 sys_status.message().data());
402 }
403 }
404 }
405
407 auto status = dungeon_editor_system_->SaveDungeon();
408 if (!status.ok()) {
409 LOG_ERROR("DungeonEditorV2", "DungeonEditorSystem save failed: %s",
410 status.message().data());
411 return status;
412 }
413 }
414
415 return absl::OkStatus();
416}
417
419 for (int i = 0; i < active_rooms_.Size; i++) {
420 int room_id = active_rooms_[i];
421 std::string card_id = absl::StrFormat("dungeon.room_%d", room_id);
422 bool panel_visible = true;
424 panel_visible = dependencies_.panel_manager->IsPanelVisible(card_id);
425 }
426
427 if (!panel_visible) {
429 room_cards_.erase(room_id);
430 active_rooms_.erase(active_rooms_.Data + i);
431 // Clean up viewer
432 room_viewers_.erase(room_id);
433 i--;
434 continue;
435 }
436
437 bool is_pinned = dependencies_.panel_manager &&
439 std::string active_category =
442 : "";
443
444 if (active_category != "Dungeon" && !is_pinned) {
445 continue;
446 }
447
448 bool open = true;
449
450 // Use unified ResourceLabelProvider for room names
451 std::string base_name = absl::StrFormat(
452 "[%03X] %s", room_id, zelda3::GetRoomLabel(room_id).c_str());
453
454 std::string card_name_str = absl::StrFormat(
455 "%s###RoomPanel%d", MakePanelTitle(base_name).c_str(), room_id);
456
457 if (room_cards_.find(room_id) == room_cards_.end()) {
458 room_cards_[room_id] = std::make_shared<gui::PanelWindow>(
459 card_name_str.c_str(), ICON_MD_GRID_ON, &open);
460 room_cards_[room_id]->SetDefaultSize(620, 700);
461 // Note: Room panels use default save settings to preserve docking state
462 }
463
464 auto& room_card = room_cards_[room_id];
465
466 ImGui::SetNextWindowClass(&room_window_class_);
467
468 // Auto-dock room panels together using a shared dock ID
469 // This ensures all room windows tab together in the same dock node
470 if (room_dock_id_ == 0) {
471 // Create a stable dock ID on first use
472 room_dock_id_ = ImGui::GetID("DungeonRoomDock");
473 }
474 ImGui::SetNextWindowDockID(room_dock_id_, ImGuiCond_FirstUseEver);
475
476 if (room_card->Begin(&open)) {
477 // Ensure focused room updates selection context
478 if (ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows)) {
479 OnRoomSelected(room_id, /*request_focus=*/false);
480 }
481 DrawRoomTab(room_id);
482 }
483 room_card->End();
484
485 if (!open) {
488 }
489
490 room_cards_.erase(room_id);
491 active_rooms_.erase(active_rooms_.Data + i);
492 room_viewers_.erase(room_id);
493 i--;
494 }
495 }
496}
497
499 const auto& theme = AgentUI::GetTheme();
500 if (room_id < 0 || room_id >= 0x128) {
501 ImGui::Text("Invalid room ID: %d", room_id);
502 return;
503 }
504
505 auto& room = rooms_[room_id];
506
507 if (!room.IsLoaded()) {
508 auto status = room_loader_.LoadRoom(room_id, room);
509 if (!status.ok()) {
510 ImGui::TextColored(theme.text_error_red, "Failed to load room: %s",
511 status.message().data());
512 return;
513 }
514
516 auto sys_status = dungeon_editor_system_->ReloadRoom(room_id);
517 if (!sys_status.ok()) {
518 LOG_ERROR("DungeonEditorV2", "Failed to load system data: %s",
519 sys_status.message().data());
520 }
521 }
522 }
523
524 if (room.IsLoaded()) {
525 bool needs_render = false;
526
527 // Chronological Step 1: Load Room Data from ROM
528 // This reads the 14-byte room header (blockset, palette, effect, tags)
529 // Reference: kRoomHeaderPointer (0xB5DD)
530 if (room.blocks().empty()) {
531 room.LoadRoomGraphics(room.blockset);
532 needs_render = true;
533 LOG_DEBUG("[DungeonEditorV2]", "Loaded room %d graphics from ROM",
534 room_id);
535 }
536
537 // Chronological Step 2: Load Objects from ROM
538 // This reads the variable-length object stream (subtype 1, 2, 3 objects)
539 // Reference: kRoomObjectPointer (0x874C)
540 // CRITICAL: This step decodes floor1/floor2 bytes which dictate the floor
541 // pattern
542 if (room.GetTileObjects().empty()) {
543 room.LoadObjects();
544 needs_render = true;
545 LOG_DEBUG("[DungeonEditorV2]", "Loaded room %d objects from ROM",
546 room_id);
547 }
548
549 // Chronological Step 3: Render Graphics to Bitmaps
550 // This executes the draw routines (bank_01.asm logic) to populate BG1/BG2
551 // buffers Sequence:
552 // 1. Draw Floor (from floor1/floor2)
553 // 2. Draw Layout (walls/floors from object list)
554 // 3. Draw Objects (subtypes 1, 2, 3)
555 auto& bg1_bitmap = room.bg1_buffer().bitmap();
556 if (needs_render || !bg1_bitmap.is_active() || bg1_bitmap.width() == 0) {
557 room.RenderRoomGraphics();
558 LOG_DEBUG("[DungeonEditorV2]", "Rendered room %d to bitmaps", room_id);
559 }
560 }
561
562 if (room.IsLoaded()) {
563 ImGui::TextColored(theme.text_success_green, ICON_MD_CHECK " Loaded");
564 } else {
565 ImGui::TextColored(theme.text_error_red, ICON_MD_PENDING " Not Loaded");
566 }
567 ImGui::SameLine();
568 ImGui::TextDisabled("Objects: %zu", room.GetTileObjects().size());
569
570 ImGui::Separator();
571
572 // Use per-room viewer
573 if (auto* viewer = GetViewerForRoom(room_id)) {
574 viewer->DrawDungeonCanvas(room_id);
575 }
576}
577
578void DungeonEditorV2::OnRoomSelected(int room_id, bool request_focus) {
579 current_room_id_ = room_id;
580
582 dungeon_editor_system_->SetExternalRoom(&rooms_[room_id]);
583 }
584
585 // Update object editor card with current viewer
588 // IMPORTANT: Update the viewer reference!
590 }
591
592 // Update sprite and item editor panels with current viewer
595 }
596 if (item_editor_panel_) {
598 }
599
600 // Sync palette with current room (must happen before early return for focus changes)
601 if (room_id >= 0 && room_id < (int)rooms_.size()) {
602 auto& room = rooms_[room_id];
603 if (!room.IsLoaded()) {
604 room_loader_.LoadRoom(room_id, room);
605 }
606
607 if (room.IsLoaded()) {
608 current_palette_id_ = room.palette;
610
611 // Update viewer and object editor palette
612 if (auto* viewer = GetViewerForRoom(room_id)) {
613 viewer->SetCurrentPaletteId(current_palette_id_);
614
615 if (game_data()) {
616 auto dungeon_main_pal_group =
618 if (current_palette_id_ < (int)dungeon_main_pal_group.size()) {
619 current_palette_ = dungeon_main_pal_group[current_palette_id_];
620 auto result =
622 if (result.ok()) {
623 current_palette_group_ = result.value();
624 viewer->SetCurrentPaletteGroup(current_palette_group_);
628 }
629 // Sync palette to graphics panel for proper sheet coloring
633 }
634 }
635 }
636 }
637 }
638 }
639 }
640
641 // Check if room is already open
642 for (int i = 0; i < active_rooms_.Size; i++) {
643 if (active_rooms_[i] == room_id) {
644 // Always ensure panel is visible, even if already in active_rooms_
646 std::string card_id = absl::StrFormat("dungeon.room_%d", room_id);
648 }
649 if (request_focus) {
650 FocusRoom(room_id);
651 }
652 return;
653 }
654 }
655
656 active_rooms_.push_back(room_id);
658
660 // Use unified ResourceLabelProvider for room names
661 std::string room_name = absl::StrFormat(
662 "[%03X] %s", room_id, zelda3::GetRoomLabel(room_id).c_str());
663
664 std::string base_card_id = absl::StrFormat("dungeon.room_%d", room_id);
665
667 {.card_id = base_card_id,
668 .display_name = room_name,
669 .window_title = ICON_MD_GRID_ON " " + room_name,
670 .icon = ICON_MD_GRID_ON,
671 .category = "Dungeon",
672 .shortcut_hint = "",
673 .visibility_flag = nullptr,
674 .priority = 200 + room_id});
675
677 }
678}
679
681 if (entrance_id < 0 || entrance_id >= static_cast<int>(entrances_.size())) {
682 return;
683 }
684 int room_id = entrances_[entrance_id].room_;
685 OnRoomSelected(room_id);
686}
687
688void DungeonEditorV2::add_room(int room_id) {
689 OnRoomSelected(room_id);
690}
691
692void DungeonEditorV2::FocusRoom(int room_id) {
693 auto it = room_cards_.find(room_id);
694 if (it != room_cards_.end()) {
695 it->second->Focus();
696 }
697}
698
705
716
720
722 if (current_room_id_ < 0 ||
723 current_room_id_ >= static_cast<int>(rooms_.size())) {
724 LOG_ERROR("DungeonEditorV2", "Cannot place object: Invalid room ID %d",
726 return;
727 }
728
729 auto& room = rooms_[current_room_id_];
730
731 LOG_INFO("DungeonEditorV2",
732 "Placing object ID=0x%02X at position (%d,%d) in room %03X", obj.id_,
733 obj.x_, obj.y_, current_room_id_);
734
735 room.RenderRoomGraphics();
736 LOG_DEBUG("DungeonEditorV2",
737 "Object placed and room re-rendered successfully");
738}
739
740void DungeonEditorV2::SwapRoomInPanel(int old_room_id, int new_room_id) {
741 // Defer the swap until after the current frame's draw phase completes
742 // This prevents modifying data structures while ImGui is still using them
743 if (new_room_id < 0 || new_room_id >= static_cast<int>(rooms_.size())) {
744 return;
745 }
746 pending_swap_.old_room_id = old_room_id;
747 pending_swap_.new_room_id = new_room_id;
748 pending_swap_.pending = true;
749}
750
752 if (!pending_swap_.pending) {
753 return;
754 }
755
756 int old_room_id = pending_swap_.old_room_id;
757 int new_room_id = pending_swap_.new_room_id;
758 pending_swap_.pending = false;
759
760 // Find the position of old_room in active_rooms_
761 int swap_index = -1;
762 for (int i = 0; i < active_rooms_.Size; i++) {
763 if (active_rooms_[i] == old_room_id) {
764 swap_index = i;
765 break;
766 }
767 }
768
769 if (swap_index < 0) {
770 // Old room not found in active rooms, just select the new one
771 OnRoomSelected(new_room_id);
772 return;
773 }
774
775 // Replace old room with new room in active_rooms_
776 active_rooms_[swap_index] = new_room_id;
778
779 // Unregister old panel
781 std::string old_card_id = absl::StrFormat("dungeon.room_%d", old_room_id);
783 }
784
785 // Clean up old room's card and viewer
786 room_cards_.erase(old_room_id);
787 room_viewers_.erase(old_room_id);
788
789 // Register new panel
791 // Use unified ResourceLabelProvider for room names
792 std::string new_room_name = absl::StrFormat(
793 "[%03X] %s", new_room_id, zelda3::GetRoomLabel(new_room_id).c_str());
794
795 std::string new_card_id = absl::StrFormat("dungeon.room_%d", new_room_id);
796
798 {.card_id = new_card_id,
799 .display_name = new_room_name,
800 .window_title = ICON_MD_GRID_ON " " + new_room_name,
801 .icon = ICON_MD_GRID_ON,
802 .category = "Dungeon",
803 .shortcut_hint = "",
804 .visibility_flag = nullptr,
805 .priority = 200 + new_room_id});
806
808 }
809
810 // Update current selection
811 OnRoomSelected(new_room_id, /*request_focus=*/false);
812}
813
815 auto it = room_viewers_.find(room_id);
816 if (it == room_viewers_.end()) {
817 auto viewer = std::make_unique<DungeonCanvasViewer>(rom_);
818 viewer->SetRooms(&rooms_);
819 viewer->SetRenderer(renderer_);
820 viewer->SetCurrentPaletteGroup(current_palette_group_);
821 viewer->SetCurrentPaletteId(current_palette_id_);
822 viewer->SetGameData(game_data_);
823
824 viewer->object_interaction().SetMutationHook(
825 [this, room_id]() { PushUndoSnapshot(room_id); });
826
827 viewer->object_interaction().SetCacheInvalidationCallback(
828 [this, room_id]() {
829 if (room_id >= 0 && room_id < static_cast<int>(rooms_.size())) {
830 rooms_[room_id].MarkObjectsDirty();
831 rooms_[room_id].RenderRoomGraphics();
832 }
833 });
834
835 viewer->object_interaction().SetObjectPlacedCallback(
836 [this](const zelda3::RoomObject& obj) { HandleObjectPlaced(obj); });
837
839 viewer->SetEditorSystem(dungeon_editor_system_.get());
840 }
841 viewer->SetRoomNavigationCallback([this](int target_room) {
842 if (target_room >= 0 && target_room < static_cast<int>(rooms_.size())) {
843 OnRoomSelected(target_room);
844 }
845 });
846 // Swap callback swaps the room in the current panel instead of opening new
847 viewer->SetRoomSwapCallback([this](int old_room, int new_room) {
848 SwapRoomInPanel(old_room, new_room);
849 });
850 viewer->SetShowObjectPanelCallback([this]() { ShowPanel(kObjectToolsId); });
851 viewer->SetShowSpritePanelCallback(
852 [this]() { ShowPanel("dungeon.sprite_editor"); });
853 viewer->SetShowItemPanelCallback(
854 [this]() { ShowPanel("dungeon.item_editor"); });
855
856 room_viewers_[room_id] = std::move(viewer);
857 return room_viewers_[room_id].get();
858 }
859 return it->second.get();
860}
861
862absl::Status DungeonEditorV2::Undo() {
864 return dungeon_editor_system_->Undo();
865 }
866 return absl::UnimplementedError("Undo not available");
867}
868
869absl::Status DungeonEditorV2::Redo() {
871 return dungeon_editor_system_->Redo();
872 }
873 return absl::UnimplementedError("Redo not available");
874}
875
876absl::Status DungeonEditorV2::Cut() {
877 if (auto* viewer = GetViewerForRoom(current_room_id_)) {
878 viewer->object_interaction().HandleCopySelected();
879 viewer->object_interaction().HandleDeleteSelected();
880 }
881 return absl::OkStatus();
882}
883
884absl::Status DungeonEditorV2::Copy() {
885 if (auto* viewer = GetViewerForRoom(current_room_id_)) {
886 viewer->object_interaction().HandleCopySelected();
887 }
888 return absl::OkStatus();
889}
890
892 if (auto* viewer = GetViewerForRoom(current_room_id_)) {
893 viewer->object_interaction().HandlePasteObjects();
894 }
895 return absl::OkStatus();
896}
897
899 if (room_id < 0 || room_id >= static_cast<int>(rooms_.size()))
900 return;
901
902 undo_history_[room_id].push_back(rooms_[room_id].GetTileObjects());
903 ClearRedo(room_id);
904}
905
907 int room_id, std::vector<zelda3::RoomObject> snapshot) {
908 if (room_id < 0 || room_id >= static_cast<int>(rooms_.size())) {
909 return absl::InvalidArgumentError("Invalid room ID");
910 }
911
912 auto& room = rooms_[room_id];
913 room.GetTileObjects() = std::move(snapshot);
914 room.RenderRoomGraphics();
915 return absl::OkStatus();
916}
917
918void DungeonEditorV2::ClearRedo(int room_id) {
919 redo_history_[room_id].clear();
920}
921
922} // 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:115
static Arena & Get()
Definition arena.cc:20
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.
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:103
std::string code_folder
Definition project.h:97
gfx::PaletteGroupMap palette_groups
Definition game_data.h:89