77 void Draw(
bool* p_open)
override {
84 constexpr int kRoomsPerRow = 16;
85 constexpr int kRoomsPerCol = 19;
86 constexpr int kTotalRooms = 0x128;
87 constexpr float kCellSpacing = 1.0f;
90 float panel_width = ImGui::GetContentRegionAvail().x;
92 float cell_size = std::max(
94 std::min(24.0f, (panel_width - kCellSpacing * (kRoomsPerRow - 1)) /
97 ImDrawList* draw_list = ImGui::GetWindowDrawList();
98 ImVec2 canvas_pos = ImGui::GetCursorScreenPos();
101 for (
int row = 0; row < kRoomsPerCol; row++) {
102 for (
int col = 0; col < kRoomsPerRow; col++) {
103 int room_id = room_index;
104 bool is_valid_room = (room_id < kTotalRooms);
107 ImVec2(canvas_pos.x + col * (cell_size + kCellSpacing),
108 canvas_pos.y + row * (cell_size + kCellSpacing));
110 ImVec2(cell_min.x + cell_size, cell_min.y + cell_size);
117 bool is_open =
false;
126 draw_list->AddRectFilled(cell_min, cell_max, bg_color);
131 ImVec4 glow_color = theme.dungeon_selection_primary;
133 ImVec2 glow_min(cell_min.x - 2, cell_min.y - 2);
134 ImVec2 glow_max(cell_max.x + 2, cell_max.y + 2);
135 draw_list->AddRect(glow_min, glow_max,
136 ImGui::ColorConvertFloat4ToU32(glow_color), 0.0f,
141 ImGui::ColorConvertFloat4ToU32(theme.dungeon_selection_primary);
142 draw_list->AddRect(cell_min, cell_max, sel_color, 0.0f, 0, 2.5f);
143 }
else if (is_open) {
144 ImU32 open_color = ImGui::ColorConvertFloat4ToU32(
145 theme.dungeon_grid_cell_selected);
146 draw_list->AddRect(cell_min, cell_max, open_color, 0.0f, 0, 2.0f);
149 ImGui::ColorConvertFloat4ToU32(theme.dungeon_grid_cell_border);
150 draw_list->AddRect(cell_min, cell_max, border_color, 0.0f, 0, 1.0f);
154 if (cell_size >= 18.0f) {
156 snprintf(label,
sizeof(label),
"%02X", room_id);
157 ImVec2 text_size = ImGui::CalcTextSize(label);
159 ImVec2(cell_min.x + (cell_size - text_size.x) * 0.5f,
160 cell_min.y + (cell_size - text_size.y) * 0.5f);
162 ImGui::ColorConvertFloat4ToU32(theme.dungeon_grid_text);
163 draw_list->AddText(text_pos, text_color, label);
167 ImGui::SetCursorScreenPos(cell_min);
169 snprintf(btn_id,
sizeof(btn_id),
"##room%d", room_id);
170 ImGui::InvisibleButton(btn_id, ImVec2(cell_size, cell_size));
172 if (ImGui::IsItemClicked()) {
173 if (ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left)) {
185 if (ImGui::BeginPopupContextItem()) {
186 const bool can_swap =
190 std::string open_label =
191 is_open ?
"Focus Room" :
"Open in Workbench";
192 if (ImGui::MenuItem(open_label.c_str())) {
201 if (ImGui::MenuItem(
"Open as Panel")) {
209 if (ImGui::MenuItem(
"Swap With Current Room",
nullptr,
false,
217 snprintf(id_buf,
sizeof(id_buf),
"0x%02X", room_id);
218 if (ImGui::MenuItem(
"Copy Room ID")) {
219 ImGui::SetClipboardText(id_buf);
223 if (ImGui::MenuItem(
"Copy Room Name")) {
224 ImGui::SetClipboardText(room_label.c_str());
231 if (ImGui::IsItemHovered()) {
232 ImGui::BeginTooltip();
238 ImGui::TextDisabled(
"Palette: %d | Blockset: %d",
239 (*
rooms_)[room_id].palette(),
240 (*
rooms_)[room_id].blockset());
243 auto& room = (*rooms_)[room_id];
246 auto& preview_bitmap = room.GetCompositeBitmap(layer_mgr);
247 if (preview_bitmap.is_active() && preview_bitmap.texture() != 0) {
250 constexpr float kThumbnailSize = 80.0f;
251 ImGui::Image((ImTextureID)(intptr_t)preview_bitmap.texture(),
252 ImVec2(kThumbnailSize, kThumbnailSize));
256 ImGui::TextDisabled(
"Click to %s", is_open ?
"focus" :
"open");
261 draw_list->AddRectFilled(
263 ImGui::ColorConvertFloat4ToU32(theme.panel_bg_darker));
271 ImGui::Dummy(ImVec2(kRoomsPerRow * (cell_size + kCellSpacing),
272 kRoomsPerCol * (cell_size + kCellSpacing)));
282 auto sample_dominant_color =
284 if (!bitmap.is_active() || bitmap.width() <= 0 || bitmap.height() <= 0 ||
285 bitmap.data() ==
nullptr || bitmap.surface() ==
nullptr ||
286 bitmap.surface()->format ==
nullptr ||
287 bitmap.surface()->format->palette ==
nullptr) {
291 constexpr int kSampleStep = 16;
292 std::array<uint32_t, 256> histogram{};
293 SDL_Palette* palette = bitmap.surface()->format->palette;
294 const uint8_t* pixels = bitmap.data();
295 const int width = bitmap.width();
296 const int height = bitmap.height();
298 for (
int y = 0; y < height; y += kSampleStep) {
299 for (
int x = 0; x < width; x += kSampleStep) {
300 const uint8_t idx = pixels[(y * width) + x];
308 uint32_t best_count = 0;
309 uint8_t best_index = 0;
310 for (
int i = 0; i < palette->ncolors && i < 256; ++i) {
311 if (histogram[
static_cast<size_t>(i)] > best_count) {
312 best_count = histogram[
static_cast<size_t>(i)];
313 best_index =
static_cast<uint8_t
>(i);
317 if (best_count == 0) {
320 const SDL_Color& c = palette->colors[best_index];
321 return IM_COL32(c.r, c.g, c.b, 255);
324 auto soften_color = [&](ImU32 color,
float blend = 0.32f) -> ImU32 {
325 ImVec4 src = ImGui::ColorConvertU32ToFloat4(color);
327 src.x = (src.x * (1.0f - blend)) + (bg.x * blend);
328 src.y = (src.y * (1.0f - blend)) + (bg.y * blend);
329 src.z = (src.z * (1.0f - blend)) + (bg.z * blend);
331 return ImGui::ColorConvertFloat4ToU32(src);
337 auto& room = (*rooms_)[room_id];
340 auto& composite = room.GetCompositeBitmap(layer_mgr);
341 if (
auto composite_color = sample_dominant_color(composite);
342 composite_color.has_value()) {
343 return soften_color(composite_color.value());
346 if (
auto bg1_color = sample_dominant_color(room.bg1_buffer().bitmap());
347 bg1_color.has_value()) {
348 return soften_color(bg1_color.value());
353 const auto clamp01 = [](
float v) {
354 return (v < 0.0f) ? 0.0f : (v > 1.0f ? 1.0f : v);
359 const float group_mix =
360 0.16f + (
static_cast<float>((room_id >> 4) & 0x03) * 0.08f);
361 const float step =
static_cast<float>(room_id & 0x07) * 0.0125f;
364 fallback.x = clamp01(dark.x + (mid.x - dark.x) * group_mix + step);
365 fallback.y = clamp01(dark.y + (mid.y - dark.y) * group_mix + step);
366 fallback.z = clamp01(dark.z + (mid.z - dark.z) * group_mix + step);
368 return ImGui::ColorConvertFloat4ToU32(fallback);