diff options
author | tfarina@chromium.org <tfarina@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2012-05-15 02:07:52 +0000 |
---|---|---|
committer | tfarina@chromium.org <tfarina@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2012-05-15 02:07:52 +0000 |
commit | f78452fc691fd36f44b13fa1443a6c002183cb58 (patch) | |
tree | 5ad6999431d2ac3ff0879394eaf4f7faf09a9c33 /ui/base | |
parent | 2534b2d81a438cbbcfce12853bb43e837dfe8b1b (diff) | |
download | chromium_src-f78452fc691fd36f44b13fa1443a6c002183cb58.zip chromium_src-f78452fc691fd36f44b13fa1443a6c002183cb58.tar.gz chromium_src-f78452fc691fd36f44b13fa1443a6c002183cb58.tar.bz2 |
ui: Move NativeTheme files into ui/base/native_theme/ directory.
BUG=103304,125844
R=ben@chromium.org
TBR=tony@chromium.org
Committed: https://src.chromium.org/viewvc/chrome?view=rev&revision=136996
Review URL: https://chromiumcodereview.appspot.com/10310136
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@137056 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'ui/base')
-rw-r--r-- | ui/base/native_theme/native_theme.cc | 24 | ||||
-rw-r--r-- | ui/base/native_theme/native_theme.h | 273 | ||||
-rw-r--r-- | ui/base/native_theme/native_theme_android.cc | 802 | ||||
-rw-r--r-- | ui/base/native_theme/native_theme_android.h | 176 | ||||
-rw-r--r-- | ui/base/native_theme/native_theme_aura.cc | 295 | ||||
-rw-r--r-- | ui/base/native_theme/native_theme_aura.h | 48 | ||||
-rw-r--r-- | ui/base/native_theme/native_theme_base.cc | 973 | ||||
-rw-r--r-- | ui/base/native_theme/native_theme_base.h | 171 | ||||
-rw-r--r-- | ui/base/native_theme/native_theme_gtk.cc | 87 | ||||
-rw-r--r-- | ui/base/native_theme/native_theme_gtk.h | 27 | ||||
-rw-r--r-- | ui/base/native_theme/native_theme_win.cc | 1779 | ||||
-rw-r--r-- | ui/base/native_theme/native_theme_win.h | 331 | ||||
-rw-r--r-- | ui/base/native_theme/native_theme_win_unittest.cc | 15 |
13 files changed, 5001 insertions, 0 deletions
diff --git a/ui/base/native_theme/native_theme.cc b/ui/base/native_theme/native_theme.cc new file mode 100644 index 0000000..c467d7e --- /dev/null +++ b/ui/base/native_theme/native_theme.cc @@ -0,0 +1,24 @@ +// 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/base/native_theme/native_theme.h" + +namespace ui { + +unsigned int NativeTheme::thumb_inactive_color_ = 0xeaeaea; +unsigned int NativeTheme::thumb_active_color_ = 0xf4f4f4; +unsigned int NativeTheme::track_color_ = 0xd3d3d3; + +void NativeTheme::SetScrollbarColors(unsigned inactive_color, + unsigned active_color, + unsigned track_color) const { + thumb_inactive_color_ = inactive_color; + thumb_active_color_ = active_color; + track_color_ = track_color; +} + +// NativeTheme::instance() is implemented in the platform specific source files, +// such as native_theme_win.cc or native_theme_linux.cc + +} // namespace ui diff --git a/ui/base/native_theme/native_theme.h b/ui/base/native_theme/native_theme.h new file mode 100644 index 0000000..b37b1e7 --- /dev/null +++ b/ui/base/native_theme/native_theme.h @@ -0,0 +1,273 @@ +// 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. + +#ifndef UI_BASE_NATIVE_THEME_NATIVE_THEME_H_ +#define UI_BASE_NATIVE_THEME_NATIVE_THEME_H_ +#pragma once + +#include "third_party/skia/include/core/SkColor.h" +#include "ui/base/ui_export.h" +#include "ui/gfx/native_widget_types.h" + +class SkCanvas; + +namespace gfx { +class Rect; +class Size; +} + +namespace ui { + +// This class supports drawing UI controls (like buttons, text fields, lists, +// comboboxes, etc) that look like the native UI controls of the underlying +// platform, such as Windows or Linux. It also supplies default colors for +// dialog box backgrounds, etc., which are obtained from the system theme where +// possible. +// +// The supported control types are listed in the Part enum. These parts can be +// in any state given by the State enum, where the actual definition of the +// state is part-specific. The supported colors are listed in the ColorId enum. +// +// Some parts require more information than simply the state in order to be +// drawn correctly, and this information is given to the Paint() method via the +// ExtraParams union. Each part that requires more information has its own +// field in the union. +// +// NativeTheme also supports getting the default size of a given part with +// the GetPartSize() method. +class UI_EXPORT NativeTheme { + public: + // The part to be painted / sized. + enum Part { + kCheckbox, + kInnerSpinButton, + kMenuList, + kMenuCheck, + kMenuCheckBackground, + kMenuPopupArrow, + kMenuPopupBackground, + kMenuPopupGutter, + kMenuPopupSeparator, + kMenuItemBackground, + kProgressBar, + kPushButton, + kRadio, + + // The order of the arrow enums is important, do not change without also + // changing the code in platform implementations. + kScrollbarDownArrow, + kScrollbarLeftArrow, + kScrollbarRightArrow, + kScrollbarUpArrow, + + kScrollbarHorizontalThumb, + kScrollbarVerticalThumb, + kScrollbarHorizontalTrack, + kScrollbarVerticalTrack, + kScrollbarHorizontalGripper, + kScrollbarVerticalGripper, + kSliderTrack, + kSliderThumb, + kTabPanelBackground, + kTextField, + kTrackbarThumb, + kTrackbarTrack, + kWindowResizeGripper, + kMaxPart, + }; + + // The state of the part. + enum State { + kDisabled, + kHovered, + kNormal, + kPressed, + kMaxState, + }; + + // Each structure below holds extra information needed when painting a given + // part. + + struct ButtonExtraParams { + bool checked; + bool indeterminate; // Whether the button state is indeterminate. + bool is_default; // Whether the button is default button. + bool has_border; + int classic_state; // Used on Windows when uxtheme is not available. + SkColor background_color; + }; + + struct InnerSpinButtonExtraParams { + bool spin_up; + bool read_only; + int classic_state; // Used on Windows when uxtheme is not available. + }; + + struct MenuArrowExtraParams { + bool pointing_right; + // Used for the disabled state to indicate if the item is both disabled and + // selected. + bool is_selected; + }; + + struct MenuCheckExtraParams { + bool is_radio; + // Used for the disabled state to indicate if the item is both disabled and + // selected. + bool is_selected; + }; + + struct MenuItemExtraParams { + bool is_selected; + }; + + struct MenuListExtraParams { + bool has_border; + bool has_border_radius; + int arrow_x; + int arrow_y; + SkColor background_color; + int classic_state; // Used on Windows when uxtheme is not available. + }; + + struct MenuSeparatorExtraParams { + bool has_gutter; + }; + + struct ProgressBarExtraParams { + double animated_seconds; + bool determinate; + int value_rect_x; + int value_rect_y; + int value_rect_width; + int value_rect_height; + }; + + struct ScrollbarArrowExtraParams { + bool is_hovering; + }; + + struct ScrollbarTrackExtraParams { + bool is_upper; + int track_x; + int track_y; + int track_width; + int track_height; + int classic_state; // Used on Windows when uxtheme is not available. + }; + + struct ScrollbarThumbExtraParams { + bool is_hovering; + }; + + struct SliderExtraParams { + bool vertical; + bool in_drag; + }; + + struct TextFieldExtraParams { + bool is_text_area; + bool is_listbox; + SkColor background_color; + bool is_read_only; + bool is_focused; + bool fill_content_area; + bool draw_edges; + int classic_state; // Used on Windows when uxtheme is not available. + }; + + struct TrackbarExtraParams { + bool vertical; + int classic_state; // Used on Windows when uxtheme is not available. + }; + + union ExtraParams { + ButtonExtraParams button; + InnerSpinButtonExtraParams inner_spin; + MenuArrowExtraParams menu_arrow; + MenuCheckExtraParams menu_check; + MenuItemExtraParams menu_item; + MenuListExtraParams menu_list; + MenuSeparatorExtraParams menu_separator; + ProgressBarExtraParams progress_bar; + ScrollbarArrowExtraParams scrollbar_arrow; + ScrollbarTrackExtraParams scrollbar_track; + ScrollbarThumbExtraParams scrollbar_thumb; + SliderExtraParams slider; + TextFieldExtraParams text_field; + TrackbarExtraParams trackbar; + }; + + // Return the size of the part. + virtual gfx::Size GetPartSize(Part part, + State state, + const ExtraParams& extra) const = 0; + + // Paint the part to the canvas. + virtual void Paint(SkCanvas* canvas, + Part part, + State state, + const gfx::Rect& rect, + const ExtraParams& extra) const = 0; + + // Supports theme specific colors. + void SetScrollbarColors(unsigned inactive_color, + unsigned active_color, + unsigned track_color) const; + + // Colors for GetSystemColor(). + enum ColorId { + // Dialogs + kColorId_DialogBackground, + // FocusableBorder + kColorId_FocusedBorderColor, + kColorId_UnfocusedBorderColor, + // TextButton + kColorId_TextButtonBackgroundColor, + kColorId_TextButtonEnabledColor, + kColorId_TextButtonDisabledColor, + kColorId_TextButtonHighlightColor, + kColorId_TextButtonHoverColor, + // MenuItem + kColorId_EnabledMenuItemForegroundColor, + kColorId_DisabledMenuItemForegroundColor, + kColorId_FocusedMenuItemBackgroundColor, + kColorId_MenuSeparatorColor, + // Label + kColorId_LabelEnabledColor, + kColorId_LabelDisabledColor, + kColorId_LabelBackgroundColor, + // Textfield + kColorId_TextfieldDefaultColor, + kColorId_TextfieldDefaultBackground, + kColorId_TextfieldSelectionColor, + kColorId_TextfieldSelectionBackgroundFocused, + kColorId_TextfieldSelectionBackgroundUnfocused, + // TODO(benrg): move other hardcoded colors here. + }; + + // Return a color from the system theme. + virtual SkColor GetSystemColor(ColorId color_id) const = 0; + + // Returns a shared instance of the native theme. + // The returned object should not be deleted by the caller. This function + // is not thread safe and should only be called from the UI thread. + // Each port of NativeTheme should provide its own implementation of this + // function, returning the port's subclass. + static const NativeTheme* instance(); + + protected: + NativeTheme() {} + virtual ~NativeTheme() {} + + static unsigned int thumb_inactive_color_; + static unsigned int thumb_active_color_; + static unsigned int track_color_; + + DISALLOW_COPY_AND_ASSIGN(NativeTheme); +}; + +} // namespace ui + +#endif // UI_BASE_NATIVE_THEME_NATIVE_THEME_H_ diff --git a/ui/base/native_theme/native_theme_android.cc b/ui/base/native_theme/native_theme_android.cc new file mode 100644 index 0000000..56e0dea --- /dev/null +++ b/ui/base/native_theme/native_theme_android.cc @@ -0,0 +1,802 @@ +// 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/base/native_theme/native_theme_android.h" + +#include <limits> + +#include "base/basictypes.h" +#include "base/logging.h" +#include "grit/gfx_resources.h" +#include "third_party/skia/include/effects/SkGradientShader.h" +#include "ui/base/resource/resource_bundle.h" +#include "ui/gfx/color_utils.h" +#include "ui/gfx/rect.h" +#include "ui/gfx/size.h" + +namespace ui { + +const unsigned int kButtonLength = 14; +const unsigned int kScrollbarWidth = 15; +const unsigned int kThumbInactiveColor = 0xeaeaea; +const unsigned int kTrackColor= 0xd3d3d3; + +// These are the default dimensions of radio buttons and checkboxes. +const int kCheckboxAndRadioWidth = 13; +const int kCheckboxAndRadioHeight = 13; + +// These sizes match the sizes in Chromium Win. +const int kSliderThumbWidth = 11; +const int kSliderThumbHeight = 21; + +const SkColor kSliderTrackBackgroundColor = SkColorSetRGB(0xe3, 0xdd, 0xd8); +const SkColor kSliderThumbLightGrey = SkColorSetRGB(0xf4, 0xf2, 0xef); +const SkColor kSliderThumbDarkGrey = SkColorSetRGB(0xea, 0xe5, 0xe0); +const SkColor kSliderThumbBorderDarkGrey = SkColorSetRGB(0x9d, 0x96, 0x8e); + +// Get lightness adjusted color. +SkColor BrightenColor(const color_utils::HSL& hsl, + SkAlpha alpha, + double lightness_amount) { + color_utils::HSL adjusted = hsl; + adjusted.l += lightness_amount; + if (adjusted.l > 1.0) + adjusted.l = 1.0; + if (adjusted.l < 0.0) + adjusted.l = 0.0; + + return color_utils::HSLToSkColor(adjusted, alpha); +} + +// static +const NativeTheme* NativeTheme::instance() { + return NativeThemeAndroid::instance(); +} + +// static +const NativeThemeAndroid* NativeThemeAndroid::instance() { + CR_DEFINE_STATIC_LOCAL(NativeThemeAndroid, s_native_theme, ()); + return &s_native_theme; +} + +gfx::Size NativeThemeAndroid::GetPartSize(Part part, + State state, + const ExtraParams& extra) const { + switch (part) { + case kScrollbarDownArrow: + case kScrollbarUpArrow: + return gfx::Size(kScrollbarWidth, kButtonLength); + case kScrollbarLeftArrow: + case kScrollbarRightArrow: + return gfx::Size(kButtonLength, kScrollbarWidth); + case kCheckbox: + case kRadio: + return gfx::Size(kCheckboxAndRadioWidth, kCheckboxAndRadioHeight); + case kSliderThumb: + // These sizes match the sizes in Chromium Win. + return gfx::Size(kSliderThumbWidth, kSliderThumbHeight); + case kInnerSpinButton: + return gfx::Size(kScrollbarWidth, 0); + case kPushButton: + case kTextField: + case kMenuList: + case kSliderTrack: + case kProgressBar: + return gfx::Size(); // No default size. + default: + NOTREACHED(); + } + return gfx::Size(); +} + +void NativeThemeAndroid::Paint(SkCanvas* canvas, + Part part, + State state, + const gfx::Rect& rect, + const ExtraParams& extra) const { + switch (part) { + case kScrollbarDownArrow: + case kScrollbarUpArrow: + case kScrollbarLeftArrow: + case kScrollbarRightArrow: + PaintArrowButton(canvas, rect, part, state); + break; + case kCheckbox: + PaintCheckbox(canvas, state, rect, extra.button); + break; + case kRadio: + PaintRadio(canvas, state, rect, extra.button); + break; + case kPushButton: + PaintButton(canvas, state, rect, extra.button); + break; + case kTextField: + PaintTextField(canvas, state, rect, extra.text_field); + break; + case kMenuList: + PaintMenuList(canvas, state, rect, extra.menu_list); + break; + case kSliderTrack: + PaintSliderTrack(canvas, state, rect, extra.slider); + break; + case kSliderThumb: + PaintSliderThumb(canvas, state, rect, extra.slider); + break; + case kInnerSpinButton: + PaintInnerSpinButton(canvas, state, rect, extra.inner_spin); + break; + case kProgressBar: + PaintProgressBar(canvas, state, rect, extra.progress_bar); + break; + default: + NOTREACHED(); + } +} + +SkColor NativeThemeAndroid::GetSystemColor(ColorId color_id) const { + NOTIMPLEMENTED(); + return SK_ColorBLACK; +} + +NativeThemeAndroid::NativeThemeAndroid() { +} + +NativeThemeAndroid::~NativeThemeAndroid() { +} + +void NativeThemeAndroid::PaintArrowButton(SkCanvas* canvas, + const gfx::Rect& rect, + Part direction, + State state) const { + int widthMiddle; + int lengthMiddle; + SkPaint paint; + if (direction == kScrollbarUpArrow || direction == kScrollbarDownArrow) { + widthMiddle = rect.width() / 2 + 1; + lengthMiddle = rect.height() / 2 + 1; + } else { + lengthMiddle = rect.width() / 2 + 1; + widthMiddle = rect.height() / 2 + 1; + } + + // Calculate button color. + SkScalar trackHSV[3]; + SkColorToHSV(kTrackColor, trackHSV); + SkColor buttonColor = SaturateAndBrighten(trackHSV, 0, 0.2); + SkColor backgroundColor = buttonColor; + if (state == kPressed) { + SkScalar buttonHSV[3]; + SkColorToHSV(buttonColor, buttonHSV); + buttonColor = SaturateAndBrighten(buttonHSV, 0, -0.1); + } else if (state == kHovered) { + SkScalar buttonHSV[3]; + SkColorToHSV(buttonColor, buttonHSV); + buttonColor = SaturateAndBrighten(buttonHSV, 0, 0.05); + } + + SkIRect skrect; + skrect.set(rect.x(), rect.y(), rect.x() + rect.width(), rect.y() + + rect.height()); + // Paint the background (the area visible behind the rounded corners). + paint.setColor(backgroundColor); + canvas->drawIRect(skrect, paint); + + // Paint the button's outline and fill the middle + SkPath outline; + switch (direction) { + case kScrollbarUpArrow: + outline.moveTo(rect.x() + 0.5, rect.y() + rect.height() + 0.5); + outline.rLineTo(0, -(rect.height() - 2)); + outline.rLineTo(2, -2); + outline.rLineTo(rect.width() - 5, 0); + outline.rLineTo(2, 2); + outline.rLineTo(0, rect.height() - 2); + break; + case kScrollbarDownArrow: + outline.moveTo(rect.x() + 0.5, rect.y() - 0.5); + outline.rLineTo(0, rect.height() - 2); + outline.rLineTo(2, 2); + outline.rLineTo(rect.width() - 5, 0); + outline.rLineTo(2, -2); + outline.rLineTo(0, -(rect.height() - 2)); + break; + case kScrollbarRightArrow: + outline.moveTo(rect.x() - 0.5, rect.y() + 0.5); + outline.rLineTo(rect.width() - 2, 0); + outline.rLineTo(2, 2); + outline.rLineTo(0, rect.height() - 5); + outline.rLineTo(-2, 2); + outline.rLineTo(-(rect.width() - 2), 0); + break; + case kScrollbarLeftArrow: + outline.moveTo(rect.x() + rect.width() + 0.5, rect.y() + 0.5); + outline.rLineTo(-(rect.width() - 2), 0); + outline.rLineTo(-2, 2); + outline.rLineTo(0, rect.height() - 5); + outline.rLineTo(2, 2); + outline.rLineTo(rect.width() - 2, 0); + break; + default: + break; + } + outline.close(); + + paint.setStyle(SkPaint::kFill_Style); + paint.setColor(buttonColor); + canvas->drawPath(outline, paint); + + paint.setAntiAlias(true); + paint.setStyle(SkPaint::kStroke_Style); + SkScalar thumbHSV[3]; + SkColorToHSV(kThumbInactiveColor, thumbHSV); + paint.setColor(OutlineColor(trackHSV, thumbHSV)); + canvas->drawPath(outline, paint); + + // If the button is disabled or read-only, the arrow is drawn with the + // outline color. + if (state != kDisabled) + paint.setColor(SK_ColorBLACK); + + paint.setAntiAlias(false); + paint.setStyle(SkPaint::kFill_Style); + + SkPath path; + // The constants in this block of code are hand-tailored to produce good + // looking arrows without anti-aliasing. + switch (direction) { + case kScrollbarUpArrow: + path.moveTo(rect.x() + widthMiddle - 4, rect.y() + lengthMiddle + 2); + path.rLineTo(7, 0); + path.rLineTo(-4, -4); + break; + case kScrollbarDownArrow: + path.moveTo(rect.x() + widthMiddle - 4, rect.y() + lengthMiddle - 3); + path.rLineTo(7, 0); + path.rLineTo(-4, 4); + break; + case kScrollbarRightArrow: + path.moveTo(rect.x() + lengthMiddle - 3, rect.y() + widthMiddle - 4); + path.rLineTo(0, 7); + path.rLineTo(4, -4); + break; + case kScrollbarLeftArrow: + path.moveTo(rect.x() + lengthMiddle + 1, rect.y() + widthMiddle - 5); + path.rLineTo(0, 9); + path.rLineTo(-4, -4); + break; + default: + break; + } + path.close(); + + canvas->drawPath(path, paint); +} + +void NativeThemeAndroid::PaintCheckbox(SkCanvas* canvas, + State state, + const gfx::Rect& rect, + const ButtonExtraParams& button) const { + ResourceBundle& rb = ResourceBundle::GetSharedInstance(); + SkBitmap* image = NULL; + if (button.indeterminate) { + image = state == kDisabled ? + rb.GetBitmapNamed(IDR_CHECKBOX_DISABLED_INDETERMINATE) : + rb.GetBitmapNamed(IDR_CHECKBOX_INDETERMINATE); + } else if (button.checked) { + image = state == kDisabled ? + rb.GetBitmapNamed(IDR_CHECKBOX_DISABLED_ON) : + rb.GetBitmapNamed(IDR_CHECKBOX_ON); + } else { + image = state == kDisabled ? + rb.GetBitmapNamed(IDR_CHECKBOX_DISABLED_OFF) : + rb.GetBitmapNamed(IDR_CHECKBOX_OFF); + } + + gfx::Rect bounds = rect.Center(gfx::Size(image->width(), image->height())); + DrawBitmapInt(canvas, *image, 0, 0, image->width(), image->height(), + bounds.x(), bounds.y(), bounds.width(), bounds.height()); +} + +void NativeThemeAndroid::PaintRadio(SkCanvas* canvas, + State state, + const gfx::Rect& rect, + const ButtonExtraParams& button) const { + ResourceBundle& rb = ResourceBundle::GetSharedInstance(); + SkBitmap* image = NULL; + if (state == kDisabled) { + image = button.checked ? + rb.GetBitmapNamed(IDR_RADIO_DISABLED_ON) : + rb.GetBitmapNamed(IDR_RADIO_DISABLED_OFF); + } else { + image = button.checked ? + rb.GetBitmapNamed(IDR_RADIO_ON) : + rb.GetBitmapNamed(IDR_RADIO_OFF); + } + + gfx::Rect bounds = rect.Center(gfx::Size(image->width(), image->height())); + DrawBitmapInt(canvas, *image, 0, 0, image->width(), image->height(), + bounds.x(), bounds.y(), bounds.width(), bounds.height()); +} + +void NativeThemeAndroid::PaintButton(SkCanvas* canvas, + State state, + const gfx::Rect& rect, + const ButtonExtraParams& button) const { + SkPaint paint; + SkRect skrect; + int kRight = rect.right(); + int kBottom = rect.bottom(); + SkColor base_color = button.background_color; + + color_utils::HSL base_hsl; + color_utils::SkColorToHSL(base_color, &base_hsl); + + // Our standard gradient is from 0xdd to 0xf8. This is the amount of + // increased luminance between those values. + SkColor light_color(BrightenColor(base_hsl, SkColorGetA(base_color), 0.105)); + + // If the button is too small, fallback to drawing a single, solid color + if (rect.width() < 5 || rect.height() < 5) { + paint.setColor(base_color); + skrect.set(rect.x(), rect.y(), kRight, kBottom); + canvas->drawRect(skrect, paint); + return; + } + + if (button.has_border) { + int kBorderAlpha = state == kHovered ? 0x80 : 0x55; + paint.setARGB(kBorderAlpha, 0, 0, 0); + canvas->drawLine(rect.x() + 1, rect.y(), kRight - 1, rect.y(), paint); + canvas->drawLine(kRight - 1, rect.y() + 1, kRight - 1, kBottom - 1, paint); + canvas->drawLine(rect.x() + 1, kBottom - 1, kRight - 1, kBottom - 1, paint); + canvas->drawLine(rect.x(), rect.y() + 1, rect.x(), kBottom - 1, paint); + } + + paint.setColor(SK_ColorBLACK); + int kLightEnd = state == kPressed ? 1 : 0; + int kDarkEnd = !kLightEnd; + SkPoint gradient_bounds[2]; + gradient_bounds[kLightEnd].iset(rect.x(), rect.y()); + gradient_bounds[kDarkEnd].iset(rect.x(), kBottom - 1); + SkColor colors[2]; + colors[0] = light_color; + colors[1] = base_color; + + SkShader* shader = SkGradientShader::CreateLinear( + gradient_bounds, colors, NULL, 2, SkShader::kClamp_TileMode, NULL); + paint.setStyle(SkPaint::kFill_Style); + paint.setShader(shader); + shader->unref(); + + if (button.has_border) { + skrect.set(rect.x() + 1, rect.y() + 1, kRight - 1, kBottom - 1); + } else { + skrect.set(rect.x(), rect.y(), kRight, kBottom); + } + canvas->drawRect(skrect, paint); + paint.setShader(NULL); + + if (button.has_border) { + paint.setColor(BrightenColor(base_hsl, SkColorGetA(base_color), -0.0588)); + canvas->drawPoint(rect.x() + 1, rect.y() + 1, paint); + canvas->drawPoint(kRight - 2, rect.y() + 1, paint); + canvas->drawPoint(rect.x() + 1, kBottom - 2, paint); + canvas->drawPoint(kRight - 2, kBottom - 2, paint); + } +} + +void NativeThemeAndroid::PaintTextField( + SkCanvas* canvas, + State state, + const gfx::Rect& rect, + const TextFieldExtraParams& text) const { + // The following drawing code simulates the user-agent css border for + // text area and text input so that we do not break layout tests. Once we + // have decided the desired looks, we should update the code here and + // the layout test expectations. + SkRect bounds; + bounds.set(rect.x(), rect.y(), rect.right() - 1, rect.bottom() - 1); + + SkPaint fill_paint; + fill_paint.setStyle(SkPaint::kFill_Style); + fill_paint.setColor(text.background_color); + canvas->drawRect(bounds, fill_paint); + + if (text.is_text_area) { + // Draw text area border: 1px solid black + SkPaint stroke_paint; + fill_paint.setStyle(SkPaint::kStroke_Style); + fill_paint.setColor(SK_ColorBLACK); + canvas->drawRect(bounds, fill_paint); + } else { + // Draw text input and listbox inset border + // Text Input: 2px inset #eee + // Listbox: 1px inset #808080 + SkColor kLightColor = text.is_listbox ? + SkColorSetRGB(0x80, 0x80, 0x80) : SkColorSetRGB(0xee, 0xee, 0xee); + SkColor kDarkColor = text.is_listbox ? + SkColorSetRGB(0x2c, 0x2c, 0x2c) : SkColorSetRGB(0x9a, 0x9a, 0x9a); + int kBorderWidth = text.is_listbox ? 1 : 2; + + SkPaint dark_paint; + dark_paint.setAntiAlias(true); + dark_paint.setStyle(SkPaint::kFill_Style); + dark_paint.setColor(kDarkColor); + + SkPaint light_paint; + light_paint.setAntiAlias(true); + light_paint.setStyle(SkPaint::kFill_Style); + light_paint.setColor(kLightColor); + + int left = rect.x(); + int top = rect.y(); + int right = rect.right(); + int bottom = rect.bottom(); + + SkPath path; + path.incReserve(4); + + // Top + path.moveTo(SkIntToScalar(left), SkIntToScalar(top)); + path.lineTo(SkIntToScalar(left + kBorderWidth), + SkIntToScalar(top + kBorderWidth)); + path.lineTo(SkIntToScalar(right - kBorderWidth), + SkIntToScalar(top + kBorderWidth)); + path.lineTo(SkIntToScalar(right), SkIntToScalar(top)); + canvas->drawPath(path, dark_paint); + + // Bottom + path.reset(); + path.moveTo(SkIntToScalar(left + kBorderWidth), + SkIntToScalar(bottom - kBorderWidth)); + path.lineTo(SkIntToScalar(left), SkIntToScalar(bottom)); + path.lineTo(SkIntToScalar(right), SkIntToScalar(bottom)); + path.lineTo(SkIntToScalar(right - kBorderWidth), + SkIntToScalar(bottom - kBorderWidth)); + canvas->drawPath(path, light_paint); + + // Left + path.reset(); + path.moveTo(SkIntToScalar(left), SkIntToScalar(top)); + path.lineTo(SkIntToScalar(left), SkIntToScalar(bottom)); + path.lineTo(SkIntToScalar(left + kBorderWidth), + SkIntToScalar(bottom - kBorderWidth)); + path.lineTo(SkIntToScalar(left + kBorderWidth), + SkIntToScalar(top + kBorderWidth)); + canvas->drawPath(path, dark_paint); + + // Right + path.reset(); + path.moveTo(SkIntToScalar(right - kBorderWidth), + SkIntToScalar(top + kBorderWidth)); + path.lineTo(SkIntToScalar(right - kBorderWidth), SkIntToScalar(bottom)); + path.lineTo(SkIntToScalar(right), SkIntToScalar(bottom)); + path.lineTo(SkIntToScalar(right), SkIntToScalar(top)); + canvas->drawPath(path, light_paint); + } +} + +void NativeThemeAndroid::PaintMenuList( + SkCanvas* canvas, + State state, + const gfx::Rect& rect, + const MenuListExtraParams& menu_list) const { + // If a border radius is specified, we let the WebCore paint the background + // and the border of the control. + if (!menu_list.has_border_radius) { + ButtonExtraParams button = { 0 }; + button.background_color = menu_list.background_color; + button.has_border = menu_list.has_border; + PaintButton(canvas, state, rect, button); + } + + SkPaint paint; + paint.setColor(SK_ColorBLACK); + paint.setAntiAlias(true); + paint.setStyle(SkPaint::kFill_Style); + + SkPath path; + path.moveTo(menu_list.arrow_x, menu_list.arrow_y - 3); + path.rLineTo(6, 0); + path.rLineTo(-3, 6); + path.close(); + canvas->drawPath(path, paint); +} + +void NativeThemeAndroid::PaintSliderTrack( + SkCanvas* canvas, + State state, + const gfx::Rect& rect, + const SliderExtraParams& slider) const { + int kMidX = rect.x() + rect.width() / 2; + int kMidY = rect.y() + rect.height() / 2; + + SkPaint paint; + paint.setColor(kSliderTrackBackgroundColor); + + SkRect skrect; + if (slider.vertical) { + skrect.set(std::max(rect.x(), kMidX - 2), + rect.y(), + std::min(rect.right(), kMidX + 2), + rect.bottom()); + } else { + skrect.set(rect.x(), + std::max(rect.y(), kMidY - 2), + rect.right(), + std::min(rect.bottom(), kMidY + 2)); + } + canvas->drawRect(skrect, paint); +} + +void NativeThemeAndroid::PaintSliderThumb( + SkCanvas* canvas, + State state, + const gfx::Rect& rect, + const SliderExtraParams& slider) const { + bool hovered = (state == kHovered) || slider.in_drag; + int kMidX = rect.x() + rect.width() / 2; + int kMidY = rect.y() + rect.height() / 2; + + SkPaint paint; + paint.setColor(hovered ? SK_ColorWHITE : kSliderThumbLightGrey); + + SkIRect skrect; + if (slider.vertical) + skrect.set(rect.x(), rect.y(), kMidX + 1, rect.bottom()); + else + skrect.set(rect.x(), rect.y(), rect.right(), kMidY + 1); + + canvas->drawIRect(skrect, paint); + + paint.setColor(hovered ? kSliderThumbLightGrey : kSliderThumbDarkGrey); + + if (slider.vertical) + skrect.set(kMidX + 1, rect.y(), rect.right(), rect.bottom()); + else + skrect.set(rect.x(), kMidY + 1, rect.right(), rect.bottom()); + + canvas->drawIRect(skrect, paint); + + paint.setColor(kSliderThumbBorderDarkGrey); + DrawBox(canvas, rect, paint); + + if (rect.height() > 10 && rect.width() > 10) { + DrawHorizLine(canvas, kMidX - 2, kMidX + 2, kMidY, paint); + DrawHorizLine(canvas, kMidX - 2, kMidX + 2, kMidY - 3, paint); + DrawHorizLine(canvas, kMidX - 2, kMidX + 2, kMidY + 3, paint); + } +} + +void NativeThemeAndroid::PaintInnerSpinButton( + SkCanvas* canvas, + State state, + const gfx::Rect& rect, + const InnerSpinButtonExtraParams& spin_button) const { + if (spin_button.read_only) + state = kDisabled; + + State north_state = state; + State south_state = state; + if (spin_button.spin_up) + south_state = south_state != kDisabled ? kNormal : kDisabled; + else + north_state = north_state != kDisabled ? kNormal : kDisabled; + + gfx::Rect half = rect; + half.set_height(rect.height() / 2); + PaintArrowButton(canvas, half, kScrollbarUpArrow, north_state); + + half.set_y(rect.y() + rect.height() / 2); + PaintArrowButton(canvas, half, kScrollbarDownArrow, south_state); +} + +void NativeThemeAndroid::PaintProgressBar( + SkCanvas* canvas, + State state, + const gfx::Rect& rect, + const ProgressBarExtraParams& progress_bar) const { + ResourceBundle& rb = ResourceBundle::GetSharedInstance(); + SkBitmap* bar_image = rb.GetBitmapNamed(IDR_PROGRESS_BAR); + SkBitmap* left_border_image = rb.GetBitmapNamed(IDR_PROGRESS_BORDER_LEFT); + SkBitmap* right_border_image = rb.GetBitmapNamed(IDR_PROGRESS_BORDER_RIGHT); + + double tile_scale = static_cast<double>(rect.height()) / + bar_image->height(); + + int new_tile_width = static_cast<int>(bar_image->width() * tile_scale); + double tile_scale_x = static_cast<double>(new_tile_width) / + bar_image->width(); + + DrawTiledImage(canvas, *bar_image, 0, 0, tile_scale_x, tile_scale, + rect.x(), rect.y(), rect.width(), rect.height()); + + if (progress_bar.value_rect_width) { + SkBitmap* value_image = rb.GetBitmapNamed(IDR_PROGRESS_VALUE); + + new_tile_width = static_cast<int>(value_image->width() * tile_scale); + tile_scale_x = static_cast<double>(new_tile_width) / + value_image->width(); + + DrawTiledImage(canvas, *value_image, 0, 0, tile_scale_x, tile_scale, + progress_bar.value_rect_x, + progress_bar.value_rect_y, + progress_bar.value_rect_width, + progress_bar.value_rect_height); + } + + int dest_left_border_width = static_cast<int>(left_border_image->width() * + tile_scale); + SkRect dest_rect; + dest_rect.iset(rect.x(), rect.y(), rect.x() + dest_left_border_width, + rect.bottom()); + canvas->drawBitmapRect(*left_border_image, NULL, dest_rect); + + int dest_right_border_width = static_cast<int>(right_border_image->width() * + tile_scale); + dest_rect.iset(rect.right() - dest_right_border_width, rect.y(), rect.right(), + rect.bottom()); + canvas->drawBitmapRect(*right_border_image, NULL, dest_rect); +} + +bool NativeThemeAndroid::IntersectsClipRectInt(SkCanvas* canvas, + int x, + int y, + int w, + int h) const { + SkRect clip; + return canvas->getClipBounds(&clip) && + clip.intersect(SkIntToScalar(x), SkIntToScalar(y), SkIntToScalar(x + w), + SkIntToScalar(y + h)); +} + +void NativeThemeAndroid::DrawBitmapInt(SkCanvas* canvas, + const SkBitmap& bitmap, + int src_x, + int src_y, + int src_w, + int src_h, + int dest_x, + int dest_y, + int dest_w, + int dest_h) const { + DLOG_ASSERT(src_x + src_w < std::numeric_limits<int16_t>::max() && + src_y + src_h < std::numeric_limits<int16_t>::max()); + if (src_w <= 0 || src_h <= 0 || dest_w <= 0 || dest_h <= 0) { + NOTREACHED() << "Attempting to draw bitmap to/from an empty rect!"; + return; + } + + if (!IntersectsClipRectInt(canvas, dest_x, dest_y, dest_w, dest_h)) + return; + + SkRect dest_rect = { SkIntToScalar(dest_x), + SkIntToScalar(dest_y), + SkIntToScalar(dest_x + dest_w), + SkIntToScalar(dest_y + dest_h) }; + + if (src_w == dest_w && src_h == dest_h) { + // Workaround for apparent bug in Skia that causes image to occasionally + // shift. + SkIRect src_rect = { src_x, src_y, src_x + src_w, src_y + src_h }; + canvas->drawBitmapRect(bitmap, &src_rect, dest_rect); + return; + } + + // Make a bitmap shader that contains the bitmap we want to draw. This is + // basically what SkCanvas.drawBitmap does internally, but it gives us + // more control over quality and will use the mipmap in the source image if + // it has one, whereas drawBitmap won't. + SkShader* shader = SkShader::CreateBitmapShader(bitmap, + SkShader::kRepeat_TileMode, + SkShader::kRepeat_TileMode); + SkMatrix shader_scale; + shader_scale.setScale(SkFloatToScalar(static_cast<float>(dest_w) / src_w), + SkFloatToScalar(static_cast<float>(dest_h) / src_h)); + shader_scale.preTranslate(SkIntToScalar(-src_x), SkIntToScalar(-src_y)); + shader_scale.postTranslate(SkIntToScalar(dest_x), SkIntToScalar(dest_y)); + shader->setLocalMatrix(shader_scale); + + // The rect will be filled by the bitmap. + SkPaint p; + p.setFilterBitmap(true); + p.setShader(shader); + shader->unref(); + canvas->drawRect(dest_rect, p); +} + +void NativeThemeAndroid::DrawTiledImage(SkCanvas* canvas, + const SkBitmap& bitmap, + int src_x, + int src_y, + double tile_scale_x, + double tile_scale_y, + int dest_x, + int dest_y, + int w, + int h) const { + SkShader* shader = SkShader::CreateBitmapShader(bitmap, + SkShader::kRepeat_TileMode, + SkShader::kRepeat_TileMode); + if (tile_scale_x != 1.0 || tile_scale_y != 1.0) { + SkMatrix shader_scale; + shader_scale.setScale(SkDoubleToScalar(tile_scale_x), + SkDoubleToScalar(tile_scale_y)); + shader->setLocalMatrix(shader_scale); + } + + SkPaint paint; + paint.setShader(shader); + paint.setXfermodeMode(SkXfermode::kSrcOver_Mode); + + // CreateBitmapShader returns a Shader with a reference count of one, we + // need to unref after paint takes ownership of the shader. + shader->unref(); + canvas->save(); + canvas->translate(SkIntToScalar(dest_x - src_x), + SkIntToScalar(dest_y - src_y)); + canvas->clipRect(SkRect::MakeXYWH(src_x, src_y, w, h)); + canvas->drawPaint(paint); + canvas->restore(); +} + +SkColor NativeThemeAndroid::SaturateAndBrighten( + SkScalar* hsv, + SkScalar saturate_amount, + SkScalar brighten_amount) const { + SkScalar color[3]; + color[0] = hsv[0]; + color[1] = Clamp(hsv[1] + saturate_amount, 0.0, 1.0); + color[2] = Clamp(hsv[2] + brighten_amount, 0.0, 1.0); + return SkHSVToColor(color); +} + +SkScalar NativeThemeAndroid::Clamp(SkScalar value, + SkScalar min, + SkScalar max) const { + return std::min(std::max(value, min), max); +} + +void NativeThemeAndroid::DrawVertLine(SkCanvas* canvas, + int x, + int y1, + int y2, + const SkPaint& paint) const { + SkIRect skrect; + skrect.set(x, y1, x + 1, y2 + 1); + canvas->drawIRect(skrect, paint); +} + +void NativeThemeAndroid::DrawHorizLine(SkCanvas* canvas, + int x1, + int x2, + int y, + const SkPaint& paint) const { + SkIRect skrect; + skrect.set(x1, y, x2 + 1, y + 1); + canvas->drawIRect(skrect, paint); +} + +void NativeThemeAndroid::DrawBox(SkCanvas* canvas, + const gfx::Rect& rect, + const SkPaint& paint) const { + int right = rect.x() + rect.width() - 1; + int bottom = rect.y() + rect.height() - 1; + DrawHorizLine(canvas, rect.x(), right, rect.y(), paint); + DrawVertLine(canvas, right, rect.y(), bottom, paint); + DrawHorizLine(canvas, rect.x(), right, bottom, paint); + DrawVertLine(canvas, rect.x(), rect.y(), bottom, paint); +} + +SkColor NativeThemeAndroid::OutlineColor(SkScalar* hsv1, SkScalar* hsv2) const { + SkScalar min_diff = Clamp((hsv1[1] + hsv2[1]) * 1.2, 0.28, 0.5); + SkScalar diff = Clamp(fabs(hsv1[2] - hsv2[2]) / 2, min_diff, 0.5); + + if (hsv1[2] + hsv2[2] > 1.0) + diff = -diff; + + return SaturateAndBrighten(hsv2, -0.2, diff); +} + +} // namespace ui diff --git a/ui/base/native_theme/native_theme_android.h b/ui/base/native_theme/native_theme_android.h new file mode 100644 index 0000000..601a4f8 --- /dev/null +++ b/ui/base/native_theme/native_theme_android.h @@ -0,0 +1,176 @@ +// 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. + +#ifndef UI_BASE_NATIVE_THEME_NATIVE_THEME_ANDROID_H_ +#define UI_BASE_NATIVE_THEME_NATIVE_THEME_ANDROID_H_ +#pragma once + +#include "base/basictypes.h" +#include "base/compiler_specific.h" +#include "skia/ext/platform_canvas.h" +#include "ui/base/native_theme/native_theme.h" + +namespace gfx { +class Rect; +class Size; +} + +namespace ui { + +// Android theming API. +class NativeThemeAndroid : public NativeTheme { + public: + // Gets our singleton instance. + static const NativeThemeAndroid* instance(); + + // Return the size of the part. + virtual gfx::Size GetPartSize(Part part, + State state, + const ExtraParams& extra) const OVERRIDE; + + // Paint the part to the canvas. + virtual void Paint(SkCanvas* canvas, + Part part, + State state, + const gfx::Rect& rect, + const ExtraParams& extra) const OVERRIDE; + + virtual SkColor GetSystemColor(ColorId color_id) const OVERRIDE; + + private: + NativeThemeAndroid(); + virtual ~NativeThemeAndroid(); + + // Draw the arrow. Used by scrollbar and inner spin button. + void PaintArrowButton(SkCanvas* gc, + const gfx::Rect& rect, + Part direction, + State state) const; + + // Draw the checkbox. + void PaintCheckbox(SkCanvas* canvas, + State state, + const gfx::Rect& rect, + const ButtonExtraParams& button) const; + + // Draw the radio. + void PaintRadio(SkCanvas* canvas, + State state, + const gfx::Rect& rect, + const ButtonExtraParams& button) const; + + // Draw the push button. + void PaintButton(SkCanvas* canvas, + State state, + const gfx::Rect& rect, + const ButtonExtraParams& button) const; + + // Draw the text field. + void PaintTextField(SkCanvas* canvas, + State state, + const gfx::Rect& rect, + const TextFieldExtraParams& text) const; + + // Draw the menu list. + void PaintMenuList(SkCanvas* canvas, + State state, + const gfx::Rect& rect, + const MenuListExtraParams& menu_list) const; + + // Draw the slider track. + void PaintSliderTrack(SkCanvas* canvas, + State state, + const gfx::Rect& rect, + const SliderExtraParams& slider) const; + + // Draw the slider thumb. + void PaintSliderThumb(SkCanvas* canvas, + State state, + const gfx::Rect& rect, + const SliderExtraParams& slider) const; + + // Draw the inner spin button. + void PaintInnerSpinButton( + SkCanvas* canvas, + State state, + const gfx::Rect& rect, + const InnerSpinButtonExtraParams& spin_button) const; + + // Draw the progress bar. + void PaintProgressBar( + SkCanvas* canvas, + State state, + const gfx::Rect& rect, + const ProgressBarExtraParams& progress_bar) const; + + // Return true if there is intersection between the |canvas| and the given + // rectangle. + bool IntersectsClipRectInt(SkCanvas* canvas, + int x, + int y, + int w, + int h) const; + + // Draw the dest rectangle with the given bitmap which might be scaled if its + // size is not same as target rectangle. + void DrawBitmapInt(SkCanvas* canvas, + const SkBitmap& bitmap, + int src_x, + int src_y, + int src_w, + int src_h, + int dest_x, + int dest_y, + int dest_w, + int dest_h) const; + + // Draw the target rectangle with the |bitmap| accroding the given + // |tile_scale_x| and |tile_scale_y| + void DrawTiledImage(SkCanvas* canvas, + const SkBitmap& bitmap, + int src_x, + int src_y, + double tile_scale_x, + double tile_scale_y, + int dest_x, + int dest_y, + int w, + int h) const; + + // Return a new color which comes from the |hsv| by adjusting saturate and + // brighten according |saturate_amount| and |brighten_amount| + SkColor SaturateAndBrighten(SkScalar* hsv, + SkScalar saturate_amount, + SkScalar brighten_amount) const; + + void DrawVertLine(SkCanvas* canvas, + int x, + int y1, + int y2, + const SkPaint& paint) const; + + void DrawHorizLine(SkCanvas* canvas, + int x1, + int x2, + int y, + const SkPaint& paint) const; + + void DrawBox(SkCanvas* canvas, + const gfx::Rect& rect, + const SkPaint& paint) const; + + // Return the |value| if it is between |min| and |max|, otherwise the |min| + // or |max| is returned dependent on which is mostly near the |value|. + SkScalar Clamp(SkScalar value, SkScalar min, SkScalar max) const; + + // Used to return the color of scrollbar based on the color of thumb and + // track. + SkColor OutlineColor(SkScalar* hsv1, SkScalar* hsv2) const; + + DISALLOW_COPY_AND_ASSIGN(NativeThemeAndroid); +}; + +} // namespace ui + +#endif // UI_BASE_NATIVE_THEME_NATIVE_THEME_ANDROID_H_ diff --git a/ui/base/native_theme/native_theme_aura.cc b/ui/base/native_theme/native_theme_aura.cc new file mode 100644 index 0000000..56336c0 --- /dev/null +++ b/ui/base/native_theme/native_theme_aura.cc @@ -0,0 +1,295 @@ +// 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/base/native_theme/native_theme_aura.h" + +#include "base/logging.h" +#include "grit/gfx_resources.h" +#include "ui/base/resource/resource_bundle.h" +#include "ui/gfx/rect.h" +#include "ui/gfx/size.h" +#include "ui/gfx/skbitmap_operations.h" + +namespace { + +const SkColor kMenuBackgroundColor = SK_ColorWHITE; + +// Theme colors returned by GetSystemColor(). +const SkColor kInvalidColorIdColor = SkColorSetRGB(255, 0, 128); +// Dialogs: +const SkColor kDialogBackgroundColor = SK_ColorWHITE; +// FocusableBorder: +const SkColor kFocusedBorderColor = SkColorSetRGB(0x4D, 0x90, 0xFE); +const SkColor kUnfocusedBorderColor = SkColorSetRGB(0xD9, 0xD9, 0xD9); +// TextButton: +const SkColor kTextButtonBackgroundColor = SkColorSetRGB(0xDE, 0xDE, 0xDE); +const SkColor kTextButtonEnabledColor = SkColorSetRGB(0x22, 0x22, 0x22); +const SkColor kTextButtonDisabledColor = SkColorSetRGB(0x99, 0x99, 0x99); +const SkColor kTextButtonHighlightColor = SkColorSetRGB(0, 0, 0); +const SkColor kTextButtonHoverColor = kTextButtonEnabledColor; +// MenuItem: +const SkColor kEnabledMenuItemForegroundColor = kTextButtonEnabledColor; +const SkColor kDisabledMenuItemForegroundColor = kTextButtonDisabledColor; +const SkColor kFocusedMenuItemBackgroundColor = SkColorSetRGB(0xF1, 0xF1, 0xF1); +const SkColor kMenuSeparatorColor = SkColorSetRGB(0xDA, 0xDA, 0xDA); +// Label: +const SkColor kLabelEnabledColor = kTextButtonEnabledColor; +const SkColor kLabelDisabledColor = kTextButtonDisabledColor; +const SkColor kLabelBackgroundColor = SK_ColorWHITE; +// Textfield: +const SkColor kTextfieldDefaultColor = SK_ColorBLACK; +const SkColor kTextfieldDefaultBackground = SK_ColorWHITE; +const SkColor kTextfieldSelectionBackgroundFocused = + SkColorSetARGB(0x54, 0x60, 0xA8, 0xEB); +const SkColor kTextfieldSelectionBackgroundUnfocused = SK_ColorLTGRAY; +const SkColor kTextfieldSelectionColor = + color_utils::AlphaBlend(SK_ColorBLACK, + kTextfieldSelectionBackgroundFocused, 0xdd); + +} // namespace + +namespace ui { + +// static +const NativeTheme* NativeTheme::instance() { + return NativeThemeAura::instance(); +} + +// static +const NativeThemeAura* NativeThemeAura::instance() { + CR_DEFINE_STATIC_LOCAL(NativeThemeAura, s_native_theme, ()); + return &s_native_theme; +} + +NativeThemeAura::NativeThemeAura() { + // We don't draw scrollbar buttons. + set_scrollbar_button_length(0); +} + +NativeThemeAura::~NativeThemeAura() { +} + +SkColor NativeThemeAura::GetSystemColor(ColorId color_id) const { + // This implementation returns hardcoded colors. + switch (color_id) { + + // Dialogs + case kColorId_DialogBackground: + return kDialogBackgroundColor; + + // FocusableBorder + case kColorId_FocusedBorderColor: + return kFocusedBorderColor; + case kColorId_UnfocusedBorderColor: + return kUnfocusedBorderColor; + + // TextButton + case kColorId_TextButtonBackgroundColor: + return kTextButtonBackgroundColor; + case kColorId_TextButtonEnabledColor: + return kTextButtonEnabledColor; + case kColorId_TextButtonDisabledColor: + return kTextButtonDisabledColor; + case kColorId_TextButtonHighlightColor: + return kTextButtonHighlightColor; + case kColorId_TextButtonHoverColor: + return kTextButtonHoverColor; + + // MenuItem + case kColorId_EnabledMenuItemForegroundColor: + return kEnabledMenuItemForegroundColor; + case kColorId_DisabledMenuItemForegroundColor: + return kDisabledMenuItemForegroundColor; + case kColorId_FocusedMenuItemBackgroundColor: + return kFocusedMenuItemBackgroundColor; + case kColorId_MenuSeparatorColor: + return kMenuSeparatorColor; + + // Label + case kColorId_LabelEnabledColor: + return kLabelEnabledColor; + case kColorId_LabelDisabledColor: + return kLabelDisabledColor; + case kColorId_LabelBackgroundColor: + return kLabelBackgroundColor; + + // Textfield + case kColorId_TextfieldDefaultColor: + return kTextfieldDefaultColor; + case kColorId_TextfieldDefaultBackground: + return kTextfieldDefaultBackground; + case kColorId_TextfieldSelectionColor: + return kTextfieldSelectionColor; + case kColorId_TextfieldSelectionBackgroundFocused: + return kTextfieldSelectionBackgroundFocused; + case kColorId_TextfieldSelectionBackgroundUnfocused: + return kTextfieldSelectionBackgroundUnfocused; + + default: + NOTREACHED() << "Invalid color_id: " << color_id; + break; + } + + return kInvalidColorIdColor; +} + +void NativeThemeAura::PaintMenuPopupBackground(SkCanvas* canvas, + const gfx::Size& size) const { + canvas->drawColor(kMenuBackgroundColor, SkXfermode::kSrc_Mode); +} + +void NativeThemeAura::PaintScrollbarTrack( + SkCanvas* canvas, + Part part, + State state, + const ScrollbarTrackExtraParams& extra_params, + const gfx::Rect& rect) const { + ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance(); + if (part == kScrollbarVerticalTrack) { + int center_offset = 0; + int center_height = rect.height(); + + if (rect.y() == extra_params.track_y) { + // TODO(derat): Honor |state| instead of only using the highlighted images + // after updating WebKit so we can draw the entire track in one go instead + // of as two separate pieces: otherwise, only the portion of the scrollbar + // that the mouse is over gets the highlighted state. + SkBitmap* top = rb.GetBitmapNamed(IDR_SCROLL_BASE_VERTICAL_TOP_H); + DrawTiledImage(canvas, *top, + 0, 0, 1.0, 1.0, + rect.x(), rect.y(), top->width(), top->height()); + center_offset += top->height(); + center_height -= top->height(); + } + + if (rect.y() + rect.height() == + extra_params.track_y + extra_params.track_height) { + SkBitmap* bottom = rb.GetBitmapNamed(IDR_SCROLL_BASE_VERTICAL_BOTTOM_H); + DrawTiledImage(canvas, *bottom, + 0, 0, 1.0, 1.0, + rect.x(), rect.y() + rect.height() - bottom->height(), + bottom->width(), bottom->height()); + center_height -= bottom->height(); + } + + if (center_height > 0) { + SkBitmap* center = rb.GetBitmapNamed(IDR_SCROLL_BASE_VERTICAL_CENTER_H); + DrawTiledImage(canvas, *center, + 0, 0, 1.0, 1.0, + rect.x(), rect.y() + center_offset, + center->width(), center_height); + } + } else { + int center_offset = 0; + int center_width = rect.width(); + + if (rect.x() == extra_params.track_x) { + SkBitmap* left = rb.GetBitmapNamed(IDR_SCROLL_BASE_HORIZONTAL_LEFT_H); + DrawTiledImage(canvas, *left, + 0, 0, 1.0, 1.0, + rect.x(), rect.y(), left->width(), left->height()); + center_offset += left->width(); + center_width -= left->width(); + } + + if (rect.x() + rect.width() == + extra_params.track_x + extra_params.track_width) { + SkBitmap* right = rb.GetBitmapNamed(IDR_SCROLL_BASE_HORIZONTAL_RIGHT_H); + DrawTiledImage(canvas, *right, + 0, 0, 1.0, 1.0, + rect.x() + rect.width() - right->width(), rect.y(), + right->width(), right->height()); + center_width -= right->width(); + } + + if (center_width > 0) { + SkBitmap* center = rb.GetBitmapNamed(IDR_SCROLL_BASE_HORIZONTAL_CENTER_H); + DrawTiledImage(canvas, *center, + 0, 0, 1.0, 1.0, + rect.x() + center_offset, rect.y(), + center_width, center->height()); + } + } +} + +void NativeThemeAura::PaintArrowButton(SkCanvas* canvas, + const gfx::Rect& rect, + Part part, + State state) const { + DCHECK(rect.IsEmpty()); +} + +void NativeThemeAura::PaintScrollbarThumb(SkCanvas* canvas, + Part part, + State state, + const gfx::Rect& rect) const { + ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance(); + if (part == kScrollbarVerticalThumb) { + int top_resource_id = + state == kHovered ? IDR_SCROLL_THUMB_VERTICAL_TOP_H : + state == kPressed ? IDR_SCROLL_THUMB_VERTICAL_TOP_P : + IDR_SCROLL_THUMB_VERTICAL_TOP; + SkBitmap* top = rb.GetBitmapNamed(top_resource_id); + DrawTiledImage(canvas, *top, + 0, 0, 1.0, 1.0, + rect.x(), rect.y(), top->width(), top->height()); + + int bottom_resource_id = + state == kHovered ? IDR_SCROLL_THUMB_VERTICAL_BOTTOM_H : + state == kPressed ? IDR_SCROLL_THUMB_VERTICAL_BOTTOM_P : + IDR_SCROLL_THUMB_VERTICAL_BOTTOM; + SkBitmap* bottom = rb.GetBitmapNamed(bottom_resource_id); + DrawTiledImage(canvas, *bottom, + 0, 0, 1.0, 1.0, + rect.x(), rect.y() + rect.height() - bottom->height(), + bottom->width(), bottom->height()); + + if (rect.height() > top->height() + bottom->height()) { + int center_resource_id = + state == kHovered ? IDR_SCROLL_THUMB_VERTICAL_CENTER_H : + state == kPressed ? IDR_SCROLL_THUMB_VERTICAL_CENTER_P : + IDR_SCROLL_THUMB_VERTICAL_CENTER; + SkBitmap* center = rb.GetBitmapNamed(center_resource_id); + DrawTiledImage(canvas, *center, + 0, 0, 1.0, 1.0, + rect.x(), rect.y() + top->height(), + center->width(), + rect.height() - top->height() - bottom->height()); + } + } else { + int left_resource_id = + state == kHovered ? IDR_SCROLL_THUMB_HORIZONTAL_LEFT_H : + state == kPressed ? IDR_SCROLL_THUMB_HORIZONTAL_LEFT_P : + IDR_SCROLL_THUMB_HORIZONTAL_LEFT; + SkBitmap* left = rb.GetBitmapNamed(left_resource_id); + DrawTiledImage(canvas, *left, + 0, 0, 1.0, 1.0, + rect.x(), rect.y(), left->width(), left->height()); + + int right_resource_id = + state == kHovered ? IDR_SCROLL_THUMB_HORIZONTAL_RIGHT_H : + state == kPressed ? IDR_SCROLL_THUMB_HORIZONTAL_RIGHT_P : + IDR_SCROLL_THUMB_HORIZONTAL_RIGHT; + SkBitmap* right = rb.GetBitmapNamed(right_resource_id); + DrawTiledImage(canvas, *right, + 0, 0, 1.0, 1.0, + rect.x() + rect.width() - right->width(), rect.y(), + right->width(), right->height()); + + if (rect.width() > left->width() + right->width()) { + int center_resource_id = + state == kHovered ? IDR_SCROLL_THUMB_HORIZONTAL_CENTER_H : + state == kPressed ? IDR_SCROLL_THUMB_HORIZONTAL_CENTER_P : + IDR_SCROLL_THUMB_HORIZONTAL_CENTER; + SkBitmap* center = rb.GetBitmapNamed(center_resource_id); + DrawTiledImage(canvas, *center, + 0, 0, 1.0, 1.0, + rect.x() + left->width(), rect.y(), + rect.width() - left->width() - right->width(), + center->height()); + } + } +} + +} // namespace ui diff --git a/ui/base/native_theme/native_theme_aura.h b/ui/base/native_theme/native_theme_aura.h new file mode 100644 index 0000000..077a42a --- /dev/null +++ b/ui/base/native_theme/native_theme_aura.h @@ -0,0 +1,48 @@ +// 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. + +#ifndef UI_BASE_NATIVE_THEME_NATIVE_THEME_AURA_H_ +#define UI_BASE_NATIVE_THEME_NATIVE_THEME_AURA_H_ +#pragma once + +#include "base/basictypes.h" +#include "base/compiler_specific.h" +#include "ui/base/native_theme/native_theme_base.h" + +namespace ui { + +// Aura implementation of native theme support. +class NativeThemeAura : public NativeThemeBase { + public: + static const NativeThemeAura* instance(); + + private: + NativeThemeAura(); + virtual ~NativeThemeAura(); + + // Overridden from NativeThemeBase: + virtual SkColor GetSystemColor(ColorId color_id) const OVERRIDE; + virtual void PaintMenuPopupBackground(SkCanvas* canvas, + const gfx::Size& size) const OVERRIDE; + virtual void PaintScrollbarTrack( + SkCanvas* canvas, + Part part, + State state, + const ScrollbarTrackExtraParams& extra_params, + const gfx::Rect& rect) const OVERRIDE; + virtual void PaintArrowButton(SkCanvas* canvas, + const gfx::Rect& rect, + Part direction, + State state) const OVERRIDE; + virtual void PaintScrollbarThumb(SkCanvas* canvas, + Part part, + State state, + const gfx::Rect& rect) const OVERRIDE; + + DISALLOW_COPY_AND_ASSIGN(NativeThemeAura); +}; + +} // namespace ui + +#endif // UI_BASE_NATIVE_THEME_NATIVE_THEME_AURA_H_ diff --git a/ui/base/native_theme/native_theme_base.cc b/ui/base/native_theme/native_theme_base.cc new file mode 100644 index 0000000..4f86670 --- /dev/null +++ b/ui/base/native_theme/native_theme_base.cc @@ -0,0 +1,973 @@ +// 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/base/native_theme/native_theme_base.h" + +#include <limits> + +#include "base/logging.h" +#include "grit/gfx_resources.h" +#include "third_party/skia/include/effects/SkGradientShader.h" +#include "ui/base/resource/resource_bundle.h" +#include "ui/gfx/color_utils.h" +#include "ui/gfx/rect.h" +#include "ui/gfx/size.h" + +namespace { + +// These are the default dimensions of radio buttons and checkboxes. +const int kCheckboxAndRadioWidth = 13; +const int kCheckboxAndRadioHeight = 13; + +// These sizes match the sizes in Chromium Win. +const int kSliderThumbWidth = 11; +const int kSliderThumbHeight = 21; + +const SkColor kSliderTrackBackgroundColor = + SkColorSetRGB(0xe3, 0xdd, 0xd8); +const SkColor kSliderThumbLightGrey = SkColorSetRGB(0xf4, 0xf2, 0xef); +const SkColor kSliderThumbDarkGrey = SkColorSetRGB(0xea, 0xe5, 0xe0); +const SkColor kSliderThumbBorderDarkGrey = + SkColorSetRGB(0x9d, 0x96, 0x8e); + +const SkColor kMenuPopupBackgroundColor = SkColorSetRGB(210, 225, 246); + +const unsigned int kDefaultScrollbarWidth = 15; +const unsigned int kDefaultScrollbarButtonLength = 14; + +// Get lightness adjusted color. +SkColor BrightenColor(const color_utils::HSL& hsl, SkAlpha alpha, + double lightness_amount) { + color_utils::HSL adjusted = hsl; + adjusted.l += lightness_amount; + if (adjusted.l > 1.0) + adjusted.l = 1.0; + if (adjusted.l < 0.0) + adjusted.l = 0.0; + + return color_utils::HSLToSkColor(adjusted, alpha); +} + +} // namespace + +namespace ui { + +gfx::Size NativeThemeBase::GetPartSize(Part part, + State state, + const ExtraParams& extra) const { + switch (part) { + // Please keep these in the order of NativeTheme::Part. + case kCheckbox: + return gfx::Size(kCheckboxAndRadioWidth, kCheckboxAndRadioHeight); + case kInnerSpinButton: + return gfx::Size(scrollbar_width_, 0); + case kMenuList: + return gfx::Size(); // No default size. + case kMenuCheck: + case kMenuCheckBackground: + case kMenuPopupArrow: + NOTIMPLEMENTED(); + break; + case kMenuPopupBackground: + return gfx::Size(); // No default size. + case kMenuPopupGutter: + case kMenuPopupSeparator: + NOTIMPLEMENTED(); + break; + case kMenuItemBackground: + case kProgressBar: + case kPushButton: + return gfx::Size(); // No default size. + case kRadio: + return gfx::Size(kCheckboxAndRadioWidth, kCheckboxAndRadioHeight); + case kScrollbarDownArrow: + case kScrollbarUpArrow: + return gfx::Size(scrollbar_width_, scrollbar_button_length_); + case kScrollbarLeftArrow: + case kScrollbarRightArrow: + return gfx::Size(scrollbar_button_length_, scrollbar_width_); + case kScrollbarHorizontalThumb: + // This matches Firefox on Linux. + return gfx::Size(2 * scrollbar_width_, scrollbar_width_); + case kScrollbarVerticalThumb: + // This matches Firefox on Linux. + return gfx::Size(scrollbar_width_, 2 * scrollbar_width_); + case kScrollbarHorizontalTrack: + return gfx::Size(0, scrollbar_width_); + case kScrollbarVerticalTrack: + return gfx::Size(scrollbar_width_, 0); + case kScrollbarHorizontalGripper: + case kScrollbarVerticalGripper: + NOTIMPLEMENTED(); + break; + case kSliderTrack: + return gfx::Size(); // No default size. + case kSliderThumb: + // These sizes match the sizes in Chromium Win. + return gfx::Size(kSliderThumbWidth, kSliderThumbHeight); + case kTabPanelBackground: + NOTIMPLEMENTED(); + break; + case kTextField: + return gfx::Size(); // No default size. + case kTrackbarThumb: + case kTrackbarTrack: + case kWindowResizeGripper: + NOTIMPLEMENTED(); + break; + default: + NOTREACHED() << "Unknown theme part: " << part; + break; + } + return gfx::Size(); +} + +void NativeThemeBase::Paint(SkCanvas* canvas, + Part part, + State state, + const gfx::Rect& rect, + const ExtraParams& extra) const { + switch (part) { + // Please keep these in the order of NativeTheme::Part. + case kCheckbox: + PaintCheckbox(canvas, state, rect, extra.button); + break; + case kInnerSpinButton: + PaintInnerSpinButton(canvas, state, rect, extra.inner_spin); + break; + case kMenuList: + PaintMenuList(canvas, state, rect, extra.menu_list); + break; + case kMenuCheck: + case kMenuCheckBackground: + case kMenuPopupArrow: + NOTIMPLEMENTED(); + break; + case kMenuPopupBackground: + PaintMenuPopupBackground(canvas, rect.size()); + break; + case kMenuPopupGutter: + case kMenuPopupSeparator: + NOTIMPLEMENTED(); + break; + case kMenuItemBackground: + PaintMenuItemBackground(canvas, state, rect, extra.menu_list); + break; + case kProgressBar: + PaintProgressBar(canvas, state, rect, extra.progress_bar); + break; + case kPushButton: + PaintButton(canvas, state, rect, extra.button); + break; + case kRadio: + PaintRadio(canvas, state, rect, extra.button); + break; + case kScrollbarDownArrow: + case kScrollbarUpArrow: + case kScrollbarLeftArrow: + case kScrollbarRightArrow: + PaintArrowButton(canvas, rect, part, state); + break; + case kScrollbarHorizontalThumb: + case kScrollbarVerticalThumb: + PaintScrollbarThumb(canvas, part, state, rect); + break; + case kScrollbarHorizontalTrack: + case kScrollbarVerticalTrack: + PaintScrollbarTrack(canvas, part, state, extra.scrollbar_track, rect); + break; + case kScrollbarHorizontalGripper: + case kScrollbarVerticalGripper: + NOTIMPLEMENTED(); + break; + case kSliderTrack: + PaintSliderTrack(canvas, state, rect, extra.slider); + break; + case kSliderThumb: + PaintSliderThumb(canvas, state, rect, extra.slider); + break; + case kTabPanelBackground: + NOTIMPLEMENTED(); + break; + case kTextField: + PaintTextField(canvas, state, rect, extra.text_field); + break; + case kTrackbarThumb: + case kTrackbarTrack: + case kWindowResizeGripper: + NOTIMPLEMENTED(); + break; + default: + NOTREACHED() << "Unknown theme part: " << part; + break; + } +} + +NativeThemeBase::NativeThemeBase() + : scrollbar_width_(kDefaultScrollbarWidth), + scrollbar_button_length_(kDefaultScrollbarButtonLength) { +} + +NativeThemeBase::~NativeThemeBase() { +} + +void NativeThemeBase::PaintArrowButton( + SkCanvas* canvas, + const gfx::Rect& rect, Part direction, State state) const { + int widthMiddle, lengthMiddle; + SkPaint paint; + if (direction == kScrollbarUpArrow || direction == kScrollbarDownArrow) { + widthMiddle = rect.width() / 2 + 1; + lengthMiddle = rect.height() / 2 + 1; + } else { + lengthMiddle = rect.width() / 2 + 1; + widthMiddle = rect.height() / 2 + 1; + } + + // Calculate button color. + SkScalar trackHSV[3]; + SkColorToHSV(track_color_, trackHSV); + SkColor buttonColor = SaturateAndBrighten(trackHSV, 0, 0.2f); + SkColor backgroundColor = buttonColor; + if (state == kPressed) { + SkScalar buttonHSV[3]; + SkColorToHSV(buttonColor, buttonHSV); + buttonColor = SaturateAndBrighten(buttonHSV, 0, -0.1f); + } else if (state == kHovered) { + SkScalar buttonHSV[3]; + SkColorToHSV(buttonColor, buttonHSV); + buttonColor = SaturateAndBrighten(buttonHSV, 0, 0.05f); + } + + SkIRect skrect; + skrect.set(rect.x(), rect.y(), rect.x() + rect.width(), rect.y() + + rect.height()); + // Paint the background (the area visible behind the rounded corners). + paint.setColor(backgroundColor); + canvas->drawIRect(skrect, paint); + + // Paint the button's outline and fill the middle + SkPath outline; + switch (direction) { + case kScrollbarUpArrow: + outline.moveTo(rect.x() + 0.5, rect.y() + rect.height() + 0.5); + outline.rLineTo(0, -(rect.height() - 2)); + outline.rLineTo(2, -2); + outline.rLineTo(rect.width() - 5, 0); + outline.rLineTo(2, 2); + outline.rLineTo(0, rect.height() - 2); + break; + case kScrollbarDownArrow: + outline.moveTo(rect.x() + 0.5, rect.y() - 0.5); + outline.rLineTo(0, rect.height() - 2); + outline.rLineTo(2, 2); + outline.rLineTo(rect.width() - 5, 0); + outline.rLineTo(2, -2); + outline.rLineTo(0, -(rect.height() - 2)); + break; + case kScrollbarRightArrow: + outline.moveTo(rect.x() - 0.5, rect.y() + 0.5); + outline.rLineTo(rect.width() - 2, 0); + outline.rLineTo(2, 2); + outline.rLineTo(0, rect.height() - 5); + outline.rLineTo(-2, 2); + outline.rLineTo(-(rect.width() - 2), 0); + break; + case kScrollbarLeftArrow: + outline.moveTo(rect.x() + rect.width() + 0.5, rect.y() + 0.5); + outline.rLineTo(-(rect.width() - 2), 0); + outline.rLineTo(-2, 2); + outline.rLineTo(0, rect.height() - 5); + outline.rLineTo(2, 2); + outline.rLineTo(rect.width() - 2, 0); + break; + default: + break; + } + outline.close(); + + paint.setStyle(SkPaint::kFill_Style); + paint.setColor(buttonColor); + canvas->drawPath(outline, paint); + + paint.setAntiAlias(true); + paint.setStyle(SkPaint::kStroke_Style); + SkScalar thumbHSV[3]; + SkColorToHSV(thumb_inactive_color_, thumbHSV); + paint.setColor(OutlineColor(trackHSV, thumbHSV)); + canvas->drawPath(outline, paint); + + // If the button is disabled or read-only, the arrow is drawn with the + // outline color. + if (state != kDisabled) + paint.setColor(SK_ColorBLACK); + + paint.setAntiAlias(false); + paint.setStyle(SkPaint::kFill_Style); + + SkPath path; + // The constants in this block of code are hand-tailored to produce good + // looking arrows without anti-aliasing. + switch (direction) { + case kScrollbarUpArrow: + path.moveTo(rect.x() + widthMiddle - 4, rect.y() + lengthMiddle + 2); + path.rLineTo(7, 0); + path.rLineTo(-4, -4); + break; + case kScrollbarDownArrow: + path.moveTo(rect.x() + widthMiddle - 4, rect.y() + lengthMiddle - 3); + path.rLineTo(7, 0); + path.rLineTo(-4, 4); + break; + case kScrollbarRightArrow: + path.moveTo(rect.x() + lengthMiddle - 3, rect.y() + widthMiddle - 4); + path.rLineTo(0, 7); + path.rLineTo(4, -4); + break; + case kScrollbarLeftArrow: + path.moveTo(rect.x() + lengthMiddle + 1, rect.y() + widthMiddle - 5); + path.rLineTo(0, 9); + path.rLineTo(-4, -4); + break; + default: + break; + } + path.close(); + + canvas->drawPath(path, paint); +} + +void NativeThemeBase::PaintScrollbarTrack(SkCanvas* canvas, + Part part, + State state, + const ScrollbarTrackExtraParams& extra_params, + const gfx::Rect& rect) const { + SkPaint paint; + SkIRect skrect; + + skrect.set(rect.x(), rect.y(), rect.right(), rect.bottom()); + SkScalar track_hsv[3]; + SkColorToHSV(track_color_, track_hsv); + paint.setColor(SaturateAndBrighten(track_hsv, 0, 0)); + canvas->drawIRect(skrect, paint); + + SkScalar thumb_hsv[3]; + SkColorToHSV(thumb_inactive_color_, thumb_hsv); + + paint.setColor(OutlineColor(track_hsv, thumb_hsv)); + DrawBox(canvas, rect, paint); +} + +void NativeThemeBase::PaintScrollbarThumb(SkCanvas* canvas, + Part part, + State state, + const gfx::Rect& rect) const { + const bool hovered = state == kHovered; + const int midx = rect.x() + rect.width() / 2; + const int midy = rect.y() + rect.height() / 2; + const bool vertical = part == kScrollbarVerticalThumb; + + SkScalar thumb[3]; + SkColorToHSV(hovered ? thumb_active_color_ : thumb_inactive_color_, thumb); + + SkPaint paint; + paint.setColor(SaturateAndBrighten(thumb, 0, 0.02f)); + + SkIRect skrect; + if (vertical) + skrect.set(rect.x(), rect.y(), midx + 1, rect.y() + rect.height()); + else + skrect.set(rect.x(), rect.y(), rect.x() + rect.width(), midy + 1); + + canvas->drawIRect(skrect, paint); + + paint.setColor(SaturateAndBrighten(thumb, 0, -0.02f)); + + if (vertical) { + skrect.set( + midx + 1, rect.y(), rect.x() + rect.width(), rect.y() + rect.height()); + } else { + skrect.set( + rect.x(), midy + 1, rect.x() + rect.width(), rect.y() + rect.height()); + } + + canvas->drawIRect(skrect, paint); + + SkScalar track[3]; + SkColorToHSV(track_color_, track); + paint.setColor(OutlineColor(track, thumb)); + DrawBox(canvas, rect, paint); + + if (rect.height() > 10 && rect.width() > 10) { + const int grippy_half_width = 2; + const int inter_grippy_offset = 3; + if (vertical) { + DrawHorizLine(canvas, + midx - grippy_half_width, + midx + grippy_half_width, + midy - inter_grippy_offset, + paint); + DrawHorizLine(canvas, + midx - grippy_half_width, + midx + grippy_half_width, + midy, + paint); + DrawHorizLine(canvas, + midx - grippy_half_width, + midx + grippy_half_width, + midy + inter_grippy_offset, + paint); + } else { + DrawVertLine(canvas, + midx - inter_grippy_offset, + midy - grippy_half_width, + midy + grippy_half_width, + paint); + DrawVertLine(canvas, + midx, + midy - grippy_half_width, + midy + grippy_half_width, + paint); + DrawVertLine(canvas, + midx + inter_grippy_offset, + midy - grippy_half_width, + midy + grippy_half_width, + paint); + } + } +} + +void NativeThemeBase::PaintCheckbox(SkCanvas* canvas, + State state, + const gfx::Rect& rect, + const ButtonExtraParams& button) const { + ResourceBundle& rb = ResourceBundle::GetSharedInstance(); + SkBitmap* image = NULL; + if (button.indeterminate) { + image = state == kDisabled ? + rb.GetBitmapNamed(IDR_CHECKBOX_DISABLED_INDETERMINATE) : + rb.GetBitmapNamed(IDR_CHECKBOX_INDETERMINATE); + } else if (button.checked) { + image = state == kDisabled ? + rb.GetBitmapNamed(IDR_CHECKBOX_DISABLED_ON) : + rb.GetBitmapNamed(IDR_CHECKBOX_ON); + } else { + image = state == kDisabled ? + rb.GetBitmapNamed(IDR_CHECKBOX_DISABLED_OFF) : + rb.GetBitmapNamed(IDR_CHECKBOX_OFF); + } + + gfx::Rect bounds = rect.Center(gfx::Size(image->width(), image->height())); + DrawBitmapInt(canvas, *image, 0, 0, image->width(), image->height(), + bounds.x(), bounds.y(), bounds.width(), bounds.height()); +} + +void NativeThemeBase::PaintRadio(SkCanvas* canvas, + State state, + const gfx::Rect& rect, + const ButtonExtraParams& button) const { + ResourceBundle& rb = ResourceBundle::GetSharedInstance(); + SkBitmap* image = NULL; + if (state == kDisabled) { + image = button.checked ? + rb.GetBitmapNamed(IDR_RADIO_DISABLED_ON) : + rb.GetBitmapNamed(IDR_RADIO_DISABLED_OFF); + } else { + image = button.checked ? + rb.GetBitmapNamed(IDR_RADIO_ON) : + rb.GetBitmapNamed(IDR_RADIO_OFF); + } + + gfx::Rect bounds = rect.Center(gfx::Size(image->width(), image->height())); + DrawBitmapInt(canvas, *image, 0, 0, image->width(), image->height(), + bounds.x(), bounds.y(), bounds.width(), bounds.height()); +} + +void NativeThemeBase::PaintButton(SkCanvas* canvas, + State state, + const gfx::Rect& rect, + const ButtonExtraParams& button) const { + SkPaint paint; + const int kRight = rect.right(); + const int kBottom = rect.bottom(); + SkRect skrect = SkRect::MakeLTRB(rect.x(), rect.y(), kRight, kBottom); + SkColor base_color = button.background_color; + + color_utils::HSL base_hsl; + color_utils::SkColorToHSL(base_color, &base_hsl); + + // Our standard gradient is from 0xdd to 0xf8. This is the amount of + // increased luminance between those values. + SkColor light_color(BrightenColor(base_hsl, SkColorGetA(base_color), 0.105)); + + // If the button is too small, fallback to drawing a single, solid color + if (rect.width() < 5 || rect.height() < 5) { + paint.setColor(base_color); + canvas->drawRect(skrect, paint); + return; + } + + paint.setColor(SK_ColorBLACK); + const int kLightEnd = state == kPressed ? 1 : 0; + const int kDarkEnd = !kLightEnd; + SkPoint gradient_bounds[2]; + gradient_bounds[kLightEnd].iset(rect.x(), rect.y()); + gradient_bounds[kDarkEnd].iset(rect.x(), kBottom - 1); + SkColor colors[2]; + colors[0] = light_color; + colors[1] = base_color; + + SkShader* shader = SkGradientShader::CreateLinear( + gradient_bounds, colors, NULL, 2, SkShader::kClamp_TileMode, NULL); + paint.setStyle(SkPaint::kFill_Style); + paint.setAntiAlias(true); + paint.setShader(shader); + shader->unref(); + + canvas->drawRoundRect(skrect, SkIntToScalar(1), SkIntToScalar(1), paint); + paint.setShader(NULL); + + if (button.has_border) { + const int kBorderAlpha = state == kHovered ? 0x80 : 0x55; + paint.setStyle(SkPaint::kStroke_Style); + paint.setStrokeWidth(SkIntToScalar(1)); + paint.setARGB(kBorderAlpha, 0, 0, 0); + skrect.inset(SkFloatToScalar(.5f), SkFloatToScalar(.5f)); + canvas->drawRoundRect(skrect, SkIntToScalar(1), SkIntToScalar(1), paint); + } +} + +void NativeThemeBase::PaintTextField(SkCanvas* canvas, + State state, + const gfx::Rect& rect, + const TextFieldExtraParams& text) const { + // The following drawing code simulates the user-agent css border for + // text area and text input so that we do not break layout tests. Once we + // have decided the desired looks, we should update the code here and + // the layout test expectations. + SkRect bounds; + bounds.set(rect.x(), rect.y(), rect.right() - 1, rect.bottom() - 1); + + SkPaint fill_paint; + fill_paint.setStyle(SkPaint::kFill_Style); + fill_paint.setColor(text.background_color); + canvas->drawRect(bounds, fill_paint); + + if (text.is_text_area) { + // Draw text area border: 1px solid black + SkPaint stroke_paint; + fill_paint.setStyle(SkPaint::kStroke_Style); + fill_paint.setColor(SK_ColorBLACK); + canvas->drawRect(bounds, fill_paint); + } else { + // Draw text input and listbox inset border + // Text Input: 2px inset #eee + // Listbox: 1px inset #808080 + const SkColor kLightColor = text.is_listbox ? + SkColorSetRGB(0x80, 0x80, 0x80) : SkColorSetRGB(0xee, 0xee, 0xee); + const SkColor kDarkColor = text.is_listbox ? + SkColorSetRGB(0x2c, 0x2c, 0x2c) : SkColorSetRGB(0x9a, 0x9a, 0x9a); + const int kBorderWidth = text.is_listbox ? 1 : 2; + + SkPaint dark_paint; + dark_paint.setAntiAlias(true); + dark_paint.setStyle(SkPaint::kFill_Style); + dark_paint.setColor(kDarkColor); + + SkPaint light_paint; + light_paint.setAntiAlias(true); + light_paint.setStyle(SkPaint::kFill_Style); + light_paint.setColor(kLightColor); + + int left = rect.x(); + int top = rect.y(); + int right = rect.right(); + int bottom = rect.bottom(); + + SkPath path; + path.incReserve(4); + + // Top + path.moveTo(SkIntToScalar(left), SkIntToScalar(top)); + path.lineTo(SkIntToScalar(left + kBorderWidth), + SkIntToScalar(top + kBorderWidth)); + path.lineTo(SkIntToScalar(right - kBorderWidth), + SkIntToScalar(top + kBorderWidth)); + path.lineTo(SkIntToScalar(right), SkIntToScalar(top)); + canvas->drawPath(path, dark_paint); + + // Bottom + path.reset(); + path.moveTo(SkIntToScalar(left + kBorderWidth), + SkIntToScalar(bottom - kBorderWidth)); + path.lineTo(SkIntToScalar(left), SkIntToScalar(bottom)); + path.lineTo(SkIntToScalar(right), SkIntToScalar(bottom)); + path.lineTo(SkIntToScalar(right - kBorderWidth), + SkIntToScalar(bottom - kBorderWidth)); + canvas->drawPath(path, light_paint); + + // Left + path.reset(); + path.moveTo(SkIntToScalar(left), SkIntToScalar(top)); + path.lineTo(SkIntToScalar(left), SkIntToScalar(bottom)); + path.lineTo(SkIntToScalar(left + kBorderWidth), + SkIntToScalar(bottom - kBorderWidth)); + path.lineTo(SkIntToScalar(left + kBorderWidth), + SkIntToScalar(top + kBorderWidth)); + canvas->drawPath(path, dark_paint); + + // Right + path.reset(); + path.moveTo(SkIntToScalar(right - kBorderWidth), + SkIntToScalar(top + kBorderWidth)); + path.lineTo(SkIntToScalar(right - kBorderWidth), SkIntToScalar(bottom)); + path.lineTo(SkIntToScalar(right), SkIntToScalar(bottom)); + path.lineTo(SkIntToScalar(right), SkIntToScalar(top)); + canvas->drawPath(path, light_paint); + } +} + +void NativeThemeBase::PaintMenuList( + SkCanvas* canvas, + State state, + const gfx::Rect& rect, + const MenuListExtraParams& menu_list) const { + // If a border radius is specified, we let the WebCore paint the background + // and the border of the control. + if (!menu_list.has_border_radius) { + ButtonExtraParams button = { 0 }; + button.background_color = menu_list.background_color; + button.has_border = menu_list.has_border; + PaintButton(canvas, state, rect, button); + } + + SkPaint paint; + paint.setColor(SK_ColorBLACK); + paint.setAntiAlias(true); + paint.setStyle(SkPaint::kFill_Style); + + SkPath path; + path.moveTo(menu_list.arrow_x, menu_list.arrow_y - 3); + path.rLineTo(6, 0); + path.rLineTo(-3, 6); + path.close(); + canvas->drawPath(path, paint); +} + +void NativeThemeBase::PaintMenuPopupBackground(SkCanvas* canvas, + const gfx::Size& size) const { + canvas->drawColor(kMenuPopupBackgroundColor, SkXfermode::kSrc_Mode); +} + +void NativeThemeBase::PaintMenuItemBackground( + SkCanvas* canvas, + State state, + const gfx::Rect& rect, + const MenuListExtraParams& menu_list) const { + // By default don't draw anything over the normal background. +} + +void NativeThemeBase::PaintSliderTrack(SkCanvas* canvas, + State state, + const gfx::Rect& rect, + const SliderExtraParams& slider) const { + const int kMidX = rect.x() + rect.width() / 2; + const int kMidY = rect.y() + rect.height() / 2; + + SkPaint paint; + paint.setColor(kSliderTrackBackgroundColor); + + SkRect skrect; + if (slider.vertical) { + skrect.set(std::max(rect.x(), kMidX - 2), + rect.y(), + std::min(rect.right(), kMidX + 2), + rect.bottom()); + } else { + skrect.set(rect.x(), + std::max(rect.y(), kMidY - 2), + rect.right(), + std::min(rect.bottom(), kMidY + 2)); + } + canvas->drawRect(skrect, paint); +} + +void NativeThemeBase::PaintSliderThumb(SkCanvas* canvas, + State state, + const gfx::Rect& rect, + const SliderExtraParams& slider) const { + const bool hovered = (state == kHovered) || slider.in_drag; + const int kMidX = rect.x() + rect.width() / 2; + const int kMidY = rect.y() + rect.height() / 2; + + SkPaint paint; + paint.setColor(hovered ? SK_ColorWHITE : kSliderThumbLightGrey); + + SkIRect skrect; + if (slider.vertical) + skrect.set(rect.x(), rect.y(), kMidX + 1, rect.bottom()); + else + skrect.set(rect.x(), rect.y(), rect.right(), kMidY + 1); + + canvas->drawIRect(skrect, paint); + + paint.setColor(hovered ? kSliderThumbLightGrey : kSliderThumbDarkGrey); + + if (slider.vertical) + skrect.set(kMidX + 1, rect.y(), rect.right(), rect.bottom()); + else + skrect.set(rect.x(), kMidY + 1, rect.right(), rect.bottom()); + + canvas->drawIRect(skrect, paint); + + paint.setColor(kSliderThumbBorderDarkGrey); + DrawBox(canvas, rect, paint); + + if (rect.height() > 10 && rect.width() > 10) { + DrawHorizLine(canvas, kMidX - 2, kMidX + 2, kMidY, paint); + DrawHorizLine(canvas, kMidX - 2, kMidX + 2, kMidY - 3, paint); + DrawHorizLine(canvas, kMidX - 2, kMidX + 2, kMidY + 3, paint); + } +} + +void NativeThemeBase::PaintInnerSpinButton(SkCanvas* canvas, + State state, + const gfx::Rect& rect, + const InnerSpinButtonExtraParams& spin_button) const { + if (spin_button.read_only) + state = kDisabled; + + State north_state = state; + State south_state = state; + if (spin_button.spin_up) + south_state = south_state != kDisabled ? kNormal : kDisabled; + else + north_state = north_state != kDisabled ? kNormal : kDisabled; + + gfx::Rect half = rect; + half.set_height(rect.height() / 2); + PaintArrowButton(canvas, half, kScrollbarUpArrow, north_state); + + half.set_y(rect.y() + rect.height() / 2); + PaintArrowButton(canvas, half, kScrollbarDownArrow, south_state); +} + +void NativeThemeBase::PaintProgressBar(SkCanvas* canvas, + State state, + const gfx::Rect& rect, + const ProgressBarExtraParams& progress_bar) const { + ResourceBundle& rb = ResourceBundle::GetSharedInstance(); + SkBitmap* bar_image = rb.GetBitmapNamed(IDR_PROGRESS_BAR); + SkBitmap* left_border_image = rb.GetBitmapNamed(IDR_PROGRESS_BORDER_LEFT); + SkBitmap* right_border_image = rb.GetBitmapNamed(IDR_PROGRESS_BORDER_RIGHT); + + double tile_scale = static_cast<double>(rect.height()) / + bar_image->height(); + + int new_tile_width = static_cast<int>(bar_image->width() * tile_scale); + double tile_scale_x = static_cast<double>(new_tile_width) / + bar_image->width(); + + DrawTiledImage(canvas, *bar_image, 0, 0, tile_scale_x, tile_scale, + rect.x(), rect.y(), rect.width(), rect.height()); + + if (progress_bar.value_rect_width) { + SkBitmap* value_image = rb.GetBitmapNamed(IDR_PROGRESS_VALUE); + + new_tile_width = static_cast<int>(value_image->width() * tile_scale); + tile_scale_x = static_cast<double>(new_tile_width) / + value_image->width(); + + DrawTiledImage(canvas, *value_image, 0, 0, tile_scale_x, tile_scale, + progress_bar.value_rect_x, + progress_bar.value_rect_y, + progress_bar.value_rect_width, + progress_bar.value_rect_height); + } + + int dest_left_border_width = static_cast<int>(left_border_image->width() * + tile_scale); + SkRect dest_rect; + dest_rect.iset(rect.x(), rect.y(), rect.x() + dest_left_border_width, + rect.bottom()); + canvas->drawBitmapRect(*left_border_image, NULL, dest_rect); + + int dest_right_border_width = static_cast<int>(right_border_image->width() * + tile_scale); + dest_rect.iset(rect.right() - dest_right_border_width, rect.y(), rect.right(), + rect.bottom()); + canvas->drawBitmapRect(*right_border_image, NULL, dest_rect); +} + +bool NativeThemeBase::IntersectsClipRectInt(SkCanvas* canvas, + int x, int y, int w, int h) const { + SkRect clip; + return canvas->getClipBounds(&clip) && + clip.intersect(SkIntToScalar(x), SkIntToScalar(y), SkIntToScalar(x + w), + SkIntToScalar(y + h)); +} + +void NativeThemeBase::DrawBitmapInt( + SkCanvas* canvas, const SkBitmap& bitmap, + int src_x, int src_y, int src_w, int src_h, + int dest_x, int dest_y, int dest_w, int dest_h) const { + DLOG_ASSERT(src_x + src_w < std::numeric_limits<int16_t>::max() && + src_y + src_h < std::numeric_limits<int16_t>::max()); + if (src_w <= 0 || src_h <= 0 || dest_w <= 0 || dest_h <= 0) { + NOTREACHED() << "Attempting to draw bitmap to/from an empty rect!"; + return; + } + + if (!IntersectsClipRectInt(canvas, dest_x, dest_y, dest_w, dest_h)) + return; + + SkRect dest_rect = { SkIntToScalar(dest_x), + SkIntToScalar(dest_y), + SkIntToScalar(dest_x + dest_w), + SkIntToScalar(dest_y + dest_h) }; + + if (src_w == dest_w && src_h == dest_h) { + // Workaround for apparent bug in Skia that causes image to occasionally + // shift. + SkIRect src_rect = { src_x, src_y, src_x + src_w, src_y + src_h }; + canvas->drawBitmapRect(bitmap, &src_rect, dest_rect); + return; + } + + // Make a bitmap shader that contains the bitmap we want to draw. This is + // basically what SkCanvas.drawBitmap does internally, but it gives us + // more control over quality and will use the mipmap in the source image if + // it has one, whereas drawBitmap won't. + SkShader* shader = SkShader::CreateBitmapShader(bitmap, + SkShader::kRepeat_TileMode, + SkShader::kRepeat_TileMode); + SkMatrix shader_scale; + shader_scale.setScale(SkFloatToScalar(static_cast<float>(dest_w) / src_w), + SkFloatToScalar(static_cast<float>(dest_h) / src_h)); + shader_scale.preTranslate(SkIntToScalar(-src_x), SkIntToScalar(-src_y)); + shader_scale.postTranslate(SkIntToScalar(dest_x), SkIntToScalar(dest_y)); + shader->setLocalMatrix(shader_scale); + + // The rect will be filled by the bitmap. + SkPaint p; + p.setFilterBitmap(true); + p.setShader(shader); + shader->unref(); + canvas->drawRect(dest_rect, p); +} + +void NativeThemeBase::DrawTiledImage(SkCanvas* canvas, + const SkBitmap& bitmap, + int src_x, int src_y, double tile_scale_x, double tile_scale_y, + int dest_x, int dest_y, int w, int h) const { + SkShader* shader = SkShader::CreateBitmapShader(bitmap, + SkShader::kRepeat_TileMode, + SkShader::kRepeat_TileMode); + if (tile_scale_x != 1.0 || tile_scale_y != 1.0) { + SkMatrix shader_scale; + shader_scale.setScale(SkDoubleToScalar(tile_scale_x), + SkDoubleToScalar(tile_scale_y)); + shader->setLocalMatrix(shader_scale); + } + + SkPaint paint; + paint.setShader(shader); + paint.setXfermodeMode(SkXfermode::kSrcOver_Mode); + + // CreateBitmapShader returns a Shader with a reference count of one, we + // need to unref after paint takes ownership of the shader. + shader->unref(); + canvas->save(); + canvas->translate(SkIntToScalar(dest_x - src_x), + SkIntToScalar(dest_y - src_y)); + canvas->clipRect(SkRect::MakeXYWH(src_x, src_y, w, h)); + canvas->drawPaint(paint); + canvas->restore(); +} + +SkColor NativeThemeBase::SaturateAndBrighten(SkScalar* hsv, + SkScalar saturate_amount, + SkScalar brighten_amount) const { + SkScalar color[3]; + color[0] = hsv[0]; + color[1] = Clamp(hsv[1] + saturate_amount, 0.0, 1.0); + color[2] = Clamp(hsv[2] + brighten_amount, 0.0, 1.0); + return SkHSVToColor(color); +} + +void NativeThemeBase::DrawVertLine(SkCanvas* canvas, + int x, + int y1, + int y2, + const SkPaint& paint) const { + SkIRect skrect; + skrect.set(x, y1, x + 1, y2 + 1); + canvas->drawIRect(skrect, paint); +} + +void NativeThemeBase::DrawHorizLine(SkCanvas* canvas, + int x1, + int x2, + int y, + const SkPaint& paint) const { + SkIRect skrect; + skrect.set(x1, y, x2 + 1, y + 1); + canvas->drawIRect(skrect, paint); +} + +void NativeThemeBase::DrawBox(SkCanvas* canvas, + const gfx::Rect& rect, + const SkPaint& paint) const { + const int right = rect.x() + rect.width() - 1; + const int bottom = rect.y() + rect.height() - 1; + DrawHorizLine(canvas, rect.x(), right, rect.y(), paint); + DrawVertLine(canvas, right, rect.y(), bottom, paint); + DrawHorizLine(canvas, rect.x(), right, bottom, paint); + DrawVertLine(canvas, rect.x(), rect.y(), bottom, paint); +} + +SkScalar NativeThemeBase::Clamp(SkScalar value, + SkScalar min, + SkScalar max) const { + return std::min(std::max(value, min), max); +} + +SkColor NativeThemeBase::OutlineColor(SkScalar* hsv1, SkScalar* hsv2) const { + // GTK Theme engines have way too much control over the layout of + // the scrollbar. We might be able to more closely approximate its + // look-and-feel, if we sent whole images instead of just colors + // from the browser to the renderer. But even then, some themes + // would just break. + // + // So, instead, we don't even try to 100% replicate the look of + // the native scrollbar. We render our own version, but we make + // sure to pick colors that blend in nicely with the system GTK + // theme. In most cases, we can just sample a couple of pixels + // from the system scrollbar and use those colors to draw our + // scrollbar. + // + // This works fine for the track color and the overall thumb + // color. But it fails spectacularly for the outline color used + // around the thumb piece. Not all themes have a clearly defined + // outline. For some of them it is partially transparent, and for + // others the thickness is very unpredictable. + // + // So, instead of trying to approximate the system theme, we + // instead try to compute a reasonable looking choice based on the + // known color of the track and the thumb piece. This is difficult + // when trying to deal both with high- and low-contrast themes, + // and both with positive and inverted themes. + // + // The following code has been tested to look OK with all of the + // default GTK themes. + SkScalar min_diff = Clamp((hsv1[1] + hsv2[1]) * 1.2f, 0.28f, 0.5f); + SkScalar diff = Clamp(fabs(hsv1[2] - hsv2[2]) / 2, min_diff, 0.5f); + + if (hsv1[2] + hsv2[2] > 1.0) + diff = -diff; + + return SaturateAndBrighten(hsv2, -0.2f, diff); +} + +} // namespace ui diff --git a/ui/base/native_theme/native_theme_base.h b/ui/base/native_theme/native_theme_base.h new file mode 100644 index 0000000..5cc86fe --- /dev/null +++ b/ui/base/native_theme/native_theme_base.h @@ -0,0 +1,171 @@ +// 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. + +#ifndef UI_BASE_NATIVE_THEME_NATIVE_THEME_BASE_H_ +#define UI_BASE_NATIVE_THEME_NATIVE_THEME_BASE_H_ +#pragma once + +#include "base/basictypes.h" +#include "base/compiler_specific.h" +#include "skia/ext/platform_canvas.h" +#include "ui/base/native_theme/native_theme.h" + +namespace gfx { +class Rect; +class Size; +} + +namespace ui { + +// Theme support for non-Windows toolkits. +class NativeThemeBase : public NativeTheme { + public: + // NativeTheme implementation: + virtual gfx::Size GetPartSize(Part part, + State state, + const ExtraParams& extra) const OVERRIDE; + virtual void Paint(SkCanvas* canvas, + Part part, + State state, + const gfx::Rect& rect, + const ExtraParams& extra) const OVERRIDE; + + protected: + NativeThemeBase(); + virtual ~NativeThemeBase(); + + // Draw the arrow. Used by scrollbar and inner spin button. + virtual void PaintArrowButton( + SkCanvas* gc, + const gfx::Rect& rect, + Part direction, + State state) const; + // Paint the scrollbar track. Done before the thumb so that it can contain + // alpha. + virtual void PaintScrollbarTrack( + SkCanvas* canvas, + Part part, + State state, + const ScrollbarTrackExtraParams& extra_params, + const gfx::Rect& rect) const; + // Draw the scrollbar thumb over the track. + virtual void PaintScrollbarThumb( + SkCanvas* canvas, + Part part, + State state, + const gfx::Rect& rect) const; + + virtual void PaintCheckbox( + SkCanvas* canvas, + State state, + const gfx::Rect& rect, + const ButtonExtraParams& button) const; + + virtual void PaintRadio( + SkCanvas* canvas, + State state, + const gfx::Rect& rect, + const ButtonExtraParams& button) const; + + virtual void PaintButton( + SkCanvas* canvas, + State state, + const gfx::Rect& rect, + const ButtonExtraParams& button) const; + + virtual void PaintTextField( + SkCanvas* canvas, + State state, + const gfx::Rect& rect, + const TextFieldExtraParams& text) const; + + virtual void PaintMenuList( + SkCanvas* canvas, + State state, + const gfx::Rect& rect, + const MenuListExtraParams& menu_list) const; + + virtual void PaintMenuPopupBackground(SkCanvas* canvas, + const gfx::Size& size) const; + + virtual void PaintMenuItemBackground( + SkCanvas* canvas, + State state, + const gfx::Rect& rect, + const MenuListExtraParams& menu_list) const; + + virtual void PaintSliderTrack( + SkCanvas* canvas, + State state, + const gfx::Rect& rect, + const SliderExtraParams& slider) const; + + virtual void PaintSliderThumb( + SkCanvas* canvas, + State state, + const gfx::Rect& rect, + const SliderExtraParams& slider) const; + + virtual void PaintInnerSpinButton( + SkCanvas* canvas, + State state, + const gfx::Rect& rect, + const InnerSpinButtonExtraParams& spin_button) const; + + virtual void PaintProgressBar( + SkCanvas* canvas, + State state, + const gfx::Rect& rect, + const ProgressBarExtraParams& progress_bar) const; + + protected: + void set_scrollbar_button_length(unsigned int length) { + scrollbar_button_length_ = length; + } + + bool IntersectsClipRectInt(SkCanvas* canvas, + int x, int y, int w, int h) const; + + void DrawBitmapInt(SkCanvas* canvas, const SkBitmap& bitmap, + int src_x, int src_y, int src_w, int src_h, + int dest_x, int dest_y, int dest_w, int dest_h) const; + + void DrawTiledImage(SkCanvas* canvas, + const SkBitmap& bitmap, + int src_x, int src_y, + double tile_scale_x, double tile_scale_y, + int dest_x, int dest_y, int w, int h) const; + + SkColor SaturateAndBrighten(SkScalar* hsv, + SkScalar saturate_amount, + SkScalar brighten_amount) const; + + private: + void DrawVertLine(SkCanvas* canvas, + int x, + int y1, + int y2, + const SkPaint& paint) const; + void DrawHorizLine(SkCanvas* canvas, + int x1, + int x2, + int y, + const SkPaint& paint) const; + void DrawBox(SkCanvas* canvas, + const gfx::Rect& rect, + const SkPaint& paint) const; + SkScalar Clamp(SkScalar value, + SkScalar min, + SkScalar max) const; + SkColor OutlineColor(SkScalar* hsv1, SkScalar* hsv2) const; + + unsigned int scrollbar_width_; + unsigned int scrollbar_button_length_; + + DISALLOW_COPY_AND_ASSIGN(NativeThemeBase); +}; + +} // namespace ui + +#endif // UI_BASE_NATIVE_THEME_NATIVE_THEME_BASE_H_ diff --git a/ui/base/native_theme/native_theme_gtk.cc b/ui/base/native_theme/native_theme_gtk.cc new file mode 100644 index 0000000..d8eab30 --- /dev/null +++ b/ui/base/native_theme/native_theme_gtk.cc @@ -0,0 +1,87 @@ +// 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/base/native_theme/native_theme_gtk.h" + +#include <gtk/gtk.h> + +#include "base/basictypes.h" +#include "base/logging.h" +#include "ui/gfx/skia_utils_gtk.h" + +namespace { + +const SkColor kInvalidColorIdColor = SkColorSetRGB(255, 0, 128); + +// Theme colors returned by GetSystemColor(). + +// FocusableBorder: +const SkColor kFocusedBorderColor = SkColorSetRGB(0x4D, 0x90, 0xFE); +const SkColor kUnfocusedBorderColor = SkColorSetRGB(0xD9, 0xD9, 0xD9); + +// TextButton: +const SkColor kTextButtonBackgroundColor = SkColorSetRGB(0xde, 0xde, 0xde); +const SkColor kTextButtonEnabledColor = SkColorSetRGB(6, 45, 117); +const SkColor kTextButtonDisabledColor = SkColorSetRGB(161, 161, 146); +const SkColor kTextButtonHighlightColor = SkColorSetARGB(200, 255, 255, 255); +const SkColor kTextButtonHoverColor = kTextButtonEnabledColor; + +} // namespace + +namespace ui { + +// static +const NativeTheme* NativeTheme::instance() { + return NativeThemeGtk::instance(); +} + +// static +const NativeThemeGtk* NativeThemeGtk::instance() { + CR_DEFINE_STATIC_LOCAL(NativeThemeGtk, s_native_theme, ()); + return &s_native_theme; +} + +SkColor NativeThemeGtk::GetSystemColor(ColorId color_id) const { + switch (color_id) { + case kColorId_DialogBackground: + // TODO(benrg): This code used to call gtk_widget_get_style() on the + // widget being styled. After refactoring, that widget is not available + // and we have to call gtk_widget_get_default_style(). Unfortunately, + // it turns out that this breaks everything (chromium bug 105609, + // chromium-os bug 23461). Need to figure out the right thing and do it. + return gfx::GdkColorToSkColor( + gtk_widget_get_default_style()->bg[GTK_STATE_NORMAL]); + + // FocusableBorder: + case kColorId_FocusedBorderColor: + return kFocusedBorderColor; + case kColorId_UnfocusedBorderColor: + return kUnfocusedBorderColor; + + // TextButton: + case kColorId_TextButtonBackgroundColor: + return kTextButtonBackgroundColor; + case kColorId_TextButtonEnabledColor: + return kTextButtonEnabledColor; + case kColorId_TextButtonDisabledColor: + return kTextButtonDisabledColor; + case kColorId_TextButtonHighlightColor: + return kTextButtonHighlightColor; + case kColorId_TextButtonHoverColor: + return kTextButtonHoverColor; + + default: + NOTREACHED() << "Invalid color_id: " << color_id; + break; + } + return kInvalidColorIdColor; +} + +NativeThemeGtk::NativeThemeGtk() { +} + +NativeThemeGtk::~NativeThemeGtk() { +} + +} // namespace ui diff --git a/ui/base/native_theme/native_theme_gtk.h b/ui/base/native_theme/native_theme_gtk.h new file mode 100644 index 0000000..257d49f --- /dev/null +++ b/ui/base/native_theme/native_theme_gtk.h @@ -0,0 +1,27 @@ +// 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. + +#ifndef UI_BASE_NATIVE_THEME_NATIVE_THEME_GTK_H_ +#define UI_BASE_NATIVE_THEME_NATIVE_THEME_GTK_H_ +#pragma once + +#include "ui/base/native_theme/native_theme_base.h" + +namespace ui { + +// GTK implementation of native theme support. +class NativeThemeGtk : public NativeThemeBase { + public: + static const NativeThemeGtk* instance(); + + virtual SkColor GetSystemColor(ColorId color_id) const OVERRIDE; + + private: + NativeThemeGtk(); + virtual ~NativeThemeGtk(); +}; + +} // namespace ui + +#endif // UI_BASE_NATIVE_THEME_NATIVE_THEME_GTK_H_ diff --git a/ui/base/native_theme/native_theme_win.cc b/ui/base/native_theme/native_theme_win.cc new file mode 100644 index 0000000..abe605f --- /dev/null +++ b/ui/base/native_theme/native_theme_win.cc @@ -0,0 +1,1779 @@ +// 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/base/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_handle.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/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" + +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(200, 200, 200); +// FocusableBorder (should be unused on Win): +const SkColor kFocusedBorderColor= SkColorSetRGB(0x4D, 0x90, 0xFE); +const SkColor kUnfocusedBorderColor = SkColorSetRGB(0xD9, 0xD9, 0xD9); +// TextButton: +const SkColor kTextButtonBackgroundColor = SkColorSetRGB(0xde, 0xde, 0xde); +const SkColor kTextButtonEnabledColor = SkColorSetRGB(6, 45, 117); +const SkColor kTextButtonDisabledColor = SkColorSetRGB(161, 161, 146); +const SkColor kTextButtonHighlightColor = SkColorSetARGB(200, 255, 255, 255); +const SkColor kTextButtonHoverColor = kTextButtonEnabledColor; +// MenuItem: +const SkColor kEnabledMenuItemForegroundColor = kTextButtonEnabledColor; +const SkColor kDisabledMenuItemForegroundColor = kTextButtonDisabledColor; +const SkColor kFocusedMenuItemBackgroundColor = SkColorSetRGB(246, 249, 253); +const SkColor kMenuSeparatorColor = SkColorSetARGB(50, 0, 0, 0); +// Label: +const SkColor kLabelEnabledColor = color_utils::GetSysSkColor(COLOR_WINDOWTEXT); +const SkColor kLabelDisabledColor = color_utils::GetSysSkColor(COLOR_GRAYTEXT); +const SkColor kLabelBackgroundColor = color_utils::GetSysSkColor(COLOR_WINDOW); +// Textfield: +const SkColor kTextfieldDefaultColor = SK_ColorBLACK; +const SkColor kTextfieldDefaultBackground = SK_ColorWHITE; +const SkColor kTextfieldSelectionColor = SK_ColorWHITE; +const SkColor kTextfieldSelectionBackgroundFocused = + SkColorSetRGB(0x1D, 0x90, 0xFF); +const SkColor kTextfieldSelectionBackgroundUnfocused = SK_ColorLTGRAY; + +SkColor WinColorToSkColor(COLORREF color) { + return SkColorSetRGB(GetRValue(color), GetGValue(color), GetBValue(color)); +} + +void SetCheckerboardShader(SkPaint* paint, const RECT& align_rect) { + // Create a 2x2 checkerboard pattern using the 3D face and highlight colors. + SkColor face = skia::COLORREFToSkColor(GetSysColor(COLOR_3DFACE)); + SkColor highlight = skia::COLORREFToSkColor(GetSysColor(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, temp_bitmap.config()); + SkShader* shader = SkShader::CreateBitmapShader(bitmap, + SkShader::kRepeat_TileMode, + SkShader::kRepeat_TileMode); + + // Align the pattern with the upper corner of |align_rect|. + SkMatrix matrix; + matrix.setTranslate(SkIntToScalar(align_rect.left), + SkIntToScalar(align_rect.top)); + shader->setLocalMatrix(matrix); + SkSafeUnref(paint->setShader(shader)); +} + +// <-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; +} + +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 = skia::COLORREFToSkColor(GetSysColor(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 +const NativeTheme* NativeTheme::instance() { + return NativeThemeWin::instance(); +} + +// static +const NativeThemeWin* NativeThemeWin::instance() { + CR_DEFINE_STATIC_LOCAL(NativeThemeWin, s_native_theme, ()); + return &s_native_theme; +} + +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) { + 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_)); +} + +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_); + } +} + +gfx::Size NativeThemeWin::GetPartSize(Part part, + State state, + const ExtraParams& extra) const { + SIZE size; + int part_id = GetWindowsPart(part, state, extra); + int state_id = GetWindowsState(part, state, extra); + + 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 (!skia::SupportsPlatformPaint(canvas)) { + // This block will only get hit with --enable-accelerated-drawing flag. + PaintToNonPlatformCanvas(canvas, part, state, rect, extra); + return; + } + + 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 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 { + switch (color_id) { + + // Dialogs + case kColorId_DialogBackground: + // TODO(benrg): Should this use the new Windows theme functions? The old + // code in DialogClientView::OnPaint used GetSysColor(COLOR_3DFACE). + return WinColorToSkColor(GetSysColor(COLOR_3DFACE)); + + // TextButton + case kColorId_TextButtonBackgroundColor: + return kTextButtonBackgroundColor; + case kColorId_TextButtonEnabledColor: + return kTextButtonEnabledColor; + case kColorId_TextButtonDisabledColor: + return kTextButtonDisabledColor; + case kColorId_TextButtonHighlightColor: + return kTextButtonHighlightColor; + case kColorId_TextButtonHoverColor: + return kTextButtonHoverColor; + + // MenuItem + case kColorId_EnabledMenuItemForegroundColor: + return kEnabledMenuItemForegroundColor; + case kColorId_DisabledMenuItemForegroundColor: + return kDisabledMenuItemForegroundColor; + case kColorId_FocusedMenuItemBackgroundColor: + return kFocusedMenuItemBackgroundColor; + case kColorId_MenuSeparatorColor: + return kMenuSeparatorColor; + + // Label + case kColorId_LabelEnabledColor: + return kLabelEnabledColor; + case kColorId_LabelDisabledColor: + return kLabelDisabledColor; + case kColorId_LabelBackgroundColor: + return kLabelBackgroundColor; + + // Textfield + case kColorId_TextfieldDefaultColor: + return kTextfieldDefaultColor; + case kColorId_TextfieldDefaultBackground: + return kTextfieldDefaultBackground; + case kColorId_TextfieldSelectionColor: + return kTextfieldSelectionColor; + case kColorId_TextfieldSelectionBackgroundFocused: + return kTextfieldSelectionBackgroundFocused; + case kColorId_TextfieldSelectionBackgroundUnfocused: + return kTextfieldSelectionBackgroundUnfocused; + + default: + NOTREACHED() << "Invalid color_id: " << color_id; + break; + } + return kInvalidColorIdColor; +} + +void NativeThemeWin::PaintToNonPlatformCanvas(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. + scoped_ptr<SkCanvas> offscreen_canvas( + skia::CreateBitmapCanvas(rect.width(), rect.height(), false)); + DCHECK(offscreen_canvas.get()); + DCHECK(skia::SupportsPlatformPaint(offscreen_canvas.get())); + + // 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. + Paint(offscreen_canvas.get(), 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, SkBitmap::kARGB_8888_Config); + + // 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); + } + + return S_OK; +} + +HRESULT NativeThemeWin::PaintMenuSeparator( + HDC hdc, + const gfx::Rect& rect, + const MenuSeparatorExtraParams& extra) const { + RECT rect_win = rect.ToRECT(); + if (!extra.has_gutter) + rect_win.top = rect.y() + rect.height() / 3 + 1; + + 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 draw_theme_(handle, hdc, SBP_ARROWBTN, state_id, &rect_win, NULL); + } + + 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 draw_theme_(handle, hdc, part_id, state_id, &rect_win, NULL); + + // 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. + const DWORD colorScrollbar = GetSysColor(COLOR_SCROLLBAR); + const DWORD color3DFace = GetSysColor(COLOR_3DFACE); + if ((colorScrollbar != color3DFace) && + (colorScrollbar != GetSysColor(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; +} + +// 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; + 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; + 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 diff --git a/ui/base/native_theme/native_theme_win.h b/ui/base/native_theme/native_theme_win.h new file mode 100644 index 0000000..260de8d --- /dev/null +++ b/ui/base/native_theme/native_theme_win.h @@ -0,0 +1,331 @@ +// 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. + +#ifndef UI_BASE_NATIVE_THEME_NATIVE_THEME_WIN_H_ +#define UI_BASE_NATIVE_THEME_NATIVE_THEME_WIN_H_ +#pragma once + +// A wrapper class for working with custom XP/Vista themes provided in +// uxtheme.dll. This is a singleton class that can be grabbed using +// NativeThemeWin::instance(). +// For more information on visual style parts and states, see: +// http://msdn.microsoft.com/library/default.asp?url=/library/en-us/shellcc/platform/commctls/userex/topics/partsandstates.asp + +#include <windows.h> +#include <uxtheme.h> + +#include "base/basictypes.h" +#include "base/compiler_specific.h" +#include "third_party/skia/include/core/SkColor.h" +#include "ui/base/native_theme/native_theme.h" +#include "ui/gfx/size.h" + +class SkCanvas; + +namespace ui { + +// Windows implementation of native theme class. +// +// At the moment, this class in in transition from an older API that consists +// of several PaintXXX methods to an API, inherited from the NativeTheme base +// class, that consists of a single Paint() method with a argument to indicate +// what kind of part to paint. +class UI_EXPORT NativeThemeWin : public NativeTheme { + public: + enum ThemeName { + BUTTON, + LIST, + MENU, + MENULIST, + SCROLLBAR, + STATUS, + TAB, + TEXTFIELD, + TRACKBAR, + WINDOW, + PROGRESS, + SPIN, + LAST + }; + + bool IsThemingActive() const; + + HRESULT GetThemeColor(ThemeName theme, + int part_id, + int state_id, + int prop_id, + SkColor* color) const; + + // Get the theme color if theming is enabled. If theming is unsupported + // for this part, use Win32's GetSysColor to find the color specified + // by default_sys_color. + SkColor GetThemeColorWithDefault(ThemeName theme, + int part_id, + int state_id, + int prop_id, + int default_sys_color) const; + + // Get the thickness of the border associated with the specified theme, + // defaulting to GetSystemMetrics edge size if themes are disabled. + // In Classic Windows, borders are typically 2px; on XP+, they are 1px. + gfx::Size GetThemeBorderSize(ThemeName theme) const; + + // Disables all theming for top-level windows in the entire process, from + // when this method is called until the process exits. All the other + // methods in this class will continue to work, but their output will ignore + // the user's theme. This is meant for use when running tests that require + // consistent visual results. + void DisableTheming() const; + + // Closes cached theme handles so we can unload the DLL or update our UI + // for a theme change. + void CloseHandles() const; + + // Returns true if classic theme is in use. + bool IsClassicTheme(ThemeName name) const; + + // Gets our singleton instance. + static const NativeThemeWin* instance(); + + HRESULT PaintTextField(HDC hdc, + int part_id, + int state_id, + int classic_state, + RECT* rect, + COLORREF color, + bool fill_content_area, + bool draw_edges) const; + + private: + NativeThemeWin(); + ~NativeThemeWin(); + + // NativeTheme Implementation: + virtual gfx::Size GetPartSize(Part part, + State state, + const ExtraParams& extra) const OVERRIDE; + virtual void Paint(SkCanvas* canvas, + Part part, + State state, + const gfx::Rect& rect, + const ExtraParams& extra) const OVERRIDE; + virtual SkColor GetSystemColor(ColorId color_id) const OVERRIDE; + + void PaintToNonPlatformCanvas(SkCanvas* canvas, + Part part, + State state, + const gfx::Rect& rect, + const ExtraParams& extra) const; + + HRESULT GetThemePartSize(ThemeName themeName, + HDC hdc, + int part_id, + int state_id, + RECT* rect, + int ts, + SIZE* size) const; + + HRESULT PaintButton(HDC hdc, + State state, + const ButtonExtraParams& extra, + int part_id, + int state_id, + RECT* rect) const; + + HRESULT PaintMenuSeparator(HDC hdc, + const gfx::Rect& rect, + const MenuSeparatorExtraParams& extra) const; + + HRESULT PaintMenuGutter(HDC hdc, const gfx::Rect& rect) const; + + // |arrow_direction| determines whether the arrow is pointing to the left or + // to the right. In RTL locales, sub-menus open from right to left and + // therefore the menu arrow should point to the left and not to the right. + HRESULT PaintMenuArrow(HDC hdc, + State state, + const gfx::Rect& rect, + const MenuArrowExtraParams& extra) const; + + HRESULT PaintMenuBackground(HDC hdc, const gfx::Rect& rect) const; + + HRESULT PaintMenuCheck(HDC hdc, + State state, + const gfx::Rect& rect, + const MenuCheckExtraParams& extra) const; + + HRESULT PaintMenuCheckBackground(HDC hdc, + State state, + const gfx::Rect& rect) const; + + HRESULT PaintMenuItemBackground(HDC hdc, + State state, + const gfx::Rect& rect, + const MenuItemExtraParams& extra) const; + + HRESULT PaintPushButton(HDC hdc, + Part part, + State state, + const gfx::Rect& rect, + const ButtonExtraParams& extra) const; + + HRESULT PaintRadioButton(HDC hdc, + Part part, + State state, + const gfx::Rect& rect, + const ButtonExtraParams& extra) const; + + HRESULT PaintCheckbox(HDC hdc, + Part part, + State state, + const gfx::Rect& rect, + const ButtonExtraParams& extra) const; + + HRESULT PaintMenuList(HDC hdc, + State state, + const gfx::Rect& rect, + const MenuListExtraParams& extra) const; + + // Paints a scrollbar arrow. |classic_state| should have the appropriate + // classic part number ORed in already. + HRESULT PaintScrollbarArrow(HDC hdc, + Part part, + State state, + const gfx::Rect& rect, + const ScrollbarArrowExtraParams& extra) const; + + HRESULT PaintScrollbarThumb(HDC hdc, + Part part, + State state, + const gfx::Rect& rect, + const ScrollbarThumbExtraParams& extra) const; + + // This method is deprecated and will be removed in the near future. + // Paints a scrollbar track section. |align_rect| is only used in classic + // mode, and makes sure the checkerboard pattern in |target_rect| is aligned + // with one presumed to be in |align_rect|. + HRESULT PaintScrollbarTrack(SkCanvas* canvas, + HDC hdc, + Part part, + State state, + const gfx::Rect& rect, + const ScrollbarTrackExtraParams& extra) const; + + HRESULT PaintSpinButton(HDC hdc, + Part part, + State state, + const gfx::Rect& rect, + const InnerSpinButtonExtraParams& extra) const; + + HRESULT PaintTrackbar(SkCanvas* canvas, + HDC hdc, + Part part, + State state, + const gfx::Rect& rect, + const TrackbarExtraParams& extra) const; + + HRESULT PaintProgressBar(HDC hdc, + const gfx::Rect& rect, + const ProgressBarExtraParams& extra) const; + + HRESULT PaintWindowResizeGripper(HDC hdc, const gfx::Rect& rect) const; + + HRESULT PaintTabPanelBackground(HDC hdc, const gfx::Rect& rect) const; + + HRESULT PaintTextField(HDC hdc, + Part part, + State state, + const gfx::Rect& rect, + const TextFieldExtraParams& extra) const; + + + // Get the windows theme name/part/state. These three helper functions are + // used only by GetPartSize(), as each of the corresponding PaintXXX() + // methods do further validation of the part and state that is required for + // getting the size. + static ThemeName GetThemeName(Part part); + static int GetWindowsPart(Part part, State state, const ExtraParams& extra); + static int GetWindowsState(Part part, State state, const ExtraParams& extra); + + HRESULT GetThemeInt(ThemeName theme, + int part_id, + int state_id, + int prop_id, + int *result) const; + + HRESULT PaintFrameControl(HDC hdc, + const gfx::Rect& rect, + UINT type, + UINT state, + bool is_selected, + State control_state) const; + + // Returns a handle to the theme data. + HANDLE GetThemeHandle(ThemeName theme_name) const; + + typedef HRESULT (WINAPI* DrawThemeBackgroundPtr)(HANDLE theme, + HDC hdc, + int part_id, + int state_id, + const RECT* rect, + const RECT* clip_rect); + typedef HRESULT (WINAPI* DrawThemeBackgroundExPtr)(HANDLE theme, + HDC hdc, + int part_id, + int state_id, + const RECT* rect, + const DTBGOPTS* opts); + typedef HRESULT (WINAPI* GetThemeColorPtr)(HANDLE hTheme, + int part_id, + int state_id, + int prop_id, + COLORREF* color); + typedef HRESULT (WINAPI* GetThemeContentRectPtr)(HANDLE hTheme, + HDC hdc, + int part_id, + int state_id, + const RECT* rect, + RECT* content_rect); + typedef HRESULT (WINAPI* GetThemePartSizePtr)(HANDLE hTheme, + HDC hdc, + int part_id, + int state_id, + RECT* rect, + int ts, + SIZE* size); + typedef HANDLE (WINAPI* OpenThemeDataPtr)(HWND window, + LPCWSTR class_list); + typedef HRESULT (WINAPI* CloseThemeDataPtr)(HANDLE theme); + + typedef void (WINAPI* SetThemeAppPropertiesPtr) (DWORD flags); + typedef BOOL (WINAPI* IsThemeActivePtr)(); + typedef HRESULT (WINAPI* GetThemeIntPtr)(HANDLE hTheme, + int part_id, + int state_id, + int prop_id, + int *value); + + // Function pointers into uxtheme.dll. + DrawThemeBackgroundPtr draw_theme_; + DrawThemeBackgroundExPtr draw_theme_ex_; + GetThemeColorPtr get_theme_color_; + GetThemeContentRectPtr get_theme_content_rect_; + GetThemePartSizePtr get_theme_part_size_; + OpenThemeDataPtr open_theme_; + CloseThemeDataPtr close_theme_; + SetThemeAppPropertiesPtr set_theme_properties_; + IsThemeActivePtr is_theme_active_; + GetThemeIntPtr get_theme_int_; + + // Handle to uxtheme.dll. + HMODULE theme_dll_; + + // A cache of open theme handles. + mutable HANDLE theme_handles_[LAST]; + + DISALLOW_COPY_AND_ASSIGN(NativeThemeWin); +}; + +} // namespace ui + +#endif // UI_BASE_NATIVE_THEME_NATIVE_THEME_WIN_H_ diff --git a/ui/base/native_theme/native_theme_win_unittest.cc b/ui/base/native_theme/native_theme_win_unittest.cc new file mode 100644 index 0000000..b711921 --- /dev/null +++ b/ui/base/native_theme/native_theme_win_unittest.cc @@ -0,0 +1,15 @@ +// 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/base/native_theme/native_theme_win.h" + +#include "testing/gtest/include/gtest/gtest.h" + +namespace ui { + +TEST(NativeThemeTest, Init) { + ASSERT_TRUE(NativeThemeWin::instance() != NULL); +} + +} // namespace ui |