diff options
| author | Jonas Kümmerlin <jonas@kuemmerlin.eu> | 2019-04-28 16:14:12 +0200 |
|---|---|---|
| committer | Jonas Kümmerlin <jonas@kuemmerlin.eu> | 2019-04-28 16:14:12 +0200 |
| commit | d64290aaffd4721518747713929d79a78ba963f4 (patch) | |
| tree | b891c41a49b5d5ad2ba3ba85e87fea262cb949c1 /alf/alfbutton.cpp | |
| parent | 6b301ea64ca71777e38611d8c49dd119808b4365 (diff) | |
add owner-drawn themed button with animation
Diffstat (limited to 'alf/alfbutton.cpp')
| -rw-r--r-- | alf/alfbutton.cpp | 432 |
1 files changed, 429 insertions, 3 deletions
diff --git a/alf/alfbutton.cpp b/alf/alfbutton.cpp index 9cf7c1d..fb8d1c3 100644 --- a/alf/alfbutton.cpp +++ b/alf/alfbutton.cpp @@ -1,5 +1,271 @@ #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; + UINT uxStatePrev; + UINT uxStateCurrent; + UINT itemStatePrev; + UINT itemStateCurrent; + DWORD uxDefaultAnimationDuration; + BOOL uxIsDefaultAnimating; +} 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, UINT 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, 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; + DWORD v = GetVersion(); + + int textlen = GetWindowTextLength(hwnd); + TCHAR *textbuf = ALF_New(TCHAR, textlen + 1); + GetWindowText(hwnd, textbuf, textlen+1); + + 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 (v >= 0x80000000) { + // 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; + + int textlen = GetWindowTextLength(hwnd); + TCHAR *textbuf = ALF_New(TCHAR, textlen + 1); + GetWindowText(hwnd, textbuf, textlen+1); + + 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) @@ -7,6 +273,8 @@ ALF__ButtonSubclassProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam, UINT (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); @@ -50,7 +318,160 @@ ALF__ButtonSubclassProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam, UINT SelectFont(hdc, oldFont); ReleaseDC(hwnd, hdc); + } else if (uMsg == 0x2000 + WM_DRAWITEM) { + LPDRAWITEMSTRUCT dis = (DRAWITEMSTRUCT *)lParam; + DWORD v = GetVersion(); + + if (!ALF_Compat_BufferedPaintRenderAnimation(hwnd, dis->hDC)) { + if (priv->hTheme) { + // Draw XP style themed button + UINT 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 == (UINT)-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 (LOBYTE(LOWORD(v)) < 4) { + // Draw pre-95 style button + ALF__Button_Render3x(hwnd, priv, dis); + } else { + // Draw 95 style button + ALF__Button_Render95(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); } @@ -63,15 +484,20 @@ ALF_AddButton(HWND win, WORD id, UINT x, UINT y, const TCHAR *text) HWND hwndButton = CreateWindowEx(0, TEXT("BUTTON"), text, - WS_CHILD | WS_TABSTOP | WS_VISIBLE | BS_PUSHBUTTON | BS_MULTILINE, + 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); - ALF_Compat_SetWindowSubclass(hwndButton, ALF__ButtonSubclassProc, 0, 0); - SetWindowPos(hwndButton, NULL, 0, 0, 0, 0, SWP_NOMOVE|SWP_NOSIZE|SWP_NOZORDER|SWP_FRAMECHANGED); + ALFButtonPriv *priv = ALF_New(ALFButtonPriv, 1); + priv->uxStateCurrent = (UINT)-1; + priv->uxStatePrev = (UINT)-1; + + ALF_Compat_SetWindowSubclass(hwndButton, ALF__ButtonSubclassProc, 0, (DWORD_PTR)priv); + + SendMessage(hwndButton, WM_THEMECHANGED, 0, 0); ALFWidgetLayoutParams p; ZeroMemory(&p, sizeof(p)); |
