7#include "absl/strings/str_format.h"
14#include "imgui/imgui.h"
36 .card_id =
MakeCardId(
"dungeon.control_panel"),
37 .display_name =
"Dungeon Controls",
39 .category =
"Dungeon",
40 .shortcut_hint =
"Ctrl+Shift+D",
45 card_registry->RegisterCard({
46 .card_id =
MakeCardId(
"dungeon.room_selector"),
47 .display_name =
"Room Selector",
49 .category =
"Dungeon",
50 .shortcut_hint =
"Ctrl+Shift+R",
55 card_registry->RegisterCard({
57 .display_name =
"Room Matrix",
59 .category =
"Dungeon",
60 .shortcut_hint =
"Ctrl+Shift+M",
65 card_registry->RegisterCard({
67 .display_name =
"Entrances",
69 .category =
"Dungeon",
70 .shortcut_hint =
"Ctrl+Shift+E",
75 card_registry->RegisterCard({
76 .card_id =
MakeCardId(
"dungeon.room_graphics"),
77 .display_name =
"Room Graphics",
79 .category =
"Dungeon",
80 .shortcut_hint =
"Ctrl+Shift+G",
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",
95 card_registry->RegisterCard({
96 .card_id =
MakeCardId(
"dungeon.palette_editor"),
97 .display_name =
"Palette Editor",
99 .category =
"Dungeon",
100 .shortcut_hint =
"Ctrl+Shift+P",
105 card_registry->RegisterCard({
106 .card_id =
MakeCardId(
"dungeon.debug_controls"),
107 .display_name =
"Debug Controls",
109 .category =
"Dungeon",
110 .shortcut_hint =
"Ctrl+Shift+B",
124 return absl::FailedPreconditionError(
"ROM not loaded");
171 if (room_id >= 0 && room_id < (
int)
rooms_.size()) {
172 rooms_[room_id].RenderRoomGraphics();
178 return absl::OkStatus();
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.");
196 return absl::OkStatus();
204 return absl::OkStatus();
209 return absl::FailedPreconditionError(
"ROM not loaded");
216 LOG_ERROR(
"DungeonEditorV2",
"Failed to save palette changes: %s",
217 status.message().data());
220 LOG_INFO(
"DungeonEditorV2",
"Saved %zu modified colors to ROM",
225 for (
auto& room :
rooms_) {
226 auto status = room.SaveObjects();
229 LOG_ERROR(
"DungeonEditorV2",
"Failed to save room: %s",
230 status.message().data());
234 return absl::OkStatus();
276 if (palette_card.
Begin()) {
294 std::string base_name;
296 base_name = absl::StrFormat(
"[%03X] %s", room_id,
zelda3::kRoomNames[room_id].data());
298 base_name = absl::StrFormat(
"Room %03X", room_id);
301 std::string card_name_str = absl::StrFormat(
"%s###RoomCard%d",
306 room_cards_[room_id] = std::make_shared<gui::EditorCard>(
323 if (room_card->Begin(&open)) {
337 if (room_id < 0 || room_id >= 0x128) {
338 ImGui::Text(
"Invalid room ID: %d", room_id);
342 auto& room =
rooms_[room_id];
345 if (!room.IsLoaded()) {
348 ImGui::TextColored(ImVec4(1, 0, 0, 1),
"Failed to load room: %s",
349 status.message().data());
356 if (room.IsLoaded()) {
357 bool needs_render =
false;
360 if (room.blocks().empty()) {
361 room.LoadRoomGraphics(room.blockset);
363 LOG_DEBUG(
"[DungeonEditorV2]",
"Loaded room %d graphics from ROM", room_id);
367 if (room.GetTileObjects().empty()) {
370 LOG_DEBUG(
"[DungeonEditorV2]",
"Loaded room %d objects from ROM", room_id);
374 auto& bg1_bitmap = room.bg1_buffer().bitmap();
375 if (needs_render || !bg1_bitmap.is_active() || bg1_bitmap.width() == 0) {
376 room.RenderRoomGraphics();
377 LOG_DEBUG(
"[DungeonEditorV2]",
"Rendered room %d to bitmaps", room_id);
382 if (room.IsLoaded()) {
383 ImGui::TextColored(ImVec4(0.4f, 0.8f, 0.4f, 1.0f),
ICON_MD_CHECK " Loaded");
385 ImGui::TextColored(ImVec4(0.8f, 0.4f, 0.4f, 1.0f),
ICON_MD_PENDING " Not Loaded");
388 ImGui::TextDisabled(
"Objects: %zu", room.GetTileObjects().size());
415 if (entrance_id < 0 || entrance_id >=
static_cast<int>(
entrances_.size())) {
445 if (selector_card.
Begin()) {
447 ImGui::Text(
"ROM not loaded");
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))) {
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);
467 std::string room_name;
471 room_name = absl::StrFormat(
"Room %03X", i);
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) {
485 std::string label = absl::StrFormat(
"[%03X] %s", i, room_name.c_str());
488 if (ImGui::Selectable(label.c_str(), is_selected)) {
492 if (ImGui::IsItemHovered() && ImGui::IsMouseDoubleClicked(0)) {
510 if (entrances_card.
Begin()) {
512 ImGui::Text(
"ROM not loaded");
518 gui::InputHexWord(
"Room ID",
reinterpret_cast<uint16_t*
>(¤t_entrance.room_));
542 gui::InputHexWord(
"Exit",
reinterpret_cast<uint16_t*
>(¤t_entrance.exit_), 50.f,
true);
545 ImGui::Text(
"Camera Boundaries");
547 ImGui::Text(
"\t\t\t\t\tNorth East South West");
549 gui::InputHexByte(
"Quadrant", ¤t_entrance.camera_boundary_qn_, 50.f,
true);
557 gui::InputHexByte(
"Full room", ¤t_entrance.camera_boundary_fn_, 50.f,
true);
568 if (ImGui::BeginChild(
"##EntrancesList", ImVec2(0, 0),
true,
569 ImGuiWindowFlags_AlwaysVerticalScrollbar)) {
570 for (
int i = 0; i < 0x8C; i++) {
572 std::string entrance_name;
576 entrance_name = absl::StrFormat(
"Spawn Point %d", i - 0x85);
581 std::string room_name =
"Unknown";
586 std::string label = absl::StrFormat(
"[%02X] %s -> %s",
587 i, entrance_name.c_str(), room_name.c_str());
590 if (ImGui::Selectable(label.c_str(), is_selected)) {
599 entrances_card.
End();
609 if (matrix_card.
Begin()) {
611 constexpr int kRoomsPerRow = 16;
612 constexpr int kRoomsPerCol = 19;
613 constexpr int kTotalRooms = 0x128;
614 constexpr float kRoomCellSize = 24.0f;
615 constexpr float kCellSpacing = 1.0f;
617 ImDrawList* draw_list = ImGui::GetWindowDrawList();
618 ImVec2 canvas_pos = ImGui::GetCursorScreenPos();
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);
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);
638 int hue = (room_id * 37) % 360;
639 int saturation = 40 + (room_id % 3) * 15;
640 int brightness = 50 + (room_id % 5) * 10;
643 float h = hue / 60.0f;
644 float s = saturation / 100.0f;
645 float v = brightness / 100.0f;
647 int i =
static_cast<int>(h);
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);
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;
668 bool is_open =
false;
677 draw_list->AddRectFilled(cell_min, cell_max, bg_color);
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) {
686 draw_list->AddRect(cell_min, cell_max,
687 IM_COL32(0, 200, 0, 255), 0.0f, 0, 2.0f);
690 draw_list->AddRect(cell_min, cell_max,
691 IM_COL32(80, 80, 80, 200), 0.0f, 0, 1.0f);
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);
702 draw_list->AddText(text_pos, IM_COL32(220, 220, 220, 255),
706 ImGui::SetCursorScreenPos(cell_min);
707 ImGui::InvisibleButton(
708 absl::StrFormat(
"##room%d", room_id).c_str(),
709 ImVec2(kRoomCellSize, kRoomCellSize));
711 if (ImGui::IsItemClicked()) {
716 if (ImGui::IsItemHovered()) {
717 ImGui::BeginTooltip();
721 ImGui::Text(
"Room %03X", room_id);
723 ImGui::Text(
"Status: %s", is_open ?
"Open" : is_current ?
"Current" :
"Closed");
724 ImGui::Text(
"Click to %s", is_open ?
"focus" :
"open");
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));
741 kRoomsPerRow * (kRoomCellSize + kCellSpacing),
742 kRoomsPerCol * (kRoomCellSize + kCellSpacing)));
755 if (graphics_card.
Begin()) {
757 ImGui::Text(
"ROM not loaded");
763 ImGui::Text(
"Blockset: %02X", room.blockset);
768 static gui::Canvas room_gfx_canvas(
"##RoomGfxCanvas", ImVec2(256 + 1, 256 + 1));
774 auto blocks = room.blocks();
777 if (blocks.empty()) {
778 room.LoadRoomGraphics(room.blockset);
779 blocks = room.blocks();
783 if (room.rom() && room.rom()->is_loaded()) {
784 room.RenderRoomGraphics();
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;
792 for (
int block : blocks) {
793 if (current_block >= 16)
break;
800 if (!gfx_sheet.texture() && gfx_sheet.is_active() && gfx_sheet.width() > 0) {
807 int row = current_block / max_blocks_per_row;
808 int col = current_block % max_blocks_per_row;
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);
814 if (gfx_sheet.texture() != 0) {
816 (ImTextureID)(intptr_t)gfx_sheet.texture(),
818 ImVec2(x + block_width, y + block_height));
821 room_gfx_canvas.
draw_list()->AddRectFilled(
823 ImVec2(x + block_width, y + block_height),
824 IM_COL32(64, 64, 64, 255));
826 ImVec2(x + 10, y + 10),
827 IM_COL32(255, 255, 255, 255),
837 ImGui::TextDisabled(
"No room selected");
850 if (debug_card.
Begin()) {
851 ImGui::TextWrapped(
"Runtime debug controls for development");
858 if (ImGui::Checkbox(
"Enable DEBUG Logs", &debug_enabled)) {
861 LOG_INFO(
"DebugControls",
"DEBUG logging ENABLED");
864 LOG_INFO(
"DebugControls",
"DEBUG logging DISABLED");
867 if (ImGui::IsItemHovered()) {
868 ImGui::SetTooltip(
"Toggle LOG_DEBUG visibility\nShortcut: Ctrl+Shift+D");
872 const char* log_levels[] = {
"DEBUG",
"INFO",
"WARNING",
"ERROR",
"FATAL"};
874 if (ImGui::Combo(
"Log Level", ¤t_level, log_levels, 5)) {
876 LOG_INFO(
"DebugControls",
"Log level set to %s", log_levels[current_level]);
888 ImGui::Text(
"Objects: %zu", room.GetTileObjects().size());
889 ImGui::Text(
"Sprites: %zu", room.GetSprites().size());
891 if (ImGui::Button(
ICON_MD_REFRESH " Force Re-render", ImVec2(-FLT_MIN, 0))) {
892 room.LoadRoomGraphics(room.blockset);
894 room.RenderRoomGraphics();
899 room.ClearTileObjects();
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();
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();
924 ImGui::TextDisabled(
"No room selected");
934 LOG_INFO(
"DebugControls",
"Manually processed texture queue");
938 ImGui::Text(
"Arena Graphics Sheets: %zu",
gfx::Arena::Get().gfx_sheets().size());
946 ImGui::Text(
"Active Rooms: %zu", active_rooms_count);
947 ImGui::Text(
"Estimated Memory: ~%zu MB", active_rooms_count * 2);
949 if (ImGui::Button(
ICON_MD_CLOSE " Close All Rooms", ImVec2(-FLT_MIN, 0))) {
952 LOG_INFO(
"DebugControls",
"Closed all room cards");
960 if (ImGui::Button(
ICON_MD_SAVE " Save All Rooms", ImVec2(-FLT_MIN, 0))) {
961 auto status =
Save();
963 LOG_INFO(
"DebugControls",
"Saved all rooms");
965 LOG_ERROR(
"DebugControls",
"Save failed: %s", status.message().data());
969 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_
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)
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_
std::string MakeCardTitle(const std::string &base_title) const
std::string MakeCardId(const std::string &base_id) 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.
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.
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)
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)
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)
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[]
EditorCardRegistry * card_registry