| // Copyright 2017 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "chrome/browser/ui/views/tabs/tab_icon.h" |
| |
| #include "base/time/default_tick_clock.h" |
| #include "cc/paint/paint_flags.h" |
| #include "chrome/browser/themes/theme_properties.h" |
| #include "chrome/browser/ui/layout_constants.h" |
| #include "chrome/browser/ui/views/tabs/tab_renderer_data.h" |
| #include "chrome/common/chrome_features.h" |
| #include "chrome/common/webui_url_constants.h" |
| #include "components/grit/components_scaled_resources.h" |
| #include "content/public/common/url_constants.h" |
| #include "ui/base/resource/resource_bundle.h" |
| #include "ui/base/theme_provider.h" |
| #include "ui/gfx/animation/animation_delegate.h" |
| #include "ui/gfx/animation/linear_animation.h" |
| #include "ui/gfx/animation/tween.h" |
| #include "ui/gfx/canvas.h" |
| #include "ui/gfx/color_palette.h" |
| #include "ui/gfx/favicon_size.h" |
| #include "ui/gfx/image/image_skia_operations.h" |
| #include "ui/native_theme/native_theme.h" |
| #include "ui/resources/grit/ui_resources.h" |
| #include "url/gurl.h" |
| |
| namespace { |
| |
| constexpr int kSlowLoadingProgressTimeMs = 2000; |
| constexpr int kFastLoadingProgressTimeMs = 400; |
| constexpr int kLoadingProgressFadeOutMs = 200; |
| constexpr int kFaviconFadeInMs = 500; |
| constexpr int kFaviconPlaceholderFadeInMs = 500; |
| constexpr int kFaviconPlaceholderFadeOutMs = 200; |
| |
| bool UseNewLoadingAnimation() { |
| return base::FeatureList::IsEnabled(features::kNewTabLoadingAnimation); |
| } |
| |
| constexpr int kAttentionIndicatorRadius = 3; |
| |
| // Returns whether the favicon for the given URL should be colored according to |
| // the browser theme. |
| bool ShouldThemifyFaviconForUrl(const GURL& url) { |
| return url.SchemeIs(content::kChromeUIScheme) && |
| url.host_piece() != chrome::kChromeUIHelpHost && |
| url.host_piece() != chrome::kChromeUIUberHost && |
| url.host_piece() != chrome::kChromeUIAppLauncherPageHost; |
| } |
| |
| bool NetworkStateIsAnimated(TabNetworkState network_state) { |
| return network_state != TabNetworkState::kNone && |
| network_state != TabNetworkState::kError; |
| } |
| |
| // Returns a rect in which the throbber should be painted. |
| gfx::RectF GetThrobberBounds(const gfx::Rect& bounds) { |
| gfx::RectF throbber_bounds(bounds); |
| constexpr float kThrobberHeightDp = 2; |
| // The throbber starts 1dp below the tab icon. |
| throbber_bounds.set_y(bounds.bottom() + 1); |
| throbber_bounds.set_height(kThrobberHeightDp); |
| return throbber_bounds; |
| } |
| |
| } // namespace |
| |
| // Helper class that manages the favicon crash animation. |
| class TabIcon::CrashAnimation : public gfx::LinearAnimation, |
| public gfx::AnimationDelegate { |
| public: |
| explicit CrashAnimation(TabIcon* target) |
| : gfx::LinearAnimation(base::TimeDelta::FromSeconds(1), 25, this), |
| target_(target) {} |
| ~CrashAnimation() override = default; |
| |
| // gfx::Animation overrides: |
| void AnimateToState(double state) override { |
| if (state < .5) { |
| // Animate the normal icon down. |
| target_->hiding_fraction_ = state * 2.0; |
| } else { |
| // Animate the crashed icon up. |
| target_->should_display_crashed_favicon_ = true; |
| target_->hiding_fraction_ = 1.0 - (state - 0.5) * 2.0; |
| } |
| target_->SchedulePaint(); |
| } |
| |
| private: |
| TabIcon* target_; |
| |
| DISALLOW_COPY_AND_ASSIGN(CrashAnimation); |
| }; |
| |
| TabIcon::LoadingAnimationState::LoadingAnimationState() = default; |
| |
| TabIcon::TabIcon() : clock_(base::DefaultTickClock::GetInstance()) { |
| set_can_process_events_within_subtree(false); |
| |
| // The minimum size to avoid clipping the attention indicator. |
| SetPreferredSize(gfx::Size(gfx::kFaviconSize + kAttentionIndicatorRadius, |
| gfx::kFaviconSize + kAttentionIndicatorRadius)); |
| |
| // Initial state (before any data) should not be animating. |
| DCHECK(!ShowingLoadingAnimation()); |
| } |
| |
| TabIcon::~TabIcon() = default; |
| |
| void TabIcon::SetData(const TabRendererData& data) { |
| const bool was_showing_load = ShowingLoadingAnimation(); |
| |
| inhibit_loading_animation_ = data.should_hide_throbber; |
| SetIcon(data.url, data.favicon); |
| SetNetworkState(data.network_state, data.load_progress); |
| SetIsCrashed(data.IsCrashed()); |
| |
| const bool showing_load = ShowingLoadingAnimation(); |
| |
| RefreshLayer(); |
| |
| if (was_showing_load && !showing_load) { |
| // Loading animation transitioning from on to off. |
| old_animation_loading_start_time_ = base::TimeTicks(); |
| waiting_state_ = gfx::ThrobberWaitingState(); |
| SchedulePaint(); |
| } else if (!was_showing_load && showing_load) { |
| // Loading animation transitioning from off to on. The animation painting |
| // function will lazily initialize the data. |
| SchedulePaint(); |
| } |
| } |
| |
| void TabIcon::SetAttention(AttentionType type, bool enabled) { |
| int previous_attention_type = attention_types_; |
| if (enabled) |
| attention_types_ |= static_cast<int>(type); |
| else |
| attention_types_ &= ~static_cast<int>(type); |
| |
| if (attention_types_ != previous_attention_type) |
| SchedulePaint(); |
| } |
| |
| bool TabIcon::ShowingLoadingAnimation() const { |
| if (inhibit_loading_animation_) |
| return false; |
| |
| if (NetworkStateIsAnimated(network_state_)) |
| return true; |
| |
| if (LoadingAnimationNeedsRepaint()) |
| return true; |
| |
| // If any animations were active in the last painted state we need to keep |
| // animations going. |
| // Note that the fade-in check is different as the fade-in progress doesn't |
| // reset as it ends but stays at 1.0. Unset means we're waiting for the |
| // animation to start. |
| if (animation_state_.loading_progress_animation_pending || |
| animation_state_.loading_progress || |
| animation_state_.loading_progress_fade_out || |
| animation_state_.favicon_placeholder_alpha || |
| animation_state_.favicon_fade_in_progress.value_or(0.0) < 1.0) { |
| return true; |
| } |
| |
| return false; |
| } |
| |
| bool TabIcon::ShowingAttentionIndicator() const { |
| return attention_types_ > 0; |
| } |
| |
| void TabIcon::SetCanPaintToLayer(bool can_paint_to_layer) { |
| if (can_paint_to_layer == can_paint_to_layer_) |
| return; |
| can_paint_to_layer_ = can_paint_to_layer; |
| RefreshLayer(); |
| } |
| |
| void TabIcon::StepLoadingAnimation(const base::TimeDelta& elapsed_time) { |
| waiting_state_.elapsed_time = elapsed_time; |
| |
| UpdatePendingAnimationState(); |
| |
| if (LoadingAnimationNeedsRepaint()) |
| SchedulePaint(); |
| |
| RefreshLayer(); |
| } |
| |
| void TabIcon::SetBackgroundColor(SkColor bg_color) { |
| bg_color_ = bg_color; |
| SchedulePaint(); |
| } |
| |
| void TabIcon::OnPaint(gfx::Canvas* canvas) { |
| // Compute the bounds adjusted for the hiding fraction. |
| gfx::Rect contents_bounds = GetContentsBounds(); |
| // Update animation state regardless of empty bounds or not, so we don't think |
| // we're perpetually animating. |
| animation_state_ = pending_animation_state_; |
| |
| if (contents_bounds.IsEmpty()) |
| return; |
| |
| gfx::Rect icon_bounds( |
| GetMirroredXWithWidthInView(0, gfx::kFaviconSize), |
| static_cast<int>(contents_bounds.height() * hiding_fraction_), |
| std::min(gfx::kFaviconSize, contents_bounds.width()), |
| std::min(gfx::kFaviconSize, contents_bounds.height())); |
| |
| // The old animation replaces the favicon and should early-abort. |
| if (!UseNewLoadingAnimation() && ShowingLoadingAnimation()) { |
| PaintLoadingAnimation(canvas, icon_bounds); |
| return; |
| } |
| |
| if (ShowingAttentionIndicator() && !should_display_crashed_favicon_) { |
| PaintAttentionIndicatorAndIcon(canvas, GetIconToPaint(), icon_bounds); |
| } else { |
| MaybePaintFaviconPlaceholder(canvas, icon_bounds); |
| MaybePaintFavicon(canvas, GetIconToPaint(), icon_bounds); |
| } |
| |
| if (ShowingLoadingAnimation()) |
| PaintLoadingAnimation(canvas, icon_bounds); |
| } |
| |
| void TabIcon::UpdatePendingAnimationState() { |
| if (last_animation_update_time_.is_null()) |
| return; |
| const base::TimeTicks now = clock_->NowTicks(); |
| |
| double animation_delta_ms = |
| (now - last_animation_update_time_).InMilliseconds(); |
| last_animation_update_time_ = now; |
| |
| pending_animation_state_.elapsed_time = waiting_state_.elapsed_time; |
| |
| if (pending_animation_state_.loading_progress_animation_pending) { |
| const int previously_painted_cycles = |
| animation_state_.elapsed_time.InMilliseconds() / |
| gfx::kNewThrobberWaitingAnimationCycleMs; |
| const int next_painted_cycles = |
| pending_animation_state_.elapsed_time.InMilliseconds() / |
| gfx::kNewThrobberWaitingAnimationCycleMs; |
| // Throbber's gone a full circle since last paint, transition to |
| // loading-progress animation. |
| if (previously_painted_cycles != next_painted_cycles) { |
| pending_animation_state_.loading_progress = 0.0; |
| pending_animation_state_.loading_progress_animation_pending = false; |
| MaybeStartFaviconFadeIn(); |
| } |
| } |
| |
| if (pending_animation_state_.favicon_placeholder_alpha) { |
| // Fade in until the favicon fade-in starts. Then fade out. |
| if (!pending_animation_state_.favicon_fade_in_progress) { |
| pending_animation_state_.favicon_placeholder_alpha = |
| std::min(*pending_animation_state_.favicon_placeholder_alpha + |
| animation_delta_ms / kFaviconPlaceholderFadeInMs, |
| 1.0); |
| } else { |
| *pending_animation_state_.favicon_placeholder_alpha -= |
| animation_delta_ms / kFaviconPlaceholderFadeOutMs; |
| if (pending_animation_state_.favicon_placeholder_alpha <= 0.0) |
| pending_animation_state_.favicon_placeholder_alpha.reset(); |
| } |
| } |
| |
| if (pending_animation_state_.loading_progress) { |
| double loading_progress_delta = |
| animation_delta_ms / (network_state_ == TabNetworkState::kLoading |
| ? kSlowLoadingProgressTimeMs |
| : kFastLoadingProgressTimeMs); |
| // Clamp the progress bar to the current target percentage. |
| pending_animation_state_.loading_progress = std::min( |
| *pending_animation_state_.loading_progress + loading_progress_delta, |
| target_loading_progress_); |
| if (*pending_animation_state_.loading_progress == 1.0) { |
| pending_animation_state_.loading_progress.reset(); |
| pending_animation_state_.loading_progress_fade_out = 0.0; |
| } |
| } |
| |
| if (pending_animation_state_.loading_progress_fade_out) { |
| *pending_animation_state_.loading_progress_fade_out += |
| animation_delta_ms / kLoadingProgressFadeOutMs; |
| if (*pending_animation_state_.loading_progress_fade_out >= 1.0) |
| pending_animation_state_.loading_progress_fade_out.reset(); |
| } |
| |
| if (pending_animation_state_.favicon_fade_in_progress) { |
| *pending_animation_state_.favicon_fade_in_progress = |
| std::min(*pending_animation_state_.favicon_fade_in_progress + |
| animation_delta_ms / kFaviconFadeInMs, |
| 1.0); |
| } |
| } |
| |
| bool TabIcon::LoadingAnimationNeedsRepaint() const { |
| if (!UseNewLoadingAnimation()) |
| return ShowingLoadingAnimation(); |
| |
| // Throbber always needs a repaint. |
| if (network_state_ == TabNetworkState::kWaiting) |
| return true; |
| |
| // Compare without |elapsed_time| as it's only used in the waiting state. |
| auto tie = [](const LoadingAnimationState& state) { |
| return std::tie(state.loading_progress_animation_pending, |
| state.loading_progress, state.loading_progress_fade_out, |
| state.favicon_placeholder_alpha, |
| state.favicon_fade_in_progress); |
| }; |
| |
| return tie(pending_animation_state_) != tie(animation_state_); |
| } |
| |
| void TabIcon::OnThemeChanged() { |
| crashed_icon_ = gfx::ImageSkia(); // Force recomputation if crashed. |
| if (!themed_favicon_.isNull()) |
| themed_favicon_ = ThemeImage(favicon_); |
| } |
| |
| void TabIcon::PaintAttentionIndicatorAndIcon(gfx::Canvas* canvas, |
| const gfx::ImageSkia& icon, |
| const gfx::Rect& bounds) { |
| gfx::Point circle_center( |
| bounds.x() + (base::i18n::IsRTL() ? 0 : gfx::kFaviconSize), |
| bounds.y() + gfx::kFaviconSize); |
| |
| // The attention indicator consists of two parts: |
| // . a clear (totally transparent) part over the bottom right (or left in rtl) |
| // of the favicon. This is done by drawing the favicon to a layer, then |
| // drawing the clear part on top of the favicon. |
| // . a circle in the bottom right (or left in rtl) of the favicon. |
| if (!icon.isNull()) { |
| canvas->SaveLayerAlpha(0xff); |
| canvas->DrawImageInt(icon, 0, 0, bounds.width(), bounds.height(), |
| bounds.x(), bounds.y(), bounds.width(), |
| bounds.height(), false); |
| cc::PaintFlags clear_flags; |
| clear_flags.setAntiAlias(true); |
| clear_flags.setBlendMode(SkBlendMode::kClear); |
| const float kIndicatorCropRadius = 4.5f; |
| canvas->DrawCircle(circle_center, kIndicatorCropRadius, clear_flags); |
| canvas->Restore(); |
| } |
| |
| // Draws the actual attention indicator. |
| cc::PaintFlags indicator_flags; |
| indicator_flags.setColor(GetNativeTheme()->GetSystemColor( |
| ui::NativeTheme::kColorId_ProminentButtonColor)); |
| indicator_flags.setAntiAlias(true); |
| canvas->DrawCircle(circle_center, kAttentionIndicatorRadius, indicator_flags); |
| } |
| |
| void TabIcon::PaintLoadingProgressIndicator(gfx::Canvas* canvas, |
| gfx::RectF bounds, |
| SkColor color) { |
| // Don't paint if both the loading-progress and fade-out animations both have |
| // finished. |
| if (!animation_state_.loading_progress && |
| !animation_state_.loading_progress_fade_out) { |
| return; |
| } |
| const double progress = gfx::Tween::CalculateValue( |
| gfx::Tween::EASE_IN_OUT, animation_state_.loading_progress.value_or(1.0)); |
| bounds.set_width(bounds.height() + |
| progress * (bounds.width() - bounds.height())); |
| |
| cc::PaintFlags flags; |
| flags.setColor(color); |
| flags.setStyle(cc::PaintFlags::kFill_Style); |
| // Disable anti-aliasing to effectively "pixel align" the rectangle. |
| flags.setAntiAlias(false); |
| if (animation_state_.loading_progress_fade_out) { |
| flags.setAlpha((1.0 - *animation_state_.loading_progress_fade_out) * |
| SK_AlphaOPAQUE); |
| } |
| |
| canvas->DrawRect(bounds, flags); |
| } |
| |
| void TabIcon::PaintLoadingAnimation(gfx::Canvas* canvas, |
| const gfx::Rect& bounds) { |
| const ui::ThemeProvider* tp = GetThemeProvider(); |
| if (UseNewLoadingAnimation()) { |
| const gfx::RectF throbber_bounds = GetThrobberBounds(bounds); |
| // Note that this tab-loading animation intentionally uses |
| // COLOR_TAB_THROBBER_SPINNING for both the waiting and loading states. |
| const SkColor loading_color = |
| tp->GetColor(ThemeProperties::COLOR_TAB_THROBBER_SPINNING); |
| if (network_state_ == TabNetworkState::kWaiting || |
| animation_state_.loading_progress_animation_pending) { |
| gfx::PaintNewThrobberWaiting(canvas, throbber_bounds, loading_color, |
| animation_state_.elapsed_time); |
| } else { |
| PaintLoadingProgressIndicator(canvas, throbber_bounds, loading_color); |
| } |
| } else { |
| if (network_state_ == TabNetworkState::kWaiting) { |
| gfx::PaintThrobberWaiting( |
| canvas, bounds, |
| tp->GetColor(ThemeProperties::COLOR_TAB_THROBBER_WAITING), |
| waiting_state_.elapsed_time); |
| } else { |
| const base::TimeTicks current_time = clock_->NowTicks(); |
| if (old_animation_loading_start_time_.is_null()) |
| old_animation_loading_start_time_ = current_time; |
| |
| waiting_state_.color = |
| tp->GetColor(ThemeProperties::COLOR_TAB_THROBBER_WAITING); |
| gfx::PaintThrobberSpinningAfterWaiting( |
| canvas, bounds, |
| tp->GetColor(ThemeProperties::COLOR_TAB_THROBBER_SPINNING), |
| current_time - old_animation_loading_start_time_, &waiting_state_); |
| } |
| } |
| } |
| |
| const gfx::ImageSkia& TabIcon::GetIconToPaint() { |
| if (should_display_crashed_favicon_) { |
| if (crashed_icon_.isNull()) { |
| // Lazily create a themed sad tab icon. |
| ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance(); |
| crashed_icon_ = ThemeImage(*rb.GetImageSkiaNamed(IDR_CRASH_SAD_FAVICON)); |
| } |
| return crashed_icon_; |
| } |
| return themed_favicon_.isNull() ? favicon_ : themed_favicon_; |
| } |
| |
| void TabIcon::MaybePaintFaviconPlaceholder(gfx::Canvas* canvas, |
| const gfx::Rect& bounds) { |
| if (!animation_state_.favicon_placeholder_alpha) |
| return; |
| cc::PaintFlags flags; |
| double placeholder_alpha = *animation_state_.favicon_placeholder_alpha; |
| const SkColor placeholder_color = |
| color_utils::IsDark(bg_color_) |
| ? SkColorSetA(SK_ColorWHITE, 32 * placeholder_alpha) |
| : SkColorSetA(SK_ColorBLACK, 16 * placeholder_alpha); |
| flags.setColor(placeholder_color); |
| flags.setStyle(cc::PaintFlags::kFill_Style); |
| flags.setAntiAlias(true); |
| |
| constexpr float kFaviconPlaceholderRadiusDp = 4; |
| canvas->DrawRoundRect(bounds, kFaviconPlaceholderRadiusDp, flags); |
| } |
| |
| void TabIcon::MaybePaintFavicon(gfx::Canvas* canvas, |
| const gfx::ImageSkia& icon, |
| const gfx::Rect& bounds) { |
| // While loading, the favicon (or placeholder) isn't drawn until it has |
| // started fading in. |
| if (!animation_state_.favicon_fade_in_progress) |
| return; |
| |
| if (icon.isNull()) |
| return; |
| |
| cc::PaintFlags flags; |
| double fade_in_progress = gfx::Tween::CalculateValue( |
| gfx::Tween::FAST_OUT_SLOW_IN, *animation_state_.favicon_fade_in_progress); |
| flags.setAlpha(fade_in_progress * SK_AlphaOPAQUE); |
| // Drop in the new favicon from the top while it's fading in. |
| const int offset = round((fade_in_progress - 1.0) * 4.0); |
| |
| canvas->DrawImageInt(icon, 0, 0, bounds.width(), bounds.height(), bounds.x(), |
| bounds.y() + offset, bounds.width(), bounds.height(), |
| false, flags); |
| } |
| |
| bool TabIcon::HasNonDefaultFavicon() const { |
| ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance(); |
| return !favicon_.isNull() && !favicon_.BackedBySameObjectAs( |
| *rb.GetImageSkiaNamed(IDR_DEFAULT_FAVICON)); |
| } |
| |
| void TabIcon::MaybeStartFaviconFadeIn() { |
| if (pending_animation_state_.favicon_fade_in_progress) |
| return; |
| |
| // The favicon shouldn't fade in before the loading-progress animation starts. |
| if (pending_animation_state_.loading_progress_animation_pending) |
| return; |
| |
| // Start fading in the favicon if we're no longer animating. |
| if (!NetworkStateIsAnimated(network_state_)) { |
| pending_animation_state_.favicon_fade_in_progress = 0.0; |
| return; |
| } |
| |
| // If we have a non-default favicon and are already loading the throbber state |
| // start fading in the favicon. |
| if (HasNonDefaultFavicon() && network_state_ == TabNetworkState::kLoading) |
| pending_animation_state_.favicon_fade_in_progress = 0.0; |
| } |
| |
| void TabIcon::SetIcon(const GURL& url, const gfx::ImageSkia& icon) { |
| // Detect when updating to the same icon. This avoids re-theming and |
| // re-painting. |
| if (favicon_.BackedBySameObjectAs(icon)) |
| return; |
| |
| favicon_ = icon; |
| |
| if (!HasNonDefaultFavicon() || ShouldThemifyFaviconForUrl(url)) { |
| themed_favicon_ = ThemeImage(icon); |
| } else { |
| themed_favicon_ = gfx::ImageSkia(); |
| } |
| |
| MaybeStartFaviconFadeIn(); |
| SchedulePaint(); |
| } |
| |
| void TabIcon::SetNetworkState(TabNetworkState network_state, |
| float load_progress) { |
| if (network_state_ != network_state) { |
| TabNetworkState old_state = network_state_; |
| network_state_ = network_state; |
| |
| bool was_animated = NetworkStateIsAnimated(old_state); |
| bool is_animated = NetworkStateIsAnimated(network_state_); |
| |
| // If we either start animating (or go from loading to waiting), reset all |
| // animations. |
| if ((!was_animated && is_animated) || |
| network_state_ == TabNetworkState::kWaiting) { |
| last_animation_update_time_ = clock_->NowTicks(); |
| pending_animation_state_ = LoadingAnimationState(); |
| pending_animation_state_.favicon_fade_in_progress.reset(); |
| } |
| |
| // If we switched to waiting, start fading in the placeholder. |
| if (network_state_ == TabNetworkState::kWaiting) |
| pending_animation_state_.favicon_placeholder_alpha = 0.0; |
| |
| if (network_state_ == TabNetworkState::kLoading) { |
| // When transitioning to loading, start the progress indicatator. |
| target_loading_progress_ = 0.0; |
| pending_animation_state_.loading_progress_animation_pending = true; |
| } else if (old_state == TabNetworkState::kLoading) { |
| target_loading_progress_ = 1.0; |
| } |
| |
| MaybeStartFaviconFadeIn(); |
| SchedulePaint(); |
| } |
| |
| if (network_state_ == TabNetworkState::kLoading) { |
| // Interpolate loading progress to a narrower range to prevent us from |
| // looking stuck doing nothing at 0% or at 100% but still not finishing. |
| constexpr float kLoadingProgressStart = 0.3; |
| constexpr float kLoadingProgressEnd = 0.7; |
| load_progress = |
| kLoadingProgressStart + |
| load_progress * (kLoadingProgressEnd - kLoadingProgressStart); |
| |
| // The loading progress looks really weird if it ever jumps backwards, so |
| // make sure it only increases. |
| if (target_loading_progress_ < load_progress) |
| target_loading_progress_ = load_progress; |
| } |
| } |
| |
| void TabIcon::SetIsCrashed(bool is_crashed) { |
| if (is_crashed == is_crashed_) |
| return; |
| is_crashed_ = is_crashed; |
| |
| if (!is_crashed_) { |
| // Transitioned from crashed to non-crashed. |
| if (crash_animation_) |
| crash_animation_->Stop(); |
| should_display_crashed_favicon_ = false; |
| hiding_fraction_ = 0.0; |
| } else { |
| // Transitioned from non-crashed to crashed. |
| if (!crash_animation_) |
| crash_animation_ = std::make_unique<CrashAnimation>(this); |
| if (!crash_animation_->is_animating()) |
| crash_animation_->Start(); |
| } |
| SchedulePaint(); |
| } |
| |
| void TabIcon::RefreshLayer() { |
| // Since the loading animation can run for a long time, paint animation to a |
| // separate layer when possible to reduce repaint overhead. |
| bool should_paint_to_layer = can_paint_to_layer_ && ShowingLoadingAnimation(); |
| if (should_paint_to_layer == !!layer()) |
| return; |
| |
| // Change layer mode. |
| if (should_paint_to_layer) { |
| SetPaintToLayer(); |
| layer()->SetFillsBoundsOpaquely(false); |
| } else { |
| DestroyLayer(); |
| } |
| } |
| |
| gfx::ImageSkia TabIcon::ThemeImage(const gfx::ImageSkia& source) { |
| return gfx::ImageSkiaOperations::CreateHSLShiftedImage( |
| source, GetThemeProvider()->GetTint(ThemeProperties::TINT_BUTTONS)); |
| } |