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 } 1207