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 |