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
checkAndInitScrollbarTheme()82 static void checkAndInitScrollbarTheme()
83 {
84 if (uxthemeLibrary() && !scrollbarTheme && IsThemeActive())
85 scrollbarTheme = OpenThemeData(0, L"Scrollbar");
86 }
87
88 #if !USE(SAFARI_THEME)
nativeTheme()89 ScrollbarTheme* ScrollbarTheme::nativeTheme()
90 {
91 static ScrollbarThemeWin winTheme;
92 return &winTheme;
93 }
94 #endif
95
ScrollbarThemeWin()96 ScrollbarThemeWin::ScrollbarThemeWin()
97 {
98 static bool initialized;
99 if (!initialized) {
100 initialized = true;
101 checkAndInitScrollbarTheme();
102 runningVista = isRunningOnVistaOrLater();
103 }
104 }
105
~ScrollbarThemeWin()106 ScrollbarThemeWin::~ScrollbarThemeWin()
107 {
108 }
109
scrollbarThickness(ScrollbarControlSize)110 int ScrollbarThemeWin::scrollbarThickness(ScrollbarControlSize)
111 {
112 static int thickness;
113 if (!thickness)
114 thickness = ::GetSystemMetrics(SM_CXVSCROLL);
115 return thickness;
116 }
117
themeChanged()118 void ScrollbarThemeWin::themeChanged()
119 {
120 if (!scrollbarTheme)
121 return;
122
123 CloseThemeData(scrollbarTheme);
124 scrollbarTheme = 0;
125 }
126
invalidateOnMouseEnterExit()127 bool ScrollbarThemeWin::invalidateOnMouseEnterExit()
128 {
129 return runningVista;
130 }
131
hasThumb(Scrollbar * scrollbar)132 bool ScrollbarThemeWin::hasThumb(Scrollbar* scrollbar)
133 {
134 return thumbLength(scrollbar) > 0;
135 }
136
backButtonRect(Scrollbar * scrollbar,ScrollbarPart part,bool)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
forwardButtonRect(Scrollbar * scrollbar,ScrollbarPart part,bool)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
trackRect(Scrollbar * scrollbar,bool)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
shouldCenterOnThumb(Scrollbar *,const PlatformMouseEvent & evt)190 bool ScrollbarThemeWin::shouldCenterOnThumb(Scrollbar*, const PlatformMouseEvent& evt)
191 {
192 return evt.shiftKey() && evt.button() == LeftButton;
193 }
194
shouldSnapBackToDragOrigin(Scrollbar * scrollbar,const PlatformMouseEvent & evt)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
paintTrackBackground(GraphicsContext * context,Scrollbar * scrollbar,const IntRect & rect)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
paintTrackPiece(GraphicsContext * context,Scrollbar * scrollbar,const IntRect & rect,ScrollbarPart partType)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
paintButton(GraphicsContext * context,Scrollbar * scrollbar,const IntRect & rect,ScrollbarPart part)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
gripperRect(int thickness,const IntRect & thumbRect)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
paintGripper(Scrollbar * scrollbar,HDC hdc,const IntRect & rect)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
paintThumb(GraphicsContext * context,Scrollbar * scrollbar,const IntRect & rect)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