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
12StackedTabStripLayout::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
30StackedTabStripLayout::~StackedTabStripLayout() {
31}
32
33void 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
51void 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
63void 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
74void 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
108void 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
150void 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
173void 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
197void 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
219bool 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
228void 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)
246std::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
261void 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
275void 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
316void 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
349int StackedTabStripLayout::ConstrainActiveX(int x) const {
350  return std::min(GetMaxX(active_index()),
351                  std::max(GetMinX(active_index()), x));
352}
353
354void 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
362void 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
372void 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
380void 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
390void 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
402void 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
407void 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
412void 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
421void 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
444void 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
458void 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
474void StackedTabStripLayout::AdjustStackedTabs() {
475  if (!requires_stacking() || tab_count() <= mini_tab_count_ + 1)
476    return;
477
478  AdjustLeadingStackedTabs();
479  AdjustTrailingStackedTabs();
480}
481
482void 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
504void 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
531void StackedTabStripLayout::SetIdealBoundsAt(int index, int x) {
532  view_model_->set_ideal_bounds(index, gfx::Rect(gfx::Point(x, 0), size_));
533}
534
535int 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
542int 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
552int StackedTabStripLayout::GetMinDragX(int index) const {
553  return x_ + stacked_padding_for_count(index - mini_tab_count_);
554}
555
556int 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
562int 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