Disable view source for Developer Tools.
[chromium-blink-merge.git] / chrome / browser / ui / views / tabs / stacked_tab_strip_layout.cc
blob88a83cec65fb8545d2f2c31ab1e0340098eb8d45
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"
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) {
30 StackedTabStripLayout::~StackedTabStripLayout() {
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;
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);
46 SetIdealBoundsAt(active_index(), ConstrainActiveX(ideal_x(active_index())));
47 LayoutByTabOffsetAfter(active_index());
48 LayoutByTabOffsetBefore(active_index());
51 void StackedTabStripLayout::SetWidth(int width) {
52 if (width_ == width)
53 return;
55 width_ = width;
56 if (!requires_stacking()) {
57 ResetToIdealState();
58 return;
60 SetActiveBoundsAndLayoutFromActiveTab();
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();
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();
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());
99 delta -= (x - initial_x);
101 if (delta > 0)
102 ExpandTabsBefore(active_index(), delta);
103 else if (delta < 0)
104 ExpandTabsAfter(active_index(), -delta);
105 AdjustStackedTabs();
108 void StackedTabStripLayout::SizeToFit() {
109 if (!tab_count())
110 return;
112 if (!requires_stacking()) {
113 ResetToIdealState();
114 return;
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);
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;
132 AdjustStackedTabs();
133 return;
136 const int max_x = width_ - size_.width();
137 if (ideal_x(tab_count() - 1) == max_x)
138 return;
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());
147 AdjustStackedTabs();
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;
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();
169 if ((add_types & kAddTypeActive) == 0)
170 MakeVisible(index);
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);
183 int delta = start_x - x_;
184 x_ = start_x;
185 if (!requires_stacking()) {
186 ResetToIdealState();
187 return;
189 if (removed_mini_tab) {
190 for (int i = mini_tab_count_; i < tab_count(); ++i)
191 SetIdealBoundsAt(i, ideal_x(i) + delta);
193 SetActiveBoundsAndLayoutFromActiveTab();
194 AdjustStackedTabs();
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();
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;
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();
228 void StackedTabStripLayout::SetActiveTabLocation(int x) {
229 if (!requires_stacking())
230 return;
232 const int index = active_index();
233 if (index <= mini_tab_count_)
234 return;
236 x = std::min(GetMaxX(index), std::max(x, GetMinX(index)));
237 if (x == ideal_x(index))
238 return;
240 SetIdealBoundsAt(index, x);
241 LayoutByTabOffsetBefore(index);
242 LayoutByTabOffsetAfter(index);
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 += "]";
257 return result;
259 #endif
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();
275 void StackedTabStripLayout::ResetToIdealState() {
276 if (tab_count() == mini_tab_count_)
277 return;
279 if (!requires_stacking()) {
280 SetIdealBoundsAt(mini_tab_count_, x_);
281 LayoutByTabOffsetAfter(mini_tab_count_);
282 return;
285 if (normal_tab_count() == 1) {
286 // TODO: might want to shrink the tab here.
287 SetIdealBoundsAt(mini_tab_count_, 0);
288 return;
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());
313 AdjustStackedTabs();
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;
321 int ideal_delta = width_for_count(index - active_index()) + padding_;
322 if (ideal_x(index) - ideal_x(active_index()) == ideal_delta)
323 return;
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;
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);
345 LayoutUsingCurrentAfter(active_index());
346 AdjustStackedTabs();
349 int StackedTabStripLayout::ConstrainActiveX(int x) const {
350 return std::min(GetMaxX(active_index()),
351 std::max(GetMinX(active_index()), x));
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();
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);
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);
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);
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())));
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)));
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)));
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)));
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)));
429 if (mini_tab_count_ == 0)
430 return;
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);
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));
450 if (to_resize <= 0)
451 continue;
452 SetIdealBoundsAt(i, ideal_x(i) + to_resize);
453 delta -= to_resize;
454 LayoutForDragBefore(i);
458 void StackedTabStripLayout::ExpandTabsAfter(int index, int delta) {
459 if (index == tab_count() - 1)
460 return; // Nothing to expand.
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);
474 void StackedTabStripLayout::AdjustStackedTabs() {
475 if (!requires_stacking() || tab_count() <= mini_tab_count_ + 1)
476 return;
478 AdjustLeadingStackedTabs();
479 AdjustTrailingStackedTabs();
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++;
489 if (ideal_x(index) - ideal_x(index - 1) <= stacked_padding_ &&
490 ideal_x(index) <= x_ + max_stacked_width()) {
491 index++;
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);
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--;
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--;
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);
527 for (; index < tab_count(); ++index)
528 SetIdealBoundsAt(index, x);
531 void StackedTabStripLayout::SetIdealBoundsAt(int index, int x) {
532 view_model_->set_ideal_bounds(index, gfx::Rect(gfx::Point(x, 0), size_));
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));
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);
552 int StackedTabStripLayout::GetMinDragX(int index) const {
553 return x_ + stacked_padding_for_count(index - mini_tab_count_);
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();
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()));