summaryrefslogtreecommitdiff
path: root/alf/alfbutton.cpp
diff options
context:
space:
mode:
authorJonas Kümmerlin <jonas@kuemmerlin.eu>2019-04-28 16:14:12 +0200
committerJonas Kümmerlin <jonas@kuemmerlin.eu>2019-04-28 16:14:12 +0200
commitd64290aaffd4721518747713929d79a78ba963f4 (patch)
treeb891c41a49b5d5ad2ba3ba85e87fea262cb949c1 /alf/alfbutton.cpp
parent6b301ea64ca71777e38611d8c49dd119808b4365 (diff)
add owner-drawn themed button with animation
Diffstat (limited to 'alf/alfbutton.cpp')
-rw-r--r--alf/alfbutton.cpp432
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));