• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #include "ui/app_list/views/app_list_item_view.h"
6 
7 #include <algorithm>
8 
9 #include "base/strings/utf_string_conversions.h"
10 #include "grit/ui_strings.h"
11 #include "ui/accessibility/ax_view_state.h"
12 #include "ui/app_list/app_list_constants.h"
13 #include "ui/app_list/app_list_folder_item.h"
14 #include "ui/app_list/app_list_item.h"
15 #include "ui/app_list/views/apps_grid_view.h"
16 #include "ui/app_list/views/cached_label.h"
17 #include "ui/app_list/views/progress_bar_view.h"
18 #include "ui/base/dragdrop/drag_utils.h"
19 #include "ui/base/l10n/l10n_util.h"
20 #include "ui/base/resource/resource_bundle.h"
21 #include "ui/compositor/layer.h"
22 #include "ui/compositor/scoped_layer_animation_settings.h"
23 #include "ui/gfx/animation/throb_animation.h"
24 #include "ui/gfx/canvas.h"
25 #include "ui/gfx/font_list.h"
26 #include "ui/gfx/image/image_skia_operations.h"
27 #include "ui/gfx/point.h"
28 #include "ui/gfx/transform_util.h"
29 #include "ui/views/background.h"
30 #include "ui/views/controls/image_view.h"
31 #include "ui/views/controls/label.h"
32 #include "ui/views/controls/menu/menu_runner.h"
33 #include "ui/views/drag_controller.h"
34 
35 namespace app_list {
36 
37 namespace {
38 
39 const int kTopPadding = 20;
40 const int kIconTitleSpacing = 7;
41 const int kProgressBarHorizontalPadding = 12;
42 
43 // The font is different on each platform. The font size is adjusted on some
44 // platforms to keep a consistent look.
45 #if defined(OS_LINUX) && !defined(OS_CHROMEOS)
46 // Reducing the font size by 2 makes it the same as the Windows font size.
47 const int kFontSizeDelta = -2;
48 #else
49 const int kFontSizeDelta = 0;
50 #endif
51 
52 // Radius of the folder dropping preview circle.
53 const int kFolderPreviewRadius = 40;
54 
55 const int kLeftRightPaddingChars = 1;
56 
57 // Scale to transform the icon when a drag starts.
58 const float kDraggingIconScale = 1.5f;
59 
60 // Delay in milliseconds of when the dragging UI should be shown for mouse drag.
61 const int kMouseDragUIDelayInMs = 200;
62 
63 }  // namespace
64 
65 // static
66 const char AppListItemView::kViewClassName[] = "ui/app_list/AppListItemView";
67 
AppListItemView(AppsGridView * apps_grid_view,AppListItem * item)68 AppListItemView::AppListItemView(AppsGridView* apps_grid_view,
69                                  AppListItem* item)
70     : CustomButton(apps_grid_view),
71       item_(item),
72       apps_grid_view_(apps_grid_view),
73       icon_(new views::ImageView),
74       title_(new CachedLabel),
75       progress_bar_(new ProgressBarView),
76       ui_state_(UI_STATE_NORMAL),
77       touch_dragging_(false) {
78   icon_->set_interactive(false);
79 
80   ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
81   title_->SetBackgroundColor(0);
82   title_->SetAutoColorReadabilityEnabled(false);
83   title_->SetEnabledColor(kGridTitleColor);
84   title_->SetFontList(
85       rb.GetFontList(kItemTextFontStyle).DeriveWithSizeDelta(kFontSizeDelta));
86   title_->SetHorizontalAlignment(gfx::ALIGN_LEFT);
87   title_->SetVisible(!item_->is_installing());
88   title_->Invalidate();
89   SetTitleSubpixelAA();
90 
91   const gfx::ShadowValue kIconShadows[] = {
92     gfx::ShadowValue(gfx::Point(0, 2), 2, SkColorSetARGB(0x24, 0, 0, 0)),
93   };
94   icon_shadows_.assign(kIconShadows, kIconShadows + arraysize(kIconShadows));
95 
96   AddChildView(icon_);
97   AddChildView(title_);
98   AddChildView(progress_bar_);
99 
100   ItemIconChanged();
101   ItemNameChanged();
102   ItemIsInstallingChanged();
103   item_->AddObserver(this);
104 
105   set_context_menu_controller(this);
106   set_request_focus_on_press(false);
107 
108   SetAnimationDuration(0);
109 }
110 
~AppListItemView()111 AppListItemView::~AppListItemView() {
112   item_->RemoveObserver(this);
113 }
114 
SetIconSize(const gfx::Size & size)115 void AppListItemView::SetIconSize(const gfx::Size& size) {
116   if (icon_size_ == size)
117     return;
118 
119   icon_size_ = size;
120   UpdateIcon();
121 }
122 
UpdateIcon()123 void AppListItemView::UpdateIcon() {
124   // Skip if |icon_size_| has not been determined.
125   if (icon_size_.IsEmpty())
126     return;
127 
128   gfx::ImageSkia icon = item_->icon();
129   // Clear icon and bail out if item icon is empty.
130   if (icon.isNull()) {
131     icon_->SetImage(NULL);
132     return;
133   }
134 
135   gfx::ImageSkia resized(gfx::ImageSkiaOperations::CreateResizedImage(icon,
136       skia::ImageOperations::RESIZE_BEST, icon_size_));
137   if (item_->has_shadow()) {
138     gfx::ImageSkia shadow(
139         gfx::ImageSkiaOperations::CreateImageWithDropShadow(resized,
140                                                             icon_shadows_));
141     icon_->SetImage(shadow);
142     return;
143   }
144 
145   icon_->SetImage(resized);
146 }
147 
UpdateTooltip()148 void AppListItemView::UpdateTooltip() {
149   std::string display_name = item_->GetDisplayName();
150   title_->SetTooltipText(display_name == item_->name() ? base::string16()
151                          : base::UTF8ToUTF16(item_->name()));
152 }
153 
SetUIState(UIState state)154 void AppListItemView::SetUIState(UIState state) {
155   if (ui_state_ == state)
156     return;
157 
158   ui_state_ = state;
159 
160   switch (ui_state_) {
161     case UI_STATE_NORMAL:
162       title_->SetVisible(!item_->is_installing());
163       progress_bar_->SetVisible(item_->is_installing());
164       break;
165     case UI_STATE_DRAGGING:
166       title_->SetVisible(false);
167       progress_bar_->SetVisible(false);
168       break;
169     case UI_STATE_DROPPING_IN_FOLDER:
170       break;
171   }
172 #if !defined(OS_WIN)
173   ui::ScopedLayerAnimationSettings settings(layer()->GetAnimator());
174   switch (ui_state_) {
175     case UI_STATE_NORMAL:
176       layer()->SetTransform(gfx::Transform());
177       break;
178     case UI_STATE_DRAGGING: {
179       const gfx::Rect bounds(layer()->bounds().size());
180       layer()->SetTransform(gfx::GetScaleTransform(
181           bounds.CenterPoint(),
182           kDraggingIconScale));
183       break;
184     }
185     case UI_STATE_DROPPING_IN_FOLDER:
186       break;
187   }
188 #endif  // !OS_WIN
189 
190   SchedulePaint();
191 }
192 
SetTouchDragging(bool touch_dragging)193 void AppListItemView::SetTouchDragging(bool touch_dragging) {
194   if (touch_dragging_ == touch_dragging)
195     return;
196 
197   touch_dragging_ = touch_dragging;
198   SetUIState(touch_dragging_ ? UI_STATE_DRAGGING : UI_STATE_NORMAL);
199 }
200 
OnMouseDragTimer()201 void AppListItemView::OnMouseDragTimer() {
202   DCHECK(apps_grid_view_->IsDraggedView(this));
203   SetUIState(UI_STATE_DRAGGING);
204 }
205 
SetTitleSubpixelAA()206 void AppListItemView::SetTitleSubpixelAA() {
207   // TODO(tapted): Enable AA for folders as well, taking care to play nice with
208   // the folder bubble animation.
209   bool enable_aa = !item_->IsInFolder() && ui_state_ == UI_STATE_NORMAL &&
210                    !item_->highlighted() &&
211                    !apps_grid_view_->IsSelectedView(this) &&
212                    !apps_grid_view_->IsAnimatingView(this);
213 
214   bool currently_enabled = title_->background() != NULL;
215   if (currently_enabled == enable_aa)
216     return;
217 
218   if (enable_aa) {
219     title_->SetBackgroundColor(app_list::kContentsBackgroundColor);
220     title_->set_background(views::Background::CreateSolidBackground(
221         app_list::kContentsBackgroundColor));
222   } else {
223     // In other cases, keep the background transparent to ensure correct
224     // interactions with animations. This will temporarily disable subpixel AA.
225     title_->SetBackgroundColor(0);
226     title_->set_background(NULL);
227   }
228   title_->Invalidate();
229   title_->SchedulePaint();
230 }
231 
Prerender()232 void AppListItemView::Prerender() {
233   title_->PaintToBackingImage();
234 }
235 
CancelContextMenu()236 void AppListItemView::CancelContextMenu() {
237   if (context_menu_runner_)
238     context_menu_runner_->Cancel();
239 }
240 
GetDragImage()241 gfx::ImageSkia AppListItemView::GetDragImage() {
242   return icon_->GetImage();
243 }
244 
OnDragEnded()245 void AppListItemView::OnDragEnded() {
246   mouse_drag_timer_.Stop();
247   SetUIState(UI_STATE_NORMAL);
248 }
249 
GetDragImageOffset()250 gfx::Point AppListItemView::GetDragImageOffset() {
251   gfx::Point image = icon_->GetImageBounds().origin();
252   return gfx::Point(icon_->x() + image.x(), icon_->y() + image.y());
253 }
254 
SetAsAttemptedFolderTarget(bool is_target_folder)255 void AppListItemView::SetAsAttemptedFolderTarget(bool is_target_folder) {
256   if (is_target_folder)
257     SetUIState(UI_STATE_DROPPING_IN_FOLDER);
258   else
259     SetUIState(UI_STATE_NORMAL);
260 }
261 
ItemIconChanged()262 void AppListItemView::ItemIconChanged() {
263   UpdateIcon();
264 }
265 
ItemNameChanged()266 void AppListItemView::ItemNameChanged() {
267   title_->SetText(base::UTF8ToUTF16(item_->GetDisplayName()));
268   title_->Invalidate();
269   UpdateTooltip();
270   // Use full name for accessibility.
271   SetAccessibleName(item_->GetItemType() == AppListFolderItem::kItemType
272                         ? l10n_util::GetStringFUTF16(
273                               IDS_APP_LIST_FOLDER_BUTTON_ACCESSIBILE_NAME,
274                               base::UTF8ToUTF16(item_->name()))
275                         : base::UTF8ToUTF16(item_->name()));
276   Layout();
277 }
278 
ItemHighlightedChanged()279 void AppListItemView::ItemHighlightedChanged() {
280   apps_grid_view_->EnsureViewVisible(this);
281   SchedulePaint();
282 }
283 
ItemIsInstallingChanged()284 void AppListItemView::ItemIsInstallingChanged() {
285   if (item_->is_installing())
286     apps_grid_view_->EnsureViewVisible(this);
287   title_->SetVisible(!item_->is_installing());
288   progress_bar_->SetVisible(item_->is_installing());
289   SchedulePaint();
290 }
291 
ItemPercentDownloadedChanged()292 void AppListItemView::ItemPercentDownloadedChanged() {
293   // A percent_downloaded() of -1 can mean it's not known how much percent is
294   // completed, or the download hasn't been marked complete, as is the case
295   // while an extension is being installed after being downloaded.
296   if (item_->percent_downloaded() == -1)
297     return;
298   progress_bar_->SetValue(item_->percent_downloaded() / 100.0);
299 }
300 
GetClassName() const301 const char* AppListItemView::GetClassName() const {
302   return kViewClassName;
303 }
304 
Layout()305 void AppListItemView::Layout() {
306   gfx::Rect rect(GetContentsBounds());
307 
308   const int left_right_padding =
309       title_->font_list().GetExpectedTextWidth(kLeftRightPaddingChars);
310   rect.Inset(left_right_padding, kTopPadding, left_right_padding, 0);
311   const int y = rect.y();
312 
313   icon_->SetBoundsRect(GetIconBoundsForTargetViewBounds(GetContentsBounds()));
314   const gfx::Size title_size = title_->GetPreferredSize();
315   gfx::Rect title_bounds(rect.x() + (rect.width() - title_size.width()) / 2,
316                          y + icon_size_.height() + kIconTitleSpacing,
317                          title_size.width(),
318                          title_size.height());
319   title_bounds.Intersect(rect);
320   title_->SetBoundsRect(title_bounds);
321 
322   gfx::Rect progress_bar_bounds(progress_bar_->GetPreferredSize());
323   progress_bar_bounds.set_x(GetContentsBounds().x() +
324                             kProgressBarHorizontalPadding);
325   progress_bar_bounds.set_y(title_bounds.y());
326   progress_bar_->SetBoundsRect(progress_bar_bounds);
327 }
328 
SchedulePaintInRect(const gfx::Rect & r)329 void AppListItemView::SchedulePaintInRect(const gfx::Rect& r) {
330   SetTitleSubpixelAA();
331   views::CustomButton::SchedulePaintInRect(r);
332 }
333 
OnPaint(gfx::Canvas * canvas)334 void AppListItemView::OnPaint(gfx::Canvas* canvas) {
335   if (apps_grid_view_->IsDraggedView(this))
336     return;
337 
338   gfx::Rect rect(GetContentsBounds());
339   if (item_->highlighted() && !item_->is_installing()) {
340     canvas->FillRect(rect, kHighlightedColor);
341     return;
342   }
343   if (apps_grid_view_->IsSelectedView(this))
344     canvas->FillRect(rect, kSelectedColor);
345 
346   if (ui_state_ == UI_STATE_DROPPING_IN_FOLDER) {
347     DCHECK(apps_grid_view_->model()->folders_enabled());
348 
349     // Draw folder dropping preview circle.
350     gfx::Point center = gfx::Point(icon_->x() + icon_->size().width() / 2,
351                                    icon_->y() + icon_->size().height() / 2);
352     SkPaint paint;
353     paint.setStyle(SkPaint::kFill_Style);
354     paint.setAntiAlias(true);
355     paint.setColor(kFolderBubbleColor);
356     canvas->DrawCircle(center, kFolderPreviewRadius, paint);
357   }
358 }
359 
ShowContextMenuForView(views::View * source,const gfx::Point & point,ui::MenuSourceType source_type)360 void AppListItemView::ShowContextMenuForView(views::View* source,
361                                              const gfx::Point& point,
362                                              ui::MenuSourceType source_type) {
363   ui::MenuModel* menu_model = item_->GetContextMenuModel();
364   if (!menu_model)
365     return;
366 
367   context_menu_runner_.reset(new views::MenuRunner(menu_model));
368   if (context_menu_runner_->RunMenuAt(GetWidget(),
369                                       NULL,
370                                       gfx::Rect(point, gfx::Size()),
371                                       views::MENU_ANCHOR_TOPLEFT,
372                                       source_type,
373                                       views::MenuRunner::HAS_MNEMONICS) ==
374       views::MenuRunner::MENU_DELETED) {
375     return;
376   }
377 }
378 
StateChanged()379 void AppListItemView::StateChanged() {
380   const bool is_folder_ui_enabled = apps_grid_view_->model()->folders_enabled();
381   if (is_folder_ui_enabled)
382     apps_grid_view_->ClearAnySelectedView();
383 
384   if (state() == STATE_HOVERED || state() == STATE_PRESSED) {
385     if (!is_folder_ui_enabled)
386       apps_grid_view_->SetSelectedView(this);
387     title_->SetEnabledColor(kGridTitleHoverColor);
388   } else {
389     if (!is_folder_ui_enabled)
390       apps_grid_view_->ClearSelectedView(this);
391     item_->SetHighlighted(false);
392     title_->SetEnabledColor(kGridTitleColor);
393   }
394   title_->Invalidate();
395 }
396 
ShouldEnterPushedState(const ui::Event & event)397 bool AppListItemView::ShouldEnterPushedState(const ui::Event& event) {
398   // Don't enter pushed state for ET_GESTURE_TAP_DOWN so that hover gray
399   // background does not show up during scroll.
400   if (event.type() == ui::ET_GESTURE_TAP_DOWN)
401     return false;
402 
403   return views::CustomButton::ShouldEnterPushedState(event);
404 }
405 
OnMousePressed(const ui::MouseEvent & event)406 bool AppListItemView::OnMousePressed(const ui::MouseEvent& event) {
407   CustomButton::OnMousePressed(event);
408 
409   if (!ShouldEnterPushedState(event))
410     return true;
411 
412   apps_grid_view_->InitiateDrag(this, AppsGridView::MOUSE, event);
413 
414   if (apps_grid_view_->IsDraggedView(this)) {
415     mouse_drag_timer_.Start(FROM_HERE,
416         base::TimeDelta::FromMilliseconds(kMouseDragUIDelayInMs),
417         this, &AppListItemView::OnMouseDragTimer);
418   }
419   return true;
420 }
421 
OnKeyPressed(const ui::KeyEvent & event)422 bool AppListItemView::OnKeyPressed(const ui::KeyEvent& event) {
423   // Disable space key to press the button. The keyboard events received
424   // by this view are forwarded from a Textfield (SearchBoxView) and key
425   // released events are not forwarded. This leaves the button in pressed
426   // state.
427   if (event.key_code() == ui::VKEY_SPACE)
428     return false;
429 
430   return CustomButton::OnKeyPressed(event);
431 }
432 
OnMouseReleased(const ui::MouseEvent & event)433 void AppListItemView::OnMouseReleased(const ui::MouseEvent& event) {
434   CustomButton::OnMouseReleased(event);
435   apps_grid_view_->EndDrag(false);
436 }
437 
OnMouseCaptureLost()438 void AppListItemView::OnMouseCaptureLost() {
439   // We don't cancel the dag on mouse capture lost for windows as entering a
440   // synchronous drag causes mouse capture to be lost and pressing escape
441   // dismisses the app list anyway.
442 #if !defined(OS_WIN)
443   CustomButton::OnMouseCaptureLost();
444   apps_grid_view_->EndDrag(true);
445 #endif
446 }
447 
OnMouseDragged(const ui::MouseEvent & event)448 bool AppListItemView::OnMouseDragged(const ui::MouseEvent& event) {
449   CustomButton::OnMouseDragged(event);
450   if (apps_grid_view_->IsDraggedView(this)) {
451     // If the drag is no longer happening, it could be because this item
452     // got removed, in which case this item has been destroyed. So, bail out
453     // now as there will be nothing else to do anyway as
454     // apps_grid_view_->dragging() will be false.
455     if (!apps_grid_view_->UpdateDragFromItem(AppsGridView::MOUSE, event))
456       return true;
457   }
458 
459   // Shows dragging UI when it's confirmed without waiting for the timer.
460   if (ui_state_ != UI_STATE_DRAGGING &&
461       apps_grid_view_->dragging() &&
462       apps_grid_view_->IsDraggedView(this)) {
463     mouse_drag_timer_.Stop();
464     SetUIState(UI_STATE_DRAGGING);
465   }
466   return true;
467 }
468 
OnGestureEvent(ui::GestureEvent * event)469 void AppListItemView::OnGestureEvent(ui::GestureEvent* event) {
470   switch (event->type()) {
471     case ui::ET_GESTURE_SCROLL_BEGIN:
472       if (touch_dragging_) {
473         apps_grid_view_->InitiateDrag(this, AppsGridView::TOUCH, *event);
474         event->SetHandled();
475       }
476       break;
477     case ui::ET_GESTURE_SCROLL_UPDATE:
478       if (touch_dragging_ && apps_grid_view_->IsDraggedView(this)) {
479         apps_grid_view_->UpdateDragFromItem(AppsGridView::TOUCH, *event);
480         event->SetHandled();
481       }
482       break;
483     case ui::ET_GESTURE_SCROLL_END:
484     case ui::ET_SCROLL_FLING_START:
485       if (touch_dragging_) {
486         SetTouchDragging(false);
487         apps_grid_view_->EndDrag(false);
488         event->SetHandled();
489       }
490       break;
491     case ui::ET_GESTURE_LONG_PRESS:
492       if (!apps_grid_view_->has_dragged_view())
493         SetTouchDragging(true);
494       event->SetHandled();
495       break;
496     case ui::ET_GESTURE_LONG_TAP:
497     case ui::ET_GESTURE_END:
498       if (touch_dragging_)
499         SetTouchDragging(false);
500       break;
501     default:
502       break;
503   }
504   if (!event->handled())
505     CustomButton::OnGestureEvent(event);
506 }
507 
OnSyncDragEnd()508 void AppListItemView::OnSyncDragEnd() {
509   SetUIState(UI_STATE_NORMAL);
510 }
511 
GetIconBounds() const512 const gfx::Rect& AppListItemView::GetIconBounds() const {
513   return icon_->bounds();
514 }
515 
SetDragUIState()516 void AppListItemView::SetDragUIState() {
517   SetUIState(UI_STATE_DRAGGING);
518 }
519 
GetIconBoundsForTargetViewBounds(const gfx::Rect & target_bounds)520 gfx::Rect AppListItemView::GetIconBoundsForTargetViewBounds(
521     const gfx::Rect& target_bounds) {
522   gfx::Rect rect(target_bounds);
523 
524   const int left_right_padding =
525       title_->font_list().GetExpectedTextWidth(kLeftRightPaddingChars);
526   rect.Inset(left_right_padding, kTopPadding, left_right_padding, 0);
527 
528   gfx::Rect icon_bounds(rect.x(), rect.y(), rect.width(), icon_size_.height());
529   icon_bounds.Inset(gfx::ShadowValue::GetMargin(icon_shadows_));
530   return icon_bounds;
531 }
532 
533 }  // namespace app_list
534