169 static const ObjectRange ranges[] = {
170 {0x00, 0xFF,
"Type 1", IM_COL32(80, 120, 180, 255)},
171 {0x100, 0x141,
"Type 2", IM_COL32(120, 80, 180, 255)},
172 {0xF80, 0xFFF,
"Type 3", IM_COL32(180, 120, 80, 255)},
177 (0xFF - 0x00 + 1) + (0x141 - 0x100 + 1) + (0xFFF - 0xF80 + 1);
179 ImGui::TextDisabled(
"%d objects", total_objects);
182 if (ImGui::IsItemHovered()) {
184 "Show rendered object thumbnails in the selector.\n"
185 "Requires a room to be loaded and may cost some performance.");
189 ImGui::TextColored(theme.text_info,
ICON_MD_LABEL " Current: 0x%03X %s",
193 ImGui::TextColored(theme.text_secondary_gray,
194 "Tip: click once to queue placement, double-click to "
195 "inspect the draw routine.");
199 ImGui::SetNextItemWidth(-1.0f);
200 ImGui::InputTextWithHint(
203 static const char* kFilterLabels[] = {
"All",
"Walls",
"Floors",
"Chests",
204 "Doors",
"Decor",
"Stairs"};
205 ImGui::SetNextItemWidth(170.0f);
207 IM_ARRAYSIZE(kFilterLabels));
213 if (ImGui::IsItemHovered()) {
218 const float item_size = 72.0f;
219 const float item_spacing = 6.0f;
220 const int columns = std::max(
221 1,
static_cast<int>((ImGui::GetContentRegionAvail().x - item_spacing) /
222 (item_size + item_spacing)));
225 float child_height = ImGui::GetContentRegionAvail().y;
226 if (ImGui::BeginChild(
"##ObjectGrid", ImVec2(0, child_height),
false,
227 ImGuiWindowFlags_AlwaysVerticalScrollbar)) {
230 for (
const auto& range : ranges) {
234 ImGui::ColorConvertU32ToFloat4(range.header_color)},
235 {ImGuiCol_HeaderHovered,
236 ImGui::ColorConvertU32ToFloat4(
237 IM_COL32((range.header_color & 0xFF) + 30,
238 ((range.header_color >> 8) & 0xFF) + 30,
239 ((range.header_color >> 16) & 0xFF) + 30, 255))}});
240 bool section_open = ImGui::CollapsingHeader(
241 absl::StrFormat(
"%s (0x%03X-0x%03X)", range.label, range.start,
244 ImGuiTreeNodeFlags_DefaultOpen);
249 int current_column = 0;
251 for (
int obj_id = range.start; obj_id <= range.end; ++obj_id) {
261 if (current_column > 0) {
265 ImGui::PushID(obj_id);
269 ImVec2 button_size(item_size, item_size);
271 if (ImGui::Selectable(
"", is_selected,
272 ImGuiSelectableFlags_AllowDoubleClick,
294 if (ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left)) {
302 ImVec2 button_pos = ImGui::GetItemRectMin();
303 ImDrawList* draw_list = ImGui::GetWindowDrawList();
306 bool rendered =
false;
315 ImU32 darker_color = IM_COL32((obj_color & 0xFF) * 0.6f,
316 ((obj_color >> 8) & 0xFF) * 0.6f,
317 ((obj_color >> 16) & 0xFF) * 0.6f, 255);
320 draw_list->AddRectFilledMultiColor(
322 ImVec2(button_pos.x + item_size, button_pos.y + item_size),
323 darker_color, darker_color, obj_color, obj_color);
327 ImVec2 symbol_size = ImGui::CalcTextSize(symbol.c_str());
329 button_pos.x + (item_size - symbol_size.x) / 2,
330 button_pos.y + (item_size - symbol_size.y) / 2 - 10);
331 draw_list->AddText(symbol_pos, IM_COL32(255, 255, 255, 180),
338 float border_thickness;
340 if (is_static_editor_obj) {
341 border_color = IM_COL32(0, 200, 255, 255);
342 border_thickness = 3.0f;
343 }
else if (is_selected) {
344 border_color = ImGui::GetColorU32(theme.dungeon_selection_primary);
345 border_thickness = 3.0f;
347 border_color = ImGui::GetColorU32(theme.panel_bg_darker);
348 border_thickness = 1.0f;
353 ImVec2(button_pos.x + item_size, button_pos.y + item_size),
354 border_color, 0.0f, 0, border_thickness);
357 if (is_static_editor_obj) {
358 ImVec2 icon_pos(button_pos.x + item_size - 14, button_pos.y + 2);
359 draw_list->AddCircleFilled(ImVec2(icon_pos.x + 6, icon_pos.y + 6), 6,
360 IM_COL32(0, 200, 255, 200));
361 draw_list->AddText(icon_pos, IM_COL32(255, 255, 255, 255),
"i");
366 std::string display_name = full_name;
367 const size_t kMaxDisplayChars = 12;
368 if (display_name.length() > kMaxDisplayChars) {
369 display_name = display_name.substr(0, kMaxDisplayChars - 2) +
"..";
373 ImVec2 name_size = ImGui::CalcTextSize(display_name.c_str());
374 ImVec2 name_pos = ImVec2(button_pos.x + (item_size - name_size.x) / 2,
375 button_pos.y + item_size - 26);
376 draw_list->AddText(name_pos,
377 ImGui::GetColorU32(theme.text_secondary_gray),
378 display_name.c_str());
381 std::string id_text = absl::StrFormat(
"%03X", obj_id);
382 ImVec2 id_size = ImGui::CalcTextSize(id_text.c_str());
383 ImVec2 id_pos = ImVec2(button_pos.x + (item_size - id_size.x) / 2,
384 button_pos.y + item_size - id_size.y - 2);
385 draw_list->AddText(id_pos, ImGui::GetColorU32(theme.text_primary),
389 if (ImGui::IsItemHovered()) {
391 {{ImGuiCol_PopupBg, theme.panel_bg_color},
392 {ImGuiCol_Border, theme.panel_border_color}});
394 if (ImGui::BeginTooltip()) {
395 ImGui::TextColored(theme.selection_primary,
"Object 0x%03X",
397 ImGui::Text(
"%s", full_name.c_str());
399 ImGui::TextColored(theme.text_secondary_gray,
"Subtype %d",
403 uint32_t layout_key = (
static_cast<uint32_t
>(obj_id) << 16) |
404 static_cast<uint32_t
>(subtype);
405 const bool can_capture_layout =
408 if (can_capture_layout &&
414 if (layout_or.ok()) {
421 ImGui::TextColored(theme.status_success,
"Tiles: %zu",
422 layout.cells.size());
424 if (can_capture_layout) {
427 room_ref.get_gfx_buffer().
data());
429 ImGui::TextColored(theme.status_active,
"Draw Routine: %d",
433 ImGui::Text(
"Layout:");
434 ImDrawList* tooltip_draw_list = ImGui::GetWindowDrawList();
435 ImVec2 grid_start = ImGui::GetCursorScreenPos();
436 float cell_size = 4.0f;
437 for (
const auto& cell : layout.cells) {
438 ImVec2 p1(grid_start.x + cell.rel_x * cell_size,
439 grid_start.y + cell.rel_y * cell_size);
440 ImVec2 p2(p1.x + cell_size, p1.y + cell_size);
441 tooltip_draw_list->AddRectFilled(p1, p2,
442 IM_COL32(200, 200, 200, 255));
443 tooltip_draw_list->AddRect(p1, p2, IM_COL32(50, 50, 50, 255));
445 ImGui::Dummy(ImVec2(layout.bounds_width * cell_size,
446 layout.bounds_height * cell_size));
450 ImGui::TextColored(ImVec4(0.7f, 0.7f, 0.7f, 1.0f),
451 "Click to select for placement");
452 ImGui::TextColored(ImVec4(0.5f, 0.8f, 1.0f, 1.0f),
453 "Double-click to view details");
460 current_column = (current_column + 1) % columns;
473 ImGui::ColorConvertU32ToFloat4(IM_COL32(100, 180, 120, 255))},
474 {ImGuiCol_HeaderHovered,
475 ImGui::ColorConvertU32ToFloat4(IM_COL32(130, 210, 150, 255))}});
476 bool custom_open = ImGui::CollapsingHeader(
"Custom Objects",
477 ImGuiTreeNodeFlags_DefaultOpen);
480 ImGui::TextColored(theme.text_secondary_gray,
481 "Create, reload, and browse custom object variants "
482 "separately from the vanilla selector.");
485 if (ImGui::SmallButton(
ICON_MD_ADD " New Custom Object")) {
493 if (ImGui::IsItemHovered()) {
494 ImGui::SetTooltip(
"Create a new custom object from scratch");
499 const std::string custom_base_path = obj_manager.GetBasePath();
500 if (custom_base_path.empty()) {
501 ImGui::TextColored(theme.text_secondary_gray,
502 "Custom objects folder: not configured");
504 ImGui::Text(
"Custom objects folder: %s", custom_base_path.c_str());
505 if (ImGui::IsItemHovered()) {
506 ImGui::SetTooltip(
"%s", custom_base_path.c_str());
510 obj_manager.ReloadAll();
513 if (ImGui::IsItemHovered()) {
515 "Reload custom object binaries and refresh previews");
518 ImGui::TextColored(theme.text_secondary_gray,
519 "Corner overrides: 0x100/0x101/0x102/0x103 use 0x31 "
520 "subtypes 02/04/03/05");
532 for (
int obj_id : {0x31, 0x32}) {
536 int subtype_count = obj_manager.GetSubtypeCount(obj_id);
537 for (
int subtype = 0; subtype < subtype_count; ++subtype) {
539 std::string subtype_name =
540 absl::StrFormat(
"%s %02X", base_name.c_str(), subtype);
548 ImGui::PushID(obj_id * 1000 + subtype);
552 ImVec2 button_size(item_size, item_size);
554 if (ImGui::Selectable(
"", is_selected,
555 ImGuiSelectableFlags_AllowDoubleClick,
559 if (ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left)) {
566 ImVec2 button_pos = ImGui::GetItemRectMin();
567 ImDrawList* draw_list = ImGui::GetWindowDrawList();
569 bool rendered =
false;
574 temp_obj.size_ = subtype;
580 ImU32 obj_color = IM_COL32(100, 180, 120, 255);
581 ImU32 darker_color = IM_COL32(60, 100, 70, 255);
583 draw_list->AddRectFilledMultiColor(
585 ImVec2(button_pos.x + item_size, button_pos.y + item_size),
586 darker_color, darker_color, obj_color, obj_color);
588 std::string symbol = (obj_id == 0x31) ?
"Trk" :
"Cus";
590 std::string sub_text = absl::StrFormat(
"%02X", subtype);
591 ImVec2 sub_size = ImGui::CalcTextSize(sub_text.c_str());
592 ImVec2 sub_pos(button_pos.x + (item_size - sub_size.x) / 2,
593 button_pos.y + (item_size - sub_size.y) / 2);
594 draw_list->AddText(sub_pos, IM_COL32(255, 255, 255, 220),
605 is_selected ? ImGui::GetColorU32(theme.dungeon_selection_primary)
606 : ImGui::GetColorU32(theme.panel_bg_darker);
607 float border_thickness = is_selected ? 3.0f : 1.0f;
610 ImVec2(button_pos.x + item_size, button_pos.y + item_size),
611 border_color, 0.0f, 0, border_thickness);
614 std::string id_text = absl::StrFormat(
"%02X:%02X", obj_id, subtype);
615 ImVec2 id_size = ImGui::CalcTextSize(id_text.c_str());
616 ImVec2 id_pos = ImVec2(button_pos.x + (item_size - id_size.x) / 2,
617 button_pos.y + item_size - id_size.y - 2);
618 draw_list->AddText(id_pos, ImGui::GetColorU32(theme.text_primary),
621 if (ImGui::IsItemHovered()) {
623 {{ImGuiCol_PopupBg, theme.panel_bg_color},
624 {ImGuiCol_Border, theme.panel_border_color}});
625 if (ImGui::BeginTooltip()) {
626 const std::string filename =
627 obj_manager.ResolveFilename(obj_id, subtype);
628 const bool has_base = !custom_base_path.empty();
629 std::filesystem::path full_path =
631 ? (std::filesystem::path(custom_base_path) / filename)
632 : std::filesystem::path();
633 const bool file_exists = has_base && !filename.empty() &&
634 std::filesystem::exists(full_path);
636 ImGui::TextColored(theme.selection_primary,
"Custom 0x%02X:%02X",
638 ImGui::Text(
"%s", subtype_name.c_str());
640 ImGui::Text(
"File: %s",
641 filename.empty() ?
"(unmapped)" : filename.c_str());
643 ImGui::TextColored(theme.text_warning_yellow,
644 "Folder not configured in project");
645 }
else if (file_exists) {
646 ImGui::TextColored(theme.status_success,
"File found");
648 ImGui::TextColored(theme.status_error,
"File missing: %s",
649 full_path.string().c_str());
652 if (obj_id == 0x31 && subtype >= 2 && subtype <= 5) {
653 const char* corner_id =
"";
655 corner_id =
"0x100 (TL)";
656 }
else if (subtype == 3) {
657 corner_id =
"0x102 (TR)";
658 }
else if (subtype == 4) {
659 corner_id =
"0x101 (BL)";
661 corner_id =
"0x103 (BR)";
664 ImGui::TextColored(theme.status_active,
665 "Also used by corner override %s",
673 custom_col = (custom_col + 1) % columns;