diff options
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_ |