Index: chrome/browser/ui/views/tabs/tab.cc |
diff --git a/chrome/browser/ui/views/tabs/tab.cc b/chrome/browser/ui/views/tabs/tab.cc |
index c828f1f42e515968c7d11840eec806832e05f77a..b6056cde652713cdf221936659d24353faf4c875 100644 |
--- a/chrome/browser/ui/views/tabs/tab.cc |
+++ b/chrome/browser/ui/views/tabs/tab.cc |
@@ -30,6 +30,8 @@ |
#include "ui/base/models/list_selection_model.h" |
#include "ui/base/resource/resource_bundle.h" |
#include "ui/base/theme_provider.h" |
+#include "ui/compositor/layer_animator.h" |
+#include "ui/compositor/paint_recorder.h" |
#include "ui/gfx/animation/animation_container.h" |
#include "ui/gfx/animation/multi_animation.h" |
#include "ui/gfx/animation/throb_animation.h" |
@@ -137,65 +139,6 @@ const int kImmersiveLoadingStepCount = 32; |
const char kTabCloseButtonName[] = "TabCloseButton"; |
const int kTabCloseButtonSize = 16; |
-// Layer-backed view for updating a waiting or loading tab spinner. |
-class ThrobberView : public views::View { |
- public: |
- explicit ThrobberView(Tab* owner) : owner_(owner) { |
- // Since the throbber animates, paint to a separate layer do reduce repaint |
- // overheads. |
- SetPaintToLayer(true); |
- SetFillsBoundsOpaquely(false); |
- } |
- |
- // views::View: |
- bool CanProcessEventsWithinSubtree() const override { return false; } |
- |
- void OnPaint(gfx::Canvas* canvas) override { |
- const TabRendererData::NetworkState state = owner_->data().network_state; |
- if (state == TabRendererData::NETWORK_STATE_NONE) |
- return; |
- |
- const gfx::Rect bounds = GetLocalBounds(); |
- |
- // Paint network activity (aka throbber) animation frame. |
- ui::ThemeProvider* tp = GetThemeProvider(); |
- if (state == TabRendererData::NETWORK_STATE_WAITING) { |
- if (waiting_start_time_ == base::TimeTicks()) |
- waiting_start_time_ = base::TimeTicks::Now(); |
- |
- waiting_state_.elapsed_time = |
- base::TimeTicks::Now() - waiting_start_time_; |
- gfx::PaintThrobberWaiting( |
- canvas, bounds, tp->GetColor(ThemeProperties::COLOR_THROBBER_WAITING), |
- waiting_state_.elapsed_time); |
- } else { |
- if (loading_start_time_ == base::TimeTicks()) |
- loading_start_time_ = base::TimeTicks::Now(); |
- |
- waiting_state_.color = |
- tp->GetColor(ThemeProperties::COLOR_THROBBER_WAITING); |
- gfx::PaintThrobberSpinningAfterWaiting( |
- canvas, bounds, |
- tp->GetColor(ThemeProperties::COLOR_THROBBER_SPINNING), |
- base::TimeTicks::Now() - loading_start_time_, &waiting_state_); |
- } |
- } |
- |
- private: |
- Tab* owner_; // Weak. Owns |this|. |
- |
- // The point in time when the tab icon was first painted in the waiting state. |
- base::TimeTicks waiting_start_time_; |
- |
- // The point in time when the tab icon was first painted in the loading state. |
- base::TimeTicks loading_start_time_; |
- |
- // Paint state for the throbber after the most recent waiting paint. |
- gfx::ThrobberWaitingState waiting_state_; |
- |
- DISALLOW_COPY_AND_ASSIGN(ThrobberView); |
-}; |
- |
void DrawIconAtLocation(gfx::Canvas* canvas, |
const gfx::ImageSkia& image, |
int image_offset, |
@@ -451,6 +394,170 @@ Tab::ImageCacheEntry::ImageCacheEntry() |
Tab::ImageCacheEntry::~ImageCacheEntry() {} |
//////////////////////////////////////////////////////////////////////////////// |
+// ThrobberView |
+ |
+// Layer-backed view for updating a waiting or loading tab spinner. |
+class Tab::ThrobberView : public views::View { |
+ public: |
+ explicit ThrobberView(Tab* owner) : owner_(owner), waiting_arc_(180) { |
+ // Since the throbber animates, paint to a separate layer do reduce repaint |
+ // overheads. |
+ SetPaintToLayer(true); |
+ SetFillsBoundsOpaquely(false); |
+ |
+ waiting_mask_.SetFillsBoundsOpaquely(false); |
+ waiting_mask_.SetMasksToBounds(true); |
+ waiting_mask_.Add(waiting_arc_.layer()); |
+ } |
+ |
+ void SchedulePaintIfRequired() { |
+ if (NeedsPaint()) |
+ SchedulePaint(); |
+ } |
+ |
+ // views::View: |
+ bool CanProcessEventsWithinSubtree() const override { return false; } |
+ |
+ void OnBoundsChanged(const gfx::Rect& previous_bounds) override { |
+ gfx::Rect bounds = GetLocalBounds(); |
+ waiting_arc_.layer()->SetBounds( |
+ gfx::Rect(0, 0, bounds.width() * Arc::kAA, bounds.height() * Arc::kAA)); |
+ |
+ bounds.set_width(bounds.width() / 2); |
+ waiting_mask_.SetBounds(bounds); |
+ } |
+ |
+ void OnPaint(gfx::Canvas* canvas) override { |
+ if (state_ == TabRendererData::NETWORK_STATE_NONE) |
+ return; |
+ |
+ const gfx::Rect bounds = GetLocalBounds(); |
+ |
+ // Paint network activity (aka throbber) animation frame. |
+ ui::ThemeProvider* tp = owner_->GetThemeProvider(); |
+ if (state_ == TabRendererData::NETWORK_STATE_WAITING) { |
+ // Painted by Arc. |
+ } else { |
+ if (loading_start_time_ == base::TimeTicks()) |
+ loading_start_time_ = base::TimeTicks::Now(); |
+ |
+ waiting_state_.color = |
+ tp->GetColor(ThemeProperties::COLOR_THROBBER_WAITING); |
+ gfx::PaintThrobberSpinningAfterWaiting( |
+ canvas, bounds, |
+ tp->GetColor(ThemeProperties::COLOR_THROBBER_SPINNING), |
+ base::TimeTicks::Now() - loading_start_time_, &waiting_state_); |
+ } |
+ } |
+ |
+ private: |
+ class Arc : public ui::LayerDelegate { |
+ public: |
+ // Since the rotation transform mis-aligns the pixel anti-aliasing done by |
+ // Skia, perform a kind of FSAA by drawing on a larger canvas and scaling |
+ // down as part of the transform. |
+ static const int kAA = 1; |
+ |
+ explicit Arc(SkScalar sweep) |
+ : color_(SK_ColorRED), sweep_(sweep), layer_(ui::LAYER_TEXTURED) { |
+ layer_.set_delegate(this); |
+ layer_.SetFillsBoundsOpaquely(false); |
+ } |
+ |
+ ui::Layer* layer() { return &layer_; } |
+ |
+ void set_color(SkColor color) { color_ = color; } |
+ |
+ void SetAngle(SkScalar angle) { |
+ const gfx::Size size = layer()->size(); |
+ gfx::Transform transform; |
+ transform.Translate(size.width() / 2.0 / kAA, size.height() / 2.0 / kAA); |
+ transform.Rotate(-angle); |
+ transform.Scale(1.0 / kAA, 1.0 / kAA); |
+ transform.Translate(-size.width() / 2, -size.height() / 2); |
+ layer()->SetTransform(transform); |
+ } |
+ |
+ // LayerDelegate: |
+ void OnPaintLayer(const ui::PaintContext& context) override { |
+ const gfx::Size size = layer()->size(); |
+ ui::PaintRecorder recorder(context, size); |
+ gfx::PaintThrobberArc(recorder.canvas(), gfx::Rect(size), color_, -90, |
+ sweep_); |
+ } |
+ |
+ void OnDelegatedFrameDamage(const gfx::Rect& damage_rect_in_dip) override {} |
+ void OnDeviceScaleFactorChanged(float device_scale_factor) override {} |
+ base::Closure PrepareForLayerBoundsChange() override { |
+ return base::Closure(); |
+ } |
+ |
+ private: |
+ SkColor color_; |
+ SkScalar sweep_; |
+ ui::Layer layer_; |
+ |
+ DISALLOW_COPY_AND_ASSIGN(Arc); |
+ }; |
+ |
+ void ApplyWaitingRotation(const base::TimeDelta& elapsed_time) { |
+ const base::TimeDelta revolution_time = |
+ base::TimeDelta::FromMilliseconds(1320); |
+ bool needs_mask = elapsed_time < revolution_time / 2; |
+ waiting_mask_.SetMasksToBounds(needs_mask); |
+ waiting_arc_.SetAngle(360 * waiting_state_.elapsed_time / revolution_time); |
+ waiting_arc_.set_color( |
+ GetThemeProvider()->GetColor(ThemeProperties::COLOR_THROBBER_WAITING)); |
+ } |
+ |
+ bool NeedsPaint() { |
+ if (bounds().IsEmpty()) |
+ return false; |
+ |
+ TabRendererData::NetworkState new_state = owner_->data().network_state; |
+ const bool changing_state = new_state != state_; |
+ |
+ // Waiting throbber is fully layer-backed. |
+ if (new_state == TabRendererData::NETWORK_STATE_WAITING) { |
+ if (waiting_start_time_ == base::TimeTicks()) |
+ waiting_start_time_ = base::TimeTicks::Now(); |
+ |
+ state_ = new_state; |
+ waiting_state_.elapsed_time = |
+ base::TimeTicks::Now() - waiting_start_time_; |
+ layer()->Add(&waiting_mask_); |
+ ApplyWaitingRotation(waiting_state_.elapsed_time); |
+ return changing_state; |
+ } |
+ |
+ if (state_ == TabRendererData::NETWORK_STATE_WAITING) |
+ layer()->Remove(&waiting_mask_); |
+ |
+ state_ = new_state; |
+ |
+ return true; |
+ } |
+ |
+ Tab* owner_; // Weak. Owns this. |
+ |
+ TabRendererData::NetworkState state_ = TabRendererData::NETWORK_STATE_NONE; |
+ |
+ // The point in time when the tab icon was first painted in the waiting state. |
+ base::TimeTicks waiting_start_time_; |
+ |
+ // The point in time when the tab icon was first painted in the loading state. |
+ base::TimeTicks loading_start_time_; |
+ |
+ // Paint state for the throbber after the most recent waiting paint. |
+ gfx::ThrobberWaitingState waiting_state_; |
+ |
+ ui::Layer waiting_mask_; |
+ Arc waiting_arc_; |
+ |
+ DISALLOW_COPY_AND_ASSIGN(ThrobberView); |
+}; |
+ |
+//////////////////////////////////////////////////////////////////////////////// |
// Tab, statics: |
// static |
@@ -1460,7 +1567,7 @@ void Tab::AdvanceLoadingAnimation(TabRendererData::NetworkState state) { |
ScheduleIconPaint(); // Repaint the icon area to update favicon visibility. |
throbber_->SetVisible(needs_throbber); |
} |
- throbber_->SchedulePaint(); |
+ throbber_->SchedulePaintIfRequired(); |
} |
int Tab::IconCapacity() const { |