[MSPAINT] Avoid flickering when resizing (#5144)

- Add CPaletteWindow::OnEraseBkgnd to avoid flickering.
- Add getColorBoxRect and drawColorBox helper functions to draw a color box.
- Add CPaletteWindow::DoHitTest helper function to do a hit test.
- Improve CPaletteWindow::OnPaint by using a memory bitmap.
- Improve readability of CMainWindow::alignChildrenToMainWindow.
CORE-18867
This commit is contained in:
Katayama Hirofumi MZ 2023-03-15 07:29:25 +09:00 committed by GitHub
parent f5840df9e9
commit 44b2a46d03
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 173 additions and 93 deletions

View file

@ -4,95 +4,162 @@
* FILE: base/applications/mspaint/palette.cpp
* PURPOSE: Window procedure of the palette window
* PROGRAMMERS: Benedikt Freisen
* Katayama Hirofumi MZ <katayama.hirofumi.mz@gmail.com>
*/
/* INCLUDES *********************************************************/
#include "precomp.h"
/* The private metrics */
#define CXY_SELECTEDBOX 15 /* width / height of a selected color box */
#define X_MARGIN 4 /* horizontal margin */
#define Y_MARGIN ((rcClient.bottom / 2) - CXY_COLORBOX) /* center position minus one color box */
#define X_COLORBOX_OFFSET (X_MARGIN + CXY_BIGBOX + X_MARGIN)
#define COLOR_COUNT 28
#define HALF_COLOR_COUNT (COLOR_COUNT / 2)
/* FUNCTIONS ********************************************************/
static VOID drawColorBox(HDC hDC, LPCRECT prc, COLORREF rgbColor, UINT nBorder)
{
RECT rc = *prc;
::FillRect(hDC, &rc, (HBRUSH)(COLOR_3DFACE + 1));
::DrawEdge(hDC, &rc, nBorder, BF_RECT | BF_ADJUST);
HBRUSH hbr = ::CreateSolidBrush(rgbColor);
::FillRect(hDC, &rc, hbr);
::DeleteObject(hbr);
}
static VOID getColorBoxRect(LPRECT prc, const RECT& rcClient, INT iColor)
{
INT dx = (iColor % HALF_COLOR_COUNT) * CXY_COLORBOX; /* delta x */
INT dy = (iColor / HALF_COLOR_COUNT) * CXY_COLORBOX; /* delta y */
prc->left = X_COLORBOX_OFFSET + dx;
prc->right = prc->left + CXY_COLORBOX;
prc->top = Y_MARGIN + dy;
prc->bottom = prc->top + CXY_COLORBOX;
}
INT CPaletteWindow::DoHitTest(INT xPos, INT yPos) const
{
RECT rcClient;
GetClientRect(&rcClient);
/* delta x and y */
INT dx = (xPos - X_COLORBOX_OFFSET), dy = (yPos - Y_MARGIN);
/* horizontal and vertical indexes */
INT ix = (dx / CXY_COLORBOX), iy = (dy / CXY_COLORBOX);
/* Is it inside of a color box? */
if (0 <= ix && ix < HALF_COLOR_COUNT && 0 <= iy && iy < 2)
return ix + (iy * HALF_COLOR_COUNT); /* return the color index */
return -1; /* Not found */
}
LRESULT CPaletteWindow::OnEraseBkgnd(UINT nMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
return TRUE; /* Avoid flickering */
}
LRESULT CPaletteWindow::OnPaint(UINT nMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
RECT rc = { 0, 0, 31, 32 };
HPEN oldPen;
HBRUSH oldBrush;
int i, a, b;
RECT rc, rcClient;
GetClientRect(&rcClient);
PAINTSTRUCT ps;
HDC hDC = BeginPaint(&ps);
for(b = 2; b < 30; b++)
for(a = 2; a < 29; a++)
if ((a + b) % 2 == 1)
SetPixel(hDC, a, b, GetSysColor(COLOR_BTNHILIGHT));
/* To avoid flickering, we use a memory bitmap.
The left and top values are zeros in client rectangle */
HDC hMemDC = ::CreateCompatibleDC(hDC);
HBITMAP hbm = ::CreateCompatibleBitmap(hDC, rcClient.right, rcClient.bottom);
HGDIOBJ hbmOld = ::SelectObject(hMemDC, hbm);
DrawEdge(hDC, &rc, EDGE_RAISED, BF_TOPLEFT);
DrawEdge(hDC, &rc, BDR_SUNKENOUTER, BF_TOPLEFT | BF_BOTTOMRIGHT);
SetRect(&rc, 11, 12, 26, 27);
DrawEdge(hDC, &rc, BDR_RAISEDINNER, BF_RECT | BF_MIDDLE);
oldPen = (HPEN) SelectObject(hDC, CreatePen(PS_NULL, 0, 0));
oldBrush = (HBRUSH) SelectObject(hDC, CreateSolidBrush(paletteModel.GetBgColor()));
Rectangle(hDC, rc.left, rc.top + 2, rc.right - 1, rc.bottom - 1);
DeleteObject(SelectObject(hDC, oldBrush));
SetRect(&rc, 4, 5, 19, 20);
DrawEdge(hDC, &rc, BDR_RAISEDINNER, BF_RECT | BF_MIDDLE);
oldBrush = (HBRUSH) SelectObject(hDC, CreateSolidBrush(paletteModel.GetFgColor()));
Rectangle(hDC, rc.left + 2, rc.top + 2, rc.right - 1, rc.bottom - 1);
DeleteObject(SelectObject(hDC, oldBrush));
DeleteObject(SelectObject(hDC, oldPen));
/* Fill the background (since WM_ERASEBKGND handling is disabled) */
::FillRect(hMemDC, &rcClient, (HBRUSH)(COLOR_3DFACE + 1));
for(i = 0; i < 28; i++)
/* Draw the big box that contains the black box and the white box */
::SetRect(&rc, X_MARGIN, Y_MARGIN, X_MARGIN + CXY_BIGBOX, Y_MARGIN + CXY_BIGBOX);
::DrawEdge(hMemDC, &rc, EDGE_SUNKEN, BF_RECT | BF_ADJUST);
COLORREF rgbLight = ::GetSysColor(COLOR_3DHIGHLIGHT);
for (INT y = rc.top; y < rc.bottom; ++y)
{
SetRect(&rc, 31 + (i % 14) * 16,
0 + (i / 14) * 16, 16 + 31 + (i % 14) * 16, 16 + 0 + (i / 14) * 16);
DrawEdge(hDC, &rc, EDGE_RAISED, BF_TOPLEFT);
DrawEdge(hDC, &rc, BDR_SUNKENOUTER, BF_RECT);
oldPen = (HPEN) SelectObject(hDC, CreatePen(PS_NULL, 0, 0));
oldBrush = (HBRUSH) SelectObject(hDC, CreateSolidBrush(paletteModel.GetColor(i)));
Rectangle(hDC, rc.left + 2, rc.top + 2, rc.right - 1, rc.bottom - 1);
DeleteObject(SelectObject(hDC, oldBrush));
DeleteObject(SelectObject(hDC, oldPen));
BOOL bLight = (y & 1);
for (INT x = rc.left; x < rc.right; ++x)
{
if (bLight)
::SetPixelV(hMemDC, x, y, rgbLight);
bLight = !bLight;
}
}
/* Draw the white box in the big box, at 5/8 position */
rc.left = X_MARGIN + (CXY_BIGBOX * 5 / 8) - (CXY_SELECTEDBOX / 2);
rc.top = Y_MARGIN + (CXY_BIGBOX * 5 / 8) - (CXY_SELECTEDBOX / 2);
rc.right = rc.left + CXY_SELECTEDBOX;
rc.bottom = rc.top + CXY_SELECTEDBOX;
drawColorBox(hMemDC, &rc, paletteModel.GetBgColor(), BDR_RAISEDINNER);
/* Draw the black box (overlapping the white box), at 3/8 position */
rc.left = X_MARGIN + (CXY_BIGBOX * 3 / 8) - (CXY_SELECTEDBOX / 2);
rc.top = Y_MARGIN + (CXY_BIGBOX * 3 / 8) - (CXY_SELECTEDBOX / 2);
rc.right = rc.left + CXY_SELECTEDBOX;
rc.bottom = rc.top + CXY_SELECTEDBOX;
drawColorBox(hMemDC, &rc, paletteModel.GetFgColor(), BDR_RAISEDINNER);
/* Draw the normal color boxes */
for (INT i = 0; i < COLOR_COUNT; i++)
{
getColorBoxRect(&rc, rcClient, i);
drawColorBox(hMemDC, &rc, paletteModel.GetColor(i), BDR_SUNKENOUTER);
}
/* Transfer bits (hDC <-- hMemDC) */
::BitBlt(hDC, 0, 0, rcClient.right, rcClient.bottom, hMemDC, 0, 0, SRCCOPY);
::SelectObject(hMemDC, hbmOld);
::DeleteDC(hMemDC);
EndPaint(&ps);
return 0;
}
LRESULT CPaletteWindow::OnLButtonDown(UINT nMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
if (GET_X_LPARAM(lParam) >= 31)
paletteModel.SetFgColor(paletteModel.GetColor((GET_X_LPARAM(lParam) - 31) / 16 + (GET_Y_LPARAM(lParam) / 16) * 14));
INT iColor = DoHitTest(GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam));
if (iColor != -1)
paletteModel.SetFgColor(paletteModel.GetColor(iColor));
return 0;
}
LRESULT CPaletteWindow::OnRButtonDown(UINT nMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
if (GET_X_LPARAM(lParam) >= 31)
paletteModel.SetBgColor(paletteModel.GetColor((GET_X_LPARAM(lParam) - 31) / 16 + (GET_Y_LPARAM(lParam) / 16) * 14));
INT iColor = DoHitTest(GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam));
if (iColor != -1)
paletteModel.SetBgColor(paletteModel.GetColor(iColor));
return 0;
}
LRESULT CPaletteWindow::OnLButtonDblClk(UINT nMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
if (GET_X_LPARAM(lParam) >= 31)
if (ChooseColor(&choosecolor))
{
paletteModel.SetColor((GET_X_LPARAM(lParam) - 31) / 16 + (GET_Y_LPARAM(lParam) / 16) * 14,
choosecolor.rgbResult);
paletteModel.SetFgColor(choosecolor.rgbResult);
}
INT iColor = DoHitTest(GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam));
if (iColor != -1 && ChooseColor(&choosecolor))
{
paletteModel.SetColor(iColor, choosecolor.rgbResult);
paletteModel.SetFgColor(choosecolor.rgbResult);
}
return 0;
}
LRESULT CPaletteWindow::OnRButtonDblClk(UINT nMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
if (GET_X_LPARAM(lParam) >= 31)
if (ChooseColor(&choosecolor))
{
paletteModel.SetColor((GET_X_LPARAM(lParam) - 31) / 16 + (GET_Y_LPARAM(lParam) / 16) * 14,
choosecolor.rgbResult);
paletteModel.SetBgColor(choosecolor.rgbResult);
}
INT iColor = DoHitTest(GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam));
if (iColor != -1 && ChooseColor(&choosecolor))
{
paletteModel.SetColor(iColor, choosecolor.rgbResult);
paletteModel.SetBgColor(choosecolor.rgbResult);
}
return 0;
}

View file

@ -8,12 +8,17 @@
#pragma once
#define CXY_COLORBOX 16 /* width / height of a normal color box */
#define CXY_BIGBOX (CXY_COLORBOX * 2) /* width / height of the big box */
#define CY_PALETTE (8 + CXY_BIGBOX + 8)
class CPaletteWindow : public CWindowImpl<CPaletteWindow>
{
public:
DECLARE_WND_CLASS_EX(_T("Palette"), CS_DBLCLKS, COLOR_BTNFACE)
BEGIN_MSG_MAP(CPaletteWindow)
MESSAGE_HANDLER(WM_ERASEBKGND, OnEraseBkgnd)
MESSAGE_HANDLER(WM_PAINT, OnPaint)
MESSAGE_HANDLER(WM_LBUTTONDOWN, OnLButtonDown)
MESSAGE_HANDLER(WM_RBUTTONDOWN, OnRButtonDown)
@ -23,6 +28,7 @@ public:
MESSAGE_HANDLER(WM_PALETTEMODELPALETTECHANGED, OnPaletteModelPaletteChanged)
END_MSG_MAP()
LRESULT OnEraseBkgnd(UINT nMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled);
LRESULT OnPaint(UINT nMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled);
LRESULT OnLButtonDown(UINT nMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled);
LRESULT OnRButtonDown(UINT nMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled);
@ -30,4 +36,7 @@ public:
LRESULT OnRButtonDblClk(UINT nMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled);
LRESULT OnPaletteModelColorChanged(UINT nMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled);
LRESULT OnPaletteModelPaletteChanged(UINT nMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled);
protected:
INT DoHitTest(INT xPos, INT yPos) const;
};

View file

@ -119,6 +119,7 @@
#define ID_SHAPE 613
#define ID_ELLIPSE 614
#define ID_RRECT 615
#define NUM_TOOLS (ID_RRECT - ID_FREESEL + 1)
#define ID_ACCELERATORS 800

View file

@ -18,15 +18,13 @@ LRESULT CToolBox::OnCreate(UINT nMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandl
HIMAGELIST hImageList;
HBITMAP tempBm;
int i;
TCHAR tooltips[16][30];
TCHAR tooltips[NUM_TOOLS][30];
/*
* FIXME: Unintentionally there is a line above the tool bar (hidden by y-offset).
* To prevent cropping of the buttons height has been increased from 200 to 205
*/
RECT toolbarPos = {1, -2, 1 + 50, -2 + 205};
toolbar.Create(TOOLBARCLASSNAME, m_hWnd, toolbarPos, NULL,
WS_CHILD | WS_VISIBLE | CCS_NOPARENTALIGN | CCS_VERT | CCS_NORESIZE | TBSTYLE_TOOLTIPS);
/* NOTE: The horizontal line above the toolbar is hidden by CCS_NODIVIDER style. */
RECT toolbarPos = {0, 0, CX_TOOLBAR, CY_TOOLBAR};
DWORD style = WS_CHILD | WS_VISIBLE | CCS_NOPARENTALIGN | CCS_VERT | CCS_NORESIZE |
TBSTYLE_TOOLTIPS | TBSTYLE_FLAT | CCS_NODIVIDER;
toolbar.Create(TOOLBARCLASSNAME, m_hWnd, toolbarPos, NULL, style);
hImageList = ImageList_Create(16, 16, ILC_COLOR24 | ILC_MASK, 16, 0);
toolbar.SendMessage(TB_SETIMAGELIST, 0, (LPARAM) hImageList);
tempBm = (HBITMAP) LoadImage(hProgInstance, MAKEINTRESOURCE(IDB_TOOLBARICONS), IMAGE_BITMAP, 256, 16, 0);
@ -34,7 +32,7 @@ LRESULT CToolBox::OnCreate(UINT nMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandl
DeleteObject(tempBm);
toolbar.SendMessage(TB_BUTTONSTRUCTSIZE, sizeof(TBBUTTON), 0);
for(i = 0; i < 16; i++)
for (i = 0; i < NUM_TOOLS; i++)
{
TBBUTTON tbbutton;
int wrapnow = 0;
@ -54,7 +52,7 @@ LRESULT CToolBox::OnCreate(UINT nMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandl
toolbar.SendMessage(TB_CHECKBUTTON, ID_PEN, MAKELPARAM(TRUE, 0));
toolbar.SendMessage(TB_SETMAXTEXTROWS, 0, 0);
toolbar.SendMessage(TB_SETBUTTONSIZE, 0, MAKELPARAM(25, 25));
toolbar.SendMessage(TB_SETBUTTONSIZE, 0, MAKELPARAM(CXY_TB_BUTTON, CXY_TB_BUTTON));
return 0;
}

View file

@ -9,6 +9,12 @@
#pragma once
#define TOOLBAR_ROWS 8
#define TOOLBAR_COLUMNS 2
#define CXY_TB_BUTTON 25
#define CX_TOOLBAR ((TOOLBAR_COLUMNS * CXY_TB_BUTTON) + 2)
#define CY_TOOLBAR ((TOOLBAR_ROWS * CXY_TB_BUTTON) + 2)
class CToolBox : public CWindowImpl<CToolBox>
{
public:

View file

@ -47,45 +47,45 @@ zoomTo(int newZoom, int mouseX, int mouseY)
void CMainWindow::alignChildrenToMainWindow()
{
int x, y, w, h;
RECT clientRect;
RECT clientRect, rc;
GetClientRect(&clientRect);
RECT rcSpace = clientRect;
if (::IsWindowVisible(hStatusBar))
{
::GetWindowRect(hStatusBar, &rc);
rcSpace.bottom -= rc.bottom - rc.top;
}
HDWP hDWP = ::BeginDeferWindowPos(3);
if (::IsWindowVisible(toolBoxContainer))
{
x = 56;
w = clientRect.right - 56;
}
else
{
x = 0;
w = clientRect.right;
}
if (::IsWindowVisible(paletteWindow))
{
y = 49;
h = clientRect.bottom - 49;
}
else
{
y = 3;
h = clientRect.bottom - 3;
hDWP = ::DeferWindowPos(hDWP, toolBoxContainer, NULL,
rcSpace.left, rcSpace.top,
CX_TOOLBAR, rcSpace.bottom - rcSpace.top,
SWP_NOZORDER | SWP_NOACTIVATE | SWP_NOREPOSITION);
rcSpace.left += CX_TOOLBAR;
}
INT statusBarHeight = 0;
if (::IsWindowVisible(hStatusBar))
if (::IsWindowVisible(paletteWindow))
{
RECT Rect;
INT borders[3];
::SendMessage(hStatusBar, SB_GETRECT, 0, (LPARAM)&Rect);
::SendMessage(hStatusBar, SB_GETBORDERS, 0, (LPARAM)&borders);
statusBarHeight = Rect.bottom - Rect.top + borders[1];
hDWP = ::DeferWindowPos(hDWP, paletteWindow, NULL,
rcSpace.left, rcSpace.top,
rcSpace.right - rcSpace.left, CY_PALETTE,
SWP_NOZORDER | SWP_NOACTIVATE | SWP_NOREPOSITION);
rcSpace.top += CY_PALETTE;
}
if (scrollboxWindow.IsWindow())
scrollboxWindow.MoveWindow(x, y, w, h - statusBarHeight, TRUE);
if (paletteWindow.IsWindow())
paletteWindow.MoveWindow(x, 9, 255, 32, TRUE);
{
hDWP = ::DeferWindowPos(hDWP, scrollboxWindow, NULL,
rcSpace.left, rcSpace.top,
rcSpace.right - rcSpace.left, rcSpace.bottom - rcSpace.top,
SWP_NOZORDER | SWP_NOACTIVATE | SWP_NOREPOSITION);
}
::EndDeferWindowPos(hDWP);
}
void CMainWindow::saveImage(BOOL overwrite)
@ -369,11 +369,10 @@ LRESULT CMainWindow::OnSize(UINT nMsg, WPARAM wParam, LPARAM lParam, BOOL& bHand
int test[] = { LOWORD(lParam) - 260, LOWORD(lParam) - 140, LOWORD(lParam) - 20 };
if (::IsWindow(hStatusBar))
{
::SendMessage(hStatusBar, WM_SIZE, wParam, lParam);
::SendMessage(hStatusBar, WM_SIZE, 0, 0);
::SendMessage(hStatusBar, SB_SETPARTS, 3, (LPARAM)&test);
}
alignChildrenToMainWindow();
Invalidate(TRUE);
return 0;
}