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 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) { 28 } 29 30 StackedTabStripLayout::~StackedTabStripLayout() { 31 } 32 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; 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 51 void 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 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(); 72 } 73 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(); 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 108 void 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 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; 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 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); 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 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(); 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 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(); 226 } 227 228 void 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) 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 += "]"; 256 } 257 return result; 258 } 259 #endif 260 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(); 273 } 274 275 void 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 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; 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 349 int StackedTabStripLayout::ConstrainActiveX(int x) const { 350 return std::min(GetMaxX(active_index()), 351 std::max(GetMinX(active_index()), x)); 352 } 353 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(); 360 } 361 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); 369 } 370 } 371 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); 377 } 378 } 379 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); 387 } 388 } 389 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()))); 399 } 400 } 401 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))); 405 } 406 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))); 410 } 411 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))); 418 } 419 } 420 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))); 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 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)); 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 458 void 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 474 void StackedTabStripLayout::AdjustStackedTabs() { 475 if (!requires_stacking() || tab_count() <= mini_tab_count_ + 1) 476 return; 477 478 AdjustLeadingStackedTabs(); 479 AdjustTrailingStackedTabs(); 480 } 481 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++; 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 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--; 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 531 void StackedTabStripLayout::SetIdealBoundsAt(int index, int x) { 532 view_model_->set_ideal_bounds(index, gfx::Rect(gfx::Point(x, 0), size_)); 533 } 534 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)); 540 } 541 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); 550 } 551 552 int StackedTabStripLayout::GetMinDragX(int index) const { 553 return x_ + stacked_padding_for_count(index - mini_tab_count_); 554 } 555 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(); 560 } 561 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())); 568 } 569