1 /* 2 * Copyright (C) 2008 Apple Inc. All Rights Reserved. 3 * 4 * Redistribution and use in source and binary forms, with or without 5 * modification, are permitted provided that the following conditions 6 * are met: 7 * 1. Redistributions of source code must retain the above copyright 8 * notice, this list of conditions and the following disclaimer. 9 * 2. Redistributions in binary form must reproduce the above copyright 10 * notice, this list of conditions and the following disclaimer in the 11 * documentation and/or other materials provided with the distribution. 12 * 13 * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY 14 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 15 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 16 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR 17 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 18 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 19 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 20 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY 21 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 22 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 23 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 */ 25 26 #include "config.h" 27 #include "ScrollbarThemeWin.h" 28 29 #include "GraphicsContext.h" 30 #include "PlatformMouseEvent.h" 31 #include "Scrollbar.h" 32 #include "SoftLinking.h" 33 #include "SystemInfo.h" 34 35 // Generic state constants 36 #define TS_NORMAL 1 37 #define TS_HOVER 2 38 #define TS_ACTIVE 3 39 #define TS_DISABLED 4 40 41 #define SP_BUTTON 1 42 #define SP_THUMBHOR 2 43 #define SP_THUMBVERT 3 44 #define SP_TRACKSTARTHOR 4 45 #define SP_TRACKENDHOR 5 46 #define SP_TRACKSTARTVERT 6 47 #define SP_TRACKENDVERT 7 48 #define SP_GRIPPERHOR 8 49 #define SP_GRIPPERVERT 9 50 51 #define TS_UP_BUTTON 0 52 #define TS_DOWN_BUTTON 4 53 #define TS_LEFT_BUTTON 8 54 #define TS_RIGHT_BUTTON 12 55 #define TS_UP_BUTTON_HOVER 17 56 #define TS_DOWN_BUTTON_HOVER 18 57 #define TS_LEFT_BUTTON_HOVER 19 58 #define TS_RIGHT_BUTTON_HOVER 20 59 60 using namespace std; 61 62 namespace WebCore { 63 64 static HANDLE scrollbarTheme; 65 static bool runningVista; 66 67 // FIXME: Refactor the soft-linking code so that it can be shared with RenderThemeWin 68 SOFT_LINK_LIBRARY(uxtheme) 69 SOFT_LINK(uxtheme, OpenThemeData, HANDLE, WINAPI, (HWND hwnd, LPCWSTR pszClassList), (hwnd, pszClassList)) 70 SOFT_LINK(uxtheme, CloseThemeData, HRESULT, WINAPI, (HANDLE hTheme), (hTheme)) 71 SOFT_LINK(uxtheme, DrawThemeBackground, HRESULT, WINAPI, (HANDLE hTheme, HDC hdc, int iPartId, int iStateId, const RECT* pRect, const RECT* pClipRect), (hTheme, hdc, iPartId, iStateId, pRect, pClipRect)) 72 SOFT_LINK(uxtheme, IsThemeActive, BOOL, WINAPI, (), ()) 73 SOFT_LINK(uxtheme, IsThemeBackgroundPartiallyTransparent, BOOL, WINAPI, (HANDLE hTheme, int iPartId, int iStateId), (hTheme, iPartId, iStateId)) 74 75 // Constants used to figure the drag rect outside which we should snap the 76 // scrollbar thumb back to its origin. These calculations are based on 77 // observing the behavior of the MSVC8 main window scrollbar + some 78 // guessing/extrapolation. 79 static const int kOffEndMultiplier = 3; 80 static const int kOffSideMultiplier = 8; 81 82 static void checkAndInitScrollbarTheme() 83 { 84 if (uxthemeLibrary() && !scrollbarTheme && IsThemeActive()) 85 scrollbarTheme = OpenThemeData(0, L"Scrollbar"); 86 } 87 88 #if !USE(SAFARI_THEME) 89 ScrollbarTheme* ScrollbarTheme::nativeTheme() 90 { 91 static ScrollbarThemeWin winTheme; 92 return &winTheme; 93 } 94 #endif 95 96 ScrollbarThemeWin::ScrollbarThemeWin() 97 { 98 static bool initialized; 99 if (!initialized) { 100 initialized = true; 101 checkAndInitScrollbarTheme(); 102 runningVista = isRunningOnVistaOrLater(); 103 } 104 } 105 106 ScrollbarThemeWin::~ScrollbarThemeWin() 107 { 108 } 109 110 int ScrollbarThemeWin::scrollbarThickness(ScrollbarControlSize) 111 { 112 static int thickness; 113 if (!thickness) 114 thickness = ::GetSystemMetrics(SM_CXVSCROLL); 115 return thickness; 116 } 117 118 void ScrollbarThemeWin::themeChanged() 119 { 120 if (!scrollbarTheme) 121 return; 122 123 CloseThemeData(scrollbarTheme); 124 scrollbarTheme = 0; 125 } 126 127 bool ScrollbarThemeWin::invalidateOnMouseEnterExit() 128 { 129 return runningVista; 130 } 131 132 bool ScrollbarThemeWin::hasThumb(Scrollbar* scrollbar) 133 { 134 return thumbLength(scrollbar) > 0; 135 } 136 137 IntRect ScrollbarThemeWin::backButtonRect(Scrollbar* scrollbar, ScrollbarPart part, bool) 138 { 139 // Windows just has single arrows. 140 if (part == BackButtonEndPart) 141 return IntRect(); 142 143 // Our desired rect is essentially 17x17. 144 145 // Our actual rect will shrink to half the available space when 146 // we have < 34 pixels left. This allows the scrollbar 147 // to scale down and function even at tiny sizes. 148 int thickness = scrollbarThickness(); 149 if (scrollbar->orientation() == HorizontalScrollbar) 150 return IntRect(scrollbar->x(), scrollbar->y(), 151 scrollbar->width() < 2 * thickness ? scrollbar->width() / 2 : thickness, thickness); 152 return IntRect(scrollbar->x(), scrollbar->y(), 153 thickness, scrollbar->height() < 2 * thickness ? scrollbar->height() / 2 : thickness); 154 } 155 156 IntRect ScrollbarThemeWin::forwardButtonRect(Scrollbar* scrollbar, ScrollbarPart part, bool) 157 { 158 // Windows just has single arrows. 159 if (part == ForwardButtonStartPart) 160 return IntRect(); 161 162 // Our desired rect is essentially 17x17. 163 164 // Our actual rect will shrink to half the available space when 165 // we have < 34 pixels left. This allows the scrollbar 166 // to scale down and function even at tiny sizes. 167 int thickness = scrollbarThickness(); 168 if (scrollbar->orientation() == HorizontalScrollbar) { 169 int w = scrollbar->width() < 2 * thickness ? scrollbar->width() / 2 : thickness; 170 return IntRect(scrollbar->x() + scrollbar->width() - w, scrollbar->y(), w, thickness); 171 } 172 173 int h = scrollbar->height() < 2 * thickness ? scrollbar->height() / 2 : thickness; 174 return IntRect(scrollbar->x(), scrollbar->y() + scrollbar->height() - h, thickness, h); 175 } 176 177 IntRect ScrollbarThemeWin::trackRect(Scrollbar* scrollbar, bool) 178 { 179 int thickness = scrollbarThickness(); 180 if (scrollbar->orientation() == HorizontalScrollbar) { 181 if (scrollbar->width() < 2 * thickness) 182 return IntRect(); 183 return IntRect(scrollbar->x() + thickness, scrollbar->y(), scrollbar->width() - 2 * thickness, thickness); 184 } 185 if (scrollbar->height() < 2 * thickness) 186 return IntRect(); 187 return IntRect(scrollbar->x(), scrollbar->y() + thickness, thickness, scrollbar->height() - 2 * thickness); 188 } 189 190 bool ScrollbarThemeWin::shouldCenterOnThumb(Scrollbar*, const PlatformMouseEvent& evt) 191 { 192 return evt.shiftKey() && evt.button() == LeftButton; 193 } 194 195 bool ScrollbarThemeWin::shouldSnapBackToDragOrigin(Scrollbar* scrollbar, const PlatformMouseEvent& evt) 196 { 197 // Find the rect within which we shouldn't snap, by expanding the track rect 198 // in both dimensions. 199 IntRect rect = trackRect(scrollbar); 200 const bool horz = scrollbar->orientation() == HorizontalScrollbar; 201 const int thickness = scrollbarThickness(scrollbar->controlSize()); 202 rect.inflateX((horz ? kOffEndMultiplier : kOffSideMultiplier) * thickness); 203 rect.inflateY((horz ? kOffSideMultiplier : kOffEndMultiplier) * thickness); 204 205 // Convert the event to local coordinates. 206 IntPoint mousePosition = scrollbar->convertFromContainingWindow(evt.pos()); 207 mousePosition.move(scrollbar->x(), scrollbar->y()); 208 209 // We should snap iff the event is outside our calculated rect. 210 return !rect.contains(mousePosition); 211 } 212 213 void ScrollbarThemeWin::paintTrackBackground(GraphicsContext* context, Scrollbar* scrollbar, const IntRect& rect) 214 { 215 // Just assume a forward track part. We only paint the track as a single piece when there is no thumb. 216 if (!hasThumb(scrollbar)) 217 paintTrackPiece(context, scrollbar, rect, ForwardTrackPart); 218 } 219 220 void ScrollbarThemeWin::paintTrackPiece(GraphicsContext* context, Scrollbar* scrollbar, const IntRect& rect, ScrollbarPart partType) 221 { 222 checkAndInitScrollbarTheme(); 223 224 bool start = partType == BackTrackPart; 225 int part; 226 if (scrollbar->orientation() == HorizontalScrollbar) 227 part = start ? SP_TRACKSTARTHOR : SP_TRACKENDHOR; 228 else 229 part = start ? SP_TRACKSTARTVERT : SP_TRACKENDVERT; 230 231 int state; 232 if (!scrollbar->enabled()) 233 state = TS_DISABLED; 234 else if ((scrollbar->hoveredPart() == BackTrackPart && start) || 235 (scrollbar->hoveredPart() == ForwardTrackPart && !start)) 236 state = (scrollbar->pressedPart() == scrollbar->hoveredPart() ? TS_ACTIVE : TS_HOVER); 237 else 238 state = TS_NORMAL; 239 240 bool alphaBlend = false; 241 if (scrollbarTheme) 242 alphaBlend = IsThemeBackgroundPartiallyTransparent(scrollbarTheme, part, state); 243 HDC hdc = context->getWindowsContext(rect, alphaBlend); 244 RECT themeRect(rect); 245 if (scrollbarTheme) 246 DrawThemeBackground(scrollbarTheme, hdc, part, state, &themeRect, 0); 247 else { 248 DWORD color3DFace = ::GetSysColor(COLOR_3DFACE); 249 DWORD colorScrollbar = ::GetSysColor(COLOR_SCROLLBAR); 250 DWORD colorWindow = ::GetSysColor(COLOR_WINDOW); 251 if ((color3DFace != colorScrollbar) && (colorWindow != colorScrollbar)) 252 ::FillRect(hdc, &themeRect, HBRUSH(COLOR_SCROLLBAR+1)); 253 else { 254 static WORD patternBits[8] = { 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55 }; 255 HBITMAP patternBitmap = ::CreateBitmap(8, 8, 1, 1, patternBits); 256 HBRUSH brush = ::CreatePatternBrush(patternBitmap); 257 SaveDC(hdc); 258 ::SetTextColor(hdc, ::GetSysColor(COLOR_3DHILIGHT)); 259 ::SetBkColor(hdc, ::GetSysColor(COLOR_3DFACE)); 260 ::SetBrushOrgEx(hdc, rect.x(), rect.y(), NULL); 261 ::SelectObject(hdc, brush); 262 ::FillRect(hdc, &themeRect, brush); 263 ::RestoreDC(hdc, -1); 264 ::DeleteObject(brush); 265 ::DeleteObject(patternBitmap); 266 } 267 } 268 context->releaseWindowsContext(hdc, rect, alphaBlend); 269 } 270 271 void ScrollbarThemeWin::paintButton(GraphicsContext* context, Scrollbar* scrollbar, const IntRect& rect, ScrollbarPart part) 272 { 273 checkAndInitScrollbarTheme(); 274 275 bool start = (part == BackButtonStartPart); 276 int xpState = 0; 277 int classicState = 0; 278 if (scrollbar->orientation() == HorizontalScrollbar) 279 xpState = start ? TS_LEFT_BUTTON : TS_RIGHT_BUTTON; 280 else 281 xpState = start ? TS_UP_BUTTON : TS_DOWN_BUTTON; 282 classicState = xpState / 4; 283 284 if (!scrollbar->enabled()) { 285 xpState += TS_DISABLED; 286 classicState |= DFCS_INACTIVE; 287 } else if ((scrollbar->hoveredPart() == BackButtonStartPart && start) || 288 (scrollbar->hoveredPart() == ForwardButtonEndPart && !start)) { 289 if (scrollbar->pressedPart() == scrollbar->hoveredPart()) { 290 xpState += TS_ACTIVE; 291 classicState |= DFCS_PUSHED | DFCS_FLAT; 292 } else 293 xpState += TS_HOVER; 294 } else { 295 if (scrollbar->hoveredPart() == NoPart || !runningVista) 296 xpState += TS_NORMAL; 297 else { 298 if (scrollbar->orientation() == HorizontalScrollbar) 299 xpState = start ? TS_LEFT_BUTTON_HOVER : TS_RIGHT_BUTTON_HOVER; 300 else 301 xpState = start ? TS_UP_BUTTON_HOVER : TS_DOWN_BUTTON_HOVER; 302 } 303 } 304 305 bool alphaBlend = false; 306 if (scrollbarTheme) 307 alphaBlend = IsThemeBackgroundPartiallyTransparent(scrollbarTheme, SP_BUTTON, xpState); 308 HDC hdc = context->getWindowsContext(rect, alphaBlend); 309 310 RECT themeRect(rect); 311 if (scrollbarTheme) 312 DrawThemeBackground(scrollbarTheme, hdc, SP_BUTTON, xpState, &themeRect, 0); 313 else 314 ::DrawFrameControl(hdc, &themeRect, DFC_SCROLL, classicState); 315 context->releaseWindowsContext(hdc, rect, alphaBlend); 316 } 317 318 static IntRect gripperRect(int thickness, const IntRect& thumbRect) 319 { 320 // Center in the thumb. 321 int gripperThickness = thickness / 2; 322 return IntRect(thumbRect.x() + (thumbRect.width() - gripperThickness) / 2, 323 thumbRect.y() + (thumbRect.height() - gripperThickness) / 2, 324 gripperThickness, gripperThickness); 325 } 326 327 static void paintGripper(Scrollbar* scrollbar, HDC hdc, const IntRect& rect) 328 { 329 if (!scrollbarTheme) 330 return; // Classic look has no gripper. 331 332 int state; 333 if (!scrollbar->enabled()) 334 state = TS_DISABLED; 335 else if (scrollbar->pressedPart() == ThumbPart) 336 state = TS_ACTIVE; // Thumb always stays active once pressed. 337 else if (scrollbar->hoveredPart() == ThumbPart) 338 state = TS_HOVER; 339 else 340 state = TS_NORMAL; 341 342 RECT themeRect(rect); 343 DrawThemeBackground(scrollbarTheme, hdc, scrollbar->orientation() == HorizontalScrollbar ? SP_GRIPPERHOR : SP_GRIPPERVERT, state, &themeRect, 0); 344 } 345 346 void ScrollbarThemeWin::paintThumb(GraphicsContext* context, Scrollbar* scrollbar, const IntRect& rect) 347 { 348 checkAndInitScrollbarTheme(); 349 350 int state; 351 if (!scrollbar->enabled()) 352 state = TS_DISABLED; 353 else if (scrollbar->pressedPart() == ThumbPart) 354 state = TS_ACTIVE; // Thumb always stays active once pressed. 355 else if (scrollbar->hoveredPart() == ThumbPart) 356 state = TS_HOVER; 357 else 358 state = TS_NORMAL; 359 360 bool alphaBlend = false; 361 if (scrollbarTheme) 362 alphaBlend = IsThemeBackgroundPartiallyTransparent(scrollbarTheme, scrollbar->orientation() == HorizontalScrollbar ? SP_THUMBHOR : SP_THUMBVERT, state); 363 HDC hdc = context->getWindowsContext(rect, alphaBlend); 364 RECT themeRect(rect); 365 if (scrollbarTheme) { 366 DrawThemeBackground(scrollbarTheme, hdc, scrollbar->orientation() == HorizontalScrollbar ? SP_THUMBHOR : SP_THUMBVERT, state, &themeRect, 0); 367 paintGripper(scrollbar, hdc, gripperRect(scrollbarThickness(), rect)); 368 } else 369 ::DrawEdge(hdc, &themeRect, EDGE_RAISED, BF_RECT | BF_MIDDLE); 370 context->releaseWindowsContext(hdc, rect, alphaBlend); 371 } 372 373 } 374 375