gtk2_border.cc revision 5c02ac1a9c1b504631c0a3d2b6e737b5d738bae1
1// Copyright 2014 The Chromium Authors. All rights reserved. 2// Use of this source code is governed by a BSD-style license that can be 3// found in the LICENSE file. 4 5#include "chrome/browser/ui/libgtk2ui/gtk2_border.h" 6 7#include <gtk/gtk.h> 8 9#include "chrome/browser/ui/libgtk2ui/gtk2_ui.h" 10#include "chrome/browser/ui/libgtk2ui/native_theme_gtk2.h" 11#include "third_party/skia/include/effects/SkLerpXfermode.h" 12#include "ui/base/theme_provider.h" 13#include "ui/gfx/animation/animation.h" 14#include "ui/gfx/canvas.h" 15#include "ui/gfx/image/image_skia_source.h" 16#include "ui/gfx/rect.h" 17#include "ui/gfx/skia_util.h" 18#include "ui/views/controls/button/label_button.h" 19#include "ui/views/native_theme_delegate.h" 20 21using views::Button; 22using views::NativeThemeDelegate; 23 24namespace libgtk2ui { 25 26namespace { 27 28const int kNumberOfFocusedStates = 2; 29 30GtkStateType GetGtkState(ui::NativeTheme::State state) { 31 switch (state) { 32 case ui::NativeTheme::kDisabled: return GTK_STATE_INSENSITIVE; 33 case ui::NativeTheme::kHovered: return GTK_STATE_PRELIGHT; 34 case ui::NativeTheme::kNormal: return GTK_STATE_NORMAL; 35 case ui::NativeTheme::kPressed: return GTK_STATE_ACTIVE; 36 case ui::NativeTheme::kMaxState: NOTREACHED() << "Unknown state: " << state; 37 } 38 return GTK_STATE_NORMAL; 39} 40 41class ButtonImageSkiaSource : public gfx::ImageSkiaSource { 42 public: 43 ButtonImageSkiaSource(const Gtk2UI* gtk2_ui, 44 const GtkStateType state, 45 const bool focused, 46 const gfx::Size& size) 47 : gtk2_ui_(gtk2_ui), 48 state_(state), 49 focused_(focused), 50 size_(size) { 51 } 52 53 virtual ~ButtonImageSkiaSource() { 54 } 55 56 virtual gfx::ImageSkiaRep GetImageForScale(float scale) OVERRIDE { 57 int w = size_.width() * scale; 58 int h = size_.height() * scale; 59 return gfx::ImageSkiaRep( 60 gtk2_ui_->DrawGtkButtonBorder(state_, focused_, w, h), scale); 61 } 62 63 private: 64 const Gtk2UI* gtk2_ui_; 65 const GtkStateType state_; 66 const bool focused_; 67 const gfx::Size size_; 68 69 DISALLOW_COPY_AND_ASSIGN(ButtonImageSkiaSource); 70}; 71 72} // namespace 73 74Gtk2Border::Gtk2Border(Gtk2UI* gtk2_ui, 75 views::LabelButton* owning_button) 76 : gtk2_ui_(gtk2_ui), 77 owning_button_(owning_button), 78 observer_manager_(this) { 79 observer_manager_.Add(NativeThemeGtk2::instance()); 80} 81 82Gtk2Border::~Gtk2Border() { 83} 84 85void Gtk2Border::Paint(const views::View& view, gfx::Canvas* canvas) { 86 DCHECK_EQ(&view, owning_button_); 87 const NativeThemeDelegate* native_theme_delegate = owning_button_; 88 gfx::Rect rect(native_theme_delegate->GetThemePaintRect()); 89 ui::NativeTheme::ExtraParams extra; 90 ui::NativeTheme::State state = native_theme_delegate->GetThemeState(&extra); 91 92 const gfx::Animation* animation = native_theme_delegate->GetThemeAnimation(); 93 if (animation && animation->is_animating()) { 94 // Linearly interpolate background and foreground painters during animation. 95 const SkRect sk_rect = gfx::RectToSkRect(rect); 96 canvas->sk_canvas()->saveLayer(&sk_rect, NULL); 97 state = native_theme_delegate->GetBackgroundThemeState(&extra); 98 PaintState(state, extra, rect, canvas); 99 100 SkPaint paint; 101 skia::RefPtr<SkXfermode> sk_lerp_xfer = 102 skia::AdoptRef(SkLerpXfermode::Create(animation->GetCurrentValue())); 103 paint.setXfermode(sk_lerp_xfer.get()); 104 canvas->sk_canvas()->saveLayer(&sk_rect, &paint); 105 state = native_theme_delegate->GetForegroundThemeState(&extra); 106 PaintState(state, extra, rect, canvas); 107 canvas->sk_canvas()->restore(); 108 109 canvas->sk_canvas()->restore(); 110 } else { 111 PaintState(state, extra, rect, canvas); 112 } 113} 114 115gfx::Insets Gtk2Border::GetInsets() const { 116 // On STYLE_TEXTUBTTON, we want the smaller insets so we can fit the GTK icon 117 // in the toolbar without cutting off the edges of the GTK image. 118 return gtk2_ui_->GetButtonInsets(); 119} 120 121gfx::Size Gtk2Border::GetMinimumSize() const { 122 gfx::Insets insets = GetInsets(); 123 return gfx::Size(insets.width(), insets.height()); 124} 125 126void Gtk2Border::OnNativeThemeUpdated(ui::NativeTheme* observed_theme) { 127 DCHECK_EQ(observed_theme, NativeThemeGtk2::instance()); 128 for (int i = 0; i < kNumberOfFocusedStates; ++i) { 129 for (int j = 0; j < views::Button::STATE_COUNT; ++j) { 130 button_images_[i][j] = gfx::ImageSkia(); 131 } 132 } 133 134 // Our owning view must have its layout invalidated because the insets could 135 // have changed. 136 owning_button_->InvalidateLayout(); 137} 138 139void Gtk2Border::PaintState(const ui::NativeTheme::State state, 140 const ui::NativeTheme::ExtraParams& extra, 141 const gfx::Rect& rect, 142 gfx::Canvas* canvas) { 143 bool focused = extra.button.is_focused; 144 Button::ButtonState views_state = Button::GetButtonStateFrom(state); 145 146 if (ShouldDrawBorder(focused, views_state)) { 147 gfx::ImageSkia* image = &button_images_[focused][views_state]; 148 149 if (image->isNull() || image->size() != rect.size()) { 150 GtkStateType gtk_state = GetGtkState(state); 151 *image = gfx::ImageSkia( 152 new ButtonImageSkiaSource(gtk2_ui_, gtk_state, focused, rect.size()), 153 rect.size()); 154 } 155 canvas->DrawImageInt(*image, rect.x(), rect.y()); 156 } 157} 158 159bool Gtk2Border::ShouldDrawBorder(bool focused, 160 views::Button::ButtonState state) { 161 // This logic should be kept in sync with the LabelButtonBorder constructor. 162 if (owning_button_->style() == Button::STYLE_BUTTON) { 163 return true; 164 } else if (owning_button_->style() == Button::STYLE_TEXTBUTTON) { 165 return focused == false && (state == Button::STATE_HOVERED || 166 state == Button::STATE_PRESSED); 167 } 168 169 return false; 170} 171 172} // namespace libgtk2ui 173