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