1 // Copyright (c) 2012 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.
5 #include "chrome/browser/ui/gtk/custom_button.h"
7 #include "base/basictypes.h"
8 #include "base/debug/trace_event.h"
9 #include "base/logging.h"
10 #include "chrome/browser/chrome_notification_types.h"
11 #include "chrome/browser/ui/gtk/gtk_chrome_button.h"
12 #include "chrome/browser/ui/gtk/gtk_theme_service.h"
13 #include "chrome/browser/ui/gtk/gtk_util.h"
14 #include "content/public/browser/notification_source.h"
15 #include "grit/theme_resources.h"
16 #include "grit/ui_resources.h"
17 #include "third_party/skia/include/core/SkBitmap.h"
18 #include "ui/base/resource/resource_bundle.h"
19 #include "ui/gfx/gtk_util.h"
20 #include "ui/gfx/image/cairo_cached_surface.h"
21 #include "ui/gfx/image/image.h"
22 #include "ui/gfx/skbitmap_operations.h"
26 GdkPixbuf
* GetImage(int resource_id
) {
29 return ui::ResourceBundle::GetSharedInstance().GetNativeImageNamed(
30 resource_id
, ui::ResourceBundle::RTL_ENABLED
).ToGdkPixbuf();
35 CustomDrawButtonBase::CustomDrawButtonBase(GtkThemeService
* theme_provider
,
40 : paint_override_(-1),
41 normal_id_(normal_id
),
42 pressed_id_(pressed_id
),
44 disabled_id_(disabled_id
),
45 theme_service_(theme_provider
),
47 for (int i
= 0; i
< (GTK_STATE_INSENSITIVE
+ 1); ++i
)
48 surfaces_
[i
].reset(new gfx::CairoCachedSurface
);
49 background_image_
.reset(new gfx::CairoCachedSurface
);
52 // Load images by pretending that we got a BROWSER_THEME_CHANGED
54 theme_provider
->InitThemesFor(this);
57 chrome::NOTIFICATION_BROWSER_THEME_CHANGED
,
58 content::Source
<ThemeService
>(theme_provider
));
60 // Load the button images from the resource bundle.
61 surfaces_
[GTK_STATE_NORMAL
]->UsePixbuf(GetImage(normal_id_
));
62 surfaces_
[GTK_STATE_ACTIVE
]->UsePixbuf(GetImage(pressed_id_
));
63 surfaces_
[GTK_STATE_PRELIGHT
]->UsePixbuf(GetImage(hover_id_
));
64 surfaces_
[GTK_STATE_SELECTED
]->UsePixbuf(NULL
);
65 surfaces_
[GTK_STATE_INSENSITIVE
]->UsePixbuf(GetImage(disabled_id_
));
69 CustomDrawButtonBase::~CustomDrawButtonBase() {
72 int CustomDrawButtonBase::Width() const {
73 return surfaces_
[0]->Width();
76 int CustomDrawButtonBase::Height() const {
77 return surfaces_
[0]->Height();
80 gboolean
CustomDrawButtonBase::OnExpose(GtkWidget
* widget
,
82 gdouble hover_state
) {
83 TRACE_EVENT0("ui::gtk", "CustomDrawButtonBase::OnExpose");
84 int paint_state
= paint_override_
>= 0 ?
85 paint_override_
: gtk_widget_get_state(widget
);
87 // If the paint state is PRELIGHT then set it to NORMAL (we will paint the
88 // hover state according to |hover_state_|).
89 if (paint_state
== GTK_STATE_PRELIGHT
)
90 paint_state
= GTK_STATE_NORMAL
;
91 bool animating_hover
= hover_state
> 0.0 &&
92 paint_state
== GTK_STATE_NORMAL
;
93 gfx::CairoCachedSurface
* pixbuf
= PixbufForState(paint_state
);
94 gfx::CairoCachedSurface
* hover_pixbuf
= PixbufForState(GTK_STATE_PRELIGHT
);
96 if (!pixbuf
|| !pixbuf
->valid())
98 if (animating_hover
&& (!hover_pixbuf
|| !hover_pixbuf
->valid()))
101 cairo_t
* cairo_context
= gdk_cairo_create(GDK_DRAWABLE(
102 gtk_widget_get_window(widget
)));
103 GtkAllocation allocation
;
104 gtk_widget_get_allocation(widget
, &allocation
);
105 cairo_translate(cairo_context
, allocation
.x
, allocation
.y
);
108 // Horizontally flip the image for non-LTR/RTL reasons.
109 cairo_translate(cairo_context
, allocation
.width
, 0.0f
);
110 cairo_scale(cairo_context
, -1.0f
, 1.0f
);
113 // The widget might be larger than the pixbuf. Paint the pixbuf flush with the
114 // start of the widget (left for LTR, right for RTL) and its bottom.
115 gfx::Rect bounds
= gfx::Rect(0, 0, pixbuf
->Width(), 0);
116 int x
= gtk_util::MirroredLeftPointForRect(widget
, bounds
);
117 int y
= allocation
.height
- pixbuf
->Height();
119 if (background_image_
->valid()) {
120 background_image_
->SetSource(cairo_context
, widget
, x
, y
);
121 cairo_paint(cairo_context
);
124 pixbuf
->SetSource(cairo_context
, widget
, x
, y
);
125 cairo_paint(cairo_context
);
127 if (animating_hover
) {
128 hover_pixbuf
->SetSource(cairo_context
, widget
, x
, y
);
129 cairo_paint_with_alpha(cairo_context
, hover_state
);
132 cairo_destroy(cairo_context
);
134 GtkWidget
* child
= gtk_bin_get_child(GTK_BIN(widget
));
136 gtk_container_propagate_expose(GTK_CONTAINER(widget
), child
, e
);
141 void CustomDrawButtonBase::SetBackground(SkColor color
,
142 const SkBitmap
& image
,
143 const SkBitmap
& mask
) {
144 if (image
.isNull() || mask
.isNull()) {
145 if (background_image_
->valid()) {
146 background_image_
->UsePixbuf(NULL
);
150 SkBitmapOperations::CreateButtonBackground(color
, image
, mask
);
152 GdkPixbuf
* pixbuf
= gfx::GdkPixbufFromSkBitmap(img
);
153 background_image_
->UsePixbuf(pixbuf
);
154 g_object_unref(pixbuf
);
158 void CustomDrawButtonBase::Observe(int type
,
159 const content::NotificationSource
& source
,
160 const content::NotificationDetails
& details
) {
161 DCHECK(theme_service_
);
162 DCHECK(chrome::NOTIFICATION_BROWSER_THEME_CHANGED
== type
);
164 surfaces_
[GTK_STATE_NORMAL
]->UsePixbuf(normal_id_
?
165 theme_service_
->GetRTLEnabledPixbufNamed(normal_id_
) : NULL
);
166 surfaces_
[GTK_STATE_ACTIVE
]->UsePixbuf(pressed_id_
?
167 theme_service_
->GetRTLEnabledPixbufNamed(pressed_id_
) : NULL
);
168 surfaces_
[GTK_STATE_PRELIGHT
]->UsePixbuf(hover_id_
?
169 theme_service_
->GetRTLEnabledPixbufNamed(hover_id_
) : NULL
);
170 surfaces_
[GTK_STATE_SELECTED
]->UsePixbuf(NULL
);
171 surfaces_
[GTK_STATE_INSENSITIVE
]->UsePixbuf(disabled_id_
?
172 theme_service_
->GetRTLEnabledPixbufNamed(disabled_id_
) : NULL
);
175 gfx::CairoCachedSurface
* CustomDrawButtonBase::PixbufForState(int state
) {
176 gfx::CairoCachedSurface
* pixbuf
= surfaces_
[state
].get();
178 // Fall back to the default image if we don't have one for this state.
179 if (!pixbuf
|| !pixbuf
->valid())
180 pixbuf
= surfaces_
[GTK_STATE_NORMAL
].get();
185 // CustomDrawHoverController ---------------------------------------------------
187 CustomDrawHoverController::CustomDrawHoverController(GtkWidget
* widget
)
188 : slide_animation_(this),
193 CustomDrawHoverController::CustomDrawHoverController()
194 : slide_animation_(this),
198 CustomDrawHoverController::~CustomDrawHoverController() {
201 void CustomDrawHoverController::Init(GtkWidget
* widget
) {
202 DCHECK(widget_
== NULL
);
204 g_signal_connect(widget_
, "enter-notify-event",
205 G_CALLBACK(OnEnterThunk
), this);
206 g_signal_connect(widget_
, "leave-notify-event",
207 G_CALLBACK(OnLeaveThunk
), this);
210 void CustomDrawHoverController::AnimationProgressed(
211 const gfx::Animation
* animation
) {
212 gtk_widget_queue_draw(widget_
);
215 gboolean
CustomDrawHoverController::OnEnter(
217 GdkEventCrossing
* event
) {
218 slide_animation_
.Show();
222 gboolean
CustomDrawHoverController::OnLeave(
224 GdkEventCrossing
* event
) {
225 // When the user is holding a mouse button, we don't want to animate.
226 if (event
->state
& (GDK_BUTTON1_MASK
| GDK_BUTTON2_MASK
| GDK_BUTTON3_MASK
))
227 slide_animation_
.Reset();
229 slide_animation_
.Hide();
233 // CustomDrawButton ------------------------------------------------------------
235 CustomDrawButton::CustomDrawButton(int normal_id
,
239 : button_base_(NULL
, normal_id
, pressed_id
, hover_id
, disabled_id
),
240 theme_service_(NULL
),
241 forcing_chrome_theme_(false) {
244 // Initialize the theme stuff with no theme_provider.
248 CustomDrawButton::CustomDrawButton(GtkThemeService
* theme_provider
,
253 const char* stock_id
,
254 GtkIconSize stock_size
)
255 : button_base_(theme_provider
, normal_id
, pressed_id
, hover_id
,
257 theme_service_(theme_provider
),
258 forcing_chrome_theme_(false) {
259 native_widget_
.Own(gtk_image_new_from_stock(stock_id
, stock_size
));
263 theme_service_
->InitThemesFor(this);
265 chrome::NOTIFICATION_BROWSER_THEME_CHANGED
,
266 content::Source
<ThemeService
>(theme_provider
));
269 CustomDrawButton::CustomDrawButton(GtkThemeService
* theme_provider
,
274 GtkWidget
* native_widget
)
275 : button_base_(theme_provider
, normal_id
, pressed_id
, hover_id
,
277 native_widget_(native_widget
),
278 theme_service_(theme_provider
),
279 forcing_chrome_theme_(false) {
282 theme_service_
->InitThemesFor(this);
284 chrome::NOTIFICATION_BROWSER_THEME_CHANGED
,
285 content::Source
<ThemeService
>(theme_provider
));
288 CustomDrawButton::~CustomDrawButton() {
290 native_widget_
.Destroy();
293 void CustomDrawButton::Init() {
294 widget_
.Own(gtk_chrome_button_new());
295 gtk_widget_set_can_focus(widget(), FALSE
);
296 g_signal_connect(widget(), "expose-event",
297 G_CALLBACK(OnCustomExposeThunk
), this);
298 hover_controller_
.Init(widget());
301 void CustomDrawButton::ForceChromeTheme() {
302 forcing_chrome_theme_
= true;
306 void CustomDrawButton::Observe(int type
,
307 const content::NotificationSource
& source
,
308 const content::NotificationDetails
& details
) {
309 DCHECK(chrome::NOTIFICATION_BROWSER_THEME_CHANGED
== type
);
313 GtkAllocation
CustomDrawButton::WidgetAllocation() const {
314 GtkAllocation allocation
;
315 gtk_widget_get_allocation(widget_
.get(), &allocation
);
319 int CustomDrawButton::SurfaceWidth() const {
320 return button_base_
.Width();
323 int CustomDrawButton::SurfaceHeight() const {
324 return button_base_
.Height();
327 void CustomDrawButton::SetPaintOverride(GtkStateType state
) {
328 button_base_
.set_paint_override(state
);
329 gtk_chrome_button_set_paint_state(GTK_CHROME_BUTTON(widget()), state
);
330 gtk_widget_queue_draw(widget());
333 void CustomDrawButton::UnsetPaintOverride() {
334 button_base_
.set_paint_override(-1);
335 gtk_chrome_button_unset_paint_state(GTK_CHROME_BUTTON(widget()));
336 gtk_widget_queue_draw(widget());
339 void CustomDrawButton::SetBackground(SkColor color
,
340 const SkBitmap
& image
,
341 const SkBitmap
& mask
) {
342 button_base_
.SetBackground(color
, image
, mask
);
345 gboolean
CustomDrawButton::OnCustomExpose(GtkWidget
* sender
,
347 UNSHIPPED_TRACE_EVENT0("ui::gtk", "CustomDrawButtonBase::OnCustomExpose");
349 // Continue processing this expose event.
352 double hover_state
= hover_controller_
.GetCurrentValue();
353 return button_base_
.OnExpose(sender
, e
, hover_state
);
358 CustomDrawButton
* CustomDrawButton::CloseButtonBar(
359 GtkThemeService
* theme_provider
) {
360 CustomDrawButton
* button
= new CustomDrawButton(theme_provider
,
361 IDR_CLOSE_1
, IDR_CLOSE_1_P
, IDR_CLOSE_1_H
, 0,
362 GTK_STOCK_CLOSE
, GTK_ICON_SIZE_MENU
);
367 CustomDrawButton
* CustomDrawButton::CloseButtonBubble(
368 GtkThemeService
* theme_provider
) {
369 CustomDrawButton
* button
= new CustomDrawButton(theme_provider
,
370 IDR_CLOSE_2
, IDR_CLOSE_2_P
, IDR_CLOSE_2_H
, 0,
371 GTK_STOCK_CLOSE
, GTK_ICON_SIZE_MENU
);
375 void CustomDrawButton::SetBrowserTheme() {
377 if (native_widget_
.get())
378 gtk_button_set_image(GTK_BUTTON(widget()), native_widget_
.get());
379 gtk_widget_set_size_request(widget(), -1, -1);
380 gtk_widget_set_app_paintable(widget(), FALSE
);
382 if (native_widget_
.get())
383 gtk_button_set_image(GTK_BUTTON(widget()), NULL
);
384 gtk_widget_set_size_request(widget(), button_base_
.Width(),
385 button_base_
.Height());
387 gtk_widget_set_app_paintable(widget(), TRUE
);
390 gtk_chrome_button_set_use_gtk_rendering(
391 GTK_CHROME_BUTTON(widget()), UseGtkTheme());
394 bool CustomDrawButton::UseGtkTheme() {
395 return !forcing_chrome_theme_
&& theme_service_
&&
396 theme_service_
->UsingNativeTheme();