diff options
author | sky@chromium.org <sky@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2009-09-11 13:39:25 +0000 |
---|---|---|
committer | sky@chromium.org <sky@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2009-09-11 13:39:25 +0000 |
commit | c10cdbdd3201adf12e39b873d3075a879625e105 (patch) | |
tree | 285ca6573dd4111a6b80352f61c12237869c85c3 /chrome/browser/views/tabs | |
parent | c7ad50f409be00bda164b2f60c29e2733eed1c94 (diff) | |
download | chromium_src-c10cdbdd3201adf12e39b873d3075a879625e105.zip chromium_src-c10cdbdd3201adf12e39b873d3075a879625e105.tar.gz chromium_src-c10cdbdd3201adf12e39b873d3075a879625e105.tar.bz2 |
Adds pinned tabs to windows. As the code between windows and gtk is
quite similar, this wasn't that much work.
BUG=none
TEST=make sure tabs behave correctly on windows, as well as testing
pinning/unpinning.
Review URL: http://codereview.chromium.org/193051
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@25973 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'chrome/browser/views/tabs')
-rw-r--r-- | chrome/browser/views/tabs/dragged_tab_controller.cc | 182 | ||||
-rw-r--r-- | chrome/browser/views/tabs/dragged_tab_controller.h | 38 | ||||
-rw-r--r-- | chrome/browser/views/tabs/dragged_tab_view.cc | 25 | ||||
-rw-r--r-- | chrome/browser/views/tabs/dragged_tab_view.h | 30 | ||||
-rw-r--r-- | chrome/browser/views/tabs/tab.cc | 7 | ||||
-rw-r--r-- | chrome/browser/views/tabs/tab_renderer.cc | 203 | ||||
-rw-r--r-- | chrome/browser/views/tabs/tab_renderer.h | 16 | ||||
-rw-r--r-- | chrome/browser/views/tabs/tab_strip.cc | 400 | ||||
-rw-r--r-- | chrome/browser/views/tabs/tab_strip.h | 20 |
9 files changed, 776 insertions, 145 deletions
diff --git a/chrome/browser/views/tabs/dragged_tab_controller.cc b/chrome/browser/views/tabs/dragged_tab_controller.cc index bd42e90c..9922404 100644 --- a/chrome/browser/views/tabs/dragged_tab_controller.cc +++ b/chrome/browser/views/tabs/dragged_tab_controller.cc @@ -31,6 +31,17 @@ static const int kHorizontalMoveThreshold = 16; // pixels +// Threshold for pinned tabs. +static const int kHorizontalPinnedMoveThreshold = 4; // pixels + +// Amount, in pixels, from the edge of the tab strip that causes a non-pinned +// tab to be pinned. See description of pin_timer_ for details. +static const int kHorizontalPinTabOffset = 16; + +// Delay, in ms, between when the user drags a tab to the edge of the tab strip +// and when the tab becomes pinned. See description of pin_timer_ for details. +static const int kPinTabDelay = 300; + namespace { // Delay, in ms, during dragging before we bring a window to front. @@ -307,7 +318,8 @@ DraggedTabController::DraggedTabController(Tab* source_tab, attached_tabstrip_(source_tabstrip), old_focused_view_(NULL), in_destructor_(false), - last_move_screen_x_(0) { + last_move_screen_x_(0), + was_pinned_(source_tabstrip->model()->IsTabPinned(source_model_index_)) { SetDraggedContents( source_tabstrip_->model()->GetTabContentsAt(source_model_index_)); // Listen for Esc key presses. @@ -334,6 +346,7 @@ void DraggedTabController::CaptureDragInfo(const gfx::Point& mouse_offset) { void DraggedTabController::Drag() { bring_to_front_timer_.Stop(); + pin_timer_.Stop(); // Before we get to dragging anywhere, ensure that we consider ourselves // attached to the source tabstrip. @@ -625,29 +638,42 @@ void DraggedTabController::MoveTab(const gfx::Point& screen_point) { gfx::Point dragged_view_point = GetDraggedViewPoint(screen_point); if (attached_tabstrip_) { + TabStripModel* attached_model = attached_tabstrip_->model(); + int from_index = attached_model->GetIndexOfTabContents(dragged_contents_); + AdjustDragPointForPinnedTabs(screen_point, &from_index, + &dragged_view_point); + // Determine the horizontal move threshold. This is dependent on the width // of tabs. The smaller the tabs compared to the standard size, the smaller // the threshold. - double unselected, selected; - attached_tabstrip_->GetCurrentTabWidths(&unselected, &selected); - double ratio = unselected / Tab::GetStandardSize().width(); - int threshold = static_cast<int>(ratio * kHorizontalMoveThreshold); + int threshold = kHorizontalPinnedMoveThreshold; + if (!view_->pinned()) { + double unselected, selected; + attached_tabstrip_->GetCurrentTabWidths(&unselected, &selected); + double ratio = unselected / Tab::GetStandardSize().width(); + threshold = static_cast<int>(ratio * kHorizontalMoveThreshold); + } // Update the model, moving the TabContents from one index to another. Do // this only if we have moved a minimum distance since the last reorder (to // prevent jitter). if (abs(screen_point.x() - last_move_screen_x_) > threshold) { - TabStripModel* attached_model = attached_tabstrip_->model(); - int from_index = - attached_model->GetIndexOfTabContents(dragged_contents_); gfx::Rect bounds = GetDraggedViewTabStripBounds(dragged_view_point); int to_index = GetInsertionIndexForDraggedBounds(bounds); to_index = NormalizeIndexToAttachedTabStrip(to_index); + if (!view_->pinned()) { + // If the dragged tab isn't pinned, don't allow the drag to a pinned + // tab. + to_index = + std::max(to_index, attached_model->IndexOfFirstNonPinnedTab()); + } if (from_index != to_index) { last_move_screen_x_ = screen_point.x(); attached_model->MoveTabContentsAt(from_index, to_index, true); } } + + StartPinTimerIfNecessary(screen_point); } // Move the View. There are no changes to the model if we're detached. view_->MoveTo(dragged_view_point); @@ -674,6 +700,97 @@ DockInfo DraggedTabController::GetDockInfoAtPoint( return info; } +void DraggedTabController::MakeDraggedTabPinned() { + MakeDraggedTabPinned(0); +} + +void DraggedTabController::MakeDraggedTabPinned(int tab_index) { + DCHECK(view_.get()); + DCHECK(!view_->pinned()); + + // Mark the tab as pinned and update the model. + view_->set_pinned(true); + attached_tabstrip_->model()->SetTabPinned(tab_index, true); + + // Reset the hotspot (mouse_offset_) for the dragged tab. Otherwise the + // dragged tab may be nowhere near the mouse. + mouse_offset_.set_x(Tab::GetPinnedWidth() / 2 - 1); + InitWindowCreatePoint(); + view_->set_mouse_tab_offset(mouse_offset_); + + // Resize the dragged tab. + view_->Resize(Tab::GetPinnedWidth()); +} + +void DraggedTabController::StartPinTimerIfNecessary( + const gfx::Point& screen_point) { + if (view_->pinned()) + return; + + TabStripModel* attached_model = attached_tabstrip_->model(); + int pinned_count = attached_model->IndexOfFirstNonPinnedTab(); + if (pinned_count > 0) + return; + + int index = attached_model->GetIndexOfTabContents(dragged_contents_); + if (index != 0) + return; + + gfx::Point local_point = + ConvertScreenPointToTabStripPoint(attached_tabstrip_, screen_point); + if (local_point.x() > kHorizontalPinTabOffset) + return; + + pin_timer_.Start( + base::TimeDelta::FromMilliseconds(kPinTabDelay), this, + &DraggedTabController::MakeDraggedTabPinned); +} + +void DraggedTabController::AdjustDragPointForPinnedTabs( + const gfx::Point& screen_point, + int* from_index, + gfx::Point* dragged_tab_point) { + TabStripModel* attached_model = attached_tabstrip_->model(); + int pinned_count = attached_model->IndexOfFirstNonPinnedTab(); + if (pinned_count == 0) + return; + + gfx::Point local_point = + ConvertScreenPointToTabStripPoint(attached_tabstrip_, screen_point); + int pinned_threshold = GetPinnedThreshold(); + if (!view_->pinned()) { + gfx::Rect tab_bounds = attached_tabstrip_->GetIdealBounds(pinned_count); + if (local_point.x() <= pinned_threshold) { + // The mouse was moved below the threshold that triggers the tab to be + // pinned. + MakeDraggedTabPinned(*from_index); + + // The dragged tab point was calculated using the old mouse_offset, which + // we just reset. Recalculate it. + *dragged_tab_point = GetDraggedViewPoint(screen_point); + } else if (local_point.x() - mouse_offset_.x() < tab_bounds.x()) { + // The mouse has not moved below the threshold that triggers the tab to + // be pinned. Make sure the dragged tab does not go before the first + // non-pinned tab. + gfx::Rect tabstrip_bounds = GetViewScreenBounds(attached_tabstrip_); + dragged_tab_point->set_x(std::max(dragged_tab_point->x(), + tabstrip_bounds.x() + tab_bounds.x())); + } + } else if (local_point.x() > pinned_threshold) { + // The mouse has moved past the point that triggers the tab to be unpinned. + // Update the dragged tab and model accordingly. + view_->set_pinned(false); + attached_model->SetTabPinned(*from_index, false); + + // Changing the tabs pinned state may have forced it to move (as can happen + // if the user rapidly drags a pinned tab past other pinned tabs). Update + // the from_index. + *from_index = attached_model->GetIndexOfTabContents(dragged_contents_); + + view_->Resize(view_->tab_width()); + } +} + TabStrip* DraggedTabController::GetTabStripForPoint( const gfx::Point& screen_point) { gfx::NativeView dragged_view = view_->GetWidget()->GetNativeView(); @@ -734,13 +851,38 @@ void DraggedTabController::Attach(TabStrip* attached_tabstrip, // the dragged representation will be a different size to others in the // TabStrip. int tab_count = attached_tabstrip_->GetTabCount(); + int pinned_tab_count = attached_tabstrip_->GetPinnedTabCount(); if (!tab) ++tab_count; double unselected_width, selected_width = 0; - attached_tabstrip_->GetDesiredTabWidths(tab_count, &unselected_width, - &selected_width); + attached_tabstrip_->GetDesiredTabWidths(tab_count, pinned_tab_count, + &unselected_width, &selected_width); EnsureDraggedView(); - view_->Attach(static_cast<int>(selected_width)); + int dragged_tab_width = static_cast<int>(selected_width); + view_->set_tab_width(dragged_tab_width); + bool pinned = false; + if (pinned_tab_count > 0) { + int insert_index; + if (tab && tab->IsVisible()) { + // Initial call to Attach. Don't use the screen_point as it's 0,0. + insert_index = source_model_index_; + } else { + gfx::Point client_point = + ConvertScreenPointToTabStripPoint(attached_tabstrip_, screen_point); + insert_index = GetInsertionIndexForDraggedBounds( + gfx::Rect(client_point, gfx::Size(1, 1))); + insert_index = std::max( + std::min(insert_index, attached_tabstrip_->model()->count()), 0); + } + if (insert_index < pinned_tab_count) { + // New tab is going to be pinned, use the pinned size for the dragged + // tab. + dragged_tab_width = Tab::GetPinnedWidth(); + pinned = true; + } + } + view_->set_pinned(pinned); + view_->Attach(dragged_tab_width); if (!tab) { // There is no Tab in |attached_tabstrip| that corresponds to the dragged @@ -822,6 +964,21 @@ void DraggedTabController::Detach() { attached_tabstrip_ = NULL; } +int DraggedTabController::GetPinnedThreshold() { + int pinned_count = attached_tabstrip_->model()->IndexOfFirstNonPinnedTab(); + if (pinned_count == 0) + return 0; + if (!view_->pinned()) { + gfx::Rect non_pinned_bounds = + attached_tabstrip_->GetIdealBounds(pinned_count); + return non_pinned_bounds.x() + Tab::GetPinnedWidth() / 2; + } + gfx::Rect last_pinned_bounds = + attached_tabstrip_->GetIdealBounds(pinned_count - 1); + return last_pinned_bounds.x() + TabStrip::pinned_to_non_pinned_gap_ + + Tab::GetPinnedWidth() / 2; +} + int DraggedTabController::GetInsertionIndexForDraggedBounds( const gfx::Rect& dragged_bounds) const { int right_tab_x = 0; @@ -920,6 +1077,7 @@ bool DraggedTabController::EndDragImpl(EndDragType type) { // the animation finishes, this is invoked twice. The second time through // type == TAB_DESTROYED. + pin_timer_.Stop(); bring_to_front_timer_.Stop(); // Hide the current dock controllers. @@ -1000,6 +1158,8 @@ void DraggedTabController::RevertDrag() { source_tabstrip_->model()->InsertTabContentsAt(source_model_index_, dragged_contents_, true, false); } + source_tabstrip_->model()->SetTabPinned(source_model_index_, was_pinned_); + // If we're not attached to any TabStrip, or attached to some other TabStrip, // we need to restore the bounds of the original TabStrip's frame, in case // it has been hidden. diff --git a/chrome/browser/views/tabs/dragged_tab_controller.h b/chrome/browser/views/tabs/dragged_tab_controller.h index 11c88a6..6b0c637 100644 --- a/chrome/browser/views/tabs/dragged_tab_controller.h +++ b/chrome/browser/views/tabs/dragged_tab_controller.h @@ -158,6 +158,29 @@ class DraggedTabController : public TabContentsDelegate, // Handles moving the Tab within a TabStrip as well as updating the View. void MoveTab(const gfx::Point& screen_point); + // Cover for MakeDraggedTabPinned(0). This is invoked from the pin_timer_. + void MakeDraggedTabPinned(); + + // Changes the dragged tab from a normal tab to pinned, updating the + // necessary state. + void MakeDraggedTabPinned(int tab_index); + + // If |screen_point| is along the edge of the tab strip and there are no + // pinned tabs in the model, pin_timer_ is started. + void StartPinTimerIfNecessary(const gfx::Point& screen_point); + + // Invoked from |MoveTab| to adjust |dragged_tab_point|. |screen_point| is + // the location of the mouse and |from_index| the index the dragged tab is + // at. + // This updates the pinned state of the dragged tab and model based on the + // location of the mouse. If |screen_point| is before the pinned threshold + // the dragged tab (and model) are pinned. If |screen_point| is after the + // pinned threshold, the dragged tab is not allowed to go before the first + // non-pinned tab and the dragged tab (and model) are marked as non-pinned. + void AdjustDragPointForPinnedTabs(const gfx::Point& screen_point, + int* from_index, + gfx::Point* dragged_tab_point); + // Returns the compatible TabStrip that is under the specified point (screen // coordinates), or NULL if there is none. TabStrip* GetTabStripForPoint(const gfx::Point& screen_point); @@ -175,6 +198,12 @@ class DraggedTabController : public TabContentsDelegate, // Detach the dragged Tab from the current TabStrip. void Detach(); + // Returns the horizontal location (relative to the tabstrip) at which the + // dragged tab is pinned. That is, if the current x location is < then the + // return value of this the dragged tab and model should be made pinned, + // otherwise the dragged tab and model should not be pinned. + int GetPinnedThreshold(); + // Returns the index where the dragged TabContents should be inserted into // the attached TabStripModel given the DraggedTabView's bounds // |dragged_bounds| in coordinates relative to the attached TabStrip. @@ -307,11 +336,20 @@ class DraggedTabController : public TabContentsDelegate, DockWindows dock_windows_; std::vector<DockDisplayer*> dock_controllers_; + // Was the tab originally pinned? Used if we end up reverting the drag. + const bool was_pinned_; + // Timer used to bring the window under the cursor to front. If the user // stops moving the mouse for a brief time over a browser window, it is // brought to front. base::OneShotTimer<DraggedTabController> bring_to_front_timer_; + // Timer used to pin the first tab. When the user drags a tab to the first + // tab in the tab strip this timer is started. If the user doesn't move the + // mouse, the tab is pinned. This timer invokes MakeDraggedTabPinned when it + // fires. + base::OneShotTimer<DraggedTabController> pin_timer_; + DISALLOW_COPY_AND_ASSIGN(DraggedTabController); }; diff --git a/chrome/browser/views/tabs/dragged_tab_view.cc b/chrome/browser/views/tabs/dragged_tab_view.cc index 2ba37bf..2c0a573 100644 --- a/chrome/browser/views/tabs/dragged_tab_view.cc +++ b/chrome/browser/views/tabs/dragged_tab_view.cc @@ -38,7 +38,8 @@ DraggedTabView::DraggedTabView(TabContents* datasource, attached_tab_size_(TabRenderer::GetMinimumSelectedSize()), photobooth_(NULL), contents_size_(contents_size), - close_animation_(this) { + close_animation_(this), + tab_width_(0) { SetParentOwned(false); renderer_->UpdateData(datasource, false); @@ -107,17 +108,37 @@ void DraggedTabView::MoveTo(const gfx::Point& screen_point) { void DraggedTabView::Attach(int selected_width) { attached_ = true; photobooth_ = NULL; - attached_tab_size_.set_width(selected_width); #if defined(OS_WIN) container_->SetOpacity(kOpaqueAlpha); #else NOTIMPLEMENTED(); #endif + Resize(selected_width); +} + +void DraggedTabView::Resize(int width) { + attached_tab_size_.set_width(width); ResizeContainer(); Update(); } +void DraggedTabView::set_pinned(bool pinned) { + renderer_->set_pinned(pinned); +} + +bool DraggedTabView::pinned() const { + return renderer_->pinned(); +} + void DraggedTabView::Detach(NativeViewPhotobooth* photobooth) { + // Detached tabs are never pinned. + renderer_->set_pinned(false); + + if (attached_tab_size_.width() != tab_width_) { + // The attached tab size differs from current tab size. Resize accordingly. + Resize(tab_width_); + } + attached_ = false; photobooth_ = photobooth; #if defined(OS_WIN) diff --git a/chrome/browser/views/tabs/dragged_tab_view.h b/chrome/browser/views/tabs/dragged_tab_view.h index e01db5c..1c212b6 100644 --- a/chrome/browser/views/tabs/dragged_tab_view.h +++ b/chrome/browser/views/tabs/dragged_tab_view.h @@ -40,9 +40,28 @@ class DraggedTabView : public views::View, // pointer at |screen_point|. void MoveTo(const gfx::Point& screen_point); + // Sets the offset of the mouse from the upper left corner of the tab. + void set_mouse_tab_offset(const gfx::Point& offset) { + mouse_tab_offset_ = offset; + } + + // Sets the non-pinned tab width. The actual width of the dragged tab is the + // value last past to Attach or Resize. |tab_width| is used when Detach is + // invoked (which triggers resizing to |tab_width|), or when dragging within + // a tab strip and the dragged tab changes state from pinned to non-pinned. + void set_tab_width(int tab_width) { tab_width_ = tab_width; } + int tab_width() const { return tab_width_; } + // Notifies the DraggedTabView that it has become attached to a TabStrip. void Attach(int selected_width); + // Resizes the dragged tab to a width of |width|. + void Resize(int width); + + // Sets whether the tab is rendered pinned or not. + void set_pinned(bool pinned); + bool pinned() const; + // Notifies the DraggedTabView that it has been detached from a TabStrip. void Detach(NativeViewPhotobooth* photobooth); @@ -55,7 +74,7 @@ class DraggedTabView : public views::View, // Returns the size of the DraggedTabView. Used when attaching to a TabStrip // to determine where to place the Tab in the attached TabStrip. - gfx::Size attached_tab_size() const { return attached_tab_size_; } + const gfx::Size& attached_tab_size() const { return attached_tab_size_; } private: // Overridden from AnimationDelegate: @@ -106,8 +125,8 @@ class DraggedTabView : public views::View, // position of detached windows. gfx::Point mouse_tab_offset_; - // The desired width of the TabRenderer when the DraggedTabView is attached - // to a TabStrip. + // The size of the tab renderer when the dragged tab is attached to a + // tabstrip. gfx::Size attached_tab_size_; // A handle to the DIB containing the current screenshot of the TabContents @@ -127,7 +146,10 @@ class DraggedTabView : public views::View, gfx::Rect animation_start_bounds_; gfx::Rect animation_end_bounds_; - DISALLOW_EVIL_CONSTRUCTORS(DraggedTabView); + // Non-pinned tab width. See description above setter for how this is used. + int tab_width_; + + DISALLOW_COPY_AND_ASSIGN(DraggedTabView); }; #endif // CHROME_BROWSER_VIEWS_TABS_DRAGGED_TAB_VIEW_H_ diff --git a/chrome/browser/views/tabs/tab.cc b/chrome/browser/views/tabs/tab.cc index 95ea76a..93d5cb2 100644 --- a/chrome/browser/views/tabs/tab.cc +++ b/chrome/browser/views/tabs/tab.cc @@ -48,7 +48,9 @@ class Tab::TabContextMenuContents : public views::SimpleMenuModel, // Overridden from views::SimpleMenuModel::Delegate: virtual bool IsCommandIdChecked(int command_id) const { - return false; + if (!tab_ || command_id != TabStripModel::CommandTogglePinned) + return false; + return tab_->pinned(); } virtual bool IsCommandIdEnabled(int command_id) const { return tab_ && tab_->delegate()->IsCommandEnabledForTab( @@ -93,6 +95,9 @@ class Tab::TabContextMenuContents : public views::SimpleMenuModel, AddItemWithStringId(TabStripModel::CommandCloseTabsOpenedBy, IDS_TAB_CXMENU_CLOSETABSOPENEDBY); AddItemWithStringId(TabStripModel::CommandRestoreTab, IDS_RESTORE_TAB); + AddSeparator(); + AddCheckItemWithStringId(TabStripModel::CommandTogglePinned, + IDS_TAB_CXMENU_PIN_TAB); menu_.reset(new views::Menu2(this)); } diff --git a/chrome/browser/views/tabs/tab_renderer.cc b/chrome/browser/views/tabs/tab_renderer.cc index 62bf4f1..4e4e7a4 100644 --- a/chrome/browser/views/tabs/tab_renderer.cc +++ b/chrome/browser/views/tabs/tab_renderer.cc @@ -7,6 +7,7 @@ #include <limits> #include "app/gfx/canvas.h" +#include "app/gfx/favicon_size.h" #include "app/gfx/font.h" #include "app/l10n_util.h" #include "app/resource_bundle.h" @@ -38,8 +39,14 @@ static const int kTitleCloseButtonSpacing = 5; static const int kStandardTitleWidth = 175; static const int kCloseButtonVertFuzz = 0; static const int kCloseButtonHorzFuzz = 5; -static const int kFaviconSize = 16; static const int kSelectedTitleColor = SK_ColorBLACK; +// Preferred width of pinned tabs. +static const int kPinnedTabWidth = 64; +// When a non-pinned tab is pinned the width of the tab animates. If the width +// of a pinned tab is >= kPinnedTabRendererAsTabWidth then the tab is rendered +// as a normal tab. This is done to avoid having the title immediately +// disappear when transitioning a tab from normal to pinned. +static const int kPinnedTabRendererAsTabWidth = kPinnedTabWidth + 30; // How long the hover state takes. static const int kHoverDurationMs = 90; @@ -144,7 +151,7 @@ void InitResources() { int GetContentHeight() { // The height of the content of the Tab is the largest of the favicon, // the title text and the close button graphic. - int content_height = std::max(kFaviconSize, title_font_height); + int content_height = std::max(kFavIconSize, title_font_height); return std::max(content_height, close_button_height); } @@ -240,6 +247,9 @@ TabRenderer::TabRenderer() theme_provider_(NULL) { InitResources(); + data_.pinned = false; + data_.animating_pinned_change = false; + // Add the Close Button. close_button_ = new TabCloseButton(this); close_button_->SetImage(views::CustomButton::BS_NORMAL, close_button_n); @@ -307,6 +317,10 @@ void TabRenderer::UpdateFromModel() { } } +void TabRenderer::set_animating_pinned_change(bool value) { + data_.animating_pinned_change = value; +} + bool TabRenderer::IsSelected() const { return true; } @@ -362,7 +376,7 @@ gfx::Size TabRenderer::GetMinimumUnselectedSize() { // static gfx::Size TabRenderer::GetMinimumSelectedSize() { gfx::Size minimum_size = GetMinimumUnselectedSize(); - minimum_size.set_width(kLeftPadding + kFaviconSize + kRightPadding); + minimum_size.set_width(kLeftPadding + kFavIconSize + kRightPadding); return minimum_size; } @@ -374,6 +388,11 @@ gfx::Size TabRenderer::GetStandardSize() { return standard_size; } +// static +int TabRenderer::GetPinnedWidth() { + return kPinnedTabWidth; +} + //////////////////////////////////////////////////////////////////////////////// // TabRenderer, protected: @@ -387,7 +406,7 @@ std::wstring TabRenderer::GetTitle() const { void TabRenderer::Paint(gfx::Canvas* canvas) { // Don't paint if we're narrower than we can render correctly. (This should // only happen during animations). - if (width() < GetMinimumUnselectedSize().width()) + if (width() < GetMinimumUnselectedSize().width() && !pinned()) return; // See if the model changes whether the icons should be painted. @@ -399,56 +418,17 @@ void TabRenderer::Paint(gfx::Canvas* canvas) { PaintTabBackground(canvas); - // Paint the loading animation if the page is currently loading, otherwise - // show the page's favicon. - if (show_icon) { - if (animation_state_ != ANIMATION_NONE) { - PaintLoadingAnimation(canvas); - } else { - canvas->save(); - canvas->ClipRectInt(0, 0, width(), height() - 4); - if (should_display_crashed_favicon_) { - canvas->DrawBitmapInt(*crashed_fav_icon, 0, 0, - crashed_fav_icon->width(), - crashed_fav_icon->height(), - favicon_bounds_.x(), - favicon_bounds_.y() + fav_icon_hiding_offset_, - kFaviconSize, kFaviconSize, - true); - } else { - if (!data_.favicon.isNull()) { - // TODO(pkasting): Use code in tab_icon_view.cc:PaintIcon() (or switch - // to using that class to render the favicon). - canvas->DrawBitmapInt(data_.favicon, 0, 0, - data_.favicon.width(), - data_.favicon.height(), - favicon_bounds_.x(), - favicon_bounds_.y() + fav_icon_hiding_offset_, - kFaviconSize, kFaviconSize, - true); - } - } - canvas->restore(); - } - } - - // Paint the Title. - string16 title = data_.title; - if (title.empty()) { - if (data_.loading) { - title = l10n_util::GetStringUTF16(IDS_TAB_LOADING_TITLE); - } else { - title = l10n_util::GetStringUTF16(IDS_TAB_UNTITLED_TITLE); - } - } else { - Browser::FormatTitleForDisplay(&title); - } - SkColor title_color = GetThemeProvider()-> GetColor(IsSelected() ? BrowserThemeProvider::COLOR_TAB_TEXT : BrowserThemeProvider::COLOR_BACKGROUND_TAB_TEXT); + if (!pinned() || width() > kPinnedTabRendererAsTabWidth) + PaintTitle(title_color, canvas); + + if (show_icon) + PaintIcon(canvas); + // If the close button color has changed, generate a new one. if (!close_button_color_ || title_color != close_button_color_) { close_button_color_ = title_color; @@ -457,10 +437,6 @@ void TabRenderer::Paint(gfx::Canvas* canvas) { rb.GetBitmapNamed(IDR_TAB_CLOSE), rb.GetBitmapNamed(IDR_TAB_CLOSE_MASK)); } - - canvas->DrawStringInt(UTF16ToWideHack(title), *title_font, title_color, - title_bounds_.x(), title_bounds_.y(), - title_bounds_.width(), title_bounds_.height()); } void TabRenderer::Layout() { @@ -475,8 +451,21 @@ void TabRenderer::Layout() { // Size the Favicon. showing_icon_ = ShouldShowIcon(); if (showing_icon_) { - int favicon_top = kTopPadding + (content_height - kFaviconSize) / 2; - favicon_bounds_.SetRect(lb.x(), favicon_top, kFaviconSize, kFaviconSize); + int favicon_top = kTopPadding + (content_height - kFavIconSize) / 2; + favicon_bounds_.SetRect(lb.x(), favicon_top, kFavIconSize, kFavIconSize); + if ((pinned() || data_.animating_pinned_change) && + width() < kPinnedTabRendererAsTabWidth) { + int pin_delta = kPinnedTabRendererAsTabWidth - kPinnedTabWidth; + int ideal_delta = width() - kPinnedTabWidth; + if (ideal_delta < pin_delta) { + int ideal_x = (kPinnedTabWidth - kFavIconSize) / 2; + int x = favicon_bounds_.x() + static_cast<int>( + (1 - static_cast<float>(ideal_delta) / + static_cast<float>(pin_delta)) * + (ideal_x - favicon_bounds_.x())); + favicon_bounds_.set_x(x); + } + } } else { favicon_bounds_.SetRect(lb.x(), lb.y(), 0, 0); } @@ -498,25 +487,28 @@ void TabRenderer::Layout() { } // Size the Title text to fill the remaining space. - int title_left = favicon_bounds_.right() + kFavIconTitleSpacing; - int title_top = kTopPadding + (content_height - title_font_height) / 2; - - // If the user has big fonts, the title will appear rendered too far down on - // the y-axis if we use the regular top padding, so we need to adjust it so - // that the text appears centered. - gfx::Size minimum_size = GetMinimumUnselectedSize(); - int text_height = title_top + title_font_height + kBottomPadding; - if (text_height > minimum_size.height()) - title_top -= (text_height - minimum_size.height()) / 2; - - int title_width; - if (close_button_->IsVisible()) { - title_width = std::max(close_button_->x() - - kTitleCloseButtonSpacing - title_left, 0); - } else { - title_width = std::max(lb.width() - title_left, 0); + if (!pinned() || width() >= kPinnedTabRendererAsTabWidth) { + // Size the Title text to fill the remaining space. + int title_left = favicon_bounds_.right() + kFavIconTitleSpacing; + int title_top = kTopPadding + (content_height - title_font_height) / 2; + // If the user has big fonts, the title will appear rendered too far down + // on the y-axis if we use the regular top padding, so we need to adjust it + // so that the text appears centered. + gfx::Size minimum_size = GetMinimumUnselectedSize(); + int text_height = title_top + title_font_height + kBottomPadding; + if (text_height > minimum_size.height()) + title_top -= (text_height - minimum_size.height()) / 2; + + int title_width; + if (close_button_->IsVisible()) { + title_width = std::max(close_button_->x() - + kTitleCloseButtonSpacing - title_left, 0); + } else { + title_width = std::max(lb.width() - title_left, 0); + } + title_bounds_.SetRect(title_left, title_top, title_width, + title_font_height); } - title_bounds_.SetRect(title_left, title_top, title_width, title_font_height); // Certain UI elements within the Tab (the favicon, etc.) are not represented // as child Views (which is the preferred method). Instead, these UI elements @@ -562,6 +554,55 @@ void TabRenderer::AnimationEnded(const Animation* animation) { //////////////////////////////////////////////////////////////////////////////// // TabRenderer, private +void TabRenderer::PaintTitle(SkColor title_color, gfx::Canvas* canvas) { + // Paint the Title. + string16 title = data_.title; + if (title.empty()) { + if (data_.loading) { + title = l10n_util::GetStringUTF16(IDS_TAB_LOADING_TITLE); + } else { + title = l10n_util::GetStringUTF16(IDS_TAB_UNTITLED_TITLE); + } + } else { + Browser::FormatTitleForDisplay(&title); + } + + canvas->DrawStringInt(UTF16ToWideHack(title), *title_font, title_color, + title_bounds_.x(), title_bounds_.y(), + title_bounds_.width(), title_bounds_.height()); +} + +void TabRenderer::PaintIcon(gfx::Canvas* canvas) { + if (animation_state_ != ANIMATION_NONE) { + PaintLoadingAnimation(canvas); + } else { + canvas->save(); + canvas->ClipRectInt(0, 0, width(), height() - kFavIconTitleSpacing); + if (should_display_crashed_favicon_) { + canvas->DrawBitmapInt(*crashed_fav_icon, 0, 0, + crashed_fav_icon->width(), + crashed_fav_icon->height(), + favicon_bounds_.x(), + favicon_bounds_.y() + fav_icon_hiding_offset_, + kFavIconSize, kFavIconSize, + true); + } else { + if (!data_.favicon.isNull()) { + // TODO(pkasting): Use code in tab_icon_view.cc:PaintIcon() (or switch + // to using that class to render the favicon). + canvas->DrawBitmapInt(data_.favicon, 0, 0, + data_.favicon.width(), + data_.favicon.height(), + favicon_bounds_.x(), + favicon_bounds_.y() + fav_icon_hiding_offset_, + kFavIconSize, kFavIconSize, + true); + } + } + canvas->restore(); + } +} + void TabRenderer::PaintTabBackground(gfx::Canvas* canvas) { if (IsSelected()) { // Sometimes detaching a tab quickly can result in the model reporting it @@ -723,10 +764,14 @@ void TabRenderer::PaintLoadingAnimation(gfx::Canvas* canvas) { // loading animation also needs to be mirrored if the View's UI layout is // right-to-left. int dst_x; - if (UILayoutIsRightToLeft()) { - dst_x = width() - kLeftPadding - image_size; + if (pinned()) { + dst_x = favicon_bounds_.x(); } else { - dst_x = kLeftPadding; + if (UILayoutIsRightToLeft()) { + dst_x = width() - kLeftPadding - image_size; + } else { + dst_x = kLeftPadding; + } } canvas->DrawBitmapInt(*frames, image_offset, 0, image_size, image_size, dst_x, dst_y, image_size, image_size, @@ -736,10 +781,12 @@ void TabRenderer::PaintLoadingAnimation(gfx::Canvas* canvas) { int TabRenderer::IconCapacity() const { if (height() < GetMinimumUnselectedSize().height()) return 0; - return (width() - kLeftPadding - kRightPadding) / kFaviconSize; + return (width() - kLeftPadding - kRightPadding) / kFavIconSize; } bool TabRenderer::ShouldShowIcon() const { + if (pinned() && height() >= GetMinimumUnselectedSize().height()) + return true; if (!data_.show_icon) { return false; } else if (IsSelected()) { @@ -752,7 +799,7 @@ bool TabRenderer::ShouldShowIcon() const { bool TabRenderer::ShouldShowCloseBox() const { // The selected tab never clips close button. - return IsSelected() || IconCapacity() >= 3; + return !pinned() && (IsSelected() || IconCapacity() >= 3); } //////////////////////////////////////////////////////////////////////////////// diff --git a/chrome/browser/views/tabs/tab_renderer.h b/chrome/browser/views/tabs/tab_renderer.h index 1f91dd3..f33cccb 100644 --- a/chrome/browser/views/tabs/tab_renderer.h +++ b/chrome/browser/views/tabs/tab_renderer.h @@ -46,6 +46,13 @@ class TabRenderer : public views::View, // See TabStripModel::TabChangedAt documentation for what loading_only means. void UpdateData(TabContents* contents, bool loading_only); + // Sets the pinned state of the tab. + void set_pinned(bool pinned) { data_.pinned = pinned; } + bool pinned() const { return data_.pinned; } + + // Are we in the process of animating a pinned state change on this tab? + void set_animating_pinned_change(bool value); + // Updates the display to reflect the contents of this TabRenderer's model. void UpdateFromModel(); @@ -83,6 +90,9 @@ class TabRenderer : public views::View, // available. static gfx::Size GetStandardSize(); + // Returns the width for pinned tabs. Pinned tabs always have this width. + static int GetPinnedWidth(); + // Loads the images to be used for the tab background. static void LoadTabImages(); @@ -124,10 +134,13 @@ class TabRenderer : public views::View, void ResetCrashedFavIcon(); // Paint various portions of the Tab + void PaintTitle(SkColor title_color, gfx::Canvas* canvas); + void PaintIcon(gfx::Canvas* canvas); void PaintTabBackground(gfx::Canvas* canvas); void PaintInactiveTabBackground(gfx::Canvas* canvas); void PaintActiveTabBackground(gfx::Canvas* canvas); void PaintHoverTabBackground(gfx::Canvas* canvas, double opacity); + void PaintPinnedTabBackground(gfx::Canvas* canvas); void PaintLoadingAnimation(gfx::Canvas* canvas); // Returns the number of favicon-size elements that can fit in the tab's @@ -172,6 +185,8 @@ class TabRenderer : public views::View, bool crashed; bool off_the_record; bool show_icon; + bool pinned; + bool animating_pinned_change; }; TabData data_; @@ -185,7 +200,6 @@ class TabRenderer : public views::View, static TabImage tab_active; static TabImage tab_inactive; static TabImage tab_alpha; - static TabImage tab_hover; // Whether we're showing the icon. It is cached so that we can detect when it // changes and layout appropriately. diff --git a/chrome/browser/views/tabs/tab_strip.cc b/chrome/browser/views/tabs/tab_strip.cc index d615e7a..0c3d5a6 100644 --- a/chrome/browser/views/tabs/tab_strip.cc +++ b/chrome/browser/views/tabs/tab_strip.cc @@ -47,6 +47,7 @@ using views::DropTargetEvent; static const int kDefaultAnimationDurationMs = 100; static const int kResizeLayoutAnimationDurationMs = 166; static const int kReorderAnimationDurationMs = 166; +static const int kPinnedTabAnimationDurationMs = 166; static const int kNewTabButtonHOffset = -5; static const int kNewTabButtonVOffset = 5; @@ -119,7 +120,9 @@ class TabStrip::TabAnimation : public AnimationDelegate { INSERT, REMOVE, MOVE, - RESIZE + RESIZE, + PIN, + PIN_MOVE }; TabAnimation(TabStrip* tabstrip, Type type) @@ -158,10 +161,15 @@ class TabStrip::TabAnimation : public AnimationDelegate { static double GetCurrentTabWidth(TabStrip* tabstrip, TabStrip::TabAnimation* animation, int index) { - double unselected, selected; - tabstrip->GetCurrentTabWidths(&unselected, &selected); Tab* tab = tabstrip->GetTabAt(index); - double tab_width = tab->IsSelected() ? selected : unselected; + double tab_width; + if (tab->pinned()) { + tab_width = Tab::GetPinnedWidth(); + } 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) @@ -184,6 +192,12 @@ class TabStrip::TabAnimation : public AnimationDelegate { 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 { @@ -199,8 +213,11 @@ class TabStrip::TabAnimation : public AnimationDelegate { // 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) { - tabstrip_->GetDesiredTabWidths(start_tab_count, &start_unselected_width_, + void GenerateStartAndEndWidths(int start_tab_count, int end_tab_count, + int start_pinned_count, + int end_pinned_count) { + tabstrip_->GetDesiredTabWidths(start_tab_count, start_pinned_count, + &start_unselected_width_, &start_selected_width_); double standard_tab_width = static_cast<double>(TabRenderer::GetStandardSize().width()); @@ -211,11 +228,26 @@ class TabStrip::TabAnimation : public AnimationDelegate { start_unselected_width_ -= minimum_tab_width / start_tab_count; } tabstrip_->GenerateIdealBounds(); - tabstrip_->GetDesiredTabWidths(end_tab_count, + tabstrip_->GetDesiredTabWidths(end_tab_count, end_pinned_count, &end_unselected_width_, &end_selected_width_); } + // Returns a value between |start| and |target| based on the current + // animation. + // TODO(sky): move this to animation. + int AnimationPosition(int start, int target) const { + return static_cast<int>(AnimationPosition(static_cast<double>(start), + static_cast<double>(target))); + } + + // Returns a value between |start| and |target| based on the current + // animation. + // TODO(sky): move this to animation. + double AnimationPosition(double start, double target) const { + return start + (target - start) * animation_.GetCurrentValue(); + } + TabStrip* tabstrip_; SlideAnimation animation_; @@ -245,7 +277,12 @@ class TabStrip::InsertTabAnimation : public TabStrip::TabAnimation { : TabAnimation(tabstrip, INSERT), index_(index) { int tab_count = tabstrip->GetTabCount(); - GenerateStartAndEndWidths(tab_count - 1, tab_count); + int end_pinned_count = tabstrip->GetPinnedTabCount(); + int start_pinned_count = end_pinned_count; + if (index < end_pinned_count) + start_pinned_count--; + GenerateStartAndEndWidths(tab_count - 1, tab_count, start_pinned_count, + end_pinned_count); } virtual ~InsertTabAnimation() {} @@ -254,19 +291,31 @@ class TabStrip::InsertTabAnimation : public TabStrip::TabAnimation { virtual double GetWidthForTab(int index) const { if (index == index_) { bool is_selected = tabstrip_->model()->selected_index() == index; - double target_width = - is_selected ? end_unselected_width_ : end_selected_width_; - double start_width = is_selected ? Tab::GetMinimumSelectedSize().width() : - Tab::GetMinimumUnselectedSize().width(); + double start_width, target_width; + if (index < tabstrip_->GetPinnedTabCount()) { + start_width = Tab::GetMinimumSelectedSize().width(); + target_width = Tab::GetPinnedWidth(); + } 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)->pinned()) + return Tab::GetPinnedWidth(); + 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()); } @@ -286,7 +335,12 @@ class TabStrip::RemoveTabAnimation : public TabStrip::TabAnimation { : TabAnimation(tabstrip, REMOVE), index_(index) { int tab_count = tabstrip->GetTabCount(); - GenerateStartAndEndWidths(tab_count, tab_count - 1); + int start_pinned_count = tabstrip->GetPinnedTabCount(); + int end_pinned_count = start_pinned_count; + if (index < start_pinned_count) + end_pinned_count--; + GenerateStartAndEndWidths(tab_count, tab_count - 1, start_pinned_count, + end_pinned_count); } // Returns the index of the tab being removed. @@ -303,15 +357,21 @@ class TabStrip::RemoveTabAnimation : public TabStrip::TabAnimation { // The tab(s) being removed are gradually shrunken depending on the state // of the animation. // Removed animated Tabs are never selected. + if (tab->pinned()) + return AnimationPosition(Tab::GetPinnedWidth(), -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); - double delta = start_width - target_width; - return start_width - (delta * animation_.GetCurrentValue()); + return AnimationPosition(start_width, target_width); } + + if (tab->pinned()) + return Tab::GetPinnedWidth(); + if (tabstrip_->available_width_for_tabs_ != -1 && index_ != tabstrip_->GetTabCount() - 1) { return TabStrip::TabAnimation::GetWidthForTab(index); @@ -430,7 +490,9 @@ class TabStrip::ResizeLayoutAnimation : public TabStrip::TabAnimation { explicit ResizeLayoutAnimation(TabStrip* tabstrip) : TabAnimation(tabstrip, RESIZE) { int tab_count = tabstrip->GetTabCount(); - GenerateStartAndEndWidths(tab_count, tab_count); + int pinned_tab_count = tabstrip->GetPinnedTabCount(); + GenerateStartAndEndWidths(tab_count, tab_count, pinned_tab_count, + pinned_tab_count); InitStartState(); } virtual ~ResizeLayoutAnimation() { @@ -449,12 +511,14 @@ class TabStrip::ResizeLayoutAnimation : public TabStrip::TabAnimation { } virtual double GetWidthForTab(int index) const { - 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()); + Tab* tab = tabstrip_->GetTabAt(index); + if (tab->pinned()) + return Tab::GetPinnedWidth(); + + if (tab->IsSelected()) + return AnimationPosition(start_selected_width_, end_selected_width_); + + return AnimationPosition(start_unselected_width_, end_unselected_width_); } private: @@ -465,10 +529,12 @@ class TabStrip::ResizeLayoutAnimation : public TabStrip::TabAnimation { void InitStartState() { for (int i = 0; i < tabstrip_->GetTabCount(); ++i) { Tab* current_tab = tabstrip_->GetTabAt(i); - if (current_tab->IsSelected()) { - start_selected_width_ = current_tab->width(); - } else { - start_unselected_width_ = current_tab->width(); + if (!current_tab->pinned()) { + if (current_tab->IsSelected()) { + start_selected_width_ = current_tab->width(); + } else { + start_unselected_width_ = current_tab->width(); + } } } } @@ -476,9 +542,170 @@ class TabStrip::ResizeLayoutAnimation : public TabStrip::TabAnimation { DISALLOW_COPY_AND_ASSIGN(ResizeLayoutAnimation); }; +//////////////////////////////////////////////////////////////////////////////// + +// Handles a tabs pinned state changing while the tab does not change position +// in the model. +class TabStrip::PinnedTabAnimation : public TabStrip::TabAnimation { + public: + explicit PinnedTabAnimation(TabStrip* tabstrip, int index) + : TabAnimation(tabstrip, PIN), + index_(index) { + int tab_count = tabstrip->GetTabCount(); + int start_pinned_count = tabstrip->GetPinnedTabCount(); + int end_pinned_count = start_pinned_count; + if (tabstrip->GetTabAt(index)->pinned()) + start_pinned_count--; + else + start_pinned_count++; + tabstrip_->GetTabAt(index)->set_animating_pinned_change(true); + GenerateStartAndEndWidths(tab_count, tab_count, start_pinned_count, + end_pinned_count); + } + + protected: + // Overridden from TabStrip::TabAnimation: + virtual int GetDuration() const { + return kPinnedTabAnimationDurationMs; + } + + virtual double GetWidthForTab(int index) const { + Tab* tab = tabstrip_->GetTabAt(index); + + if (index == index_) { + if (tab->pinned()) { + return AnimationPosition( + start_selected_width_, + static_cast<double>(Tab::GetPinnedWidth())); + } else { + return AnimationPosition(static_cast<double>(Tab::GetPinnedWidth()), + end_selected_width_); + } + } else if (tab->pinned()) { + return Tab::GetPinnedWidth(); + } + + if (tab->IsSelected()) + return AnimationPosition(start_selected_width_, end_selected_width_); + + return AnimationPosition(start_unselected_width_, end_unselected_width_); + } + + private: + // Index of the tab whose pinned state changed. + int index_; + + DISALLOW_COPY_AND_ASSIGN(PinnedTabAnimation); +}; + +//////////////////////////////////////////////////////////////////////////////// + +// Handles the animation when a tabs pinned state changes and the tab moves as a +// result. +class TabStrip::PinAndMoveAnimation : public TabStrip::TabAnimation { + public: + explicit PinAndMoveAnimation(TabStrip* tabstrip, + int from_index, + int to_index, + const gfx::Rect& start_bounds) + : TabAnimation(tabstrip, PIN_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_pinned_count = tabstrip->GetPinnedTabCount(); + int end_pinned_count = start_pinned_count; + if (tabstrip->GetTabAt(to_index)->pinned()) + start_pinned_count--; + else + start_pinned_count++; + GenerateStartAndEndWidths(tab_count, tab_count, start_pinned_count, + end_pinned_count); + target_bounds_ = tabstrip->GetIdealBounds(to_index); + tab_->set_animating_pinned_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 = AnimationPosition(start_bounds_.x(), target_bounds_.x()); + int width = AnimationPosition(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_->resize_layout_scheduled_ = false; + TabStrip::TabAnimation::AnimationEnded(animation); + } + + virtual double GetGapWidth(int index) { + if (to_index_ < from_index_) { + // The tab was pinned. + if (index == to_index_) { + double current_size = AnimationPosition(0, target_bounds_.width()); + if (current_size < -kTabHOffset) + return -(current_size + kTabHOffset); + } else if (index == from_index_ + 1) { + return AnimationPosition(start_bounds_.width(), 0); + } + } else { + // The tab was unpinned. + if (index == from_index_) { + return AnimationPosition(Tab::GetPinnedWidth() + 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 AnimationPosition(0, target_bounds_.width()); + + if (tab->pinned()) + return Tab::GetPinnedWidth(); + + if (tab->IsSelected()) + return AnimationPosition(start_selected_width_, end_selected_width_); + + return AnimationPosition(start_unselected_width_, end_unselected_width_); + } + + private: + // The tab being moved. + 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(PinAndMoveAnimation); +}; + /////////////////////////////////////////////////////////////////////////////// // TabStrip, public: +// static +const int TabStrip::pinned_to_non_pinned_gap_ = 3; + TabStrip::TabStrip(TabStripModel* model) : model_(model), resize_layout_factory_(this), @@ -616,9 +843,11 @@ void TabStrip::Layout() { for (int i = 0; i < tab_count; ++i) { const gfx::Rect& bounds = tab_data_.at(i).ideal_bounds; - GetTabAt(i)->SetBounds(bounds.x(), bounds.y(), bounds.width(), - bounds.height()); - tab_right = bounds.right() + kTabHOffset; + Tab* tab = GetTabAt(i); + tab->set_animating_pinned_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_); SchedulePaint(); @@ -778,16 +1007,11 @@ void TabStrip::TabInsertedAt(TabContents* contents, // Only insert if we're not already in the list. if (!contains_tab) { - if (index == TabStripModel::kNoTab) { - TabData d = { tab, gfx::Rect() }; - tab_data_.push_back(d); - tab->UpdateData(contents, false); - } else { - TabData d = { tab, gfx::Rect() }; - tab_data_.insert(tab_data_.begin() + index, d); - tab->UpdateData(contents, false); - } + TabData d = { tab, gfx::Rect() }; + tab_data_.insert(tab_data_.begin() + index, d); + tab->UpdateData(contents, false); } + tab->set_pinned(model_->IsTabPinned(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. @@ -833,12 +1057,18 @@ void TabStrip::TabSelectedAt(TabContents* old_contents, void TabStrip::TabMoved(TabContents* contents, int from_index, int to_index, bool pinned_state_changed) { + 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_pinned(model_->IsTabPinned(to_index)); tab_data_.insert(tab_data_.begin() + to_index, data); - GenerateIdealBounds(); - StartMoveTabAnimation(from_index, to_index); + if (pinned_state_changed) { + StartPinAndMoveTabAnimation(from_index, to_index, start_bounds); + } else { + GenerateIdealBounds(); + StartMoveTabAnimation(from_index, to_index); + } } void TabStrip::TabChangedAt(TabContents* contents, int index, @@ -850,6 +1080,11 @@ void TabStrip::TabChangedAt(TabContents* contents, int index, tab->UpdateFromModel(); } +void TabStrip::TabPinnedStateChanged(TabContents* contents, int index) { + GetTabAt(index)->set_pinned(model_->IsTabPinned(index)); + StartPinnedTabAnimation(index); +} + /////////////////////////////////////////////////////////////////////////////// // TabStrip, Tab::Delegate implementation: @@ -1188,14 +1423,19 @@ void TabStrip::GetCurrentTabWidths(double* unselected_width, } void TabStrip::GetDesiredTabWidths(int tab_count, + int pinned_tab_count, double* unselected_width, double* selected_width) const { + DCHECK(tab_count >= 0 && pinned_tab_count >= 0 && + pinned_tab_count <= tab_count); const double min_unselected_width = Tab::GetMinimumUnselectedSize().width(); const double min_selected_width = Tab::GetMinimumSelectedSize().width(); + + *unselected_width = min_unselected_width; + *selected_width = min_selected_width; + if (tab_count == 0) { // Return immediately to avoid divide-by-zero below. - *unselected_width = min_unselected_width; - *selected_width = min_selected_width; return; } @@ -1217,6 +1457,18 @@ void TabStrip::GetDesiredTabWidths(int tab_count, available_width = available_width_for_tabs_; } + if (pinned_tab_count > 0) { + available_width -= pinned_tab_count * (Tab::GetPinnedWidth() + + kTabHOffset); + tab_count -= pinned_tab_count; + if (tab_count == 0) { + *selected_width = *unselected_width = Tab::GetStandardSize().width(); + return; + } + // Account for gap between the last pinned tab and first non-pinned tab. + available_width -= pinned_to_non_pinned_gap_; + } + // Calculate the desired tab widths by dividing the available space into equal // portions. Don't let tabs get larger than the "standard width" or smaller // than the minimum width for each type, respectively. @@ -1252,6 +1504,14 @@ void TabStrip::GetDesiredTabWidths(int tab_count, } } +int TabStrip::GetTabHOffset(int tab_index) { + if (tab_index < GetTabCount() && GetTabAt(tab_index - 1)->pinned() && + !GetTabAt(tab_index)->pinned()) { + return pinned_to_non_pinned_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 @@ -1266,9 +1526,15 @@ void TabStrip::ResizeLayoutTabs() { RemoveMessageLoopObserver(); available_width_for_tabs_ = -1; + int pinned_tab_count = GetPinnedTabCount(); + if (pinned_tab_count == GetTabCount()) { + // Only pinned tabs, we know the tab widths won't have changed (all pinned + // tabs have the same width), so there is nothing to do. + return; + } + Tab* first_tab = GetTabAt(pinned_tab_count); double unselected, selected; - GetDesiredTabWidths(GetTabCount(), &unselected, &selected); - Tab* first_tab = GetTabAt(0); + GetDesiredTabWidths(GetTabCount(), pinned_tab_count, &unselected, &selected); int w = Round(first_tab->IsSelected() ? selected : selected); // We only want to run the animation if we're not already at the desired @@ -1320,6 +1586,7 @@ gfx::Rect TabStrip::GetDropBounds(int drop_index, int center_x; if (drop_index < GetTabCount()) { Tab* tab = GetTabAt(drop_index); + // TODO(sky): update these for pinned tabs. if (drop_before) center_x = tab->x() - (kTabHOffset / 2); else @@ -1472,7 +1739,7 @@ TabStrip::DropInfo::~DropInfo() { void TabStrip::GenerateIdealBounds() { int tab_count = GetTabCount(); double unselected, selected; - GetDesiredTabWidths(tab_count, &unselected, &selected); + GetDesiredTabWidths(tab_count, GetPinnedTabCount(), &unselected, &selected); current_unselected_width_ = unselected; current_selected_width_ = selected; @@ -1484,14 +1751,16 @@ void TabStrip::GenerateIdealBounds() { for (int i = 0; i < tab_count; ++i) { Tab* tab = GetTabAt(i); double tab_width = unselected; - if (tab->IsSelected()) + if (tab->pinned()) + tab_width = Tab::GetPinnedWidth(); + 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, tab_height); tab_data_.at(i).ideal_bounds = state; - tab_x = end_of_tab + kTabHOffset; + tab_x = end_of_tab + GetTabHOffset(i + 1); } } @@ -1521,13 +1790,15 @@ void TabStrip::AnimationLayout(double unselected_width) { 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 + kTabHOffset; + tab_x = end_of_tab + GetTabHOffset(i + 1); } LayoutNewTabButton(tab_x, unselected_width); SchedulePaint(); @@ -1570,6 +1841,23 @@ void TabStrip::StartMoveTabAnimation(int from_index, int to_index) { active_animation_->Start(); } +void TabStrip::StartPinnedTabAnimation(int index) { + if (active_animation_.get()) + active_animation_->Stop(); + active_animation_.reset(new PinnedTabAnimation(this, index)); + active_animation_->Start(); +} + +void TabStrip::StartPinAndMoveTabAnimation(int from_index, + int to_index, + const gfx::Rect& start_bounds) { + if (active_animation_.get()) + active_animation_->Stop(); + active_animation_.reset( + new PinAndMoveAnimation(this, from_index, to_index, start_bounds)); + active_animation_->Start(); +} + bool TabStrip::CanUpdateDisplay() { // Don't bother laying out/painting when we're closing all tabs. if (model_->closing_all()) { @@ -1584,6 +1872,11 @@ bool TabStrip::CanUpdateDisplay() { void TabStrip::FinishAnimation(TabStrip::TabAnimation* animation, bool layout) { active_animation_.reset(NULL); + + // Reset the animation state of each tab. + for (int i = 0, count = GetTabCount(); i < count; ++i) + GetTabAt(i)->set_animating_pinned_change(false); + if (layout) Layout(); } @@ -1600,6 +1893,17 @@ int TabStrip::GetIndexOfTab(const Tab* tab) const { return -1; } +int TabStrip::GetPinnedTabCount() const { + int pinned_count = 0; + for (size_t i = 0; i < tab_data_.size(); ++i) { + if (tab_data_[i].tab->pinned()) + pinned_count++; + else + return pinned_count; + } + return pinned_count; +} + int TabStrip::GetAvailableWidthForTabs(Tab* last_tab) const { return last_tab->x() + last_tab->width(); } diff --git a/chrome/browser/views/tabs/tab_strip.h b/chrome/browser/views/tabs/tab_strip.h index d598a2c..c996ba8 100644 --- a/chrome/browser/views/tabs/tab_strip.h +++ b/chrome/browser/views/tabs/tab_strip.h @@ -101,6 +101,7 @@ class TabStrip : public views::View, bool pinned_state_changed); virtual void TabChangedAt(TabContents* contents, int index, bool loading_only); + virtual void TabPinnedStateChanged(TabContents* contents, int index); // Tab::Delegate implementation: virtual bool IsTabSelected(const Tab* tab) const; @@ -146,9 +147,14 @@ class TabStrip : public views::View, virtual BrowserTabStrip* AsBrowserTabStrip(); virtual TabStrip* AsTabStrip(); + // Horizontal gap between pinned and non-pinned tabs. + static const int pinned_to_non_pinned_gap_; + private: class InsertTabAnimation; class MoveTabAnimation; + class PinAndMoveAnimation; + class PinnedTabAnimation; class RemoveTabAnimation; class ResizeLayoutAnimation; class TabAnimation; @@ -156,6 +162,8 @@ class TabStrip : public views::View, friend class DraggedTabController; friend class InsertTabAnimation; friend class MoveTabAnimation; + friend class PinAndMoveAnimation; + friend class PinnedTabAnimation; friend class RemoveTabAnimation; friend class ResizeLayoutAnimation; friend class TabAnimation; @@ -182,6 +190,9 @@ class TabStrip : public views::View, // Gets the number of Tabs in the collection. int GetTabCount() const; + // Returns the number of pinned tabs. + int GetPinnedTabCount() const; + // -- Tab Resize Layout ----------------------------------------------------- // Returns the exact (unrounded) current width of each tab. @@ -192,10 +203,16 @@ class TabStrip : public views::View, // desired strip width and number of tabs. If // |width_of_tabs_for_mouse_close_| is nonnegative we use that value in // calculating the desired strip width; otherwise we use the current width. + // |pinned_tab_count| gives the number of pinned tabs, and |tab_count| the + // number of pinned and non-pinned tabs. void GetDesiredTabWidths(int tab_count, + int pinned_tab_count, 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(); @@ -252,6 +269,9 @@ class TabStrip : public views::View, void StartInsertTabAnimation(int index); void StartRemoveTabAnimation(int index, TabContents* contents); void StartMoveTabAnimation(int from_index, int to_index); + void StartPinnedTabAnimation(int index); + void StartPinAndMoveTabAnimation(int from_index, int to_index, + const gfx::Rect& start_bounds); // Returns true if detach or select changes in the model should be reflected // in the TabStrip. This returns false if we're closing all tabs in the |