Home | History | Annotate | Download | only in tabs
      1 // Copyright (c) 2011 The Chromium Authors. All rights reserved.
      2 // Use of this source code is governed by a BSD-style license that can be
      3 // found in the LICENSE file.
      4 
      5 #include "chrome/browser/ui/touch/tabs/touch_tab_strip.h"
      6 
      7 #include <algorithm>
      8 #include <cmath>
      9 
     10 #include "chrome/browser/ui/touch/tabs/touch_tab.h"
     11 #include "chrome/browser/ui/view_ids.h"
     12 #include "chrome/browser/ui/views/tabs/browser_tab_strip_controller.h"
     13 #include "ui/gfx/canvas_skia.h"
     14 #include "views/metrics.h"
     15 #include "views/window/non_client_view.h"
     16 #include "views/window/window.h"
     17 
     18 static const int kTouchTabStripHeight = 64;
     19 static const int kTouchTabWidth = 64;
     20 static const int kTouchTabHeight = 64;
     21 static const int kScrollThreshold = 4;
     22 
     23 TouchTabStrip::TouchTabStrip(TabStripController* controller)
     24     : BaseTabStrip(controller, BaseTabStrip::HORIZONTAL_TAB_STRIP),
     25       in_tab_close_(false),
     26       last_tap_time_(base::Time::FromInternalValue(0)),
     27       last_tapped_view_(NULL),
     28       initial_mouse_x_(0),
     29       initial_scroll_offset_(0),
     30       scroll_offset_(0),
     31       scrolling_(false),
     32       initial_tab_(NULL),
     33       min_scroll_offset_(0) {
     34   Init();
     35 }
     36 
     37 TouchTabStrip::~TouchTabStrip() {
     38   // The animations may reference the tabs. Shut down the animation before we
     39   // delete the tabs.
     40   StopAnimating(false);
     41 
     42   DestroyDragController();
     43 
     44   // The children (tabs) may callback to us from their destructor. Delete them
     45   // so that if they call back we aren't in a weird state.
     46   RemoveAllChildViews(true);
     47 }
     48 
     49 ////////////////////////////////////////////////////////////////////////////////
     50 // TouchTabStrip, AbstractTabStripView implementation:
     51 
     52 bool TouchTabStrip::IsPositionInWindowCaption(const gfx::Point& point) {
     53   // The entire tabstrip is mine. No part of it belongs to the window caption.
     54   return false;
     55 }
     56 
     57 void TouchTabStrip::SetBackgroundOffset(const gfx::Point& offset) {
     58   for (int i = 0; i < tab_count(); ++i)
     59     GetTabAtTabDataIndex(i)->set_background_offset(offset);
     60 }
     61 
     62 ////////////////////////////////////////////////////////////////////////////////
     63 // TouchTabStrip, BaseTabStrip implementation:
     64 
     65 void TouchTabStrip::PrepareForCloseAt(int model_index) {
     66   if (!in_tab_close_ && IsAnimating()) {
     67     // Cancel any current animations. We do this as remove uses the current
     68     // ideal bounds and we need to know ideal bounds is in a good state.
     69     StopAnimating(true);
     70   }
     71 
     72   in_tab_close_ = true;
     73 }
     74 
     75 void TouchTabStrip::StartHighlight(int model_index) {
     76 }
     77 
     78 void TouchTabStrip::StopAllHighlighting() {
     79 }
     80 
     81 BaseTab* TouchTabStrip::CreateTabForDragging() {
     82   return NULL;
     83 }
     84 
     85 void TouchTabStrip::RemoveTabAt(int model_index) {
     86   StartRemoveTabAnimation(model_index);
     87 }
     88 
     89 void TouchTabStrip::SelectTabAt(int old_model_index, int new_model_index) {
     90   SchedulePaint();
     91 }
     92 
     93 void TouchTabStrip::TabTitleChangedNotLoading(int model_index) {
     94 }
     95 
     96 BaseTab* TouchTabStrip::CreateTab() {
     97   return new TouchTab(this);
     98 }
     99 
    100 void TouchTabStrip::StartInsertTabAnimation(int model_index) {
    101   PrepareForAnimation();
    102 
    103   in_tab_close_ = false;
    104 
    105   GenerateIdealBounds();
    106 
    107   int tab_data_index = ModelIndexToTabIndex(model_index);
    108   BaseTab* tab = base_tab_at_tab_index(tab_data_index);
    109   if (model_index == 0) {
    110     tab->SetBounds(0, ideal_bounds(tab_data_index).y(), 0,
    111                    ideal_bounds(tab_data_index).height());
    112   } else {
    113     BaseTab* last_tab = base_tab_at_tab_index(tab_data_index - 1);
    114     tab->SetBounds(last_tab->bounds().right(),
    115                    ideal_bounds(tab_data_index).y(), 0,
    116                    ideal_bounds(tab_data_index).height());
    117   }
    118 
    119   AnimateToIdealBounds();
    120 }
    121 
    122 void TouchTabStrip::AnimateToIdealBounds() {
    123   for (int i = 0; i < tab_count(); ++i) {
    124     TouchTab* tab = GetTabAtTabDataIndex(i);
    125     if (!tab->closing() && !tab->dragging())
    126       bounds_animator().AnimateViewTo(tab, ideal_bounds(i));
    127   }
    128 }
    129 
    130 bool TouchTabStrip::ShouldHighlightCloseButtonAfterRemove() {
    131   return in_tab_close_;
    132 }
    133 
    134 void TouchTabStrip::GenerateIdealBounds() {
    135   gfx::Rect bounds;
    136   int tab_x = 0;
    137   int tab_y = 0;
    138   for (int i = 0; i < tab_count(); ++i) {
    139     TouchTab* tab = GetTabAtTabDataIndex(i);
    140     if (!tab->closing()) {
    141       int x = tab_x + scroll_offset_;
    142       if (tab->IsSelected()) {
    143         // limit the extent to which this tab can be displaced.
    144         x = std::min(std::max(0, x), width() - kTouchTabWidth);
    145       }
    146       set_ideal_bounds(i, gfx::Rect(x, tab_y,
    147                                     kTouchTabWidth, kTouchTabHeight));
    148       // offset the next tab to the right by the width of this tab
    149       tab_x += kTouchTabWidth;
    150     }
    151   }
    152   min_scroll_offset_ = std::min(0, width() - tab_x);
    153 }
    154 
    155 void TouchTabStrip::LayoutDraggedTabsAt(const std::vector<BaseTab*>& tabs,
    156                                         BaseTab* active_tab,
    157                                         const gfx::Point& location,
    158                                         bool initial_drag) {
    159   // Not needed as dragging isn't supported.
    160   NOTIMPLEMENTED();
    161 }
    162 
    163 void TouchTabStrip::CalculateBoundsForDraggedTabs(
    164     const std::vector<BaseTab*>& tabs,
    165     std::vector<gfx::Rect>* bounds) {
    166   // Not needed as dragging isn't supported.
    167   NOTIMPLEMENTED();
    168 }
    169 
    170 int TouchTabStrip::GetSizeNeededForTabs(const std::vector<BaseTab*>& tabs) {
    171   // Not needed as dragging isn't supported.
    172   NOTIMPLEMENTED();
    173   return 0;
    174 }
    175 
    176 // TODO(wyck): Someday we might like to get a "scroll" interaction event by way
    177 // of views, triggered by the gesture manager, and/or mouse scroll wheel.
    178 // For now, we're just handling a single scroll with these mouse events:
    179 // OnMousePressed, OnMouseDragged, and OnMouseReleased.
    180 
    181 bool TouchTabStrip::OnMousePressed(const views::MouseEvent& event) {
    182   // When we press the mouse button, we begin a drag
    183   BeginScroll(event.location());
    184   return true;
    185 }
    186 
    187 bool TouchTabStrip::OnMouseDragged(const views::MouseEvent& event) {
    188   ContinueScroll(event.location());
    189   return true;
    190 }
    191 
    192 void TouchTabStrip::OnMouseReleased(const views::MouseEvent& event) {
    193   EndScroll(event.location());
    194 }
    195 
    196 void TouchTabStrip::OnMouseCaptureLost() {
    197   CancelScroll();
    198 }
    199 
    200 void TouchTabStrip::BeginScroll(const gfx::Point& point ) {
    201   initial_mouse_x_ = point.x();
    202   initial_scroll_offset_ = scroll_offset_;
    203   initial_tab_ = static_cast<TouchTab*>(GetTabAtLocal(point));
    204 }
    205 
    206 void TouchTabStrip::ContinueScroll(const gfx::Point& point) {
    207   int delta_x = point.x() - initial_mouse_x_;
    208   if (std::abs(delta_x) > kScrollThreshold)
    209     scrolling_ = true;
    210   if (scrolling_)
    211     ScrollTo(delta_x);
    212   DoLayout();
    213   SchedulePaint();
    214 }
    215 
    216 void TouchTabStrip::EndScroll(const gfx::Point& point) {
    217   int delta_x = point.x() - initial_mouse_x_;
    218   if (scrolling_) {
    219     scrolling_ = false;
    220     ScrollTo(delta_x);
    221     StopAnimating(false);
    222     GenerateIdealBounds();
    223     AnimateToIdealBounds();
    224   } else {
    225     TouchTab* tab = static_cast<TouchTab*>(GetTabAtLocal(point));
    226     if (tab && tab == initial_tab_)
    227       SelectTab(tab);
    228     DoLayout();
    229     SchedulePaint();
    230   }
    231   initial_tab_ = NULL;
    232 }
    233 
    234 void TouchTabStrip::CancelScroll() {
    235   // Cancel the scroll by scrolling back to the initial position (deltax = 0).
    236   ScrollTo(0);
    237   StopAnimating(false);
    238   GenerateIdealBounds();
    239   AnimateToIdealBounds();
    240 }
    241 
    242 void TouchTabStrip::ScrollTo(int delta_x) {
    243   scroll_offset_ = initial_scroll_offset_ + delta_x;
    244   // Limit the scrolling here.
    245   // When scrolling beyond the limits of min and max offsets, the displacement
    246   // is adjusted to 25% of what would normally applied (divided by 4).
    247   // Perhaps in the future, Hooke's law could be used to model more physically
    248   // based spring-like behavior.
    249   int max_scroll_offset = 0;  // Because there's never content to the left of 0.
    250   if (scroll_offset_ > max_scroll_offset) {
    251     if (scrolling_) {
    252       scroll_offset_ = max_scroll_offset
    253           + std::min((scroll_offset_ - max_scroll_offset) / 4,
    254                      kTouchTabWidth);
    255     } else {
    256       scroll_offset_ = max_scroll_offset;
    257     }
    258   }
    259   if (scroll_offset_ < min_scroll_offset_) {
    260     if (scrolling_) {
    261       scroll_offset_ = min_scroll_offset_
    262           + std::max((scroll_offset_ - min_scroll_offset_) / 4,
    263                      -kTouchTabWidth);
    264     } else {
    265       scroll_offset_ = min_scroll_offset_;
    266     }
    267   }
    268 }
    269 
    270 TouchTab* TouchTabStrip::GetTabAtTabDataIndex(int tab_data_index) const {
    271   return static_cast<TouchTab*>(base_tab_at_tab_index(tab_data_index));
    272 }
    273 
    274 ////////////////////////////////////////////////////////////////////////////////
    275 // TouchTabStrip, private:
    276 
    277 void TouchTabStrip::Init() {
    278   SetID(VIEW_ID_TAB_STRIP);
    279 }
    280 
    281 ////////////////////////////////////////////////////////////////////////////////
    282 // TouchTabStrip, views::View overrides, private:
    283 
    284 gfx::Size TouchTabStrip::GetPreferredSize() {
    285   return gfx::Size(0, kTouchTabStripHeight);
    286 }
    287 
    288 void TouchTabStrip::PaintChildren(gfx::Canvas* canvas) {
    289   // Tabs are painted in reverse order, so they stack to the left.
    290   TouchTab* selected_tab = NULL;
    291   TouchTab* dragging_tab = NULL;
    292 
    293   for (int i = tab_count() - 1; i >= 0; --i) {
    294     TouchTab* tab = GetTabAtTabDataIndex(i);
    295     // We must ask the _Tab's_ model, not ourselves, because in some situations
    296     // the model will be different to this object, e.g. when a Tab is being
    297     // removed after its TabContents has been destroyed.
    298     if (tab->dragging()) {
    299       dragging_tab = tab;
    300     } else if (!tab->IsSelected()) {
    301       tab->Paint(canvas);
    302     } else {
    303       selected_tab = tab;
    304     }
    305   }
    306 
    307   if (GetWindow()->non_client_view()->UseNativeFrame()) {
    308     // Make sure unselected tabs are somewhat transparent.
    309     SkPaint paint;
    310     paint.setColor(SkColorSetARGB(200, 255, 255, 255));
    311     paint.setXfermodeMode(SkXfermode::kDstIn_Mode);
    312     paint.setStyle(SkPaint::kFill_Style);
    313     canvas->DrawRectInt(0, 0, width(),
    314         height() - 2,  // Visible region that overlaps the toolbar.
    315         paint);
    316   }
    317 
    318   // Paint the selected tab last, so it overlaps all the others.
    319   if (selected_tab)
    320     selected_tab->Paint(canvas);
    321 
    322   // And the dragged tab.
    323   if (dragging_tab)
    324     dragging_tab->Paint(canvas);
    325 }
    326 
    327 views::View::TouchStatus TouchTabStrip::OnTouchEvent(
    328     const views::TouchEvent& event) {
    329   if (event.type() != ui::ET_TOUCH_PRESSED)
    330     return TOUCH_STATUS_UNKNOWN;
    331 
    332   views::View* view = GetEventHandlerForPoint(event.location());
    333   if (view && view != this && view->GetID() != VIEW_ID_TAB)
    334     return TOUCH_STATUS_UNKNOWN;
    335 
    336   base::TimeDelta delta = event.time_stamp() - last_tap_time_;
    337 
    338   if (delta.InMilliseconds() < views::GetDoubleClickInterval() &&
    339       view == last_tapped_view_) {
    340     // If double tapped the empty space, open a new tab. If double tapped a tab,
    341     // close it.
    342     if (view == this)
    343       controller()->CreateNewTab();
    344     else
    345       CloseTab(static_cast<BaseTab*>(view));
    346 
    347     last_tap_time_ = base::Time::FromInternalValue(0);
    348     last_tapped_view_ = NULL;
    349     return TOUCH_STATUS_END;
    350   }
    351 
    352   last_tap_time_ = event.time_stamp();
    353   last_tapped_view_ = view;
    354   return TOUCH_STATUS_UNKNOWN;
    355 }
    356 
    357 void TouchTabStrip::ViewHierarchyChanged(bool is_add,
    358                                          View* parent,
    359                                          View* child) {
    360   if (!is_add && last_tapped_view_ == child)
    361     last_tapped_view_ = NULL;
    362 }
    363