• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
StackedTabStripLayout(const gfx::Size & size,int padding,int stacked_padding,int max_stacked_count,views::ViewModel * view_model)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 
~StackedTabStripLayout()30 StackedTabStripLayout::~StackedTabStripLayout() {
31 }
32 
SetXAndMiniCount(int x,int mini_tab_count)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 
SetWidth(int width)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 
SetActiveIndex(int index)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 
DragActiveTab(int delta)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 
SizeToFit()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 
AddTab(int index,int add_types,int start_x)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 
RemoveTab(int index,int start_x,int old_x)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 
MoveTab(int from,int to,int new_active_index,int start_x,int mini_tab_count)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 
IsStacked(int index) const219 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 
SetActiveTabLocation(int x)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)
BoundsString() const246 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 
Reset(int x,int width,int mini_tab_count,int active_index)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 
ResetToIdealState()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 
MakeVisible(int index)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 
ConstrainActiveX(int x) const349 int StackedTabStripLayout::ConstrainActiveX(int x) const {
350   return std::min(GetMaxX(active_index()),
351                   std::max(GetMinX(active_index()), x));
352 }
353 
SetActiveBoundsAndLayoutFromActiveTab()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 
LayoutByTabOffsetAfter(int index)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 
LayoutByTabOffsetBefore(int index)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 
LayoutUsingCurrentAfter(int index)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 
LayoutUsingCurrentBefore(int index)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 
PushTabsAfter(int index,int delta)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 
PushTabsBefore(int index,int delta)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 
LayoutForDragAfter(int index)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 
LayoutForDragBefore(int index)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 
ExpandTabsBefore(int index,int delta)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 
ExpandTabsAfter(int index,int delta)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 
AdjustStackedTabs()474 void StackedTabStripLayout::AdjustStackedTabs() {
475   if (!requires_stacking() || tab_count() <= mini_tab_count_ + 1)
476     return;
477 
478   AdjustLeadingStackedTabs();
479   AdjustTrailingStackedTabs();
480 }
481 
AdjustLeadingStackedTabs()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 
AdjustTrailingStackedTabs()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 
SetIdealBoundsAt(int index,int x)531 void StackedTabStripLayout::SetIdealBoundsAt(int index, int x) {
532   view_model_->set_ideal_bounds(index, gfx::Rect(gfx::Point(x, 0), size_));
533 }
534 
GetMinX(int index) const535 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 
GetMaxX(int index) const542 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 
GetMinDragX(int index) const552 int StackedTabStripLayout::GetMinDragX(int index) const {
553   return x_ + stacked_padding_for_count(index - mini_tab_count_);
554 }
555 
GetMaxDragX(int index) const556 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 
GetMinXCompressed(int index) const562 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