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"
11 #include "content/public/browser/user_metrics.h"
13 using base::UserMetricsAction
;
15 StackedTabStripLayout::StackedTabStripLayout(const gfx::Size
& size
,
18 int max_stacked_count
,
19 views::ViewModelBase
* view_model
)
22 stacked_padding_(stacked_padding
),
23 max_stacked_count_(max_stacked_count
),
24 view_model_(view_model
),
28 pinned_tab_to_non_pinned_tab_(0),
33 StackedTabStripLayout::~StackedTabStripLayout() {
36 void StackedTabStripLayout::SetXAndPinnedCount(int x
, int pinned_tab_count
) {
39 pinned_tab_count_
= pinned_tab_count
;
40 pinned_tab_to_non_pinned_tab_
= 0;
41 if (!requires_stacking() || tab_count() == pinned_tab_count
) {
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
) {
59 if (!requires_stacking()) {
63 SetActiveBoundsAndLayoutFromActiveTab();
66 void StackedTabStripLayout::SetActiveIndex(int index
) {
67 int old
= active_index();
68 active_index_
= index
;
69 if (old
== active_index() || !requires_stacking())
71 SetIdealBoundsAt(active_index(), ConstrainActiveX(ideal_x(active_index())));
72 LayoutByTabOffsetBefore(active_index());
73 LayoutByTabOffsetAfter(active_index());
77 void StackedTabStripLayout::DragActiveTab(int delta
) {
78 if (delta
== 0 || !requires_stacking())
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());
88 } else if (delta
< 0 && initial_x
== GetMaxX(active_index())) {
89 LayoutByTabOffsetBefore(active_index());
93 std::min(initial_x
+ delta
, GetMaxDragX(active_index())) :
94 std::max(initial_x
+ delta
, GetMinDragX(active_index()));
96 SetIdealBoundsAt(active_index(), x
);
98 PushTabsAfter(active_index(), (x
- initial_x
));
99 LayoutForDragBefore(active_index());
101 PushTabsBefore(active_index(), initial_x
- x
);
102 LayoutForDragAfter(active_index());
104 delta
-= (x
- initial_x
);
107 ExpandTabsBefore(active_index(), delta
);
109 ExpandTabsAfter(active_index(), -delta
);
113 void StackedTabStripLayout::SizeToFit() {
117 if (!requires_stacking()) {
122 if (ideal_x(0) != first_tab_x_
) {
123 // Tabs have been dragged to the right. Pull in the tabs from left to right
125 int delta
= ideal_x(0) - first_tab_x_
;
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
);
141 const int max_x
= width_
- size_
.width();
142 if (ideal_x(tab_count() - 1) == max_x
)
145 // Tabs have been dragged to the left. Pull in tabs from right to left to fill
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());
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
)
160 if (add_types
& kAddTypePinned
)
163 if (!requires_stacking() || normal_tab_count() <= 1) {
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());
174 if ((add_types
& kAddTypeActive
) == 0)
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_
)
183 bool removed_pinned_tab
= index
< pinned_tab_count_
;
184 if (removed_pinned_tab
) {
186 DCHECK_GE(pinned_tab_count_
, 0);
188 int delta
= start_x
- x_
;
190 if (!requires_stacking()) {
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();
202 void StackedTabStripLayout::MoveTab(int from
,
204 int new_active_index
,
206 int pinned_tab_count
) {
208 pinned_tab_count_
= pinned_tab_count
;
209 active_index_
= new_active_index
;
210 if (!requires_stacking() || tab_count() == pinned_tab_count_
) {
213 SetIdealBoundsAt(active_index(),
214 ConstrainActiveX(ideal_x(active_index())));
215 LayoutByTabOffsetAfter(active_index());
216 LayoutByTabOffsetBefore(active_index());
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_
)
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())
237 const int index
= active_index();
238 if (index
<= pinned_tab_count_
)
241 x
= std::min(GetMaxX(index
), std::max(x
, GetMinX(index
)));
242 if (x
== ideal_x(index
))
245 SetIdealBoundsAt(index
, x
);
246 LayoutByTabOffsetBefore(index
);
247 LayoutByTabOffsetAfter(index
);
251 std::string
StackedTabStripLayout::BoundsString() const {
253 for (int i
= 0; i
< view_model_
->view_size(); ++i
) {
256 if (i
== active_index())
258 result
+= base::IntToString(view_model_
->ideal_bounds(i
).x());
259 if (i
== active_index())
266 void StackedTabStripLayout::Reset(int x
,
268 int pinned_tab_count
,
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
;
280 void StackedTabStripLayout::ResetToIdealState() {
281 if (tab_count() == pinned_tab_count_
)
284 if (!requires_stacking()) {
285 SetIdealBoundsAt(pinned_tab_count_
, x_
);
286 LayoutByTabOffsetAfter(pinned_tab_count_
);
290 if (normal_tab_count() == 1) {
291 // TODO: might want to shrink the tab here.
292 SetIdealBoundsAt(pinned_tab_count_
, 0);
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() <
301 SetIdealBoundsAt(pinned_tab_count_
, x_
);
302 LayoutByTabOffsetAfter(pinned_tab_count_
);
303 } else if (width_for_count(trailing_count
) + max_stacked_width() <
305 SetIdealBoundsAt(tab_count() - 1, width_
- size_
.width());
306 LayoutByTabOffsetBefore(tab_count() - 1);
308 int index
= active_index();
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
);
316 } while (index
>= pinned_tab_count_
&& ideal_x(pinned_tab_count_
) != x_
&&
317 ideal_x(tab_count() - 1) != width_
- size_
.width());
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
))
327 int ideal_delta
= width_for_count(index
- active_index()) - overlap_
;
328 if (ideal_x(index
) - ideal_x(active_index()) == ideal_delta
)
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());
339 if (ideal_x(index
) - ideal_x(active_index()) == ideal_delta
)
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());
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());
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_
)
401 max_x
= std::min(max_x
, ideal_x(i
+ 1) - stacked_padding_
);
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();
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();
432 i
, std::max(min_x
, std::min(ideal_x(i
), max_x
)));
435 if (pinned_tab_count_
== 0)
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_
);
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
));
458 SetIdealBoundsAt(i
, ideal_x(i
) + 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
);
474 SetIdealBoundsAt(i
, ideal_x(i
) - to_resize
);
476 LayoutForDragAfter(i
);
480 void StackedTabStripLayout::AdjustStackedTabs() {
481 if (!requires_stacking() || tab_count() <= pinned_tab_count_
+ 1)
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()) {
495 if (ideal_x(index
) - ideal_x(index
- 1) <= stacked_padding_
&&
496 ideal_x(index
) <= x_
+ max_stacked_width()) {
499 if (index
<= pinned_tab_count_
+ max_stacked_count_
- 1)
501 int max_stacked
= index
;
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
) {
518 if (index
> active_index() &&
519 ideal_x(index
) - ideal_x(index
- 1) <= stacked_padding_
&&
520 ideal_x(index
- 1) >= max_stacked_x
) {
523 if (index
>= tab_count() - max_stacked_count_
)
525 int first_stacked
= index
;
526 int x
= width_
- size_
.width() -
527 std::min(tab_count() - first_stacked
, max_stacked_count_
) *
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());
571 width_
- width_for_count(tab_count() - index
),
572 ideal_x(active_index()) +
573 stacked_padding_for_count(index
- active_index()));