From a53b13b6b641a8d6148b1a5c6071a43b442d740f Mon Sep 17 00:00:00 2001 From: DanS Date: Wed, 15 Jul 2026 09:31:48 -0500 Subject: [PATCH] feat(image-picker): smooth scroll, inset scrollbar, 2-column folder grid MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Smooth (lerped) wheel scrolling via ApplySmoothScroll on the list, matching the app's other modal lists. - The list is now a bordered/rounded outer frame whose 6px padding insets the scrollbar so it clears the card's rounded corners; the inner scroll child is transparent (the frame draws the single background — no more box-in-a-box from the ChildBg being left on the stack across both BeginChild calls). - Folders render as a 2-column grid of thin rounded rectangles (folder icon + name) instead of full-width rows, so more folders are visible at a glance. Co-Authored-By: Claude Opus 4.8 (1M context) --- src/ui/windows/image_picker.h | 48 +++++++++++++++++++++++++---------- 1 file changed, 34 insertions(+), 14 deletions(-) diff --git a/src/ui/windows/image_picker.h b/src/ui/windows/image_picker.h index 5dd26f6..c023c47 100644 --- a/src/ui/windows/image_picker.h +++ b/src/ui/windows/image_picker.h @@ -153,8 +153,19 @@ public: float listH = ImGui::GetContentRegionAvail().y - footerBlockH; listH = std::max(listH, 160.0f * dp); const float fullW = ImGui::GetContentRegionAvail().x; + // Bordered/rounded outer frame whose 6px padding insets the inner scrollbar so it clears the + // card's rounded corners; the inner child scrolls smoothly (ApplySmoothScroll lerps the wheel). ImGui::PushStyleColor(ImGuiCol_ChildBg, WithAlpha(OnSurface(), 20)); - ImGui::BeginChild("##imgList", ImVec2(fullW, listH), true); + ImGui::PushStyleVar(ImGuiStyleVar_ChildRounding, 6.0f * dp); + ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(6.0f * dp, 6.0f * dp)); + ImGui::BeginChild("##imgListFrame", ImVec2(fullW, listH), true, + ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse); + ImGui::PopStyleVar(2); // ChildRounding + outer WindowPadding (captured by the frame child) + ImGui::PopStyleColor(); // ChildBg — the frame already drew it; the inner child stays transparent + ImGui::PushStyleVar(ImGuiStyleVar_ScrollbarSize, 11.0f * dp); + ImGui::PushStyleVar(ImGuiStyleVar_ScrollbarRounding, 5.5f * dp); + ImGui::BeginChild("##imgList", ImVec2(0, 0), false, ImGuiWindowFlags_NoScrollWithMouse); + ApplySmoothScroll(); ImDrawList* dl = ImGui::GetWindowDrawList(); const float padX = Layout::spacingSm(); @@ -165,27 +176,35 @@ public: ImGui::Unindent(padX); } - // Folder rows (compact, clickable to descend). - { - const float rowH = rowFont->LegacySize + Layout::spacingSm(); - const float rowW = ImGui::GetContentRegionAvail().x; + // Folders: a 2-column grid of thin rounded rectangles (folder icon + name), clickable to descend. + if (!s_subdirs.empty()) { + const int cols = 2; + const float cellGap = Layout::spacingSm(); + const float availW = ImGui::GetContentRegionAvail().x; + const float cellW = (availW - cellGap * (cols - 1)) / (float)cols; + const float cellH = rowFont->LegacySize + Layout::spacingSm() * 1.5f; + int col = 0; for (std::size_t i = 0; i < s_subdirs.size(); ++i) { - ImVec2 rMin = ImGui::GetCursorScreenPos(); - ImVec2 rMax(rMin.x + rowW, rMin.y + rowH); + if (col != 0) ImGui::SameLine(0, cellGap); + ImVec2 mn = ImGui::GetCursorScreenPos(); + ImVec2 mx(mn.x + cellW, mn.y + cellH); ImGui::PushID((int)(i + 1)); - const bool clicked = ImGui::InvisibleButton("##idir", ImVec2(rowW, rowH)); + const bool clicked = ImGui::InvisibleButton("##idir", ImVec2(cellW, cellH)); const bool hov = ImGui::IsItemHovered(); ImGui::PopID(); - if (hov) { dl->AddRectFilled(rMin, rMax, WithAlpha(OnSurface(), 22), 6.0f * dp); ImGui::SetMouseCursor(ImGuiMouseCursor_Hand); } - const float midY = (rMin.y + rMax.y) * 0.5f; + dl->AddRectFilled(mn, mx, WithAlpha(OnSurface(), hov ? 34 : 16), 6.0f * dp); + if (hov) { dl->AddRect(mn, mx, WithAlpha(OnSurface(), 70), 6.0f * dp, 0, 1.0f); ImGui::SetMouseCursor(ImGuiMouseCursor_Hand); } + const float midY = (mn.y + mx.y) * 0.5f; const float icoSz = rowFont->LegacySize * 1.05f; - float x = rMin.x + padX; + float x = mn.x + padX; dl->AddText(icoFont, icoSz, ImVec2(x, midY - icoSz * 0.5f), hov ? Primary() : OnSurfaceMedium(), ICON_MD_FOLDER); x += icoSz + Layout::spacingSm(); - std::string nm = fit(s_subdirs[i], rowFont, (rMax.x - padX) - x); + std::string nm = fit(s_subdirs[i], rowFont, (mx.x - padX) - x); dl->AddText(rowFont, rowFont->LegacySize, ImVec2(x, midY - rowFont->LegacySize * 0.5f), OnSurface(), nm.c_str()); if (clicked) pendingNav = (std::filesystem::path(s_dir) / s_subdirs[i]).string(); + col = (col + 1) % cols; } + if (!s_images.empty()) ImGui::Dummy(ImVec2(0, Layout::spacingSm())); // gap before the thumbnails } // Thumbnail grid. @@ -233,8 +252,9 @@ public: } } - ImGui::EndChild(); - ImGui::PopStyleColor(); + ImGui::EndChild(); // inner scrolling list + ImGui::PopStyleVar(2); // ScrollbarSize + ScrollbarRounding + ImGui::EndChild(); // outer frame // ---- Status ---------------------------------------------------------------------------- ImGui::Dummy(ImVec2(0, Layout::spacingXs()));