Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(40)

Side by Side Diff: ash/shelf/shelf_view.cc

Issue 2228713003: mash: Convert ShelfView to wm common types, clean up Shelf access (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: fix control reaches end of non-void function warnings Created 4 years, 4 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« no previous file with comments | « ash/shelf/shelf_view.h ('k') | ash/shelf/shelf_view_unittest.cc » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(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 "ash/shelf/shelf_view.h"
6
7 #include <algorithm>
8 #include <memory>
9
10 #include "ash/common/ash_constants.h"
11 #include "ash/common/ash_switches.h"
12 #include "ash/common/drag_drop/drag_image_view.h"
13 #include "ash/common/scoped_root_window_for_new_windows.h"
14 #include "ash/common/shelf/app_list_button.h"
15 #include "ash/common/shelf/overflow_bubble.h"
16 #include "ash/common/shelf/overflow_bubble_view.h"
17 #include "ash/common/shelf/overflow_button.h"
18 #include "ash/common/shelf/shelf_button.h"
19 #include "ash/common/shelf/shelf_constants.h"
20 #include "ash/common/shelf/shelf_delegate.h"
21 #include "ash/common/shelf/shelf_menu_model.h"
22 #include "ash/common/shelf/shelf_model.h"
23 #include "ash/common/shelf/wm_shelf.h"
24 #include "ash/common/shell_delegate.h"
25 #include "ash/common/wm/root_window_finder.h"
26 #include "ash/common/wm_lookup.h"
27 #include "ash/common/wm_shell.h"
28 #include "ash/common/wm_window.h"
29 #include "ash/shelf/shelf.h"
30 #include "ash/shelf/shelf_icon_observer.h"
31 #include "ash/shelf/shelf_widget.h"
32 #include "base/auto_reset.h"
33 #include "base/metrics/histogram.h"
34 #include "grit/ash_strings.h"
35 #include "ui/accessibility/ax_view_state.h"
36 #include "ui/aura/window.h"
37 #include "ui/base/l10n/l10n_util.h"
38 #include "ui/base/models/simple_menu_model.h"
39 #include "ui/base/resource/resource_bundle.h"
40 #include "ui/compositor/layer.h"
41 #include "ui/compositor/layer_animator.h"
42 #include "ui/compositor/scoped_animation_duration_scale_mode.h"
43 #include "ui/events/event_utils.h"
44 #include "ui/gfx/canvas.h"
45 #include "ui/gfx/geometry/point.h"
46 #include "ui/views/animation/bounds_animator.h"
47 #include "ui/views/border.h"
48 #include "ui/views/controls/button/image_button.h"
49 #include "ui/views/controls/menu/menu_model_adapter.h"
50 #include "ui/views/controls/menu/menu_runner.h"
51 #include "ui/views/focus/focus_search.h"
52 #include "ui/views/view_model.h"
53 #include "ui/views/view_model_utils.h"
54 #include "ui/views/widget/widget.h"
55 #include "ui/wm/core/coordinate_conversion.h"
56
57 using gfx::Animation;
58 using views::View;
59
60 namespace ash {
61
62 const int SHELF_ALIGNMENT_UMA_ENUM_VALUE_BOTTOM = 0;
63 const int SHELF_ALIGNMENT_UMA_ENUM_VALUE_LEFT = 1;
64 const int SHELF_ALIGNMENT_UMA_ENUM_VALUE_RIGHT = 2;
65 const int SHELF_ALIGNMENT_UMA_ENUM_VALUE_COUNT = 3;
66
67 // Default amount content is inset on the left edge.
68 const int kDefaultLeadingInset = 8;
69
70 // Additional spacing for the left and right side of icons.
71 const int kHorizontalIconSpacing = 2;
72
73 // Inset for items which do not have an icon.
74 const int kHorizontalNoIconInsetSpacing =
75 kHorizontalIconSpacing + kDefaultLeadingInset;
76
77 // The proportion of the shelf space reserved for non-panel icons. Panels
78 // may flow into this space but will be put into the overflow bubble if there
79 // is contention for the space.
80 const float kReservedNonPanelIconProportion = 0.67f;
81
82 // This is the command id of the menu item which contains the name of the menu.
83 const int kCommandIdOfMenuName = 0;
84
85 // The background color of the active item in the list.
86 const SkColor kActiveListItemBackgroundColor = SkColorSetRGB(203, 219, 241);
87
88 // The background color of the active & hovered item in the list.
89 const SkColor kFocusedActiveListItemBackgroundColor =
90 SkColorSetRGB(193, 211, 236);
91
92 // The text color of the caption item in a list.
93 const SkColor kCaptionItemForegroundColor = SK_ColorBLACK;
94
95 // The maximum allowable length of a menu line of an application menu in pixels.
96 const int kMaximumAppMenuItemLength = 350;
97
98 // The distance of the cursor from the outer rim of the shelf before it
99 // separates.
100 const int kRipOffDistance = 48;
101
102 // The rip off drag and drop proxy image should get scaled by this factor.
103 const float kDragAndDropProxyScale = 1.5f;
104
105 // The opacity represents that this partially disappeared item will get removed.
106 const float kDraggedImageOpacity = 0.5f;
107
108 namespace {
109
110 // A class to temporarily disable a given bounds animator.
111 class BoundsAnimatorDisabler {
112 public:
113 explicit BoundsAnimatorDisabler(views::BoundsAnimator* bounds_animator)
114 : old_duration_(bounds_animator->GetAnimationDuration()),
115 bounds_animator_(bounds_animator) {
116 bounds_animator_->SetAnimationDuration(1);
117 }
118
119 ~BoundsAnimatorDisabler() {
120 bounds_animator_->SetAnimationDuration(old_duration_);
121 }
122
123 private:
124 // The previous animation duration.
125 int old_duration_;
126 // The bounds animator which gets used.
127 views::BoundsAnimator* bounds_animator_;
128
129 DISALLOW_COPY_AND_ASSIGN(BoundsAnimatorDisabler);
130 };
131
132 // The MenuModelAdapter gets slightly changed to adapt the menu appearance to
133 // our requirements.
134 // TODO(bruthig): ShelfMenuModelAdapter does not appear to be used, remove it.
135 class ShelfMenuModelAdapter : public views::MenuModelAdapter {
136 public:
137 explicit ShelfMenuModelAdapter(ShelfMenuModel* menu_model);
138
139 // views::MenuModelAdapter:
140 const gfx::FontList* GetLabelFontList(int command_id) const override;
141 bool IsCommandEnabled(int id) const override;
142 void GetHorizontalIconMargins(int id,
143 int icon_size,
144 int* left_margin,
145 int* right_margin) const override;
146 bool GetForegroundColor(int command_id,
147 bool is_hovered,
148 SkColor* override_color) const override;
149 bool GetBackgroundColor(int command_id,
150 bool is_hovered,
151 SkColor* override_color) const override;
152 int GetMaxWidthForMenu(views::MenuItemView* menu) override;
153 bool ShouldReserveSpaceForSubmenuIndicator() const override;
154
155 private:
156 ShelfMenuModel* menu_model_;
157
158 DISALLOW_COPY_AND_ASSIGN(ShelfMenuModelAdapter);
159 };
160
161 ShelfMenuModelAdapter::ShelfMenuModelAdapter(ShelfMenuModel* menu_model)
162 : MenuModelAdapter(menu_model), menu_model_(menu_model) {}
163
164 const gfx::FontList* ShelfMenuModelAdapter::GetLabelFontList(
165 int command_id) const {
166 if (command_id != kCommandIdOfMenuName)
167 return MenuModelAdapter::GetLabelFontList(command_id);
168
169 ui::ResourceBundle* rb = &ui::ResourceBundle::GetSharedInstance();
170 return &rb->GetFontList(ui::ResourceBundle::BoldFont);
171 }
172
173 bool ShelfMenuModelAdapter::IsCommandEnabled(int id) const {
174 return id != kCommandIdOfMenuName;
175 }
176
177 bool ShelfMenuModelAdapter::GetForegroundColor(int command_id,
178 bool is_hovered,
179 SkColor* override_color) const {
180 if (command_id != kCommandIdOfMenuName)
181 return false;
182
183 *override_color = kCaptionItemForegroundColor;
184 return true;
185 }
186
187 bool ShelfMenuModelAdapter::GetBackgroundColor(int command_id,
188 bool is_hovered,
189 SkColor* override_color) const {
190 if (!menu_model_->IsCommandActive(command_id))
191 return false;
192
193 *override_color = is_hovered ? kFocusedActiveListItemBackgroundColor
194 : kActiveListItemBackgroundColor;
195 return true;
196 }
197
198 void ShelfMenuModelAdapter::GetHorizontalIconMargins(int command_id,
199 int icon_size,
200 int* left_margin,
201 int* right_margin) const {
202 *left_margin = kHorizontalIconSpacing;
203 *right_margin = (command_id != kCommandIdOfMenuName)
204 ? kHorizontalIconSpacing
205 : -(icon_size + kHorizontalNoIconInsetSpacing);
206 }
207
208 int ShelfMenuModelAdapter::GetMaxWidthForMenu(views::MenuItemView* menu) {
209 return kMaximumAppMenuItemLength;
210 }
211
212 bool ShelfMenuModelAdapter::ShouldReserveSpaceForSubmenuIndicator() const {
213 return false;
214 }
215
216 // Custom FocusSearch used to navigate the shelf in the order items are in
217 // the ViewModel.
218 class ShelfFocusSearch : public views::FocusSearch {
219 public:
220 explicit ShelfFocusSearch(views::ViewModel* view_model)
221 : FocusSearch(nullptr, true, true), view_model_(view_model) {}
222 ~ShelfFocusSearch() override {}
223
224 // views::FocusSearch overrides:
225 View* FindNextFocusableView(View* starting_view,
226 bool reverse,
227 Direction direction,
228 bool check_starting_view,
229 views::FocusTraversable** focus_traversable,
230 View** focus_traversable_view) override {
231 int index = view_model_->GetIndexOfView(starting_view);
232 if (index == -1)
233 return view_model_->view_at(0);
234
235 if (reverse) {
236 --index;
237 if (index < 0)
238 index = view_model_->view_size() - 1;
239 } else {
240 ++index;
241 if (index >= view_model_->view_size())
242 index = 0;
243 }
244 return view_model_->view_at(index);
245 }
246
247 private:
248 views::ViewModel* view_model_;
249
250 DISALLOW_COPY_AND_ASSIGN(ShelfFocusSearch);
251 };
252
253 // AnimationDelegate used when inserting a new item. This steadily increases the
254 // opacity of the layer as the animation progress.
255 class FadeInAnimationDelegate : public gfx::AnimationDelegate {
256 public:
257 explicit FadeInAnimationDelegate(views::View* view) : view_(view) {}
258 ~FadeInAnimationDelegate() override {}
259
260 // AnimationDelegate overrides:
261 void AnimationProgressed(const Animation* animation) override {
262 view_->layer()->SetOpacity(animation->GetCurrentValue());
263 view_->layer()->ScheduleDraw();
264 }
265 void AnimationEnded(const Animation* animation) override {
266 view_->layer()->SetOpacity(1.0f);
267 view_->layer()->ScheduleDraw();
268 }
269 void AnimationCanceled(const Animation* animation) override {
270 view_->layer()->SetOpacity(1.0f);
271 view_->layer()->ScheduleDraw();
272 }
273
274 private:
275 views::View* view_;
276
277 DISALLOW_COPY_AND_ASSIGN(FadeInAnimationDelegate);
278 };
279
280 void ReflectItemStatus(const ShelfItem& item, ShelfButton* button) {
281 switch (item.status) {
282 case STATUS_CLOSED:
283 button->ClearState(ShelfButton::STATE_ACTIVE);
284 button->ClearState(ShelfButton::STATE_RUNNING);
285 button->ClearState(ShelfButton::STATE_ATTENTION);
286 break;
287 case STATUS_RUNNING:
288 button->ClearState(ShelfButton::STATE_ACTIVE);
289 button->AddState(ShelfButton::STATE_RUNNING);
290 button->ClearState(ShelfButton::STATE_ATTENTION);
291 break;
292 case STATUS_ACTIVE:
293 button->AddState(ShelfButton::STATE_ACTIVE);
294 button->ClearState(ShelfButton::STATE_RUNNING);
295 button->ClearState(ShelfButton::STATE_ATTENTION);
296 break;
297 case STATUS_ATTENTION:
298 button->ClearState(ShelfButton::STATE_ACTIVE);
299 button->ClearState(ShelfButton::STATE_RUNNING);
300 button->AddState(ShelfButton::STATE_ATTENTION);
301 break;
302 }
303 }
304
305 } // namespace
306
307 // AnimationDelegate used when deleting an item. This steadily decreased the
308 // opacity of the layer as the animation progress.
309 class ShelfView::FadeOutAnimationDelegate : public gfx::AnimationDelegate {
310 public:
311 FadeOutAnimationDelegate(ShelfView* host, views::View* view)
312 : shelf_view_(host), view_(view) {}
313 ~FadeOutAnimationDelegate() override {}
314
315 // AnimationDelegate overrides:
316 void AnimationProgressed(const Animation* animation) override {
317 view_->layer()->SetOpacity(1 - animation->GetCurrentValue());
318 view_->layer()->ScheduleDraw();
319 }
320 void AnimationEnded(const Animation* animation) override {
321 shelf_view_->OnFadeOutAnimationEnded();
322 }
323 void AnimationCanceled(const Animation* animation) override {}
324
325 private:
326 ShelfView* shelf_view_;
327 std::unique_ptr<views::View> view_;
328
329 DISALLOW_COPY_AND_ASSIGN(FadeOutAnimationDelegate);
330 };
331
332 // AnimationDelegate used to trigger fading an element in. When an item is
333 // inserted this delegate is attached to the animation that expands the size of
334 // the item. When done it kicks off another animation to fade the item in.
335 class ShelfView::StartFadeAnimationDelegate : public gfx::AnimationDelegate {
336 public:
337 StartFadeAnimationDelegate(ShelfView* host, views::View* view)
338 : shelf_view_(host), view_(view) {}
339 ~StartFadeAnimationDelegate() override {}
340
341 // AnimationDelegate overrides:
342 void AnimationEnded(const Animation* animation) override {
343 shelf_view_->FadeIn(view_);
344 }
345 void AnimationCanceled(const Animation* animation) override {
346 view_->layer()->SetOpacity(1.0f);
347 }
348
349 private:
350 ShelfView* shelf_view_;
351 views::View* view_;
352
353 DISALLOW_COPY_AND_ASSIGN(StartFadeAnimationDelegate);
354 };
355
356 // static
357 const int ShelfView::kMinimumDragDistance = 8;
358
359 ShelfView::ShelfView(ShelfModel* model,
360 ShelfDelegate* delegate,
361 WmShelf* wm_shelf,
362 Shelf* shelf)
363 : model_(model),
364 delegate_(delegate),
365 wm_shelf_(wm_shelf),
366 shelf_(shelf),
367 view_model_(new views::ViewModel),
368 first_visible_index_(0),
369 last_visible_index_(-1),
370 overflow_button_(nullptr),
371 owner_overflow_bubble_(nullptr),
372 tooltip_(this),
373 drag_pointer_(NONE),
374 drag_view_(nullptr),
375 start_drag_index_(-1),
376 context_menu_id_(0),
377 leading_inset_(kDefaultLeadingInset),
378 cancelling_drag_model_changed_(false),
379 last_hidden_index_(0),
380 closing_event_time_(base::TimeTicks()),
381 drag_and_drop_item_pinned_(false),
382 drag_and_drop_shelf_id_(0),
383 drag_replaced_view_(nullptr),
384 dragged_off_shelf_(false),
385 snap_back_from_rip_off_view_(nullptr),
386 overflow_mode_(false),
387 main_shelf_(nullptr),
388 dragged_off_from_overflow_to_shelf_(false),
389 is_repost_event_on_same_item_(false),
390 last_pressed_index_(-1) {
391 DCHECK(model_);
392 DCHECK(delegate_);
393 DCHECK(wm_shelf_);
394 bounds_animator_.reset(new views::BoundsAnimator(this));
395 bounds_animator_->AddObserver(this);
396 set_context_menu_controller(this);
397 focus_search_.reset(new ShelfFocusSearch(view_model_.get()));
398 }
399
400 ShelfView::~ShelfView() {
401 bounds_animator_->RemoveObserver(this);
402 model_->RemoveObserver(this);
403 }
404
405 void ShelfView::Init() {
406 model_->AddObserver(this);
407
408 const ShelfItems& items(model_->items());
409 for (ShelfItems::const_iterator i = items.begin(); i != items.end(); ++i) {
410 views::View* child = CreateViewForItem(*i);
411 child->SetPaintToLayer(true);
412 view_model_->Add(child, static_cast<int>(i - items.begin()));
413 AddChildView(child);
414 }
415 overflow_button_ = new OverflowButton(this, wm_shelf_);
416 overflow_button_->set_context_menu_controller(this);
417 ConfigureChildView(overflow_button_);
418 AddChildView(overflow_button_);
419
420 // We'll layout when our bounds change.
421 }
422
423 void ShelfView::OnShelfAlignmentChanged() {
424 overflow_button_->OnShelfAlignmentChanged();
425 LayoutToIdealBounds();
426 for (int i = 0; i < view_model_->view_size(); ++i) {
427 if (i >= first_visible_index_ && i <= last_visible_index_)
428 view_model_->view_at(i)->Layout();
429 }
430 tooltip_.Close();
431 if (overflow_bubble_)
432 overflow_bubble_->Hide();
433 // For crbug.com/587931, because AppListButton layout logic is in OnPaint.
434 AppListButton* app_list_button = GetAppListButton();
435 if (app_list_button)
436 app_list_button->SchedulePaint();
437 }
438
439 void ShelfView::SchedulePaintForAllButtons() {
440 for (int i = 0; i < view_model_->view_size(); ++i) {
441 if (i >= first_visible_index_ && i <= last_visible_index_)
442 view_model_->view_at(i)->SchedulePaint();
443 }
444 if (overflow_button_ && overflow_button_->visible())
445 overflow_button_->SchedulePaint();
446 }
447
448 gfx::Rect ShelfView::GetIdealBoundsOfItemIcon(ShelfID id) {
449 int index = model_->ItemIndexByID(id);
450 if (index == -1)
451 return gfx::Rect();
452 // Map all items from overflow area to the overflow button. Note that the
453 // section between last_index_hidden_ and model_->FirstPanelIndex() is the
454 // list of invisible panel items. However, these items are currently nowhere
455 // represented and get dropped instead - see (crbug.com/378907). As such there
456 // is no way to address them or place them. We therefore move them over the
457 // overflow button.
458 if (index > last_visible_index_ && index < model_->FirstPanelIndex())
459 index = last_visible_index_ + 1;
460 const gfx::Rect& ideal_bounds(view_model_->ideal_bounds(index));
461 DCHECK_NE(TYPE_APP_LIST, model_->items()[index].type);
462 views::View* view = view_model_->view_at(index);
463 CHECK_EQ(ShelfButton::kViewClassName, view->GetClassName());
464 ShelfButton* button = static_cast<ShelfButton*>(view);
465 gfx::Rect icon_bounds = button->GetIconBounds();
466 return gfx::Rect(GetMirroredXWithWidthInView(
467 ideal_bounds.x() + icon_bounds.x(), icon_bounds.width()),
468 ideal_bounds.y() + icon_bounds.y(), icon_bounds.width(),
469 icon_bounds.height());
470 }
471
472 void ShelfView::UpdatePanelIconPosition(ShelfID id,
473 const gfx::Point& midpoint) {
474 int current_index = model_->ItemIndexByID(id);
475 int first_panel_index = model_->FirstPanelIndex();
476 if (current_index < first_panel_index)
477 return;
478
479 gfx::Point midpoint_in_view(GetMirroredXInView(midpoint.x()), midpoint.y());
480 int target_index = current_index;
481 while (target_index > first_panel_index &&
482 shelf_->PrimaryAxisValue(view_model_->ideal_bounds(target_index).x(),
483 view_model_->ideal_bounds(target_index).y()) >
484 shelf_->PrimaryAxisValue(midpoint_in_view.x(),
485 midpoint_in_view.y())) {
486 --target_index;
487 }
488 while (target_index < view_model_->view_size() - 1 &&
489 shelf_->PrimaryAxisValue(
490 view_model_->ideal_bounds(target_index).right(),
491 view_model_->ideal_bounds(target_index).bottom()) <
492 shelf_->PrimaryAxisValue(midpoint_in_view.x(),
493 midpoint_in_view.y())) {
494 ++target_index;
495 }
496 if (current_index != target_index)
497 model_->Move(current_index, target_index);
498 }
499
500 bool ShelfView::IsShowingMenu() const {
501 return launcher_menu_runner_.get() && launcher_menu_runner_->IsRunning();
502 }
503
504 bool ShelfView::IsShowingOverflowBubble() const {
505 return overflow_bubble_.get() && overflow_bubble_->IsShowing();
506 }
507
508 AppListButton* ShelfView::GetAppListButton() const {
509 for (int i = 0; i < model_->item_count(); ++i) {
510 if (model_->items()[i].type == TYPE_APP_LIST) {
511 views::View* view = view_model_->view_at(i);
512 CHECK_EQ(AppListButton::kViewClassName, view->GetClassName());
513 return static_cast<AppListButton*>(view);
514 }
515 }
516
517 NOTREACHED() << "Applist button not found";
518 return nullptr;
519 }
520
521 bool ShelfView::ShouldHideTooltip(const gfx::Point& cursor_location) const {
522 gfx::Rect tooltip_bounds;
523 for (int i = 0; i < child_count(); ++i) {
524 const views::View* child = child_at(i);
525 if (child != overflow_button_ && ShouldShowTooltipForView(child))
526 tooltip_bounds.Union(child->GetMirroredBounds());
527 }
528 return !tooltip_bounds.Contains(cursor_location);
529 }
530
531 bool ShelfView::ShouldShowTooltipForView(const views::View* view) const {
532 if (view == GetAppListButton() &&
533 WmShell::Get()->GetAppListTargetVisibility()) {
534 return false;
535 }
536 const ShelfItem* item = ShelfItemForView(view);
537 if (!item)
538 return false;
539 return model_->GetShelfItemDelegate(item->id)->ShouldShowTooltip();
540 }
541
542 base::string16 ShelfView::GetTitleForView(const views::View* view) const {
543 const ShelfItem* item = ShelfItemForView(view);
544 if (!item || !model_->GetShelfItemDelegate(item->id))
545 return base::string16();
546 return model_->GetShelfItemDelegate(item->id)->GetTitle();
547 }
548
549 gfx::Rect ShelfView::GetVisibleItemsBoundsInScreen() {
550 gfx::Size preferred_size = GetPreferredSize();
551 gfx::Point origin(GetMirroredXWithWidthInView(0, preferred_size.width()), 0);
552 ConvertPointToScreen(this, &origin);
553 return gfx::Rect(origin, preferred_size);
554 }
555
556 ////////////////////////////////////////////////////////////////////////////////
557 // ShelfView, FocusTraversable implementation:
558
559 views::FocusSearch* ShelfView::GetFocusSearch() {
560 return focus_search_.get();
561 }
562
563 views::FocusTraversable* ShelfView::GetFocusTraversableParent() {
564 return parent()->GetFocusTraversable();
565 }
566
567 View* ShelfView::GetFocusTraversableParentView() {
568 return this;
569 }
570
571 void ShelfView::CreateDragIconProxy(
572 const gfx::Point& location_in_screen_coordinates,
573 const gfx::ImageSkia& icon,
574 views::View* replaced_view,
575 const gfx::Vector2d& cursor_offset_from_center,
576 float scale_factor) {
577 drag_replaced_view_ = replaced_view;
578 WmWindow* root_window =
579 WmLookup::Get()
580 ->GetWindowForWidget(drag_replaced_view_->GetWidget())
581 ->GetRootWindow();
582 drag_image_.reset(new DragImageView(
583 root_window, ui::DragDropTypes::DRAG_EVENT_SOURCE_MOUSE));
584 drag_image_->SetImage(icon);
585 gfx::Size size = drag_image_->GetPreferredSize();
586 size.set_width(size.width() * scale_factor);
587 size.set_height(size.height() * scale_factor);
588 drag_image_offset_ = gfx::Vector2d(size.width() / 2, size.height() / 2) +
589 cursor_offset_from_center;
590 gfx::Rect drag_image_bounds(
591 location_in_screen_coordinates - drag_image_offset_, size);
592 drag_image_->SetBoundsInScreen(drag_image_bounds);
593 drag_image_->SetWidgetVisible(true);
594 }
595
596 void ShelfView::UpdateDragIconProxy(
597 const gfx::Point& location_in_screen_coordinates) {
598 // TODO(jennyz): Investigate why drag_image_ becomes null at this point per
599 // crbug.com/34722, while the app list item is still being dragged around.
600 if (drag_image_) {
601 drag_image_->SetScreenPosition(location_in_screen_coordinates -
602 drag_image_offset_);
603 }
604 }
605
606 void ShelfView::DestroyDragIconProxy() {
607 drag_image_.reset();
608 drag_image_offset_ = gfx::Vector2d(0, 0);
609 }
610
611 bool ShelfView::StartDrag(const std::string& app_id,
612 const gfx::Point& location_in_screen_coordinates) {
613 // Bail if an operation is already going on - or the cursor is not inside.
614 // This could happen if mouse / touch operations overlap.
615 if (drag_and_drop_shelf_id_ ||
616 !GetBoundsInScreen().Contains(location_in_screen_coordinates))
617 return false;
618
619 // If the AppsGridView (which was dispatching this event) was opened by our
620 // button, ShelfView dragging operations are locked and we have to unlock.
621 CancelDrag(-1);
622 drag_and_drop_item_pinned_ = false;
623 drag_and_drop_app_id_ = app_id;
624 drag_and_drop_shelf_id_ =
625 delegate_->GetShelfIDForAppID(drag_and_drop_app_id_);
626 // Check if the application is known and pinned - if not, we have to pin it so
627 // that we can re-arrange the shelf order accordingly. Note that items have
628 // to be pinned to give them the same (order) possibilities as a shortcut.
629 // When an item is dragged from overflow to shelf, IsShowingOverflowBubble()
630 // returns true. At this time, we don't need to pin the item.
631 if (!IsShowingOverflowBubble() &&
632 (!drag_and_drop_shelf_id_ || !delegate_->IsAppPinned(app_id))) {
633 delegate_->PinAppWithID(app_id);
634 drag_and_drop_shelf_id_ =
635 delegate_->GetShelfIDForAppID(drag_and_drop_app_id_);
636 if (!drag_and_drop_shelf_id_)
637 return false;
638 drag_and_drop_item_pinned_ = true;
639 }
640 views::View* drag_and_drop_view =
641 view_model_->view_at(model_->ItemIndexByID(drag_and_drop_shelf_id_));
642 DCHECK(drag_and_drop_view);
643
644 // Since there is already an icon presented by the caller, we hide this item
645 // for now. That has to be done by reducing the size since the visibility will
646 // change once a regrouping animation is performed.
647 pre_drag_and_drop_size_ = drag_and_drop_view->size();
648 drag_and_drop_view->SetSize(gfx::Size());
649
650 // First we have to center the mouse cursor over the item.
651 gfx::Point pt = drag_and_drop_view->GetBoundsInScreen().CenterPoint();
652 views::View::ConvertPointFromScreen(drag_and_drop_view, &pt);
653 gfx::Point point_in_root =
654 wm::GetRootWindowAt(location_in_screen_coordinates)
655 ->ConvertPointFromScreen(location_in_screen_coordinates);
656 ui::MouseEvent event(ui::ET_MOUSE_PRESSED, pt, point_in_root,
657 ui::EventTimeForNow(), 0, 0);
658 PointerPressedOnButton(drag_and_drop_view, DRAG_AND_DROP, event);
659
660 // Drag the item where it really belongs.
661 Drag(location_in_screen_coordinates);
662 return true;
663 }
664
665 bool ShelfView::Drag(const gfx::Point& location_in_screen_coordinates) {
666 if (!drag_and_drop_shelf_id_ ||
667 !GetBoundsInScreen().Contains(location_in_screen_coordinates))
668 return false;
669
670 gfx::Point pt = location_in_screen_coordinates;
671 views::View* drag_and_drop_view =
672 view_model_->view_at(model_->ItemIndexByID(drag_and_drop_shelf_id_));
673 ConvertPointFromScreen(drag_and_drop_view, &pt);
674 gfx::Point point_in_root =
675 wm::GetRootWindowAt(location_in_screen_coordinates)
676 ->ConvertPointFromScreen(location_in_screen_coordinates);
677 ui::MouseEvent event(ui::ET_MOUSE_DRAGGED, pt, point_in_root,
678 ui::EventTimeForNow(), 0, 0);
679 PointerDraggedOnButton(drag_and_drop_view, DRAG_AND_DROP, event);
680 return true;
681 }
682
683 void ShelfView::EndDrag(bool cancel) {
684 if (!drag_and_drop_shelf_id_)
685 return;
686
687 views::View* drag_and_drop_view =
688 view_model_->view_at(model_->ItemIndexByID(drag_and_drop_shelf_id_));
689 PointerReleasedOnButton(drag_and_drop_view, DRAG_AND_DROP, cancel);
690
691 // Either destroy the temporarily created item - or - make the item visible.
692 if (drag_and_drop_item_pinned_ && cancel) {
693 delegate_->UnpinAppWithID(drag_and_drop_app_id_);
694 } else if (drag_and_drop_view) {
695 if (cancel) {
696 // When a hosted drag gets canceled, the item can remain in the same slot
697 // and it might have moved within the bounds. In that case the item need
698 // to animate back to its correct location.
699 AnimateToIdealBounds();
700 } else {
701 drag_and_drop_view->SetSize(pre_drag_and_drop_size_);
702 }
703 }
704
705 drag_and_drop_shelf_id_ = 0;
706 }
707
708 bool ShelfView::ShouldEventActivateButton(View* view, const ui::Event& event) {
709 if (dragging())
710 return false;
711
712 // Ignore if we are already in a pointer event sequence started with a repost
713 // event on the same shelf item. See crbug.com/343005 for more detail.
714 if (is_repost_event_on_same_item_)
715 return false;
716
717 // Don't activate the item twice on double-click. Otherwise the window starts
718 // animating open due to the first click, then immediately minimizes due to
719 // the second click. The user most likely intended to open or minimize the
720 // item once, not do both.
721 if (event.flags() & ui::EF_IS_DOUBLE_CLICK)
722 return false;
723
724 // Ignore if this is a repost event on the last pressed shelf item.
725 int index = view_model_->GetIndexOfView(view);
726 if (index == -1)
727 return false;
728 return !IsRepostEvent(event) || last_pressed_index_ != index;
729 }
730
731 void ShelfView::PointerPressedOnButton(views::View* view,
732 Pointer pointer,
733 const ui::LocatedEvent& event) {
734 if (drag_view_)
735 return;
736
737 int index = view_model_->GetIndexOfView(view);
738 if (index == -1 || view_model_->view_size() <= 1)
739 return; // View is being deleted, ignore request.
740
741 ShelfItemDelegate* item_delegate =
742 model_->GetShelfItemDelegate(model_->items()[index].id);
743 if (!item_delegate->IsDraggable())
744 return; // View is not draggable, ignore request.
745
746 // Only when the repost event occurs on the same shelf item, we should ignore
747 // the call in ShelfView::ButtonPressed(...).
748 is_repost_event_on_same_item_ =
749 IsRepostEvent(event) && (last_pressed_index_ == index);
750
751 CHECK_EQ(ShelfButton::kViewClassName, view->GetClassName());
752 drag_view_ = static_cast<ShelfButton*>(view);
753 drag_origin_ = gfx::Point(event.x(), event.y());
754 UMA_HISTOGRAM_ENUMERATION("Ash.ShelfAlignmentUsage",
755 shelf_->SelectValueForShelfAlignment(
756 SHELF_ALIGNMENT_UMA_ENUM_VALUE_BOTTOM,
757 SHELF_ALIGNMENT_UMA_ENUM_VALUE_LEFT,
758 SHELF_ALIGNMENT_UMA_ENUM_VALUE_RIGHT),
759 SHELF_ALIGNMENT_UMA_ENUM_VALUE_COUNT);
760 }
761
762 void ShelfView::PointerDraggedOnButton(views::View* view,
763 Pointer pointer,
764 const ui::LocatedEvent& event) {
765 // To prepare all drag types (moving an item in the shelf and dragging off),
766 // we should check the x-axis and y-axis offset.
767 if (!dragging() && drag_view_ &&
768 ((std::abs(event.x() - drag_origin_.x()) >= kMinimumDragDistance) ||
769 (std::abs(event.y() - drag_origin_.y()) >= kMinimumDragDistance))) {
770 PrepareForDrag(pointer, event);
771 }
772 if (drag_pointer_ == pointer)
773 ContinueDrag(event);
774 }
775
776 void ShelfView::PointerReleasedOnButton(views::View* view,
777 Pointer pointer,
778 bool canceled) {
779 is_repost_event_on_same_item_ = false;
780
781 if (canceled) {
782 CancelDrag(-1);
783 } else if (drag_pointer_ == pointer) {
784 FinalizeRipOffDrag(false);
785 drag_pointer_ = NONE;
786 AnimateToIdealBounds();
787 }
788 // If the drag pointer is NONE, no drag operation is going on and the
789 // drag_view can be released.
790 if (drag_pointer_ == NONE)
791 drag_view_ = nullptr;
792 }
793
794 void ShelfView::LayoutToIdealBounds() {
795 if (bounds_animator_->IsAnimating()) {
796 AnimateToIdealBounds();
797 return;
798 }
799
800 IdealBounds ideal_bounds;
801 CalculateIdealBounds(&ideal_bounds);
802 views::ViewModelUtils::SetViewBoundsToIdealBounds(*view_model_);
803 overflow_button_->SetBoundsRect(ideal_bounds.overflow_bounds);
804 }
805
806 void ShelfView::UpdateShelfItemBackground(int alpha) {
807 GetAppListButton()->SetBackgroundAlpha(alpha);
808 overflow_button_->SetBackgroundAlpha(alpha);
809 }
810
811 void ShelfView::UpdateAllButtonsVisibilityInOverflowMode() {
812 // The overflow button is not shown in overflow mode.
813 overflow_button_->SetVisible(false);
814 DCHECK_LT(last_visible_index_, view_model_->view_size());
815 for (int i = 0; i < view_model_->view_size(); ++i) {
816 bool visible = i >= first_visible_index_ && i <= last_visible_index_;
817 // To track the dragging of |drag_view_| continuously, its visibility
818 // should be always true regardless of its position.
819 if (dragged_off_from_overflow_to_shelf_ &&
820 view_model_->view_at(i) == drag_view_)
821 view_model_->view_at(i)->SetVisible(true);
822 else
823 view_model_->view_at(i)->SetVisible(visible);
824 }
825 }
826
827 void ShelfView::CalculateIdealBounds(IdealBounds* bounds) const {
828 int available_size = shelf_->PrimaryAxisValue(width(), height());
829 DCHECK(model_->item_count() == view_model_->view_size());
830 if (!available_size)
831 return;
832
833 int first_panel_index = model_->FirstPanelIndex();
834 int last_button_index = first_panel_index - 1;
835
836 int x = 0;
837 int y = 0;
838 int button_size = GetShelfConstant(SHELF_BUTTON_SIZE);
839 int button_spacing = GetShelfConstant(SHELF_BUTTON_SPACING);
840
841 int w = shelf_->PrimaryAxisValue(button_size, width());
842 int h = shelf_->PrimaryAxisValue(height(), button_size);
843 for (int i = 0; i < view_model_->view_size(); ++i) {
844 if (i < first_visible_index_) {
845 view_model_->set_ideal_bounds(i, gfx::Rect(x, y, 0, 0));
846 continue;
847 }
848
849 view_model_->set_ideal_bounds(i, gfx::Rect(x, y, w, h));
850 x = shelf_->PrimaryAxisValue(x + w + button_spacing, x);
851 y = shelf_->PrimaryAxisValue(y, y + h + button_spacing);
852 }
853
854 if (is_overflow_mode()) {
855 const_cast<ShelfView*>(this)->UpdateAllButtonsVisibilityInOverflowMode();
856 return;
857 }
858
859 // Right aligned icons.
860 int end_position = available_size - button_spacing;
861 x = shelf_->PrimaryAxisValue(end_position, 0);
862 y = shelf_->PrimaryAxisValue(0, end_position);
863 for (int i = view_model_->view_size() - 1; i >= first_panel_index; --i) {
864 x = shelf_->PrimaryAxisValue(x - w - button_spacing, x);
865 y = shelf_->PrimaryAxisValue(y, y - h - button_spacing);
866 view_model_->set_ideal_bounds(i, gfx::Rect(x, y, w, h));
867 end_position = shelf_->PrimaryAxisValue(x, y);
868 }
869
870 // Icons on the left / top are guaranteed up to kLeftIconProportion of
871 // the available space.
872 int last_icon_position =
873 shelf_->PrimaryAxisValue(
874 view_model_->ideal_bounds(last_button_index).right(),
875 view_model_->ideal_bounds(last_button_index).bottom()) +
876 button_size;
877 int reserved_icon_space = available_size * kReservedNonPanelIconProportion;
878 if (last_icon_position < reserved_icon_space)
879 end_position = last_icon_position;
880 else
881 end_position = std::max(end_position, reserved_icon_space);
882
883 bounds->overflow_bounds.set_size(
884 gfx::Size(shelf_->PrimaryAxisValue(w, width()),
885 shelf_->PrimaryAxisValue(height(), h)));
886
887 last_visible_index_ = DetermineLastVisibleIndex(end_position - button_size);
888 last_hidden_index_ = DetermineFirstVisiblePanelIndex(end_position) - 1;
889 bool show_overflow = last_visible_index_ < last_button_index ||
890 last_hidden_index_ >= first_panel_index;
891
892 // Create Space for the overflow button
893 if (show_overflow) {
894 // The following code makes sure that platform apps icons (aligned to left /
895 // top) are favored over panel apps icons (aligned to right / bottom).
896 if (last_visible_index_ > 0 && last_visible_index_ < last_button_index) {
897 // This condition means that we will take one platform app and replace it
898 // with the overflow button and put the app in the overflow bubble.
899 // This happens when the space needed for platform apps exceeds the
900 // reserved area for non-panel icons,
901 // (i.e. |last_icon_position| > |reserved_icon_space|).
902 --last_visible_index_;
903 } else if (last_hidden_index_ >= first_panel_index &&
904 last_hidden_index_ < view_model_->view_size() - 1) {
905 // This condition means that we will take a panel app icon and replace it
906 // with the overflow button.
907 // This happens when there is still room for platform apps in the reserved
908 // area for non-panel icons,
909 // (i.e. |last_icon_position| < |reserved_icon_space|).
910 ++last_hidden_index_;
911 }
912 }
913
914 for (int i = 0; i < view_model_->view_size(); ++i) {
915 bool visible = i <= last_visible_index_ || i > last_hidden_index_;
916 // To receive drag event continuously from |drag_view_| during the dragging
917 // off from the shelf, don't make |drag_view_| invisible. It will be
918 // eventually invisible and removed from the |view_model_| by
919 // FinalizeRipOffDrag().
920 if (dragged_off_shelf_ && view_model_->view_at(i) == drag_view_)
921 continue;
922 view_model_->view_at(i)->SetVisible(visible);
923 }
924
925 overflow_button_->SetVisible(show_overflow);
926 if (show_overflow) {
927 DCHECK_NE(0, view_model_->view_size());
928 if (last_visible_index_ == -1) {
929 x = 0;
930 y = 0;
931 } else {
932 x = shelf_->PrimaryAxisValue(
933 view_model_->ideal_bounds(last_visible_index_).right(),
934 view_model_->ideal_bounds(last_visible_index_).x());
935 y = shelf_->PrimaryAxisValue(
936 view_model_->ideal_bounds(last_visible_index_).y(),
937 view_model_->ideal_bounds(last_visible_index_).bottom());
938 }
939 // Set all hidden panel icon positions to be on the overflow button.
940 for (int i = first_panel_index; i <= last_hidden_index_; ++i)
941 view_model_->set_ideal_bounds(i, gfx::Rect(x, y, w, h));
942
943 // Add more space between last visible item and overflow button.
944 // Without this, two buttons look too close compared with other items.
945 x = shelf_->PrimaryAxisValue(x + button_spacing, x);
946 y = shelf_->PrimaryAxisValue(y, y + button_spacing);
947
948 bounds->overflow_bounds.set_x(x);
949 bounds->overflow_bounds.set_y(y);
950 if (overflow_bubble_.get() && overflow_bubble_->IsShowing())
951 UpdateOverflowRange(overflow_bubble_->shelf_view());
952 } else {
953 if (overflow_bubble_)
954 overflow_bubble_->Hide();
955 }
956 }
957
958 int ShelfView::DetermineLastVisibleIndex(int max_value) const {
959 int index = model_->FirstPanelIndex() - 1;
960 while (index >= 0 &&
961 shelf_->PrimaryAxisValue(view_model_->ideal_bounds(index).right(),
962 view_model_->ideal_bounds(index).bottom()) >
963 max_value) {
964 index--;
965 }
966 return index;
967 }
968
969 int ShelfView::DetermineFirstVisiblePanelIndex(int min_value) const {
970 int index = model_->FirstPanelIndex();
971 while (index < view_model_->view_size() &&
972 shelf_->PrimaryAxisValue(view_model_->ideal_bounds(index).right(),
973 view_model_->ideal_bounds(index).bottom()) <
974 min_value) {
975 ++index;
976 }
977 return index;
978 }
979
980 void ShelfView::AddIconObserver(ShelfIconObserver* observer) {
981 observers_.AddObserver(observer);
982 }
983
984 void ShelfView::RemoveIconObserver(ShelfIconObserver* observer) {
985 observers_.RemoveObserver(observer);
986 }
987
988 void ShelfView::AnimateToIdealBounds() {
989 IdealBounds ideal_bounds;
990 CalculateIdealBounds(&ideal_bounds);
991 for (int i = 0; i < view_model_->view_size(); ++i) {
992 View* view = view_model_->view_at(i);
993 bounds_animator_->AnimateViewTo(view, view_model_->ideal_bounds(i));
994 // Now that the item animation starts, we have to make sure that the
995 // padding of the first gets properly transferred to the new first item.
996 if (i && view->border())
997 view->SetBorder(views::Border::NullBorder());
998 }
999 overflow_button_->SetBoundsRect(ideal_bounds.overflow_bounds);
1000 }
1001
1002 views::View* ShelfView::CreateViewForItem(const ShelfItem& item) {
1003 views::View* view = nullptr;
1004 switch (item.type) {
1005 case TYPE_BROWSER_SHORTCUT:
1006 case TYPE_APP_SHORTCUT:
1007 case TYPE_WINDOWED_APP:
1008 case TYPE_PLATFORM_APP:
1009 case TYPE_DIALOG:
1010 case TYPE_APP_PANEL:
1011 case TYPE_IME_MENU: {
1012 ShelfButton* button = new ShelfButton(this, this);
1013 button->SetImage(item.image);
1014 ReflectItemStatus(item, button);
1015 view = button;
1016 break;
1017 }
1018
1019 case TYPE_APP_LIST: {
1020 view = new AppListButton(this, this, wm_shelf_);
1021 break;
1022 }
1023
1024 case TYPE_UNDEFINED:
1025 return nullptr;
1026 }
1027
1028 view->set_context_menu_controller(this);
1029 ConfigureChildView(view);
1030 return view;
1031 }
1032
1033 void ShelfView::FadeIn(views::View* view) {
1034 view->SetVisible(true);
1035 view->layer()->SetOpacity(0);
1036 AnimateToIdealBounds();
1037 bounds_animator_->SetAnimationDelegate(
1038 view, std::unique_ptr<gfx::AnimationDelegate>(
1039 new FadeInAnimationDelegate(view)));
1040 }
1041
1042 void ShelfView::PrepareForDrag(Pointer pointer, const ui::LocatedEvent& event) {
1043 DCHECK(!dragging());
1044 DCHECK(drag_view_);
1045 drag_pointer_ = pointer;
1046 start_drag_index_ = view_model_->GetIndexOfView(drag_view_);
1047
1048 if (start_drag_index_ == -1) {
1049 CancelDrag(-1);
1050 return;
1051 }
1052
1053 // If the item is no longer draggable, bail out.
1054 ShelfItemDelegate* item_delegate =
1055 model_->GetShelfItemDelegate(model_->items()[start_drag_index_].id);
1056 if (!item_delegate->IsDraggable()) {
1057 CancelDrag(-1);
1058 return;
1059 }
1060
1061 // Move the view to the front so that it appears on top of other views.
1062 ReorderChildView(drag_view_, -1);
1063 bounds_animator_->StopAnimatingView(drag_view_);
1064
1065 drag_view_->OnDragStarted(&event);
1066 }
1067
1068 void ShelfView::ContinueDrag(const ui::LocatedEvent& event) {
1069 // Due to a syncing operation the application might have been removed.
1070 // Bail if it is gone.
1071 int current_index = view_model_->GetIndexOfView(drag_view_);
1072 DCHECK_NE(-1, current_index);
1073
1074 ShelfItemDelegate* item_delegate =
1075 model_->GetShelfItemDelegate(model_->items()[current_index].id);
1076 if (!item_delegate->IsDraggable()) {
1077 CancelDrag(-1);
1078 return;
1079 }
1080
1081 // If this is not a drag and drop host operation and not the app list item,
1082 // check if the item got ripped off the shelf - if it did we are done.
1083 if (!drag_and_drop_shelf_id_ &&
1084 RemovableByRipOff(current_index) != NOT_REMOVABLE) {
1085 if (HandleRipOffDrag(event))
1086 return;
1087 // The rip off handler could have changed the location of the item.
1088 current_index = view_model_->GetIndexOfView(drag_view_);
1089 }
1090
1091 // TODO: I don't think this works correctly with RTL.
1092 gfx::Point drag_point(event.location());
1093 ConvertPointToTarget(drag_view_, this, &drag_point);
1094
1095 // Constrain the location to the range of valid indices for the type.
1096 std::pair<int, int> indices(GetDragRange(current_index));
1097 int first_drag_index = indices.first;
1098 int last_drag_index = indices.second;
1099 // If the last index isn't valid, we're overflowing. Constrain to the app list
1100 // (which is the last visible item).
1101 if (first_drag_index < model_->FirstPanelIndex() &&
1102 last_drag_index > last_visible_index_)
1103 last_drag_index = last_visible_index_;
1104 int x = 0, y = 0;
1105 if (shelf_->IsHorizontalAlignment()) {
1106 x = std::max(view_model_->ideal_bounds(indices.first).x(),
1107 drag_point.x() - drag_origin_.x());
1108 x = std::min(view_model_->ideal_bounds(last_drag_index).right() -
1109 view_model_->ideal_bounds(current_index).width(),
1110 x);
1111 if (drag_view_->x() == x)
1112 return;
1113 drag_view_->SetX(x);
1114 } else {
1115 y = std::max(view_model_->ideal_bounds(indices.first).y(),
1116 drag_point.y() - drag_origin_.y());
1117 y = std::min(view_model_->ideal_bounds(last_drag_index).bottom() -
1118 view_model_->ideal_bounds(current_index).height(),
1119 y);
1120 if (drag_view_->y() == y)
1121 return;
1122 drag_view_->SetY(y);
1123 }
1124
1125 int target_index = views::ViewModelUtils::DetermineMoveIndex(
1126 *view_model_, drag_view_,
1127 shelf_->IsHorizontalAlignment() ? views::ViewModelUtils::HORIZONTAL
1128 : views::ViewModelUtils::VERTICAL,
1129 x, y);
1130 target_index =
1131 std::min(indices.second, std::max(target_index, indices.first));
1132
1133 int first_draggable_item = 0;
1134 while (first_draggable_item < static_cast<int>(model_->items().size()) &&
1135 !model_->GetShelfItemDelegate(model_->items()[first_draggable_item].id)
1136 ->IsDraggable()) {
1137 first_draggable_item++;
1138 }
1139
1140 target_index = std::max(target_index, first_draggable_item);
1141
1142 if (target_index == current_index)
1143 return;
1144
1145 // Change the model, the ShelfItemMoved() callback will handle the
1146 // |view_model_| update.
1147 model_->Move(current_index, target_index);
1148 bounds_animator_->StopAnimatingView(drag_view_);
1149 }
1150
1151 bool ShelfView::HandleRipOffDrag(const ui::LocatedEvent& event) {
1152 int current_index = view_model_->GetIndexOfView(drag_view_);
1153 DCHECK_NE(-1, current_index);
1154 std::string dragged_app_id =
1155 delegate_->GetAppIDForShelfID(model_->items()[current_index].id);
1156
1157 gfx::Point screen_location = event.root_location();
1158 ::wm::ConvertPointToScreen(GetWidget()->GetNativeWindow()->GetRootWindow(),
1159 &screen_location);
1160
1161 // To avoid ugly forwards and backwards flipping we use different constants
1162 // for ripping off / re-inserting the items.
1163 if (dragged_off_shelf_) {
1164 // If the shelf/overflow bubble bounds contains |screen_location| we insert
1165 // the item back into the shelf.
1166 if (GetBoundsForDragInsertInScreen().Contains(screen_location)) {
1167 if (dragged_off_from_overflow_to_shelf_) {
1168 // During the dragging an item from Shelf to Overflow, it can enter here
1169 // directly because both are located very closly.
1170 main_shelf_->EndDrag(true);
1171 // Stops the animation of |drag_view_| and sets its bounds explicitly
1172 // becase ContinueDrag() stops its animation. Without this, unexpected
1173 // bounds will be set.
1174 bounds_animator_->StopAnimatingView(drag_view_);
1175 int drag_view_index = view_model_->GetIndexOfView(drag_view_);
1176 drag_view_->SetBoundsRect(view_model_->ideal_bounds(drag_view_index));
1177 dragged_off_from_overflow_to_shelf_ = false;
1178 }
1179 // Destroy our proxy view item.
1180 DestroyDragIconProxy();
1181 // Re-insert the item and return simply false since the caller will handle
1182 // the move as in any normal case.
1183 dragged_off_shelf_ = false;
1184 drag_view_->layer()->SetOpacity(1.0f);
1185 // The size of Overflow bubble should be updated immediately when an item
1186 // is re-inserted.
1187 if (is_overflow_mode())
1188 PreferredSizeChanged();
1189 return false;
1190 } else if (is_overflow_mode() &&
1191 main_shelf_->GetBoundsForDragInsertInScreen().Contains(
1192 screen_location)) {
1193 if (!dragged_off_from_overflow_to_shelf_) {
1194 dragged_off_from_overflow_to_shelf_ = true;
1195 drag_image_->SetOpacity(1.0f);
1196 main_shelf_->StartDrag(dragged_app_id, screen_location);
1197 } else {
1198 main_shelf_->Drag(screen_location);
1199 }
1200 } else if (dragged_off_from_overflow_to_shelf_) {
1201 // Makes the |drag_image_| partially disappear again.
1202 dragged_off_from_overflow_to_shelf_ = false;
1203 drag_image_->SetOpacity(kDraggedImageOpacity);
1204 main_shelf_->EndDrag(true);
1205 bounds_animator_->StopAnimatingView(drag_view_);
1206 int drag_view_index = view_model_->GetIndexOfView(drag_view_);
1207 drag_view_->SetBoundsRect(view_model_->ideal_bounds(drag_view_index));
1208 }
1209 // Move our proxy view item.
1210 UpdateDragIconProxy(screen_location);
1211 return true;
1212 }
1213 // Check if we are too far away from the shelf to enter the ripped off state.
1214 // Determine the distance to the shelf.
1215 int delta = CalculateShelfDistance(screen_location);
1216 if (delta > kRipOffDistance) {
1217 // Create a proxy view item which can be moved anywhere.
1218 CreateDragIconProxy(event.root_location(), drag_view_->GetImage(),
1219 drag_view_, gfx::Vector2d(0, 0),
1220 kDragAndDropProxyScale);
1221 drag_view_->layer()->SetOpacity(0.0f);
1222 dragged_off_shelf_ = true;
1223 if (RemovableByRipOff(current_index) == REMOVABLE) {
1224 // Move the item to the front of the first panel item and hide it.
1225 // ShelfItemMoved() callback will handle the |view_model_| update and
1226 // call AnimateToIdealBounds().
1227 if (current_index != model_->FirstPanelIndex() - 1) {
1228 model_->Move(current_index, model_->FirstPanelIndex() - 1);
1229 StartFadeInLastVisibleItem();
1230 } else if (is_overflow_mode()) {
1231 // Overflow bubble should be shrunk when an item is ripped off.
1232 PreferredSizeChanged();
1233 }
1234 // Make the item partially disappear to show that it will get removed if
1235 // dropped.
1236 drag_image_->SetOpacity(kDraggedImageOpacity);
1237 }
1238 return true;
1239 }
1240 return false;
1241 }
1242
1243 void ShelfView::FinalizeRipOffDrag(bool cancel) {
1244 if (!dragged_off_shelf_)
1245 return;
1246 // Make sure we do not come in here again.
1247 dragged_off_shelf_ = false;
1248
1249 // Coming here we should always have a |drag_view_|.
1250 DCHECK(drag_view_);
1251 int current_index = view_model_->GetIndexOfView(drag_view_);
1252 // If the view isn't part of the model anymore (|current_index| == -1), a sync
1253 // operation must have removed it. In that case we shouldn't change the model
1254 // and only delete the proxy image.
1255 if (current_index == -1) {
1256 DestroyDragIconProxy();
1257 return;
1258 }
1259
1260 // Set to true when the animation should snap back to where it was before.
1261 bool snap_back = false;
1262 // Items which cannot be dragged off will be handled as a cancel.
1263 if (!cancel) {
1264 if (dragged_off_from_overflow_to_shelf_) {
1265 dragged_off_from_overflow_to_shelf_ = false;
1266 main_shelf_->EndDrag(false);
1267 drag_view_->layer()->SetOpacity(1.0f);
1268 } else if (RemovableByRipOff(current_index) != REMOVABLE) {
1269 // Make sure we do not try to remove un-removable items like items which
1270 // were not pinned or have to be always there.
1271 cancel = true;
1272 snap_back = true;
1273 } else {
1274 // Make sure the item stays invisible upon removal.
1275 drag_view_->SetVisible(false);
1276 std::string app_id =
1277 delegate_->GetAppIDForShelfID(model_->items()[current_index].id);
1278 delegate_->UnpinAppWithID(app_id);
1279 }
1280 }
1281 if (cancel || snap_back) {
1282 if (dragged_off_from_overflow_to_shelf_) {
1283 dragged_off_from_overflow_to_shelf_ = false;
1284 // Main shelf handles revert of dragged item.
1285 main_shelf_->EndDrag(true);
1286 drag_view_->layer()->SetOpacity(1.0f);
1287 } else if (!cancelling_drag_model_changed_) {
1288 // Only do something if the change did not come through a model change.
1289 gfx::Rect drag_bounds = drag_image_->GetBoundsInScreen();
1290 gfx::Point relative_to = GetBoundsInScreen().origin();
1291 gfx::Rect target(
1292 gfx::PointAtOffsetFromOrigin(drag_bounds.origin() - relative_to),
1293 drag_bounds.size());
1294 drag_view_->SetBoundsRect(target);
1295 // Hide the status from the active item since we snap it back now. Upon
1296 // animation end the flag gets cleared if |snap_back_from_rip_off_view_|
1297 // is set.
1298 snap_back_from_rip_off_view_ = drag_view_;
1299 drag_view_->AddState(ShelfButton::STATE_HIDDEN);
1300 // When a canceling drag model is happening, the view model is diverged
1301 // from the menu model and movements / animations should not be done.
1302 model_->Move(current_index, start_drag_index_);
1303 AnimateToIdealBounds();
1304 }
1305 drag_view_->layer()->SetOpacity(1.0f);
1306 }
1307 DestroyDragIconProxy();
1308 }
1309
1310 ShelfView::RemovableState ShelfView::RemovableByRipOff(int index) const {
1311 DCHECK(index >= 0 && index < model_->item_count());
1312 ShelfItemType type = model_->items()[index].type;
1313 if (type == TYPE_APP_LIST || type == TYPE_DIALOG)
1314 return NOT_REMOVABLE;
1315
1316 std::string app_id = delegate_->GetAppIDForShelfID(model_->items()[index].id);
1317 ShelfItemDelegate* item_delegate =
1318 model_->GetShelfItemDelegate(model_->items()[index].id);
1319 if (!item_delegate->CanPin())
1320 return NOT_REMOVABLE;
1321 // Note: Only pinned app shortcuts can be removed!
1322 return (type == TYPE_APP_SHORTCUT && delegate_->IsAppPinned(app_id))
1323 ? REMOVABLE
1324 : DRAGGABLE;
1325 }
1326
1327 bool ShelfView::SameDragType(ShelfItemType typea, ShelfItemType typeb) const {
1328 switch (typea) {
1329 case TYPE_APP_SHORTCUT:
1330 case TYPE_BROWSER_SHORTCUT:
1331 return (typeb == TYPE_APP_SHORTCUT || typeb == TYPE_BROWSER_SHORTCUT);
1332 case TYPE_APP_LIST:
1333 case TYPE_PLATFORM_APP:
1334 case TYPE_WINDOWED_APP:
1335 case TYPE_APP_PANEL:
1336 case TYPE_DIALOG:
1337 case TYPE_IME_MENU:
1338 return typeb == typea;
1339 case TYPE_UNDEFINED:
1340 NOTREACHED() << "ShelfItemType must be set.";
1341 return false;
1342 }
1343 NOTREACHED();
1344 return false;
1345 }
1346
1347 std::pair<int, int> ShelfView::GetDragRange(int index) {
1348 int min_index = -1;
1349 int max_index = -1;
1350 ShelfItemType type = model_->items()[index].type;
1351 for (int i = 0; i < model_->item_count(); ++i) {
1352 if (SameDragType(model_->items()[i].type, type)) {
1353 if (min_index == -1)
1354 min_index = i;
1355 max_index = i;
1356 }
1357 }
1358 return std::pair<int, int>(min_index, max_index);
1359 }
1360
1361 void ShelfView::ConfigureChildView(views::View* view) {
1362 view->SetPaintToLayer(true);
1363 view->layer()->SetFillsBoundsOpaquely(false);
1364 }
1365
1366 void ShelfView::ToggleOverflowBubble() {
1367 if (IsShowingOverflowBubble()) {
1368 overflow_bubble_->Hide();
1369 return;
1370 }
1371
1372 if (!overflow_bubble_)
1373 overflow_bubble_.reset(new OverflowBubble(wm_shelf_));
1374
1375 ShelfView* overflow_view =
1376 new ShelfView(model_, delegate_, wm_shelf_, shelf_);
1377 overflow_view->overflow_mode_ = true;
1378 overflow_view->Init();
1379 overflow_view->set_owner_overflow_bubble(overflow_bubble_.get());
1380 overflow_view->OnShelfAlignmentChanged();
1381 overflow_view->main_shelf_ = this;
1382 UpdateOverflowRange(overflow_view);
1383
1384 overflow_bubble_->Show(overflow_button_, overflow_view);
1385
1386 wm_shelf_->UpdateVisibilityState();
1387 }
1388
1389 void ShelfView::OnFadeOutAnimationEnded() {
1390 AnimateToIdealBounds();
1391 StartFadeInLastVisibleItem();
1392 }
1393
1394 void ShelfView::StartFadeInLastVisibleItem() {
1395 // If overflow button is visible and there is a valid new last item, fading
1396 // the new last item in after sliding animation is finished.
1397 if (overflow_button_->visible() && last_visible_index_ >= 0) {
1398 views::View* last_visible_view = view_model_->view_at(last_visible_index_);
1399 last_visible_view->layer()->SetOpacity(0);
1400 bounds_animator_->SetAnimationDelegate(
1401 last_visible_view,
1402 std::unique_ptr<gfx::AnimationDelegate>(
1403 new StartFadeAnimationDelegate(this, last_visible_view)));
1404 }
1405 }
1406
1407 void ShelfView::UpdateOverflowRange(ShelfView* overflow_view) const {
1408 const int first_overflow_index = last_visible_index_ + 1;
1409 const int last_overflow_index = last_hidden_index_;
1410 DCHECK_LE(first_overflow_index, last_overflow_index);
1411 DCHECK_LT(last_overflow_index, view_model_->view_size());
1412
1413 overflow_view->first_visible_index_ = first_overflow_index;
1414 overflow_view->last_visible_index_ = last_overflow_index;
1415 }
1416
1417 gfx::Rect ShelfView::GetBoundsForDragInsertInScreen() {
1418 gfx::Size preferred_size;
1419 if (is_overflow_mode()) {
1420 DCHECK(owner_overflow_bubble_);
1421 gfx::Rect bubble_bounds =
1422 owner_overflow_bubble_->bubble_view()->GetBubbleBounds();
1423 preferred_size = bubble_bounds.size();
1424 } else {
1425 const int last_button_index = view_model_->view_size() - 1;
1426 gfx::Rect last_button_bounds =
1427 view_model_->view_at(last_button_index)->bounds();
1428 if (overflow_button_->visible() &&
1429 model_->GetItemIndexForType(TYPE_APP_PANEL) == -1) {
1430 // When overflow button is visible and shelf has no panel items,
1431 // last_button_bounds should be overflow button's bounds.
1432 last_button_bounds = overflow_button_->bounds();
1433 }
1434
1435 if (shelf_->IsHorizontalAlignment()) {
1436 preferred_size = gfx::Size(last_button_bounds.right() + leading_inset_,
1437 GetShelfConstant(SHELF_SIZE));
1438 } else {
1439 preferred_size = gfx::Size(GetShelfConstant(SHELF_SIZE),
1440 last_button_bounds.bottom() + leading_inset_);
1441 }
1442 }
1443 gfx::Point origin(GetMirroredXWithWidthInView(0, preferred_size.width()), 0);
1444
1445 // In overflow mode, we should use OverflowBubbleView as a source for
1446 // converting |origin| to screen coordinates. When a scroll operation is
1447 // occurred in OverflowBubble, the bounds of ShelfView in OverflowBubble can
1448 // be changed.
1449 if (is_overflow_mode())
1450 ConvertPointToScreen(owner_overflow_bubble_->bubble_view(), &origin);
1451 else
1452 ConvertPointToScreen(this, &origin);
1453
1454 return gfx::Rect(origin, preferred_size);
1455 }
1456
1457 int ShelfView::CancelDrag(int modified_index) {
1458 FinalizeRipOffDrag(true);
1459 if (!drag_view_)
1460 return modified_index;
1461 bool was_dragging = dragging();
1462 int drag_view_index = view_model_->GetIndexOfView(drag_view_);
1463 drag_pointer_ = NONE;
1464 drag_view_ = nullptr;
1465 if (drag_view_index == modified_index) {
1466 // The view that was being dragged is being modified. Don't do anything.
1467 return modified_index;
1468 }
1469 if (!was_dragging)
1470 return modified_index;
1471
1472 // Restore previous position, tracking the position of the modified view.
1473 bool at_end = modified_index == view_model_->view_size();
1474 views::View* modified_view = (modified_index >= 0 && !at_end)
1475 ? view_model_->view_at(modified_index)
1476 : nullptr;
1477 model_->Move(drag_view_index, start_drag_index_);
1478
1479 // If the modified view will be at the end of the list, return the new end of
1480 // the list.
1481 if (at_end)
1482 return view_model_->view_size();
1483 return modified_view ? view_model_->GetIndexOfView(modified_view) : -1;
1484 }
1485
1486 gfx::Size ShelfView::GetPreferredSize() const {
1487 IdealBounds ideal_bounds;
1488 CalculateIdealBounds(&ideal_bounds);
1489 const int shelf_size = GetShelfConstant(SHELF_SIZE);
1490
1491 int last_button_index =
1492 is_overflow_mode() ? last_visible_index_ : view_model_->view_size() - 1;
1493
1494 // When an item is dragged off from the overflow bubble, it is moved to last
1495 // position and and changed to invisible. Overflow bubble size should be
1496 // shrunk to fit only for visible items.
1497 // If |dragged_off_from_overflow_to_shelf_| is set, there will be no invisible
1498 // items in the shelf.
1499 if (is_overflow_mode() && dragged_off_shelf_ &&
1500 !dragged_off_from_overflow_to_shelf_ &&
1501 RemovableByRipOff(view_model_->GetIndexOfView(drag_view_)) == REMOVABLE)
1502 last_button_index--;
1503
1504 const gfx::Rect last_button_bounds =
1505 last_button_index >= first_visible_index_
1506 ? view_model_->ideal_bounds(last_button_index)
1507 : gfx::Rect(gfx::Size(shelf_size, shelf_size));
1508
1509 if (shelf_->IsHorizontalAlignment())
1510 return gfx::Size(last_button_bounds.right() + leading_inset_, shelf_size);
1511
1512 return gfx::Size(shelf_size, last_button_bounds.bottom() + leading_inset_);
1513 }
1514
1515 void ShelfView::OnBoundsChanged(const gfx::Rect& previous_bounds) {
1516 // This bounds change is produced by the shelf movement and all content has
1517 // to follow. Using an animation at that time would produce a time lag since
1518 // the animation of the BoundsAnimator has itself a delay before it arrives
1519 // at the required location. As such we tell the animator to go there
1520 // immediately.
1521 BoundsAnimatorDisabler disabler(bounds_animator_.get());
1522 LayoutToIdealBounds();
1523 FOR_EACH_OBSERVER(ShelfIconObserver, observers_,
1524 OnShelfIconPositionsChanged());
1525
1526 if (IsShowingOverflowBubble())
1527 overflow_bubble_->Hide();
1528 }
1529
1530 views::FocusTraversable* ShelfView::GetPaneFocusTraversable() {
1531 return this;
1532 }
1533
1534 void ShelfView::GetAccessibleState(ui::AXViewState* state) {
1535 state->role = ui::AX_ROLE_TOOLBAR;
1536 state->name = l10n_util::GetStringUTF16(IDS_ASH_SHELF_ACCESSIBLE_NAME);
1537 }
1538
1539 void ShelfView::ViewHierarchyChanged(
1540 const ViewHierarchyChangedDetails& details) {
1541 if (details.is_add && details.child == this)
1542 tooltip_.Init();
1543 }
1544
1545 void ShelfView::OnGestureEvent(ui::GestureEvent* event) {
1546 if (wm_shelf_->ProcessGestureEvent(*event))
1547 event->StopPropagation();
1548 }
1549
1550 void ShelfView::ShelfItemAdded(int model_index) {
1551 {
1552 base::AutoReset<bool> cancelling_drag(&cancelling_drag_model_changed_,
1553 true);
1554 model_index = CancelDrag(model_index);
1555 }
1556 views::View* view = CreateViewForItem(model_->items()[model_index]);
1557 AddChildView(view);
1558 // Hide the view, it'll be made visible when the animation is done. Using
1559 // opacity 0 here to avoid messing with CalculateIdealBounds which touches
1560 // the view's visibility.
1561 view->layer()->SetOpacity(0);
1562 view_model_->Add(view, model_index);
1563
1564 // Give the button its ideal bounds. That way if we end up animating the
1565 // button before this animation completes it doesn't appear at some random
1566 // spot (because it was in the middle of animating from 0,0 0x0 to its
1567 // target).
1568 IdealBounds ideal_bounds;
1569 CalculateIdealBounds(&ideal_bounds);
1570 view->SetBoundsRect(view_model_->ideal_bounds(model_index));
1571
1572 // The first animation moves all the views to their target position. |view|
1573 // is hidden, so it visually appears as though we are providing space for
1574 // it. When done we'll fade the view in.
1575 AnimateToIdealBounds();
1576 if (model_index <= last_visible_index_ ||
1577 model_index >= model_->FirstPanelIndex()) {
1578 bounds_animator_->SetAnimationDelegate(
1579 view, std::unique_ptr<gfx::AnimationDelegate>(
1580 new StartFadeAnimationDelegate(this, view)));
1581 } else {
1582 // Undo the hiding if animation does not run.
1583 view->layer()->SetOpacity(1.0f);
1584 }
1585 }
1586
1587 void ShelfView::ShelfItemRemoved(int model_index, ShelfID id) {
1588 if (id == context_menu_id_)
1589 launcher_menu_runner_->Cancel();
1590 {
1591 base::AutoReset<bool> cancelling_drag(&cancelling_drag_model_changed_,
1592 true);
1593 model_index = CancelDrag(model_index);
1594 }
1595 views::View* view = view_model_->view_at(model_index);
1596 view_model_->Remove(model_index);
1597
1598 // When the overflow bubble is visible, the overflow range needs to be set
1599 // before CalculateIdealBounds() gets called. Otherwise CalculateIdealBounds()
1600 // could trigger a ShelfItemChanged() by hiding the overflow bubble and
1601 // since the overflow bubble is not yet synced with the ShelfModel this
1602 // could cause a crash.
1603 if (overflow_bubble_ && overflow_bubble_->IsShowing()) {
1604 last_hidden_index_ =
1605 std::min(last_hidden_index_, view_model_->view_size() - 1);
1606 UpdateOverflowRange(overflow_bubble_->shelf_view());
1607 }
1608
1609 if (view->visible()) {
1610 // The first animation fades out the view. When done we'll animate the rest
1611 // of the views to their target location.
1612 bounds_animator_->AnimateViewTo(view, view->bounds());
1613 bounds_animator_->SetAnimationDelegate(
1614 view, std::unique_ptr<gfx::AnimationDelegate>(
1615 new FadeOutAnimationDelegate(this, view)));
1616 } else {
1617 // We don't need to show a fade out animation for invisible |view|. When an
1618 // item is ripped out from the shelf, its |view| is already invisible.
1619 AnimateToIdealBounds();
1620 }
1621
1622 if (view == tooltip_.GetCurrentAnchorView())
1623 tooltip_.Close();
1624 }
1625
1626 void ShelfView::ShelfItemChanged(int model_index, const ShelfItem& old_item) {
1627 const ShelfItem& item(model_->items()[model_index]);
1628 if (old_item.type != item.type) {
1629 // Type changed, swap the views.
1630 model_index = CancelDrag(model_index);
1631 std::unique_ptr<views::View> old_view(view_model_->view_at(model_index));
1632 bounds_animator_->StopAnimatingView(old_view.get());
1633 // Removing and re-inserting a view in our view model will strip the ideal
1634 // bounds from the item. To avoid recalculation of everything the bounds
1635 // get remembered and restored after the insertion to the previous value.
1636 gfx::Rect old_ideal_bounds = view_model_->ideal_bounds(model_index);
1637 view_model_->Remove(model_index);
1638 views::View* new_view = CreateViewForItem(item);
1639 AddChildView(new_view);
1640 view_model_->Add(new_view, model_index);
1641 view_model_->set_ideal_bounds(model_index, old_ideal_bounds);
1642 new_view->SetBoundsRect(old_view->bounds());
1643 if (overflow_button_ && overflow_button_->visible())
1644 AnimateToIdealBounds();
1645 return;
1646 }
1647
1648 views::View* view = view_model_->view_at(model_index);
1649 switch (item.type) {
1650 case TYPE_BROWSER_SHORTCUT:
1651 // Fallthrough for the new Shelf since it needs to show the activation
1652 // change as well.
1653 case TYPE_APP_SHORTCUT:
1654 case TYPE_WINDOWED_APP:
1655 case TYPE_PLATFORM_APP:
1656 case TYPE_DIALOG:
1657 case TYPE_APP_PANEL:
1658 case TYPE_IME_MENU: {
1659 CHECK_EQ(ShelfButton::kViewClassName, view->GetClassName());
1660 ShelfButton* button = static_cast<ShelfButton*>(view);
1661 ReflectItemStatus(item, button);
1662 button->SetImage(item.image);
1663 button->SchedulePaint();
1664 break;
1665 }
1666
1667 default:
1668 break;
1669 }
1670 }
1671
1672 void ShelfView::ShelfItemMoved(int start_index, int target_index) {
1673 view_model_->Move(start_index, target_index);
1674 // When cancelling a drag due to a shelf item being added, the currently
1675 // dragged item is moved back to its initial position. AnimateToIdealBounds
1676 // will be called again when the new item is added to the |view_model_| but
1677 // at this time the |view_model_| is inconsistent with the |model_|.
1678 if (!cancelling_drag_model_changed_)
1679 AnimateToIdealBounds();
1680 }
1681
1682 void ShelfView::OnSetShelfItemDelegate(ShelfID id,
1683 ShelfItemDelegate* item_delegate) {}
1684
1685 void ShelfView::ButtonPressed(views::Button* sender,
1686 const ui::Event& event,
1687 views::InkDrop* ink_drop) {
1688 if (sender == overflow_button_) {
1689 ToggleOverflowBubble();
1690 shelf_button_pressed_metric_tracker_.ButtonPressed(
1691 event, sender, ShelfItemDelegate::kNoAction);
1692 return;
1693 }
1694
1695 // None of the checks in ShouldEventActivateButton() affects overflow button.
1696 // So, it is safe to be checked after handling overflow button.
1697 if (!ShouldEventActivateButton(sender, event))
1698 return;
1699
1700 // Record the index for the last pressed shelf item.
1701 last_pressed_index_ = view_model_->GetIndexOfView(sender);
1702 DCHECK_LT(-1, last_pressed_index_);
1703
1704 // Place new windows on the same display as the button.
1705 WmWindow* window = WmLookup::Get()->GetWindowForWidget(sender->GetWidget());
1706 scoped_root_window_for_new_windows_.reset(
1707 new ScopedRootWindowForNewWindows(window->GetRootWindow()));
1708
1709 // Slow down activation animations if shift key is pressed.
1710 std::unique_ptr<ui::ScopedAnimationDurationScaleMode> slowing_animations;
1711 if (event.IsShiftDown()) {
1712 slowing_animations.reset(new ui::ScopedAnimationDurationScaleMode(
1713 ui::ScopedAnimationDurationScaleMode::SLOW_DURATION));
1714 }
1715
1716 // Collect usage statistics before we decide what to do with the click.
1717 switch (model_->items()[last_pressed_index_].type) {
1718 case TYPE_APP_SHORTCUT:
1719 case TYPE_WINDOWED_APP:
1720 case TYPE_PLATFORM_APP:
1721 case TYPE_BROWSER_SHORTCUT:
1722 WmShell::Get()->RecordUserMetricsAction(UMA_LAUNCHER_CLICK_ON_APP);
1723 break;
1724
1725 case TYPE_APP_LIST:
1726 WmShell::Get()->RecordUserMetricsAction(
1727 UMA_LAUNCHER_CLICK_ON_APPLIST_BUTTON);
1728 break;
1729
1730 case TYPE_APP_PANEL:
1731 case TYPE_DIALOG:
1732 case TYPE_IME_MENU:
1733 break;
1734
1735 case TYPE_UNDEFINED:
1736 NOTREACHED() << "ShelfItemType must be set.";
1737 break;
1738 }
1739
1740 ShelfItemDelegate::PerformedAction performed_action =
1741 model_->GetShelfItemDelegate(model_->items()[last_pressed_index_].id)
1742 ->ItemSelected(event);
1743
1744 shelf_button_pressed_metric_tracker_.ButtonPressed(event, sender,
1745 performed_action);
1746
1747 // For the app list menu no TRIGGERED ink drop effect is needed and it
1748 // handles its own ACTIVATED/DEACTIVATED states.
1749 if (performed_action == ShelfItemDelegate::kNewWindowCreated ||
1750 (performed_action != ShelfItemDelegate::kAppListMenuShown &&
1751 !ShowListMenuForView(model_->items()[last_pressed_index_], sender, event,
1752 ink_drop))) {
1753 ink_drop->AnimateToState(views::InkDropState::ACTION_TRIGGERED);
1754 }
1755 // Allow the menu to clear |scoped_root_window_for_new_windows_| during
1756 // OnMenuClosed.
1757 if (!IsShowingMenu())
1758 scoped_root_window_for_new_windows_.reset();
1759 }
1760
1761 bool ShelfView::ShowListMenuForView(const ShelfItem& item,
1762 views::View* source,
1763 const ui::Event& event,
1764 views::InkDrop* ink_drop) {
1765 ShelfItemDelegate* item_delegate = model_->GetShelfItemDelegate(item.id);
1766 std::unique_ptr<ui::MenuModel> list_menu_model(
1767 item_delegate->CreateApplicationMenu(event.flags()));
1768
1769 // Make sure we have a menu and it has at least two items in addition to the
1770 // application title and the 3 spacing separators.
1771 if (!list_menu_model.get() || list_menu_model->GetItemCount() <= 5)
1772 return false;
1773
1774 ink_drop->AnimateToState(views::InkDropState::ACTIVATED);
1775 context_menu_id_ = item.id;
1776 ShowMenu(std::move(list_menu_model), source, gfx::Point(), false,
1777 ui::GetMenuSourceTypeForEvent(event), ink_drop);
1778 return true;
1779 }
1780
1781 void ShelfView::ShowContextMenuForView(views::View* source,
1782 const gfx::Point& point,
1783 ui::MenuSourceType source_type) {
1784 last_pressed_index_ = -1;
1785
1786 const ShelfItem* item = ShelfItemForView(source);
1787 if (!item) {
1788 WmShell::Get()->ShowContextMenu(point, source_type);
1789 return;
1790 }
1791
1792 std::unique_ptr<ui::MenuModel> context_menu_model(
1793 WmShell::Get()->delegate()->CreateContextMenu(wm_shelf_, item));
1794 if (!context_menu_model)
1795 return;
1796
1797 context_menu_id_ = item ? item->id : 0;
1798 ShowMenu(std::move(context_menu_model), source, point, true, source_type,
1799 nullptr);
1800 }
1801
1802 void ShelfView::ShowMenu(std::unique_ptr<ui::MenuModel> menu_model,
1803 views::View* source,
1804 const gfx::Point& click_point,
1805 bool context_menu,
1806 ui::MenuSourceType source_type,
1807 views::InkDrop* ink_drop) {
1808 menu_model_ = std::move(menu_model);
1809 menu_model_adapter_.reset(new views::MenuModelAdapter(
1810 menu_model_.get(),
1811 base::Bind(&ShelfView::OnMenuClosed, base::Unretained(this), ink_drop)));
1812
1813 closing_event_time_ = base::TimeTicks();
1814 int run_types = views::MenuRunner::ASYNC;
1815 if (context_menu)
1816 run_types |= views::MenuRunner::CONTEXT_MENU;
1817 launcher_menu_runner_.reset(
1818 new views::MenuRunner(menu_model_adapter_->CreateMenu(), run_types));
1819
1820 // Place new windows on the same display as the button that spawned the menu.
1821 WmWindow* window = WmLookup::Get()->GetWindowForWidget(source->GetWidget());
1822 scoped_root_window_for_new_windows_.reset(
1823 new ScopedRootWindowForNewWindows(window->GetRootWindow()));
1824
1825 views::MenuAnchorPosition menu_alignment = views::MENU_ANCHOR_TOPLEFT;
1826 gfx::Rect anchor = gfx::Rect(click_point, gfx::Size());
1827
1828 if (!context_menu) {
1829 // Application lists use a bubble.
1830 // It is possible to invoke the menu while it is sliding into view. To cover
1831 // that case, the screen coordinates are offsetted by the animation delta.
1832 anchor = source->GetBoundsInScreen() + (window->GetTargetBounds().origin() -
1833 window->GetBounds().origin());
1834
1835 // Adjust the anchor location for shelf items with asymmetrical borders.
1836 if (source->border())
1837 anchor.Inset(source->border()->GetInsets());
1838
1839 // Determine the menu alignment dependent on the shelf.
1840 menu_alignment = shelf_->SelectValueForShelfAlignment(
1841 views::MENU_ANCHOR_BUBBLE_ABOVE, views::MENU_ANCHOR_BUBBLE_RIGHT,
1842 views::MENU_ANCHOR_BUBBLE_LEFT);
1843 }
1844
1845 shelf_->shelf_widget()->ForceUndimming(true);
1846 // NOTE: if you convert to HAS_MNEMONICS be sure to update menu building code.
1847 launcher_menu_runner_->RunMenuAt(source->GetWidget(), nullptr, anchor,
1848 menu_alignment, source_type);
1849 }
1850
1851 void ShelfView::OnMenuClosed(views::InkDrop* ink_drop) {
1852 context_menu_id_ = 0;
1853 shelf_->shelf_widget()->ForceUndimming(false);
1854
1855 // Hide the hide overflow bubble after showing a context menu for its items.
1856 if (owner_overflow_bubble_)
1857 owner_overflow_bubble_->HideBubbleAndRefreshButton();
1858
1859 closing_event_time_ = launcher_menu_runner_->closing_event_time();
1860
1861 if (ink_drop)
1862 ink_drop->AnimateToState(views::InkDropState::DEACTIVATED);
1863
1864 launcher_menu_runner_.reset();
1865 menu_model_adapter_.reset();
1866 menu_model_.reset();
1867 scoped_root_window_for_new_windows_.reset();
1868
1869 // Auto-hide or alignment might have changed, but only for this shelf.
1870 wm_shelf_->UpdateVisibilityState();
1871 }
1872
1873 void ShelfView::OnBoundsAnimatorProgressed(views::BoundsAnimator* animator) {
1874 FOR_EACH_OBSERVER(ShelfIconObserver, observers_,
1875 OnShelfIconPositionsChanged());
1876 PreferredSizeChanged();
1877 }
1878
1879 void ShelfView::OnBoundsAnimatorDone(views::BoundsAnimator* animator) {
1880 if (snap_back_from_rip_off_view_ && animator == bounds_animator_.get()) {
1881 if (!animator->IsAnimating(snap_back_from_rip_off_view_)) {
1882 // Coming here the animation of the ShelfButton is finished and the
1883 // previously hidden status can be shown again. Since the button itself
1884 // might have gone away or changed locations we check that the button
1885 // is still in the shelf and show its status again.
1886 for (int index = 0; index < view_model_->view_size(); index++) {
1887 views::View* view = view_model_->view_at(index);
1888 if (view == snap_back_from_rip_off_view_) {
1889 CHECK_EQ(ShelfButton::kViewClassName, view->GetClassName());
1890 ShelfButton* button = static_cast<ShelfButton*>(view);
1891 button->ClearState(ShelfButton::STATE_HIDDEN);
1892 break;
1893 }
1894 }
1895 snap_back_from_rip_off_view_ = nullptr;
1896 }
1897 }
1898 }
1899
1900 bool ShelfView::IsRepostEvent(const ui::Event& event) {
1901 if (closing_event_time_.is_null())
1902 return false;
1903
1904 // If the current (press down) event is a repost event, the time stamp of
1905 // these two events should be the same.
1906 return closing_event_time_ == event.time_stamp();
1907 }
1908
1909 const ShelfItem* ShelfView::ShelfItemForView(const views::View* view) const {
1910 const int view_index = view_model_->GetIndexOfView(view);
1911 return (view_index < 0) ? nullptr : &(model_->items()[view_index]);
1912 }
1913
1914 int ShelfView::CalculateShelfDistance(const gfx::Point& coordinate) const {
1915 const gfx::Rect bounds = GetBoundsInScreen();
1916 int distance = shelf_->SelectValueForShelfAlignment(
1917 bounds.y() - coordinate.y(), coordinate.x() - bounds.right(),
1918 bounds.x() - coordinate.x());
1919 return distance > 0 ? distance : 0;
1920 }
1921
1922 } // namespace ash
OLDNEW
« no previous file with comments | « ash/shelf/shelf_view.h ('k') | ash/shelf/shelf_view_unittest.cc » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698