diff options
author | ben@chromium.org <ben@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2009-07-07 18:44:31 +0000 |
---|---|---|
committer | ben@chromium.org <ben@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2009-07-07 18:44:31 +0000 |
commit | 7c0560f904487819f2c408c87899d1ccfa91c1fe (patch) | |
tree | da949466cd6dd09f5ce575b206a65b6ba0e1a2f9 /chrome/browser/views/tabs | |
parent | 93a2c7241cddc33af51cdc3daea03e414f544ec8 (diff) | |
download | chromium_src-7c0560f904487819f2c408c87899d1ccfa91c1fe.zip chromium_src-7c0560f904487819f2c408c87899d1ccfa91c1fe.tar.gz chromium_src-7c0560f904487819f2c408c87899d1ccfa91c1fe.tar.bz2 |
Basics of a new TabStrip.It's very, very rough, but I wanted to check it in so I don't have to keep typing svn pset as I pass patches back and forth between machines.Behind a command line flag --enable-tabtastic2.I'm trying to split the TabContents specific stuff off of the TabStrip so it's more generic (and more easily mocked for unit testing of various layout conditions). Hence TabStrip vs. BrowserTabStrip. TabStrip may move into views/ once this process is complete.Animator is a utility that can be associated with a View that (at this point) animates that View's bounds from wherever it is now to somewhere else. The TabStrip uses this to do animations for individual Tabs that are independent of each other - a limitation of the old TabStrip is that only one animation is ever active at a time so its animations are a little jumpy compared to other products.Also, detached tab dragging shows the live contents, with all animations/video/etc.Like I said, this is really rough, but I didn't want it to grow any bigger. I will write up a design doc later.http://crbug.com/9032TEST=TBD... will finally be doing some for TabStrip layout!
Review URL: http://codereview.chromium.org/42490
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@20053 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'chrome/browser/views/tabs')
-rw-r--r-- | chrome/browser/views/tabs/browser_tab_strip.cc | 148 | ||||
-rw-r--r-- | chrome/browser/views/tabs/browser_tab_strip.h | 58 | ||||
-rw-r--r-- | chrome/browser/views/tabs/tab_2.cc | 119 | ||||
-rw-r--r-- | chrome/browser/views/tabs/tab_2.h | 114 | ||||
-rw-r--r-- | chrome/browser/views/tabs/tab_strip_2.cc | 406 | ||||
-rw-r--r-- | chrome/browser/views/tabs/tab_strip_2.h | 163 |
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_ |