// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "ui/native_theme/native_theme_win.h"
#include <windows.h>
#include <uxtheme.h>
#include <vsstyle.h>
#include <vssym32.h>
#include "base/basictypes.h"
#include "base/logging.h"
#include "base/memory/scoped_ptr.h"
#include "base/win/scoped_gdi_object.h"
#include "base/win/scoped_hdc.h"
#include "base/win/scoped_select_object.h"
#include "base/win/windows_version.h"
#include "skia/ext/bitmap_platform_device.h"
#include "skia/ext/platform_canvas.h"
#include "skia/ext/skia_utils_win.h"
#include "third_party/skia/include/core/SkCanvas.h"
#include "third_party/skia/include/core/SkColorPriv.h"
#include "third_party/skia/include/core/SkShader.h"
#include "ui/gfx/color_utils.h"
#include "ui/gfx/gdi_util.h"
#include "ui/gfx/rect.h"
#include "ui/gfx/rect_conversions.h"
#include "ui/gfx/win/dpi.h"
#include "ui/native_theme/common_theme.h"
// This was removed from Winvers.h but is still used.
#if !defined(COLOR_MENUHIGHLIGHT)
#define COLOR_MENUHIGHLIGHT 29
#endif
namespace {
// TODO: Obtain the correct colors using GetSysColor.
// Theme colors returned by GetSystemColor().
const SkColor kInvalidColorIdColor = SkColorSetRGB(255, 0, 128);
// Dialogs:
const SkColor kDialogBackgroundColor = SkColorSetRGB(251, 251, 251);
// FocusableBorder:
const SkColor kFocusedBorderColor = SkColorSetRGB(0x4d, 0x90, 0xfe);
const SkColor kUnfocusedBorderColor = SkColorSetRGB(0xd9, 0xd9, 0xd9);
// Button:
const SkColor kButtonBackgroundColor = SkColorSetRGB(0xde, 0xde, 0xde);
const SkColor kButtonHighlightColor = SkColorSetARGB(200, 255, 255, 255);
const SkColor kButtonHoverColor = SkColorSetRGB(6, 45, 117);
const SkColor kButtonHoverBackgroundColor = SkColorSetRGB(0xEA, 0xEA, 0xEA);
// MenuItem:
const SkColor kEnabledMenuItemForegroundColor = SkColorSetRGB(6, 45, 117);
const SkColor kDisabledMenuItemForegroundColor = SkColorSetRGB(161, 161, 146);
const SkColor kFocusedMenuItemBackgroundColor = SkColorSetRGB(246, 249, 253);
const SkColor kMenuSeparatorColor = SkColorSetARGB(50, 0, 0, 0);
// Table:
const SkColor kTreeSelectionBackgroundUnfocused = SkColorSetRGB(240, 240, 240);
// Windows system color IDs cached and updated by the native theme.
const int kSystemColors[] = {
COLOR_3DFACE,
COLOR_BTNTEXT,
COLOR_GRAYTEXT,
COLOR_HIGHLIGHT,
COLOR_HIGHLIGHTTEXT,
COLOR_SCROLLBAR,
COLOR_WINDOW,
COLOR_WINDOWTEXT,
COLOR_BTNFACE,
COLOR_MENUHIGHLIGHT,
};
void SetCheckerboardShader(SkPaint* paint, const RECT& align_rect) {
// Create a 2x2 checkerboard pattern using the 3D face and highlight colors.
const SkColor face = color_utils::GetSysSkColor(COLOR_3DFACE);
const SkColor highlight = color_utils::GetSysSkColor(COLOR_3DHILIGHT);
SkColor buffer[] = { face, highlight, highlight, face };
// Confusing bit: we first create a temporary bitmap with our desired pattern,
// then copy it to another bitmap. The temporary bitmap doesn't take
// ownership of the pixel data, and so will point to garbage when this
// function returns. The copy will copy the pixel data into a place owned by
// the bitmap, which is in turn owned by the shader, etc., so it will live
// until we're done using it.
SkBitmap temp_bitmap;
temp_bitmap.setConfig(SkBitmap::kARGB_8888_Config, 2, 2);
temp_bitmap.setPixels(buffer);
SkBitmap bitmap;
temp_bitmap.copyTo(&bitmap);
// Align the pattern with the upper corner of |align_rect|.
SkMatrix local_matrix;
local_matrix.setTranslate(SkIntToScalar(align_rect.left),
SkIntToScalar(align_rect.top));
skia::RefPtr<SkShader> shader =
skia::AdoptRef(SkShader::CreateBitmapShader(bitmap,
SkShader::kRepeat_TileMode,
SkShader::kRepeat_TileMode,
&local_matrix));
paint->setShader(shader.get());
}
// <-a->
// [ ***** ]
// ____ | |
// <-a-> <------b----->
// a: object_width
// b: frame_width
// *: animating object
//
// - the animation goes from "[" to "]" repeatedly.
// - the animation offset is at first "|"
//
int ComputeAnimationProgress(int frame_width,
int object_width,
int pixels_per_second,
double animated_seconds) {
int animation_width = frame_width + object_width;
double interval = static_cast<double>(animation_width) / pixels_per_second;
double ratio = fmod(animated_seconds, interval) / interval;
return static_cast<int>(animation_width * ratio) - object_width;
}
RECT InsetRect(const RECT* rect, int size) {
gfx::Rect result(*rect);
result.Inset(size, size);
return result.ToRECT();
}
} // namespace
namespace ui {
bool NativeThemeWin::IsThemingActive() const {
if (is_theme_active_)
return !!is_theme_active_();
return false;
}
bool NativeThemeWin::IsUsingHighContrastTheme() const {
if (is_using_high_contrast_valid_)
return is_using_high_contrast_;
HIGHCONTRAST result;
result.cbSize = sizeof(HIGHCONTRAST);
is_using_high_contrast_ =
SystemParametersInfo(SPI_GETHIGHCONTRAST, result.cbSize, &result, 0) &&
(result.dwFlags & HCF_HIGHCONTRASTON) == HCF_HIGHCONTRASTON;
is_using_high_contrast_valid_ = true;
return is_using_high_contrast_;
}
HRESULT NativeThemeWin::GetThemeColor(ThemeName theme,
int part_id,
int state_id,
int prop_id,
SkColor* color) const {
HANDLE handle = GetThemeHandle(theme);
if (handle && get_theme_color_) {
COLORREF color_ref;
if (get_theme_color_(handle, part_id, state_id, prop_id, &color_ref) ==
S_OK) {
*color = skia::COLORREFToSkColor(color_ref);
return S_OK;
}
}
return E_NOTIMPL;
}
SkColor NativeThemeWin::GetThemeColorWithDefault(ThemeName theme,
int part_id,
int state_id,
int prop_id,
int default_sys_color) const {
SkColor color;
if (GetThemeColor(theme, part_id, state_id, prop_id, &color) != S_OK)
color = color_utils::GetSysSkColor(default_sys_color);
return color;
}
gfx::Size NativeThemeWin::GetThemeBorderSize(ThemeName theme) const {
// For simplicity use the wildcard state==0, part==0, since it works
// for the cases we currently depend on.
int border;
if (GetThemeInt(theme, 0, 0, TMT_BORDERSIZE, &border) == S_OK)
return gfx::Size(border, border);
else
return gfx::Size(GetSystemMetrics(SM_CXEDGE), GetSystemMetrics(SM_CYEDGE));
}
void NativeThemeWin::DisableTheming() const {
if (!set_theme_properties_)
return;
set_theme_properties_(0);
}
void NativeThemeWin::CloseHandles() const {
if (!close_theme_)
return;
for (int i = 0; i < LAST; ++i) {
if (theme_handles_[i]) {
close_theme_(theme_handles_[i]);
theme_handles_[i] = NULL;
}
}
}
bool NativeThemeWin::IsClassicTheme(ThemeName name) const {
if (!theme_dll_)
return true;
return !GetThemeHandle(name);
}
// static
NativeThemeWin* NativeThemeWin::instance() {
CR_DEFINE_STATIC_LOCAL(NativeThemeWin, s_native_theme, ());
return &s_native_theme;
}
gfx::Size NativeThemeWin::GetPartSize(Part part,
State state,
const ExtraParams& extra) const {
gfx::Size part_size = CommonThemeGetPartSize(part, state, extra);
if (!part_size.IsEmpty())
return part_size;
// The GetThemePartSize call below returns the default size without
// accounting for user customization (crbug/218291).
switch (part) {
case kScrollbarDownArrow:
case kScrollbarLeftArrow:
case kScrollbarRightArrow:
case kScrollbarUpArrow:
case kScrollbarHorizontalThumb:
case kScrollbarVerticalThumb:
case kScrollbarHorizontalTrack:
case kScrollbarVerticalTrack: {
int size = gfx::win::GetSystemMetricsInDIP(SM_CXVSCROLL);
if (size == 0)
size = 17;
return gfx::Size(size, size);
}
}
int part_id = GetWindowsPart(part, state, extra);
int state_id = GetWindowsState(part, state, extra);
SIZE size;
HDC hdc = GetDC(NULL);
HRESULT hr = GetThemePartSize(GetThemeName(part), hdc, part_id, state_id,
NULL, TS_TRUE, &size);
ReleaseDC(NULL, hdc);
if (FAILED(hr)) {
// TODO(rogerta): For now, we need to support radio buttons and checkboxes
// when theming is not enabled. Support for other parts can be added
// if/when needed.
switch (part) {
case kCheckbox:
case kRadio:
// TODO(rogerta): I was not able to find any API to get the default
// size of these controls, so determined these values empirically.
size.cx = 13;
size.cy = 13;
break;
default:
size.cx = 0;
size.cy = 0;
break;
}
}
return gfx::Size(size.cx, size.cy);
}
void NativeThemeWin::Paint(SkCanvas* canvas,
Part part,
State state,
const gfx::Rect& rect,
const ExtraParams& extra) const {
if (rect.IsEmpty())
return;
switch (part) {
case kComboboxArrow:
CommonThemePaintComboboxArrow(canvas, rect);
return;
case kMenuPopupGutter:
CommonThemePaintMenuGutter(canvas, rect);
return;
case kMenuPopupSeparator:
CommonThemePaintMenuSeparator(canvas, rect, extra.menu_separator);
return;
case kMenuPopupBackground:
CommonThemePaintMenuBackground(canvas, rect);
return;
case kMenuItemBackground:
CommonThemePaintMenuItemBackground(canvas, state, rect);
return;
}
bool needs_paint_indirect = false;
if (!skia::SupportsPlatformPaint(canvas)) {
// This block will only get hit with --enable-accelerated-drawing flag.
needs_paint_indirect = true;
} else {
// Scrollbar components on Windows Classic theme (on all Windows versions)
// have particularly problematic alpha values, so always draw them
// indirectly. In addition, scrollbar thumbs and grippers for the Windows XP
// theme (available only on Windows XP) also need their alpha values
// fixed.
switch (part) {
case kScrollbarDownArrow:
case kScrollbarUpArrow:
case kScrollbarLeftArrow:
case kScrollbarRightArrow:
if (!GetThemeHandle(SCROLLBAR))
needs_paint_indirect = true;
break;
case kScrollbarHorizontalThumb:
case kScrollbarVerticalThumb:
case kScrollbarHorizontalGripper:
case kScrollbarVerticalGripper:
if (!GetThemeHandle(SCROLLBAR) ||
base::win::GetVersion() == base::win::VERSION_XP)
needs_paint_indirect = true;
break;
default:
break;
}
}
if (needs_paint_indirect)
PaintIndirect(canvas, part, state, rect, extra);
else
PaintDirect(canvas, part, state, rect, extra);
}
NativeThemeWin::NativeThemeWin()
: theme_dll_(LoadLibrary(L"uxtheme.dll")),
draw_theme_(NULL),
draw_theme_ex_(NULL),
get_theme_color_(NULL),
get_theme_content_rect_(NULL),
get_theme_part_size_(NULL),
open_theme_(NULL),
close_theme_(NULL),
set_theme_properties_(NULL),
is_theme_active_(NULL),
get_theme_int_(NULL),
color_change_listener_(this),
is_using_high_contrast_(false),
is_using_high_contrast_valid_(false) {
if (theme_dll_) {
draw_theme_ = reinterpret_cast<DrawThemeBackgroundPtr>(
GetProcAddress(theme_dll_, "DrawThemeBackground"));
draw_theme_ex_ = reinterpret_cast<DrawThemeBackgroundExPtr>(
GetProcAddress(theme_dll_, "DrawThemeBackgroundEx"));
get_theme_color_ = reinterpret_cast<GetThemeColorPtr>(
GetProcAddress(theme_dll_, "GetThemeColor"));
get_theme_content_rect_ = reinterpret_cast<GetThemeContentRectPtr>(
GetProcAddress(theme_dll_, "GetThemeBackgroundContentRect"));
get_theme_part_size_ = reinterpret_cast<GetThemePartSizePtr>(
GetProcAddress(theme_dll_, "GetThemePartSize"));
open_theme_ = reinterpret_cast<OpenThemeDataPtr>(
GetProcAddress(theme_dll_, "OpenThemeData"));
close_theme_ = reinterpret_cast<CloseThemeDataPtr>(
GetProcAddress(theme_dll_, "CloseThemeData"));
set_theme_properties_ = reinterpret_cast<SetThemeAppPropertiesPtr>(
GetProcAddress(theme_dll_, "SetThemeAppProperties"));
is_theme_active_ = reinterpret_cast<IsThemeActivePtr>(
GetProcAddress(theme_dll_, "IsThemeActive"));
get_theme_int_ = reinterpret_cast<GetThemeIntPtr>(
GetProcAddress(theme_dll_, "GetThemeInt"));
}
memset(theme_handles_, 0, sizeof(theme_handles_));
// Initialize the cached system colors.
UpdateSystemColors();
}
NativeThemeWin::~NativeThemeWin() {
if (theme_dll_) {
// todo (cpu): fix this soon. Making a call to CloseHandles() here breaks
// certain tests and the reliability bots.
// CloseHandles();
FreeLibrary(theme_dll_);
}
}
void NativeThemeWin::OnSysColorChange() {
UpdateSystemColors();
is_using_high_contrast_valid_ = false;
NotifyObservers();
}
void NativeThemeWin::UpdateSystemColors() {
for (int i = 0; i < arraysize(kSystemColors); ++i) {
system_colors_[kSystemColors[i]] =
color_utils::GetSysSkColor(kSystemColors[i]);
}
}
void NativeThemeWin::PaintDirect(SkCanvas* canvas,
Part part,
State state,
const gfx::Rect& rect,
const ExtraParams& extra) const {
skia::ScopedPlatformPaint scoped_platform_paint(canvas);
HDC hdc = scoped_platform_paint.GetPlatformSurface();
switch (part) {
case kCheckbox:
PaintCheckbox(hdc, part, state, rect, extra.button);
break;
case kRadio:
PaintRadioButton(hdc, part, state, rect, extra.button);
break;
case kPushButton:
PaintPushButton(hdc, part, state, rect, extra.button);
break;
case kMenuPopupArrow:
PaintMenuArrow(hdc, state, rect, extra.menu_arrow);
break;
case kMenuPopupGutter:
PaintMenuGutter(hdc, rect);
break;
case kMenuPopupSeparator:
PaintMenuSeparator(hdc, rect, extra.menu_separator);
break;
case kMenuPopupBackground:
PaintMenuBackground(hdc, rect);
break;
case kMenuCheck:
PaintMenuCheck(hdc, state, rect, extra.menu_check);
break;
case kMenuCheckBackground:
PaintMenuCheckBackground(hdc, state, rect);
break;
case kMenuItemBackground:
PaintMenuItemBackground(hdc, state, rect, extra.menu_item);
break;
case kMenuList:
PaintMenuList(hdc, state, rect, extra.menu_list);
break;
case kScrollbarDownArrow:
case kScrollbarUpArrow:
case kScrollbarLeftArrow:
case kScrollbarRightArrow:
PaintScrollbarArrow(hdc, part, state, rect, extra.scrollbar_arrow);
break;
case kScrollbarHorizontalTrack:
case kScrollbarVerticalTrack:
PaintScrollbarTrack(canvas, hdc, part, state, rect,
extra.scrollbar_track);
break;
case kScrollbarCorner:
canvas->drawColor(SK_ColorWHITE, SkXfermode::kSrc_Mode);
break;
case kScrollbarHorizontalThumb:
case kScrollbarVerticalThumb:
case kScrollbarHorizontalGripper:
case kScrollbarVerticalGripper:
PaintScrollbarThumb(hdc, part, state, rect, extra.scrollbar_thumb);
break;
case kInnerSpinButton:
PaintSpinButton(hdc, part, state, rect, extra.inner_spin);
break;
case kTrackbarThumb:
case kTrackbarTrack:
PaintTrackbar(canvas, hdc, part, state, rect, extra.trackbar);
break;
case kProgressBar:
PaintProgressBar(hdc, rect, extra.progress_bar);
break;
case kWindowResizeGripper:
PaintWindowResizeGripper(hdc, rect);
break;
case kTabPanelBackground:
PaintTabPanelBackground(hdc, rect);
break;
case kTextField:
PaintTextField(hdc, part, state, rect, extra.text_field);
break;
case kSliderTrack:
case kSliderThumb:
default:
// While transitioning NativeThemeWin to the single Paint() entry point,
// unsupported parts will DCHECK here.
NOTREACHED();
}
}
SkColor NativeThemeWin::GetSystemColor(ColorId color_id) const {
SkColor color;
if (CommonThemeGetSystemColor(color_id, &color))
return color;
switch (color_id) {
// Windows
case kColorId_WindowBackground:
return system_colors_[COLOR_WINDOW];
// Dialogs
case kColorId_DialogBackground:
if (gfx::IsInvertedColorScheme())
return color_utils::InvertColor(kDialogBackgroundColor);
return kDialogBackgroundColor;
// FocusableBorder
case kColorId_FocusedBorderColor:
return kFocusedBorderColor;
case kColorId_UnfocusedBorderColor:
return kUnfocusedBorderColor;
// Button
case kColorId_ButtonBackgroundColor:
return kButtonBackgroundColor;
case kColorId_ButtonEnabledColor:
return system_colors_[COLOR_BTNTEXT];
case kColorId_ButtonDisabledColor:
return system_colors_[COLOR_GRAYTEXT];
case kColorId_ButtonHighlightColor:
return kButtonHighlightColor;
case kColorId_ButtonHoverColor:
return kButtonHoverColor;
case kColorId_ButtonHoverBackgroundColor:
return kButtonHoverBackgroundColor;
// MenuItem
case kColorId_EnabledMenuItemForegroundColor:
return kEnabledMenuItemForegroundColor;
case kColorId_DisabledMenuItemForegroundColor:
return kDisabledMenuItemForegroundColor;
case kColorId_DisabledEmphasizedMenuItemForegroundColor:
return SK_ColorBLACK;
case kColorId_FocusedMenuItemBackgroundColor:
return kFocusedMenuItemBackgroundColor;
case kColorId_MenuSeparatorColor:
return kMenuSeparatorColor;
// Label
case kColorId_LabelEnabledColor:
return system_colors_[COLOR_BTNTEXT];
case kColorId_LabelDisabledColor:
return system_colors_[COLOR_GRAYTEXT];
case kColorId_LabelBackgroundColor:
return system_colors_[COLOR_WINDOW];
// Textfield
case kColorId_TextfieldDefaultColor:
return system_colors_[COLOR_WINDOWTEXT];
case kColorId_TextfieldDefaultBackground:
return system_colors_[COLOR_WINDOW];
case kColorId_TextfieldReadOnlyColor:
return system_colors_[COLOR_GRAYTEXT];
case kColorId_TextfieldReadOnlyBackground:
return system_colors_[COLOR_3DFACE];
case kColorId_TextfieldSelectionColor:
return system_colors_[COLOR_HIGHLIGHTTEXT];
case kColorId_TextfieldSelectionBackgroundFocused:
return system_colors_[COLOR_HIGHLIGHT];
// Tree
// NOTE: these aren't right for all themes, but as close as I could get.
case kColorId_TreeBackground:
return system_colors_[COLOR_WINDOW];
case kColorId_TreeText:
return system_colors_[COLOR_WINDOWTEXT];
case kColorId_TreeSelectedText:
return system_colors_[COLOR_HIGHLIGHTTEXT];
case kColorId_TreeSelectedTextUnfocused:
return system_colors_[COLOR_BTNTEXT];
case kColorId_TreeSelectionBackgroundFocused:
return system_colors_[COLOR_HIGHLIGHT];
case kColorId_TreeSelectionBackgroundUnfocused:
return system_colors_[IsUsingHighContrastTheme() ?
COLOR_MENUHIGHLIGHT : COLOR_BTNFACE];
case kColorId_TreeArrow:
return system_colors_[COLOR_WINDOWTEXT];
// Table
case kColorId_TableBackground:
return system_colors_[COLOR_WINDOW];
case kColorId_TableText:
return system_colors_[COLOR_WINDOWTEXT];
case kColorId_TableSelectedText:
return system_colors_[COLOR_HIGHLIGHTTEXT];
case kColorId_TableSelectedTextUnfocused:
return system_colors_[COLOR_BTNTEXT];
case kColorId_TableSelectionBackgroundFocused:
return system_colors_[COLOR_HIGHLIGHT];
case kColorId_TableSelectionBackgroundUnfocused:
return system_colors_[IsUsingHighContrastTheme() ?
COLOR_MENUHIGHLIGHT : COLOR_BTNFACE];
case kColorId_TableGroupingIndicatorColor:
return system_colors_[COLOR_GRAYTEXT];
// Results Tables
case kColorId_ResultsTableNormalBackground:
return system_colors_[COLOR_WINDOW];
case kColorId_ResultsTableHoveredBackground:
return color_utils::AlphaBlend(system_colors_[COLOR_HIGHLIGHT],
system_colors_[COLOR_WINDOW], 0x40);
case kColorId_ResultsTableSelectedBackground:
return system_colors_[COLOR_HIGHLIGHT];
case kColorId_ResultsTableNormalText:
case kColorId_ResultsTableHoveredText:
return system_colors_[COLOR_WINDOWTEXT];
case kColorId_ResultsTableSelectedText:
return system_colors_[COLOR_HIGHLIGHTTEXT];
case kColorId_ResultsTableNormalDimmedText:
return color_utils::AlphaBlend(system_colors_[COLOR_WINDOWTEXT],
system_colors_[COLOR_WINDOW], 0x80);
case kColorId_ResultsTableHoveredDimmedText:
return color_utils::AlphaBlend(
system_colors_[COLOR_WINDOWTEXT],
GetSystemColor(kColorId_ResultsTableHoveredBackground), 0x80);
case kColorId_ResultsTableSelectedDimmedText:
return color_utils::AlphaBlend(system_colors_[COLOR_HIGHLIGHTTEXT],
system_colors_[COLOR_HIGHLIGHT], 0x80);
case kColorId_ResultsTableNormalUrl:
return color_utils::GetReadableColor(SkColorSetRGB(0, 128, 0),
system_colors_[COLOR_WINDOW]);
case kColorId_ResultsTableHoveredUrl:
return color_utils::GetReadableColor(
SkColorSetRGB(0, 128, 0),
GetSystemColor(kColorId_ResultsTableHoveredBackground));
case kColorId_ResultsTableSelectedUrl:
return color_utils::GetReadableColor(SkColorSetRGB(0, 128, 0),
system_colors_[COLOR_HIGHLIGHT]);
case kColorId_ResultsTableNormalDivider:
return color_utils::AlphaBlend(system_colors_[COLOR_WINDOWTEXT],
system_colors_[COLOR_WINDOW], 0x34);
case kColorId_ResultsTableHoveredDivider:
return color_utils::AlphaBlend(
system_colors_[COLOR_WINDOWTEXT],
GetSystemColor(kColorId_ResultsTableHoveredBackground), 0x34);
case kColorId_ResultsTableSelectedDivider:
return color_utils::AlphaBlend(system_colors_[COLOR_HIGHLIGHTTEXT],
system_colors_[COLOR_HIGHLIGHT], 0x34);
default:
NOTREACHED();
break;
}
return kInvalidColorIdColor;
}
void NativeThemeWin::PaintIndirect(SkCanvas* canvas,
Part part,
State state,
const gfx::Rect& rect,
const ExtraParams& extra) const {
// TODO(asvitkine): This path is pretty inefficient - for each paint operation
// it creates a new offscreen bitmap Skia canvas. This can
// be sped up by doing it only once per part/state and
// keeping a cache of the resulting bitmaps.
// Create an offscreen canvas that is backed by an HDC.
skia::RefPtr<skia::BitmapPlatformDevice> device = skia::AdoptRef(
skia::BitmapPlatformDevice::Create(
rect.width(), rect.height(), false, NULL));
DCHECK(device);
if (!device)
return;
SkCanvas offscreen_canvas(device.get());
DCHECK(skia::SupportsPlatformPaint(&offscreen_canvas));
// Some of the Windows theme drawing operations do not write correct alpha
// values for fully-opaque pixels; instead the pixels get alpha 0. This is
// especially a problem on Windows XP or when using the Classic theme.
//
// To work-around this, mark all pixels with a placeholder value, to detect
// which pixels get touched by the paint operation. After paint, set any
// pixels that have alpha 0 to opaque and placeholders to fully-transparent.
const SkColor placeholder = SkColorSetARGB(1, 0, 0, 0);
offscreen_canvas.clear(placeholder);
// Offset destination rects to have origin (0,0).
gfx::Rect adjusted_rect(rect.size());
ExtraParams adjusted_extra(extra);
switch (part) {
case kProgressBar:
adjusted_extra.progress_bar.value_rect_x = 0;
adjusted_extra.progress_bar.value_rect_y = 0;
break;
case kScrollbarHorizontalTrack:
case kScrollbarVerticalTrack:
adjusted_extra.scrollbar_track.track_x = 0;
adjusted_extra.scrollbar_track.track_y = 0;
break;
default: break;
}
// Draw the theme controls using existing HDC-drawing code.
PaintDirect(&offscreen_canvas,
part,
state,
adjusted_rect,
adjusted_extra);
// Copy the pixels to a bitmap that has ref-counted pixel storage, which is
// necessary to have when drawing to a SkPicture.
const SkBitmap& hdc_bitmap =
offscreen_canvas.getDevice()->accessBitmap(false);
SkBitmap bitmap;
hdc_bitmap.copyTo(&bitmap, kPMColor_SkColorType);
// Post-process the pixels to fix up the alpha values (see big comment above).
const SkPMColor placeholder_value = SkPreMultiplyColor(placeholder);
const int pixel_count = rect.width() * rect.height();
SkPMColor* pixels = bitmap.getAddr32(0, 0);
for (int i = 0; i < pixel_count; i++) {
if (pixels[i] == placeholder_value) {
// Pixel wasn't touched - make it fully transparent.
pixels[i] = SkPackARGB32(0, 0, 0, 0);
} else if (SkGetPackedA32(pixels[i]) == 0) {
// Pixel was touched but has incorrect alpha of 0, make it fully opaque.
pixels[i] = SkPackARGB32(0xFF,
SkGetPackedR32(pixels[i]),
SkGetPackedG32(pixels[i]),
SkGetPackedB32(pixels[i]));
}
}
// Draw the offscreen bitmap to the destination canvas.
canvas->drawBitmap(bitmap, rect.x(), rect.y());
}
HRESULT NativeThemeWin::GetThemePartSize(ThemeName theme_name,
HDC hdc,
int part_id,
int state_id,
RECT* rect,
int ts,
SIZE* size) const {
HANDLE handle = GetThemeHandle(theme_name);
if (handle && get_theme_part_size_)
return get_theme_part_size_(handle, hdc, part_id, state_id, rect, ts, size);
return E_NOTIMPL;
}
HRESULT NativeThemeWin::PaintButton(HDC hdc,
State state,
const ButtonExtraParams& extra,
int part_id,
int state_id,
RECT* rect) const {
HANDLE handle = GetThemeHandle(BUTTON);
if (handle && draw_theme_)
return draw_theme_(handle, hdc, part_id, state_id, rect, NULL);
// Adjust classic_state based on part, state, and extras.
int classic_state = extra.classic_state;
switch (part_id) {
case BP_CHECKBOX:
classic_state |= DFCS_BUTTONCHECK;
break;
case BP_RADIOBUTTON:
classic_state |= DFCS_BUTTONRADIO;
break;
case BP_PUSHBUTTON:
classic_state |= DFCS_BUTTONPUSH;
break;
default:
NOTREACHED() << "Unknown part_id: " << part_id;
break;
}
switch (state) {
case kDisabled:
classic_state |= DFCS_INACTIVE;
break;
case kPressed:
classic_state |= DFCS_PUSHED;
break;
case kNormal:
case kHovered:
break;
default:
NOTREACHED() << "Unknown state: " << state;
break;
}
if (extra.checked)
classic_state |= DFCS_CHECKED;
// Draw it manually.
// All pressed states have both low bits set, and no other states do.
const bool focused = ((state_id & ETS_FOCUSED) == ETS_FOCUSED);
const bool pressed = ((state_id & PBS_PRESSED) == PBS_PRESSED);
if ((BP_PUSHBUTTON == part_id) && (pressed || focused)) {
// BP_PUSHBUTTON has a focus rect drawn around the outer edge, and the
// button itself is shrunk by 1 pixel.
HBRUSH brush = GetSysColorBrush(COLOR_3DDKSHADOW);
if (brush) {
FrameRect(hdc, rect, brush);
InflateRect(rect, -1, -1);
}
}
DrawFrameControl(hdc, rect, DFC_BUTTON, classic_state);
// Draw the focus rectangle (the dotted line box) only on buttons. For radio
// and checkboxes, we let webkit draw the focus rectangle (orange glow).
if ((BP_PUSHBUTTON == part_id) && focused) {
// The focus rect is inside the button. The exact number of pixels depends
// on whether we're in classic mode or using uxtheme.
if (handle && get_theme_content_rect_) {
get_theme_content_rect_(handle, hdc, part_id, state_id, rect, rect);
} else {
InflateRect(rect, -GetSystemMetrics(SM_CXEDGE),
-GetSystemMetrics(SM_CYEDGE));
}
DrawFocusRect(hdc, rect);
}
// Classic theme doesn't support indeterminate checkboxes. We draw
// a recangle inside a checkbox like IE10 does.
if (part_id == BP_CHECKBOX && extra.indeterminate) {
RECT inner_rect = *rect;
// "4 / 13" is same as IE10 in classic theme.
int padding = (inner_rect.right - inner_rect.left) * 4 / 13;
InflateRect(&inner_rect, -padding, -padding);
int color_index = state == kDisabled ? COLOR_GRAYTEXT : COLOR_WINDOWTEXT;
FillRect(hdc, &inner_rect, GetSysColorBrush(color_index));
}
return S_OK;
}
HRESULT NativeThemeWin::PaintMenuSeparator(
HDC hdc,
const gfx::Rect& rect,
const MenuSeparatorExtraParams& extra) const {
RECT rect_win = rect.ToRECT();
HANDLE handle = GetThemeHandle(MENU);
if (handle && draw_theme_) {
// Delta is needed for non-classic to move separator up slightly.
--rect_win.top;
--rect_win.bottom;
return draw_theme_(handle, hdc, MENU_POPUPSEPARATOR, MPI_NORMAL, &rect_win,
NULL);
}
DrawEdge(hdc, &rect_win, EDGE_ETCHED, BF_TOP);
return S_OK;
}
HRESULT NativeThemeWin::PaintMenuGutter(HDC hdc,
const gfx::Rect& rect) const {
RECT rect_win = rect.ToRECT();
HANDLE handle = GetThemeHandle(MENU);
if (handle && draw_theme_)
return draw_theme_(handle, hdc, MENU_POPUPGUTTER, MPI_NORMAL, &rect_win,
NULL);
return E_NOTIMPL;
}
HRESULT NativeThemeWin::PaintMenuArrow(HDC hdc,
State state,
const gfx::Rect& rect,
const MenuArrowExtraParams& extra)
const {
int state_id = MSM_NORMAL;
if (state == kDisabled)
state_id = MSM_DISABLED;
HANDLE handle = GetThemeHandle(MENU);
RECT rect_win = rect.ToRECT();
if (handle && draw_theme_) {
if (extra.pointing_right) {
return draw_theme_(handle, hdc, MENU_POPUPSUBMENU, state_id, &rect_win,
NULL);
} else {
// There is no way to tell the uxtheme API to draw a left pointing arrow;
// it doesn't have a flag equivalent to DFCS_MENUARROWRIGHT. But they
// are needed for RTL locales on Vista. So use a memory DC and mirror
// the region with GDI's StretchBlt.
gfx::Rect r(rect);
base::win::ScopedCreateDC mem_dc(CreateCompatibleDC(hdc));
base::win::ScopedBitmap mem_bitmap(CreateCompatibleBitmap(hdc, r.width(),
r.height()));
base::win::ScopedSelectObject select_bitmap(mem_dc, mem_bitmap);
// Copy and horizontally mirror the background from hdc into mem_dc. Use
// a negative-width source rect, starting at the rightmost pixel.
StretchBlt(mem_dc, 0, 0, r.width(), r.height(),
hdc, r.right()-1, r.y(), -r.width(), r.height(), SRCCOPY);
// Draw the arrow.
RECT theme_rect = {0, 0, r.width(), r.height()};
HRESULT result = draw_theme_(handle, mem_dc, MENU_POPUPSUBMENU,
state_id, &theme_rect, NULL);
// Copy and mirror the result back into mem_dc.
StretchBlt(hdc, r.x(), r.y(), r.width(), r.height(),
mem_dc, r.width()-1, 0, -r.width(), r.height(), SRCCOPY);
return result;
}
}
// For some reason, Windows uses the name DFCS_MENUARROWRIGHT to indicate a
// left pointing arrow. This makes the following 'if' statement slightly
// counterintuitive.
UINT pfc_state;
if (extra.pointing_right)
pfc_state = DFCS_MENUARROW;
else
pfc_state = DFCS_MENUARROWRIGHT;
return PaintFrameControl(hdc, rect, DFC_MENU, pfc_state, extra.is_selected,
state);
}
HRESULT NativeThemeWin::PaintMenuBackground(HDC hdc,
const gfx::Rect& rect) const {
HANDLE handle = GetThemeHandle(MENU);
RECT rect_win = rect.ToRECT();
if (handle && draw_theme_) {
HRESULT result = draw_theme_(handle, hdc, MENU_POPUPBACKGROUND, 0,
&rect_win, NULL);
FrameRect(hdc, &rect_win, GetSysColorBrush(COLOR_3DSHADOW));
return result;
}
FillRect(hdc, &rect_win, GetSysColorBrush(COLOR_MENU));
DrawEdge(hdc, &rect_win, EDGE_RAISED, BF_RECT);
return S_OK;
}
HRESULT NativeThemeWin::PaintMenuCheck(
HDC hdc,
State state,
const gfx::Rect& rect,
const MenuCheckExtraParams& extra) const {
HANDLE handle = GetThemeHandle(MENU);
int state_id;
if (extra.is_radio) {
state_id = state == kDisabled ? MC_BULLETDISABLED : MC_BULLETNORMAL;
} else {
state_id = state == kDisabled ? MC_CHECKMARKDISABLED : MC_CHECKMARKNORMAL;
}
RECT rect_win = rect.ToRECT();
if (handle && draw_theme_)
return draw_theme_(handle, hdc, MENU_POPUPCHECK, state_id, &rect_win, NULL);
return PaintFrameControl(hdc, rect, DFC_MENU,
extra.is_radio ? DFCS_MENUBULLET : DFCS_MENUCHECK,
extra.is_selected, state);
}
HRESULT NativeThemeWin::PaintMenuCheckBackground(HDC hdc,
State state,
const gfx::Rect& rect) const {
HANDLE handle = GetThemeHandle(MENU);
int state_id = state == kDisabled ? MCB_DISABLED : MCB_NORMAL;
RECT rect_win = rect.ToRECT();
if (handle && draw_theme_)
return draw_theme_(handle, hdc, MENU_POPUPCHECKBACKGROUND, state_id,
&rect_win, NULL);
// Nothing to do for background.
return S_OK;
}
HRESULT NativeThemeWin::PaintMenuItemBackground(
HDC hdc,
State state,
const gfx::Rect& rect,
const MenuItemExtraParams& extra) const {
HANDLE handle = GetThemeHandle(MENU);
RECT rect_win = rect.ToRECT();
int state_id;
switch (state) {
case kNormal:
state_id = MPI_NORMAL;
break;
case kDisabled:
state_id = extra.is_selected ? MPI_DISABLEDHOT : MPI_DISABLED;
break;
case kHovered:
state_id = MPI_HOT;
break;
default:
NOTREACHED() << "Invalid state " << state;
break;
}
if (handle && draw_theme_)
return draw_theme_(handle, hdc, MENU_POPUPITEM, state_id, &rect_win, NULL);
if (extra.is_selected)
FillRect(hdc, &rect_win, GetSysColorBrush(COLOR_HIGHLIGHT));
return S_OK;
}
HRESULT NativeThemeWin::PaintPushButton(HDC hdc,
Part part,
State state,
const gfx::Rect& rect,
const ButtonExtraParams& extra) const {
int state_id;
switch (state) {
case kDisabled:
state_id = PBS_DISABLED;
break;
case kHovered:
state_id = PBS_HOT;
break;
case kNormal:
state_id = extra.is_default ? PBS_DEFAULTED : PBS_NORMAL;
break;
case kPressed:
state_id = PBS_PRESSED;
break;
default:
NOTREACHED() << "Invalid state: " << state;
break;
}
RECT rect_win = rect.ToRECT();
return PaintButton(hdc, state, extra, BP_PUSHBUTTON, state_id, &rect_win);
}
HRESULT NativeThemeWin::PaintRadioButton(HDC hdc,
Part part,
State state,
const gfx::Rect& rect,
const ButtonExtraParams& extra) const {
int state_id;
switch (state) {
case kDisabled:
state_id = extra.checked ? RBS_CHECKEDDISABLED : RBS_UNCHECKEDDISABLED;
break;
case kHovered:
state_id = extra.checked ? RBS_CHECKEDHOT : RBS_UNCHECKEDHOT;
break;
case kNormal:
state_id = extra.checked ? RBS_CHECKEDNORMAL : RBS_UNCHECKEDNORMAL;
break;
case kPressed:
state_id = extra.checked ? RBS_CHECKEDPRESSED : RBS_UNCHECKEDPRESSED;
break;
default:
NOTREACHED() << "Invalid state: " << state;
break;
}
RECT rect_win = rect.ToRECT();
return PaintButton(hdc, state, extra, BP_RADIOBUTTON, state_id, &rect_win);
}
HRESULT NativeThemeWin::PaintCheckbox(HDC hdc,
Part part,
State state,
const gfx::Rect& rect,
const ButtonExtraParams& extra) const {
int state_id;
switch (state) {
case kDisabled:
state_id = extra.checked ? CBS_CHECKEDDISABLED :
extra.indeterminate ? CBS_MIXEDDISABLED :
CBS_UNCHECKEDDISABLED;
break;
case kHovered:
state_id = extra.checked ? CBS_CHECKEDHOT :
extra.indeterminate ? CBS_MIXEDHOT :
CBS_UNCHECKEDHOT;
break;
case kNormal:
state_id = extra.checked ? CBS_CHECKEDNORMAL :
extra.indeterminate ? CBS_MIXEDNORMAL :
CBS_UNCHECKEDNORMAL;
break;
case kPressed:
state_id = extra.checked ? CBS_CHECKEDPRESSED :
extra.indeterminate ? CBS_MIXEDPRESSED :
CBS_UNCHECKEDPRESSED;
break;
default:
NOTREACHED() << "Invalid state: " << state;
break;
}
RECT rect_win = rect.ToRECT();
return PaintButton(hdc, state, extra, BP_CHECKBOX, state_id, &rect_win);
}
HRESULT NativeThemeWin::PaintMenuList(HDC hdc,
State state,
const gfx::Rect& rect,
const MenuListExtraParams& extra) const {
HANDLE handle = GetThemeHandle(MENULIST);
RECT rect_win = rect.ToRECT();
int state_id;
switch (state) {
case kNormal:
state_id = CBXS_NORMAL;
break;
case kDisabled:
state_id = CBXS_DISABLED;
break;
case kHovered:
state_id = CBXS_HOT;
break;
case kPressed:
state_id = CBXS_PRESSED;
break;
default:
NOTREACHED() << "Invalid state " << state;
break;
}
if (handle && draw_theme_)
return draw_theme_(handle, hdc, CP_DROPDOWNBUTTON, state_id, &rect_win,
NULL);
// Draw it manually.
DrawFrameControl(hdc, &rect_win, DFC_SCROLL,
DFCS_SCROLLCOMBOBOX | extra.classic_state);
return S_OK;
}
HRESULT NativeThemeWin::PaintScrollbarArrow(
HDC hdc,
Part part,
State state,
const gfx::Rect& rect,
const ScrollbarArrowExtraParams& extra) const {
static const int state_id_matrix[4][kMaxState] = {
ABS_DOWNDISABLED, ABS_DOWNHOT, ABS_DOWNNORMAL, ABS_DOWNPRESSED,
ABS_LEFTDISABLED, ABS_LEFTHOT, ABS_LEFTNORMAL, ABS_LEFTPRESSED,
ABS_RIGHTDISABLED, ABS_RIGHTHOT, ABS_RIGHTNORMAL, ABS_RIGHTPRESSED,
ABS_UPDISABLED, ABS_UPHOT, ABS_UPNORMAL, ABS_UPPRESSED
};
HANDLE handle = GetThemeHandle(SCROLLBAR);
RECT rect_win = rect.ToRECT();
if (handle && draw_theme_) {
int index = part - kScrollbarDownArrow;
DCHECK(index >=0 && index < 4);
int state_id = state_id_matrix[index][state];
// Hovering means that the cursor is over the scroolbar, but not over the
// specific arrow itself. We don't want to show it "hot" mode, but only
// in "hover" mode.
if (state == kHovered && extra.is_hovering) {
switch (part) {
case kScrollbarDownArrow:
state_id = ABS_DOWNHOVER;
break;
case kScrollbarLeftArrow:
state_id = ABS_LEFTHOVER;
break;
case kScrollbarRightArrow:
state_id = ABS_RIGHTHOVER;
break;
case kScrollbarUpArrow:
state_id = ABS_UPHOVER;
break;
default:
NOTREACHED() << "Invalid part: " << part;
break;
}
}
return PaintScaledTheme(handle, hdc, SBP_ARROWBTN, state_id, rect);
}
int classic_state = DFCS_SCROLLDOWN;
switch (part) {
case kScrollbarDownArrow:
classic_state = DFCS_SCROLLDOWN;
break;
case kScrollbarLeftArrow:
classic_state = DFCS_SCROLLLEFT;
break;
case kScrollbarRightArrow:
classic_state = DFCS_SCROLLRIGHT;
break;
case kScrollbarUpArrow:
classic_state = DFCS_SCROLLUP;
break;
default:
NOTREACHED() << "Invalid part: " << part;
break;
}
switch (state) {
case kDisabled:
classic_state |= DFCS_INACTIVE;
break;
case kHovered:
classic_state |= DFCS_HOT;
break;
case kNormal:
break;
case kPressed:
classic_state |= DFCS_PUSHED;
break;
default:
NOTREACHED() << "Invalid state: " << state;
break;
}
DrawFrameControl(hdc, &rect_win, DFC_SCROLL, classic_state);
return S_OK;
}
HRESULT NativeThemeWin::PaintScrollbarThumb(
HDC hdc,
Part part,
State state,
const gfx::Rect& rect,
const ScrollbarThumbExtraParams& extra) const {
HANDLE handle = GetThemeHandle(SCROLLBAR);
RECT rect_win = rect.ToRECT();
int part_id;
int state_id;
switch (part) {
case NativeTheme::kScrollbarHorizontalThumb:
part_id = SBP_THUMBBTNHORZ;
break;
case NativeTheme::kScrollbarVerticalThumb:
part_id = SBP_THUMBBTNVERT;
break;
case NativeTheme::kScrollbarHorizontalGripper:
part_id = SBP_GRIPPERHORZ;
break;
case NativeTheme::kScrollbarVerticalGripper:
part_id = SBP_GRIPPERVERT;
break;
default:
NOTREACHED() << "Invalid part: " << part;
break;
}
switch (state) {
case kDisabled:
state_id = SCRBS_DISABLED;
break;
case kHovered:
state_id = extra.is_hovering ? SCRBS_HOVER : SCRBS_HOT;
break;
case kNormal:
state_id = SCRBS_NORMAL;
break;
case kPressed:
state_id = SCRBS_PRESSED;
break;
default:
NOTREACHED() << "Invalid state: " << state;
break;
}
if (handle && draw_theme_)
return PaintScaledTheme(handle, hdc, part_id, state_id, rect);
// Draw it manually.
if ((part_id == SBP_THUMBBTNHORZ) || (part_id == SBP_THUMBBTNVERT))
DrawEdge(hdc, &rect_win, EDGE_RAISED, BF_RECT | BF_MIDDLE);
// Classic mode doesn't have a gripper.
return S_OK;
}
HRESULT NativeThemeWin::PaintScrollbarTrack(
SkCanvas* canvas,
HDC hdc,
Part part,
State state,
const gfx::Rect& rect,
const ScrollbarTrackExtraParams& extra) const {
HANDLE handle = GetThemeHandle(SCROLLBAR);
RECT rect_win = rect.ToRECT();
int part_id;
int state_id;
switch (part) {
case NativeTheme::kScrollbarHorizontalTrack:
part_id = extra.is_upper ? SBP_UPPERTRACKHORZ : SBP_LOWERTRACKHORZ;
break;
case NativeTheme::kScrollbarVerticalTrack:
part_id = extra.is_upper ? SBP_UPPERTRACKVERT : SBP_LOWERTRACKVERT;
break;
default:
NOTREACHED() << "Invalid part: " << part;
break;
}
switch (state) {
case kDisabled:
state_id = SCRBS_DISABLED;
break;
case kHovered:
state_id = SCRBS_HOVER;
break;
case kNormal:
state_id = SCRBS_NORMAL;
break;
case kPressed:
state_id = SCRBS_PRESSED;
break;
default:
NOTREACHED() << "Invalid state: " << state;
break;
}
if (handle && draw_theme_)
return draw_theme_(handle, hdc, part_id, state_id, &rect_win, NULL);
// Draw it manually.
if ((system_colors_[COLOR_SCROLLBAR] != system_colors_[COLOR_3DFACE]) &&
(system_colors_[COLOR_SCROLLBAR] != system_colors_[COLOR_WINDOW])) {
FillRect(hdc, &rect_win, reinterpret_cast<HBRUSH>(COLOR_SCROLLBAR + 1));
} else {
SkPaint paint;
RECT align_rect = gfx::Rect(extra.track_x, extra.track_y, extra.track_width,
extra.track_height).ToRECT();
SetCheckerboardShader(&paint, align_rect);
canvas->drawIRect(skia::RECTToSkIRect(rect_win), paint);
}
if (extra.classic_state & DFCS_PUSHED)
InvertRect(hdc, &rect_win);
return S_OK;
}
HRESULT NativeThemeWin::PaintSpinButton(
HDC hdc,
Part part,
State state,
const gfx::Rect& rect,
const InnerSpinButtonExtraParams& extra) const {
HANDLE handle = GetThemeHandle(SPIN);
RECT rect_win = rect.ToRECT();
int part_id = extra.spin_up ? SPNP_UP : SPNP_DOWN;
int state_id;
switch (state) {
case kDisabled:
state_id = extra.spin_up ? UPS_DISABLED : DNS_DISABLED;
break;
case kHovered:
state_id = extra.spin_up ? UPS_HOT : DNS_HOT;
break;
case kNormal:
state_id = extra.spin_up ? UPS_NORMAL : DNS_NORMAL;
break;
case kPressed:
state_id = extra.spin_up ? UPS_PRESSED : DNS_PRESSED;
break;
default:
NOTREACHED() << "Invalid state " << state;
break;
}
if (handle && draw_theme_)
return draw_theme_(handle, hdc, part_id, state_id, &rect_win, NULL);
DrawFrameControl(hdc, &rect_win, DFC_SCROLL, extra.classic_state);
return S_OK;
}
HRESULT NativeThemeWin::PaintTrackbar(
SkCanvas* canvas,
HDC hdc,
Part part,
State state,
const gfx::Rect& rect,
const TrackbarExtraParams& extra) const {
int part_id = part == kTrackbarTrack ? TKP_TRACK : TKP_THUMBBOTTOM;
if (extra.vertical)
part_id = part == kTrackbarTrack ? TKP_TRACKVERT : TKP_THUMBVERT;
int state_id = 0;
switch (state) {
case kDisabled:
state_id = TUS_DISABLED;
break;
case kHovered:
state_id = TUS_HOT;
break;
case kNormal:
state_id = TUS_NORMAL;
break;
case kPressed:
state_id = TUS_PRESSED;
break;
default:
NOTREACHED() << "Invalid state " << state;
break;
}
// Make the channel be 4 px thick in the center of the supplied rect. (4 px
// matches what XP does in various menus; GetThemePartSize() doesn't seem to
// return good values here.)
RECT rect_win = rect.ToRECT();
RECT channel_rect = rect.ToRECT();
const int channel_thickness = 4;
if (part_id == TKP_TRACK) {
channel_rect.top +=
((channel_rect.bottom - channel_rect.top - channel_thickness) / 2);
channel_rect.bottom = channel_rect.top + channel_thickness;
} else if (part_id == TKP_TRACKVERT) {
channel_rect.left +=
((channel_rect.right - channel_rect.left - channel_thickness) / 2);
channel_rect.right = channel_rect.left + channel_thickness;
} // else this isn't actually a channel, so |channel_rect| == |rect|.
HANDLE handle = GetThemeHandle(TRACKBAR);
if (handle && draw_theme_)
return draw_theme_(handle, hdc, part_id, state_id, &channel_rect, NULL);
// Classic mode, draw it manually.
if ((part_id == TKP_TRACK) || (part_id == TKP_TRACKVERT)) {
DrawEdge(hdc, &channel_rect, EDGE_SUNKEN, BF_RECT);
} else if (part_id == TKP_THUMBVERT) {
DrawEdge(hdc, &rect_win, EDGE_RAISED, BF_RECT | BF_SOFT | BF_MIDDLE);
} else {
// Split rect into top and bottom pieces.
RECT top_section = rect.ToRECT();
RECT bottom_section = rect.ToRECT();
top_section.bottom -= ((bottom_section.right - bottom_section.left) / 2);
bottom_section.top = top_section.bottom;
DrawEdge(hdc, &top_section, EDGE_RAISED,
BF_LEFT | BF_TOP | BF_RIGHT | BF_SOFT | BF_MIDDLE | BF_ADJUST);
// Split triangular piece into two diagonals.
RECT& left_half = bottom_section;
RECT right_half = bottom_section;
right_half.left += ((bottom_section.right - bottom_section.left) / 2);
left_half.right = right_half.left;
DrawEdge(hdc, &left_half, EDGE_RAISED,
BF_DIAGONAL_ENDTOPLEFT | BF_SOFT | BF_MIDDLE | BF_ADJUST);
DrawEdge(hdc, &right_half, EDGE_RAISED,
BF_DIAGONAL_ENDBOTTOMLEFT | BF_SOFT | BF_MIDDLE | BF_ADJUST);
// If the button is pressed, draw hatching.
if (extra.classic_state & DFCS_PUSHED) {
SkPaint paint;
SetCheckerboardShader(&paint, rect_win);
// Fill all three pieces with the pattern.
canvas->drawIRect(skia::RECTToSkIRect(top_section), paint);
SkScalar left_triangle_top = SkIntToScalar(left_half.top);
SkScalar left_triangle_right = SkIntToScalar(left_half.right);
SkPath left_triangle;
left_triangle.moveTo(SkIntToScalar(left_half.left), left_triangle_top);
left_triangle.lineTo(left_triangle_right, left_triangle_top);
left_triangle.lineTo(left_triangle_right,
SkIntToScalar(left_half.bottom));
left_triangle.close();
canvas->drawPath(left_triangle, paint);
SkScalar right_triangle_left = SkIntToScalar(right_half.left);
SkScalar right_triangle_top = SkIntToScalar(right_half.top);
SkPath right_triangle;
right_triangle.moveTo(right_triangle_left, right_triangle_top);
right_triangle.lineTo(SkIntToScalar(right_half.right),
right_triangle_top);
right_triangle.lineTo(right_triangle_left,
SkIntToScalar(right_half.bottom));
right_triangle.close();
canvas->drawPath(right_triangle, paint);
}
}
return S_OK;
}
HRESULT NativeThemeWin::PaintProgressBar(
HDC hdc,
const gfx::Rect& rect,
const ProgressBarExtraParams& extra) const {
// There is no documentation about the animation speed, frame-rate, nor
// size of moving overlay of the indeterminate progress bar.
// So we just observed real-world programs and guessed following parameters.
const int kDeteminateOverlayPixelsPerSecond = 300;
const int kDeteminateOverlayWidth = 120;
const int kIndeterminateOverlayPixelsPerSecond = 175;
const int kVistaIndeterminateOverlayWidth = 120;
const int kXPIndeterminateOverlayWidth = 55;
// The thickness of the bar frame inside |value_rect|
const int kXPBarPadding = 3;
RECT bar_rect = rect.ToRECT();
RECT value_rect = gfx::Rect(extra.value_rect_x,
extra.value_rect_y,
extra.value_rect_width,
extra.value_rect_height).ToRECT();
bool pre_vista = base::win::GetVersion() < base::win::VERSION_VISTA;
HANDLE handle = GetThemeHandle(PROGRESS);
if (handle && draw_theme_ && draw_theme_ex_) {
draw_theme_(handle, hdc, PP_BAR, 0, &bar_rect, NULL);
int bar_width = bar_rect.right - bar_rect.left;
if (extra.determinate) {
// TODO(morrita): this RTL guess can be wrong.
// We should pass the direction from WebKit side.
bool is_rtl = (bar_rect.right == value_rect.right &&
bar_rect.left != value_rect.left);
// We should care the direction here because PP_CNUNK painting
// is asymmetric.
DTBGOPTS value_draw_options;
value_draw_options.dwSize = sizeof(DTBGOPTS);
value_draw_options.dwFlags = is_rtl ? DTBG_MIRRORDC : 0;
value_draw_options.rcClip = bar_rect;
if (pre_vista) {
// On XP, progress bar is chunk-style and has no glossy effect.
// We need to shrink destination rect to fit the part inside the bar
// with an appropriate margin.
RECT shrunk_value_rect = InsetRect(&value_rect, kXPBarPadding);
draw_theme_ex_(handle, hdc, PP_CHUNK, 0,
&shrunk_value_rect, &value_draw_options);
} else {
// On Vista or later, the progress bar part has a
// single-block value part. It also has glossy effect.
// And the value part has exactly same height as the bar part
// so we don't need to shrink the rect.
draw_theme_ex_(handle, hdc, PP_FILL, 0,
&value_rect, &value_draw_options);
int dx = ComputeAnimationProgress(bar_width,
kDeteminateOverlayWidth,
kDeteminateOverlayPixelsPerSecond,
extra.animated_seconds);
RECT overlay_rect = value_rect;
overlay_rect.left += dx;
overlay_rect.right = overlay_rect.left + kDeteminateOverlayWidth;
draw_theme_(handle, hdc, PP_MOVEOVERLAY, 0, &overlay_rect, &value_rect);
}
} else {
// A glossy overlay for indeterminate progress bar has small pause
// after each animation. We emulate this by adding an invisible margin
// the animation has to traverse.
int width_with_margin = bar_width + kIndeterminateOverlayPixelsPerSecond;
int overlay_width = pre_vista ?
kXPIndeterminateOverlayWidth : kVistaIndeterminateOverlayWidth;
int dx = ComputeAnimationProgress(width_with_margin,
overlay_width,
kIndeterminateOverlayPixelsPerSecond,
extra.animated_seconds);
RECT overlay_rect = bar_rect;
overlay_rect.left += dx;
overlay_rect.right = overlay_rect.left + overlay_width;
if (pre_vista) {
RECT shrunk_rect = InsetRect(&overlay_rect, kXPBarPadding);
RECT shrunk_bar_rect = InsetRect(&bar_rect, kXPBarPadding);
draw_theme_(handle, hdc, PP_CHUNK, 0, &shrunk_rect, &shrunk_bar_rect);
} else {
draw_theme_(handle, hdc, PP_MOVEOVERLAY, 0, &overlay_rect, &bar_rect);
}
}
return S_OK;
}
HBRUSH bg_brush = GetSysColorBrush(COLOR_BTNFACE);
HBRUSH fg_brush = GetSysColorBrush(COLOR_BTNSHADOW);
FillRect(hdc, &bar_rect, bg_brush);
FillRect(hdc, &value_rect, fg_brush);
DrawEdge(hdc, &bar_rect, EDGE_SUNKEN, BF_RECT | BF_ADJUST);
return S_OK;
}
HRESULT NativeThemeWin::PaintWindowResizeGripper(HDC hdc,
const gfx::Rect& rect) const {
HANDLE handle = GetThemeHandle(STATUS);
RECT rect_win = rect.ToRECT();
if (handle && draw_theme_) {
// Paint the status bar gripper. There doesn't seem to be a
// standard gripper in Windows for the space between
// scrollbars. This is pretty close, but it's supposed to be
// painted over a status bar.
return draw_theme_(handle, hdc, SP_GRIPPER, 0, &rect_win, NULL);
}
// Draw a windows classic scrollbar gripper.
DrawFrameControl(hdc, &rect_win, DFC_SCROLL, DFCS_SCROLLSIZEGRIP);
return S_OK;
}
HRESULT NativeThemeWin::PaintTabPanelBackground(HDC hdc,
const gfx::Rect& rect) const {
HANDLE handle = GetThemeHandle(TAB);
RECT rect_win = rect.ToRECT();
if (handle && draw_theme_)
return draw_theme_(handle, hdc, TABP_BODY, 0, &rect_win, NULL);
// Classic just renders a flat color background.
FillRect(hdc, &rect_win, reinterpret_cast<HBRUSH>(COLOR_3DFACE + 1));
return S_OK;
}
HRESULT NativeThemeWin::PaintTextField(
HDC hdc,
Part part,
State state,
const gfx::Rect& rect,
const TextFieldExtraParams& extra) const {
int part_id = EP_EDITTEXT;
int state_id = ETS_NORMAL;
switch (state) {
case kNormal:
if (extra.is_read_only) {
state_id = ETS_READONLY;
} else if (extra.is_focused) {
state_id = ETS_FOCUSED;
} else {
state_id = ETS_NORMAL;
}
break;
case kHovered:
state_id = ETS_HOT;
break;
case kPressed:
state_id = ETS_SELECTED;
break;
case kDisabled:
state_id = ETS_DISABLED;
break;
default:
NOTREACHED() << "Invalid state: " << state;
break;
}
RECT rect_win = rect.ToRECT();
return PaintTextField(hdc, part_id, state_id, extra.classic_state,
&rect_win,
skia::SkColorToCOLORREF(extra.background_color),
extra.fill_content_area, extra.draw_edges);
}
HRESULT NativeThemeWin::PaintTextField(HDC hdc,
int part_id,
int state_id,
int classic_state,
RECT* rect,
COLORREF color,
bool fill_content_area,
bool draw_edges) const {
// TODO(ojan): http://b/1210017 Figure out how to give the ability to
// exclude individual edges from being drawn.
HANDLE handle = GetThemeHandle(TEXTFIELD);
// TODO(mpcomplete): can we detect if the color is specified by the user,
// and if not, just use the system color?
// CreateSolidBrush() accepts a RGB value but alpha must be 0.
HBRUSH bg_brush = CreateSolidBrush(color);
HRESULT hr;
// DrawThemeBackgroundEx was introduced in XP SP2, so that it's possible
// draw_theme_ex_ is NULL and draw_theme_ is non-null.
if (handle && (draw_theme_ex_ || (draw_theme_ && draw_edges))) {
if (draw_theme_ex_) {
static const DTBGOPTS omit_border_options = {
sizeof(DTBGOPTS),
DTBG_OMITBORDER,
{ 0, 0, 0, 0 }
};
const DTBGOPTS* draw_opts = draw_edges ? NULL : &omit_border_options;
hr = draw_theme_ex_(handle, hdc, part_id, state_id, rect, draw_opts);
} else {
hr = draw_theme_(handle, hdc, part_id, state_id, rect, NULL);
}
// TODO(maruel): Need to be fixed if get_theme_content_rect_ is NULL.
if (fill_content_area && get_theme_content_rect_) {
RECT content_rect;
hr = get_theme_content_rect_(handle, hdc, part_id, state_id, rect,
&content_rect);
FillRect(hdc, &content_rect, bg_brush);
}
} else {
// Draw it manually.
if (draw_edges)
DrawEdge(hdc, rect, EDGE_SUNKEN, BF_RECT | BF_ADJUST);
if (fill_content_area) {
FillRect(hdc, rect, (classic_state & DFCS_INACTIVE) ?
reinterpret_cast<HBRUSH>(COLOR_BTNFACE + 1) : bg_brush);
}
hr = S_OK;
}
DeleteObject(bg_brush);
return hr;
}
HRESULT NativeThemeWin::PaintScaledTheme(HANDLE theme,
HDC hdc,
int part_id,
int state_id,
const gfx::Rect& rect) const {
// Correct the scaling and positioning of sub-components such as scrollbar
// arrows and thumb grippers in the event that the world transform applies
// scaling (e.g. in high-DPI mode).
XFORM save_transform;
if (GetWorldTransform(hdc, &save_transform)) {
float scale = save_transform.eM11;
if (scale != 1 && save_transform.eM12 == 0) {
ModifyWorldTransform(hdc, NULL, MWT_IDENTITY);
gfx::Rect scaled_rect = gfx::ToEnclosedRect(
gfx::ScaleRect(rect, scale));
RECT bounds = gfx::Rect(scaled_rect.x() + save_transform.eDx,
scaled_rect.y() + save_transform.eDy,
scaled_rect.width(),
scaled_rect.height()).ToRECT();
HRESULT result = draw_theme_(theme, hdc, part_id, state_id, &bounds,
NULL);
SetWorldTransform(hdc, &save_transform);
return result;
}
}
RECT bounds = rect.ToRECT();
return draw_theme_(theme, hdc, part_id, state_id, &bounds, NULL);
}
// static
NativeThemeWin::ThemeName NativeThemeWin::GetThemeName(Part part) {
ThemeName name;
switch (part) {
case kCheckbox:
case kRadio:
case kPushButton:
name = BUTTON;
break;
case kInnerSpinButton:
name = SPIN;
break;
case kMenuCheck:
case kMenuPopupGutter:
case kMenuList:
case kMenuPopupArrow:
case kMenuPopupSeparator:
name = MENU;
break;
case kProgressBar:
name = PROGRESS;
break;
case kScrollbarDownArrow:
case kScrollbarLeftArrow:
case kScrollbarRightArrow:
case kScrollbarUpArrow:
case kScrollbarHorizontalThumb:
case kScrollbarVerticalThumb:
case kScrollbarHorizontalTrack:
case kScrollbarVerticalTrack:
name = SCROLLBAR;
break;
case kSliderTrack:
case kSliderThumb:
name = TRACKBAR;
break;
case kTextField:
name = TEXTFIELD;
break;
case kWindowResizeGripper:
name = STATUS;
break;
default:
NOTREACHED() << "Invalid part: " << part;
break;
}
return name;
}
// static
int NativeThemeWin::GetWindowsPart(Part part,
State state,
const ExtraParams& extra) {
int part_id;
switch (part) {
case kCheckbox:
part_id = BP_CHECKBOX;
break;
case kMenuCheck:
part_id = MENU_POPUPCHECK;
break;
case kMenuPopupArrow:
part_id = MENU_POPUPSUBMENU;
break;
case kMenuPopupGutter:
part_id = MENU_POPUPGUTTER;
break;
case kMenuPopupSeparator:
part_id = MENU_POPUPSEPARATOR;
break;
case kPushButton:
part_id = BP_PUSHBUTTON;
break;
case kRadio:
part_id = BP_RADIOBUTTON;
break;
case kWindowResizeGripper:
part_id = SP_GRIPPER;
break;
case kScrollbarDownArrow:
case kScrollbarLeftArrow:
case kScrollbarRightArrow:
case kScrollbarUpArrow:
part_id = SBP_ARROWBTN;
break;
case kScrollbarHorizontalThumb:
part_id = SBP_THUMBBTNHORZ;
break;
case kScrollbarVerticalThumb:
part_id = SBP_THUMBBTNVERT;
break;
default:
NOTREACHED() << "Invalid part: " << part;
break;
}
return part_id;
}
int NativeThemeWin::GetWindowsState(Part part,
State state,
const ExtraParams& extra) {
int state_id;
switch (part) {
case kCheckbox:
switch (state) {
case kNormal:
state_id = CBS_UNCHECKEDNORMAL;
break;
case kHovered:
state_id = CBS_UNCHECKEDHOT;
break;
case kPressed:
state_id = CBS_UNCHECKEDPRESSED;
break;
case kDisabled:
state_id = CBS_UNCHECKEDDISABLED;
break;
default:
NOTREACHED() << "Invalid state: " << state;
break;
}
break;
case kMenuCheck:
switch (state) {
case kNormal:
case kHovered:
case kPressed:
state_id = extra.menu_check.is_radio ? MC_BULLETNORMAL
: MC_CHECKMARKNORMAL;
break;
case kDisabled:
state_id = extra.menu_check.is_radio ? MC_BULLETDISABLED
: MC_CHECKMARKDISABLED;
break;
default:
NOTREACHED() << "Invalid state: " << state;
break;
}
break;
case kMenuPopupArrow:
case kMenuPopupGutter:
case kMenuPopupSeparator:
switch (state) {
case kNormal:
state_id = MBI_NORMAL;
break;
case kHovered:
state_id = MBI_HOT;
break;
case kPressed:
state_id = MBI_PUSHED;
break;
case kDisabled:
state_id = MBI_DISABLED;
break;
default:
NOTREACHED() << "Invalid state: " << state;
break;
}
break;
case kPushButton:
switch (state) {
case kNormal:
state_id = PBS_NORMAL;
break;
case kHovered:
state_id = PBS_HOT;
break;
case kPressed:
state_id = PBS_PRESSED;
break;
case kDisabled:
state_id = PBS_DISABLED;
break;
default:
NOTREACHED() << "Invalid state: " << state;
break;
}
break;
case kRadio:
switch (state) {
case kNormal:
state_id = RBS_UNCHECKEDNORMAL;
break;
case kHovered:
state_id = RBS_UNCHECKEDHOT;
break;
case kPressed:
state_id = RBS_UNCHECKEDPRESSED;
break;
case kDisabled:
state_id = RBS_UNCHECKEDDISABLED;
break;
default:
NOTREACHED() << "Invalid state: " << state;
break;
}
break;
case kWindowResizeGripper:
switch (state) {
case kNormal:
case kHovered:
case kPressed:
case kDisabled:
state_id = 1; // gripper has no windows state
break;
default:
NOTREACHED() << "Invalid state: " << state;
break;
}
break;
case kScrollbarDownArrow:
switch (state) {
case kNormal:
state_id = ABS_DOWNNORMAL;
break;
case kHovered:
// Mimic ScrollbarThemeChromiumWin.cpp in WebKit.
state_id = base::win::GetVersion() < base::win::VERSION_VISTA ?
ABS_DOWNHOT : ABS_DOWNHOVER;
break;
case kPressed:
state_id = ABS_DOWNPRESSED;
break;
case kDisabled:
state_id = ABS_DOWNDISABLED;
break;
default:
NOTREACHED() << "Invalid state: " << state;
break;
}
break;
case kScrollbarLeftArrow:
switch (state) {
case kNormal:
state_id = ABS_LEFTNORMAL;
break;
case kHovered:
// Mimic ScrollbarThemeChromiumWin.cpp in WebKit.
state_id = base::win::GetVersion() < base::win::VERSION_VISTA ?
ABS_LEFTHOT : ABS_LEFTHOVER;
break;
case kPressed:
state_id = ABS_LEFTPRESSED;
break;
case kDisabled:
state_id = ABS_LEFTDISABLED;
break;
default:
NOTREACHED() << "Invalid state: " << state;
break;
}
break;
case kScrollbarRightArrow:
switch (state) {
case kNormal:
state_id = ABS_RIGHTNORMAL;
break;
case kHovered:
// Mimic ScrollbarThemeChromiumWin.cpp in WebKit.
state_id = base::win::GetVersion() < base::win::VERSION_VISTA ?
ABS_RIGHTHOT : ABS_RIGHTHOVER;
break;
case kPressed:
state_id = ABS_RIGHTPRESSED;
break;
case kDisabled:
state_id = ABS_RIGHTDISABLED;
break;
default:
NOTREACHED() << "Invalid state: " << state;
break;
}
break;
case kScrollbarUpArrow:
switch (state) {
case kNormal:
state_id = ABS_UPNORMAL;
break;
case kHovered:
// Mimic ScrollbarThemeChromiumWin.cpp in WebKit.
state_id = base::win::GetVersion() < base::win::VERSION_VISTA ?
ABS_UPHOT : ABS_UPHOVER;
break;
case kPressed:
state_id = ABS_UPPRESSED;
break;
case kDisabled:
state_id = ABS_UPDISABLED;
break;
default:
NOTREACHED() << "Invalid state: " << state;
break;
}
break;
case kScrollbarHorizontalThumb:
case kScrollbarVerticalThumb:
switch (state) {
case kNormal:
state_id = SCRBS_NORMAL;
break;
case kHovered:
// Mimic WebKit's behaviour in ScrollbarThemeChromiumWin.cpp.
state_id = base::win::GetVersion() < base::win::VERSION_VISTA ?
SCRBS_HOT : SCRBS_HOVER;
break;
case kPressed:
state_id = SCRBS_PRESSED;
break;
case kDisabled:
state_id = SCRBS_DISABLED;
break;
default:
NOTREACHED() << "Invalid state: " << state;
break;
}
break;
default:
NOTREACHED() << "Invalid part: " << part;
break;
}
return state_id;
}
HRESULT NativeThemeWin::GetThemeInt(ThemeName theme,
int part_id,
int state_id,
int prop_id,
int *value) const {
HANDLE handle = GetThemeHandle(theme);
if (handle && get_theme_int_)
return get_theme_int_(handle, part_id, state_id, prop_id, value);
return E_NOTIMPL;
}
HRESULT NativeThemeWin::PaintFrameControl(HDC hdc,
const gfx::Rect& rect,
UINT type,
UINT state,
bool is_selected,
State control_state) const {
const int width = rect.width();
const int height = rect.height();
// DrawFrameControl for menu arrow/check wants a monochrome bitmap.
base::win::ScopedBitmap mask_bitmap(CreateBitmap(width, height, 1, 1, NULL));
if (mask_bitmap == NULL)
return E_OUTOFMEMORY;
base::win::ScopedCreateDC bitmap_dc(CreateCompatibleDC(NULL));
base::win::ScopedSelectObject select_bitmap(bitmap_dc, mask_bitmap);
RECT local_rect = { 0, 0, width, height };
DrawFrameControl(bitmap_dc, &local_rect, type, state);
// We're going to use BitBlt with a b&w mask. This results in using the dest
// dc's text color for the black bits in the mask, and the dest dc's
// background color for the white bits in the mask. DrawFrameControl draws the
// check in black, and the background in white.
int bg_color_key;
int text_color_key;
switch (control_state) {
case NativeTheme::kHovered:
bg_color_key = COLOR_HIGHLIGHT;
text_color_key = COLOR_HIGHLIGHTTEXT;
break;
case NativeTheme::kNormal:
bg_color_key = COLOR_MENU;
text_color_key = COLOR_MENUTEXT;
break;
case NativeTheme::kDisabled:
bg_color_key = is_selected ? COLOR_HIGHLIGHT : COLOR_MENU;
text_color_key = COLOR_GRAYTEXT;
break;
default:
NOTREACHED();
bg_color_key = COLOR_MENU;
text_color_key = COLOR_MENUTEXT;
break;
}
COLORREF old_bg_color = SetBkColor(hdc, GetSysColor(bg_color_key));
COLORREF old_text_color = SetTextColor(hdc, GetSysColor(text_color_key));
BitBlt(hdc, rect.x(), rect.y(), width, height, bitmap_dc, 0, 0, SRCCOPY);
SetBkColor(hdc, old_bg_color);
SetTextColor(hdc, old_text_color);
return S_OK;
}
HANDLE NativeThemeWin::GetThemeHandle(ThemeName theme_name) const {
if (!open_theme_ || theme_name < 0 || theme_name >= LAST)
return 0;
if (theme_handles_[theme_name])
return theme_handles_[theme_name];
// Not found, try to load it.
HANDLE handle = 0;
switch (theme_name) {
case BUTTON:
handle = open_theme_(NULL, L"Button");
break;
case LIST:
handle = open_theme_(NULL, L"Listview");
break;
case MENU:
handle = open_theme_(NULL, L"Menu");
break;
case MENULIST:
handle = open_theme_(NULL, L"Combobox");
break;
case SCROLLBAR:
handle = open_theme_(NULL, L"Scrollbar");
break;
case STATUS:
handle = open_theme_(NULL, L"Status");
break;
case TAB:
handle = open_theme_(NULL, L"Tab");
break;
case TEXTFIELD:
handle = open_theme_(NULL, L"Edit");
break;
case TRACKBAR:
handle = open_theme_(NULL, L"Trackbar");
break;
case WINDOW:
handle = open_theme_(NULL, L"Window");
break;
case PROGRESS:
handle = open_theme_(NULL, L"Progress");
break;
case SPIN:
handle = open_theme_(NULL, L"Spin");
break;
default:
NOTREACHED();
}
theme_handles_[theme_name] = handle;
return handle;
}
} // namespace ui