Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
10 changes: 10 additions & 0 deletions Tests/GalleryIntegrationTests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<IDWriteTextLayout> 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<DWRITE_LINE_METRICS> 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;
Expand Down
32 changes: 24 additions & 8 deletions Viewer/GalleryGridCanvas.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<IDWriteTextLayout> 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<IDWriteInlineObject> 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<IDWriteTextLayout>& 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<IDWriteInlineObject> 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() {
Expand Down
2 changes: 2 additions & 0 deletions Viewer/GalleryGridCanvas.h
Original file line number Diff line number Diff line change
Expand Up @@ -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<IDWriteTextLayout>& layout);
void DropDevice();
bool PaintGpu(const std::vector<int>& visible, double now, bool& pending);
void PaintFallback(CDC& dc, const std::vector<int>& visible, double now);
Expand Down
2 changes: 1 addition & 1 deletion docs/STORE_LISTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand Down
Loading