Merge Chromium + Blink git repositories
[chromium-blink-merge.git] / chrome / browser / ui / views / tabs / stacked_tab_strip_layout.cc
bloba66b8e40fbcd722f167a3f6d3f31d5f51ad0058a
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.
5 #include "chrome/browser/ui/views/tabs/stacked_tab_strip_layout.h"
7 #include <stdio.h>
9 #include "base/logging.h"
10 #include "base/strings/string_number_conversions.h"
11 #include "content/public/browser/user_metrics.h"
13 using base::UserMetricsAction;
15 StackedTabStripLayout::StackedTabStripLayout(const gfx::Size& size,
16 int overlap,
17 int stacked_padding,
18 int max_stacked_count,
19 views::ViewModelBase* view_model)
20 : size_(size),
21 overlap_(overlap),
22 stacked_padding_(stacked_padding),
23 max_stacked_count_(max_stacked_count),
24 view_model_(view_model),
25 x_(0),
26 width_(0),
27 pinned_tab_count_(0),
28 pinned_tab_to_non_pinned_tab_(0),
29 active_index_(-1),
30 first_tab_x_(0) {
33 StackedTabStripLayout::~StackedTabStripLayout() {
36 void StackedTabStripLayout::SetXAndPinnedCount(int x, int pinned_tab_count) {
37 first_tab_x_ = x;
38 x_ = x;
39 pinned_tab_count_ = pinned_tab_count;
40 pinned_tab_to_non_pinned_tab_ = 0;
41 if (!requires_stacking() || tab_count() == pinned_tab_count) {
42 ResetToIdealState();
43 return;
45 if (pinned_tab_count > 0) {
46 pinned_tab_to_non_pinned_tab_ = x - ideal_x(pinned_tab_count - 1);
47 first_tab_x_ = ideal_x(0);
49 SetIdealBoundsAt(active_index(), ConstrainActiveX(ideal_x(active_index())));
50 LayoutByTabOffsetAfter(active_index());
51 LayoutByTabOffsetBefore(active_index());
54 void StackedTabStripLayout::SetWidth(int width) {
55 if (width_ == width)
56 return;
58 width_ = width;
59 if (!requires_stacking()) {
60 ResetToIdealState();
61 return;
63 SetActiveBoundsAndLayoutFromActiveTab();
66 void StackedTabStripLayout::SetActiveIndex(int index) {
67 int old = active_index();
68 active_index_ = index;
69 if (old == active_index() || !requires_stacking())
70 return;
71 SetIdealBoundsAt(active_index(), ConstrainActiveX(ideal_x(active_index())));
72 LayoutByTabOffsetBefore(active_index());
73 LayoutByTabOffsetAfter(active_index());
74 AdjustStackedTabs();
77 void StackedTabStripLayout::DragActiveTab(int delta) {
78 if (delta == 0 || !requires_stacking())
79 return;
81 content::RecordAction(UserMetricsAction("StackedTab_DragActiveTab"));
82 int initial_x = ideal_x(active_index());
83 // If we're at a particular edge and start dragging, expose all the tabs after
84 // the tab (or before when dragging to the left).
85 if (delta > 0 && initial_x == GetMinX(active_index())) {
86 LayoutByTabOffsetAfter(active_index());
87 AdjustStackedTabs();
88 } else if (delta < 0 && initial_x == GetMaxX(active_index())) {
89 LayoutByTabOffsetBefore(active_index());
90 ResetToIdealState();
92 int x = delta > 0 ?
93 std::min(initial_x + delta, GetMaxDragX(active_index())) :
94 std::max(initial_x + delta, GetMinDragX(active_index()));
95 if (x != initial_x) {
96 SetIdealBoundsAt(active_index(), x);
97 if (delta > 0) {
98 PushTabsAfter(active_index(), (x - initial_x));
99 LayoutForDragBefore(active_index());
100 } else {
101 PushTabsBefore(active_index(), initial_x - x);
102 LayoutForDragAfter(active_index());
104 delta -= (x - initial_x);
106 if (delta > 0)
107 ExpandTabsBefore(active_index(), delta);
108 else if (delta < 0)
109 ExpandTabsAfter(active_index(), -delta);
110 AdjustStackedTabs();
113 void StackedTabStripLayout::SizeToFit() {
114 if (!tab_count())
115 return;
117 if (!requires_stacking()) {
118 ResetToIdealState();
119 return;
122 if (ideal_x(0) != first_tab_x_) {
123 // Tabs have been dragged to the right. Pull in the tabs from left to right
124 // to fill in space.
125 int delta = ideal_x(0) - first_tab_x_;
126 int i = 0;
127 for (; i < pinned_tab_count_; ++i) {
128 gfx::Rect pinned_bounds(view_model_->ideal_bounds(i));
129 pinned_bounds.set_x(ideal_x(i) - delta);
130 view_model_->set_ideal_bounds(i, pinned_bounds);
132 for (; delta > 0 && i < tab_count() - 1; ++i) {
133 const int exposed = tab_offset() - (ideal_x(i + 1) - ideal_x(i));
134 SetIdealBoundsAt(i, ideal_x(i) - delta);
135 delta -= exposed;
137 AdjustStackedTabs();
138 return;
141 const int max_x = width_ - size_.width();
142 if (ideal_x(tab_count() - 1) == max_x)
143 return;
145 // Tabs have been dragged to the left. Pull in tabs from right to left to fill
146 // in space.
147 SetIdealBoundsAt(tab_count() - 1, max_x);
148 for (int i = tab_count() - 2; i > pinned_tab_count_ &&
149 ideal_x(i + 1) - ideal_x(i) > tab_offset(); --i) {
150 SetIdealBoundsAt(i, ideal_x(i + 1) - tab_offset());
152 AdjustStackedTabs();
155 void StackedTabStripLayout::AddTab(int index, int add_types, int start_x) {
156 if (add_types & kAddTypeActive)
157 active_index_ = index;
158 else if (active_index_ >= index)
159 active_index_++;
160 if (add_types & kAddTypePinned)
161 pinned_tab_count_++;
162 x_ = start_x;
163 if (!requires_stacking() || normal_tab_count() <= 1) {
164 ResetToIdealState();
165 return;
167 int active_x = (index + 1 == tab_count()) ?
168 width_ - size_.width() : ideal_x(index + 1);
169 SetIdealBoundsAt(active_index(), ConstrainActiveX(active_x));
170 LayoutByTabOffsetAfter(active_index());
171 LayoutByTabOffsetBefore(active_index());
172 AdjustStackedTabs();
174 if ((add_types & kAddTypeActive) == 0)
175 MakeVisible(index);
178 void StackedTabStripLayout::RemoveTab(int index, int start_x, int old_x) {
179 if (index == active_index_)
180 active_index_ = std::min(active_index_, tab_count() - 1);
181 else if (index < active_index_)
182 active_index_--;
183 bool removed_pinned_tab = index < pinned_tab_count_;
184 if (removed_pinned_tab) {
185 pinned_tab_count_--;
186 DCHECK_GE(pinned_tab_count_, 0);
188 int delta = start_x - x_;
189 x_ = start_x;
190 if (!requires_stacking()) {
191 ResetToIdealState();
192 return;
194 if (removed_pinned_tab) {
195 for (int i = pinned_tab_count_; i < tab_count(); ++i)
196 SetIdealBoundsAt(i, ideal_x(i) + delta);
198 SetActiveBoundsAndLayoutFromActiveTab();
199 AdjustStackedTabs();
202 void StackedTabStripLayout::MoveTab(int from,
203 int to,
204 int new_active_index,
205 int start_x,
206 int pinned_tab_count) {
207 x_ = start_x;
208 pinned_tab_count_ = pinned_tab_count;
209 active_index_ = new_active_index;
210 if (!requires_stacking() || tab_count() == pinned_tab_count_) {
211 ResetToIdealState();
212 } else {
213 SetIdealBoundsAt(active_index(),
214 ConstrainActiveX(ideal_x(active_index())));
215 LayoutByTabOffsetAfter(active_index());
216 LayoutByTabOffsetBefore(active_index());
217 AdjustStackedTabs();
219 pinned_tab_to_non_pinned_tab_ = pinned_tab_count > 0 ?
220 start_x - ideal_x(pinned_tab_count - 1) : 0;
221 first_tab_x_ = pinned_tab_count > 0 ? ideal_x(0) : start_x;
224 bool StackedTabStripLayout::IsStacked(int index) const {
225 if (index == active_index() || tab_count() == pinned_tab_count_ ||
226 index < pinned_tab_count_)
227 return false;
228 if (index > active_index())
229 return ideal_x(index) != ideal_x(index - 1) + tab_offset();
230 return ideal_x(index + 1) != ideal_x(index) + tab_offset();
233 void StackedTabStripLayout::SetActiveTabLocation(int x) {
234 if (!requires_stacking())
235 return;
237 const int index = active_index();
238 if (index <= pinned_tab_count_)
239 return;
241 x = std::min(GetMaxX(index), std::max(x, GetMinX(index)));
242 if (x == ideal_x(index))
243 return;
245 SetIdealBoundsAt(index, x);
246 LayoutByTabOffsetBefore(index);
247 LayoutByTabOffsetAfter(index);
250 #if !defined(NDEBUG)
251 std::string StackedTabStripLayout::BoundsString() const {
252 std::string result;
253 for (int i = 0; i < view_model_->view_size(); ++i) {
254 if (!result.empty())
255 result += " ";
256 if (i == active_index())
257 result += "[";
258 result += base::IntToString(view_model_->ideal_bounds(i).x());
259 if (i == active_index())
260 result += "]";
262 return result;
264 #endif
266 void StackedTabStripLayout::Reset(int x,
267 int width,
268 int pinned_tab_count,
269 int active_index) {
270 x_ = x;
271 width_ = width;
272 pinned_tab_count_ = pinned_tab_count;
273 pinned_tab_to_non_pinned_tab_ = pinned_tab_count > 0 ?
274 x - ideal_x(pinned_tab_count - 1) : 0;
275 first_tab_x_ = pinned_tab_count > 0 ? ideal_x(0) : x;
276 active_index_ = active_index;
277 ResetToIdealState();
280 void StackedTabStripLayout::ResetToIdealState() {
281 if (tab_count() == pinned_tab_count_)
282 return;
284 if (!requires_stacking()) {
285 SetIdealBoundsAt(pinned_tab_count_, x_);
286 LayoutByTabOffsetAfter(pinned_tab_count_);
287 return;
290 if (normal_tab_count() == 1) {
291 // TODO: might want to shrink the tab here.
292 SetIdealBoundsAt(pinned_tab_count_, 0);
293 return;
296 int available_width = width_ - x_;
297 int leading_count = active_index() - pinned_tab_count_;
298 int trailing_count = tab_count() - active_index();
299 if (width_for_count(leading_count + 1) + max_stacked_width() <
300 available_width) {
301 SetIdealBoundsAt(pinned_tab_count_, x_);
302 LayoutByTabOffsetAfter(pinned_tab_count_);
303 } else if (width_for_count(trailing_count) + max_stacked_width() <
304 available_width) {
305 SetIdealBoundsAt(tab_count() - 1, width_ - size_.width());
306 LayoutByTabOffsetBefore(tab_count() - 1);
307 } else {
308 int index = active_index();
309 do {
310 int stacked_padding =
311 stacked_padding_for_count(index - pinned_tab_count_);
312 SetIdealBoundsAt(index, x_ + stacked_padding);
313 LayoutByTabOffsetAfter(index);
314 LayoutByTabOffsetBefore(index);
315 index--;
316 } while (index >= pinned_tab_count_ && ideal_x(pinned_tab_count_) != x_ &&
317 ideal_x(tab_count() - 1) != width_ - size_.width());
319 AdjustStackedTabs();
322 void StackedTabStripLayout::MakeVisible(int index) {
323 // Currently no need to support tabs openning before |index| visible.
324 if (index <= active_index() || !requires_stacking() || !IsStacked(index))
325 return;
327 int ideal_delta = width_for_count(index - active_index()) - overlap_;
328 if (ideal_x(index) - ideal_x(active_index()) == ideal_delta)
329 return;
331 // First push active index as far to the left as it'll go.
332 int active_x = std::max(GetMinX(active_index()),
333 std::min(ideal_x(index) - ideal_delta,
334 ideal_x(active_index())));
335 SetIdealBoundsAt(active_index(), active_x);
336 LayoutUsingCurrentBefore(active_index());
337 LayoutUsingCurrentAfter(active_index());
338 AdjustStackedTabs();
339 if (ideal_x(index) - ideal_x(active_index()) == ideal_delta)
340 return;
342 // If we get here active_index() is left aligned. Push |index| as far to
343 // the right as possible.
344 int x = std::min(GetMaxX(index), active_x + ideal_delta);
345 SetIdealBoundsAt(index, x);
346 LayoutByTabOffsetAfter(index);
347 for (int next_x = x, i = index - 1; i > active_index(); --i) {
348 next_x = std::max(GetMinXCompressed(i), next_x - tab_offset());
349 SetIdealBoundsAt(i, next_x);
351 LayoutUsingCurrentAfter(active_index());
352 AdjustStackedTabs();
355 int StackedTabStripLayout::ConstrainActiveX(int x) const {
356 return std::min(GetMaxX(active_index()),
357 std::max(GetMinX(active_index()), x));
360 void StackedTabStripLayout::SetActiveBoundsAndLayoutFromActiveTab() {
361 int x = ConstrainActiveX(ideal_x(active_index()));
362 SetIdealBoundsAt(active_index(), x);
363 LayoutUsingCurrentBefore(active_index());
364 LayoutUsingCurrentAfter(active_index());
365 AdjustStackedTabs();
368 void StackedTabStripLayout::LayoutByTabOffsetAfter(int index) {
369 for (int i = index + 1; i < tab_count(); ++i) {
370 int max_x = width_ - size_.width() -
371 stacked_padding_for_count(tab_count() - i - 1);
372 int x = std::min(max_x,
373 view_model_->ideal_bounds(i - 1).x() + tab_offset());
374 SetIdealBoundsAt(i, x);
378 void StackedTabStripLayout::LayoutByTabOffsetBefore(int index) {
379 for (int i = index - 1; i >= pinned_tab_count_; --i) {
380 int min_x = x_ + stacked_padding_for_count(i - pinned_tab_count_);
381 int x = std::max(min_x, ideal_x(i + 1) - (tab_offset()));
382 SetIdealBoundsAt(i, x);
386 void StackedTabStripLayout::LayoutUsingCurrentAfter(int index) {
387 for (int i = index + 1; i < tab_count(); ++i) {
388 int min_x = width_ - width_for_count(tab_count() - i);
389 int x = std::max(min_x,
390 std::min(ideal_x(i), ideal_x(i - 1) + tab_offset()));
391 x = std::min(GetMaxX(i), x);
392 SetIdealBoundsAt(i, x);
396 void StackedTabStripLayout::LayoutUsingCurrentBefore(int index) {
397 for (int i = index - 1; i >= pinned_tab_count_; --i) {
398 int max_x = x_ + width_for_count(i - pinned_tab_count_);
399 if (i > pinned_tab_count_)
400 max_x -= overlap_;
401 max_x = std::min(max_x, ideal_x(i + 1) - stacked_padding_);
402 SetIdealBoundsAt(
403 i, std::min(max_x,
404 std::max(ideal_x(i), ideal_x(i + 1) - tab_offset())));
408 void StackedTabStripLayout::PushTabsAfter(int index, int delta) {
409 for (int i = index + 1; i < tab_count(); ++i)
410 SetIdealBoundsAt(i, std::min(ideal_x(i) + delta, GetMaxDragX(i)));
413 void StackedTabStripLayout::PushTabsBefore(int index, int delta) {
414 for (int i = index - 1; i > pinned_tab_count_; --i)
415 SetIdealBoundsAt(i, std::max(ideal_x(i) - delta, GetMinDragX(i)));
418 void StackedTabStripLayout::LayoutForDragAfter(int index) {
419 for (int i = index + 1; i < tab_count(); ++i) {
420 const int min_x = ideal_x(i - 1) + stacked_padding_;
421 const int max_x = ideal_x(i - 1) + tab_offset();
422 SetIdealBoundsAt(
423 i, std::max(min_x, std::min(ideal_x(i), max_x)));
427 void StackedTabStripLayout::LayoutForDragBefore(int index) {
428 for (int i = index - 1; i >= pinned_tab_count_; --i) {
429 const int max_x = ideal_x(i + 1) - stacked_padding_;
430 const int min_x = ideal_x(i + 1) - tab_offset();
431 SetIdealBoundsAt(
432 i, std::max(min_x, std::min(ideal_x(i), max_x)));
435 if (pinned_tab_count_ == 0)
436 return;
438 // Pull in the pinned tabs.
439 const int delta = (pinned_tab_count_ > 1) ? ideal_x(1) - ideal_x(0) : 0;
440 for (int i = pinned_tab_count_ - 1; i >= 0; --i) {
441 gfx::Rect pinned_bounds(view_model_->ideal_bounds(i));
442 if (i == pinned_tab_count_ - 1)
443 pinned_bounds.set_x(ideal_x(i + 1) - pinned_tab_to_non_pinned_tab_);
444 else
445 pinned_bounds.set_x(ideal_x(i + 1) - delta);
446 view_model_->set_ideal_bounds(i, pinned_bounds);
450 void StackedTabStripLayout::ExpandTabsBefore(int index, int delta) {
451 for (int i = index - 1; i >= pinned_tab_count_ && delta > 0; --i) {
452 const int max_x = ideal_x(active_index()) -
453 stacked_padding_for_count(active_index() - i);
454 int to_resize = std::min(delta, max_x - ideal_x(i));
456 if (to_resize <= 0)
457 continue;
458 SetIdealBoundsAt(i, ideal_x(i) + to_resize);
459 delta -= to_resize;
460 LayoutForDragBefore(i);
464 void StackedTabStripLayout::ExpandTabsAfter(int index, int delta) {
465 if (index == tab_count() - 1)
466 return; // Nothing to expand.
468 for (int i = index + 1; i < tab_count() && delta > 0; ++i) {
469 const int min_compressed =
470 ideal_x(active_index()) + stacked_padding_for_count(i - active_index());
471 const int to_resize = std::min(ideal_x(i) - min_compressed, delta);
472 if (to_resize <= 0)
473 continue;
474 SetIdealBoundsAt(i, ideal_x(i) - to_resize);
475 delta -= to_resize;
476 LayoutForDragAfter(i);
480 void StackedTabStripLayout::AdjustStackedTabs() {
481 if (!requires_stacking() || tab_count() <= pinned_tab_count_ + 1)
482 return;
484 AdjustLeadingStackedTabs();
485 AdjustTrailingStackedTabs();
488 void StackedTabStripLayout::AdjustLeadingStackedTabs() {
489 int index = pinned_tab_count_ + 1;
490 while (index < active_index() &&
491 ideal_x(index) - ideal_x(index - 1) <= stacked_padding_ &&
492 ideal_x(index) <= x_ + max_stacked_width()) {
493 index++;
495 if (ideal_x(index) - ideal_x(index - 1) <= stacked_padding_ &&
496 ideal_x(index) <= x_ + max_stacked_width()) {
497 index++;
499 if (index <= pinned_tab_count_ + max_stacked_count_ - 1)
500 return;
501 int max_stacked = index;
502 int x = x_;
503 index = pinned_tab_count_;
504 for (; index < max_stacked - max_stacked_count_ - 1; ++index)
505 SetIdealBoundsAt(index, x);
506 for (; index < max_stacked; ++index, x += stacked_padding_)
507 SetIdealBoundsAt(index, x);
510 void StackedTabStripLayout::AdjustTrailingStackedTabs() {
511 int index = tab_count() - 1;
512 int max_stacked_x = width_ - size_.width() - max_stacked_width();
513 while (index > active_index() &&
514 ideal_x(index) - ideal_x(index - 1) <= stacked_padding_ &&
515 ideal_x(index - 1) >= max_stacked_x) {
516 index--;
518 if (index > active_index() &&
519 ideal_x(index) - ideal_x(index - 1) <= stacked_padding_ &&
520 ideal_x(index - 1) >= max_stacked_x) {
521 index--;
523 if (index >= tab_count() - max_stacked_count_)
524 return;
525 int first_stacked = index;
526 int x = width_ - size_.width() -
527 std::min(tab_count() - first_stacked, max_stacked_count_) *
528 stacked_padding_;
529 for (; index < first_stacked + max_stacked_count_;
530 ++index, x += stacked_padding_) {
531 SetIdealBoundsAt(index, x);
533 for (; index < tab_count(); ++index)
534 SetIdealBoundsAt(index, x);
537 void StackedTabStripLayout::SetIdealBoundsAt(int index, int x) {
538 view_model_->set_ideal_bounds(index, gfx::Rect(gfx::Point(x, 0), size_));
541 int StackedTabStripLayout::GetMinX(int index) const {
542 int leading_count = index - pinned_tab_count_;
543 int trailing_count = tab_count() - index;
544 return std::max(x_ + stacked_padding_for_count(leading_count),
545 width_ - width_for_count(trailing_count));
548 int StackedTabStripLayout::GetMaxX(int index) const {
549 int leading_count = index - pinned_tab_count_;
550 int trailing_count = tab_count() - index - 1;
551 int trailing_offset = stacked_padding_for_count(trailing_count);
552 int leading_size = width_for_count(leading_count) + x_;
553 if (leading_count > 0)
554 leading_size -= overlap_;
555 return std::min(width_ - trailing_offset - size_.width(), leading_size);
558 int StackedTabStripLayout::GetMinDragX(int index) const {
559 return x_ + stacked_padding_for_count(index - pinned_tab_count_);
562 int StackedTabStripLayout::GetMaxDragX(int index) const {
563 const int trailing_offset =
564 stacked_padding_for_count(tab_count() - index - 1);
565 return width_ - trailing_offset - size_.width();
568 int StackedTabStripLayout::GetMinXCompressed(int index) const {
569 DCHECK_GT(index, active_index());
570 return std::max(
571 width_ - width_for_count(tab_count() - index),
572 ideal_x(active_index()) +
573 stacked_padding_for_count(index - active_index()));