Adding shelf animation for maximized state (fading like on android)
[chromium-blink-merge.git] / ash / launcher / launcher_view.cc
blob164c99ceca8743057d8f3baabb51f88c8499f050
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/launcher/launcher_view.h"
7 #include <algorithm>
9 #include "ash/ash_constants.h"
10 #include "ash/ash_switches.h"
11 #include "ash/launcher/app_list_button.h"
12 #include "ash/launcher/launcher_button.h"
13 #include "ash/launcher/launcher_delegate.h"
14 #include "ash/launcher/launcher_icon_observer.h"
15 #include "ash/launcher/launcher_model.h"
16 #include "ash/launcher/launcher_tooltip_manager.h"
17 #include "ash/launcher/overflow_bubble.h"
18 #include "ash/launcher/overflow_button.h"
19 #include "ash/launcher/tabbed_launcher_button.h"
20 #include "ash/root_window_controller.h"
21 #include "ash/shelf/shelf_layout_manager.h"
22 #include "ash/shelf/shelf_widget.h"
23 #include "ash/shell_delegate.h"
24 #include "base/auto_reset.h"
25 #include "base/memory/scoped_ptr.h"
26 #include "grit/ash_resources.h"
27 #include "grit/ash_strings.h"
28 #include "ui/aura/window.h"
29 #include "ui/base/l10n/l10n_util.h"
30 #include "ui/base/models/simple_menu_model.h"
31 #include "ui/base/resource/resource_bundle.h"
32 #include "ui/compositor/layer.h"
33 #include "ui/compositor/layer_animator.h"
34 #include "ui/compositor/scoped_animation_duration_scale_mode.h"
35 #include "ui/gfx/canvas.h"
36 #include "ui/views/animation/bounds_animator.h"
37 #include "ui/views/border.h"
38 #include "ui/views/controls/button/image_button.h"
39 #include "ui/views/controls/menu/menu_model_adapter.h"
40 #include "ui/views/controls/menu/menu_runner.h"
41 #include "ui/views/focus/focus_search.h"
42 #include "ui/views/focus_border.h"
43 #include "ui/views/view_model.h"
44 #include "ui/views/view_model_utils.h"
45 #include "ui/views/widget/widget.h"
47 using ui::Animation;
48 using views::View;
50 namespace ash {
51 namespace internal {
53 // Default amount content is inset on the left edge.
54 const int kDefaultLeadingInset = 8;
56 // Minimum distance before drag starts.
57 const int kMinimumDragDistance = 8;
59 // Size between the buttons.
60 const int kButtonSpacing = 4;
62 // Additional spacing for the left and right side of icons.
63 const int kHorizontalIconSpacing = 2;
65 // Inset for items which do not have an icon.
66 const int kHorizontalNoIconInsetSpacing =
67 kHorizontalIconSpacing + kDefaultLeadingInset;
69 // The proportion of the launcher space reserved for non-panel icons. Panels
70 // may flow into this space but will be put into the overflow bubble if there
71 // is contention for the space.
72 const float kReservedNonPanelIconProportion = 0.67f;
74 // This is the command id of the menu item which contains the name of the menu.
75 const int kCommandIdOfMenuName = 0;
77 // The background color of the active item in the list.
78 const SkColor kActiveListItemBackgroundColor = SkColorSetRGB(203 , 219, 241);
80 // The background color of the active & hovered item in the list.
81 const SkColor kFocusedActiveListItemBackgroundColor =
82 SkColorSetRGB(193, 211, 236);
84 // The text color of the caption item in a list.
85 const SkColor kCaptionItemForegroundColor = SK_ColorBLACK;
87 // The maximum allowable length of a menu line of an application menu in pixels.
88 const int kMaximumAppMenuItemLength = 350;
90 namespace {
92 // The MenuModelAdapter gets slightly changed to adapt the menu appearance to
93 // our requirements.
94 class LauncherMenuModelAdapter
95 : public views::MenuModelAdapter {
96 public:
97 explicit LauncherMenuModelAdapter(ash::LauncherMenuModel* menu_model);
99 // Overriding MenuModelAdapter's MenuDelegate implementation.
100 virtual const gfx::Font* GetLabelFont(int command_id) const OVERRIDE;
101 virtual bool IsCommandEnabled(int id) const OVERRIDE;
102 virtual void GetHorizontalIconMargins(int id,
103 int icon_size,
104 int* left_margin,
105 int* right_margin) const OVERRIDE;
106 virtual bool GetForegroundColor(int command_id,
107 bool is_hovered,
108 SkColor* override_color) const OVERRIDE;
109 virtual bool GetBackgroundColor(int command_id,
110 bool is_hovered,
111 SkColor* override_color) const OVERRIDE;
112 virtual int GetMaxWidthForMenu(views::MenuItemView* menu) OVERRIDE;
113 virtual bool ShouldReserveSpaceForSubmenuIndicator() const OVERRIDE;
115 private:
116 ash::LauncherMenuModel* launcher_menu_model_;
118 DISALLOW_COPY_AND_ASSIGN(LauncherMenuModelAdapter);
122 LauncherMenuModelAdapter::LauncherMenuModelAdapter(
123 ash::LauncherMenuModel* menu_model)
124 : MenuModelAdapter(menu_model),
125 launcher_menu_model_(menu_model) {}
127 const gfx::Font* LauncherMenuModelAdapter::GetLabelFont(
128 int command_id) const {
129 if (command_id != kCommandIdOfMenuName)
130 return MenuModelAdapter::GetLabelFont(command_id);
132 ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
133 return &rb.GetFont(ui::ResourceBundle::BoldFont);
136 bool LauncherMenuModelAdapter::IsCommandEnabled(int id) const {
137 return id != kCommandIdOfMenuName;
140 bool LauncherMenuModelAdapter::GetForegroundColor(
141 int command_id,
142 bool is_hovered,
143 SkColor* override_color) const {
144 if (command_id != kCommandIdOfMenuName)
145 return false;
147 *override_color = kCaptionItemForegroundColor;
148 return true;
151 bool LauncherMenuModelAdapter::GetBackgroundColor(
152 int command_id,
153 bool is_hovered,
154 SkColor* override_color) const {
155 if (!launcher_menu_model_->IsCommandActive(command_id))
156 return false;
158 *override_color = is_hovered ? kFocusedActiveListItemBackgroundColor :
159 kActiveListItemBackgroundColor;
160 return true;
163 void LauncherMenuModelAdapter::GetHorizontalIconMargins(
164 int command_id,
165 int icon_size,
166 int* left_margin,
167 int* right_margin) const {
168 *left_margin = kHorizontalIconSpacing;
169 *right_margin = (command_id != kCommandIdOfMenuName) ?
170 kHorizontalIconSpacing : -(icon_size + kHorizontalNoIconInsetSpacing);
173 int LauncherMenuModelAdapter::GetMaxWidthForMenu(views::MenuItemView* menu) {
174 return kMaximumAppMenuItemLength;
177 bool LauncherMenuModelAdapter::ShouldReserveSpaceForSubmenuIndicator() const {
178 return false;
181 // Custom FocusSearch used to navigate the launcher in the order items are in
182 // the ViewModel.
183 class LauncherFocusSearch : public views::FocusSearch {
184 public:
185 explicit LauncherFocusSearch(views::ViewModel* view_model)
186 : FocusSearch(NULL, true, true),
187 view_model_(view_model) {}
188 virtual ~LauncherFocusSearch() {}
190 // views::FocusSearch overrides:
191 virtual View* FindNextFocusableView(
192 View* starting_view,
193 bool reverse,
194 Direction direction,
195 bool check_starting_view,
196 views::FocusTraversable** focus_traversable,
197 View** focus_traversable_view) OVERRIDE {
198 int index = view_model_->GetIndexOfView(starting_view);
199 if (index == -1)
200 return view_model_->view_at(0);
202 if (reverse) {
203 --index;
204 if (index < 0)
205 index = view_model_->view_size() - 1;
206 } else {
207 ++index;
208 if (index >= view_model_->view_size())
209 index = 0;
211 return view_model_->view_at(index);
214 private:
215 views::ViewModel* view_model_;
217 DISALLOW_COPY_AND_ASSIGN(LauncherFocusSearch);
220 class LauncherButtonFocusBorder : public views::FocusBorder {
221 public:
222 LauncherButtonFocusBorder() {}
223 virtual ~LauncherButtonFocusBorder() {}
225 private:
226 // views::FocusBorder overrides:
227 virtual void Paint(const View& view, gfx::Canvas* canvas) const OVERRIDE {
228 gfx::Rect rect(view.GetLocalBounds());
229 rect.Inset(1, 1);
230 canvas->DrawRect(rect, kFocusBorderColor);
233 DISALLOW_COPY_AND_ASSIGN(LauncherButtonFocusBorder);
236 // AnimationDelegate that deletes a view when done. This is used when a launcher
237 // item is removed, which triggers a remove animation. When the animation is
238 // done we delete the view.
239 class DeleteViewAnimationDelegate
240 : public views::BoundsAnimator::OwnedAnimationDelegate {
241 public:
242 explicit DeleteViewAnimationDelegate(views::View* view) : view_(view) {}
243 virtual ~DeleteViewAnimationDelegate() {}
245 private:
246 scoped_ptr<views::View> view_;
248 DISALLOW_COPY_AND_ASSIGN(DeleteViewAnimationDelegate);
251 // AnimationDelegate used when inserting a new item. This steadily increases the
252 // opacity of the layer as the animation progress.
253 class FadeInAnimationDelegate
254 : public views::BoundsAnimator::OwnedAnimationDelegate {
255 public:
256 explicit FadeInAnimationDelegate(views::View* view) : view_(view) {}
257 virtual ~FadeInAnimationDelegate() {}
259 // AnimationDelegate overrides:
260 virtual void AnimationProgressed(const Animation* animation) OVERRIDE {
261 view_->layer()->SetOpacity(animation->GetCurrentValue());
262 view_->layer()->ScheduleDraw();
264 virtual void AnimationEnded(const Animation* animation) OVERRIDE {
265 view_->layer()->SetOpacity(1.0f);
266 view_->layer()->ScheduleDraw();
268 virtual void AnimationCanceled(const Animation* animation) OVERRIDE {
269 view_->layer()->SetOpacity(1.0f);
270 view_->layer()->ScheduleDraw();
273 private:
274 views::View* view_;
276 DISALLOW_COPY_AND_ASSIGN(FadeInAnimationDelegate);
279 void ReflectItemStatus(const ash::LauncherItem& item,
280 LauncherButton* button) {
281 switch (item.status) {
282 case STATUS_CLOSED:
283 button->ClearState(LauncherButton::STATE_ACTIVE);
284 button->ClearState(LauncherButton::STATE_RUNNING);
285 button->ClearState(LauncherButton::STATE_ATTENTION);
286 break;
287 case STATUS_RUNNING:
288 button->ClearState(LauncherButton::STATE_ACTIVE);
289 button->AddState(LauncherButton::STATE_RUNNING);
290 button->ClearState(LauncherButton::STATE_ATTENTION);
291 break;
292 case STATUS_ACTIVE:
293 button->AddState(LauncherButton::STATE_ACTIVE);
294 button->ClearState(LauncherButton::STATE_RUNNING);
295 button->ClearState(LauncherButton::STATE_ATTENTION);
296 break;
297 case STATUS_ATTENTION:
298 button->ClearState(LauncherButton::STATE_ACTIVE);
299 button->ClearState(LauncherButton::STATE_RUNNING);
300 button->AddState(LauncherButton::STATE_ATTENTION);
301 break;
305 } // namespace
307 // AnimationDelegate used when deleting an item. This steadily decreased the
308 // opacity of the layer as the animation progress.
309 class LauncherView::FadeOutAnimationDelegate
310 : public views::BoundsAnimator::OwnedAnimationDelegate {
311 public:
312 FadeOutAnimationDelegate(LauncherView* host, views::View* view)
313 : launcher_view_(host),
314 view_(view) {}
315 virtual ~FadeOutAnimationDelegate() {}
317 // AnimationDelegate overrides:
318 virtual void AnimationProgressed(const Animation* animation) OVERRIDE {
319 view_->layer()->SetOpacity(1 - animation->GetCurrentValue());
320 view_->layer()->ScheduleDraw();
322 virtual void AnimationEnded(const Animation* animation) OVERRIDE {
323 launcher_view_->OnFadeOutAnimationEnded();
325 virtual void AnimationCanceled(const Animation* animation) OVERRIDE {
328 private:
329 LauncherView* launcher_view_;
330 scoped_ptr<views::View> view_;
332 DISALLOW_COPY_AND_ASSIGN(FadeOutAnimationDelegate);
335 // AnimationDelegate used to trigger fading an element in. When an item is
336 // inserted this delegate is attached to the animation that expands the size of
337 // the item. When done it kicks off another animation to fade the item in.
338 class LauncherView::StartFadeAnimationDelegate
339 : public views::BoundsAnimator::OwnedAnimationDelegate {
340 public:
341 StartFadeAnimationDelegate(LauncherView* host,
342 views::View* view)
343 : launcher_view_(host),
344 view_(view) {}
345 virtual ~StartFadeAnimationDelegate() {}
347 // AnimationDelegate overrides:
348 virtual void AnimationEnded(const Animation* animation) OVERRIDE {
349 launcher_view_->FadeIn(view_);
351 virtual void AnimationCanceled(const Animation* animation) OVERRIDE {
352 view_->layer()->SetOpacity(1.0f);
355 private:
356 LauncherView* launcher_view_;
357 views::View* view_;
359 DISALLOW_COPY_AND_ASSIGN(StartFadeAnimationDelegate);
362 LauncherView::LauncherView(LauncherModel* model,
363 LauncherDelegate* delegate,
364 ShelfLayoutManager* shelf_layout_manager)
365 : model_(model),
366 delegate_(delegate),
367 view_model_(new views::ViewModel),
368 first_visible_index_(0),
369 last_visible_index_(-1),
370 overflow_button_(NULL),
371 drag_pointer_(NONE),
372 drag_view_(NULL),
373 drag_offset_(0),
374 start_drag_index_(-1),
375 context_menu_id_(0),
376 leading_inset_(kDefaultLeadingInset),
377 cancelling_drag_model_changed_(false),
378 last_hidden_index_(0),
379 closing_event_time_(base::TimeDelta()),
380 got_deleted_(NULL) {
381 DCHECK(model_);
382 bounds_animator_.reset(new views::BoundsAnimator(this));
383 bounds_animator_->AddObserver(this);
384 set_context_menu_controller(this);
385 focus_search_.reset(new LauncherFocusSearch(view_model_.get()));
386 tooltip_.reset(new LauncherTooltipManager(
387 shelf_layout_manager, this));
390 LauncherView::~LauncherView() {
391 bounds_animator_->RemoveObserver(this);
392 model_->RemoveObserver(this);
393 // If we are inside the MenuRunner, we need to know if we were getting
394 // deleted while it was running.
395 if (got_deleted_)
396 *got_deleted_ = true;
399 void LauncherView::Init() {
400 model_->AddObserver(this);
402 const LauncherItems& items(model_->items());
403 for (LauncherItems::const_iterator i = items.begin(); i != items.end(); ++i) {
404 views::View* child = CreateViewForItem(*i);
405 child->SetPaintToLayer(true);
406 view_model_->Add(child, static_cast<int>(i - items.begin()));
407 AddChildView(child);
409 LauncherStatusChanged();
410 overflow_button_ = new OverflowButton(this);
411 overflow_button_->set_context_menu_controller(this);
412 ConfigureChildView(overflow_button_);
413 AddChildView(overflow_button_);
414 UpdateFirstButtonPadding();
416 // We'll layout when our bounds change.
419 void LauncherView::OnShelfAlignmentChanged() {
420 UpdateFirstButtonPadding();
421 overflow_button_->OnShelfAlignmentChanged();
422 LayoutToIdealBounds();
423 for (int i=0; i < view_model_->view_size(); ++i) {
424 // TODO: remove when AppIcon is a Launcher Button.
425 if (TYPE_APP_LIST == model_->items()[i].type) {
426 ShelfLayoutManager* shelf = tooltip_->shelf_layout_manager();
427 static_cast<AppListButton*>(view_model_->view_at(i))->SetImageAlignment(
428 shelf->SelectValueForShelfAlignment(
429 views::ImageButton::ALIGN_CENTER,
430 views::ImageButton::ALIGN_LEFT,
431 views::ImageButton::ALIGN_RIGHT,
432 views::ImageButton::ALIGN_CENTER),
433 shelf->SelectValueForShelfAlignment(
434 views::ImageButton::ALIGN_TOP,
435 views::ImageButton::ALIGN_MIDDLE,
436 views::ImageButton::ALIGN_MIDDLE,
437 views::ImageButton::ALIGN_BOTTOM));
439 if (i >= first_visible_index_ && i <= last_visible_index_)
440 view_model_->view_at(i)->Layout();
442 tooltip_->UpdateArrowLocation();
443 if (overflow_bubble_.get())
444 overflow_bubble_->Hide();
447 gfx::Rect LauncherView::GetIdealBoundsOfItemIcon(LauncherID id) {
448 int index = model_->ItemIndexByID(id);
449 if (index == -1 || (index > last_visible_index_ &&
450 index < model_->FirstPanelIndex()))
451 return gfx::Rect();
452 const gfx::Rect& ideal_bounds(view_model_->ideal_bounds(index));
453 DCHECK_NE(TYPE_APP_LIST, model_->items()[index].type);
454 LauncherButton* button =
455 static_cast<LauncherButton*>(view_model_->view_at(index));
456 gfx::Rect icon_bounds = button->GetIconBounds();
457 return gfx::Rect(ideal_bounds.x() + icon_bounds.x(),
458 ideal_bounds.y() + icon_bounds.y(),
459 icon_bounds.width(),
460 icon_bounds.height());
463 void LauncherView::UpdatePanelIconPosition(LauncherID id,
464 const gfx::Point& midpoint) {
465 int current_index = model_->ItemIndexByID(id);
466 int first_panel_index = model_->FirstPanelIndex();
467 if (current_index < first_panel_index)
468 return;
470 ShelfLayoutManager* shelf = tooltip_->shelf_layout_manager();
471 int target_index = current_index;
472 while (target_index > first_panel_index &&
473 shelf->PrimaryAxisValue(view_model_->ideal_bounds(target_index).x(),
474 view_model_->ideal_bounds(target_index).y()) >
475 shelf->PrimaryAxisValue(midpoint.x(), midpoint.y())) {
476 --target_index;
478 while (target_index < view_model_->view_size() - 1 &&
479 shelf->PrimaryAxisValue(
480 view_model_->ideal_bounds(target_index).right(),
481 view_model_->ideal_bounds(target_index).bottom()) <
482 shelf->PrimaryAxisValue(midpoint.x(), midpoint.y())) {
483 ++target_index;
485 if (current_index != target_index)
486 model_->Move(current_index, target_index);
489 bool LauncherView::IsShowingMenu() const {
490 #if !defined(OS_MACOSX)
491 return (launcher_menu_runner_.get() &&
492 launcher_menu_runner_->IsRunning());
493 #endif
494 return false;
497 bool LauncherView::IsShowingOverflowBubble() const {
498 return overflow_bubble_.get() && overflow_bubble_->IsShowing();
501 views::View* LauncherView::GetAppListButtonView() const {
502 for (int i = 0; i < model_->item_count(); ++i) {
503 if (model_->items()[i].type == TYPE_APP_LIST)
504 return view_model_->view_at(i);
507 NOTREACHED() << "Applist button not found";
508 return NULL;
511 ////////////////////////////////////////////////////////////////////////////////
512 // LauncherView, FocusTraversable implementation:
514 views::FocusSearch* LauncherView::GetFocusSearch() {
515 return focus_search_.get();
518 views::FocusTraversable* LauncherView::GetFocusTraversableParent() {
519 return parent()->GetFocusTraversable();
522 View* LauncherView::GetFocusTraversableParentView() {
523 return this;
526 void LauncherView::LayoutToIdealBounds() {
527 IdealBounds ideal_bounds;
528 CalculateIdealBounds(&ideal_bounds);
530 if (bounds_animator_->IsAnimating())
531 AnimateToIdealBounds();
532 else
533 views::ViewModelUtils::SetViewBoundsToIdealBounds(*view_model_);
535 overflow_button_->SetBoundsRect(ideal_bounds.overflow_bounds);
538 void LauncherView::CalculateIdealBounds(IdealBounds* bounds) {
539 ShelfLayoutManager* shelf = tooltip_->shelf_layout_manager();
541 int available_size = shelf->PrimaryAxisValue(width(), height());
542 DCHECK(model_->item_count() == view_model_->view_size());
543 if (!available_size)
544 return;
546 int first_panel_index = model_->FirstPanelIndex();
547 int app_list_index = first_panel_index - 1;
549 // Initial x,y values account both leading_inset in primary
550 // coordinate and secondary coordinate based on the dynamic edge of the
551 // launcher (eg top edge on bottom-aligned launcher).
552 int x = shelf->SelectValueForShelfAlignment(
553 leading_inset(),
556 leading_inset());
557 int y = shelf->SelectValueForShelfAlignment(
559 leading_inset(),
560 leading_inset(),
562 int w = shelf->PrimaryAxisValue(kLauncherPreferredSize, width());
563 int h = shelf->PrimaryAxisValue(height(), kLauncherPreferredSize);
564 for (int i = 0; i < view_model_->view_size(); ++i) {
565 if (i < first_visible_index_) {
566 view_model_->set_ideal_bounds(i, gfx::Rect(x, y, 0, 0));
567 continue;
570 view_model_->set_ideal_bounds(i, gfx::Rect(x, y, w, h));
571 if (i != app_list_index) {
572 x = shelf->PrimaryAxisValue(x + w + kButtonSpacing, x);
573 y = shelf->PrimaryAxisValue(y, y + h + kButtonSpacing);
577 if (is_overflow_mode()) {
578 DCHECK_LT(last_visible_index_, view_model_->view_size());
579 for (int i = 0; i < view_model_->view_size(); ++i) {
580 view_model_->view_at(i)->SetVisible(
581 i >= first_visible_index_ &&
582 i != app_list_index &&
583 i <= last_visible_index_);
585 return;
588 // To address Fitt's law, we make the first launcher button include the
589 // leading inset (if there is one).
590 if (view_model_->view_size() > 0) {
591 view_model_->set_ideal_bounds(0, gfx::Rect(gfx::Size(
592 shelf->PrimaryAxisValue(leading_inset() + w, w),
593 shelf->PrimaryAxisValue(h, leading_inset() + h))));
596 // Right aligned icons.
597 int end_position = available_size - kButtonSpacing;
598 x = shelf->PrimaryAxisValue(end_position, 0);
599 y = shelf->PrimaryAxisValue(0, end_position);
600 for (int i = view_model_->view_size() - 1;
601 i >= first_panel_index; --i) {
602 x = shelf->PrimaryAxisValue(x - w - kButtonSpacing, x);
603 y = shelf->PrimaryAxisValue(y, y - h - kButtonSpacing);
604 view_model_->set_ideal_bounds(i, gfx::Rect(x, y, w, h));
605 end_position = shelf->PrimaryAxisValue(x, y);
608 // Icons on the left / top are guaranteed up to kLeftIconProportion of
609 // the available space.
610 int last_icon_position = shelf->PrimaryAxisValue(
611 view_model_->ideal_bounds(first_panel_index - 1).right(),
612 view_model_->ideal_bounds(first_panel_index - 1).bottom()) +
613 2 * kLauncherPreferredSize + leading_inset();
614 int reserved_icon_space = available_size * kReservedNonPanelIconProportion;
615 if (last_icon_position < reserved_icon_space)
616 end_position = last_icon_position;
617 else
618 end_position = std::max(end_position, reserved_icon_space);
620 bounds->overflow_bounds.set_size(gfx::Size(
621 shelf->PrimaryAxisValue(w, width()),
622 shelf->PrimaryAxisValue(height(), h)));
623 last_visible_index_ = DetermineLastVisibleIndex(
624 end_position - leading_inset() - 2 * kLauncherPreferredSize);
625 last_hidden_index_ = DetermineFirstVisiblePanelIndex(end_position) - 1;
626 bool show_overflow = (last_visible_index_ + 1 < app_list_index ||
627 last_hidden_index_ >= first_panel_index);
629 for (int i = 0; i < view_model_->view_size(); ++i) {
630 view_model_->view_at(i)->SetVisible(
631 i <= last_visible_index_ ||
632 i == app_list_index ||
633 i > last_hidden_index_);
636 overflow_button_->SetVisible(show_overflow);
637 if (show_overflow) {
638 DCHECK_NE(0, view_model_->view_size());
639 if (last_visible_index_ == -1) {
640 x = shelf->SelectValueForShelfAlignment(
641 leading_inset(),
644 leading_inset());
645 y = shelf->SelectValueForShelfAlignment(
647 leading_inset(),
648 leading_inset(),
650 } else if (last_visible_index_ == app_list_index) {
651 x = view_model_->ideal_bounds(last_visible_index_).x();
652 y = view_model_->ideal_bounds(last_visible_index_).y();
653 } else {
654 x = shelf->PrimaryAxisValue(
655 view_model_->ideal_bounds(last_visible_index_).right(),
656 view_model_->ideal_bounds(last_visible_index_).x());
657 y = shelf->PrimaryAxisValue(
658 view_model_->ideal_bounds(last_visible_index_).y(),
659 view_model_->ideal_bounds(last_visible_index_).bottom());
661 gfx::Rect app_list_bounds = view_model_->ideal_bounds(app_list_index);
662 bounds->overflow_bounds.set_x(x);
663 bounds->overflow_bounds.set_y(y);
665 // Set all hidden panel icon positions to be on the overflow button.
666 for (int i = first_panel_index; i <= last_hidden_index_; ++i)
667 view_model_->set_ideal_bounds(i, gfx::Rect(x, y, w, h));
669 x = shelf->PrimaryAxisValue(x + w + kButtonSpacing, x);
670 y = shelf->PrimaryAxisValue(y, y + h + kButtonSpacing);
671 app_list_bounds.set_x(x);
672 app_list_bounds.set_y(y);
673 view_model_->set_ideal_bounds(app_list_index, app_list_bounds);
675 if (overflow_bubble_.get() && overflow_bubble_->IsShowing())
676 UpdateOverflowRange(overflow_bubble_->launcher_view());
677 } else {
678 if (overflow_bubble_.get())
679 overflow_bubble_->Hide();
683 int LauncherView::DetermineLastVisibleIndex(int max_value) const {
684 ShelfLayoutManager* shelf = tooltip_->shelf_layout_manager();
686 int index = model_->FirstPanelIndex() - 1;
687 while (index >= 0 &&
688 shelf->PrimaryAxisValue(
689 view_model_->ideal_bounds(index).right(),
690 view_model_->ideal_bounds(index).bottom()) > max_value) {
691 index--;
693 return index;
696 int LauncherView::DetermineFirstVisiblePanelIndex(int min_value) const {
697 ShelfLayoutManager* shelf = tooltip_->shelf_layout_manager();
699 int index = model_->FirstPanelIndex();
700 while (index < view_model_->view_size() &&
701 shelf->PrimaryAxisValue(
702 view_model_->ideal_bounds(index).right(),
703 view_model_->ideal_bounds(index).bottom()) < min_value) {
704 ++index;
706 return index;
709 void LauncherView::AddIconObserver(LauncherIconObserver* observer) {
710 observers_.AddObserver(observer);
713 void LauncherView::RemoveIconObserver(LauncherIconObserver* observer) {
714 observers_.RemoveObserver(observer);
717 void LauncherView::AnimateToIdealBounds() {
718 IdealBounds ideal_bounds;
719 CalculateIdealBounds(&ideal_bounds);
720 for (int i = 0; i < view_model_->view_size(); ++i) {
721 View* view = view_model_->view_at(i);
722 bounds_animator_->AnimateViewTo(view, view_model_->ideal_bounds(i));
723 // Now that the item animation starts, we have to make sure that the
724 // padding of the first gets properly transferred to the new first item.
725 if (i && view->border())
726 view->set_border(NULL);
727 else if (!i && !view->border())
728 UpdateFirstButtonPadding();
730 overflow_button_->SetBoundsRect(ideal_bounds.overflow_bounds);
733 views::View* LauncherView::CreateViewForItem(const LauncherItem& item) {
734 views::View* view = NULL;
735 switch (item.type) {
736 case TYPE_TABBED: {
737 TabbedLauncherButton* button =
738 TabbedLauncherButton::Create(
739 this,
740 this,
741 tooltip_->shelf_layout_manager(),
742 item.is_incognito ?
743 TabbedLauncherButton::STATE_INCOGNITO :
744 TabbedLauncherButton::STATE_NOT_INCOGNITO);
745 button->SetTabImage(item.image);
746 ReflectItemStatus(item, button);
747 view = button;
748 break;
751 case TYPE_APP_SHORTCUT:
752 case TYPE_WINDOWED_APP:
753 case TYPE_PLATFORM_APP:
754 case TYPE_APP_PANEL: {
755 LauncherButton* button = LauncherButton::Create(
756 this, this, tooltip_->shelf_layout_manager());
757 button->SetImage(item.image);
758 ReflectItemStatus(item, button);
759 view = button;
760 break;
763 case TYPE_APP_LIST: {
764 // TODO(dave): turn this into a LauncherButton too.
765 AppListButton* button = new AppListButton(this, this);
766 ShelfLayoutManager* shelf = tooltip_->shelf_layout_manager();
767 button->SetImageAlignment(
768 shelf->SelectValueForShelfAlignment(
769 views::ImageButton::ALIGN_CENTER,
770 views::ImageButton::ALIGN_LEFT,
771 views::ImageButton::ALIGN_RIGHT,
772 views::ImageButton::ALIGN_CENTER),
773 shelf->SelectValueForShelfAlignment(
774 views::ImageButton::ALIGN_TOP,
775 views::ImageButton::ALIGN_MIDDLE,
776 views::ImageButton::ALIGN_MIDDLE,
777 views::ImageButton::ALIGN_BOTTOM));
778 view = button;
779 break;
782 case TYPE_BROWSER_SHORTCUT: {
783 ResourceBundle& rb = ResourceBundle::GetSharedInstance();
784 LauncherButton* button = LauncherButton::Create(
785 this, this, tooltip_->shelf_layout_manager());
786 int image_id = delegate_ ?
787 delegate_->GetBrowserShortcutResourceId() :
788 IDR_AURA_LAUNCHER_BROWSER_SHORTCUT;
789 button->SetImage(*rb.GetImageNamed(image_id).ToImageSkia());
790 view = button;
791 break;
794 default:
795 break;
797 view->set_context_menu_controller(this);
798 view->set_focus_border(new LauncherButtonFocusBorder);
800 DCHECK(view);
801 ConfigureChildView(view);
802 return view;
805 void LauncherView::FadeIn(views::View* view) {
806 view->SetVisible(true);
807 view->layer()->SetOpacity(0);
808 AnimateToIdealBounds();
809 bounds_animator_->SetAnimationDelegate(
810 view, new FadeInAnimationDelegate(view), true);
813 void LauncherView::PrepareForDrag(Pointer pointer,
814 const ui::LocatedEvent& event) {
815 DCHECK(!dragging());
816 DCHECK(drag_view_);
817 drag_pointer_ = pointer;
818 start_drag_index_ = view_model_->GetIndexOfView(drag_view_);
820 // If the item is no longer draggable, bail out.
821 if (start_drag_index_ == -1 ||
822 !delegate_->IsDraggable(model_->items()[start_drag_index_])) {
823 CancelDrag(-1);
824 return;
827 // Move the view to the front so that it appears on top of other views.
828 ReorderChildView(drag_view_, -1);
829 bounds_animator_->StopAnimatingView(drag_view_);
832 void LauncherView::ContinueDrag(const ui::LocatedEvent& event) {
833 ShelfLayoutManager* shelf = tooltip_->shelf_layout_manager();
835 // TODO: I don't think this works correctly with RTL.
836 gfx::Point drag_point(event.location());
837 views::View::ConvertPointToTarget(drag_view_, this, &drag_point);
838 int current_index = view_model_->GetIndexOfView(drag_view_);
839 DCHECK_NE(-1, current_index);
841 // If the item is no longer draggable, bail out.
842 if (current_index == -1 ||
843 !delegate_->IsDraggable(model_->items()[current_index])) {
844 CancelDrag(-1);
845 return;
848 // Constrain the location to the range of valid indices for the type.
849 std::pair<int, int> indices(GetDragRange(current_index));
850 int first_drag_index = indices.first;
851 int last_drag_index = indices.second;
852 // If the last index isn't valid, we're overflowing. Constrain to the app list
853 // (which is the last visible item).
854 if (first_drag_index < model_->FirstPanelIndex() &&
855 last_drag_index > last_visible_index_)
856 last_drag_index = last_visible_index_;
857 int x = 0, y = 0;
858 if (shelf->IsHorizontalAlignment()) {
859 x = std::max(view_model_->ideal_bounds(indices.first).x(),
860 drag_point.x() - drag_offset_);
861 x = std::min(view_model_->ideal_bounds(last_drag_index).right() -
862 view_model_->ideal_bounds(current_index).width(),
864 if (drag_view_->x() == x)
865 return;
866 drag_view_->SetX(x);
867 } else {
868 y = std::max(view_model_->ideal_bounds(indices.first).y(),
869 drag_point.y() - drag_offset_);
870 y = std::min(view_model_->ideal_bounds(last_drag_index).bottom() -
871 view_model_->ideal_bounds(current_index).height(),
873 if (drag_view_->y() == y)
874 return;
875 drag_view_->SetY(y);
878 int target_index =
879 views::ViewModelUtils::DetermineMoveIndex(
880 *view_model_, drag_view_,
881 shelf->IsHorizontalAlignment() ?
882 views::ViewModelUtils::HORIZONTAL :
883 views::ViewModelUtils::VERTICAL,
884 x, y);
885 target_index =
886 std::min(indices.second, std::max(target_index, indices.first));
887 if (target_index == current_index)
888 return;
890 // Change the model, the LauncherItemMoved() callback will handle the
891 // |view_model_| update.
892 model_->Move(current_index, target_index);
893 bounds_animator_->StopAnimatingView(drag_view_);
896 bool LauncherView::SameDragType(LauncherItemType typea,
897 LauncherItemType typeb) const {
898 switch (typea) {
899 case TYPE_TABBED:
900 case TYPE_PLATFORM_APP:
901 return (typeb == TYPE_TABBED || typeb == TYPE_PLATFORM_APP);
902 case TYPE_APP_SHORTCUT:
903 case TYPE_WINDOWED_APP:
904 case TYPE_APP_LIST:
905 case TYPE_APP_PANEL:
906 case TYPE_BROWSER_SHORTCUT:
907 return typeb == typea;
909 NOTREACHED();
910 return false;
913 std::pair<int, int> LauncherView::GetDragRange(int index) {
914 int min_index = -1;
915 int max_index = -1;
916 LauncherItemType type = model_->items()[index].type;
917 for (int i = 0; i < model_->item_count(); ++i) {
918 if (SameDragType(model_->items()[i].type, type)) {
919 if (min_index == -1)
920 min_index = i;
921 max_index = i;
924 return std::pair<int, int>(min_index, max_index);
927 void LauncherView::ConfigureChildView(views::View* view) {
928 view->SetPaintToLayer(true);
929 view->layer()->SetFillsBoundsOpaquely(false);
932 void LauncherView::ToggleOverflowBubble() {
933 if (IsShowingOverflowBubble()) {
934 overflow_bubble_->Hide();
935 return;
938 if (!overflow_bubble_.get())
939 overflow_bubble_.reset(new OverflowBubble());
941 LauncherView* overflow_view = new LauncherView(
942 model_, delegate_, tooltip_->shelf_layout_manager());
943 overflow_view->Init();
944 overflow_view->OnShelfAlignmentChanged();
945 UpdateOverflowRange(overflow_view);
947 overflow_bubble_->Show(overflow_button_, overflow_view);
949 Shell::GetInstance()->UpdateShelfVisibility();
952 void LauncherView::UpdateFirstButtonPadding() {
953 ShelfLayoutManager* shelf = tooltip_->shelf_layout_manager();
955 // Creates an empty border for first launcher button to make included leading
956 // inset act as the button's padding. This is only needed on button creation
957 // and when shelf alignment changes.
958 if (view_model_->view_size() > 0) {
959 view_model_->view_at(0)->set_border(views::Border::CreateEmptyBorder(
960 shelf->PrimaryAxisValue(0, leading_inset()),
961 shelf->PrimaryAxisValue(leading_inset(), 0),
963 0));
967 void LauncherView::OnFadeOutAnimationEnded() {
968 AnimateToIdealBounds();
970 // If overflow button is visible and there is a valid new last item, fading
971 // the new last item in after sliding animation is finished.
972 if (overflow_button_->visible() && last_visible_index_ >= 0) {
973 views::View* last_visible_view = view_model_->view_at(last_visible_index_);
974 last_visible_view->layer()->SetOpacity(0);
975 bounds_animator_->SetAnimationDelegate(
976 last_visible_view,
977 new LauncherView::StartFadeAnimationDelegate(this, last_visible_view),
978 true);
982 void LauncherView::UpdateOverflowRange(LauncherView* overflow_view) {
983 const int first_overflow_index = last_visible_index_ + 1;
984 const int last_overflow_index = last_hidden_index_;
985 DCHECK_LE(first_overflow_index, last_overflow_index);
986 DCHECK_LT(last_overflow_index, view_model_->view_size());
988 overflow_view->first_visible_index_ = first_overflow_index;
989 overflow_view->last_visible_index_ = last_overflow_index;
992 bool LauncherView::ShouldHideTooltip(const gfx::Point& cursor_location) {
993 gfx::Rect active_bounds;
995 for (int i = 0; i < child_count(); ++i) {
996 views::View* child = child_at(i);
997 if (child == overflow_button_)
998 continue;
999 if (!ShouldShowTooltipForView(child))
1000 continue;
1002 gfx::Rect child_bounds = child->GetMirroredBounds();
1003 active_bounds.Union(child_bounds);
1006 return !active_bounds.Contains(cursor_location);
1009 int LauncherView::CancelDrag(int modified_index) {
1010 if (!drag_view_)
1011 return modified_index;
1012 bool was_dragging = dragging();
1013 int drag_view_index = view_model_->GetIndexOfView(drag_view_);
1014 drag_pointer_ = NONE;
1015 drag_view_ = NULL;
1016 if (drag_view_index == modified_index) {
1017 // The view that was being dragged is being modified. Don't do anything.
1018 return modified_index;
1020 if (!was_dragging)
1021 return modified_index;
1023 // Restore previous position, tracking the position of the modified view.
1024 bool at_end = modified_index == view_model_->view_size();
1025 views::View* modified_view =
1026 (modified_index >= 0 && !at_end) ?
1027 view_model_->view_at(modified_index) : NULL;
1028 model_->Move(drag_view_index, start_drag_index_);
1030 // If the modified view will be at the end of the list, return the new end of
1031 // the list.
1032 if (at_end)
1033 return view_model_->view_size();
1034 return modified_view ? view_model_->GetIndexOfView(modified_view) : -1;
1037 gfx::Size LauncherView::GetPreferredSize() {
1038 IdealBounds ideal_bounds;
1039 CalculateIdealBounds(&ideal_bounds);
1041 const int app_list_index = view_model_->view_size() - 1;
1042 const int last_button_index = is_overflow_mode() ?
1043 last_visible_index_ : app_list_index;
1044 const gfx::Rect last_button_bounds =
1045 last_button_index >= first_visible_index_ ?
1046 view_model_->view_at(last_button_index)->bounds() :
1047 gfx::Rect(gfx::Size(kLauncherPreferredSize,
1048 kLauncherPreferredSize));
1050 ShelfLayoutManager* shelf = tooltip_->shelf_layout_manager();
1052 if (shelf->IsHorizontalAlignment()) {
1053 return gfx::Size(last_button_bounds.right() + leading_inset(),
1054 kLauncherPreferredSize);
1057 return gfx::Size(kLauncherPreferredSize,
1058 last_button_bounds.bottom() + leading_inset());
1061 void LauncherView::OnBoundsChanged(const gfx::Rect& previous_bounds) {
1062 LayoutToIdealBounds();
1063 FOR_EACH_OBSERVER(LauncherIconObserver, observers_,
1064 OnLauncherIconPositionsChanged());
1066 if (IsShowingOverflowBubble())
1067 overflow_bubble_->Hide();
1070 views::FocusTraversable* LauncherView::GetPaneFocusTraversable() {
1071 return this;
1074 void LauncherView::OnGestureEvent(ui::GestureEvent* event) {
1075 if (gesture_handler_.ProcessGestureEvent(*event))
1076 event->StopPropagation();
1079 void LauncherView::LauncherItemAdded(int model_index) {
1081 base::AutoReset<bool> cancelling_drag(
1082 &cancelling_drag_model_changed_, true);
1083 model_index = CancelDrag(model_index);
1085 views::View* view = CreateViewForItem(model_->items()[model_index]);
1086 AddChildView(view);
1087 // Hide the view, it'll be made visible when the animation is done. Using
1088 // opacity 0 here to avoid messing with CalculateIdealBounds which touches
1089 // the view's visibility.
1090 view->layer()->SetOpacity(0);
1091 view_model_->Add(view, model_index);
1093 // Give the button its ideal bounds. That way if we end up animating the
1094 // button before this animation completes it doesn't appear at some random
1095 // spot (because it was in the middle of animating from 0,0 0x0 to its
1096 // target).
1097 IdealBounds ideal_bounds;
1098 CalculateIdealBounds(&ideal_bounds);
1099 view->SetBoundsRect(view_model_->ideal_bounds(model_index));
1101 // The first animation moves all the views to their target position. |view|
1102 // is hidden, so it visually appears as though we are providing space for
1103 // it. When done we'll fade the view in.
1104 AnimateToIdealBounds();
1105 if (model_index <= last_visible_index_ ||
1106 model_index >= model_->FirstPanelIndex()) {
1107 bounds_animator_->SetAnimationDelegate(
1108 view, new StartFadeAnimationDelegate(this, view), true);
1109 } else {
1110 // Undo the hiding if animation does not run.
1111 view->layer()->SetOpacity(1.0f);
1115 void LauncherView::LauncherItemRemoved(int model_index, LauncherID id) {
1116 #if !defined(OS_MACOSX)
1117 if (id == context_menu_id_)
1118 launcher_menu_runner_.reset();
1119 #endif
1121 base::AutoReset<bool> cancelling_drag(
1122 &cancelling_drag_model_changed_, true);
1123 model_index = CancelDrag(model_index);
1125 views::View* view = view_model_->view_at(model_index);
1126 view_model_->Remove(model_index);
1127 // The first animation fades out the view. When done we'll animate the rest of
1128 // the views to their target location.
1129 bounds_animator_->AnimateViewTo(view, view->bounds());
1130 bounds_animator_->SetAnimationDelegate(
1131 view, new FadeOutAnimationDelegate(this, view), true);
1133 // If overflow bubble is visible, sanitize overflow range first and when the
1134 // above animation finishes, CalculateIdealBounds will be called to get
1135 // correct overflow range. CalculateIdealBounds could hide overflow bubble
1136 // and triggers LauncherItemChanged. And since we are still in the middle
1137 // of LauncherItemRemoved, LauncherView in overflow bubble is not synced
1138 // with LauncherModel and will crash.
1139 if (overflow_bubble_ && overflow_bubble_->IsShowing()) {
1140 last_hidden_index_ = std::min(last_hidden_index_,
1141 view_model_->view_size() - 1);
1142 UpdateOverflowRange(overflow_bubble_->launcher_view());
1146 void LauncherView::LauncherItemChanged(int model_index,
1147 const ash::LauncherItem& old_item) {
1148 const LauncherItem& item(model_->items()[model_index]);
1149 if (old_item.type != item.type) {
1150 // Type changed, swap the views.
1151 model_index = CancelDrag(model_index);
1152 scoped_ptr<views::View> old_view(view_model_->view_at(model_index));
1153 bounds_animator_->StopAnimatingView(old_view.get());
1154 view_model_->Remove(model_index);
1155 views::View* new_view = CreateViewForItem(item);
1156 AddChildView(new_view);
1157 view_model_->Add(new_view, model_index);
1158 new_view->SetBoundsRect(old_view->bounds());
1159 return;
1162 views::View* view = view_model_->view_at(model_index);
1163 switch (item.type) {
1164 case TYPE_TABBED: {
1165 TabbedLauncherButton* button = static_cast<TabbedLauncherButton*>(view);
1166 gfx::Size pref = button->GetPreferredSize();
1167 button->SetTabImage(item.image);
1168 if (pref != button->GetPreferredSize())
1169 AnimateToIdealBounds();
1170 else
1171 button->SchedulePaint();
1172 ReflectItemStatus(item, button);
1173 break;
1175 case TYPE_BROWSER_SHORTCUT:
1176 if (!Shell::IsLauncherPerDisplayEnabled())
1177 break;
1178 // Fallthrough for the new Launcher since it needs to show the activation
1179 // change as well.
1180 case TYPE_APP_SHORTCUT:
1181 case TYPE_WINDOWED_APP:
1182 case TYPE_PLATFORM_APP:
1183 case TYPE_APP_PANEL: {
1184 LauncherButton* button = static_cast<LauncherButton*>(view);
1185 ReflectItemStatus(item, button);
1186 // The browser shortcut is currently not a "real" item and as such the
1187 // the image is bogous as well. We therefore keep the image as is for it.
1188 if (item.type != TYPE_BROWSER_SHORTCUT)
1189 button->SetImage(item.image);
1190 button->SchedulePaint();
1191 break;
1194 default:
1195 break;
1199 void LauncherView::LauncherItemMoved(int start_index, int target_index) {
1200 view_model_->Move(start_index, target_index);
1201 // When cancelling a drag due to a launcher item being added, the currently
1202 // dragged item is moved back to its initial position. AnimateToIdealBounds
1203 // will be called again when the new item is added to the |view_model_| but
1204 // at this time the |view_model_| is inconsistent with the |model_|.
1205 if (!cancelling_drag_model_changed_)
1206 AnimateToIdealBounds();
1209 void LauncherView::LauncherStatusChanged() {
1210 AppListButton* app_list_button =
1211 static_cast<AppListButton*>(GetAppListButtonView());
1212 if (model_->status() == LauncherModel::STATUS_LOADING)
1213 app_list_button->StartLoadingAnimation();
1214 else
1215 app_list_button->StopLoadingAnimation();
1218 void LauncherView::PointerPressedOnButton(views::View* view,
1219 Pointer pointer,
1220 const ui::LocatedEvent& event) {
1221 if (drag_view_)
1222 return;
1224 tooltip_->Close();
1225 int index = view_model_->GetIndexOfView(view);
1226 if (index == -1 ||
1227 view_model_->view_size() <= 1 ||
1228 !delegate_->IsDraggable(model_->items()[index]))
1229 return; // View is being deleted or not draggable, ignore request.
1231 ShelfLayoutManager* shelf = tooltip_->shelf_layout_manager();
1233 drag_view_ = view;
1234 drag_offset_ = shelf->PrimaryAxisValue(event.x(), event.y());
1237 void LauncherView::PointerDraggedOnButton(views::View* view,
1238 Pointer pointer,
1239 const ui::LocatedEvent& event) {
1240 ShelfLayoutManager* shelf = tooltip_->shelf_layout_manager();
1241 if (!dragging() && drag_view_ &&
1242 shelf->PrimaryAxisValue(abs(event.x() - drag_offset_),
1243 abs(event.y() - drag_offset_)) >=
1244 kMinimumDragDistance) {
1245 PrepareForDrag(pointer, event);
1247 if (drag_pointer_ == pointer)
1248 ContinueDrag(event);
1251 void LauncherView::PointerReleasedOnButton(views::View* view,
1252 Pointer pointer,
1253 bool canceled) {
1254 if (canceled) {
1255 CancelDrag(-1);
1256 } else if (drag_pointer_ == pointer) {
1257 drag_pointer_ = NONE;
1258 drag_view_ = NULL;
1259 AnimateToIdealBounds();
1263 void LauncherView::MouseMovedOverButton(views::View* view) {
1264 if (!ShouldShowTooltipForView(view))
1265 return;
1267 if (!tooltip_->IsVisible())
1268 tooltip_->ResetTimer();
1271 void LauncherView::MouseEnteredButton(views::View* view) {
1272 if (!ShouldShowTooltipForView(view))
1273 return;
1275 if (tooltip_->IsVisible()) {
1276 tooltip_->ShowImmediately(view, GetAccessibleName(view));
1277 } else {
1278 tooltip_->ShowDelayed(view, GetAccessibleName(view));
1282 void LauncherView::MouseExitedButton(views::View* view) {
1283 if (!tooltip_->IsVisible())
1284 tooltip_->StopTimer();
1287 string16 LauncherView::GetAccessibleName(const views::View* view) {
1288 int view_index = view_model_->GetIndexOfView(view);
1289 // May be -1 while in the process of animating closed.
1290 if (view_index == -1)
1291 return string16();
1293 switch (model_->items()[view_index].type) {
1294 case TYPE_TABBED:
1295 case TYPE_APP_PANEL:
1296 case TYPE_APP_SHORTCUT:
1297 case TYPE_WINDOWED_APP:
1298 case TYPE_PLATFORM_APP:
1299 return delegate_->GetTitle(model_->items()[view_index]);
1301 case TYPE_APP_LIST:
1302 return model_->status() == LauncherModel::STATUS_LOADING ?
1303 l10n_util::GetStringUTF16(IDS_AURA_APP_LIST_SYNCING_TITLE) :
1304 l10n_util::GetStringUTF16(IDS_AURA_APP_LIST_TITLE);
1306 case TYPE_BROWSER_SHORTCUT:
1307 return Shell::GetInstance()->delegate()->GetProductName();
1309 return string16();
1312 void LauncherView::ButtonPressed(views::Button* sender,
1313 const ui::Event& event) {
1314 // Do not handle mouse release during drag.
1315 if (dragging())
1316 return;
1318 if (sender == overflow_button_) {
1319 ToggleOverflowBubble();
1320 return;
1323 int view_index = view_model_->GetIndexOfView(sender);
1324 // May be -1 while in the process of animating closed.
1325 if (view_index == -1)
1326 return;
1328 // If the previous menu was closed by the same event as this one, we ignore
1329 // the call.
1330 if (!IsUsableEvent(event))
1331 return;
1333 tooltip_->Close();
1336 // Slow down activation animations if shift key is pressed.
1337 scoped_ptr<ui::ScopedAnimationDurationScaleMode> slowing_animations;
1338 if (event.IsShiftDown()) {
1339 slowing_animations.reset(new ui::ScopedAnimationDurationScaleMode(
1340 ui::ScopedAnimationDurationScaleMode::SLOW_DURATION));
1343 // Collect usage statistics before we decide what to do with the click.
1344 switch (model_->items()[view_index].type) {
1345 case TYPE_APP_SHORTCUT:
1346 case TYPE_WINDOWED_APP:
1347 case TYPE_PLATFORM_APP:
1348 Shell::GetInstance()->delegate()->RecordUserMetricsAction(
1349 UMA_LAUNCHER_CLICK_ON_APP);
1350 // Fallthrough
1351 case TYPE_TABBED:
1352 case TYPE_APP_PANEL:
1353 delegate_->ItemClicked(model_->items()[view_index], event);
1354 break;
1356 case TYPE_APP_LIST:
1357 Shell::GetInstance()->delegate()->RecordUserMetricsAction(
1358 UMA_LAUNCHER_CLICK_ON_APPLIST_BUTTON);
1359 Shell::GetInstance()->ToggleAppList(GetWidget()->GetNativeView());
1360 break;
1362 case TYPE_BROWSER_SHORTCUT:
1363 // Click on browser icon is counted in app clicks.
1364 Shell::GetInstance()->delegate()->RecordUserMetricsAction(
1365 UMA_LAUNCHER_CLICK_ON_APP);
1366 delegate_->OnBrowserShortcutClicked(event.flags());
1367 break;
1371 if (model_->items()[view_index].type != TYPE_APP_LIST)
1372 ShowListMenuForView(model_->items()[view_index], sender, event.flags());
1375 bool LauncherView::ShowListMenuForView(const LauncherItem& item,
1376 views::View* source,
1377 int event_flags) {
1378 scoped_ptr<ash::LauncherMenuModel> menu_model;
1379 menu_model.reset(delegate_->CreateApplicationMenu(item, event_flags));
1381 // Make sure we have a menu and it has at least two items in addition to the
1382 // application title and the 3 spacing separators.
1383 if (!menu_model.get() || menu_model->GetItemCount() <= 5)
1384 return false;
1386 ShowMenu(scoped_ptr<views::MenuModelAdapter>(
1387 new LauncherMenuModelAdapter(menu_model.get())),
1388 source,
1389 gfx::Point(),
1390 false);
1391 return true;
1394 void LauncherView::ShowContextMenuForView(views::View* source,
1395 const gfx::Point& point) {
1396 int view_index = view_model_->GetIndexOfView(source);
1397 if (view_index != -1 &&
1398 model_->items()[view_index].type == TYPE_APP_LIST) {
1399 view_index = -1;
1402 if (view_index == -1) {
1403 Shell::GetInstance()->ShowContextMenu(point);
1404 return;
1406 scoped_ptr<ui::MenuModel> menu_model(delegate_->CreateContextMenu(
1407 model_->items()[view_index],
1408 source->GetWidget()->GetNativeView()->GetRootWindow()));
1409 if (!menu_model.get())
1410 return;
1411 base::AutoReset<LauncherID> reseter(
1412 &context_menu_id_,
1413 view_index == -1 ? 0 : model_->items()[view_index].id);
1415 ShowMenu(scoped_ptr<views::MenuModelAdapter>(
1416 new views::MenuModelAdapter(menu_model.get())),
1417 source,
1418 point,
1419 true);
1422 void LauncherView::ShowMenu(
1423 scoped_ptr<views::MenuModelAdapter> menu_model_adapter,
1424 views::View* source,
1425 const gfx::Point& click_point,
1426 bool context_menu) {
1427 closing_event_time_ = base::TimeDelta();
1428 launcher_menu_runner_.reset(
1429 new views::MenuRunner(menu_model_adapter->CreateMenu()));
1431 // Determine the menu alignment dependent on the shelf.
1432 views::MenuItemView::AnchorPosition menu_alignment =
1433 views::MenuItemView::TOPLEFT;
1434 gfx::Rect anchor_point = gfx::Rect(click_point, gfx::Size());
1436 ShelfWidget* shelf = RootWindowController::ForLauncher(
1437 GetWidget()->GetNativeView())->shelf();
1438 if (!context_menu) {
1439 // Application lists use a bubble.
1440 ash::ShelfAlignment align = shelf->GetAlignment();
1441 anchor_point = source->GetBoundsInScreen();
1443 // Launcher items can have an asymmetrical border for spacing reasons.
1444 // Adjust anchor location for this.
1445 if (source->border())
1446 anchor_point.Inset(source->border()->GetInsets());
1448 switch (align) {
1449 case ash::SHELF_ALIGNMENT_BOTTOM:
1450 menu_alignment = views::MenuItemView::BUBBLE_ABOVE;
1451 break;
1452 case ash::SHELF_ALIGNMENT_LEFT:
1453 menu_alignment = views::MenuItemView::BUBBLE_RIGHT;
1454 break;
1455 case ash::SHELF_ALIGNMENT_RIGHT:
1456 menu_alignment = views::MenuItemView::BUBBLE_LEFT;
1457 break;
1458 case ash::SHELF_ALIGNMENT_TOP:
1459 menu_alignment = views::MenuItemView::BUBBLE_BELOW;
1460 break;
1463 // If this gets deleted while we are in the menu, the launcher will be gone
1464 // as well.
1465 bool got_deleted = false;
1466 got_deleted_ = &got_deleted;
1468 shelf->ForceUndimming(true);
1469 // NOTE: if you convert to HAS_MNEMONICS be sure and update menu building
1470 // code.
1471 if (launcher_menu_runner_->RunMenuAt(
1472 source->GetWidget(),
1473 NULL,
1474 anchor_point,
1475 menu_alignment,
1476 views::MenuRunner::CONTEXT_MENU) == views::MenuRunner::MENU_DELETED) {
1477 if (!got_deleted) {
1478 got_deleted_ = NULL;
1479 shelf->ForceUndimming(false);
1481 return;
1483 got_deleted_ = NULL;
1484 shelf->ForceUndimming(false);
1486 // Unpinning an item will reset the |launcher_menu_runner_| before coming
1487 // here.
1488 if (launcher_menu_runner_.get())
1489 closing_event_time_ = launcher_menu_runner_->closing_event_time();
1490 Shell::GetInstance()->UpdateShelfVisibility();
1493 void LauncherView::OnBoundsAnimatorProgressed(views::BoundsAnimator* animator) {
1494 FOR_EACH_OBSERVER(LauncherIconObserver, observers_,
1495 OnLauncherIconPositionsChanged());
1496 PreferredSizeChanged();
1499 void LauncherView::OnBoundsAnimatorDone(views::BoundsAnimator* animator) {
1502 bool LauncherView::IsUsableEvent(const ui::Event& event) {
1503 if (closing_event_time_ == base::TimeDelta())
1504 return true;
1506 base::TimeDelta delta =
1507 base::TimeDelta(event.time_stamp() - closing_event_time_);
1508 closing_event_time_ = base::TimeDelta();
1509 // TODO(skuhne): This time seems excessive, but it appears that the reposting
1510 // takes that long. Need to come up with a better way of doing this.
1511 return (delta.InMilliseconds() < 0 || delta.InMilliseconds() > 130);
1514 const LauncherItem* LauncherView::LauncherItemForView(
1515 const views::View* view) const {
1516 int view_index = view_model_->GetIndexOfView(view);
1517 if (view_index == -1)
1518 return NULL;
1519 return &(model_->items()[view_index]);
1522 bool LauncherView::ShouldShowTooltipForView(const views::View* view) const {
1523 if (view == GetAppListButtonView() &&
1524 Shell::GetInstance()->GetAppListWindow())
1525 return false;
1526 const LauncherItem* item = LauncherItemForView(view);
1527 return (!item || delegate_->ShouldShowTooltip(*item));
1530 } // namespace internal
1531 } // namespace ash