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