#include "alfpriv.h" #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 typedef struct { BOOL isDefault; BOOL isHot; HTHEME hTheme; int uxStatePrev; int uxStateCurrent; UINT itemStatePrev; UINT itemStateCurrent; DWORD uxDefaultAnimationDuration; BOOL uxIsDefaultAnimating; int dpi; } ALFButtonPriv; static void CALLBACK ALF__Button_DefaultAnimatingTimerProc(HWND hwnd, UINT uMsg, UINT_PTR idEvent, DWORD dwTime) { (void)uMsg; (void)dwTime; ALFButtonPriv *priv = (ALFButtonPriv *)idEvent; priv->uxIsDefaultAnimating = !priv->uxIsDefaultAnimating; InvalidateRect(hwnd, NULL, FALSE); } static BOOL CALLBACK ALF__Button_Text_DrawStateProc(HDC hdc, LPARAM lData, WPARAM wData, int cx, int cy) { (void)wData; int oldBkMode = SetBkMode(hdc, TRANSPARENT); RECT r = { 0, 0, cx, cy }; DrawText(hdc, (TCHAR*)lData, -1, &r, (UINT)wData); SetBkMode(hdc, oldBkMode); return TRUE; } static void ALF__Button_RenderUxtheme(HWND hwnd, ALFButtonPriv *priv, int uxstate, UINT itemState, HDC hDC, LPRECT pRcItem) { if (ALF_Compat_IsThemeBackgroundPartiallyTransparent(priv->hTheme, BP_PUSHBUTTON, uxstate)) { ALF_Compat_DrawThemeParentBackground(hwnd, hDC, pRcItem); } RECT r = *pRcItem; InflateRect(&r, 1, 1); // HACK! get rid of 1px transparent border ALF_Compat_DrawThemeBackground(priv->hTheme, hDC, BP_PUSHBUTTON, uxstate, &r, pRcItem); RECT content = r; ALF_Compat_GetThemeBackgroundContentRect(priv->hTheme, hDC, BP_PUSHBUTTON, uxstate, &r, &content); if ((itemState & ODS_FOCUS) && !(itemState & ODS_NOFOCUSRECT)) DrawFocusRect(hDC, &content); int textlen = GetWindowTextLengthW(hwnd); WCHAR *textbuf = ALF_New(WCHAR, (SIZE_T)textlen + 1); GetWindowTextW(hwnd, textbuf, textlen+1); UINT style = DT_CENTER; if (itemState & ODS_NOACCEL) style |= DT_HIDEPREFIX; RECT textbounds = content; ALF_Compat_GetThemeTextExtent(priv->hTheme, hDC, BP_PUSHBUTTON, uxstate, textbuf, -1, style, &content, &textbounds); RECT texttarget = content; texttarget.top += ((content.bottom - content.top) - (textbounds.bottom - textbounds.top)) / 2; texttarget.left += ((content.right - content.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(priv->hTheme, hDC, BP_PUSHBUTTON, uxstate, textbuf, -1, style, 0, &texttarget); ALF_Free(textbuf); } static void ALF__Button_Render95(HWND hwnd, ALFButtonPriv *priv, LPDRAWITEMSTRUCT dis) { RECT r = dis->rcItem; TCHAR *textbuf = ALF_Text(hwnd); if (priv->isDefault) { 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; if (dis->itemState & ODS_HOTLIGHT) dfcs |= DFCS_HOT; DrawFrameControl(dis->hDC, &r, DFC_BUTTON, dfcs); if ((dis->itemState & ODS_FOCUS) && !(dis->itemState & ODS_NOFOCUSRECT)) { RECT f = r; InflateRect(&f, -1, -1); DrawFocusRect(dis->hDC, &f); } RECT textbounds = dis->rcItem; DrawText(dis->hDC, textbuf, -1, &textbounds, DT_LEFT | DT_CALCRECT); RECT texttarget = dis->rcItem; texttarget.top += ((dis->rcItem.bottom - dis->rcItem.top) - (textbounds.bottom - textbounds.top)) / 2 - 1; texttarget.left += ((dis->rcItem.right - dis->rcItem.left) - (textbounds.right - textbounds.left)) / 2; texttarget.right = texttarget.left + (textbounds.right - textbounds.left); texttarget.bottom = texttarget.top + (textbounds.bottom - textbounds.top); if (dis->itemState & ODS_SELECTED) { texttarget.top += 1; texttarget.left += 1; texttarget.right += 1; texttarget.bottom += 1; } UINT style = DT_CENTER; if (dis->itemState & ODS_NOACCEL) style |= DT_HIDEPREFIX; if (dis->itemState & ODS_DISABLED) { if (ALF_Compat_IsWin9x()) { // Win9x just uses gray text. DSS_DISABLED is broken there, too, // so we can't get the NT look even if we wanted to COLORREF oldTextColor = SetTextColor(dis->hDC, GetSysColor(COLOR_GRAYTEXT)); POINT oldorg = { 0, 0 }; SetViewportOrgEx(dis->hDC, texttarget.left, texttarget.top, &oldorg); ALF__Button_Text_DrawStateProc(dis->hDC, (LPARAM)textbuf, (WPARAM)style, texttarget.right - texttarget.left, texttarget.bottom - texttarget.top); SetViewportOrgEx(dis->hDC, oldorg.x, oldorg.y, NULL); SetTextColor(dis->hDC, oldTextColor); } else { DrawState(dis->hDC, NULL, ALF__Button_Text_DrawStateProc, (LPARAM)textbuf, (WPARAM)style, texttarget.left, texttarget.top, texttarget.right - texttarget.left, texttarget.bottom - texttarget.top, DST_COMPLEX | DSS_DISABLED); } } else { POINT oldorg = { 0, 0 }; SetViewportOrgEx(dis->hDC, texttarget.left, texttarget.top, &oldorg); ALF__Button_Text_DrawStateProc(dis->hDC, (LPARAM)textbuf, (WPARAM)style, texttarget.right - texttarget.left, texttarget.bottom - texttarget.top); SetViewportOrgEx(dis->hDC, oldorg.x, oldorg.y, NULL); } ALF_Free(textbuf); } static void ALF__Button_Render3x(HWND hwnd, ALFButtonPriv *priv, LPDRAWITEMSTRUCT dis) { RECT r = dis->rcItem; ALF_Compat_DrawThemeParentBackground(hwnd, dis->hDC, &r); TCHAR *textbuf = ALF_Text(hwnd); HBRUSH framecolor = GetSysColorBrush(COLOR_WINDOWFRAME); HBRUSH facecolor = GetSysColorBrush(COLOR_BTNFACE); // black frame if (priv->isDefault) { RECT rt = { r.left + 1, r.top, r.right - 1, r.top + 2 }; RECT rl = { r.left, r.top + 1, r.left + 2, r.bottom - 1 }; RECT rr = { r.right - 2, r.top + 1, r.right, r.bottom - 1 }; RECT rb = { r.left + 1, r.bottom - 2, r.right - 1, r.bottom }; FillRect(dis->hDC, &rt, framecolor); FillRect(dis->hDC, &rl, framecolor); FillRect(dis->hDC, &rr, framecolor); FillRect(dis->hDC, &rb, framecolor); InflateRect(&r, -2, -2); } else { RECT rt = { r.left + 1, r.top, r.right - 1, r.top + 1 }; RECT rl = { r.left, r.top + 1, r.left + 1, r.bottom - 1 }; RECT rr = { r.right - 1, r.top + 1, r.right, r.bottom - 1 }; RECT rb = { r.left + 1, r.bottom - 1, r.right - 1, r.bottom }; FillRect(dis->hDC, &rt, framecolor); FillRect(dis->hDC, &rl, framecolor); FillRect(dis->hDC, &rr, framecolor); FillRect(dis->hDC, &rb, framecolor); InflateRect(&r, -1, -1); } // 3d button if (dis->itemState & ODS_SELECTED) { DrawEdge(dis->hDC, &r, BDR_SUNKENOUTER, BF_TOPLEFT); RECT f = r; f.left++; f.top++; FillRect(dis->hDC, &f, facecolor); InflateRect(&r, -2, -2); } else { DrawEdge(dis->hDC, &r, BDR_RAISEDINNER, BF_RECT); InflateRect(&r, -1, -1); DrawEdge(dis->hDC, &r, BDR_RAISEDINNER, BF_RECT); InflateRect(&r, -1, -1); FillRect(dis->hDC, &r, facecolor); } RECT textbounds = dis->rcItem; DrawText(dis->hDC, textbuf, -1, &textbounds, DT_LEFT | DT_CALCRECT); RECT texttarget = dis->rcItem; texttarget.top += ((dis->rcItem.bottom - dis->rcItem.top) - (textbounds.bottom - textbounds.top)) / 2; texttarget.left += ((dis->rcItem.right - dis->rcItem.left) - (textbounds.right - textbounds.left)) / 2; texttarget.right = texttarget.left + (textbounds.right - textbounds.left); texttarget.bottom = texttarget.top + (textbounds.bottom - textbounds.top); if (dis->itemState & ODS_SELECTED) { texttarget.top += 1; texttarget.left += 1; texttarget.right += 1; texttarget.bottom += 1; } if (dis->itemState & ODS_FOCUS) { DrawFocusRect(dis->hDC, &texttarget); } int oldBkMode = SetBkMode(dis->hDC, TRANSPARENT); if (dis->itemState & ODS_DISABLED) { // FIXME! This is how NT 3.51 does it, but Win 3.1 is different COLORREF oldTextColor = SetTextColor(dis->hDC, GetSysColor(COLOR_BTNSHADOW)); DrawText(dis->hDC, textbuf, -1, &texttarget, DT_CENTER); SetTextColor(dis->hDC, oldTextColor); } else { DrawText(dis->hDC, textbuf, -1, &texttarget, DT_CENTER); } SetBkMode(dis->hDC, oldBkMode); ALF_Free(textbuf); } /* BUTTON */ static LRESULT CALLBACK ALF__ButtonSubclassProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam, UINT_PTR uIdSubclass, DWORD_PTR dwRefData) { (void)uIdSubclass; (void)dwRefData; ALFButtonPriv *priv = (ALFButtonPriv *)dwRefData; if (uMsg == ALF_WM_QUERYSIZE) { HDC hdc = GetDC(hwnd); HFONT font = (HFONT)SendMessage(hwnd, WM_GETFONT, 0, 0); HFONT oldFont = 0; if (font) oldFont = SelectFont(hdc, font); // calc drawtext style LONG style = GetWindowLong(hwnd, GWL_STYLE); UINT format = DT_LEFT | DT_EXPANDTABS | DT_CALCRECT; if ((style & BS_MULTILINE) == 0) format |= DT_SINGLELINE; RECT r = { 0, 0, 0x7FFFFFFF, 100 }; TCHAR *textbuf = ALF_Text(hwnd); DrawText(hdc, textbuf, -1, &r, format); ALF_Free(textbuf); int xpadding = ALF_Compat_GetSystemMetricsForDpi(SM_CXEDGE, (UINT)priv->dpi) * 2 + 6; int ypadding = ALF_Compat_GetSystemMetricsForDpi(SM_CYEDGE, (UINT)priv->dpi) * 2 + 4; SIZE *pSize = (SIZE*)(void*)lParam; if (pSize->cx < r.right - r.left + xpadding) { pSize->cx = r.right - r.left + xpadding; } if (pSize->cy < r.bottom - r.top + ypadding) { pSize->cy = r.bottom - r.top + ypadding; } if (pSize->cx < pSize->cy) { pSize->cx = pSize->cy; } if (font) SelectFont(hdc, oldFont); ReleaseDC(hwnd, hdc); } else if (uMsg == 0x2000 + WM_DRAWITEM) { LPDRAWITEMSTRUCT dis = (DRAWITEMSTRUCT *)lParam; if (!ALF_Compat_BufferedPaintRenderAnimation(hwnd, dis->hDC)) { if (priv->hTheme) { // Draw XP style themed button int stateid = PBS_NORMAL; if (priv->isDefault && IsChild(GetForegroundWindow(), hwnd)) stateid = priv->uxIsDefaultAnimating ? PBS_DEFAULTED_ANIMATING : PBS_DEFAULTED; if (priv->isHot) stateid = PBS_HOT; if (dis->itemState & ODS_SELECTED) stateid = PBS_PRESSED; if (dis->itemState & ODS_DISABLED) stateid = PBS_DISABLED; if (priv->uxStatePrev == -1) { // initial draw priv->uxStateCurrent = stateid; priv->itemStateCurrent = dis->itemState; if (priv->uxStateCurrent == PBS_DEFAULTED || priv->uxStateCurrent == PBS_DEFAULTED_ANIMATING) priv->uxStateCurrent = PBS_NORMAL; } priv->uxStatePrev = priv->uxStateCurrent; priv->uxStateCurrent = stateid; priv->itemStatePrev = priv->itemStateCurrent; priv->itemStateCurrent = dis->itemState; 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); priv->uxIsDefaultAnimating = 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__Button_DefaultAnimatingTimerProc); } } HDC hdcFrom = NULL, hdcTo = NULL; ALF_Compat_HANIMATIONBUFFER hbpAnimation; hbpAnimation = ALF_Compat_BeginBufferedAnimation(hwnd, dis->hDC, &dis->rcItem, 0, NULL, &animParams, &hdcFrom, &hdcTo); if (hbpAnimation) { HFONT font = (HFONT)GetCurrentObject(dis->hDC, OBJ_FONT); if (hdcFrom) { SelectFont(hdcFrom, font); ALF__Button_RenderUxtheme(hwnd, priv, priv->uxStatePrev, priv->itemStatePrev, hdcFrom, &dis->rcItem); } if (hdcTo) { SelectFont(hdcTo, font); ALF__Button_RenderUxtheme(hwnd, priv, priv->uxStateCurrent, priv->itemStateCurrent, hdcTo, &dis->rcItem); } ALF_Compat_EndBufferedAnimation(hbpAnimation, TRUE); } else { ALF__Button_RenderUxtheme(hwnd, priv, stateid, dis->itemState, dis->hDC, &dis->rcItem); } } else if (ALF_Compat_IsMinWindowsVersion(4, 0)) { // Draw 95 style button ALF__Button_Render95(hwnd, priv, dis); } else { // Draw pre-95 style button ALF__Button_Render3x(hwnd, priv, dis); } } return TRUE; } else if (uMsg == WM_GETDLGCODE) { if (priv->isDefault) { return DLGC_DEFPUSHBUTTON | DLGC_BUTTON; } else { return DLGC_UNDEFPUSHBUTTON | DLGC_BUTTON; } } else if (uMsg == BM_SETSTYLE) { // HACK! stop windows from resetting the owner draw flag priv->isDefault = (wParam & BS_DEFPUSHBUTTON) ? 1 : 0; return ALF_Compat_DefSubclassProc(hwnd, BM_SETSTYLE, (wParam & 0xFFFFFFF0) | BS_OWNERDRAW, lParam); } else if (uMsg == WM_THEMECHANGED) { 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); } else if (uMsg == WM_MOUSEMOVE) { if (!priv->isHot) { TRACKMOUSEEVENT tme; ZeroMemory(&tme, sizeof(tme)); tme.cbSize = sizeof(tme); tme.dwFlags = TME_LEAVE; tme.hwndTrack = hwnd; if (ALF_Compat_TrackMouseEvent(&tme)) { priv->isHot = TRUE; InvalidateRect(hwnd, NULL, FALSE); } } } else if (uMsg == WM_MOUSELEAVE) { if (priv->isHot) { priv->isHot = FALSE; InvalidateRect(hwnd, NULL, FALSE); } } else if (uMsg == WM_DESTROY) { ALF_Compat_CloseThemeData(priv->hTheme); ALF_Free(priv); ALF_Compat_RemoveWindowSubclass(hwnd, ALF__ButtonSubclassProc, 0); } else if (uMsg == WM_ERASEBKGND) { return TRUE; } else if (uMsg == WM_SETTEXT) { ALF_InvalidateLayout(GetParent(hwnd)); } else if (uMsg == WM_SETFONT) { ALF_InvalidateLayout(GetParent(hwnd)); } else if (uMsg == ALF_WM_DPICHANGE) { priv->dpi = (int)lParam; ALF_InvalidateLayout(GetParent(hwnd)); return TRUE; } return ALF_Compat_DefSubclassProc(hwnd, uMsg, wParam, lParam); } HWND ALF_AddButton(HWND win, WORD id, int x, int y, const TCHAR *text) { HWND hwndButton = CreateWindowEx(0, TEXT("BUTTON"), text, WS_CHILD | WS_TABSTOP | WS_VISIBLE | BS_PUSHBUTTON | BS_MULTILINE | BS_OWNERDRAW, 0, 0, 100, 100, win, (HMENU)(int)id, (HINSTANCE)GetWindowLongPtr(win, GWLP_HINSTANCE), NULL); ALFButtonPriv *priv = ALF_New(ALFButtonPriv, 1); priv->uxStateCurrent = -1; priv->uxStatePrev = -1; priv->dpi = 96; ALF_Compat_SetWindowSubclass(hwndButton, ALF__ButtonSubclassProc, 0, (DWORD_PTR)priv); SendMessage(hwndButton, WM_THEMECHANGED, 0, 0); ALF_AddWidget(win, x, y, hwndButton, 0, 0, ALF_LAYOUT_SIZE_QUERY | ALF_LAYOUT_INHERITFONT | ALF_LAYOUT_SENDDPICHANGE); return hwndButton; } void ALF_SetDefaultButton(HWND win, WORD id) { SendMessage(win, DM_SETDEFID, (WPARAM)id, 0); }