diff options
author | sky@chromium.org <sky@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2009-07-13 20:47:12 +0000 |
---|---|---|
committer | sky@chromium.org <sky@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2009-07-13 20:47:12 +0000 |
commit | 72d1a935f1adb1023d32c7c1906dbf64fdb02705 (patch) | |
tree | 0b5c6e4746a5037c6ba0066d26f0e2898dfff0ae | |
parent | f30f5e4d4d551ff6f95335599acf2f089aafc816 (diff) | |
download | chromium_src-72d1a935f1adb1023d32c7c1906dbf64fdb02705.zip chromium_src-72d1a935f1adb1023d32c7c1906dbf64fdb02705.tar.gz chromium_src-72d1a935f1adb1023d32c7c1906dbf64fdb02705.tar.bz2 |
Adds rendering and dragging of pinned tabs to gtk.
BUG=16634
TEST=not yet
Review URL: http://codereview.chromium.org/149544
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@20523 0039d316-1c4b-4281-b951-d872f2087c98
-rw-r--r-- | chrome/app/generated_resources.grd | 3 | ||||
-rw-r--r-- | chrome/browser/gtk/tabs/dragged_tab_controller_gtk.cc | 126 | ||||
-rw-r--r-- | chrome/browser/gtk/tabs/dragged_tab_controller_gtk.h | 21 | ||||
-rw-r--r-- | chrome/browser/gtk/tabs/dragged_tab_gtk.cc | 29 | ||||
-rw-r--r-- | chrome/browser/gtk/tabs/dragged_tab_gtk.h | 28 | ||||
-rw-r--r-- | chrome/browser/gtk/tabs/tab_gtk.cc | 13 | ||||
-rw-r--r-- | chrome/browser/gtk/tabs/tab_renderer_gtk.cc | 184 | ||||
-rw-r--r-- | chrome/browser/gtk/tabs/tab_renderer_gtk.h | 21 | ||||
-rw-r--r-- | chrome/browser/gtk/tabs/tab_strip_gtk.cc | 380 | ||||
-rw-r--r-- | chrome/browser/gtk/tabs/tab_strip_gtk.h | 22 |
10 files changed, 684 insertions, 143 deletions
diff --git a/chrome/app/generated_resources.grd b/chrome/app/generated_resources.grd index 6b2e5d3..d035b1e 100644 --- a/chrome/app/generated_resources.grd +++ b/chrome/app/generated_resources.grd @@ -2471,6 +2471,9 @@ each locale. --> <message name="IDS_TAB_CXMENU_CLOSETABSOPENEDBY" desc="The label of the 'Close Tabs opened by this Tab' Tab context menu item."> Close tabs opened by this tab </message> + <message name="IDS_TAB_CXMENU_PIN_TAB" desc="The label of the tab context menu item for pinning a tab."> + Pin tab + </message> <!-- Items in the bookmarks destination mode --> <message name="IDS_BOOKMARKS_DEST_MODE_BOOKMARK_BAR_ON_BOOKMARK_BAR" desc="Text shown when entry is on the bookmark bar"> diff --git a/chrome/browser/gtk/tabs/dragged_tab_controller_gtk.cc b/chrome/browser/gtk/tabs/dragged_tab_controller_gtk.cc index 3b5947f..37a5b0d 100644 --- a/chrome/browser/gtk/tabs/dragged_tab_controller_gtk.cc +++ b/chrome/browser/gtk/tabs/dragged_tab_controller_gtk.cc @@ -23,6 +23,9 @@ const int kBringToFrontDelay = 750; // their indexes. const int kHorizontalMoveThreshold = 16; // pixels +// Threshold for pinned tabs. +const int kHorizontalPinnedMoveThreshold = 4; // pixels + // How far a drag must pull a tab out of the tabstrip in order to detach it. const int kVerticalDetachMagnetism = 15; // pixels @@ -37,7 +40,8 @@ DraggedTabControllerGtk::DraggedTabControllerGtk(TabGtk* source_tab, source_model_index_(source_tabstrip->GetIndexOfTab(source_tab)), attached_tabstrip_(source_tabstrip), 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_)); } @@ -251,24 +255,34 @@ void DraggedTabControllerGtk::MoveTab(const gfx::Point& screen_point) { gfx::Point dragged_tab_point = GetDraggedTabPoint(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_tab_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 / TabGtk::GetStandardSize().width(); - int threshold = static_cast<int>(ratio * kHorizontalMoveThreshold); + int threshold = kHorizontalPinnedMoveThreshold; + if (!dragged_tab_->is_pinned()) { + double unselected, selected; + attached_tabstrip_->GetCurrentTabWidths(&unselected, &selected); + double ratio = unselected / TabGtk::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 = GetDraggedTabTabStripBounds(dragged_tab_point); int to_index = GetInsertionIndexForDraggedBounds(bounds); to_index = NormalizeIndexToAttachedTabStrip(to_index); + if (!dragged_tab_->is_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); @@ -280,6 +294,56 @@ void DraggedTabControllerGtk::MoveTab(const gfx::Point& screen_point) { dragged_tab_->MoveTo(dragged_tab_point); } +void DraggedTabControllerGtk::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 (!dragged_tab_->is_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. + + // Mark the tab as pinned and update the model. + dragged_tab_->set_pinned(true); + attached_model->SetTabPinned(from_index, true); + + // Resize the dragged tab. + mouse_offset_.set_x(TabGtk::GetPinnedWidth() / 2 - 1); + InitWindowCreatePoint(); + dragged_tab_->set_mouse_tab_offset(mouse_offset_); + dragged_tab_->Resize(TabGtk::GetPinnedWidth()); + + // The dragged tab point was calculated using the old mouse_offset, which + // we just reset. Recalculate it. + *dragged_tab_point = GetDraggedTabPoint(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 = + gtk_util::GetWidgetScreenBounds(attached_tabstrip_->tabstrip_.get()); + 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. + dragged_tab_->set_pinned(false); + attached_model->SetTabPinned(from_index, false); + + dragged_tab_->Resize(dragged_tab_->tab_width()); + } +} + TabStripGtk* DraggedTabControllerGtk::GetTabStripForPoint( const gfx::Point& screen_point) { GtkWidget* dragged_window = dragged_tab_->widget(); @@ -340,13 +404,38 @@ void DraggedTabControllerGtk::Attach(TabStripGtk* 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 = 0, selected_width = 0; - attached_tabstrip_->GetDesiredTabWidths(tab_count, &unselected_width, - &selected_width); + attached_tabstrip_->GetDesiredTabWidths(tab_count, pinned_tab_count, + &unselected_width, &selected_width); EnsureDraggedTab(); - dragged_tab_->Attach(static_cast<int>(selected_width)); + int dragged_tab_width = static_cast<int>(selected_width); + dragged_tab_->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 = TabGtk::GetPinnedWidth(); + pinned = true; + } + } + dragged_tab_->set_pinned(pinned); + dragged_tab_->Attach(dragged_tab_width); if (!tab) { // There is no tab in |attached_tabstrip| that corresponds to the dragged @@ -426,6 +515,20 @@ gfx::Point DraggedTabControllerGtk::ConvertScreenPointToTabStripPoint( return gfx::Point(screen_point.x() - x, screen_point.y() - y); } +int DraggedTabControllerGtk::GetPinnedThreshold() { + int pinned_count = attached_tabstrip_->model()->IndexOfFirstNonPinnedTab(); + if (pinned_count == 0) + return 0; + if (!dragged_tab_->is_pinned()) { + gfx::Rect non_pinned_bounds = + attached_tabstrip_->GetIdealBounds(pinned_count); + return non_pinned_bounds.x() + TabGtk::GetPinnedWidth() / 2; + } + gfx::Rect last_pinned_bounds = + attached_tabstrip_->GetIdealBounds(pinned_count - 1); + return last_pinned_bounds.x() + TabGtk::GetPinnedWidth() / 2; +} + gfx::Rect DraggedTabControllerGtk::GetDraggedTabTabStripBounds( const gfx::Point& screen_point) { gfx::Point client_point = @@ -600,6 +703,7 @@ void DraggedTabControllerGtk::RevertDrag() { source_tabstrip_->model()->InsertTabContentsAt(source_model_index_, dragged_contents_, true, false); } + source_tabstrip_->model()->SetTabPinned(source_model_index_, was_pinned_); source_tab_->SetVisible(true); } diff --git a/chrome/browser/gtk/tabs/dragged_tab_controller_gtk.h b/chrome/browser/gtk/tabs/dragged_tab_controller_gtk.h index 45a669d..3d06a6f 100644 --- a/chrome/browser/gtk/tabs/dragged_tab_controller_gtk.h +++ b/chrome/browser/gtk/tabs/dragged_tab_controller_gtk.h @@ -108,6 +108,18 @@ class DraggedTabControllerGtk : public NotificationObserver, // Handles moving the Tab within a TabStrip as well as updating the View. void MoveTab(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. TabStripGtk* GetTabStripForPoint(const gfx::Point& screen_point); @@ -127,6 +139,12 @@ class DraggedTabControllerGtk : public NotificationObserver, gfx::Point ConvertScreenPointToTabStripPoint(TabStripGtk* tabstrip, const gfx::Point& screen_point); + // 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(); + // Retrieve the bounds of the DraggedTabGtk, relative to the attached // TabStrip, given location of the dragged tab in screen coordinates. gfx::Rect GetDraggedTabTabStripBounds(const gfx::Point& screen_point); @@ -243,6 +261,9 @@ class DraggedTabControllerGtk : public NotificationObserver, typedef std::set<GtkWidget*> DockWindows; DockWindows dock_windows_; + // 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. diff --git a/chrome/browser/gtk/tabs/dragged_tab_gtk.cc b/chrome/browser/gtk/tabs/dragged_tab_gtk.cc index aab4b8c..45340ee 100644 --- a/chrome/browser/gtk/tabs/dragged_tab_gtk.cc +++ b/chrome/browser/gtk/tabs/dragged_tab_gtk.cc @@ -48,7 +48,8 @@ DraggedTabGtk::DraggedTabGtk(TabContents* datasource, mouse_tab_offset_(mouse_tab_offset), attached_tab_size_(TabRendererGtk::GetMinimumSelectedSize()), contents_size_(contents_size), - close_animation_(this) { + close_animation_(this), + tab_width_(0) { renderer_->UpdateData(datasource, false); container_ = gtk_window_new(GTK_WINDOW_POPUP); @@ -76,15 +77,35 @@ void DraggedTabGtk::MoveTo(const gfx::Point& screen_point) { void DraggedTabGtk::Attach(int selected_width) { attached_ = true; - attached_tab_size_.set_width(selected_width); - ResizeContainer(); - Update(); + Resize(selected_width); if (gtk_util::IsScreenComposited()) gdk_window_set_opacity(container_->window, kOpaqueAlpha); } +void DraggedTabGtk::Resize(int width) { + attached_tab_size_.set_width(width); + ResizeContainer(); + Update(); +} + +void DraggedTabGtk::set_pinned(bool pinned) { + renderer_->set_pinned(pinned); +} + +bool DraggedTabGtk::is_pinned() const { + return renderer_->is_pinned(); +} + void DraggedTabGtk::Detach(GtkWidget* contents, BackingStore* backing_store) { + // 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; contents_ = contents; backing_store_ = backing_store; diff --git a/chrome/browser/gtk/tabs/dragged_tab_gtk.h b/chrome/browser/gtk/tabs/dragged_tab_gtk.h index 0732a12..c88c10b 100644 --- a/chrome/browser/gtk/tabs/dragged_tab_gtk.h +++ b/chrome/browser/gtk/tabs/dragged_tab_gtk.h @@ -30,9 +30,28 @@ class DraggedTabGtk : public AnimationDelegate { // 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 dragged tab 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 is_pinned() const; + // Notifies the dragged tab that it has been detached from a tabstrip. // |contents| is the widget that contains the dragged tab contents, while // |backing_store| is the backing store that holds a server-side bitmap of the @@ -50,7 +69,7 @@ class DraggedTabGtk : public AnimationDelegate { // Returns the size of the dragged tab. 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_; } GtkWidget* widget() const { return container_; } @@ -119,8 +138,8 @@ class DraggedTabGtk : public AnimationDelegate { // position of detached windows. gfx::Point mouse_tab_offset_; - // The desired width of the tab renderer when the dragged tab 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_; // The dimensions of the TabContents being dragged. @@ -136,6 +155,9 @@ class DraggedTabGtk : public AnimationDelegate { gfx::Rect animation_start_bounds_; gfx::Rect animation_end_bounds_; + // Non-pinned tab width. See description above setter for how this is used. + int tab_width_; + DISALLOW_COPY_AND_ASSIGN(DraggedTabGtk); }; diff --git a/chrome/browser/gtk/tabs/tab_gtk.cc b/chrome/browser/gtk/tabs/tab_gtk.cc index 54f6428..0ac9711 100644 --- a/chrome/browser/gtk/tabs/tab_gtk.cc +++ b/chrome/browser/gtk/tabs/tab_gtk.cc @@ -53,6 +53,12 @@ class TabGtk::ContextMenuController : public MenuGtk::Delegate { menu_->AppendMenuItemWithLabel( TabStripModel::CommandRestoreTab, l10n_util::GetStringUTF8(IDS_RESTORE_TAB)); + if (TabStripModel::IsTabPinningEnabled()) { + menu_->AppendSeparator(); + menu_->AppendCheckMenuItemWithLabel( + TabStripModel::CommandTogglePinned, + l10n_util::GetStringUTF8(IDS_TAB_CXMENU_PIN_TAB)); + } } virtual ~ContextMenuController() {} @@ -67,7 +73,6 @@ class TabGtk::ContextMenuController : public MenuGtk::Delegate { } private: - // MenuGtk::Delegate implementation: virtual bool IsCommandEnabled(int command_id) const { if (!tab_) @@ -77,6 +82,12 @@ class TabGtk::ContextMenuController : public MenuGtk::Delegate { static_cast<TabStripModel::ContextMenuCommand>(command_id), tab_); } + virtual bool IsItemChecked(int command_id) const { + if (!tab_ || command_id != TabStripModel::CommandTogglePinned) + return false; + return tab_->is_pinned(); + } + virtual void ExecuteCommand(int command_id) { if (!tab_) return; diff --git a/chrome/browser/gtk/tabs/tab_renderer_gtk.cc b/chrome/browser/gtk/tabs/tab_renderer_gtk.cc index 78ddcc4..55e4b32 100644 --- a/chrome/browser/gtk/tabs/tab_renderer_gtk.cc +++ b/chrome/browser/gtk/tabs/tab_renderer_gtk.cc @@ -5,6 +5,7 @@ #include "chrome/browser/gtk/tabs/tab_renderer_gtk.h" #include "app/gfx/canvas_paint.h" +#include "app/gfx/favicon_size.h" #include "app/l10n_util.h" #include "app/resource_bundle.h" #include "chrome/browser/browser.h" @@ -27,8 +28,11 @@ const int kBottomPadding = 5; const int kFavIconTitleSpacing = 4; const int kTitleCloseButtonSpacing = 5; const int kStandardTitleWidth = 175; -const int kFavIconSize = 16; const int kDropShadowOffset = 2; +// Value added to pinned_tab_pref_width_ to get +// pinned_tab_renderer_as_tab_width_. See description of +// pinned_tab_renderer_as_tab_width_ for details. +const int kPinnedTabRendererAsNonPinnedWidth = 30; // How long the hover state takes. const int kHoverDurationMs = 90; @@ -88,13 +92,14 @@ bool TabRendererGtk::initialized_ = false; TabRendererGtk::TabImage TabRendererGtk::tab_active_ = {0}; TabRendererGtk::TabImage TabRendererGtk::tab_inactive_ = {0}; TabRendererGtk::TabImage TabRendererGtk::tab_alpha = {0}; -TabRendererGtk::TabImage TabRendererGtk::tab_hover_ = {0}; gfx::Font* TabRendererGtk::title_font_ = NULL; int TabRendererGtk::title_font_height_ = 0; int TabRendererGtk::close_button_width_ = 0; int TabRendererGtk::close_button_height_ = 0; SkColor TabRendererGtk::selected_title_color_ = SK_ColorBLACK; SkColor TabRendererGtk::unselected_title_color_ = SkColorSetRGB(64, 64, 64); +int TabRendererGtk::pinned_tab_renderer_as_tab_width_ = 0; +int TabRendererGtk::pinned_tab_pref_width_ = 0; //////////////////////////////////////////////////////////////////////////////// // TabRendererGtk::LoadingAnimation, public: @@ -178,6 +183,8 @@ TabRendererGtk::TabRendererGtk() loading_animation_(&loading_animation_data) { InitResources(); + data_.pinned = false; + tab_.Own(gtk_fixed_new()); gtk_widget_set_app_paintable(tab_.get(), TRUE); g_signal_connect(G_OBJECT(tab_.get()), "expose-event", @@ -225,6 +232,14 @@ void TabRendererGtk::UpdateFromModel() { } } +void TabRendererGtk::set_pinned(bool pinned) { + data_.pinned = pinned; +} + +bool TabRendererGtk::is_pinned() const { + return data_.pinned; +} + bool TabRendererGtk::IsSelected() const { return true; } @@ -236,8 +251,10 @@ bool TabRendererGtk::IsVisible() const { void TabRendererGtk::SetVisible(bool visible) const { if (visible) { gtk_widget_show(tab_.get()); + if (data_.pinned) + gtk_widget_show(close_button_->widget()); } else { - gtk_widget_hide(tab_.get()); + gtk_widget_hide_all(tab_.get()); } } @@ -272,6 +289,11 @@ gfx::Size TabRendererGtk::GetStandardSize() { } // static +int TabRendererGtk::GetPinnedWidth() { + return pinned_tab_pref_width_; +} + +// static int TabRendererGtk::GetContentHeight() { // The height of the content of the Tab is the largest of the favicon, // the title text and the close button graphic. @@ -300,6 +322,10 @@ void TabRendererGtk::LoadTabImages() { close_button_width_ = rb.GetBitmapNamed(IDR_TAB_CLOSE)->width(); close_button_height_ = rb.GetBitmapNamed(IDR_TAB_CLOSE)->height(); + + pinned_tab_pref_width_ = kLeftPadding + kFavIconSize + kRightPadding; + pinned_tab_renderer_as_tab_width_ = pinned_tab_pref_width_ + + kPinnedTabRendererAsNonPinnedWidth; } // static @@ -376,7 +402,7 @@ void TabRendererGtk::ResetCrashedFavIcon() { void TabRendererGtk::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() && !is_pinned()) return; // See if the model changes whether the icons should be painted. @@ -387,55 +413,11 @@ void TabRendererGtk::Paint(gfx::Canvas* canvas) { Layout(); PaintTabBackground(canvas); + if (!is_pinned() || width() > pinned_tab_renderer_as_tab_width_) + PaintTitle(canvas); - if (show_icon) { - if (loading_animation_.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(); - } - } - - // 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 = IsSelected() ? selected_title_color_ - : unselected_title_color_; - canvas->DrawStringInt(UTF16ToWideHack(title), *title_font_, title_color, - title_bounds_.x(), title_bounds_.y(), - title_bounds_.width(), title_bounds_.height()); + if (show_icon) + PaintIcon(canvas); } SkBitmap TabRendererGtk::PaintBitmap() { @@ -484,26 +466,29 @@ void TabRendererGtk::Layout() { close_button_bounds_.SetRect(0, 0, 0, 0); } - // 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_bounds_.width() && close_button_bounds_.height()) { - title_width = std::max(close_button_bounds_.x() - - kTitleCloseButtonSpacing - title_left, 0); - } else { - title_width = std::max(local_bounds.width() - title_left, 0); + if (!is_pinned() || width() >= pinned_tab_renderer_as_tab_width_) { + // 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_bounds_.width() && close_button_bounds_.height()) { + title_width = std::max(close_button_bounds_.x() - + kTitleCloseButtonSpacing - title_left, 0); + } else { + title_width = std::max(local_bounds.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_); favicon_bounds_.set_x( gtk_util::MirroredLeftPointForRect(tab_.get(), favicon_bounds_)); @@ -537,6 +522,57 @@ void TabRendererGtk::PaintTab(GdkEventExpose* event) { Paint(&canvas); } +void TabRendererGtk::PaintTitle(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); + } + + SkColor title_color = IsSelected() ? selected_title_color_ + : unselected_title_color_; + canvas->DrawStringInt(UTF16ToWideHack(title), *title_font_, title_color, + title_bounds_.x(), title_bounds_.y(), + title_bounds_.width(), title_bounds_.height()); +} + +void TabRendererGtk::PaintIcon(gfx::Canvas* canvas) { + if (loading_animation_.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 TabRendererGtk::PaintTabBackground(gfx::Canvas* canvas) { if (IsSelected()) { // Sometimes detaching a tab quickly can result in the model reporting it @@ -674,7 +710,9 @@ int TabRendererGtk::IconCapacity() const { } bool TabRendererGtk::ShouldShowIcon() const { - if (!data_.show_icon) { + if (is_pinned() && height() >= GetMinimumUnselectedSize().height()) { + return true; + } else if (!data_.show_icon) { return false; } else if (IsSelected()) { // The selected tab clips favicon before close button. @@ -686,7 +724,7 @@ bool TabRendererGtk::ShouldShowIcon() const { bool TabRendererGtk::ShouldShowCloseBox() const { // The selected tab never clips close button. - return IsSelected() || IconCapacity() >= 3; + return !is_pinned() && (IsSelected() || IconCapacity() >= 3); } CustomDrawButton* TabRendererGtk::MakeCloseButton() { diff --git a/chrome/browser/gtk/tabs/tab_renderer_gtk.h b/chrome/browser/gtk/tabs/tab_renderer_gtk.h index ac6a5ce..9f52a6c 100644 --- a/chrome/browser/gtk/tabs/tab_renderer_gtk.h +++ b/chrome/browser/gtk/tabs/tab_renderer_gtk.h @@ -80,6 +80,10 @@ class TabRendererGtk : public AnimationDelegate { // update everything. virtual void UpdateData(TabContents* contents, bool loading_only); + // Sets the pinned state of the tab. + void set_pinned(bool pinned); + bool is_pinned() const; + // Updates the display to reflect the contents of this TabRenderer's model. void UpdateFromModel(); @@ -119,6 +123,9 @@ class TabRendererGtk : public AnimationDelegate { // 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(); @@ -166,6 +173,7 @@ class TabRendererGtk : public AnimationDelegate { bool crashed; bool off_the_record; bool show_icon; + bool pinned; }; // TODO(jhawkins): Move into TabResources class. @@ -212,9 +220,12 @@ class TabRendererGtk : public AnimationDelegate { void PaintTab(GdkEventExpose* event); // Paint various portions of the Tab + void PaintTitle(gfx::Canvas* canvas); + void PaintIcon(gfx::Canvas* canvas); void PaintTabBackground(gfx::Canvas* canvas); void PaintInactiveTabBackground(gfx::Canvas* canvas); void PaintActiveTabBackground(gfx::Canvas* canvas); + void PaintPinnedTabBackground(gfx::Canvas* canvas); void PaintLoadingAnimation(gfx::Canvas* canvas); // Returns the number of favicon-size elements that can fit in the tab's @@ -255,7 +266,6 @@ class TabRendererGtk : public AnimationDelegate { static TabImage tab_active_; static TabImage tab_inactive_; static TabImage tab_alpha; - static TabImage tab_hover_; static gfx::Font* title_font_; static int title_font_height_; @@ -266,6 +276,15 @@ class TabRendererGtk : public AnimationDelegate { static SkColor selected_title_color_; static SkColor unselected_title_color_; + // Preferred width of pinned tabs. + static int pinned_tab_pref_width_; + + // When a non-pinned tab is pinned the width of the tab animates. If the + // width of a pinned tab is >= pinned_tab_renderer_as_tab_width 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 int pinned_tab_renderer_as_tab_width_; + // The GtkDrawingArea we draw the tab on. OwnedWidgetGtk tab_; diff --git a/chrome/browser/gtk/tabs/tab_strip_gtk.cc b/chrome/browser/gtk/tabs/tab_strip_gtk.cc index 2648964..b7e549d 100644 --- a/chrome/browser/gtk/tabs/tab_strip_gtk.cc +++ b/chrome/browser/gtk/tabs/tab_strip_gtk.cc @@ -38,6 +38,7 @@ const int kDefaultAnimationDurationMs = 100; const int kResizeLayoutAnimationDurationMs = 166; const int kReorderAnimationDurationMs = 166; const int kAnimateToBoundsDurationMs = 150; +const int kPinnedTabAnimationDurationMs = 166; const int kNewTabButtonHOffset = -5; const int kNewTabButtonVOffset = 5; @@ -93,7 +94,8 @@ class TabStripGtk::TabAnimation : public AnimationDelegate { REMOVE, MOVE, RESIZE, - SNAP + PIN, + PIN_MOVE }; TabAnimation(TabStripGtk* tabstrip, Type type) @@ -132,10 +134,15 @@ class TabStripGtk::TabAnimation : public AnimationDelegate { static double GetCurrentTabWidth(TabStripGtk* tabstrip, TabStripGtk::TabAnimation* animation, int index) { - double unselected, selected; - tabstrip->GetCurrentTabWidths(&unselected, &selected); TabGtk* tab = tabstrip->GetTabAt(index); - double tab_width = tab->IsSelected() ? selected : unselected; + double tab_width; + if (tab->is_pinned()) { + tab_width = TabGtk::GetPinnedWidth(); + } else { + double unselected, selected; + tabstrip->GetCurrentTabWidths(&unselected, &selected); + tab_width = tab->IsSelected() ? selected : unselected; + } if (animation) { double specified_tab_width = animation->GetWidthForTab(index); @@ -160,6 +167,12 @@ class TabStripGtk::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 { @@ -175,8 +188,11 @@ class TabStripGtk::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>(TabRendererGtk::GetStandardSize().width()); @@ -189,11 +205,26 @@ class TabStripGtk::TabAnimation : public AnimationDelegate { } 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(); + } + TabStripGtk* tabstrip_; SlideAnimation animation_; @@ -223,7 +254,12 @@ class InsertTabAnimation : public TabStripGtk::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() {} @@ -232,11 +268,17 @@ class InsertTabAnimation : public TabStripGtk::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 ? TabGtk::GetMinimumSelectedSize().width() : - TabGtk::GetMinimumUnselectedSize().width(); + double start_width, target_width; + if (index < tabstrip_->GetPinnedTabCount()) { + start_width = TabGtk::GetMinimumSelectedSize().width(); + target_width = TabGtk::GetPinnedWidth(); + } else { + target_width = + is_selected ? end_unselected_width_ : end_selected_width_; + start_width = + is_selected ? TabGtk::GetMinimumSelectedSize().width() : + TabGtk::GetMinimumUnselectedSize().width(); + } double delta = target_width - start_width; if (delta > 0) @@ -245,6 +287,9 @@ class InsertTabAnimation : public TabStripGtk::TabAnimation { return start_width; } + if (tabstrip_->GetTabAt(index)->is_pinned()) + return TabGtk::GetPinnedWidth(); + if (tabstrip_->GetTabAt(index)->IsSelected()) { double delta = end_selected_width_ - start_selected_width_; return start_selected_width_ + (delta * animation_.GetCurrentValue()); @@ -269,7 +314,12 @@ class RemoveTabAnimation : public TabStripGtk::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); } virtual ~RemoveTabAnimation() {} @@ -285,6 +335,9 @@ class RemoveTabAnimation : public TabStripGtk::TabAnimation { if (index == index_) { // The tab(s) being removed are gradually shrunken depending on the state // of the animation. + if (tab->is_pinned()) + return AnimationPosition(TabGtk::GetPinnedWidth(), -kTabHOffset); + // Removed animated Tabs are never selected. double start_width = start_unselected_width_; // Make sure target_width is at least abs(kTabHOffset), otherwise if @@ -292,10 +345,12 @@ class RemoveTabAnimation : public TabStripGtk::TabAnimation { double target_width = std::max(abs(kTabHOffset), TabGtk::GetMinimumUnselectedSize().width() + kTabHOffset); - double delta = start_width - target_width; - return start_width - (delta * animation_.GetCurrentValue()); + return AnimationPosition(start_width, target_width); } + if (tab->is_pinned()) + return TabGtk::GetPinnedWidth(); + if (tabstrip_->available_width_for_tabs_ != -1 && index_ != tabstrip_->GetTabCount() - 1) { return TabStripGtk::TabAnimation::GetWidthForTab(index); @@ -386,7 +441,9 @@ class ResizeLayoutAnimation : public TabStripGtk::TabAnimation { explicit ResizeLayoutAnimation(TabStripGtk* 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() {} @@ -404,13 +461,15 @@ class ResizeLayoutAnimation : public TabStripGtk::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()); - } + TabGtk* tab = tabstrip_->GetTabAt(index); - double delta = end_unselected_width_ - start_unselected_width_; - return start_unselected_width_ + (delta * animation_.GetCurrentValue()); + if (tab->is_pinned()) + return TabGtk::GetPinnedWidth(); + + if (tab->IsSelected()) + return AnimationPosition(start_selected_width_, end_selected_width_); + + return AnimationPosition(start_unselected_width_, end_unselected_width_); } private: @@ -421,10 +480,12 @@ class ResizeLayoutAnimation : public TabStripGtk::TabAnimation { void InitStartState() { for (int i = 0; i < tabstrip_->GetTabCount(); ++i) { TabGtk* 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->is_pinned()) { + if (current_tab->IsSelected()) { + start_selected_width_ = current_tab->width(); + } else { + start_unselected_width_ = current_tab->width(); + } } } } @@ -433,6 +494,162 @@ class ResizeLayoutAnimation : public TabStripGtk::TabAnimation { }; //////////////////////////////////////////////////////////////////////////////// + +// Handles a tabs pinned state changing while the tab does not change position +// in the model. +class PinnedTabAnimation : public TabStripGtk::TabAnimation { + public: + explicit PinnedTabAnimation(TabStripGtk* 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)->is_pinned()) + start_pinned_count--; + else + start_pinned_count++; + GenerateStartAndEndWidths(tab_count, tab_count, start_pinned_count, + end_pinned_count); + } + + protected: + // Overridden from TabStripGtk::TabAnimation: + virtual int GetDuration() const { + return kPinnedTabAnimationDurationMs; + } + + virtual double GetWidthForTab(int index) const { + TabGtk* tab = tabstrip_->GetTabAt(index); + + if (index == index_) { + if (tab->is_pinned()) { + return AnimationPosition( + start_selected_width_, + static_cast<double>(TabGtk::GetPinnedWidth())); + } else { + return AnimationPosition(static_cast<double>(TabGtk::GetPinnedWidth()), + end_selected_width_); + } + } else if (tab->is_pinned()) { + return TabGtk::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 PinAndMoveAnimation : public TabStripGtk::TabAnimation { + public: + explicit PinAndMoveAnimation(TabStripGtk* 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)->is_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); + } + + // 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()); + tabstrip_->SetTabBounds(tab_, tab_bounds); + } + + virtual void AnimationEnded(const Animation* animation) { + tabstrip_->resize_layout_scheduled_ = false; + TabStripGtk::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(TabGtk::GetPinnedWidth() + kTabHOffset, 0); + } + } + return 0; + } + + protected: + // Overridden from TabStripGtk::TabAnimation: + virtual int GetDuration() const { return kReorderAnimationDurationMs; } + + virtual double GetWidthForTab(int index) const { + TabGtk* tab = tabstrip_->GetTabAt(index); + + if (index == to_index_) + return AnimationPosition(0, target_bounds_.width()); + + if (tab->is_pinned()) + return TabGtk::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. + TabGtk* 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); +}; + +//////////////////////////////////////////////////////////////////////////////// // TabStripGtk, public: TabStripGtk::TabStripGtk(TabStripModel* model) @@ -537,7 +754,8 @@ void TabStripGtk::Layout() { const gfx::Rect& bounds = tab_data_.at(i).ideal_bounds; TabGtk* tab = GetTabAt(i); SetTabBounds(tab, bounds); - tab_right = bounds.right() + kTabHOffset; + tab_right = bounds.right(); + tab_right += GetTabHOffset(i + 1); } LayoutNewTabButton(static_cast<double>(tab_right), current_unselected_width_); @@ -685,16 +903,11 @@ void TabStripGtk::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)); if (gtk_widget_get_parent(tab->widget()) != tabstrip_.get()) gtk_fixed_put(GTK_FIXED(tabstrip_.get()), tab->widget(), 0, 0); @@ -742,13 +955,20 @@ void TabStripGtk::TabSelectedAt(TabContents* old_contents, void TabStripGtk::TabMoved(TabContents* contents, int from_index, - int to_index) { + int to_index, + bool pinned_state_changed) { + gfx::Rect start_bounds = GetIdealBounds(from_index); TabGtk* 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 TabStripGtk::TabChangedAt(TabContents* contents, int index, @@ -761,6 +981,11 @@ void TabStripGtk::TabChangedAt(TabContents* contents, int index, gtk_widget_queue_draw(tabstrip_.get()); } +void TabStripGtk::TabPinnedStateChanged(TabContents* contents, int index) { + GetTabAt(index)->set_pinned(model_->IsTabPinned(index)); + StartPinnedTabAnimation(index); +} + //////////////////////////////////////////////////////////////////////////////// // TabStripGtk, TabGtk::TabDelegate implementation: @@ -895,6 +1120,17 @@ int TabStripGtk::GetTabCount() const { return static_cast<int>(tab_data_.size()); } +int TabStripGtk::GetPinnedTabCount() const { + int pinned_count = 0; + for (size_t i = 0; i < tab_data_.size(); ++i) { + if (tab_data_[i].tab->is_pinned()) + pinned_count++; + else + return pinned_count; + } + return pinned_count; +} + int TabStripGtk::GetAvailableWidthForTabs(TabGtk* last_tab) const { return last_tab->x() + last_tab->width(); } @@ -950,7 +1186,7 @@ void TabStripGtk::HandleGlobalMouseMoveEvent() { void TabStripGtk::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; @@ -962,14 +1198,16 @@ void TabStripGtk::GenerateIdealBounds() { for (int i = 0; i < tab_count; ++i) { TabGtk* tab = GetTabAt(i); double tab_width = unselected; - if (tab->IsSelected()) + if (tab->is_pinned()) + tab_width = TabGtk::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); } } @@ -1005,17 +1243,21 @@ void TabStripGtk::LayoutTabOverviewButton() { #endif void TabStripGtk::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 = TabGtk::GetMinimumUnselectedSize().width(); const double min_selected_width = TabGtk::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; } @@ -1041,6 +1283,18 @@ void TabStripGtk::GetDesiredTabWidths(int tab_count, available_width = available_width_for_tabs_; } + if (pinned_tab_count > 0) { + available_width -= (pinned_tab_count * TabGtk::GetPinnedWidth() - + std::max(0, pinned_tab_count - 1) * kTabHOffset); + tab_count -= pinned_tab_count; + if (tab_count == 0) { + *selected_width = *unselected_width = TabGtk::GetStandardSize().width(); + return; + } + // For spacing between last pinned tab and normal tab. + available_width -= kTabHOffset; + } + // 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. @@ -1078,6 +1332,10 @@ void TabStripGtk::GetDesiredTabWidths(int tab_count, } } +int TabStripGtk::GetTabHOffset(int tab_index) { + return kTabHOffset; +} + int TabStripGtk::tab_start_x() const { return 0; } @@ -1090,9 +1348,15 @@ void TabStripGtk::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; + } + TabGtk* first_tab = GetTabAt(pinned_tab_count); double unselected, selected; - GetDesiredTabWidths(GetTabCount(), &unselected, &selected); - TabGtk* first_tab = GetTabAt(0); + GetDesiredTabWidths(GetTabCount(), pinned_tab_count, &unselected, &selected); int w = Round(first_tab->IsSelected() ? selected : unselected); // We only want to run the animation if we're not already at the desired @@ -1139,6 +1403,7 @@ gfx::Rect TabStripGtk::GetDropBounds(int drop_index, int center_x; if (drop_index < GetTabCount()) { TabGtk* tab = GetTabAt(drop_index); + // TODO(sky): update these for pinned tabs. if (drop_before) center_x = tab->x() - (kTabHOffset / 2); else @@ -1368,6 +1633,8 @@ void TabStripGtk::AnimationLayout(double unselected_width) { double tab_x = tab_start_x(); 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); @@ -1375,7 +1642,7 @@ void TabStripGtk::AnimationLayout(double unselected_width) { gfx::Rect bounds(rounded_tab_x, 0, Round(end_of_tab) - rounded_tab_x, tab_height); SetTabBounds(tab, bounds); - tab_x = end_of_tab + kTabHOffset; + tab_x = end_of_tab + GetTabHOffset(i + 1); } LayoutNewTabButton(tab_x, unselected_width); #if defined(OS_CHROMEOS) @@ -1422,6 +1689,23 @@ void TabStripGtk::StartResizeLayoutAnimation() { active_animation_->Start(); } +void TabStripGtk::StartPinnedTabAnimation(int index) { + if (active_animation_.get()) + active_animation_->Stop(); + active_animation_.reset(new PinnedTabAnimation(this, index)); + active_animation_->Start(); +} + +void TabStripGtk::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 TabStripGtk::CanUpdateDisplay() { // Don't bother laying out/painting when we're closing all tabs. if (model_->closing_all()) { diff --git a/chrome/browser/gtk/tabs/tab_strip_gtk.h b/chrome/browser/gtk/tabs/tab_strip_gtk.h index 3f48263..91b83f0 100644 --- a/chrome/browser/gtk/tabs/tab_strip_gtk.h +++ b/chrome/browser/gtk/tabs/tab_strip_gtk.h @@ -97,9 +97,13 @@ class TabStripGtk : public TabStripModelObserver, TabContents* contents, int index, bool user_gesture); - virtual void TabMoved(TabContents* contents, int from_index, int to_index); + virtual void TabMoved(TabContents* contents, + int from_index, + int to_index, + bool pinned_state_changed); virtual void TabChangedAt(TabContents* contents, int index, bool loading_only); + virtual void TabPinnedStateChanged(TabContents* contents, int index); // TabGtk::TabDelegate implementation: virtual bool IsTabSelected(const TabGtk* tab) const; @@ -128,6 +132,8 @@ class TabStripGtk : public TabStripModelObserver, friend class InsertTabAnimation; friend class RemoveTabAnimation; friend class MoveTabAnimation; + friend class PinAndMoveAnimation; + friend class PinnedTabAnimation; friend class ResizeLayoutAnimation; friend class TabAnimation; @@ -231,6 +237,9 @@ class TabStripGtk : public TabStripModelObserver, // Gets the number of Tabs in the collection. int GetTabCount() const; + // Returns the number of pinned tabs. + int GetPinnedTabCount() const; + // Retrieves the Tab at the specified index. TabGtk* GetTabAt(int index) const; @@ -242,10 +251,16 @@ class TabStripGtk : public TabStripModelObserver, // 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); + // Returns the x-coordinate tabs start from. int tab_start_x() const; @@ -324,8 +339,11 @@ class TabStripGtk : public TabStripModelObserver, // Starts various types of TabStrip animations. void StartInsertTabAnimation(int index); void StartRemoveTabAnimation(int index, TabContents* contents); - void StartResizeLayoutAnimation(); void StartMoveTabAnimation(int from_index, int to_index); + void StartResizeLayoutAnimation(); + 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 |