[MSPAINT] Update cursor shape for brush and rubber (#8974)

Improve UI/UX by updating mouse
cursor shape.
JIRA issue: CORE-20520
- Add CStyledCursor class.
- Enhance CCanvasWindow::OnSetCursor
  for TOOL_BRUSH and TOOL_RUBBER.
- Add CCanvasWindow::m_hBrushCursor
  CCanvasWindow::m_hRubberCursor to
  keep cursor shapes.
- Send WM_SETCURSOR from ToolsModel
  to update the cursor shape.
Co-authored-by: Hermès BÉLUSCA - MAÏTO <[email protected]>
This commit is contained in:
Katayama Hirofumi MZ
2026-05-19 10:23:58 +09:00
committed by GitHub
parent 0e64048812
commit 146e1415d0
4 changed files with 192 additions and 1 deletions
+147
View File
@@ -3,6 +3,7 @@
* LICENSE: LGPL-2.0-or-later (https://spdx.org/licenses/LGPL-2.0-or-later)
* PURPOSE: Providing the canvas window class
* COPYRIGHT: Copyright 2015 Benedikt Freisen <[email protected]>
* Copyright 2026 Katayama Hirofumi MZ <[email protected]>
*/
#include "precomp.h"
@@ -11,6 +12,137 @@ CCanvasWindow canvasWindow;
/* FUNCTIONS ********************************************************/
HCURSOR
CStyledCursor::CreateStyledCursor(BrushStyle style, INT radius, COLORREF color, BOOL is_rubber)
{
const INT diameter = 2 * radius;
if (diameter <= 2)
{
HCURSOR hCursor = ::LoadCursor(NULL, IDC_CROSS);
return hCursor ? CopyCursor(hCursor) : NULL;
}
const INT crosshair1 = 6, crosshair2 = crosshair1 - 2;
const INT width = diameter + 2 * crosshair1, height = diameter + 2 * crosshair1;
const DWORD hotX = width / 2, hotY = height / 2;
HDC hdcScreen = ::GetDC(NULL);
if (!hdcScreen)
return NULL;
HDC hdcMem = ::CreateCompatibleDC(hdcScreen);
if (!hdcMem)
{
::ReleaseDC(NULL, hdcScreen);
return NULL;
}
RECT rc = { 0, 0, width, height };
// Create the AND mask bitmap. This must be monochrome (1bpp):
// white bits are transparent, black bits are opaque.
HBITMAP hbmMask = ::CreateBitmap(width, height, 1, 1, NULL);
if (hbmMask)
{
HBITMAP hbmOld = (HBITMAP)::SelectObject(hdcMem, hbmMask);
// Fill with white brush
::FillRect(hdcMem, &rc, (HBRUSH)::GetStockObject(WHITE_BRUSH));
if (!is_rubber)
{
// Draw crosshair with white pen
::SelectObject(hdcMem, (HPEN)::GetStockObject(WHITE_PEN));
::MoveToEx(hdcMem, 0, hotY, NULL);
::LineTo(hdcMem, crosshair2, hotY);
::MoveToEx(hdcMem, width - crosshair2, hotY, NULL);
::LineTo(hdcMem, width, hotY);
::MoveToEx(hdcMem, hotX, 0, NULL);
::LineTo(hdcMem, hotX, crosshair2);
::MoveToEx(hdcMem, hotX, height - crosshair2, NULL);
::LineTo(hdcMem, hotX, height);
}
// Draw brush or erase with black color
if (is_rubber)
Erase(hdcMem, hotX, hotY, hotX, hotY, RGB(0, 0, 0), radius + 1);
else
Brush(hdcMem, hotX, hotY, hotX, hotY, RGB(0, 0, 0), style, diameter);
if (is_rubber)
InflateRect(&rc, -1, -1);
::SelectObject(hdcMem, hbmOld);
}
// Create the color (XOR) bitmap
HBITMAP hbmColor = ::CreateCompatibleBitmap(hdcScreen, width, height);
if (hbmColor)
{
HBITMAP hbmOld = (HBITMAP)::SelectObject(hdcMem, hbmColor);
// Fill with black brush
::FillRect(hdcMem, &rc, (HBRUSH)::GetStockObject(BLACK_BRUSH));
if (is_rubber)
{
// Draw for rubber border
INT avg = (GetRValue(color) + GetGValue(color) + GetBValue(color)) / 3;
COLORREF color2 = (avg > 255 / 2) ? RGB(0, 0, 0) : RGB(255, 255, 255);
Erase(hdcMem, hotX, hotY, hotX, hotY, color2, radius + 1);
}
else
{
// Draw crosshair with white pen
::SelectObject(hdcMem, (HPEN)::GetStockObject(WHITE_PEN));
::MoveToEx(hdcMem, 0, hotY, NULL);
::LineTo(hdcMem, crosshair2, hotY);
::MoveToEx(hdcMem, width - crosshair2, hotY, NULL);
::LineTo(hdcMem, width, hotY);
::MoveToEx(hdcMem, hotX, 0, NULL);
::LineTo(hdcMem, hotX, crosshair2);
::MoveToEx(hdcMem, hotX, height - crosshair2, NULL);
::LineTo(hdcMem, hotX, height);
}
// Draw brush or erase with color
if (is_rubber)
Erase(hdcMem, hotX, hotY, hotX, hotY, color, radius);
else
Brush(hdcMem, hotX, hotY, hotX, hotY, color, style, diameter);
::SelectObject(hdcMem, hbmOld);
}
::ReleaseDC(NULL, hdcScreen);
::DeleteDC(hdcMem);
ICONINFO ii = { FALSE, hotX, hotY, hbmMask, hbmColor };
HCURSOR hCursor = (HCURSOR)::CreateIconIndirect(&ii);
::DeleteObject(hbmMask);
::DeleteObject(hbmColor);
return hCursor;
}
void CStyledCursor::SetStyle(BrushStyle style, INT radius, COLORREF color, BOOL is_rubber)
{
if (m_hCursor && m_style == style && m_radius == radius && m_color == color &&
m_is_rubber == is_rubber)
{
return;
}
if (m_hCursor)
DestroyCursor(m_hCursor);
m_hCursor = CreateStyledCursor(style, radius, color, is_rubber);
m_style = style;
m_radius = radius;
m_color = color;
m_is_rubber = is_rubber;
}
CCanvasWindow::CCanvasWindow()
: m_drawing(FALSE)
, m_hitCanvasSizeBox(HIT_NONE)
@@ -641,6 +773,21 @@ LRESULT CCanvasWindow::OnSetCursor(UINT nMsg, WPARAM wParam, LPARAM lParam, BOOL
case TOOL_AIRBRUSH:
::SetCursor(::LoadCursorW(g_hinstExe, MAKEINTRESOURCEW(IDC_AIRBRUSH)));
break;
case TOOL_RUBBER:
{
m_hRubberCursor.SetStyle(BrushStyleSquare, toolsModel.GetRubberRadius(),
paletteModel.GetBgColor(), TRUE);
m_hRubberCursor.SetCursor();
break;
}
case TOOL_BRUSH:
{
m_hBrushCursor.SetStyle(toolsModel.GetBrushStyle(),
toolsModel.GetBrushWidth() / 2,
paletteModel.GetFgColor(), FALSE);
m_hBrushCursor.SetCursor();
break;
}
default:
::SetCursor(::LoadCursorW(NULL, (LPCWSTR)IDC_CROSS));
}
+34
View File
@@ -3,10 +3,42 @@
* LICENSE: LGPL-2.0-or-later (https://spdx.org/licenses/LGPL-2.0-or-later)
* PURPOSE: Providing the canvas window class
* COPYRIGHT: Copyright 2015 Benedikt Freisen <[email protected]>
* Copyright 2026 Katayama Hirofumi MZ <[email protected]>
*/
#pragma once
enum BrushStyle : int;
class CStyledCursor
{
public:
~CStyledCursor()
{
if (m_hCursor)
::DestroyCursor(m_hCursor);
}
void SetStyle(BrushStyle style, INT radius, COLORREF color, BOOL is_rubber);
void SetCursor()
{
if (m_hCursor)
::SetCursor(m_hCursor);
}
operator HCURSOR() const { return m_hCursor; }
protected:
HCURSOR m_hCursor = NULL;
BrushStyle m_style;
INT m_radius = -1;
COLORREF m_color = CLR_INVALID;
BOOL m_is_rubber = FALSE;
static HCURSOR CreateStyledCursor(BrushStyle style, INT radius, COLORREF color, BOOL is_rubber);
};
class CCanvasWindow : public CWindowImpl<CCanvasWindow>
{
public:
@@ -56,6 +88,8 @@ public:
protected:
HITTEST m_hitCanvasSizeBox;
CStyledCursor m_hBrushCursor;
CStyledCursor m_hRubberCursor;
POINT m_ptOrig; // The origin of drag start
CRect m_rcResizing; // Resizing rectagle
+9
View File
@@ -72,10 +72,17 @@ INT ToolsModel::GetBrushWidth() const
return m_brushWidth;
}
void ToolsModel::SendSetCursor()
{
canvasWindow.SendMessage(WM_SETCURSOR, (WPARAM)(HWND)canvasWindow,
MAKELPARAM(HTCLIENT, WM_MOUSEMOVE));
}
void ToolsModel::SetBrushWidth(INT nBrushWidth)
{
m_brushWidth = nBrushWidth;
NotifyToolSettingsChanged();
SendSetCursor();
imageModel.NotifyImageChanged();
}
@@ -128,6 +135,7 @@ BrushStyle ToolsModel::GetBrushStyle() const
void ToolsModel::SetBrushStyle(BrushStyle nBrushStyle)
{
m_brushStyle = nBrushStyle;
SendSetCursor();
NotifyToolSettingsChanged();
}
@@ -199,6 +207,7 @@ int ToolsModel::GetRubberRadius() const
void ToolsModel::SetRubberRadius(int nRubberRadius)
{
m_rubberRadius = nRubberRadius;
SendSetCursor();
NotifyToolSettingsChanged();
}
+2 -1
View File
@@ -28,7 +28,7 @@ enum TOOLTYPE
TOOL_MAX = TOOL_RRECT,
};
enum BrushStyle
enum BrushStyle : int
{
BrushStyleRound,
BrushStyleSquare,
@@ -81,6 +81,7 @@ private:
ToolBase *m_pToolObject;
ToolBase *GetOrCreateTool(TOOLTYPE nTool);
void SendSetCursor();
public:
ToolsModel();