| /* |
| * Copyright (C) 2008, 2013 Apple Inc. All Rights Reserved. |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions |
| * are met: |
| * 1. Redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer. |
| * 2. Redistributions in binary form must reproduce the above copyright |
| * notice, this list of conditions and the following disclaimer in the |
| * documentation and/or other materials provided with the distribution. |
| * |
| * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY |
| * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
| * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR |
| * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR |
| * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, |
| * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, |
| * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR |
| * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY |
| * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
| * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| */ |
| |
| #include "config.h" |
| #include "ScrollbarThemeWin.h" |
| |
| #include "GDIUtilities.h" |
| #include "GraphicsContext.h" |
| #include "HWndDC.h" |
| #include "LocalWindowsContext.h" |
| #include "PlatformMouseEvent.h" |
| #include "Scrollbar.h" |
| #include "SystemInfo.h" |
| #include <wtf/SoftLinking.h> |
| #include <wtf/win/GDIObject.h> |
| |
| // Generic state constants |
| #define TS_NORMAL 1 |
| #define TS_HOVER 2 |
| #define TS_ACTIVE 3 |
| #define TS_DISABLED 4 |
| |
| #define SP_BUTTON 1 |
| #define SP_THUMBHOR 2 |
| #define SP_THUMBVERT 3 |
| #define SP_TRACKSTARTHOR 4 |
| #define SP_TRACKENDHOR 5 |
| #define SP_TRACKSTARTVERT 6 |
| #define SP_TRACKENDVERT 7 |
| #define SP_GRIPPERHOR 8 |
| #define SP_GRIPPERVERT 9 |
| |
| #define TS_UP_BUTTON 0 |
| #define TS_DOWN_BUTTON 4 |
| #define TS_LEFT_BUTTON 8 |
| #define TS_RIGHT_BUTTON 12 |
| #define TS_UP_BUTTON_HOVER 17 |
| #define TS_DOWN_BUTTON_HOVER 18 |
| #define TS_LEFT_BUTTON_HOVER 19 |
| #define TS_RIGHT_BUTTON_HOVER 20 |
| |
| |
| namespace WebCore { |
| |
| static HANDLE scrollbarTheme; |
| static bool runningVista; |
| |
| // FIXME: Refactor the soft-linking code so that it can be shared with RenderThemeWin |
| SOFT_LINK_LIBRARY(uxtheme) |
| SOFT_LINK(uxtheme, OpenThemeData, HANDLE, WINAPI, (HWND hwnd, LPCWSTR pszClassList), (hwnd, pszClassList)) |
| SOFT_LINK(uxtheme, CloseThemeData, HRESULT, WINAPI, (HANDLE hTheme), (hTheme)) |
| 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)) |
| SOFT_LINK(uxtheme, IsThemeActive, BOOL, WINAPI, (), ()) |
| SOFT_LINK(uxtheme, IsThemeBackgroundPartiallyTransparent, BOOL, WINAPI, (HANDLE hTheme, int iPartId, int iStateId), (hTheme, iPartId, iStateId)) |
| |
| // Constants used to figure the drag rect outside which we should snap the |
| // scrollbar thumb back to its origin. These calculations are based on |
| // observing the behavior of the MSVC8 main window scrollbar + some |
| // guessing/extrapolation. |
| static const int kOffEndMultiplier = 3; |
| static const int kOffSideMultiplier = 8; |
| |
| static void checkAndInitScrollbarTheme() |
| { |
| if (uxthemeLibrary() && !scrollbarTheme && IsThemeActive()) |
| scrollbarTheme = OpenThemeData(0, L"Scrollbar"); |
| } |
| |
| ScrollbarTheme& ScrollbarTheme::nativeTheme() |
| { |
| static ScrollbarThemeWin winTheme; |
| return winTheme; |
| } |
| |
| ScrollbarThemeWin::ScrollbarThemeWin() |
| { |
| static bool initialized; |
| if (!initialized) { |
| initialized = true; |
| checkAndInitScrollbarTheme(); |
| runningVista = (windowsVersion() >= WindowsVista); |
| } |
| } |
| |
| ScrollbarThemeWin::~ScrollbarThemeWin() = default; |
| |
| static int scrollbarThicknessInPixels() |
| { |
| static int thickness = ::GetSystemMetrics(SM_CXVSCROLL); |
| return thickness; |
| } |
| |
| int ScrollbarThemeWin::scrollbarThickness(ScrollbarControlSize, ScrollbarExpansionState) |
| { |
| float inverseScaleFactor = 1.0f / deviceScaleFactorForWindow(0); |
| return clampTo<int>(inverseScaleFactor * scrollbarThicknessInPixels()); |
| } |
| |
| void ScrollbarThemeWin::themeChanged() |
| { |
| if (!scrollbarTheme) |
| return; |
| |
| CloseThemeData(scrollbarTheme); |
| scrollbarTheme = 0; |
| } |
| |
| bool ScrollbarThemeWin::invalidateOnMouseEnterExit() |
| { |
| return runningVista; |
| } |
| |
| bool ScrollbarThemeWin::hasThumb(Scrollbar& scrollbar) |
| { |
| return thumbLength(scrollbar) > 0; |
| } |
| |
| IntRect ScrollbarThemeWin::backButtonRect(Scrollbar& scrollbar, ScrollbarPart part, bool) |
| { |
| // Windows just has single arrows. |
| if (part == BackButtonEndPart) |
| return IntRect(); |
| |
| // Our desired rect is essentially 17x17. |
| |
| // Our actual rect will shrink to half the available space when |
| // we have < 34 pixels left. This allows the scrollbar |
| // to scale down and function even at tiny sizes. |
| int thickness = scrollbarThickness(); |
| if (scrollbar.orientation() == HorizontalScrollbar) |
| return IntRect(scrollbar.x(), scrollbar.y(), |
| scrollbar.width() < 2 * thickness ? scrollbar.width() / 2 : thickness, thickness); |
| return IntRect(scrollbar.x(), scrollbar.y(), |
| thickness, scrollbar.height() < 2 * thickness ? scrollbar.height() / 2 : thickness); |
| } |
| |
| IntRect ScrollbarThemeWin::forwardButtonRect(Scrollbar& scrollbar, ScrollbarPart part, bool) |
| { |
| // Windows just has single arrows. |
| if (part == ForwardButtonStartPart) |
| return IntRect(); |
| |
| // Our desired rect is essentially 17x17. |
| |
| // Our actual rect will shrink to half the available space when |
| // we have < 34 pixels left. This allows the scrollbar |
| // to scale down and function even at tiny sizes. |
| int thickness = scrollbarThickness(); |
| if (scrollbar.orientation() == HorizontalScrollbar) { |
| int w = scrollbar.width() < 2 * thickness ? scrollbar.width() / 2 : thickness; |
| return IntRect(scrollbar.x() + scrollbar.width() - w, scrollbar.y(), w, thickness); |
| } |
| |
| int h = scrollbar.height() < 2 * thickness ? scrollbar.height() / 2 : thickness; |
| return IntRect(scrollbar.x(), scrollbar.y() + scrollbar.height() - h, thickness, h); |
| } |
| |
| IntRect ScrollbarThemeWin::trackRect(Scrollbar& scrollbar, bool) |
| { |
| int thickness = scrollbarThickness(); |
| if (scrollbar.orientation() == HorizontalScrollbar) { |
| if (scrollbar.width() < 2 * thickness) |
| return IntRect(); |
| return IntRect(scrollbar.x() + thickness, scrollbar.y(), scrollbar.width() - 2 * thickness, thickness); |
| } |
| if (scrollbar.height() < 2 * thickness) |
| return IntRect(); |
| return IntRect(scrollbar.x(), scrollbar.y() + thickness, thickness, scrollbar.height() - 2 * thickness); |
| } |
| |
| ScrollbarButtonPressAction ScrollbarThemeWin::handleMousePressEvent(Scrollbar&, const PlatformMouseEvent& event, ScrollbarPart pressedPart) |
| { |
| if (event.button() == RightButton) |
| return ScrollbarButtonPressAction::None; |
| |
| switch (pressedPart) { |
| case BackTrackPart: |
| case ForwardTrackPart: |
| if (event.shiftKey() && event.button() == LeftButton) |
| return ScrollbarButtonPressAction::CenterOnThumb; |
| break; |
| case ThumbPart: |
| return ScrollbarButtonPressAction::StartDrag; |
| default: |
| break; |
| } |
| |
| return ScrollbarButtonPressAction::Scroll; |
| } |
| |
| bool ScrollbarThemeWin::shouldSnapBackToDragOrigin(Scrollbar& scrollbar, const PlatformMouseEvent& evt) |
| { |
| // Find the rect within which we shouldn't snap, by expanding the track rect |
| // in both dimensions. |
| IntRect rect = trackRect(scrollbar); |
| const bool horz = scrollbar.orientation() == HorizontalScrollbar; |
| const int thickness = scrollbarThickness(scrollbar.controlSize()); |
| rect.inflateX((horz ? kOffEndMultiplier : kOffSideMultiplier) * thickness); |
| rect.inflateY((horz ? kOffSideMultiplier : kOffEndMultiplier) * thickness); |
| |
| // Convert the event to local coordinates. |
| IntPoint mousePosition = scrollbar.convertFromContainingWindow(evt.position()); |
| mousePosition.move(scrollbar.x(), scrollbar.y()); |
| |
| // We should snap iff the event is outside our calculated rect. |
| return !rect.contains(mousePosition); |
| } |
| |
| void ScrollbarThemeWin::paintTrackBackground(GraphicsContext& context, Scrollbar& scrollbar, const IntRect& rect) |
| { |
| // Just assume a forward track part. We only paint the track as a single piece when there is no thumb. |
| if (!hasThumb(scrollbar)) |
| paintTrackPiece(context, scrollbar, rect, ForwardTrackPart); |
| } |
| |
| void ScrollbarThemeWin::paintTrackPiece(GraphicsContext& context, Scrollbar& scrollbar, const IntRect& rect, ScrollbarPart partType) |
| { |
| checkAndInitScrollbarTheme(); |
| |
| bool start = partType == BackTrackPart; |
| int part; |
| if (scrollbar.orientation() == HorizontalScrollbar) |
| part = start ? SP_TRACKSTARTHOR : SP_TRACKENDHOR; |
| else |
| part = start ? SP_TRACKSTARTVERT : SP_TRACKENDVERT; |
| |
| int state; |
| if (!scrollbar.enabled()) |
| state = TS_DISABLED; |
| else if ((scrollbar.hoveredPart() == BackTrackPart && start) || |
| (scrollbar.hoveredPart() == ForwardTrackPart && !start)) |
| state = (scrollbar.pressedPart() == scrollbar.hoveredPart() ? TS_ACTIVE : TS_HOVER); |
| else |
| state = TS_NORMAL; |
| |
| bool alphaBlend = false; |
| if (scrollbarTheme) |
| alphaBlend = IsThemeBackgroundPartiallyTransparent(scrollbarTheme, part, state); |
| |
| LocalWindowsContext windowsContext(context, rect, alphaBlend); |
| RECT themeRect(rect); |
| |
| if (scrollbarTheme) |
| DrawThemeBackground(scrollbarTheme, windowsContext.hdc(), part, state, &themeRect, 0); |
| else { |
| DWORD color3DFace = ::GetSysColor(COLOR_3DFACE); |
| DWORD colorScrollbar = ::GetSysColor(COLOR_SCROLLBAR); |
| DWORD colorWindow = ::GetSysColor(COLOR_WINDOW); |
| HDC hdc = windowsContext.hdc(); |
| if ((color3DFace != colorScrollbar) && (colorWindow != colorScrollbar)) |
| ::FillRect(hdc, &themeRect, HBRUSH(COLOR_SCROLLBAR+1)); |
| else { |
| static WORD patternBits[8] = { 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55 }; |
| auto patternBitmap = adoptGDIObject(::CreateBitmap(8, 8, 1, 1, patternBits)); |
| auto brush = adoptGDIObject(::CreatePatternBrush(patternBitmap.get())); |
| SaveDC(hdc); |
| ::SetTextColor(hdc, ::GetSysColor(COLOR_3DHILIGHT)); |
| ::SetBkColor(hdc, ::GetSysColor(COLOR_3DFACE)); |
| ::SetBrushOrgEx(hdc, rect.x(), rect.y(), NULL); |
| ::SelectObject(hdc, brush.get()); |
| ::FillRect(hdc, &themeRect, brush.get()); |
| ::RestoreDC(hdc, -1); |
| } |
| } |
| |
| if (!alphaBlend && !context.isInTransparencyLayer()) |
| DIBPixelData::setRGBABitmapAlpha(windowsContext.hdc(), rect, 255); |
| } |
| |
| void ScrollbarThemeWin::paintButton(GraphicsContext& context, Scrollbar& scrollbar, const IntRect& rect, ScrollbarPart part) |
| { |
| checkAndInitScrollbarTheme(); |
| |
| bool start = (part == BackButtonStartPart); |
| int xpState = 0; |
| int classicState = 0; |
| if (scrollbar.orientation() == HorizontalScrollbar) |
| xpState = start ? TS_LEFT_BUTTON : TS_RIGHT_BUTTON; |
| else |
| xpState = start ? TS_UP_BUTTON : TS_DOWN_BUTTON; |
| classicState = xpState / 4; |
| |
| if (!scrollbar.enabled()) { |
| xpState += TS_DISABLED; |
| classicState |= DFCS_INACTIVE; |
| } else if ((scrollbar.hoveredPart() == BackButtonStartPart && start) || |
| (scrollbar.hoveredPart() == ForwardButtonEndPart && !start)) { |
| if (scrollbar.pressedPart() == scrollbar.hoveredPart()) { |
| xpState += TS_ACTIVE; |
| classicState |= DFCS_PUSHED; |
| classicState |= DFCS_FLAT; |
| } else |
| xpState += TS_HOVER; |
| } else { |
| if (scrollbar.hoveredPart() == NoPart || !runningVista) |
| xpState += TS_NORMAL; |
| else { |
| if (scrollbar.orientation() == HorizontalScrollbar) |
| xpState = start ? TS_LEFT_BUTTON_HOVER : TS_RIGHT_BUTTON_HOVER; |
| else |
| xpState = start ? TS_UP_BUTTON_HOVER : TS_DOWN_BUTTON_HOVER; |
| } |
| } |
| |
| bool alphaBlend = false; |
| if (scrollbarTheme) |
| alphaBlend = IsThemeBackgroundPartiallyTransparent(scrollbarTheme, SP_BUTTON, xpState); |
| |
| // There seems to be a bug in DrawThemeBackground when the device context is scaled. |
| // We can work around this by scaling the drawing rectangle instead. |
| auto scaleFactor = context.scaleFactor().width(); |
| auto scaledRect = rect; |
| scaledRect.scale(scaleFactor); |
| context.save(); |
| context.scale(FloatSize(1.0f / scaleFactor, 1.0f / scaleFactor)); |
| |
| { |
| LocalWindowsContext windowsContext(context, scaledRect, alphaBlend); |
| RECT themeRect(scaledRect); |
| if (scrollbarTheme) |
| DrawThemeBackground(scrollbarTheme, windowsContext.hdc(), SP_BUTTON, xpState, &themeRect, 0); |
| else |
| ::DrawFrameControl(windowsContext.hdc(), &themeRect, DFC_SCROLL, classicState); |
| |
| if (!alphaBlend && !context.isInTransparencyLayer()) |
| DIBPixelData::setRGBABitmapAlpha(windowsContext.hdc(), scaledRect, 255); |
| } |
| context.restore(); |
| } |
| |
| static IntRect gripperRect(int thickness, const IntRect& thumbRect) |
| { |
| // Center in the thumb. |
| int gripperThickness = thickness / 2; |
| return IntRect(thumbRect.x() + (thumbRect.width() - gripperThickness) / 2, |
| thumbRect.y() + (thumbRect.height() - gripperThickness) / 2, |
| gripperThickness, gripperThickness); |
| } |
| |
| static void paintGripper(Scrollbar& scrollbar, HDC hdc, const IntRect& rect) |
| { |
| if (!scrollbarTheme) |
| return; // Classic look has no gripper. |
| |
| int state; |
| if (!scrollbar.enabled()) |
| state = TS_DISABLED; |
| else if (scrollbar.pressedPart() == ThumbPart) |
| state = TS_ACTIVE; // Thumb always stays active once pressed. |
| else if (scrollbar.hoveredPart() == ThumbPart) |
| state = TS_HOVER; |
| else |
| state = TS_NORMAL; |
| |
| RECT themeRect(rect); |
| DrawThemeBackground(scrollbarTheme, hdc, scrollbar.orientation() == HorizontalScrollbar ? SP_GRIPPERHOR : SP_GRIPPERVERT, state, &themeRect, 0); |
| } |
| |
| void ScrollbarThemeWin::paintThumb(GraphicsContext& context, Scrollbar& scrollbar, const IntRect& rect) |
| { |
| checkAndInitScrollbarTheme(); |
| |
| int state; |
| if (!scrollbar.enabled()) |
| state = TS_DISABLED; |
| else if (scrollbar.pressedPart() == ThumbPart) |
| state = TS_ACTIVE; // Thumb always stays active once pressed. |
| else if (scrollbar.hoveredPart() == ThumbPart) |
| state = TS_HOVER; |
| else |
| state = TS_NORMAL; |
| |
| bool alphaBlend = false; |
| if (scrollbarTheme) |
| alphaBlend = IsThemeBackgroundPartiallyTransparent(scrollbarTheme, scrollbar.orientation() == HorizontalScrollbar ? SP_THUMBHOR : SP_THUMBVERT, state); |
| LocalWindowsContext windowsContext(context, rect, alphaBlend); |
| RECT themeRect(rect); |
| if (scrollbarTheme) { |
| DrawThemeBackground(scrollbarTheme, windowsContext.hdc(), scrollbar.orientation() == HorizontalScrollbar ? SP_THUMBHOR : SP_THUMBVERT, state, &themeRect, 0); |
| paintGripper(scrollbar, windowsContext.hdc(), gripperRect(scrollbarThickness(), rect)); |
| } else |
| ::DrawEdge(windowsContext.hdc(), &themeRect, EDGE_RAISED, BF_RECT | BF_MIDDLE); |
| |
| if (!alphaBlend && !context.isInTransparencyLayer()) |
| DIBPixelData::setRGBABitmapAlpha(windowsContext.hdc(), rect, 255); |
| } |
| |
| } |
| |