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