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