Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(379)

Side by Side Diff: chrome/browser/ui/gtk/tabs/tab_renderer_gtk.cc

Issue 231733005: Delete the GTK+ port of Chrome. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: Remerge to ToT Created 6 years, 8 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
(Empty)
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.
4
5 #include "chrome/browser/ui/gtk/tabs/tab_renderer_gtk.h"
6
7 #include <algorithm>
8 #include <utility>
9
10 #include "base/debug/trace_event.h"
11 #include "base/strings/utf_string_conversions.h"
12 #include "chrome/browser/chrome_notification_types.h"
13 #include "chrome/browser/defaults.h"
14 #include "chrome/browser/extensions/tab_helper.h"
15 #include "chrome/browser/favicon/favicon_tab_helper.h"
16 #include "chrome/browser/profiles/profile.h"
17 #include "chrome/browser/themes/theme_properties.h"
18 #include "chrome/browser/ui/browser.h"
19 #include "chrome/browser/ui/gtk/bookmarks/bookmark_utils_gtk.h"
20 #include "chrome/browser/ui/gtk/custom_button.h"
21 #include "chrome/browser/ui/gtk/gtk_theme_service.h"
22 #include "chrome/browser/ui/gtk/gtk_util.h"
23 #include "chrome/browser/ui/tab_contents/core_tab_helper.h"
24 #include "chrome/browser/ui/tabs/tab_utils.h"
25 #include "content/public/browser/notification_source.h"
26 #include "content/public/browser/web_contents.h"
27 #include "grit/generated_resources.h"
28 #include "grit/theme_resources.h"
29 #include "grit/ui_resources.h"
30 #include "skia/ext/image_operations.h"
31 #include "ui/base/gtk/gtk_screen_util.h"
32 #include "ui/base/l10n/l10n_util.h"
33 #include "ui/base/resource/resource_bundle.h"
34 #include "ui/gfx/animation/slide_animation.h"
35 #include "ui/gfx/animation/throb_animation.h"
36 #include "ui/gfx/canvas_skia_paint.h"
37 #include "ui/gfx/favicon_size.h"
38 #include "ui/gfx/gtk_compat.h"
39 #include "ui/gfx/gtk_util.h"
40 #include "ui/gfx/image/cairo_cached_surface.h"
41 #include "ui/gfx/image/image.h"
42 #include "ui/gfx/pango_util.h"
43 #include "ui/gfx/platform_font_pango.h"
44 #include "ui/gfx/skbitmap_operations.h"
45
46 using content::WebContents;
47
48 namespace {
49
50 const int kFontPixelSize = 12;
51 const int kLeftPadding = 16;
52 const int kTopPadding = 6;
53 const int kRightPadding = 15;
54 const int kBottomPadding = 5;
55 const int kFaviconTitleSpacing = 4;
56 const int kTitleCloseButtonSpacing = 5;
57 const int kStandardTitleWidth = 175;
58 const int kDropShadowOffset = 2;
59 const int kInactiveTabBackgroundOffsetY = 15;
60
61 // When a non-mini-tab becomes a mini-tab the width of the tab animates. If
62 // the width of a mini-tab is >= kMiniTabRendererAsNormalTabWidth then the tab
63 // is rendered as a normal tab. This is done to avoid having the title
64 // immediately disappear when transitioning a tab from normal to mini-tab.
65 const int kMiniTabRendererAsNormalTabWidth =
66 browser_defaults::kMiniTabWidth + 30;
67
68 // The tab images are designed to overlap the toolbar by 1 pixel. For now we
69 // don't actually overlap the toolbar, so this is used to know how many pixels
70 // at the bottom of the tab images are to be ignored.
71 const int kToolbarOverlap = 1;
72
73 // How long the hover state takes.
74 const int kHoverDurationMs = 90;
75
76 // How opaque to make the hover state (out of 1).
77 const double kHoverOpacity = 0.33;
78
79 // Opacity for non-active selected tabs.
80 const double kSelectedTabOpacity = 0.45;
81
82 // Selected (but not active) tabs have their throb value scaled down by this.
83 const double kSelectedTabThrobScale = 0.5;
84
85 // Max opacity for the mini-tab title change animation.
86 const double kMiniTitleChangeThrobOpacity = 0.75;
87
88 // Duration for when the title of an inactive mini-tab changes.
89 const int kMiniTitleChangeThrobDuration = 1000;
90
91 // The horizontal offset used to position the close button in the tab.
92 const int kCloseButtonHorzFuzz = 4;
93
94 // Gets the bounds of |widget| relative to |parent|.
95 gfx::Rect GetWidgetBoundsRelativeToParent(GtkWidget* parent,
96 GtkWidget* widget) {
97 gfx::Rect bounds = ui::GetWidgetScreenBounds(widget);
98 bounds.Offset(-ui::GetWidgetScreenOffset(parent));
99 return bounds;
100 }
101
102 // Returns a GdkPixbuf after resizing the SkBitmap as necessary to the
103 // specified desired width and height. Caller must g_object_unref the returned
104 // pixbuf when no longer used.
105 GdkPixbuf* GetResizedGdkPixbufFromSkBitmap(const SkBitmap& bitmap,
106 int dest_w,
107 int dest_h) {
108 float float_dest_w = static_cast<float>(dest_w);
109 float float_dest_h = static_cast<float>(dest_h);
110 int bitmap_w = bitmap.width();
111 int bitmap_h = bitmap.height();
112
113 // Scale proportionately.
114 float scale = std::min(float_dest_w / bitmap_w,
115 float_dest_h / bitmap_h);
116 int final_dest_w = static_cast<int>(bitmap_w * scale);
117 int final_dest_h = static_cast<int>(bitmap_h * scale);
118
119 GdkPixbuf* pixbuf;
120 if (final_dest_w == bitmap_w && final_dest_h == bitmap_h) {
121 pixbuf = gfx::GdkPixbufFromSkBitmap(bitmap);
122 } else {
123 SkBitmap resized_icon = skia::ImageOperations::Resize(
124 bitmap,
125 skia::ImageOperations::RESIZE_BETTER,
126 final_dest_w, final_dest_h);
127 pixbuf = gfx::GdkPixbufFromSkBitmap(resized_icon);
128 }
129 return pixbuf;
130 }
131
132 } // namespace
133
134 TabRendererGtk::LoadingAnimation::Data::Data(
135 GtkThemeService* theme_service) {
136 // The loading animation image is a strip of states. Each state must be
137 // square, so the height must divide the width evenly.
138 SkBitmap loading_animation_frames =
139 theme_service->GetImageNamed(IDR_THROBBER).AsBitmap();
140 DCHECK(!loading_animation_frames.isNull());
141 DCHECK_EQ(loading_animation_frames.width() %
142 loading_animation_frames.height(), 0);
143 loading_animation_frame_count =
144 loading_animation_frames.width() /
145 loading_animation_frames.height();
146
147 SkBitmap waiting_animation_frames =
148 theme_service->GetImageNamed(IDR_THROBBER_WAITING).AsBitmap();
149 DCHECK(!waiting_animation_frames.isNull());
150 DCHECK_EQ(waiting_animation_frames.width() %
151 waiting_animation_frames.height(), 0);
152 waiting_animation_frame_count =
153 waiting_animation_frames.width() /
154 waiting_animation_frames.height();
155
156 waiting_to_loading_frame_count_ratio =
157 waiting_animation_frame_count /
158 loading_animation_frame_count;
159 // TODO(beng): eventually remove this when we have a proper themeing system.
160 // themes not supporting IDR_THROBBER_WAITING are causing this
161 // value to be 0 which causes DIV0 crashes. The value of 5
162 // matches the current bitmaps in our source.
163 if (waiting_to_loading_frame_count_ratio == 0)
164 waiting_to_loading_frame_count_ratio = 5;
165 }
166
167 TabRendererGtk::LoadingAnimation::Data::Data(
168 int loading, int waiting, int waiting_to_loading)
169 : loading_animation_frame_count(loading),
170 waiting_animation_frame_count(waiting),
171 waiting_to_loading_frame_count_ratio(waiting_to_loading) {
172 }
173
174 bool TabRendererGtk::initialized_ = false;
175 int TabRendererGtk::tab_active_l_width_ = 0;
176 int TabRendererGtk::tab_active_l_height_ = 0;
177 int TabRendererGtk::tab_inactive_l_height_ = 0;
178 gfx::Font* TabRendererGtk::title_font_ = NULL;
179 int TabRendererGtk::title_font_height_ = 0;
180 int TabRendererGtk::close_button_width_ = 0;
181 int TabRendererGtk::close_button_height_ = 0;
182
183 ////////////////////////////////////////////////////////////////////////////////
184 // TabRendererGtk::LoadingAnimation, public:
185 //
186 TabRendererGtk::LoadingAnimation::LoadingAnimation(
187 GtkThemeService* theme_service)
188 : data_(new Data(theme_service)),
189 theme_service_(theme_service),
190 animation_state_(ANIMATION_NONE),
191 animation_frame_(0) {
192 registrar_.Add(this,
193 chrome::NOTIFICATION_BROWSER_THEME_CHANGED,
194 content::Source<ThemeService>(theme_service_));
195 }
196
197 TabRendererGtk::LoadingAnimation::LoadingAnimation(
198 const LoadingAnimation::Data& data)
199 : data_(new Data(data)),
200 theme_service_(NULL),
201 animation_state_(ANIMATION_NONE),
202 animation_frame_(0) {
203 }
204
205 TabRendererGtk::LoadingAnimation::~LoadingAnimation() {}
206
207 bool TabRendererGtk::LoadingAnimation::ValidateLoadingAnimation(
208 AnimationState animation_state) {
209 bool has_changed = false;
210 if (animation_state_ != animation_state) {
211 // The waiting animation is the reverse of the loading animation, but at a
212 // different rate - the following reverses and scales the animation_frame_
213 // so that the frame is at an equivalent position when going from one
214 // animation to the other.
215 if (animation_state_ == ANIMATION_WAITING &&
216 animation_state == ANIMATION_LOADING) {
217 animation_frame_ = data_->loading_animation_frame_count -
218 (animation_frame_ / data_->waiting_to_loading_frame_count_ratio);
219 }
220 animation_state_ = animation_state;
221 has_changed = true;
222 }
223
224 if (animation_state_ != ANIMATION_NONE) {
225 animation_frame_ = (animation_frame_ + 1) %
226 ((animation_state_ == ANIMATION_WAITING) ?
227 data_->waiting_animation_frame_count :
228 data_->loading_animation_frame_count);
229 has_changed = true;
230 } else {
231 animation_frame_ = 0;
232 }
233 return has_changed;
234 }
235
236 void TabRendererGtk::LoadingAnimation::Observe(
237 int type,
238 const content::NotificationSource& source,
239 const content::NotificationDetails& details) {
240 DCHECK(type == chrome::NOTIFICATION_BROWSER_THEME_CHANGED);
241 data_.reset(new Data(theme_service_));
242 }
243
244 TabRendererGtk::TabData::TabData()
245 : is_default_favicon(false),
246 loading(false),
247 crashed(false),
248 incognito(false),
249 show_icon(true),
250 mini(false),
251 blocked(false),
252 animating_mini_change(false),
253 app(false),
254 media_state(TAB_MEDIA_STATE_NONE),
255 previous_media_state(TAB_MEDIA_STATE_NONE) {
256 }
257
258 TabRendererGtk::TabData::~TabData() {}
259
260 ////////////////////////////////////////////////////////////////////////////////
261 // FaviconCrashAnimation
262 //
263 // A custom animation subclass to manage the favicon crash animation.
264 class TabRendererGtk::FaviconCrashAnimation : public gfx::LinearAnimation,
265 public gfx::AnimationDelegate {
266 public:
267 explicit FaviconCrashAnimation(TabRendererGtk* target)
268 : gfx::LinearAnimation(1000, 25, this),
269 target_(target) {
270 }
271 virtual ~FaviconCrashAnimation() {}
272
273 // gfx::Animation overrides:
274 virtual void AnimateToState(double state) OVERRIDE {
275 const double kHidingOffset = 27;
276
277 if (state < .5) {
278 target_->SetFaviconHidingOffset(
279 static_cast<int>(floor(kHidingOffset * 2.0 * state)));
280 } else {
281 target_->DisplayCrashedFavicon();
282 target_->SetFaviconHidingOffset(
283 static_cast<int>(
284 floor(kHidingOffset - ((state - .5) * 2.0 * kHidingOffset))));
285 }
286 }
287
288 // gfx::AnimationDelegate overrides:
289 virtual void AnimationCanceled(const gfx::Animation* animation) OVERRIDE {
290 target_->SetFaviconHidingOffset(0);
291 }
292
293 private:
294 TabRendererGtk* target_;
295
296 DISALLOW_COPY_AND_ASSIGN(FaviconCrashAnimation);
297 };
298
299 ////////////////////////////////////////////////////////////////////////////////
300 // TabRendererGtk, public:
301
302 TabRendererGtk::TabRendererGtk(GtkThemeService* theme_service)
303 : showing_icon_(false),
304 showing_media_indicator_(false),
305 showing_close_button_(false),
306 favicon_hiding_offset_(0),
307 should_display_crashed_favicon_(false),
308 animating_media_state_(TAB_MEDIA_STATE_NONE),
309 loading_animation_(theme_service),
310 background_offset_x_(0),
311 background_offset_y_(kInactiveTabBackgroundOffsetY),
312 theme_service_(theme_service),
313 close_button_color_(0),
314 is_active_(false),
315 selected_title_color_(SK_ColorBLACK),
316 unselected_title_color_(SkColorSetRGB(64, 64, 64)) {
317 InitResources();
318
319 theme_service_->InitThemesFor(this);
320 registrar_.Add(this, chrome::NOTIFICATION_BROWSER_THEME_CHANGED,
321 content::Source<ThemeService>(theme_service_));
322
323 tab_.Own(gtk_fixed_new());
324 gtk_widget_set_app_paintable(tab_.get(), TRUE);
325 g_signal_connect(tab_.get(), "expose-event",
326 G_CALLBACK(OnExposeEventThunk), this);
327 g_signal_connect(tab_.get(), "size-allocate",
328 G_CALLBACK(OnSizeAllocateThunk), this);
329 close_button_.reset(MakeCloseButton());
330 gtk_widget_show(tab_.get());
331
332 hover_animation_.reset(new gfx::SlideAnimation(this));
333 hover_animation_->SetSlideDuration(kHoverDurationMs);
334 }
335
336 TabRendererGtk::~TabRendererGtk() {
337 tab_.Destroy();
338 }
339
340 void TabRendererGtk::Observe(int type,
341 const content::NotificationSource& source,
342 const content::NotificationDetails& details) {
343 DCHECK(chrome::NOTIFICATION_BROWSER_THEME_CHANGED);
344 selected_title_color_ =
345 theme_service_->GetColor(ThemeProperties::COLOR_TAB_TEXT);
346 unselected_title_color_ =
347 theme_service_->GetColor(ThemeProperties::COLOR_BACKGROUND_TAB_TEXT);
348 }
349
350 void TabRendererGtk::UpdateData(WebContents* contents,
351 bool app,
352 bool loading_only) {
353 DCHECK(contents);
354 FaviconTabHelper* favicon_tab_helper =
355 FaviconTabHelper::FromWebContents(contents);
356
357 if (!loading_only) {
358 data_.title = contents->GetTitle();
359 data_.incognito = contents->GetBrowserContext()->IsOffTheRecord();
360
361 TabMediaState next_media_state;
362 if (contents->IsCrashed()) {
363 data_.crashed = true;
364 next_media_state = TAB_MEDIA_STATE_NONE;
365 } else {
366 data_.crashed = false;
367 next_media_state = chrome::GetTabMediaStateForContents(contents);
368 }
369 if (data_.media_state != next_media_state) {
370 data_.previous_media_state = data_.media_state;
371 data_.media_state = next_media_state;
372 }
373
374 data_.favicon = favicon_tab_helper->GetFavicon().AsBitmap();
375 data_.app = app;
376
377 // Make a cairo cached version of the favicon.
378 if (!data_.favicon.isNull()) {
379 // Instead of resizing the icon during each frame, create our resized
380 // icon resource now, send it to the xserver and use that each frame
381 // instead.
382
383 // For source images smaller than the favicon square, scale them as if
384 // they were padded to fit the favicon square, so we don't blow up tiny
385 // favicons into larger or nonproportional results.
386 GdkPixbuf* pixbuf = GetResizedGdkPixbufFromSkBitmap(data_.favicon,
387 gfx::kFaviconSize, gfx::kFaviconSize);
388 data_.cairo_favicon.UsePixbuf(pixbuf);
389 g_object_unref(pixbuf);
390 } else {
391 data_.cairo_favicon.Reset();
392 }
393
394 // This is kind of a hacky way to determine whether our icon is the default
395 // favicon. But the plumbing that would be necessary to do it right would
396 // be a good bit of work and would sully code for other platforms which
397 // don't care to custom-theme the favicon. Hopefully the default favicon
398 // will eventually be chromium-themable and this code will go away.
399 data_.is_default_favicon =
400 (data_.favicon.pixelRef() ==
401 ui::ResourceBundle::GetSharedInstance().GetImageNamed(
402 IDR_DEFAULT_FAVICON).AsBitmap().pixelRef());
403 }
404
405 // Loading state also involves whether we show the favicon, since that's where
406 // we display the throbber.
407 data_.loading = contents->IsLoading();
408 data_.show_icon = favicon_tab_helper->ShouldDisplayFavicon();
409 }
410
411 void TabRendererGtk::UpdateFromModel() {
412 // Force a layout, since the tab may have grown a favicon.
413 Layout();
414 SchedulePaint();
415
416 if (data_.crashed) {
417 if (!should_display_crashed_favicon_ && !IsPerformingCrashAnimation())
418 StartCrashAnimation();
419 } else {
420 if (IsPerformingCrashAnimation())
421 StopCrashAnimation();
422 ResetCrashedFavicon();
423 }
424
425 if (data_.media_state != data_.previous_media_state) {
426 data_.previous_media_state = data_.media_state;
427 if (data_.media_state != TAB_MEDIA_STATE_NONE)
428 animating_media_state_ = data_.media_state;
429 StartMediaIndicatorAnimation();
430 }
431 }
432
433 void TabRendererGtk::SetBlocked(bool blocked) {
434 if (data_.blocked == blocked)
435 return;
436 data_.blocked = blocked;
437 // TODO(zelidrag) bug 32399: Make tabs pulse on Linux as well.
438 }
439
440 bool TabRendererGtk::is_blocked() const {
441 return data_.blocked;
442 }
443
444 bool TabRendererGtk::IsActive() const {
445 return is_active_;
446 }
447
448 bool TabRendererGtk::IsSelected() const {
449 return true;
450 }
451
452 bool TabRendererGtk::IsVisible() const {
453 return gtk_widget_get_visible(tab_.get());
454 }
455
456 void TabRendererGtk::SetVisible(bool visible) const {
457 if (visible) {
458 gtk_widget_show(tab_.get());
459 if (data_.mini)
460 gtk_widget_show(close_button_->widget());
461 } else {
462 gtk_widget_hide_all(tab_.get());
463 }
464 }
465
466 bool TabRendererGtk::ValidateLoadingAnimation(AnimationState animation_state) {
467 return loading_animation_.ValidateLoadingAnimation(animation_state);
468 }
469
470 void TabRendererGtk::PaintFaviconArea(GtkWidget* widget, cairo_t* cr) {
471 DCHECK(ShouldShowIcon());
472
473 cairo_rectangle(cr,
474 x() + favicon_bounds_.x(),
475 y() + favicon_bounds_.y(),
476 favicon_bounds_.width(),
477 favicon_bounds_.height());
478 cairo_clip(cr);
479
480 // The tab is rendered into a windowless widget whose offset is at the
481 // coordinate event->area. Translate by these offsets so we can render at
482 // (0,0) to match Windows' rendering metrics.
483 cairo_matrix_t cairo_matrix;
484 cairo_matrix_init_translate(&cairo_matrix, x(), y());
485 cairo_set_matrix(cr, &cairo_matrix);
486
487 // Which background should we be painting?
488 int theme_id;
489 int offset_y = 0;
490 if (IsActive()) {
491 theme_id = IDR_THEME_TOOLBAR;
492 } else {
493 theme_id = data_.incognito ? IDR_THEME_TAB_BACKGROUND_INCOGNITO :
494 IDR_THEME_TAB_BACKGROUND;
495
496 if (!theme_service_->HasCustomImage(theme_id))
497 offset_y = background_offset_y_;
498 }
499
500 // Paint the background behind the favicon.
501 const gfx::Image tab_bg = theme_service_->GetImageNamed(theme_id);
502 tab_bg.ToCairo()->SetSource(cr, widget, -x(), -offset_y);
503 cairo_pattern_set_extend(cairo_get_source(cr), CAIRO_EXTEND_REPEAT);
504 cairo_rectangle(cr,
505 favicon_bounds_.x(), favicon_bounds_.y(),
506 favicon_bounds_.width(), favicon_bounds_.height());
507 cairo_fill(cr);
508
509 if (!IsActive()) {
510 double throb_value = GetThrobValue();
511 if (throb_value > 0) {
512 cairo_push_group(cr);
513 gfx::Image active_bg =
514 theme_service_->GetImageNamed(IDR_THEME_TOOLBAR);
515 active_bg.ToCairo()->SetSource(cr, widget, -x(), 0);
516 cairo_pattern_set_extend(cairo_get_source(cr), CAIRO_EXTEND_REPEAT);
517
518 cairo_rectangle(cr,
519 favicon_bounds_.x(), favicon_bounds_.y(),
520 favicon_bounds_.width(), favicon_bounds_.height());
521 cairo_fill(cr);
522
523 cairo_pop_group_to_source(cr);
524 cairo_paint_with_alpha(cr, throb_value);
525 }
526 }
527
528 PaintIcon(widget, cr);
529 }
530
531 void TabRendererGtk::MaybeAdjustLeftForMiniTab(gfx::Rect* icon_bounds) const {
532 if (!(mini() || data_.animating_mini_change) ||
533 bounds_.width() >= kMiniTabRendererAsNormalTabWidth)
534 return;
535 const int mini_delta = kMiniTabRendererAsNormalTabWidth - GetMiniWidth();
536 const int ideal_delta = bounds_.width() - GetMiniWidth();
537 const int ideal_x = (GetMiniWidth() - icon_bounds->width()) / 2;
538 icon_bounds->set_x(icon_bounds->x() + static_cast<int>(
539 (1 - static_cast<float>(ideal_delta) / static_cast<float>(mini_delta)) *
540 (ideal_x - icon_bounds->x())));
541 }
542
543 // static
544 gfx::Size TabRendererGtk::GetMinimumUnselectedSize() {
545 InitResources();
546
547 gfx::Size minimum_size;
548 minimum_size.set_width(kLeftPadding + kRightPadding);
549 // Since we use bitmap images, the real minimum height of the image is
550 // defined most accurately by the height of the end cap images.
551 minimum_size.set_height(tab_active_l_height_ - kToolbarOverlap);
552 return minimum_size;
553 }
554
555 // static
556 gfx::Size TabRendererGtk::GetMinimumSelectedSize() {
557 gfx::Size minimum_size = GetMinimumUnselectedSize();
558 minimum_size.set_width(kLeftPadding + gfx::kFaviconSize + kRightPadding);
559 return minimum_size;
560 }
561
562 // static
563 gfx::Size TabRendererGtk::GetStandardSize() {
564 gfx::Size standard_size = GetMinimumUnselectedSize();
565 standard_size.Enlarge(kFaviconTitleSpacing + kStandardTitleWidth, 0);
566 return standard_size;
567 }
568
569 // static
570 int TabRendererGtk::GetMiniWidth() {
571 return browser_defaults::kMiniTabWidth;
572 }
573
574 // static
575 int TabRendererGtk::GetContentHeight() {
576 // The height of the content of the Tab is the largest of the favicon,
577 // the title text and the close button graphic.
578 int content_height = std::max(gfx::kFaviconSize, title_font_height_);
579 return std::max(content_height, close_button_height_);
580 }
581
582 gfx::Rect TabRendererGtk::GetNonMirroredBounds(GtkWidget* parent) const {
583 // The tabstrip widget is a windowless widget so the tab widget's allocation
584 // is relative to the browser titlebar. We need the bounds relative to the
585 // tabstrip.
586 gfx::Rect bounds = GetWidgetBoundsRelativeToParent(parent, widget());
587 bounds.set_x(gtk_util::MirroredLeftPointForRect(parent, bounds));
588 return bounds;
589 }
590
591 gfx::Rect TabRendererGtk::GetRequisition() const {
592 return gfx::Rect(requisition_.x(), requisition_.y(),
593 requisition_.width(), requisition_.height());
594 }
595
596 void TabRendererGtk::StartMiniTabTitleAnimation() {
597 if (!mini_title_animation_.get()) {
598 mini_title_animation_.reset(new gfx::ThrobAnimation(this));
599 mini_title_animation_->SetThrobDuration(kMiniTitleChangeThrobDuration);
600 }
601
602 if (!mini_title_animation_->is_animating())
603 mini_title_animation_->StartThrobbing(-1);
604 }
605
606 void TabRendererGtk::StopMiniTabTitleAnimation() {
607 if (mini_title_animation_.get())
608 mini_title_animation_->Stop();
609 }
610
611 void TabRendererGtk::SetBounds(const gfx::Rect& bounds) {
612 requisition_ = bounds;
613 gtk_widget_set_size_request(tab_.get(), bounds.width(), bounds.height());
614 }
615
616 ////////////////////////////////////////////////////////////////////////////////
617 // TabRendererGtk, protected:
618
619 void TabRendererGtk::Raise() const {
620 if (gtk_button_get_event_window(GTK_BUTTON(close_button_->widget())))
621 gdk_window_raise(gtk_button_get_event_window(
622 GTK_BUTTON(close_button_->widget())));
623 }
624
625 base::string16 TabRendererGtk::GetTitle() const {
626 return data_.title;
627 }
628
629 ///////////////////////////////////////////////////////////////////////////////
630 // TabRendererGtk, gfx::AnimationDelegate implementation:
631
632 void TabRendererGtk::AnimationProgressed(const gfx::Animation* animation) {
633 gtk_widget_queue_draw(tab_.get());
634 }
635
636 void TabRendererGtk::AnimationCanceled(const gfx::Animation* animation) {
637 if (media_indicator_animation_ == animation)
638 animating_media_state_ = data_.media_state;
639 AnimationEnded(animation);
640 }
641
642 void TabRendererGtk::AnimationEnded(const gfx::Animation* animation) {
643 if (media_indicator_animation_ == animation)
644 animating_media_state_ = data_.media_state;
645 gtk_widget_queue_draw(tab_.get());
646 }
647
648 ////////////////////////////////////////////////////////////////////////////////
649 // TabRendererGtk, private:
650
651 void TabRendererGtk::StartCrashAnimation() {
652 if (!crash_animation_.get())
653 crash_animation_.reset(new FaviconCrashAnimation(this));
654 crash_animation_->Stop();
655 crash_animation_->Start();
656 }
657
658 void TabRendererGtk::StopCrashAnimation() {
659 if (!crash_animation_.get())
660 return;
661 crash_animation_->Stop();
662 }
663
664 bool TabRendererGtk::IsPerformingCrashAnimation() const {
665 return crash_animation_.get() && crash_animation_->is_animating();
666 }
667
668 void TabRendererGtk::StartMediaIndicatorAnimation() {
669 media_indicator_animation_ =
670 chrome::CreateTabMediaIndicatorFadeAnimation(data_.media_state);
671 media_indicator_animation_->set_delegate(this);
672 media_indicator_animation_->Start();
673 }
674
675 void TabRendererGtk::SetFaviconHidingOffset(int offset) {
676 favicon_hiding_offset_ = offset;
677 SchedulePaint();
678 }
679
680 void TabRendererGtk::DisplayCrashedFavicon() {
681 should_display_crashed_favicon_ = true;
682 }
683
684 void TabRendererGtk::ResetCrashedFavicon() {
685 should_display_crashed_favicon_ = false;
686 }
687
688 void TabRendererGtk::Paint(GtkWidget* widget, cairo_t* cr) {
689 // Don't paint if we're narrower than we can render correctly. (This should
690 // only happen during animations).
691 if (width() < GetMinimumUnselectedSize().width() && !mini())
692 return;
693
694 // See if the model changes whether the icons should be painted.
695 const bool show_icon = ShouldShowIcon();
696 const bool show_media_indicator = ShouldShowMediaIndicator();
697 const bool show_close_button = ShouldShowCloseBox();
698 if (show_icon != showing_icon_ ||
699 show_media_indicator != showing_media_indicator_ ||
700 show_close_button != showing_close_button_)
701 Layout();
702
703 PaintTabBackground(widget, cr);
704
705 if (!mini() || width() > kMiniTabRendererAsNormalTabWidth)
706 PaintTitle(widget, cr);
707
708 if (show_icon)
709 PaintIcon(widget, cr);
710
711 if (show_media_indicator)
712 PaintMediaIndicator(widget, cr);
713 }
714
715 cairo_surface_t* TabRendererGtk::PaintToSurface(GtkWidget* widget,
716 cairo_t* cr) {
717 cairo_surface_t* target = cairo_get_target(cr);
718 cairo_surface_t* out_surface = cairo_surface_create_similar(
719 target,
720 CAIRO_CONTENT_COLOR_ALPHA,
721 width(), height());
722
723 cairo_t* out_cr = cairo_create(out_surface);
724 Paint(widget, out_cr);
725 cairo_destroy(out_cr);
726
727 return out_surface;
728 }
729
730 void TabRendererGtk::SchedulePaint() {
731 gtk_widget_queue_draw(tab_.get());
732 }
733
734 gfx::Rect TabRendererGtk::GetLocalBounds() {
735 return gfx::Rect(0, 0, bounds_.width(), bounds_.height());
736 }
737
738 void TabRendererGtk::Layout() {
739 gfx::Rect local_bounds = GetLocalBounds();
740 if (local_bounds.IsEmpty())
741 return;
742 local_bounds.Inset(kLeftPadding, kTopPadding, kRightPadding, kBottomPadding);
743
744 // Figure out who is tallest.
745 int content_height = GetContentHeight();
746
747 // Size the Favicon.
748 showing_icon_ = ShouldShowIcon();
749 if (showing_icon_) {
750 int favicon_top = kTopPadding + (content_height - gfx::kFaviconSize) / 2;
751 favicon_bounds_.SetRect(local_bounds.x(), favicon_top,
752 gfx::kFaviconSize, gfx::kFaviconSize);
753 MaybeAdjustLeftForMiniTab(&favicon_bounds_);
754 } else {
755 favicon_bounds_.SetRect(local_bounds.x(), local_bounds.y(), 0, 0);
756 }
757
758 // Size the Close button.
759 showing_close_button_ = ShouldShowCloseBox();
760 if (showing_close_button_) {
761 int close_button_top = kTopPadding +
762 (content_height - close_button_height_) / 2;
763 int close_button_left =
764 local_bounds.right() - close_button_width_ + kCloseButtonHorzFuzz;
765 close_button_bounds_.SetRect(close_button_left,
766 close_button_top,
767 close_button_width_,
768 close_button_height_);
769
770 // If the close button color has changed, generate a new one.
771 if (theme_service_) {
772 SkColor tab_text_color =
773 theme_service_->GetColor(ThemeProperties::COLOR_TAB_TEXT);
774 if (!close_button_color_ || tab_text_color != close_button_color_) {
775 close_button_color_ = tab_text_color;
776 ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
777 close_button_->SetBackground(close_button_color_,
778 rb.GetImageNamed(IDR_CLOSE_1).AsBitmap(),
779 rb.GetImageNamed(IDR_CLOSE_1_MASK).AsBitmap());
780 }
781 }
782 } else {
783 close_button_bounds_.SetRect(0, 0, 0, 0);
784 }
785
786 showing_media_indicator_ = ShouldShowMediaIndicator();
787 if (showing_media_indicator_) {
788 const gfx::Image& media_indicator_image =
789 chrome::GetTabMediaIndicatorImage(animating_media_state_);
790 media_indicator_bounds_.set_width(media_indicator_image.Width());
791 media_indicator_bounds_.set_height(media_indicator_image.Height());
792 media_indicator_bounds_.set_y(
793 kTopPadding + (content_height - media_indicator_bounds_.height()) / 2);
794 const int right = showing_close_button_ ?
795 close_button_bounds_.x() : local_bounds.right();
796 media_indicator_bounds_.set_x(std::max(
797 local_bounds.x(), right - media_indicator_bounds_.width()));
798 MaybeAdjustLeftForMiniTab(&media_indicator_bounds_);
799 } else {
800 media_indicator_bounds_.SetRect(local_bounds.x(), local_bounds.y(), 0, 0);
801 }
802
803 if (!mini() || width() >= kMiniTabRendererAsNormalTabWidth) {
804 // Size the Title text to fill the remaining space.
805 int title_left = favicon_bounds_.right() + kFaviconTitleSpacing;
806 int title_top = kTopPadding;
807
808 // If the user has big fonts, the title will appear rendered too far down
809 // on the y-axis if we use the regular top padding, so we need to adjust it
810 // so that the text appears centered.
811 gfx::Size minimum_size = GetMinimumUnselectedSize();
812 int text_height = title_top + title_font_height_ + kBottomPadding;
813 if (text_height > minimum_size.height())
814 title_top -= (text_height - minimum_size.height()) / 2;
815
816 int title_width;
817 if (showing_media_indicator_) {
818 title_width = media_indicator_bounds_.x() - kTitleCloseButtonSpacing -
819 title_left;
820 } else if (close_button_bounds_.width() && close_button_bounds_.height()) {
821 title_width = close_button_bounds_.x() - kTitleCloseButtonSpacing -
822 title_left;
823 } else {
824 title_width = local_bounds.width() - title_left;
825 }
826 title_width = std::max(title_width, 0);
827 title_bounds_.SetRect(title_left, title_top, title_width, content_height);
828 }
829
830 favicon_bounds_.set_x(
831 gtk_util::MirroredLeftPointForRect(tab_.get(), favicon_bounds_));
832 media_indicator_bounds_.set_x(
833 gtk_util::MirroredLeftPointForRect(tab_.get(), media_indicator_bounds_));
834 close_button_bounds_.set_x(
835 gtk_util::MirroredLeftPointForRect(tab_.get(), close_button_bounds_));
836 title_bounds_.set_x(
837 gtk_util::MirroredLeftPointForRect(tab_.get(), title_bounds_));
838
839 MoveCloseButtonWidget();
840 }
841
842 void TabRendererGtk::MoveCloseButtonWidget() {
843 if (!close_button_bounds_.IsEmpty()) {
844 gtk_fixed_move(GTK_FIXED(tab_.get()), close_button_->widget(),
845 close_button_bounds_.x(), close_button_bounds_.y());
846 gtk_widget_show(close_button_->widget());
847 } else {
848 gtk_widget_hide(close_button_->widget());
849 }
850 }
851
852 void TabRendererGtk::PaintTab(GtkWidget* widget, GdkEventExpose* event) {
853 cairo_t* cr = gdk_cairo_create(gtk_widget_get_window(widget));
854 gdk_cairo_rectangle(cr, &event->area);
855 cairo_clip(cr);
856
857 // The tab is rendered into a windowless widget whose offset is at the
858 // coordinate event->area. Translate by these offsets so we can render at
859 // (0,0) to match Windows' rendering metrics.
860 cairo_matrix_t cairo_matrix;
861 cairo_matrix_init_translate(&cairo_matrix, event->area.x, event->area.y);
862 cairo_set_matrix(cr, &cairo_matrix);
863
864 // Save the original x offset so we can position background images properly.
865 background_offset_x_ = event->area.x;
866
867 Paint(widget, cr);
868 cairo_destroy(cr);
869 }
870
871 void TabRendererGtk::PaintTitle(GtkWidget* widget, cairo_t* cr) {
872 if (title_bounds_.IsEmpty())
873 return;
874
875 // Paint the Title.
876 base::string16 title = data_.title;
877 if (title.empty()) {
878 title = data_.loading ?
879 l10n_util::GetStringUTF16(IDS_TAB_LOADING_TITLE) :
880 CoreTabHelper::GetDefaultTitle();
881 } else {
882 Browser::FormatTitleForDisplay(&title);
883 }
884
885 GtkAllocation allocation;
886 gtk_widget_get_allocation(widget, &allocation);
887 gfx::Rect bounds(allocation);
888
889 // Draw the text directly onto the Cairo context. This is necessary for
890 // getting the draw order correct, and automatically applying transformations
891 // such as scaling when a tab is detached.
892 gfx::CanvasSkiaPaintCairo canvas(cr, bounds.size(), true);
893
894 SkColor title_color = IsSelected() ? selected_title_color_
895 : unselected_title_color_;
896
897 // Disable subpixel rendering. This does not work because the canvas has a
898 // transparent background.
899 int flags = gfx::Canvas::NO_ELLIPSIS | gfx::Canvas::NO_SUBPIXEL_RENDERING;
900 canvas.DrawFadeTruncatingStringRectWithFlags(
901 title, gfx::Canvas::TruncateFadeTail, gfx::FontList(*title_font_),
902 title_color, title_bounds_, flags);
903 }
904
905 void TabRendererGtk::PaintIcon(GtkWidget* widget, cairo_t* cr) {
906 if (loading_animation_.animation_state() != ANIMATION_NONE) {
907 PaintLoadingAnimation(widget, cr);
908 return;
909 }
910
911 gfx::CairoCachedSurface* to_display = NULL;
912 if (should_display_crashed_favicon_) {
913 to_display = theme_service_->GetImageNamed(IDR_SAD_FAVICON).ToCairo();
914 } else if (!data_.favicon.isNull()) {
915 if (data_.is_default_favicon && theme_service_->UsingNativeTheme()) {
916 to_display = GtkThemeService::GetDefaultFavicon(true).ToCairo();
917 } else if (data_.cairo_favicon.valid()) {
918 to_display = &data_.cairo_favicon;
919 }
920 }
921
922 if (to_display) {
923 to_display->SetSource(cr, widget, favicon_bounds_.x(),
924 favicon_bounds_.y() + favicon_hiding_offset_);
925 cairo_paint(cr);
926 }
927 }
928
929 void TabRendererGtk::PaintMediaIndicator(GtkWidget* widget, cairo_t* cr) {
930 if (media_indicator_bounds_.IsEmpty() || !media_indicator_animation_)
931 return;
932
933 double opaqueness = media_indicator_animation_->GetCurrentValue();
934 if (data_.media_state == TAB_MEDIA_STATE_NONE)
935 opaqueness = 1.0 - opaqueness; // Fading out, not in.
936
937 const gfx::Image& media_indicator_image =
938 chrome::GetTabMediaIndicatorImage(animating_media_state_);
939 media_indicator_image.ToCairo()->SetSource(
940 cr, widget, media_indicator_bounds_.x(), media_indicator_bounds_.y());
941 cairo_paint_with_alpha(cr, opaqueness);
942 }
943
944 void TabRendererGtk::PaintTabBackground(GtkWidget* widget, cairo_t* cr) {
945 if (IsActive()) {
946 PaintActiveTabBackground(widget, cr);
947 } else {
948 PaintInactiveTabBackground(widget, cr);
949
950 double throb_value = GetThrobValue();
951 if (throb_value > 0) {
952 cairo_push_group(cr);
953 PaintActiveTabBackground(widget, cr);
954 cairo_pop_group_to_source(cr);
955 cairo_paint_with_alpha(cr, throb_value);
956 }
957 }
958 }
959
960 void TabRendererGtk::DrawTabBackground(
961 cairo_t* cr,
962 GtkWidget* widget,
963 const gfx::Image& tab_bg,
964 int offset_x,
965 int offset_y) {
966 tab_bg.ToCairo()->SetSource(cr, widget, -offset_x, -offset_y);
967 cairo_pattern_set_extend(cairo_get_source(cr), CAIRO_EXTEND_REPEAT);
968
969 ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
970
971 // Draw left edge
972 gfx::Image& tab_l_mask = rb.GetNativeImageNamed(IDR_TAB_ALPHA_LEFT);
973 tab_l_mask.ToCairo()->MaskSource(cr, widget, 0, 0);
974
975 // Draw center
976 cairo_rectangle(cr,
977 tab_active_l_width_, kDropShadowOffset,
978 width() - (2 * tab_active_l_width_),
979 tab_inactive_l_height_);
980 cairo_fill(cr);
981
982 // Draw right edge
983 gfx::Image& tab_r_mask = rb.GetNativeImageNamed(IDR_TAB_ALPHA_RIGHT);
984 tab_r_mask.ToCairo()->MaskSource(cr, widget,
985 width() - tab_active_l_width_, 0);
986 }
987
988 void TabRendererGtk::DrawTabShadow(cairo_t* cr,
989 GtkWidget* widget,
990 int left_idr,
991 int center_idr,
992 int right_idr) {
993 ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
994 gfx::Image& active_image_l = rb.GetNativeImageNamed(left_idr);
995 gfx::Image& active_image_c = rb.GetNativeImageNamed(center_idr);
996 gfx::Image& active_image_r = rb.GetNativeImageNamed(right_idr);
997
998 // Draw left drop shadow
999 active_image_l.ToCairo()->SetSource(cr, widget, 0, 0);
1000 cairo_paint(cr);
1001
1002 // Draw the center shadow
1003 active_image_c.ToCairo()->SetSource(cr, widget, 0, 0);
1004 cairo_pattern_set_extend(cairo_get_source(cr), CAIRO_EXTEND_REPEAT);
1005 cairo_rectangle(cr, tab_active_l_width_, 0,
1006 width() - (2 * tab_active_l_width_),
1007 height());
1008 cairo_fill(cr);
1009
1010 // Draw right drop shadow
1011 active_image_r.ToCairo()->SetSource(
1012 cr, widget, width() - active_image_r.ToCairo()->Width(), 0);
1013 cairo_paint(cr);
1014 }
1015
1016 void TabRendererGtk::PaintInactiveTabBackground(GtkWidget* widget,
1017 cairo_t* cr) {
1018 int theme_id = data_.incognito ?
1019 IDR_THEME_TAB_BACKGROUND_INCOGNITO : IDR_THEME_TAB_BACKGROUND;
1020
1021 gfx::Image tab_bg = theme_service_->GetImageNamed(theme_id);
1022
1023 // If the theme is providing a custom background image, then its top edge
1024 // should be at the top of the tab. Otherwise, we assume that the background
1025 // image is a composited foreground + frame image.
1026 int offset_y = theme_service_->HasCustomImage(theme_id) ?
1027 0 : background_offset_y_;
1028
1029 DrawTabBackground(cr, widget, tab_bg, background_offset_x_, offset_y);
1030
1031 DrawTabShadow(cr, widget, IDR_TAB_INACTIVE_LEFT, IDR_TAB_INACTIVE_CENTER,
1032 IDR_TAB_INACTIVE_RIGHT);
1033 }
1034
1035 void TabRendererGtk::PaintActiveTabBackground(GtkWidget* widget,
1036 cairo_t* cr) {
1037 gfx::Image tab_bg = theme_service_->GetImageNamed(IDR_THEME_TOOLBAR);
1038
1039 DrawTabBackground(cr, widget, tab_bg, background_offset_x_, 0);
1040 DrawTabShadow(cr, widget, IDR_TAB_ACTIVE_LEFT, IDR_TAB_ACTIVE_CENTER,
1041 IDR_TAB_ACTIVE_RIGHT);
1042 }
1043
1044 void TabRendererGtk::PaintLoadingAnimation(GtkWidget* widget,
1045 cairo_t* cr) {
1046 int id = loading_animation_.animation_state() == ANIMATION_WAITING ?
1047 IDR_THROBBER_WAITING : IDR_THROBBER;
1048 gfx::Image throbber = theme_service_->GetImageNamed(id);
1049
1050 const int image_size = throbber.ToCairo()->Height();
1051 const int image_offset = loading_animation_.animation_frame() * image_size;
1052 DCHECK(image_size == favicon_bounds_.height());
1053 DCHECK(image_size == favicon_bounds_.width());
1054
1055 throbber.ToCairo()->SetSource(cr, widget, favicon_bounds_.x() - image_offset,
1056 favicon_bounds_.y());
1057 cairo_rectangle(cr, favicon_bounds_.x(), favicon_bounds_.y(),
1058 image_size, image_size);
1059 cairo_fill(cr);
1060 }
1061
1062 int TabRendererGtk::IconCapacity() const {
1063 if (height() < GetMinimumUnselectedSize().height())
1064 return 0;
1065 const int available_width =
1066 std::max(0, width() - kLeftPadding - kRightPadding);
1067 const int kPaddingBetweenIcons = 2;
1068 if (available_width >= gfx::kFaviconSize &&
1069 available_width < (gfx::kFaviconSize + kPaddingBetweenIcons)) {
1070 return 1;
1071 }
1072 return available_width / (gfx::kFaviconSize + kPaddingBetweenIcons);
1073 }
1074
1075 bool TabRendererGtk::ShouldShowIcon() const {
1076 return chrome::ShouldTabShowFavicon(
1077 IconCapacity(), mini(), IsActive(), data_.show_icon,
1078 animating_media_state_);
1079 }
1080
1081 bool TabRendererGtk::ShouldShowMediaIndicator() const {
1082 return chrome::ShouldTabShowMediaIndicator(
1083 IconCapacity(), mini(), IsActive(), data_.show_icon,
1084 animating_media_state_);
1085 }
1086
1087 bool TabRendererGtk::ShouldShowCloseBox() const {
1088 return chrome::ShouldTabShowCloseButton(IconCapacity(), mini(), IsActive());
1089 }
1090
1091 CustomDrawButton* TabRendererGtk::MakeCloseButton() {
1092 CustomDrawButton* button = CustomDrawButton::CloseButtonBar(theme_service_);
1093 button->ForceChromeTheme();
1094
1095 g_signal_connect(button->widget(), "clicked",
1096 G_CALLBACK(OnCloseButtonClickedThunk), this);
1097 g_signal_connect(button->widget(), "button-release-event",
1098 G_CALLBACK(OnCloseButtonMouseReleaseThunk), this);
1099 g_signal_connect(button->widget(), "enter-notify-event",
1100 G_CALLBACK(OnEnterNotifyEventThunk), this);
1101 g_signal_connect(button->widget(), "leave-notify-event",
1102 G_CALLBACK(OnLeaveNotifyEventThunk), this);
1103 gtk_widget_set_can_focus(button->widget(), FALSE);
1104 gtk_fixed_put(GTK_FIXED(tab_.get()), button->widget(), 0, 0);
1105
1106 return button;
1107 }
1108
1109 double TabRendererGtk::GetThrobValue() {
1110 bool is_selected = IsSelected();
1111 double min = is_selected ? kSelectedTabOpacity : 0;
1112 double scale = is_selected ? kSelectedTabThrobScale : 1;
1113
1114 if (mini_title_animation_.get() && mini_title_animation_->is_animating()) {
1115 return mini_title_animation_->GetCurrentValue() *
1116 kMiniTitleChangeThrobOpacity * scale + min;
1117 }
1118
1119 if (hover_animation_.get())
1120 return kHoverOpacity * hover_animation_->GetCurrentValue() * scale + min;
1121
1122 return is_selected ? kSelectedTabOpacity : 0;
1123 }
1124
1125 void TabRendererGtk::CloseButtonClicked() {
1126 // Nothing to do.
1127 }
1128
1129 void TabRendererGtk::OnCloseButtonClicked(GtkWidget* widget) {
1130 CloseButtonClicked();
1131 }
1132
1133 gboolean TabRendererGtk::OnCloseButtonMouseRelease(GtkWidget* widget,
1134 GdkEventButton* event) {
1135 if (event->button == 2) {
1136 CloseButtonClicked();
1137 return TRUE;
1138 }
1139
1140 return FALSE;
1141 }
1142
1143 gboolean TabRendererGtk::OnExposeEvent(GtkWidget* widget,
1144 GdkEventExpose* event) {
1145 TRACE_EVENT0("ui::gtk", "TabRendererGtk::OnExposeEvent");
1146
1147 PaintTab(widget, event);
1148 gtk_container_propagate_expose(GTK_CONTAINER(tab_.get()),
1149 close_button_->widget(), event);
1150 return TRUE;
1151 }
1152
1153 void TabRendererGtk::OnSizeAllocate(GtkWidget* widget,
1154 GtkAllocation* allocation) {
1155 gfx::Rect bounds = gfx::Rect(allocation->x, allocation->y,
1156 allocation->width, allocation->height);
1157
1158 // Nothing to do if the bounds are the same. If we don't catch this, we'll
1159 // get an infinite loop of size-allocate signals.
1160 if (bounds_ == bounds)
1161 return;
1162
1163 bounds_ = bounds;
1164 Layout();
1165 }
1166
1167 gboolean TabRendererGtk::OnEnterNotifyEvent(GtkWidget* widget,
1168 GdkEventCrossing* event) {
1169 hover_animation_->SetTweenType(gfx::Tween::EASE_OUT);
1170 hover_animation_->Show();
1171 return FALSE;
1172 }
1173
1174 gboolean TabRendererGtk::OnLeaveNotifyEvent(GtkWidget* widget,
1175 GdkEventCrossing* event) {
1176 hover_animation_->SetTweenType(gfx::Tween::EASE_IN);
1177 hover_animation_->Hide();
1178 return FALSE;
1179 }
1180
1181 // static
1182 void TabRendererGtk::InitResources() {
1183 if (initialized_)
1184 return;
1185
1186 // Grab the pixel sizes of our masking images.
1187 ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
1188 GdkPixbuf* tab_active_l = rb.GetNativeImageNamed(
1189 IDR_TAB_ACTIVE_LEFT).ToGdkPixbuf();
1190 tab_active_l_width_ = gdk_pixbuf_get_width(tab_active_l);
1191 tab_active_l_height_ = gdk_pixbuf_get_height(tab_active_l);
1192
1193 GdkPixbuf* tab_inactive_l = rb.GetNativeImageNamed(
1194 IDR_TAB_INACTIVE_LEFT).ToGdkPixbuf();
1195 tab_inactive_l_height_ = gdk_pixbuf_get_height(tab_inactive_l);
1196
1197 GdkPixbuf* tab_close = rb.GetNativeImageNamed(IDR_CLOSE_1).ToGdkPixbuf();
1198 close_button_width_ = gdk_pixbuf_get_width(tab_close);
1199 close_button_height_ = gdk_pixbuf_get_height(tab_close);
1200
1201 const gfx::Font& base_font = rb.GetFont(ui::ResourceBundle::BaseFont);
1202 title_font_ = new gfx::Font(base_font.GetFontName(), kFontPixelSize);
1203 title_font_height_ = title_font_->GetHeight();
1204
1205 initialized_ = true;
1206 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698