Home | History | Annotate | Download | only in tabs
      1 // Copyright (c) 2012 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/views/tabs/stacked_tab_strip_layout.h"
      6 
      7 #include <stdio.h>
      8 
      9 #include "base/logging.h"
     10 #include "base/strings/string_number_conversions.h"
     11 
     12 StackedTabStripLayout::StackedTabStripLayout(const gfx::Size& size,
     13                                              int padding,
     14                                              int stacked_padding,
     15                                              int max_stacked_count,
     16                                              views::ViewModel* view_model)
     17     : size_(size),
     18       padding_(padding),
     19       stacked_padding_(stacked_padding),
     20       max_stacked_count_(max_stacked_count),
     21       view_model_(view_model),
     22       x_(0),
     23       width_(0),
     24       mini_tab_count_(0),
     25       mini_tab_to_non_mini_tab_(0),
     26       active_index_(-1),
     27       first_tab_x_(0) {
     28 }
     29 
     30 StackedTabStripLayout::~StackedTabStripLayout() {
     31 }
     32 
     33 void StackedTabStripLayout::SetXAndMiniCount(int x, int mini_tab_count) {
     34   first_tab_x_ = x;
     35   x_ = x;
     36   mini_tab_count_ = mini_tab_count;
     37   mini_tab_to_non_mini_tab_ = 0;
     38   if (!requires_stacking() || tab_count() == mini_tab_count) {
     39     ResetToIdealState();
     40     return;
     41   }
     42   if (mini_tab_count > 0) {
     43     mini_tab_to_non_mini_tab_ = x - ideal_x(mini_tab_count - 1);
     44     first_tab_x_ = ideal_x(0);
     45   }
     46   SetIdealBoundsAt(active_index(), ConstrainActiveX(ideal_x(active_index())));
     47   LayoutByTabOffsetAfter(active_index());
     48   LayoutByTabOffsetBefore(active_index());
     49 }
     50 
     51 void StackedTabStripLayout::SetWidth(int width) {
     52   if (width_ == width)
     53     return;
     54 
     55   width_ = width;
     56   if (!requires_stacking()) {
     57     ResetToIdealState();
     58     return;
     59   }
     60   SetActiveBoundsAndLayoutFromActiveTab();
     61 }
     62 
     63 void StackedTabStripLayout::SetActiveIndex(int index) {
     64   int old = active_index();
     65   active_index_ = index;
     66   if (old == active_index() || !requires_stacking())
     67     return;
     68   SetIdealBoundsAt(active_index(), ConstrainActiveX(ideal_x(active_index())));
     69   LayoutByTabOffsetBefore(active_index());
     70   LayoutByTabOffsetAfter(active_index());
     71   AdjustStackedTabs();
     72 }
     73 
     74 void StackedTabStripLayout::DragActiveTab(int delta) {
     75   if (delta == 0 || !requires_stacking())
     76     return;
     77   int initial_x = ideal_x(active_index());
     78   // If we're at a particular edge and start dragging, expose all the tabs after
     79   // the tab (or before when dragging to the left).
     80   if (delta > 0 && initial_x == GetMinX(active_index())) {
     81     LayoutByTabOffsetAfter(active_index());
     82     AdjustStackedTabs();
     83   } else if (delta < 0 && initial_x == GetMaxX(active_index())) {
     84     LayoutByTabOffsetBefore(active_index());
     85     ResetToIdealState();
     86   }
     87   int x = delta > 0 ?
     88       std::min(initial_x + delta, GetMaxDragX(active_index())) :
     89       std::max(initial_x + delta, GetMinDragX(active_index()));
     90   if (x != initial_x) {
     91     SetIdealBoundsAt(active_index(), x);
     92     if (delta > 0) {
     93       PushTabsAfter(active_index(), (x - initial_x));
     94       LayoutForDragBefore(active_index());
     95     } else {
     96       PushTabsBefore(active_index(), initial_x - x);
     97       LayoutForDragAfter(active_index());
     98     }
     99     delta -= (x - initial_x);
    100   }
    101   if (delta > 0)
    102     ExpandTabsBefore(active_index(), delta);
    103   else if (delta < 0)
    104     ExpandTabsAfter(active_index(), -delta);
    105   AdjustStackedTabs();
    106 }
    107 
    108 void StackedTabStripLayout::SizeToFit() {
    109   if (!tab_count())
    110     return;
    111 
    112   if (!requires_stacking()) {
    113     ResetToIdealState();
    114     return;
    115   }
    116 
    117   if (ideal_x(0) != first_tab_x_) {
    118     // Tabs have been dragged to the right. Pull in the tabs from left to right
    119     // to fill in space.
    120     int delta = ideal_x(0) - first_tab_x_;
    121     int i = 0;
    122     for (; i < mini_tab_count_; ++i) {
    123       gfx::Rect mini_bounds(view_model_->ideal_bounds(i));
    124       mini_bounds.set_x(ideal_x(i) - delta);
    125       view_model_->set_ideal_bounds(i, mini_bounds);
    126     }
    127     for (; delta > 0 && i < tab_count() - 1; ++i) {
    128       const int exposed = tab_offset() - (ideal_x(i + 1) - ideal_x(i));
    129       SetIdealBoundsAt(i, ideal_x(i) - delta);
    130       delta -= exposed;
    131     }
    132     AdjustStackedTabs();
    133     return;
    134   }
    135 
    136   const int max_x = width_ - size_.width();
    137   if (ideal_x(tab_count() - 1) == max_x)
    138     return;
    139 
    140   // Tabs have been dragged to the left. Pull in tabs from right to left to fill
    141   // in space.
    142   SetIdealBoundsAt(tab_count() - 1, max_x);
    143   for (int i = tab_count() - 2; i > mini_tab_count_ &&
    144            ideal_x(i + 1) - ideal_x(i) > tab_offset(); --i) {
    145     SetIdealBoundsAt(i, ideal_x(i + 1) - tab_offset());
    146   }
    147   AdjustStackedTabs();
    148 }
    149 
    150 void StackedTabStripLayout::AddTab(int index, int add_types, int start_x) {
    151   if (add_types & kAddTypeActive)
    152     active_index_ = index;
    153   else if (active_index_ >= index)
    154     active_index_++;
    155   if (add_types & kAddTypeMini)
    156     mini_tab_count_++;
    157   x_ = start_x;
    158   if (!requires_stacking() || normal_tab_count() <= 1) {
    159     ResetToIdealState();
    160     return;
    161   }
    162   int active_x = (index + 1 == tab_count()) ?
    163       width_ - size_.width() : ideal_x(index + 1);
    164   SetIdealBoundsAt(active_index(), ConstrainActiveX(active_x));
    165   LayoutByTabOffsetAfter(active_index());
    166   LayoutByTabOffsetBefore(active_index());
    167   AdjustStackedTabs();
    168 
    169   if ((add_types & kAddTypeActive) == 0)
    170     MakeVisible(index);
    171 }
    172 
    173 void StackedTabStripLayout::RemoveTab(int index, int start_x, int old_x) {
    174   if (index == active_index_)
    175     active_index_ = std::min(active_index_, tab_count() - 1);
    176   else if (index < active_index_)
    177     active_index_--;
    178   bool removed_mini_tab = index < mini_tab_count_;
    179   if (removed_mini_tab) {
    180     mini_tab_count_--;
    181     DCHECK_GE(mini_tab_count_, 0);
    182   }
    183   int delta = start_x - x_;
    184   x_ = start_x;
    185   if (!requires_stacking()) {
    186     ResetToIdealState();
    187     return;
    188   }
    189   if (removed_mini_tab) {
    190     for (int i = mini_tab_count_; i < tab_count(); ++i)
    191       SetIdealBoundsAt(i, ideal_x(i) + delta);
    192   }
    193   SetActiveBoundsAndLayoutFromActiveTab();
    194   AdjustStackedTabs();
    195 }
    196 
    197 void StackedTabStripLayout::MoveTab(int from,
    198                                     int to,
    199                                     int new_active_index,
    200                                     int start_x,
    201                                     int mini_tab_count) {
    202   x_ = start_x;
    203   mini_tab_count_ = mini_tab_count;
    204   active_index_ = new_active_index;
    205   if (!requires_stacking() || tab_count() == mini_tab_count_) {
    206     ResetToIdealState();
    207   } else {
    208     SetIdealBoundsAt(active_index(),
    209                      ConstrainActiveX(ideal_x(active_index())));
    210     LayoutByTabOffsetAfter(active_index());
    211     LayoutByTabOffsetBefore(active_index());
    212     AdjustStackedTabs();
    213   }
    214   mini_tab_to_non_mini_tab_ = mini_tab_count > 0 ?
    215       start_x - ideal_x(mini_tab_count - 1) : 0;
    216   first_tab_x_ = mini_tab_count > 0 ? ideal_x(0) : start_x;
    217 }
    218 
    219 bool StackedTabStripLayout::IsStacked(int index) const {
    220   if (index == active_index() || tab_count() == mini_tab_count_ ||
    221       index < mini_tab_count_)
    222     return false;
    223   if (index > active_index())
    224     return ideal_x(index) != ideal_x(index - 1) + tab_offset();
    225   return ideal_x(index + 1) != ideal_x(index) + tab_offset();
    226 }
    227 
    228 void StackedTabStripLayout::SetActiveTabLocation(int x) {
    229   if (!requires_stacking())
    230     return;
    231 
    232   const int index = active_index();
    233   if (index <= mini_tab_count_)
    234     return;
    235 
    236   x = std::min(GetMaxX(index), std::max(x, GetMinX(index)));
    237   if (x == ideal_x(index))
    238     return;
    239 
    240   SetIdealBoundsAt(index, x);
    241   LayoutByTabOffsetBefore(index);
    242   LayoutByTabOffsetAfter(index);
    243 }
    244 
    245 #if !defined(NDEBUG)
    246 std::string StackedTabStripLayout::BoundsString() const {
    247   std::string result;
    248   for (int i = 0; i < view_model_->view_size(); ++i) {
    249     if (!result.empty())
    250       result += " ";
    251     if (i == active_index())
    252       result += "[";
    253     result += base::IntToString(view_model_->ideal_bounds(i).x());
    254     if (i == active_index())
    255       result += "]";
    256   }
    257   return result;
    258 }
    259 #endif
    260 
    261 void StackedTabStripLayout::Reset(int x,
    262                                   int width,
    263                                   int mini_tab_count,
    264                                   int active_index) {
    265   x_ = x;
    266   width_ = width;
    267   mini_tab_count_ = mini_tab_count;
    268   mini_tab_to_non_mini_tab_ = mini_tab_count > 0 ?
    269       x - ideal_x(mini_tab_count - 1) : 0;
    270   first_tab_x_ = mini_tab_count > 0 ? ideal_x(0) : x;
    271   active_index_ = active_index;
    272   ResetToIdealState();
    273 }
    274 
    275 void StackedTabStripLayout::ResetToIdealState() {
    276   if (tab_count() == mini_tab_count_)
    277     return;
    278 
    279   if (!requires_stacking()) {
    280     SetIdealBoundsAt(mini_tab_count_, x_);
    281     LayoutByTabOffsetAfter(mini_tab_count_);
    282     return;
    283   }
    284 
    285   if (normal_tab_count() == 1) {
    286     // TODO: might want to shrink the tab here.
    287     SetIdealBoundsAt(mini_tab_count_, 0);
    288     return;
    289   }
    290 
    291   int available_width = width_ - x_;
    292   int leading_count = active_index() - mini_tab_count_;
    293   int trailing_count = tab_count() - active_index();
    294   if (width_for_count(leading_count + 1) + max_stacked_width() <
    295       available_width) {
    296     SetIdealBoundsAt(mini_tab_count_, x_);
    297     LayoutByTabOffsetAfter(mini_tab_count_);
    298   } else if (width_for_count(trailing_count) + max_stacked_width() <
    299              available_width) {
    300     SetIdealBoundsAt(tab_count() - 1, width_ - size_.width());
    301     LayoutByTabOffsetBefore(tab_count() - 1);
    302   } else {
    303     int index = active_index();
    304     do {
    305       int stacked_padding = stacked_padding_for_count(index - mini_tab_count_);
    306       SetIdealBoundsAt(index, x_ + stacked_padding);
    307       LayoutByTabOffsetAfter(index);
    308       LayoutByTabOffsetBefore(index);
    309       index--;
    310     } while (index >= mini_tab_count_ && ideal_x(mini_tab_count_) != x_ &&
    311              ideal_x(tab_count() - 1) != width_ - size_.width());
    312   }
    313   AdjustStackedTabs();
    314 }
    315 
    316 void StackedTabStripLayout::MakeVisible(int index) {
    317   // Currently no need to support tabs openning before |index| visible.
    318   if (index <= active_index() || !requires_stacking() || !IsStacked(index))
    319     return;
    320 
    321   int ideal_delta = width_for_count(index - active_index()) + padding_;
    322   if (ideal_x(index) - ideal_x(active_index()) == ideal_delta)
    323     return;
    324 
    325   // First push active index as far to the left as it'll go.
    326   int active_x = std::max(GetMinX(active_index()),
    327                           std::min(ideal_x(index) - ideal_delta,
    328                                    ideal_x(active_index())));
    329   SetIdealBoundsAt(active_index(), active_x);
    330   LayoutUsingCurrentBefore(active_index());
    331   LayoutUsingCurrentAfter(active_index());
    332   AdjustStackedTabs();
    333   if (ideal_x(index) - ideal_x(active_index()) == ideal_delta)
    334     return;
    335 
    336   // If we get here active_index() is left aligned. Push |index| as far to
    337   // the right as possible.
    338   int x = std::min(GetMaxX(index), active_x + ideal_delta);
    339   SetIdealBoundsAt(index, x);
    340   LayoutByTabOffsetAfter(index);
    341   for (int next_x = x, i = index - 1; i > active_index(); --i) {
    342     next_x = std::max(GetMinXCompressed(i), next_x - tab_offset());
    343     SetIdealBoundsAt(i, next_x);
    344   }
    345   LayoutUsingCurrentAfter(active_index());
    346   AdjustStackedTabs();
    347 }
    348 
    349 int StackedTabStripLayout::ConstrainActiveX(int x) const {
    350   return std::min(GetMaxX(active_index()),
    351                   std::max(GetMinX(active_index()), x));
    352 }
    353 
    354 void StackedTabStripLayout::SetActiveBoundsAndLayoutFromActiveTab() {
    355   int x = ConstrainActiveX(ideal_x(active_index()));
    356   SetIdealBoundsAt(active_index(), x);
    357   LayoutUsingCurrentBefore(active_index());
    358   LayoutUsingCurrentAfter(active_index());
    359   AdjustStackedTabs();
    360 }
    361 
    362 void StackedTabStripLayout::LayoutByTabOffsetAfter(int index) {
    363   for (int i = index + 1; i < tab_count(); ++i) {
    364     int max_x = width_ - size_.width() -
    365         stacked_padding_for_count(tab_count() - i - 1);
    366     int x = std::min(max_x,
    367                      view_model_->ideal_bounds(i - 1).x() + tab_offset());
    368     SetIdealBoundsAt(i, x);
    369   }
    370 }
    371 
    372 void StackedTabStripLayout::LayoutByTabOffsetBefore(int index) {
    373   for (int i = index - 1; i >= mini_tab_count_; --i) {
    374     int min_x = x_ + stacked_padding_for_count(i - mini_tab_count_);
    375     int x = std::max(min_x, ideal_x(i + 1) - (tab_offset()));
    376     SetIdealBoundsAt(i, x);
    377   }
    378 }
    379 
    380 void StackedTabStripLayout::LayoutUsingCurrentAfter(int index) {
    381   for (int i = index + 1; i < tab_count(); ++i) {
    382     int min_x = width_ - width_for_count(tab_count() - i);
    383     int x = std::max(min_x,
    384                      std::min(ideal_x(i), ideal_x(i - 1) + tab_offset()));
    385     x = std::min(GetMaxX(i), x);
    386     SetIdealBoundsAt(i, x);
    387   }
    388 }
    389 
    390 void StackedTabStripLayout::LayoutUsingCurrentBefore(int index) {
    391   for (int i = index - 1; i >= mini_tab_count_; --i) {
    392     int max_x = x_ + width_for_count(i - mini_tab_count_);
    393     if (i > mini_tab_count_)
    394       max_x += padding_;
    395     max_x = std::min(max_x, ideal_x(i + 1) - stacked_padding_);
    396     SetIdealBoundsAt(
    397         i, std::min(max_x,
    398                     std::max(ideal_x(i), ideal_x(i + 1) - tab_offset())));
    399   }
    400 }
    401 
    402 void StackedTabStripLayout::PushTabsAfter(int index, int delta) {
    403   for (int i = index + 1; i < tab_count(); ++i)
    404     SetIdealBoundsAt(i, std::min(ideal_x(i) + delta, GetMaxDragX(i)));
    405 }
    406 
    407 void StackedTabStripLayout::PushTabsBefore(int index, int delta) {
    408   for (int i = index - 1; i > mini_tab_count_; --i)
    409     SetIdealBoundsAt(i, std::max(ideal_x(i) - delta, GetMinDragX(i)));
    410 }
    411 
    412 void StackedTabStripLayout::LayoutForDragAfter(int index) {
    413   for (int i = index + 1; i < tab_count(); ++i) {
    414     const int min_x = ideal_x(i - 1) + stacked_padding_;
    415     const int max_x = ideal_x(i - 1) + tab_offset();
    416     SetIdealBoundsAt(
    417         i, std::max(min_x, std::min(ideal_x(i), max_x)));
    418   }
    419 }
    420 
    421 void StackedTabStripLayout::LayoutForDragBefore(int index) {
    422   for (int i = index - 1; i >= mini_tab_count_; --i) {
    423     const int max_x = ideal_x(i + 1) - stacked_padding_;
    424     const int min_x = ideal_x(i + 1) - tab_offset();
    425     SetIdealBoundsAt(
    426         i, std::max(min_x, std::min(ideal_x(i), max_x)));
    427   }
    428 
    429   if (mini_tab_count_ == 0)
    430     return;
    431 
    432   // Pull in the mini-tabs.
    433   const int delta = (mini_tab_count_ > 1) ? ideal_x(1) - ideal_x(0) : 0;
    434   for (int i = mini_tab_count_ - 1; i >= 0; --i) {
    435     gfx::Rect mini_bounds(view_model_->ideal_bounds(i));
    436     if (i == mini_tab_count_ - 1)
    437       mini_bounds.set_x(ideal_x(i + 1) - mini_tab_to_non_mini_tab_);
    438     else
    439       mini_bounds.set_x(ideal_x(i + 1) - delta);
    440     view_model_->set_ideal_bounds(i, mini_bounds);
    441   }
    442 }
    443 
    444 void StackedTabStripLayout::ExpandTabsBefore(int index, int delta) {
    445   for (int i = index - 1; i >= mini_tab_count_ && delta > 0; --i) {
    446     const int max_x = ideal_x(active_index()) -
    447         stacked_padding_for_count(active_index() - i);
    448     int to_resize = std::min(delta, max_x - ideal_x(i));
    449 
    450     if (to_resize <= 0)
    451       continue;
    452     SetIdealBoundsAt(i, ideal_x(i) + to_resize);
    453     delta -= to_resize;
    454     LayoutForDragBefore(i);
    455   }
    456 }
    457 
    458 void StackedTabStripLayout::ExpandTabsAfter(int index, int delta) {
    459   if (index == tab_count() - 1)
    460     return;  // Nothing to expand.
    461 
    462   for (int i = index + 1; i < tab_count() && delta > 0; ++i) {
    463     const int min_compressed =
    464         ideal_x(active_index()) + stacked_padding_for_count(i - active_index());
    465     const int to_resize = std::min(ideal_x(i) - min_compressed, delta);
    466     if (to_resize <= 0)
    467       continue;
    468     SetIdealBoundsAt(i, ideal_x(i) - to_resize);
    469     delta -= to_resize;
    470     LayoutForDragAfter(i);
    471   }
    472 }
    473 
    474 void StackedTabStripLayout::AdjustStackedTabs() {
    475   if (!requires_stacking() || tab_count() <= mini_tab_count_ + 1)
    476     return;
    477 
    478   AdjustLeadingStackedTabs();
    479   AdjustTrailingStackedTabs();
    480 }
    481 
    482 void StackedTabStripLayout::AdjustLeadingStackedTabs() {
    483   int index = mini_tab_count_ + 1;
    484   while (index < active_index() &&
    485          ideal_x(index) - ideal_x(index - 1) <= stacked_padding_ &&
    486          ideal_x(index) <= x_ + max_stacked_width()) {
    487     index++;
    488   }
    489   if (ideal_x(index) - ideal_x(index - 1) <= stacked_padding_ &&
    490       ideal_x(index) <= x_ + max_stacked_width()) {
    491     index++;
    492   }
    493   if (index <= mini_tab_count_ + max_stacked_count_ - 1)
    494     return;
    495   int max_stacked = index;
    496   int x = x_;
    497   index = mini_tab_count_;
    498   for (; index < max_stacked - max_stacked_count_ - 1; ++index)
    499     SetIdealBoundsAt(index, x);
    500   for (; index < max_stacked; ++index, x += stacked_padding_)
    501     SetIdealBoundsAt(index, x);
    502 }
    503 
    504 void StackedTabStripLayout::AdjustTrailingStackedTabs() {
    505   int index = tab_count() - 1;
    506   int max_stacked_x = width_ - size_.width() - max_stacked_width();
    507   while (index > active_index() &&
    508          ideal_x(index) - ideal_x(index - 1) <= stacked_padding_ &&
    509          ideal_x(index - 1) >= max_stacked_x) {
    510     index--;
    511   }
    512   if (index > active_index() &&
    513       ideal_x(index) - ideal_x(index - 1) <= stacked_padding_ &&
    514       ideal_x(index - 1) >= max_stacked_x) {
    515     index--;
    516   }
    517   if (index >= tab_count() - max_stacked_count_)
    518     return;
    519   int first_stacked = index;
    520   int x = width_ - size_.width() -
    521       std::min(tab_count() - first_stacked, max_stacked_count_) *
    522       stacked_padding_;
    523   for (; index < first_stacked + max_stacked_count_;
    524        ++index, x += stacked_padding_) {
    525     SetIdealBoundsAt(index, x);
    526   }
    527   for (; index < tab_count(); ++index)
    528     SetIdealBoundsAt(index, x);
    529 }
    530 
    531 void StackedTabStripLayout::SetIdealBoundsAt(int index, int x) {
    532   view_model_->set_ideal_bounds(index, gfx::Rect(gfx::Point(x, 0), size_));
    533 }
    534 
    535 int StackedTabStripLayout::GetMinX(int index) const {
    536   int leading_count = index - mini_tab_count_;
    537   int trailing_count = tab_count() - index;
    538   return std::max(x_ + stacked_padding_for_count(leading_count),
    539                   width_ - width_for_count(trailing_count));
    540 }
    541 
    542 int StackedTabStripLayout::GetMaxX(int index) const {
    543   int leading_count = index - mini_tab_count_;
    544   int trailing_count = tab_count() - index - 1;
    545   int trailing_offset = stacked_padding_for_count(trailing_count);
    546   int leading_size = width_for_count(leading_count) + x_;
    547   if (leading_count > 0)
    548     leading_size += padding_;
    549   return std::min(width_ - trailing_offset - size_.width(), leading_size);
    550 }
    551 
    552 int StackedTabStripLayout::GetMinDragX(int index) const {
    553   return x_ + stacked_padding_for_count(index - mini_tab_count_);
    554 }
    555 
    556 int StackedTabStripLayout::GetMaxDragX(int index) const {
    557   const int trailing_offset =
    558       stacked_padding_for_count(tab_count() - index - 1);
    559   return width_ - trailing_offset - size_.width();
    560 }
    561 
    562 int StackedTabStripLayout::GetMinXCompressed(int index) const {
    563   DCHECK_GT(index, active_index());
    564   return std::max(
    565       width_ - width_for_count(tab_count() - index),
    566       ideal_x(active_index()) +
    567           stacked_padding_for_count(index - active_index()));
    568 }
    569