#include "alfpriv.h" /* Themed "NT" Button for Win2k and up */ #define BP_PUSHBUTTON 1 #define PBS_NORMAL 1 #define PBS_HOT 2 #define PBS_PRESSED 3 #define PBS_DISABLED 4 #define PBS_DEFAULTED 5 #define PBS_DEFAULTED_ANIMATING 6 #define ALF_NTBTN_FLAG_UXTHEME 1 #define ALF_NTBTN_FLAG_IS_DISABLED 2 #define ALF_NTBTN_FLAG_IS_DEFAULT 4 #define ALF_NTBTN_FLAG_IS_HOT 8 #define ALF_NTBTN_FLAG_IS_FOCUSED 16 #define ALF_NTBTN_FLAG_IS_PRESSED 32 #define ALF_NTBTN_FLAG_HIDEFOCUS 64 #define ALF_NTBTN_FLAG_HIDEACCEL 128 #define ALF_NTBTN_FLAG_DEFAULT_ANIMATING 256 typedef struct { ALFCustomButtonMeasureFunc measureFunc; ALFCustomButtonPaintFunc paintFunc; void *closure; } ALFCustomButtonCreateParams; typedef struct { DWORD drawFlags; HTHEME hTheme; int uxStatePrev; int uxStateCurrent; DWORD uxDrawFlagsCurrent; DWORD uxDrawFlagsPrev; DWORD uxDefaultAnimationDuration; int dpi; HFONT font; ALFColor bgcolor; ALFCustomButtonMeasureFunc measureFunc; ALFCustomButtonPaintFunc paintFunc; void *closure; } ALFNtButtonPriv; static ALFNtButtonPriv * ALF_NtButton_InitializePriv(CREATESTRUCT *cs) { ALFNtButtonPriv *priv = ALF_New(ALFNtButtonPriv, 1); priv->drawFlags = ALF_NTBTN_FLAG_UXTHEME; priv->uxStateCurrent = -1; priv->uxStatePrev = -1; priv->dpi = 96; priv->bgcolor = ALF_COLOR_SYS(COLOR_BTNFACE); ALFControlCreateParams *ccp = (ALFControlCreateParams *)cs->lpCreateParams; if (ccp->param) { ALFCustomButtonCreateParams *p = (ALFCustomButtonCreateParams *)ccp->param; priv->measureFunc = p->measureFunc; priv->paintFunc = p->paintFunc; priv->closure = p->closure; } return priv; } static void ALF_NtButton_FreePriv(ALFNtButtonPriv *priv) { ALF_Compat_CloseThemeData(priv->hTheme); ALF_Free(priv); } static void ALF_NtButton_ModifyDrawFlags(HWND hwnd, ALFNtButtonPriv *priv, DWORD add, DWORD remove, BOOL redraw) { DWORD newFlags = priv->drawFlags; newFlags |= add; newFlags &= ~remove; if (newFlags != priv->drawFlags) { priv->drawFlags = newFlags; if (redraw) { InvalidateRect(hwnd, NULL, TRUE); } } } static void ALF_NtButton_HandleUIState(HWND hwnd, ALFNtButtonPriv *priv) { LRESULT uiState = SendMessageW(hwnd, WM_QUERYUISTATE, 0, 0); DWORD add = 0; DWORD remove = 0; if (uiState & UISF_HIDEACCEL) { add |= ALF_NTBTN_FLAG_HIDEACCEL; } else { remove |= ALF_NTBTN_FLAG_HIDEACCEL; } if (uiState & UISF_HIDEFOCUS) { add |= ALF_NTBTN_FLAG_HIDEFOCUS; } else { remove |= ALF_NTBTN_FLAG_HIDEFOCUS; } ALF_NtButton_ModifyDrawFlags(hwnd, priv, add, remove, TRUE); } static void ALF_NtButton_HandleThemeChange(HWND hwnd, ALFNtButtonPriv *priv) { ALF_Compat_CloseThemeData(priv->hTheme); priv->hTheme = NULL; if (ALF_Compat_IsAppThemed()) priv->hTheme = ALF_Compat_OpenThemeData(hwnd, L"Button"); priv->uxDefaultAnimationDuration = 0; ALF_Compat_GetThemeTransitionDuration(priv->hTheme, BP_PUSHBUTTON, PBS_DEFAULTED, PBS_DEFAULTED_ANIMATING, TMT_TRANSITIONDURATION, &priv->uxDefaultAnimationDuration); InvalidateRect(hwnd, NULL, TRUE); } static void ALF_NtButton_CalculateSize(HWND hwnd, ALFNtButtonPriv *priv, SIZE *pSize) { HDC hdc = GetDC(hwnd); HFONT oldFont = SelectFont(hdc, priv->font); int xpadding = ALF_CentipointsToPixels(600, priv->dpi); int ypadding = ALF_CentipointsToPixels(600, priv->dpi); SIZE s = { 0, 0 }; if (priv->measureFunc) { s = priv->measureFunc(hwnd, priv->closure, priv->dpi, hdc, priv->hTheme, BP_PUSHBUTTON, PBS_NORMAL); } if (pSize->cx < s.cx + xpadding) { pSize->cx = s.cx + xpadding; } if (pSize->cy < s.cy + ypadding) { pSize->cy = s.cy + ypadding; } if (pSize->cx < pSize->cy) { pSize->cx = pSize->cy; } SelectFont(hdc, oldFont); ReleaseDC(hwnd, hdc); } static void CALLBACK ALF_NtButton_DefaultAnimatingTimerProc(HWND hwnd, UINT uMsg, UINT_PTR idEvent, DWORD dwTime) { (void)uMsg; (void)dwTime; ALFNtButtonPriv *priv = (ALFNtButtonPriv *)idEvent; if (priv->drawFlags & ALF_NTBTN_FLAG_DEFAULT_ANIMATING) { ALF_NtButton_ModifyDrawFlags(hwnd, priv, 0, ALF_NTBTN_FLAG_DEFAULT_ANIMATING, TRUE); } else { ALF_NtButton_ModifyDrawFlags(hwnd, priv, ALF_NTBTN_FLAG_DEFAULT_ANIMATING, 0, TRUE); } } static void ALF_NtButton_RenderUxtheme(HWND hwnd, ALFNtButtonPriv *priv, int uxpart, int uxstate, DWORD drawFlags, HDC hDC, RECT *rcPaint) { HFONT oldfont = SelectFont(hDC, priv->font); RECT r; GetClientRect(hwnd, &r); if (ALF_Compat_IsThemeBackgroundPartiallyTransparent(priv->hTheme, uxpart, uxstate)) { if (priv->bgcolor == ALF_COLOR_TRANSPARENT) { ALF_Compat_DrawThemeParentBackground(hwnd, hDC, rcPaint); } else { ALF_FillRect(hDC, rcPaint, priv->bgcolor); } } ALF_Compat_DrawThemeBackground(priv->hTheme, hDC, BP_PUSHBUTTON, uxstate, &r, rcPaint); RECT content = r; ALF_Compat_GetThemeBackgroundContentRect(priv->hTheme, hDC, BP_PUSHBUTTON, uxstate, &r, &content); if ((drawFlags & ALF_NTBTN_FLAG_IS_FOCUSED) && !(drawFlags & ALF_NTBTN_FLAG_HIDEFOCUS)) DrawFocusRect(hDC, &content); UINT itemState = 0; if (drawFlags & ALF_NTBTN_FLAG_IS_DEFAULT) itemState |= ODS_DEFAULT; if (drawFlags & ALF_NTBTN_FLAG_IS_DISABLED) itemState |= ODS_DISABLED; if (drawFlags & ALF_NTBTN_FLAG_IS_FOCUSED) itemState |= ODS_FOCUS; if (drawFlags & ALF_NTBTN_FLAG_IS_HOT) itemState |= ODS_HOTLIGHT; if (drawFlags & ALF_NTBTN_FLAG_IS_PRESSED) itemState |= ODS_SELECTED; if (drawFlags & ALF_NTBTN_FLAG_HIDEACCEL) itemState |= ODS_NOACCEL; if (drawFlags & ALF_NTBTN_FLAG_HIDEFOCUS) itemState |= ODS_NOFOCUSRECT; if (priv->paintFunc) { priv->paintFunc(hwnd, priv->closure, priv->dpi, itemState, hDC, &content, priv->hTheme, BP_PUSHBUTTON, uxstate); } SelectFont(hDC, oldfont); } static void ALF_NtButton_RenderClassic(HWND hwnd, ALFNtButtonPriv *priv, HDC dc, RECT *rcPaint) { (void)rcPaint; RECT rcClient; GetClientRect(hwnd, &rcClient); HFONT oldfont = SelectFont(dc, priv->font); RECT r = rcClient; if (priv->drawFlags & ALF_NTBTN_FLAG_IS_DEFAULT) { HBRUSH framecolor = GetSysColorBrush(COLOR_WINDOWFRAME); FrameRect(dc, &r, framecolor); InflateRect(&r, -1, -1); } UINT dfcs = DFCS_BUTTONPUSH | DFCS_ADJUSTRECT; if (priv->drawFlags & ALF_NTBTN_FLAG_IS_PRESSED) dfcs |= DFCS_FLAT; if (priv->drawFlags & ALF_NTBTN_FLAG_IS_DISABLED) dfcs |= DFCS_INACTIVE; if (priv->drawFlags & ALF_NTBTN_FLAG_IS_HOT) dfcs |= DFCS_HOT; DrawFrameControl(dc, &r, DFC_BUTTON, dfcs); UINT itemState = 0; if (priv->drawFlags & ALF_NTBTN_FLAG_IS_DEFAULT) itemState |= ODS_DEFAULT; if (priv->drawFlags & ALF_NTBTN_FLAG_IS_DISABLED) itemState |= ODS_DISABLED; if (priv->drawFlags & ALF_NTBTN_FLAG_IS_FOCUSED) itemState |= ODS_FOCUS; if (priv->drawFlags & ALF_NTBTN_FLAG_IS_HOT) itemState |= ODS_HOTLIGHT; if (priv->drawFlags & ALF_NTBTN_FLAG_IS_PRESSED) itemState |= ODS_SELECTED; if (priv->drawFlags & ALF_NTBTN_FLAG_HIDEACCEL) itemState |= ODS_NOACCEL; if (priv->drawFlags & ALF_NTBTN_FLAG_HIDEFOCUS) itemState |= ODS_NOFOCUSRECT; if (priv->paintFunc) { RECT c = r; if (priv->drawFlags & ALF_NTBTN_FLAG_IS_PRESSED) { c.left += 1; c.top += 1; c.right += 1; c.bottom += 1; } priv->paintFunc(hwnd, priv->closure, priv->dpi, itemState, dc, &c, NULL, 0, 0); } if ((priv->drawFlags & ALF_NTBTN_FLAG_IS_FOCUSED) && !(priv->drawFlags & ALF_NTBTN_FLAG_HIDEFOCUS)) { RECT f = r; InflateRect(&f, -1, -1); DrawFocusRect(dc, &f); } SelectFont(dc, oldfont); } static void ALF_NtButton_Paint(HWND hwnd, ALFNtButtonPriv *priv, HDC dc, RECT *r) { if (!ALF_Compat_BufferedPaintRenderAnimation(hwnd, dc)) { if ((priv->drawFlags & ALF_NTBTN_FLAG_UXTHEME) && priv->hTheme) { // Draw XP style themed button int stateid = PBS_NORMAL; if (priv->drawFlags & ALF_NTBTN_FLAG_IS_DEFAULT) stateid = (priv->drawFlags & ALF_NTBTN_FLAG_DEFAULT_ANIMATING) ? PBS_DEFAULTED_ANIMATING : PBS_DEFAULTED; if (priv->drawFlags & ALF_NTBTN_FLAG_IS_HOT) stateid = PBS_HOT; if (priv->drawFlags & ALF_NTBTN_FLAG_IS_PRESSED) stateid = PBS_PRESSED; if (priv->drawFlags & ALF_NTBTN_FLAG_IS_DISABLED) stateid = PBS_DISABLED; if (priv->uxStatePrev == -1) { // initial draw priv->uxStateCurrent = stateid; priv->uxDrawFlagsCurrent = priv->drawFlags; if (priv->uxStateCurrent == PBS_DEFAULTED || priv->uxStateCurrent == PBS_DEFAULTED_ANIMATING) priv->uxStateCurrent = PBS_NORMAL; } priv->uxStatePrev = priv->uxStateCurrent; priv->uxStateCurrent = stateid; priv->uxDrawFlagsPrev = priv->uxDrawFlagsCurrent; priv->uxDrawFlagsCurrent = priv->drawFlags; ALF_Compat_BP_ANIMATIONPARAMS animParams; ZeroMemory(&animParams, sizeof(animParams)); animParams.cbSize = sizeof(animParams); animParams.style = ALF_Compat_BPAS_LINEAR; if (priv->uxStateCurrent != priv->uxStatePrev) { if ((priv->uxStateCurrent == PBS_DEFAULTED && priv->uxStatePrev == PBS_DEFAULTED_ANIMATING) || (priv->uxStatePrev == PBS_DEFAULTED && priv->uxStateCurrent == PBS_DEFAULTED_ANIMATING)) { animParams.dwDuration = priv->uxDefaultAnimationDuration; } else if (priv->uxStateCurrent == PBS_DEFAULTED_ANIMATING) { // Win7 misses these transition times, use the one for PBS_DEFAULTED ALF_Compat_GetThemeTransitionDuration(priv->hTheme, BP_PUSHBUTTON, priv->uxStatePrev, PBS_DEFAULTED, TMT_TRANSITIONDURATION, &animParams.dwDuration); } else if (priv->uxStatePrev == PBS_DEFAULTED_ANIMATING) { // Win7 misses these transition times, use the one for PBS_DEFAULTED ALF_Compat_GetThemeTransitionDuration(priv->hTheme, BP_PUSHBUTTON, PBS_DEFAULTED, priv->uxStateCurrent, TMT_TRANSITIONDURATION, &animParams.dwDuration); } else { ALF_Compat_GetThemeTransitionDuration(priv->hTheme, BP_PUSHBUTTON, priv->uxStatePrev, priv->uxStateCurrent, TMT_TRANSITIONDURATION, &animParams.dwDuration); } if ((priv->uxStatePrev == PBS_DEFAULTED || priv->uxStatePrev == PBS_DEFAULTED_ANIMATING) && !(priv->uxStateCurrent == PBS_DEFAULTED || priv->uxStateCurrent == PBS_DEFAULTED_ANIMATING)) { KillTimer(hwnd, (UINT_PTR)priv); ALF_NtButton_ModifyDrawFlags(hwnd, priv, 0, ALF_NTBTN_FLAG_DEFAULT_ANIMATING, FALSE); } if (!(priv->uxStatePrev == PBS_DEFAULTED || priv->uxStatePrev == PBS_DEFAULTED_ANIMATING) && (priv->uxStateCurrent == PBS_DEFAULTED || priv->uxStateCurrent == PBS_DEFAULTED_ANIMATING) && priv->uxDefaultAnimationDuration) { SetTimer(hwnd, (UINT_PTR)priv, priv->uxDefaultAnimationDuration, ALF_NtButton_DefaultAnimatingTimerProc); } } HDC hdcFrom = NULL, hdcTo = NULL; ALF_Compat_HANIMATIONBUFFER hbpAnimation; hbpAnimation = ALF_Compat_BeginBufferedAnimation(hwnd, dc, r, 0, NULL, &animParams, &hdcFrom, &hdcTo); if (hbpAnimation) { if (hdcFrom) { ALF_NtButton_RenderUxtheme(hwnd, priv, BP_PUSHBUTTON, priv->uxStatePrev, priv->uxDrawFlagsPrev, hdcFrom, r); } if (hdcTo) { ALF_NtButton_RenderUxtheme(hwnd, priv, BP_PUSHBUTTON, priv->uxStateCurrent, priv->uxDrawFlagsCurrent, hdcTo, r); } ALF_Compat_EndBufferedAnimation(hbpAnimation, TRUE); } else { ALF_NtButton_RenderUxtheme(hwnd, priv, BP_PUSHBUTTON, priv->uxStateCurrent, priv->uxDrawFlagsCurrent, dc, r); } } else { // Draw classic unthemed button ALF_NtButton_RenderClassic(hwnd, priv, dc, r); } } } static void ALF_NtButton_Clicked(HWND hwnd, ALFNtButtonPriv *priv) { (void)priv; SendMessage(GetParent(hwnd), WM_COMMAND, MAKEWPARAM(GetWindowLong(hwnd, GWL_ID), BN_CLICKED), (LPARAM)hwnd); } static LRESULT CALLBACK ALF_NtButton_WndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { ALFNtButtonPriv *priv = (ALFNtButtonPriv *)GetWindowLongPtr(hwnd, 0); if (uMsg == WM_CREATE) { priv = ALF_NtButton_InitializePriv((CREATESTRUCT *)lParam); SetWindowLongPtr(hwnd, 0, (LONG_PTR)priv); ALF_NtButton_HandleThemeChange(hwnd, priv); ALF_NtButton_HandleUIState(hwnd, priv); } else if (uMsg == ALF_WM_QUERYSIZE) { ALF_NtButton_CalculateSize(hwnd, priv, (SIZE*)lParam);; return TRUE; } else if (uMsg == WM_PAINT) { PAINTSTRUCT ps; HDC hdc = BeginPaint(hwnd, &ps); ALF_NtButton_Paint(hwnd, priv, hdc, &ps.rcPaint); EndPaint(hwnd, &ps); return TRUE; } else if (uMsg == WM_PRINTCLIENT) { RECT rc; GetClientRect(hwnd, &rc); ALF_NtButton_Paint(hwnd, priv, (HDC)wParam, &rc); } else if (uMsg == WM_GETDLGCODE) { if (priv->drawFlags & ALF_NTBTN_FLAG_IS_DEFAULT) { return DLGC_DEFPUSHBUTTON | DLGC_BUTTON; } else { return DLGC_UNDEFPUSHBUTTON | DLGC_BUTTON; } } else if (uMsg == BM_SETSTYLE) { BOOL newDefault = (wParam & BS_DEFPUSHBUTTON) ? 1 : 0; if (newDefault) { ALF_NtButton_ModifyDrawFlags(hwnd, priv, ALF_NTBTN_FLAG_IS_DEFAULT, 0, TRUE); } else { ALF_NtButton_ModifyDrawFlags(hwnd, priv, 0, ALF_NTBTN_FLAG_IS_DEFAULT, TRUE); } } else if (uMsg == BM_CLICK) { ALF_NtButton_Clicked(hwnd, priv); return 0; } else if (uMsg == WM_STYLECHANGED) { BOOL newDefault = (GetWindowLong(hwnd, GWL_STYLE) & BS_DEFPUSHBUTTON) ? 1 : 0; if (newDefault) { ALF_NtButton_ModifyDrawFlags(hwnd, priv, ALF_NTBTN_FLAG_IS_DEFAULT, 0, TRUE); } else { ALF_NtButton_ModifyDrawFlags(hwnd, priv, 0, ALF_NTBTN_FLAG_IS_DEFAULT, TRUE); } } else if (uMsg == WM_THEMECHANGED) { ALF_NtButton_HandleThemeChange(hwnd, priv); } else if (uMsg == WM_MOUSEMOVE) { if (!(priv->drawFlags & ALF_NTBTN_FLAG_IS_HOT)) { TRACKMOUSEEVENT tme; ZeroMemory(&tme, sizeof(tme)); tme.cbSize = sizeof(tme); tme.dwFlags = TME_LEAVE; tme.hwndTrack = hwnd; if (ALF_Compat_TrackMouseEvent(&tme)) { ALF_NtButton_ModifyDrawFlags(hwnd, priv, ALF_NTBTN_FLAG_IS_HOT, 0, TRUE); } } RECT rcWindow; GetWindowRect(hwnd, &rcWindow); DWORD pos = GetMessagePos(); if (GET_X_LPARAM(pos) >= rcWindow.left && GET_X_LPARAM(pos) < rcWindow.right && GET_Y_LPARAM(pos) >= rcWindow.top && GET_Y_LPARAM(pos) < rcWindow.bottom) { if (wParam & MK_LBUTTON) { ALF_NtButton_ModifyDrawFlags(hwnd, priv, ALF_NTBTN_FLAG_IS_PRESSED, 0, TRUE); } } else { ALF_NtButton_ModifyDrawFlags(hwnd, priv, 0, ALF_NTBTN_FLAG_IS_PRESSED, TRUE); } } else if (uMsg == WM_MOUSELEAVE) { ALF_NtButton_ModifyDrawFlags(hwnd, priv, 0, ALF_NTBTN_FLAG_IS_HOT, TRUE); } else if (uMsg == WM_SETFOCUS) { ALF_NtButton_ModifyDrawFlags(hwnd, priv, ALF_NTBTN_FLAG_IS_FOCUSED, 0, TRUE); } else if (uMsg == WM_KILLFOCUS) { ALF_NtButton_ModifyDrawFlags(hwnd, priv, 0, ALF_NTBTN_FLAG_IS_FOCUSED | ALF_NTBTN_FLAG_IS_PRESSED, TRUE); } else if (uMsg == WM_LBUTTONDOWN) { if (IsWindowEnabled(hwnd)) { if (!(priv->drawFlags & ALF_NTBTN_FLAG_IS_FOCUSED)) { SetFocus(hwnd); } ALF_NtButton_ModifyDrawFlags(hwnd, priv, ALF_NTBTN_FLAG_IS_PRESSED, 0, TRUE); SetCapture(hwnd); } return 0; } else if (uMsg == WM_LBUTTONUP) { ReleaseCapture(); if (IsWindowEnabled(hwnd)) { RECT rcClient; GetClientRect(hwnd, &rcClient); ALF_NtButton_ModifyDrawFlags(hwnd, priv, 0, ALF_NTBTN_FLAG_IS_PRESSED, TRUE); if (GET_X_LPARAM(lParam) >= rcClient.left && GET_X_LPARAM(lParam) < rcClient.right && GET_Y_LPARAM(lParam) >= rcClient.top && GET_Y_LPARAM(lParam) < rcClient.bottom) { ALF_NtButton_Clicked(hwnd, priv); } } return 0; } else if (uMsg == WM_KEYDOWN) { if (GetCapture() != hwnd && IsWindowEnabled(hwnd)) { if (wParam == VK_SPACE && LOWORD(lParam) == 1) { ALF_NtButton_ModifyDrawFlags(hwnd, priv, ALF_NTBTN_FLAG_IS_PRESSED, 0, TRUE); } else { ALF_NtButton_ModifyDrawFlags(hwnd, priv, 0, ALF_NTBTN_FLAG_IS_PRESSED, TRUE); } } } else if (uMsg == WM_KEYUP) { if (GetCapture() != hwnd && IsWindowEnabled(hwnd)) { if (wParam == VK_SPACE) { if (priv->drawFlags & ALF_NTBTN_FLAG_IS_PRESSED) { ALF_NtButton_ModifyDrawFlags(hwnd, priv, 0, ALF_NTBTN_FLAG_IS_PRESSED, TRUE); ALF_NtButton_Clicked(hwnd, priv); } } } } else if (uMsg == WM_ERASEBKGND) { return TRUE; } else if (uMsg == WM_SETTEXT) { ALF_InvalidateLayout(GetParent(hwnd)); InvalidateRect(hwnd, NULL, TRUE); } else if (uMsg == WM_SETFONT) { priv->font = (HFONT)wParam; if (LOWORD(lParam) != 0) InvalidateRect(hwnd, NULL, TRUE); ALF_InvalidateLayout(GetParent(hwnd)); return 0; } else if (uMsg == WM_GETFONT) { return (LRESULT)priv->font; } else if (uMsg == ALF_WM_DPICHANGE) { priv->dpi = (int)lParam; ALF_NtButton_HandleThemeChange(hwnd, priv); // theme caches bitmaps, need to reload them ALF_InvalidateLayout(GetParent(hwnd)); return TRUE; } else if (uMsg == ALF_WM_SETBGCOLOR) { ALFColor newcolor = (ALFColor)lParam; if (priv->bgcolor != newcolor) { priv->bgcolor = newcolor; if (priv->hTheme) { InvalidateRect(hwnd, NULL, TRUE); } } return TRUE; } else if (uMsg == ALF_WM_GETBGCOLOR) { return (LRESULT)priv->bgcolor; } else if (uMsg == ALF_WM_BACKGROUNDCHANGE) { if (priv->bgcolor == ALF_COLOR_TRANSPARENT && priv->hTheme) { InvalidateRect(hwnd, NULL, TRUE); } } else if (uMsg == WM_SIZE) { InvalidateRect(hwnd, NULL, TRUE); } else if (uMsg == WM_UPDATEUISTATE) { LRESULT rv = DefWindowProc(hwnd, uMsg, wParam, lParam); ALF_NtButton_HandleUIState(hwnd, priv); return rv; } else if (uMsg == WM_ENABLE) { if (wParam) { ALF_NtButton_ModifyDrawFlags(hwnd, priv, 0, ALF_NTBTN_FLAG_IS_DISABLED, TRUE); } else { ALF_NtButton_ModifyDrawFlags(hwnd, priv, ALF_NTBTN_FLAG_IS_DISABLED, ALF_NTBTN_FLAG_IS_PRESSED, TRUE); } } else if (uMsg == WM_DESTROY) { ALF_NtButton_FreePriv(priv); priv = NULL; SetWindowLongPtr(hwnd, 0, 0); } return DefWindowProc(hwnd, uMsg, wParam, lParam); } static HWND ALF_NtButton_Create(HWND win, WORD id, int x, int y, const TCHAR *text, ALFCustomButtonMeasureFunc measureFunc, ALFCustomButtonPaintFunc paintFunc, void *closure) { ALFCustomButtonCreateParams p = { measureFunc, paintFunc, closure }; HWND hwndButton = ALF_CreateControlWindow(0, text, WS_CHILD | WS_VISIBLE | WS_TABSTOP, 0, 0, 100, 100, win, (HMENU)(ULONG_PTR)id, ALF_NtButton_WndProc, &p); int minwidth = 5625; int minheight = 1725; ALF_AddControl(win, x, y, hwndButton, minwidth, minheight, ALF_LAYOUT_SIZE_QUERY | ALF_LAYOUT_INHERITFONT | ALF_LAYOUT_SENDDPICHANGE | ALF_LAYOUT_INHERITBGCOLOR | ALF_LAYOUT_SENDBGCHANGE); return hwndButton; } /* Classic Button for Win9x and NT3.51 */ // Win9x/Win32s absolutely needs a real button window, otherwise the dialog manager will get confused #define ALF_CLSCBTN_FLAG_IS_DEFAULT ((DWORD)1) typedef struct { WNDPROC origWndProc; DWORD flags; int dpi; BYTE thunk[12]; ALFCustomButtonMeasureFunc measureFunc; ALFCustomButtonPaintFunc paintFunc; void *closure; } ALFClassicButtonPriv; static LRESULT CALLBACK ALF_ClassicButton_WndProc(ALFClassicButtonPriv *priv, HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam); static ALFClassicButtonPriv * ALF_ClassicButton_InitializePriv(HWND hwnd) { ALFClassicButtonPriv *priv = ALF_New(ALFClassicButtonPriv, 1); // HACK! use thunk since we want to leave GWLP_USERDATA to the control user // this doesn't work on non-x86 and also doesn't work with DEP, // but it's ok since that code is only running on Win9x and NT4 priv->thunk[0] = 0x58; // pop eax priv->thunk[1] = 0x68; // push ptr DWORD privptr = (DWORD)(ULONG_PTR)priv; CopyMemory(&priv->thunk[2], &privptr, 4); priv->thunk[6] = 0x50; // push eax priv->thunk[7] = 0xe9; // jmp DWORD procptr = (DWORD)((ULONG_PTR)ALF_ClassicButton_WndProc - ((ULONG_PTR)priv->thunk + sizeof(priv->thunk))); CopyMemory(&priv->thunk[8], &procptr, 4); priv->dpi = (int)ALF_Compat_GetDpiForWindow(hwnd); // on Win9x/NT4, DPI will not ever change return priv; } static void ALF_ClassicButton_FreePriv(ALFClassicButtonPriv *priv) { ALF_Free(priv); } static void ALF_ClassicButton_CalculateSize(HWND hwnd, ALFClassicButtonPriv *priv, SIZE *pSize) { (void)priv; HDC hdc = GetDC(hwnd); HFONT font = (HFONT)SendMessage(hwnd, WM_GETFONT, 0, 0); HFONT oldFont = SelectFont(hdc, font); SIZE s = { 0, 0 }; if (priv->measureFunc) s = priv->measureFunc(hwnd, priv->closure, priv->dpi, hdc, NULL, 0, 0); int xpadding = 8; int ypadding = 8; if (pSize->cx < s.cx + xpadding) { pSize->cx = s.cx + xpadding; } if (pSize->cy < s.cy + ypadding) { pSize->cy = s.cy + ypadding; } if (pSize->cx < pSize->cy) { pSize->cx = pSize->cy; } SelectFont(hdc, oldFont); ReleaseDC(hwnd, hdc); } static void ALF_ClassicButton_Paint(HWND hwnd, ALFClassicButtonPriv *priv, DRAWITEMSTRUCT *dis) { RECT r = dis->rcItem; TCHAR *textbuf = ALF_Text(hwnd); if (priv->flags & ALF_CLSCBTN_FLAG_IS_DEFAULT) { HBRUSH framecolor = GetSysColorBrush(COLOR_WINDOWFRAME); FrameRect(dis->hDC, &r, framecolor); InflateRect(&r, -1, -1); } UINT dfcs = DFCS_BUTTONPUSH | DFCS_ADJUSTRECT; if (dis->itemState & ODS_SELECTED) dfcs |= DFCS_FLAT; if (dis->itemState & ODS_DISABLED) dfcs |= DFCS_INACTIVE; DrawFrameControl(dis->hDC, &r, DFC_BUTTON, dfcs); if (priv->paintFunc) { RECT texttarget = r; if (dis->itemState & ODS_SELECTED) { texttarget.top += 1; texttarget.left += 1; texttarget.right += 1; texttarget.bottom += 1; } priv->paintFunc(hwnd, priv->closure, priv->dpi, dis->itemState, dis->hDC, &texttarget, NULL, 0, 0); } if (dis->itemState & ODS_FOCUS) { RECT f = r; InflateRect(&f, -1, -1); DrawFocusRect(dis->hDC, &f); } ALF_Free(textbuf); } static LRESULT CALLBACK ALF_ClassicButton_WndProc(ALFClassicButtonPriv *priv, HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { if (!priv) return DefWindowProc(hwnd, uMsg, wParam, lParam); if (uMsg == ALF_WM_QUERYSIZE) { ALF_ClassicButton_CalculateSize(hwnd, priv, (SIZE*)lParam); return TRUE; } else if (uMsg == 0x2000 + WM_DRAWITEM) { LPDRAWITEMSTRUCT dis = (DRAWITEMSTRUCT *)lParam; ALF_ClassicButton_Paint(hwnd, priv, dis); return TRUE; } else if (uMsg == WM_ERASEBKGND) { return TRUE; } else if (uMsg == BM_SETSTYLE) { priv->flags &= ~ALF_CLSCBTN_FLAG_IS_DEFAULT; if (wParam & BS_DEFPUSHBUTTON) priv->flags |= ALF_CLSCBTN_FLAG_IS_DEFAULT; wParam = BS_OWNERDRAW; } else if (uMsg == WM_GETDLGCODE) { if (priv->flags & ALF_CLSCBTN_FLAG_IS_DEFAULT) { return (LRESULT)DLGC_DEFPUSHBUTTON; } else { return (LRESULT)DLGC_UNDEFPUSHBUTTON; } } else if (uMsg == WM_SETFONT) { ALF_InvalidateLayout(GetParent(hwnd)); InvalidateRect(hwnd, NULL, TRUE); } else if (uMsg == WM_DESTROY) { WNDPROC o = priv->origWndProc; SetWindowLongPtr(hwnd, GWLP_WNDPROC, (LONG_PTR)o); ALF_ClassicButton_FreePriv(priv); return CallWindowProc(o, hwnd, uMsg, wParam, lParam); } return CallWindowProc(priv->origWndProc, hwnd, uMsg, wParam, lParam); } static HWND ALF_ClassicButton_Create(HWND win, WORD id, int x, int y, const TCHAR *text, ALFCustomButtonMeasureFunc measureFunc, ALFCustomButtonPaintFunc paintFunc, void *closure) { HWND hwndButton = CreateWindowEx(0, TEXT("BUTTON"), text, WS_CHILD | WS_TABSTOP | WS_VISIBLE | BS_OWNERDRAW, 0, 0, 100, 100, win, (HMENU)(ULONG_PTR)id, ALF_HINSTANCE, NULL); ALFClassicButtonPriv *priv = ALF_ClassicButton_InitializePriv(hwndButton); priv->measureFunc = measureFunc; priv->paintFunc = paintFunc; priv->closure = closure; int minwidth = 5625; int minheight = 1725; priv->origWndProc = (WNDPROC)GetWindowLongPtr(hwndButton, GWLP_WNDPROC); SetWindowLongPtr(hwndButton, GWLP_WNDPROC, (LONG_PTR)priv->thunk); ALF_AddControl(win, x, y, hwndButton, minwidth, minheight, ALF_LAYOUT_SIZE_QUERY | ALF_LAYOUT_INHERITFONT); return hwndButton; } /* Factory and helper functions for both button types */ HWND ALF_AddCustomButton(HWND win, WORD id, int x, int y, const TCHAR *text, ALFCustomButtonMeasureFunc measureFunc, ALFCustomButtonPaintFunc paintFunc, void *closure) { if (ALF_Compat_Is2k()) { return ALF_NtButton_Create(win, id, x, y, text, measureFunc, paintFunc, closure); } else { return ALF_ClassicButton_Create(win, id, x, y, text, measureFunc, paintFunc, closure); } } /* text measurement and painting functions */ SIZE ALF_CustomButtonMeasureText(HWND btn, void *closure, int dpi, HDC hdc, HANDLE theme, int themePartId, int themeStateId) { (void)closure; RECT r = { 0, 0, 0x7FFFFFFF, 100 }; if (theme) { int l = GetWindowTextLengthW(btn); WCHAR *text = ALF_New(WCHAR, (SIZE_T)l + 1); GetWindowTextW(btn, text, l + 1); ALF_Compat_GetThemeTextExtent(theme, hdc, themePartId, themeStateId, text, -1, DT_LEFT | DT_EXPANDTABS, &r, &r); ALF_Free(text); } else { TCHAR *text = ALF_Text(btn); DrawText(hdc, text, -1, &r, DT_LEFT | DT_EXPANDTABS | DT_CALCRECT); ALF_Free(text); } int xpadding = ALF_CentipointsToPixels(1200, dpi); SIZE s = { r.right - r.left + xpadding, r.bottom - r.top }; return s; } void ALF_CustomButtonPaintText(HWND btn, void *closure, int dpi, UINT itemState, HDC hdc, RECT *rc, HANDLE theme, int themePartId, int themeStateId) { (void)closure; (void)dpi; if (theme) { int l = GetWindowTextLengthW(btn); WCHAR *text = ALF_New(WCHAR, (SIZE_T)l + 1); GetWindowTextW(btn, text, l + 1); UINT style = DT_CENTER | DT_NOCLIP; if (itemState & ODS_NOACCEL) style |= DT_HIDEPREFIX; RECT textbounds = *rc; ALF_Compat_GetThemeTextExtent(theme, hdc, themePartId, themeStateId, text, -1, style, rc, &textbounds); RECT texttarget = *rc; texttarget.top += ((rc->bottom - rc->top) - (textbounds.bottom - textbounds.top)) / 2; texttarget.left += ((rc->right - rc->left) - (textbounds.right - textbounds.left)) / 2; texttarget.right = texttarget.left + (textbounds.right - textbounds.left); texttarget.bottom = texttarget.top + (textbounds.bottom - textbounds.top); ALF_Compat_DrawThemeText(theme, hdc, themePartId, themeStateId, text, -1, style, 0, &texttarget); ALF_Free(text); } else { TCHAR *textbuf = ALF_Text(btn); RECT textbounds = *rc; DrawText(hdc, textbuf, -1, &textbounds, DT_LEFT | DT_CALCRECT | DT_NOCLIP); textbounds.bottom++; // this appears to be the secret for correct vertical centering RECT texttarget = *rc; texttarget.top += (texttarget.bottom - texttarget.top - textbounds.bottom + textbounds.top) / 2; texttarget.left += (texttarget.right - texttarget.left - textbounds.right + textbounds.left) / 2; texttarget.right = texttarget.left + (textbounds.right - textbounds.left); texttarget.bottom = texttarget.top + (textbounds.bottom - textbounds.top); UINT style = DT_CENTER; COLORREF oldBkColor = SetBkColor(hdc, GetSysColor(COLOR_BTNFACE)); if (itemState & ODS_DISABLED) { ALF_Compat_DrawDisabledText(hdc, textbuf, -1, &texttarget, style); } else { COLORREF oldTextColor = SetTextColor(hdc, GetSysColor(COLOR_BTNTEXT)); DrawText(hdc, textbuf, -1, &texttarget, style); SetTextColor(hdc, oldTextColor); } SetBkColor(hdc, oldBkColor); ALF_Free(textbuf); } } SIZE ALF_CustomButtonMeasureIcon(HWND btn, void *closure, int dpi, HDC hdc, HANDLE theme, int themePartId, int themeState) { (void)btn; (void)dpi; (void)hdc; (void)theme; (void)themePartId; (void)themeState; return ALF_IconSize((HICON)closure); } void ALF_CustomButtonPaintIcon(HWND btn, void *closure, int dpi, UINT itemState, HDC hdc, RECT *rcContent, HANDLE theme, int themePartId, int themeStateId) { (void)btn; (void)dpi; (void)itemState; (void)theme; (void)themePartId; (void)themeStateId; SIZE iconsize = ALF_IconSize((HICON)closure); int iconx = (rcContent->right - rcContent->left - iconsize.cx) / 2; int icony = (rcContent->bottom - rcContent->top - iconsize.cy) / 2; DrawIconEx(hdc, rcContent->left + iconx, rcContent->top + icony, ((HICON)closure), 0, 0, 0, NULL, DI_NORMAL); }