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/panels/docked_panel_collection.h" 6 7 #include <math.h> 8 9 #include <algorithm> 10 #include <vector> 11 12 #include "base/auto_reset.h" 13 #include "base/bind.h" 14 #include "base/logging.h" 15 #include "base/message_loop/message_loop.h" 16 #include "chrome/browser/chrome_notification_types.h" 17 #include "chrome/browser/ui/panels/panel_drag_controller.h" 18 #include "chrome/browser/ui/panels/panel_manager.h" 19 #include "chrome/browser/ui/panels/panel_mouse_watcher.h" 20 #include "content/public/browser/notification_service.h" 21 #include "content/public/browser/notification_source.h" 22 23 namespace { 24 // Width of spacing around panel collection and the left/right edges of the 25 // screen. 26 const int kPanelCollectionLeftMargin = 6; 27 const int kPanelCollectionRightMargin = 24; 28 29 // Occasionally some system, like Windows, might not bring up or down the bottom 30 // bar when the mouse enters or leaves the bottom screen area. This is the 31 // maximum time we will wait for the bottom bar visibility change notification. 32 // After the time expires, we bring up/down the titlebars as planned. 33 const int kMaxDelayWaitForBottomBarVisibilityChangeMs = 1000; 34 35 // See usage below. 36 #if defined(TOOLKIT_GTK) 37 const int kDelayBeforeCollapsingFromTitleOnlyStateMs = 2000; 38 #else 39 const int kDelayBeforeCollapsingFromTitleOnlyStateMs = 0; 40 #endif 41 42 // After focus changed, one panel lost active status, another got it, 43 // we refresh layout with a delay. 44 const int kRefreshLayoutAfterActivePanelChangeDelayMs = 600; // arbitrary 45 46 // As we refresh panel positions, some or all panels may move. We make sure 47 // we do not animate too many panels at once as this tends to perform poorly. 48 const int kNumPanelsToAnimateSimultaneously = 3; 49 50 } // namespace 51 52 DockedPanelCollection::DockedPanelCollection(PanelManager* panel_manager) 53 : PanelCollection(PanelCollection::DOCKED), 54 panel_manager_(panel_manager), 55 minimized_panel_count_(0), 56 are_titlebars_up_(false), 57 minimizing_all_(false), 58 delayed_titlebar_action_(NO_ACTION), 59 titlebar_action_factory_(this), 60 refresh_action_factory_(this) { 61 panel_manager_->display_settings_provider()->AddDesktopBarObserver(this); 62 OnDisplayChanged(); 63 } 64 65 DockedPanelCollection::~DockedPanelCollection() { 66 DCHECK(panels_.empty()); 67 DCHECK_EQ(0, minimized_panel_count_); 68 panel_manager_->display_settings_provider()->RemoveDesktopBarObserver(this); 69 } 70 71 void DockedPanelCollection::OnDisplayChanged() { 72 work_area_ = 73 panel_manager_->display_settings_provider()->GetPrimaryWorkArea(); 74 work_area_.set_x(work_area_.x() + kPanelCollectionLeftMargin); 75 work_area_.set_width(work_area_.width() - 76 kPanelCollectionLeftMargin - kPanelCollectionRightMargin); 77 78 if (panels_.empty()) 79 return; 80 81 for (Panels::const_iterator iter = panels_.begin(); 82 iter != panels_.end(); ++iter) { 83 (*iter)->LimitSizeToWorkArea(work_area_); 84 } 85 86 RefreshLayout(); 87 } 88 89 void DockedPanelCollection::AddPanel(Panel* panel, 90 PositioningMask positioning_mask) { 91 // This method does not handle minimized panels. 92 DCHECK_EQ(Panel::EXPANDED, panel->expansion_state()); 93 94 DCHECK(panel->initialized()); 95 DCHECK_NE(this, panel->collection()); 96 panel->set_collection(this); 97 98 bool default_position = (positioning_mask & KNOWN_POSITION) == 0; 99 bool update_bounds = (positioning_mask & DO_NOT_UPDATE_BOUNDS) == 0; 100 101 if (default_position) { 102 gfx::Size full_size = panel->full_size(); 103 gfx::Point pt = GetDefaultPositionForPanel(full_size); 104 panel->SetPanelBounds(gfx::Rect(pt, full_size)); 105 panels_.push_back(panel); 106 } else { 107 DCHECK(update_bounds); 108 int x = panel->GetBounds().x(); 109 Panels::iterator iter = panels_.begin(); 110 for (; iter != panels_.end(); ++iter) 111 if (x > (*iter)->GetBounds().x()) 112 break; 113 panels_.insert(iter, panel); 114 } 115 116 if (update_bounds) { 117 if ((positioning_mask & DELAY_LAYOUT_REFRESH) != 0) 118 ScheduleLayoutRefresh(); 119 else 120 RefreshLayout(); 121 } 122 } 123 124 gfx::Point DockedPanelCollection::GetDefaultPositionForPanel( 125 const gfx::Size& full_size) const { 126 int x = 0; 127 if (!panels_.empty() && 128 panels_.back()->GetBounds().x() < work_area_.x()) { 129 // Panels go off screen. Make sure the default position will place 130 // the panel in view. 131 Panels::const_reverse_iterator iter = panels_.rbegin(); 132 for (; iter != panels_.rend(); ++iter) { 133 if ((*iter)->GetBounds().x() >= work_area_.x()) { 134 x = (*iter)->GetBounds().x(); 135 break; 136 } 137 } 138 // At least one panel should fit on the screen. 139 DCHECK(x > work_area_.x()); 140 } else { 141 x = std::max(GetRightMostAvailablePosition() - full_size.width(), 142 work_area_.x()); 143 } 144 return gfx::Point(x, work_area_.bottom() - full_size.height()); 145 } 146 147 int DockedPanelCollection::StartingRightPosition() const { 148 return work_area_.right(); 149 } 150 151 int DockedPanelCollection::GetRightMostAvailablePosition() const { 152 return panels_.empty() ? StartingRightPosition() : 153 (panels_.back()->GetBounds().x() - kPanelsHorizontalSpacing); 154 } 155 156 void DockedPanelCollection::RemovePanel(Panel* panel, RemovalReason reason) { 157 DCHECK_EQ(this, panel->collection()); 158 panel->set_collection(NULL); 159 160 // Optimize for the common case of removing the last panel. 161 DCHECK(!panels_.empty()); 162 if (panels_.back() == panel) { 163 panels_.pop_back(); 164 165 // Update the saved panel placement if needed. This is because 166 // we might remove |saved_panel_placement_.left_panel|. 167 if (saved_panel_placement_.panel && 168 saved_panel_placement_.left_panel == panel) 169 saved_panel_placement_.left_panel = NULL; 170 171 } else { 172 Panels::iterator iter = find(panels_.begin(), panels_.end(), panel); 173 DCHECK(iter != panels_.end()); 174 iter = panels_.erase(iter); 175 176 // Update the saved panel placement if needed. This is because 177 // we might remove |saved_panel_placement_.left_panel|. 178 if (saved_panel_placement_.panel && 179 saved_panel_placement_.left_panel == panel) 180 saved_panel_placement_.left_panel = *iter; 181 } 182 183 if (panel->expansion_state() != Panel::EXPANDED) 184 UpdateMinimizedPanelCount(); 185 186 RefreshLayout(); 187 } 188 189 void DockedPanelCollection::SavePanelPlacement(Panel* panel) { 190 DCHECK(!saved_panel_placement_.panel); 191 192 saved_panel_placement_.panel = panel; 193 194 // To recover panel to its original placement, we only need to track the panel 195 // that is placed after it. 196 Panels::iterator iter = find(panels_.begin(), panels_.end(), panel); 197 DCHECK(iter != panels_.end()); 198 ++iter; 199 saved_panel_placement_.left_panel = (iter == panels_.end()) ? NULL : *iter; 200 } 201 202 void DockedPanelCollection::RestorePanelToSavedPlacement() { 203 DCHECK(saved_panel_placement_.panel); 204 205 Panel* panel = saved_panel_placement_.panel; 206 207 // Find next panel after this panel. 208 Panels::iterator iter = std::find(panels_.begin(), panels_.end(), panel); 209 DCHECK(iter != panels_.end()); 210 Panels::iterator next_iter = iter; 211 next_iter++; 212 Panel* next_panel = (next_iter == panels_.end()) ? NULL : *iter; 213 214 // Restoring is only needed when this panel is not in the right position. 215 if (next_panel != saved_panel_placement_.left_panel) { 216 // Remove this panel from its current position. 217 panels_.erase(iter); 218 219 // Insert this panel into its previous position. 220 if (saved_panel_placement_.left_panel) { 221 Panels::iterator iter_to_insert_before = std::find(panels_.begin(), 222 panels_.end(), saved_panel_placement_.left_panel); 223 DCHECK(iter_to_insert_before != panels_.end()); 224 panels_.insert(iter_to_insert_before, panel); 225 } else { 226 panels_.push_back(panel); 227 } 228 } 229 230 RefreshLayout(); 231 232 DiscardSavedPanelPlacement(); 233 } 234 235 void DockedPanelCollection::DiscardSavedPanelPlacement() { 236 DCHECK(saved_panel_placement_.panel); 237 saved_panel_placement_.panel = NULL; 238 saved_panel_placement_.left_panel = NULL; 239 } 240 241 panel::Resizability DockedPanelCollection::GetPanelResizability( 242 const Panel* panel) const { 243 return (panel->expansion_state() == Panel::EXPANDED) ? 244 panel::RESIZABLE_EXCEPT_BOTTOM : panel::NOT_RESIZABLE; 245 } 246 247 void DockedPanelCollection::OnPanelResizedByMouse(Panel* panel, 248 const gfx::Rect& new_bounds) { 249 DCHECK_EQ(this, panel->collection()); 250 panel->set_full_size(new_bounds.size()); 251 } 252 253 void DockedPanelCollection::OnPanelExpansionStateChanged(Panel* panel) { 254 gfx::Rect panel_bounds = panel->GetBounds(); 255 AdjustPanelBoundsPerExpansionState(panel, &panel_bounds); 256 panel->SetPanelBounds(panel_bounds); 257 258 UpdateMinimizedPanelCount(); 259 260 // Ensure minimized panel does not get the focus. If minimizing all, 261 // the active panel will be deactivated once when all panels are minimized 262 // rather than per minimized panel. 263 if (panel->expansion_state() != Panel::EXPANDED && !minimizing_all_ && 264 panel->IsActive()) { 265 panel->Deactivate(); 266 // The layout will refresh itself in response 267 // to (de)activation notification. 268 } 269 } 270 271 void DockedPanelCollection::AdjustPanelBoundsPerExpansionState(Panel* panel, 272 gfx::Rect* bounds) { 273 Panel::ExpansionState expansion_state = panel->expansion_state(); 274 switch (expansion_state) { 275 case Panel::EXPANDED: 276 bounds->set_height(panel->full_size().height()); 277 278 break; 279 case Panel::TITLE_ONLY: 280 bounds->set_height(panel->TitleOnlyHeight()); 281 282 break; 283 case Panel::MINIMIZED: 284 bounds->set_height(panel::kMinimizedPanelHeight); 285 286 break; 287 default: 288 NOTREACHED(); 289 break; 290 } 291 292 int bottom = GetBottomPositionForExpansionState(expansion_state); 293 bounds->set_y(bottom - bounds->height()); 294 } 295 296 void DockedPanelCollection::OnPanelAttentionStateChanged(Panel* panel) { 297 DCHECK_EQ(this, panel->collection()); 298 Panel::ExpansionState state = panel->expansion_state(); 299 if (panel->IsDrawingAttention()) { 300 // Bring up the titlebar to get user's attention. 301 if (state == Panel::MINIMIZED) 302 panel->SetExpansionState(Panel::TITLE_ONLY); 303 return; 304 } 305 306 // Panel is no longer drawing attention, but leave the panel in 307 // title-only mode if all titlebars are currently up. 308 if (state != Panel::TITLE_ONLY || are_titlebars_up_) 309 return; 310 311 // Leave titlebar up if panel is being dragged. 312 if (panel_manager_->drag_controller()->dragging_panel() == panel) 313 return; 314 315 // Leave titlebar up if mouse is in/below the panel. 316 const gfx::Point mouse_position = 317 panel_manager_->mouse_watcher()->GetMousePosition(); 318 gfx::Rect bounds = panel->GetBounds(); 319 if (bounds.x() <= mouse_position.x() && 320 mouse_position.x() <= bounds.right() && 321 mouse_position.y() >= bounds.y()) 322 return; 323 324 // Bring down the titlebar now that panel is not drawing attention. 325 panel->SetExpansionState(Panel::MINIMIZED); 326 } 327 328 void DockedPanelCollection::OnPanelTitlebarClicked(Panel* panel, 329 panel::ClickModifier modifier) { 330 DCHECK_EQ(this, panel->collection()); 331 if (!IsPanelMinimized(panel)) 332 return; 333 334 if (modifier == panel::APPLY_TO_ALL) 335 RestoreAll(); 336 else 337 RestorePanel(panel); 338 } 339 340 void DockedPanelCollection::ActivatePanel(Panel* panel) { 341 DCHECK_EQ(this, panel->collection()); 342 343 // Make sure the panel is expanded when activated so the user input 344 // does not go into a collapsed window. 345 panel->SetExpansionState(Panel::EXPANDED); 346 347 // If the layout needs to be refreshed, it will happen in response to 348 // the activation notification (and with a slight delay to let things settle). 349 } 350 351 void DockedPanelCollection::MinimizePanel(Panel* panel) { 352 DCHECK_EQ(this, panel->collection()); 353 354 if (panel->expansion_state() != Panel::EXPANDED) 355 return; 356 357 panel->SetExpansionState(panel->IsDrawingAttention() ? 358 Panel::TITLE_ONLY : Panel::MINIMIZED); 359 } 360 361 void DockedPanelCollection::RestorePanel(Panel* panel) { 362 DCHECK_EQ(this, panel->collection()); 363 panel->SetExpansionState(Panel::EXPANDED); 364 } 365 366 void DockedPanelCollection::MinimizeAll() { 367 // Set minimizing_all_ to prevent deactivation of each panel when it 368 // is minimized. See comments in OnPanelExpansionStateChanged. 369 base::AutoReset<bool> pin(&minimizing_all_, true); 370 Panel* minimized_active_panel = NULL; 371 for (Panels::const_iterator iter = panels_.begin(); 372 iter != panels_.end(); ++iter) { 373 if ((*iter)->IsActive()) 374 minimized_active_panel = *iter; 375 MinimizePanel(*iter); 376 } 377 378 // When a single panel is minimized, it is deactivated to ensure that 379 // a minimized panel does not have focus. However, when minimizing all, 380 // the deactivation is only done once after all panels are minimized, 381 // rather than per minimized panel, both for efficiency and to avoid 382 // temporary activations of random not-yet-minimized panels. 383 if (minimized_active_panel) { 384 minimized_active_panel->Deactivate(); 385 // Layout will be refreshed in response to (de)activation notification. 386 } 387 } 388 389 void DockedPanelCollection::RestoreAll() { 390 for (Panels::const_iterator iter = panels_.begin(); 391 iter != panels_.end(); ++iter) { 392 RestorePanel(*iter); 393 } 394 } 395 396 void DockedPanelCollection::OnMinimizeButtonClicked( 397 Panel* panel, panel::ClickModifier modifier) { 398 if (modifier == panel::APPLY_TO_ALL) 399 MinimizeAll(); 400 else 401 MinimizePanel(panel); 402 } 403 404 void DockedPanelCollection::OnRestoreButtonClicked( 405 Panel* panel, panel::ClickModifier modifier) { 406 if (modifier == panel::APPLY_TO_ALL) 407 RestoreAll(); 408 else 409 RestorePanel(panel); 410 } 411 412 bool DockedPanelCollection::CanShowMinimizeButton(const Panel* panel) const { 413 return !IsPanelMinimized(panel); 414 } 415 416 bool DockedPanelCollection::CanShowRestoreButton(const Panel* panel) const { 417 return IsPanelMinimized(panel); 418 } 419 420 bool DockedPanelCollection::IsPanelMinimized(const Panel* panel) const { 421 return panel->expansion_state() != Panel::EXPANDED; 422 } 423 424 bool DockedPanelCollection::UsesAlwaysOnTopPanels() const { 425 return true; 426 } 427 428 void DockedPanelCollection::UpdateMinimizedPanelCount() { 429 int prev_minimized_panel_count = minimized_panel_count_; 430 minimized_panel_count_ = 0; 431 for (Panels::const_iterator panel_iter = panels_.begin(); 432 panel_iter != panels_.end(); ++panel_iter) { 433 if ((*panel_iter)->expansion_state() != Panel::EXPANDED) 434 minimized_panel_count_++; 435 } 436 437 if (prev_minimized_panel_count == 0 && minimized_panel_count_ > 0) 438 panel_manager_->mouse_watcher()->AddObserver(this); 439 else if (prev_minimized_panel_count > 0 && minimized_panel_count_ == 0) 440 panel_manager_->mouse_watcher()->RemoveObserver(this); 441 442 DCHECK_LE(minimized_panel_count_, num_panels()); 443 } 444 445 void DockedPanelCollection::ResizePanelWindow( 446 Panel* panel, 447 const gfx::Size& preferred_window_size) { 448 DCHECK_EQ(this, panel->collection()); 449 // Make sure the new size does not violate panel's size restrictions. 450 gfx::Size new_size(preferred_window_size.width(), 451 preferred_window_size.height()); 452 new_size = panel->ClampSize(new_size); 453 454 if (new_size == panel->full_size()) 455 return; 456 457 panel->set_full_size(new_size); 458 459 RefreshLayout(); 460 } 461 462 bool DockedPanelCollection::ShouldBringUpTitlebars(int mouse_x, 463 int mouse_y) const { 464 // We should always bring up the titlebar if the mouse is over the 465 // visible auto-hiding bottom bar. 466 DisplaySettingsProvider* provider = 467 panel_manager_->display_settings_provider(); 468 if (provider->IsAutoHidingDesktopBarEnabled( 469 DisplaySettingsProvider::DESKTOP_BAR_ALIGNED_BOTTOM) && 470 provider->GetDesktopBarVisibility( 471 DisplaySettingsProvider::DESKTOP_BAR_ALIGNED_BOTTOM) == 472 DisplaySettingsProvider::DESKTOP_BAR_VISIBLE) { 473 int bottom_bar_bottom = work_area_.bottom(); 474 int bottom_bar_y = bottom_bar_bottom - provider->GetDesktopBarThickness( 475 DisplaySettingsProvider::DESKTOP_BAR_ALIGNED_BOTTOM); 476 if (bottom_bar_y <= mouse_y && mouse_y <= bottom_bar_bottom) 477 return true; 478 } 479 480 // Bring up titlebars if any panel needs the titlebar up. 481 Panel* dragging_panel = panel_manager_->drag_controller()->dragging_panel(); 482 if (dragging_panel && 483 dragging_panel->collection()->type() != PanelCollection::DOCKED) 484 dragging_panel = NULL; 485 for (Panels::const_iterator iter = panels_.begin(); 486 iter != panels_.end(); ++iter) { 487 Panel* panel = *iter; 488 Panel::ExpansionState state = panel->expansion_state(); 489 // Skip the expanded panel. 490 if (state == Panel::EXPANDED) 491 continue; 492 493 // If the panel is showing titlebar only, we want to keep it up when it is 494 // being dragged. 495 if (state == Panel::TITLE_ONLY && panel == dragging_panel) 496 return true; 497 498 // We do not want to bring up other minimized panels if the mouse is over 499 // the panel that pops up the titlebar to attract attention. 500 if (panel->IsDrawingAttention()) 501 continue; 502 503 gfx::Rect bounds = panel->GetBounds(); 504 if (bounds.x() <= mouse_x && mouse_x <= bounds.right() && 505 mouse_y >= bounds.y()) 506 return true; 507 } 508 return false; 509 } 510 511 void DockedPanelCollection::BringUpOrDownTitlebars(bool bring_up) { 512 if (are_titlebars_up_ == bring_up) 513 return; 514 515 are_titlebars_up_ = bring_up; 516 int task_delay_ms = 0; 517 518 // If the auto-hiding bottom bar exists, delay the action until the bottom 519 // bar is fully visible or hidden. We do not want both bottom bar and panel 520 // titlebar to move at the same time but with different speeds. 521 DisplaySettingsProvider* provider = 522 panel_manager_->display_settings_provider(); 523 if (provider->IsAutoHidingDesktopBarEnabled( 524 DisplaySettingsProvider::DESKTOP_BAR_ALIGNED_BOTTOM)) { 525 DisplaySettingsProvider::DesktopBarVisibility visibility = 526 provider->GetDesktopBarVisibility( 527 DisplaySettingsProvider::DESKTOP_BAR_ALIGNED_BOTTOM); 528 if (visibility != 529 (bring_up ? DisplaySettingsProvider::DESKTOP_BAR_VISIBLE 530 : DisplaySettingsProvider::DESKTOP_BAR_HIDDEN)) { 531 // Occasionally some system, like Windows, might not bring up or down the 532 // bottom bar when the mouse enters or leaves the bottom screen area. 533 // Thus, we schedule a delayed task to do the work if we do not receive 534 // the bottom bar visibility change notification within a certain period 535 // of time. 536 task_delay_ms = kMaxDelayWaitForBottomBarVisibilityChangeMs; 537 } 538 } 539 540 // On some OSes, the interaction with native Taskbars/Docks may be improved 541 // if the panels do not go back to minimized state too fast. For example, 542 // with a taskbar in auto-hide mode, the taskbar will cover the panel in 543 // title-only mode which appears on hover. Leaving it up for a little longer 544 // would allow the user to be able to click on it. 545 // 546 // Currently, no platforms use both delays. 547 DCHECK(task_delay_ms == 0 || 548 kDelayBeforeCollapsingFromTitleOnlyStateMs == 0); 549 if (!bring_up && task_delay_ms == 0) { 550 task_delay_ms = kDelayBeforeCollapsingFromTitleOnlyStateMs; 551 } 552 553 // OnAutoHidingDesktopBarVisibilityChanged will handle this. 554 delayed_titlebar_action_ = bring_up ? BRING_UP : BRING_DOWN; 555 556 // If user moves the mouse in and out of mouse tracking area, we might have 557 // previously posted but not yet dispatched task in the queue. New action 558 // should always 'reset' the delays so cancel any tasks that haven't run yet 559 // and post a new one. 560 titlebar_action_factory_.InvalidateWeakPtrs(); 561 base::MessageLoop::current()->PostDelayedTask( 562 FROM_HERE, 563 base::Bind(&DockedPanelCollection::DelayedBringUpOrDownTitlebarsCheck, 564 titlebar_action_factory_.GetWeakPtr()), 565 base::TimeDelta::FromMilliseconds( 566 PanelManager::AdjustTimeInterval(task_delay_ms))); 567 } 568 569 void DockedPanelCollection::DelayedBringUpOrDownTitlebarsCheck() { 570 // Task was already processed or cancelled - bail out. 571 if (delayed_titlebar_action_ == NO_ACTION) 572 return; 573 574 bool need_to_bring_up_titlebars = (delayed_titlebar_action_ == BRING_UP); 575 576 delayed_titlebar_action_ = NO_ACTION; 577 578 // Check if the action is still needed based on the latest mouse position. The 579 // user could move the mouse into the tracking area and then quickly move it 580 // out of the area. In case of this, cancel the action. 581 if (are_titlebars_up_ != need_to_bring_up_titlebars) 582 return; 583 584 DoBringUpOrDownTitlebars(need_to_bring_up_titlebars); 585 } 586 587 void DockedPanelCollection::DoBringUpOrDownTitlebars(bool bring_up) { 588 for (Panels::const_iterator iter = panels_.begin(); 589 iter != panels_.end(); ++iter) { 590 Panel* panel = *iter; 591 592 // Skip any panel that is drawing the attention. 593 if (panel->IsDrawingAttention()) 594 continue; 595 596 if (bring_up) { 597 if (panel->expansion_state() == Panel::MINIMIZED) 598 panel->SetExpansionState(Panel::TITLE_ONLY); 599 } else { 600 if (panel->expansion_state() == Panel::TITLE_ONLY) 601 panel->SetExpansionState(Panel::MINIMIZED); 602 } 603 } 604 } 605 606 int DockedPanelCollection::GetBottomPositionForExpansionState( 607 Panel::ExpansionState expansion_state) const { 608 int bottom = work_area_.bottom(); 609 // If there is an auto-hiding desktop bar aligned to the bottom edge, we need 610 // to move the title-only panel above the auto-hiding desktop bar. 611 DisplaySettingsProvider* provider = 612 panel_manager_->display_settings_provider(); 613 if (expansion_state == Panel::TITLE_ONLY && 614 provider->IsAutoHidingDesktopBarEnabled( 615 DisplaySettingsProvider::DESKTOP_BAR_ALIGNED_BOTTOM)) { 616 bottom -= provider->GetDesktopBarThickness( 617 DisplaySettingsProvider::DESKTOP_BAR_ALIGNED_BOTTOM); 618 } 619 620 return bottom; 621 } 622 623 void DockedPanelCollection::OnMouseMove(const gfx::Point& mouse_position) { 624 bool bring_up_titlebars = ShouldBringUpTitlebars(mouse_position.x(), 625 mouse_position.y()); 626 BringUpOrDownTitlebars(bring_up_titlebars); 627 } 628 629 void DockedPanelCollection::OnAutoHidingDesktopBarVisibilityChanged( 630 DisplaySettingsProvider::DesktopBarAlignment alignment, 631 DisplaySettingsProvider::DesktopBarVisibility visibility) { 632 if (delayed_titlebar_action_ == NO_ACTION) 633 return; 634 635 DisplaySettingsProvider::DesktopBarVisibility expected_visibility = 636 delayed_titlebar_action_ == BRING_UP 637 ? DisplaySettingsProvider::DESKTOP_BAR_VISIBLE 638 : DisplaySettingsProvider::DESKTOP_BAR_HIDDEN; 639 if (visibility != expected_visibility) 640 return; 641 642 DoBringUpOrDownTitlebars(delayed_titlebar_action_ == BRING_UP); 643 delayed_titlebar_action_ = NO_ACTION; 644 } 645 646 void DockedPanelCollection::OnAutoHidingDesktopBarThicknessChanged( 647 DisplaySettingsProvider::DesktopBarAlignment alignment, int thickness) { 648 RefreshLayout(); 649 } 650 651 void DockedPanelCollection::RefreshLayout() { 652 int total_active_width = 0; 653 int total_inactive_width = 0; 654 655 for (Panels::const_iterator panel_iter = panels_.begin(); 656 panel_iter != panels_.end(); ++panel_iter) { 657 Panel* panel = *panel_iter; 658 if (panel->IsActive()) 659 total_active_width += panel->full_size().width(); 660 else 661 total_inactive_width += panel->full_size().width(); 662 } 663 664 double display_width_for_inactive_panels = 665 work_area_.width() - total_active_width - 666 kPanelsHorizontalSpacing * panels_.size(); 667 double overflow_squeeze_factor = (total_inactive_width > 0) ? 668 std::min(display_width_for_inactive_panels / total_inactive_width, 1.0) : 669 1.0; 670 671 // We want to calculate all bounds first, then apply them in a specific order. 672 typedef std::pair<Panel*, gfx::Rect> PanelBoundsInfo; 673 // The next pair of variables will hold panels that move, respectively, 674 // to the right and to the left. We want to process them from the center 675 // outwards, so one is a stack and another is a queue. 676 std::vector<PanelBoundsInfo> moving_right; 677 std::queue<PanelBoundsInfo> moving_left; 678 679 int rightmost_position = StartingRightPosition(); 680 for (Panels::const_iterator panel_iter = panels_.begin(); 681 panel_iter != panels_.end(); ++panel_iter) { 682 Panel* panel = *panel_iter; 683 gfx::Rect old_bounds = panel->GetBounds(); 684 gfx::Rect new_bounds = old_bounds; 685 AdjustPanelBoundsPerExpansionState(panel, &new_bounds); 686 687 new_bounds.set_width( 688 WidthToDisplayPanelInCollection(panel->IsActive(), 689 overflow_squeeze_factor, 690 panel->full_size().width())); 691 int x = rightmost_position - new_bounds.width(); 692 new_bounds.set_x(x); 693 694 if (x < old_bounds.x() || 695 (x == old_bounds.x() && new_bounds.width() <= old_bounds.width())) 696 moving_left.push(std::make_pair(panel, new_bounds)); 697 else 698 moving_right.push_back(std::make_pair(panel, new_bounds)); 699 700 rightmost_position = x - kPanelsHorizontalSpacing; 701 } 702 703 // Update panels going in both directions. 704 // This is important on Mac where bounds changes are slow and you see a 705 // "wave" instead of a smooth sliding effect. 706 int num_animated = 0; 707 bool going_right = true; 708 while (!moving_right.empty() || !moving_left.empty()) { 709 PanelBoundsInfo bounds_info; 710 // Alternate between processing the panels that moving left and right, 711 // starting from the center. 712 going_right = !going_right; 713 bool take_panel_on_right = 714 (going_right && !moving_right.empty()) || 715 moving_left.empty(); 716 if (take_panel_on_right) { 717 bounds_info = moving_right.back(); 718 moving_right.pop_back(); 719 } else { 720 bounds_info = moving_left.front(); 721 moving_left.pop(); 722 } 723 724 // Don't update the docked panel that is in preview mode. 725 Panel* panel = bounds_info.first; 726 gfx::Rect bounds = bounds_info.second; 727 if (!panel->in_preview_mode() && bounds != panel->GetBounds()) { 728 // We animate a limited number of panels, starting with the 729 // "most important" ones, that is, ones that are close to the center 730 // of the action. Other panels are moved instantly to improve performance. 731 if (num_animated < kNumPanelsToAnimateSimultaneously) { 732 panel->SetPanelBounds(bounds); // Animates. 733 ++num_animated; 734 } else { 735 panel->SetPanelBoundsInstantly(bounds); 736 } 737 } 738 } 739 740 content::NotificationService::current()->Notify( 741 chrome::NOTIFICATION_PANEL_COLLECTION_UPDATED, 742 content::Source<PanelCollection>(this), 743 content::NotificationService::NoDetails()); 744 } 745 746 int DockedPanelCollection::WidthToDisplayPanelInCollection( 747 bool is_for_active_panel, double squeeze_factor, int full_width) const { 748 return is_for_active_panel ? full_width : 749 std::max(panel::kPanelMinWidth, 750 static_cast<int>(floor(full_width * squeeze_factor))); 751 } 752 753 void DockedPanelCollection::CloseAll() { 754 // This should only be called at the end of tests to clean up. 755 756 // Make a copy of the iterator as closing panels can modify the vector. 757 Panels panels_copy = panels_; 758 759 // Start from the bottom to avoid reshuffling. 760 for (Panels::reverse_iterator iter = panels_copy.rbegin(); 761 iter != panels_copy.rend(); ++iter) 762 (*iter)->Close(); 763 } 764 765 void DockedPanelCollection::UpdatePanelOnCollectionChange(Panel* panel) { 766 panel->set_attention_mode(Panel::USE_PANEL_ATTENTION); 767 panel->ShowShadow(true); 768 panel->UpdateMinimizeRestoreButtonVisibility(); 769 panel->SetWindowCornerStyle(panel::TOP_ROUNDED); 770 } 771 772 void DockedPanelCollection::ScheduleLayoutRefresh() { 773 refresh_action_factory_.InvalidateWeakPtrs(); 774 base::MessageLoop::current()->PostDelayedTask( 775 FROM_HERE, 776 base::Bind(&DockedPanelCollection::RefreshLayout, 777 refresh_action_factory_.GetWeakPtr()), 778 base::TimeDelta::FromMilliseconds(PanelManager::AdjustTimeInterval( 779 kRefreshLayoutAfterActivePanelChangeDelayMs))); 780 } 781 782 void DockedPanelCollection::OnPanelActiveStateChanged(Panel* panel) { 783 // Refresh layout, but wait till active states settle. 784 // This lets us avoid refreshing too many times when one panel loses 785 // focus and another gains it. 786 ScheduleLayoutRefresh(); 787 } 788 789 gfx::Rect DockedPanelCollection::GetInitialPanelBounds( 790 const gfx::Rect& requested_bounds) const { 791 gfx::Rect initial_bounds = requested_bounds; 792 initial_bounds.set_origin( 793 GetDefaultPositionForPanel(requested_bounds.size())); 794 return initial_bounds; 795 } 796 797 bool DockedPanelCollection::HasPanel(Panel* panel) const { 798 return find(panels_.begin(), panels_.end(), panel) != panels_.end(); 799 } 800