516 static const ObjectRange ranges[] = {
517 {0x00, 0xFF,
"Type 1", IM_COL32(80, 120, 180, 255)},
518 {0x100, 0x141,
"Type 2", IM_COL32(120, 80, 180, 255)},
519 {0xF80, 0xFFF,
"Type 3", IM_COL32(180, 120, 80, 255)},
524 (0xFF - 0x00 + 1) + (0x141 - 0x100 + 1) + (0xFFF - 0xF80 + 1);
528 if (ImGui::IsItemHovered()) {
530 "Enable to show actual object graphics.\n"
531 "Requires a room to be loaded.\n"
532 "May impact performance.");
535 ImGui::TextDisabled(
"(%d objects)", total_objects);
538 ImGui::SetNextItemWidth(-1.0f);
539 ImGui::InputTextWithHint(
542 static const char* kFilterLabels[] = {
"All",
"Walls",
"Floors",
"Chests",
543 "Doors",
"Decor",
"Stairs"};
544 ImGui::SetNextItemWidth(160.0f);
546 IM_ARRAYSIZE(kFilterLabels));
552 if (ImGui::IsItemHovered()) {
557 const float item_size = 72.0f;
558 const float item_spacing = 6.0f;
559 const int columns = std::max(
560 1,
static_cast<int>((ImGui::GetContentRegionAvail().x - item_spacing) /
561 (item_size + item_spacing)));
564 float child_height = ImGui::GetContentRegionAvail().y;
565 if (ImGui::BeginChild(
"##ObjectGrid", ImVec2(0, child_height),
false,
566 ImGuiWindowFlags_AlwaysVerticalScrollbar)) {
569 for (
const auto& range : ranges) {
573 ImGui::ColorConvertU32ToFloat4(range.header_color)},
574 {ImGuiCol_HeaderHovered,
575 ImGui::ColorConvertU32ToFloat4(
576 IM_COL32((range.header_color & 0xFF) + 30,
577 ((range.header_color >> 8) & 0xFF) + 30,
578 ((range.header_color >> 16) & 0xFF) + 30, 255))}});
579 bool section_open = ImGui::CollapsingHeader(
580 absl::StrFormat(
"%s (0x%03X-0x%03X)", range.label, range.start,
583 ImGuiTreeNodeFlags_DefaultOpen);
588 int current_column = 0;
590 for (
int obj_id = range.start; obj_id <= range.end; ++obj_id) {
600 if (current_column > 0) {
604 ImGui::PushID(obj_id);
608 ImVec2 button_size(item_size, item_size);
610 if (ImGui::Selectable(
"", is_selected,
611 ImGuiSelectableFlags_AllowDoubleClick,
633 if (ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left)) {
641 ImVec2 button_pos = ImGui::GetItemRectMin();
642 ImDrawList* draw_list = ImGui::GetWindowDrawList();
645 bool rendered =
false;
654 ImU32 darker_color = IM_COL32((obj_color & 0xFF) * 0.6f,
655 ((obj_color >> 8) & 0xFF) * 0.6f,
656 ((obj_color >> 16) & 0xFF) * 0.6f, 255);
659 draw_list->AddRectFilledMultiColor(
661 ImVec2(button_pos.x + item_size, button_pos.y + item_size),
662 darker_color, darker_color, obj_color, obj_color);
666 ImVec2 symbol_size = ImGui::CalcTextSize(symbol.c_str());
668 button_pos.x + (item_size - symbol_size.x) / 2,
669 button_pos.y + (item_size - symbol_size.y) / 2 - 10);
670 draw_list->AddText(symbol_pos, IM_COL32(255, 255, 255, 180),
677 float border_thickness;
679 if (is_static_editor_obj) {
680 border_color = IM_COL32(0, 200, 255, 255);
681 border_thickness = 3.0f;
682 }
else if (is_selected) {
683 border_color = ImGui::GetColorU32(theme.dungeon_selection_primary);
684 border_thickness = 3.0f;
686 border_color = ImGui::GetColorU32(theme.panel_bg_darker);
687 border_thickness = 1.0f;
692 ImVec2(button_pos.x + item_size, button_pos.y + item_size),
693 border_color, 0.0f, 0, border_thickness);
696 if (is_static_editor_obj) {
697 ImVec2 icon_pos(button_pos.x + item_size - 14, button_pos.y + 2);
698 draw_list->AddCircleFilled(ImVec2(icon_pos.x + 6, icon_pos.y + 6), 6,
699 IM_COL32(0, 200, 255, 200));
700 draw_list->AddText(icon_pos, IM_COL32(255, 255, 255, 255),
"i");
705 std::string display_name = full_name;
706 const size_t kMaxDisplayChars = 12;
707 if (display_name.length() > kMaxDisplayChars) {
708 display_name = display_name.substr(0, kMaxDisplayChars - 2) +
"..";
712 ImVec2 name_size = ImGui::CalcTextSize(display_name.c_str());
713 ImVec2 name_pos = ImVec2(button_pos.x + (item_size - name_size.x) / 2,
714 button_pos.y + item_size - 26);
715 draw_list->AddText(name_pos,
716 ImGui::GetColorU32(theme.text_secondary_gray),
717 display_name.c_str());
720 std::string id_text = absl::StrFormat(
"%03X", obj_id);
721 ImVec2 id_size = ImGui::CalcTextSize(id_text.c_str());
722 ImVec2 id_pos = ImVec2(button_pos.x + (item_size - id_size.x) / 2,
723 button_pos.y + item_size - id_size.y - 2);
724 draw_list->AddText(id_pos, ImGui::GetColorU32(theme.text_primary),
728 if (ImGui::IsItemHovered()) {
730 {{ImGuiCol_PopupBg, theme.panel_bg_color},
731 {ImGuiCol_Border, theme.panel_border_color}});
733 if (ImGui::BeginTooltip()) {
734 ImGui::TextColored(theme.selection_primary,
"Object 0x%03X",
736 ImGui::Text(
"%s", full_name.c_str());
738 ImGui::TextColored(theme.text_secondary_gray,
"Subtype %d",
742 uint32_t layout_key = (
static_cast<uint32_t
>(obj_id) << 16) |
743 static_cast<uint32_t
>(subtype);
744 const bool can_capture_layout =
747 if (can_capture_layout &&
753 if (layout_or.ok()) {
760 ImGui::TextColored(theme.status_success,
"Tiles: %zu",
761 layout.cells.size());
763 if (can_capture_layout) {
766 room_ref.get_gfx_buffer().
data());
768 ImGui::TextColored(theme.status_active,
"Draw Routine: %d",
772 ImGui::Text(
"Layout:");
773 ImDrawList* tooltip_draw_list = ImGui::GetWindowDrawList();
774 ImVec2 grid_start = ImGui::GetCursorScreenPos();
775 float cell_size = 4.0f;
776 for (
const auto& cell : layout.cells) {
777 ImVec2 p1(grid_start.x + cell.rel_x * cell_size,
778 grid_start.y + cell.rel_y * cell_size);
779 ImVec2 p2(p1.x + cell_size, p1.y + cell_size);
780 tooltip_draw_list->AddRectFilled(p1, p2,
781 IM_COL32(200, 200, 200, 255));
782 tooltip_draw_list->AddRect(p1, p2, IM_COL32(50, 50, 50, 255));
784 ImGui::Dummy(ImVec2(layout.bounds_width * cell_size,
785 layout.bounds_height * cell_size));
789 ImGui::TextColored(ImVec4(0.7f, 0.7f, 0.7f, 1.0f),
790 "Click to select for placement");
791 ImGui::TextColored(ImVec4(0.5f, 0.8f, 1.0f, 1.0f),
792 "Double-click to view details");
799 current_column = (current_column + 1) % columns;
808 ImGui::ColorConvertU32ToFloat4(IM_COL32(100, 180, 120, 255))},
809 {ImGuiCol_HeaderHovered,
810 ImGui::ColorConvertU32ToFloat4(IM_COL32(130, 210, 150, 255))}});
811 bool custom_open = ImGui::CollapsingHeader(
"Custom Objects",
812 ImGuiTreeNodeFlags_DefaultOpen);
817 if (ImGui::SmallButton(
ICON_MD_ADD " New Custom Object")) {
825 if (ImGui::IsItemHovered()) {
826 ImGui::SetTooltip(
"Create a new custom object from scratch");
831 const std::string custom_base_path = obj_manager.GetBasePath();
832 if (custom_base_path.empty()) {
833 ImGui::TextColored(theme.text_secondary_gray,
834 "Custom objects folder: not configured");
836 ImGui::Text(
"Custom objects folder: %s", custom_base_path.c_str());
837 if (ImGui::IsItemHovered()) {
838 ImGui::SetTooltip(
"%s", custom_base_path.c_str());
842 obj_manager.ReloadAll();
845 if (ImGui::IsItemHovered()) {
847 "Reload custom object binaries and refresh previews");
850 ImGui::TextColored(theme.text_secondary_gray,
851 "Corner overrides: 0x100/0x101/0x102/0x103 use 0x31 "
852 "subtypes 02/04/03/05");
864 for (
int obj_id : {0x31, 0x32}) {
868 int subtype_count = obj_manager.GetSubtypeCount(obj_id);
869 for (
int subtype = 0; subtype < subtype_count; ++subtype) {
871 std::string subtype_name =
872 absl::StrFormat(
"%s %02X", base_name.c_str(), subtype);
880 ImGui::PushID(obj_id * 1000 + subtype);
884 ImVec2 button_size(item_size, item_size);
886 if (ImGui::Selectable(
"", is_selected,
887 ImGuiSelectableFlags_AllowDoubleClick,
891 if (ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left)) {
898 ImVec2 button_pos = ImGui::GetItemRectMin();
899 ImDrawList* draw_list = ImGui::GetWindowDrawList();
901 bool rendered =
false;
906 temp_obj.size_ = subtype;
912 ImU32 obj_color = IM_COL32(100, 180, 120, 255);
913 ImU32 darker_color = IM_COL32(60, 100, 70, 255);
915 draw_list->AddRectFilledMultiColor(
917 ImVec2(button_pos.x + item_size, button_pos.y + item_size),
918 darker_color, darker_color, obj_color, obj_color);
920 std::string symbol = (obj_id == 0x31) ?
"Trk" :
"Cus";
922 std::string sub_text = absl::StrFormat(
"%02X", subtype);
923 ImVec2 sub_size = ImGui::CalcTextSize(sub_text.c_str());
924 ImVec2 sub_pos(button_pos.x + (item_size - sub_size.x) / 2,
925 button_pos.y + (item_size - sub_size.y) / 2);
926 draw_list->AddText(sub_pos, IM_COL32(255, 255, 255, 220),
937 is_selected ? ImGui::GetColorU32(theme.dungeon_selection_primary)
938 : ImGui::GetColorU32(theme.panel_bg_darker);
939 float border_thickness = is_selected ? 3.0f : 1.0f;
942 ImVec2(button_pos.x + item_size, button_pos.y + item_size),
943 border_color, 0.0f, 0, border_thickness);
946 std::string id_text = absl::StrFormat(
"%02X:%02X", obj_id, subtype);
947 ImVec2 id_size = ImGui::CalcTextSize(id_text.c_str());
948 ImVec2 id_pos = ImVec2(button_pos.x + (item_size - id_size.x) / 2,
949 button_pos.y + item_size - id_size.y - 2);
950 draw_list->AddText(id_pos, ImGui::GetColorU32(theme.text_primary),
953 if (ImGui::IsItemHovered()) {
955 {{ImGuiCol_PopupBg, theme.panel_bg_color},
956 {ImGuiCol_Border, theme.panel_border_color}});
957 if (ImGui::BeginTooltip()) {
958 const std::string filename =
959 obj_manager.ResolveFilename(obj_id, subtype);
960 const bool has_base = !custom_base_path.empty();
961 std::filesystem::path full_path =
963 ? (std::filesystem::path(custom_base_path) / filename)
964 : std::filesystem::path();
965 const bool file_exists = has_base && !filename.empty() &&
966 std::filesystem::exists(full_path);
968 ImGui::TextColored(theme.selection_primary,
"Custom 0x%02X:%02X",
970 ImGui::Text(
"%s", subtype_name.c_str());
972 ImGui::Text(
"File: %s",
973 filename.empty() ?
"(unmapped)" : filename.c_str());
975 ImGui::TextColored(theme.text_warning_yellow,
976 "Folder not configured in project");
977 }
else if (file_exists) {
978 ImGui::TextColored(theme.status_success,
"File found");
980 ImGui::TextColored(theme.status_error,
"File missing: %s",
981 full_path.string().c_str());
984 if (obj_id == 0x31 && subtype >= 2 && subtype <= 5) {
985 const char* corner_id =
"";
987 corner_id =
"0x100 (TL)";
988 }
else if (subtype == 3) {
989 corner_id =
"0x102 (TR)";
990 }
else if (subtype == 4) {
991 corner_id =
"0x101 (BL)";
993 corner_id =
"0x103 (BR)";
996 ImGui::TextColored(theme.status_active,
997 "Also used by corner override %s",
1000 ImGui::EndTooltip();
1005 custom_col = (custom_col + 1) % columns;