6#include "absl/strings/str_format.h"
12#include "imgui/imgui.h"
32 card_manager.RegisterCard({
33 .card_id =
"dungeon.control_panel",
34 .display_name =
"Dungeon Controls",
36 .category =
"Dungeon",
37 .shortcut_hint =
"Ctrl+Shift+D",
42 card_manager.RegisterCard({
43 .card_id =
"dungeon.room_selector",
44 .display_name =
"Room Selector",
46 .category =
"Dungeon",
47 .shortcut_hint =
"Ctrl+Shift+R",
52 card_manager.RegisterCard({
53 .card_id =
"dungeon.room_matrix",
54 .display_name =
"Room Matrix",
56 .category =
"Dungeon",
57 .shortcut_hint =
"Ctrl+Shift+M",
62 card_manager.RegisterCard({
63 .card_id =
"dungeon.entrances",
64 .display_name =
"Entrances",
66 .category =
"Dungeon",
67 .shortcut_hint =
"Ctrl+Shift+E",
72 card_manager.RegisterCard({
73 .card_id =
"dungeon.room_graphics",
74 .display_name =
"Room Graphics",
76 .category =
"Dungeon",
77 .shortcut_hint =
"Ctrl+Shift+G",
82 card_manager.RegisterCard({
83 .card_id =
"dungeon.object_editor",
84 .display_name =
"Object Editor",
86 .category =
"Dungeon",
87 .shortcut_hint =
"Ctrl+Shift+O",
92 card_manager.RegisterCard({
93 .card_id =
"dungeon.palette_editor",
94 .display_name =
"Palette Editor",
96 .category =
"Dungeon",
97 .shortcut_hint =
"Ctrl+Shift+P",
102 card_manager.RegisterCard({
103 .card_id =
"dungeon.debug_controls",
104 .display_name =
"Debug Controls",
106 .category =
"Dungeon",
107 .shortcut_hint =
"Ctrl+Shift+B",
117 return absl::FailedPreconditionError(
"ROM not loaded");
159 if (room_id >= 0 && room_id < (
int)
rooms_.size()) {
160 rooms_[room_id].RenderRoomGraphics();
166 return absl::OkStatus();
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.");
184 return absl::OkStatus();
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;
203 if (ImGui::Begin(
"##DungeonControlIcon",
nullptr, icon_flags)) {
208 if (ImGui::IsItemHovered()) {
209 ImGui::SetTooltip(
"Open Dungeon Controls");
218 return absl::OkStatus();
223 return absl::FailedPreconditionError(
"ROM not loaded");
227 for (
auto& room :
rooms_) {
228 auto status = room.SaveObjects();
231 LOG_ERROR(
"DungeonEditorV2",
"Failed to save room: %s", status.message().data());
235 return absl::OkStatus();
279 ImGui::SetNextWindowSize(ImVec2(280, 280), ImGuiCond_FirstUseEver);
280 ImGui::SetNextWindowPos(ImVec2(10, 100), ImGuiCond_FirstUseEver);
282 ImGuiWindowFlags flags = ImGuiWindowFlags_None;
285 ImGui::TextWrapped(
"Welcome to Dungeon Editor V2!");
286 ImGui::TextDisabled(
"Use checkboxes below to open cards");
292 ImGui::Text(
"Quick Toggles:");
295 if (ImGui::BeginTable(
"##QuickToggles", 2, ImGuiTableFlags_SizingStretchSame)) {
296 ImGui::TableNextRow();
297 ImGui::TableNextColumn();
300 ImGui::TableNextColumn();
303 ImGui::TableNextRow();
304 ImGui::TableNextColumn();
307 ImGui::TableNextColumn();
310 ImGui::TableNextRow();
311 ImGui::TableNextColumn();
314 ImGui::TableNextColumn();
317 ImGui::TableNextRow();
318 ImGui::TableNextColumn();
331 if (ImGui::IsItemHovered()) {
332 ImGui::SetTooltip(
"Collapse to floating icon. Rooms stay open.");
377 if (palette_card.
Begin()) {
395 std::string base_name;
397 base_name = absl::StrFormat(
"[%03X] %s", room_id,
zelda3::kRoomNames[room_id].data());
399 base_name = absl::StrFormat(
"Room %03X", room_id);
402 std::string card_name_str = absl::StrFormat(
"%s###RoomCard%d",
407 room_cards_[room_id] = std::make_shared<gui::EditorCard>(
424 if (room_card->Begin(&open)) {
438 if (room_id < 0 || room_id >= 0x128) {
439 ImGui::Text(
"Invalid room ID: %d", room_id);
443 auto& room =
rooms_[room_id];
446 if (!room.IsLoaded()) {
449 ImGui::TextColored(ImVec4(1, 0, 0, 1),
"Failed to load room: %s",
450 status.message().data());
457 if (room.IsLoaded()) {
458 bool needs_render =
false;
461 if (room.blocks().empty()) {
462 room.LoadRoomGraphics(room.blockset);
464 LOG_DEBUG(
"[DungeonEditorV2]",
"Loaded room %d graphics from ROM", room_id);
468 if (room.GetTileObjects().empty()) {
471 LOG_DEBUG(
"[DungeonEditorV2]",
"Loaded room %d objects from ROM", room_id);
475 auto& bg1_bitmap = room.bg1_buffer().bitmap();
476 if (needs_render || !bg1_bitmap.is_active() || bg1_bitmap.width() == 0) {
477 room.RenderRoomGraphics();
478 LOG_DEBUG(
"[DungeonEditorV2]",
"Rendered room %d to bitmaps", room_id);
483 if (room.IsLoaded()) {
484 ImGui::TextColored(ImVec4(0.4f, 0.8f, 0.4f, 1.0f),
ICON_MD_CHECK " Loaded");
486 ImGui::TextColored(ImVec4(0.8f, 0.4f, 0.4f, 1.0f),
ICON_MD_PENDING " Not Loaded");
489 ImGui::TextDisabled(
"Objects: %zu", room.GetTileObjects().size());
516 if (entrance_id < 0 || entrance_id >=
static_cast<int>(
entrances_.size())) {
546 if (selector_card.
Begin()) {
548 ImGui::Text(
"ROM not loaded");
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))) {
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);
571 std::string room_name;
575 room_name = absl::StrFormat(
"Room %03X", i);
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) {
589 std::string label = absl::StrFormat(
"[%03X] %s", i, room_name.c_str());
592 if (ImGui::Selectable(label.c_str(), is_selected)) {
596 if (ImGui::IsItemHovered() && ImGui::IsMouseDoubleClicked(0)) {
613 if (entrances_card.
Begin()) {
615 ImGui::Text(
"ROM not loaded");
616 entrances_card.
End();
624 gui::InputHexWord(
"Room ID",
reinterpret_cast<uint16_t*
>(¤t_entrance.room_));
648 gui::InputHexWord(
"Exit",
reinterpret_cast<uint16_t*
>(¤t_entrance.exit_), 50.f,
true);
651 ImGui::Text(
"Camera Boundaries");
653 ImGui::Text(
"\t\t\t\t\tNorth East South West");
655 gui::InputHexByte(
"Quadrant", ¤t_entrance.camera_boundary_qn_, 50.f,
true);
663 gui::InputHexByte(
"Full room", ¤t_entrance.camera_boundary_fn_, 50.f,
true);
674 if (ImGui::BeginChild(
"##EntrancesList", ImVec2(0, 0),
true,
675 ImGuiWindowFlags_AlwaysVerticalScrollbar)) {
676 for (
int i = 0; i < 0x8C; i++) {
678 std::string entrance_name;
682 entrance_name = absl::StrFormat(
"Spawn Point %d", i - 0x85);
687 std::string room_name =
"Unknown";
692 std::string label = absl::StrFormat(
"[%02X] %s -> %s",
693 i, entrance_name.c_str(), room_name.c_str());
696 if (ImGui::Selectable(label.c_str(), is_selected)) {
704 entrances_card.
End();
714 if (matrix_card.
Begin()) {
716 constexpr int kRoomsPerRow = 16;
717 constexpr int kRoomsPerCol = 19;
718 constexpr int kTotalRooms = 0x128;
719 constexpr float kRoomCellSize = 24.0f;
720 constexpr float kCellSpacing = 1.0f;
722 ImDrawList* draw_list = ImGui::GetWindowDrawList();
723 ImVec2 canvas_pos = ImGui::GetCursorScreenPos();
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);
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);
743 int hue = (room_id * 37) % 360;
744 int saturation = 40 + (room_id % 3) * 15;
745 int brightness = 50 + (room_id % 5) * 10;
748 float h = hue / 60.0f;
749 float s = saturation / 100.0f;
750 float v = brightness / 100.0f;
752 int i =
static_cast<int>(h);
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);
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;
773 bool is_open =
false;
782 draw_list->AddRectFilled(cell_min, cell_max, bg_color);
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) {
791 draw_list->AddRect(cell_min, cell_max,
792 IM_COL32(0, 200, 0, 255), 0.0f, 0, 2.0f);
795 draw_list->AddRect(cell_min, cell_max,
796 IM_COL32(80, 80, 80, 200), 0.0f, 0, 1.0f);
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);
807 draw_list->AddText(text_pos, IM_COL32(220, 220, 220, 255),
811 ImGui::SetCursorScreenPos(cell_min);
812 ImGui::InvisibleButton(
813 absl::StrFormat(
"##room%d", room_id).c_str(),
814 ImVec2(kRoomCellSize, kRoomCellSize));
816 if (ImGui::IsItemClicked()) {
821 if (ImGui::IsItemHovered()) {
822 ImGui::BeginTooltip();
826 ImGui::Text(
"Room %03X", room_id);
828 ImGui::Text(
"Status: %s", is_open ?
"Open" : is_current ?
"Current" :
"Closed");
829 ImGui::Text(
"Click to %s", is_open ?
"focus" :
"open");
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));
846 kRoomsPerRow * (kRoomCellSize + kCellSpacing),
847 kRoomsPerCol * (kRoomCellSize + kCellSpacing)));
860 if (graphics_card.
Begin()) {
862 ImGui::Text(
"ROM not loaded");
872 ImGui::Text(
"Blockset: %02X", room.blockset);
877 static gui::Canvas room_gfx_canvas(
"##RoomGfxCanvas", ImVec2(256 + 1, 256 + 1));
883 auto blocks = room.blocks();
886 if (blocks.empty()) {
887 room.LoadRoomGraphics(room.blockset);
888 blocks = room.blocks();
892 if (room.rom() && room.rom()->is_loaded()) {
893 room.RenderRoomGraphics();
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;
901 for (
int block : blocks) {
902 if (current_block >= 16)
break;
909 if (!gfx_sheet.texture() && gfx_sheet.is_active() && gfx_sheet.width() > 0) {
916 int row = current_block / max_blocks_per_row;
917 int col = current_block % max_blocks_per_row;
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);
923 if (gfx_sheet.texture() != 0) {
925 (ImTextureID)(intptr_t)gfx_sheet.texture(),
927 ImVec2(x + block_width, y + block_height));
930 room_gfx_canvas.
draw_list()->AddRectFilled(
932 ImVec2(x + block_width, y + block_height),
933 IM_COL32(64, 64, 64, 255));
935 ImVec2(x + 10, y + 10),
936 IM_COL32(255, 255, 255, 255),
946 ImGui::TextDisabled(
"No room selected");
959 if (debug_card.
Begin()) {
960 ImGui::TextWrapped(
"Runtime debug controls for development");
967 if (ImGui::Checkbox(
"Enable DEBUG Logs", &debug_enabled)) {
970 LOG_INFO(
"DebugControls",
"DEBUG logging ENABLED");
973 LOG_INFO(
"DebugControls",
"DEBUG logging DISABLED");
976 if (ImGui::IsItemHovered()) {
977 ImGui::SetTooltip(
"Toggle LOG_DEBUG visibility\nShortcut: Ctrl+Shift+D");
981 const char* log_levels[] = {
"DEBUG",
"INFO",
"WARNING",
"ERROR",
"FATAL"};
983 if (ImGui::Combo(
"Log Level", ¤t_level, log_levels, 5)) {
985 LOG_INFO(
"DebugControls",
"Log level set to %s", log_levels[current_level]);
997 ImGui::Text(
"Objects: %zu", room.GetTileObjects().size());
998 ImGui::Text(
"Sprites: %zu", room.GetSprites().size());
1000 if (ImGui::Button(
ICON_MD_REFRESH " Force Re-render", ImVec2(-FLT_MIN, 0))) {
1001 room.LoadRoomGraphics(room.blockset);
1003 room.RenderRoomGraphics();
1008 room.ClearTileObjects();
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();
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();
1033 ImGui::TextDisabled(
"No room selected");
1043 LOG_INFO(
"DebugControls",
"Manually processed texture queue");
1047 ImGui::Text(
"Arena Graphics Sheets: %zu",
gfx::Arena::Get().gfx_sheets().size());
1055 ImGui::Text(
"Active Rooms: %zu", active_rooms_count);
1056 ImGui::Text(
"Estimated Memory: ~%zu MB", active_rooms_count * 2);
1058 if (ImGui::Button(
ICON_MD_CLOSE " Close All Rooms", ImVec2(-FLT_MIN, 0))) {
1061 LOG_INFO(
"DebugControls",
"Closed all room cards");
1069 if (ImGui::Button(
ICON_MD_SAVE " Save All Rooms", ImVec2(-FLT_MIN, 0))) {
1070 auto status =
Save();
1072 LOG_INFO(
"DebugControls",
"Saved all rooms");
1074 LOG_ERROR(
"DebugControls",
"Save failed: %s", status.message().data());
1078 if (ImGui::Button(
ICON_MD_REPLAY " Reload Current Room", ImVec2(-FLT_MIN, 0))) {
The Rom class is used to load, save, and modify Rom data.
auto palette_group() const
void SetCurrentPaletteId(uint64_t id)
void SetCurrentPaletteGroup(const gfx::PaletteGroup &group)
void DrawDungeonCanvas(int room_id)
void SetRooms(std::array< zelda3::Room, 0x128 > *rooms)
std::array< zelda3::Room, 0x128 > rooms_
void DrawEntrancesListCard()
uint64_t current_palette_group_id_
void DrawDebugControlsCard()
void add_room(int room_id)
std::unique_ptr< ObjectEditorCard > object_editor_card_
std::array< zelda3::RoomEntrance, 0x8C > entrances_
bool show_palette_editor_
gfx::PaletteGroup current_palette_group_
void OnEntranceSelected(int entrance_id)
absl::Status Save() override
gfx::IRenderer * renderer_
void FocusRoom(int room_id)
void Initialize() override
gui::DungeonObjectEmulatorPreview object_emulator_preview_
void DrawRoomMatrixCard()
DungeonCanvasViewer canvas_viewer_
void ProcessDeferredTextures()
gfx::SnesPalette current_palette_
ImVector< int > active_rooms_
bool show_entrances_list_
gui::PaletteEditorWidget palette_editor_
bool show_debug_controls_
void DrawRoomTab(int room_id)
absl::Status Update() override
ImGuiWindowClass room_window_class_
DungeonRoomLoader room_loader_
DungeonObjectSelector object_selector_
DungeonRoomSelector room_selector_
std::unordered_map< int, std::shared_ptr< gui::EditorCard > > room_cards_
bool control_panel_minimized_
void DrawRoomGraphicsCard()
void OnRoomSelected(int room_id)
uint64_t current_palette_id_
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)
int current_room_id() const
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
void QueueTextureCommand(TextureCommandType type, Bitmap *bitmap)
void ProcessTextureQueue(IRenderer *renderer)
std::array< gfx::Bitmap, 223 > & gfx_sheets()
Get reference to all graphics sheets.
Defines an abstract interface for all rendering operations.
Modern, robust canvas for drawing and manipulating graphics.
bool DrawTileSelector(int size, int size_y=0)
void DrawBackground(ImVec2 canvas_size=ImVec2(0, 0))
void DrawGrid(float grid_step=64.0f, int tile_id_offset=8)
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)
static LogManager & instance()
void DisableDebugLogging()
void EnableDebugLogging()
Toggle debug logging on/off at runtime.
LogLevel GetLogLevel() const
void SetLogLevel(LogLevel level)
Runtime log level control (for debug card)
bool IsDebugEnabled() const
#define ICON_MD_GRID_VIEW
#define ICON_MD_CONSTRUCTION
#define ICON_MD_BUG_REPORT
#define ICON_MD_CLEANING_SERVICES
#define ICON_MD_DOOR_FRONT
#define ICON_MD_DELETE_SWEEP
#define LOG_DEBUG(category, format,...)
#define LOG_ERROR(category, format,...)
#define LOG_INFO(category, format,...)
#define RETURN_IF_ERROR(expression)
#define ASSIGN_OR_RETURN(type_variable_name, expression)
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)
bool InputHexByte(const char *label, uint8_t *data, float input_width, bool no_step)
LogLevel
Defines the severity levels for log messages. This allows for filtering messages based on their impor...
constexpr int NumberOfRooms
constexpr std::string_view kRoomNames[]
constexpr const char * kEntranceNames[]