Index: chrome/browser/ui/views/tabs/tab_strip.cc |
diff --git a/chrome/browser/ui/views/tabs/tab_strip.cc b/chrome/browser/ui/views/tabs/tab_strip.cc |
index 013601c47cb9ed974816d6a2b82a3d2d21bf2db7..2bcc792ed27075721aef397583370e5cc8275bf1 100644 |
--- a/chrome/browser/ui/views/tabs/tab_strip.cc |
+++ b/chrome/browser/ui/views/tabs/tab_strip.cc |
@@ -127,23 +127,56 @@ base::string16 GetClipboardText() { |
return clipboard_text; |
} |
+// Animation delegate used for any automatic tab movement. Hides the tab if it |
+// is not fully visible within the tabstrip area, to prevent overflow clipping. |
+class TabAnimationDelegate : public gfx::AnimationDelegate { |
+ public: |
+ TabAnimationDelegate(TabStrip* tab_strip, Tab* tab); |
+ virtual ~TabAnimationDelegate(); |
+ |
+ virtual void AnimationProgressed(const gfx::Animation* animation) OVERRIDE; |
+ |
+ protected: |
+ TabStrip* tab_strip() { return tab_strip_; } |
+ Tab* tab() { return tab_; } |
+ |
+ private: |
+ TabStrip* const tab_strip_; |
+ Tab* const tab_; |
+ |
+ DISALLOW_COPY_AND_ASSIGN(TabAnimationDelegate); |
+}; |
+ |
+TabAnimationDelegate::TabAnimationDelegate(TabStrip* tab_strip, Tab* tab) |
+ : tab_strip_(tab_strip), |
+ tab_(tab) { |
+} |
+ |
+TabAnimationDelegate::~TabAnimationDelegate() { |
+} |
+ |
+void TabAnimationDelegate::AnimationProgressed( |
+ const gfx::Animation* animation) { |
+ tab_->SetVisible(tab_strip_->ShouldTabBeVisible(tab_)); |
+} |
+ |
// Animation delegate used when a dragged tab is released. When done sets the |
// dragging state to false. |
-class ResetDraggingStateDelegate : public gfx::AnimationDelegate { |
+class ResetDraggingStateDelegate : public TabAnimationDelegate { |
public: |
- explicit ResetDraggingStateDelegate(Tab* tab); |
+ ResetDraggingStateDelegate(TabStrip* tab_strip, Tab* tab); |
virtual ~ResetDraggingStateDelegate(); |
virtual void AnimationEnded(const gfx::Animation* animation) OVERRIDE; |
virtual void AnimationCanceled(const gfx::Animation* animation) OVERRIDE; |
private: |
- Tab* tab_; |
- |
DISALLOW_COPY_AND_ASSIGN(ResetDraggingStateDelegate); |
}; |
-ResetDraggingStateDelegate::ResetDraggingStateDelegate(Tab* tab) : tab_(tab) { |
+ResetDraggingStateDelegate::ResetDraggingStateDelegate(TabStrip* tab_strip, |
+ Tab* tab) |
+ : TabAnimationDelegate(tab_strip, tab) { |
} |
ResetDraggingStateDelegate::~ResetDraggingStateDelegate() { |
@@ -151,7 +184,8 @@ ResetDraggingStateDelegate::~ResetDraggingStateDelegate() { |
void ResetDraggingStateDelegate::AnimationEnded( |
const gfx::Animation* animation) { |
- tab_->set_dragging(false); |
+ tab()->set_dragging(false); |
+ AnimationProgressed(animation); // Forces tab visibility to update. |
} |
void ResetDraggingStateDelegate::AnimationCanceled( |
@@ -456,7 +490,7 @@ class NewTabButtonTargeter : public views::MaskedViewTargeter { |
// |
// AnimationDelegate used when removing a tab. Does the necessary cleanup when |
// done. |
-class TabStrip::RemoveTabDelegate : public gfx::AnimationDelegate { |
+class TabStrip::RemoveTabDelegate : public TabAnimationDelegate { |
public: |
RemoveTabDelegate(TabStrip* tab_strip, Tab* tab); |
@@ -464,28 +498,31 @@ class TabStrip::RemoveTabDelegate : public gfx::AnimationDelegate { |
virtual void AnimationCanceled(const gfx::Animation* animation) OVERRIDE; |
private: |
- void CompleteRemove(); |
- |
- // 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. |
- void HighlightCloseButton(); |
- |
- TabStrip* tabstrip_; |
- Tab* tab_; |
- |
DISALLOW_COPY_AND_ASSIGN(RemoveTabDelegate); |
}; |
TabStrip::RemoveTabDelegate::RemoveTabDelegate(TabStrip* tab_strip, |
Tab* tab) |
- : tabstrip_(tab_strip), |
- tab_(tab) { |
+ : TabAnimationDelegate(tab_strip, tab) { |
} |
void TabStrip::RemoveTabDelegate::AnimationEnded( |
const gfx::Animation* animation) { |
- CompleteRemove(); |
+ DCHECK(tab()->closing()); |
+ tab_strip()->RemoveAndDeleteTab(tab()); |
+ |
+ // 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. Note that this is not required (and |
+ // indeed may crash!) for removes spawned by non-mouse closes and |
+ // drag-detaches. |
+ if (!tab_strip()->IsDragSessionActive() && |
+ tab_strip()->ShouldHighlightCloseButtonAfterRemove()) { |
+ // The widget can apparently be null during shutdown. |
+ views::Widget* widget = tab_strip()->GetWidget(); |
+ if (widget) |
+ widget->SynthesizeMouseMoveEvent(); |
+ } |
} |
void TabStrip::RemoveTabDelegate::AnimationCanceled( |
@@ -493,28 +530,6 @@ void TabStrip::RemoveTabDelegate::AnimationCanceled( |
AnimationEnded(animation); |
} |
-void TabStrip::RemoveTabDelegate::CompleteRemove() { |
- DCHECK(tab_->closing()); |
- tabstrip_->RemoveAndDeleteTab(tab_); |
- HighlightCloseButton(); |
-} |
- |
-void TabStrip::RemoveTabDelegate::HighlightCloseButton() { |
- if (tabstrip_->IsDragSessionActive() || |
- !tabstrip_->ShouldHighlightCloseButtonAfterRemove()) { |
- // This function is not required (and indeed may crash!) for removes |
- // spawned by non-mouse closes and drag-detaches. |
- return; |
- } |
- |
- views::Widget* widget = tabstrip_->GetWidget(); |
- // This can be null during shutdown. See http://crbug.com/42737. |
- if (!widget) |
- return; |
- |
- widget->SynthesizeMouseMoveEvent(); |
-} |
- |
/////////////////////////////////////////////////////////////////////////////// |
// TabStrip, public: |
@@ -720,6 +735,54 @@ void TabStrip::SetTabData(int model_index, const TabRendererData& data) { |
SwapLayoutIfNecessary(); |
} |
+bool TabStrip::ShouldTabBeVisible(const Tab* tab) const { |
+ // When stacking tabs, all tabs should always be visible. |
+ if (stacked_layout_) |
+ return true; |
+ |
+ // If the tab is currently clipped, it shouldn't be visible. Note that we |
+ // allow dragged tabs to draw over the "New Tab button" region as well, |
+ // because either the New Tab button will be hidden, or the dragged tabs will |
+ // be animating back to their normal positions and we don't want to hide them |
+ // in the New Tab button region in case they re-appear after leaving it. |
+ // (This prevents flickeriness.) We never draw non-dragged tabs in New Tab |
+ // button area, even when the button is invisible, so that they don't appear |
+ // to "pop in" when the button disappears. |
+ // TODO: Probably doesn't work for RTL |
+ int right_edge = tab->bounds().right(); |
+ const int visible_width = tab->dragging() ? width() : tab_area_width(); |
+ if (right_edge > visible_width) |
+ return false; |
+ |
+ // Non-clipped dragging tabs should always be visible. |
+ if (tab->dragging()) |
+ return true; |
+ |
+ // Let all non-clipped closing tabs be visible. These will probably finish |
+ // closing before the user changes the active tab, so there's little reason to |
+ // try and make the more complex logic below apply. |
+ if (tab->closing()) |
+ return true; |
+ |
+ // Now we need to check whether the tab isn't currently clipped, but could |
+ // become clipped if we changed the active tab, widening either this tab or |
+ // the tabstrip portion before it. |
+ |
+ // Mini tabs don't change size when activated, so any tab in the mini tab |
+ // region is safe. |
+ if (tab->data().mini) |
+ return true; |
+ |
+ // If the active tab is on or before this tab, we're safe. |
+ if (controller_->GetActiveIndex() <= GetModelIndexOfTab(tab)) |
+ return true; |
+ |
+ // We need to check what would happen if the active tab were to move to this |
+ // tab or before. |
+ return (right_edge + current_selected_width_ - current_unselected_width_) <= |
+ tab_area_width(); |
+} |
+ |
void TabStrip::PrepareForCloseAt(int model_index, CloseTabSource source) { |
if (!in_tab_close_ && IsAnimating()) { |
// Cancel any current animations. We do this as remove uses the current |
@@ -1492,8 +1555,13 @@ void TabStrip::ScheduleRemoveTabAnimation(Tab* tab) { |
void TabStrip::AnimateToIdealBounds() { |
for (int i = 0; i < tab_count(); ++i) { |
Tab* tab = tab_at(i); |
- if (!tab->dragging()) |
+ if (!tab->dragging()) { |
bounds_animator_.AnimateViewTo(tab, ideal_bounds(i)); |
+ bounds_animator_.SetAnimationDelegate( |
+ tab, |
+ scoped_ptr<gfx::AnimationDelegate>( |
+ new TabAnimationDelegate(this, tab))); |
+ } |
} |
bounds_animator_.AnimateViewTo(newtab_button_, newtab_button_bounds_); |
@@ -1516,6 +1584,7 @@ void TabStrip::DoLayout() { |
GenerateIdealBounds(); |
views::ViewModelUtils::SetViewBoundsToIdealBounds(tabs_); |
+ SetTabVisibility(); |
SchedulePaint(); |
@@ -1523,6 +1592,25 @@ void TabStrip::DoLayout() { |
newtab_button_->SetBoundsRect(newtab_button_bounds_); |
} |
+void TabStrip::SetTabVisibility() { |
+ // We could probably be more efficient here by making use of the fact that the |
+ // tabstrip will always have any visible tabs, and then any invisible tabs, so |
+ // we could e.g. binary-search for the changeover point. But since we have to |
+ // iterate through all the tabs to call SetVisible() anyway, it doesn't seem |
+ // worth it. |
+ for (int i = 0; i < tab_count(); ++i) { |
+ Tab* tab = tab_at(i); |
+ tab->SetVisible(ShouldTabBeVisible(tab)); |
+ } |
+ for (TabsClosingMap::const_iterator i(tabs_closing_map_.begin()); |
+ i != tabs_closing_map_.end(); ++i) { |
+ for (Tabs::const_iterator j(i->second.begin()); j != i->second.end(); ++j) { |
+ Tab* tab = *j; |
+ tab->SetVisible(ShouldTabBeVisible(tab)); |
+ } |
+ } |
+} |
+ |
void TabStrip::DragActiveTab(const std::vector<int>& initial_positions, |
int delta) { |
DCHECK_EQ(tab_count(), static_cast<int>(initial_positions.size())); |
@@ -1628,7 +1716,8 @@ void TabStrip::LayoutDraggedTabsAt(const Tabs& tabs, |
const gfx::Point& location, |
bool initial_drag) { |
// Immediately hide the new tab button if the last tab is being dragged. |
- if (GetLastVisibleTab()->dragging()) |
+ const Tab* last_visible_tab = GetLastVisibleTab(); |
+ if (last_visible_tab && last_visible_tab->dragging()) |
newtab_button_->SetVisible(false); |
std::vector<gfx::Rect> bounds; |
CalculateBoundsForDraggedTabs(tabs, &bounds); |
@@ -1653,6 +1742,7 @@ void TabStrip::LayoutDraggedTabsAt(const Tabs& tabs, |
tab->SetBoundsRect(new_bounds); |
} |
} |
+ SetTabVisibility(); |
} |
void TabStrip::CalculateBoundsForDraggedTabs(const Tabs& tabs, |
@@ -1690,7 +1780,14 @@ int TabStrip::GetMiniTabCount() const { |
} |
const Tab* TabStrip::GetLastVisibleTab() const { |
- return tab_at(tab_count() - 1); |
+ for (int i = tab_count() - 1; i >= 0; --i) { |
+ const Tab* tab = tab_at(i); |
+ if (tab->visible()) |
+ return tab; |
+ } |
+ // While in normal use the tabstrip should always be wide enough to have at |
+ // least one visible tab, it can be zero-width in tests, meaning we get here. |
+ return NULL; |
} |
void TabStrip::RemoveTabFromViewModel(int index) { |
@@ -1761,6 +1858,7 @@ void TabStrip::StartedDraggingTabs(const Tabs& tabs) { |
DCHECK_NE(-1, tab_data_index); |
tabs[i]->SetBoundsRect(ideal_bounds(tab_data_index)); |
} |
+ SetTabVisibility(); |
SchedulePaint(); |
} |
@@ -1810,7 +1908,8 @@ void TabStrip::StoppedDraggingTab(Tab* tab, bool* is_first_tab) { |
// dragging true for the tab otherwise it'll draw beneath the new tab button. |
bounds_animator_.SetAnimationDelegate( |
tab, |
- scoped_ptr<gfx::AnimationDelegate>(new ResetDraggingStateDelegate(tab))); |
+ scoped_ptr<gfx::AnimationDelegate>( |
+ new ResetDraggingStateDelegate(this, tab))); |
} |
void TabStrip::OwnDragController(TabDragController* controller) { |
@@ -1958,22 +2057,8 @@ void TabStrip::GetDesiredTabWidths(int tab_count, |
} |
// Determine how much space we can actually allocate to tabs. |
- int available_width; |
- if (available_width_for_tabs_ < 0) { |
- available_width = width() - new_tab_button_width(); |
- } else { |
- // Interesting corner case: if |available_width_for_tabs_| > the result |
- // of the calculation in the conditional arm above, the strip is in |
- // overflow. We can either use the specified width or the true available |
- // width here; the first preserves the consistent "leave the last tab under |
- // the user's mouse so they can close many tabs" behavior at the cost of |
- // prolonging the glitchy appearance of the overflow state, while the second |
- // gets us out of overflow as soon as possible but forces the user to move |
- // their mouse for a few tabs' worth of closing. We choose visual |
- // imperfection over behavioral imperfection and select the first option. |
- available_width = available_width_for_tabs_; |
- } |
- |
+ int available_width = (available_width_for_tabs_ < 0) ? |
+ tab_area_width() : available_width_for_tabs_; |
if (mini_tab_count > 0) { |
available_width -= |
mini_tab_count * (Tab::GetMiniWidth() + kTabHorizontalOffset); |
@@ -2460,6 +2545,7 @@ void TabStrip::SwapLayoutIfNecessary() { |
} |
PrepareForAnimation(); |
GenerateIdealBounds(); |
+ SetTabVisibility(); |
AnimateToIdealBounds(); |
} |