| /* |
| * Theming - Scrollbar control |
| * |
| * Copyright (c) 2015 Mark Harmstone |
| * |
| * This library is free software; you can redistribute it and/or |
| * modify it under the terms of the GNU Lesser General Public |
| * License as published by the Free Software Foundation; either |
| * version 2.1 of the License, or (at your option) any later version. |
| * |
| * This library is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| * Lesser General Public License for more details. |
| * |
| * You should have received a copy of the GNU Lesser General Public |
| * License along with this library; if not, write to the Free Software |
| * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA |
| * |
| */ |
| |
| #include <stdarg.h> |
| #include <string.h> |
| #include <stdlib.h> |
| |
| #include "windef.h" |
| #include "winbase.h" |
| #include "wingdi.h" |
| #include "winuser.h" |
| #include "uxtheme.h" |
| #include "vssym32.h" |
| #include "comctl32.h" |
| #include "wine/debug.h" |
| |
| /* Minimum size of the thumb in pixels */ |
| #define SCROLL_MIN_THUMB 6 |
| |
| /* Minimum size of the rectangle between the arrows */ |
| #define SCROLL_MIN_RECT 4 |
| |
| enum SCROLL_HITTEST |
| { |
| SCROLL_NOWHERE, /* Outside the scroll bar */ |
| SCROLL_TOP_ARROW, /* Top or left arrow */ |
| SCROLL_TOP_RECT, /* Rectangle between the top arrow and the thumb */ |
| SCROLL_THUMB, /* Thumb rectangle */ |
| SCROLL_BOTTOM_RECT, /* Rectangle between the thumb and the bottom arrow */ |
| SCROLL_BOTTOM_ARROW /* Bottom or right arrow */ |
| }; |
| |
| static HWND tracking_win = 0; |
| static enum SCROLL_HITTEST tracking_hot_part = SCROLL_NOWHERE; |
| |
| WINE_DEFAULT_DEBUG_CHANNEL(theme_scroll); |
| |
| static void calc_thumb_dimensions(unsigned int size, SCROLLINFO *si, unsigned int *thumbpos, unsigned int *thumbsize) |
| { |
| if (size <= SCROLL_MIN_RECT) |
| *thumbpos = *thumbsize = 0; |
| else if (si->nPage > si->nMax - si->nMin) |
| *thumbpos = *thumbsize = 0; |
| else { |
| if (si->nPage > 0) { |
| *thumbsize = MulDiv(size, si->nPage, si->nMax - si->nMin + 1); |
| if (*thumbsize < SCROLL_MIN_THUMB) *thumbsize = SCROLL_MIN_THUMB; |
| } |
| else *thumbsize = GetSystemMetrics(SM_CXVSCROLL); |
| |
| if (size < *thumbsize) |
| *thumbpos = *thumbsize = 0; |
| else { |
| int max = si->nMax - max(si->nPage - 1, 0); |
| size -= *thumbsize; |
| if (si->nMin >= max) |
| *thumbpos = 0; |
| else |
| *thumbpos = MulDiv(size, si->nTrackPos - si->nMin, max - si->nMin); |
| } |
| } |
| } |
| |
| static enum SCROLL_HITTEST hit_test(HWND hwnd, HTHEME theme, POINT pt) |
| { |
| RECT r; |
| DWORD style = GetWindowLongW(hwnd, GWL_STYLE); |
| BOOL vertical = style & SBS_VERT; |
| SIZE sz; |
| SCROLLINFO si; |
| unsigned int offset, size, upsize, downsize, thumbpos, thumbsize; |
| |
| GetWindowRect(hwnd, &r); |
| OffsetRect(&r, -r.left, -r.top); |
| |
| if (vertical) { |
| offset = pt.y; |
| size = r.bottom; |
| |
| if (FAILED(GetThemePartSize(theme, NULL, SBP_ARROWBTN, ABS_UPNORMAL, NULL, TS_DRAW, &sz))) { |
| WARN("Could not get up arrow size.\n"); |
| upsize = 0; |
| } else |
| upsize = sz.cy; |
| |
| if (FAILED(GetThemePartSize(theme, NULL, SBP_ARROWBTN, ABS_DOWNNORMAL, NULL, TS_DRAW, &sz))) { |
| WARN("Could not get down arrow size.\n"); |
| downsize = 0; |
| } else |
| downsize = sz.cy; |
| } else { |
| offset = pt.x; |
| size = r.right; |
| |
| if (FAILED(GetThemePartSize(theme, NULL, SBP_ARROWBTN, ABS_LEFTNORMAL, NULL, TS_DRAW, &sz))) { |
| WARN("Could not get left arrow size.\n"); |
| upsize = 0; |
| } else |
| upsize = sz.cx; |
| |
| if (FAILED(GetThemePartSize(theme, NULL, SBP_ARROWBTN, ABS_RIGHTNORMAL, NULL, TS_DRAW, &sz))) { |
| WARN("Could not get right arrow size.\n"); |
| downsize = 0; |
| } else |
| downsize = sz.cx; |
| } |
| |
| if (pt.x < 0 || pt.x > r.right || pt.y < 0 || pt.y > r.bottom) |
| return SCROLL_NOWHERE; |
| |
| if (size < SCROLL_MIN_RECT + upsize + downsize) |
| upsize = downsize = (size - SCROLL_MIN_RECT)/2; |
| |
| if (offset < upsize) |
| return SCROLL_TOP_ARROW; |
| |
| if (offset > size - downsize) |
| return SCROLL_BOTTOM_ARROW; |
| |
| si.cbSize = sizeof(si); |
| si.fMask = SIF_ALL; |
| if (!GetScrollInfo(hwnd, SB_CTL, &si)) { |
| WARN("GetScrollInfo failed.\n"); |
| return SCROLL_NOWHERE; |
| } |
| |
| calc_thumb_dimensions(size - upsize - downsize, &si, &thumbpos, &thumbsize); |
| |
| if (offset < upsize + thumbpos) |
| return SCROLL_TOP_RECT; |
| else if (offset < upsize + thumbpos + thumbsize) |
| return SCROLL_THUMB; |
| else |
| return SCROLL_BOTTOM_RECT; |
| } |
| |
| static void redraw_part(HWND hwnd, HTHEME theme, enum SCROLL_HITTEST part) |
| { |
| DWORD style = GetWindowLongW(hwnd, GWL_STYLE); |
| BOOL vertical = style & SBS_VERT; |
| SIZE sz; |
| RECT r, partrect; |
| unsigned int size, upsize, downsize; |
| |
| if (part == SCROLL_NOWHERE) { /* redraw everything */ |
| InvalidateRect(hwnd, NULL, TRUE); |
| return; |
| } |
| |
| GetWindowRect(hwnd, &r); |
| OffsetRect(&r, -r.left, -r.top); |
| |
| if (vertical) { |
| size = r.bottom; |
| |
| if (FAILED(GetThemePartSize(theme, NULL, SBP_ARROWBTN, ABS_UPNORMAL, NULL, TS_DRAW, &sz))) { |
| WARN("Could not get up arrow size.\n"); |
| upsize = 0; |
| } else |
| upsize = sz.cy; |
| |
| if (FAILED(GetThemePartSize(theme, NULL, SBP_ARROWBTN, ABS_DOWNNORMAL, NULL, TS_DRAW, &sz))) { |
| WARN("Could not get down arrow size.\n"); |
| downsize = 0; |
| } else |
| downsize = sz.cy; |
| } else { |
| size = r.right; |
| |
| if (FAILED(GetThemePartSize(theme, NULL, SBP_ARROWBTN, ABS_LEFTNORMAL, NULL, TS_DRAW, &sz))) { |
| WARN("Could not get left arrow size.\n"); |
| upsize = 0; |
| } else |
| upsize = sz.cx; |
| |
| if (FAILED(GetThemePartSize(theme, NULL, SBP_ARROWBTN, ABS_RIGHTNORMAL, NULL, TS_DRAW, &sz))) { |
| WARN("Could not get right arrow size.\n"); |
| downsize = 0; |
| } else |
| downsize = sz.cx; |
| } |
| |
| if (size < SCROLL_MIN_RECT + upsize + downsize) |
| upsize = downsize = (size - SCROLL_MIN_RECT)/2; |
| |
| partrect = r; |
| |
| if (part == SCROLL_TOP_ARROW) { |
| if (vertical) |
| partrect.bottom = partrect.top + upsize; |
| else |
| partrect.right = partrect.left + upsize; |
| } else if (part == SCROLL_BOTTOM_ARROW) { |
| if (vertical) |
| partrect.top = partrect.bottom - downsize; |
| else |
| partrect.left = partrect.right - downsize; |
| } else { |
| unsigned int thumbpos, thumbsize; |
| SCROLLINFO si; |
| |
| si.cbSize = sizeof(si); |
| si.fMask = SIF_ALL; |
| if (!GetScrollInfo(hwnd, SB_CTL, &si)) { |
| WARN("GetScrollInfo failed.\n"); |
| return; |
| } |
| |
| calc_thumb_dimensions(size - upsize - downsize, &si, &thumbpos, &thumbsize); |
| |
| if (part == SCROLL_TOP_RECT) { |
| if (vertical) { |
| partrect.top = r.top + upsize; |
| partrect.bottom = partrect.top + thumbpos; |
| } else { |
| partrect.left = r.left + upsize; |
| partrect.right = partrect.left + thumbpos; |
| } |
| } else if (part == SCROLL_THUMB) { |
| if (vertical) { |
| partrect.top = r.top + upsize + thumbpos; |
| partrect.bottom = partrect.top + thumbsize; |
| } else { |
| partrect.left = r.left + upsize + thumbpos; |
| partrect.right = partrect.left + thumbsize; |
| } |
| } else if (part == SCROLL_BOTTOM_RECT) { |
| if (vertical) { |
| partrect.top = r.top + upsize + thumbpos + thumbsize; |
| partrect.bottom = r.bottom - downsize; |
| } else { |
| partrect.left = r.left + upsize + thumbpos + thumbsize; |
| partrect.right = r.right - downsize; |
| } |
| } |
| } |
| |
| InvalidateRect(hwnd, &partrect, TRUE); |
| } |
| |
| static void scroll_event(HWND hwnd, HTHEME theme, UINT msg, POINT pt) |
| { |
| enum SCROLL_HITTEST hittest; |
| TRACKMOUSEEVENT tme; |
| |
| if (GetWindowLongW(hwnd, GWL_STYLE) & (SBS_SIZEGRIP | SBS_SIZEBOX)) |
| return; |
| |
| hittest = hit_test(hwnd, theme, pt); |
| |
| switch (msg) |
| { |
| case WM_MOUSEMOVE: |
| hittest = hit_test(hwnd, theme, pt); |
| tracking_win = hwnd; |
| break; |
| |
| case WM_MOUSELEAVE: |
| if (tracking_win == hwnd) { |
| hittest = SCROLL_NOWHERE; |
| } |
| break; |
| } |
| |
| tme.cbSize = sizeof(tme); |
| tme.dwFlags = TME_QUERY; |
| TrackMouseEvent(&tme); |
| |
| if (!(tme.dwFlags & TME_LEAVE) || tme.hwndTrack != hwnd) { |
| tme.dwFlags = TME_LEAVE; |
| tme.hwndTrack = hwnd; |
| TrackMouseEvent(&tme); |
| } |
| |
| if (tracking_win != hwnd && msg == WM_MOUSELEAVE) { |
| redraw_part(hwnd, theme, SCROLL_NOWHERE); |
| return; |
| } |
| |
| if (tracking_win == hwnd && hittest != tracking_hot_part) { |
| enum SCROLL_HITTEST oldhotpart = tracking_hot_part; |
| |
| tracking_hot_part = hittest; |
| |
| if (hittest != SCROLL_NOWHERE) |
| redraw_part(hwnd, theme, hittest); |
| else |
| tracking_win = 0; |
| |
| if (oldhotpart != SCROLL_NOWHERE) |
| redraw_part(hwnd, theme, oldhotpart); |
| } |
| } |
| |
| static void paint_scrollbar(HWND hwnd, HTHEME theme) |
| { |
| HDC dc; |
| PAINTSTRUCT ps; |
| RECT r; |
| DWORD style = GetWindowLongW(hwnd, GWL_STYLE); |
| BOOL vertical = style & SBS_VERT; |
| BOOL disabled = !IsWindowEnabled(hwnd); |
| |
| GetWindowRect(hwnd, &r); |
| OffsetRect(&r, -r.left, -r.top); |
| |
| dc = BeginPaint(hwnd, &ps); |
| |
| if (style & SBS_SIZEBOX || style & SBS_SIZEGRIP) { |
| int state; |
| |
| if (style & SBS_SIZEBOXTOPLEFTALIGN) |
| state = SZB_TOPLEFTALIGN; |
| else |
| state = SZB_RIGHTALIGN; |
| |
| DrawThemeBackground(theme, dc, SBP_SIZEBOX, state, &r, NULL); |
| } else { |
| SCROLLBARINFO sbi; |
| SCROLLINFO si; |
| unsigned int thumbpos, thumbsize; |
| int uppertrackstate, lowertrackstate, thumbstate; |
| RECT partrect, trackrect; |
| SIZE grippersize; |
| |
| sbi.cbSize = sizeof(sbi); |
| GetScrollBarInfo(hwnd, OBJID_CLIENT, &sbi); |
| |
| si.cbSize = sizeof(si); |
| si.fMask = SIF_ALL; |
| GetScrollInfo(hwnd, SB_CTL, &si); |
| |
| trackrect = r; |
| |
| if (disabled) { |
| uppertrackstate = SCRBS_DISABLED; |
| lowertrackstate = SCRBS_DISABLED; |
| thumbstate = SCRBS_DISABLED; |
| } else { |
| uppertrackstate = SCRBS_NORMAL; |
| lowertrackstate = SCRBS_NORMAL; |
| thumbstate = SCRBS_NORMAL; |
| |
| if (tracking_win == hwnd) { |
| if (tracking_hot_part == SCROLL_TOP_RECT) |
| uppertrackstate = SCRBS_HOT; |
| else if (tracking_hot_part == SCROLL_BOTTOM_RECT) |
| lowertrackstate = SCRBS_HOT; |
| else if (tracking_hot_part == SCROLL_THUMB) |
| thumbstate = SCRBS_HOT; |
| } |
| } |
| |
| if (vertical) { |
| SIZE upsize, downsize; |
| int uparrowstate, downarrowstate; |
| |
| if (disabled) { |
| uparrowstate = ABS_UPDISABLED; |
| downarrowstate = ABS_DOWNDISABLED; |
| } else { |
| uparrowstate = ABS_UPNORMAL; |
| downarrowstate = ABS_DOWNNORMAL; |
| |
| if (tracking_win == hwnd) { |
| if (tracking_hot_part == SCROLL_TOP_ARROW) |
| uparrowstate = ABS_UPHOT; |
| else if (tracking_hot_part == SCROLL_BOTTOM_ARROW) |
| downarrowstate = ABS_DOWNHOT; |
| } |
| } |
| |
| if (FAILED(GetThemePartSize(theme, dc, SBP_ARROWBTN, uparrowstate, NULL, TS_DRAW, &upsize))) { |
| WARN("Could not get up arrow size.\n"); |
| return; |
| } |
| |
| if (FAILED(GetThemePartSize(theme, dc, SBP_ARROWBTN, downarrowstate, NULL, TS_DRAW, &downsize))) { |
| WARN("Could not get down arrow size.\n"); |
| return; |
| } |
| |
| if (r.bottom - r.top - upsize.cy - downsize.cy < SCROLL_MIN_RECT) |
| upsize.cy = downsize.cy = (r.bottom - r.top - SCROLL_MIN_RECT)/2; |
| |
| partrect = r; |
| partrect.bottom = partrect.top + upsize.cy; |
| DrawThemeBackground(theme, dc, SBP_ARROWBTN, uparrowstate, &partrect, NULL); |
| |
| trackrect.top = partrect.bottom; |
| |
| partrect.bottom = r.bottom; |
| partrect.top = partrect.bottom - downsize.cy; |
| DrawThemeBackground(theme, dc, SBP_ARROWBTN, downarrowstate, &partrect, NULL); |
| |
| trackrect.bottom = partrect.top; |
| |
| calc_thumb_dimensions(trackrect.bottom - trackrect.top, &si, &thumbpos, &thumbsize); |
| |
| if (thumbpos > 0) { |
| partrect.top = trackrect.top; |
| partrect.bottom = partrect.top + thumbpos; |
| |
| DrawThemeBackground(theme, dc, SBP_UPPERTRACKVERT, uppertrackstate, &partrect, NULL); |
| } |
| |
| if (thumbsize > 0) { |
| partrect.top = trackrect.top + thumbpos; |
| partrect.bottom = partrect.top + thumbsize; |
| |
| DrawThemeBackground(theme, dc, SBP_THUMBBTNVERT, thumbstate, &partrect, NULL); |
| |
| if (SUCCEEDED(GetThemePartSize(theme, dc, SBP_GRIPPERVERT, thumbstate, NULL, TS_DRAW, &grippersize))) { |
| MARGINS margins; |
| |
| if (SUCCEEDED(GetThemeMargins(theme, dc, SBP_THUMBBTNVERT, thumbstate, TMT_CONTENTMARGINS, &partrect, &margins))) { |
| if (grippersize.cy <= (thumbsize - margins.cyTopHeight - margins.cyBottomHeight)) |
| DrawThemeBackground(theme, dc, SBP_GRIPPERVERT, thumbstate, &partrect, NULL); |
| } |
| } |
| } |
| |
| if (thumbpos + thumbsize < trackrect.bottom - trackrect.top) { |
| partrect.bottom = trackrect.bottom; |
| partrect.top = trackrect.top + thumbsize + thumbpos; |
| |
| DrawThemeBackground(theme, dc, SBP_LOWERTRACKVERT, lowertrackstate, &partrect, NULL); |
| } |
| } else { |
| SIZE leftsize, rightsize; |
| int leftarrowstate, rightarrowstate; |
| |
| if (disabled) { |
| leftarrowstate = ABS_LEFTDISABLED; |
| rightarrowstate = ABS_RIGHTDISABLED; |
| } else { |
| leftarrowstate = ABS_LEFTNORMAL; |
| rightarrowstate = ABS_RIGHTNORMAL; |
| |
| if (tracking_win == hwnd) { |
| if (tracking_hot_part == SCROLL_TOP_ARROW) |
| leftarrowstate = ABS_LEFTHOT; |
| else if (tracking_hot_part == SCROLL_BOTTOM_ARROW) |
| rightarrowstate = ABS_RIGHTHOT; |
| } |
| } |
| |
| if (FAILED(GetThemePartSize(theme, dc, SBP_ARROWBTN, leftarrowstate, NULL, TS_DRAW, &leftsize))) { |
| WARN("Could not get left arrow size.\n"); |
| return; |
| } |
| |
| if (FAILED(GetThemePartSize(theme, dc, SBP_ARROWBTN, rightarrowstate, NULL, TS_DRAW, &rightsize))) { |
| WARN("Could not get right arrow size.\n"); |
| return; |
| } |
| |
| if (r.right - r.left - leftsize.cx - rightsize.cx < SCROLL_MIN_RECT) |
| leftsize.cx = rightsize.cx = (r.right - r.left - SCROLL_MIN_RECT)/2; |
| |
| partrect = r; |
| partrect.right = partrect.left + leftsize.cx; |
| DrawThemeBackground(theme, dc, SBP_ARROWBTN, leftarrowstate, &partrect, NULL); |
| |
| trackrect.left = partrect.right; |
| |
| partrect.right = r.right; |
| partrect.left = partrect.right - rightsize.cx; |
| DrawThemeBackground(theme, dc, SBP_ARROWBTN, rightarrowstate, &partrect, NULL); |
| |
| trackrect.right = partrect.left; |
| |
| calc_thumb_dimensions(trackrect.right - trackrect.left, &si, &thumbpos, &thumbsize); |
| |
| if (thumbpos > 0) { |
| partrect.left = trackrect.left; |
| partrect.right = partrect.left + thumbpos; |
| |
| DrawThemeBackground(theme, dc, SBP_UPPERTRACKHORZ, uppertrackstate, &partrect, NULL); |
| } |
| |
| if (thumbsize > 0) { |
| partrect.left = trackrect.left + thumbpos; |
| partrect.right = partrect.left + thumbsize; |
| |
| DrawThemeBackground(theme, dc, SBP_THUMBBTNHORZ, thumbstate, &partrect, NULL); |
| |
| if (SUCCEEDED(GetThemePartSize(theme, dc, SBP_GRIPPERHORZ, thumbstate, NULL, TS_DRAW, &grippersize))) { |
| MARGINS margins; |
| |
| if (SUCCEEDED(GetThemeMargins(theme, dc, SBP_THUMBBTNHORZ, thumbstate, TMT_CONTENTMARGINS, &partrect, &margins))) { |
| if (grippersize.cx <= (thumbsize - margins.cxLeftWidth - margins.cxRightWidth)) |
| DrawThemeBackground(theme, dc, SBP_GRIPPERHORZ, thumbstate, &partrect, NULL); |
| } |
| } |
| } |
| |
| if (thumbpos + thumbsize < trackrect.right - trackrect.left) { |
| partrect.right = trackrect.right; |
| partrect.left = trackrect.left + thumbsize + thumbpos; |
| |
| DrawThemeBackground(theme, dc, SBP_LOWERTRACKHORZ, lowertrackstate, &partrect, NULL); |
| } |
| } |
| } |
| |
| EndPaint(hwnd, &ps); |
| } |
| |
| LRESULT CALLBACK THEMING_ScrollbarSubclassProc (HWND hwnd, UINT msg, |
| WPARAM wParam, LPARAM lParam, |
| ULONG_PTR dwRefData) |
| { |
| const WCHAR* themeClass = WC_SCROLLBARW; |
| HTHEME theme; |
| LRESULT result; |
| POINT pt; |
| |
| TRACE("(%p, 0x%x, %lu, %lu, %lu)\n", hwnd, msg, wParam, lParam, dwRefData); |
| |
| switch (msg) { |
| case WM_CREATE: |
| result = THEMING_CallOriginalClass(hwnd, msg, wParam, lParam); |
| OpenThemeData(hwnd, themeClass); |
| return result; |
| |
| case WM_DESTROY: |
| theme = GetWindowTheme(hwnd); |
| CloseThemeData(theme); |
| return THEMING_CallOriginalClass(hwnd, msg, wParam, lParam); |
| |
| case WM_THEMECHANGED: |
| theme = GetWindowTheme(hwnd); |
| CloseThemeData(theme); |
| OpenThemeData(hwnd, themeClass); |
| break; |
| |
| case WM_SYSCOLORCHANGE: |
| theme = GetWindowTheme(hwnd); |
| if (!theme) return THEMING_CallOriginalClass(hwnd, msg, wParam, lParam); |
| /* Do nothing. When themed, a WM_THEMECHANGED will be received, too, |
| * which will do the repaint. */ |
| break; |
| |
| case WM_PAINT: |
| theme = GetWindowTheme(hwnd); |
| if (!theme) return THEMING_CallOriginalClass(hwnd, msg, wParam, lParam); |
| |
| paint_scrollbar(hwnd, theme); |
| break; |
| |
| case WM_MOUSEMOVE: |
| case WM_MOUSELEAVE: |
| theme = GetWindowTheme(hwnd); |
| if (!theme) return THEMING_CallOriginalClass(hwnd, msg, wParam, lParam); |
| |
| pt.x = (short)LOWORD(lParam); |
| pt.y = (short)HIWORD(lParam); |
| scroll_event(hwnd, theme, msg, pt); |
| break; |
| |
| default: |
| return THEMING_CallOriginalClass(hwnd, msg, wParam, lParam); |
| } |
| |
| return 0; |
| } |