diff options
author | sky@chromium.org <sky@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2010-04-03 23:54:07 +0000 |
---|---|---|
committer | sky@chromium.org <sky@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2010-04-03 23:54:07 +0000 |
commit | 28a2912d9aeba1a743af7fabe883ad514f12503e (patch) | |
tree | 156784bfbb3ad68404ffcc403c491a0b8f73f999 | |
parent | a28a4111d65e74afd46a8873ff346225cf0c028a (diff) | |
download | chromium_src-28a2912d9aeba1a743af7fabe883ad514f12503e.zip chromium_src-28a2912d9aeba1a743af7fabe883ad514f12503e.tar.gz chromium_src-28a2912d9aeba1a743af7fabe883ad514f12503e.tar.bz2 |
Changes tab strip to use BoundsAnimator for tab strip animations and
adds a new animation when inserting a tab. I've changed the code to
make it clear when an index is in terms of the model, and when it is
in terms of tab_data_. I may replace using an index for tab data with
an actual int wrapper class to avoid mistakes, but I'll do that
separately.
BUG=27783
TEST=thorougly test all animations in the tab strip, as well as
dragging tabs in an out.
Review URL: http://codereview.chromium.org/1553008
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@43582 0039d316-1c4b-4281-b951-d872f2087c98
-rw-r--r-- | chrome/browser/views/tabs/dragged_tab_controller.cc | 9 | ||||
-rw-r--r-- | chrome/browser/views/tabs/tab_renderer.cc | 66 | ||||
-rw-r--r-- | chrome/browser/views/tabs/tab_renderer.h | 35 | ||||
-rw-r--r-- | chrome/browser/views/tabs/tab_strip.cc | 1337 | ||||
-rw-r--r-- | chrome/browser/views/tabs/tab_strip.h | 248 | ||||
-rw-r--r-- | views/animation/bounds_animator.cc | 10 |
6 files changed, 762 insertions, 943 deletions
diff --git a/chrome/browser/views/tabs/dragged_tab_controller.cc b/chrome/browser/views/tabs/dragged_tab_controller.cc index ce26ac7..3521a88 100644 --- a/chrome/browser/views/tabs/dragged_tab_controller.cc +++ b/chrome/browser/views/tabs/dragged_tab_controller.cc @@ -316,7 +316,7 @@ DraggedTabController::DraggedTabController(Tab* source_tab, original_delegate_(NULL), source_tab_(source_tab), source_tabstrip_(source_tabstrip), - source_model_index_(source_tabstrip->GetIndexOfTab(source_tab)), + source_model_index_(source_tabstrip->GetModelIndexOfTab(source_tab)), attached_tabstrip_(source_tabstrip), old_focused_view_(NULL), in_destructor_(false), @@ -508,7 +508,7 @@ void DraggedTabController::InitWindowCreatePoint() { // first_tab based on source_tabstrip_, not attached_tabstrip_. Otherwise, // the window_create_point_ is not in the correct coordinate system. Please // refer to http://crbug.com/6223 comment #15 for detailed information. - Tab* first_tab = source_tabstrip_->GetTabAt(0); + Tab* first_tab = source_tabstrip_->GetTabAtTabDataIndex(0); views::View::ConvertPointToWidget(first_tab, &first_source_tab_point_); UpdateWindowCreatePoint(); } @@ -963,8 +963,9 @@ gfx::Point DraggedTabController::GetDraggedViewPoint( Tab* DraggedTabController::GetTabMatchingDraggedContents( TabStrip* tabstrip) const { - int index = tabstrip->model()->GetIndexOfTabContents(dragged_contents_); - return index == TabStripModel::kNoTab ? NULL : tabstrip->GetTabAt(index); + int model_index = tabstrip->model()->GetIndexOfTabContents(dragged_contents_); + return model_index == TabStripModel::kNoTab ? + NULL : tabstrip->GetTabAtModelIndex(model_index); } bool DraggedTabController::EndDragImpl(EndDragType type) { diff --git a/chrome/browser/views/tabs/tab_renderer.cc b/chrome/browser/views/tabs/tab_renderer.cc index b84dc20..f301b03 100644 --- a/chrome/browser/views/tabs/tab_renderer.cc +++ b/chrome/browser/views/tabs/tab_renderer.cc @@ -6,6 +6,7 @@ #include <limits> +#include "app/animation_container.h" #include "app/l10n_util.h" #include "app/resource_bundle.h" #include "app/slide_animation.h" @@ -80,6 +81,10 @@ static int loading_animation_frame_count = 0; static int waiting_animation_frame_count = 0; static int waiting_to_loading_frame_count_ratio = 0; +// Used when |render_as_new_tab| is true. +static SkBitmap* new_tab_mask = NULL; +static SkBitmap* new_tab_shadow = NULL; + TabRenderer::TabImage TabRenderer::tab_alpha = {0}; TabRenderer::TabImage TabRenderer::tab_active = {0}; TabRenderer::TabImage TabRenderer::tab_inactive = {0}; @@ -281,6 +286,10 @@ TabRenderer::~TabRenderer() { delete crash_animation_; } +void TabRenderer::SizeToNewTabButtonImages() { + SetBounds(x(), y(), new_tab_shadow->width(), new_tab_shadow->height()); +} + void TabRenderer::ViewHierarchyChanged(bool is_add, View* parent, View* child) { if (parent->GetThemeProvider()) SetThemeProvider(parent->GetThemeProvider()); @@ -403,6 +412,11 @@ void TabRenderer::StopMiniTabTitleAnimation() { mini_title_animation_->Stop(); } +void TabRenderer::SetAnimationContainer(AnimationContainer* container) { + container_ = container; + pulse_animation_->SetContainer(container); +} + void TabRenderer::PaintIcon(gfx::Canvas* canvas) { if (animation_state_ != ANIMATION_NONE) { PaintLoadingAnimation(canvas); @@ -498,6 +512,11 @@ void TabRenderer::OnMouseExited(const views::MouseEvent& e) { // TabRenderer, views::View overrides: void TabRenderer::Paint(gfx::Canvas* canvas) { + if (data_.render_as_new_tab) { + PaintAsNewTab(canvas); + return; + } + // Don't paint if we're narrower than we can render correctly. (This should // only happen during animations). if (width() < GetMinimumUnselectedSize().width() && !mini()) @@ -815,6 +834,47 @@ void TabRenderer::PaintLoadingAnimation(gfx::Canvas* canvas) { false); } +void TabRenderer::PaintAsNewTab(gfx::Canvas* canvas) { + bool is_otr = data_.off_the_record; + + // The tab image needs to be lined up with the background image + // so that it feels partially transparent. These offsets represent the tab + // position within the frame background image. + int offset = GetX(views::View::APPLY_MIRRORING_TRANSFORMATION) + + background_offset_.x(); + + int tab_id; + if (GetWidget() && + GetWidget()->GetWindow()->GetNonClientView()->UseNativeFrame()) { + tab_id = IDR_THEME_TAB_BACKGROUND_V; + } else { + tab_id = is_otr ? IDR_THEME_TAB_BACKGROUND_INCOGNITO : + IDR_THEME_TAB_BACKGROUND; + } + + SkBitmap* tab_bg = GetThemeProvider()->GetBitmapNamed(tab_id); + + // If the theme is providing a custom background image, then its top edge + // should be at the top of the tab. Otherwise, we assume that the background + // image is a composited foreground + frame image. + int bg_offset_y = GetThemeProvider()->HasCustomImage(tab_id) ? + 0 : background_offset_.y(); + + SkBitmap image = SkBitmapOperations::CreateTiledBitmap( + *tab_bg, offset, bg_offset_y, new_tab_mask->width(), + new_tab_mask->height()); + image = SkBitmapOperations::CreateMaskedBitmap(image, *new_tab_mask); + canvas->DrawBitmapInt(image, + 0, 0, image.width(), image.height(), + 0, 0, image.width(), image.height(), + false); + + canvas->DrawBitmapInt(*new_tab_shadow, + 0, 0, new_tab_shadow->width(), new_tab_shadow->height(), + 0, 0, new_tab_shadow->width(), new_tab_shadow->height(), + false); +} + int TabRenderer::IconCapacity() const { if (height() < GetMinimumUnselectedSize().height()) return 0; @@ -840,6 +900,9 @@ bool TabRenderer::ShouldShowCloseBox() const { } double TabRenderer::GetThrobValue() { + if (data_.alpha != 1) + return data_.alpha; + if (pulse_animation_->IsAnimating()) return pulse_animation_->GetCurrentValue() * kHoverOpacity; @@ -903,6 +966,9 @@ void TabRenderer::LoadTabImages() { loading_animation_frames = rb.GetBitmapNamed(IDR_THROBBER); waiting_animation_frames = rb.GetBitmapNamed(IDR_THROBBER_WAITING); + + new_tab_mask = rb.GetBitmapNamed(IDR_TAB_ALPHA_NEW_TAB); + new_tab_shadow = rb.GetBitmapNamed(IDR_TAB_NEW_TAB_SHADOW); } void TabRenderer::SetBlocked(bool blocked) { diff --git a/chrome/browser/views/tabs/tab_renderer.h b/chrome/browser/views/tabs/tab_renderer.h index 2e309c6..dfb7272 100644 --- a/chrome/browser/views/tabs/tab_renderer.h +++ b/chrome/browser/views/tabs/tab_renderer.h @@ -1,4 +1,4 @@ -// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved. +// Copyright (c) 2010 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. @@ -6,12 +6,14 @@ #define CHROME_BROWSER_VIEWS_TABS_TAB_RENDERER_H__ #include "app/animation.h" +#include "base/ref_counted.h" #include "base/scoped_ptr.h" #include "base/string16.h" #include "gfx/point.h" #include "views/controls/button/image_button.h" #include "views/view.h" +class AnimationContainer; class SlideAnimation; class TabContents; class ThrobAnimation; @@ -37,6 +39,11 @@ class TabRenderer : public views::View, TabRenderer(); virtual ~TabRenderer(); + // Sizes the renderer to the size of the new tab images. This is used when + // during the new tab animation. See TabStrip's description of AnimationType + // for details. + void SizeToNewTabButtonImages(); + // Overridden from views: void ViewHierarchyChanged(bool is_add, View* parent, View* child); ThemeProvider* GetThemeProvider(); @@ -59,6 +66,18 @@ class TabRenderer : public views::View, void set_phantom(bool phantom) { data_.phantom = phantom; } bool phantom() const { return data_.phantom; } + // Used during new tab animation to force the tab to render a new tab like + // animation. + void set_render_as_new_tab(bool value) { data_.render_as_new_tab = value; } + + // Sets the alpha value to render the tab at. This is used during the new + // tab animation. + void set_alpha(double value) { data_.alpha = value; } + + // Forces the tab to render unselected even though it is selected. + void set_render_unselected(bool value) { data_.render_unselected = value; } + bool render_unselected() const { return data_.render_unselected; } + // Are we in the process of animating a mini tab state change on this tab? void set_animating_mini_change(bool value); @@ -93,6 +112,9 @@ class TabRenderer : public views::View, theme_provider_ = provider; } + // Sets the container all animations run from. + void SetAnimationContainer(AnimationContainer* container); + // Paints the icon. Most of the time you'll want to invoke Paint directly, but // in certain situations this invoked outside of Paint. void PaintIcon(gfx::Canvas* canvas); @@ -158,6 +180,7 @@ class TabRenderer : public views::View, void PaintInactiveTabBackground(gfx::Canvas* canvas); void PaintActiveTabBackground(gfx::Canvas* canvas); void PaintLoadingAnimation(gfx::Canvas* canvas); + void PaintAsNewTab(gfx::Canvas* canvas); // Returns the number of favicon-size elements that can fit in the tab's // current size. @@ -211,7 +234,10 @@ class TabRenderer : public views::View, mini(false), blocked(false), animating_mini_change(false), - phantom(false) { + phantom(false), + render_as_new_tab(false), + render_unselected(false), + alpha(1) { } SkBitmap favicon; @@ -224,6 +250,9 @@ class TabRenderer : public views::View, bool blocked; bool animating_mini_change; bool phantom; + bool render_as_new_tab; + bool render_unselected; + double alpha; }; TabData data_; @@ -260,6 +289,8 @@ class TabRenderer : public views::View, ThemeProvider* theme_provider_; + scoped_refptr<AnimationContainer> container_; + static void InitClass(); static bool initialized_; diff --git a/chrome/browser/views/tabs/tab_strip.cc b/chrome/browser/views/tabs/tab_strip.cc index 1a9edb3..373186e 100644 --- a/chrome/browser/views/tabs/tab_strip.cc +++ b/chrome/browser/views/tabs/tab_strip.cc @@ -4,12 +4,14 @@ #include "chrome/browser/views/tabs/tab_strip.h" +#include "app/animation_container.h" #include "app/drag_drop_types.h" #include "app/l10n_util.h" #include "app/os_exchange_data.h" #include "app/resource_bundle.h" #include "app/slide_animation.h" #include "base/command_line.h" +#include "base/compiler_specific.h" #include "base/stl_util-inl.h" #include "chrome/browser/browser.h" #include "chrome/browser/browser_theme_provider.h" @@ -25,6 +27,7 @@ #include "chrome/browser/views/tabs/tab.h" #include "chrome/common/chrome_switches.h" #include "chrome/common/pref_names.h" +#include "chrome/common/url_constants.h" #include "gfx/canvas.h" #include "gfx/path.h" #include "gfx/size.h" @@ -54,10 +57,21 @@ using views::DropTargetEvent; -static const int kDefaultAnimationDurationMs = 200; -static const int kResizeLayoutAnimationDurationMs = 200; -static const int kReorderAnimationDurationMs = 200; -static const int kMiniTabAnimationDurationMs = 200; +// Duration of the first step in a new tab animation. +static const int kNewTabDurationMs = 50; + +// Duration of the last step in the new tab animation. +static const int kNewTab3DurationMs = 100; + +// Amount in pixels newly inserted tabs go past target bounds before animating +// to final position. This is used for ANIMATION_NEW_TAB_2. +static const int kNewTabOvershoot = 9; + +// Amount in pixels the newly inserted tab is clipped against the previous +// tab while animating. This is used to make sure the user doesn't see the +// newly inserted tab behind other tabs and so that its shadow isn't visible +// until the user can actually see the tab. +static const int kNetTabSelectedOffset = -13; static const int kNewTabButtonHOffset = -5; static const int kNewTabButtonVOffset = 5; @@ -81,6 +95,27 @@ static inline int Round(double x) { return static_cast<int>(floor(x + 0.5)); } +namespace { + +// Animation delegate used during new tab animation step 2 to vary the alpha of +// the tab. +class NewTabAlphaDelegate + : public views::BoundsAnimator::OwnedAnimationDelegate { + public: + explicit NewTabAlphaDelegate(Tab* tab) : tab_(tab) { + } + + virtual void AnimationProgressed(const Animation* animation) { + if (tab_->render_unselected()) + tab_->set_alpha(animation->GetCurrentValue()); + } + + private: + Tab* tab_; + + DISALLOW_COPY_AND_ASSIGN(NewTabAlphaDelegate); +}; + /////////////////////////////////////////////////////////////////////////////// // NewTabButton // @@ -124,292 +159,32 @@ class NewTabButton : public views::ImageButton { DISALLOW_COPY_AND_ASSIGN(NewTabButton); }; -/////////////////////////////////////////////////////////////////////////////// -// -// TabAnimation -// -// A base class for all TabStrip animations. -// -class TabStrip::TabAnimation : public AnimationDelegate { - public: - friend class TabStrip; - - // Possible types of animation. - enum Type { - INSERT, - REMOVE, - MOVE, - RESIZE, - MINI, - MINI_MOVE - }; - - TabAnimation(TabStrip* tabstrip, Type type) - : tabstrip_(tabstrip), - animation_(this), - start_selected_width_(0), - start_unselected_width_(0), - end_selected_width_(0), - end_unselected_width_(0), - layout_on_completion_(false), - type_(type) { - } - virtual ~TabAnimation() {} - - Type type() const { return type_; } - - void Start() { - animation_.SetSlideDuration(GetDuration()); - animation_.SetTweenType(SlideAnimation::EASE_OUT); - if (!animation_.IsShowing()) { - animation_.Reset(); - animation_.Show(); - } - } - - void Stop() { - animation_.Stop(); - } - - void set_layout_on_completion(bool layout_on_completion) { - layout_on_completion_ = layout_on_completion; - } - - // Retrieves the width for the Tab at the specified index if an animation is - // active. - static double GetCurrentTabWidth(TabStrip* tabstrip, - TabStrip::TabAnimation* animation, - int index) { - Tab* tab = tabstrip->GetTabAt(index); - double tab_width; - if (tab->mini()) { - tab_width = Tab::GetMiniWidth(); - } else { - double unselected, selected; - tabstrip->GetCurrentTabWidths(&unselected, &selected); - tab_width = tab->IsSelected() ? selected : unselected; - } - if (animation) { - double specified_tab_width = animation->GetWidthForTab(index); - if (specified_tab_width != -1) - tab_width = specified_tab_width; - } - return tab_width; - } +} // namespace - // Overridden from AnimationDelegate: - virtual void AnimationProgressed(const Animation* animation) { - tabstrip_->AnimationLayout(end_unselected_width_); +// AnimationDelegate used when removing a tab. Does the necessary cleanup when +// done. +class TabStrip::RemoveTabDelegate + : public views::BoundsAnimator::OwnedAnimationDelegate { + public: + RemoveTabDelegate(TabStrip* tab_strip, Tab* tab) + : tabstrip_(tab_strip), + tab_(tab) { } virtual void AnimationEnded(const Animation* animation) { - tabstrip_->FinishAnimation(this, layout_on_completion_); - // This object is destroyed now, so we can't do anything else after this. + CompleteRemove(); } virtual void AnimationCanceled(const Animation* animation) { - AnimationEnded(animation); - } - - // Returns the gap before the tab at the specified index. Subclass if during - // an animation you need to insert a gap before a tab. - virtual double GetGapWidth(int index) { - return 0; - } - - protected: - // Returns the duration of the animation. - virtual int GetDuration() const { - return kDefaultAnimationDurationMs; - } - - // Subclasses override to return the width of the Tab at the specified index - // at the current animation frame. -1 indicates the default width should be - // used for the Tab. - virtual double GetWidthForTab(int index) const { - return -1; // Use default. - } - - // Figure out the desired start and end widths for the specified pre- and - // post- animation tab counts. - void GenerateStartAndEndWidths(int start_tab_count, int end_tab_count, - int start_mini_count, - int end_mini_count) { - tabstrip_->GetDesiredTabWidths(start_tab_count, start_mini_count, - &start_unselected_width_, - &start_selected_width_); - double standard_tab_width = - static_cast<double>(TabRenderer::GetStandardSize().width()); - if (start_tab_count < end_tab_count && - start_unselected_width_ < standard_tab_width) { - double minimum_tab_width = - static_cast<double>(TabRenderer::GetMinimumUnselectedSize().width()); - start_unselected_width_ -= minimum_tab_width / start_tab_count; - } - tabstrip_->GenerateIdealBounds(); - tabstrip_->GetDesiredTabWidths(end_tab_count, end_mini_count, - &end_unselected_width_, - &end_selected_width_); - } - - TabStrip* tabstrip_; - SlideAnimation animation_; - - double start_selected_width_; - double start_unselected_width_; - double end_selected_width_; - double end_unselected_width_; - - private: - // True if a complete re-layout is required upon completion of the animation. - // Subclasses set this if they don't perform a complete layout - // themselves and canceling the animation may leave the strip in an - // inconsistent state. - bool layout_on_completion_; - - const Type type_; - - DISALLOW_COPY_AND_ASSIGN(TabAnimation); -}; - -/////////////////////////////////////////////////////////////////////////////// - -// Handles insertion of a Tab at |index|. -class TabStrip::InsertTabAnimation : public TabStrip::TabAnimation { - public: - explicit InsertTabAnimation(TabStrip* tabstrip, int index) - : TabAnimation(tabstrip, INSERT), - index_(index) { - int tab_count = tabstrip->GetTabCount(); - int end_mini_count = tabstrip->GetMiniTabCount(); - int start_mini_count = end_mini_count; - if (index < end_mini_count) - start_mini_count--; - GenerateStartAndEndWidths(tab_count - 1, tab_count, start_mini_count, - end_mini_count); - } - virtual ~InsertTabAnimation() {} - - protected: - // Overridden from TabStrip::TabAnimation: - virtual double GetWidthForTab(int index) const { - if (index == index_) { - bool is_selected = tabstrip_->model()->selected_index() == index; - double start_width, target_width; - if (index < tabstrip_->GetMiniTabCount()) { - start_width = Tab::GetMinimumSelectedSize().width(); - target_width = Tab::GetMiniWidth(); - } else { - target_width = - is_selected ? end_unselected_width_ : end_selected_width_; - start_width = - is_selected ? Tab::GetMinimumSelectedSize().width() : - Tab::GetMinimumUnselectedSize().width(); - } - double delta = target_width - start_width; - if (delta > 0) - return start_width + (delta * animation_.GetCurrentValue()); - return start_width; - } - - if (tabstrip_->GetTabAt(index)->mini()) - return Tab::GetMiniWidth(); - - if (tabstrip_->GetTabAt(index)->IsSelected()) { - double delta = end_selected_width_ - start_selected_width_; - return start_selected_width_ + (delta * animation_.GetCurrentValue()); - } - - double delta = end_unselected_width_ - start_unselected_width_; - return start_unselected_width_ + (delta * animation_.GetCurrentValue()); + CompleteRemove(); } private: - int index_; - - DISALLOW_COPY_AND_ASSIGN(InsertTabAnimation); -}; - -/////////////////////////////////////////////////////////////////////////////// - -// Handles removal of a Tab from |index| -class TabStrip::RemoveTabAnimation : public TabStrip::TabAnimation { - public: - RemoveTabAnimation(TabStrip* tabstrip, int index, TabContents* contents) - : TabAnimation(tabstrip, REMOVE), - index_(index) { - int tab_count = tabstrip->GetTabCount(); - int start_mini_count = tabstrip->GetMiniTabCount(); - int end_mini_count = start_mini_count; - if (index < start_mini_count) - end_mini_count--; - GenerateStartAndEndWidths(tab_count, tab_count - 1, start_mini_count, - end_mini_count); - // If the last non-mini-tab is being removed we force a layout on - // completion. This is necessary as the value returned by GetTabHOffset - // changes once the tab is actually removed (which happens at the end of - // the animation), and unless we layout GetTabHOffset won't be called after - // the removal. - // We do the same when the last mini-tab is being removed for the same - // reason. - set_layout_on_completion(start_mini_count > 0 && - (end_mini_count == 0 || - (start_mini_count == end_mini_count && - tab_count == start_mini_count + 1))); - } - - // Returns the index of the tab being removed. - int index() const { return index_; } - - virtual ~RemoveTabAnimation() { - } - - protected: - // Overridden from TabStrip::TabAnimation: - virtual double GetWidthForTab(int index) const { - Tab* tab = tabstrip_->GetTabAt(index); - if (index == index_) { - // The tab(s) being removed are gradually shrunken depending on the state - // of the animation. - // Removed animated Tabs are never selected. - if (tab->mini()) { - return animation_.CurrentValueBetween(Tab::GetMiniWidth(), - -kTabHOffset); - } - - double start_width = start_unselected_width_; - // Make sure target_width is at least abs(kTabHOffset), otherwise if - // less than kTabHOffset during layout tabs get negatively offset. - double target_width = - std::max(abs(kTabHOffset), - Tab::GetMinimumUnselectedSize().width() + kTabHOffset); - return animation_.CurrentValueBetween(start_width, target_width); - } - - if (tab->mini()) - return Tab::GetMiniWidth(); - - if (tabstrip_->available_width_for_tabs_ != -1 && - index_ != tabstrip_->GetTabCount() - 1) { - return TabStrip::TabAnimation::GetWidthForTab(index); - } - // All other tabs are sized according to the start/end widths specified at - // the start of the animation. - if (tab->IsSelected()) { - double delta = end_selected_width_ - start_selected_width_; - return start_selected_width_ + (delta * animation_.GetCurrentValue()); - } - double delta = end_unselected_width_ - start_unselected_width_; - return start_unselected_width_ + (delta * animation_.GetCurrentValue()); - } - - virtual void AnimationEnded(const Animation* animation) { - tabstrip_->RemoveTabAt(index_); + void CompleteRemove() { + tabstrip_->RemoveTab(tab_); HighlightCloseButton(); - TabStrip::TabAnimation::AnimationEnded(animation); } - private: // When the animation completes, we send the Container a message to simulate // a mouse moved event at the current mouse position. This tickles the Tab // the mouse is currently over to show the "hot" state of the close button. @@ -439,293 +214,10 @@ class TabStrip::RemoveTabAnimation : public TabStrip::TabAnimation { #endif } - int index_; - - DISALLOW_COPY_AND_ASSIGN(RemoveTabAnimation); -}; - -/////////////////////////////////////////////////////////////////////////////// - -// Handles the movement of a Tab from one position to another. -class TabStrip::MoveTabAnimation : public TabStrip::TabAnimation { - public: - MoveTabAnimation(TabStrip* tabstrip, int tab_a_index, int tab_b_index) - : TabAnimation(tabstrip, MOVE), - start_tab_a_bounds_(tabstrip_->GetIdealBounds(tab_b_index)), - start_tab_b_bounds_(tabstrip_->GetIdealBounds(tab_a_index)) { - tab_a_ = tabstrip_->GetTabAt(tab_a_index); - tab_b_ = tabstrip_->GetTabAt(tab_b_index); - - // Since we don't do a full TabStrip re-layout, we need to force a full - // layout upon completion since we're not guaranteed to be in a good state - // if for example the animation is canceled. - set_layout_on_completion(true); - } - virtual ~MoveTabAnimation() {} - - // Overridden from AnimationDelegate: - virtual void AnimationProgressed(const Animation* animation) { - // Position Tab A - double distance = start_tab_b_bounds_.x() - start_tab_a_bounds_.x(); - double delta = distance * animation_.GetCurrentValue(); - double new_x = start_tab_a_bounds_.x() + delta; - tab_a_->SetBounds(Round(new_x), tab_a_->y(), tab_a_->width(), - tab_a_->height()); - - // Position Tab B - distance = start_tab_a_bounds_.x() - start_tab_b_bounds_.x(); - delta = distance * animation_.GetCurrentValue(); - new_x = start_tab_b_bounds_.x() + delta; - tab_b_->SetBounds(Round(new_x), tab_b_->y(), tab_b_->width(), - tab_b_->height()); - - tabstrip_->SchedulePaint(); - } - - protected: - // Overridden from TabStrip::TabAnimation: - virtual int GetDuration() const { return kReorderAnimationDurationMs; } - - private: - // The two tabs being exchanged. - Tab* tab_a_; - Tab* tab_b_; - - // ...and their bounds. - gfx::Rect start_tab_a_bounds_; - gfx::Rect start_tab_b_bounds_; - - DISALLOW_COPY_AND_ASSIGN(MoveTabAnimation); -}; - -/////////////////////////////////////////////////////////////////////////////// - -// Handles the animated resize layout of the entire TabStrip from one width -// to another. -class TabStrip::ResizeLayoutAnimation : public TabStrip::TabAnimation { - public: - explicit ResizeLayoutAnimation(TabStrip* tabstrip) - : TabAnimation(tabstrip, RESIZE) { - int tab_count = tabstrip->GetTabCount(); - int mini_tab_count = tabstrip->GetMiniTabCount(); - GenerateStartAndEndWidths(tab_count, tab_count, mini_tab_count, - mini_tab_count); - InitStartState(); - } - virtual ~ResizeLayoutAnimation() { - } - - // Overridden from AnimationDelegate: - virtual void AnimationEnded(const Animation* animation) { - tabstrip_->needs_resize_layout_ = false; - TabStrip::TabAnimation::AnimationEnded(animation); - } - - protected: - // Overridden from TabStrip::TabAnimation: - virtual int GetDuration() const { - return kResizeLayoutAnimationDurationMs; - } - - virtual double GetWidthForTab(int index) const { - Tab* tab = tabstrip_->GetTabAt(index); - if (tab->mini()) - return Tab::GetMiniWidth(); - - if (tab->IsSelected()) { - return animation_.CurrentValueBetween(start_selected_width_, - end_selected_width_); - } - - return animation_.CurrentValueBetween(start_unselected_width_, - end_unselected_width_); - } - - private: - // We need to start from the current widths of the Tabs as they were last - // laid out, _not_ the last known good state, which is what'll be done if we - // don't measure the Tab sizes here and just go with the default TabAnimation - // behavior... - void InitStartState() { - for (int i = 0; i < tabstrip_->GetTabCount(); ++i) { - Tab* current_tab = tabstrip_->GetTabAt(i); - if (!current_tab->mini()) { - if (current_tab->IsSelected()) { - start_selected_width_ = current_tab->width(); - } else { - start_unselected_width_ = current_tab->width(); - } - } - } - } - - DISALLOW_COPY_AND_ASSIGN(ResizeLayoutAnimation); -}; - -// Handles a tabs mini-state changing while the tab does not change position -// in the model. -class TabStrip::MiniTabAnimation : public TabStrip::TabAnimation { - public: - explicit MiniTabAnimation(TabStrip* tabstrip, int index) - : TabAnimation(tabstrip, MINI), - index_(index) { - int tab_count = tabstrip->GetTabCount(); - int start_mini_count = tabstrip->GetMiniTabCount(); - int end_mini_count = start_mini_count; - if (tabstrip->GetTabAt(index)->mini()) - start_mini_count--; - else - start_mini_count++; - tabstrip_->GetTabAt(index)->set_animating_mini_change(true); - GenerateStartAndEndWidths(tab_count, tab_count, start_mini_count, - end_mini_count); - } - - protected: - // Overridden from TabStrip::TabAnimation: - virtual int GetDuration() const { - return kMiniTabAnimationDurationMs; - } - - virtual double GetWidthForTab(int index) const { - Tab* tab = tabstrip_->GetTabAt(index); - - if (index == index_) { - if (tab->mini()) { - return animation_.CurrentValueBetween( - start_selected_width_, - static_cast<double>(Tab::GetMiniWidth())); - } else { - return animation_.CurrentValueBetween( - static_cast<double>(Tab::GetMiniWidth()), - end_selected_width_); - } - } else if (tab->mini()) { - return Tab::GetMiniWidth(); - } - - if (tab->IsSelected()) { - return animation_.CurrentValueBetween(start_selected_width_, - end_selected_width_); - } - - return animation_.CurrentValueBetween(start_unselected_width_, - end_unselected_width_); - } - - private: - // Index of the tab whose mini state changed. - int index_; - - DISALLOW_COPY_AND_ASSIGN(MiniTabAnimation); -}; - -//////////////////////////////////////////////////////////////////////////////// - -// Handles the animation when a tabs mini state changes and the tab moves as a -// result. -class TabStrip::MiniMoveAnimation : public TabStrip::TabAnimation { - public: - explicit MiniMoveAnimation(TabStrip* tabstrip, - int from_index, - int to_index, - const gfx::Rect& start_bounds) - : TabAnimation(tabstrip, MINI_MOVE), - tab_(tabstrip->GetTabAt(to_index)), - start_bounds_(start_bounds), - from_index_(from_index), - to_index_(to_index) { - int tab_count = tabstrip->GetTabCount(); - int start_mini_count = tabstrip->GetMiniTabCount(); - int end_mini_count = start_mini_count; - if (tabstrip->GetTabAt(to_index)->mini()) - start_mini_count--; - else - start_mini_count++; - GenerateStartAndEndWidths(tab_count, tab_count, start_mini_count, - end_mini_count); - target_bounds_ = tabstrip->GetIdealBounds(to_index); - tab_->set_animating_mini_change(true); - } - - // Overridden from AnimationDelegate: - virtual void AnimationProgressed(const Animation* animation) { - // Do the normal layout. - TabAnimation::AnimationProgressed(animation); - - // Then special case the position of the tab being moved. - int x = animation_.CurrentValueBetween(start_bounds_.x(), - target_bounds_.x()); - int width = animation_.CurrentValueBetween(start_bounds_.width(), - target_bounds_.width()); - gfx::Rect tab_bounds(x, start_bounds_.y(), width, - start_bounds_.height()); - tab_->SetBounds(tab_bounds); - } - - virtual void AnimationEnded(const Animation* animation) { - tabstrip_->needs_resize_layout_ = false; - TabStrip::TabAnimation::AnimationEnded(animation); - } - - virtual double GetGapWidth(int index) { - if (to_index_ < from_index_) { - // The tab was mini. - if (index == to_index_) { - double current_size = - animation_.CurrentValueBetween(0, target_bounds_.width()); - if (current_size < -kTabHOffset) - return -(current_size + kTabHOffset); - } else if (index == from_index_ + 1) { - return animation_.CurrentValueBetween(start_bounds_.width(), 0); - } - } else { - // The tab was made a normal tab. - if (index == from_index_) { - return animation_.CurrentValueBetween(Tab::GetMiniWidth() + - kTabHOffset, 0); - } - } - return 0; - } - - protected: - // Overridden from TabStrip::TabAnimation: - virtual int GetDuration() const { return kReorderAnimationDurationMs; } - - virtual double GetWidthForTab(int index) const { - Tab* tab = tabstrip_->GetTabAt(index); - - if (index == to_index_) - return animation_.CurrentValueBetween(0, target_bounds_.width()); - - if (tab->mini()) - return Tab::GetMiniWidth(); - - if (tab->IsSelected()) { - return animation_.CurrentValueBetween(start_selected_width_, - end_selected_width_); - } - - return animation_.CurrentValueBetween(start_unselected_width_, - end_unselected_width_); - } - - private: - // The tab being moved. + TabStrip* tabstrip_; Tab* tab_; - // Initial bounds of tab_. - gfx::Rect start_bounds_; - - // Target bounds. - gfx::Rect target_bounds_; - - // Start and end indices of the tab. - int from_index_; - int to_index_; - - DISALLOW_COPY_AND_ASSIGN(MiniMoveAnimation); + DISALLOW_COPY_AND_ASSIGN(RemoveTabDelegate); }; /////////////////////////////////////////////////////////////////////////////// @@ -741,13 +233,14 @@ TabStrip::TabStrip(TabStripModel* model) needs_resize_layout_(false), current_unselected_width_(Tab::GetStandardSize().width()), current_selected_width_(Tab::GetStandardSize().width()), - available_width_for_tabs_(-1) { + available_width_for_tabs_(-1), + animation_container_(new AnimationContainer()), + ALLOW_THIS_IN_INITIALIZER_LIST(bounds_animator_(this)), + animation_type_(ANIMATION_DEFAULT) { Init(); } TabStrip::~TabStrip() { - active_animation_.reset(NULL); - // TODO(beng): (1031854) Restore this line once XPFrame/VistaFrame are dead. // model_->RemoveObserver(this); @@ -775,36 +268,34 @@ void TabStrip::DestroyDragController() { void TabStrip::DestroyDraggedSourceTab(Tab* tab) { // We could be running an animation that references this Tab. - if (active_animation_.get()) - active_animation_->Stop(); + StopAnimating(true); + // Make sure we leave the tab_data_ vector in a consistent state, otherwise // we'll be pointing to tabs that have been deleted and removed from the // child view list. - std::vector<TabData>::iterator it = tab_data_.begin(); - for (; it != tab_data_.end(); ++it) { - if (it->tab == tab) { - if (!model_->closing_all()) - NOTREACHED() << "Leaving in an inconsistent state!"; - tab_data_.erase(it); - break; - } + int tab_data_index = TabDataIndexOfTab(tab); + if (tab_data_index != -1) { + if (!model_->closing_all()) + NOTREACHED() << "Leaving in an inconsistent state!"; + tab_data_.erase(tab_data_.begin() + tab_data_index); } - tab->GetParent()->RemoveChildView(tab); + delete tab; + // Force a layout here, because if we've just quickly drag detached a Tab, // the stopping of the active animation above may have left the TabStrip in a // bad (visual) state. Layout(); } -gfx::Rect TabStrip::GetIdealBounds(int index) { - DCHECK_GE(index, 0); - DCHECK_LT(index, GetTabCount()); - return tab_data_.at(index).ideal_bounds; +gfx::Rect TabStrip::GetIdealBounds(int tab_data_index) { + DCHECK_GE(tab_data_index, 0); + DCHECK_LT(tab_data_index, GetTabCount()); + return tab_data_[tab_data_index].ideal_bounds; } Tab* TabStrip::GetSelectedTab() const { - return GetTabAtAdjustForAnimation(model()->selected_index()); + return GetTabAtModelIndex(model()->selected_index()); } void TabStrip::InitTabStripButtons() { @@ -836,7 +327,7 @@ int TabStrip::GetPreferredHeight() { void TabStrip::SetBackgroundOffset(const gfx::Point& offset) { int tab_count = GetTabCount(); for (int i = 0; i < tab_count; ++i) - GetTabAt(i)->SetBackgroundOffset(offset); + GetTabAtTabDataIndex(i)->SetBackgroundOffset(offset); } bool TabStrip::IsPositionInWindowCaption(const gfx::Point& point) { @@ -871,12 +362,10 @@ bool TabStrip::IsDragSessionActive() const { } void TabStrip::UpdateLoadingAnimations() { - for (int i = 0, index = 0; i < GetTabCount(); ++i, ++index) { - Tab* current_tab = GetTabAt(i); - if (current_tab->closing()) { - --index; - } else { - TabContents* contents = model_->GetTabContentsAt(index); + for (int i = 0, model_index = 0; i < GetTabCount(); ++i) { + Tab* current_tab = GetTabAtTabDataIndex(i); + if (!current_tab->closing()) { + TabContents* contents = model_->GetTabContentsAt(model_index); if (!contents || !contents->is_loading()) { current_tab->ValidateLoadingAnimation(Tab::ANIMATION_NONE); } else if (contents->waiting_for_response()) { @@ -884,12 +373,13 @@ void TabStrip::UpdateLoadingAnimations() { } else { current_tab->ValidateLoadingAnimation(Tab::ANIMATION_LOADING); } + model_index++; } } } bool TabStrip::IsAnimating() const { - return active_animation_.get() != NULL; + return bounds_animator_.IsAnimating() || new_tab_timer_.IsRunning(); } TabStrip* TabStrip::AsTabStrip() { @@ -901,6 +391,7 @@ TabStrip* TabStrip::AsTabStrip() { void TabStrip::PaintChildren(gfx::Canvas* canvas) { // Tabs are painted in reverse order, so they stack to the left. + int tab_count = GetTabCount(); // Phantom tabs appear behind all other tabs and are rendered first. To make // them slightly transparent we render them to a different layer. @@ -910,8 +401,8 @@ void TabStrip::PaintChildren(gfx::Canvas* canvas) { canvas->saveLayerAlpha(&bounds, kPhantomTabAlpha, SkCanvas::kARGB_ClipLayer_SaveFlag); canvas->drawARGB(0, 255, 255, 255, SkXfermode::kClear_Mode); - for (int i = GetTabCount() - 1; i >= 0; --i) { - Tab* tab = GetTabAt(i); + for (int i = tab_count - 1; i >= 0; --i) { + Tab* tab = GetTabAtTabDataIndex(i); if (tab->phantom()) tab->ProcessPaint(canvas); } @@ -920,8 +411,8 @@ void TabStrip::PaintChildren(gfx::Canvas* canvas) { canvas->saveLayerAlpha(&bounds, kPhantomTabIconAlpha, SkCanvas::kARGB_ClipLayer_SaveFlag); canvas->drawARGB(0, 255, 255, 255, SkXfermode::kClear_Mode); - for (int i = GetTabCount() - 1; i >= 0; --i) { - Tab* tab = GetTabAt(i); + for (int i = tab_count - 1; i >= 0; --i) { + Tab* tab = GetTabAtTabDataIndex(i); if (tab->phantom()) { canvas->save(); canvas->ClipRectInt(tab->MirroredX(), tab->y(), tab->width(), @@ -936,14 +427,25 @@ void TabStrip::PaintChildren(gfx::Canvas* canvas) { Tab* selected_tab = NULL; - for (int i = GetTabCount() - 1; i >= 0; --i) { - Tab* tab = GetTabAt(i); + for (int i = tab_count - 1; i >= 0; --i) { + Tab* tab = GetTabAtTabDataIndex(i); // We must ask the _Tab's_ model, not ourselves, because in some situations // the model will be different to this object, e.g. when a Tab is being // removed after its TabContents has been destroyed. if (!tab->phantom()) { if (!tab->IsSelected()) { - tab->ProcessPaint(canvas); + if (tab->render_unselected() && model_->count() > 1) { + // See comment above kNetTabAnimationSelectedOffset as to why we do + // this. + Tab* last_tab = GetTabAtModelIndex(model_->count() - 2); + canvas->save(); + int clip_x = last_tab->bounds().right() + kNetTabSelectedOffset; + canvas->ClipRectInt(clip_x, 0, width() - clip_x, height()); + tab->ProcessPaint(canvas); + canvas->restore(); + } else { + tab->ProcessPaint(canvas); + } } else { selected_tab = tab; } @@ -962,23 +464,30 @@ void TabStrip::PaintChildren(gfx::Canvas* canvas) { paint); } + if (animation_type_ == ANIMATION_NEW_TAB_3) + newtab_button_->ProcessPaint(canvas); + // Paint the selected tab last, so it overlaps all the others. if (selected_tab) selected_tab->ProcessPaint(canvas); // Paint the New Tab button. - newtab_button_->ProcessPaint(canvas); + if (animation_type_ != ANIMATION_NEW_TAB_1 && + animation_type_ != ANIMATION_NEW_TAB_2 && + animation_type_ != ANIMATION_NEW_TAB_3) { + newtab_button_->ProcessPaint(canvas); + } } // Overridden to support automation. See automation_proxy_uitest.cc. views::View* TabStrip::GetViewByID(int view_id) const { if (GetTabCount() > 0) { if (view_id == VIEW_ID_TAB_LAST) { - return GetTabAt(GetTabCount() - 1); + return GetTabAtTabDataIndex(GetTabCount() - 1); } else if ((view_id >= VIEW_ID_TAB_0) && (view_id < VIEW_ID_TAB_LAST)) { int index = view_id - VIEW_ID_TAB_0; if (index >= 0 && index < GetTabCount()) { - return GetTabAt(index); + return GetTabAtTabDataIndex(index); } else { return NULL; } @@ -991,22 +500,15 @@ views::View* TabStrip::GetViewByID(int view_id) const { void TabStrip::Layout() { // Called from: // - window resize - // - animation completion - if (active_animation_.get()) - active_animation_->Stop(); + StopAnimating(false); + GenerateIdealBounds(); - int tab_count = GetTabCount(); - int tab_right = 0; - for (int i = 0; i < tab_count; ++i) { - const gfx::Rect& bounds = tab_data_.at(i).ideal_bounds; - Tab* tab = GetTabAt(i); - tab->set_animating_mini_change(false); - tab->SetBounds(bounds.x(), bounds.y(), bounds.width(), bounds.height()); - tab_right = bounds.right(); - tab_right += GetTabHOffset(i + 1); - } - LayoutNewTabButton(static_cast<double>(tab_right), current_unselected_width_); + for (int i = 0, tab_count = GetTabCount(); i < tab_count; ++i) + tab_data_[i].tab->SetBounds(tab_data_[i].ideal_bounds); + + newtab_button_->SetBounds(newtab_button_bounds_); + SchedulePaint(); } @@ -1015,6 +517,9 @@ gfx::Size TabStrip::GetPreferredSize() { } void TabStrip::OnDragEntered(const DropTargetEvent& event) { + // Force animations to stop, otherwise it makes the index calculation tricky. + StopAnimating(true); + UpdateDropIndex(event); } @@ -1085,10 +590,10 @@ views::View* TabStrip::GetViewForPoint(const gfx::Point& point) { // left-adjacent Tab, so we look ahead for it as we walk. int tab_count = GetTabCount(); for (int i = 0; i < tab_count; ++i) { - Tab* next_tab = i < (tab_count - 1) ? GetTabAt(i + 1) : NULL; + Tab* next_tab = i < (tab_count - 1) ? GetTabAtTabDataIndex(i + 1) : NULL; if (next_tab && next_tab->IsSelected() && IsPointInTab(next_tab, point)) return next_tab; - Tab* tab = GetTabAt(i); + Tab* tab = GetTabAtTabDataIndex(i); if (IsPointInTab(tab, point)) return tab; } @@ -1117,16 +622,15 @@ void TabStrip::ViewHierarchyChanged(bool is_add, // TabStrip, TabStripModelObserver implementation: void TabStrip::TabInsertedAt(TabContents* contents, - int index, + int model_index, bool foreground) { DCHECK(contents); - DCHECK(index == TabStripModel::kNoTab || model_->ContainsIndex(index)); + DCHECK(model_index == TabStripModel::kNoTab || + model_->ContainsIndex(model_index)); // This tab may be attached to another browser window, we should notify // renderer. contents->render_view_host()->UpdateBrowserWindowId( contents->controller().window_id().id()); - if (active_animation_.get()) - active_animation_->Stop(); bool contains_tab = false; Tab* tab = NULL; @@ -1145,11 +649,7 @@ void TabStrip::TabInsertedAt(TabContents* contents, } // See if we're already in the list. We don't want to add ourselves twice. - std::vector<TabData>::const_iterator iter = tab_data_.begin(); - for (; iter != tab_data_.end() && !contains_tab; ++iter) { - if (iter->tab == tab) - contains_tab = true; - } + contains_tab = (TabDataIndexOfTab(tab) != -1); } // Otherwise we need to make a new Tab. @@ -1159,40 +659,41 @@ void TabStrip::TabInsertedAt(TabContents* contents, // Only insert if we're not already in the list. if (!contains_tab) { TabData d = { tab, gfx::Rect() }; - tab_data_.insert(tab_data_.begin() + index, d); - tab->UpdateData(contents, model_->IsPhantomTab(index), false); + tab_data_.insert(tab_data_.begin() + + ModelIndexToTabDataIndex(model_index), d); + tab->UpdateData(contents, model_->IsPhantomTab(model_index), false); } - tab->set_mini(model_->IsMiniTab(index)); - tab->SetBlocked(model_->IsTabBlocked(index)); + tab->set_mini(model_->IsMiniTab(model_index)); + tab->SetBlocked(model_->IsTabBlocked(model_index)); // We only add the tab to the child list if it's not already - an invisible // tab maintained by the DraggedTabController will already be parented. - if (!tab->GetParent()) + if (!tab->GetParent()) { AddChildView(tab); + tab->SetAnimationContainer(animation_container_.get()); + } // Don't animate the first tab, it looks weird, and don't animate anything // if the containing window isn't visible yet. if (GetTabCount() > 1 && GetWindow() && GetWindow()->IsVisible()) { - StartInsertTabAnimation(index); + if (ShouldStartIntertTabAnimationAtEnd(model_index, foreground)) { + StartInsertTabAnimationAtEnd(); + } else { + StartInsertTabAnimation(model_index); + } } else { Layout(); } } -void TabStrip::TabDetachedAt(TabContents* contents, int index) { - GenerateIdealBounds(); - StartRemoveTabAnimation(index, contents); - // Have to do this _after_ calling StartRemoveTabAnimation, so that any - // previous remove is completed fully and index is valid in sync with the - // model index. - GetTabAt(index)->set_closing(true); +void TabStrip::TabDetachedAt(TabContents* contents, int model_index) { + StartRemoveTabAnimation(model_index); } void TabStrip::TabSelectedAt(TabContents* old_contents, TabContents* new_contents, - int index, + int model_index, bool user_gesture) { - DCHECK(index >= 0 && index < GetTabCount()); // We have "tiny tabs" if the tabs are so tiny that the unselected ones are // a different size to the selected ones. bool tiny_tabs = current_unselected_width_ != current_selected_width_; @@ -1202,117 +703,133 @@ void TabStrip::TabSelectedAt(TabContents* old_contents, SchedulePaint(); } - int old_index = model_->GetIndexOfTabContents(old_contents); - if (old_index >= 0) - GetTabAt(old_index)->StopMiniTabTitleAnimation(); + int old_model_index = model_->GetIndexOfTabContents(old_contents); + if (old_model_index >= 0) { + GetTabAtTabDataIndex(ModelIndexToTabDataIndex(old_model_index))-> + StopMiniTabTitleAnimation(); + } } -void TabStrip::TabMoved(TabContents* contents, int from_index, int to_index) { - gfx::Rect start_bounds = GetIdealBounds(from_index); - Tab* tab = GetTabAt(from_index); - tab_data_.erase(tab_data_.begin() + from_index); - TabData data = {tab, gfx::Rect()}; - tab->set_mini(model_->IsMiniTab(to_index)); - tab->SetBlocked(model_->IsTabBlocked(to_index)); - tab_data_.insert(tab_data_.begin() + to_index, data); - if (tab->phantom() != model_->IsPhantomTab(to_index)) - tab->set_phantom(!tab->phantom()); - GenerateIdealBounds(); - StartMoveTabAnimation(from_index, to_index); +void TabStrip::TabMoved(TabContents* contents, + int from_model_index, + int to_model_index) { + StartMoveTabAnimation(from_model_index, to_model_index); } -void TabStrip::TabChangedAt(TabContents* contents, int index, +void TabStrip::TabChangedAt(TabContents* contents, + int model_index, TabChangeType change_type) { // Index is in terms of the model. Need to make sure we adjust that index in // case we have an animation going. - Tab* tab = GetTabAtAdjustForAnimation(index); + Tab* tab = GetTabAtModelIndex(model_index); if (change_type == TITLE_NOT_LOADING) { if (tab->mini() && !tab->IsSelected()) tab->StartMiniTabTitleAnimation(); // We'll receive another notification of the change asynchronously. return; } - tab->UpdateData(contents, model_->IsPhantomTab(index), + tab->UpdateData(contents, model_->IsPhantomTab(model_index), change_type == LOADING_ONLY); tab->UpdateFromModel(); } void TabStrip::TabReplacedAt(TabContents* old_contents, TabContents* new_contents, - int index) { - TabChangedAt(new_contents, index, ALL); + int model_index) { + TabChangedAt(new_contents, model_index, ALL); } -void TabStrip::TabMiniStateChanged(TabContents* contents, int index) { - GetTabAt(index)->set_mini(model_->IsMiniTab(index)); +void TabStrip::TabMiniStateChanged(TabContents* contents, int model_index) { + GetTabAtModelIndex(model_index)->set_mini( + model_->IsMiniTab(model_index)); // Don't animate if the window isn't visible yet. The window won't be visible // when dragging a mini-tab to a new window. if (GetWindow() && GetWindow()->IsVisible()) - StartMiniTabAnimation(index); + StartMiniTabAnimation(); else Layout(); } -void TabStrip::TabBlockedStateChanged(TabContents* contents, int index) { - GetTabAt(index)->SetBlocked(model_->IsTabBlocked(index)); +void TabStrip::TabBlockedStateChanged(TabContents* contents, int model_index) { + GetTabAtModelIndex(model_index)->SetBlocked( + model_->IsTabBlocked(model_index)); } /////////////////////////////////////////////////////////////////////////////// // TabStrip, Tab::Delegate implementation: bool TabStrip::IsTabSelected(const Tab* tab) const { - if (tab->closing()) + if (tab->closing() || tab->render_unselected()) return false; - return GetIndexOfTab(tab) == model_->selected_index(); + return GetModelIndexOfTab(tab) == model_->selected_index(); } bool TabStrip::IsTabPinned(const Tab* tab) const { if (tab->closing()) return false; - return model_->IsTabPinned(GetIndexOfTab(tab)); + return model_->IsTabPinned(GetModelIndexOfTab(tab)); } void TabStrip::SelectTab(Tab* tab) { - int index = GetIndexOfTab(tab); - if (model_->ContainsIndex(index)) - model_->SelectTabContentsAt(index, true); + int model_index = GetModelIndexOfTab(tab); + if (model_->ContainsIndex(model_index)) + model_->SelectTabContentsAt(model_index, true); } void TabStrip::CloseTab(Tab* tab) { - int tab_index = GetIndexOfTab(tab); - if (model_->ContainsIndex(tab_index)) { - TabContents* contents = model_->GetTabContentsAt(tab_index); + int model_index = GetModelIndexOfTab(tab); + if (model_->ContainsIndex(model_index)) { + TabContents* contents = model_->GetTabContentsAt(model_index); if (contents) UserMetrics::RecordAction(UserMetricsAction("CloseTab_Mouse"), contents->profile()); - Tab* last_tab = GetTabAt(GetTabCount() - 1); - // Limit the width available to the TabStrip for laying out Tabs, so that - // Tabs are not resized until a later time (when the mouse pointer leaves - // the TabStrip). - available_width_for_tabs_ = GetAvailableWidthForTabs(last_tab); - needs_resize_layout_ = true; - AddMessageLoopObserver(); + if (model_index + 1 != model_->count() && model_->count() > 1) { + Tab* last_tab = GetTabAtModelIndex(model_->count() - 2); + // Limit the width available to the TabStrip for laying out Tabs, so that + // Tabs are not resized until a later time (when the mouse pointer leaves + // the TabStrip). + available_width_for_tabs_ = GetAvailableWidthForTabs(last_tab); + needs_resize_layout_ = true; + AddMessageLoopObserver(); + } else if (model_->count() > 1) { + Tab* last_tab = GetTabAtModelIndex(model_->count() - 1); + // Limit the width available to the TabStrip for laying out Tabs, so that + // Tabs are not resized until a later time (when the mouse pointer leaves + // the TabStrip). + available_width_for_tabs_ = GetAvailableWidthForTabs(last_tab); + needs_resize_layout_ = true; + AddMessageLoopObserver(); + } // Note that the next call might not close the tab (because of unload // hanlders or if the delegate veto the close). - model_->CloseTabContentsAt(tab_index); + model_->CloseTabContentsAt(model_index); } } bool TabStrip::IsCommandEnabledForTab( TabStripModel::ContextMenuCommand command_id, const Tab* tab) const { - int index = GetIndexOfTab(tab); - if (model_->ContainsIndex(index)) - return model_->IsContextMenuCommandEnabled(index, command_id); + int model_index = GetModelIndexOfTab(tab); + if (model_->ContainsIndex(model_index)) + return model_->IsContextMenuCommandEnabled(model_index, command_id); return false; } void TabStrip::ExecuteCommandForTab( TabStripModel::ContextMenuCommand command_id, Tab* tab) { - int index = GetIndexOfTab(tab); - if (model_->ContainsIndex(index)) - model_->ExecuteContextMenuCommand(index, command_id); + int model_index = GetModelIndexOfTab(tab); + if (model_->ContainsIndex(model_index)) + model_->ExecuteContextMenuCommand(model_index, command_id); +} + +void TabStrip::OnBoundsAnimatorDone(views::BoundsAnimator* animator) { + AnimationType last_type = animation_type_; + + ResetAnimationState(false); + + if (last_type == ANIMATION_NEW_TAB_2) + NewTabAnimation2Done(); } void TabStrip::StartHighlightTabsForCommand( @@ -1320,13 +837,13 @@ void TabStrip::StartHighlightTabsForCommand( if (command_id == TabStripModel::CommandCloseTabsOpenedBy || command_id == TabStripModel::CommandCloseOtherTabs || command_id == TabStripModel::CommandCloseTabsToRight) { - int index = GetIndexOfTab(tab); - if (model_->ContainsIndex(index)) { + int model_index = GetModelIndexOfTab(tab); + if (model_->ContainsIndex(model_index)) { std::vector<int> indices = - model_->GetIndicesClosedByCommand(index, command_id); + model_->GetIndicesClosedByCommand(model_index, command_id); for (std::vector<int>::const_iterator i = indices.begin(); i != indices.end(); ++i) { - GetTabAtAdjustForAnimation(*i)->StartPulse(); + GetTabAtModelIndex(*i)->StartPulse(); } } } @@ -1344,7 +861,7 @@ void TabStrip::StopHighlightTabsForCommand( void TabStrip::StopAllHighlighting() { for (int i = 0; i < GetTabCount(); ++i) - GetTabAt(i)->StopPulse(); + GetTabAtTabDataIndex(i)->StopPulse(); } void TabStrip::MaybeStartDrag(Tab* tab, const views::MouseEvent& event) { @@ -1354,8 +871,8 @@ void TabStrip::MaybeStartDrag(Tab* tab, const views::MouseEvent& event) { // the user is dragging. if (IsAnimating() || tab->closing() || !HasAvailableDragActions()) return; - int index = GetIndexOfTab(tab); - if (!model_->ContainsIndex(index)) { + int model_index = GetModelIndexOfTab(tab); + if (!model_->ContainsIndex(model_index)) { CHECK(false); return; } @@ -1463,15 +980,18 @@ void TabStrip::DidProcessEvent(GdkEvent* event) { void TabStrip::Init() { SetID(VIEW_ID_TAB_STRIP); model_->AddObserver(this); - newtab_button_size_.SetSize(kNewTabButtonWidth, kNewTabButtonHeight); - if (browser_defaults::kSizeTabButtonToTopOfTabStrip) - newtab_button_size_.set_height(kNewTabButtonHeight + kNewTabButtonVOffset); + newtab_button_bounds_.SetRect(0, 0, kNewTabButtonWidth, kNewTabButtonHeight); + if (browser_defaults::kSizeTabButtonToTopOfTabStrip) { + newtab_button_bounds_.set_height( + kNewTabButtonHeight + kNewTabButtonVOffset); + } if (drop_indicator_width == 0) { // Direction doesn't matter, both images are the same size. SkBitmap* drop_image = GetDropArrowImage(true); drop_indicator_width = drop_image->width(); drop_indicator_height = drop_image->height(); } + bounds_animator_.set_observer(this); } void TabStrip::LoadNewTabButtonImage() { @@ -1501,20 +1021,14 @@ void TabStrip::LoadNewTabButtonImage() { delete tp; } -Tab* TabStrip::GetTabAt(int index) const { - DCHECK_GE(index, 0); - DCHECK_LT(index, GetTabCount()); - return tab_data_.at(index).tab; +Tab* TabStrip::GetTabAtTabDataIndex(int tab_data_index) const { + DCHECK_GE(tab_data_index, 0); + DCHECK_LT(tab_data_index, GetTabCount()); + return tab_data_[tab_data_index].tab; } -Tab* TabStrip::GetTabAtAdjustForAnimation(int index) const { - if (active_animation_.get() && - active_animation_->type() == TabAnimation::REMOVE && - index >= - static_cast<RemoveTabAnimation*>(active_animation_.get())->index()) { - index++; - } - return GetTabAt(index); +Tab* TabStrip::GetTabAtModelIndex(int model_index) const { + return GetTabAtTabDataIndex(ModelIndexToTabDataIndex(model_index)); } int TabStrip::GetTabCount() const { @@ -1547,7 +1061,7 @@ void TabStrip::GetDesiredTabWidths(int tab_count, int available_width; if (available_width_for_tabs_ < 0) { available_width = width(); - available_width -= (kNewTabButtonHOffset + newtab_button_size_.width()); + available_width -= (kNewTabButtonHOffset + newtab_button_bounds_.width()); } else { // Interesting corner case: if |available_width_for_tabs_| > the result // of the calculation in the conditional arm above, the strip is in @@ -1607,14 +1121,6 @@ void TabStrip::GetDesiredTabWidths(int tab_count, } } -int TabStrip::GetTabHOffset(int tab_index) { - if (tab_index < GetTabCount() && GetTabAt(tab_index - 1)->mini() && - !GetTabAt(tab_index)->mini()) { - return mini_to_non_mini_gap_ + kTabHOffset; - } - return kTabHOffset; -} - void TabStrip::ResizeLayoutTabs() { // We've been called back after the TabStrip has been emptied out (probably // just prior to the window being destroyed). We need to do nothing here or @@ -1635,7 +1141,7 @@ void TabStrip::ResizeLayoutTabs() { // mini-tabs have the same width), so there is nothing to do. return; } - Tab* first_tab = GetTabAt(mini_tab_count); + Tab* first_tab = GetTabAtTabDataIndex(mini_tab_count); double unselected, selected; GetDesiredTabWidths(GetTabCount(), mini_tab_count, &unselected, &selected); int w = Round(first_tab->IsSelected() ? selected : selected); @@ -1688,14 +1194,13 @@ gfx::Rect TabStrip::GetDropBounds(int drop_index, DCHECK(drop_index != -1); int center_x; if (drop_index < GetTabCount()) { - Tab* tab = GetTabAt(drop_index); - // TODO(sky): update these for mini-tabs. + Tab* tab = GetTabAtTabDataIndex(drop_index); if (drop_before) center_x = tab->x() - (kTabHOffset / 2); else center_x = tab->x() + (tab->width() / 2); } else { - Tab* last_tab = GetTabAt(drop_index - 1); + Tab* last_tab = GetTabAtTabDataIndex(drop_index - 1); center_x = last_tab->x() + last_tab->width() + (kTabHOffset / 2); } @@ -1731,7 +1236,7 @@ void TabStrip::UpdateDropIndex(const DropTargetEvent& event) { const int x = MirroredXCoordinateInsideView(event.x()); // We don't allow replacing the urls of mini-tabs. for (int i = GetMiniTabCount(); i < GetTabCount(); ++i) { - Tab* tab = GetTabAt(i); + Tab* tab = GetTabAtTabDataIndex(i); const int tab_max_x = tab->x() + tab->width(); const int hot_width = tab->width() / 3; if (x < tab_max_x) { @@ -1749,25 +1254,26 @@ void TabStrip::UpdateDropIndex(const DropTargetEvent& event) { SetDropIndex(GetTabCount(), true); } -void TabStrip::SetDropIndex(int index, bool drop_before) { - if (index == -1) { +void TabStrip::SetDropIndex(int tab_data_index, bool drop_before) { + if (tab_data_index == -1) { if (drop_info_.get()) drop_info_.reset(NULL); return; } - if (drop_info_.get() && drop_info_->drop_index == index && + if (drop_info_.get() && drop_info_->drop_index == tab_data_index && drop_info_->drop_before == drop_before) { return; } bool is_beneath; - gfx::Rect drop_bounds = GetDropBounds(index, drop_before, &is_beneath); + gfx::Rect drop_bounds = GetDropBounds(tab_data_index, drop_before, + &is_beneath); if (!drop_info_.get()) { - drop_info_.reset(new DropInfo(index, drop_before, !is_beneath)); + drop_info_.reset(new DropInfo(tab_data_index, drop_before, !is_beneath)); } else { - drop_info_->drop_index = index; + drop_info_->drop_index = tab_data_index; drop_info_->drop_before = drop_before; if (is_beneath == drop_info_->point_down) { drop_info_->point_down = !is_beneath; @@ -1844,8 +1350,19 @@ TabStrip::DropInfo::~DropInfo() { // - Tab reorder void TabStrip::GenerateIdealBounds() { int tab_count = GetTabCount(); + int non_closing_tab_count = 0; + int mini_tab_count = 0; + for (int i = 0; i < tab_count; ++i) { + if (!tab_data_[i].tab->closing()) { + ++non_closing_tab_count; + if (tab_data_[i].tab->mini()) + mini_tab_count++; + } + } + double unselected, selected; - GetDesiredTabWidths(tab_count, GetMiniTabCount(), &unselected, &selected); + GetDesiredTabWidths(non_closing_tab_count, mini_tab_count, &unselected, + &selected); current_unselected_width_ = unselected; current_selected_width_ = selected; @@ -1854,139 +1371,258 @@ void TabStrip::GenerateIdealBounds() { // selected state or the number of tabs in the strip! int tab_height = Tab::GetStandardSize().height(); double tab_x = 0; + bool last_was_mini = false; for (int i = 0; i < tab_count; ++i) { - Tab* tab = GetTabAt(i); - double tab_width = unselected; - if (tab->mini()) - tab_width = Tab::GetMiniWidth(); - else if (tab->IsSelected()) - tab_width = selected; - double end_of_tab = tab_x + tab_width; - int rounded_tab_x = Round(tab_x); - gfx::Rect state(rounded_tab_x, 0, Round(end_of_tab) - rounded_tab_x, + if (!tab_data_[i].tab->closing()) { + Tab* tab = GetTabAtTabDataIndex(i); + double tab_width = unselected; + if (tab->mini()) { + tab_width = Tab::GetMiniWidth(); + } else { + if (last_was_mini) { + // Give a bigger gap between mini and non-mini tabs. + tab_x += mini_to_non_mini_gap_; + } + if (tab->IsSelected()) + tab_width = selected; + } + double end_of_tab = tab_x + tab_width; + int rounded_tab_x = Round(tab_x); + tab_data_[i].ideal_bounds = + gfx::Rect(rounded_tab_x, 0, Round(end_of_tab) - rounded_tab_x, tab_height); - tab_data_.at(i).ideal_bounds = state; - tab_x = end_of_tab + GetTabHOffset(i + 1); + tab_x = end_of_tab + kTabHOffset; + last_was_mini = tab->mini(); + } } -} -void TabStrip::LayoutNewTabButton(double last_tab_right, - double unselected_width) { - int delta = abs(Round(unselected_width) - Tab::GetStandardSize().width()); - int v_offset = browser_defaults::kSizeTabButtonToTopOfTabStrip ? + // Update bounds of new tab button. + int new_tab_x; + int new_tab_y = browser_defaults::kSizeTabButtonToTopOfTabStrip ? 0 : kNewTabButtonVOffset; - if (delta > 1 && !needs_resize_layout_) { + if (abs(Round(unselected) - Tab::GetStandardSize().width()) > 1 && + available_width_for_tabs_ == -1) { // We're shrinking tabs, so we need to anchor the New Tab button to the // right edge of the TabStrip's bounds, rather than the right edge of the // right-most Tab, otherwise it'll bounce when animating. - newtab_button_->SetBounds(width() - newtab_button_size_.width(), - v_offset, - newtab_button_size_.width(), - newtab_button_size_.height()); + new_tab_x = width() - newtab_button_bounds_.width(); } else { - newtab_button_->SetBounds( - Round(last_tab_right - kTabHOffset) + kNewTabButtonHOffset, - v_offset, newtab_button_size_.width(), newtab_button_size_.height()); + new_tab_x = Round(tab_x - kTabHOffset) + kNewTabButtonHOffset; } + newtab_button_bounds_.set_origin(gfx::Point(new_tab_x, new_tab_y)); } -// Called from: -// - animation tick -void TabStrip::AnimationLayout(double unselected_width) { - int tab_height = Tab::GetStandardSize().height(); - double tab_x = 0; - for (int i = 0; i < GetTabCount(); ++i) { - TabAnimation* animation = active_animation_.get(); - if (animation) - tab_x += animation->GetGapWidth(i); - double tab_width = TabAnimation::GetCurrentTabWidth(this, animation, i); - double end_of_tab = tab_x + tab_width; - int rounded_tab_x = Round(tab_x); - Tab* tab = GetTabAt(i); - tab->SetBounds(rounded_tab_x, 0, Round(end_of_tab) - rounded_tab_x, - tab_height); - tab_x = end_of_tab + GetTabHOffset(i + 1); - } - LayoutNewTabButton(tab_x, unselected_width); - SchedulePaint(); +void TabStrip::NewTabAnimation1Done() { + int tab_data_index = static_cast<int>(tab_data_.size() - 1); + Tab* tab = GetTabAtTabDataIndex(tab_data_index); + + gfx::Rect old_tab_bounds = tab->bounds(); + + GenerateIdealBounds(); + + gfx::Rect& end_bounds = tab_data_[tab_data_index].ideal_bounds; + end_bounds.Offset(kNewTabOvershoot, 0); + + int x = old_tab_bounds.right() - end_bounds.width(); + int w = end_bounds.width(); + if (x < 0) { + w += x; + x = 0; + } + tab->SetBounds(x, old_tab_bounds.y(), w, end_bounds.height()); + + AnimateToIdealBounds(); + + animation_type_ = ANIMATION_NEW_TAB_2; + tab->set_render_as_new_tab(false); + tab->set_render_unselected(true); + tab->set_alpha(0); + + // BoundsAnimator takes ownership of NewTabAlphaDelegate. + bounds_animator_.SetAnimationDelegate(tab, new NewTabAlphaDelegate(tab), + true); +} + +void TabStrip::NewTabAnimation2Done() { + animation_type_ = ANIMATION_NEW_TAB_3; + + GenerateIdealBounds(); + + AnimateToIdealBounds(); + + SlideAnimation* animation = new SlideAnimation(NULL); + animation->SetSlideDuration(kNewTab3DurationMs); + animation->SetTweenType(SlideAnimation::EASE_IN_OUT); + + // BoundsAnimator takes ownership of animation. + bounds_animator_.SetAnimationForView(tab_data_.back().tab, animation); +} + +void TabStrip::AnimateToIdealBounds() { + for (size_t i = 0; i < tab_data_.size(); ++i) { + if (!tab_data_[i].tab->closing()) { + bounds_animator_.AnimateViewTo(tab_data_[i].tab, + tab_data_[i].ideal_bounds, + false); + } + } + + if (animation_type_ != ANIMATION_NEW_TAB_3) { + bounds_animator_.AnimateViewTo(newtab_button_, + newtab_button_bounds_, + false); + } +} + +bool TabStrip::ShouldStartIntertTabAnimationAtEnd(int model_index, + bool foreground) { + return foreground && (model_index + 1 == model_->count()) && + (model_->GetTabContentsAt(model_index)->GetURL() == + GURL(chrome::kChromeUINewTabURL)); } void TabStrip::StartResizeLayoutAnimation() { - if (active_animation_.get()) - active_animation_->Stop(); - active_animation_.reset(new ResizeLayoutAnimation(this)); - active_animation_->Start(); + ResetAnimationState(true); + GenerateIdealBounds(); + AnimateToIdealBounds(); } -void TabStrip::StartInsertTabAnimation(int index) { +void TabStrip::StartInsertTabAnimationAtEnd() { + ResetAnimationState(true); + // The TabStrip can now use its entire width to lay out Tabs. available_width_for_tabs_ = -1; - if (active_animation_.get()) - active_animation_->Stop(); - active_animation_.reset(new InsertTabAnimation(this, index)); - active_animation_->Start(); + + animation_type_ = ANIMATION_NEW_TAB_1; + + GenerateIdealBounds(); + + int tab_data_index = ModelIndexToTabDataIndex(model_->count() - 1); + Tab* tab = tab_data_[tab_data_index].tab; + tab->SizeToNewTabButtonImages(); + tab->SetBounds(newtab_button_->x() + + (newtab_button_->width() - tab->width()) / 2, + tab_data_[tab_data_index].ideal_bounds.y(), + tab->width(), tab->height()); + tab->set_render_as_new_tab(true); + + new_tab_timer_.Start(base::TimeDelta::FromMilliseconds(kNewTabDurationMs), + this, &TabStrip::NewTabAnimation1Done); } -void TabStrip::StartRemoveTabAnimation(int index, TabContents* contents) { - if (active_animation_.get()) { - // Some animations (e.g. MoveTabAnimation) cause there to be a Layout when - // they're completed (which includes canceled). Since |tab_data_| is now - // inconsistent with TabStripModel, doing this Layout will crash now, so - // we ask the MoveTabAnimation to skip its Layout (the state will be - // corrected by the RemoveTabAnimation we're about to initiate). - active_animation_->set_layout_on_completion(false); - active_animation_->Stop(); +void TabStrip::StartInsertTabAnimation(int model_index) { + ResetAnimationState(true); + + // The TabStrip can now use its entire width to lay out Tabs. + available_width_for_tabs_ = -1; + + GenerateIdealBounds(); + + int tab_data_index = ModelIndexToTabDataIndex(model_index); + Tab* tab = tab_data_[tab_data_index].tab; + if (model_index == 0) { + tab->SetBounds(0, tab_data_[tab_data_index].ideal_bounds.y(), 0, + tab_data_[tab_data_index].ideal_bounds.height()); + } else { + Tab* last_tab = tab_data_[tab_data_index - 1].tab; + tab->SetBounds(last_tab->bounds().right() + kTabHOffset, + tab_data_[tab_data_index].ideal_bounds.y(), 0, + tab_data_[tab_data_index].ideal_bounds.height()); } - active_animation_.reset(new RemoveTabAnimation(this, index, contents)); - active_animation_->Start(); + + AnimateToIdealBounds(); } -void TabStrip::StartMoveTabAnimation(int from_index, int to_index) { - if (active_animation_.get()) - active_animation_->Stop(); - active_animation_.reset(new MoveTabAnimation(this, from_index, to_index)); - active_animation_->Start(); +void TabStrip::StartRemoveTabAnimation(int model_index) { + ResetAnimationState(true); + + // Mark the tab as closing. + int tab_data_index = ModelIndexToTabDataIndex(model_index); + Tab* tab = tab_data_[tab_data_index].tab; + tab->set_closing(true); + + // Start an animation for the tabs. + GenerateIdealBounds(); + AnimateToIdealBounds(); + + // Animate the tab being closed to 0x0. + gfx::Rect tab_bounds = tab->bounds(); + tab_bounds.set_width(0); + bounds_animator_.AnimateViewTo(tab, tab_bounds, false); + + // Register delegate to do cleanup when done, BoundsAnimator takes + // ownership of RemoveTabDelegate. + bounds_animator_.SetAnimationDelegate(tab, new RemoveTabDelegate(this, tab), + true); } -void TabStrip::StartMiniTabAnimation(int index) { - if (active_animation_.get()) - active_animation_->Stop(); - active_animation_.reset(new MiniTabAnimation(this, index)); - active_animation_->Start(); +void TabStrip::StartMoveTabAnimation(int from_model_index, + int to_model_index) { + ResetAnimationState(true); + + int from_tab_data_index = ModelIndexToTabDataIndex(from_model_index); + + Tab* tab = tab_data_[from_tab_data_index].tab; + tab_data_.erase(tab_data_.begin() + from_tab_data_index); + + TabData data = {tab, gfx::Rect()}; + tab->set_mini(model_->IsMiniTab(to_model_index)); + tab->SetBlocked(model_->IsTabBlocked(to_model_index)); + + int to_tab_data_index = ModelIndexToTabDataIndex(to_model_index); + + tab_data_.insert(tab_data_.begin() + to_tab_data_index, data); + if (tab->phantom() != model_->IsPhantomTab(to_model_index)) + tab->set_phantom(!tab->phantom()); + + GenerateIdealBounds(); + AnimateToIdealBounds(); } -void TabStrip::StartMiniMoveTabAnimation(int from_index, - int to_index, - const gfx::Rect& start_bounds) { - if (active_animation_.get()) - active_animation_->Stop(); - active_animation_.reset( - new MiniMoveAnimation(this, from_index, to_index, start_bounds)); - active_animation_->Start(); +void TabStrip::StartMiniTabAnimation() { + ResetAnimationState(true); + + GenerateIdealBounds(); + AnimateToIdealBounds(); } -void TabStrip::FinishAnimation(TabStrip::TabAnimation* animation, - bool layout) { - active_animation_.reset(NULL); +void TabStrip::StopAnimating(bool layout) { + if (!IsAnimating()) + return; + + new_tab_timer_.Stop(); - // Reset the animation state of each tab. - for (int i = 0, count = GetTabCount(); i < count; ++i) - GetTabAt(i)->set_animating_mini_change(false); + if (bounds_animator_.IsAnimating()) { + // Cancelling the animation triggers OnBoundsAnimatorDone, which invokes + // ResetAnimationState. + bounds_animator_.Cancel(); + } else { + ResetAnimationState(false); + } + + DCHECK(!IsAnimating()); if (layout) Layout(); } -int TabStrip::GetIndexOfTab(const Tab* tab) const { - for (int i = 0, index = 0; i < GetTabCount(); ++i, ++index) { - Tab* current_tab = GetTabAt(i); - if (current_tab->closing()) { - --index; - } else if (current_tab == tab) { - return index; - } +void TabStrip::ResetAnimationState(bool stop_new_tab_timer) { + if (animation_type_ == ANIMATION_NEW_TAB_2) + newtab_button_->SchedulePaint(); + + if (stop_new_tab_timer) + new_tab_timer_.Stop(); + + animation_type_ = ANIMATION_DEFAULT; + + // Reset the animation state of each tab. + for (int i = 0, count = GetTabCount(); i < count; ++i) { + Tab* tab = GetTabAtTabDataIndex(i); + tab->set_animating_mini_change(false); + tab->set_render_as_new_tab(false); + tab->set_render_unselected(false); + tab->set_alpha(1); } - return -1; } int TabStrip::GetMiniTabCount() const { @@ -2011,22 +1647,21 @@ bool TabStrip::IsPointInTab(Tab* tab, return tab->HitTest(point_in_tab_coords); } -void TabStrip::RemoveTabAt(int index) { - Tab* removed = tab_data_.at(index).tab; +void TabStrip::RemoveTab(Tab* tab) { + int tab_data_index = TabDataIndexOfTab(tab); + + DCHECK(tab_data_index != -1); // Remove the Tab from the TabStrip's list... - tab_data_.erase(tab_data_.begin() + index); + tab_data_.erase(tab_data_.begin() + tab_data_index); // If the TabContents being detached was removed as a result of a drag // gesture from its corresponding Tab, we don't want to remove the Tab from // the child list, because if we do so it'll stop receiving events and the // drag will stall. So we only remove if a drag isn't active, or the Tab // was for some other TabContents. - if (!IsDragSessionActive() || !drag_controller_->IsDragSourceTab(removed)) { - removed->GetParent()->RemoveChildView(removed); - delete removed; - } - GenerateIdealBounds(); + if (!IsDragSessionActive() || !drag_controller_->IsDragSourceTab(tab)) + delete tab; } void TabStrip::HandleGlobalMouseMoveEvent() { @@ -2049,8 +1684,40 @@ void TabStrip::HandleGlobalMouseMoveEvent() { bool TabStrip::HasPhantomTabs() const { for (int i = 0; i < GetTabCount(); ++i) { - if (GetTabAt(i)->phantom()) + if (GetTabAtTabDataIndex(i)->phantom()) return true; } return false; } + +int TabStrip::GetModelIndexOfTab(const Tab* tab) const { + for (int i = 0, model_index = 0; i < GetTabCount(); ++i) { + Tab* current_tab = GetTabAtTabDataIndex(i); + if (!current_tab->closing()) { + if (current_tab == tab) + return model_index; + model_index++; + } + } + return -1; +} + +int TabStrip::ModelIndexToTabDataIndex(int model_index) const { + int current_model_index = 0; + for (size_t i = 0; i < tab_data_.size(); ++i) { + if (!tab_data_[i].tab->closing()) { + if (current_model_index == model_index) + return i; + current_model_index++; + } + } + return tab_data_.size(); +} + +int TabStrip::TabDataIndexOfTab(Tab* tab) const { + for (size_t i = 0; i < tab_data_.size(); ++i) { + if (tab_data_[i].tab == tab) + return i; + } + return -1; +} diff --git a/chrome/browser/views/tabs/tab_strip.h b/chrome/browser/views/tabs/tab_strip.h index af6cc34..e1cbfda 100644 --- a/chrome/browser/views/tabs/tab_strip.h +++ b/chrome/browser/views/tabs/tab_strip.h @@ -5,11 +5,16 @@ #ifndef CHROME_BROWSER_VIEWS_TABS_TAB_STRIP_H_ #define CHROME_BROWSER_VIEWS_TABS_TAB_STRIP_H_ +#include "app/animation_container.h" #include "base/message_loop.h" +#include "base/ref_counted.h" +#include "base/timer.h" #include "chrome/browser/tabs/tab_strip_model.h" #include "chrome/browser/views/tabs/base_tab_strip.h" #include "chrome/browser/views/tabs/tab.h" #include "gfx/point.h" +#include "gfx/rect.h" +#include "views/animation/bounds_animator.h" #include "views/controls/button/image_button.h" class DraggedTabController; @@ -42,7 +47,8 @@ class TabStrip : public BaseTabStrip, public TabStripModelObserver, public Tab::TabDelegate, public views::ButtonListener, - public MessageLoopForUI::Observer { + public MessageLoopForUI::Observer, + public views::BoundsAnimatorObserver { public: explicit TabStrip(TabStripModel* model); virtual ~TabStrip(); @@ -62,7 +68,7 @@ class TabStrip : public BaseTabStrip, void DestroyDraggedSourceTab(Tab* tab); // Retrieves the ideal bounds for the Tab at the specified index. - gfx::Rect GetIdealBounds(int index); + gfx::Rect GetIdealBounds(int tab_data_index); // Returns the currently selected tab. Tab* GetSelectedTab() const; @@ -103,6 +109,9 @@ class TabStrip : public BaseTabStrip, virtual views::View* GetViewForPoint(const gfx::Point& point); virtual void ThemeChanged(); + // BoundsAnimator::Observer overrides: + virtual void OnBoundsAnimatorDone(views::BoundsAnimator* animator); + protected: // Creates a new tab. virtual Tab* CreateTab(); @@ -114,21 +123,24 @@ class TabStrip : public BaseTabStrip, // TabStripModelObserver implementation: virtual void TabInsertedAt(TabContents* contents, - int index, + int model_index, bool foreground); - virtual void TabDetachedAt(TabContents* contents, int index); + virtual void TabDetachedAt(TabContents* contents, int model_index); virtual void TabSelectedAt(TabContents* old_contents, TabContents* contents, - int index, + int model_index, bool user_gesture); - virtual void TabMoved(TabContents* contents, int from_index, int to_index); - virtual void TabChangedAt(TabContents* contents, int index, + virtual void TabMoved(TabContents* contents, + int from_model_index, + int to_model_index); + virtual void TabChangedAt(TabContents* contents, + int model_index, TabChangeType change_type); virtual void TabReplacedAt(TabContents* old_contents, TabContents* new_contents, - int index); - virtual void TabMiniStateChanged(TabContents* contents, int index); - virtual void TabBlockedStateChanged(TabContents* contents, int index); + int model_index); + virtual void TabMiniStateChanged(TabContents* contents, int model_index); + virtual void TabBlockedStateChanged(TabContents* contents, int model_index); // Tab::Delegate implementation: virtual bool IsTabSelected(const Tab* tab) const; @@ -165,22 +177,70 @@ class TabStrip : public BaseTabStrip, static const int mini_to_non_mini_gap_; private: - class InsertTabAnimation; - class MiniMoveAnimation; - class MiniTabAnimation; - class MoveTabAnimation; - class RemoveTabAnimation; - class ResizeLayoutAnimation; - class TabAnimation; + class RemoveTabDelegate; friend class DraggedTabController; - friend class InsertTabAnimation; - friend class MiniMoveAnimation; - friend class MiniTabAnimation; - friend class MoveTabAnimation; - friend class RemoveTabAnimation; - friend class ResizeLayoutAnimation; - friend class TabAnimation; + + // AnimationType used for tracking animations that require additional + // state beyond just animating the bounds of a view. + // + // Currently the only animation special cased is that of inserting the new tab + // page at the end of the tab strip. Here's the steps that take place when + // this happens. + // . The newly inserted tab is set to render for the new tab animation + // |set_render_as_new_tab|. The timer new_tab_timer_ is used to determine + // when to turn this off. This is represented by state ANIMATION_NEW_TAB_1. + // . The new tab is rendered in the background with an ever increasing alpha + // value and the tab goes slightly past the new tab button. The new tab + // button is not visible during this animation. This is represented by the + // state ANIMATION_NEW_TAB_2. + // . The new tab is animated to its final position and the new tab button is + // rendered beneath the selected tab. This is represented by the state + // ANIMATION_NEW_TAB_3. + enum AnimationType { + ANIMATION_DEFAULT, + + ANIMATION_NEW_TAB_1, + ANIMATION_NEW_TAB_2, + ANIMATION_NEW_TAB_3 + }; + + // Used during a drop session of a url. Tracks the position of the drop as + // well as a window used to highlight where the drop occurs. + struct DropInfo { + DropInfo(int index, bool drop_before, bool paint_down); + ~DropInfo(); + + // Index of the tab to drop on. If drop_before is true, the drop should + // occur between the tab at drop_index - 1 and drop_index. + // WARNING: if drop_before is true it is possible this will == tab_count, + // which indicates the drop should create a new tab at the end of the tabs. + int drop_index; + bool drop_before; + + // Direction the arrow should point in. If true, the arrow is displayed + // above the tab and points down. If false, the arrow is displayed beneath + // the tab and points up. + bool point_down; + + // Renders the drop indicator. + // TODO(beng): should be views::Widget. +#if defined(OS_WIN) + views::WidgetWin* arrow_window; +#else + views::WidgetGtk* arrow_window; +#endif + views::ImageView* arrow_view; + + private: + DISALLOW_COPY_AND_ASSIGN(DropInfo); + }; + + // The Tabs we contain, and their last generated "good" bounds. + struct TabData { + Tab* tab; + gfx::Rect ideal_bounds; + }; TabStrip(); void Init(); @@ -188,9 +248,9 @@ class TabStrip : public BaseTabStrip, // Set the images for the new tab button. void LoadNewTabButtonImage(); - // Retrieves the Tab at the specified index. Take care in using this, you may - // need to use GetTabAtAdjustForAnimation. - Tab* GetTabAt(int index) const; + // Retrieves the Tab at the specified index. Remember, the specified index + // is in terms of tab_data, *not* the model. + Tab* GetTabAtTabDataIndex(int tab_data_index) const; // Returns the tab at the specified index. If a remove animation is on going // and the index is >= the index of the tab being removed, the index is @@ -198,10 +258,13 @@ class TabStrip : public BaseTabStrip, // do not line up with the indices of the view. This method adjusts the index // accordingly. // - // Use this instead of GetTabAt if the index comes from the model. - Tab* GetTabAtAdjustForAnimation(int index) const; + // Use this instead of GetTabAtTabDataIndex if the index comes from the model. + Tab* GetTabAtModelIndex(int model_index) const; // Gets the number of Tabs in the collection. + // WARNING: this is the number of tabs displayed by the tabstrip, which if + // an animation is ongoing is not necessarily the same as the number of tabs + // in the model. int GetTabCount() const; // Returns the number of mini-tabs. @@ -224,9 +287,6 @@ class TabStrip : public BaseTabStrip, double* unselected_width, double* selected_width) const; - // Returns the horizontal offset before the tab at |tab_index|. - int GetTabHOffset(int tab_index); - // Perform an animated resize-relayout of the TabStrip immediately. void ResizeLayoutTabs(); @@ -250,7 +310,7 @@ class TabStrip : public BaseTabStrip, void UpdateDropIndex(const views::DropTargetEvent& event); // Sets the location of the drop, repainting as necessary. - void SetDropIndex(int index, bool drop_before); + void SetDropIndex(int tab_data_index, bool drop_before); // Returns the drop effect for dropping a URL on the tab strip. This does // not query the data in anyway, it only looks at the source operations. @@ -268,32 +328,38 @@ class TabStrip : public BaseTabStrip, // stable representations of Tab positions. void GenerateIdealBounds(); - // Lays out the New Tab button, assuming the right edge of the last Tab on - // the TabStrip at |last_tab_right|. - void LayoutNewTabButton(double last_tab_right, double unselected_width); + // Both of these are invoked when a part of the new tab animation completes. + // They configure state for the next step in the animation and start it. + void NewTabAnimation1Done(); + void NewTabAnimation2Done(); + + // Animates all the views to their ideal bounds. + // NOTE: this does *not* invoke GenerateIdealBounds, it uses the bounds + // currently set in ideal_bounds. + void AnimateToIdealBounds(); - // A generic Layout method for various classes of TabStrip animations, - // including Insert, Remove and Resize Layout cases/ - void AnimationLayout(double unselected_width); + // Returns true if a new tab inserted at specified index should start the + // new tab animation. See description above AnimationType for details on + // this animation. + bool ShouldStartIntertTabAnimationAtEnd(int model_index, bool foreground); // Starts various types of TabStrip animations. void StartResizeLayoutAnimation(); - void StartInsertTabAnimation(int index); - void StartRemoveTabAnimation(int index, TabContents* contents); - void StartMoveTabAnimation(int from_index, int to_index); - void StartMiniTabAnimation(int index); - void StartMiniMoveTabAnimation(int from_index, - int to_index, - const gfx::Rect& start_bounds); - - // Notifies the TabStrip that the specified TabAnimation has completed. - // Optionally a full Layout will be performed, specified by |layout|. - void FinishAnimation(TabAnimation* animation, bool layout); - - // Finds the index of the TabContents corresponding to |tab| in our - // associated TabStripModel, or -1 if there is none (e.g. the specified |tab| - // is being animated closed). - int GetIndexOfTab(const Tab* tab) const; + void StartInsertTabAnimationAtEnd(); + void StartInsertTabAnimation(int model_index); + void StartRemoveTabAnimation(int model_index); + void StartMoveTabAnimation(int from_model_index, + int to_model_index); + void StartMiniTabAnimation(); + + // Stops any ongoing animations. If |layout| is true and an animation is + // ongoing this does a layout. + void StopAnimating(bool layout); + + // Resets all state related to animations. This is invoked when an animation + // completes, prior to starting an animation or when we cancel an animation. + // If |stop_new_tab_timer| is true, |new_tab_timer_| is stopped. + void ResetAnimationState(bool stop_new_tab_timer); // Calculates the available width for tabs, assuming a Tab is to be closed. int GetAvailableWidthForTabs(Tab* last_tab) const; @@ -302,9 +368,9 @@ class TabStrip : public BaseTabStrip, // hit-test region of the specified Tab. bool IsPointInTab(Tab* tab, const gfx::Point& point_in_tabstrip_coords); - // Cleans up the Tab from the TabStrip at the specified |index|. This is - // called from the tab animation code and is not a general-purpose method. - void RemoveTabAt(int index); + // Cleans up the Tab from the TabStrip. This is called from the tab animation + // code and is not a general-purpose method. + void RemoveTab(Tab* tab); // Called from the message loop observer when a mouse movement has occurred // anywhere over our containing window. @@ -313,6 +379,19 @@ class TabStrip : public BaseTabStrip, // Returns true if any of the tabs are phantom. bool HasPhantomTabs() const; + // Returns the index of the specified tab in the model coordiate system, or + // -1 if tab is closing or not in |tab_data_|. + int GetModelIndexOfTab(const Tab* tab) const; + + // Returns the index into |tab_data_| corresponding to the index from the + // TabStripModel, or |tab_data_.size()| if there is no tab representing + // |model_index|. + int ModelIndexToTabDataIndex(int model_index) const; + + // Returns the index into |tab_data_| corresponding to the specified tab, or + // -1 if the tab isn't in |tab_data_|. + int TabDataIndexOfTab(Tab* tab) const; + // -- Member Variables ------------------------------------------------------ // Our model. @@ -331,7 +410,9 @@ class TabStrip : public BaseTabStrip, // The "New Tab" button. views::ImageButton* newtab_button_; - gfx::Size newtab_button_size_; + + // Ideal bounds of the new tab button. + gfx::Rect newtab_button_bounds_; // The current widths of various types of tabs. We save these so that, as // users close tabs while we're holding them at the same size, we can lay out @@ -359,37 +440,6 @@ class TabStrip : public BaseTabStrip, static const int kNewTabButtonWidth = 28; static const int kNewTabButtonHeight = 18; - // Used during a drop session of a url. Tracks the position of the drop as - // well as a window used to highlight where the drop occurs. - struct DropInfo { - DropInfo(int index, bool drop_before, bool paint_down); - ~DropInfo(); - - // Index of the tab to drop on. If drop_before is true, the drop should - // occur between the tab at drop_index - 1 and drop_index. - // WARNING: if drop_before is true it is possible this will == tab_count, - // which indicates the drop should create a new tab at the end of the tabs. - int drop_index; - bool drop_before; - - // Direction the arrow should point in. If true, the arrow is displayed - // above the tab and points down. If false, the arrow is displayed beneath - // the tab and points up. - bool point_down; - - // Renders the drop indicator. - // TODO(beng): should be views::Widget. -#if defined(OS_WIN) - views::WidgetWin* arrow_window; -#else - views::WidgetGtk* arrow_window; -#endif - views::ImageView* arrow_view; - - private: - DISALLOW_COPY_AND_ASSIGN(DropInfo); - }; - // Valid for the lifetime of a drag over us. scoped_ptr<DropInfo> drop_info_; @@ -397,15 +447,19 @@ class TabStrip : public BaseTabStrip, // the drag session. scoped_ptr<DraggedTabController> drag_controller_; - // The Tabs we contain, and their last generated "good" bounds. - struct TabData { - Tab* tab; - gfx::Rect ideal_bounds; - }; std::vector<TabData> tab_data_; - // The currently running animation. - scoped_ptr<TabAnimation> active_animation_; + // To ensure all tabs pulse at the same time they share the same animation + // container. This is that animation container. + scoped_refptr<AnimationContainer> animation_container_; + + views::BoundsAnimator bounds_animator_; + + // Used for stage 1 of new tab animation. + base::OneShotTimer<TabStrip> new_tab_timer_; + + // Set for special animations. + AnimationType animation_type_; DISALLOW_COPY_AND_ASSIGN(TabStrip); }; diff --git a/views/animation/bounds_animator.cc b/views/animation/bounds_animator.cc index 6dbd105..3a88242 100644 --- a/views/animation/bounds_animator.cc +++ b/views/animation/bounds_animator.cc @@ -44,13 +44,13 @@ void BoundsAnimator::AnimateViewTo(View* view, // We delay deleting the view until the end so that we don't prematurely // send out notification that we're done. current_animation.reset(ResetAnimationForView(view)); - } else if (target == view->bounds()) { - // View is already at the target location, delete it if necessary. - if (delete_when_done) - delete view; - return; } + // NOTE: we don't check if the view is already at the target location. Doing + // so leads to odd cases where no animations may be present after invoking + // AnimateViewTo. AnimationProgressed does nothing when the bounds of the + // view don't change. + Data& data = data_[view]; data.start_bounds = view->bounds(); data.target_bounds = target; |