From 146e1415d0134c72453ea1ddbb4f268a5bf6d2b4 Mon Sep 17 00:00:00 2001 From: Katayama Hirofumi MZ Date: Tue, 19 May 2026 10:23:58 +0900 Subject: [PATCH] [MSPAINT] Update cursor shape for brush and rubber (#8974) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- base/applications/mspaint/canvas.cpp | 147 +++++++++++++++++++++++ base/applications/mspaint/canvas.h | 34 ++++++ base/applications/mspaint/toolsmodel.cpp | 9 ++ base/applications/mspaint/toolsmodel.h | 3 +- 4 files changed, 192 insertions(+), 1 deletion(-) diff --git a/base/applications/mspaint/canvas.cpp b/base/applications/mspaint/canvas.cpp index d95395320ab..31c3e83aaa1 100644 --- a/base/applications/mspaint/canvas.cpp +++ b/base/applications/mspaint/canvas.cpp @@ -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 + * Copyright 2026 Katayama Hirofumi MZ */ #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)); } diff --git a/base/applications/mspaint/canvas.h b/base/applications/mspaint/canvas.h index 9c803d572b6..fc0e1117cff 100644 --- a/base/applications/mspaint/canvas.h +++ b/base/applications/mspaint/canvas.h @@ -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 + * Copyright 2026 Katayama Hirofumi MZ */ #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 { 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 diff --git a/base/applications/mspaint/toolsmodel.cpp b/base/applications/mspaint/toolsmodel.cpp index f789fbf9d6a..1fa514ad66b 100644 --- a/base/applications/mspaint/toolsmodel.cpp +++ b/base/applications/mspaint/toolsmodel.cpp @@ -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(); } diff --git a/base/applications/mspaint/toolsmodel.h b/base/applications/mspaint/toolsmodel.h index 43d4c6ecf28..d3a0181bd1d 100644 --- a/base/applications/mspaint/toolsmodel.h +++ b/base/applications/mspaint/toolsmodel.h @@ -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();