diff options
author | Zufu Liu <unknown> | 2024-05-09 11:33:05 +1000 |
---|---|---|
committer | Zufu Liu <unknown> | 2024-05-09 11:33:05 +1000 |
commit | d37feef72ce5c132f8fc1cc2cbadf3ee027a5471 (patch) | |
tree | 212a3721d26742f718d343104d0a4519bbf5ab8d | |
parent | b9e09f34badc402f80a4a53c507576affd97b8bc (diff) | |
download | scintilla-mirror-d37feef72ce5c132f8fc1cc2cbadf3ee027a5471.tar.gz |
Bug [#2321]. Scale reverse arrow cursor with Direct2D when enabled.
Implement arrow cursor colour choice.
React to most settings changes.
-rw-r--r-- | win32/PlatWin.cxx | 347 | ||||
-rw-r--r-- | win32/PlatWin.h | 3 | ||||
-rw-r--r-- | win32/ScintillaWin.cxx | 37 |
3 files changed, 292 insertions, 95 deletions
diff --git a/win32/PlatWin.cxx b/win32/PlatWin.cxx index 144077b95..2a5553f68 100644 --- a/win32/PlatWin.cxx +++ b/win32/PlatWin.cxx @@ -2786,110 +2786,316 @@ void Window::InvalidateRectangle(PRectangle rc) { ::InvalidateRect(HwndFromWindowID(wid), &rcw, FALSE); } -HCURSOR LoadReverseArrowCursor(UINT dpi, int cursorBaseSize) noexcept { - class CursorHelper { - public: - ICONINFO info{}; - BITMAP bmp{}; - bool HasBitmap() const noexcept { - return bmp.bmWidth > 0; +namespace { + +std::optional<DWORD> RegGetDWORD(HKEY hKey, LPCWSTR valueName) noexcept { + DWORD value = 0; + DWORD type = REG_NONE; + DWORD size = sizeof(DWORD); + const LSTATUS status = ::RegQueryValueExW(hKey, valueName, nullptr, &type, reinterpret_cast<LPBYTE>(&value), &size); + if (status == ERROR_SUCCESS && type == REG_DWORD) { + return value; + } + return {}; +} + +#if defined(USE_D2D) + +using BrushSolid = std::unique_ptr<ID2D1SolidColorBrush, UnknownReleaser>; + +BrushSolid BrushSolidCreate(ID2D1RenderTarget *pTarget, COLORREF colour) noexcept { + ID2D1SolidColorBrush *pBrush = nullptr; + const D2D_COLOR_F col = ColorFromColourAlpha(ColourRGBA::FromRGB(colour)); + const HRESULT hr = pTarget->CreateSolidColorBrush(col, &pBrush); + if (FAILED(hr) || !pBrush) { + return {}; + } + return BrushSolid(pBrush); +} + +using Geometry = std::unique_ptr<ID2D1PathGeometry, UnknownReleaser>; + +Geometry GeometryCreate() noexcept { + ID2D1PathGeometry *geometry = nullptr; + const HRESULT hr = pD2DFactory->CreatePathGeometry(&geometry); + if (FAILED(hr) || !geometry) { + return {}; + } + return Geometry(geometry); +} + +using GeometrySink = std::unique_ptr<ID2D1GeometrySink, UnknownReleaser>; + +GeometrySink GeometrySinkCreate(ID2D1PathGeometry *geometry) noexcept { + ID2D1GeometrySink *sink = nullptr; + const HRESULT hr = geometry->Open(&sink); + if (FAILED(hr) || !sink) { + return {}; + } + return GeometrySink(sink); +} + +#endif + +class CursorHelper { + HDC hMemDC {}; + HBITMAP hBitmap {}; + HBITMAP hOldBitmap {}; + DWORD *pixels = nullptr; + const int width; + const int height; + + static constexpr float arrow[][2] = { + { 32.0f - 12.73606f,32.0f - 19.04075f }, + { 32.0f - 7.80159f, 32.0f - 19.04075f }, + { 32.0f - 9.82813f, 32.0f - 14.91828f }, + { 32.0f - 6.88341f, 32.0f - 13.42515f }, + { 32.0f - 4.62301f, 32.0f - 18.05872f }, + { 32.0f - 1.26394f, 32.0f - 14.78295f }, + { 32.0f - 1.26394f, 32.0f - 30.57485f }, + }; + +public: + ~CursorHelper() { + if (hOldBitmap) { + SelectBitmap(hMemDC, hOldBitmap); + } + if (hBitmap) { + ::DeleteObject(hBitmap); + } + if (hMemDC) { + ::DeleteDC(hMemDC); } + } - CursorHelper(const HCURSOR cursor) noexcept { - Init(cursor); + CursorHelper(int width_, int height_) noexcept : width{width_}, height{height_} { + hMemDC = ::CreateCompatibleDC({}); + if (!hMemDC) { + return; } - ~CursorHelper() { - CleanUp(); + + // https://learn.microsoft.com/en-us/windows/win32/menurc/using-cursors#creating-a-cursor + BITMAPV5HEADER bi {}; + bi.bV5Size = sizeof(BITMAPV5HEADER); + bi.bV5Width = width; + bi.bV5Height = height; + bi.bV5Planes = 1; + bi.bV5BitCount = 32; + bi.bV5Compression = BI_BITFIELDS; + // The following mask specification specifies a supported 32 BPP alpha format for Windows XP. + bi.bV5RedMask = 0x00FF0000U; + bi.bV5GreenMask = 0x0000FF00U; + bi.bV5BlueMask = 0x000000FFU; + bi.bV5AlphaMask = 0xFF000000U; + + // Create the DIB section with an alpha channel. + hBitmap = CreateDIBSection(hMemDC, reinterpret_cast<BITMAPINFO *>(&bi), DIB_RGB_COLORS, reinterpret_cast<void **>(&pixels), nullptr, 0); + if (hBitmap) { + hOldBitmap = SelectBitmap(hMemDC, hBitmap); } + } + + bool HasBitmap() const noexcept { + return hOldBitmap != nullptr; + } - CursorHelper &operator=(const HCURSOR cursor) noexcept { - CleanUp(); - Init(cursor); - return *this; + HCURSOR Create() noexcept { + HCURSOR cursor {}; + // Create an empty mask bitmap. + HBITMAP hMonoBitmap = ::CreateBitmap(width, height, 1, 1, nullptr); + if (hMonoBitmap) { + // https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-createiconindirect + // hBitmap should not already be selected into a device context + SelectBitmap(hMemDC, hOldBitmap); + hOldBitmap = {}; + ICONINFO info = {false, static_cast<DWORD>(width - 1), 0, hMonoBitmap, hBitmap}; + cursor = ::CreateIconIndirect(&info); + ::DeleteObject(hMonoBitmap); } + return cursor; + } - bool MatchesSize(const int width, const int height) noexcept { - return bmp.bmWidth == width && bmp.bmHeight == height; +#if defined(USE_D2D) + + bool DrawD2D(COLORREF fillColour, COLORREF strokeColour) noexcept { + if (!LoadD2D()) { + return false; } - HCURSOR CreateFlippedCursor() noexcept { - if (info.hbmMask) - FlipBitmap(info.hbmMask, bmp.bmWidth, bmp.bmHeight); - if (info.hbmColor) - FlipBitmap(info.hbmColor, bmp.bmWidth, bmp.bmHeight); - info.xHotspot = bmp.bmWidth - 1 - info.xHotspot; + D2D1_RENDER_TARGET_PROPERTIES drtp {}; + drtp.type = D2D1_RENDER_TARGET_TYPE_DEFAULT; + drtp.usage = D2D1_RENDER_TARGET_USAGE_NONE; + drtp.minLevel = D2D1_FEATURE_LEVEL_DEFAULT; + drtp.dpiX = 96.f; + drtp.dpiY = 96.f; + drtp.pixelFormat = D2D1::PixelFormat( + DXGI_FORMAT_B8G8R8A8_UNORM, + D2D1_ALPHA_MODE_PREMULTIPLIED + ); - return ::CreateIconIndirect(&info); + ID2D1DCRenderTarget *pTarget_ = nullptr; + HRESULT hr = pD2DFactory->CreateDCRenderTarget(&drtp, &pTarget_); + if (FAILED(hr) || !pTarget_) { + return false; } + const std::unique_ptr<ID2D1DCRenderTarget, UnknownReleaser> pTarget(pTarget_); - private: - void Init(const HCURSOR &cursor) noexcept { - if (::GetIconInfo(cursor, &info)) { - ::GetObject(info.hbmMask, sizeof(bmp), &bmp); - PLATFORM_ASSERT(HasBitmap()); - } + const RECT rc = {0, 0, width, height}; + hr = pTarget_->BindDC(hMemDC, &rc); + if (FAILED(hr)) { + return false; } - void CleanUp() noexcept { - if (info.hbmMask) - ::DeleteObject(info.hbmMask); - if (info.hbmColor) - ::DeleteObject(info.hbmColor); - info = {}; - bmp = {}; + pTarget->BeginDraw(); + + // Draw something on the bitmap section. + constexpr size_t nPoints = std::size(arrow); + D2D1_POINT_2F points[nPoints]{}; + const FLOAT scale = width/32.0f; + for (size_t i = 0; i < nPoints; i++) { + points[i].x = arrow[i][0] * scale; + points[i].y = arrow[i][1] * scale; } - static void FlipBitmap(const HBITMAP bitmap, const int width, const int height) noexcept { - HDC hdc = ::CreateCompatibleDC({}); - if (hdc) { - HBITMAP prevBmp = SelectBitmap(hdc, bitmap); - ::StretchBlt(hdc, width - 1, 0, -width, height, hdc, 0, 0, width, height, SRCCOPY); - SelectBitmap(hdc, prevBmp); - ::DeleteDC(hdc); + Geometry geometry = GeometryCreate(); + if (!geometry) { + return false; + } + + GeometrySink sink = GeometrySinkCreate(geometry.get()); + if (!sink) { + return false; + } + + sink->BeginFigure(points[0], D2D1_FIGURE_BEGIN_FILLED); + for (size_t i = 1; i < nPoints; i++) { + sink->AddLine(points[i]); + } + sink->EndFigure(D2D1_FIGURE_END_CLOSED); + hr = sink->Close(); + if (FAILED(hr)) { + return false; + } + + if (const BrushSolid pBrushFill = BrushSolidCreate(pTarget.get(), fillColour)) { + pTarget->FillGeometry(geometry.get(), pBrushFill.get()); + } + + if (const BrushSolid pBrushStroke = BrushSolidCreate(pTarget.get(), strokeColour)) { + pTarget->DrawGeometry(geometry.get(), pBrushStroke.get(), scale); + } + + hr = pTarget->EndDraw(); + return SUCCEEDED(hr); + } +#endif + + void Draw(COLORREF fillColour, COLORREF strokeColour) noexcept { +#if defined(USE_D2D) + if (DrawD2D(fillColour, strokeColour)) { + return; + } +#endif + + // Draw something on the DIB section. + constexpr size_t nPoints = std::size(arrow); + POINT points[nPoints]{}; + const float scale = width/32.0f; + for (size_t i = 0; i < nPoints; i++) { + points[i].x = std::lround(arrow[i][0] * scale); + points[i].y = std::lround(arrow[i][1] * scale); + } + + const DWORD penWidth = std::lround(scale); + HPEN pen; + if (penWidth > 1) { + const LOGBRUSH brushParameters { BS_SOLID, strokeColour, 0 }; + pen = ::ExtCreatePen(PS_GEOMETRIC | PS_ENDCAP_ROUND | PS_JOIN_MITER, + penWidth, + &brushParameters, + 0, + nullptr); + } else { + pen = ::CreatePen(PS_INSIDEFRAME, 1, strokeColour); + } + + HPEN penOld = SelectPen(hMemDC, pen); + HBRUSH brush = ::CreateSolidBrush(fillColour); + HBRUSH brushOld = SelectBrush(hMemDC, brush); + ::Polygon(hMemDC, points, static_cast<int>(nPoints)); + SelectPen(hMemDC, penOld); + SelectBrush(hMemDC, brushOld); + ::DeleteObject(pen); + ::DeleteObject(brush); + + // Set the alpha values for each pixel in the cursor. + for (int i = 0; i < width*height; i++) { + if (*pixels != 0) { + *pixels |= 0xFF000000U; } + pixels++; } - }; + } +}; + +} - HCURSOR reverseArrowCursor {}; +HCURSOR LoadReverseArrowCursor(UINT dpi) noexcept { + // https://learn.microsoft.com/en-us/answers/questions/815036/windows-cursor-size + constexpr DWORD defaultCursorBaseSize = 32; + constexpr DWORD maxCursorBaseSize = 16*(1 + 15); // 16*(1 + CursorSize) + DWORD cursorBaseSize = 0; + HKEY hKey {}; + LSTATUS status = ::RegOpenKeyExW(HKEY_CURRENT_USER, L"Control Panel\\Cursors", 0, KEY_QUERY_VALUE, &hKey); + if (status == ERROR_SUCCESS) { + if (std::optional<DWORD> baseSize = RegGetDWORD(hKey, L"CursorBaseSize")) { + // CursorBaseSize is multiple of 16 + cursorBaseSize = std::min(*baseSize & ~15, maxCursorBaseSize); + } + ::RegCloseKey(hKey); + } - int width; - int height; + int width = 0; + int height = 0; if (cursorBaseSize > defaultCursorBaseSize) { width = ::MulDiv(cursorBaseSize, dpi, USER_DEFAULT_SCREEN_DPI); height = width; } else { width = SystemMetricsForDpi(SM_CXCURSOR, dpi); height = SystemMetricsForDpi(SM_CYCURSOR, dpi); + PLATFORM_ASSERT(width == height); } - DPI_AWARENESS_CONTEXT oldContext = nullptr; - if (fnAreDpiAwarenessContextsEqual && fnAreDpiAwarenessContextsEqual(fnGetThreadDpiAwarenessContext(), DPI_AWARENESS_CONTEXT_UNAWARE_GDISCALED)) { - oldContext = fnSetThreadDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2); - PLATFORM_ASSERT(oldContext != nullptr); + CursorHelper cursorHelper(width, height); + if (!cursorHelper.HasBitmap()) { + return {}; } - const HCURSOR cursor = static_cast<HCURSOR>(::LoadImage({}, IDC_ARROW, IMAGE_CURSOR, width, height, LR_SHARED)); - if (cursor) { - CursorHelper cursorHelper(cursor); - - if (cursorHelper.HasBitmap() && !cursorHelper.MatchesSize(width, height)) { - const HCURSOR copy = static_cast<HCURSOR>(::CopyImage(cursor, IMAGE_CURSOR, width, height, LR_COPYFROMRESOURCE | LR_COPYRETURNORG)); - if (copy) { - cursorHelper = copy; - ::DestroyCursor(copy); + COLORREF fillColour = RGB(0xff, 0xff, 0xfe); + COLORREF strokeColour = RGB(0, 0, 1); + status = ::RegOpenKeyExW(HKEY_CURRENT_USER, L"Software\\Microsoft\\Accessibility", 0, KEY_QUERY_VALUE, &hKey); + if (status == ERROR_SUCCESS) { + if (std::optional<DWORD> cursorType = RegGetDWORD(hKey, L"CursorType")) { + switch (*cursorType) { + case 1: // black + case 4: // black + std::swap(fillColour, strokeColour); + break; + case 6: // custom + if (std::optional<DWORD> cursorColor = RegGetDWORD(hKey, L"CursorColor")) { + fillColour = *cursorColor; + } + break; + default: // 0, 3 white, 2, 5 invert + break; } } - - if (cursorHelper.HasBitmap()) { - reverseArrowCursor = cursorHelper.CreateFlippedCursor(); - } + ::RegCloseKey(hKey); } - if (oldContext) { - fnSetThreadDpiAwarenessContext(oldContext); - } - - return reverseArrowCursor; + cursorHelper.Draw(fillColour, strokeColour); + HCURSOR cursor = cursorHelper.Create(); + return cursor; } void Window::SetCursor(Cursor curs) { @@ -2915,6 +3121,7 @@ void Window::SetCursor(Cursor curs) { case Cursor::reverseArrow: case Cursor::arrow: case Cursor::invalid: // Should not occur, but just in case. + default: ::SetCursor(::LoadCursor(NULL,IDC_ARROW)); break; } diff --git a/win32/PlatWin.h b/win32/PlatWin.h index a901ec78d..ec1281979 100644 --- a/win32/PlatWin.h +++ b/win32/PlatWin.h @@ -50,8 +50,7 @@ float GetDeviceScaleFactorWhenGdiScalingActive(HWND hWnd) noexcept; int SystemMetricsForDpi(int nIndex, UINT dpi) noexcept; -constexpr int defaultCursorBaseSize = 32; -HCURSOR LoadReverseArrowCursor(UINT dpi, int cursorBaseSize) noexcept; +HCURSOR LoadReverseArrowCursor(UINT dpi) noexcept; class MouseWheelDelta { int wheelDelta = 0; diff --git a/win32/ScintillaWin.cxx b/win32/ScintillaWin.cxx index 5e2532c84..decf0e8f6 100644 --- a/win32/ScintillaWin.cxx +++ b/win32/ScintillaWin.cxx @@ -293,9 +293,8 @@ public: class GlobalMemory; class ReverseArrowCursor { - UINT dpi = USER_DEFAULT_SCREEN_DPI; - UINT cursorBaseSize = defaultCursorBaseSize; HCURSOR cursor {}; + bool valid = false; public: ReverseArrowCursor() noexcept {} @@ -310,17 +309,20 @@ public: } } - HCURSOR Load(UINT dpi_, UINT cursorBaseSize_) noexcept { + void Invalidate() noexcept { + valid = false; + } + + HCURSOR Load(UINT dpi) noexcept { if (cursor) { - if (dpi == dpi_ && cursorBaseSize == cursorBaseSize_) { + if (valid) { return cursor; } ::DestroyCursor(cursor); } - dpi = dpi_; - cursorBaseSize = cursorBaseSize_; - cursor = LoadReverseArrowCursor(dpi_, cursorBaseSize_); + valid = true; + cursor = LoadReverseArrowCursor(dpi); return cursor ? cursor : ::LoadCursor({}, IDC_ARROW); } }; @@ -361,7 +363,6 @@ class ScintillaWin : MouseWheelDelta horizontalWheelDelta; UINT dpi = USER_DEFAULT_SCREEN_DPI; - UINT cursorBaseSize = defaultCursorBaseSize; ReverseArrowCursor reverseArrowCursor; PRectangle rectangleClient; @@ -794,7 +795,7 @@ void ScintillaWin::DisplayCursor(Window::Cursor c) { c = static_cast<Window::Cursor>(cursorMode); } if (c == Window::Cursor::reverseArrow) { - ::SetCursor(reverseArrowCursor.Load(static_cast<UINT>(dpi * deviceScaleFactor), cursorBaseSize)); + ::SetCursor(reverseArrowCursor.Load(static_cast<UINT>(dpi * deviceScaleFactor))); } else { wMain.SetCursor(c); } @@ -2134,6 +2135,7 @@ sptr_t ScintillaWin::WndProc(Message iMessage, uptr_t wParam, sptr_t lParam) { case WM_DPICHANGED: dpi = HIWORD(wParam); + reverseArrowCursor.Invalidate(); InvalidateStyleRedraw(); break; @@ -2141,6 +2143,7 @@ sptr_t ScintillaWin::WndProc(Message iMessage, uptr_t wParam, sptr_t lParam) { const UINT dpiNow = DpiForWindow(wMain.GetID()); if (dpi != dpiNow) { dpi = dpiNow; + reverseArrowCursor.Invalidate(); InvalidateStyleRedraw(); } } @@ -3216,6 +3219,8 @@ LRESULT ScintillaWin::ImeOnDocumentFeed(LPARAM lParam) const { } void ScintillaWin::GetMouseParameters() noexcept { + // mouse pointer size and colour may changed + reverseArrowCursor.Invalidate(); // This retrieves the number of lines per scroll as configured in the Mouse Properties sheet in Control Panel ::SystemParametersInfo(SPI_GETWHEELSCROLLLINES, 0, &linesPerScroll, 0); if (!::SystemParametersInfo(SPI_GETWHEELSCROLLCHARS, 0, &charsPerScroll, 0)) { @@ -3223,20 +3228,6 @@ void ScintillaWin::GetMouseParameters() noexcept { charsPerScroll = (linesPerScroll == WHEEL_PAGESCROLL) ? 3 : linesPerScroll; } ::SystemParametersInfo(SPI_GETMOUSEVANISH, 0, &typingWithoutCursor, 0); - - // https://learn.microsoft.com/en-us/answers/questions/815036/windows-cursor-size - HKEY hKey; - LSTATUS status = ::RegOpenKeyExW(HKEY_CURRENT_USER, L"Control Panel\\Cursors", 0, KEY_READ, &hKey); - if (status == ERROR_SUCCESS) { - DWORD baseSize = 0; - DWORD type = REG_DWORD; - DWORD size = sizeof(DWORD); - status = ::RegQueryValueExW(hKey, L"CursorBaseSize", nullptr, &type, reinterpret_cast<LPBYTE>(&baseSize), &size); - if (status == ERROR_SUCCESS && type == REG_DWORD) { - cursorBaseSize = baseSize; - } - ::RegCloseKey(hKey); - } } void ScintillaWin::CopyToGlobal(GlobalMemory &gmUnicode, const SelectionText &selectedText) { |