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.
5 #include "ash/shelf/shelf_view.h"
9 #include "ash/ash_constants.h"
10 #include "ash/ash_switches.h"
11 #include "ash/drag_drop/drag_image_view.h"
12 #include "ash/launcher/launcher_button.h"
13 #include "ash/launcher/launcher_delegate.h"
14 #include "ash/launcher/launcher_item_delegate.h"
15 #include "ash/launcher/launcher_item_delegate_manager.h"
16 #include "ash/launcher/launcher_model.h"
17 #include "ash/root_window_controller.h"
18 #include "ash/scoped_target_root_window.h"
19 #include "ash/shelf/alternate_app_list_button.h"
20 #include "ash/shelf/app_list_button.h"
21 #include "ash/shelf/overflow_bubble.h"
22 #include "ash/shelf/overflow_button.h"
23 #include "ash/shelf/shelf_icon_observer.h"
24 #include "ash/shelf/shelf_layout_manager.h"
25 #include "ash/shelf/shelf_tooltip_manager.h"
26 #include "ash/shelf/shelf_widget.h"
27 #include "ash/shell_delegate.h"
28 #include "base/auto_reset.h"
29 #include "base/memory/scoped_ptr.h"
30 #include "grit/ash_resources.h"
31 #include "grit/ash_strings.h"
32 #include "ui/aura/client/screen_position_client.h"
33 #include "ui/aura/root_window.h"
34 #include "ui/aura/window.h"
35 #include "ui/base/accessibility/accessible_view_state.h"
36 #include "ui/base/l10n/l10n_util.h"
37 #include "ui/base/models/simple_menu_model.h"
38 #include "ui/base/resource/resource_bundle.h"
39 #include "ui/compositor/layer.h"
40 #include "ui/compositor/layer_animator.h"
41 #include "ui/compositor/scoped_animation_duration_scale_mode.h"
42 #include "ui/gfx/canvas.h"
43 #include "ui/gfx/point.h"
44 #include "ui/views/animation/bounds_animator.h"
45 #include "ui/views/border.h"
46 #include "ui/views/controls/button/image_button.h"
47 #include "ui/views/controls/menu/menu_model_adapter.h"
48 #include "ui/views/controls/menu/menu_runner.h"
49 #include "ui/views/focus/focus_search.h"
50 #include "ui/views/focus_border.h"
51 #include "ui/views/view_model.h"
52 #include "ui/views/view_model_utils.h"
53 #include "ui/views/widget/widget.h"
61 // Default amount content is inset on the left edge.
62 const int kDefaultLeadingInset = 8;
64 // Minimum distance before drag starts.
65 const int kMinimumDragDistance = 8;
67 // Size between the buttons.
68 const int kButtonSpacing = 4;
69 const int kAlternateButtonSpacing = 10;
71 // Size allocated to for each button.
72 const int kButtonSize = 44;
74 // Additional spacing for the left and right side of icons.
75 const int kHorizontalIconSpacing = 2;
77 // Inset for items which do not have an icon.
78 const int kHorizontalNoIconInsetSpacing =
79 kHorizontalIconSpacing + kDefaultLeadingInset;
81 // The proportion of the launcher space reserved for non-panel icons. Panels
82 // may flow into this space but will be put into the overflow bubble if there
83 // is contention for the space.
84 const float kReservedNonPanelIconProportion = 0.67f;
86 // This is the command id of the menu item which contains the name of the menu.
87 const int kCommandIdOfMenuName = 0;
89 // The background color of the active item in the list.
90 const SkColor kActiveListItemBackgroundColor = SkColorSetRGB(203 , 219, 241);
92 // The background color of the active & hovered item in the list.
93 const SkColor kFocusedActiveListItemBackgroundColor =
94 SkColorSetRGB(193, 211, 236);
96 // The text color of the caption item in a list.
97 const SkColor kCaptionItemForegroundColor = SK_ColorBLACK;
99 // The maximum allowable length of a menu line of an application menu in pixels.
100 const int kMaximumAppMenuItemLength = 350;
102 // The distance of the cursor from the outer rim of the shelf before it
103 // separates / re-inserts. Note that the rip off distance is bigger then the
104 // re-insertion distance to avoid "flickering" between the two states.
105 const int kRipOffDistance = 48;
106 const int kReinsertDistance = 32;
108 // The rip off drag and drop proxy image should get scaled by this factor.
109 const float kDragAndDropProxyScale = 1.5f;
113 // The MenuModelAdapter gets slightly changed to adapt the menu appearance to
115 class LauncherMenuModelAdapter
116 : public views::MenuModelAdapter {
118 explicit LauncherMenuModelAdapter(ash::LauncherMenuModel* menu_model);
120 // Overriding MenuModelAdapter's MenuDelegate implementation.
121 virtual const gfx::Font* GetLabelFont(int command_id) const OVERRIDE;
122 virtual bool IsCommandEnabled(int id) const OVERRIDE;
123 virtual void GetHorizontalIconMargins(int id,
126 int* right_margin) const OVERRIDE;
127 virtual bool GetForegroundColor(int command_id,
129 SkColor* override_color) const OVERRIDE;
130 virtual bool GetBackgroundColor(int command_id,
132 SkColor* override_color) const OVERRIDE;
133 virtual int GetMaxWidthForMenu(views::MenuItemView* menu) OVERRIDE;
134 virtual bool ShouldReserveSpaceForSubmenuIndicator() const OVERRIDE;
137 ash::LauncherMenuModel* launcher_menu_model_;
139 DISALLOW_COPY_AND_ASSIGN(LauncherMenuModelAdapter);
142 LauncherMenuModelAdapter::LauncherMenuModelAdapter(
143 ash::LauncherMenuModel* menu_model)
144 : MenuModelAdapter(menu_model),
145 launcher_menu_model_(menu_model) {}
147 const gfx::Font* LauncherMenuModelAdapter::GetLabelFont(
148 int command_id) const {
149 if (command_id != kCommandIdOfMenuName)
150 return MenuModelAdapter::GetLabelFont(command_id);
152 ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
153 return &rb.GetFont(ui::ResourceBundle::BoldFont);
156 bool LauncherMenuModelAdapter::IsCommandEnabled(int id) const {
157 return id != kCommandIdOfMenuName;
160 bool LauncherMenuModelAdapter::GetForegroundColor(
163 SkColor* override_color) const {
164 if (command_id != kCommandIdOfMenuName)
167 *override_color = kCaptionItemForegroundColor;
171 bool LauncherMenuModelAdapter::GetBackgroundColor(
174 SkColor* override_color) const {
175 if (!launcher_menu_model_->IsCommandActive(command_id))
178 *override_color = is_hovered ? kFocusedActiveListItemBackgroundColor :
179 kActiveListItemBackgroundColor;
183 void LauncherMenuModelAdapter::GetHorizontalIconMargins(
187 int* right_margin) const {
188 *left_margin = kHorizontalIconSpacing;
189 *right_margin = (command_id != kCommandIdOfMenuName) ?
190 kHorizontalIconSpacing : -(icon_size + kHorizontalNoIconInsetSpacing);
193 int LauncherMenuModelAdapter::GetMaxWidthForMenu(views::MenuItemView* menu) {
194 return kMaximumAppMenuItemLength;
197 bool LauncherMenuModelAdapter::ShouldReserveSpaceForSubmenuIndicator() const {
201 // Custom FocusSearch used to navigate the launcher in the order items are in
203 class LauncherFocusSearch : public views::FocusSearch {
205 explicit LauncherFocusSearch(views::ViewModel* view_model)
206 : FocusSearch(NULL, true, true),
207 view_model_(view_model) {}
208 virtual ~LauncherFocusSearch() {}
210 // views::FocusSearch overrides:
211 virtual View* FindNextFocusableView(
215 bool check_starting_view,
216 views::FocusTraversable** focus_traversable,
217 View** focus_traversable_view) OVERRIDE {
218 int index = view_model_->GetIndexOfView(starting_view);
220 return view_model_->view_at(0);
225 index = view_model_->view_size() - 1;
228 if (index >= view_model_->view_size())
231 return view_model_->view_at(index);
235 views::ViewModel* view_model_;
237 DISALLOW_COPY_AND_ASSIGN(LauncherFocusSearch);
240 class LauncherButtonFocusBorder : public views::FocusBorder {
242 LauncherButtonFocusBorder() {}
243 virtual ~LauncherButtonFocusBorder() {}
246 // views::FocusBorder overrides:
247 virtual void Paint(const View& view, gfx::Canvas* canvas) const OVERRIDE {
248 gfx::Rect rect(view.GetLocalBounds());
250 canvas->DrawRect(rect, kFocusBorderColor);
253 DISALLOW_COPY_AND_ASSIGN(LauncherButtonFocusBorder);
256 // AnimationDelegate used when inserting a new item. This steadily increases the
257 // opacity of the layer as the animation progress.
258 class FadeInAnimationDelegate
259 : public views::BoundsAnimator::OwnedAnimationDelegate {
261 explicit FadeInAnimationDelegate(views::View* view) : view_(view) {}
262 virtual ~FadeInAnimationDelegate() {}
264 // AnimationDelegate overrides:
265 virtual void AnimationProgressed(const Animation* animation) OVERRIDE {
266 view_->layer()->SetOpacity(animation->GetCurrentValue());
267 view_->layer()->ScheduleDraw();
269 virtual void AnimationEnded(const Animation* animation) OVERRIDE {
270 view_->layer()->SetOpacity(1.0f);
271 view_->layer()->ScheduleDraw();
273 virtual void AnimationCanceled(const Animation* animation) OVERRIDE {
274 view_->layer()->SetOpacity(1.0f);
275 view_->layer()->ScheduleDraw();
281 DISALLOW_COPY_AND_ASSIGN(FadeInAnimationDelegate);
284 void ReflectItemStatus(const ash::LauncherItem& item,
285 LauncherButton* button) {
286 switch (item.status) {
288 button->ClearState(LauncherButton::STATE_ACTIVE);
289 button->ClearState(LauncherButton::STATE_RUNNING);
290 button->ClearState(LauncherButton::STATE_ATTENTION);
293 button->ClearState(LauncherButton::STATE_ACTIVE);
294 button->AddState(LauncherButton::STATE_RUNNING);
295 button->ClearState(LauncherButton::STATE_ATTENTION);
298 button->AddState(LauncherButton::STATE_ACTIVE);
299 button->ClearState(LauncherButton::STATE_RUNNING);
300 button->ClearState(LauncherButton::STATE_ATTENTION);
302 case STATUS_ATTENTION:
303 button->ClearState(LauncherButton::STATE_ACTIVE);
304 button->ClearState(LauncherButton::STATE_RUNNING);
305 button->AddState(LauncherButton::STATE_ATTENTION);
310 // Get the event location in screen coordinates.
311 gfx::Point GetPositionInScreen(const gfx::Point& root_location,
313 gfx::Point root_location_in_screen = root_location;
314 aura::Window* root_window =
315 view->GetWidget()->GetNativeWindow()->GetRootWindow();
316 aura::client::GetScreenPositionClient(root_window->GetRootWindow())->
317 ConvertPointToScreen(root_window, &root_location_in_screen);
318 return root_location_in_screen;
323 // AnimationDelegate used when deleting an item. This steadily decreased the
324 // opacity of the layer as the animation progress.
325 class ShelfView::FadeOutAnimationDelegate
326 : public views::BoundsAnimator::OwnedAnimationDelegate {
328 FadeOutAnimationDelegate(ShelfView* host, views::View* view)
331 virtual ~FadeOutAnimationDelegate() {}
333 // AnimationDelegate overrides:
334 virtual void AnimationProgressed(const Animation* animation) OVERRIDE {
335 view_->layer()->SetOpacity(1 - animation->GetCurrentValue());
336 view_->layer()->ScheduleDraw();
338 virtual void AnimationEnded(const Animation* animation) OVERRIDE {
339 shelf_view_->OnFadeOutAnimationEnded();
341 virtual void AnimationCanceled(const Animation* animation) OVERRIDE {
345 ShelfView* shelf_view_;
346 scoped_ptr<views::View> view_;
348 DISALLOW_COPY_AND_ASSIGN(FadeOutAnimationDelegate);
351 // AnimationDelegate used to trigger fading an element in. When an item is
352 // inserted this delegate is attached to the animation that expands the size of
353 // the item. When done it kicks off another animation to fade the item in.
354 class ShelfView::StartFadeAnimationDelegate
355 : public views::BoundsAnimator::OwnedAnimationDelegate {
357 StartFadeAnimationDelegate(ShelfView* host,
361 virtual ~StartFadeAnimationDelegate() {}
363 // AnimationDelegate overrides:
364 virtual void AnimationEnded(const Animation* animation) OVERRIDE {
365 shelf_view_->FadeIn(view_);
367 virtual void AnimationCanceled(const Animation* animation) OVERRIDE {
368 view_->layer()->SetOpacity(1.0f);
372 ShelfView* shelf_view_;
375 DISALLOW_COPY_AND_ASSIGN(StartFadeAnimationDelegate);
378 ShelfView::ShelfView(LauncherModel* model,
379 LauncherDelegate* delegate,
380 ShelfLayoutManager* shelf_layout_manager)
383 view_model_(new views::ViewModel),
384 first_visible_index_(0),
385 last_visible_index_(-1),
386 overflow_button_(NULL),
387 owner_overflow_bubble_(NULL),
391 start_drag_index_(-1),
393 leading_inset_(kDefaultLeadingInset),
394 cancelling_drag_model_changed_(false),
395 last_hidden_index_(0),
396 closing_event_time_(base::TimeDelta()),
398 drag_and_drop_item_pinned_(false),
399 drag_and_drop_launcher_id_(0),
400 dragged_off_shelf_(false),
401 snap_back_from_rip_off_view_(NULL),
402 item_manager_(Shell::GetInstance()->launcher_item_delegate_manager()),
403 layout_manager_(shelf_layout_manager) {
405 bounds_animator_.reset(new views::BoundsAnimator(this));
406 bounds_animator_->AddObserver(this);
407 set_context_menu_controller(this);
408 focus_search_.reset(new LauncherFocusSearch(view_model_.get()));
409 tooltip_.reset(new ShelfTooltipManager(shelf_layout_manager, this));
412 ShelfView::~ShelfView() {
413 bounds_animator_->RemoveObserver(this);
414 model_->RemoveObserver(this);
415 // If we are inside the MenuRunner, we need to know if we were getting
416 // deleted while it was running.
418 *got_deleted_ = true;
421 void ShelfView::Init() {
422 model_->AddObserver(this);
424 const LauncherItems& items(model_->items());
425 for (LauncherItems::const_iterator i = items.begin(); i != items.end(); ++i) {
426 views::View* child = CreateViewForItem(*i);
427 child->SetPaintToLayer(true);
428 view_model_->Add(child, static_cast<int>(i - items.begin()));
431 LauncherStatusChanged();
432 overflow_button_ = new OverflowButton(this);
433 overflow_button_->set_context_menu_controller(this);
434 ConfigureChildView(overflow_button_);
435 AddChildView(overflow_button_);
436 UpdateFirstButtonPadding();
438 // We'll layout when our bounds change.
441 void ShelfView::OnShelfAlignmentChanged() {
442 UpdateFirstButtonPadding();
443 overflow_button_->OnShelfAlignmentChanged();
444 LayoutToIdealBounds();
445 for (int i=0; i < view_model_->view_size(); ++i) {
446 // TODO: remove when AppIcon is a Launcher Button.
447 if (TYPE_APP_LIST == model_->items()[i].type &&
448 !ash::switches::UseAlternateShelfLayout()) {
449 static_cast<AppListButton*>(view_model_->view_at(i))->SetImageAlignment(
450 layout_manager_->SelectValueForShelfAlignment(
451 views::ImageButton::ALIGN_CENTER,
452 views::ImageButton::ALIGN_LEFT,
453 views::ImageButton::ALIGN_RIGHT,
454 views::ImageButton::ALIGN_CENTER),
455 layout_manager_->SelectValueForShelfAlignment(
456 views::ImageButton::ALIGN_TOP,
457 views::ImageButton::ALIGN_MIDDLE,
458 views::ImageButton::ALIGN_MIDDLE,
459 views::ImageButton::ALIGN_BOTTOM));
461 if (i >= first_visible_index_ && i <= last_visible_index_)
462 view_model_->view_at(i)->Layout();
465 if (overflow_bubble_)
466 overflow_bubble_->Hide();
469 void ShelfView::SchedulePaintForAllButtons() {
470 for (int i = 0; i < view_model_->view_size(); ++i) {
471 if (i >= first_visible_index_ && i <= last_visible_index_)
472 view_model_->view_at(i)->SchedulePaint();
474 if (overflow_button_ && overflow_button_->visible())
475 overflow_button_->SchedulePaint();
478 gfx::Rect ShelfView::GetIdealBoundsOfItemIcon(LauncherID id) {
479 int index = model_->ItemIndexByID(id);
480 if (index == -1 || (index > last_visible_index_ &&
481 index < model_->FirstPanelIndex()))
483 const gfx::Rect& ideal_bounds(view_model_->ideal_bounds(index));
484 DCHECK_NE(TYPE_APP_LIST, model_->items()[index].type);
485 LauncherButton* button =
486 static_cast<LauncherButton*>(view_model_->view_at(index));
487 gfx::Rect icon_bounds = button->GetIconBounds();
488 return gfx::Rect(GetMirroredXWithWidthInView(
489 ideal_bounds.x() + icon_bounds.x(), icon_bounds.width()),
490 ideal_bounds.y() + icon_bounds.y(),
492 icon_bounds.height());
495 void ShelfView::UpdatePanelIconPosition(LauncherID id,
496 const gfx::Point& midpoint) {
497 int current_index = model_->ItemIndexByID(id);
498 int first_panel_index = model_->FirstPanelIndex();
499 if (current_index < first_panel_index)
502 gfx::Point midpoint_in_view(GetMirroredXInView(midpoint.x()),
504 int target_index = current_index;
505 while (target_index > first_panel_index &&
506 layout_manager_->PrimaryAxisValue(
507 view_model_->ideal_bounds(target_index).x(),
508 view_model_->ideal_bounds(target_index).y()) >
509 layout_manager_->PrimaryAxisValue(midpoint_in_view.x(),
510 midpoint_in_view.y())) {
513 while (target_index < view_model_->view_size() - 1 &&
514 layout_manager_->PrimaryAxisValue(
515 view_model_->ideal_bounds(target_index).right(),
516 view_model_->ideal_bounds(target_index).bottom()) <
517 layout_manager_->PrimaryAxisValue(midpoint_in_view.x(),
518 midpoint_in_view.y())) {
521 if (current_index != target_index)
522 model_->Move(current_index, target_index);
525 bool ShelfView::IsShowingMenu() const {
526 return (launcher_menu_runner_.get() &&
527 launcher_menu_runner_->IsRunning());
530 bool ShelfView::IsShowingOverflowBubble() const {
531 return overflow_bubble_.get() && overflow_bubble_->IsShowing();
534 views::View* ShelfView::GetAppListButtonView() const {
535 for (int i = 0; i < model_->item_count(); ++i) {
536 if (model_->items()[i].type == TYPE_APP_LIST)
537 return view_model_->view_at(i);
540 NOTREACHED() << "Applist button not found";
544 ////////////////////////////////////////////////////////////////////////////////
545 // ShelfView, FocusTraversable implementation:
547 views::FocusSearch* ShelfView::GetFocusSearch() {
548 return focus_search_.get();
551 views::FocusTraversable* ShelfView::GetFocusTraversableParent() {
552 return parent()->GetFocusTraversable();
555 View* ShelfView::GetFocusTraversableParentView() {
559 void ShelfView::CreateDragIconProxy(
560 const gfx::Point& location_in_screen_coordinates,
561 const gfx::ImageSkia& icon,
562 views::View* replaced_view,
563 const gfx::Vector2d& cursor_offset_from_center,
564 float scale_factor) {
565 drag_replaced_view_ = replaced_view;
566 drag_image_.reset(new ash::internal::DragImageView(
567 drag_replaced_view_->GetWidget()->GetNativeWindow()->GetRootWindow(),
568 ui::DragDropTypes::DRAG_EVENT_SOURCE_MOUSE));
569 drag_image_->SetImage(icon);
570 gfx::Size size = drag_image_->GetPreferredSize();
571 size.set_width(size.width() * scale_factor);
572 size.set_height(size.height() * scale_factor);
573 drag_image_offset_ = gfx::Vector2d(size.width() / 2, size.height() / 2) +
574 cursor_offset_from_center;
575 gfx::Rect drag_image_bounds(
576 GetPositionInScreen(location_in_screen_coordinates,
577 drag_replaced_view_) - drag_image_offset_, size);
578 drag_image_->SetBoundsInScreen(drag_image_bounds);
579 drag_image_->SetWidgetVisible(true);
582 void ShelfView::UpdateDragIconProxy(
583 const gfx::Point& location_in_screen_coordinates) {
584 drag_image_->SetScreenPosition(
585 GetPositionInScreen(location_in_screen_coordinates,
586 drag_replaced_view_) - drag_image_offset_);
589 void ShelfView::DestroyDragIconProxy() {
591 drag_image_offset_ = gfx::Vector2d(0, 0);
594 bool ShelfView::StartDrag(const std::string& app_id,
595 const gfx::Point& location_in_screen_coordinates) {
596 // Bail if an operation is already going on - or the cursor is not inside.
597 // This could happen if mouse / touch operations overlap.
598 if (drag_and_drop_launcher_id_ ||
599 !GetBoundsInScreen().Contains(location_in_screen_coordinates))
602 // If the AppsGridView (which was dispatching this event) was opened by our
603 // button, ShelfView dragging operations are locked and we have to unlock.
605 drag_and_drop_item_pinned_ = false;
606 drag_and_drop_app_id_ = app_id;
607 drag_and_drop_launcher_id_ =
608 delegate_->GetLauncherIDForAppID(drag_and_drop_app_id_);
609 // Check if the application is known and pinned - if not, we have to pin it so
610 // that we can re-arrange the launcher order accordingly. Note that items have
611 // to be pinned to give them the same (order) possibilities as a shortcut.
612 if (!drag_and_drop_launcher_id_ || !delegate_->IsAppPinned(app_id)) {
613 delegate_->PinAppWithID(app_id);
614 drag_and_drop_launcher_id_ =
615 delegate_->GetLauncherIDForAppID(drag_and_drop_app_id_);
616 if (!drag_and_drop_launcher_id_)
618 drag_and_drop_item_pinned_ = true;
620 views::View* drag_and_drop_view = view_model_->view_at(
621 model_->ItemIndexByID(drag_and_drop_launcher_id_));
622 DCHECK(drag_and_drop_view);
624 // Since there is already an icon presented by the caller, we hide this item
625 // for now. That has to be done by reducing the size since the visibility will
626 // change once a regrouping animation is performed.
627 pre_drag_and_drop_size_ = drag_and_drop_view->size();
628 drag_and_drop_view->SetSize(gfx::Size());
630 // First we have to center the mouse cursor over the item.
631 gfx::Point pt = drag_and_drop_view->GetBoundsInScreen().CenterPoint();
632 views::View::ConvertPointFromScreen(drag_and_drop_view, &pt);
633 ui::MouseEvent event(ui::ET_MOUSE_PRESSED,
634 pt, location_in_screen_coordinates, 0);
635 PointerPressedOnButton(
636 drag_and_drop_view, LauncherButtonHost::DRAG_AND_DROP, event);
638 // Drag the item where it really belongs.
639 Drag(location_in_screen_coordinates);
643 bool ShelfView::Drag(const gfx::Point& location_in_screen_coordinates) {
644 if (!drag_and_drop_launcher_id_ ||
645 !GetBoundsInScreen().Contains(location_in_screen_coordinates))
648 gfx::Point pt = location_in_screen_coordinates;
649 views::View* drag_and_drop_view = view_model_->view_at(
650 model_->ItemIndexByID(drag_and_drop_launcher_id_));
651 views::View::ConvertPointFromScreen(drag_and_drop_view, &pt);
653 ui::MouseEvent event(ui::ET_MOUSE_DRAGGED, pt, gfx::Point(), 0);
654 PointerDraggedOnButton(
655 drag_and_drop_view, LauncherButtonHost::DRAG_AND_DROP, event);
659 void ShelfView::EndDrag(bool cancel) {
660 if (!drag_and_drop_launcher_id_)
663 views::View* drag_and_drop_view = view_model_->view_at(
664 model_->ItemIndexByID(drag_and_drop_launcher_id_));
665 PointerReleasedOnButton(
666 drag_and_drop_view, LauncherButtonHost::DRAG_AND_DROP, cancel);
668 // Either destroy the temporarily created item - or - make the item visible.
669 if (drag_and_drop_item_pinned_ && cancel)
670 delegate_->UnpinAppWithID(drag_and_drop_app_id_);
671 else if (drag_and_drop_view) {
673 // When a hosted drag gets canceled, the item can remain in the same slot
674 // and it might have moved within the bounds. In that case the item need
675 // to animate back to its correct location.
676 AnimateToIdealBounds();
678 drag_and_drop_view->SetSize(pre_drag_and_drop_size_);
682 drag_and_drop_launcher_id_ = 0;
685 void ShelfView::LayoutToIdealBounds() {
686 if (bounds_animator_->IsAnimating()) {
687 AnimateToIdealBounds();
691 IdealBounds ideal_bounds;
692 CalculateIdealBounds(&ideal_bounds);
693 views::ViewModelUtils::SetViewBoundsToIdealBounds(*view_model_);
694 overflow_button_->SetBoundsRect(ideal_bounds.overflow_bounds);
697 void ShelfView::CalculateIdealBounds(IdealBounds* bounds) {
698 int available_size = layout_manager_->PrimaryAxisValue(width(), height());
699 DCHECK(model_->item_count() == view_model_->view_size());
703 int first_panel_index = model_->FirstPanelIndex();
704 int last_button_index = first_panel_index - 1;
706 // Initial x,y values account both leading_inset in primary
707 // coordinate and secondary coordinate based on the dynamic edge of the
708 // launcher (eg top edge on bottom-aligned launcher).
709 int inset = ash::switches::UseAlternateShelfLayout() ? 0 : leading_inset_;
710 int x = layout_manager_->SelectValueForShelfAlignment(inset, 0, 0, inset);
711 int y = layout_manager_->SelectValueForShelfAlignment(0, inset, inset, 0);
713 int button_size = ash::switches::UseAlternateShelfLayout() ?
714 kButtonSize : kLauncherPreferredSize;
715 int button_spacing = ash::switches::UseAlternateShelfLayout() ?
716 kAlternateButtonSpacing : kButtonSpacing;
718 int w = layout_manager_->PrimaryAxisValue(button_size, width());
719 int h = layout_manager_->PrimaryAxisValue(height(), button_size);
720 for (int i = 0; i < view_model_->view_size(); ++i) {
721 if (i < first_visible_index_) {
722 view_model_->set_ideal_bounds(i, gfx::Rect(x, y, 0, 0));
726 view_model_->set_ideal_bounds(i, gfx::Rect(x, y, w, h));
727 if (i != last_button_index) {
728 x = layout_manager_->PrimaryAxisValue(x + w + button_spacing, x);
729 y = layout_manager_->PrimaryAxisValue(y, y + h + button_spacing);
733 if (is_overflow_mode()) {
734 // The overflow button is not shown in overflow mode.
735 overflow_button_->SetVisible(false);
736 DCHECK_LT(last_visible_index_, view_model_->view_size());
737 for (int i = 0; i < view_model_->view_size(); ++i) {
738 bool visible = i >= first_visible_index_ &&
739 i <= last_visible_index_;
740 if (!ash::switches::UseAlternateShelfLayout())
741 visible &= i != last_button_index;
742 view_model_->view_at(i)->SetVisible(visible);
747 // To address Fitt's law, we make the first launcher button include the
748 // leading inset (if there is one).
749 if (!ash::switches::UseAlternateShelfLayout()) {
750 if (view_model_->view_size() > 0) {
751 view_model_->set_ideal_bounds(0, gfx::Rect(gfx::Size(
752 layout_manager_->PrimaryAxisValue(inset + w, w),
753 layout_manager_->PrimaryAxisValue(h, inset + h))));
757 // Right aligned icons.
758 int end_position = available_size - button_spacing;
759 x = layout_manager_->PrimaryAxisValue(end_position, 0);
760 y = layout_manager_->PrimaryAxisValue(0, end_position);
761 for (int i = view_model_->view_size() - 1;
762 i >= first_panel_index; --i) {
763 x = layout_manager_->PrimaryAxisValue(x - w - button_spacing, x);
764 y = layout_manager_->PrimaryAxisValue(y, y - h - button_spacing);
765 view_model_->set_ideal_bounds(i, gfx::Rect(x, y, w, h));
766 end_position = layout_manager_->PrimaryAxisValue(x, y);
769 // Icons on the left / top are guaranteed up to kLeftIconProportion of
770 // the available space.
771 int last_icon_position = layout_manager_->PrimaryAxisValue(
772 view_model_->ideal_bounds(last_button_index).right(),
773 view_model_->ideal_bounds(last_button_index).bottom())
774 + button_size + inset;
775 if (!ash::switches::UseAlternateShelfLayout())
776 last_icon_position += button_size;
777 int reserved_icon_space = available_size * kReservedNonPanelIconProportion;
778 if (last_icon_position < reserved_icon_space)
779 end_position = last_icon_position;
781 end_position = std::max(end_position, reserved_icon_space);
783 bounds->overflow_bounds.set_size(
784 gfx::Size(layout_manager_->PrimaryAxisValue(w, width()),
785 layout_manager_->PrimaryAxisValue(height(), h)));
787 if (ash::switches::UseAlternateShelfLayout()) {
788 last_visible_index_ = DetermineLastVisibleIndex(
789 end_position - button_size);
791 last_visible_index_ = DetermineLastVisibleIndex(
792 end_position - inset - 2 * button_size);
794 last_hidden_index_ = DetermineFirstVisiblePanelIndex(end_position) - 1;
796 ((ash::switches::UseAlternateShelfLayout() ? 0 : 1) +
797 last_visible_index_ < last_button_index ||
798 last_hidden_index_ >= first_panel_index);
800 // Create Space for the overflow button
801 if (show_overflow && ash::switches::UseAlternateShelfLayout() &&
802 last_visible_index_ > 0 && last_visible_index_ < last_button_index)
803 --last_visible_index_;
804 for (int i = 0; i < view_model_->view_size(); ++i) {
805 bool visible = i <= last_visible_index_ || i > last_hidden_index_;
806 // Always show the app list.
807 if (!ash::switches::UseAlternateShelfLayout())
808 visible |= (i == last_button_index);
809 // To receive drag event continously from |drag_view_| during the dragging
810 // off from the shelf, don't make |drag_view_| invisible. It will be
811 // eventually invisible and removed from the |view_model_| by
812 // FinalizeRipOffDrag().
813 if (dragged_off_shelf_ && view_model_->view_at(i) == drag_view_)
815 view_model_->view_at(i)->SetVisible(visible);
818 overflow_button_->SetVisible(show_overflow);
820 DCHECK_NE(0, view_model_->view_size());
821 if (last_visible_index_ == -1) {
822 x = layout_manager_->SelectValueForShelfAlignment(inset, 0, 0, inset);
823 y = layout_manager_->SelectValueForShelfAlignment(0, inset, inset, 0);
824 } else if (last_visible_index_ == last_button_index
825 && !ash::switches::UseAlternateShelfLayout()) {
826 x = view_model_->ideal_bounds(last_visible_index_).x();
827 y = view_model_->ideal_bounds(last_visible_index_).y();
829 x = layout_manager_->PrimaryAxisValue(
830 view_model_->ideal_bounds(last_visible_index_).right(),
831 view_model_->ideal_bounds(last_visible_index_).x());
832 y = layout_manager_->PrimaryAxisValue(
833 view_model_->ideal_bounds(last_visible_index_).y(),
834 view_model_->ideal_bounds(last_visible_index_).bottom());
836 // Set all hidden panel icon positions to be on the overflow button.
837 for (int i = first_panel_index; i <= last_hidden_index_; ++i)
838 view_model_->set_ideal_bounds(i, gfx::Rect(x, y, w, h));
840 // Add more space between last visible item and overflow button.
841 // Without this, two buttons look too close compared with other items.
842 if (ash::switches::UseAlternateShelfLayout()) {
843 x = layout_manager_->PrimaryAxisValue(x + button_spacing, x);
844 y = layout_manager_->PrimaryAxisValue(y, y + button_spacing);
847 bounds->overflow_bounds.set_x(x);
848 bounds->overflow_bounds.set_y(y);
849 if (!ash::switches::UseAlternateShelfLayout()) {
850 // Position app list after overflow button.
851 gfx::Rect app_list_bounds = view_model_->ideal_bounds(last_button_index);
853 x = layout_manager_->PrimaryAxisValue(x + w + button_spacing, x);
854 y = layout_manager_->PrimaryAxisValue(y, y + h + button_spacing);
855 app_list_bounds.set_x(x);
856 app_list_bounds.set_y(y);
857 view_model_->set_ideal_bounds(last_button_index, app_list_bounds);
859 if (overflow_bubble_.get() && overflow_bubble_->IsShowing())
860 UpdateOverflowRange(overflow_bubble_->shelf_view());
862 if (overflow_bubble_)
863 overflow_bubble_->Hide();
867 int ShelfView::DetermineLastVisibleIndex(int max_value) const {
868 int index = model_->FirstPanelIndex() - 1;
870 layout_manager_->PrimaryAxisValue(
871 view_model_->ideal_bounds(index).right(),
872 view_model_->ideal_bounds(index).bottom()) > max_value) {
878 int ShelfView::DetermineFirstVisiblePanelIndex(int min_value) const {
879 int index = model_->FirstPanelIndex();
880 while (index < view_model_->view_size() &&
881 layout_manager_->PrimaryAxisValue(
882 view_model_->ideal_bounds(index).right(),
883 view_model_->ideal_bounds(index).bottom()) < min_value) {
889 void ShelfView::AddIconObserver(ShelfIconObserver* observer) {
890 observers_.AddObserver(observer);
893 void ShelfView::RemoveIconObserver(ShelfIconObserver* observer) {
894 observers_.RemoveObserver(observer);
897 void ShelfView::AnimateToIdealBounds() {
898 IdealBounds ideal_bounds;
899 CalculateIdealBounds(&ideal_bounds);
900 for (int i = 0; i < view_model_->view_size(); ++i) {
901 View* view = view_model_->view_at(i);
902 bounds_animator_->AnimateViewTo(view, view_model_->ideal_bounds(i));
903 // Now that the item animation starts, we have to make sure that the
904 // padding of the first gets properly transferred to the new first item.
905 if (i && view->border())
906 view->set_border(NULL);
907 else if (!i && !view->border())
908 UpdateFirstButtonPadding();
910 overflow_button_->SetBoundsRect(ideal_bounds.overflow_bounds);
913 views::View* ShelfView::CreateViewForItem(const LauncherItem& item) {
914 views::View* view = NULL;
916 case TYPE_BROWSER_SHORTCUT:
917 case TYPE_APP_SHORTCUT:
918 case TYPE_WINDOWED_APP:
919 case TYPE_PLATFORM_APP:
920 case TYPE_APP_PANEL: {
921 LauncherButton* button =
922 LauncherButton::Create(this, this, layout_manager_);
923 button->SetImage(item.image);
924 ReflectItemStatus(item, button);
929 case TYPE_APP_LIST: {
930 if (ash::switches::UseAlternateShelfLayout()) {
931 view = new AlternateAppListButton(this,
933 layout_manager_->shelf_widget());
935 // TODO(dave): turn this into a LauncherButton too.
936 AppListButton* button = new AppListButton(this, this);
937 button->SetImageAlignment(
938 layout_manager_->SelectValueForShelfAlignment(
939 views::ImageButton::ALIGN_CENTER,
940 views::ImageButton::ALIGN_LEFT,
941 views::ImageButton::ALIGN_RIGHT,
942 views::ImageButton::ALIGN_CENTER),
943 layout_manager_->SelectValueForShelfAlignment(
944 views::ImageButton::ALIGN_TOP,
945 views::ImageButton::ALIGN_MIDDLE,
946 views::ImageButton::ALIGN_MIDDLE,
947 views::ImageButton::ALIGN_BOTTOM));
956 view->set_context_menu_controller(this);
957 view->set_focus_border(new LauncherButtonFocusBorder);
960 ConfigureChildView(view);
964 void ShelfView::FadeIn(views::View* view) {
965 view->SetVisible(true);
966 view->layer()->SetOpacity(0);
967 AnimateToIdealBounds();
968 bounds_animator_->SetAnimationDelegate(
969 view, new FadeInAnimationDelegate(view), true);
972 void ShelfView::PrepareForDrag(Pointer pointer, const ui::LocatedEvent& event) {
975 drag_pointer_ = pointer;
976 start_drag_index_ = view_model_->GetIndexOfView(drag_view_);
978 if (start_drag_index_== -1) {
983 // If the item is no longer draggable, bail out.
984 LauncherItemDelegate* item_delegate = item_manager_->GetLauncherItemDelegate(
985 model_->items()[start_drag_index_].id);
986 if (!item_delegate->IsDraggable()) {
991 // Move the view to the front so that it appears on top of other views.
992 ReorderChildView(drag_view_, -1);
993 bounds_animator_->StopAnimatingView(drag_view_);
996 void ShelfView::ContinueDrag(const ui::LocatedEvent& event) {
997 // Due to a syncing operation the application might have been removed.
998 // Bail if it is gone.
999 int current_index = view_model_->GetIndexOfView(drag_view_);
1000 DCHECK_NE(-1, current_index);
1002 LauncherItemDelegate* item_delegate = item_manager_->GetLauncherItemDelegate(
1003 model_->items()[current_index].id);
1004 if (!item_delegate->IsDraggable()) {
1009 // If this is not a drag and drop host operation and not the app list item,
1010 // check if the item got ripped off the shelf - if it did we are done.
1011 if (!drag_and_drop_launcher_id_ && ash::switches::UseDragOffShelf() &&
1012 RemovableByRipOff(current_index) != NOT_REMOVABLE) {
1013 if (HandleRipOffDrag(event))
1015 // The rip off handler could have changed the location of the item.
1016 current_index = view_model_->GetIndexOfView(drag_view_);
1019 // TODO: I don't think this works correctly with RTL.
1020 gfx::Point drag_point(event.location());
1021 views::View::ConvertPointToTarget(drag_view_, this, &drag_point);
1023 // Constrain the location to the range of valid indices for the type.
1024 std::pair<int, int> indices(GetDragRange(current_index));
1025 int first_drag_index = indices.first;
1026 int last_drag_index = indices.second;
1027 // If the last index isn't valid, we're overflowing. Constrain to the app list
1028 // (which is the last visible item).
1029 if (first_drag_index < model_->FirstPanelIndex() &&
1030 last_drag_index > last_visible_index_)
1031 last_drag_index = last_visible_index_;
1033 if (layout_manager_->IsHorizontalAlignment()) {
1034 x = std::max(view_model_->ideal_bounds(indices.first).x(),
1035 drag_point.x() - drag_offset_);
1036 x = std::min(view_model_->ideal_bounds(last_drag_index).right() -
1037 view_model_->ideal_bounds(current_index).width(),
1039 if (drag_view_->x() == x)
1041 drag_view_->SetX(x);
1043 y = std::max(view_model_->ideal_bounds(indices.first).y(),
1044 drag_point.y() - drag_offset_);
1045 y = std::min(view_model_->ideal_bounds(last_drag_index).bottom() -
1046 view_model_->ideal_bounds(current_index).height(),
1048 if (drag_view_->y() == y)
1050 drag_view_->SetY(y);
1054 views::ViewModelUtils::DetermineMoveIndex(
1055 *view_model_, drag_view_,
1056 layout_manager_->IsHorizontalAlignment() ?
1057 views::ViewModelUtils::HORIZONTAL :
1058 views::ViewModelUtils::VERTICAL,
1061 std::min(indices.second, std::max(target_index, indices.first));
1062 if (target_index == current_index)
1065 // Change the model, the LauncherItemMoved() callback will handle the
1066 // |view_model_| update.
1067 model_->Move(current_index, target_index);
1068 bounds_animator_->StopAnimatingView(drag_view_);
1071 bool ShelfView::HandleRipOffDrag(const ui::LocatedEvent& event) {
1072 // Determine the distance to the shelf.
1073 int delta = CalculateShelfDistance(event.root_location());
1074 int current_index = view_model_->GetIndexOfView(drag_view_);
1075 DCHECK_NE(-1, current_index);
1076 // To avoid ugly forwards and backwards flipping we use different constants
1077 // for ripping off / re-inserting the items.
1078 if (dragged_off_shelf_) {
1079 // If the re-insertion distance is undercut we insert the item back into
1080 // the shelf. Note that the reinsertion value is slightly smaller then the
1081 // rip off distance to avoid flickering.
1082 if (delta < kReinsertDistance) {
1083 // Destroy our proxy view item.
1084 DestroyDragIconProxy();
1085 // Re-insert the item and return simply false since the caller will handle
1086 // the move as in any normal case.
1087 dragged_off_shelf_ = false;
1088 drag_view_->layer()->SetOpacity(1.0f);
1091 // Move our proxy view item.
1092 UpdateDragIconProxy(event.root_location());
1095 // Check if we are too far away from the shelf to enter the ripped off state.
1096 if (delta > kRipOffDistance) {
1097 // Create a proxy view item which can be moved anywhere.
1098 LauncherButton* button = static_cast<LauncherButton*>(drag_view_);
1099 CreateDragIconProxy(event.root_location(),
1102 gfx::Vector2d(0, 0),
1103 kDragAndDropProxyScale);
1104 drag_view_->layer()->SetOpacity(0.0f);
1105 dragged_off_shelf_ = true;
1106 if (RemovableByRipOff(current_index) == REMOVABLE) {
1107 // Move the item to the front of the first panel item and hide it.
1108 // LauncherItemMoved() callback will handle the |view_model_| update and
1109 // call AnimateToIdealBounds().
1110 model_->Move(current_index, model_->FirstPanelIndex() - 1);
1111 StartFadeInLastVisibleItem();
1112 // Make the item partially disappear to show that it will get removed if
1114 drag_image_->SetOpacity(0.5f);
1121 void ShelfView::FinalizeRipOffDrag(bool cancel) {
1122 if (!dragged_off_shelf_)
1124 // Make sure we do not come in here again.
1125 dragged_off_shelf_ = false;
1127 // Coming here we should always have a |drag_view_|.
1129 int current_index = view_model_->GetIndexOfView(drag_view_);
1130 // If the view isn't part of the model anymore (|current_index| == -1), a sync
1131 // operation must have removed it. In that case we shouldn't change the model
1132 // and only delete the proxy image.
1133 if (current_index == -1) {
1134 DestroyDragIconProxy();
1138 // Set to true when the animation should snap back to where it was before.
1139 bool snap_back = false;
1140 // Items which cannot be dragged off will be handled as a cancel.
1142 // Make sure we do not try to remove un-removable items like items which
1143 // were not pinned or have to be always there.
1144 if (RemovableByRipOff(current_index) != REMOVABLE) {
1148 // Make sure the item stays invisible upon removal.
1149 drag_view_->SetVisible(false);
1150 std::string app_id =
1151 delegate_->GetAppIDForLauncherID(model_->items()[current_index].id);
1152 delegate_->UnpinAppWithID(app_id);
1155 if (cancel || snap_back) {
1156 if (!cancelling_drag_model_changed_) {
1157 // Only do something if the change did not come through a model change.
1158 gfx::Rect drag_bounds = drag_image_->GetBoundsInScreen();
1159 gfx::Point relative_to = GetBoundsInScreen().origin();
1161 gfx::PointAtOffsetFromOrigin(drag_bounds.origin()- relative_to),
1162 drag_bounds.size());
1163 drag_view_->SetBoundsRect(target);
1164 // Hide the status from the active item since we snap it back now. Upon
1165 // animation end the flag gets cleared if |snap_back_from_rip_off_view_|
1167 snap_back_from_rip_off_view_ = drag_view_;
1168 LauncherButton* button = static_cast<LauncherButton*>(drag_view_);
1169 button->AddState(LauncherButton::STATE_HIDDEN);
1170 // When a canceling drag model is happening, the view model is diverged
1171 // from the menu model and movements / animations should not be done.
1172 model_->Move(current_index, start_drag_index_);
1173 AnimateToIdealBounds();
1175 drag_view_->layer()->SetOpacity(1.0f);
1177 DestroyDragIconProxy();
1180 ShelfView::RemovableState ShelfView::RemovableByRipOff(int index) {
1181 LauncherItemType type = model_->items()[index].type;
1182 if (type == TYPE_APP_LIST || !delegate_->CanPin())
1183 return NOT_REMOVABLE;
1184 std::string app_id =
1185 delegate_->GetAppIDForLauncherID(model_->items()[index].id);
1186 // Note: Only pinned app shortcuts can be removed!
1187 return (type == TYPE_APP_SHORTCUT && delegate_->IsAppPinned(app_id)) ?
1188 REMOVABLE : DRAGGABLE;
1191 bool ShelfView::SameDragType(LauncherItemType typea,
1192 LauncherItemType typeb) const {
1194 case TYPE_APP_SHORTCUT:
1195 case TYPE_BROWSER_SHORTCUT:
1196 return (typeb == TYPE_APP_SHORTCUT || typeb == TYPE_BROWSER_SHORTCUT);
1198 case TYPE_PLATFORM_APP:
1199 case TYPE_WINDOWED_APP:
1200 case TYPE_APP_PANEL:
1201 return typeb == typea;
1202 case TYPE_UNDEFINED:
1203 NOTREACHED() << "LauncherItemType must be set.";
1210 std::pair<int, int> ShelfView::GetDragRange(int index) {
1213 LauncherItemType type = model_->items()[index].type;
1214 for (int i = 0; i < model_->item_count(); ++i) {
1215 if (SameDragType(model_->items()[i].type, type)) {
1216 if (min_index == -1)
1221 return std::pair<int, int>(min_index, max_index);
1224 void ShelfView::ConfigureChildView(views::View* view) {
1225 view->SetPaintToLayer(true);
1226 view->layer()->SetFillsBoundsOpaquely(false);
1229 void ShelfView::ToggleOverflowBubble() {
1230 if (IsShowingOverflowBubble()) {
1231 overflow_bubble_->Hide();
1235 if (!overflow_bubble_)
1236 overflow_bubble_.reset(new OverflowBubble());
1238 ShelfView* overflow_view =
1239 new ShelfView(model_, delegate_, layout_manager_);
1240 overflow_view->Init();
1241 overflow_view->set_owner_overflow_bubble(overflow_bubble_.get());
1242 overflow_view->OnShelfAlignmentChanged();
1243 UpdateOverflowRange(overflow_view);
1245 overflow_bubble_->Show(overflow_button_, overflow_view);
1247 Shell::GetInstance()->UpdateShelfVisibility();
1250 void ShelfView::UpdateFirstButtonPadding() {
1251 if (ash::switches::UseAlternateShelfLayout())
1254 // Creates an empty border for first launcher button to make included leading
1255 // inset act as the button's padding. This is only needed on button creation
1256 // and when shelf alignment changes.
1257 if (view_model_->view_size() > 0) {
1258 view_model_->view_at(0)->set_border(views::Border::CreateEmptyBorder(
1259 layout_manager_->PrimaryAxisValue(0, leading_inset_),
1260 layout_manager_->PrimaryAxisValue(leading_inset_, 0),
1266 void ShelfView::OnFadeOutAnimationEnded() {
1267 AnimateToIdealBounds();
1268 StartFadeInLastVisibleItem();
1271 void ShelfView::StartFadeInLastVisibleItem() {
1272 // If overflow button is visible and there is a valid new last item, fading
1273 // the new last item in after sliding animation is finished.
1274 if (overflow_button_->visible() && last_visible_index_ >= 0) {
1275 views::View* last_visible_view = view_model_->view_at(last_visible_index_);
1276 last_visible_view->layer()->SetOpacity(0);
1277 bounds_animator_->SetAnimationDelegate(
1279 new ShelfView::StartFadeAnimationDelegate(this, last_visible_view),
1284 void ShelfView::UpdateOverflowRange(ShelfView* overflow_view) {
1285 const int first_overflow_index = last_visible_index_ + 1;
1286 const int last_overflow_index = last_hidden_index_;
1287 DCHECK_LE(first_overflow_index, last_overflow_index);
1288 DCHECK_LT(last_overflow_index, view_model_->view_size());
1290 overflow_view->first_visible_index_ = first_overflow_index;
1291 overflow_view->last_visible_index_ = last_overflow_index;
1294 bool ShelfView::ShouldHideTooltip(const gfx::Point& cursor_location) {
1295 gfx::Rect active_bounds;
1297 for (int i = 0; i < child_count(); ++i) {
1298 views::View* child = child_at(i);
1299 if (child == overflow_button_)
1301 if (!ShouldShowTooltipForView(child))
1304 gfx::Rect child_bounds = child->GetMirroredBounds();
1305 active_bounds.Union(child_bounds);
1308 return !active_bounds.Contains(cursor_location);
1311 gfx::Rect ShelfView::GetVisibleItemsBoundsInScreen() {
1312 gfx::Size preferred_size = GetPreferredSize();
1313 gfx::Point origin(GetMirroredXWithWidthInView(0, preferred_size.width()), 0);
1314 ConvertPointToScreen(this, &origin);
1315 return gfx::Rect(origin, preferred_size);
1318 int ShelfView::CancelDrag(int modified_index) {
1319 FinalizeRipOffDrag(true);
1321 return modified_index;
1322 bool was_dragging = dragging();
1323 int drag_view_index = view_model_->GetIndexOfView(drag_view_);
1324 drag_pointer_ = NONE;
1326 if (drag_view_index == modified_index) {
1327 // The view that was being dragged is being modified. Don't do anything.
1328 return modified_index;
1331 return modified_index;
1333 // Restore previous position, tracking the position of the modified view.
1334 bool at_end = modified_index == view_model_->view_size();
1335 views::View* modified_view =
1336 (modified_index >= 0 && !at_end) ?
1337 view_model_->view_at(modified_index) : NULL;
1338 model_->Move(drag_view_index, start_drag_index_);
1340 // If the modified view will be at the end of the list, return the new end of
1343 return view_model_->view_size();
1344 return modified_view ? view_model_->GetIndexOfView(modified_view) : -1;
1347 gfx::Size ShelfView::GetPreferredSize() {
1348 IdealBounds ideal_bounds;
1349 CalculateIdealBounds(&ideal_bounds);
1351 const int preferred_size = layout_manager_->GetPreferredShelfSize();
1353 const int app_list_index = view_model_->view_size() - 1;
1354 const int last_button_index = is_overflow_mode() ?
1355 last_visible_index_ : app_list_index;
1356 const gfx::Rect last_button_bounds =
1357 last_button_index >= first_visible_index_ ?
1358 view_model_->view_at(last_button_index)->bounds() :
1359 gfx::Rect(gfx::Size(preferred_size, preferred_size));
1361 if (layout_manager_->IsHorizontalAlignment()) {
1362 return gfx::Size(last_button_bounds.right() + leading_inset_,
1366 return gfx::Size(preferred_size,
1367 last_button_bounds.bottom() + leading_inset_);
1370 void ShelfView::OnBoundsChanged(const gfx::Rect& previous_bounds) {
1371 LayoutToIdealBounds();
1372 FOR_EACH_OBSERVER(ShelfIconObserver, observers_,
1373 OnShelfIconPositionsChanged());
1375 if (IsShowingOverflowBubble())
1376 overflow_bubble_->Hide();
1379 views::FocusTraversable* ShelfView::GetPaneFocusTraversable() {
1383 void ShelfView::GetAccessibleState(ui::AccessibleViewState* state) {
1384 state->role = ui::AccessibilityTypes::ROLE_TOOLBAR;
1385 state->name = l10n_util::GetStringUTF16(IDS_ASH_SHELF_ACCESSIBLE_NAME);
1388 void ShelfView::OnGestureEvent(ui::GestureEvent* event) {
1389 if (gesture_handler_.ProcessGestureEvent(*event))
1390 event->StopPropagation();
1393 void ShelfView::LauncherItemAdded(int model_index) {
1395 base::AutoReset<bool> cancelling_drag(
1396 &cancelling_drag_model_changed_, true);
1397 model_index = CancelDrag(model_index);
1399 views::View* view = CreateViewForItem(model_->items()[model_index]);
1401 // Hide the view, it'll be made visible when the animation is done. Using
1402 // opacity 0 here to avoid messing with CalculateIdealBounds which touches
1403 // the view's visibility.
1404 view->layer()->SetOpacity(0);
1405 view_model_->Add(view, model_index);
1407 // Give the button its ideal bounds. That way if we end up animating the
1408 // button before this animation completes it doesn't appear at some random
1409 // spot (because it was in the middle of animating from 0,0 0x0 to its
1411 IdealBounds ideal_bounds;
1412 CalculateIdealBounds(&ideal_bounds);
1413 view->SetBoundsRect(view_model_->ideal_bounds(model_index));
1415 // The first animation moves all the views to their target position. |view|
1416 // is hidden, so it visually appears as though we are providing space for
1417 // it. When done we'll fade the view in.
1418 AnimateToIdealBounds();
1419 if (model_index <= last_visible_index_ ||
1420 model_index >= model_->FirstPanelIndex()) {
1421 bounds_animator_->SetAnimationDelegate(
1422 view, new StartFadeAnimationDelegate(this, view), true);
1424 // Undo the hiding if animation does not run.
1425 view->layer()->SetOpacity(1.0f);
1429 void ShelfView::LauncherItemRemoved(int model_index, LauncherID id) {
1430 if (id == context_menu_id_)
1431 launcher_menu_runner_.reset();
1433 base::AutoReset<bool> cancelling_drag(
1434 &cancelling_drag_model_changed_, true);
1435 model_index = CancelDrag(model_index);
1437 views::View* view = view_model_->view_at(model_index);
1438 view_model_->Remove(model_index);
1440 // When the overflow bubble is visible, the overflow range needs to be set
1441 // before CalculateIdealBounds() gets called. Otherwise CalculateIdealBounds()
1442 // could trigger a LauncherItemChanged() by hiding the overflow bubble and
1443 // since the overflow bubble is not yet synced with the LauncherModel this
1444 // could cause a crash.
1445 if (overflow_bubble_ && overflow_bubble_->IsShowing()) {
1446 last_hidden_index_ = std::min(last_hidden_index_,
1447 view_model_->view_size() - 1);
1448 UpdateOverflowRange(overflow_bubble_->shelf_view());
1451 if (view->visible()) {
1452 // The first animation fades out the view. When done we'll animate the rest
1453 // of the views to their target location.
1454 bounds_animator_->AnimateViewTo(view, view->bounds());
1455 bounds_animator_->SetAnimationDelegate(
1456 view, new FadeOutAnimationDelegate(this, view), true);
1458 // We don't need to show a fade out animation for invisible |view|. When an
1459 // item is ripped out from the shelf, its |view| is already invisible.
1460 AnimateToIdealBounds();
1463 // Close the tooltip because it isn't needed any longer and its anchor view
1464 // will be deleted soon.
1465 if (tooltip_->GetCurrentAnchorView() == view)
1469 void ShelfView::LauncherItemChanged(int model_index,
1470 const ash::LauncherItem& old_item) {
1471 const LauncherItem& item(model_->items()[model_index]);
1472 if (old_item.type != item.type) {
1473 // Type changed, swap the views.
1474 model_index = CancelDrag(model_index);
1475 scoped_ptr<views::View> old_view(view_model_->view_at(model_index));
1476 bounds_animator_->StopAnimatingView(old_view.get());
1477 // Removing and re-inserting a view in our view model will strip the ideal
1478 // bounds from the item. To avoid recalculation of everything the bounds
1479 // get remembered and restored after the insertion to the previous value.
1480 gfx::Rect old_ideal_bounds = view_model_->ideal_bounds(model_index);
1481 view_model_->Remove(model_index);
1482 views::View* new_view = CreateViewForItem(item);
1483 AddChildView(new_view);
1484 view_model_->Add(new_view, model_index);
1485 view_model_->set_ideal_bounds(model_index, old_ideal_bounds);
1486 new_view->SetBoundsRect(old_view->bounds());
1490 views::View* view = view_model_->view_at(model_index);
1491 switch (item.type) {
1492 case TYPE_BROWSER_SHORTCUT:
1493 // Fallthrough for the new Launcher since it needs to show the activation
1495 case TYPE_APP_SHORTCUT:
1496 case TYPE_WINDOWED_APP:
1497 case TYPE_PLATFORM_APP:
1498 case TYPE_APP_PANEL: {
1499 LauncherButton* button = static_cast<LauncherButton*>(view);
1500 ReflectItemStatus(item, button);
1501 // The browser shortcut is currently not a "real" item and as such the
1502 // the image is bogous as well. We therefore keep the image as is for it.
1503 if (item.type != TYPE_BROWSER_SHORTCUT)
1504 button->SetImage(item.image);
1505 button->SchedulePaint();
1514 void ShelfView::LauncherItemMoved(int start_index, int target_index) {
1515 view_model_->Move(start_index, target_index);
1516 // When cancelling a drag due to a launcher item being added, the currently
1517 // dragged item is moved back to its initial position. AnimateToIdealBounds
1518 // will be called again when the new item is added to the |view_model_| but
1519 // at this time the |view_model_| is inconsistent with the |model_|.
1520 if (!cancelling_drag_model_changed_)
1521 AnimateToIdealBounds();
1524 void ShelfView::LauncherStatusChanged() {
1525 if (ash::switches::UseAlternateShelfLayout())
1527 AppListButton* app_list_button =
1528 static_cast<AppListButton*>(GetAppListButtonView());
1529 if (model_->status() == LauncherModel::STATUS_LOADING)
1530 app_list_button->StartLoadingAnimation();
1532 app_list_button->StopLoadingAnimation();
1535 void ShelfView::PointerPressedOnButton(views::View* view,
1537 const ui::LocatedEvent& event) {
1541 int index = view_model_->GetIndexOfView(view);
1545 LauncherItemDelegate* item_delegate = item_manager_->GetLauncherItemDelegate(
1546 model_->items()[index].id);
1547 if (view_model_->view_size() <= 1 || !item_delegate->IsDraggable())
1548 return; // View is being deleted or not draggable, ignore request.
1551 drag_offset_ = layout_manager_->PrimaryAxisValue(event.x(), event.y());
1554 void ShelfView::PointerDraggedOnButton(views::View* view,
1556 const ui::LocatedEvent& event) {
1557 // To prepare all drag types (moving an item in the shelf and dragging off),
1558 // we should check the x-axis and y-axis offset.
1559 if (!dragging() && drag_view_ &&
1560 ((abs(event.x() - drag_offset_) >= kMinimumDragDistance) ||
1561 (abs(event.y() - drag_offset_) >= kMinimumDragDistance))) {
1562 PrepareForDrag(pointer, event);
1564 if (drag_pointer_ == pointer)
1565 ContinueDrag(event);
1568 void ShelfView::PointerReleasedOnButton(views::View* view,
1573 } else if (drag_pointer_ == pointer) {
1574 FinalizeRipOffDrag(false);
1575 drag_pointer_ = NONE;
1576 AnimateToIdealBounds();
1578 // If the drag pointer is NONE, no drag operation is going on and the
1579 // drag_view can be released.
1580 if (drag_pointer_ == NONE)
1584 void ShelfView::MouseMovedOverButton(views::View* view) {
1585 if (!ShouldShowTooltipForView(view))
1588 if (!tooltip_->IsVisible())
1589 tooltip_->ResetTimer();
1592 void ShelfView::MouseEnteredButton(views::View* view) {
1593 if (!ShouldShowTooltipForView(view))
1596 if (tooltip_->IsVisible()) {
1597 tooltip_->ShowImmediately(view, GetAccessibleName(view));
1599 tooltip_->ShowDelayed(view, GetAccessibleName(view));
1603 void ShelfView::MouseExitedButton(views::View* view) {
1604 if (!tooltip_->IsVisible())
1605 tooltip_->StopTimer();
1608 base::string16 ShelfView::GetAccessibleName(const views::View* view) {
1609 int view_index = view_model_->GetIndexOfView(view);
1610 // May be -1 while in the process of animating closed.
1611 if (view_index == -1)
1612 return base::string16();
1614 LauncherItemDelegate* item_delegate = item_manager_->GetLauncherItemDelegate(
1615 model_->items()[view_index].id);
1616 return item_delegate->GetTitle();
1619 void ShelfView::ButtonPressed(views::Button* sender, const ui::Event& event) {
1620 // Do not handle mouse release during drag.
1624 if (sender == overflow_button_) {
1625 ToggleOverflowBubble();
1629 int view_index = view_model_->GetIndexOfView(sender);
1630 // May be -1 while in the process of animating closed.
1631 if (view_index == -1)
1634 // If the previous menu was closed by the same event as this one, we ignore
1636 if (!IsUsableEvent(event))
1640 ScopedTargetRootWindow scoped_target(
1641 sender->GetWidget()->GetNativeView()->GetRootWindow());
1642 // Slow down activation animations if shift key is pressed.
1643 scoped_ptr<ui::ScopedAnimationDurationScaleMode> slowing_animations;
1644 if (event.IsShiftDown()) {
1645 slowing_animations.reset(new ui::ScopedAnimationDurationScaleMode(
1646 ui::ScopedAnimationDurationScaleMode::SLOW_DURATION));
1649 // Collect usage statistics before we decide what to do with the click.
1650 switch (model_->items()[view_index].type) {
1651 case TYPE_APP_SHORTCUT:
1652 case TYPE_WINDOWED_APP:
1653 case TYPE_PLATFORM_APP:
1654 case TYPE_BROWSER_SHORTCUT:
1655 Shell::GetInstance()->delegate()->RecordUserMetricsAction(
1656 UMA_LAUNCHER_CLICK_ON_APP);
1660 Shell::GetInstance()->delegate()->RecordUserMetricsAction(
1661 UMA_LAUNCHER_CLICK_ON_APPLIST_BUTTON);
1664 case TYPE_APP_PANEL:
1667 case TYPE_UNDEFINED:
1668 NOTREACHED() << "LauncherItemType must be set.";
1672 LauncherItemDelegate* item_delegate =
1673 item_manager_->GetLauncherItemDelegate(
1674 model_->items()[view_index].id);
1675 item_delegate->ItemSelected(event);
1677 ShowListMenuForView(model_->items()[view_index], sender, event);
1681 bool ShelfView::ShowListMenuForView(const LauncherItem& item,
1682 views::View* source,
1683 const ui::Event& event) {
1684 scoped_ptr<ash::LauncherMenuModel> menu_model;
1685 LauncherItemDelegate* item_delegate =
1686 item_manager_->GetLauncherItemDelegate(item.id);
1687 menu_model.reset(item_delegate->CreateApplicationMenu(event.flags()));
1689 // Make sure we have a menu and it has at least two items in addition to the
1690 // application title and the 3 spacing separators.
1691 if (!menu_model.get() || menu_model->GetItemCount() <= 5)
1694 ShowMenu(scoped_ptr<views::MenuModelAdapter>(
1695 new LauncherMenuModelAdapter(menu_model.get())),
1699 ui::GetMenuSourceTypeForEvent(event));
1703 void ShelfView::ShowContextMenuForView(views::View* source,
1704 const gfx::Point& point,
1705 ui::MenuSourceType source_type) {
1706 int view_index = view_model_->GetIndexOfView(source);
1707 // TODO(simon.hong81): Create LauncherContextMenu for applist in its
1708 // LauncherItemDelegate.
1709 if (view_index != -1 &&
1710 model_->items()[view_index].type == TYPE_APP_LIST) {
1714 if (view_index == -1) {
1715 Shell::GetInstance()->ShowContextMenu(point, source_type);
1718 scoped_ptr<ui::MenuModel> menu_model;
1719 LauncherItemDelegate* item_delegate = item_manager_->GetLauncherItemDelegate(
1720 model_->items()[view_index].id);
1721 menu_model.reset(item_delegate->CreateContextMenu(
1722 source->GetWidget()->GetNativeView()->GetRootWindow()));
1726 base::AutoReset<LauncherID> reseter(
1728 view_index == -1 ? 0 : model_->items()[view_index].id);
1730 ShowMenu(scoped_ptr<views::MenuModelAdapter>(
1731 new views::MenuModelAdapter(menu_model.get())),
1738 void ShelfView::ShowMenu(scoped_ptr<views::MenuModelAdapter> menu_model_adapter,
1739 views::View* source,
1740 const gfx::Point& click_point,
1742 ui::MenuSourceType source_type) {
1743 closing_event_time_ = base::TimeDelta();
1744 launcher_menu_runner_.reset(
1745 new views::MenuRunner(menu_model_adapter->CreateMenu()));
1747 ScopedTargetRootWindow scoped_target(
1748 source->GetWidget()->GetNativeView()->GetRootWindow());
1750 // Determine the menu alignment dependent on the shelf.
1751 views::MenuItemView::AnchorPosition menu_alignment =
1752 views::MenuItemView::TOPLEFT;
1753 gfx::Rect anchor_point = gfx::Rect(click_point, gfx::Size());
1755 ShelfWidget* shelf = RootWindowController::ForLauncher(
1756 GetWidget()->GetNativeView())->shelf();
1757 if (!context_menu) {
1758 // Application lists use a bubble.
1759 ash::ShelfAlignment align = shelf->GetAlignment();
1760 anchor_point = source->GetBoundsInScreen();
1762 // It is possible to invoke the menu while it is sliding into view. To cover
1763 // that case, the screen coordinates are offsetted by the animation delta.
1764 gfx::Vector2d offset =
1765 source->GetWidget()->GetNativeWindow()->bounds().origin() -
1766 source->GetWidget()->GetNativeWindow()->GetTargetBounds().origin();
1767 anchor_point.set_x(anchor_point.x() - offset.x());
1768 anchor_point.set_y(anchor_point.y() - offset.y());
1770 // Launcher items can have an asymmetrical border for spacing reasons.
1771 // Adjust anchor location for this.
1772 if (source->border())
1773 anchor_point.Inset(source->border()->GetInsets());
1776 case ash::SHELF_ALIGNMENT_BOTTOM:
1777 menu_alignment = views::MenuItemView::BUBBLE_ABOVE;
1779 case ash::SHELF_ALIGNMENT_LEFT:
1780 menu_alignment = views::MenuItemView::BUBBLE_RIGHT;
1782 case ash::SHELF_ALIGNMENT_RIGHT:
1783 menu_alignment = views::MenuItemView::BUBBLE_LEFT;
1785 case ash::SHELF_ALIGNMENT_TOP:
1786 menu_alignment = views::MenuItemView::BUBBLE_BELOW;
1790 // If this gets deleted while we are in the menu, the launcher will be gone
1792 bool got_deleted = false;
1793 got_deleted_ = &got_deleted;
1795 shelf->ForceUndimming(true);
1796 // NOTE: if you convert to HAS_MNEMONICS be sure and update menu building
1798 if (launcher_menu_runner_->RunMenuAt(
1799 source->GetWidget(),
1804 context_menu ? views::MenuRunner::CONTEXT_MENU : 0) ==
1805 views::MenuRunner::MENU_DELETED) {
1807 got_deleted_ = NULL;
1808 shelf->ForceUndimming(false);
1812 got_deleted_ = NULL;
1813 shelf->ForceUndimming(false);
1815 // If it is a context menu and we are showing overflow bubble
1816 // we want to hide overflow bubble.
1817 if (owner_overflow_bubble_)
1818 owner_overflow_bubble_->HideBubbleAndRefreshButton();
1820 // Unpinning an item will reset the |launcher_menu_runner_| before coming
1822 if (launcher_menu_runner_)
1823 closing_event_time_ = launcher_menu_runner_->closing_event_time();
1824 Shell::GetInstance()->UpdateShelfVisibility();
1827 void ShelfView::OnBoundsAnimatorProgressed(views::BoundsAnimator* animator) {
1828 FOR_EACH_OBSERVER(ShelfIconObserver, observers_,
1829 OnShelfIconPositionsChanged());
1830 PreferredSizeChanged();
1833 void ShelfView::OnBoundsAnimatorDone(views::BoundsAnimator* animator) {
1834 if (snap_back_from_rip_off_view_ && animator == bounds_animator_) {
1835 if (!animator->IsAnimating(snap_back_from_rip_off_view_)) {
1836 // Coming here the animation of the LauncherButton is finished and the
1837 // previously hidden status can be shown again. Since the button itself
1838 // might have gone away or changed locations we check that the button
1839 // is still in the shelf and show its status again.
1840 for (int index = 0; index < view_model_->view_size(); index++) {
1841 views::View* view = view_model_->view_at(index);
1842 if (view == snap_back_from_rip_off_view_) {
1843 LauncherButton* button = static_cast<LauncherButton*>(view);
1844 button->ClearState(LauncherButton::STATE_HIDDEN);
1848 snap_back_from_rip_off_view_ = NULL;
1853 bool ShelfView::IsUsableEvent(const ui::Event& event) {
1854 if (closing_event_time_ == base::TimeDelta())
1857 base::TimeDelta delta =
1858 base::TimeDelta(event.time_stamp() - closing_event_time_);
1859 closing_event_time_ = base::TimeDelta();
1860 // TODO(skuhne): This time seems excessive, but it appears that the reposting
1861 // takes that long. Need to come up with a better way of doing this.
1862 return (delta.InMilliseconds() < 0 || delta.InMilliseconds() > 130);
1865 const LauncherItem* ShelfView::LauncherItemForView(
1866 const views::View* view) const {
1867 int view_index = view_model_->GetIndexOfView(view);
1868 if (view_index == -1)
1870 return &(model_->items()[view_index]);
1873 bool ShelfView::ShouldShowTooltipForView(const views::View* view) const {
1874 if (view == GetAppListButtonView() &&
1875 Shell::GetInstance()->GetAppListWindow())
1877 const LauncherItem* item = LauncherItemForView(view);
1880 LauncherItemDelegate* item_delegate =
1881 item_manager_->GetLauncherItemDelegate(item->id);
1882 return item_delegate->ShouldShowTooltip();
1885 int ShelfView::CalculateShelfDistance(const gfx::Point& coordinate) const {
1886 ShelfWidget* shelf = RootWindowController::ForLauncher(
1887 GetWidget()->GetNativeView())->shelf();
1888 ash::ShelfAlignment align = shelf->GetAlignment();
1889 const gfx::Rect bounds = GetBoundsInScreen();
1892 case ash::SHELF_ALIGNMENT_BOTTOM:
1893 distance = bounds.y() - coordinate.y();
1895 case ash::SHELF_ALIGNMENT_LEFT:
1896 distance = coordinate.x() - bounds.right();
1898 case ash::SHELF_ALIGNMENT_RIGHT:
1899 distance = bounds.x() - coordinate.x();
1901 case ash::SHELF_ALIGNMENT_TOP:
1902 distance = coordinate.y() - bounds.bottom();
1905 return distance > 0 ? distance : 0;
1908 } // namespace internal