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