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"
9 #include "base/logging.h"
10 #include "base/strings/string_number_conversions.h"
12 StackedTabStripLayout::StackedTabStripLayout(const gfx::Size
& size
,
15 int max_stacked_count
,
16 views::ViewModelBase
* view_model
)
19 stacked_padding_(stacked_padding
),
20 max_stacked_count_(max_stacked_count
),
21 view_model_(view_model
),
25 mini_tab_to_non_mini_tab_(0),
30 StackedTabStripLayout::~StackedTabStripLayout() {
33 void StackedTabStripLayout::SetXAndMiniCount(int x
, int mini_tab_count
) {
36 mini_tab_count_
= mini_tab_count
;
37 mini_tab_to_non_mini_tab_
= 0;
38 if (!requires_stacking() || tab_count() == mini_tab_count
) {
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
) {
56 if (!requires_stacking()) {
60 SetActiveBoundsAndLayoutFromActiveTab();
63 void StackedTabStripLayout::SetActiveIndex(int index
) {
64 int old
= active_index();
65 active_index_
= index
;
66 if (old
== active_index() || !requires_stacking())
68 SetIdealBoundsAt(active_index(), ConstrainActiveX(ideal_x(active_index())));
69 LayoutByTabOffsetBefore(active_index());
70 LayoutByTabOffsetAfter(active_index());
74 void StackedTabStripLayout::DragActiveTab(int delta
) {
75 if (delta
== 0 || !requires_stacking())
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());
83 } else if (delta
< 0 && initial_x
== GetMaxX(active_index())) {
84 LayoutByTabOffsetBefore(active_index());
88 std::min(initial_x
+ delta
, GetMaxDragX(active_index())) :
89 std::max(initial_x
+ delta
, GetMinDragX(active_index()));
91 SetIdealBoundsAt(active_index(), x
);
93 PushTabsAfter(active_index(), (x
- initial_x
));
94 LayoutForDragBefore(active_index());
96 PushTabsBefore(active_index(), initial_x
- x
);
97 LayoutForDragAfter(active_index());
99 delta
-= (x
- initial_x
);
102 ExpandTabsBefore(active_index(), delta
);
104 ExpandTabsAfter(active_index(), -delta
);
108 void StackedTabStripLayout::SizeToFit() {
112 if (!requires_stacking()) {
117 if (ideal_x(0) != first_tab_x_
) {
118 // Tabs have been dragged to the right. Pull in the tabs from left to right
120 int delta
= ideal_x(0) - first_tab_x_
;
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
);
136 const int max_x
= width_
- size_
.width();
137 if (ideal_x(tab_count() - 1) == max_x
)
140 // Tabs have been dragged to the left. Pull in tabs from right to left to fill
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());
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
)
155 if (add_types
& kAddTypeMini
)
158 if (!requires_stacking() || normal_tab_count() <= 1) {
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());
169 if ((add_types
& kAddTypeActive
) == 0)
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_
)
178 bool removed_mini_tab
= index
< mini_tab_count_
;
179 if (removed_mini_tab
) {
181 DCHECK_GE(mini_tab_count_
, 0);
183 int delta
= start_x
- x_
;
185 if (!requires_stacking()) {
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();
197 void StackedTabStripLayout::MoveTab(int from
,
199 int new_active_index
,
201 int mini_tab_count
) {
203 mini_tab_count_
= mini_tab_count
;
204 active_index_
= new_active_index
;
205 if (!requires_stacking() || tab_count() == mini_tab_count_
) {
208 SetIdealBoundsAt(active_index(),
209 ConstrainActiveX(ideal_x(active_index())));
210 LayoutByTabOffsetAfter(active_index());
211 LayoutByTabOffsetBefore(active_index());
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_
)
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())
232 const int index
= active_index();
233 if (index
<= mini_tab_count_
)
236 x
= std::min(GetMaxX(index
), std::max(x
, GetMinX(index
)));
237 if (x
== ideal_x(index
))
240 SetIdealBoundsAt(index
, x
);
241 LayoutByTabOffsetBefore(index
);
242 LayoutByTabOffsetAfter(index
);
246 std::string
StackedTabStripLayout::BoundsString() const {
248 for (int i
= 0; i
< view_model_
->view_size(); ++i
) {
251 if (i
== active_index())
253 result
+= base::IntToString(view_model_
->ideal_bounds(i
).x());
254 if (i
== active_index())
261 void StackedTabStripLayout::Reset(int x
,
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
;
275 void StackedTabStripLayout::ResetToIdealState() {
276 if (tab_count() == mini_tab_count_
)
279 if (!requires_stacking()) {
280 SetIdealBoundsAt(mini_tab_count_
, x_
);
281 LayoutByTabOffsetAfter(mini_tab_count_
);
285 if (normal_tab_count() == 1) {
286 // TODO: might want to shrink the tab here.
287 SetIdealBoundsAt(mini_tab_count_
, 0);
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() <
296 SetIdealBoundsAt(mini_tab_count_
, x_
);
297 LayoutByTabOffsetAfter(mini_tab_count_
);
298 } else if (width_for_count(trailing_count
) + max_stacked_width() <
300 SetIdealBoundsAt(tab_count() - 1, width_
- size_
.width());
301 LayoutByTabOffsetBefore(tab_count() - 1);
303 int index
= active_index();
305 int stacked_padding
= stacked_padding_for_count(index
- mini_tab_count_
);
306 SetIdealBoundsAt(index
, x_
+ stacked_padding
);
307 LayoutByTabOffsetAfter(index
);
308 LayoutByTabOffsetBefore(index
);
310 } while (index
>= mini_tab_count_
&& ideal_x(mini_tab_count_
) != x_
&&
311 ideal_x(tab_count() - 1) != width_
- size_
.width());
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
))
321 int ideal_delta
= width_for_count(index
- active_index()) + padding_
;
322 if (ideal_x(index
) - ideal_x(active_index()) == ideal_delta
)
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());
333 if (ideal_x(index
) - ideal_x(active_index()) == ideal_delta
)
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());
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());
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_
)
395 max_x
= std::min(max_x
, ideal_x(i
+ 1) - stacked_padding_
);
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();
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();
426 i
, std::max(min_x
, std::min(ideal_x(i
), max_x
)));
429 if (mini_tab_count_
== 0)
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_
);
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
));
452 SetIdealBoundsAt(i
, ideal_x(i
) + 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
);
468 SetIdealBoundsAt(i
, ideal_x(i
) - to_resize
);
470 LayoutForDragAfter(i
);
474 void StackedTabStripLayout::AdjustStackedTabs() {
475 if (!requires_stacking() || tab_count() <= mini_tab_count_
+ 1)
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()) {
489 if (ideal_x(index
) - ideal_x(index
- 1) <= stacked_padding_
&&
490 ideal_x(index
) <= x_
+ max_stacked_width()) {
493 if (index
<= mini_tab_count_
+ max_stacked_count_
- 1)
495 int max_stacked
= index
;
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
) {
512 if (index
> active_index() &&
513 ideal_x(index
) - ideal_x(index
- 1) <= stacked_padding_
&&
514 ideal_x(index
- 1) >= max_stacked_x
) {
517 if (index
>= tab_count() - max_stacked_count_
)
519 int first_stacked
= index
;
520 int x
= width_
- size_
.width() -
521 std::min(tab_count() - first_stacked
, max_stacked_count_
) *
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());
565 width_
- width_for_count(tab_count() - index
),
566 ideal_x(active_index()) +
567 stacked_padding_for_count(index
- active_index()));