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 "ui/app_list/views/apps_grid_view.h" 6 7 #include <algorithm> 8 #include <set> 9 #include <string> 10 11 #include "base/guid.h" 12 #include "ui/app_list/app_list_constants.h" 13 #include "ui/app_list/app_list_folder_item.h" 14 #include "ui/app_list/app_list_item.h" 15 #include "ui/app_list/app_list_switches.h" 16 #include "ui/app_list/views/app_list_drag_and_drop_host.h" 17 #include "ui/app_list/views/app_list_folder_view.h" 18 #include "ui/app_list/views/app_list_item_view.h" 19 #include "ui/app_list/views/apps_grid_view_delegate.h" 20 #include "ui/app_list/views/page_switcher.h" 21 #include "ui/app_list/views/pulsing_block_view.h" 22 #include "ui/app_list/views/top_icon_animation_view.h" 23 #include "ui/compositor/scoped_layer_animation_settings.h" 24 #include "ui/events/event.h" 25 #include "ui/gfx/animation/animation.h" 26 #include "ui/views/border.h" 27 #include "ui/views/view_model_utils.h" 28 #include "ui/views/widget/widget.h" 29 30 #if defined(USE_AURA) 31 #include "ui/aura/window.h" 32 #include "ui/aura/window_event_dispatcher.h" 33 #if defined(OS_WIN) 34 #include "ui/views/win/hwnd_util.h" 35 #endif // defined(OS_WIN) 36 #endif // defined(USE_AURA) 37 38 #if defined(OS_WIN) 39 #include "base/command_line.h" 40 #include "base/files/file_path.h" 41 #include "base/win/shortcut.h" 42 #include "ui/base/dragdrop/drag_utils.h" 43 #include "ui/base/dragdrop/drop_target_win.h" 44 #include "ui/base/dragdrop/os_exchange_data.h" 45 #include "ui/base/dragdrop/os_exchange_data_provider_win.h" 46 #include "ui/gfx/win/dpi.h" 47 #endif 48 49 namespace app_list { 50 51 namespace { 52 53 // Distance a drag needs to be from the app grid to be considered 'outside', at 54 // which point we rearrange the apps to their pre-drag configuration, as a drop 55 // then would be canceled. We have a buffer to make it easier to drag apps to 56 // other pages. 57 const int kDragBufferPx = 20; 58 59 // Padding space in pixels for fixed layout. 60 const int kLeftRightPadding = 20; 61 const int kTopPadding = 1; 62 63 // Padding space in pixels between pages. 64 const int kPagePadding = 40; 65 66 // Preferred tile size when showing in fixed layout. 67 const int kPreferredTileWidth = 88; 68 const int kPreferredTileHeight = 98; 69 70 // Width in pixels of the area on the sides that triggers a page flip. 71 const int kPageFlipZoneSize = 40; 72 73 // Delay in milliseconds to do the page flip. 74 const int kPageFlipDelayInMs = 1000; 75 76 // How many pages on either side of the selected one we prerender. 77 const int kPrerenderPages = 1; 78 79 // The drag and drop proxy should get scaled by this factor. 80 const float kDragAndDropProxyScale = 1.5f; 81 82 // Delays in milliseconds to show folder dropping preview circle. 83 const int kFolderDroppingDelay = 150; 84 85 // Delays in milliseconds to show re-order preview. 86 const int kReorderDelay = 120; 87 88 // Delays in milliseconds to show folder item reparent UI. 89 const int kFolderItemReparentDelay = 50; 90 91 // Radius of the circle, in which if entered, show folder dropping preview 92 // UI. 93 const int kFolderDroppingCircleRadius = 15; 94 95 96 // RowMoveAnimationDelegate is used when moving an item into a different row. 97 // Before running the animation, the item's layer is re-created and kept in 98 // the original position, then the item is moved to just before its target 99 // position and opacity set to 0. When the animation runs, this delegate moves 100 // the layer and fades it out while fading in the item at the same time. 101 class RowMoveAnimationDelegate : public gfx::AnimationDelegate { 102 public: 103 RowMoveAnimationDelegate(views::View* view, 104 ui::Layer* layer, 105 const gfx::Rect& layer_target) 106 : view_(view), 107 layer_(layer), 108 layer_start_(layer ? layer->bounds() : gfx::Rect()), 109 layer_target_(layer_target) { 110 } 111 virtual ~RowMoveAnimationDelegate() {} 112 113 // gfx::AnimationDelegate overrides: 114 virtual void AnimationProgressed(const gfx::Animation* animation) OVERRIDE { 115 view_->layer()->SetOpacity(animation->GetCurrentValue()); 116 view_->layer()->ScheduleDraw(); 117 118 if (layer_) { 119 layer_->SetOpacity(1 - animation->GetCurrentValue()); 120 layer_->SetBounds(animation->CurrentValueBetween(layer_start_, 121 layer_target_)); 122 layer_->ScheduleDraw(); 123 } 124 } 125 virtual void AnimationEnded(const gfx::Animation* animation) OVERRIDE { 126 view_->layer()->SetOpacity(1.0f); 127 view_->SchedulePaint(); 128 } 129 virtual void AnimationCanceled(const gfx::Animation* animation) OVERRIDE { 130 view_->layer()->SetOpacity(1.0f); 131 view_->SchedulePaint(); 132 } 133 134 private: 135 // The view that needs to be wrapped. Owned by views hierarchy. 136 views::View* view_; 137 138 scoped_ptr<ui::Layer> layer_; 139 const gfx::Rect layer_start_; 140 const gfx::Rect layer_target_; 141 142 DISALLOW_COPY_AND_ASSIGN(RowMoveAnimationDelegate); 143 }; 144 145 // ItemRemoveAnimationDelegate is used to show animation for removing an item. 146 // This happens when user drags an item into a folder. The dragged item will 147 // be removed from the original list after it is dropped into the folder. 148 class ItemRemoveAnimationDelegate : public gfx::AnimationDelegate { 149 public: 150 explicit ItemRemoveAnimationDelegate(views::View* view) 151 : view_(view) { 152 } 153 154 virtual ~ItemRemoveAnimationDelegate() { 155 } 156 157 // gfx::AnimationDelegate overrides: 158 virtual void AnimationProgressed(const gfx::Animation* animation) OVERRIDE { 159 view_->layer()->SetOpacity(1 - animation->GetCurrentValue()); 160 view_->layer()->ScheduleDraw(); 161 } 162 163 private: 164 scoped_ptr<views::View> view_; 165 166 DISALLOW_COPY_AND_ASSIGN(ItemRemoveAnimationDelegate); 167 }; 168 169 // ItemMoveAnimationDelegate observes when an item finishes animating when it is 170 // not moving between rows. This is to ensure an item is repainted for the 171 // "zoom out" case when releasing an item being dragged. 172 class ItemMoveAnimationDelegate : public gfx::AnimationDelegate { 173 public: 174 ItemMoveAnimationDelegate(views::View* view) : view_(view) {} 175 176 virtual void AnimationEnded(const gfx::Animation* animation) OVERRIDE { 177 view_->SchedulePaint(); 178 } 179 virtual void AnimationCanceled(const gfx::Animation* animation) OVERRIDE { 180 view_->SchedulePaint(); 181 } 182 183 private: 184 views::View* view_; 185 186 DISALLOW_COPY_AND_ASSIGN(ItemMoveAnimationDelegate); 187 }; 188 189 // Gets the distance between the centers of the |rect_1| and |rect_2|. 190 int GetDistanceBetweenRects(gfx::Rect rect_1, 191 gfx::Rect rect_2) { 192 return (rect_1.CenterPoint() - rect_2.CenterPoint()).Length(); 193 } 194 195 // Returns true if the |item| is an folder item. 196 bool IsFolderItem(AppListItem* item) { 197 return (item->GetItemType() == AppListFolderItem::kItemType); 198 } 199 200 bool IsOEMFolderItem(AppListItem* item) { 201 return IsFolderItem(item) && 202 (static_cast<AppListFolderItem*>(item))->folder_type() == 203 AppListFolderItem::FOLDER_TYPE_OEM; 204 } 205 206 } // namespace 207 208 #if defined(OS_WIN) 209 // Interprets drag events sent from Windows via the drag/drop API and forwards 210 // them to AppsGridView. 211 // On Windows, in order to have the OS perform the drag properly we need to 212 // provide it with a shortcut file which may or may not exist at the time the 213 // drag is started. Therefore while waiting for that shortcut to be located we 214 // just do a regular "internal" drag and transition into the synchronous drag 215 // when the shortcut is found/created. Hence a synchronous drag is an optional 216 // phase of a regular drag and non-Windows platforms drags are equivalent to a 217 // Windows drag that never enters the synchronous drag phase. 218 class SynchronousDrag : public ui::DragSourceWin { 219 public: 220 SynchronousDrag(AppsGridView* grid_view, 221 AppListItemView* drag_view, 222 const gfx::Point& drag_view_offset) 223 : grid_view_(grid_view), 224 drag_view_(drag_view), 225 drag_view_offset_(drag_view_offset), 226 has_shortcut_path_(false), 227 running_(false), 228 canceled_(false) {} 229 230 void set_shortcut_path(const base::FilePath& shortcut_path) { 231 has_shortcut_path_ = true; 232 shortcut_path_ = shortcut_path; 233 } 234 235 bool running() { return running_; } 236 237 bool CanRun() { 238 return has_shortcut_path_ && !running_; 239 } 240 241 void Run() { 242 DCHECK(CanRun()); 243 244 // Prevent the synchronous dragger being destroyed while the drag is 245 // running. 246 scoped_refptr<SynchronousDrag> this_ref = this; 247 running_ = true; 248 249 ui::OSExchangeData data; 250 SetupExchangeData(&data); 251 252 // Hide the dragged view because the OS is going to create its own. 253 drag_view_->SetVisible(false); 254 255 // Blocks until the drag is finished. Calls into the ui::DragSourceWin 256 // methods. 257 DWORD effects; 258 DoDragDrop(ui::OSExchangeDataProviderWin::GetIDataObject(data), 259 this, DROPEFFECT_MOVE | DROPEFFECT_LINK, &effects); 260 261 // If |drag_view_| is NULL the drag was ended by some reentrant code. 262 if (drag_view_) { 263 // Make the drag view visible again. 264 drag_view_->SetVisible(true); 265 drag_view_->OnSyncDragEnd(); 266 267 grid_view_->EndDrag(canceled_ || !IsCursorWithinGridView()); 268 } 269 } 270 271 void EndDragExternally() { 272 CancelDrag(); 273 drag_view_ = NULL; 274 } 275 276 private: 277 // Overridden from ui::DragSourceWin. 278 virtual void OnDragSourceCancel() OVERRIDE { 279 canceled_ = true; 280 } 281 282 virtual void OnDragSourceDrop() OVERRIDE { 283 } 284 285 virtual void OnDragSourceMove() OVERRIDE { 286 grid_view_->UpdateDrag(AppsGridView::MOUSE, GetCursorInGridViewCoords()); 287 } 288 289 void SetupExchangeData(ui::OSExchangeData* data) { 290 data->SetFilename(shortcut_path_); 291 gfx::ImageSkia image(drag_view_->GetDragImage()); 292 gfx::Size image_size(image.size()); 293 drag_utils::SetDragImageOnDataObject( 294 image, 295 drag_view_offset_ - drag_view_->GetDragImageOffset(), 296 data); 297 } 298 299 HWND GetGridViewHWND() { 300 return views::HWNDForView(grid_view_); 301 } 302 303 bool IsCursorWithinGridView() { 304 POINT p; 305 GetCursorPos(&p); 306 return GetGridViewHWND() == WindowFromPoint(p); 307 } 308 309 gfx::Point GetCursorInGridViewCoords() { 310 POINT p; 311 GetCursorPos(&p); 312 ScreenToClient(GetGridViewHWND(), &p); 313 gfx::Point grid_view_pt(p.x, p.y); 314 grid_view_pt = gfx::win::ScreenToDIPPoint(grid_view_pt); 315 views::View::ConvertPointFromWidget(grid_view_, &grid_view_pt); 316 return grid_view_pt; 317 } 318 319 AppsGridView* grid_view_; 320 AppListItemView* drag_view_; 321 gfx::Point drag_view_offset_; 322 bool has_shortcut_path_; 323 base::FilePath shortcut_path_; 324 bool running_; 325 bool canceled_; 326 327 DISALLOW_COPY_AND_ASSIGN(SynchronousDrag); 328 }; 329 #endif // defined(OS_WIN) 330 331 AppsGridView::AppsGridView(AppsGridViewDelegate* delegate) 332 : model_(NULL), 333 item_list_(NULL), 334 delegate_(delegate), 335 folder_delegate_(NULL), 336 page_switcher_view_(NULL), 337 cols_(0), 338 rows_per_page_(0), 339 selected_view_(NULL), 340 drag_view_(NULL), 341 drag_start_page_(-1), 342 #if defined(OS_WIN) 343 use_synchronous_drag_(true), 344 #endif 345 drag_pointer_(NONE), 346 drop_attempt_(DROP_FOR_NONE), 347 drag_and_drop_host_(NULL), 348 forward_events_to_drag_and_drop_host_(false), 349 page_flip_target_(-1), 350 page_flip_delay_in_ms_(kPageFlipDelayInMs), 351 bounds_animator_(this), 352 activated_folder_item_view_(NULL), 353 dragging_for_reparent_item_(false) { 354 SetPaintToLayer(true); 355 // Clip any icons that are outside the grid view's bounds. These icons would 356 // otherwise be visible to the user when the grid view is off screen. 357 layer()->SetMasksToBounds(true); 358 SetFillsBoundsOpaquely(false); 359 360 pagination_model_.SetTransitionDurations(kPageTransitionDurationInMs, 361 kOverscrollPageTransitionDurationMs); 362 363 pagination_model_.AddObserver(this); 364 page_switcher_view_ = new PageSwitcher(&pagination_model_); 365 AddChildView(page_switcher_view_); 366 } 367 368 AppsGridView::~AppsGridView() { 369 // Coming here |drag_view_| should already be canceled since otherwise the 370 // drag would disappear after the app list got animated away and closed, 371 // which would look odd. 372 DCHECK(!drag_view_); 373 if (drag_view_) 374 EndDrag(true); 375 376 if (model_) 377 model_->RemoveObserver(this); 378 pagination_model_.RemoveObserver(this); 379 380 if (item_list_) 381 item_list_->RemoveObserver(this); 382 383 // Make sure |page_switcher_view_| is deleted before |pagination_model_|. 384 view_model_.Clear(); 385 RemoveAllChildViews(true); 386 } 387 388 void AppsGridView::SetLayout(int icon_size, int cols, int rows_per_page) { 389 icon_size_.SetSize(icon_size, icon_size); 390 cols_ = cols; 391 rows_per_page_ = rows_per_page; 392 393 SetBorder(views::Border::CreateEmptyBorder( 394 kTopPadding, kLeftRightPadding, 0, kLeftRightPadding)); 395 } 396 397 void AppsGridView::ResetForShowApps() { 398 activated_folder_item_view_ = NULL; 399 ClearDragState(); 400 layer()->SetOpacity(1.0f); 401 SetVisible(true); 402 // Set all views to visible in case they weren't made visible again by an 403 // incomplete animation. 404 for (int i = 0; i < view_model_.view_size(); ++i) { 405 view_model_.view_at(i)->SetVisible(true); 406 } 407 CHECK_EQ(item_list_->item_count(), 408 static_cast<size_t>(view_model_.view_size())); 409 } 410 411 void AppsGridView::SetModel(AppListModel* model) { 412 if (model_) 413 model_->RemoveObserver(this); 414 415 model_ = model; 416 if (model_) 417 model_->AddObserver(this); 418 419 Update(); 420 } 421 422 void AppsGridView::SetItemList(AppListItemList* item_list) { 423 if (item_list_) 424 item_list_->RemoveObserver(this); 425 item_list_ = item_list; 426 if (item_list_) 427 item_list_->AddObserver(this); 428 Update(); 429 } 430 431 void AppsGridView::SetSelectedView(views::View* view) { 432 if (IsSelectedView(view) || IsDraggedView(view)) 433 return; 434 435 Index index = GetIndexOfView(view); 436 if (IsValidIndex(index)) 437 SetSelectedItemByIndex(index); 438 } 439 440 void AppsGridView::ClearSelectedView(views::View* view) { 441 if (view && IsSelectedView(view)) { 442 selected_view_->SchedulePaint(); 443 selected_view_ = NULL; 444 } 445 } 446 447 void AppsGridView::ClearAnySelectedView() { 448 if (selected_view_) { 449 selected_view_->SchedulePaint(); 450 selected_view_ = NULL; 451 } 452 } 453 454 bool AppsGridView::IsSelectedView(const views::View* view) const { 455 return selected_view_ == view; 456 } 457 458 void AppsGridView::EnsureViewVisible(const views::View* view) { 459 if (pagination_model_.has_transition()) 460 return; 461 462 Index index = GetIndexOfView(view); 463 if (IsValidIndex(index)) 464 pagination_model_.SelectPage(index.page, false); 465 } 466 467 void AppsGridView::InitiateDrag(AppListItemView* view, 468 Pointer pointer, 469 const ui::LocatedEvent& event) { 470 DCHECK(view); 471 if (drag_view_ || pulsing_blocks_model_.view_size()) 472 return; 473 474 drag_view_ = view; 475 drag_view_init_index_ = GetIndexOfView(drag_view_); 476 drag_view_offset_ = event.location(); 477 drag_start_page_ = pagination_model_.selected_page(); 478 ExtractDragLocation(event, &drag_start_grid_view_); 479 drag_view_start_ = gfx::Point(drag_view_->x(), drag_view_->y()); 480 } 481 482 void AppsGridView::StartSettingUpSynchronousDrag() { 483 #if defined(OS_WIN) 484 if (!delegate_ || !use_synchronous_drag_) 485 return; 486 487 // Folders can't be integrated with the OS. 488 if (IsFolderItem(drag_view_->item())) 489 return; 490 491 // Favor the drag and drop host over native win32 drag. For the Win8/ash 492 // launcher we want to have ashes drag and drop over win32's. 493 if (drag_and_drop_host_) 494 return; 495 496 // Never create a second synchronous drag if the drag started in a folder. 497 if (IsDraggingForReparentInRootLevelGridView()) 498 return; 499 500 synchronous_drag_ = new SynchronousDrag(this, drag_view_, drag_view_offset_); 501 delegate_->GetShortcutPathForApp(drag_view_->item()->id(), 502 base::Bind(&AppsGridView::OnGotShortcutPath, 503 base::Unretained(this), 504 synchronous_drag_)); 505 #endif 506 } 507 508 bool AppsGridView::RunSynchronousDrag() { 509 #if defined(OS_WIN) 510 if (!synchronous_drag_) 511 return false; 512 513 if (synchronous_drag_->CanRun()) { 514 if (IsDraggingForReparentInHiddenGridView()) 515 folder_delegate_->SetRootLevelDragViewVisible(false); 516 synchronous_drag_->Run(); 517 synchronous_drag_ = NULL; 518 return true; 519 } else if (!synchronous_drag_->running()) { 520 // The OS drag is not ready yet. If the root grid has a drag view because 521 // a reparent has started, ensure it is visible. 522 if (IsDraggingForReparentInHiddenGridView()) 523 folder_delegate_->SetRootLevelDragViewVisible(true); 524 } 525 #endif 526 return false; 527 } 528 529 void AppsGridView::CleanUpSynchronousDrag() { 530 #if defined(OS_WIN) 531 if (synchronous_drag_) 532 synchronous_drag_->EndDragExternally(); 533 534 synchronous_drag_ = NULL; 535 #endif 536 } 537 538 #if defined(OS_WIN) 539 void AppsGridView::OnGotShortcutPath( 540 scoped_refptr<SynchronousDrag> synchronous_drag, 541 const base::FilePath& path) { 542 // Drag may have ended before we get the shortcut path or a new drag may have 543 // begun. 544 if (synchronous_drag_ != synchronous_drag) 545 return; 546 // Setting the shortcut path here means the next time we hit UpdateDrag() 547 // we'll enter the synchronous drag. 548 // NOTE we don't Run() the drag here because that causes animations not to 549 // update for some reason. 550 synchronous_drag_->set_shortcut_path(path); 551 DCHECK(synchronous_drag_->CanRun()); 552 } 553 #endif 554 555 bool AppsGridView::UpdateDragFromItem(Pointer pointer, 556 const ui::LocatedEvent& event) { 557 DCHECK(drag_view_); 558 559 gfx::Point drag_point_in_grid_view; 560 ExtractDragLocation(event, &drag_point_in_grid_view); 561 UpdateDrag(pointer, drag_point_in_grid_view); 562 if (!dragging()) 563 return false; 564 565 // If a drag and drop host is provided, see if the drag operation needs to be 566 // forwarded. 567 gfx::Point location_in_screen = drag_point_in_grid_view; 568 views::View::ConvertPointToScreen(this, &location_in_screen); 569 DispatchDragEventToDragAndDropHost(location_in_screen); 570 if (drag_and_drop_host_) 571 drag_and_drop_host_->UpdateDragIconProxy(location_in_screen); 572 return true; 573 } 574 575 void AppsGridView::UpdateDrag(Pointer pointer, const gfx::Point& point) { 576 if (folder_delegate_) 577 UpdateDragStateInsideFolder(pointer, point); 578 579 // EndDrag was called before if |drag_view_| is NULL. 580 if (!drag_view_) 581 return; 582 583 if (RunSynchronousDrag()) 584 return; 585 586 gfx::Vector2d drag_vector(point - drag_start_grid_view_); 587 if (!dragging() && ExceededDragThreshold(drag_vector)) { 588 drag_pointer_ = pointer; 589 // Move the view to the front so that it appears on top of other views. 590 ReorderChildView(drag_view_, -1); 591 bounds_animator_.StopAnimatingView(drag_view_); 592 // Stopping the animation may have invalidated our drag view due to the 593 // view hierarchy changing. 594 if (!drag_view_) 595 return; 596 597 StartSettingUpSynchronousDrag(); 598 if (!dragging_for_reparent_item_) 599 StartDragAndDropHostDrag(point); 600 } 601 602 if (drag_pointer_ != pointer) 603 return; 604 605 last_drag_point_ = point; 606 const Index last_drop_target = drop_target_; 607 DropAttempt last_drop_attempt = drop_attempt_; 608 CalculateDropTarget(last_drag_point_, false); 609 610 if (IsPointWithinDragBuffer(last_drag_point_)) 611 MaybeStartPageFlipTimer(last_drag_point_); 612 else 613 StopPageFlipTimer(); 614 615 gfx::Point page_switcher_point(last_drag_point_); 616 views::View::ConvertPointToTarget(this, page_switcher_view_, 617 &page_switcher_point); 618 page_switcher_view_->UpdateUIForDragPoint(page_switcher_point); 619 620 if (!EnableFolderDragDropUI()) { 621 if (last_drop_target != drop_target_) 622 AnimateToIdealBounds(); 623 drag_view_->SetPosition(drag_view_start_ + drag_vector); 624 return; 625 } 626 627 // Update drag with folder UI enabled. 628 if (last_drop_target != drop_target_ || 629 last_drop_attempt != drop_attempt_) { 630 if (drop_attempt_ == DROP_FOR_REORDER) { 631 folder_dropping_timer_.Stop(); 632 reorder_timer_.Start(FROM_HERE, 633 base::TimeDelta::FromMilliseconds(kReorderDelay), 634 this, &AppsGridView::OnReorderTimer); 635 } else if (drop_attempt_ == DROP_FOR_FOLDER) { 636 reorder_timer_.Stop(); 637 folder_dropping_timer_.Start(FROM_HERE, 638 base::TimeDelta::FromMilliseconds(kFolderDroppingDelay), 639 this, &AppsGridView::OnFolderDroppingTimer); 640 } 641 642 // Reset the previous drop target. 643 SetAsFolderDroppingTarget(last_drop_target, false); 644 } 645 646 drag_view_->SetPosition(drag_view_start_ + drag_vector); 647 } 648 649 void AppsGridView::EndDrag(bool cancel) { 650 // EndDrag was called before if |drag_view_| is NULL. 651 if (!drag_view_) 652 return; 653 654 // Coming here a drag and drop was in progress. 655 bool landed_in_drag_and_drop_host = forward_events_to_drag_and_drop_host_; 656 if (forward_events_to_drag_and_drop_host_) { 657 DCHECK(!IsDraggingForReparentInRootLevelGridView()); 658 forward_events_to_drag_and_drop_host_ = false; 659 drag_and_drop_host_->EndDrag(cancel); 660 if (IsDraggingForReparentInHiddenGridView()) { 661 folder_delegate_->DispatchEndDragEventForReparent( 662 true /* events_forwarded_to_drag_drop_host */, 663 cancel /* cancel_drag */); 664 } 665 } else { 666 if (IsDraggingForReparentInHiddenGridView()) { 667 // Forward the EndDrag event to the root level grid view. 668 folder_delegate_->DispatchEndDragEventForReparent( 669 false /* events_forwarded_to_drag_drop_host */, 670 cancel /* cancel_drag */); 671 EndDragForReparentInHiddenFolderGridView(); 672 return; 673 } 674 675 if (!cancel && dragging()) { 676 // Regular drag ending path, ie, not for reparenting. 677 CalculateDropTarget(last_drag_point_, true); 678 if (IsValidIndex(drop_target_)) { 679 if (!EnableFolderDragDropUI()) { 680 MoveItemInModel(drag_view_, drop_target_); 681 } else { 682 if (drop_attempt_ == DROP_FOR_REORDER) 683 MoveItemInModel(drag_view_, drop_target_); 684 else if (drop_attempt_ == DROP_FOR_FOLDER) 685 MoveItemToFolder(drag_view_, drop_target_); 686 } 687 } 688 } 689 } 690 691 if (drag_and_drop_host_) { 692 // If we had a drag and drop proxy icon, we delete it and make the real 693 // item visible again. 694 drag_and_drop_host_->DestroyDragIconProxy(); 695 if (landed_in_drag_and_drop_host) { 696 // Move the item directly to the target location, avoiding the "zip back" 697 // animation if the user was pinning it to the shelf. 698 int i = drop_target_.slot; 699 gfx::Rect bounds = view_model_.ideal_bounds(i); 700 drag_view_->SetBoundsRect(bounds); 701 } 702 // Fade in slowly if it landed in the shelf. 703 SetViewHidden(drag_view_, 704 false /* show */, 705 !landed_in_drag_and_drop_host /* animate */); 706 } 707 708 // The drag can be ended after the synchronous drag is created but before it 709 // is Run(). 710 CleanUpSynchronousDrag(); 711 712 SetAsFolderDroppingTarget(drop_target_, false); 713 ClearDragState(); 714 AnimateToIdealBounds(); 715 716 StopPageFlipTimer(); 717 718 // If user releases mouse inside a folder's grid view, burst the folder 719 // container ink bubble. 720 if (folder_delegate_ && !IsDraggingForReparentInHiddenGridView()) 721 folder_delegate_->UpdateFolderViewBackground(false); 722 } 723 724 void AppsGridView::StopPageFlipTimer() { 725 page_flip_timer_.Stop(); 726 page_flip_target_ = -1; 727 } 728 729 AppListItemView* AppsGridView::GetItemViewAt(int index) const { 730 DCHECK(index >= 0 && index < view_model_.view_size()); 731 return static_cast<AppListItemView*>(view_model_.view_at(index)); 732 } 733 734 void AppsGridView::SetTopItemViewsVisible(bool visible) { 735 int top_item_count = std::min(static_cast<int>(kNumFolderTopItems), 736 view_model_.view_size()); 737 for (int i = 0; i < top_item_count; ++i) 738 GetItemViewAt(i)->SetVisible(visible); 739 } 740 741 void AppsGridView::ScheduleShowHideAnimation(bool show) { 742 // Stop any previous animation. 743 layer()->GetAnimator()->StopAnimating(); 744 745 // Set initial state. 746 SetVisible(true); 747 layer()->SetOpacity(show ? 0.0f : 1.0f); 748 749 ui::ScopedLayerAnimationSettings animation(layer()->GetAnimator()); 750 animation.AddObserver(this); 751 animation.SetTweenType( 752 show ? kFolderFadeInTweenType : kFolderFadeOutTweenType); 753 animation.SetTransitionDuration(base::TimeDelta::FromMilliseconds( 754 show ? kFolderTransitionInDurationMs : kFolderTransitionOutDurationMs)); 755 756 layer()->SetOpacity(show ? 1.0f : 0.0f); 757 } 758 759 void AppsGridView::InitiateDragFromReparentItemInRootLevelGridView( 760 AppListItemView* original_drag_view, 761 const gfx::Rect& drag_view_rect, 762 const gfx::Point& drag_point) { 763 DCHECK(original_drag_view && !drag_view_); 764 DCHECK(!dragging_for_reparent_item_); 765 766 // Create a new AppListItemView to duplicate the original_drag_view in the 767 // folder's grid view. 768 AppListItemView* view = new AppListItemView(this, original_drag_view->item()); 769 AddChildView(view); 770 drag_view_ = view; 771 drag_view_->SetPaintToLayer(true); 772 // Note: For testing purpose, SetFillsBoundsOpaquely can be set to true to 773 // show the gray background. 774 drag_view_->SetFillsBoundsOpaquely(false); 775 drag_view_->SetIconSize(icon_size_); 776 drag_view_->SetBoundsRect(drag_view_rect); 777 drag_view_->SetDragUIState(); // Hide the title of the drag_view_. 778 779 // Hide the drag_view_ for drag icon proxy. 780 SetViewHidden(drag_view_, 781 true /* hide */, 782 true /* no animate */); 783 784 // Add drag_view_ to the end of the view_model_. 785 view_model_.Add(drag_view_, view_model_.view_size()); 786 787 drag_start_page_ = pagination_model_.selected_page(); 788 drag_start_grid_view_ = drag_point; 789 790 drag_view_start_ = gfx::Point(drag_view_->x(), drag_view_->y()); 791 792 // Set the flag in root level grid view. 793 dragging_for_reparent_item_ = true; 794 } 795 796 void AppsGridView::UpdateDragFromReparentItem(Pointer pointer, 797 const gfx::Point& drag_point) { 798 DCHECK(drag_view_); 799 DCHECK(IsDraggingForReparentInRootLevelGridView()); 800 801 UpdateDrag(pointer, drag_point); 802 } 803 804 bool AppsGridView::IsDraggedView(const views::View* view) const { 805 return drag_view_ == view; 806 } 807 808 void AppsGridView::ClearDragState() { 809 drop_attempt_ = DROP_FOR_NONE; 810 drag_pointer_ = NONE; 811 drop_target_ = Index(); 812 drag_start_grid_view_ = gfx::Point(); 813 drag_start_page_ = -1; 814 drag_view_offset_ = gfx::Point(); 815 816 if (drag_view_) { 817 drag_view_->OnDragEnded(); 818 if (IsDraggingForReparentInRootLevelGridView()) { 819 const int drag_view_index = view_model_.GetIndexOfView(drag_view_); 820 CHECK_EQ(view_model_.view_size() - 1, drag_view_index); 821 DeleteItemViewAtIndex(drag_view_index); 822 } 823 } 824 drag_view_ = NULL; 825 dragging_for_reparent_item_ = false; 826 } 827 828 void AppsGridView::SetDragViewVisible(bool visible) { 829 DCHECK(drag_view_); 830 SetViewHidden(drag_view_, !visible, true); 831 } 832 833 void AppsGridView::SetDragAndDropHostOfCurrentAppList( 834 ApplicationDragAndDropHost* drag_and_drop_host) { 835 drag_and_drop_host_ = drag_and_drop_host; 836 } 837 838 void AppsGridView::Prerender(int page_index) { 839 Layout(); 840 int start = std::max(0, (page_index - kPrerenderPages) * tiles_per_page()); 841 int end = std::min(view_model_.view_size(), 842 (page_index + 1 + kPrerenderPages) * tiles_per_page()); 843 for (int i = start; i < end; i++) { 844 AppListItemView* v = static_cast<AppListItemView*>(view_model_.view_at(i)); 845 v->Prerender(); 846 } 847 } 848 849 bool AppsGridView::IsAnimatingView(views::View* view) { 850 return bounds_animator_.IsAnimating(view); 851 } 852 853 gfx::Size AppsGridView::GetPreferredSize() const { 854 const gfx::Insets insets(GetInsets()); 855 const gfx::Size tile_size = gfx::Size(kPreferredTileWidth, 856 kPreferredTileHeight); 857 const int page_switcher_height = 858 page_switcher_view_->GetPreferredSize().height(); 859 return gfx::Size( 860 tile_size.width() * cols_ + insets.width(), 861 tile_size.height() * rows_per_page_ + 862 page_switcher_height + insets.height()); 863 } 864 865 bool AppsGridView::GetDropFormats( 866 int* formats, 867 std::set<OSExchangeData::CustomFormat>* custom_formats) { 868 // TODO(koz): Only accept a specific drag type for app shortcuts. 869 *formats = OSExchangeData::FILE_NAME; 870 return true; 871 } 872 873 bool AppsGridView::CanDrop(const OSExchangeData& data) { 874 return true; 875 } 876 877 int AppsGridView::OnDragUpdated(const ui::DropTargetEvent& event) { 878 return ui::DragDropTypes::DRAG_MOVE; 879 } 880 881 void AppsGridView::Layout() { 882 if (bounds_animator_.IsAnimating()) 883 bounds_animator_.Cancel(); 884 885 CalculateIdealBounds(); 886 for (int i = 0; i < view_model_.view_size(); ++i) { 887 views::View* view = view_model_.view_at(i); 888 if (view != drag_view_) 889 view->SetBoundsRect(view_model_.ideal_bounds(i)); 890 } 891 views::ViewModelUtils::SetViewBoundsToIdealBounds(pulsing_blocks_model_); 892 893 const int page_switcher_height = 894 page_switcher_view_->GetPreferredSize().height(); 895 gfx::Rect rect(GetContentsBounds()); 896 rect.set_y(rect.bottom() - page_switcher_height); 897 rect.set_height(page_switcher_height); 898 page_switcher_view_->SetBoundsRect(rect); 899 } 900 901 bool AppsGridView::OnKeyPressed(const ui::KeyEvent& event) { 902 bool handled = false; 903 if (selected_view_) 904 handled = selected_view_->OnKeyPressed(event); 905 906 if (!handled) { 907 const int forward_dir = base::i18n::IsRTL() ? -1 : 1; 908 switch (event.key_code()) { 909 case ui::VKEY_LEFT: 910 MoveSelected(0, -forward_dir, 0); 911 return true; 912 case ui::VKEY_RIGHT: 913 MoveSelected(0, forward_dir, 0); 914 return true; 915 case ui::VKEY_UP: 916 MoveSelected(0, 0, -1); 917 return true; 918 case ui::VKEY_DOWN: 919 MoveSelected(0, 0, 1); 920 return true; 921 case ui::VKEY_PRIOR: { 922 MoveSelected(-1, 0, 0); 923 return true; 924 } 925 case ui::VKEY_NEXT: { 926 MoveSelected(1, 0, 0); 927 return true; 928 } 929 default: 930 break; 931 } 932 } 933 934 return handled; 935 } 936 937 bool AppsGridView::OnKeyReleased(const ui::KeyEvent& event) { 938 bool handled = false; 939 if (selected_view_) 940 handled = selected_view_->OnKeyReleased(event); 941 942 return handled; 943 } 944 945 void AppsGridView::ViewHierarchyChanged( 946 const ViewHierarchyChangedDetails& details) { 947 if (!details.is_add && details.parent == this) { 948 // The view being delete should not have reference in |view_model_|. 949 CHECK_EQ(-1, view_model_.GetIndexOfView(details.child)); 950 951 if (selected_view_ == details.child) 952 selected_view_ = NULL; 953 if (activated_folder_item_view_ == details.child) 954 activated_folder_item_view_ = NULL; 955 956 if (drag_view_ == details.child) 957 EndDrag(true); 958 959 bounds_animator_.StopAnimatingView(details.child); 960 } 961 } 962 963 void AppsGridView::Update() { 964 DCHECK(!selected_view_ && !drag_view_); 965 view_model_.Clear(); 966 if (!item_list_ || !item_list_->item_count()) 967 return; 968 for (size_t i = 0; i < item_list_->item_count(); ++i) { 969 views::View* view = CreateViewForItemAtIndex(i); 970 view_model_.Add(view, i); 971 AddChildView(view); 972 } 973 UpdatePaging(); 974 UpdatePulsingBlockViews(); 975 Layout(); 976 SchedulePaint(); 977 } 978 979 void AppsGridView::UpdatePaging() { 980 int total_page = view_model_.view_size() && tiles_per_page() 981 ? (view_model_.view_size() - 1) / tiles_per_page() + 1 982 : 0; 983 984 pagination_model_.SetTotalPages(total_page); 985 } 986 987 void AppsGridView::UpdatePulsingBlockViews() { 988 const int existing_items = item_list_ ? item_list_->item_count() : 0; 989 const int available_slots = 990 tiles_per_page() - existing_items % tiles_per_page(); 991 const int desired = model_->status() == AppListModel::STATUS_SYNCING ? 992 available_slots : 0; 993 994 if (pulsing_blocks_model_.view_size() == desired) 995 return; 996 997 while (pulsing_blocks_model_.view_size() > desired) { 998 views::View* view = pulsing_blocks_model_.view_at(0); 999 pulsing_blocks_model_.Remove(0); 1000 delete view; 1001 } 1002 1003 while (pulsing_blocks_model_.view_size() < desired) { 1004 views::View* view = new PulsingBlockView( 1005 gfx::Size(kPreferredTileWidth, kPreferredTileHeight), true); 1006 pulsing_blocks_model_.Add(view, 0); 1007 AddChildView(view); 1008 } 1009 } 1010 1011 views::View* AppsGridView::CreateViewForItemAtIndex(size_t index) { 1012 // The drag_view_ might be pending for deletion, therefore view_model_ 1013 // may have one more item than item_list_. 1014 DCHECK_LE(index, item_list_->item_count()); 1015 AppListItemView* view = new AppListItemView(this, 1016 item_list_->item_at(index)); 1017 view->SetIconSize(icon_size_); 1018 view->SetPaintToLayer(true); 1019 view->SetFillsBoundsOpaquely(false); 1020 return view; 1021 } 1022 1023 AppsGridView::Index AppsGridView::GetIndexFromModelIndex( 1024 int model_index) const { 1025 return Index(model_index / tiles_per_page(), model_index % tiles_per_page()); 1026 } 1027 1028 int AppsGridView::GetModelIndexFromIndex(const Index& index) const { 1029 return index.page * tiles_per_page() + index.slot; 1030 } 1031 1032 void AppsGridView::SetSelectedItemByIndex(const Index& index) { 1033 if (GetIndexOfView(selected_view_) == index) 1034 return; 1035 1036 views::View* new_selection = GetViewAtIndex(index); 1037 if (!new_selection) 1038 return; // Keep current selection. 1039 1040 if (selected_view_) 1041 selected_view_->SchedulePaint(); 1042 1043 EnsureViewVisible(new_selection); 1044 selected_view_ = new_selection; 1045 selected_view_->SchedulePaint(); 1046 selected_view_->NotifyAccessibilityEvent( 1047 ui::AX_EVENT_FOCUS, true); 1048 } 1049 1050 bool AppsGridView::IsValidIndex(const Index& index) const { 1051 return index.page >= 0 && index.page < pagination_model_.total_pages() && 1052 index.slot >= 0 && index.slot < tiles_per_page() && 1053 GetModelIndexFromIndex(index) < view_model_.view_size(); 1054 } 1055 1056 AppsGridView::Index AppsGridView::GetIndexOfView( 1057 const views::View* view) const { 1058 const int model_index = view_model_.GetIndexOfView(view); 1059 if (model_index == -1) 1060 return Index(); 1061 1062 return GetIndexFromModelIndex(model_index); 1063 } 1064 1065 views::View* AppsGridView::GetViewAtIndex(const Index& index) const { 1066 if (!IsValidIndex(index)) 1067 return NULL; 1068 1069 const int model_index = GetModelIndexFromIndex(index); 1070 return view_model_.view_at(model_index); 1071 } 1072 1073 void AppsGridView::MoveSelected(int page_delta, 1074 int slot_x_delta, 1075 int slot_y_delta) { 1076 if (!selected_view_) 1077 return SetSelectedItemByIndex(Index(pagination_model_.selected_page(), 0)); 1078 1079 const Index& selected = GetIndexOfView(selected_view_); 1080 int target_slot = selected.slot + slot_x_delta + slot_y_delta * cols_; 1081 1082 if (selected.slot % cols_ == 0 && slot_x_delta == -1) { 1083 if (selected.page > 0) { 1084 page_delta = -1; 1085 target_slot = selected.slot + cols_ - 1; 1086 } else { 1087 target_slot = selected.slot; 1088 } 1089 } 1090 1091 if (selected.slot % cols_ == cols_ - 1 && slot_x_delta == 1) { 1092 if (selected.page < pagination_model_.total_pages() - 1) { 1093 page_delta = 1; 1094 target_slot = selected.slot - cols_ + 1; 1095 } else { 1096 target_slot = selected.slot; 1097 } 1098 } 1099 1100 // Clamp the target slot to the last item if we are moving to the last page 1101 // but our target slot is past the end of the item list. 1102 if (page_delta && 1103 selected.page + page_delta == pagination_model_.total_pages() - 1) { 1104 int last_item_slot = (view_model_.view_size() - 1) % tiles_per_page(); 1105 if (last_item_slot < target_slot) { 1106 target_slot = last_item_slot; 1107 } 1108 } 1109 1110 int target_page = std::min(pagination_model_.total_pages() - 1, 1111 std::max(selected.page + page_delta, 0)); 1112 SetSelectedItemByIndex(Index(target_page, target_slot)); 1113 } 1114 1115 void AppsGridView::CalculateIdealBounds() { 1116 gfx::Rect rect(GetContentsBounds()); 1117 if (rect.IsEmpty()) 1118 return; 1119 1120 gfx::Size tile_size(kPreferredTileWidth, kPreferredTileHeight); 1121 1122 gfx::Rect grid_rect(gfx::Size(tile_size.width() * cols_, 1123 tile_size.height() * rows_per_page_)); 1124 grid_rect.Intersect(rect); 1125 1126 // Page width including padding pixels. A tile.x + page_width means the same 1127 // tile slot in the next page. 1128 const int page_width = grid_rect.width() + kPagePadding; 1129 1130 // If there is a transition, calculates offset for current and target page. 1131 const int current_page = pagination_model_.selected_page(); 1132 const PaginationModel::Transition& transition = 1133 pagination_model_.transition(); 1134 const bool is_valid = pagination_model_.is_valid_page(transition.target_page); 1135 1136 // Transition to right means negative offset. 1137 const int dir = transition.target_page > current_page ? -1 : 1; 1138 const int transition_offset = is_valid ? 1139 transition.progress * page_width * dir : 0; 1140 1141 const int total_views = 1142 view_model_.view_size() + pulsing_blocks_model_.view_size(); 1143 int slot_index = 0; 1144 for (int i = 0; i < total_views; ++i) { 1145 if (i < view_model_.view_size() && view_model_.view_at(i) == drag_view_) { 1146 if (EnableFolderDragDropUI() && drop_attempt_ == DROP_FOR_FOLDER) 1147 ++slot_index; 1148 continue; 1149 } 1150 1151 Index view_index = GetIndexFromModelIndex(slot_index); 1152 1153 if (drop_target_ == view_index) { 1154 if (EnableFolderDragDropUI() && drop_attempt_ == DROP_FOR_FOLDER) { 1155 view_index = GetIndexFromModelIndex(slot_index); 1156 } else if (!EnableFolderDragDropUI() || 1157 drop_attempt_ == DROP_FOR_REORDER) { 1158 ++slot_index; 1159 view_index = GetIndexFromModelIndex(slot_index); 1160 } 1161 } 1162 1163 // Decides an x_offset for current item. 1164 int x_offset = 0; 1165 if (view_index.page < current_page) 1166 x_offset = -page_width; 1167 else if (view_index.page > current_page) 1168 x_offset = page_width; 1169 1170 if (is_valid) { 1171 if (view_index.page == current_page || 1172 view_index.page == transition.target_page) { 1173 x_offset += transition_offset; 1174 } 1175 } 1176 1177 const int row = view_index.slot / cols_; 1178 const int col = view_index.slot % cols_; 1179 gfx::Rect tile_slot( 1180 gfx::Point(grid_rect.x() + col * tile_size.width() + x_offset, 1181 grid_rect.y() + row * tile_size.height()), 1182 tile_size); 1183 if (i < view_model_.view_size()) { 1184 view_model_.set_ideal_bounds(i, tile_slot); 1185 } else { 1186 pulsing_blocks_model_.set_ideal_bounds(i - view_model_.view_size(), 1187 tile_slot); 1188 } 1189 1190 ++slot_index; 1191 } 1192 } 1193 1194 void AppsGridView::AnimateToIdealBounds() { 1195 const gfx::Rect visible_bounds(GetVisibleBounds()); 1196 1197 CalculateIdealBounds(); 1198 for (int i = 0; i < view_model_.view_size(); ++i) { 1199 views::View* view = view_model_.view_at(i); 1200 if (view == drag_view_) 1201 continue; 1202 1203 const gfx::Rect& target = view_model_.ideal_bounds(i); 1204 if (bounds_animator_.GetTargetBounds(view) == target) 1205 continue; 1206 1207 const gfx::Rect& current = view->bounds(); 1208 const bool current_visible = visible_bounds.Intersects(current); 1209 const bool target_visible = visible_bounds.Intersects(target); 1210 const bool visible = current_visible || target_visible; 1211 1212 const int y_diff = target.y() - current.y(); 1213 if (visible && y_diff && y_diff % kPreferredTileHeight == 0) { 1214 AnimationBetweenRows(view, 1215 current_visible, 1216 current, 1217 target_visible, 1218 target); 1219 } else if (visible || bounds_animator_.IsAnimating(view)) { 1220 bounds_animator_.AnimateViewTo(view, target); 1221 bounds_animator_.SetAnimationDelegate( 1222 view, 1223 scoped_ptr<gfx::AnimationDelegate>( 1224 new ItemMoveAnimationDelegate(view))); 1225 } else { 1226 view->SetBoundsRect(target); 1227 } 1228 } 1229 } 1230 1231 void AppsGridView::AnimationBetweenRows(views::View* view, 1232 bool animate_current, 1233 const gfx::Rect& current, 1234 bool animate_target, 1235 const gfx::Rect& target) { 1236 // Determine page of |current| and |target|. -1 means in the left invisible 1237 // page, 0 is the center visible page and 1 means in the right invisible page. 1238 const int current_page = current.x() < 0 ? -1 : 1239 current.x() >= width() ? 1 : 0; 1240 const int target_page = target.x() < 0 ? -1 : 1241 target.x() >= width() ? 1 : 0; 1242 1243 const int dir = current_page < target_page || 1244 (current_page == target_page && current.y() < target.y()) ? 1 : -1; 1245 1246 scoped_ptr<ui::Layer> layer; 1247 if (animate_current) { 1248 layer = view->RecreateLayer(); 1249 layer->SuppressPaint(); 1250 1251 view->SetFillsBoundsOpaquely(false); 1252 view->layer()->SetOpacity(0.f); 1253 } 1254 1255 gfx::Rect current_out(current); 1256 current_out.Offset(dir * kPreferredTileWidth, 0); 1257 1258 gfx::Rect target_in(target); 1259 if (animate_target) 1260 target_in.Offset(-dir * kPreferredTileWidth, 0); 1261 view->SetBoundsRect(target_in); 1262 bounds_animator_.AnimateViewTo(view, target); 1263 1264 bounds_animator_.SetAnimationDelegate( 1265 view, 1266 scoped_ptr<gfx::AnimationDelegate>( 1267 new RowMoveAnimationDelegate(view, layer.release(), current_out))); 1268 } 1269 1270 void AppsGridView::ExtractDragLocation(const ui::LocatedEvent& event, 1271 gfx::Point* drag_point) { 1272 #if defined(USE_AURA) && !defined(OS_WIN) 1273 // Use root location of |event| instead of location in |drag_view_|'s 1274 // coordinates because |drag_view_| has a scale transform and location 1275 // could have integer round error and causes jitter. 1276 *drag_point = event.root_location(); 1277 1278 // GetWidget() could be NULL for tests. 1279 if (GetWidget()) { 1280 aura::Window::ConvertPointToTarget( 1281 GetWidget()->GetNativeWindow()->GetRootWindow(), 1282 GetWidget()->GetNativeWindow(), 1283 drag_point); 1284 } 1285 1286 views::View::ConvertPointFromWidget(this, drag_point); 1287 #else 1288 // For non-aura, root location is not clearly defined but |drag_view_| does 1289 // not have the scale transform. So no round error would be introduced and 1290 // it's okay to use View::ConvertPointToTarget. 1291 *drag_point = event.location(); 1292 views::View::ConvertPointToTarget(drag_view_, this, drag_point); 1293 #endif 1294 } 1295 1296 void AppsGridView::CalculateDropTarget(const gfx::Point& drag_point, 1297 bool use_page_button_hovering) { 1298 if (EnableFolderDragDropUI()) { 1299 CalculateDropTargetWithFolderEnabled(drag_point, use_page_button_hovering); 1300 return; 1301 } 1302 1303 int current_page = pagination_model_.selected_page(); 1304 gfx::Point point(drag_point); 1305 if (!IsPointWithinDragBuffer(drag_point)) { 1306 point = drag_start_grid_view_; 1307 current_page = drag_start_page_; 1308 } 1309 1310 if (use_page_button_hovering && 1311 page_switcher_view_->bounds().Contains(point)) { 1312 gfx::Point page_switcher_point(point); 1313 views::View::ConvertPointToTarget(this, page_switcher_view_, 1314 &page_switcher_point); 1315 int page = page_switcher_view_->GetPageForPoint(page_switcher_point); 1316 if (pagination_model_.is_valid_page(page)) { 1317 drop_target_.page = page; 1318 drop_target_.slot = tiles_per_page() - 1; 1319 } 1320 } else { 1321 gfx::Rect bounds(GetContentsBounds()); 1322 const int drop_row = (point.y() - bounds.y()) / kPreferredTileHeight; 1323 const int drop_col = std::min(cols_ - 1, 1324 (point.x() - bounds.x()) / kPreferredTileWidth); 1325 1326 drop_target_.page = current_page; 1327 drop_target_.slot = std::max(0, std::min( 1328 tiles_per_page() - 1, 1329 drop_row * cols_ + drop_col)); 1330 } 1331 1332 // Limits to the last possible slot on last page. 1333 if (drop_target_.page == pagination_model_.total_pages() - 1) { 1334 drop_target_.slot = std::min( 1335 (view_model_.view_size() - 1) % tiles_per_page(), 1336 drop_target_.slot); 1337 } 1338 } 1339 1340 1341 void AppsGridView::CalculateDropTargetWithFolderEnabled( 1342 const gfx::Point& drag_point, 1343 bool use_page_button_hovering) { 1344 gfx::Point point(drag_point); 1345 if (!IsPointWithinDragBuffer(drag_point)) { 1346 point = drag_start_grid_view_; 1347 } 1348 1349 if (use_page_button_hovering && 1350 page_switcher_view_->bounds().Contains(point)) { 1351 gfx::Point page_switcher_point(point); 1352 views::View::ConvertPointToTarget(this, page_switcher_view_, 1353 &page_switcher_point); 1354 int page = page_switcher_view_->GetPageForPoint(page_switcher_point); 1355 if (pagination_model_.is_valid_page(page)) 1356 drop_attempt_ = DROP_FOR_NONE; 1357 } else { 1358 DCHECK(drag_view_); 1359 // Try to find the nearest target for folder dropping or re-ordering. 1360 drop_target_ = GetNearestTileForDragView(); 1361 } 1362 } 1363 1364 void AppsGridView::OnReorderTimer() { 1365 if (drop_attempt_ == DROP_FOR_REORDER) 1366 AnimateToIdealBounds(); 1367 } 1368 1369 void AppsGridView::OnFolderItemReparentTimer() { 1370 DCHECK(folder_delegate_); 1371 if (drag_out_of_folder_container_ && drag_view_) { 1372 folder_delegate_->ReparentItem(drag_view_, last_drag_point_); 1373 1374 // Set the flag in the folder's grid view. 1375 dragging_for_reparent_item_ = true; 1376 1377 // Do not observe any data change since it is going to be hidden. 1378 item_list_->RemoveObserver(this); 1379 item_list_ = NULL; 1380 } 1381 } 1382 1383 void AppsGridView::OnFolderDroppingTimer() { 1384 if (drop_attempt_ == DROP_FOR_FOLDER) 1385 SetAsFolderDroppingTarget(drop_target_, true); 1386 } 1387 1388 void AppsGridView::UpdateDragStateInsideFolder(Pointer pointer, 1389 const gfx::Point& drag_point) { 1390 if (IsUnderOEMFolder()) 1391 return; 1392 1393 if (IsDraggingForReparentInHiddenGridView()) { 1394 // Dispatch drag event to root level grid view for re-parenting folder 1395 // folder item purpose. 1396 DispatchDragEventForReparent(pointer, drag_point); 1397 return; 1398 } 1399 1400 // Regular drag and drop in a folder's grid view. 1401 folder_delegate_->UpdateFolderViewBackground(true); 1402 1403 // Calculate if the drag_view_ is dragged out of the folder's container 1404 // ink bubble. 1405 gfx::Rect bounds_to_folder_view = ConvertRectToParent(drag_view_->bounds()); 1406 gfx::Point pt = bounds_to_folder_view.CenterPoint(); 1407 bool is_item_dragged_out_of_folder = 1408 folder_delegate_->IsPointOutsideOfFolderBoundary(pt); 1409 if (is_item_dragged_out_of_folder) { 1410 if (!drag_out_of_folder_container_) { 1411 folder_item_reparent_timer_.Start( 1412 FROM_HERE, 1413 base::TimeDelta::FromMilliseconds(kFolderItemReparentDelay), 1414 this, 1415 &AppsGridView::OnFolderItemReparentTimer); 1416 drag_out_of_folder_container_ = true; 1417 } 1418 } else { 1419 folder_item_reparent_timer_.Stop(); 1420 drag_out_of_folder_container_ = false; 1421 } 1422 } 1423 1424 bool AppsGridView::IsDraggingForReparentInRootLevelGridView() const { 1425 return (!folder_delegate_ && dragging_for_reparent_item_); 1426 } 1427 1428 bool AppsGridView::IsDraggingForReparentInHiddenGridView() const { 1429 return (folder_delegate_ && dragging_for_reparent_item_); 1430 } 1431 1432 gfx::Rect AppsGridView::GetTargetIconRectInFolder( 1433 AppListItemView* drag_item_view, 1434 AppListItemView* folder_item_view) { 1435 gfx::Rect view_ideal_bounds = view_model_.ideal_bounds( 1436 view_model_.GetIndexOfView(folder_item_view)); 1437 gfx::Rect icon_ideal_bounds = 1438 folder_item_view->GetIconBoundsForTargetViewBounds(view_ideal_bounds); 1439 AppListFolderItem* folder_item = 1440 static_cast<AppListFolderItem*>(folder_item_view->item()); 1441 return folder_item->GetTargetIconRectInFolderForItem( 1442 drag_item_view->item(), icon_ideal_bounds); 1443 } 1444 1445 bool AppsGridView::IsUnderOEMFolder() { 1446 if (!folder_delegate_) 1447 return false; 1448 1449 return folder_delegate_->IsOEMFolder(); 1450 } 1451 1452 void AppsGridView::DispatchDragEventForReparent(Pointer pointer, 1453 const gfx::Point& drag_point) { 1454 folder_delegate_->DispatchDragEventForReparent(pointer, drag_point); 1455 } 1456 1457 void AppsGridView::EndDragFromReparentItemInRootLevel( 1458 bool events_forwarded_to_drag_drop_host, 1459 bool cancel_drag) { 1460 // EndDrag was called before if |drag_view_| is NULL. 1461 if (!drag_view_) 1462 return; 1463 1464 DCHECK(IsDraggingForReparentInRootLevelGridView()); 1465 bool cancel_reparent = cancel_drag || drop_attempt_ == DROP_FOR_NONE; 1466 if (!events_forwarded_to_drag_drop_host && !cancel_reparent) { 1467 CalculateDropTarget(last_drag_point_, true); 1468 if (IsValidIndex(drop_target_)) { 1469 if (drop_attempt_ == DROP_FOR_REORDER) { 1470 ReparentItemForReorder(drag_view_, drop_target_); 1471 } else if (drop_attempt_ == DROP_FOR_FOLDER) { 1472 ReparentItemToAnotherFolder(drag_view_, drop_target_); 1473 } 1474 } 1475 SetViewHidden(drag_view_, false /* show */, true /* no animate */); 1476 } 1477 1478 // The drag can be ended after the synchronous drag is created but before it 1479 // is Run(). 1480 CleanUpSynchronousDrag(); 1481 1482 SetAsFolderDroppingTarget(drop_target_, false); 1483 if (cancel_reparent) { 1484 CancelFolderItemReparent(drag_view_); 1485 } else { 1486 // By setting |drag_view_| to NULL here, we prevent ClearDragState() from 1487 // cleaning up the newly created AppListItemView, effectively claiming 1488 // ownership of the newly created drag view. 1489 drag_view_->OnDragEnded(); 1490 drag_view_ = NULL; 1491 } 1492 ClearDragState(); 1493 AnimateToIdealBounds(); 1494 1495 StopPageFlipTimer(); 1496 } 1497 1498 void AppsGridView::EndDragForReparentInHiddenFolderGridView() { 1499 if (drag_and_drop_host_) { 1500 // If we had a drag and drop proxy icon, we delete it and make the real 1501 // item visible again. 1502 drag_and_drop_host_->DestroyDragIconProxy(); 1503 } 1504 1505 // The drag can be ended after the synchronous drag is created but before it 1506 // is Run(). 1507 CleanUpSynchronousDrag(); 1508 1509 SetAsFolderDroppingTarget(drop_target_, false); 1510 ClearDragState(); 1511 } 1512 1513 void AppsGridView::OnFolderItemRemoved() { 1514 DCHECK(folder_delegate_); 1515 item_list_ = NULL; 1516 } 1517 1518 void AppsGridView::StartDragAndDropHostDrag(const gfx::Point& grid_location) { 1519 // When a drag and drop host is given, the item can be dragged out of the app 1520 // list window. In that case a proxy widget needs to be used. 1521 // Note: This code has very likely to be changed for Windows (non metro mode) 1522 // when a |drag_and_drop_host_| gets implemented. 1523 if (!drag_view_ || !drag_and_drop_host_) 1524 return; 1525 1526 gfx::Point screen_location = grid_location; 1527 views::View::ConvertPointToScreen(this, &screen_location); 1528 1529 // Determine the mouse offset to the center of the icon so that the drag and 1530 // drop host follows this layer. 1531 gfx::Vector2d delta = drag_view_offset_ - 1532 drag_view_->GetLocalBounds().CenterPoint(); 1533 delta.set_y(delta.y() + drag_view_->title()->size().height() / 2); 1534 1535 // We have to hide the original item since the drag and drop host will do 1536 // the OS dependent code to "lift off the dragged item". 1537 DCHECK(!IsDraggingForReparentInRootLevelGridView()); 1538 drag_and_drop_host_->CreateDragIconProxy(screen_location, 1539 drag_view_->item()->icon(), 1540 drag_view_, 1541 delta, 1542 kDragAndDropProxyScale); 1543 SetViewHidden(drag_view_, 1544 true /* hide */, 1545 true /* no animation */); 1546 } 1547 1548 void AppsGridView::DispatchDragEventToDragAndDropHost( 1549 const gfx::Point& location_in_screen_coordinates) { 1550 if (!drag_view_ || !drag_and_drop_host_) 1551 return; 1552 1553 if (GetLocalBounds().Contains(last_drag_point_)) { 1554 // The event was issued inside the app menu and we should get all events. 1555 if (forward_events_to_drag_and_drop_host_) { 1556 // The DnD host was previously called and needs to be informed that the 1557 // session returns to the owner. 1558 forward_events_to_drag_and_drop_host_ = false; 1559 drag_and_drop_host_->EndDrag(true); 1560 } 1561 } else { 1562 if (IsFolderItem(drag_view_->item())) 1563 return; 1564 1565 // The event happened outside our app menu and we might need to dispatch. 1566 if (forward_events_to_drag_and_drop_host_) { 1567 // Dispatch since we have already started. 1568 if (!drag_and_drop_host_->Drag(location_in_screen_coordinates)) { 1569 // The host is not active any longer and we cancel the operation. 1570 forward_events_to_drag_and_drop_host_ = false; 1571 drag_and_drop_host_->EndDrag(true); 1572 } 1573 } else { 1574 if (drag_and_drop_host_->StartDrag(drag_view_->item()->id(), 1575 location_in_screen_coordinates)) { 1576 // From now on we forward the drag events. 1577 forward_events_to_drag_and_drop_host_ = true; 1578 // Any flip operations are stopped. 1579 StopPageFlipTimer(); 1580 } 1581 } 1582 } 1583 } 1584 1585 void AppsGridView::MaybeStartPageFlipTimer(const gfx::Point& drag_point) { 1586 if (!IsPointWithinDragBuffer(drag_point)) 1587 StopPageFlipTimer(); 1588 int new_page_flip_target = -1; 1589 1590 if (page_switcher_view_->bounds().Contains(drag_point)) { 1591 gfx::Point page_switcher_point(drag_point); 1592 views::View::ConvertPointToTarget(this, page_switcher_view_, 1593 &page_switcher_point); 1594 new_page_flip_target = 1595 page_switcher_view_->GetPageForPoint(page_switcher_point); 1596 } 1597 1598 // TODO(xiyuan): Fix this for RTL. 1599 if (new_page_flip_target == -1 && drag_point.x() < kPageFlipZoneSize) 1600 new_page_flip_target = pagination_model_.selected_page() - 1; 1601 1602 if (new_page_flip_target == -1 && 1603 drag_point.x() > width() - kPageFlipZoneSize) { 1604 new_page_flip_target = pagination_model_.selected_page() + 1; 1605 } 1606 1607 if (new_page_flip_target == page_flip_target_) 1608 return; 1609 1610 StopPageFlipTimer(); 1611 if (pagination_model_.is_valid_page(new_page_flip_target)) { 1612 page_flip_target_ = new_page_flip_target; 1613 1614 if (page_flip_target_ != pagination_model_.selected_page()) { 1615 page_flip_timer_.Start(FROM_HERE, 1616 base::TimeDelta::FromMilliseconds(page_flip_delay_in_ms_), 1617 this, &AppsGridView::OnPageFlipTimer); 1618 } 1619 } 1620 } 1621 1622 void AppsGridView::OnPageFlipTimer() { 1623 DCHECK(pagination_model_.is_valid_page(page_flip_target_)); 1624 pagination_model_.SelectPage(page_flip_target_, true); 1625 } 1626 1627 void AppsGridView::MoveItemInModel(views::View* item_view, 1628 const Index& target) { 1629 int current_model_index = view_model_.GetIndexOfView(item_view); 1630 DCHECK_GE(current_model_index, 0); 1631 1632 int target_model_index = GetModelIndexFromIndex(target); 1633 if (target_model_index == current_model_index) 1634 return; 1635 1636 item_list_->RemoveObserver(this); 1637 item_list_->MoveItem(current_model_index, target_model_index); 1638 view_model_.Move(current_model_index, target_model_index); 1639 item_list_->AddObserver(this); 1640 1641 if (pagination_model_.selected_page() != target.page) 1642 pagination_model_.SelectPage(target.page, false); 1643 } 1644 1645 void AppsGridView::MoveItemToFolder(views::View* item_view, 1646 const Index& target) { 1647 const std::string& source_item_id = 1648 static_cast<AppListItemView*>(item_view)->item()->id(); 1649 AppListItemView* target_view = 1650 static_cast<AppListItemView*>(GetViewAtSlotOnCurrentPage(target.slot)); 1651 const std::string& target_view_item_id = target_view->item()->id(); 1652 1653 // Make change to data model. 1654 item_list_->RemoveObserver(this); 1655 std::string folder_item_id = 1656 model_->MergeItems(target_view_item_id, source_item_id); 1657 item_list_->AddObserver(this); 1658 if (folder_item_id.empty()) { 1659 LOG(ERROR) << "Unable to merge into item id: " << target_view_item_id; 1660 return; 1661 } 1662 if (folder_item_id != target_view_item_id) { 1663 // New folder was created, change the view model to replace the old target 1664 // view with the new folder item view. 1665 size_t folder_item_index; 1666 if (item_list_->FindItemIndex(folder_item_id, &folder_item_index)) { 1667 int target_view_index = view_model_.GetIndexOfView(target_view); 1668 gfx::Rect target_view_bounds = target_view->bounds(); 1669 DeleteItemViewAtIndex(target_view_index); 1670 views::View* target_folder_view = 1671 CreateViewForItemAtIndex(folder_item_index); 1672 target_folder_view->SetBoundsRect(target_view_bounds); 1673 view_model_.Add(target_folder_view, target_view_index); 1674 AddChildView(target_folder_view); 1675 } else { 1676 LOG(ERROR) << "Folder no longer in item_list: " << folder_item_id; 1677 } 1678 } 1679 1680 // Fade out the drag_view_ and delete it when animation ends. 1681 int drag_view_index = view_model_.GetIndexOfView(drag_view_); 1682 view_model_.Remove(drag_view_index); 1683 bounds_animator_.AnimateViewTo(drag_view_, drag_view_->bounds()); 1684 bounds_animator_.SetAnimationDelegate( 1685 drag_view_, 1686 scoped_ptr<gfx::AnimationDelegate>( 1687 new ItemRemoveAnimationDelegate(drag_view_))); 1688 UpdatePaging(); 1689 } 1690 1691 void AppsGridView::ReparentItemForReorder(views::View* item_view, 1692 const Index& target) { 1693 item_list_->RemoveObserver(this); 1694 model_->RemoveObserver(this); 1695 1696 AppListItem* reparent_item = static_cast<AppListItemView*>(item_view)->item(); 1697 DCHECK(reparent_item->IsInFolder()); 1698 const std::string source_folder_id = reparent_item->folder_id(); 1699 AppListFolderItem* source_folder = 1700 static_cast<AppListFolderItem*>(item_list_->FindItem(source_folder_id)); 1701 1702 int target_model_index = GetModelIndexFromIndex(target); 1703 1704 // Remove the source folder view if there is only 1 item in it, since the 1705 // source folder will be deleted after its only child item removed from it. 1706 if (source_folder->ChildItemCount() == 1u) { 1707 const int deleted_folder_index = 1708 view_model_.GetIndexOfView(activated_folder_item_view()); 1709 DeleteItemViewAtIndex(deleted_folder_index); 1710 1711 // Adjust |target_model_index| if it is beyond the deleted folder index. 1712 if (target_model_index > deleted_folder_index) 1713 --target_model_index; 1714 } 1715 1716 // Move the item from its parent folder to top level item list. 1717 // Must move to target_model_index, the location we expect the target item 1718 // to be, not the item location we want to insert before. 1719 int current_model_index = view_model_.GetIndexOfView(item_view); 1720 syncer::StringOrdinal target_position; 1721 if (target_model_index < static_cast<int>(item_list_->item_count())) 1722 target_position = item_list_->item_at(target_model_index)->position(); 1723 model_->MoveItemToFolderAt(reparent_item, "", target_position); 1724 view_model_.Move(current_model_index, target_model_index); 1725 1726 RemoveLastItemFromReparentItemFolderIfNecessary(source_folder_id); 1727 1728 item_list_->AddObserver(this); 1729 model_->AddObserver(this); 1730 UpdatePaging(); 1731 } 1732 1733 void AppsGridView::ReparentItemToAnotherFolder(views::View* item_view, 1734 const Index& target) { 1735 DCHECK(IsDraggingForReparentInRootLevelGridView()); 1736 1737 AppListItemView* target_view = 1738 static_cast<AppListItemView*>(GetViewAtSlotOnCurrentPage(target.slot)); 1739 if (!target_view) 1740 return; 1741 1742 // Make change to data model. 1743 item_list_->RemoveObserver(this); 1744 1745 AppListItem* reparent_item = static_cast<AppListItemView*>(item_view)->item(); 1746 DCHECK(reparent_item->IsInFolder()); 1747 const std::string source_folder_id = reparent_item->folder_id(); 1748 AppListFolderItem* source_folder = 1749 static_cast<AppListFolderItem*>(item_list_->FindItem(source_folder_id)); 1750 1751 // Remove the source folder view if there is only 1 item in it, since the 1752 // source folder will be deleted after its only child item merged into the 1753 // target item. 1754 if (source_folder->ChildItemCount() == 1u) 1755 DeleteItemViewAtIndex( 1756 view_model_.GetIndexOfView(activated_folder_item_view())); 1757 1758 AppListItem* target_item = target_view->item(); 1759 1760 // Move item to the target folder. 1761 std::string target_id_after_merge = 1762 model_->MergeItems(target_item->id(), reparent_item->id()); 1763 if (target_id_after_merge.empty()) { 1764 LOG(ERROR) << "Unable to reparent to item id: " << target_item->id(); 1765 item_list_->AddObserver(this); 1766 return; 1767 } 1768 1769 if (target_id_after_merge != target_item->id()) { 1770 // New folder was created, change the view model to replace the old target 1771 // view with the new folder item view. 1772 const std::string& new_folder_id = reparent_item->folder_id(); 1773 size_t new_folder_index; 1774 if (item_list_->FindItemIndex(new_folder_id, &new_folder_index)) { 1775 int target_view_index = view_model_.GetIndexOfView(target_view); 1776 DeleteItemViewAtIndex(target_view_index); 1777 views::View* new_folder_view = 1778 CreateViewForItemAtIndex(new_folder_index); 1779 view_model_.Add(new_folder_view, target_view_index); 1780 AddChildView(new_folder_view); 1781 } else { 1782 LOG(ERROR) << "Folder no longer in item_list: " << new_folder_id; 1783 } 1784 } 1785 1786 RemoveLastItemFromReparentItemFolderIfNecessary(source_folder_id); 1787 1788 item_list_->AddObserver(this); 1789 1790 // Fade out the drag_view_ and delete it when animation ends. 1791 int drag_view_index = view_model_.GetIndexOfView(drag_view_); 1792 view_model_.Remove(drag_view_index); 1793 bounds_animator_.AnimateViewTo(drag_view_, drag_view_->bounds()); 1794 bounds_animator_.SetAnimationDelegate( 1795 drag_view_, 1796 scoped_ptr<gfx::AnimationDelegate>( 1797 new ItemRemoveAnimationDelegate(drag_view_))); 1798 UpdatePaging(); 1799 } 1800 1801 // After moving the re-parenting item out of the folder, if there is only 1 item 1802 // left, remove the last item out of the folder, delete the folder and insert it 1803 // to the data model at the same position. Make the same change to view_model_ 1804 // accordingly. 1805 void AppsGridView::RemoveLastItemFromReparentItemFolderIfNecessary( 1806 const std::string& source_folder_id) { 1807 AppListFolderItem* source_folder = 1808 static_cast<AppListFolderItem*>(item_list_->FindItem(source_folder_id)); 1809 if (!source_folder || source_folder->ChildItemCount() != 1u) 1810 return; 1811 1812 // Delete view associated with the folder item to be removed. 1813 DeleteItemViewAtIndex( 1814 view_model_.GetIndexOfView(activated_folder_item_view())); 1815 1816 // Now make the data change to remove the folder item in model. 1817 AppListItem* last_item = source_folder->item_list()->item_at(0); 1818 model_->MoveItemToFolderAt(last_item, "", source_folder->position()); 1819 1820 // Create a new item view for the last item in folder. 1821 size_t last_item_index; 1822 if (!item_list_->FindItemIndex(last_item->id(), &last_item_index) || 1823 last_item_index > static_cast<size_t>(view_model_.view_size())) { 1824 NOTREACHED(); 1825 return; 1826 } 1827 views::View* last_item_view = CreateViewForItemAtIndex(last_item_index); 1828 view_model_.Add(last_item_view, last_item_index); 1829 AddChildView(last_item_view); 1830 } 1831 1832 void AppsGridView::CancelFolderItemReparent(AppListItemView* drag_item_view) { 1833 // The icon of the dragged item must target to its final ideal bounds after 1834 // the animation completes. 1835 CalculateIdealBounds(); 1836 1837 gfx::Rect target_icon_rect = 1838 GetTargetIconRectInFolder(drag_item_view, activated_folder_item_view_); 1839 1840 gfx::Rect drag_view_icon_to_grid = 1841 drag_item_view->ConvertRectToParent(drag_item_view->GetIconBounds()); 1842 drag_view_icon_to_grid.ClampToCenteredSize( 1843 gfx::Size(kPreferredIconDimension, kPreferredIconDimension)); 1844 TopIconAnimationView* icon_view = new TopIconAnimationView( 1845 drag_item_view->item()->icon(), 1846 target_icon_rect, 1847 false); /* animate like closing folder */ 1848 AddChildView(icon_view); 1849 icon_view->SetBoundsRect(drag_view_icon_to_grid); 1850 icon_view->TransformView(); 1851 } 1852 1853 void AppsGridView::CancelContextMenusOnCurrentPage() { 1854 int start = pagination_model_.selected_page() * tiles_per_page(); 1855 int end = std::min(view_model_.view_size(), start + tiles_per_page()); 1856 for (int i = start; i < end; ++i) { 1857 AppListItemView* view = 1858 static_cast<AppListItemView*>(view_model_.view_at(i)); 1859 view->CancelContextMenu(); 1860 } 1861 } 1862 1863 void AppsGridView::DeleteItemViewAtIndex(int index) { 1864 views::View* item_view = view_model_.view_at(index); 1865 view_model_.Remove(index); 1866 if (item_view == drag_view_) 1867 drag_view_ = NULL; 1868 delete item_view; 1869 } 1870 1871 bool AppsGridView::IsPointWithinDragBuffer(const gfx::Point& point) const { 1872 gfx::Rect rect(GetLocalBounds()); 1873 rect.Inset(-kDragBufferPx, -kDragBufferPx, -kDragBufferPx, -kDragBufferPx); 1874 return rect.Contains(point); 1875 } 1876 1877 void AppsGridView::ButtonPressed(views::Button* sender, 1878 const ui::Event& event) { 1879 if (dragging()) 1880 return; 1881 1882 if (strcmp(sender->GetClassName(), AppListItemView::kViewClassName)) 1883 return; 1884 1885 if (delegate_) { 1886 // Always set the previous activated_folder_item_view_ to be visible. This 1887 // prevents a case where the item would remain hidden due the 1888 // |activated_folder_item_view_| changing during the animation. We only 1889 // need to track |activated_folder_item_view_| in the root level grid view. 1890 if (!folder_delegate_) { 1891 if (activated_folder_item_view_) 1892 activated_folder_item_view_->SetVisible(true); 1893 AppListItemView* pressed_item_view = 1894 static_cast<AppListItemView*>(sender); 1895 if (IsFolderItem(pressed_item_view->item())) 1896 activated_folder_item_view_ = pressed_item_view; 1897 else 1898 activated_folder_item_view_ = NULL; 1899 } 1900 delegate_->ActivateApp(static_cast<AppListItemView*>(sender)->item(), 1901 event.flags()); 1902 } 1903 } 1904 1905 void AppsGridView::OnListItemAdded(size_t index, AppListItem* item) { 1906 EndDrag(true); 1907 1908 views::View* view = CreateViewForItemAtIndex(index); 1909 view_model_.Add(view, index); 1910 AddChildView(view); 1911 1912 UpdatePaging(); 1913 UpdatePulsingBlockViews(); 1914 Layout(); 1915 SchedulePaint(); 1916 } 1917 1918 void AppsGridView::OnListItemRemoved(size_t index, AppListItem* item) { 1919 EndDrag(true); 1920 1921 DeleteItemViewAtIndex(index); 1922 1923 UpdatePaging(); 1924 UpdatePulsingBlockViews(); 1925 Layout(); 1926 SchedulePaint(); 1927 } 1928 1929 void AppsGridView::OnListItemMoved(size_t from_index, 1930 size_t to_index, 1931 AppListItem* item) { 1932 EndDrag(true); 1933 view_model_.Move(from_index, to_index); 1934 1935 UpdatePaging(); 1936 AnimateToIdealBounds(); 1937 } 1938 1939 void AppsGridView::TotalPagesChanged() { 1940 } 1941 1942 void AppsGridView::SelectedPageChanged(int old_selected, int new_selected) { 1943 if (dragging()) { 1944 CalculateDropTarget(last_drag_point_, true); 1945 Layout(); 1946 MaybeStartPageFlipTimer(last_drag_point_); 1947 } else { 1948 ClearSelectedView(selected_view_); 1949 Layout(); 1950 } 1951 } 1952 1953 void AppsGridView::TransitionStarted() { 1954 CancelContextMenusOnCurrentPage(); 1955 } 1956 1957 void AppsGridView::TransitionChanged() { 1958 // Update layout for valid page transition only since over-scroll no longer 1959 // animates app icons. 1960 const PaginationModel::Transition& transition = 1961 pagination_model_.transition(); 1962 if (pagination_model_.is_valid_page(transition.target_page)) 1963 Layout(); 1964 } 1965 1966 void AppsGridView::OnAppListModelStatusChanged() { 1967 UpdatePulsingBlockViews(); 1968 Layout(); 1969 SchedulePaint(); 1970 } 1971 1972 void AppsGridView::SetViewHidden(views::View* view, bool hide, bool immediate) { 1973 ui::ScopedLayerAnimationSettings animator(view->layer()->GetAnimator()); 1974 animator.SetPreemptionStrategy( 1975 immediate ? ui::LayerAnimator::IMMEDIATELY_SET_NEW_TARGET : 1976 ui::LayerAnimator::BLEND_WITH_CURRENT_ANIMATION); 1977 view->layer()->SetOpacity(hide ? 0 : 1); 1978 } 1979 1980 void AppsGridView::OnImplicitAnimationsCompleted() { 1981 if (layer()->opacity() == 0.0f) 1982 SetVisible(false); 1983 } 1984 1985 bool AppsGridView::EnableFolderDragDropUI() { 1986 // Enable drag and drop folder UI only if it is at the app list root level 1987 // and the switch is on and the target folder can still accept new items. 1988 return model_->folders_enabled() && !folder_delegate_ && 1989 CanDropIntoTarget(drop_target_); 1990 } 1991 1992 bool AppsGridView::CanDropIntoTarget(const Index& drop_target) { 1993 views::View* target_view = GetViewAtSlotOnCurrentPage(drop_target.slot); 1994 if (!target_view) 1995 return true; 1996 1997 AppListItem* target_item = 1998 static_cast<AppListItemView*>(target_view)->item(); 1999 // Items can be dropped into non-folders (which have no children) or folders 2000 // that have fewer than the max allowed items. 2001 // OEM folder does not allow to drag/drop other items in it. 2002 return target_item->ChildItemCount() < kMaxFolderItems && 2003 !IsOEMFolderItem(target_item); 2004 } 2005 2006 // TODO(jennyz): Optimize the calculation for finding nearest tile. 2007 AppsGridView::Index AppsGridView::GetNearestTileForDragView() { 2008 Index nearest_tile; 2009 nearest_tile.page = -1; 2010 nearest_tile.slot = -1; 2011 int d_min = -1; 2012 2013 // Calculate the top left tile |drag_view| intersects. 2014 gfx::Point pt = drag_view_->bounds().origin(); 2015 CalculateNearestTileForVertex(pt, &nearest_tile, &d_min); 2016 2017 // Calculate the top right tile |drag_view| intersects. 2018 pt = drag_view_->bounds().top_right(); 2019 CalculateNearestTileForVertex(pt, &nearest_tile, &d_min); 2020 2021 // Calculate the bottom left tile |drag_view| intersects. 2022 pt = drag_view_->bounds().bottom_left(); 2023 CalculateNearestTileForVertex(pt, &nearest_tile, &d_min); 2024 2025 // Calculate the bottom right tile |drag_view| intersects. 2026 pt = drag_view_->bounds().bottom_right(); 2027 CalculateNearestTileForVertex(pt, &nearest_tile, &d_min); 2028 2029 const int d_folder_dropping = 2030 kFolderDroppingCircleRadius + kPreferredIconDimension / 2; 2031 const int d_reorder = 2032 kReorderDroppingCircleRadius + kPreferredIconDimension / 2; 2033 2034 // If user drags an item across pages to the last page, and targets it 2035 // to the last empty slot on it, push the last item for re-ordering. 2036 if (IsLastPossibleDropTarget(nearest_tile) && d_min < d_reorder) { 2037 drop_attempt_ = DROP_FOR_REORDER; 2038 nearest_tile.slot = nearest_tile.slot - 1; 2039 return nearest_tile; 2040 } 2041 2042 if (IsValidIndex(nearest_tile)) { 2043 if (d_min < d_folder_dropping) { 2044 views::View* target_view = GetViewAtSlotOnCurrentPage(nearest_tile.slot); 2045 if (target_view && 2046 !IsFolderItem(static_cast<AppListItemView*>(drag_view_)->item())) { 2047 // If a non-folder item is dragged to the target slot with an item 2048 // sitting on it, attempt to drop the dragged item into the folder 2049 // containing the item on nearest_tile. 2050 drop_attempt_ = DROP_FOR_FOLDER; 2051 return nearest_tile; 2052 } else { 2053 // If the target slot is blank, or the dragged item is a folder, attempt 2054 // to re-order. 2055 drop_attempt_ = DROP_FOR_REORDER; 2056 return nearest_tile; 2057 } 2058 } else if (d_min < d_reorder) { 2059 // Entering the re-order circle of the slot. 2060 drop_attempt_ = DROP_FOR_REORDER; 2061 return nearest_tile; 2062 } 2063 } 2064 2065 // If |drag_view| is not entering the re-order or fold dropping region of 2066 // any items, cancel any previous re-order or folder dropping timer, and 2067 // return itself. 2068 drop_attempt_ = DROP_FOR_NONE; 2069 reorder_timer_.Stop(); 2070 folder_dropping_timer_.Stop(); 2071 2072 // When dragging for reparent a folder item, it should go back to its parent 2073 // folder item if there is no drop target. 2074 if (IsDraggingForReparentInRootLevelGridView()) { 2075 DCHECK(activated_folder_item_view_); 2076 return GetIndexOfView(activated_folder_item_view_); 2077 } 2078 2079 return GetIndexOfView(drag_view_); 2080 } 2081 2082 void AppsGridView::CalculateNearestTileForVertex(const gfx::Point& vertex, 2083 Index* nearest_tile, 2084 int* d_min) { 2085 Index target_index; 2086 gfx::Rect target_bounds = GetTileBoundsForPoint(vertex, &target_index); 2087 2088 if (target_bounds.IsEmpty() || target_index == *nearest_tile) 2089 return; 2090 2091 // Do not count the tile, where drag_view_ used to sit on and is still moving 2092 // on top of it, in calculating nearest tile for drag_view_. 2093 views::View* target_view = GetViewAtSlotOnCurrentPage(target_index.slot); 2094 if (target_index == drag_view_init_index_ && !target_view && 2095 !IsDraggingForReparentInRootLevelGridView()) { 2096 return; 2097 } 2098 2099 int d_center = GetDistanceBetweenRects(drag_view_->bounds(), target_bounds); 2100 if (*d_min < 0 || d_center < *d_min) { 2101 *d_min = d_center; 2102 *nearest_tile = target_index; 2103 } 2104 } 2105 2106 gfx::Rect AppsGridView::GetTileBoundsForPoint(const gfx::Point& point, 2107 Index *tile_index) { 2108 // Check if |point| is outside of contents bounds. 2109 gfx::Rect bounds(GetContentsBounds()); 2110 if (!bounds.Contains(point)) 2111 return gfx::Rect(); 2112 2113 // Calculate which tile |point| is enclosed in. 2114 int x = point.x(); 2115 int y = point.y(); 2116 int col = (x - bounds.x()) / kPreferredTileWidth; 2117 int row = (y - bounds.y()) / kPreferredTileHeight; 2118 gfx::Rect tile_rect = GetTileBounds(row, col); 2119 2120 // Check if |point| is outside a valid item's tile. 2121 Index index(pagination_model_.selected_page(), row * cols_ + col); 2122 *tile_index = index; 2123 return tile_rect; 2124 } 2125 2126 gfx::Rect AppsGridView::GetTileBounds(int row, int col) const { 2127 gfx::Rect bounds(GetContentsBounds()); 2128 gfx::Size tile_size(kPreferredTileWidth, kPreferredTileHeight); 2129 gfx::Rect grid_rect(gfx::Size(tile_size.width() * cols_, 2130 tile_size.height() * rows_per_page_)); 2131 grid_rect.Intersect(bounds); 2132 gfx::Rect tile_rect( 2133 gfx::Point(grid_rect.x() + col * tile_size.width(), 2134 grid_rect.y() + row * tile_size.height()), 2135 tile_size); 2136 return tile_rect; 2137 } 2138 2139 bool AppsGridView::IsLastPossibleDropTarget(const Index& index) const { 2140 int last_possible_slot = view_model_.view_size() % tiles_per_page(); 2141 return (index.page == pagination_model_.total_pages() - 1 && 2142 index.slot == last_possible_slot + 1); 2143 } 2144 2145 views::View* AppsGridView::GetViewAtSlotOnCurrentPage(int slot) { 2146 if (slot < 0) 2147 return NULL; 2148 2149 // Calculate the original bound of the tile at |index|. 2150 int row = slot / cols_; 2151 int col = slot % cols_; 2152 gfx::Rect tile_rect = GetTileBounds(row, col); 2153 2154 for (int i = 0; i < view_model_.view_size(); ++i) { 2155 views::View* view = view_model_.view_at(i); 2156 if (view->bounds() == tile_rect && view != drag_view_) 2157 return view; 2158 } 2159 return NULL; 2160 } 2161 2162 void AppsGridView::SetAsFolderDroppingTarget(const Index& target_index, 2163 bool is_target_folder) { 2164 AppListItemView* target_view = 2165 static_cast<AppListItemView*>( 2166 GetViewAtSlotOnCurrentPage(target_index.slot)); 2167 if (target_view) 2168 target_view->SetAsAttemptedFolderTarget(is_target_folder); 2169 } 2170 2171 } // namespace app_list 2172