Signed-off-by: Gabriel Ivăncescu gabrielopcode@gmail.com ---
Command Links have hot-tracking even when they don't use a theme. The default glyph (green arrow) is used when the button has no image (bitmap/icon) or imagelist, and seems to be hardcoded (i.e. BM_GETIMAGE and BCM_GETIMAGELIST must return NULL to pass the tests).
I've added the glyph as a bitmap with 3 hardcoded states to avoid duplication, they are very close to the ones in Windows 7 in appearance. I don't know where Windows stores them but I placed them in comctl32's resources. They were created from scratch by just filling a custom-drawn arrow shape and adding a white outline + shadow to it. (I can supply the 1024x1024 original image before I downsampled it, if that's needed)
Note that the glyphs are 17x17, which seems weird, but it is the exact size required to fit the alignment on Windows. I've been setting an image with BM_SETIMAGE on Windows and comparing to see the button's text alignment, only a 17x17 image properly aligned the text. They are always 17x17, even if the app is DPI aware and the font is larger.
Lastly the "disabled" state of the glyph is white with 50% transparency (no shadow but a dark outline). When changing the background color on Windows, this is what's needed to match it.
Note that this bitmap glyph is only used when there's no theme. Themes use BP_COMMANDLINKGLYPH to draw the default glyph and it looks differently. And it also works with BCCL_NOGLYPH in the button's image list (invalid ImageList with size 0).
dlls/comctl32/button.c | 191 +++++++++++++++++++++++++++++++++- dlls/comctl32/comctl32.h | 3 + dlls/comctl32/comctl32.rc | 3 + dlls/comctl32/idb_cmdlink.bmp | Bin 0 -> 3522 bytes 4 files changed, 195 insertions(+), 2 deletions(-) create mode 100644 dlls/comctl32/idb_cmdlink.bmp
diff --git a/dlls/comctl32/button.c b/dlls/comctl32/button.c index baa58d8..4f2fbce 100644 --- a/dlls/comctl32/button.c +++ b/dlls/comctl32/button.c @@ -105,6 +105,7 @@ static void GB_Paint( const BUTTON_INFO *infoPtr, HDC hDC, UINT action ); static void UB_Paint( const BUTTON_INFO *infoPtr, HDC hDC, UINT action ); static void OB_Paint( const BUTTON_INFO *infoPtr, HDC hDC, UINT action ); static void SB_Paint( const BUTTON_INFO *infoPtr, HDC hDC, UINT action ); +static void CL_Paint( const BUTTON_INFO *infoPtr, HDC hDC, UINT action ); static void BUTTON_CheckAutoRadioButton( HWND hwnd ); static void get_split_button_rects(const BUTTON_INFO*, const RECT*, RECT*, RECT*); static BOOL notify_split_button_dropdown(const BUTTON_INFO*, const POINT*, HWND); @@ -161,8 +162,8 @@ static const pfPaint btnPaintFunc[MAX_BTN_TYPE] = OB_Paint, /* BS_OWNERDRAW */ SB_Paint, /* BS_SPLITBUTTON */ SB_Paint, /* BS_DEFSPLITBUTTON */ - PB_Paint, /* BS_COMMANDLINK */ - PB_Paint /* BS_DEFCOMMANDLINK */ + CL_Paint, /* BS_COMMANDLINK */ + CL_Paint /* BS_DEFCOMMANDLINK */ };
typedef void (*pfThemedPaint)( HTHEME theme, const BUTTON_INFO *infoPtr, HDC hdc, int drawState, UINT dtflags, BOOL focused); @@ -219,6 +220,12 @@ static const pfGetIdealSize btnGetIdealSizeFunc[MAX_BTN_TYPE] = { PB_GetIdealSize /* BS_DEFCOMMANDLINK */ };
+/* Fixed margin for command links, regardless of DPI (based on tests done on Windows) */ +enum { command_link_margin = 6 }; + +/* The width and height for the default command link glyph (when there's no image) */ +enum { command_link_defglyph_size = 17 }; + static inline UINT get_button_type( LONG window_style ) { return (window_style & BS_TYPEMASK); @@ -2297,6 +2304,186 @@ static void draw_split_button_dropdown_glyph(const BUTTON_INFO *infoPtr, HDC hdc }
+/********************************************************************** + * Command Link Functions + */ +static void CL_Paint( const BUTTON_INFO *infoPtr, HDC hDC, UINT action ) +{ + LONG style = GetWindowLongW(infoPtr->hwnd, GWL_STYLE); + LONG state = infoPtr->state; + + RECT rc, content_rect; + NMCUSTOMDRAW nmcd; + HPEN pen, old_pen; + HBRUSH old_brush; + INT old_bk_mode; + LRESULT cdrf; + HWND parent; + HRGN hrgn; + + GetClientRect(infoPtr->hwnd, &rc); + + /* Command Links are not affected by the button's font, and are based + on the default message font. Furthermore, they are not affected by + any of the alignment styles (and always align with the top-left). */ + if (!(parent = GetParent(infoPtr->hwnd))) parent = infoPtr->hwnd; + SendMessageW(parent, WM_CTLCOLORBTN, (WPARAM)hDC, (LPARAM)infoPtr->hwnd); + + hrgn = set_control_clipping(hDC, &rc); + + pen = CreatePen(PS_SOLID, 1, GetSysColor(COLOR_WINDOWFRAME)); + old_pen = SelectObject(hDC, pen); + old_brush = SelectObject(hDC, GetSysColorBrush(COLOR_BTNFACE)); + old_bk_mode = SetBkMode(hDC, TRANSPARENT); + + init_custom_draw(&nmcd, infoPtr, hDC, &rc); + + /* Send erase notifications */ + cdrf = SendMessageW(parent, WM_NOTIFY, nmcd.hdr.idFrom, (LPARAM)&nmcd); + if (cdrf & CDRF_SKIPDEFAULT) goto cleanup; + content_rect = rc; + + if (get_button_type(style) == BS_DEFCOMMANDLINK) + { + if (action != ODA_FOCUS) + Rectangle(hDC, rc.left, rc.top, rc.right, rc.bottom); + InflateRect(&rc, -1, -1); + } + + /* Skip the frame drawing if only focus has changed */ + if (action != ODA_FOCUS) + { + if (!(state & (BST_HOT | BST_PUSHED | BST_CHECKED | BST_INDETERMINATE))) + FillRect(hDC, &rc, GetSysColorBrush(COLOR_BTNFACE)); + else + { + UINT flags = DFCS_BUTTONPUSH; + + if (style & BS_FLAT) flags |= DFCS_MONO; + else if (state & BST_PUSHED) flags |= DFCS_PUSHED; + + if (state & (BST_CHECKED | BST_INDETERMINATE)) + flags |= DFCS_CHECKED; + DrawFrameControl(hDC, &rc, DFC_BUTTON, flags); + } + } + + if (cdrf & CDRF_NOTIFYPOSTERASE) + { + nmcd.dwDrawStage = CDDS_POSTERASE; + SendMessageW(parent, WM_NOTIFY, nmcd.hdr.idFrom, (LPARAM)&nmcd); + } + + /* Send paint notifications */ + nmcd.dwDrawStage = CDDS_PREPAINT; + cdrf = SendMessageW(parent, WM_NOTIFY, nmcd.hdr.idFrom, (LPARAM)&nmcd); + if (cdrf & CDRF_SKIPDEFAULT) goto cleanup; + + if (!(cdrf & CDRF_DOERASE) && action != ODA_FOCUS) + { + UINT flags = IsWindowEnabled(infoPtr->hwnd) ? DSS_NORMAL : DSS_DISABLED; + COLORREF old_color = SetTextColor(hDC, GetSysColor(flags == DSS_NORMAL ? + COLOR_BTNTEXT : COLOR_GRAYTEXT)); + HIMAGELIST defimg = NULL; + NONCLIENTMETRICSW ncm; + UINT txt_h = 0; + SIZE img_size; + + /* Command Links ignore the margins of the image list or its alignment */ + if (infoPtr->u.image || infoPtr->imagelist.himl) + img_size = BUTTON_GetImageSize(infoPtr); + else + { + img_size.cx = img_size.cy = command_link_defglyph_size; + defimg = ImageList_LoadImageW(COMCTL32_hModule, (LPCWSTR)MAKEINTRESOURCE(IDB_CMDLINK), + img_size.cx, 3, CLR_NONE, IMAGE_BITMAP, LR_CREATEDIBSECTION); + } + + /* Shrink rect by the command link margin, except on bottom (just the frame) */ + InflateRect(&content_rect, -command_link_margin, -command_link_margin); + content_rect.bottom += command_link_margin - 2; + + ncm.cbSize = sizeof(ncm); + if (SystemParametersInfoW(SPI_GETNONCLIENTMETRICS, sizeof(ncm), &ncm, 0)) + { + LONG note_weight = ncm.lfMessageFont.lfWeight; + RECT r = content_rect; + WCHAR *text; + HFONT font; + + if (img_size.cx) r.left += img_size.cx + command_link_margin; + + /* Draw the text */ + ncm.lfMessageFont.lfWeight = FW_BOLD; + if ((font = CreateFontIndirectW(&ncm.lfMessageFont))) + { + if ((text = get_button_text(infoPtr))) + { + SelectObject(hDC, font); + txt_h = DrawTextW(hDC, text, -1, &r, + DT_TOP | DT_LEFT | DT_WORDBREAK | DT_END_ELLIPSIS); + heap_free(text); + } + DeleteObject(font); + } + + /* Draw the note */ + ncm.lfMessageFont.lfWeight = note_weight; + if (infoPtr->note && (font = CreateFontIndirectW(&ncm.lfMessageFont))) + { + r.top += txt_h + 2; + SelectObject(hDC, font); + DrawTextW(hDC, infoPtr->note, infoPtr->note_length, &r, + DT_TOP | DT_LEFT | DT_WORDBREAK | DT_NOPREFIX); + DeleteObject(font); + } + } + + /* Position the image at the vertical center of the drawn text (not note) */ + txt_h = min(txt_h, content_rect.bottom - content_rect.top); + if (img_size.cy < txt_h) content_rect.top += (txt_h - img_size.cy) / 2; + + content_rect.right = content_rect.left + img_size.cx; + content_rect.bottom = content_rect.top + img_size.cy; + + if (defimg) + { + int i = 0; + if (flags == DSS_DISABLED) i = 2; + else if (state & BST_HOT) i = 1; + + ImageList_Draw(defimg, i, hDC, content_rect.left, content_rect.top, ILD_NORMAL); + ImageList_Destroy(defimg); + } + else + BUTTON_DrawImage(infoPtr, hDC, NULL, flags, &content_rect); + + SetTextColor(hDC, old_color); + } + + if (cdrf & CDRF_NOTIFYPOSTPAINT) + { + nmcd.dwDrawStage = CDDS_POSTPAINT; + SendMessageW(parent, WM_NOTIFY, nmcd.hdr.idFrom, (LPARAM)&nmcd); + } + if (cdrf & CDRF_SKIPPOSTPAINT) goto cleanup; + + if (action == ODA_FOCUS || (state & BST_FOCUS)) + { + InflateRect(&rc, -2, -2); + DrawFocusRect(hDC, &rc); + } + +cleanup: + SelectObject(hDC, old_pen); + SelectObject(hDC, old_brush); + SetBkMode(hDC, old_bk_mode); + SelectClipRgn(hDC, hrgn); + if (hrgn) DeleteObject(hrgn); + DeleteObject(pen); +} + + /********************************************************************** * Themed Paint Functions */ diff --git a/dlls/comctl32/comctl32.h b/dlls/comctl32/comctl32.h index b68b914..f3e889c 100644 --- a/dlls/comctl32/comctl32.h +++ b/dlls/comctl32/comctl32.h @@ -80,6 +80,9 @@ extern HBRUSH COMCTL32_hPattern55AABrush DECLSPEC_HIDDEN;
#define IDT_CHECK 401
+/* Command Link arrow */ +#define IDB_CMDLINK 402 +
/* Cursors */ #define IDC_MOVEBUTTON 102 diff --git a/dlls/comctl32/comctl32.rc b/dlls/comctl32/comctl32.rc index 3f8e148..c9aa1ba 100644 --- a/dlls/comctl32/comctl32.rc +++ b/dlls/comctl32/comctl32.rc @@ -144,6 +144,9 @@ IDB_HIST_SMALL BITMAP idb_hist_small.bmp /* @makedep: idb_hist_large.bmp */ IDB_HIST_LARGE BITMAP idb_hist_large.bmp
+/* @makedep: idb_cmdlink.bmp */ +IDB_CMDLINK BITMAP idb_cmdlink.bmp + /* @makedep: idc_copy.cur */ IDC_COPY CURSOR idc_copy.cur
diff --git a/dlls/comctl32/idb_cmdlink.bmp b/dlls/comctl32/idb_cmdlink.bmp new file mode 100644 index 0000000000000000000000000000000000000000..4b3f07b3aae4259a653d8de2efa12ec413dcb252 GIT binary patch literal 3522 zcmb`JX-rgC6vr<v#T9L-Ye|7-OwdS!3&wsZG`3b-H9|tQe5h^B5SWw+`_dUekl+$a zM{R?9v`TH7mRPjZ#3CA7M8Y-*4pS?XfDR56l>M>2)Bk<ro6HEzV9H5;@4ol$z5jR4 zJ9pc#?bci-**aW(z#8x}aA97!GWHef-Dg{nUW|X4f-0`&1y+Og;7zauxB?g8%qBy_ z**7LqKLd@t!P{Uf*bY7b{$MGXNIa9FflvJ}b15h&@G}~XXP-QI($Lk_WhyT(zXHWz zz~fm97J=ztJev#+t9EQ`?DD9nsDPN5n3>j>j&Jz5;r`7s7!0uk0|OmtW+nMOaHXp5 z+d^e!<!`9t@oWZuz#U8lwl+#5T~N~6URTl&OjyHKk0mB1t|~1pJqv%n%gM<}i-?Gr zXV<{~LH@oQUVDUugsf|BZq9EVGKoI-82K11)b;55@7%d_32lPFMz9jh2Yg<Pts$Dk zl}{P1?6goPIMjg5Xw&I*aiS=m04d;Dc6PQ_rBd;4j%7S=uK_-{13W!Fw_!~EgnlC_ z&0(~xg3+=nMr-cQr+@p*-Pf*NJCAleo_7IXV)z<?6=iIVw4N~XX~q8l{%z0@#?^qt zXhU9nD=I3o;ZG(=6$IgUYHF%#bW7>Y5`{t$c<$V}eT|Kcr6!Z9wxgrtpP`{40exv_ zO#pcpGl|VrzSP~_-E!v4nQZjr>p=i`o#RL&)Eq#HCML0^Wn2x&SPcK&4R}OGMlMz= zm99ZSL7@!|4PV2bbg;*4HlM=W*(tY|%c7zpWm{WYjW8he<}@A@zci|7&tK7$Qm3W^ z_ci2y%Y!_AXI6IWw~LP-KQ=%s0UCVnEYHu+|3cCj6nc-h9~8A_6=k$WQ<_;#2U|6? zzQKb$?=vgPN24`lf0C1vw-go@W@Bs|!kFe9C@wBe!5YNlkjbd4t2?DutKXCR@v^_a z-*DoAPJHKvFD<^zBo^f}vf~##+n}jd4`Y2OLU$g^ir(H{<H=T?xWVX4ORJd$JxT{? zo9#8YFB*;JeT<8X@OdBD3;4L=@OwE9DK~h|Dk&*B5gi@P=Z0h>BO^}_-`h`XE-UDj z@7aWiEsda-mX;a_1OhKf<KcetuTYRX#^bBEofBVgufd(sYPDOD-yfd&pYbdP9tXdF zK9;@6-D!*uek70#^Wrkv4G6;mrIjYp=hs6i;^t=*esdS8{|uuyau?FvQ_R9iMq3Lu z(BR-;Pgz;n1qdt$Zg{>TX^cSQ?<Cq?6H2Ok&@k+xC}S8Y{#Z!!t}`p!Do1NrypiJh z2xID-+S=M9)z#I9^?JRos;Vj-x!+?OPknv;Y2<|O<5oi~tKdu4@bGXg+V+DXFbIYL zZTvZqW*lJ>K4-lNIT&neYAVM6*aQ)NM4IYMV^c{W&H9zmtO7<WZj7yAbqF_@_lq#^ zmtj75hlhu|1qTOz4Bz=$#&eT~J^W-sLc*uEedK%8&&FO9h`AHf+1aTD1@KwW6eZL2 z1B|ADIfq=y)b&VgZ*MmqJ$m#L^j!d^In}rejXWkX=Mpsf#@CRwY)km8K%Vx)$1IQv zzj87&Gh=MqjCYSU*B$xHHFcRpmkdTO=@!Z!m5EqOnsak=Bhi<iF<)}3(Pt8;Ttxq8 zl(kP>L)Lz7ulyFa7rB}t*E=c^`%L8MFv^h~Kkguw%BZluQ0(dHG3DjuC896iR~?=$ z?KEyd;{r5VUQ9#wF)1nOEzFzq&^(WQZ6D&`<6**v@gIU*Ht=|W*w@$h@aom8aS-5V zWd2Y1nZ=<^hKBrsxVX60@$vB=;(Y1oEG9R%E8+eE?hDQfSr}J3?76$pfS=d+xN;f? M7n=+XuJa=HAE4=@9{>OV
literal 0 HcmV?d00001