Use "= default" to denote default constructor or destructor
[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 "SystemInfo.h"
36 #include <wtf/SoftLinking.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
65 namespace WebCore {
66 using namespace std;
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() = default;
109
110 static int scrollbarThicknessInPixels()
111 {
112     static int thickness = ::GetSystemMetrics(SM_CXVSCROLL);
113     return thickness;
114 }
115
116 int ScrollbarThemeWin::scrollbarThickness(ScrollbarControlSize, ScrollbarExpansionState)
117 {
118     float inverseScaleFactor = 1.0f / deviceScaleFactorForWindow(0);
119     return clampTo<int>(inverseScaleFactor * scrollbarThicknessInPixels());
120 }
121
122 void ScrollbarThemeWin::themeChanged()
123 {
124     if (!scrollbarTheme)
125         return;
126
127     CloseThemeData(scrollbarTheme);
128     scrollbarTheme = 0;
129 }
130
131 bool ScrollbarThemeWin::invalidateOnMouseEnterExit()
132 {
133     return runningVista;
134 }
135
136 bool ScrollbarThemeWin::hasThumb(Scrollbar& scrollbar)
137 {
138     return thumbLength(scrollbar) > 0;
139 }
140
141 IntRect ScrollbarThemeWin::backButtonRect(Scrollbar& scrollbar, ScrollbarPart part, bool)
142 {
143     // Windows just has single arrows.
144     if (part == BackButtonEndPart)
145         return IntRect();
146
147     // Our desired rect is essentially 17x17.
148     
149     // Our actual rect will shrink to half the available space when
150     // we have < 34 pixels left.  This allows the scrollbar
151     // to scale down and function even at tiny sizes.
152     int thickness = scrollbarThickness();
153     if (scrollbar.orientation() == HorizontalScrollbar)
154         return IntRect(scrollbar.x(), scrollbar.y(),
155                        scrollbar.width() < 2 * thickness ? scrollbar.width() / 2 : thickness, thickness);
156     return IntRect(scrollbar.x(), scrollbar.y(),
157                    thickness, scrollbar.height() < 2 * thickness ? scrollbar.height() / 2 : thickness);
158 }
159
160 IntRect ScrollbarThemeWin::forwardButtonRect(Scrollbar& scrollbar, ScrollbarPart part, bool)
161 {
162     // Windows just has single arrows.
163     if (part == ForwardButtonStartPart)
164         return IntRect();
165     
166     // Our desired rect is essentially 17x17.
167     
168     // Our actual rect will shrink to half the available space when
169     // we have < 34 pixels left.  This allows the scrollbar
170     // to scale down and function even at tiny sizes.
171     int thickness = scrollbarThickness();
172     if (scrollbar.orientation() == HorizontalScrollbar) {
173         int w = scrollbar.width() < 2 * thickness ? scrollbar.width() / 2 : thickness;
174         return IntRect(scrollbar.x() + scrollbar.width() - w, scrollbar.y(), w, thickness);
175     }
176     
177     int h = scrollbar.height() < 2 * thickness ? scrollbar.height() / 2 : thickness;
178     return IntRect(scrollbar.x(), scrollbar.y() + scrollbar.height() - h, thickness, h);
179 }
180
181 IntRect ScrollbarThemeWin::trackRect(Scrollbar& scrollbar, bool)
182 {
183     int thickness = scrollbarThickness();
184     if (scrollbar.orientation() == HorizontalScrollbar) {
185         if (scrollbar.width() < 2 * thickness)
186             return IntRect();
187         return IntRect(scrollbar.x() + thickness, scrollbar.y(), scrollbar.width() - 2 * thickness, thickness);
188     }
189     if (scrollbar.height() < 2 * thickness)
190         return IntRect();
191     return IntRect(scrollbar.x(), scrollbar.y() + thickness, thickness, scrollbar.height() - 2 * thickness);
192 }
193
194 ScrollbarButtonPressAction ScrollbarThemeWin::handleMousePressEvent(Scrollbar&, const PlatformMouseEvent& event, ScrollbarPart pressedPart)
195 {
196     if (event.button() == RightButton)
197         return ScrollbarButtonPressAction::None;
198
199     switch (pressedPart) {
200     case BackTrackPart:
201     case ForwardTrackPart:
202         if (event.shiftKey() && event.button() == LeftButton)
203             return ScrollbarButtonPressAction::CenterOnThumb;
204         break;
205     case ThumbPart:
206         return ScrollbarButtonPressAction::StartDrag;
207     default:
208         break;
209     }
210
211     return ScrollbarButtonPressAction::Scroll;
212 }
213
214 bool ScrollbarThemeWin::shouldSnapBackToDragOrigin(Scrollbar& scrollbar, const PlatformMouseEvent& evt)
215 {
216     // Find the rect within which we shouldn't snap, by expanding the track rect
217     // in both dimensions.
218     IntRect rect = trackRect(scrollbar);
219     const bool horz = scrollbar.orientation() == HorizontalScrollbar;
220     const int thickness = scrollbarThickness(scrollbar.controlSize());
221     rect.inflateX((horz ? kOffEndMultiplier : kOffSideMultiplier) * thickness);
222     rect.inflateY((horz ? kOffSideMultiplier : kOffEndMultiplier) * thickness);
223
224     // Convert the event to local coordinates.
225     IntPoint mousePosition = scrollbar.convertFromContainingWindow(evt.position());
226     mousePosition.move(scrollbar.x(), scrollbar.y());
227
228     // We should snap iff the event is outside our calculated rect.
229     return !rect.contains(mousePosition);
230 }
231
232 void ScrollbarThemeWin::paintTrackBackground(GraphicsContext& context, Scrollbar& scrollbar, const IntRect& rect)
233 {
234     // Just assume a forward track part.  We only paint the track as a single piece when there is no thumb.
235     if (!hasThumb(scrollbar))
236         paintTrackPiece(context, scrollbar, rect, ForwardTrackPart);
237 }
238
239 void ScrollbarThemeWin::paintTrackPiece(GraphicsContext& context, Scrollbar& scrollbar, const IntRect& rect, ScrollbarPart partType)
240 {
241     checkAndInitScrollbarTheme();
242
243     bool start = partType == BackTrackPart;
244     int part;
245     if (scrollbar.orientation() == HorizontalScrollbar)
246         part = start ? SP_TRACKSTARTHOR : SP_TRACKENDHOR;
247     else
248         part = start ? SP_TRACKSTARTVERT : SP_TRACKENDVERT;
249
250     int state;
251     if (!scrollbar.enabled())
252         state = TS_DISABLED;
253     else if ((scrollbar.hoveredPart() == BackTrackPart && start) ||
254              (scrollbar.hoveredPart() == ForwardTrackPart && !start))
255         state = (scrollbar.pressedPart() == scrollbar.hoveredPart() ? TS_ACTIVE : TS_HOVER);
256     else
257         state = TS_NORMAL;
258
259     bool alphaBlend = false;
260     if (scrollbarTheme)
261         alphaBlend = IsThemeBackgroundPartiallyTransparent(scrollbarTheme, part, state);
262
263     LocalWindowsContext windowsContext(context, rect, alphaBlend);
264     RECT themeRect(rect);
265
266     if (scrollbarTheme)
267         DrawThemeBackground(scrollbarTheme, windowsContext.hdc(), part, state, &themeRect, 0);
268     else {
269         DWORD color3DFace = ::GetSysColor(COLOR_3DFACE);
270         DWORD colorScrollbar = ::GetSysColor(COLOR_SCROLLBAR);
271         DWORD colorWindow = ::GetSysColor(COLOR_WINDOW);
272         HDC hdc = windowsContext.hdc();
273         if ((color3DFace != colorScrollbar) && (colorWindow != colorScrollbar))
274             ::FillRect(hdc, &themeRect, HBRUSH(COLOR_SCROLLBAR+1));
275         else {
276             static WORD patternBits[8] = { 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55 };
277             auto patternBitmap = adoptGDIObject(::CreateBitmap(8, 8, 1, 1, patternBits));
278             auto brush = adoptGDIObject(::CreatePatternBrush(patternBitmap.get()));
279             SaveDC(hdc);
280             ::SetTextColor(hdc, ::GetSysColor(COLOR_3DHILIGHT));
281             ::SetBkColor(hdc, ::GetSysColor(COLOR_3DFACE));
282             ::SetBrushOrgEx(hdc, rect.x(), rect.y(), NULL);
283             ::SelectObject(hdc, brush.get());
284             ::FillRect(hdc, &themeRect, brush.get());
285             ::RestoreDC(hdc, -1);
286         }
287     }
288
289     if (!alphaBlend && !context.isInTransparencyLayer())
290         DIBPixelData::setRGBABitmapAlpha(windowsContext.hdc(), rect, 255);
291 }
292
293 void ScrollbarThemeWin::paintButton(GraphicsContext& context, Scrollbar& scrollbar, const IntRect& rect, ScrollbarPart part)
294 {
295     checkAndInitScrollbarTheme();
296
297     bool start = (part == BackButtonStartPart);
298     int xpState = 0;
299     int classicState = 0;
300     if (scrollbar.orientation() == HorizontalScrollbar)
301         xpState = start ? TS_LEFT_BUTTON : TS_RIGHT_BUTTON;
302     else
303         xpState = start ? TS_UP_BUTTON : TS_DOWN_BUTTON;
304     classicState = xpState / 4;
305
306     if (!scrollbar.enabled()) {
307         xpState += TS_DISABLED;
308         classicState |= DFCS_INACTIVE;
309     } else if ((scrollbar.hoveredPart() == BackButtonStartPart && start) ||
310                (scrollbar.hoveredPart() == ForwardButtonEndPart && !start)) {
311         if (scrollbar.pressedPart() == scrollbar.hoveredPart()) {
312             xpState += TS_ACTIVE;
313             classicState |= DFCS_PUSHED;
314             classicState |= DFCS_FLAT;
315         } else
316             xpState += TS_HOVER;
317     } else {
318         if (scrollbar.hoveredPart() == NoPart || !runningVista)
319             xpState += TS_NORMAL;
320         else {
321             if (scrollbar.orientation() == HorizontalScrollbar)
322                 xpState = start ? TS_LEFT_BUTTON_HOVER : TS_RIGHT_BUTTON_HOVER;
323             else
324                 xpState = start ? TS_UP_BUTTON_HOVER : TS_DOWN_BUTTON_HOVER;
325         }
326     }
327
328     bool alphaBlend = false;
329     if (scrollbarTheme)
330         alphaBlend = IsThemeBackgroundPartiallyTransparent(scrollbarTheme, SP_BUTTON, xpState);
331
332     // There seems to be a bug in DrawThemeBackground when the device context is scaled.
333     // We can work around this by scaling the drawing rectangle instead.
334     auto scaleFactor = context.scaleFactor().width();
335     auto scaledRect = rect;
336     scaledRect.scale(scaleFactor);
337     context.save();
338     context.scale(FloatSize(1.0f / scaleFactor, 1.0f / scaleFactor));
339
340     {
341         LocalWindowsContext windowsContext(context, scaledRect, alphaBlend);
342         RECT themeRect(scaledRect);
343         if (scrollbarTheme)
344             DrawThemeBackground(scrollbarTheme, windowsContext.hdc(), SP_BUTTON, xpState, &themeRect, 0);
345         else
346             ::DrawFrameControl(windowsContext.hdc(), &themeRect, DFC_SCROLL, classicState);
347
348         if (!alphaBlend && !context.isInTransparencyLayer())
349             DIBPixelData::setRGBABitmapAlpha(windowsContext.hdc(), scaledRect, 255);
350     }
351     context.restore();
352 }
353
354 static IntRect gripperRect(int thickness, const IntRect& thumbRect)
355 {
356     // Center in the thumb.
357     int gripperThickness = thickness / 2;
358     return IntRect(thumbRect.x() + (thumbRect.width() - gripperThickness) / 2,
359                    thumbRect.y() + (thumbRect.height() - gripperThickness) / 2,
360                    gripperThickness, gripperThickness);
361 }
362
363 static void paintGripper(Scrollbar& scrollbar, HDC hdc, const IntRect& rect)
364 {
365     if (!scrollbarTheme)
366         return;  // Classic look has no gripper.
367    
368     int state;
369     if (!scrollbar.enabled())
370         state = TS_DISABLED;
371     else if (scrollbar.pressedPart() == ThumbPart)
372         state = TS_ACTIVE; // Thumb always stays active once pressed.
373     else if (scrollbar.hoveredPart() == ThumbPart)
374         state = TS_HOVER;
375     else
376         state = TS_NORMAL;
377
378     RECT themeRect(rect);
379     DrawThemeBackground(scrollbarTheme, hdc, scrollbar.orientation() == HorizontalScrollbar ? SP_GRIPPERHOR : SP_GRIPPERVERT, state, &themeRect, 0);
380 }
381
382 void ScrollbarThemeWin::paintThumb(GraphicsContext& context, Scrollbar& scrollbar, const IntRect& rect)
383 {
384     checkAndInitScrollbarTheme();
385
386     int state;
387     if (!scrollbar.enabled())
388         state = TS_DISABLED;
389     else if (scrollbar.pressedPart() == ThumbPart)
390         state = TS_ACTIVE; // Thumb always stays active once pressed.
391     else if (scrollbar.hoveredPart() == ThumbPart)
392         state = TS_HOVER;
393     else
394         state = TS_NORMAL;
395
396     bool alphaBlend = false;
397     if (scrollbarTheme)
398         alphaBlend = IsThemeBackgroundPartiallyTransparent(scrollbarTheme, scrollbar.orientation() == HorizontalScrollbar ? SP_THUMBHOR : SP_THUMBVERT, state);
399     LocalWindowsContext windowsContext(context, rect, alphaBlend);
400     RECT themeRect(rect);
401     if (scrollbarTheme) {
402         DrawThemeBackground(scrollbarTheme, windowsContext.hdc(), scrollbar.orientation() == HorizontalScrollbar ? SP_THUMBHOR : SP_THUMBVERT, state, &themeRect, 0);
403         paintGripper(scrollbar, windowsContext.hdc(), gripperRect(scrollbarThickness(), rect));
404     } else
405         ::DrawEdge(windowsContext.hdc(), &themeRect, EDGE_RAISED, BF_RECT | BF_MIDDLE);
406
407     if (!alphaBlend && !context.isInTransparencyLayer())
408         DIBPixelData::setRGBABitmapAlpha(windowsContext.hdc(), rect, 255);
409 }
410
411 }
412