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#include "dungeon_editor_v2.h"
3
4#include <algorithm>
5#include <cstdio>
6
7#include "absl/strings/str_format.h"
11#include "zelda3/dungeon/room.h"
12#include "app/gui/core/icons.h"
13#include "app/gui/core/input.h"
14#include "imgui/imgui.h"
15#include "util/log.h"
16
17namespace yaze::editor {
18
19// No table layout needed - all cards are independent
20
22 renderer_ = renderer;
23 rom_ = rom;
24 // Don't initialize emulator preview yet - ROM might not be loaded
25 // Will be initialized in Load() instead
26
27 // Setup docking class for room windows (ImGui::GetID will be called in Update when ImGui is ready)
28 room_window_class_.DockingAllowUnclassed = true; // Room windows can dock with anything
29 room_window_class_.DockingAlwaysTabBar = true; // Always show tabs when multiple rooms
30
31 // Register all cards with EditorCardRegistry (dependency injection)
32 if (!dependencies_.card_registry) return;
33 auto* card_registry = dependencies_.card_registry;
34
35 card_registry->RegisterCard({
36 .card_id = MakeCardId("dungeon.control_panel"),
37 .display_name = "Dungeon Controls",
38 .icon = ICON_MD_CASTLE,
39 .category = "Dungeon",
40 .shortcut_hint = "Ctrl+Shift+D",
41 .visibility_flag = &show_control_panel_,
42 .priority = 10
43 });
44
45 card_registry->RegisterCard({
46 .card_id = MakeCardId("dungeon.room_selector"),
47 .display_name = "Room Selector",
48 .icon = ICON_MD_LIST,
49 .category = "Dungeon",
50 .shortcut_hint = "Ctrl+Shift+R",
51 .visibility_flag = &show_room_selector_,
52 .priority = 20
53 });
54
55 card_registry->RegisterCard({
56 .card_id = MakeCardId("dungeon.room_matrix"),
57 .display_name = "Room Matrix",
58 .icon = ICON_MD_GRID_VIEW,
59 .category = "Dungeon",
60 .shortcut_hint = "Ctrl+Shift+M",
61 .visibility_flag = &show_room_matrix_,
62 .priority = 30
63 });
64
65 card_registry->RegisterCard({
66 .card_id = MakeCardId("dungeon.entrances"),
67 .display_name = "Entrances",
68 .icon = ICON_MD_DOOR_FRONT,
69 .category = "Dungeon",
70 .shortcut_hint = "Ctrl+Shift+E",
71 .visibility_flag = &show_entrances_list_,
72 .priority = 40
73 });
74
75 card_registry->RegisterCard({
76 .card_id = MakeCardId("dungeon.room_graphics"),
77 .display_name = "Room Graphics",
78 .icon = ICON_MD_IMAGE,
79 .category = "Dungeon",
80 .shortcut_hint = "Ctrl+Shift+G",
81 .visibility_flag = &show_room_graphics_,
82 .priority = 50
83 });
84
85 card_registry->RegisterCard({
86 .card_id = MakeCardId("dungeon.object_editor"),
87 .display_name = "Object Editor",
89 .category = "Dungeon",
90 .shortcut_hint = "Ctrl+Shift+O",
91 .visibility_flag = &show_object_editor_,
92 .priority = 60
93 });
94
95 card_registry->RegisterCard({
96 .card_id = MakeCardId("dungeon.palette_editor"),
97 .display_name = "Palette Editor",
98 .icon = ICON_MD_PALETTE,
99 .category = "Dungeon",
100 .shortcut_hint = "Ctrl+Shift+P",
101 .visibility_flag = &show_palette_editor_,
102 .priority = 70
103 });
104
105 card_registry->RegisterCard({
106 .card_id = MakeCardId("dungeon.debug_controls"),
107 .display_name = "Debug Controls",
108 .icon = ICON_MD_BUG_REPORT,
109 .category = "Dungeon",
110 .shortcut_hint = "Ctrl+Shift+B",
111 .visibility_flag = &show_debug_controls_,
112 .priority = 80
113 });
114
115 // Show control panel and room selector by default when Dungeon Editor is activated
116 show_control_panel_ = true;
117 show_room_selector_ = true;
118}
119
121
122absl::Status DungeonEditorV2::Load() {
123 if (!rom_ || !rom_->is_loaded()) {
124 return absl::FailedPreconditionError("ROM not loaded");
125 }
126
127 // Load all rooms using the loader component - DEFERRED for lazy loading
128 // RETURN_IF_ERROR(room_loader_.LoadAllRooms(rooms_));
130
131 // Load palette group
132 auto dungeon_main_pal_group = rom_->palette_group().dungeon_main;
133 current_palette_ = dungeon_main_pal_group[current_palette_group_id_];
136
137 // Initialize components with loaded data
142 [this](int room_id) { OnRoomSelected(room_id); });
143
147
151
152 // NOW initialize emulator preview with loaded ROM
154
155 // Initialize centralized PaletteManager with ROM data
156 // This MUST be done before initializing palette_editor_
158
159 // Initialize palette editor with loaded ROM
161
162 // Initialize unified object editor card
163 object_editor_card_ = std::make_unique<ObjectEditorCard>(renderer_, rom_, &canvas_viewer_);
164
165 // Wire palette changes to trigger room re-renders
166 // PaletteManager now tracks all modifications globally
167 palette_editor_.SetOnPaletteChanged([this](int /*palette_id*/) {
168 // Re-render all active rooms when palette changes
169 for (int i = 0; i < active_rooms_.Size; i++) {
170 int room_id = active_rooms_[i];
171 if (room_id >= 0 && room_id < (int)rooms_.size()) {
172 rooms_[room_id].RenderRoomGraphics();
173 }
174 }
175 });
176
177 is_loaded_ = true;
178 return absl::OkStatus();
179}
180
182 // Initialize docking class ID on first Update (when ImGui is ready)
183 if (room_window_class_.ClassId == 0) {
184 room_window_class_.ClassId = ImGui::GetID("DungeonRoomClass");
185 }
186
187 if (!is_loaded_) {
188 // CARD-BASED EDITOR: Create a minimal loading card
189 gui::EditorCard loading_card("Dungeon Editor Loading", ICON_MD_CASTLE);
190 loading_card.SetDefaultSize(400, 200);
191 if (loading_card.Begin()) {
192 ImGui::TextColored(ImVec4(0.5f, 0.5f, 0.5f, 1.0f), "Loading dungeon data...");
193 ImGui::TextWrapped("Independent editor cards will appear once ROM data is loaded.");
194 }
195 loading_card.End();
196 return absl::OkStatus();
197 }
198
199 // CARD-BASED EDITOR: All windows are independent top-level cards
200 // No parent wrapper - this allows closing control panel without affecting rooms
201
202 DrawLayout();
203
204 return absl::OkStatus();
205}
206
207absl::Status DungeonEditorV2::Save() {
208 if (!rom_ || !rom_->is_loaded()) {
209 return absl::FailedPreconditionError("ROM not loaded");
210 }
211
212 // Save palette changes first (if any)
213 if (gfx::PaletteManager::Get().HasUnsavedChanges()) {
214 auto status = gfx::PaletteManager::Get().SaveAllToRom();
215 if (!status.ok()) {
216 LOG_ERROR("DungeonEditorV2", "Failed to save palette changes: %s",
217 status.message().data());
218 return status;
219 }
220 LOG_INFO("DungeonEditorV2", "Saved %zu modified colors to ROM",
221 gfx::PaletteManager::Get().GetModifiedColorCount());
222 }
223
224 // Save all rooms (SaveObjects will handle which ones need saving)
225 for (auto& room : rooms_) {
226 auto status = room.SaveObjects();
227 if (!status.ok()) {
228 // Log error but continue with other rooms
229 LOG_ERROR("DungeonEditorV2", "Failed to save room: %s",
230 status.message().data());
231 }
232 }
233
234 return absl::OkStatus();
235}
236
238 // NO TABLE LAYOUT - All independent dockable EditorCards
239 // All cards check their visibility flags and can be closed with X button
240
241 // 1. Room Selector Card (independent, dockable)
244 // Card handles its own closing via &show_room_selector_ in constructor
245 }
246
247 // 2. Room Matrix Card (visual navigation)
248 if (show_room_matrix_) {
250 // Card handles its own closing via &show_room_matrix_ in constructor
251 }
252
253 // 3. Entrances List Card
256 // Card handles its own closing via &show_entrances_list_ in constructor
257 }
258
259 // 4. Room Graphics Card
262 // Card handles its own closing via &show_room_graphics_ in constructor
263 }
264
265 // 5. Unified Object Editor Card
268 // ObjectEditorCard handles closing via p_open parameter
269 }
270
271 // 6. Palette Editor Card (independent, dockable)
273 gui::EditorCard palette_card(
274 MakeCardTitle("Palette Editor").c_str(),
276 if (palette_card.Begin()) {
278 }
279 palette_card.End();
280 // Card handles its own closing via &show_palette_editor_ in constructor
281 }
282
283 // 7. Debug Controls Card (independent, dockable)
286 }
287
288 // 8. Active Room Cards (independent, dockable, tracked for jump-to)
289 for (int i = 0; i < active_rooms_.Size; i++) {
290 int room_id = active_rooms_[i];
291 bool open = true;
292
293 // Create session-aware card title with room ID prominent
294 std::string base_name;
295 if (room_id >= 0 && static_cast<size_t>(room_id) < std::size(zelda3::kRoomNames)) {
296 base_name = absl::StrFormat("[%03X] %s", room_id, zelda3::kRoomNames[room_id].data());
297 } else {
298 base_name = absl::StrFormat("Room %03X", room_id);
299 }
300
301 std::string card_name_str = absl::StrFormat("%s###RoomCard%d",
302 MakeCardTitle(base_name).c_str(), room_id);
303
304 // Track or create card for jump-to functionality
305 if (room_cards_.find(room_id) == room_cards_.end()) {
306 room_cards_[room_id] = std::make_shared<gui::EditorCard>(
307 card_name_str.c_str(), ICON_MD_GRID_ON, &open);
308 room_cards_[room_id]->SetDefaultSize(700, 600);
309
310 // Set default position for first room to be docked with main window
311 if (active_rooms_.Size == 1) {
313 }
314 }
315
316 auto& room_card = room_cards_[room_id];
317
318 // CRITICAL: Use docking class BEFORE Begin() to make rooms dock together
319 // This creates a separate docking space for all room cards
320 ImGui::SetNextWindowClass(&room_window_class_);
321
322 // Make room cards fully dockable and independent
323 if (room_card->Begin(&open)) {
324 DrawRoomTab(room_id);
325 }
326 room_card->End();
327
328 if (!open) {
329 room_cards_.erase(room_id);
330 active_rooms_.erase(active_rooms_.Data + i);
331 i--;
332 }
333 }
334}
335
337 if (room_id < 0 || room_id >= 0x128) {
338 ImGui::Text("Invalid room ID: %d", room_id);
339 return;
340 }
341
342 auto& room = rooms_[room_id];
343
344 // Lazy load room data
345 if (!room.IsLoaded()) {
346 auto status = room_loader_.LoadRoom(room_id, room);
347 if (!status.ok()) {
348 ImGui::TextColored(ImVec4(1, 0, 0, 1), "Failed to load room: %s",
349 status.message().data());
350 return;
351 }
352 }
353
354 // Initialize room graphics and objects in CORRECT ORDER
355 // Critical sequence: 1. Load data from ROM, 2. Load objects (sets floor graphics), 3. Render
356 if (room.IsLoaded()) {
357 bool needs_render = false;
358
359 // Step 1: Load room data from ROM (blocks, blockset info)
360 if (room.blocks().empty()) {
361 room.LoadRoomGraphics(room.blockset);
362 needs_render = true;
363 LOG_DEBUG("[DungeonEditorV2]", "Loaded room %d graphics from ROM", room_id);
364 }
365
366 // Step 2: Load objects from ROM (CRITICAL: sets floor1_graphics_, floor2_graphics_!)
367 if (room.GetTileObjects().empty()) {
368 room.LoadObjects();
369 needs_render = true;
370 LOG_DEBUG("[DungeonEditorV2]", "Loaded room %d objects from ROM", room_id);
371 }
372
373 // Step 3: Render to bitmaps (now floor graphics are set correctly!)
374 auto& bg1_bitmap = room.bg1_buffer().bitmap();
375 if (needs_render || !bg1_bitmap.is_active() || bg1_bitmap.width() == 0) {
376 room.RenderRoomGraphics(); // Includes RenderObjectsToBackground() internally
377 LOG_DEBUG("[DungeonEditorV2]", "Rendered room %d to bitmaps", room_id);
378 }
379 }
380
381 // Room ID moved to card title - just show load status now
382 if (room.IsLoaded()) {
383 ImGui::TextColored(ImVec4(0.4f, 0.8f, 0.4f, 1.0f), ICON_MD_CHECK " Loaded");
384 } else {
385 ImGui::TextColored(ImVec4(0.8f, 0.4f, 0.4f, 1.0f), ICON_MD_PENDING " Not Loaded");
386 }
387 ImGui::SameLine();
388 ImGui::TextDisabled("Objects: %zu", room.GetTileObjects().size());
389
390 ImGui::Separator();
391
392 // Canvas - fully delegated to DungeonCanvasViewer
393 // DungeonCanvasViewer has DrawDungeonCanvas() method
395}
396
398 current_room_id_ = room_id;
399
400 // Check if already open
401 for (int i = 0; i < active_rooms_.Size; i++) {
402 if (active_rooms_[i] == room_id) {
403 // Focus the existing room card
404 FocusRoom(room_id);
405 return;
406 }
407 }
408
409 // Add new room to be opened as a card
410 active_rooms_.push_back(room_id);
412}
413
415 if (entrance_id < 0 || entrance_id >= static_cast<int>(entrances_.size())) {
416 return;
417 }
418
419 // Get the room ID associated with this entrance
420 int room_id = entrances_[entrance_id].room_;
421
422 // Open and focus the room
423 OnRoomSelected(room_id);
424}
425
426void DungeonEditorV2::add_room(int room_id) {
427 OnRoomSelected(room_id);
428}
429
430void DungeonEditorV2::FocusRoom(int room_id) {
431 // Focus the room card if it exists
432 auto it = room_cards_.find(room_id);
433 if (it != room_cards_.end()) {
434 it->second->Focus();
435 }
436}
437
439 gui::EditorCard selector_card(
440 MakeCardTitle("Rooms List").c_str(),
442
443 selector_card.SetDefaultSize(350, 600);
444
445 if (selector_card.Begin()) {
446 if (!rom_ || !rom_->is_loaded()) {
447 ImGui::Text("ROM not loaded");
448 } else {
449 // Add text filter
450 static char room_filter[256] = "";
451 ImGui::SetNextItemWidth(-1);
452 if (ImGui::InputTextWithHint("##RoomFilter", ICON_MD_SEARCH " Filter rooms...",
453 room_filter, sizeof(room_filter))) {
454 // Filter updated
455 }
456
457 ImGui::Separator();
458
459 // Scrollable room list - simple and reliable
460 if (ImGui::BeginChild("##RoomsList", ImVec2(0, 0), true,
461 ImGuiWindowFlags_AlwaysVerticalScrollbar)) {
462 std::string filter_str = room_filter;
463 std::transform(filter_str.begin(), filter_str.end(), filter_str.begin(), ::tolower);
464
465 for (int i = 0; i < zelda3::NumberOfRooms; i++) {
466 // Get room name
467 std::string room_name;
468 if (i < static_cast<int>(std::size(zelda3::kRoomNames))) {
469 room_name = std::string(zelda3::kRoomNames[i]);
470 } else {
471 room_name = absl::StrFormat("Room %03X", i);
472 }
473
474 // Apply filter
475 if (!filter_str.empty()) {
476 std::string name_lower = room_name;
477 std::transform(name_lower.begin(), name_lower.end(),
478 name_lower.begin(), ::tolower);
479 if (name_lower.find(filter_str) == std::string::npos) {
480 continue;
481 }
482 }
483
484 // Simple selectable with room ID and name
485 std::string label = absl::StrFormat("[%03X] %s", i, room_name.c_str());
486 bool is_selected = (current_room_id_ == i);
487
488 if (ImGui::Selectable(label.c_str(), is_selected)) {
490 }
491
492 if (ImGui::IsItemHovered() && ImGui::IsMouseDoubleClicked(0)) {
494 }
495 }
496 ImGui::EndChild();
497 }
498 }
499 }
500 selector_card.End();
501}
502
504 gui::EditorCard entrances_card(
505 MakeCardTitle("Entrances").c_str(),
507
508 entrances_card.SetDefaultSize(400, 700);
509
510 if (entrances_card.Begin()) {
511 if (!rom_ || !rom_->is_loaded()) {
512 ImGui::Text("ROM not loaded");
513 } else {
514 // Full entrance configuration UI (matching dungeon_room_selector layout)
515 auto& current_entrance = entrances_[current_entrance_id_];
516
517 gui::InputHexWord("Entrance ID", &current_entrance.entrance_id_);
518 gui::InputHexWord("Room ID", reinterpret_cast<uint16_t*>(&current_entrance.room_));
519 ImGui::SameLine();
520 gui::InputHexByte("Dungeon ID", &current_entrance.dungeon_id_, 50.f, true);
521
522 gui::InputHexByte("Blockset", &current_entrance.blockset_, 50.f, true);
523 ImGui::SameLine();
524 gui::InputHexByte("Music", &current_entrance.music_, 50.f, true);
525 ImGui::SameLine();
526 gui::InputHexByte("Floor", &current_entrance.floor_);
527
528 ImGui::Separator();
529
530 gui::InputHexWord("Player X ", &current_entrance.x_position_);
531 ImGui::SameLine();
532 gui::InputHexWord("Player Y ", &current_entrance.y_position_);
533
534 gui::InputHexWord("Camera X", &current_entrance.camera_trigger_x_);
535 ImGui::SameLine();
536 gui::InputHexWord("Camera Y", &current_entrance.camera_trigger_y_);
537
538 gui::InputHexWord("Scroll X ", &current_entrance.camera_x_);
539 ImGui::SameLine();
540 gui::InputHexWord("Scroll Y ", &current_entrance.camera_y_);
541
542 gui::InputHexWord("Exit", reinterpret_cast<uint16_t*>(&current_entrance.exit_), 50.f, true);
543
544 ImGui::Separator();
545 ImGui::Text("Camera Boundaries");
546 ImGui::Separator();
547 ImGui::Text("\t\t\t\t\tNorth East South West");
548
549 gui::InputHexByte("Quadrant", &current_entrance.camera_boundary_qn_, 50.f, true);
550 ImGui::SameLine();
551 gui::InputHexByte("##QE", &current_entrance.camera_boundary_qe_, 50.f, true);
552 ImGui::SameLine();
553 gui::InputHexByte("##QS", &current_entrance.camera_boundary_qs_, 50.f, true);
554 ImGui::SameLine();
555 gui::InputHexByte("##QW", &current_entrance.camera_boundary_qw_, 50.f, true);
556
557 gui::InputHexByte("Full room", &current_entrance.camera_boundary_fn_, 50.f, true);
558 ImGui::SameLine();
559 gui::InputHexByte("##FE", &current_entrance.camera_boundary_fe_, 50.f, true);
560 ImGui::SameLine();
561 gui::InputHexByte("##FS", &current_entrance.camera_boundary_fs_, 50.f, true);
562 ImGui::SameLine();
563 gui::InputHexByte("##FW", &current_entrance.camera_boundary_fw_, 50.f, true);
564
565 ImGui::Separator();
566
567 // Entrance list - simple and reliable
568 if (ImGui::BeginChild("##EntrancesList", ImVec2(0, 0), true,
569 ImGuiWindowFlags_AlwaysVerticalScrollbar)) {
570 for (int i = 0; i < 0x8C; i++) {
571 // The last seven are spawn points
572 std::string entrance_name;
573 if (i < 0x85) {
574 entrance_name = std::string(zelda3::kEntranceNames[i]);
575 } else {
576 entrance_name = absl::StrFormat("Spawn Point %d", i - 0x85);
577 }
578
579 // Get associated room name
580 int room_id = entrances_[i].room_;
581 std::string room_name = "Unknown";
582 if (room_id >= 0 && room_id < static_cast<int>(std::size(zelda3::kRoomNames))) {
583 room_name = std::string(zelda3::kRoomNames[room_id]);
584 }
585
586 std::string label = absl::StrFormat("[%02X] %s -> %s",
587 i, entrance_name.c_str(), room_name.c_str());
588
589 bool is_selected = (current_entrance_id_ == i);
590 if (ImGui::Selectable(label.c_str(), is_selected)) {
593 }
594 }
595 ImGui::EndChild();
596 }
597 }
598 }
599 entrances_card.End();
600}
601
603 gui::EditorCard matrix_card(
604 MakeCardTitle("Room Matrix").c_str(),
606
607 matrix_card.SetDefaultSize(440, 520);
608
609 if (matrix_card.Begin()) {
610 // 16 wide x 19 tall = 304 cells (296 rooms + 8 empty)
611 constexpr int kRoomsPerRow = 16;
612 constexpr int kRoomsPerCol = 19;
613 constexpr int kTotalRooms = 0x128; // 296 rooms (0x00-0x127)
614 constexpr float kRoomCellSize = 24.0f; // Smaller cells like ZScream
615 constexpr float kCellSpacing = 1.0f; // Tighter spacing
616
617 ImDrawList* draw_list = ImGui::GetWindowDrawList();
618 ImVec2 canvas_pos = ImGui::GetCursorScreenPos();
619
620 int room_index = 0;
621 for (int row = 0; row < kRoomsPerCol; row++) {
622 for (int col = 0; col < kRoomsPerRow; col++) {
623 int room_id = room_index;
624 bool is_valid_room = (room_id < kTotalRooms);
625
626 ImVec2 cell_min = ImVec2(
627 canvas_pos.x + col * (kRoomCellSize + kCellSpacing),
628 canvas_pos.y + row * (kRoomCellSize + kCellSpacing));
629 ImVec2 cell_max = ImVec2(
630 cell_min.x + kRoomCellSize,
631 cell_min.y + kRoomCellSize);
632
633 if (is_valid_room) {
634 // Use simple deterministic color based on room ID (no loading needed)
635 ImU32 bg_color;
636
637 // Generate color from room ID - much faster than loading
638 int hue = (room_id * 37) % 360; // Distribute colors across spectrum
639 int saturation = 40 + (room_id % 3) * 15;
640 int brightness = 50 + (room_id % 5) * 10;
641
642 // Convert HSV to RGB
643 float h = hue / 60.0f;
644 float s = saturation / 100.0f;
645 float v = brightness / 100.0f;
646
647 int i = static_cast<int>(h);
648 float f = h - i;
649 int p = static_cast<int>(v * (1 - s) * 255);
650 int q = static_cast<int>(v * (1 - s * f) * 255);
651 int t = static_cast<int>(v * (1 - s * (1 - f)) * 255);
652 int val = static_cast<int>(v * 255);
653
654 switch (i % 6) {
655 case 0: bg_color = IM_COL32(val, t, p, 255); break;
656 case 1: bg_color = IM_COL32(q, val, p, 255); break;
657 case 2: bg_color = IM_COL32(p, val, t, 255); break;
658 case 3: bg_color = IM_COL32(p, q, val, 255); break;
659 case 4: bg_color = IM_COL32(t, p, val, 255); break;
660 case 5: bg_color = IM_COL32(val, p, q, 255); break;
661 default: bg_color = IM_COL32(60, 60, 70, 255); break;
662 }
663
664 // Check if room is currently selected
665 bool is_current = (current_room_id_ == room_id);
666
667 // Check if room is open in a card
668 bool is_open = false;
669 for (int i = 0; i < active_rooms_.Size; i++) {
670 if (active_rooms_[i] == room_id) {
671 is_open = true;
672 break;
673 }
674 }
675
676 // Draw cell background with palette color
677 draw_list->AddRectFilled(cell_min, cell_max, bg_color);
678
679 // Draw outline ONLY for current/open rooms
680 if (is_current) {
681 // Light green for current room
682 draw_list->AddRect(cell_min, cell_max,
683 IM_COL32(144, 238, 144, 255), 0.0f, 0, 2.5f);
684 } else if (is_open) {
685 // Green for open rooms
686 draw_list->AddRect(cell_min, cell_max,
687 IM_COL32(0, 200, 0, 255), 0.0f, 0, 2.0f);
688 } else {
689 // Subtle gray border for all rooms
690 draw_list->AddRect(cell_min, cell_max,
691 IM_COL32(80, 80, 80, 200), 0.0f, 0, 1.0f);
692 }
693
694 // Draw room ID (small text)
695 std::string room_label = absl::StrFormat("%02X", room_id);
696 ImVec2 text_size = ImGui::CalcTextSize(room_label.c_str());
697 ImVec2 text_pos = ImVec2(
698 cell_min.x + (kRoomCellSize - text_size.x) * 0.5f,
699 cell_min.y + (kRoomCellSize - text_size.y) * 0.5f);
700
701 // Use smaller font if available
702 draw_list->AddText(text_pos, IM_COL32(220, 220, 220, 255),
703 room_label.c_str());
704
705 // Handle clicks
706 ImGui::SetCursorScreenPos(cell_min);
707 ImGui::InvisibleButton(
708 absl::StrFormat("##room%d", room_id).c_str(),
709 ImVec2(kRoomCellSize, kRoomCellSize));
710
711 if (ImGui::IsItemClicked()) {
712 OnRoomSelected(room_id);
713 }
714
715 // Hover tooltip with room name and status
716 if (ImGui::IsItemHovered()) {
717 ImGui::BeginTooltip();
718 if (room_id < static_cast<int>(std::size(zelda3::kRoomNames))) {
719 ImGui::Text("%s", zelda3::kRoomNames[room_id].data());
720 } else {
721 ImGui::Text("Room %03X", room_id);
722 }
723 ImGui::Text("Status: %s", is_open ? "Open" : is_current ? "Current" : "Closed");
724 ImGui::Text("Click to %s", is_open ? "focus" : "open");
725 ImGui::EndTooltip();
726 }
727 } else {
728 // Empty cell
729 draw_list->AddRectFilled(cell_min, cell_max,
730 IM_COL32(30, 30, 30, 255));
731 draw_list->AddRect(cell_min, cell_max,
732 IM_COL32(50, 50, 50, 255));
733 }
734
735 room_index++;
736 }
737 }
738
739 // Advance cursor past the grid
740 ImGui::Dummy(ImVec2(
741 kRoomsPerRow * (kRoomCellSize + kCellSpacing),
742 kRoomsPerCol * (kRoomCellSize + kCellSpacing)));
743 }
744 matrix_card.End();
745}
746
748 gui::EditorCard graphics_card(
749 MakeCardTitle("Room Graphics").c_str(),
751
752 graphics_card.SetDefaultSize(350, 500);
754
755 if (graphics_card.Begin()) {
756 if (!rom_ || !rom_->is_loaded()) {
757 ImGui::Text("ROM not loaded");
758 } else if (current_room_id_ >= 0 && current_room_id_ < static_cast<int>(rooms_.size())) {
759 // Show graphics for current room
760 auto& room = rooms_[current_room_id_];
761
762 ImGui::Text("Room %03X Graphics", current_room_id_);
763 ImGui::Text("Blockset: %02X", room.blockset);
764 ImGui::Separator();
765
766 // Create a canvas for displaying room graphics (16 blocks, 2 columns, 8 rows)
767 // Each block is 128x32, so 2 cols = 256 wide, 8 rows = 256 tall
768 static gui::Canvas room_gfx_canvas("##RoomGfxCanvas", ImVec2(256 + 1, 256 + 1));
769
770 room_gfx_canvas.DrawBackground();
771 room_gfx_canvas.DrawContextMenu();
772 room_gfx_canvas.DrawTileSelector(32);
773
774 auto blocks = room.blocks();
775
776 // Load graphics for this room if not already loaded
777 if (blocks.empty()) {
778 room.LoadRoomGraphics(room.blockset);
779 blocks = room.blocks();
780 }
781
782 // Only render room graphics if ROM is properly loaded
783 if (room.rom() && room.rom()->is_loaded()) {
784 room.RenderRoomGraphics();
785 }
786
787 int current_block = 0;
788 constexpr int max_blocks_per_row = 2;
789 constexpr int block_width = 128;
790 constexpr int block_height = 32;
791
792 for (int block : blocks) {
793 if (current_block >= 16) break; // Show first 16 blocks
794
795 // Ensure the graphics sheet is loaded
796 if (block < static_cast<int>(gfx::Arena::Get().gfx_sheets().size())) {
797 auto& gfx_sheet = gfx::Arena::Get().gfx_sheets()[block];
798
799 // Create texture if it doesn't exist
800 if (!gfx_sheet.texture() && gfx_sheet.is_active() && gfx_sheet.width() > 0) {
804 }
805
806 // Calculate grid position
807 int row = current_block / max_blocks_per_row;
808 int col = current_block % max_blocks_per_row;
809
810 int x = room_gfx_canvas.zero_point().x + 2 + (col * block_width);
811 int y = room_gfx_canvas.zero_point().y + 2 + (row * block_height);
812
813 // Draw if texture is valid
814 if (gfx_sheet.texture() != 0) {
815 room_gfx_canvas.draw_list()->AddImage(
816 (ImTextureID)(intptr_t)gfx_sheet.texture(),
817 ImVec2(x, y),
818 ImVec2(x + block_width, y + block_height));
819 } else {
820 // Draw placeholder for missing graphics
821 room_gfx_canvas.draw_list()->AddRectFilled(
822 ImVec2(x, y),
823 ImVec2(x + block_width, y + block_height),
824 IM_COL32(64, 64, 64, 255));
825 room_gfx_canvas.draw_list()->AddText(
826 ImVec2(x + 10, y + 10),
827 IM_COL32(255, 255, 255, 255),
828 "No Graphics");
829 }
830 }
831 current_block++;
832 }
833
834 room_gfx_canvas.DrawGrid(32.0f);
835 room_gfx_canvas.DrawOverlay();
836 } else {
837 ImGui::TextDisabled("No room selected");
838 }
839 }
840 graphics_card.End();
841}
842
844 gui::EditorCard debug_card(
845 MakeCardTitle("Debug Controls").c_str(),
847
848 debug_card.SetDefaultSize(350, 500);
849
850 if (debug_card.Begin()) {
851 ImGui::TextWrapped("Runtime debug controls for development");
852 ImGui::Separator();
853
854 // ===== LOGGING CONTROLS =====
855 ImGui::SeparatorText(ICON_MD_TERMINAL " Logging");
856
857 bool debug_enabled = util::LogManager::instance().IsDebugEnabled();
858 if (ImGui::Checkbox("Enable DEBUG Logs", &debug_enabled)) {
859 if (debug_enabled) {
861 LOG_INFO("DebugControls", "DEBUG logging ENABLED");
862 } else {
864 LOG_INFO("DebugControls", "DEBUG logging DISABLED");
865 }
866 }
867 if (ImGui::IsItemHovered()) {
868 ImGui::SetTooltip("Toggle LOG_DEBUG visibility\nShortcut: Ctrl+Shift+D");
869 }
870
871 // Log level selector
872 const char* log_levels[] = {"DEBUG", "INFO", "WARNING", "ERROR", "FATAL"};
873 int current_level = static_cast<int>(util::LogManager::instance().GetLogLevel());
874 if (ImGui::Combo("Log Level", &current_level, log_levels, 5)) {
875 util::LogManager::instance().SetLogLevel(static_cast<util::LogLevel>(current_level));
876 LOG_INFO("DebugControls", "Log level set to %s", log_levels[current_level]);
877 }
878
879 ImGui::Separator();
880
881 // ===== ROOM RENDERING CONTROLS =====
882 ImGui::SeparatorText(ICON_MD_IMAGE " Rendering");
883
884 if (current_room_id_ >= 0 && current_room_id_ < static_cast<int>(rooms_.size())) {
885 auto& room = rooms_[current_room_id_];
886
887 ImGui::Text("Current Room: %03X", current_room_id_);
888 ImGui::Text("Objects: %zu", room.GetTileObjects().size());
889 ImGui::Text("Sprites: %zu", room.GetSprites().size());
890
891 if (ImGui::Button(ICON_MD_REFRESH " Force Re-render", ImVec2(-FLT_MIN, 0))) {
892 room.LoadRoomGraphics(room.blockset);
893 room.LoadObjects();
894 room.RenderRoomGraphics();
895 LOG_INFO("DebugControls", "Forced re-render of room %03X", current_room_id_);
896 }
897
898 if (ImGui::Button(ICON_MD_CLEANING_SERVICES " Clear Room Buffers", ImVec2(-FLT_MIN, 0))) {
899 room.ClearTileObjects();
900 LOG_INFO("DebugControls", "Cleared room %03X buffers", current_room_id_);
901 }
902
903 ImGui::Separator();
904
905 // Floor graphics override
906 ImGui::Text("Floor Graphics Override:");
907 uint8_t floor1 = room.floor1();
908 uint8_t floor2 = room.floor2();
909 static uint8_t floor_min = 0;
910 static uint8_t floor_max = 15;
911 if (ImGui::SliderScalar("Floor1", ImGuiDataType_U8, &floor1, &floor_min, &floor_max)) {
912 room.set_floor1(floor1);
913 if (room.rom() && room.rom()->is_loaded()) {
914 room.RenderRoomGraphics();
915 }
916 }
917 if (ImGui::SliderScalar("Floor2", ImGuiDataType_U8, &floor2, &floor_min, &floor_max)) {
918 room.set_floor2(floor2);
919 if (room.rom() && room.rom()->is_loaded()) {
920 room.RenderRoomGraphics();
921 }
922 }
923 } else {
924 ImGui::TextDisabled("No room selected");
925 }
926
927 ImGui::Separator();
928
929 // ===== TEXTURE CONTROLS =====
930 ImGui::SeparatorText(ICON_MD_TEXTURE " Textures");
931
932 if (ImGui::Button(ICON_MD_DELETE_SWEEP " Process Texture Queue", ImVec2(-FLT_MIN, 0))) {
934 LOG_INFO("DebugControls", "Manually processed texture queue");
935 }
936
937 // Texture stats
938 ImGui::Text("Arena Graphics Sheets: %zu", gfx::Arena::Get().gfx_sheets().size());
939
940 ImGui::Separator();
941
942 // ===== MEMORY CONTROLS =====
943 ImGui::SeparatorText(ICON_MD_MEMORY " Memory");
944
945 size_t active_rooms_count = active_rooms_.Size;
946 ImGui::Text("Active Rooms: %zu", active_rooms_count);
947 ImGui::Text("Estimated Memory: ~%zu MB", active_rooms_count * 2); // 2MB per room
948
949 if (ImGui::Button(ICON_MD_CLOSE " Close All Rooms", ImVec2(-FLT_MIN, 0))) {
950 active_rooms_.clear();
951 room_cards_.clear();
952 LOG_INFO("DebugControls", "Closed all room cards");
953 }
954
955 ImGui::Separator();
956
957 // ===== QUICK ACTIONS =====
958 ImGui::SeparatorText(ICON_MD_FLASH_ON " Quick Actions");
959
960 if (ImGui::Button(ICON_MD_SAVE " Save All Rooms", ImVec2(-FLT_MIN, 0))) {
961 auto status = Save();
962 if (status.ok()) {
963 LOG_INFO("DebugControls", "Saved all rooms");
964 } else {
965 LOG_ERROR("DebugControls", "Save failed: %s", status.message().data());
966 }
967 }
968
969 if (ImGui::Button(ICON_MD_REPLAY " Reload Current Room", ImVec2(-FLT_MIN, 0))) {
970 if (current_room_id_ >= 0 && current_room_id_ < static_cast<int>(rooms_.size())) {
972 if (status.ok()) {
973 LOG_INFO("DebugControls", "Reloaded room %03X", current_room_id_);
974 }
975 }
976 }
977 }
978 debug_card.End();
979}
980
982 // Process queued texture commands via Arena's deferred system
983 // This is critical for ensuring textures are actually created and updated
985}
986
987} // namespace yaze::editor
988
The Rom class is used to load, save, and modify Rom data.
Definition rom.h:74
auto palette_group() const
Definition rom.h:216
bool is_loaded() const
Definition rom.h:200
void SetCurrentPaletteGroup(const gfx::PaletteGroup &group)
void SetRooms(std::array< zelda3::Room, 0x128 > *rooms)
std::array< zelda3::Room, 0x128 > rooms_
std::unique_ptr< ObjectEditorCard > object_editor_card_
std::array< zelda3::RoomEntrance, 0x8C > entrances_
gfx::PaletteGroup current_palette_group_
void OnEntranceSelected(int entrance_id)
gui::DungeonObjectEmulatorPreview object_emulator_preview_
gui::PaletteEditorWidget palette_editor_
absl::Status Update() override
DungeonObjectSelector object_selector_
std::unordered_map< int, std::shared_ptr< gui::EditorCard > > room_cards_
void set_rooms(std::array< zelda3::Room, 0x128 > *rooms)
void SetCurrentPaletteGroup(const gfx::PaletteGroup &palette_group)
void SetCurrentPaletteId(uint64_t palette_id)
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 set_room_selected_callback(std::function< void(int)> callback)
void set_active_rooms(const ImVector< int > &rooms)
void set_rooms(std::array< zelda3::Room, 0x128 > *rooms)
void RegisterCard(size_t session_id, const CardInfo &base_info)
Register a card for a specific session.
EditorDependencies dependencies_
Definition editor.h:165
std::string MakeCardTitle(const std::string &base_title) const
Definition editor.h:168
std::string MakeCardId(const std::string &base_id) const
Definition editor.h:176
void QueueTextureCommand(TextureCommandType type, Bitmap *bitmap)
Definition arena.cc:32
void ProcessTextureQueue(IRenderer *renderer)
Definition arena.cc:36
std::array< gfx::Bitmap, 223 > & gfx_sheets()
Get reference to all graphics sheets.
Definition arena.h:78
static Arena & Get()
Definition arena.cc:15
Defines an abstract interface for all rendering operations.
Definition irenderer.h:35
void Initialize(Rom *rom)
Initialize the palette manager with ROM data.
static PaletteManager & Get()
Get the singleton instance.
absl::Status SaveAllToRom()
Save ALL modified palettes to ROM.
Modern, robust canvas for drawing and manipulating graphics.
Definition canvas.h:59
void DrawContextMenu()
Definition canvas.cc:428
auto draw_list() const
Definition canvas.h:293
bool DrawTileSelector(int size, int size_y=0)
Definition canvas.cc:862
auto zero_point() const
Definition canvas.h:294
void DrawBackground(ImVec2 canvas_size=ImVec2(0, 0))
Definition canvas.cc:372
void DrawGrid(float grid_step=64.0f, int tile_id_offset=8)
Definition canvas.cc:1304
void Initialize(gfx::IRenderer *renderer, Rom *rom)
Draggable, dockable card for editor sub-windows.
bool Begin(bool *p_open=nullptr)
void SetDefaultSize(float width, float height)
void SetPosition(Position pos)
void SetOnPaletteChanged(std::function< void(int palette_id)> callback)
static LogManager & instance()
Definition log.cc:29
void DisableDebugLogging()
Definition log.h:75
void EnableDebugLogging()
Toggle debug logging on/off at runtime.
Definition log.h:74
LogLevel GetLogLevel() const
Definition log.h:69
void SetLogLevel(LogLevel level)
Runtime log level control (for debug card)
Definition log.h:68
bool IsDebugEnabled() const
Definition log.h:76
#define ICON_MD_GRID_VIEW
Definition icons.h:895
#define ICON_MD_TEXTURE
Definition icons.h:1963
#define ICON_MD_MEMORY
Definition icons.h:1193
#define ICON_MD_SEARCH
Definition icons.h:1671
#define ICON_MD_CHECK
Definition icons.h:395
#define ICON_MD_CONSTRUCTION
Definition icons.h:456
#define ICON_MD_REFRESH
Definition icons.h:1570
#define ICON_MD_BUG_REPORT
Definition icons.h:325
#define ICON_MD_CASTLE
Definition icons.h:378
#define ICON_MD_REPLAY
Definition icons.h:1586
#define ICON_MD_GRID_ON
Definition icons.h:894
#define ICON_MD_LIST
Definition icons.h:1092
#define ICON_MD_FLASH_ON
Definition icons.h:788
#define ICON_MD_CLEANING_SERVICES
Definition icons.h:413
#define ICON_MD_PENDING
Definition icons.h:1396
#define ICON_MD_DOOR_FRONT
Definition icons.h:611
#define ICON_MD_IMAGE
Definition icons.h:980
#define ICON_MD_TERMINAL
Definition icons.h:1949
#define ICON_MD_SAVE
Definition icons.h:1642
#define ICON_MD_PALETTE
Definition icons.h:1368
#define ICON_MD_CLOSE
Definition icons.h:416
#define ICON_MD_DELETE_SWEEP
Definition icons.h:531
#define LOG_DEBUG(category, format,...)
Definition log.h:104
#define LOG_ERROR(category, format,...)
Definition log.h:110
#define LOG_INFO(category, format,...)
Definition log.h:106
#define RETURN_IF_ERROR(expression)
Definition macro.h:53
#define ASSIGN_OR_RETURN(type_variable_name, expression)
Definition macro.h:61
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.
bool InputHexWord(const char *label, uint16_t *data, float input_width, bool no_step)
Definition input.cc:175
bool InputHexByte(const char *label, uint8_t *data, float input_width, bool no_step)
Definition input.cc:189
LogLevel
Defines the severity levels for log messages. This allows for filtering messages based on their impor...
Definition log.h:27
constexpr int NumberOfRooms
Definition room.h:60
constexpr std::string_view kRoomNames[]
Definition room.h:484
constexpr const char * kEntranceNames[]
Definition common.h:52
EditorCardRegistry * card_registry
Definition editor.h:80