ScrollbarThemeWin.cpp [plain text]
#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>
#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 {
using namespace std;
static HANDLE scrollbarTheme;
static bool runningVista;
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))
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)
{
if (part == BackButtonEndPart)
return IntRect();
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)
{
if (part == ForwardButtonStartPart)
return IntRect();
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)
{
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);
IntPoint mousePosition = scrollbar.convertFromContainingWindow(evt.position());
mousePosition.move(scrollbar.x(), scrollbar.y());
return !rect.contains(mousePosition);
}
void ScrollbarThemeWin::paintTrackBackground(GraphicsContext& context, Scrollbar& scrollbar, const IntRect& rect)
{
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);
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)
{
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;
int state;
if (!scrollbar.enabled())
state = TS_DISABLED;
else if (scrollbar.pressedPart() == ThumbPart)
state = TS_ACTIVE; 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; 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);
}
}