diff options
author | Neil <nyamatongwe@gmail.com> | 2025-03-18 12:09:17 +1100 |
---|---|---|
committer | Neil <nyamatongwe@gmail.com> | 2025-03-18 12:09:17 +1100 |
commit | 4eb2079f2ba24ea5727fb89857b235b34643cf30 (patch) | |
tree | 0267ed085cc0b95660990fda2125d9aaeef1ded9 | |
parent | f5abf444eea42122e9837d8fc8fb094d91c8da62 (diff) | |
download | scintilla-mirror-4eb2079f2ba24ea5727fb89857b235b34643cf30.tar.gz |
Use DirectWrite for autocompletion lists when DirectWrite chosen for document
text.
-rw-r--r-- | doc/ScintillaHistory.html | 3 | ||||
-rw-r--r-- | win32/PlatWin.cxx | 339 |
2 files changed, 207 insertions, 135 deletions
diff --git a/doc/ScintillaHistory.html b/doc/ScintillaHistory.html index a5640ea70..01429dc42 100644 --- a/doc/ScintillaHistory.html +++ b/doc/ScintillaHistory.html @@ -602,6 +602,9 @@ <a href="https://sourceforge.net/p/scintilla/bugs/2463/">Bug #2463</a>. </li> <li> + On Win32, use DirectWrite for autocompletion lists when DirectWrite chosen for document text. + </li> + <li> On Qt, draw clipped UTF-8 text correctly. <a href="https://sourceforge.net/p/scintilla/bugs/2464/">Bug #2464</a>. </li> diff --git a/win32/PlatWin.cxx b/win32/PlatWin.cxx index e24f82675..8dae4867d 100644 --- a/win32/PlatWin.cxx +++ b/win32/PlatWin.cxx @@ -388,6 +388,8 @@ constexpr D2D1_TEXT_ANTIALIAS_MODE DWriteMapFontQuality(FontQuality extraFontFla // Both GDI and DirectWrite can produce a HFONT for use in list boxes struct FontWin : public Font { [[nodiscard]] virtual HFONT HFont() const noexcept = 0; + [[nodiscard]] virtual std::unique_ptr<FontWin> Duplicate() const = 0; + [[nodiscard]] virtual CharacterSet GetCharacterSet() const noexcept = 0; }; void SetLogFont(LOGFONTW &lf, const char *faceName, CharacterSet characterSet, XYPOSITION size, FontWeight weight, bool italic, FontQuality extraFontFlag) { @@ -403,7 +405,11 @@ void SetLogFont(LOGFONTW &lf, const char *faceName, CharacterSet characterSet, X struct FontGDI : public FontWin { HFONT hfont = {}; - explicit FontGDI(const FontParameters &fp) { + CharacterSet characterSet = CharacterSet::Ansi; + FontGDI(HFONT hfont_, CharacterSet characterSet_) noexcept : hfont(hfont_), characterSet(characterSet_) { + // Takes ownership and deletes the font + } + explicit FontGDI(const FontParameters &fp) : characterSet(fp.characterSet) { LOGFONTW lf; SetLogFont(lf, fp.faceName, fp.characterSet, fp.size, fp.weight, fp.italic, fp.extraFontFlag); hfont = ::CreateFontIndirectW(&lf); @@ -425,6 +431,13 @@ struct FontGDI : public FontWin { } return ::CreateFontIndirectW(&lf); } + [[nodiscard]] std::unique_ptr<FontWin> Duplicate() const override { + HFONT hfontCopy = HFont(); + return std::make_unique<FontGDI>(hfontCopy, characterSet); + } + [[nodiscard]] CharacterSet GetCharacterSet() const noexcept override { + return characterSet; + } }; #if defined(USE_D2D) @@ -478,8 +491,16 @@ struct FontDirectWrite : public FontWin { } } } + // Allow copy constructor. Has to explicitly copy each field since can't use =default as deleted in Font. + FontDirectWrite(const FontDirectWrite &other) noexcept { + pTextFormat = other.pTextFormat; + extraFontFlag = other.extraFontFlag; + characterSet = other.characterSet; + yAscent = other.yAscent; + yDescent = other.yDescent; + yInternalLeading = other.yInternalLeading; + } // Deleted so FontDirectWrite objects can not be copied. - FontDirectWrite(const FontDirectWrite &) = delete; FontDirectWrite(FontDirectWrite &&) = delete; FontDirectWrite &operator=(const FontDirectWrite &) = delete; FontDirectWrite &operator=(FontDirectWrite &&) = delete; @@ -511,6 +532,14 @@ struct FontDirectWrite : public FontWin { } return pfm; } + + [[nodiscard]] std::unique_ptr<FontWin> Duplicate() const override { + return std::make_unique<FontDirectWrite>(*this); + } + + [[nodiscard]] CharacterSet GetCharacterSet() const noexcept override { + return characterSet; + } }; #endif @@ -973,7 +1002,9 @@ HBITMAP BitMapSection(HDC hdc, int width, int height, DWORD **pixels) noexcept { const BITMAPV5HEADER bi = BitMapHeader(width, height); void *image = nullptr; HBITMAP hbm = ::CreateDIBSection(hdc, reinterpret_cast<const BITMAPINFO *>(&bi), DIB_RGB_COLORS, &image, {}, 0); - *pixels = static_cast<DWORD *>(image); + if (pixels) { + *pixels = static_cast<DWORD *>(image); + } return hbm; } @@ -3236,14 +3267,61 @@ ListBox::ListBox() noexcept = default; ListBox::~ListBox() noexcept = default; +namespace { + +ColourRGBA ColourElement(std::optional<ColourRGBA> colour, int nIndex) { + if (colour.has_value()) { + return colour.value(); + } + const DWORD colourValue = ::GetSysColor(nIndex); + return ColourRGBA(colourValue); +} + +struct LBGraphics { + HDC hMemDC{}; + HBITMAP hBitmap{}; + HBITMAP hOldBitmap{}; + std::unique_ptr<Surface> pixmapLine; +#if defined(USE_D2D) + DCRenderTarget pBMDCTarget; +#endif + + ~LBGraphics() { + Release(); + } + + void Release() noexcept { + pixmapLine.reset(); +#if defined(USE_D2D) + pBMDCTarget = nullptr; +#endif + if (hOldBitmap) { + SelectBitmap(hMemDC, hOldBitmap); + } + hOldBitmap = {}; + if (hBitmap) { + ::DeleteObject(hBitmap); + } + hBitmap = {}; + if (hMemDC) { + ::DeleteDC(hMemDC); + } + hMemDC = {}; + } +}; + +} + class ListBoxX : public ListBox { int lineHeight = 10; HFONT fontCopy {}; + std::unique_ptr<FontWin> fontWin; Technology technology = Technology::Default; RGBAImageSet images; LineToItem lti; HWND lb {}; bool unicodeMode = false; + int codePage = 0; int desiredVisibleRows = 9; unsigned int maxItemCharacters = 0; unsigned int aveCharWidth = 8; @@ -3261,6 +3339,8 @@ class ListBoxX : public ListBox { ListOptions options; DWORD frameStyle = WS_THICKFRAME; + LBGraphics graphics; + HWND GetHWND() const noexcept; void AppendListItem(const char *text, const char *numword); void AdjustWindowRect(PRectangle *rc, UINT dpiAdjust) const noexcept; @@ -3277,7 +3357,7 @@ class ListBoxX : public ListBox { void StartResize(WPARAM); LRESULT NcHitTest(WPARAM, LPARAM) const; void CentreItem(int n); - void Paint(HDC); + void AllocateBitMap(); static LRESULT PASCAL ControlWndProc(HWND hWnd, UINT iMessage, WPARAM wParam, LPARAM lParam); static constexpr POINT ItemInset {0, 0}; // Padding around whole item @@ -3295,6 +3375,7 @@ public: ::DeleteObject(fontCopy); fontCopy = {}; } + graphics.Release(); } void SetFont(const Font *font) override; void Create(Window &parent_, int ctrlID_, Point location_, int lineHeight_, bool unicodeMode_, Technology technology_) override; @@ -3331,6 +3412,7 @@ void ListBoxX::Create(Window &parent_, int ctrlID_, Point location_, int lineHei location = location_; lineHeight = lineHeight_; unicodeMode = unicodeMode_; + codePage = unicodeMode ? CpUtf8 : 0; technology = technology_; HWND hwndParent = HwndFromWindow(*parent); HINSTANCE hinstanceParent = GetWindowInstance(hwndParent); @@ -3358,6 +3440,9 @@ void ListBoxX::SetFont(const Font *font) { } fontCopy = pfm->HFont(); SetWindowFont(lb, fontCopy, 0); + fontWin = pfm->Duplicate(); + codePage = unicodeMode ? CpUtf8 : CodePageFromCharSet(fontWin->GetCharacterSet(), 1252); + graphics.Release(); } } @@ -3386,26 +3471,21 @@ PRectangle ListBoxX::GetDesiredRect() { rcDesired.bottom = rcDesired.top + ItemHeight() * rows; int width = MinClientWidth(); - HDC hdc = ::GetDC(lb); - HFONT oldFont = SelectFont(hdc, fontCopy); - SIZE textSize = {0, 0}; + int textSize = 0; int len = 0; + int averageCharWidth = 8; if (widestItem) { len = static_cast<int>(strlen(widestItem)); - if (unicodeMode) { - const TextWide tbuf(widestItem, CpUtf8); - ::GetTextExtentPoint32W(hdc, tbuf.buffer, tbuf.tlen, &textSize); - } else { - ::GetTextExtentPoint32A(hdc, widestItem, len, &textSize); - } + // Make a measuring surface + std::unique_ptr<Surface> surfaceItem(Surface::Allocate(technology)); + surfaceItem->Init(GetID()); + surfaceItem->SetMode(SurfaceMode(codePage, false)); + textSize = static_cast<int>(std::ceil(surfaceItem->WidthText(fontWin.get(), widestItem))); + maxCharWidth = static_cast<int>(std::ceil(surfaceItem->WidthText(fontWin.get(), "W"))); + averageCharWidth = static_cast<int>(surfaceItem->AverageCharWidth(fontWin.get())); } - TEXTMETRIC tm; - ::GetTextMetrics(hdc, &tm); - maxCharWidth = tm.tmMaxCharWidth; - SelectFont(hdc, oldFont); - ::ReleaseDC(lb, hdc); - const int widthDesired = std::max(textSize.cx, (len + 1) * tm.tmAveCharWidth); + const int widthDesired = std::max(textSize, (len + 1) * averageCharWidth); if (width < widthDesired) width = widthDesired; @@ -3482,103 +3562,76 @@ void ListBoxX::ClearRegisteredImages() { images.Clear(); } -namespace { - -int ColourOfElement(std::optional<ColourRGBA> colour, int nIndex) { - if (colour.has_value()) { - return colour.value().OpaqueRGB(); +void ListBoxX::Draw(DRAWITEMSTRUCT *pDrawItem) { + if ((pDrawItem->itemAction != ODA_SELECT) && (pDrawItem->itemAction != ODA_DRAWENTIRE)) { + return; } - return ::GetSysColor(nIndex); -} - -void FillRectColour(HDC hdc, const RECT *lprc, int colour) noexcept { - const HBRUSH brush = ::CreateSolidBrush(colour); - ::FillRect(hdc, lprc, brush); - ::DeleteObject(brush); -} + if (!graphics.pixmapLine) { + AllocateBitMap(); + if (!graphics.pixmapLine) { + // Failed to allocate, so release fully and give up + graphics.Release(); + return; + } + } +#if defined(USE_D2D) + if (graphics.pBMDCTarget) { + graphics.pBMDCTarget->BeginDraw(); + } +#endif -} + const PRectangle rcItemBase = PRectangleFromRECT(pDrawItem->rcItem); + const PRectangle rcItem(0, 0, rcItemBase.Width(), rcItemBase.Height()); + PRectangle rcBox = rcItem; + rcBox.left += TextOffset(); + ColourRGBA colourFore; + ColourRGBA colourBack; + if (pDrawItem->itemState & ODS_SELECTED) { + PRectangle rcImage = rcItem; + rcImage.right = rcBox.left; + // The image is not highlighted + graphics.pixmapLine->FillRectangle(rcImage, ColourElement(options.back, COLOR_WINDOW)); + colourBack = ColourElement(options.backSelected, COLOR_HIGHLIGHT); + graphics.pixmapLine->FillRectangle(rcBox, colourBack); + colourFore = ColourElement(options.foreSelected, COLOR_HIGHLIGHTTEXT); + } else { + colourBack = ColourElement(options.back, COLOR_WINDOW); + graphics.pixmapLine->FillRectangle(rcItem, colourBack); + colourFore = ColourElement(options.fore, COLOR_WINDOWTEXT); + } -void ListBoxX::Draw(DRAWITEMSTRUCT *pDrawItem) { - if ((pDrawItem->itemAction == ODA_SELECT) || (pDrawItem->itemAction == ODA_DRAWENTIRE)) { - RECT rcBox = pDrawItem->rcItem; - rcBox.left += TextOffset(); - if (pDrawItem->itemState & ODS_SELECTED) { - RECT rcImage = pDrawItem->rcItem; - rcImage.right = rcBox.left; - // The image is not highlighted - FillRectColour(pDrawItem->hDC, &rcImage, ColourOfElement(options.back, COLOR_WINDOW)); - FillRectColour(pDrawItem->hDC, &rcBox, ColourOfElement(options.backSelected, COLOR_HIGHLIGHT)); - ::SetBkColor(pDrawItem->hDC, ColourOfElement(options.backSelected, COLOR_HIGHLIGHT)); - ::SetTextColor(pDrawItem->hDC, ColourOfElement(options.foreSelected, COLOR_HIGHLIGHTTEXT)); - } else { - FillRectColour(pDrawItem->hDC, &pDrawItem->rcItem, ColourOfElement(options.back, COLOR_WINDOW)); - ::SetBkColor(pDrawItem->hDC, ColourOfElement(options.back, COLOR_WINDOW)); - ::SetTextColor(pDrawItem->hDC, ColourOfElement(options.fore, COLOR_WINDOWTEXT)); - } + const ListItemData item = lti.Get(pDrawItem->itemID); + const int pixId = item.pixId; + const char *text = item.text; - const ListItemData item = lti.Get(pDrawItem->itemID); - const int pixId = item.pixId; - const char *text = item.text; - const int len = static_cast<int>(strlen(text)); + const PRectangle rcText = rcBox.Inset(Point(TextInset.x, TextInset.y)); - RECT rcText = rcBox; - ::InsetRect(&rcText, TextInset.x, TextInset.y); + const double ascent = graphics.pixmapLine->Ascent(fontWin.get()); + graphics.pixmapLine->DrawTextClipped(rcText, fontWin.get(), rcText.top + ascent, text, colourFore, colourBack); - if (unicodeMode) { - const TextWide tbuf(text, CpUtf8); - ::DrawTextW(pDrawItem->hDC, tbuf.buffer, tbuf.tlen, &rcText, DT_NOPREFIX|DT_END_ELLIPSIS|DT_SINGLELINE|DT_NOCLIP); - } else { - ::DrawTextA(pDrawItem->hDC, text, len, &rcText, DT_NOPREFIX|DT_END_ELLIPSIS|DT_SINGLELINE|DT_NOCLIP); - } + // Draw the image, if any + const RGBAImage *pimage = images.Get(pixId); + if (pimage) { + const XYPOSITION left = rcItem.left + ItemInset.x + ImageInset.x; + PRectangle rcImage = rcItem; + rcImage.left = left; + rcImage.right = rcImage.left + images.GetWidth(); + graphics.pixmapLine->DrawRGBAImage(rcImage, + pimage->GetWidth(), pimage->GetHeight(), pimage->Pixels()); + } - // Draw the image, if any - const RGBAImage *pimage = images.Get(pixId); - if (pimage) { - std::unique_ptr<Surface> surfaceItem(Surface::Allocate(technology)); - if (technology == Technology::Default) { - surfaceItem->Init(pDrawItem->hDC, pDrawItem->hwndItem); - const long left = pDrawItem->rcItem.left + ItemInset.x + ImageInset.x; - const PRectangle rcImage = PRectangle::FromInts(left, pDrawItem->rcItem.top, - left + images.GetWidth(), pDrawItem->rcItem.bottom); - surfaceItem->DrawRGBAImage(rcImage, - pimage->GetWidth(), pimage->GetHeight(), pimage->Pixels()); - ::SetTextAlign(pDrawItem->hDC, TA_TOP); - } else { #if defined(USE_D2D) - const D2D1_RENDER_TARGET_PROPERTIES props = D2D1::RenderTargetProperties( - D2D1_RENDER_TARGET_TYPE_DEFAULT, - D2D1::PixelFormat( - DXGI_FORMAT_B8G8R8A8_UNORM, - D2D1_ALPHA_MODE_IGNORE), - 0, - 0, - D2D1_RENDER_TARGET_USAGE_NONE, - D2D1_FEATURE_LEVEL_DEFAULT - ); - DCRenderTarget pDCRT; - HRESULT hr = CreateDCRenderTarget(&props, pDCRT); - if (SUCCEEDED(hr) && pDCRT) { - const long left = pDrawItem->rcItem.left + ItemInset.x + ImageInset.x; - - RECT rcItem = pDrawItem->rcItem; - rcItem.left = left; - rcItem.right = rcItem.left + images.GetWidth(); - - hr = pDCRT->BindDC(pDrawItem->hDC, &rcItem); - if (SUCCEEDED(hr)) { - surfaceItem->Init(pDCRT.Get(), pDrawItem->hwndItem); - pDCRT->BeginDraw(); - const PRectangle rcImage = PRectangle::FromInts(0, 0, images.GetWidth(), rcItem.bottom - rcItem.top); - surfaceItem->DrawRGBAImage(rcImage, - pimage->GetWidth(), pimage->GetHeight(), pimage->Pixels()); - pDCRT->EndDraw(); - } - } -#endif - } + if (graphics.pBMDCTarget) { + const HRESULT hrEnd = graphics.pBMDCTarget->EndDraw(); + if (FAILED(hrEnd)) { + return; } } +#endif + + // Blit from hMemDC to hDC + const SIZE extent = SizeOfRect(pDrawItem->rcItem); + ::BitBlt(pDrawItem->hDC, pDrawItem->rcItem.left, pDrawItem->rcItem.top, extent.cx, extent.cy, graphics.hMemDC, 0, 0, SRCCOPY); } void ListBoxX::AppendListItem(const char *text, const char *numword) { @@ -3863,25 +3916,50 @@ void ListBoxX::CentreItem(int n) { } } -// Performs a double-buffered paint operation to avoid flicker -void ListBoxX::Paint(HDC hDC) { - const POINT extent = GetClientExtent(); - HBITMAP hBitmap = ::CreateCompatibleBitmap(hDC, extent.x, extent.y); - HDC bitmapDC = ::CreateCompatibleDC(hDC); - HBITMAP hBitmapOld = SelectBitmap(bitmapDC, hBitmap); - // The list background is mainly erased during painting, but can be a small - // unpainted area when at the end of a non-integrally sized list with a - // vertical scroll bar - const RECT rc = { 0, 0, extent.x, extent.y }; - FillRectColour(bitmapDC, &rc, ColourOfElement(options.back, COLOR_WINDOWTEXT)); - // Paint the entire client area and vertical scrollbar - ::SendMessage(lb, WM_PRINT, reinterpret_cast<WPARAM>(bitmapDC), PRF_CLIENT|PRF_NONCLIENT); - ::BitBlt(hDC, 0, 0, extent.x, extent.y, bitmapDC, 0, 0, SRCCOPY); - // Select a stock brush to prevent warnings from BoundsChecker - SelectBrush(bitmapDC, GetStockBrush(WHITE_BRUSH)); - SelectBitmap(bitmapDC, hBitmapOld); - ::DeleteDC(bitmapDC); - ::DeleteObject(hBitmap); +void ListBoxX::AllocateBitMap() { + graphics.hMemDC = ::CreateCompatibleDC({}); + if (!graphics.hMemDC) { + return; + } + + const SIZE extent { GetClientExtent().x, lineHeight }; + + graphics.hBitmap = BitMapSection(graphics.hMemDC, extent.cx, -extent.cy, nullptr); + if (!graphics.hBitmap) { + return; + } + graphics.hOldBitmap = SelectBitmap(graphics.hMemDC, graphics.hBitmap); + + // Make a surface + graphics.pixmapLine = Surface::Allocate(technology); + graphics.pixmapLine->SetMode(SurfaceMode(codePage, false)); + +#if defined(USE_D2D) + if (technology != Technology::Default) { + if (!LoadD2D()) { + return; + } + + const D2D1_RENDER_TARGET_PROPERTIES drtp = D2D1::RenderTargetProperties( + D2D1_RENDER_TARGET_TYPE_DEFAULT, + { DXGI_FORMAT_B8G8R8A8_UNORM, D2D1_ALPHA_MODE_PREMULTIPLIED }); + + HRESULT hr = CreateDCRenderTarget(&drtp, graphics.pBMDCTarget); + if (FAILED(hr) || !graphics.pBMDCTarget) { + return; + } + + const RECT rcExtent = { 0, 0, extent.cx, extent.cy }; + hr = graphics.pBMDCTarget->BindDC(graphics.hMemDC, &rcExtent); + if (SUCCEEDED(hr)) { + graphics.pixmapLine->Init(graphics.pBMDCTarget.Get(), GetID()); + } + return; + } +#endif + + // Either technology == Technology::Default or USE_D2D turned off + graphics.pixmapLine->Init(graphics.hMemDC, GetID()); } LRESULT PASCAL ListBoxX::ControlWndProc(HWND hWnd, UINT iMessage, WPARAM wParam, LPARAM lParam) { @@ -3891,16 +3969,6 @@ LRESULT PASCAL ListBoxX::ControlWndProc(HWND hWnd, UINT iMessage, WPARAM wParam, case WM_ERASEBKGND: return TRUE; - case WM_PAINT: { - PAINTSTRUCT ps; - HDC hDC = ::BeginPaint(hWnd, &ps); - if (lbx) { - lbx->Paint(hDC); - } - ::EndPaint(hWnd, &ps); - } - return 0; - case WM_MOUSEACTIVATE: // This prevents the view activating when the scrollbar is clicked return MA_NOACTIVATE; @@ -3967,6 +4035,7 @@ LRESULT ListBoxX::WndProc(HWND hWnd, UINT iMessage, WPARAM wParam, LPARAM lParam case WM_SIZE: if (lb) { + graphics.Release(); // Bitmap must be reallocated to new size. SetRedraw(false); ::SetWindowPos(lb, {}, 0, 0, LOWORD(lParam), HIWORD(lParam), SWP_NOZORDER | SWP_NOACTIVATE | SWP_NOMOVE); // Ensure the selection remains visible |