summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorsky@chromium.org <sky@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2009-07-13 20:47:12 +0000
committersky@chromium.org <sky@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2009-07-13 20:47:12 +0000
commit72d1a935f1adb1023d32c7c1906dbf64fdb02705 (patch)
tree0b5c6e4746a5037c6ba0066d26f0e2898dfff0ae
parentf30f5e4d4d551ff6f95335599acf2f089aafc816 (diff)
downloadchromium_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.grd3
-rw-r--r--chrome/browser/gtk/tabs/dragged_tab_controller_gtk.cc126
-rw-r--r--chrome/browser/gtk/tabs/dragged_tab_controller_gtk.h21
-rw-r--r--chrome/browser/gtk/tabs/dragged_tab_gtk.cc29
-rw-r--r--chrome/browser/gtk/tabs/dragged_tab_gtk.h28
-rw-r--r--chrome/browser/gtk/tabs/tab_gtk.cc13
-rw-r--r--chrome/browser/gtk/tabs/tab_renderer_gtk.cc184
-rw-r--r--chrome/browser/gtk/tabs/tab_renderer_gtk.h21
-rw-r--r--chrome/browser/gtk/tabs/tab_strip_gtk.cc380
-rw-r--r--chrome/browser/gtk/tabs/tab_strip_gtk.h22
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