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