80 void Draw(
bool* p_open)
override {
87 constexpr int kRoomsPerRow = 16;
88 constexpr int kRoomsPerCol = 19;
89 constexpr int kTotalRooms = 0x128;
90 constexpr float kCellSpacing = 1.0f;
94 ImGui::PushID(
"RoomMatrixFilter");
95 ImGui::SetNextItemWidth(
96 std::max(140.0f, ImGui::GetContentRegionAvail().x - 82.0f));
97 ImGui::InputTextWithHint(
"##Search",
ICON_MD_SEARCH " Filter room id/name",
102 ImGui::BeginDisabled();
108 ImGui::EndDisabled();
110 if (ImGui::IsItemHovered()) {
111 ImGui::SetTooltip(
"Clear filter");
118 const ImVec2 avail = ImGui::GetContentRegionAvail();
119 const float panel_width = std::max(1.0f, avail.x);
120 const float panel_height = std::max(1.0f, avail.y);
121 const float cell_size_by_width =
122 (panel_width - kCellSpacing * (kRoomsPerRow - 1)) / kRoomsPerRow;
123 const float cell_size_by_height =
124 (panel_height - kCellSpacing * (kRoomsPerCol - 1)) / kRoomsPerCol;
125 const float cell_size = std::clamp(
126 std::min(cell_size_by_width, cell_size_by_height), 12.0f, 24.0f);
128 const float grid_width =
129 kRoomsPerRow * cell_size + kCellSpacing * (kRoomsPerRow - 1);
130 const float grid_height =
131 kRoomsPerCol * cell_size + kCellSpacing * (kRoomsPerCol - 1);
132 const float x_offset = std::max(0.0f, (panel_width - grid_width) * 0.5f);
133 const float y_offset = std::max(0.0f, (panel_height - grid_height) * 0.5f);
135 ImDrawList* draw_list = ImGui::GetWindowDrawList();
136 ImVec2 canvas_pos = ImGui::GetCursorScreenPos();
137 canvas_pos.x += x_offset;
138 canvas_pos.y += y_offset;
141 for (
int row = 0; row < kRoomsPerCol; row++) {
142 for (
int col = 0; col < kRoomsPerRow; col++) {
143 int room_id = room_index;
144 bool is_valid_room = (room_id < kTotalRooms);
148 ImVec2(canvas_pos.x + col * (cell_size + kCellSpacing),
149 canvas_pos.y + row * (cell_size + kCellSpacing));
151 ImVec2(cell_min.x + cell_size, cell_min.y + cell_size);
156 if (!matches_filter) {
158 bg_color, ImGui::ColorConvertFloat4ToU32(theme.panel_bg_darker),
163 bool is_open =
false;
172 draw_list->AddRectFilled(cell_min, cell_max, bg_color);
177 ImVec4 glow_color = theme.dungeon_selection_primary;
178 glow_color.w = matches_filter ? 0.3f : 0.18f;
179 ImVec2 glow_min(cell_min.x - 2, cell_min.y - 2);
180 ImVec2 glow_max(cell_max.x + 2, cell_max.y + 2);
181 draw_list->AddRect(glow_min, glow_max,
182 ImGui::ColorConvertFloat4ToU32(glow_color), 0.0f,
187 ImGui::ColorConvertFloat4ToU32(theme.dungeon_selection_primary);
188 draw_list->AddRect(cell_min, cell_max, sel_color, 0.0f, 0, 2.5f);
189 }
else if (is_open) {
190 ImU32 open_color = ImGui::ColorConvertFloat4ToU32(
191 theme.dungeon_grid_cell_selected);
192 draw_list->AddRect(cell_min, cell_max, open_color, 0.0f, 0, 2.0f);
195 ImGui::ColorConvertFloat4ToU32(theme.dungeon_grid_cell_border);
196 if (!matches_filter) {
199 ImGui::ColorConvertFloat4ToU32(theme.panel_bg_darker), 0.5f);
201 draw_list->AddRect(cell_min, cell_max, border_color, 0.0f, 0, 1.0f);
205 if (cell_size >= 18.0f) {
207 snprintf(label,
sizeof(label),
"%02X", room_id);
208 ImVec2 text_size = ImGui::CalcTextSize(label);
210 ImVec2(cell_min.x + (cell_size - text_size.x) * 0.5f,
211 cell_min.y + (cell_size - text_size.y) * 0.5f);
212 ImVec4 text_color_vec = theme.dungeon_grid_text;
213 if (!matches_filter) {
214 text_color_vec.w *= 0.55f;
216 ImU32 text_color = ImGui::ColorConvertFloat4ToU32(text_color_vec);
217 draw_list->AddText(text_pos, text_color, label);
221 ImGui::SetCursorScreenPos(cell_min);
223 snprintf(btn_id,
sizeof(btn_id),
"##room%d", room_id);
224 ImGui::InvisibleButton(btn_id, ImVec2(cell_size, cell_size));
226 if (ImGui::IsItemClicked()) {
227 if (ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left)) {
239 if (ImGui::BeginPopupContextItem()) {
240 const bool can_swap =
244 std::string open_label =
245 is_open ?
"Focus Room" :
"Open in Workbench";
246 if (ImGui::MenuItem(open_label.c_str())) {
255 if (ImGui::MenuItem(
"Open as Panel")) {
263 if (ImGui::MenuItem(
"Swap With Current Room",
nullptr,
false,
271 snprintf(id_buf,
sizeof(id_buf),
"0x%02X", room_id);
272 if (ImGui::MenuItem(
"Copy Room ID")) {
273 ImGui::SetClipboardText(id_buf);
277 if (ImGui::MenuItem(
"Copy Room Name")) {
278 ImGui::SetClipboardText(room_label.c_str());
285 if (ImGui::IsItemHovered()) {
286 ImGui::BeginTooltip();
289 ImGui::TextDisabled(
"Room 0x%02X", room_id);
292 ImGui::TextColored(theme.dungeon_selection_primary,
294 }
else if (is_open) {
295 ImGui::TextColored(theme.status_success,
301 if (loaded_room !=
nullptr) {
303 ImGui::TextDisabled(
"Palette: %d | Blockset: %d",
304 loaded_room->palette(),
305 loaded_room->blockset());
308 auto& room = *loaded_room;
311 auto& preview_bitmap = room.GetCompositeBitmap(layer_mgr);
312 if (preview_bitmap.is_active() &&
313 preview_bitmap.texture() != 0) {
316 constexpr float kThumbnailSize = 80.0f;
317 ImGui::Image((ImTextureID)(intptr_t)preview_bitmap.texture(),
318 ImVec2(kThumbnailSize, kThumbnailSize));
324 ImGui::TextDisabled(
"Click to %s", is_open ?
"focus" :
"open");
325 ImGui::TextDisabled(
"Double-click to open as panel");
326 ImGui::TextDisabled(
"Right-click for actions");
331 draw_list->AddRectFilled(
333 ImGui::ColorConvertFloat4ToU32(theme.panel_bg_darker));
342 ImVec2(panel_width, std::max(grid_height + y_offset, panel_height)));
383 ImDrawList* draw_list = ImGui::GetWindowDrawList();
384 const ImVec2 pos = ImGui::GetCursorScreenPos();
385 const float size = ImGui::GetFrameHeight() - 6.0f;
386 const ImVec2 min = ImVec2(pos.x, pos.y + 3.0f);
387 const ImVec2 max = ImVec2(pos.x + size, pos.y + size + 3.0f);
388 draw_list->AddRectFilled(min, max, ImGui::ColorConvertFloat4ToU32(color),
390 draw_list->AddRect(min, max, ImGui::GetColorU32(ImGuiCol_Border), 3.0f);
391 ImGui::Dummy(ImVec2(size + 6.0f, size + 6.0f));
392 ImGui::SameLine(0.0f, 6.0f);
393 ImGui::TextUnformatted(label);
461 auto sample_dominant_color =
462 [](
const gfx::Bitmap& bitmap) -> std::optional<ImU32> {
463 if (!bitmap.is_active() || bitmap.width() <= 0 || bitmap.height() <= 0 ||
464 bitmap.data() ==
nullptr || bitmap.surface() ==
nullptr ||
465 bitmap.surface()->format ==
nullptr ||
466 bitmap.surface()->format->palette ==
nullptr) {
470 constexpr int kSampleStep = 16;
471 std::array<uint32_t, 256> histogram{};
472 SDL_Palette* palette = bitmap.surface()->format->palette;
473 const uint8_t* pixels = bitmap.data();
474 const int width = bitmap.width();
475 const int height = bitmap.height();
477 for (
int y = 0; y < height; y += kSampleStep) {
478 for (
int x = 0; x < width; x += kSampleStep) {
479 const uint8_t idx = pixels[(y * width) + x];
487 uint32_t best_count = 0;
488 uint8_t best_index = 0;
489 for (
int i = 0; i < palette->ncolors && i < 256; ++i) {
490 if (histogram[
static_cast<size_t>(i)] > best_count) {
491 best_count = histogram[
static_cast<size_t>(i)];
492 best_index =
static_cast<uint8_t
>(i);
496 if (best_count == 0) {
499 const SDL_Color& c = palette->colors[best_index];
500 return IM_COL32(c.r, c.g, c.b, 255);
503 auto soften_color = [&](ImU32 color,
float blend = 0.32f) -> ImU32 {
504 ImVec4 src = ImGui::ColorConvertU32ToFloat4(color);
506 src.x = (src.x * (1.0f - blend)) + (bg.x * blend);
507 src.y = (src.y * (1.0f - blend)) + (bg.y * blend);
508 src.z = (src.z * (1.0f - blend)) + (bg.z * blend);
510 return ImGui::ColorConvertFloat4ToU32(src);
513 auto palette_fallback_color = [&](
int palette_id,
514 int blockset_id) -> ImU32 {
516 const float palette_mix =
517 0.26f + (
static_cast<float>(palette_id & 0x07) * 0.055f);
518 const float blockset_mix =
519 0.08f + (
static_cast<float>(blockset_id & 0x0F) * 0.018f);
521 tint.x = std::clamp(tint.x + blockset_mix, 0.0f, 1.0f);
522 tint.y = std::clamp(tint.y + (palette_mix * 0.35f), 0.0f, 1.0f);
523 tint.z = std::clamp(tint.z - (blockset_mix * 0.2f), 0.0f, 1.0f);
526 mixed.x = std::clamp(
527 (bg.x * (1.0f - palette_mix)) + (tint.x * palette_mix), 0.0f, 1.0f);
528 mixed.y = std::clamp(
529 (bg.y * (1.0f - palette_mix)) + (tint.y * palette_mix), 0.0f, 1.0f);
530 mixed.z = std::clamp(
531 (bg.z * (1.0f - palette_mix)) + (tint.z * palette_mix), 0.0f, 1.0f);
533 return ImGui::ColorConvertFloat4ToU32(mixed);
540 if (room !=
nullptr) {
543 auto& composite = room->GetCompositeBitmap(layer_mgr);
544 if (
auto composite_color = sample_dominant_color(composite);
545 composite_color.has_value()) {
546 return soften_color(composite_color.value());
549 if (
auto bg1_color = sample_dominant_color(room->bg1_buffer().bitmap());
550 bg1_color.has_value()) {
551 return soften_color(bg1_color.value());
554 return palette_fallback_color(room->palette(), room->blockset());
557 if (
auto room_meta =
GetRoomMetadata(room_id); room_meta.has_value()) {
558 return palette_fallback_color(room_meta->first, room_meta->second);
563 const auto clamp01 = [](
float v) {
564 return (v < 0.0f) ? 0.0f : (v > 1.0f ? 1.0f : v);
569 const float group_mix =
570 0.16f + (
static_cast<float>((room_id >> 4) & 0x03) * 0.08f);
571 const float step =
static_cast<float>(room_id & 0x07) * 0.0125f;
574 fallback.x = clamp01(dark.x + (mid.x - dark.x) * group_mix + step);
575 fallback.y = clamp01(dark.y + (mid.y - dark.y) * group_mix + step);
576 fallback.z = clamp01(dark.z + (mid.z - dark.z) * group_mix + step);
578 return ImGui::ColorConvertFloat4ToU32(fallback);