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 "ash/wm/panels/panel_layout_manager.h" 6 7 #include <algorithm> 8 #include <map> 9 10 #include "ash/screen_util.h" 11 #include "ash/shelf/shelf.h" 12 #include "ash/shelf/shelf_layout_manager.h" 13 #include "ash/shelf/shelf_types.h" 14 #include "ash/shelf/shelf_util.h" 15 #include "ash/shelf/shelf_widget.h" 16 #include "ash/shell.h" 17 #include "ash/shell_window_ids.h" 18 #include "ash/wm/window_animations.h" 19 #include "ash/wm/window_state.h" 20 #include "ash/wm/window_util.h" 21 #include "base/auto_reset.h" 22 #include "base/bind.h" 23 #include "base/bind_helpers.h" 24 #include "third_party/skia/include/core/SkColor.h" 25 #include "third_party/skia/include/core/SkPaint.h" 26 #include "third_party/skia/include/core/SkPath.h" 27 #include "ui/aura/client/focus_client.h" 28 #include "ui/aura/client/window_tree_client.h" 29 #include "ui/aura/window.h" 30 #include "ui/aura/window_delegate.h" 31 #include "ui/aura/window_event_dispatcher.h" 32 #include "ui/aura/window_tracker.h" 33 #include "ui/compositor/scoped_layer_animation_settings.h" 34 #include "ui/gfx/canvas.h" 35 #include "ui/gfx/rect.h" 36 #include "ui/gfx/vector2d.h" 37 #include "ui/views/background.h" 38 #include "ui/views/widget/widget.h" 39 #include "ui/wm/public/activation_client.h" 40 41 namespace ash { 42 namespace { 43 44 const int kPanelIdealSpacing = 4; 45 46 const float kMaxHeightFactor = .80f; 47 const float kMaxWidthFactor = .50f; 48 49 // Duration for panel animations. 50 const int kPanelSlideDurationMilliseconds = 50; 51 const int kCalloutFadeDurationMilliseconds = 50; 52 53 // Offset used when sliding panel in/out of the shelf. Used for minimizing, 54 // restoring and the initial showing of a panel. 55 const int kPanelSlideInOffset = 20; 56 57 // Callout arrow dimensions. 58 const int kArrowWidth = 18; 59 const int kArrowHeight = 9; 60 61 class CalloutWidgetBackground : public views::Background { 62 public: 63 CalloutWidgetBackground() : alignment_(SHELF_ALIGNMENT_BOTTOM) { 64 } 65 66 virtual void Paint(gfx::Canvas* canvas, views::View* view) const OVERRIDE { 67 SkPath path; 68 switch (alignment_) { 69 case SHELF_ALIGNMENT_BOTTOM: 70 path.moveTo(SkIntToScalar(0), SkIntToScalar(0)); 71 path.lineTo(SkIntToScalar(kArrowWidth / 2), 72 SkIntToScalar(kArrowHeight)); 73 path.lineTo(SkIntToScalar(kArrowWidth), SkIntToScalar(0)); 74 break; 75 case SHELF_ALIGNMENT_LEFT: 76 path.moveTo(SkIntToScalar(kArrowHeight), SkIntToScalar(kArrowWidth)); 77 path.lineTo(SkIntToScalar(0), SkIntToScalar(kArrowWidth / 2)); 78 path.lineTo(SkIntToScalar(kArrowHeight), SkIntToScalar(0)); 79 break; 80 case SHELF_ALIGNMENT_TOP: 81 path.moveTo(SkIntToScalar(0), SkIntToScalar(kArrowHeight)); 82 path.lineTo(SkIntToScalar(kArrowWidth / 2), SkIntToScalar(0)); 83 path.lineTo(SkIntToScalar(kArrowWidth), SkIntToScalar(kArrowHeight)); 84 break; 85 case SHELF_ALIGNMENT_RIGHT: 86 path.moveTo(SkIntToScalar(0), SkIntToScalar(0)); 87 path.lineTo(SkIntToScalar(kArrowHeight), 88 SkIntToScalar(kArrowWidth / 2)); 89 path.lineTo(SkIntToScalar(0), SkIntToScalar(kArrowWidth)); 90 break; 91 } 92 // Hard code the arrow color for now. 93 SkPaint paint; 94 paint.setStyle(SkPaint::kFill_Style); 95 paint.setColor(SkColorSetARGB(0xff, 0xe5, 0xe5, 0xe5)); 96 canvas->DrawPath(path, paint); 97 } 98 99 ShelfAlignment alignment() { 100 return alignment_; 101 } 102 103 void set_alignment(ShelfAlignment alignment) { 104 alignment_ = alignment; 105 } 106 107 private: 108 ShelfAlignment alignment_; 109 110 DISALLOW_COPY_AND_ASSIGN(CalloutWidgetBackground); 111 }; 112 113 struct VisiblePanelPositionInfo { 114 VisiblePanelPositionInfo() 115 : min_major(0), 116 max_major(0), 117 major_pos(0), 118 major_length(0), 119 window(NULL), 120 slide_in(false) {} 121 122 int min_major; 123 int max_major; 124 int major_pos; 125 int major_length; 126 aura::Window* window; 127 bool slide_in; 128 }; 129 130 bool CompareWindowMajor(const VisiblePanelPositionInfo& win1, 131 const VisiblePanelPositionInfo& win2) { 132 return win1.major_pos < win2.major_pos; 133 } 134 135 void FanOutPanels(std::vector<VisiblePanelPositionInfo>::iterator first, 136 std::vector<VisiblePanelPositionInfo>::iterator last) { 137 int num_panels = last - first; 138 if (num_panels == 1) { 139 (*first).major_pos = std::max((*first).min_major, std::min( 140 (*first).max_major, (*first).major_pos)); 141 } 142 if (num_panels <= 1) 143 return; 144 145 if (num_panels == 2) { 146 // If there are two adjacent overlapping windows, separate them by the 147 // minimum major_length necessary. 148 std::vector<VisiblePanelPositionInfo>::iterator second = first + 1; 149 int separation = (*first).major_length / 2 + (*second).major_length / 2 + 150 kPanelIdealSpacing; 151 int overlap = (*first).major_pos + separation - (*second).major_pos; 152 (*first).major_pos = std::max((*first).min_major, 153 (*first).major_pos - overlap / 2); 154 (*second).major_pos = std::min((*second).max_major, 155 (*first).major_pos + separation); 156 // Recalculate the first panel position in case the second one was 157 // constrained on the right. 158 (*first).major_pos = std::max((*first).min_major, 159 (*second).major_pos - separation); 160 return; 161 } 162 163 // If there are more than two overlapping windows, fan them out from minimum 164 // position to maximum position equally spaced. 165 int delta = ((*(last - 1)).max_major - (*first).min_major) / (num_panels - 1); 166 int major_pos = (*first).min_major; 167 for (std::vector<VisiblePanelPositionInfo>::iterator iter = first; 168 iter != last; ++iter) { 169 (*iter).major_pos = std::max((*iter).min_major, 170 std::min((*iter).max_major, major_pos)); 171 major_pos += delta; 172 } 173 } 174 175 bool BoundsAdjacent(const gfx::Rect& bounds1, const gfx::Rect& bounds2) { 176 return bounds1.x() == bounds2.right() || 177 bounds1.y() == bounds2.bottom() || 178 bounds1.right() == bounds2.x() || 179 bounds1.bottom() == bounds2.y(); 180 } 181 182 gfx::Vector2d GetSlideInAnimationOffset(ShelfAlignment alignment) { 183 gfx::Vector2d offset; 184 switch (alignment) { 185 case SHELF_ALIGNMENT_BOTTOM: 186 offset.set_y(kPanelSlideInOffset); 187 break; 188 case SHELF_ALIGNMENT_LEFT: 189 offset.set_x(-kPanelSlideInOffset); 190 break; 191 case SHELF_ALIGNMENT_RIGHT: 192 offset.set_x(kPanelSlideInOffset); 193 break; 194 case SHELF_ALIGNMENT_TOP: 195 offset.set_y(-kPanelSlideInOffset); 196 break; 197 } 198 return offset; 199 } 200 201 } // namespace 202 203 class PanelCalloutWidget : public views::Widget { 204 public: 205 explicit PanelCalloutWidget(aura::Window* container) 206 : background_(NULL) { 207 InitWidget(container); 208 } 209 210 void SetAlignment(ShelfAlignment alignment) { 211 gfx::Rect callout_bounds = GetWindowBoundsInScreen(); 212 if (alignment == SHELF_ALIGNMENT_BOTTOM || 213 alignment == SHELF_ALIGNMENT_TOP) { 214 callout_bounds.set_width(kArrowWidth); 215 callout_bounds.set_height(kArrowHeight); 216 } else { 217 callout_bounds.set_width(kArrowHeight); 218 callout_bounds.set_height(kArrowWidth); 219 } 220 GetNativeWindow()->SetBounds(callout_bounds); 221 if (background_->alignment() != alignment) { 222 background_->set_alignment(alignment); 223 SchedulePaintInRect(gfx::Rect(gfx::Point(), callout_bounds.size())); 224 } 225 } 226 227 private: 228 void InitWidget(aura::Window* parent) { 229 views::Widget::InitParams params; 230 params.type = views::Widget::InitParams::TYPE_POPUP; 231 params.opacity = views::Widget::InitParams::TRANSLUCENT_WINDOW; 232 params.keep_on_top = true; 233 params.ownership = views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET; 234 params.parent = parent; 235 params.bounds = ScreenUtil::ConvertRectToScreen(parent, gfx::Rect()); 236 params.bounds.set_width(kArrowWidth); 237 params.bounds.set_height(kArrowHeight); 238 params.accept_events = false; 239 set_focus_on_creation(false); 240 Init(params); 241 DCHECK_EQ(GetNativeView()->GetRootWindow(), parent->GetRootWindow()); 242 views::View* content_view = new views::View; 243 background_ = new CalloutWidgetBackground; 244 content_view->set_background(background_); 245 SetContentsView(content_view); 246 GetNativeWindow()->layer()->SetOpacity(0); 247 } 248 249 // Weak pointer owned by this widget's content view. 250 CalloutWidgetBackground* background_; 251 252 DISALLOW_COPY_AND_ASSIGN(PanelCalloutWidget); 253 }; 254 255 //////////////////////////////////////////////////////////////////////////////// 256 // PanelLayoutManager public implementation: 257 PanelLayoutManager::PanelLayoutManager(aura::Window* panel_container) 258 : panel_container_(panel_container), 259 in_add_window_(false), 260 in_layout_(false), 261 show_callout_widgets_(true), 262 dragged_panel_(NULL), 263 shelf_(NULL), 264 shelf_layout_manager_(NULL), 265 last_active_panel_(NULL), 266 weak_factory_(this) { 267 DCHECK(panel_container); 268 aura::client::GetActivationClient(Shell::GetPrimaryRootWindow())-> 269 AddObserver(this); 270 Shell::GetInstance()->display_controller()->AddObserver(this); 271 Shell::GetInstance()->AddShellObserver(this); 272 } 273 274 PanelLayoutManager::~PanelLayoutManager() { 275 Shutdown(); 276 } 277 278 void PanelLayoutManager::Shutdown() { 279 if (shelf_layout_manager_) 280 shelf_layout_manager_->RemoveObserver(this); 281 shelf_layout_manager_ = NULL; 282 for (PanelList::iterator iter = panel_windows_.begin(); 283 iter != panel_windows_.end(); ++iter) { 284 delete iter->callout_widget; 285 } 286 panel_windows_.clear(); 287 if (shelf_) 288 shelf_->RemoveIconObserver(this); 289 shelf_ = NULL; 290 aura::client::GetActivationClient(Shell::GetPrimaryRootWindow())-> 291 RemoveObserver(this); 292 Shell::GetInstance()->display_controller()->RemoveObserver(this); 293 Shell::GetInstance()->RemoveShellObserver(this); 294 } 295 296 void PanelLayoutManager::StartDragging(aura::Window* panel) { 297 DCHECK(!dragged_panel_); 298 dragged_panel_ = panel; 299 Relayout(); 300 } 301 302 void PanelLayoutManager::FinishDragging() { 303 dragged_panel_ = NULL; 304 Relayout(); 305 } 306 307 void PanelLayoutManager::SetShelf(Shelf* shelf) { 308 DCHECK(!shelf_); 309 DCHECK(!shelf_layout_manager_); 310 shelf_ = shelf; 311 shelf_->AddIconObserver(this); 312 if (shelf_->shelf_widget()) { 313 shelf_layout_manager_ = ash::ShelfLayoutManager::ForShelf( 314 shelf_->shelf_widget()->GetNativeWindow()); 315 WillChangeVisibilityState(shelf_layout_manager_->visibility_state()); 316 shelf_layout_manager_->AddObserver(this); 317 } 318 } 319 320 void PanelLayoutManager::ToggleMinimize(aura::Window* panel) { 321 DCHECK(panel->parent() == panel_container_); 322 wm::WindowState* window_state = wm::GetWindowState(panel); 323 if (window_state->IsMinimized()) 324 window_state->Restore(); 325 else 326 window_state->Minimize(); 327 } 328 329 void PanelLayoutManager::SetShowCalloutWidgets(bool show) { 330 if (show_callout_widgets_ == show) 331 return; 332 show_callout_widgets_ = show; 333 UpdateCallouts(); 334 } 335 336 views::Widget* PanelLayoutManager::GetCalloutWidgetForPanel( 337 aura::Window* panel) { 338 DCHECK(panel->parent() == panel_container_); 339 PanelList::iterator found = 340 std::find(panel_windows_.begin(), panel_windows_.end(), panel); 341 DCHECK(found != panel_windows_.end()); 342 return found->callout_widget; 343 } 344 345 //////////////////////////////////////////////////////////////////////////////// 346 // PanelLayoutManager, aura::LayoutManager implementation: 347 void PanelLayoutManager::OnWindowResized() { 348 Relayout(); 349 } 350 351 void PanelLayoutManager::OnWindowAddedToLayout(aura::Window* child) { 352 if (child->type() == ui::wm::WINDOW_TYPE_POPUP) 353 return; 354 if (in_add_window_) 355 return; 356 base::AutoReset<bool> auto_reset_in_add_window(&in_add_window_, true); 357 if (!wm::GetWindowState(child)->panel_attached()) { 358 // This should only happen when a window is added to panel container as a 359 // result of bounds change from within the application during a drag. 360 // If so we have already stopped the drag and should reparent the panel 361 // back to appropriate container and ignore it. 362 // TODO(varkha): Updating bounds during a drag can cause problems and a more 363 // general solution is needed. See http://crbug.com/251813 . 364 aura::Window* old_parent = child->parent(); 365 aura::client::ParentWindowWithContext( 366 child, child, child->GetRootWindow()->GetBoundsInScreen()); 367 wm::ReparentTransientChildrenOfChild(child, old_parent, child->parent()); 368 DCHECK(child->parent()->id() != kShellWindowId_PanelContainer); 369 return; 370 } 371 PanelInfo panel_info; 372 panel_info.window = child; 373 panel_info.callout_widget = new PanelCalloutWidget(panel_container_); 374 panel_info.slide_in = child != dragged_panel_; 375 panel_windows_.push_back(panel_info); 376 child->AddObserver(this); 377 wm::GetWindowState(child)->AddObserver(this); 378 Relayout(); 379 } 380 381 void PanelLayoutManager::OnWillRemoveWindowFromLayout(aura::Window* child) { 382 } 383 384 void PanelLayoutManager::OnWindowRemovedFromLayout(aura::Window* child) { 385 if (child->type() == ui::wm::WINDOW_TYPE_POPUP) 386 return; 387 PanelList::iterator found = 388 std::find(panel_windows_.begin(), panel_windows_.end(), child); 389 if (found != panel_windows_.end()) { 390 delete found->callout_widget; 391 panel_windows_.erase(found); 392 } 393 if (restore_windows_on_shelf_visible_) 394 restore_windows_on_shelf_visible_->Remove(child); 395 child->RemoveObserver(this); 396 wm::GetWindowState(child)->RemoveObserver(this); 397 398 if (dragged_panel_ == child) 399 dragged_panel_ = NULL; 400 401 if (last_active_panel_ == child) 402 last_active_panel_ = NULL; 403 404 Relayout(); 405 } 406 407 void PanelLayoutManager::OnChildWindowVisibilityChanged(aura::Window* child, 408 bool visible) { 409 if (visible) 410 wm::GetWindowState(child)->Restore(); 411 Relayout(); 412 } 413 414 void PanelLayoutManager::SetChildBounds(aura::Window* child, 415 const gfx::Rect& requested_bounds) { 416 gfx::Rect bounds(requested_bounds); 417 const gfx::Rect& max_bounds = panel_container_->GetRootWindow()->bounds(); 418 const int max_width = max_bounds.width() * kMaxWidthFactor; 419 const int max_height = max_bounds.height() * kMaxHeightFactor; 420 if (bounds.width() > max_width) 421 bounds.set_width(max_width); 422 if (bounds.height() > max_height) 423 bounds.set_height(max_height); 424 425 // Reposition dragged panel in the panel order. 426 if (dragged_panel_ == child) { 427 PanelList::iterator dragged_panel_iter = 428 std::find(panel_windows_.begin(), panel_windows_.end(), dragged_panel_); 429 DCHECK(dragged_panel_iter != panel_windows_.end()); 430 PanelList::iterator new_position; 431 for (new_position = panel_windows_.begin(); 432 new_position != panel_windows_.end(); 433 ++new_position) { 434 const gfx::Rect& bounds = (*new_position).window->bounds(); 435 if (bounds.x() + bounds.width()/2 <= requested_bounds.x()) break; 436 } 437 if (new_position != dragged_panel_iter) { 438 PanelInfo dragged_panel_info = *dragged_panel_iter; 439 panel_windows_.erase(dragged_panel_iter); 440 panel_windows_.insert(new_position, dragged_panel_info); 441 } 442 } 443 // Respect the minimum size of the window. 444 if (child->delegate()) { 445 const gfx::Size& min_size = child->delegate()->GetMinimumSize(); 446 bounds.set_width(std::max(min_size.width(), bounds.width())); 447 bounds.set_height(std::max(min_size.height(), bounds.height())); 448 } 449 450 SetChildBoundsDirect(child, bounds); 451 Relayout(); 452 } 453 454 //////////////////////////////////////////////////////////////////////////////// 455 // PanelLayoutManager, ShelfIconObserver implementation: 456 457 void PanelLayoutManager::OnShelfIconPositionsChanged() { 458 // TODO: As this is called for every animation step now. Relayout needs to be 459 // updated to use current icon position instead of use the ideal bounds so 460 // that the panels slide with their icons instead of jumping. 461 Relayout(); 462 } 463 464 //////////////////////////////////////////////////////////////////////////////// 465 // PanelLayoutManager, ash::ShellObserver implementation: 466 467 void PanelLayoutManager::OnShelfAlignmentChanged(aura::Window* root_window) { 468 if (panel_container_->GetRootWindow() == root_window) 469 Relayout(); 470 } 471 472 ///////////////////////////////////////////////////////////////////////////// 473 // PanelLayoutManager, WindowObserver implementation: 474 475 void PanelLayoutManager::OnWindowPropertyChanged(aura::Window* window, 476 const void* key, 477 intptr_t old) { 478 // Trigger a relayout to position the panels whenever the panel icon is set 479 // or changes. 480 if (key == kShelfID) 481 Relayout(); 482 } 483 484 ///////////////////////////////////////////////////////////////////////////// 485 // PanelLayoutManager, WindowStateObserver implementation: 486 487 void PanelLayoutManager::OnPostWindowStateTypeChange( 488 wm::WindowState* window_state, 489 wm::WindowStateType old_type) { 490 // If the shelf is currently hidden then windows will not actually be shown 491 // but the set to restore when the shelf becomes visible is updated. 492 if (restore_windows_on_shelf_visible_) { 493 if (window_state->IsMinimized()) { 494 MinimizePanel(window_state->window()); 495 restore_windows_on_shelf_visible_->Remove(window_state->window()); 496 } else { 497 restore_windows_on_shelf_visible_->Add(window_state->window()); 498 } 499 return; 500 } 501 502 if (window_state->IsMinimized()) 503 MinimizePanel(window_state->window()); 504 else 505 RestorePanel(window_state->window()); 506 } 507 508 //////////////////////////////////////////////////////////////////////////////// 509 // PanelLayoutManager, aura::client::ActivationChangeObserver implementation: 510 511 void PanelLayoutManager::OnWindowActivated(aura::Window* gained_active, 512 aura::Window* lost_active) { 513 // Ignore if the panel that is not managed by this was activated. 514 if (gained_active && gained_active->type() == ui::wm::WINDOW_TYPE_PANEL && 515 gained_active->parent() == panel_container_) { 516 UpdateStacking(gained_active); 517 UpdateCallouts(); 518 } 519 } 520 521 //////////////////////////////////////////////////////////////////////////////// 522 // PanelLayoutManager, DisplayController::Observer implementation: 523 524 void PanelLayoutManager::OnDisplayConfigurationChanged() { 525 Relayout(); 526 } 527 528 //////////////////////////////////////////////////////////////////////////////// 529 // PanelLayoutManager, ShelfLayoutManagerObserver implementation: 530 531 void PanelLayoutManager::WillChangeVisibilityState( 532 ShelfVisibilityState new_state) { 533 // On entering / leaving full screen mode the shelf visibility state is 534 // changed to / from SHELF_HIDDEN. In this state, panel windows should hide 535 // to allow the full-screen application to use the full screen. 536 bool shelf_hidden = new_state == ash::SHELF_HIDDEN; 537 if (!shelf_hidden) { 538 if (restore_windows_on_shelf_visible_) { 539 scoped_ptr<aura::WindowTracker> restore_windows( 540 restore_windows_on_shelf_visible_.Pass()); 541 for (aura::WindowTracker::Windows::const_iterator iter = 542 restore_windows->windows().begin(); iter != 543 restore_windows->windows().end(); ++iter) { 544 RestorePanel(*iter); 545 } 546 } 547 return; 548 } 549 550 if (restore_windows_on_shelf_visible_) 551 return; 552 scoped_ptr<aura::WindowTracker> minimized_windows(new aura::WindowTracker); 553 for (PanelList::iterator iter = panel_windows_.begin(); 554 iter != panel_windows_.end();) { 555 aura::Window* window = iter->window; 556 // Minimizing a panel window may remove it from the panel_windows_ list. 557 // Advance the iterator before minimizing it: http://crbug.com/393047. 558 ++iter; 559 if (window != dragged_panel_ && window->IsVisible()) { 560 minimized_windows->Add(window); 561 wm::GetWindowState(window)->Minimize(); 562 } 563 } 564 restore_windows_on_shelf_visible_ = minimized_windows.Pass(); 565 } 566 567 //////////////////////////////////////////////////////////////////////////////// 568 // PanelLayoutManager private implementation: 569 570 void PanelLayoutManager::MinimizePanel(aura::Window* panel) { 571 ::wm::SetWindowVisibilityAnimationType( 572 panel, WINDOW_VISIBILITY_ANIMATION_TYPE_MINIMIZE); 573 ui::Layer* layer = panel->layer(); 574 ui::ScopedLayerAnimationSettings panel_slide_settings(layer->GetAnimator()); 575 panel_slide_settings.SetPreemptionStrategy( 576 ui::LayerAnimator::IMMEDIATELY_ANIMATE_TO_NEW_TARGET); 577 panel_slide_settings.SetTransitionDuration( 578 base::TimeDelta::FromMilliseconds(kPanelSlideDurationMilliseconds)); 579 gfx::Rect bounds(panel->bounds()); 580 bounds.Offset(GetSlideInAnimationOffset( 581 shelf_->shelf_widget()->GetAlignment())); 582 SetChildBoundsDirect(panel, bounds); 583 panel->Hide(); 584 layer->SetOpacity(0); 585 if (wm::IsActiveWindow(panel)) 586 wm::DeactivateWindow(panel); 587 Relayout(); 588 } 589 590 void PanelLayoutManager::RestorePanel(aura::Window* panel) { 591 PanelList::iterator found = 592 std::find(panel_windows_.begin(), panel_windows_.end(), panel); 593 DCHECK(found != panel_windows_.end()); 594 found->slide_in = true; 595 Relayout(); 596 } 597 598 void PanelLayoutManager::Relayout() { 599 if (!shelf_ || !shelf_->shelf_widget()) 600 return; 601 602 if (in_layout_) 603 return; 604 base::AutoReset<bool> auto_reset_in_layout(&in_layout_, true); 605 606 ShelfAlignment alignment = shelf_->shelf_widget()->GetAlignment(); 607 bool horizontal = alignment == SHELF_ALIGNMENT_TOP || 608 alignment == SHELF_ALIGNMENT_BOTTOM; 609 gfx::Rect shelf_bounds = ash::ScreenUtil::ConvertRectFromScreen( 610 panel_container_, shelf_->shelf_widget()->GetWindowBoundsInScreen()); 611 int panel_start_bounds = kPanelIdealSpacing; 612 int panel_end_bounds = horizontal ? 613 panel_container_->bounds().width() - kPanelIdealSpacing : 614 panel_container_->bounds().height() - kPanelIdealSpacing; 615 aura::Window* active_panel = NULL; 616 std::vector<VisiblePanelPositionInfo> visible_panels; 617 for (PanelList::iterator iter = panel_windows_.begin(); 618 iter != panel_windows_.end(); ++iter) { 619 aura::Window* panel = iter->window; 620 iter->callout_widget->SetAlignment(alignment); 621 622 // Consider the dragged panel as part of the layout as long as it is 623 // touching the shelf. 624 if ((!panel->IsVisible() && !iter->slide_in) || 625 (panel == dragged_panel_ && 626 !BoundsAdjacent(panel->bounds(), shelf_bounds))) { 627 continue; 628 } 629 630 // If the shelf is currently hidden (full-screen mode), minimize panel until 631 // full-screen mode is exited. When a panel is dragged from another display 632 // the shelf state does not update before the panel is added so we exclude 633 // the dragged panel. 634 if (panel != dragged_panel_ && restore_windows_on_shelf_visible_) { 635 wm::GetWindowState(panel)->Minimize(); 636 restore_windows_on_shelf_visible_->Add(panel); 637 continue; 638 } 639 640 gfx::Rect icon_bounds = shelf_->GetScreenBoundsOfItemIconForWindow(panel); 641 642 // If both the icon width and height are 0 then there is no icon in the 643 // shelf. If the shelf is hidden, one of the height or width will be 644 // 0 but the position in the shelf and major dimension is still reported 645 // correctly and the panel can be aligned above where the hidden icon is. 646 if (icon_bounds.width() == 0 && icon_bounds.height() == 0) 647 continue; 648 649 if (panel->HasFocus() || 650 panel->Contains( 651 aura::client::GetFocusClient(panel)->GetFocusedWindow())) { 652 DCHECK(!active_panel); 653 active_panel = panel; 654 } 655 icon_bounds = ScreenUtil::ConvertRectFromScreen(panel_container_, 656 icon_bounds); 657 gfx::Point icon_origin = icon_bounds.origin(); 658 VisiblePanelPositionInfo position_info; 659 int icon_start = horizontal ? icon_origin.x() : icon_origin.y(); 660 int icon_end = icon_start + (horizontal ? icon_bounds.width() : 661 icon_bounds.height()); 662 position_info.major_length = horizontal ? 663 panel->bounds().width() : panel->bounds().height(); 664 position_info.min_major = std::max( 665 panel_start_bounds + position_info.major_length / 2, 666 icon_end - position_info.major_length / 2); 667 position_info.max_major = std::min( 668 icon_start + position_info.major_length / 2, 669 panel_end_bounds - position_info.major_length / 2); 670 position_info.major_pos = (icon_start + icon_end) / 2; 671 position_info.window = panel; 672 position_info.slide_in = iter->slide_in; 673 iter->slide_in = false; 674 visible_panels.push_back(position_info); 675 } 676 677 // Sort panels by their X positions and fan out groups of overlapping panels. 678 // The fan out method may result in new overlapping panels however given that 679 // the panels start at least a full panel width apart this overlap will 680 // never completely obscure a panel. 681 // TODO(flackr): Rearrange panels if new overlaps are introduced. 682 std::sort(visible_panels.begin(), visible_panels.end(), CompareWindowMajor); 683 size_t first_overlapping_panel = 0; 684 for (size_t i = 1; i < visible_panels.size(); ++i) { 685 if (visible_panels[i - 1].major_pos + 686 visible_panels[i - 1].major_length / 2 < visible_panels[i].major_pos - 687 visible_panels[i].major_length / 2) { 688 FanOutPanels(visible_panels.begin() + first_overlapping_panel, 689 visible_panels.begin() + i); 690 first_overlapping_panel = i; 691 } 692 } 693 FanOutPanels(visible_panels.begin() + first_overlapping_panel, 694 visible_panels.end()); 695 696 for (size_t i = 0; i < visible_panels.size(); ++i) { 697 if (visible_panels[i].window == dragged_panel_) 698 continue; 699 bool slide_in = visible_panels[i].slide_in; 700 gfx::Rect bounds = visible_panels[i].window->GetTargetBounds(); 701 switch (alignment) { 702 case SHELF_ALIGNMENT_BOTTOM: 703 bounds.set_y(shelf_bounds.y() - bounds.height()); 704 break; 705 case SHELF_ALIGNMENT_LEFT: 706 bounds.set_x(shelf_bounds.right()); 707 break; 708 case SHELF_ALIGNMENT_RIGHT: 709 bounds.set_x(shelf_bounds.x() - bounds.width()); 710 break; 711 case SHELF_ALIGNMENT_TOP: 712 bounds.set_y(shelf_bounds.bottom()); 713 break; 714 } 715 bool on_shelf = visible_panels[i].window->GetTargetBounds() == bounds; 716 717 if (horizontal) { 718 bounds.set_x(visible_panels[i].major_pos - 719 visible_panels[i].major_length / 2); 720 } else { 721 bounds.set_y(visible_panels[i].major_pos - 722 visible_panels[i].major_length / 2); 723 } 724 725 ui::Layer* layer = visible_panels[i].window->layer(); 726 if (slide_in) { 727 // New windows shift up from the shelf into position and fade in. 728 layer->SetOpacity(0); 729 gfx::Rect initial_bounds(bounds); 730 initial_bounds.Offset(GetSlideInAnimationOffset(alignment)); 731 SetChildBoundsDirect(visible_panels[i].window, initial_bounds); 732 // Set on shelf so that the panel animates into its target position. 733 on_shelf = true; 734 } 735 736 if (on_shelf) { 737 ui::ScopedLayerAnimationSettings panel_slide_settings( 738 layer->GetAnimator()); 739 panel_slide_settings.SetPreemptionStrategy( 740 ui::LayerAnimator::IMMEDIATELY_ANIMATE_TO_NEW_TARGET); 741 panel_slide_settings.SetTransitionDuration( 742 base::TimeDelta::FromMilliseconds(kPanelSlideDurationMilliseconds)); 743 SetChildBoundsDirect(visible_panels[i].window, bounds); 744 if (slide_in) { 745 layer->SetOpacity(1); 746 visible_panels[i].window->Show(); 747 } 748 } else { 749 // If the shelf moved don't animate, move immediately to the new 750 // target location. 751 SetChildBoundsDirect(visible_panels[i].window, bounds); 752 } 753 } 754 755 UpdateStacking(active_panel); 756 UpdateCallouts(); 757 } 758 759 void PanelLayoutManager::UpdateStacking(aura::Window* active_panel) { 760 if (!active_panel) { 761 if (!last_active_panel_) 762 return; 763 active_panel = last_active_panel_; 764 } 765 766 ShelfAlignment alignment = shelf_->alignment(); 767 bool horizontal = alignment == SHELF_ALIGNMENT_TOP || 768 alignment == SHELF_ALIGNMENT_BOTTOM; 769 770 // We want to to stack the panels like a deck of cards: 771 // ,--,--,--,-------.--.--. 772 // | | | | | | | 773 // | | | | | | | 774 // 775 // We use the middle of each panel to figure out how to stack the panels. This 776 // allows us to update the stacking when a panel is being dragged around by 777 // the titlebar--even though it doesn't update the shelf icon positions, we 778 // still want the visual effect. 779 std::map<int, aura::Window*> window_ordering; 780 for (PanelList::const_iterator it = panel_windows_.begin(); 781 it != panel_windows_.end(); ++it) { 782 gfx::Rect bounds = it->window->bounds(); 783 window_ordering.insert(std::make_pair(horizontal ? 784 bounds.x() + bounds.width() / 2 : 785 bounds.y() + bounds.height() / 2, 786 it->window)); 787 } 788 789 aura::Window* previous_panel = NULL; 790 for (std::map<int, aura::Window*>::const_iterator it = 791 window_ordering.begin(); 792 it != window_ordering.end() && it->second != active_panel; ++it) { 793 if (previous_panel) 794 panel_container_->StackChildAbove(it->second, previous_panel); 795 previous_panel = it->second; 796 } 797 798 previous_panel = NULL; 799 for (std::map<int, aura::Window*>::const_reverse_iterator it = 800 window_ordering.rbegin(); 801 it != window_ordering.rend() && it->second != active_panel; ++it) { 802 if (previous_panel) 803 panel_container_->StackChildAbove(it->second, previous_panel); 804 previous_panel = it->second; 805 } 806 807 panel_container_->StackChildAtTop(active_panel); 808 if (dragged_panel_ && dragged_panel_->parent() == panel_container_) 809 panel_container_->StackChildAtTop(dragged_panel_); 810 last_active_panel_ = active_panel; 811 } 812 813 void PanelLayoutManager::UpdateCallouts() { 814 ShelfAlignment alignment = shelf_->alignment(); 815 bool horizontal = alignment == SHELF_ALIGNMENT_TOP || 816 alignment == SHELF_ALIGNMENT_BOTTOM; 817 818 for (PanelList::iterator iter = panel_windows_.begin(); 819 iter != panel_windows_.end(); ++iter) { 820 aura::Window* panel = iter->window; 821 views::Widget* callout_widget = iter->callout_widget; 822 823 gfx::Rect current_bounds = panel->GetBoundsInScreen(); 824 gfx::Rect bounds = ScreenUtil::ConvertRectToScreen( 825 panel->parent(), 826 panel->GetTargetBounds()); 827 gfx::Rect icon_bounds = shelf_->GetScreenBoundsOfItemIconForWindow(panel); 828 if (icon_bounds.IsEmpty() || !panel->layer()->GetTargetVisibility() || 829 panel == dragged_panel_ || !show_callout_widgets_) { 830 callout_widget->Hide(); 831 callout_widget->GetNativeWindow()->layer()->SetOpacity(0); 832 continue; 833 } 834 835 gfx::Rect callout_bounds = callout_widget->GetWindowBoundsInScreen(); 836 gfx::Vector2d slide_vector = bounds.origin() - current_bounds.origin(); 837 int slide_distance = horizontal ? slide_vector.x() : slide_vector.y(); 838 int distance_until_over_panel = 0; 839 if (horizontal) { 840 callout_bounds.set_x( 841 icon_bounds.x() + (icon_bounds.width() - callout_bounds.width()) / 2); 842 distance_until_over_panel = std::max( 843 current_bounds.x() - callout_bounds.x(), 844 callout_bounds.right() - current_bounds.right()); 845 } else { 846 callout_bounds.set_y( 847 icon_bounds.y() + (icon_bounds.height() - 848 callout_bounds.height()) / 2); 849 distance_until_over_panel = std::max( 850 current_bounds.y() - callout_bounds.y(), 851 callout_bounds.bottom() - current_bounds.bottom()); 852 } 853 switch (alignment) { 854 case SHELF_ALIGNMENT_BOTTOM: 855 callout_bounds.set_y(bounds.bottom()); 856 break; 857 case SHELF_ALIGNMENT_LEFT: 858 callout_bounds.set_x(bounds.x() - callout_bounds.width()); 859 break; 860 case SHELF_ALIGNMENT_RIGHT: 861 callout_bounds.set_x(bounds.right()); 862 break; 863 case SHELF_ALIGNMENT_TOP: 864 callout_bounds.set_y(bounds.y() - callout_bounds.height()); 865 break; 866 } 867 callout_bounds = ScreenUtil::ConvertRectFromScreen( 868 callout_widget->GetNativeWindow()->parent(), 869 callout_bounds); 870 871 SetChildBoundsDirect(callout_widget->GetNativeWindow(), callout_bounds); 872 panel_container_->StackChildAbove(callout_widget->GetNativeWindow(), 873 panel); 874 875 ui::Layer* layer = callout_widget->GetNativeWindow()->layer(); 876 // If the panel is not over the callout position or has just become visible 877 // then fade in the callout. 878 if ((distance_until_over_panel > 0 || layer->GetTargetOpacity() < 1)) { 879 if (distance_until_over_panel > 0 && 880 slide_distance >= distance_until_over_panel) { 881 // If the panel is not yet over the callout, then delay fading in 882 // the callout until after the panel should be over it. 883 int delay = kPanelSlideDurationMilliseconds * 884 distance_until_over_panel / slide_distance; 885 layer->SetOpacity(0); 886 layer->GetAnimator()->StopAnimating(); 887 layer->GetAnimator()->SchedulePauseForProperties( 888 base::TimeDelta::FromMilliseconds(delay), 889 ui::LayerAnimationElement::OPACITY); 890 } 891 ui::ScopedLayerAnimationSettings callout_settings(layer->GetAnimator()); 892 callout_settings.SetPreemptionStrategy( 893 ui::LayerAnimator::REPLACE_QUEUED_ANIMATIONS); 894 callout_settings.SetTransitionDuration( 895 base::TimeDelta::FromMilliseconds( 896 kCalloutFadeDurationMilliseconds)); 897 layer->SetOpacity(1); 898 } 899 900 // Show after changing the opacity animation. This way we don't have a 901 // state where the widget is visible but the opacity is 0. 902 callout_widget->Show(); 903 } 904 } 905 906 //////////////////////////////////////////////////////////////////////////////// 907 // keyboard::KeyboardControllerObserver implementation: 908 909 void PanelLayoutManager::OnKeyboardBoundsChanging( 910 const gfx::Rect& keyboard_bounds) { 911 gfx::Rect parent_bounds = panel_container_->bounds(); 912 int available_space = parent_bounds.height() - keyboard_bounds.height(); 913 for (PanelList::iterator iter = panel_windows_.begin(); 914 iter != panel_windows_.end(); 915 ++iter) { 916 aura::Window* panel = iter->window; 917 wm::WindowState* panel_state = wm::GetWindowState(panel); 918 if (keyboard_bounds.height() > 0) { 919 // Save existing bounds, so that we can restore them when the keyboard 920 // hides. 921 panel_state->SaveCurrentBoundsForRestore(); 922 923 gfx::Rect panel_bounds = ScreenUtil::ConvertRectToScreen( 924 panel->parent(), panel->GetTargetBounds()); 925 int delta = panel_bounds.height() - available_space; 926 // Ensure panels are not pushed above the parent boundaries, shrink any 927 // panels that violate this constraint. 928 if (delta > 0) { 929 SetChildBounds(panel, 930 gfx::Rect(panel_bounds.x(), 931 panel_bounds.y() + delta, 932 panel_bounds.width(), 933 panel_bounds.height() - delta)); 934 } 935 } else if (panel_state->HasRestoreBounds()) { 936 // Keyboard hidden, restore original bounds if they exist. 937 SetChildBounds(panel, panel_state->GetRestoreBoundsInScreen()); 938 } 939 } 940 // This bounds change will have caused a change to the Shelf which does not 941 // propogate automatically to this class, so manually recalculate bounds. 942 OnWindowResized(); 943 } 944 945 } // namespace ash 946