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