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