summaryrefslogtreecommitdiffstats
path: root/chrome/browser/views/tabs
diff options
context:
space:
mode:
Diffstat (limited to 'chrome/browser/views/tabs')
-rw-r--r--chrome/browser/views/tabs/browser_tab_strip.cc148
-rw-r--r--chrome/browser/views/tabs/browser_tab_strip.h58
-rw-r--r--chrome/browser/views/tabs/tab_2.cc119
-rw-r--r--chrome/browser/views/tabs/tab_2.h114
-rw-r--r--chrome/browser/views/tabs/tab_strip_2.cc406
-rw-r--r--chrome/browser/views/tabs/tab_strip_2.h163
6 files changed, 1008 insertions, 0 deletions
diff --git a/chrome/browser/views/tabs/browser_tab_strip.cc b/chrome/browser/views/tabs/browser_tab_strip.cc
new file mode 100644
index 0000000..4a3be6b
--- /dev/null
+++ b/chrome/browser/views/tabs/browser_tab_strip.cc
@@ -0,0 +1,148 @@
+// Copyright (c) 2009 The Chromium Authors. All rights reserved. Use of this
+// source code is governed by a BSD-style license that can be found in the
+// LICENSE file.
+
+#include "chrome/browser/views/tabs/browser_tab_strip.h"
+
+#include "chrome/browser/tab_contents/tab_contents.h"
+
+namespace {
+
+////////////////////////////////////////////////////////////////////////////////
+// RemovingTabModel
+// After a tab is removed from the TabStrip2's model, the model can no longer
+// provide information about the tab needed to update the display, however the
+// TabStrip2 continues to display the tab until it animates out of existence.
+// During this period, this model serves as a dummy. It is created and assigned
+// to the tab when the tab is removed from the model and owned by the Tab2.
+
+class RemovingTabModel : public Tab2Model {
+ public:
+ explicit RemovingTabModel(TabContents* source)
+ : title_(source->GetTitle()) {
+ }
+
+ // Overridden from Tab2Model:
+ virtual string16 GetTitle(Tab2* tab) const { return title_; }
+ virtual bool IsSelected(Tab2* tab) const { return false; }
+ virtual void SelectTab(Tab2* tab) { NOTREACHED(); }
+ virtual void CaptureDragInfo(Tab2* tab,
+ const views::MouseEvent& drag_event) {
+ NOTREACHED();
+ }
+ virtual bool DragTab(Tab2* tab, const views::MouseEvent& drag_event) {
+ NOTREACHED();
+ return false;
+ }
+ virtual void DragEnded(Tab2* tab) {
+ NOTREACHED();
+ }
+ virtual views::AnimatorDelegate* AsAnimatorDelegate() {
+ NOTREACHED();
+ return NULL;
+ }
+
+ private:
+ string16 title_;
+
+ DISALLOW_COPY_AND_ASSIGN(RemovingTabModel);
+};
+
+} // namespace
+
+////////////////////////////////////////////////////////////////////////////////
+// BrowserTabStrip, public:
+
+BrowserTabStrip::BrowserTabStrip(TabStripModel* model)
+ : TabStrip2(this),
+ model_(model) {
+ model_->AddObserver(this);
+}
+
+BrowserTabStrip::~BrowserTabStrip() {
+ model_->RemoveObserver(this);
+}
+
+TabContents* BrowserTabStrip::DetachTab(int index) {
+ TabContents* contents = model_->GetTabContentsAt(index);
+ model_->DetachTabContentsAt(index);
+ return contents;
+}
+
+void BrowserTabStrip::AttachTab(TabContents* contents,
+ const gfx::Point& screen_point,
+ const gfx::Rect& tab_screen_bounds) {
+ gfx::Point tabstrip_point(screen_point);
+
+ gfx::Point screen_origin;
+ View::ConvertPointToScreen(this, &screen_origin);
+ tabstrip_point.Offset(-screen_origin.x(), -screen_origin.y());
+
+ int index = GetInsertionIndexForPoint(tabstrip_point);
+ model_->InsertTabContentsAt(index, contents, true, false);
+
+ gfx::Point origin(tab_screen_bounds.origin());
+ View::ConvertPointToView(NULL, this, &origin);
+ ResumeDraggingTab(index, gfx::Rect(origin, tab_screen_bounds.size()));
+ // TODO(beng): post task to continue dragging now.
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// BrowserTabStrip, TabStripModelObserver overrides:
+
+void BrowserTabStrip::TabInsertedAt(TabContents* contents,
+ int index,
+ bool foreground) {
+ AddTabAt(index);
+}
+
+void BrowserTabStrip::TabDetachedAt(TabContents* contents, int index) {
+ RemoveTabAt(index, new RemovingTabModel(contents));
+}
+
+void BrowserTabStrip::TabSelectedAt(TabContents* old_contents,
+ TabContents* contents,
+ int index,
+ bool user_gesture) {
+ TabStrip2::SelectTabAt(index);
+}
+
+void BrowserTabStrip::TabMoved(TabContents* contents,
+ int from_index,
+ int to_index) {
+ TabStrip2::MoveTabAt(from_index, to_index);
+}
+
+void BrowserTabStrip::TabChangedAt(TabContents* contents, int index) {
+ // TODO
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// BrowserTabStrip, TabStrip2Model overrides:
+
+string16 BrowserTabStrip::GetTitle(int index) const {
+ return model_->GetTabContentsAt(index)->GetTitle();
+}
+
+bool BrowserTabStrip::IsSelected(int index) const {
+ return model_->selected_index() == index;
+}
+
+void BrowserTabStrip::SelectTabAt(int index) {
+ model_->SelectTabContentsAt(index, true);
+}
+
+bool BrowserTabStrip::CanDragTabs() const {
+ return model_->delegate()->GetDragActions() != 0;
+}
+
+void BrowserTabStrip::MoveTabAt(int index, int to_index) {
+ model_->MoveTabContentsAt(index, to_index, true);
+}
+
+void BrowserTabStrip::DetachTabAt(int index, const gfx::Rect& window_bounds,
+ const gfx::Rect& tab_bounds) {
+ TabContents* contents = DetachTab(index);
+ model_->delegate()->ContinueDraggingDetachedTab(contents, window_bounds,
+ tab_bounds);
+}
diff --git a/chrome/browser/views/tabs/browser_tab_strip.h b/chrome/browser/views/tabs/browser_tab_strip.h
new file mode 100644
index 0000000..f88de2d
--- /dev/null
+++ b/chrome/browser/views/tabs/browser_tab_strip.h
@@ -0,0 +1,58 @@
+// Copyright (c) 2009 The Chromium Authors. All rights reserved. Use of this
+// source code is governed by a BSD-style license that can be found in the
+// LICENSE file.
+
+#ifndef CHROME_BROWSER_VIEWS_TABS_BROWSER_TAB_STRIP_H_
+#define CHROME_BROWSER_VIEWS_TABS_BROWSER_TAB_STRIP_H_
+
+#include "chrome/browser/views/tabs/tab_strip_2.h"
+#include "chrome/browser/tabs/tab_strip_model.h"
+
+// A specialization fo TabStrip2 for the browser window.
+//
+// TODO(beng): This shouldn't be a subclass of TabStrip2, rather it should own
+// one.
+class BrowserTabStrip : public TabStrip2,
+ public TabStrip2Model,
+ public TabStripModelObserver {
+ public:
+ explicit BrowserTabStrip(TabStripModel* model);
+ virtual ~BrowserTabStrip();
+
+ // Detaches the tab at the specified index.
+ TabContents* DetachTab(int index);
+
+ // Attaches the specified TabContents at the appropriate position given the
+ // mouse cursor at the specified screen position.
+ void AttachTab(TabContents* contents, const gfx::Point& screen_point,
+ const gfx::Rect& tab_screen_bounds);
+
+ // Overridden from TabStripModelObserver:
+ virtual void TabInsertedAt(TabContents* contents,
+ int index,
+ bool foreground);
+ virtual void TabDetachedAt(TabContents* contents, int index);
+ virtual void TabSelectedAt(TabContents* old_contents,
+ TabContents* contents,
+ int index,
+ bool user_gesture);
+ virtual void TabMoved(TabContents* contents, int from_index, int to_index);
+ virtual void TabChangedAt(TabContents* contents, int index);
+
+ // Overridden from TabStrip2Model:
+ virtual string16 GetTitle(int index) const;
+ virtual bool IsSelected(int index) const;
+ virtual void SelectTabAt(int index);
+ virtual bool CanDragTabs() const;
+ virtual void MoveTabAt(int index, int to_index);
+ virtual void DetachTabAt(int index,
+ const gfx::Rect& window_bounds,
+ const gfx::Rect& tab_bounds);
+
+ private:
+ TabStripModel* model_;
+
+ DISALLOW_COPY_AND_ASSIGN(BrowserTabStrip);
+};
+
+#endif // #ifndef CHROME_BROWSER_VIEWS_TABS_BROWSER_TAB_STRIP_H_
diff --git a/chrome/browser/views/tabs/tab_2.cc b/chrome/browser/views/tabs/tab_2.cc
new file mode 100644
index 0000000..a541f3a
--- /dev/null
+++ b/chrome/browser/views/tabs/tab_2.cc
@@ -0,0 +1,119 @@
+// Copyright (c) 2009 The Chromium Authors. All rights reserved. Use of this
+// source code is governed by a BSD-style license that can be found in the
+// LICENSE file.
+
+#include "chrome/browser/views/tabs/tab_2.h"
+
+#include "app/gfx/canvas.h"
+#include "app/gfx/font.h"
+#include "app/gfx/path.h"
+#include "views/animator.h"
+
+static const SkScalar kTabCapWidth = 15;
+static const SkScalar kTabTopCurveWidth = 4;
+static const SkScalar kTabBottomCurveWidth = 3;
+
+////////////////////////////////////////////////////////////////////////////////
+// Tab2, public:
+
+Tab2::Tab2(Tab2Model* model)
+ : model_(model),
+ dragging_(false),
+ removing_(false) {
+}
+
+Tab2::~Tab2() {
+}
+
+void Tab2::SetRemovingModel(Tab2Model* model) {
+ removing_model_.reset(model);
+ model_ = removing_model_.get();
+}
+
+bool Tab2::IsAnimating() const {
+ return animator_.get() && animator_->IsAnimating();
+}
+
+views::Animator* Tab2::GetAnimator() {
+ if (!animator_.get())
+ animator_.reset(new views::Animator(this, model_->AsAnimatorDelegate()));
+ return animator_.get();
+}
+
+// static
+gfx::Size Tab2::GetStandardSize() {
+ return gfx::Size(140, 27);
+}
+
+void Tab2::AddTabShapeToPath(gfx::Path* path) const {
+ SkScalar h = SkIntToScalar(height());
+ SkScalar w = SkIntToScalar(width());
+
+ path->moveTo(0, h);
+
+ // Left end cap.
+ path->lineTo(kTabBottomCurveWidth, h - kTabBottomCurveWidth);
+ path->lineTo(kTabCapWidth - kTabTopCurveWidth, kTabTopCurveWidth);
+ path->lineTo(kTabCapWidth, 0);
+
+ // Connect to the right cap.
+ path->lineTo(w - kTabCapWidth, 0);
+
+ // Right end cap.
+ path->lineTo(w - kTabCapWidth + kTabTopCurveWidth, kTabTopCurveWidth);
+ path->lineTo(w - kTabBottomCurveWidth, h - kTabBottomCurveWidth);
+ path->lineTo(w, h);
+
+ // Close out the path.
+ path->lineTo(0, h);
+ path->close();
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Tab2, views::View overrides:
+
+gfx::Size Tab2::GetPreferredSize() {
+ return gfx::Size();
+}
+
+void Tab2::Layout() {
+}
+
+void Tab2::Paint(gfx::Canvas* canvas) {
+ SkColor color = model_->IsSelected(this) ? SK_ColorRED : SK_ColorGREEN;
+
+ gfx::Path path;
+ AddTabShapeToPath(&path);
+ SkPaint paint;
+ paint.setAntiAlias(true);
+ paint.setColor(color);
+ canvas->drawPath(path, paint);
+
+ // TODO(beng): less ad-hoc
+ canvas->DrawStringInt(model_->GetTitle(this), gfx::Font(), SK_ColorBLACK,
+ 5, 3, 100, 20);
+}
+
+bool Tab2::OnMousePressed(const views::MouseEvent& event) {
+ if (event.IsLeftMouseButton()) {
+ model_->SelectTab(this);
+ model_->CaptureDragInfo(this, event);
+ }
+ return true;
+}
+
+bool Tab2::OnMouseDragged(const views::MouseEvent& event) {
+ dragging_ = true;
+ return model_->DragTab(this, event);
+}
+
+void Tab2::OnMouseReleased(const views::MouseEvent& event, bool canceled) {
+ if (dragging_) {
+ dragging_ = false;
+ model_->DragEnded(this);
+ }
+}
+
+void Tab2::DidChangeBounds(const gfx::Rect& previous,
+ const gfx::Rect& current) {
+}
diff --git a/chrome/browser/views/tabs/tab_2.h b/chrome/browser/views/tabs/tab_2.h
new file mode 100644
index 0000000..f5ae969
--- /dev/null
+++ b/chrome/browser/views/tabs/tab_2.h
@@ -0,0 +1,114 @@
+// Copyright (c) 2009 The Chromium Authors. All rights reserved. Use of this
+// source code is governed by a BSD-style license that can be found in the
+// LICENSE file.
+
+#ifndef CHROME_BROWSER_VIEWS_TABS_TAB_2_H_
+#define CHROME_BROWSER_VIEWS_TABS_TAB_2_H_
+
+#include "base/string16.h"
+#include "views/view.h"
+
+class Tab2;
+
+namespace gfx {
+class Canvas;
+class Path;
+};
+
+namespace views {
+class AnimationContext;
+class Animator;
+class AnimatorDelegate;
+}
+
+// An interface implemented by an object that provides data to the Tab2.
+// The Tab2 sometimes owns the Tab2Model. See |removing_model_| in Tab2.
+class Tab2Model {
+ public:
+ virtual ~Tab2Model() {}
+
+ // Tab2 presentation state.
+ virtual string16 GetTitle(Tab2* tab) const = 0;
+ virtual bool IsSelected(Tab2* tab) const = 0;
+
+ // The Tab2 has been clicked and should become selected.
+ virtual void SelectTab(Tab2* tab) = 0;
+
+ // The mouse has been pressed down on the Tab2, pertinent information for any
+ // drag that might occur should be captured at this time.
+ virtual void CaptureDragInfo(Tab2* tab,
+ const views::MouseEvent& drag_event) = 0;
+
+ // The mouse has been dragged after a press on the Tab2.
+ virtual bool DragTab(Tab2* tab, const views::MouseEvent& drag_event) = 0;
+
+ // The current drag operation has ended.
+ virtual void DragEnded(Tab2* tab) = 0;
+
+ // TODO(beng): get rid of this once animator is on View.
+ virtual views::AnimatorDelegate* AsAnimatorDelegate() = 0;
+};
+
+// A view that represents a Tab in a TabStrip2.
+class Tab2 : public views::View {
+ public:
+ explicit Tab2(Tab2Model* model);
+ virtual ~Tab2();
+
+ bool dragging() const { return dragging_; }
+
+ bool removing() const { return removing_; }
+ void set_removing(bool removing) { removing_ = removing; }
+
+ // Assigns and takes ownership of a model object to be used when painting this
+ // Tab2 after the underlying data object has been removed from TabStrip2's
+ // model.
+ void SetRemovingModel(Tab2Model* model);
+
+ // Returns true if the Tab2 is being animated.
+ bool IsAnimating() const;
+
+ // Returns the Tab2's animator, creating one if necessary.
+ // TODO(beng): consider moving to views::View.
+ views::Animator* GetAnimator();
+
+ // Returns the ideal size of the Tab2.
+ static gfx::Size GetStandardSize();
+
+ // Adds the shape of the tab to the specified path. Used to create a clipped
+ // window during detached window dragging operations.
+ void AddTabShapeToPath(gfx::Path* path) const;
+
+ // Overridden from views::View:
+ virtual gfx::Size GetPreferredSize();
+ virtual void Layout();
+ virtual void Paint(gfx::Canvas* canvas);
+ virtual bool OnMousePressed(const views::MouseEvent& event);
+ virtual bool OnMouseDragged(const views::MouseEvent& event);
+ virtual void OnMouseReleased(const views::MouseEvent& event,
+ bool canceled);
+ virtual void DidChangeBounds(const gfx::Rect& previous,
+ const gfx::Rect& current);
+
+ private:
+ Tab2Model* model_;
+
+ // True if the Tab2 is being dragged currently.
+ bool dragging_;
+
+ // True if the Tab2 represents an object removed from its containing
+ // TabStrip2's model, and is currently being animated closed.
+ bool removing_;
+
+ // Our animator.
+ scoped_ptr<views::Animator> animator_;
+
+ // A dummy model to use for painting the tab after it's been removed from the
+ // TabStrip2's model but while it's still visible in the presentation (being
+ // animated out of existence).
+ scoped_ptr<Tab2Model> removing_model_;
+
+ DISALLOW_COPY_AND_ASSIGN(Tab2);
+};
+
+#endif // #ifndef CHROME_BROWSER_VIEWS_TABS_TAB_2_H_
diff --git a/chrome/browser/views/tabs/tab_strip_2.cc b/chrome/browser/views/tabs/tab_strip_2.cc
new file mode 100644
index 0000000..579882c
--- /dev/null
+++ b/chrome/browser/views/tabs/tab_strip_2.cc
@@ -0,0 +1,406 @@
+// Copyright (c) 2009 The Chromium Authors. All rights reserved. Use of this
+// source code is governed by a BSD-style license that can be found in the
+// LICENSE file.
+
+#include "chrome/browser/views/tabs/tab_strip_2.h"
+
+#include "app/gfx/canvas.h"
+#include "app/slide_animation.h"
+#include "app/win_util.h"
+#include "base/command_line.h"
+#include "base/message_loop.h"
+#include "chrome/common/chrome_switches.h"
+#include "views/animator.h"
+#include "views/screen.h"
+#include "views/widget/widget.h"
+#include "views/window/non_client_view.h"
+#include "views/window/window.h"
+
+static const int kHorizontalMoveThreshold = 16; // pixels
+
+////////////////////////////////////////////////////////////////////////////////
+// TabStrip2, public:
+
+TabStrip2::TabStrip2(TabStrip2Model* model)
+ : model_(model),
+ last_move_screen_x_(0),
+ detach_factory_(this),
+ drag_start_factory_(this) {
+}
+
+TabStrip2::~TabStrip2() {
+}
+
+// static
+bool TabStrip2::Enabled() {
+ return CommandLine::ForCurrentProcess()->HasSwitch(
+ switches::kEnableTabtastic2);
+}
+
+void TabStrip2::AddTabAt(int index) {
+ Tab2* tab = new Tab2(this);
+ int insertion_index = GetInternalIndex(index);
+ tabs_.insert(tabs_.begin() + insertion_index, tab);
+ AddChildView(insertion_index, tab);
+ LayoutImpl(LS_TAB_ADD);
+}
+
+void TabStrip2::RemoveTabAt(int index, Tab2Model* removing_model) {
+ Tab2* tab = GetTabAt(GetInternalIndex(index));
+
+ DCHECK(!tab->removing());
+ tab->set_removing(true);
+
+ DCHECK(removing_model);
+ tab->SetRemovingModel(removing_model);
+
+ LayoutImpl(LS_TAB_REMOVE);
+}
+
+void TabStrip2::SelectTabAt(int index) {
+ LayoutImpl(LS_TAB_SELECT);
+ SchedulePaint();
+}
+
+void TabStrip2::MoveTabAt(int index, int to_index) {
+ int from_index = GetInternalIndex(index);
+ Tab2* tab = GetTabAt(from_index);
+ tabs_.erase(tabs_.begin() + from_index);
+ tabs_.insert(tabs_.begin() + GetInternalIndex(to_index), tab);
+ LayoutImpl(LS_TAB_DRAG_REORDER);
+}
+
+int TabStrip2::GetTabCount() const {
+ return tabs_.size();
+}
+
+Tab2* TabStrip2::GetTabAt(int index) const {
+ return tabs_.at(index);
+}
+
+int TabStrip2::GetTabIndex(Tab2* tab) const {
+ std::vector<Tab2*>::const_iterator it = find(tabs_.begin(), tabs_.end(), tab);
+ if (it != tabs_.end())
+ return it - tabs_.begin();
+ return -1;
+}
+
+int TabStrip2::GetInsertionIndexForPoint(const gfx::Point& point) const {
+ int tab_count = GetTabCount();
+ for (int i = 0; i < tab_count; ++i) {
+ if (GetTabAt(i)->removing())
+ continue;
+ gfx::Rect tab_bounds = GetTabAt(i)->bounds();
+ gfx::Rect tab_left_half = tab_bounds;
+ tab_left_half.set_width(tab_left_half.width() / 2);
+ if (point.x() >= tab_left_half.x() && point.x() <= tab_left_half.right())
+ return i;
+ gfx::Rect tab_right_half = tab_bounds;
+ tab_right_half.set_x(tab_right_half.width() / 2);
+ tab_right_half.set_width(tab_right_half.x());
+ if (point.x() > tab_right_half.x() && point.x() <= tab_right_half.right())
+ if (tab_right_half.Contains(point))
+ return i + 1;
+ }
+ return tab_count;
+}
+
+gfx::Rect TabStrip2::GetDraggedTabScreenBounds(const gfx::Point& screen_point) {
+ gfx::Point tab_screen_origin(screen_point);
+ tab_screen_origin.Offset(mouse_tab_offset_.x(), mouse_tab_offset_.y());
+ return gfx::Rect(tab_screen_origin, GetTabAt(0)->bounds().size());
+}
+
+void TabStrip2::SetDraggedTabBounds(int index, const gfx::Rect& tab_bounds) {
+ // This function should only ever be called goats
+ Tab2* dragged_tab = GetTabAt(index);
+ dragged_tab->SetBounds(tab_bounds);
+ SchedulePaint();
+}
+
+void TabStrip2::SendDraggedTabHome() {
+ LayoutImpl(LS_TAB_DRAG_REORDER);
+}
+
+void TabStrip2::ResumeDraggingTab(int index, const gfx::Rect& tab_bounds) {
+ MessageLoop::current()->PostTask(FROM_HERE,
+ drag_start_factory_.NewRunnableMethod(&TabStrip2::StartDragTabImpl, index,
+ tab_bounds));
+}
+
+// static
+bool TabStrip2::IsDragRearrange(TabStrip2* tabstrip,
+ const gfx::Point& screen_point) {
+ gfx::Point origin;
+ View::ConvertPointToScreen(tabstrip, &origin);
+ gfx::Rect tabstrip_bounds_in_screen_coords(origin, tabstrip->bounds().size());
+ if (tabstrip_bounds_in_screen_coords.Contains(screen_point))
+ return true;
+
+ // The tab is only detached if the tab is moved outside the bounds of the
+ // TabStrip to the left or right, or a certain distance above or below the
+ // TabStrip defined by the vertical detach magnetism below. This is to
+ // prevent accidental detaches when rearranging horizontally.
+ static const int kVerticalDetachMagnetism = 45;
+
+ bool rearrange = true;
+ if (screen_point.x() < tabstrip_bounds_in_screen_coords.right() &&
+ screen_point.x() >= tabstrip_bounds_in_screen_coords.x()) {
+ int lower_threshold =
+ tabstrip_bounds_in_screen_coords.bottom() + kVerticalDetachMagnetism;
+ int upper_threshold =
+ tabstrip_bounds_in_screen_coords.y() - kVerticalDetachMagnetism;
+ return screen_point.y() >= upper_threshold &&
+ screen_point.y() <= lower_threshold;
+ }
+ return false;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// TabStrip2, Tab2Model implementation:
+
+string16 TabStrip2::GetTitle(Tab2* tab) const {
+ return model_->GetTitle(GetTabIndex(tab));
+}
+
+bool TabStrip2::IsSelected(Tab2* tab) const {
+ return model_->IsSelected(GetTabIndex(tab));
+}
+
+void TabStrip2::SelectTab(Tab2* tab) {
+ model_->SelectTabAt(GetTabIndex(tab));
+}
+
+void TabStrip2::CaptureDragInfo(Tab2* tab,
+ const views::MouseEvent& drag_event) {
+ mouse_tab_offset_ = drag_event.location();
+}
+
+bool TabStrip2::DragTab(Tab2* tab, const views::MouseEvent& drag_event) {
+ if (!model_->CanDragTabs())
+ return false;
+
+ int tab_x = tab->x() + drag_event.location().x() - mouse_tab_offset_.x();
+ if (tab_x < 0)
+ tab_x = 0;
+ if ((tab_x + tab->width()) > bounds().right())
+ tab_x = bounds().right() - tab_x - tab->width();
+ tab->SetBounds(tab_x, tab->y(), tab->width(), tab->height());
+ SchedulePaint();
+
+ int tab_index = GetTabIndex(tab);
+ int dest_index = tab_index;
+
+ Tab2* next_tab = NULL;
+ Tab2* prev_tab = NULL;
+ int next_tab_index = tab_index + 1;
+ if (next_tab_index < GetTabCount())
+ next_tab = GetTabAt(next_tab_index);
+ int prev_tab_index = tab_index - 1;
+ if (prev_tab_index >= 0)
+ prev_tab = GetTabAt(prev_tab_index);
+
+ if (next_tab) {
+ int next_tab_middle_x = next_tab->x() + next_tab->bounds().width() / 2;
+ if (!next_tab->IsAnimating() && tab->bounds().right() > next_tab_middle_x)
+ ++dest_index;
+ }
+ if (prev_tab) {
+ int prev_tab_middle_x = prev_tab->x() + prev_tab->bounds().width() / 2;
+ if (!prev_tab->IsAnimating() && tab->bounds().x() < prev_tab_middle_x)
+ --dest_index;
+ }
+
+ gfx::Point screen_point = views::Screen::GetCursorScreenPoint();
+ if (IsDragRearrange(this, screen_point)) {
+ if (abs(screen_point.x() - last_move_screen_x_) >
+ kHorizontalMoveThreshold) {
+ if (dest_index != tab_index) {
+ last_move_screen_x_ = screen_point.x();
+ model_->MoveTabAt(tab_index, dest_index);
+ }
+ }
+ } else {
+ // We're going to detach. We need to release mouse capture so that further
+ // mouse events will be sent to the appropriate window (the detached window)
+ // and so that we don't recursively create nested message loops (dragging
+ // is done by windows in a nested message loop).
+ ReleaseCapture();
+ MessageLoop::current()->PostTask(FROM_HERE,
+ detach_factory_.NewRunnableMethod(&TabStrip2::DragDetachTabImpl,
+ tab, tab_index));
+ }
+ return true;
+}
+
+void TabStrip2::DragEnded(Tab2* tab) {
+ LayoutImpl(LS_TAB_DRAG_NORMALIZE);
+}
+
+views::AnimatorDelegate* TabStrip2::AsAnimatorDelegate() {
+ return this;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// TabStrip2, views::View overrides:
+
+gfx::Size TabStrip2::GetPreferredSize() {
+ return gfx::Size(0, 27);
+}
+
+void TabStrip2::Layout() {
+ LayoutImpl(LS_OTHER);
+}
+
+void TabStrip2::Paint(gfx::Canvas* canvas) {
+ canvas->FillRectInt(SK_ColorBLUE, 0, 0, width(), height());
+}
+
+void TabStrip2::PaintChildren(gfx::Canvas* canvas) {
+ // Paint the tabs in reverse order, so they stack to the left.
+ Tab2* selected_tab = NULL;
+ for (int i = GetTabCount() - 1; i >= 0; --i) {
+ Tab2* tab = GetTabAt(i);
+ // We must ask the _Tab's_ model, not ourselves, because in some situations
+ // the model will be different to this object, e.g. when a Tab is being
+ // removed after its TabContents has been destroyed.
+ if (!IsSelected(tab)) {
+ tab->ProcessPaint(canvas);
+ } else {
+ selected_tab = tab;
+ }
+ }
+
+ if (GetWindow()->GetNonClientView()->UseNativeFrame()) {
+ // Make sure unselected tabs are somewhat transparent.
+ SkPaint paint;
+ paint.setColor(SkColorSetARGB(200, 255, 255, 255));
+ paint.setXfermodeMode(SkXfermode::kDstIn_Mode);
+ paint.setStyle(SkPaint::kFill_Style);
+ canvas->FillRectInt(
+ 0, 0, width(),
+ height() - 2, // Visible region that overlaps the toolbar.
+ paint);
+ }
+
+ // Paint the selected tab last, so it overlaps all the others.
+ if (selected_tab)
+ selected_tab->ProcessPaint(canvas);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// TabStrip2, views::AnimatorDelegate implementation:
+
+views::View* TabStrip2::GetClampedView(views::View* host) {
+ int tab_count = GetTabCount();
+ for (int i = 0; i < tab_count; ++i) {
+ Tab2* tab = GetTabAt(i);
+ if (tab == host && i > 0)
+ return GetTabAt(i - 1);
+ }
+ return NULL;
+}
+
+void TabStrip2::AnimationCompletedForHost(View* host) {
+ Tab2* tab = static_cast<Tab2*>(host);
+ if (tab->removing()) {
+ tabs_.erase(find(tabs_.begin(), tabs_.end(), tab));
+ RemoveChildView(tab);
+ delete tab;
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// TabStrip2, private:
+
+int TabStrip2::GetAnimateFlagsForLayoutSource(LayoutSource source) const {
+ switch (source) {
+ case LS_TAB_ADD:
+ case LS_TAB_SELECT:
+ case LS_TAB_REMOVE:
+ return views::Animator::ANIMATE_WIDTH | views::Animator::ANIMATE_X |
+ views::Animator::ANIMATE_CLAMP;
+ case LS_TAB_DRAG_REORDER:
+ case LS_TAB_DRAG_NORMALIZE:
+ return views::Animator::ANIMATE_X;
+ }
+ DCHECK(source == LS_OTHER);
+ return views::Animator::ANIMATE_NONE;
+}
+
+void TabStrip2::LayoutImpl(LayoutSource source) {
+ int child_count = GetTabCount();
+ if (child_count > 0) {
+ int child_width = width() / child_count;
+ child_width = std::min(child_width, Tab2::GetStandardSize().width());
+
+ int animate_flags = GetAnimateFlagsForLayoutSource(source);
+ int removing_count = 0;
+ for (int i = 0; i < child_count; ++i) {
+ Tab2* tab = GetTabAt(i);
+ if (tab->removing())
+ ++removing_count;
+ if (!tab->dragging()) {
+ int tab_x = i * child_width - removing_count * child_width;
+ int tab_width = tab->removing() ? 0 : child_width;
+ gfx::Rect new_bounds(tab_x, 0, tab_width, height());
+
+ // Tabs that are currently being removed can have their bounds reset
+ // when another tab in the tabstrip is removed before their remove
+ // animation completes. Before they are given a new target bounds to
+ // animate to, we need to unset the removing property so that they are
+ // not pre-emptively deleted.
+ bool removing = tab->removing();
+ tab->set_removing(false);
+ tab->GetAnimator()->AnimateToBounds(new_bounds, animate_flags);
+ // Now restore the removing property.
+ tab->set_removing(removing);
+ }
+ }
+ }
+}
+
+void TabStrip2::DragDetachTabImpl(Tab2* tab, int index) {
+ gfx::Rect tab_bounds = tab->bounds();
+
+ // Determine the origin of the new window. We start with the current mouse
+ // position:
+ gfx::Point new_window_origin(views::Screen::GetCursorScreenPoint());
+ // Subtract the offset of the mouse pointer from the tab top left when the
+ // drag action began.
+ new_window_origin.Offset(-mouse_tab_offset_.x(), -mouse_tab_offset_.y());
+ // Subtract the offset of the tab's current position from the window.
+ gfx::Point tab_window_origin;
+ View::ConvertPointToWidget(tab, &tab_window_origin);
+ new_window_origin.Offset(-tab_window_origin.x(), -tab_window_origin.y());
+
+ // The new window is created with the same size as the source window but at
+ // the origin calculated above.
+ gfx::Rect new_window_bounds = GetWindow()->GetBounds();
+ new_window_bounds.set_origin(new_window_origin);
+
+ model_->DetachTabAt(index, new_window_bounds, tab_bounds);
+}
+
+void TabStrip2::StartDragTabImpl(int index, const gfx::Rect& tab_bounds) {
+ SetDraggedTabBounds(index, tab_bounds);
+ gfx::Rect tab_local_bounds(tab_bounds);
+ tab_local_bounds.set_origin(gfx::Point());
+ GetWidget()->GenerateMousePressedForView(GetTabAt(index),
+ tab_local_bounds.CenterPoint());
+}
+
+int TabStrip2::GetInternalIndex(int public_index) const {
+ std::vector<Tab2*>::const_iterator it;
+ int internal_index = public_index;
+ int valid_tab_count = 0;
+ for (it = tabs_.begin(); it != tabs_.end(); ++it) {
+ if (public_index >= valid_tab_count)
+ break;
+ if ((*it)->removing())
+ ++internal_index;
+ else
+ ++valid_tab_count;
+ }
+ return internal_index;
+}
diff --git a/chrome/browser/views/tabs/tab_strip_2.h b/chrome/browser/views/tabs/tab_strip_2.h
new file mode 100644
index 0000000..7bbce90
--- /dev/null
+++ b/chrome/browser/views/tabs/tab_strip_2.h
@@ -0,0 +1,163 @@
+// Copyright (c) 2009 The Chromium Authors. All rights reserved. Use of this
+// source code is governed by a BSD-style license that can be found in the
+// LICENSE file.
+
+#ifndef CHROME_BROWSER_VIEWS_TABS_TAB_STRIP_2_H_
+#define CHROME_BROWSER_VIEWS_TABS_TAB_STRIP_2_H_
+
+#include <vector>
+
+#include "base/task.h"
+#include "chrome/browser/views/tabs/tab_2.h"
+#include "views/animator.h"
+#include "views/view.h"
+
+namespace gfx {
+class Canvas;
+}
+
+// An interface implemented by an object that provides state for objects in the
+// TabStrip2. This object is never owned by the TabStrip2.
+// TODO(beng): maybe TabStrip2Delegate?
+class TabStrip2Model {
+ public:
+ // Get presentation state for a particular Tab2.
+ virtual string16 GetTitle(int index) const = 0;
+ virtual bool IsSelected(int index) const = 0;
+
+ // The Tab2 at the specified index has been selected.
+ virtual void SelectTabAt(int index) = 0;
+
+ // Returns true if Tab2s can be dragged.
+ virtual bool CanDragTabs() const = 0;
+
+ // The Tab2 at the specified source index has moved to the specified
+ // destination index.
+ virtual void MoveTabAt(int index, int to_index) = 0;
+
+ // The Tab2 at the specified index was detached. |window_bounds| are the
+ // screen bounds of the current window, and |tab_bounds| are the bounds of the
+ // Tab2 in screen coordinates.
+ virtual void DetachTabAt(int index,
+ const gfx::Rect& window_bounds,
+ const gfx::Rect& tab_bounds) = 0;
+};
+
+// A TabStrip view.
+class TabStrip2 : public views::View,
+ public Tab2Model,
+ public views::AnimatorDelegate {
+ public:
+ explicit TabStrip2(TabStrip2Model* model);
+ virtual ~TabStrip2();
+
+ // Returns true if the new TabStrip is enabled.
+ static bool Enabled();
+
+ // API for adding, removing, selecting and moving tabs around.
+ void AddTabAt(int index);
+ void RemoveTabAt(int index, Tab2Model* removing_model);
+ void SelectTabAt(int index);
+ void MoveTabAt(int index, int to_index);
+
+ int GetTabCount() const;
+ Tab2* GetTabAt(int index) const;
+ int GetTabIndex(Tab2* tab) const;
+
+ // Returns the index to insert an item into the TabStrip at for a drop at the
+ // specified point in TabStrip coordinates.
+ int GetInsertionIndexForPoint(const gfx::Point& point) const;
+
+ // Returns the bounds of the Tab2 under |screen_point| in screen coordinates.
+ gfx::Rect GetDraggedTabScreenBounds(const gfx::Point& screen_point);
+
+ // Sets the bounds of the Tab2 at the specified index to |tab_bounds|.
+ void SetDraggedTabBounds(int index, const gfx::Rect& tab_bounds);
+
+ // Animates the dragged Tab2 to the location implied by its index in the
+ // model.
+ void SendDraggedTabHome();
+
+ // Continue a drag operation on the Tab2 at the specified index.
+ void ResumeDraggingTab(int index, const gfx::Rect& tab_bounds);
+
+ // Returns true if the mouse pointer at the specified point (screen bounds)
+ // constitutes a rearrange rather than a detach.
+ static bool IsDragRearrange(TabStrip2* tabstrip,
+ const gfx::Point& screen_point);
+
+ // Overridden from Tab2Model:
+ virtual string16 GetTitle(Tab2* tab) const;
+ virtual bool IsSelected(Tab2* tab) const;
+ virtual void SelectTab(Tab2* tab);
+ virtual void CaptureDragInfo(Tab2* tab, const views::MouseEvent& drag_event);
+ virtual bool DragTab(Tab2* tab, const views::MouseEvent& drag_event);
+ virtual void DragEnded(Tab2* tab);
+ virtual views::AnimatorDelegate* AsAnimatorDelegate();
+
+ // Overridden from views::View:
+ virtual gfx::Size GetPreferredSize();
+ virtual void Layout();
+ virtual void Paint(gfx::Canvas* canvas);
+
+ private:
+ virtual void PaintChildren(gfx::Canvas* canvas);
+
+ // Overridden from views::AnimatorDelegate:
+ virtual views::View* GetClampedView(views::View* host);
+ virtual void AnimationCompletedForHost(View* host);
+
+ // Specifies what kind of TabStrip2 operation initiated the Layout, so the
+ // heuristic can adapt accordingly.
+ enum LayoutSource {
+ LS_TAB_ADD,
+ LS_TAB_REMOVE,
+ LS_TAB_SELECT,
+ LS_TAB_DRAG_REORDER,
+ LS_TAB_DRAG_NORMALIZE,
+ LS_OTHER
+ };
+
+ // Returns the animation directions for the specified layout source event.
+ int GetAnimateFlagsForLayoutSource(LayoutSource source) const;
+
+ // Lays out the contents of the TabStrip2.
+ void LayoutImpl(LayoutSource source);
+
+ // Execute the tab detach operation after a return to the message loop.
+ void DragDetachTabImpl(Tab2* tab, int index);
+
+ // Execute the drag initiation operation after a return to the message loop.
+ void StartDragTabImpl(int index, const gfx::Rect& tab_bounds);
+
+ // Returns the index into |tabs_| that corresponds to a publicly visible
+ // index. The index spaces are different since when a tab is closed we retain
+ // the tab in the presentation (and this our tab vector) until the tab has
+ // animated itself out of existence, but the clients of our API expect that
+ // index to be synchronously removed.
+ int GetInternalIndex(int public_index) const;
+
+ TabStrip2Model* model_;
+
+ // A vector of our Tabs. Stored separately from the child views, the child
+ // view order does not map directly to the presentation order, and because
+ // we can have child views that aren't Tab2s.
+ std::vector<Tab2*> tabs_;
+
+ // The position of the mouse relative to the widget when drag information was
+ // captured.
+ gfx::Point mouse_tab_offset_;
+
+ // The last position of the mouse along the horizontal axis of the TabStrip
+ // prior to the current drag event. Used to determine that the mouse has moved
+ // beyond the minimum horizontal threshold to initiate a drag operation.
+ int last_move_screen_x_;
+
+ // Factories to help break up work and avoid nesting message loops.
+ ScopedRunnableMethodFactory<TabStrip2> detach_factory_;
+ ScopedRunnableMethodFactory<TabStrip2> drag_start_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(TabStrip2);
+};
+
+#endif // #ifndef CHROME_BROWSER_VIEWS_TABS_TAB_STRIP_2_H_