diff --git a/CHANGELOG.md b/CHANGELOG.md index 7942c39..324aeaf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,13 @@ All notable changes to Q1View are documented here. Releases follow [semantic ver --- +## [2.7.21] — 2026-09-16 + +### Changed +- Windows Viewer: wrap long folder names across the available gallery-card height and add an ellipsis only at the end of the final visible line, matching the DirectWrite and GDI fallback renderers. + +--- + ## [2.7.20] — 2026-09-13 ### Changed @@ -415,7 +422,8 @@ All notable changes to Q1View are documented here. Releases follow [semantic ver --- -[Unreleased]: https://github.com/chammoru/Q1View/compare/v2.7.20...HEAD +[Unreleased]: https://github.com/chammoru/Q1View/compare/v2.7.21...HEAD +[2.7.21]: https://github.com/chammoru/Q1View/compare/v2.7.20...v2.7.21 [2.7.20]: https://github.com/chammoru/Q1View/compare/v2.7.19...v2.7.20 [2.7.19]: https://github.com/chammoru/Q1View/compare/v2.7.18...v2.7.19 [2.7.18]: https://github.com/chammoru/Q1View/compare/v2.7.17...v2.7.18 diff --git a/Tests/GalleryIntegrationTests.cpp b/Tests/GalleryIntegrationTests.cpp index d715a7d..00735a9 100644 --- a/Tests/GalleryIntegrationTests.cpp +++ b/Tests/GalleryIntegrationTests.cpp @@ -89,6 +89,16 @@ struct GalleryIntegrationTests { pane.GetItemCount() == 0, "large grid keeps one parent tile without list-control rows or image-list copies"); Require(grid.mDevice && grid.mContext && grid.mTarget && grid.mFontCollection && grid.mBadgeText, "actual Direct2D/D3D11 target uses the bundled font collection"); + Microsoft::WRL::ComPtr longFolderLayout; + Require(grid.CreateFolderTextLayout( + L"[This is a deliberately very long folder name that must wrap before it is trimmed]", + 110.0f, 52.0f, longFolderLayout), "long-folder DirectWrite layout created"); + UINT32 lineCount = 0; + longFolderLayout->GetLineMetrics(nullptr, 0, &lineCount); + std::vector lineMetrics(lineCount); + Require(lineCount > 1 && SUCCEEDED(longFolderLayout->GetLineMetrics( + lineMetrics.data(), lineCount, &lineCount)) && lineMetrics.back().isTrimmed, + "long folder name wraps before the final visible line is ellipsized"); Require(!grid.mCache.empty(), "worker results populate CPU thumbnail cache"); bool uploaded = false; for (auto& p : grid.mCache) if (p.second.gpu) uploaded = true; diff --git a/Viewer/GalleryGridCanvas.cpp b/Viewer/GalleryGridCanvas.cpp index 74126f9..be1de61 100644 --- a/Viewer/GalleryGridCanvas.cpp +++ b/Viewer/GalleryGridCanvas.cpp @@ -180,23 +180,39 @@ void CGalleryGridCanvas::DrawFolderCardGpu(int index, const q1view::GalleryRect& const D2D1_RECT_F labelRect = D2D1::RectF(r.x + pad, r.y + pad, r.x + r.size - pad, r.y + r.size - pad); Ptr layout; - if (SUCCEEDED(mWriteFactory->CreateTextLayout(label, label.GetLength(), mText.Get(), - labelRect.right - labelRect.left, labelRect.bottom - labelRect.top, &layout))) { - DWRITE_TRIMMING trimming = { DWRITE_TRIMMING_GRANULARITY_CHARACTER, 0, 0 }; - Ptr ellipsis; - if (SUCCEEDED(mWriteFactory->CreateEllipsisTrimmingSign(mText.Get(), &ellipsis))) - layout->SetTrimming(&trimming, ellipsis.Get()); - layout->SetWordWrapping(DWRITE_WORD_WRAPPING_NO_WRAP); + if (CreateFolderTextLayout(label, labelRect.right - labelRect.left, + labelRect.bottom - labelRect.top, layout)) { mContext->DrawTextLayout(D2D1::Point2F(labelRect.left, labelRect.top), layout.Get(), mBrush.Get()); } } + +bool CGalleryGridCanvas::CreateFolderTextLayout(const CString& label, float width, float height, + Ptr& layout) { + layout.Reset(); + if (!mWriteFactory || !mText || width <= 0 || height <= 0 || + FAILED(mWriteFactory->CreateTextLayout(label, label.GetLength(), mText.Get(), width, height, &layout))) + return false; + DWRITE_TRIMMING trimming = { DWRITE_TRIMMING_GRANULARITY_CHARACTER, 0, 0 }; + Ptr ellipsis; + if (SUCCEEDED(mWriteFactory->CreateEllipsisTrimmingSign(mText.Get(), &ellipsis))) + layout->SetTrimming(&trimming, ellipsis.Get()); + // Fill the card with wrapped text first. DirectWrite applies the trimming + // sign only to the final visible line when the layout height overflows. + layout->SetWordWrapping(DWRITE_WORD_WRAPPING_WRAP); + return true; +} void CGalleryGridCanvas::DrawFolderCardFallback(CDC& dc, int index, const CRect& rect) { dc.FillSolidRect(rect, Q1UI_COLOR_SURFACE); dc.SetBkMode(TRANSPARENT); dc.SetTextColor(Q1UI_COLOR_ACCENT); CFont* oldFont = dc.SelectObject(&mOwner.mFolderFont); const int pad = std::max(4, int(rect.Width() * .08f)); CRect label = rect; label.DeflateRect(pad, pad); - dc.DrawText(Label(index), label, DT_CENTER | DT_VCENTER | DT_SINGLELINE | DT_END_ELLIPSIS | DT_NOPREFIX); + const CString text = Label(index); + CRect measured(0, 0, label.Width(), 0); + dc.DrawText(text, measured, DT_CENTER | DT_WORDBREAK | DT_CALCRECT | DT_NOPREFIX); + if (measured.Height() < label.Height()) + label.top += (label.Height() - measured.Height()) / 2; + dc.DrawText(text, label, DT_CENTER | DT_WORDBREAK | DT_END_ELLIPSIS | DT_NOPREFIX); dc.SelectObject(oldFont); } void CGalleryGridCanvas::DropDevice() { diff --git a/Viewer/GalleryGridCanvas.h b/Viewer/GalleryGridCanvas.h index c8ec75a..3ab3d85 100644 --- a/Viewer/GalleryGridCanvas.h +++ b/Viewer/GalleryGridCanvas.h @@ -58,6 +58,8 @@ class CGalleryGridCanvas : public CWnd { void ClearCache(); void TrimCache(); bool EnsureDevice(int width, int height); + bool CreateFolderTextLayout(const CString& label, float width, float height, + Ptr& layout); void DropDevice(); bool PaintGpu(const std::vector& visible, double now, bool& pending); void PaintFallback(CDC& dc, const std::vector& visible, double now); diff --git a/docs/STORE_LISTING.md b/docs/STORE_LISTING.md index 83a342b..4750849 100644 --- a/docs/STORE_LISTING.md +++ b/docs/STORE_LISTING.md @@ -41,7 +41,7 @@ Viewer also supports image-sequence navigation, video playback, frame/time progr BT.2020 HLG videos are tone-mapped for accurate color and contrast on standard SDR Windows displays instead of being shown with a washed-out legacy color conversion. -Right-click a thumbnail to open it, copy the file or its path, reveal it in File Explorer, or view Windows properties. Gallery grids focus on useful image and video previews, while compact mode keeps the complete folder list. Directories use clean **[Folder name]** and **[..]** notation with balanced Pretendard typography instead of decorative folder artwork; Backspace and Alt+Up go to the parent, and from a drive root open the top-level logical-drive chooser without interrupting the photo or video in the main view, while Viewer shortcuts remain available when the drawer has focus. +Right-click a thumbnail to open it, copy the file or its path, reveal it in File Explorer, or view Windows properties. Gallery grids focus on useful image and video previews, while compact mode keeps the complete folder list. Directories use clean **[Folder name]** and **[..]** notation with balanced Pretendard typography instead of decorative folder artwork; long names wrap across the card and use an ellipsis only on the final visible line. Backspace and Alt+Up go to the parent, and from a drive root open the top-level logical-drive chooser without interrupting the photo or video in the main view, while Viewer shortcuts remain available when the drawer has focus. Choose Auto, Smooth (Bilinear), or Pixel Exact (Nearest) image scaling from the View menu. Auto keeps scanned text clear at moderate reductions, suppresses aliasing when large photos are reduced substantially, and preserves exact pixels during close inspection.