85 void Draw(
bool* p_open)
override {
92 constexpr int kRoomsPerRow = 16;
93 constexpr int kRoomsPerCol = 19;
94 constexpr int kTotalRooms = 0x128;
95 constexpr float kCellSpacing = 1.0f;
99 ImGui::PushID(
"RoomMatrixFilter");
100 ImGui::SetNextItemWidth(
101 std::max(140.0f, ImGui::GetContentRegionAvail().x - 82.0f));
102 ImGui::InputTextWithHint(
"##Search",
ICON_MD_SEARCH " Filter room id/name",
107 ImGui::BeginDisabled();
113 ImGui::EndDisabled();
115 if (ImGui::IsItemHovered()) {
116 ImGui::SetTooltip(tr(
"Clear filter"));
123 const ImVec2 avail = ImGui::GetContentRegionAvail();
124 const float panel_width = std::max(1.0f, avail.x);
125 const float panel_height = std::max(1.0f, avail.y);
126 const float cell_size_by_width =
127 (panel_width - kCellSpacing * (kRoomsPerRow - 1)) / kRoomsPerRow;
128 const float cell_size_by_height =
129 (panel_height - kCellSpacing * (kRoomsPerCol - 1)) / kRoomsPerCol;
130 const float cell_size = std::clamp(
131 std::min(cell_size_by_width, cell_size_by_height), 12.0f, 24.0f);
133 const float grid_width =
134 kRoomsPerRow * cell_size + kCellSpacing * (kRoomsPerRow - 1);
135 const float grid_height =
136 kRoomsPerCol * cell_size + kCellSpacing * (kRoomsPerCol - 1);
137 const float x_offset = std::max(0.0f, (panel_width - grid_width) * 0.5f);
138 const float y_offset = std::max(0.0f, (panel_height - grid_height) * 0.5f);
140 ImDrawList* draw_list = ImGui::GetWindowDrawList();
141 ImVec2 canvas_pos = ImGui::GetCursorScreenPos();
142 canvas_pos.x += x_offset;
143 canvas_pos.y += y_offset;
146 for (
int row = 0; row < kRoomsPerCol; row++) {
147 for (
int col = 0; col < kRoomsPerRow; col++) {
148 int room_id = room_index;
149 bool is_valid_room = (room_id < kTotalRooms);
153 ImVec2(canvas_pos.x + col * (cell_size + kCellSpacing),
154 canvas_pos.y + row * (cell_size + kCellSpacing));
156 ImVec2(cell_min.x + cell_size, cell_min.y + cell_size);
161 if (!matches_filter) {
163 bg_color, ImGui::ColorConvertFloat4ToU32(theme.panel_bg_darker),
168 bool is_open =
false;
177 draw_list->AddRectFilled(cell_min, cell_max, bg_color);
182 ImVec4 glow_color = theme.dungeon_selection_primary;
183 glow_color.w = matches_filter ? 0.3f : 0.18f;
184 ImVec2 glow_min(cell_min.x - 2, cell_min.y - 2);
185 ImVec2 glow_max(cell_max.x + 2, cell_max.y + 2);
186 draw_list->AddRect(glow_min, glow_max,
187 ImGui::ColorConvertFloat4ToU32(glow_color), 0.0f,
192 ImGui::ColorConvertFloat4ToU32(theme.dungeon_selection_primary);
193 draw_list->AddRect(cell_min, cell_max, sel_color, 0.0f, 0, 2.5f);
194 }
else if (is_open) {
195 ImU32 open_color = ImGui::ColorConvertFloat4ToU32(
196 theme.dungeon_grid_cell_selected);
197 draw_list->AddRect(cell_min, cell_max, open_color, 0.0f, 0, 2.0f);
200 ImGui::ColorConvertFloat4ToU32(theme.dungeon_grid_cell_border);
201 if (!matches_filter) {
204 ImGui::ColorConvertFloat4ToU32(theme.panel_bg_darker), 0.5f);
206 draw_list->AddRect(cell_min, cell_max, border_color, 0.0f, 0, 1.0f);
210 if (cell_size >= 18.0f) {
212 snprintf(label,
sizeof(label),
"%02X", room_id);
213 ImVec2 text_size = ImGui::CalcTextSize(label);
215 ImVec2(cell_min.x + (cell_size - text_size.x) * 0.5f,
216 cell_min.y + (cell_size - text_size.y) * 0.5f);
217 ImVec4 text_color_vec = theme.dungeon_grid_text;
218 if (!matches_filter) {
219 text_color_vec.w *= 0.55f;
221 ImU32 text_color = ImGui::ColorConvertFloat4ToU32(text_color_vec);
222 draw_list->AddText(text_pos, text_color, label);
226 ImGui::SetCursorScreenPos(cell_min);
228 snprintf(btn_id,
sizeof(btn_id),
"##room%d", room_id);
229 ImGui::InvisibleButton(btn_id, ImVec2(cell_size, cell_size));
231 if (ImGui::IsItemClicked()) {
232 if (ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left)) {
244 if (ImGui::BeginPopupContextItem()) {
245 const bool can_swap =
249 std::string open_label =
250 is_open ?
"Focus Room" :
"Open in Workbench";
251 if (ImGui::MenuItem(open_label.c_str())) {
260 if (ImGui::MenuItem(tr(
"Open as Panel"))) {
268 if (ImGui::MenuItem(tr(
"Swap With Current Room"),
nullptr,
false,
276 snprintf(id_buf,
sizeof(id_buf),
"0x%02X", room_id);
277 if (ImGui::MenuItem(tr(
"Copy Room ID"))) {
278 ImGui::SetClipboardText(id_buf);
282 if (ImGui::MenuItem(tr(
"Copy Room Name"))) {
283 ImGui::SetClipboardText(room_label.c_str());
290 if (ImGui::IsItemHovered()) {
291 ImGui::BeginTooltip();
294 ImGui::TextDisabled(tr(
"Room 0x%02X"), room_id);
297 ImGui::TextColored(theme.dungeon_selection_primary,
299 }
else if (is_open) {
300 ImGui::TextColored(theme.status_success,
306 if (loaded_room !=
nullptr) {
308 ImGui::TextDisabled(tr(
"Palette: %d | Blockset: %d"),
309 loaded_room->palette(),
310 loaded_room->blockset());
317 if (preview_bitmap.is_active() &&
318 preview_bitmap.texture() != 0) {
321 constexpr float kThumbnailSize = 80.0f;
322 ImGui::Image((ImTextureID)(intptr_t)preview_bitmap.texture(),
323 ImVec2(kThumbnailSize, kThumbnailSize));
329 ImGui::TextDisabled(tr(
"Click to %s"), is_open ?
"focus" :
"open");
330 ImGui::TextDisabled(tr(
"Double-click to open as panel"));
331 ImGui::TextDisabled(tr(
"Right-click for actions"));
336 draw_list->AddRectFilled(
338 ImGui::ColorConvertFloat4ToU32(theme.panel_bg_darker));
347 ImVec2(panel_width, std::max(grid_height + y_offset, panel_height)));
395 ImDrawList* draw_list = ImGui::GetWindowDrawList();
396 const ImVec2 pos = ImGui::GetCursorScreenPos();
397 const float size = ImGui::GetFrameHeight() - 6.0f;
398 const ImVec2 min = ImVec2(pos.x, pos.y + 3.0f);
399 const ImVec2 max = ImVec2(pos.x + size, pos.y + size + 3.0f);
400 draw_list->AddRectFilled(min, max, ImGui::ColorConvertFloat4ToU32(color),
402 draw_list->AddRect(min, max, ImGui::GetColorU32(ImGuiCol_Border), 3.0f);
403 ImGui::Dummy(ImVec2(size + 6.0f, size + 6.0f));
404 ImGui::SameLine(0.0f, 6.0f);
405 ImGui::TextUnformatted(label);
473 auto sample_dominant_color =
474 [](
const gfx::Bitmap& bitmap) -> std::optional<ImU32> {
475 if (!bitmap.is_active() || bitmap.width() <= 0 || bitmap.height() <= 0 ||
476 bitmap.data() ==
nullptr || bitmap.surface() ==
nullptr ||
477 bitmap.surface()->format ==
nullptr ||
478 bitmap.surface()->format->palette ==
nullptr) {
482 constexpr int kSampleStep = 16;
483 std::array<uint32_t, 256> histogram{};
484 SDL_Palette* palette = bitmap.surface()->format->palette;
485 const uint8_t* pixels = bitmap.data();
486 const int width = bitmap.width();
487 const int height = bitmap.height();
489 for (
int y = 0; y < height; y += kSampleStep) {
490 for (
int x = 0; x < width; x += kSampleStep) {
491 const uint8_t idx = pixels[(y * width) + x];
499 uint32_t best_count = 0;
500 uint8_t best_index = 0;
501 for (
int i = 0; i < palette->ncolors && i < 256; ++i) {
502 if (histogram[
static_cast<size_t>(i)] > best_count) {
503 best_count = histogram[
static_cast<size_t>(i)];
504 best_index =
static_cast<uint8_t
>(i);
508 if (best_count == 0) {
511 const SDL_Color& c = palette->colors[best_index];
512 return IM_COL32(c.r, c.g, c.b, 255);
515 auto soften_color = [&](ImU32 color,
float blend = 0.32f) -> ImU32 {
516 ImVec4 src = ImGui::ColorConvertU32ToFloat4(color);
518 src.x = (src.x * (1.0f - blend)) + (bg.x * blend);
519 src.y = (src.y * (1.0f - blend)) + (bg.y * blend);
520 src.z = (src.z * (1.0f - blend)) + (bg.z * blend);
522 return ImGui::ColorConvertFloat4ToU32(src);
525 auto palette_fallback_color = [&](
int palette_id,
526 int blockset_id) -> ImU32 {
528 const float palette_mix =
529 0.26f + (
static_cast<float>(palette_id & 0x07) * 0.055f);
530 const float blockset_mix =
531 0.08f + (
static_cast<float>(blockset_id & 0x0F) * 0.018f);
533 tint.x = std::clamp(tint.x + blockset_mix, 0.0f, 1.0f);
534 tint.y = std::clamp(tint.y + (palette_mix * 0.35f), 0.0f, 1.0f);
535 tint.z = std::clamp(tint.z - (blockset_mix * 0.2f), 0.0f, 1.0f);
538 mixed.x = std::clamp(
539 (bg.x * (1.0f - palette_mix)) + (tint.x * palette_mix), 0.0f, 1.0f);
540 mixed.y = std::clamp(
541 (bg.y * (1.0f - palette_mix)) + (tint.y * palette_mix), 0.0f, 1.0f);
542 mixed.z = std::clamp(
543 (bg.z * (1.0f - palette_mix)) + (tint.z * palette_mix), 0.0f, 1.0f);
545 return ImGui::ColorConvertFloat4ToU32(mixed);
552 if (room !=
nullptr) {
554 const uint64_t source_revision = room->composite_source_revision();
557 cache_it->second.source_revision != source_revision) {
564 sample_dominant_color(composite)})
567 if (cache_it->second.color.has_value()) {
568 return soften_color(cache_it->second.color.value());
571 if (
auto bg1_color = sample_dominant_color(room->bg1_buffer().bitmap());
572 bg1_color.has_value()) {
573 return soften_color(bg1_color.value());
576 return palette_fallback_color(room->palette(), room->blockset());
579 if (
auto room_meta =
GetRoomMetadata(room_id); room_meta.has_value()) {
580 return palette_fallback_color(room_meta->first, room_meta->second);
585 const auto clamp01 = [](
float v) {
586 return (v < 0.0f) ? 0.0f : (v > 1.0f ? 1.0f : v);
591 const float group_mix =
592 0.16f + (
static_cast<float>((room_id >> 4) & 0x03) * 0.08f);
593 const float step =
static_cast<float>(room_id & 0x07) * 0.0125f;
596 fallback.x = clamp01(dark.x + (mid.x - dark.x) * group_mix + step);
597 fallback.y = clamp01(dark.y + (mid.y - dark.y) * group_mix + step);
598 fallback.z = clamp01(dark.z + (mid.z - dark.z) * group_mix + step);
600 return ImGui::ColorConvertFloat4ToU32(fallback);