1 /* 2 * Theming - Scrollbar control 3 * 4 * Copyright (c) 2015 Mark Harmstone 5 * 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. 10 * 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. 15 * 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 19 * 20 */ 21 22 #include "comctl32.h" 23 24 WINE_DEFAULT_DEBUG_CHANNEL(theme_scroll); 25 26 /* Minimum size of the thumb in pixels */ 27 #define SCROLL_MIN_THUMB 6 28 29 /* Minimum size of the rectangle between the arrows */ 30 #define SCROLL_MIN_RECT 4 31 32 enum SCROLL_HITTEST 33 { 34 SCROLL_NOWHERE, /* Outside the scroll bar */ 35 SCROLL_TOP_ARROW, /* Top or left arrow */ 36 SCROLL_TOP_RECT, /* Rectangle between the top arrow and the thumb */ 37 SCROLL_THUMB, /* Thumb rectangle */ 38 SCROLL_BOTTOM_RECT, /* Rectangle between the thumb and the bottom arrow */ 39 SCROLL_BOTTOM_ARROW /* Bottom or right arrow */ 40 }; 41 42 static HWND tracking_win = 0; 43 static enum SCROLL_HITTEST tracking_hot_part = SCROLL_NOWHERE; 44 45 static void calc_thumb_dimensions(unsigned int size, SCROLLINFO *si, unsigned int *thumbpos, unsigned int *thumbsize) 46 { 47 if (size <= SCROLL_MIN_RECT) 48 *thumbpos = *thumbsize = 0; 49 else if (si->nPage > si->nMax - si->nMin) 50 *thumbpos = *thumbsize = 0; 51 else { 52 if (si->nPage > 0) { 53 *thumbsize = MulDiv(size, si->nPage, si->nMax - si->nMin + 1); 54 if (*thumbsize < SCROLL_MIN_THUMB) *thumbsize = SCROLL_MIN_THUMB; 55 } 56 else *thumbsize = GetSystemMetrics(SM_CXVSCROLL); 57 58 if (size < *thumbsize) 59 *thumbpos = *thumbsize = 0; 60 else { 61 int max = si->nMax - max(si->nPage - 1, 0); 62 size -= *thumbsize; 63 if (si->nMin >= max) 64 *thumbpos = 0; 65 else 66 *thumbpos = MulDiv(size, si->nTrackPos - si->nMin, max - si->nMin); 67 } 68 } 69 } 70 71 static enum SCROLL_HITTEST hit_test(HWND hwnd, HTHEME theme, POINT pt) 72 { 73 RECT r; 74 DWORD style = GetWindowLongW(hwnd, GWL_STYLE); 75 BOOL vertical = style & SBS_VERT; 76 SIZE sz; 77 SCROLLINFO si; 78 unsigned int offset, size, upsize, downsize, thumbpos, thumbsize; 79 80 GetWindowRect(hwnd, &r); 81 OffsetRect(&r, -r.left, -r.top); 82 83 if (vertical) { 84 offset = pt.y; 85 size = r.bottom; 86 87 if (FAILED(GetThemePartSize(theme, NULL, SBP_ARROWBTN, ABS_UPNORMAL, NULL, TS_DRAW, &sz))) { 88 WARN("Could not get up arrow size.\n"); 89 upsize = 0; 90 } else 91 upsize = sz.cy; 92 93 if (FAILED(GetThemePartSize(theme, NULL, SBP_ARROWBTN, ABS_DOWNNORMAL, NULL, TS_DRAW, &sz))) { 94 WARN("Could not get down arrow size.\n"); 95 downsize = 0; 96 } else 97 downsize = sz.cy; 98 } else { 99 offset = pt.x; 100 size = r.right; 101 102 if (FAILED(GetThemePartSize(theme, NULL, SBP_ARROWBTN, ABS_LEFTNORMAL, NULL, TS_DRAW, &sz))) { 103 WARN("Could not get left arrow size.\n"); 104 upsize = 0; 105 } else 106 upsize = sz.cx; 107 108 if (FAILED(GetThemePartSize(theme, NULL, SBP_ARROWBTN, ABS_RIGHTNORMAL, NULL, TS_DRAW, &sz))) { 109 WARN("Could not get right arrow size.\n"); 110 downsize = 0; 111 } else 112 downsize = sz.cx; 113 } 114 115 if (pt.x < 0 || pt.x > r.right || pt.y < 0 || pt.y > r.bottom) 116 return SCROLL_NOWHERE; 117 118 if (size < SCROLL_MIN_RECT + upsize + downsize) 119 upsize = downsize = (size - SCROLL_MIN_RECT)/2; 120 121 if (offset < upsize) 122 return SCROLL_TOP_ARROW; 123 124 if (offset > size - downsize) 125 return SCROLL_BOTTOM_ARROW; 126 127 si.cbSize = sizeof(si); 128 si.fMask = SIF_ALL; 129 if (!GetScrollInfo(hwnd, SB_CTL, &si)) { 130 WARN("GetScrollInfo failed.\n"); 131 return SCROLL_NOWHERE; 132 } 133 134 calc_thumb_dimensions(size - upsize - downsize, &si, &thumbpos, &thumbsize); 135 136 if (offset < upsize + thumbpos) 137 return SCROLL_TOP_RECT; 138 else if (offset < upsize + thumbpos + thumbsize) 139 return SCROLL_THUMB; 140 else 141 return SCROLL_BOTTOM_RECT; 142 } 143 144 static void redraw_part(HWND hwnd, HTHEME theme, enum SCROLL_HITTEST part) 145 { 146 DWORD style = GetWindowLongW(hwnd, GWL_STYLE); 147 BOOL vertical = style & SBS_VERT; 148 SIZE sz; 149 RECT r, partrect; 150 unsigned int size, upsize, downsize; 151 152 if (part == SCROLL_NOWHERE) { /* redraw everything */ 153 InvalidateRect(hwnd, NULL, TRUE); 154 return; 155 } 156 157 GetWindowRect(hwnd, &r); 158 OffsetRect(&r, -r.left, -r.top); 159 160 if (vertical) { 161 size = r.bottom; 162 163 if (FAILED(GetThemePartSize(theme, NULL, SBP_ARROWBTN, ABS_UPNORMAL, NULL, TS_DRAW, &sz))) { 164 WARN("Could not get up arrow size.\n"); 165 upsize = 0; 166 } else 167 upsize = sz.cy; 168 169 if (FAILED(GetThemePartSize(theme, NULL, SBP_ARROWBTN, ABS_DOWNNORMAL, NULL, TS_DRAW, &sz))) { 170 WARN("Could not get down arrow size.\n"); 171 downsize = 0; 172 } else 173 downsize = sz.cy; 174 } else { 175 size = r.right; 176 177 if (FAILED(GetThemePartSize(theme, NULL, SBP_ARROWBTN, ABS_LEFTNORMAL, NULL, TS_DRAW, &sz))) { 178 WARN("Could not get left arrow size.\n"); 179 upsize = 0; 180 } else 181 upsize = sz.cx; 182 183 if (FAILED(GetThemePartSize(theme, NULL, SBP_ARROWBTN, ABS_RIGHTNORMAL, NULL, TS_DRAW, &sz))) { 184 WARN("Could not get right arrow size.\n"); 185 downsize = 0; 186 } else 187 downsize = sz.cx; 188 } 189 190 if (size < SCROLL_MIN_RECT + upsize + downsize) 191 upsize = downsize = (size - SCROLL_MIN_RECT)/2; 192 193 partrect = r; 194 195 if (part == SCROLL_TOP_ARROW) { 196 if (vertical) 197 partrect.bottom = partrect.top + upsize; 198 else 199 partrect.right = partrect.left + upsize; 200 } else if (part == SCROLL_BOTTOM_ARROW) { 201 if (vertical) 202 partrect.top = partrect.bottom - downsize; 203 else 204 partrect.left = partrect.right - downsize; 205 } else { 206 unsigned int thumbpos, thumbsize; 207 SCROLLINFO si; 208 209 si.cbSize = sizeof(si); 210 si.fMask = SIF_ALL; 211 if (!GetScrollInfo(hwnd, SB_CTL, &si)) { 212 WARN("GetScrollInfo failed.\n"); 213 return; 214 } 215 216 calc_thumb_dimensions(size - upsize - downsize, &si, &thumbpos, &thumbsize); 217 218 if (part == SCROLL_TOP_RECT) { 219 if (vertical) { 220 partrect.top = r.top + upsize; 221 partrect.bottom = partrect.top + thumbpos; 222 } else { 223 partrect.left = r.left + upsize; 224 partrect.right = partrect.left + thumbpos; 225 } 226 } else if (part == SCROLL_THUMB) { 227 if (vertical) { 228 partrect.top = r.top + upsize + thumbpos; 229 partrect.bottom = partrect.top + thumbsize; 230 } else { 231 partrect.left = r.left + upsize + thumbpos; 232 partrect.right = partrect.left + thumbsize; 233 } 234 } else if (part == SCROLL_BOTTOM_RECT) { 235 if (vertical) { 236 partrect.top = r.top + upsize + thumbpos + thumbsize; 237 partrect.bottom = r.bottom - downsize; 238 } else { 239 partrect.left = r.left + upsize + thumbpos + thumbsize; 240 partrect.right = r.right - downsize; 241 } 242 } 243 } 244 245 InvalidateRect(hwnd, &partrect, TRUE); 246 } 247 248 static void scroll_event(HWND hwnd, HTHEME theme, UINT msg, POINT pt) 249 { 250 enum SCROLL_HITTEST hittest; 251 TRACKMOUSEEVENT tme; 252 253 if (GetWindowLongW(hwnd, GWL_STYLE) & (SBS_SIZEGRIP | SBS_SIZEBOX)) 254 return; 255 256 hittest = hit_test(hwnd, theme, pt); 257 258 switch (msg) 259 { 260 case WM_MOUSEMOVE: 261 hittest = hit_test(hwnd, theme, pt); 262 tracking_win = hwnd; 263 break; 264 265 case WM_MOUSELEAVE: 266 if (tracking_win == hwnd) { 267 hittest = SCROLL_NOWHERE; 268 } 269 break; 270 } 271 272 tme.cbSize = sizeof(tme); 273 tme.dwFlags = TME_QUERY; 274 TrackMouseEvent(&tme); 275 276 if (!(tme.dwFlags & TME_LEAVE) || tme.hwndTrack != hwnd) { 277 tme.dwFlags = TME_LEAVE; 278 tme.hwndTrack = hwnd; 279 TrackMouseEvent(&tme); 280 } 281 282 if (tracking_win != hwnd && msg == WM_MOUSELEAVE) { 283 redraw_part(hwnd, theme, SCROLL_NOWHERE); 284 return; 285 } 286 287 if (tracking_win == hwnd && hittest != tracking_hot_part) { 288 enum SCROLL_HITTEST oldhotpart = tracking_hot_part; 289 290 tracking_hot_part = hittest; 291 292 if (hittest != SCROLL_NOWHERE) 293 redraw_part(hwnd, theme, hittest); 294 else 295 tracking_win = 0; 296 297 if (oldhotpart != SCROLL_NOWHERE) 298 redraw_part(hwnd, theme, oldhotpart); 299 } 300 } 301 302 static void paint_scrollbar(HWND hwnd, HTHEME theme) 303 { 304 HDC dc; 305 PAINTSTRUCT ps; 306 RECT r; 307 DWORD style = GetWindowLongW(hwnd, GWL_STYLE); 308 BOOL vertical = style & SBS_VERT; 309 BOOL disabled = !IsWindowEnabled(hwnd); 310 311 GetWindowRect(hwnd, &r); 312 OffsetRect(&r, -r.left, -r.top); 313 314 dc = BeginPaint(hwnd, &ps); 315 316 if (style & SBS_SIZEBOX || style & SBS_SIZEGRIP) { 317 int state; 318 319 if (style & SBS_SIZEBOXTOPLEFTALIGN) 320 state = SZB_TOPLEFTALIGN; 321 else 322 state = SZB_RIGHTALIGN; 323 324 DrawThemeBackground(theme, dc, SBP_SIZEBOX, state, &r, NULL); 325 } else { 326 SCROLLBARINFO sbi; 327 SCROLLINFO si; 328 unsigned int thumbpos, thumbsize; 329 int uppertrackstate, lowertrackstate, thumbstate; 330 RECT partrect, trackrect; 331 SIZE grippersize; 332 333 sbi.cbSize = sizeof(sbi); 334 GetScrollBarInfo(hwnd, OBJID_CLIENT, &sbi); 335 336 si.cbSize = sizeof(si); 337 si.fMask = SIF_ALL; 338 GetScrollInfo(hwnd, SB_CTL, &si); 339 340 trackrect = r; 341 342 if (disabled) { 343 uppertrackstate = SCRBS_DISABLED; 344 lowertrackstate = SCRBS_DISABLED; 345 thumbstate = SCRBS_DISABLED; 346 } else { 347 uppertrackstate = SCRBS_NORMAL; 348 lowertrackstate = SCRBS_NORMAL; 349 thumbstate = SCRBS_NORMAL; 350 351 if (tracking_win == hwnd) { 352 if (tracking_hot_part == SCROLL_TOP_RECT) 353 uppertrackstate = SCRBS_HOT; 354 else if (tracking_hot_part == SCROLL_BOTTOM_RECT) 355 lowertrackstate = SCRBS_HOT; 356 else if (tracking_hot_part == SCROLL_THUMB) 357 thumbstate = SCRBS_HOT; 358 } 359 } 360 361 if (vertical) { 362 SIZE upsize, downsize; 363 int uparrowstate, downarrowstate; 364 365 if (disabled) { 366 uparrowstate = ABS_UPDISABLED; 367 downarrowstate = ABS_DOWNDISABLED; 368 } else { 369 uparrowstate = ABS_UPNORMAL; 370 downarrowstate = ABS_DOWNNORMAL; 371 372 if (tracking_win == hwnd) { 373 if (tracking_hot_part == SCROLL_TOP_ARROW) 374 uparrowstate = ABS_UPHOT; 375 else if (tracking_hot_part == SCROLL_BOTTOM_ARROW) 376 downarrowstate = ABS_DOWNHOT; 377 } 378 } 379 380 if (FAILED(GetThemePartSize(theme, dc, SBP_ARROWBTN, uparrowstate, NULL, TS_DRAW, &upsize))) { 381 WARN("Could not get up arrow size.\n"); 382 return; 383 } 384 385 if (FAILED(GetThemePartSize(theme, dc, SBP_ARROWBTN, downarrowstate, NULL, TS_DRAW, &downsize))) { 386 WARN("Could not get down arrow size.\n"); 387 return; 388 } 389 390 if (r.bottom - r.top - upsize.cy - downsize.cy < SCROLL_MIN_RECT) 391 upsize.cy = downsize.cy = (r.bottom - r.top - SCROLL_MIN_RECT)/2; 392 393 partrect = r; 394 partrect.bottom = partrect.top + upsize.cy; 395 DrawThemeBackground(theme, dc, SBP_ARROWBTN, uparrowstate, &partrect, NULL); 396 397 trackrect.top = partrect.bottom; 398 399 partrect.bottom = r.bottom; 400 partrect.top = partrect.bottom - downsize.cy; 401 DrawThemeBackground(theme, dc, SBP_ARROWBTN, downarrowstate, &partrect, NULL); 402 403 trackrect.bottom = partrect.top; 404 405 calc_thumb_dimensions(trackrect.bottom - trackrect.top, &si, &thumbpos, &thumbsize); 406 407 if (thumbpos > 0) { 408 partrect.top = trackrect.top; 409 partrect.bottom = partrect.top + thumbpos; 410 411 DrawThemeBackground(theme, dc, SBP_UPPERTRACKVERT, uppertrackstate, &partrect, NULL); 412 } 413 414 if (thumbsize > 0) { 415 partrect.top = trackrect.top + thumbpos; 416 partrect.bottom = partrect.top + thumbsize; 417 418 DrawThemeBackground(theme, dc, SBP_THUMBBTNVERT, thumbstate, &partrect, NULL); 419 420 if (SUCCEEDED(GetThemePartSize(theme, dc, SBP_GRIPPERVERT, thumbstate, NULL, TS_DRAW, &grippersize))) { 421 MARGINS margins; 422 423 if (SUCCEEDED(GetThemeMargins(theme, dc, SBP_THUMBBTNVERT, thumbstate, TMT_CONTENTMARGINS, &partrect, &margins))) { 424 if (grippersize.cy <= (thumbsize - margins.cyTopHeight - margins.cyBottomHeight)) 425 DrawThemeBackground(theme, dc, SBP_GRIPPERVERT, thumbstate, &partrect, NULL); 426 } 427 } 428 } 429 430 if (thumbpos + thumbsize < trackrect.bottom - trackrect.top) { 431 partrect.bottom = trackrect.bottom; 432 partrect.top = trackrect.top + thumbsize + thumbpos; 433 434 DrawThemeBackground(theme, dc, SBP_LOWERTRACKVERT, lowertrackstate, &partrect, NULL); 435 } 436 } else { 437 SIZE leftsize, rightsize; 438 int leftarrowstate, rightarrowstate; 439 440 if (disabled) { 441 leftarrowstate = ABS_LEFTDISABLED; 442 rightarrowstate = ABS_RIGHTDISABLED; 443 } else { 444 leftarrowstate = ABS_LEFTNORMAL; 445 rightarrowstate = ABS_RIGHTNORMAL; 446 447 if (tracking_win == hwnd) { 448 if (tracking_hot_part == SCROLL_TOP_ARROW) 449 leftarrowstate = ABS_LEFTHOT; 450 else if (tracking_hot_part == SCROLL_BOTTOM_ARROW) 451 rightarrowstate = ABS_RIGHTHOT; 452 } 453 } 454 455 if (FAILED(GetThemePartSize(theme, dc, SBP_ARROWBTN, leftarrowstate, NULL, TS_DRAW, &leftsize))) { 456 WARN("Could not get left arrow size.\n"); 457 return; 458 } 459 460 if (FAILED(GetThemePartSize(theme, dc, SBP_ARROWBTN, rightarrowstate, NULL, TS_DRAW, &rightsize))) { 461 WARN("Could not get right arrow size.\n"); 462 return; 463 } 464 465 if (r.right - r.left - leftsize.cx - rightsize.cx < SCROLL_MIN_RECT) 466 leftsize.cx = rightsize.cx = (r.right - r.left - SCROLL_MIN_RECT)/2; 467 468 partrect = r; 469 partrect.right = partrect.left + leftsize.cx; 470 DrawThemeBackground(theme, dc, SBP_ARROWBTN, leftarrowstate, &partrect, NULL); 471 472 trackrect.left = partrect.right; 473 474 partrect.right = r.right; 475 partrect.left = partrect.right - rightsize.cx; 476 DrawThemeBackground(theme, dc, SBP_ARROWBTN, rightarrowstate, &partrect, NULL); 477 478 trackrect.right = partrect.left; 479 480 calc_thumb_dimensions(trackrect.right - trackrect.left, &si, &thumbpos, &thumbsize); 481 482 if (thumbpos > 0) { 483 partrect.left = trackrect.left; 484 partrect.right = partrect.left + thumbpos; 485 486 DrawThemeBackground(theme, dc, SBP_UPPERTRACKHORZ, uppertrackstate, &partrect, NULL); 487 } 488 489 if (thumbsize > 0) { 490 partrect.left = trackrect.left + thumbpos; 491 partrect.right = partrect.left + thumbsize; 492 493 DrawThemeBackground(theme, dc, SBP_THUMBBTNHORZ, thumbstate, &partrect, NULL); 494 495 if (SUCCEEDED(GetThemePartSize(theme, dc, SBP_GRIPPERHORZ, thumbstate, NULL, TS_DRAW, &grippersize))) { 496 MARGINS margins; 497 498 if (SUCCEEDED(GetThemeMargins(theme, dc, SBP_THUMBBTNHORZ, thumbstate, TMT_CONTENTMARGINS, &partrect, &margins))) { 499 if (grippersize.cx <= (thumbsize - margins.cxLeftWidth - margins.cxRightWidth)) 500 DrawThemeBackground(theme, dc, SBP_GRIPPERHORZ, thumbstate, &partrect, NULL); 501 } 502 } 503 } 504 505 if (thumbpos + thumbsize < trackrect.right - trackrect.left) { 506 partrect.right = trackrect.right; 507 partrect.left = trackrect.left + thumbsize + thumbpos; 508 509 DrawThemeBackground(theme, dc, SBP_LOWERTRACKHORZ, lowertrackstate, &partrect, NULL); 510 } 511 } 512 } 513 514 EndPaint(hwnd, &ps); 515 } 516 517 LRESULT CALLBACK THEMING_ScrollbarSubclassProc (HWND hwnd, UINT msg, 518 WPARAM wParam, LPARAM lParam, 519 ULONG_PTR dwRefData) 520 { 521 const WCHAR* themeClass = WC_SCROLLBARW; 522 HTHEME theme; 523 LRESULT result; 524 POINT pt; 525 526 TRACE("(%p, 0x%x, %lu, %lu, %lu)\n", hwnd, msg, wParam, lParam, dwRefData); 527 528 switch (msg) { 529 case WM_CREATE: 530 result = THEMING_CallOriginalClass(hwnd, msg, wParam, lParam); 531 OpenThemeData(hwnd, themeClass); 532 return result; 533 534 case WM_DESTROY: 535 theme = GetWindowTheme(hwnd); 536 CloseThemeData(theme); 537 return THEMING_CallOriginalClass(hwnd, msg, wParam, lParam); 538 539 case WM_THEMECHANGED: 540 theme = GetWindowTheme(hwnd); 541 CloseThemeData(theme); 542 OpenThemeData(hwnd, themeClass); 543 break; 544 545 case WM_SYSCOLORCHANGE: 546 theme = GetWindowTheme(hwnd); 547 if (!theme) return THEMING_CallOriginalClass(hwnd, msg, wParam, lParam); 548 /* Do nothing. When themed, a WM_THEMECHANGED will be received, too, 549 * which will do the repaint. */ 550 break; 551 552 case WM_PAINT: 553 theme = GetWindowTheme(hwnd); 554 if (!theme) return THEMING_CallOriginalClass(hwnd, msg, wParam, lParam); 555 556 paint_scrollbar(hwnd, theme); 557 break; 558 559 case WM_MOUSEMOVE: 560 case WM_MOUSELEAVE: 561 theme = GetWindowTheme(hwnd); 562 if (!theme) return THEMING_CallOriginalClass(hwnd, msg, wParam, lParam); 563 564 pt.x = (short)LOWORD(lParam); 565 pt.y = (short)HIWORD(lParam); 566 scroll_event(hwnd, theme, msg, pt); 567 break; 568 569 default: 570 return THEMING_CallOriginalClass(hwnd, msg, wParam, lParam); 571 } 572 573 return 0; 574 } 575