2 * Theming - Scrollbar control
4 * Copyright (c) 2015 Mark Harmstone
6 * This library is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Lesser General Public
8 * License as published by the Free Software Foundation; either
9 * version 2.1 of the License, or (at your option) any later version.
11 * This library is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * Lesser General Public License for more details.
16 * You should have received a copy of the GNU Lesser General Public
17 * License along with this library; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
33 #include "wine/debug.h"
35 /* Minimum size of the thumb in pixels */
36 #define SCROLL_MIN_THUMB 6
38 /* Minimum size of the rectangle between the arrows */
39 #define SCROLL_MIN_RECT 4
43 SCROLL_NOWHERE
, /* Outside the scroll bar */
44 SCROLL_TOP_ARROW
, /* Top or left arrow */
45 SCROLL_TOP_RECT
, /* Rectangle between the top arrow and the thumb */
46 SCROLL_THUMB
, /* Thumb rectangle */
47 SCROLL_BOTTOM_RECT
, /* Rectangle between the thumb and the bottom arrow */
48 SCROLL_BOTTOM_ARROW
/* Bottom or right arrow */
51 static HWND tracking_win
= 0;
52 static enum SCROLL_HITTEST tracking_hot_part
= SCROLL_NOWHERE
;
54 WINE_DEFAULT_DEBUG_CHANNEL(theme_scroll
);
56 static void calc_thumb_dimensions(unsigned int size
, SCROLLINFO
*si
, unsigned int *thumbpos
, unsigned int *thumbsize
)
58 if (size
<= SCROLL_MIN_RECT
)
59 *thumbpos
= *thumbsize
= 0;
60 else if (si
->nPage
> si
->nMax
- si
->nMin
)
61 *thumbpos
= *thumbsize
= 0;
64 *thumbsize
= MulDiv(size
, si
->nPage
, si
->nMax
- si
->nMin
+ 1);
65 if (*thumbsize
< SCROLL_MIN_THUMB
) *thumbsize
= SCROLL_MIN_THUMB
;
67 else *thumbsize
= GetSystemMetrics(SM_CXVSCROLL
);
69 if (size
< *thumbsize
)
70 *thumbpos
= *thumbsize
= 0;
72 int max
= si
->nMax
- max(si
->nPage
- 1, 0);
77 *thumbpos
= MulDiv(size
, si
->nTrackPos
- si
->nMin
, max
- si
->nMin
);
82 static enum SCROLL_HITTEST
hit_test(HWND hwnd
, HTHEME theme
, POINT pt
)
85 DWORD style
= GetWindowLongW(hwnd
, GWL_STYLE
);
86 BOOL vertical
= style
& SBS_VERT
;
89 unsigned int offset
, size
, upsize
, downsize
, thumbpos
, thumbsize
;
91 GetWindowRect(hwnd
, &r
);
92 OffsetRect(&r
, -r
.left
, -r
.top
);
98 if (FAILED(GetThemePartSize(theme
, NULL
, SBP_ARROWBTN
, ABS_UPNORMAL
, NULL
, TS_DRAW
, &sz
))) {
99 WARN("Could not get up arrow size.\n");
104 if (FAILED(GetThemePartSize(theme
, NULL
, SBP_ARROWBTN
, ABS_DOWNNORMAL
, NULL
, TS_DRAW
, &sz
))) {
105 WARN("Could not get down arrow size.\n");
113 if (FAILED(GetThemePartSize(theme
, NULL
, SBP_ARROWBTN
, ABS_LEFTNORMAL
, NULL
, TS_DRAW
, &sz
))) {
114 WARN("Could not get left arrow size.\n");
119 if (FAILED(GetThemePartSize(theme
, NULL
, SBP_ARROWBTN
, ABS_RIGHTNORMAL
, NULL
, TS_DRAW
, &sz
))) {
120 WARN("Could not get right arrow size.\n");
126 if (pt
.x
< 0 || pt
.x
> r
.right
|| pt
.y
< 0 || pt
.y
> r
.bottom
)
127 return SCROLL_NOWHERE
;
129 if (size
< SCROLL_MIN_RECT
+ upsize
+ downsize
)
130 upsize
= downsize
= (size
- SCROLL_MIN_RECT
)/2;
133 return SCROLL_TOP_ARROW
;
135 if (offset
> size
- downsize
)
136 return SCROLL_BOTTOM_ARROW
;
138 si
.cbSize
= sizeof(si
);
140 if (!GetScrollInfo(hwnd
, SB_CTL
, &si
)) {
141 WARN("GetScrollInfo failed.\n");
142 return SCROLL_NOWHERE
;
145 calc_thumb_dimensions(size
- upsize
- downsize
, &si
, &thumbpos
, &thumbsize
);
147 if (offset
< upsize
+ thumbpos
)
148 return SCROLL_TOP_RECT
;
149 else if (offset
< upsize
+ thumbpos
+ thumbsize
)
152 return SCROLL_BOTTOM_RECT
;
155 static void redraw_part(HWND hwnd
, HTHEME theme
, enum SCROLL_HITTEST part
)
157 DWORD style
= GetWindowLongW(hwnd
, GWL_STYLE
);
158 BOOL vertical
= style
& SBS_VERT
;
161 unsigned int size
, upsize
, downsize
;
163 if (part
== SCROLL_NOWHERE
) { /* redraw everything */
164 InvalidateRect(hwnd
, NULL
, TRUE
);
168 GetWindowRect(hwnd
, &r
);
169 OffsetRect(&r
, -r
.left
, -r
.top
);
174 if (FAILED(GetThemePartSize(theme
, NULL
, SBP_ARROWBTN
, ABS_UPNORMAL
, NULL
, TS_DRAW
, &sz
))) {
175 WARN("Could not get up arrow size.\n");
180 if (FAILED(GetThemePartSize(theme
, NULL
, SBP_ARROWBTN
, ABS_DOWNNORMAL
, NULL
, TS_DRAW
, &sz
))) {
181 WARN("Could not get down arrow size.\n");
188 if (FAILED(GetThemePartSize(theme
, NULL
, SBP_ARROWBTN
, ABS_LEFTNORMAL
, NULL
, TS_DRAW
, &sz
))) {
189 WARN("Could not get left arrow size.\n");
194 if (FAILED(GetThemePartSize(theme
, NULL
, SBP_ARROWBTN
, ABS_RIGHTNORMAL
, NULL
, TS_DRAW
, &sz
))) {
195 WARN("Could not get right arrow size.\n");
201 if (size
< SCROLL_MIN_RECT
+ upsize
+ downsize
)
202 upsize
= downsize
= (size
- SCROLL_MIN_RECT
)/2;
206 if (part
== SCROLL_TOP_ARROW
) {
208 partrect
.bottom
= partrect
.top
+ upsize
;
210 partrect
.right
= partrect
.left
+ upsize
;
211 } else if (part
== SCROLL_BOTTOM_ARROW
) {
213 partrect
.top
= partrect
.bottom
- downsize
;
215 partrect
.left
= partrect
.right
- downsize
;
217 unsigned int thumbpos
, thumbsize
;
220 si
.cbSize
= sizeof(si
);
222 if (!GetScrollInfo(hwnd
, SB_CTL
, &si
)) {
223 WARN("GetScrollInfo failed.\n");
227 calc_thumb_dimensions(size
- upsize
- downsize
, &si
, &thumbpos
, &thumbsize
);
229 if (part
== SCROLL_TOP_RECT
) {
231 partrect
.top
= r
.top
+ upsize
;
232 partrect
.bottom
= partrect
.top
+ thumbpos
;
234 partrect
.left
= r
.left
+ upsize
;
235 partrect
.right
= partrect
.left
+ thumbpos
;
237 } else if (part
== SCROLL_THUMB
) {
239 partrect
.top
= r
.top
+ upsize
+ thumbpos
;
240 partrect
.bottom
= partrect
.top
+ thumbsize
;
242 partrect
.left
= r
.left
+ upsize
+ thumbpos
;
243 partrect
.right
= partrect
.left
+ thumbsize
;
245 } else if (part
== SCROLL_BOTTOM_RECT
) {
247 partrect
.top
= r
.top
+ upsize
+ thumbpos
+ thumbsize
;
248 partrect
.bottom
= r
.bottom
- downsize
;
250 partrect
.left
= r
.left
+ upsize
+ thumbpos
+ thumbsize
;
251 partrect
.right
= r
.right
- downsize
;
256 InvalidateRect(hwnd
, &partrect
, TRUE
);
259 static void scroll_event(HWND hwnd
, HTHEME theme
, UINT msg
, POINT pt
)
261 enum SCROLL_HITTEST hittest
;
264 if (GetWindowLongW(hwnd
, GWL_STYLE
) & (SBS_SIZEGRIP
| SBS_SIZEBOX
))
267 hittest
= hit_test(hwnd
, theme
, pt
);
272 hittest
= hit_test(hwnd
, theme
, pt
);
277 if (tracking_win
== hwnd
) {
278 hittest
= SCROLL_NOWHERE
;
283 tme
.cbSize
= sizeof(tme
);
284 tme
.dwFlags
= TME_QUERY
;
285 TrackMouseEvent(&tme
);
287 if (!(tme
.dwFlags
& TME_LEAVE
) || tme
.hwndTrack
!= hwnd
) {
288 tme
.dwFlags
= TME_LEAVE
;
289 tme
.hwndTrack
= hwnd
;
290 TrackMouseEvent(&tme
);
293 if (tracking_win
!= hwnd
&& msg
== WM_MOUSELEAVE
) {
294 redraw_part(hwnd
, theme
, SCROLL_NOWHERE
);
298 if (tracking_win
== hwnd
&& hittest
!= tracking_hot_part
) {
299 enum SCROLL_HITTEST oldhotpart
= tracking_hot_part
;
301 tracking_hot_part
= hittest
;
303 if (hittest
!= SCROLL_NOWHERE
)
304 redraw_part(hwnd
, theme
, hittest
);
308 if (oldhotpart
!= SCROLL_NOWHERE
)
309 redraw_part(hwnd
, theme
, oldhotpart
);
313 static void paint_scrollbar(HWND hwnd
, HTHEME theme
)
318 DWORD style
= GetWindowLongW(hwnd
, GWL_STYLE
);
319 BOOL vertical
= style
& SBS_VERT
;
320 BOOL disabled
= !IsWindowEnabled(hwnd
);
322 GetWindowRect(hwnd
, &r
);
323 OffsetRect(&r
, -r
.left
, -r
.top
);
325 dc
= BeginPaint(hwnd
, &ps
);
327 if (style
& SBS_SIZEBOX
|| style
& SBS_SIZEGRIP
) {
330 if (style
& SBS_SIZEBOXTOPLEFTALIGN
)
331 state
= SZB_TOPLEFTALIGN
;
333 state
= SZB_RIGHTALIGN
;
335 DrawThemeBackground(theme
, dc
, SBP_SIZEBOX
, state
, &r
, NULL
);
338 unsigned int thumbpos
, thumbsize
;
339 int uppertrackstate
, lowertrackstate
, thumbstate
;
340 RECT partrect
, trackrect
;
343 si
.cbSize
= sizeof(si
);
345 GetScrollInfo(hwnd
, SB_CTL
, &si
);
350 uppertrackstate
= SCRBS_DISABLED
;
351 lowertrackstate
= SCRBS_DISABLED
;
352 thumbstate
= SCRBS_DISABLED
;
354 uppertrackstate
= SCRBS_NORMAL
;
355 lowertrackstate
= SCRBS_NORMAL
;
356 thumbstate
= SCRBS_NORMAL
;
358 if (tracking_win
== hwnd
) {
359 if (tracking_hot_part
== SCROLL_TOP_RECT
)
360 uppertrackstate
= SCRBS_HOT
;
361 else if (tracking_hot_part
== SCROLL_BOTTOM_RECT
)
362 lowertrackstate
= SCRBS_HOT
;
363 else if (tracking_hot_part
== SCROLL_THUMB
)
364 thumbstate
= SCRBS_HOT
;
369 SIZE upsize
, downsize
;
370 int uparrowstate
, downarrowstate
;
373 uparrowstate
= ABS_UPDISABLED
;
374 downarrowstate
= ABS_DOWNDISABLED
;
376 uparrowstate
= ABS_UPNORMAL
;
377 downarrowstate
= ABS_DOWNNORMAL
;
379 if (tracking_win
== hwnd
) {
380 if (tracking_hot_part
== SCROLL_TOP_ARROW
)
381 uparrowstate
= ABS_UPHOT
;
382 else if (tracking_hot_part
== SCROLL_BOTTOM_ARROW
)
383 downarrowstate
= ABS_DOWNHOT
;
387 if (FAILED(GetThemePartSize(theme
, dc
, SBP_ARROWBTN
, uparrowstate
, NULL
, TS_DRAW
, &upsize
))) {
388 WARN("Could not get up arrow size.\n");
392 if (FAILED(GetThemePartSize(theme
, dc
, SBP_ARROWBTN
, downarrowstate
, NULL
, TS_DRAW
, &downsize
))) {
393 WARN("Could not get down arrow size.\n");
397 if (r
.bottom
- r
.top
- upsize
.cy
- downsize
.cy
< SCROLL_MIN_RECT
)
398 upsize
.cy
= downsize
.cy
= (r
.bottom
- r
.top
- SCROLL_MIN_RECT
)/2;
401 partrect
.bottom
= partrect
.top
+ upsize
.cy
;
402 DrawThemeBackground(theme
, dc
, SBP_ARROWBTN
, uparrowstate
, &partrect
, NULL
);
404 trackrect
.top
= partrect
.bottom
;
406 partrect
.bottom
= r
.bottom
;
407 partrect
.top
= partrect
.bottom
- downsize
.cy
;
408 DrawThemeBackground(theme
, dc
, SBP_ARROWBTN
, downarrowstate
, &partrect
, NULL
);
410 trackrect
.bottom
= partrect
.top
;
412 calc_thumb_dimensions(trackrect
.bottom
- trackrect
.top
, &si
, &thumbpos
, &thumbsize
);
415 partrect
.top
= trackrect
.top
;
416 partrect
.bottom
= partrect
.top
+ thumbpos
;
418 DrawThemeBackground(theme
, dc
, SBP_UPPERTRACKVERT
, uppertrackstate
, &partrect
, NULL
);
422 partrect
.top
= trackrect
.top
+ thumbpos
;
423 partrect
.bottom
= partrect
.top
+ thumbsize
;
425 DrawThemeBackground(theme
, dc
, SBP_THUMBBTNVERT
, thumbstate
, &partrect
, NULL
);
427 if (SUCCEEDED(GetThemePartSize(theme
, dc
, SBP_GRIPPERVERT
, thumbstate
, NULL
, TS_DRAW
, &grippersize
))) {
430 if (SUCCEEDED(GetThemeMargins(theme
, dc
, SBP_THUMBBTNVERT
, thumbstate
, TMT_CONTENTMARGINS
, &partrect
, &margins
))) {
431 if (grippersize
.cy
<= (thumbsize
- margins
.cyTopHeight
- margins
.cyBottomHeight
))
432 DrawThemeBackground(theme
, dc
, SBP_GRIPPERVERT
, thumbstate
, &partrect
, NULL
);
437 if (thumbpos
+ thumbsize
< trackrect
.bottom
- trackrect
.top
) {
438 partrect
.bottom
= trackrect
.bottom
;
439 partrect
.top
= trackrect
.top
+ thumbsize
+ thumbpos
;
441 DrawThemeBackground(theme
, dc
, SBP_LOWERTRACKVERT
, lowertrackstate
, &partrect
, NULL
);
444 SIZE leftsize
, rightsize
;
445 int leftarrowstate
, rightarrowstate
;
448 leftarrowstate
= ABS_LEFTDISABLED
;
449 rightarrowstate
= ABS_RIGHTDISABLED
;
451 leftarrowstate
= ABS_LEFTNORMAL
;
452 rightarrowstate
= ABS_RIGHTNORMAL
;
454 if (tracking_win
== hwnd
) {
455 if (tracking_hot_part
== SCROLL_TOP_ARROW
)
456 leftarrowstate
= ABS_LEFTHOT
;
457 else if (tracking_hot_part
== SCROLL_BOTTOM_ARROW
)
458 rightarrowstate
= ABS_RIGHTHOT
;
462 if (FAILED(GetThemePartSize(theme
, dc
, SBP_ARROWBTN
, leftarrowstate
, NULL
, TS_DRAW
, &leftsize
))) {
463 WARN("Could not get left arrow size.\n");
467 if (FAILED(GetThemePartSize(theme
, dc
, SBP_ARROWBTN
, rightarrowstate
, NULL
, TS_DRAW
, &rightsize
))) {
468 WARN("Could not get right arrow size.\n");
472 if (r
.right
- r
.left
- leftsize
.cx
- rightsize
.cx
< SCROLL_MIN_RECT
)
473 leftsize
.cx
= rightsize
.cx
= (r
.right
- r
.left
- SCROLL_MIN_RECT
)/2;
476 partrect
.right
= partrect
.left
+ leftsize
.cx
;
477 DrawThemeBackground(theme
, dc
, SBP_ARROWBTN
, leftarrowstate
, &partrect
, NULL
);
479 trackrect
.left
= partrect
.right
;
481 partrect
.right
= r
.right
;
482 partrect
.left
= partrect
.right
- rightsize
.cx
;
483 DrawThemeBackground(theme
, dc
, SBP_ARROWBTN
, rightarrowstate
, &partrect
, NULL
);
485 trackrect
.right
= partrect
.left
;
487 calc_thumb_dimensions(trackrect
.right
- trackrect
.left
, &si
, &thumbpos
, &thumbsize
);
490 partrect
.left
= trackrect
.left
;
491 partrect
.right
= partrect
.left
+ thumbpos
;
493 DrawThemeBackground(theme
, dc
, SBP_UPPERTRACKHORZ
, uppertrackstate
, &partrect
, NULL
);
497 partrect
.left
= trackrect
.left
+ thumbpos
;
498 partrect
.right
= partrect
.left
+ thumbsize
;
500 DrawThemeBackground(theme
, dc
, SBP_THUMBBTNHORZ
, thumbstate
, &partrect
, NULL
);
502 if (SUCCEEDED(GetThemePartSize(theme
, dc
, SBP_GRIPPERHORZ
, thumbstate
, NULL
, TS_DRAW
, &grippersize
))) {
505 if (SUCCEEDED(GetThemeMargins(theme
, dc
, SBP_THUMBBTNHORZ
, thumbstate
, TMT_CONTENTMARGINS
, &partrect
, &margins
))) {
506 if (grippersize
.cx
<= (thumbsize
- margins
.cxLeftWidth
- margins
.cxRightWidth
))
507 DrawThemeBackground(theme
, dc
, SBP_GRIPPERHORZ
, thumbstate
, &partrect
, NULL
);
512 if (thumbpos
+ thumbsize
< trackrect
.right
- trackrect
.left
) {
513 partrect
.right
= trackrect
.right
;
514 partrect
.left
= trackrect
.left
+ thumbsize
+ thumbpos
;
516 DrawThemeBackground(theme
, dc
, SBP_LOWERTRACKHORZ
, lowertrackstate
, &partrect
, NULL
);
524 LRESULT CALLBACK
THEMING_ScrollbarSubclassProc (HWND hwnd
, UINT msg
,
525 WPARAM wParam
, LPARAM lParam
,
528 const WCHAR
* themeClass
= WC_SCROLLBARW
;
533 TRACE("(%p, 0x%x, %lu, %lu, %lu)\n", hwnd
, msg
, wParam
, lParam
, dwRefData
);
537 result
= THEMING_CallOriginalClass(hwnd
, msg
, wParam
, lParam
);
538 OpenThemeData(hwnd
, themeClass
);
542 theme
= GetWindowTheme(hwnd
);
543 CloseThemeData(theme
);
544 return THEMING_CallOriginalClass(hwnd
, msg
, wParam
, lParam
);
546 case WM_THEMECHANGED
:
547 theme
= GetWindowTheme(hwnd
);
548 CloseThemeData(theme
);
549 OpenThemeData(hwnd
, themeClass
);
552 case WM_SYSCOLORCHANGE
:
553 theme
= GetWindowTheme(hwnd
);
554 if (!theme
) return THEMING_CallOriginalClass(hwnd
, msg
, wParam
, lParam
);
555 /* Do nothing. When themed, a WM_THEMECHANGED will be received, too,
556 * which will do the repaint. */
560 theme
= GetWindowTheme(hwnd
);
561 if (!theme
) return THEMING_CallOriginalClass(hwnd
, msg
, wParam
, lParam
);
563 paint_scrollbar(hwnd
, theme
);
568 theme
= GetWindowTheme(hwnd
);
569 if (!theme
) return THEMING_CallOriginalClass(hwnd
, msg
, wParam
, lParam
);
571 pt
.x
= (short)LOWORD(lParam
);
572 pt
.y
= (short)HIWORD(lParam
);
573 scroll_event(hwnd
, theme
, msg
, pt
);
577 return THEMING_CallOriginalClass(hwnd
, msg
, wParam
, lParam
);