Skip to content

Commit

Permalink
Add D2D reverse arrow cursor drawing code, issue #635.
Browse files Browse the repository at this point in the history
  • Loading branch information
zufuliu committed May 5, 2024
1 parent 52aaec8 commit 1291ba3
Showing 1 changed file with 230 additions and 92 deletions.
322 changes: 230 additions & 92 deletions scintilla/win32/PlatWin.cxx
Original file line number Diff line number Diff line change
Expand Up @@ -249,7 +249,11 @@ static void LoadD2DOnce() noexcept
bool LoadD2D() noexcept {
#if USE_STD_CALL_ONCE
static std::once_flag once;
std::call_once(once, LoadD2DOnce);
try {
std::call_once(once, LoadD2DOnce);
} catch (...) {
// ignore
}
#elif USE_WIN32_INIT_ONCE
static INIT_ONCE once = INIT_ONCE_STATIC_INIT;
::InitOnceExecuteOnce(&once, LoadD2DOnce, nullptr, nullptr);
Expand Down Expand Up @@ -869,8 +873,8 @@ class DIBSection {
HDC hMemDC{};
HBITMAP hbmMem{};
HBITMAP hbmOld{};
SIZE size{};
DWORD *pixels = nullptr;
const SIZE size;
public:
DIBSection(HDC hdc, SIZE size_) noexcept;
// Deleted so DIBSection objects can not be copied.
Expand All @@ -880,7 +884,7 @@ class DIBSection {
DIBSection &operator=(DIBSection&&) = delete;
~DIBSection() noexcept;
operator bool() const noexcept {
return hbmMem != nullptr;
return hbmOld != nullptr;
}
DWORD *Pixels() const noexcept {
return pixels;
Expand All @@ -901,21 +905,18 @@ class DIBSection {
void SetSymmetric(LONG x, LONG y, DWORD value) noexcept;
};

DIBSection::DIBSection(HDC hdc, SIZE size_) noexcept {
DIBSection::DIBSection(HDC hdc, SIZE size_) noexcept : size{size_} {
hMemDC = ::CreateCompatibleDC(hdc);
if (!hMemDC) {
return;
}

size = size_;

// -size.y makes bitmap start from top
const BITMAPINFO bpih = { {sizeof(BITMAPINFOHEADER), size.cx, -size.cy, 1, 32, BI_RGB, 0, 0, 0, 0, 0}, {{0, 0, 0, 0}} };
hbmMem = CreateDIBSection(hMemDC, &bpih, DIB_RGB_COLORS, reinterpret_cast<void **>(&pixels), nullptr, 0);
if (!hbmMem) {
return;
if (hbmMem) {
hbmOld = SelectBitmap(hMemDC, hbmMem);
}
hbmOld = SelectBitmap(hMemDC, hbmMem);
}

DIBSection::~DIBSection() noexcept {
Expand Down Expand Up @@ -3002,6 +3003,220 @@ std::optional<DWORD> RegGetDWORD(HKEY hKey, LPCWSTR valueName) noexcept {
return {};
}
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(int width_, int height_) noexcept : width{width_}, height{height_} {
hMemDC = ::CreateCompatibleDC({});
if (!hMemDC) {
return;
}
// 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;
}
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;
}
using BrushSolid = std::unique_ptr<ID2D1SolidColorBrush, UnknownReleaser>;
static 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);
}
bool DrawD2D(COLORREF fillColour, COLORREF strokeColour) noexcept {
if (!LoadD2D()) {
return false;
}
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
);
ID2D1DCRenderTarget *pTarget_ = nullptr;
HRESULT hr = pD2DFactory->CreateDCRenderTarget(&drtp, &pTarget_);
if (FAILED(hr) || !pTarget_) {
return false;
}
const std::unique_ptr<ID2D1DCRenderTarget, UnknownReleaser> pTarget(pTarget_);
const RECT rc = {0, 0, width, height};
hr = pTarget_->BindDC(hMemDC, &rc);
if (FAILED(hr)) {
return false;
}
pTarget->BeginDraw();
// Clear to transparent
// pTarget->Clear(D2D1::ColorF(0.0, 0.0, 0.0, 0.0));
// 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;
}
ID2D1PathGeometry *geometry_ = nullptr;
hr = pD2DFactory->CreatePathGeometry(&geometry_);
if (FAILED(hr) || !geometry_) {
return false;
}
const std::unique_ptr<ID2D1PathGeometry, UnknownReleaser> geometry(geometry_);
ID2D1GeometrySink *sink_ = nullptr;
hr = geometry->Open(&sink_);
if (FAILED(hr) || !sink_) {
return false;
}
const std::unique_ptr<ID2D1GeometrySink, UnknownReleaser> sink(sink_);
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);
}
void Draw(COLORREF fillColour, COLORREF strokeColour) noexcept {
if (DrawD2D(fillColour, strokeColour)) {
return;
}
// 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 LoadReverseArrowCursor(HCURSOR cursor, UINT dpi) noexcept {
Expand Down Expand Up @@ -3030,112 +3245,35 @@ HCURSOR LoadReverseArrowCursor(HCURSOR cursor, UINT dpi) noexcept {
PLATFORM_ASSERT(width == height);
}
HDC hMemDC = ::CreateCompatibleDC({});
if (!hMemDC) {
CursorHelper cursorHelper(width, height);
if (!cursorHelper.HasBitmap()) {
return {};
}
// 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.
DWORD *pixels = nullptr;
HBITMAP hBitmap = CreateDIBSection(hMemDC, reinterpret_cast<BITMAPINFO *>(&bi), DIB_RGB_COLORS, reinterpret_cast<void **>(&pixels), nullptr, 0);
if (!hBitmap) {
::DeleteDC(hMemDC);
return {};
}
// Draw something on the DIB section.
HBITMAP hOldBitmap = SelectBitmap(hMemDC, hBitmap);
// polygon colour and coordinates from C:\Windows\Cursors\arrow.svg
COLORREF fillColour = RGB(0xff, 0xff, 0xfe);
COLORREF strokeColour = RGB(0, 0, 1);
static constexpr float arrow[] = {
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,
};
status = ::RegOpenKeyExW(HKEY_CURRENT_USER, L"Software\\Microsoft\\Accessibility", 0, KEY_QUERY_VALUE, &hKey);
if (status == ERROR_SUCCESS) {
if (auto cursorType = RegGetDWORD(hKey, L"CursorType")) {
switch (*cursorType) {
case 1: // black
case 4: // black
std::swap(fillColour, strokeColour);
break;
case 6: // custom
if (auto cursorColor = RegGetDWORD(hKey, L"CursorColor")) {
fillColour = *cursorColor;
}
break;
default: // 0 white, 2 invert
default: // 0, 3 white, 2, 5 invert
break;
}
}
::RegCloseKey(hKey);
}
POINT points[std::size(arrow)/2];
const float scale = width/32.0f;
for (size_t i = 0, index = 0; i < std::size(arrow); i += 2, index++) {
points[index].x = std::lround(arrow[i] * scale);
points[index].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>(std::size(points)));
SelectPen(hMemDC, penOld);
SelectBrush(hMemDC, brushOld);
SelectBitmap(hMemDC, hOldBitmap);
::DeleteObject(pen);
::DeleteObject(brush);
::DeleteDC(hMemDC);
// Set the alpha values for each pixel in the cursor.
for (int i = 0; i < width*height; i++) {
if (*pixels != 0) {
*pixels |= 0xFF000000U;
}
pixels++;
}
// Create an empty mask bitmap.
HBITMAP hMonoBitmap = ::CreateBitmap(width, height, 1, 1, nullptr);
ICONINFO info = {false, static_cast<DWORD>(width - 1), 0, hMonoBitmap, hBitmap};
cursor = ::CreateIconIndirect(&info);
::DeleteObject(hMonoBitmap);
::DeleteObject(hBitmap);
cursorHelper.Draw(fillColour, strokeColour);
cursor = cursorHelper.Create();
return cursor;
}
#endif // reverse arrow cursor
Expand Down

0 comments on commit 1291ba3

Please sign in to comment.