| OLD | NEW |
| (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 } | |
| OLD | NEW |