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