81 void Draw(
bool* p_open)
override {
88 constexpr int kRoomsPerRow = 16;
89 constexpr int kRoomsPerCol = 19;
90 constexpr int kTotalRooms = 0x128;
91 constexpr float kCellSpacing = 1.0f;
95 ImGui::PushID(
"RoomMatrixFilter");
96 ImGui::SetNextItemWidth(
97 std::max(140.0f, ImGui::GetContentRegionAvail().x - 82.0f));
98 ImGui::InputTextWithHint(
"##Search",
ICON_MD_SEARCH " Filter room id/name",
103 ImGui::BeginDisabled();
109 ImGui::EndDisabled();
111 if (ImGui::IsItemHovered()) {
112 ImGui::SetTooltip(tr(
"Clear filter"));
119 const ImVec2 avail = ImGui::GetContentRegionAvail();
120 const float panel_width = std::max(1.0f, avail.x);
121 const float panel_height = std::max(1.0f, avail.y);
122 const float cell_size_by_width =
123 (panel_width - kCellSpacing * (kRoomsPerRow - 1)) / kRoomsPerRow;
124 const float cell_size_by_height =
125 (panel_height - kCellSpacing * (kRoomsPerCol - 1)) / kRoomsPerCol;
126 const float cell_size = std::clamp(
127 std::min(cell_size_by_width, cell_size_by_height), 12.0f, 24.0f);
129 const float grid_width =
130 kRoomsPerRow * cell_size + kCellSpacing * (kRoomsPerRow - 1);
131 const float grid_height =
132 kRoomsPerCol * cell_size + kCellSpacing * (kRoomsPerCol - 1);
133 const float x_offset = std::max(0.0f, (panel_width - grid_width) * 0.5f);
134 const float y_offset = std::max(0.0f, (panel_height - grid_height) * 0.5f);
136 ImDrawList* draw_list = ImGui::GetWindowDrawList();
137 ImVec2 canvas_pos = ImGui::GetCursorScreenPos();
138 canvas_pos.x += x_offset;
139 canvas_pos.y += y_offset;
142 for (
int row = 0; row < kRoomsPerCol; row++) {
143 for (
int col = 0; col < kRoomsPerRow; col++) {
144 int room_id = room_index;
145 bool is_valid_room = (room_id < kTotalRooms);
149 ImVec2(canvas_pos.x + col * (cell_size + kCellSpacing),
150 canvas_pos.y + row * (cell_size + kCellSpacing));
152 ImVec2(cell_min.x + cell_size, cell_min.y + cell_size);
157 if (!matches_filter) {
159 bg_color, ImGui::ColorConvertFloat4ToU32(theme.panel_bg_darker),
164 bool is_open =
false;
173 draw_list->AddRectFilled(cell_min, cell_max, bg_color);
178 ImVec4 glow_color = theme.dungeon_selection_primary;
179 glow_color.w = matches_filter ? 0.3f : 0.18f;
180 ImVec2 glow_min(cell_min.x - 2, cell_min.y - 2);
181 ImVec2 glow_max(cell_max.x + 2, cell_max.y + 2);
182 draw_list->AddRect(glow_min, glow_max,
183 ImGui::ColorConvertFloat4ToU32(glow_color), 0.0f,
188 ImGui::ColorConvertFloat4ToU32(theme.dungeon_selection_primary);
189 draw_list->AddRect(cell_min, cell_max, sel_color, 0.0f, 0, 2.5f);
190 }
else if (is_open) {
191 ImU32 open_color = ImGui::ColorConvertFloat4ToU32(
192 theme.dungeon_grid_cell_selected);
193 draw_list->AddRect(cell_min, cell_max, open_color, 0.0f, 0, 2.0f);
196 ImGui::ColorConvertFloat4ToU32(theme.dungeon_grid_cell_border);
197 if (!matches_filter) {
200 ImGui::ColorConvertFloat4ToU32(theme.panel_bg_darker), 0.5f);
202 draw_list->AddRect(cell_min, cell_max, border_color, 0.0f, 0, 1.0f);
206 if (cell_size >= 18.0f) {
208 snprintf(label,
sizeof(label),
"%02X", room_id);
209 ImVec2 text_size = ImGui::CalcTextSize(label);
211 ImVec2(cell_min.x + (cell_size - text_size.x) * 0.5f,
212 cell_min.y + (cell_size - text_size.y) * 0.5f);
213 ImVec4 text_color_vec = theme.dungeon_grid_text;
214 if (!matches_filter) {
215 text_color_vec.w *= 0.55f;
217 ImU32 text_color = ImGui::ColorConvertFloat4ToU32(text_color_vec);
218 draw_list->AddText(text_pos, text_color, label);
222 ImGui::SetCursorScreenPos(cell_min);
224 snprintf(btn_id,
sizeof(btn_id),
"##room%d", room_id);
225 ImGui::InvisibleButton(btn_id, ImVec2(cell_size, cell_size));
227 if (ImGui::IsItemClicked()) {
228 if (ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left)) {
240 if (ImGui::BeginPopupContextItem()) {
241 const bool can_swap =
245 std::string open_label =
246 is_open ?
"Focus Room" :
"Open in Workbench";
247 if (ImGui::MenuItem(open_label.c_str())) {
256 if (ImGui::MenuItem(tr(
"Open as Panel"))) {
264 if (ImGui::MenuItem(tr(
"Swap With Current Room"),
nullptr,
false,
272 snprintf(id_buf,
sizeof(id_buf),
"0x%02X", room_id);
273 if (ImGui::MenuItem(tr(
"Copy Room ID"))) {
274 ImGui::SetClipboardText(id_buf);
278 if (ImGui::MenuItem(tr(
"Copy Room Name"))) {
279 ImGui::SetClipboardText(room_label.c_str());
286 if (ImGui::IsItemHovered()) {
287 ImGui::BeginTooltip();
290 ImGui::TextDisabled(tr(
"Room 0x%02X"), room_id);
293 ImGui::TextColored(theme.dungeon_selection_primary,
295 }
else if (is_open) {
296 ImGui::TextColored(theme.status_success,
302 if (loaded_room !=
nullptr) {
304 ImGui::TextDisabled(tr(
"Palette: %d | Blockset: %d"),
305 loaded_room->palette(),
306 loaded_room->blockset());
309 auto& room = *loaded_room;
312 auto& preview_bitmap = room.GetCompositeBitmap(layer_mgr);
313 if (preview_bitmap.is_active() &&
314 preview_bitmap.texture() != 0) {
317 constexpr float kThumbnailSize = 80.0f;
318 ImGui::Image((ImTextureID)(intptr_t)preview_bitmap.texture(),
319 ImVec2(kThumbnailSize, kThumbnailSize));
325 ImGui::TextDisabled(tr(
"Click to %s"), is_open ?
"focus" :
"open");
326 ImGui::TextDisabled(tr(
"Double-click to open as panel"));
327 ImGui::TextDisabled(tr(
"Right-click for actions"));
332 draw_list->AddRectFilled(
334 ImGui::ColorConvertFloat4ToU32(theme.panel_bg_darker));
343 ImVec2(panel_width, std::max(grid_height + y_offset, panel_height)));
384 ImDrawList* draw_list = ImGui::GetWindowDrawList();
385 const ImVec2 pos = ImGui::GetCursorScreenPos();
386 const float size = ImGui::GetFrameHeight() - 6.0f;
387 const ImVec2 min = ImVec2(pos.x, pos.y + 3.0f);
388 const ImVec2 max = ImVec2(pos.x + size, pos.y + size + 3.0f);
389 draw_list->AddRectFilled(min, max, ImGui::ColorConvertFloat4ToU32(color),
391 draw_list->AddRect(min, max, ImGui::GetColorU32(ImGuiCol_Border), 3.0f);
392 ImGui::Dummy(ImVec2(size + 6.0f, size + 6.0f));
393 ImGui::SameLine(0.0f, 6.0f);
394 ImGui::TextUnformatted(label);
462 auto sample_dominant_color =
463 [](
const gfx::Bitmap& bitmap) -> std::optional<ImU32> {
464 if (!bitmap.is_active() || bitmap.width() <= 0 || bitmap.height() <= 0 ||
465 bitmap.data() ==
nullptr || bitmap.surface() ==
nullptr ||
466 bitmap.surface()->format ==
nullptr ||
467 bitmap.surface()->format->palette ==
nullptr) {
471 constexpr int kSampleStep = 16;
472 std::array<uint32_t, 256> histogram{};
473 SDL_Palette* palette = bitmap.surface()->format->palette;
474 const uint8_t* pixels = bitmap.data();
475 const int width = bitmap.width();
476 const int height = bitmap.height();
478 for (
int y = 0; y < height; y += kSampleStep) {
479 for (
int x = 0; x < width; x += kSampleStep) {
480 const uint8_t idx = pixels[(y * width) + x];
488 uint32_t best_count = 0;
489 uint8_t best_index = 0;
490 for (
int i = 0; i < palette->ncolors && i < 256; ++i) {
491 if (histogram[
static_cast<size_t>(i)] > best_count) {
492 best_count = histogram[
static_cast<size_t>(i)];
493 best_index =
static_cast<uint8_t
>(i);
497 if (best_count == 0) {
500 const SDL_Color& c = palette->colors[best_index];
501 return IM_COL32(c.r, c.g, c.b, 255);
504 auto soften_color = [&](ImU32 color,
float blend = 0.32f) -> ImU32 {
505 ImVec4 src = ImGui::ColorConvertU32ToFloat4(color);
507 src.x = (src.x * (1.0f - blend)) + (bg.x * blend);
508 src.y = (src.y * (1.0f - blend)) + (bg.y * blend);
509 src.z = (src.z * (1.0f - blend)) + (bg.z * blend);
511 return ImGui::ColorConvertFloat4ToU32(src);
514 auto palette_fallback_color = [&](
int palette_id,
515 int blockset_id) -> ImU32 {
517 const float palette_mix =
518 0.26f + (
static_cast<float>(palette_id & 0x07) * 0.055f);
519 const float blockset_mix =
520 0.08f + (
static_cast<float>(blockset_id & 0x0F) * 0.018f);
522 tint.x = std::clamp(tint.x + blockset_mix, 0.0f, 1.0f);
523 tint.y = std::clamp(tint.y + (palette_mix * 0.35f), 0.0f, 1.0f);
524 tint.z = std::clamp(tint.z - (blockset_mix * 0.2f), 0.0f, 1.0f);
527 mixed.x = std::clamp(
528 (bg.x * (1.0f - palette_mix)) + (tint.x * palette_mix), 0.0f, 1.0f);
529 mixed.y = std::clamp(
530 (bg.y * (1.0f - palette_mix)) + (tint.y * palette_mix), 0.0f, 1.0f);
531 mixed.z = std::clamp(
532 (bg.z * (1.0f - palette_mix)) + (tint.z * palette_mix), 0.0f, 1.0f);
534 return ImGui::ColorConvertFloat4ToU32(mixed);
541 if (room !=
nullptr) {
544 auto& composite = room->GetCompositeBitmap(layer_mgr);
545 if (
auto composite_color = sample_dominant_color(composite);
546 composite_color.has_value()) {
547 return soften_color(composite_color.value());
550 if (
auto bg1_color = sample_dominant_color(room->bg1_buffer().bitmap());
551 bg1_color.has_value()) {
552 return soften_color(bg1_color.value());
555 return palette_fallback_color(room->palette(), room->blockset());
558 if (
auto room_meta =
GetRoomMetadata(room_id); room_meta.has_value()) {
559 return palette_fallback_color(room_meta->first, room_meta->second);
564 const auto clamp01 = [](
float v) {
565 return (v < 0.0f) ? 0.0f : (v > 1.0f ? 1.0f : v);
570 const float group_mix =
571 0.16f + (
static_cast<float>((room_id >> 4) & 0x03) * 0.08f);
572 const float step =
static_cast<float>(room_id & 0x07) * 0.0125f;
575 fallback.x = clamp01(dark.x + (mid.x - dark.x) * group_mix + step);
576 fallback.y = clamp01(dark.y + (mid.y - dark.y) * group_mix + step);
577 fallback.z = clamp01(dark.z + (mid.z - dark.z) * group_mix + step);
579 return ImGui::ColorConvertFloat4ToU32(fallback);