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
61 using namespace std;
62
63 namespace WebCore {
64
65 static HANDLE scrollbarTheme;
66 static bool runningVista;
67
68 // FIXME: Refactor the soft-linking code so that it can be shared with RenderThemeWin
69 SOFT_LINK_LIBRARY(uxtheme)
70 SOFT_LINK(uxtheme, OpenThemeData, HANDLE, WINAPI, (HWND hwnd, LPCWSTR pszClassList), (hwnd, pszClassList))
71 SOFT_LINK(uxtheme, CloseThemeData, HRESULT, WINAPI, (HANDLE hTheme), (hTheme))
72 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))
73 SOFT_LINK(uxtheme, IsThemeActive, BOOL, WINAPI, (), ())
74 SOFT_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.
80 static const int kOffEndMultiplier = 3;
81 static const int kOffSideMultiplier = 8;
82
checkAndInitScrollbarTheme()83 static void checkAndInitScrollbarTheme()
84 {
85 if (uxthemeLibrary() && !scrollbarTheme && IsThemeActive())
86 scrollbarTheme = OpenThemeData(0, L"Scrollbar");
87 }
88
89 #if !USE(SAFARI_THEME)
nativeTheme()90 ScrollbarTheme* ScrollbarTheme::nativeTheme()
91 {
92 static ScrollbarThemeWin winTheme;
93 return &winTheme;
94 }
95 #endif
96
ScrollbarThemeWin()97 ScrollbarThemeWin::ScrollbarThemeWin()
98 {
99 static bool initialized;
100 if (!initialized) {
101 initialized = true;
102 checkAndInitScrollbarTheme();
103 runningVista = (windowsVersion() >= WindowsVista);
104 }
105 }
106
~ScrollbarThemeWin()107 ScrollbarThemeWin::~ScrollbarThemeWin()
108 {
109 }
110
scrollbarThickness(ScrollbarControlSize)111 int ScrollbarThemeWin::scrollbarThickness(ScrollbarControlSize)
112 {
113 static int thickness;
114 if (!thickness)
115 thickness = ::GetSystemMetrics(SM_CXVSCROLL);
116 return thickness;
117 }
118
themeChanged()119 void ScrollbarThemeWin::themeChanged()
120 {
121 if (!scrollbarTheme)
122 return;
123
124 CloseThemeData(scrollbarTheme);
125 scrollbarTheme = 0;
126 }
127
invalidateOnMouseEnterExit()128 bool ScrollbarThemeWin::invalidateOnMouseEnterExit()
129 {
130 return runningVista;
131 }
132
hasThumb(Scrollbar * scrollbar)133 bool ScrollbarThemeWin::hasThumb(Scrollbar* scrollbar)
134 {
135 return thumbLength(scrollbar) > 0;
136 }
137
backButtonRect(Scrollbar * scrollbar,ScrollbarPart part,bool)138 IntRect 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
forwardButtonRect(Scrollbar * scrollbar,ScrollbarPart part,bool)157 IntRect 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
trackRect(Scrollbar * scrollbar,bool)178 IntRect 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
shouldCenterOnThumb(Scrollbar *,const PlatformMouseEvent & evt)191 bool ScrollbarThemeWin::shouldCenterOnThumb(Scrollbar*, const PlatformMouseEvent& evt)
192 {
193 return evt.shiftKey() && evt.button() == LeftButton;
194 }
195
shouldSnapBackToDragOrigin(Scrollbar * scrollbar,const PlatformMouseEvent & evt)196 bool 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
paintTrackBackground(GraphicsContext * context,Scrollbar * scrollbar,const IntRect & rect)214 void 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
paintTrackPiece(GraphicsContext * context,Scrollbar * scrollbar,const IntRect & rect,ScrollbarPart partType)221 void 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
paintButton(GraphicsContext * context,Scrollbar * scrollbar,const IntRect & rect,ScrollbarPart part)274 void 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
gripperRect(int thickness,const IntRect & thumbRect)323 static 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
paintGripper(Scrollbar * scrollbar,HDC hdc,const IntRect & rect)332 static 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
paintThumb(GraphicsContext * context,Scrollbar * scrollbar,const IntRect & rect)351 void 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