OLD | NEW |
| (Empty) |
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/apps_grid_view.h" | |
6 | |
7 #include <algorithm> | |
8 | |
9 #include "ui/app_list/app_list_item_view.h" | |
10 #include "ui/app_list/apps_grid_view_delegate.h" | |
11 #include "ui/app_list/page_switcher.h" | |
12 #include "ui/app_list/pagination_model.h" | |
13 #include "ui/app_list/pulsing_block_view.h" | |
14 #include "ui/base/animation/animation.h" | |
15 #include "ui/base/events/event.h" | |
16 #include "ui/views/border.h" | |
17 #include "ui/views/view_model_utils.h" | |
18 #include "ui/views/widget/widget.h" | |
19 | |
20 #if defined(USE_AURA) | |
21 #include "ui/aura/root_window.h" | |
22 #endif | |
23 | |
24 namespace { | |
25 | |
26 // Padding space in pixels for fixed layout. | |
27 const int kLeftRightPadding = 20; | |
28 const int kTopPadding = 1; | |
29 | |
30 // Padding space in pixels between pages. | |
31 const int kPagePadding = 40; | |
32 | |
33 // Preferred tile size when showing in fixed layout. | |
34 const int kPreferredTileWidth = 88; | |
35 const int kPreferredTileHeight = 98; | |
36 | |
37 // Width in pixels of the area on the sides that triggers a page flip. | |
38 const int kPageFlipZoneSize = 40; | |
39 | |
40 // Delay in milliseconds to do the page flip. | |
41 const int kPageFlipDelayInMs = 1000; | |
42 | |
43 // RowMoveAnimationDelegate is used when moving an item into a different row. | |
44 // Before running the animation, the item's layer is re-created and kept in | |
45 // the original position, then the item is moved to just before its target | |
46 // position and opacity set to 0. When the animation runs, this delegate moves | |
47 // the layer and fades it out while fading in the item at the same time. | |
48 class RowMoveAnimationDelegate | |
49 : public views::BoundsAnimator::OwnedAnimationDelegate { | |
50 public: | |
51 RowMoveAnimationDelegate(views::View* view, | |
52 ui::Layer* layer, | |
53 const gfx::Rect& layer_target) | |
54 : view_(view), | |
55 layer_(layer), | |
56 layer_start_(layer ? layer->bounds() : gfx::Rect()), | |
57 layer_target_(layer_target) { | |
58 } | |
59 virtual ~RowMoveAnimationDelegate() {} | |
60 | |
61 // ui::AnimationDelegate overrides: | |
62 virtual void AnimationProgressed(const ui::Animation* animation) OVERRIDE { | |
63 view_->layer()->SetOpacity(animation->GetCurrentValue()); | |
64 view_->layer()->ScheduleDraw(); | |
65 | |
66 if (layer_) { | |
67 layer_->SetOpacity(1 - animation->GetCurrentValue()); | |
68 layer_->SetBounds(animation->CurrentValueBetween(layer_start_, | |
69 layer_target_)); | |
70 layer_->ScheduleDraw(); | |
71 } | |
72 } | |
73 virtual void AnimationEnded(const ui::Animation* animation) OVERRIDE { | |
74 view_->layer()->SetOpacity(1.0f); | |
75 view_->layer()->ScheduleDraw(); | |
76 } | |
77 virtual void AnimationCanceled(const ui::Animation* animation) OVERRIDE { | |
78 view_->layer()->SetOpacity(1.0f); | |
79 view_->layer()->ScheduleDraw(); | |
80 } | |
81 | |
82 private: | |
83 // The view that needs to be wrapped. Owned by views hierarchy. | |
84 views::View* view_; | |
85 | |
86 scoped_ptr<ui::Layer> layer_; | |
87 const gfx::Rect layer_start_; | |
88 const gfx::Rect layer_target_; | |
89 | |
90 DISALLOW_COPY_AND_ASSIGN(RowMoveAnimationDelegate); | |
91 }; | |
92 | |
93 } // namespace | |
94 | |
95 namespace app_list { | |
96 | |
97 AppsGridView::AppsGridView(AppsGridViewDelegate* delegate, | |
98 PaginationModel* pagination_model) | |
99 : model_(NULL), | |
100 delegate_(delegate), | |
101 pagination_model_(pagination_model), | |
102 page_switcher_view_(new PageSwitcher(pagination_model)), | |
103 cols_(0), | |
104 rows_per_page_(0), | |
105 selected_view_(NULL), | |
106 drag_view_(NULL), | |
107 drag_pointer_(NONE), | |
108 page_flip_target_(-1), | |
109 page_flip_delay_in_ms_(kPageFlipDelayInMs), | |
110 ALLOW_THIS_IN_INITIALIZER_LIST(bounds_animator_(this)) { | |
111 pagination_model_->AddObserver(this); | |
112 AddChildView(page_switcher_view_); | |
113 } | |
114 | |
115 AppsGridView::~AppsGridView() { | |
116 if (model_) { | |
117 model_->RemoveObserver(this); | |
118 model_->apps()->RemoveObserver(this); | |
119 } | |
120 pagination_model_->RemoveObserver(this); | |
121 } | |
122 | |
123 void AppsGridView::SetLayout(int icon_size, int cols, int rows_per_page) { | |
124 icon_size_.SetSize(icon_size, icon_size); | |
125 cols_ = cols; | |
126 rows_per_page_ = rows_per_page; | |
127 | |
128 set_border(views::Border::CreateEmptyBorder(kTopPadding, | |
129 kLeftRightPadding, | |
130 0, | |
131 kLeftRightPadding)); | |
132 } | |
133 | |
134 void AppsGridView::SetModel(AppListModel* model) { | |
135 if (model_) { | |
136 model_->RemoveObserver(this); | |
137 model_->apps()->RemoveObserver(this); | |
138 } | |
139 | |
140 model_ = model; | |
141 if (model_) { | |
142 model_->AddObserver(this); | |
143 model_->apps()->AddObserver(this); | |
144 } | |
145 Update(); | |
146 } | |
147 | |
148 void AppsGridView::SetSelectedView(views::View* view) { | |
149 if (IsSelectedView(view) || IsDraggedView(view)) | |
150 return; | |
151 | |
152 Index index = GetIndexOfView(view); | |
153 if (IsValidIndex(index)) | |
154 SetSelectedItemByIndex(index); | |
155 } | |
156 | |
157 void AppsGridView::ClearSelectedView(views::View* view) { | |
158 if (view && IsSelectedView(view)) { | |
159 selected_view_->SchedulePaint(); | |
160 selected_view_ = NULL; | |
161 } | |
162 } | |
163 | |
164 bool AppsGridView::IsSelectedView(const views::View* view) const { | |
165 return selected_view_ == view; | |
166 } | |
167 | |
168 void AppsGridView::EnsureViewVisible(const views::View* view) { | |
169 if (pagination_model_->has_transition()) | |
170 return; | |
171 | |
172 Index index = GetIndexOfView(view); | |
173 if (IsValidIndex(index)) | |
174 pagination_model_->SelectPage(index.page, false); | |
175 } | |
176 | |
177 void AppsGridView::InitiateDrag(views::View* view, | |
178 Pointer pointer, | |
179 const ui::LocatedEvent& event) { | |
180 if (drag_view_ || pulsing_blocks_model_.view_size()) | |
181 return; | |
182 | |
183 drag_view_ = view; | |
184 drag_start_ = event.location(); | |
185 } | |
186 | |
187 void AppsGridView::UpdateDrag(views::View* view, | |
188 Pointer pointer, | |
189 const ui::LocatedEvent& event) { | |
190 if (!dragging() && drag_view_ && | |
191 ExceededDragThreshold(event.location() - drag_start_)) { | |
192 drag_pointer_ = pointer; | |
193 // Move the view to the front so that it appears on top of other views. | |
194 ReorderChildView(drag_view_, -1); | |
195 bounds_animator_.StopAnimatingView(drag_view_); | |
196 } | |
197 if (drag_pointer_ != pointer) | |
198 return; | |
199 | |
200 ExtractDragLocation(event, &last_drag_point_); | |
201 | |
202 const Index last_drop_target = drop_target_; | |
203 CalculateDropTarget(last_drag_point_, false); | |
204 MaybeStartPageFlipTimer(last_drag_point_); | |
205 | |
206 gfx::Point page_switcher_point(last_drag_point_); | |
207 views::View::ConvertPointToTarget(this, page_switcher_view_, | |
208 &page_switcher_point); | |
209 page_switcher_view_->UpdateUIForDragPoint(page_switcher_point); | |
210 | |
211 if (last_drop_target != drop_target_) | |
212 AnimateToIdealBounds(); | |
213 drag_view_->SetPosition( | |
214 gfx::PointAtOffsetFromOrigin(last_drag_point_ - drag_start_)); | |
215 } | |
216 | |
217 void AppsGridView::EndDrag(bool cancel) { | |
218 if (!cancel && dragging() && drag_view_) { | |
219 CalculateDropTarget(last_drag_point_, true); | |
220 if (IsValidIndex(drop_target_)) | |
221 MoveItemInModel(drag_view_, drop_target_); | |
222 } | |
223 | |
224 drag_pointer_ = NONE; | |
225 drop_target_ = Index(); | |
226 if (drag_view_) { | |
227 drag_view_ = NULL; | |
228 AnimateToIdealBounds(); | |
229 } | |
230 | |
231 page_flip_timer_.Stop(); | |
232 page_flip_target_ = -1; | |
233 } | |
234 | |
235 bool AppsGridView::IsDraggedView(const views::View* view) const { | |
236 return drag_view_ == view; | |
237 } | |
238 | |
239 gfx::Size AppsGridView::GetPreferredSize() { | |
240 const gfx::Insets insets(GetInsets()); | |
241 const gfx::Size tile_size = gfx::Size(kPreferredTileWidth, | |
242 kPreferredTileHeight); | |
243 const int page_switcher_height = | |
244 page_switcher_view_->GetPreferredSize().height(); | |
245 return gfx::Size( | |
246 tile_size.width() * cols_ + insets.width(), | |
247 tile_size.height() * rows_per_page_ + | |
248 page_switcher_height + insets.height()); | |
249 } | |
250 | |
251 void AppsGridView::Layout() { | |
252 if (bounds_animator_.IsAnimating()) | |
253 bounds_animator_.Cancel(); | |
254 | |
255 CalculateIdealBounds(); | |
256 for (int i = 0; i < view_model_.view_size(); ++i) { | |
257 views::View* view = view_model_.view_at(i); | |
258 if (view != drag_view_) | |
259 view->SetBoundsRect(view_model_.ideal_bounds(i)); | |
260 } | |
261 views::ViewModelUtils::SetViewBoundsToIdealBounds(pulsing_blocks_model_); | |
262 | |
263 const int page_switcher_height = | |
264 page_switcher_view_->GetPreferredSize().height(); | |
265 gfx::Rect rect(GetContentsBounds()); | |
266 rect.set_y(rect.bottom() - page_switcher_height); | |
267 rect.set_height(page_switcher_height); | |
268 page_switcher_view_->SetBoundsRect(rect); | |
269 } | |
270 | |
271 bool AppsGridView::OnKeyPressed(const ui::KeyEvent& event) { | |
272 bool handled = false; | |
273 if (selected_view_) | |
274 handled = selected_view_->OnKeyPressed(event); | |
275 | |
276 if (!handled) { | |
277 switch (event.key_code()) { | |
278 case ui::VKEY_LEFT: | |
279 MoveSelected(0, -1); | |
280 return true; | |
281 case ui::VKEY_RIGHT: | |
282 MoveSelected(0, 1); | |
283 return true; | |
284 case ui::VKEY_UP: | |
285 MoveSelected(0, -cols_); | |
286 return true; | |
287 case ui::VKEY_DOWN: | |
288 MoveSelected(0, cols_); | |
289 return true; | |
290 case ui::VKEY_PRIOR: { | |
291 MoveSelected(-1, 0); | |
292 return true; | |
293 } | |
294 case ui::VKEY_NEXT: { | |
295 MoveSelected(1, 0); | |
296 return true; | |
297 } | |
298 default: | |
299 break; | |
300 } | |
301 } | |
302 | |
303 return handled; | |
304 } | |
305 | |
306 bool AppsGridView::OnKeyReleased(const ui::KeyEvent& event) { | |
307 bool handled = false; | |
308 if (selected_view_) | |
309 handled = selected_view_->OnKeyReleased(event); | |
310 | |
311 return handled; | |
312 } | |
313 | |
314 void AppsGridView::ViewHierarchyChanged(bool is_add, | |
315 views::View* parent, | |
316 views::View* child) { | |
317 if (!is_add && parent == this) { | |
318 if (selected_view_ == child) | |
319 selected_view_ = NULL; | |
320 | |
321 if (drag_view_ == child) | |
322 EndDrag(true); | |
323 | |
324 bounds_animator_.StopAnimatingView(child); | |
325 } | |
326 } | |
327 | |
328 void AppsGridView::Update() { | |
329 DCHECK(!selected_view_ && !drag_view_); | |
330 | |
331 view_model_.Clear(); | |
332 if (model_ && model_->apps()->item_count()) | |
333 ListItemsAdded(0, model_->apps()->item_count()); | |
334 } | |
335 | |
336 void AppsGridView::UpdatePaging() { | |
337 if (!view_model_.view_size() || !tiles_per_page()) { | |
338 pagination_model_->SetTotalPages(0); | |
339 return; | |
340 } | |
341 | |
342 pagination_model_->SetTotalPages( | |
343 (view_model_.view_size() - 1) / tiles_per_page() + 1); | |
344 } | |
345 | |
346 void AppsGridView::UpdatePulsingBlockViews() { | |
347 const int available_slots = | |
348 tiles_per_page() - model_->apps()->item_count() % tiles_per_page(); | |
349 const int desired = model_->status() == AppListModel::STATUS_SYNCING ? | |
350 available_slots : 0; | |
351 | |
352 if (pulsing_blocks_model_.view_size() == desired) | |
353 return; | |
354 | |
355 while (pulsing_blocks_model_.view_size() > desired) { | |
356 views::View* view = pulsing_blocks_model_.view_at(0); | |
357 pulsing_blocks_model_.Remove(0); | |
358 delete view; | |
359 } | |
360 | |
361 while (pulsing_blocks_model_.view_size() < desired) { | |
362 views::View* view = new PulsingBlockView( | |
363 gfx::Size(kPreferredTileWidth, kPreferredTileHeight), true); | |
364 pulsing_blocks_model_.Add(view, 0); | |
365 AddChildView(view); | |
366 } | |
367 } | |
368 | |
369 views::View* AppsGridView::CreateViewForItemAtIndex(size_t index) { | |
370 DCHECK_LT(index, model_->apps()->item_count()); | |
371 AppListItemView* view = new AppListItemView(this, | |
372 model_->apps()->GetItemAt(index)); | |
373 view->SetIconSize(icon_size_); | |
374 #if defined(USE_AURA) | |
375 view->SetPaintToLayer(true); | |
376 view->SetFillsBoundsOpaquely(false); | |
377 #endif | |
378 return view; | |
379 } | |
380 | |
381 void AppsGridView::SetSelectedItemByIndex(const Index& index) { | |
382 if (GetIndexOfView(selected_view_) == index) | |
383 return; | |
384 | |
385 views::View* new_selection = GetViewAtIndex(index); | |
386 if (!new_selection) | |
387 return; // Keep current selection. | |
388 | |
389 if (selected_view_) | |
390 selected_view_->SchedulePaint(); | |
391 | |
392 selected_view_ = new_selection; | |
393 EnsureViewVisible(selected_view_); | |
394 selected_view_->SchedulePaint(); | |
395 if (GetWidget()) { | |
396 GetWidget()->NotifyAccessibilityEvent( | |
397 selected_view_, ui::AccessibilityTypes::EVENT_FOCUS, true); | |
398 } | |
399 } | |
400 | |
401 bool AppsGridView::IsValidIndex(const Index& index) const { | |
402 return index.page >= 0 && index.page < pagination_model_->total_pages() && | |
403 index.slot >= 0 && index.slot < tiles_per_page() && | |
404 index.page * tiles_per_page() + index.slot < view_model_.view_size(); | |
405 } | |
406 | |
407 AppsGridView::Index AppsGridView::GetIndexOfView( | |
408 const views::View* view) const { | |
409 const int model_index = view_model_.GetIndexOfView(view); | |
410 if (model_index == -1) | |
411 return Index(); | |
412 | |
413 return Index(model_index / tiles_per_page(), model_index % tiles_per_page()); | |
414 } | |
415 | |
416 views::View* AppsGridView::GetViewAtIndex(const Index& index) const { | |
417 if (!IsValidIndex(index)) | |
418 return NULL; | |
419 | |
420 const int model_index = index.page * tiles_per_page() + index.slot; | |
421 return view_model_.view_at(model_index); | |
422 } | |
423 | |
424 void AppsGridView::MoveSelected(int page_delta, int slot_delta) { | |
425 if (!selected_view_) | |
426 return SetSelectedItemByIndex(Index(0, 0)); | |
427 | |
428 const Index& selected = GetIndexOfView(selected_view_); | |
429 int target_slot = selected.slot + slot_delta; | |
430 if (target_slot < 0) { | |
431 page_delta += (target_slot + 1) / tiles_per_page() - 1; | |
432 target_slot = tiles_per_page() + (target_slot + 1) % tiles_per_page() - 1; | |
433 } else if (target_slot > tiles_per_page()) { | |
434 page_delta += target_slot / tiles_per_page(); | |
435 target_slot %= tiles_per_page(); | |
436 } | |
437 | |
438 int target_page = std::min(pagination_model_->total_pages() - 1, | |
439 std::max(selected.page + page_delta, 0)); | |
440 SetSelectedItemByIndex(Index(target_page, target_slot)); | |
441 } | |
442 | |
443 void AppsGridView::CalculateIdealBounds() { | |
444 gfx::Rect rect(GetContentsBounds()); | |
445 if (rect.IsEmpty()) | |
446 return; | |
447 | |
448 gfx::Size tile_size(kPreferredTileWidth, kPreferredTileHeight); | |
449 | |
450 gfx::Rect grid_rect(gfx::Size(tile_size.width() * cols_, | |
451 tile_size.height() * rows_per_page_)); | |
452 grid_rect.Intersect(rect); | |
453 | |
454 // Page width including padding pixels. A tile.x + page_width means the same | |
455 // tile slot in the next page. | |
456 const int page_width = grid_rect.width() + kPagePadding; | |
457 | |
458 // If there is a transition, calculates offset for current and target page. | |
459 const int current_page = pagination_model_->selected_page(); | |
460 const PaginationModel::Transition& transition = | |
461 pagination_model_->transition(); | |
462 const bool is_valid = | |
463 pagination_model_->is_valid_page(transition.target_page); | |
464 | |
465 // Transition to right means negative offset. | |
466 const int dir = transition.target_page > current_page ? -1 : 1; | |
467 const int transition_offset = is_valid ? | |
468 transition.progress * page_width * dir : 0; | |
469 | |
470 const int total_views = | |
471 view_model_.view_size() + pulsing_blocks_model_.view_size(); | |
472 int slot_index = 0; | |
473 for (int i = 0; i < total_views; ++i) { | |
474 if (i < view_model_.view_size() && view_model_.view_at(i) == drag_view_) | |
475 continue; | |
476 | |
477 int page = slot_index / tiles_per_page(); | |
478 int slot = slot_index % tiles_per_page(); | |
479 | |
480 if (drop_target_.page == page && drop_target_.slot == slot) { | |
481 ++slot_index; | |
482 page = slot_index / tiles_per_page(); | |
483 slot = slot_index % tiles_per_page(); | |
484 } | |
485 | |
486 // Decides an x_offset for current item. | |
487 int x_offset = 0; | |
488 if (page < current_page) | |
489 x_offset = -page_width; | |
490 else if (page > current_page) | |
491 x_offset = page_width; | |
492 | |
493 if (is_valid) { | |
494 if (page == current_page || page == transition.target_page) | |
495 x_offset += transition_offset; | |
496 } | |
497 | |
498 const int row = slot / cols_; | |
499 const int col = slot % cols_; | |
500 gfx::Rect tile_slot( | |
501 gfx::Point(grid_rect.x() + col * tile_size.width() + x_offset, | |
502 grid_rect.y() + row * tile_size.height()), | |
503 tile_size); | |
504 if (i < view_model_.view_size()) { | |
505 view_model_.set_ideal_bounds(i, tile_slot); | |
506 } else { | |
507 pulsing_blocks_model_.set_ideal_bounds(i - view_model_.view_size(), | |
508 tile_slot); | |
509 } | |
510 | |
511 ++slot_index; | |
512 } | |
513 } | |
514 | |
515 void AppsGridView::AnimateToIdealBounds() { | |
516 const gfx::Rect visible_bounds(GetVisibleBounds()); | |
517 | |
518 CalculateIdealBounds(); | |
519 for (int i = 0; i < view_model_.view_size(); ++i) { | |
520 views::View* view = view_model_.view_at(i); | |
521 if (view == drag_view_) | |
522 continue; | |
523 | |
524 const gfx::Rect& target = view_model_.ideal_bounds(i); | |
525 if (bounds_animator_.GetTargetBounds(view) == target) | |
526 continue; | |
527 | |
528 const gfx::Rect& current = view->bounds(); | |
529 const bool current_visible = visible_bounds.Intersects(current); | |
530 const bool target_visible = visible_bounds.Intersects(target); | |
531 const bool visible = current_visible || target_visible; | |
532 | |
533 const int y_diff = target.y() - current.y(); | |
534 if (visible && y_diff && y_diff % kPreferredTileHeight == 0) { | |
535 AnimationBetweenRows(view, | |
536 current_visible, | |
537 current, | |
538 target_visible, | |
539 target); | |
540 } else { | |
541 bounds_animator_.AnimateViewTo(view, target); | |
542 } | |
543 } | |
544 } | |
545 | |
546 void AppsGridView::AnimationBetweenRows(views::View* view, | |
547 bool animate_current, | |
548 const gfx::Rect& current, | |
549 bool animate_target, | |
550 const gfx::Rect& target) { | |
551 // Determine page of |current| and |target|. -1 means in the left invisible | |
552 // page, 0 is the center visible page and 1 means in the right invisible page. | |
553 const int current_page = current.x() < 0 ? -1 : | |
554 current.x() >= width() ? 1 : 0; | |
555 const int target_page = target.x() < 0 ? -1 : | |
556 target.x() >= width() ? 1 : 0; | |
557 | |
558 const int dir = current_page < target_page || | |
559 (current_page == target_page && current.y() < target.y()) ? 1 : -1; | |
560 | |
561 #if defined(USE_AURA) | |
562 scoped_ptr<ui::Layer> layer; | |
563 if (animate_current) { | |
564 layer.reset(view->RecreateLayer()); | |
565 layer->SuppressPaint(); | |
566 | |
567 view->SetFillsBoundsOpaquely(false); | |
568 view->layer()->SetOpacity(0.f); | |
569 } | |
570 | |
571 gfx::Rect current_out(current); | |
572 current_out.Offset(dir * kPreferredTileWidth, 0); | |
573 #endif | |
574 | |
575 gfx::Rect target_in(target); | |
576 if (animate_target) | |
577 target_in.Offset(-dir * kPreferredTileWidth, 0); | |
578 view->SetBoundsRect(target_in); | |
579 bounds_animator_.AnimateViewTo(view, target); | |
580 | |
581 #if defined(USE_AURA) | |
582 bounds_animator_.SetAnimationDelegate( | |
583 view, | |
584 new RowMoveAnimationDelegate(view, layer.release(), current_out), | |
585 true); | |
586 #endif | |
587 } | |
588 | |
589 void AppsGridView::ExtractDragLocation(const ui::LocatedEvent& event, | |
590 gfx::Point* drag_point) { | |
591 #if defined(USE_AURA) | |
592 // Use root location of |event| instead of location in |drag_view_|'s | |
593 // coordinates because |drag_view_| has a scale transform and location | |
594 // could have integer round error and causes jitter. | |
595 *drag_point = event.root_location(); | |
596 | |
597 // GetWidget() could be NULL for tests. | |
598 if (GetWidget()) { | |
599 aura::Window::ConvertPointToTarget( | |
600 GetWidget()->GetNativeWindow()->GetRootWindow(), | |
601 GetWidget()->GetNativeWindow(), | |
602 drag_point); | |
603 } | |
604 | |
605 views::View::ConvertPointFromWidget(this, drag_point); | |
606 #else | |
607 // For non-aura, root location is not clearly defined but |drag_view_| does | |
608 // not have the scale transform. So no round error would be introduced and | |
609 // it's okay to use View::ConvertPointToTarget. | |
610 *drag_point = event.location(); | |
611 views::View::ConvertPointToTarget(drag_view_, this, drag_point); | |
612 #endif | |
613 } | |
614 | |
615 void AppsGridView::CalculateDropTarget(const gfx::Point& drag_point, | |
616 bool use_page_button_hovering) { | |
617 const int current_page = pagination_model_->selected_page(); | |
618 | |
619 if (use_page_button_hovering && | |
620 page_switcher_view_->bounds().Contains(drag_point)) { | |
621 gfx::Point page_switcher_point(drag_point); | |
622 views::View::ConvertPointToTarget(this, page_switcher_view_, | |
623 &page_switcher_point); | |
624 int page = page_switcher_view_->GetPageForPoint(page_switcher_point); | |
625 if (pagination_model_->is_valid_page(page)) { | |
626 drop_target_.page = page; | |
627 drop_target_.slot = tiles_per_page() - 1; | |
628 } | |
629 } else { | |
630 const int drop_row = drag_point.y() / kPreferredTileHeight; | |
631 const int drop_col = std::min(cols_ - 1, | |
632 drag_point.x() / kPreferredTileWidth); | |
633 | |
634 drop_target_.page = current_page; | |
635 drop_target_.slot = std::max(0, std::min( | |
636 tiles_per_page() - 1, | |
637 drop_row * cols_ + drop_col)); | |
638 } | |
639 | |
640 // Limits to the last possible slot on last page. | |
641 if (drop_target_.page == pagination_model_->total_pages() - 1) { | |
642 drop_target_.slot = std::min( | |
643 (view_model_.view_size() - 1) % tiles_per_page(), | |
644 drop_target_.slot); | |
645 } | |
646 } | |
647 | |
648 void AppsGridView::MaybeStartPageFlipTimer(const gfx::Point& drag_point) { | |
649 int new_page_flip_target = -1; | |
650 | |
651 if (page_switcher_view_->bounds().Contains(drag_point)) { | |
652 gfx::Point page_switcher_point(drag_point); | |
653 views::View::ConvertPointToTarget(this, page_switcher_view_, | |
654 &page_switcher_point); | |
655 new_page_flip_target = | |
656 page_switcher_view_->GetPageForPoint(page_switcher_point); | |
657 } | |
658 | |
659 // TODO(xiyuan): Fix this for RTL. | |
660 if (new_page_flip_target == -1 && drag_point.x() < kPageFlipZoneSize) | |
661 new_page_flip_target = pagination_model_->selected_page() - 1; | |
662 | |
663 if (new_page_flip_target == -1 && | |
664 drag_point.x() > width() - kPageFlipZoneSize) { | |
665 new_page_flip_target = pagination_model_->selected_page() + 1; | |
666 } | |
667 | |
668 if (new_page_flip_target == page_flip_target_) | |
669 return; | |
670 | |
671 if (pagination_model_->is_valid_page(new_page_flip_target)) { | |
672 page_flip_target_ = new_page_flip_target; | |
673 page_flip_timer_.Stop(); | |
674 | |
675 if (page_flip_target_ != pagination_model_->selected_page()) { | |
676 page_flip_timer_.Start(FROM_HERE, | |
677 base::TimeDelta::FromMilliseconds(page_flip_delay_in_ms_), | |
678 this, &AppsGridView::OnPageFlipTimer); | |
679 } | |
680 } else { | |
681 page_flip_target_ = -1; | |
682 page_flip_timer_.Stop(); | |
683 } | |
684 } | |
685 | |
686 void AppsGridView::OnPageFlipTimer() { | |
687 DCHECK(pagination_model_->is_valid_page(page_flip_target_)); | |
688 pagination_model_->SelectPage(page_flip_target_, true); | |
689 } | |
690 | |
691 void AppsGridView::MoveItemInModel(views::View* item_view, | |
692 const Index& target) { | |
693 int current_model_index = view_model_.GetIndexOfView(item_view); | |
694 DCHECK_GE(current_model_index, 0); | |
695 | |
696 int target_model_index = target.page * tiles_per_page() + target.slot; | |
697 if (target_model_index == current_model_index) | |
698 return; | |
699 | |
700 model_->apps()->RemoveObserver(this); | |
701 model_->apps()->Move(current_model_index, target_model_index); | |
702 view_model_.Move(current_model_index, target_model_index); | |
703 model_->apps()->AddObserver(this); | |
704 | |
705 if (pagination_model_->selected_page() != target.page) | |
706 pagination_model_->SelectPage(target.page, false); | |
707 } | |
708 | |
709 void AppsGridView::ButtonPressed(views::Button* sender, | |
710 const ui::Event& event) { | |
711 if (dragging()) | |
712 return; | |
713 | |
714 if (sender->GetClassName() != AppListItemView::kViewClassName) | |
715 return; | |
716 | |
717 if (delegate_) { | |
718 delegate_->ActivateApp(static_cast<AppListItemView*>(sender)->model(), | |
719 event.flags()); | |
720 } | |
721 } | |
722 | |
723 void AppsGridView::ListItemsAdded(size_t start, size_t count) { | |
724 EndDrag(true); | |
725 | |
726 for (size_t i = start; i < start + count; ++i) { | |
727 views::View* view = CreateViewForItemAtIndex(i); | |
728 view_model_.Add(view, i); | |
729 AddChildView(view); | |
730 } | |
731 | |
732 UpdatePaging(); | |
733 UpdatePulsingBlockViews(); | |
734 Layout(); | |
735 SchedulePaint(); | |
736 } | |
737 | |
738 void AppsGridView::ListItemsRemoved(size_t start, size_t count) { | |
739 EndDrag(true); | |
740 | |
741 for (size_t i = 0; i < count; ++i) { | |
742 views::View* view = view_model_.view_at(start); | |
743 view_model_.Remove(start); | |
744 delete view; | |
745 } | |
746 | |
747 UpdatePaging(); | |
748 UpdatePulsingBlockViews(); | |
749 Layout(); | |
750 SchedulePaint(); | |
751 } | |
752 | |
753 void AppsGridView::ListItemMoved(size_t index, size_t target_index) { | |
754 EndDrag(true); | |
755 view_model_.Move(index, target_index); | |
756 | |
757 UpdatePaging(); | |
758 AnimateToIdealBounds(); | |
759 } | |
760 | |
761 void AppsGridView::ListItemsChanged(size_t start, size_t count) { | |
762 NOTREACHED(); | |
763 } | |
764 | |
765 void AppsGridView::TotalPagesChanged() { | |
766 } | |
767 | |
768 void AppsGridView::SelectedPageChanged(int old_selected, int new_selected) { | |
769 if (dragging()) { | |
770 CalculateDropTarget(last_drag_point_, true); | |
771 Layout(); | |
772 MaybeStartPageFlipTimer(last_drag_point_); | |
773 } else { | |
774 Layout(); | |
775 } | |
776 } | |
777 | |
778 void AppsGridView::TransitionChanged() { | |
779 // Update layout for valid page transition only since over-scroll no longer | |
780 // animates app icons. | |
781 const PaginationModel::Transition& transition = | |
782 pagination_model_->transition(); | |
783 if (pagination_model_->is_valid_page(transition.target_page)) | |
784 Layout(); | |
785 } | |
786 | |
787 void AppsGridView::OnAppListModelStatusChanged() { | |
788 UpdatePulsingBlockViews(); | |
789 Layout(); | |
790 SchedulePaint(); | |
791 } | |
792 | |
793 } // namespace app_list | |
OLD | NEW |