Updating trunk VERSION from 2139.0 to 2140.0
[chromium-blink-merge.git] / ui / app_list / views / app_list_item_view.cc
blob737e21f0aac6289b0ac364ddba55963e5f2baee0
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 "ui/app_list/views/app_list_item_view.h"
7 #include <algorithm>
9 #include "base/strings/utf_string_conversions.h"
10 #include "ui/accessibility/ax_view_state.h"
11 #include "ui/app_list/app_list_constants.h"
12 #include "ui/app_list/app_list_folder_item.h"
13 #include "ui/app_list/app_list_item.h"
14 #include "ui/app_list/views/apps_grid_view.h"
15 #include "ui/app_list/views/cached_label.h"
16 #include "ui/app_list/views/progress_bar_view.h"
17 #include "ui/base/dragdrop/drag_utils.h"
18 #include "ui/base/l10n/l10n_util.h"
19 #include "ui/base/resource/resource_bundle.h"
20 #include "ui/compositor/layer.h"
21 #include "ui/compositor/scoped_layer_animation_settings.h"
22 #include "ui/gfx/animation/throb_animation.h"
23 #include "ui/gfx/canvas.h"
24 #include "ui/gfx/font_list.h"
25 #include "ui/gfx/image/image_skia_operations.h"
26 #include "ui/gfx/point.h"
27 #include "ui/gfx/shadow_value.h"
28 #include "ui/gfx/transform_util.h"
29 #include "ui/strings/grit/ui_strings.h"
30 #include "ui/views/background.h"
31 #include "ui/views/controls/image_view.h"
32 #include "ui/views/controls/label.h"
33 #include "ui/views/controls/menu/menu_runner.h"
34 #include "ui/views/drag_controller.h"
36 namespace app_list {
38 namespace {
40 const int kTopPadding = 20;
41 const int kIconTitleSpacing = 7;
42 const int kProgressBarHorizontalPadding = 12;
44 // The font is different on each platform. The font size is adjusted on some
45 // platforms to keep a consistent look.
46 #if defined(OS_LINUX) && !defined(OS_CHROMEOS)
47 // Reducing the font size by 2 makes it the same as the Windows font size.
48 const int kFontSizeDelta = -2;
49 #else
50 const int kFontSizeDelta = 0;
51 #endif
53 // Radius of the folder dropping preview circle.
54 const int kFolderPreviewRadius = 40;
56 const int kLeftRightPaddingChars = 1;
58 // Scale to transform the icon when a drag starts.
59 const float kDraggingIconScale = 1.5f;
61 // Delay in milliseconds of when the dragging UI should be shown for mouse drag.
62 const int kMouseDragUIDelayInMs = 200;
64 const gfx::ShadowValues& GetIconShadows() {
65 CR_DEFINE_STATIC_LOCAL(
66 const gfx::ShadowValues,
67 icon_shadows,
68 (1,
69 gfx::ShadowValue(gfx::Point(0, 2), 2, SkColorSetARGB(0x24, 0, 0, 0))));
70 return icon_shadows;
73 } // namespace
75 // static
76 const char AppListItemView::kViewClassName[] = "ui/app_list/AppListItemView";
78 AppListItemView::AppListItemView(AppsGridView* apps_grid_view,
79 AppListItem* item)
80 : CustomButton(apps_grid_view),
81 is_folder_(item->GetItemType() == AppListFolderItem::kItemType),
82 is_in_folder_(item->IsInFolder()),
83 item_weak_(item),
84 apps_grid_view_(apps_grid_view),
85 icon_(new views::ImageView),
86 title_(new CachedLabel),
87 progress_bar_(new ProgressBarView),
88 ui_state_(UI_STATE_NORMAL),
89 touch_dragging_(false),
90 is_installing_(false),
91 is_highlighted_(false) {
92 icon_->set_interactive(false);
94 ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
95 title_->SetBackgroundColor(0);
96 title_->SetAutoColorReadabilityEnabled(false);
97 title_->SetEnabledColor(kGridTitleColor);
98 title_->SetFontList(
99 rb.GetFontList(kItemTextFontStyle).DeriveWithSizeDelta(kFontSizeDelta));
100 title_->SetHorizontalAlignment(gfx::ALIGN_LEFT);
101 title_->Invalidate();
102 SetTitleSubpixelAA();
104 AddChildView(icon_);
105 AddChildView(title_);
106 AddChildView(progress_bar_);
108 SetIcon(item->icon(), item->has_shadow());
109 SetItemName(base::UTF8ToUTF16(item->GetDisplayName()),
110 base::UTF8ToUTF16(item->name()));
111 SetItemIsInstalling(item->is_installing());
112 SetItemIsHighlighted(item->highlighted());
113 item->AddObserver(this);
115 set_context_menu_controller(this);
116 set_request_focus_on_press(false);
118 SetAnimationDuration(0);
121 AppListItemView::~AppListItemView() {
122 if (item_weak_)
123 item_weak_->RemoveObserver(this);
126 void AppListItemView::SetIcon(const gfx::ImageSkia& icon, bool has_shadow) {
127 // Clear icon and bail out if item icon is empty.
128 if (icon.isNull()) {
129 icon_->SetImage(NULL);
130 return;
133 gfx::ImageSkia resized(gfx::ImageSkiaOperations::CreateResizedImage(
134 icon,
135 skia::ImageOperations::RESIZE_BEST,
136 gfx::Size(kGridIconDimension, kGridIconDimension)));
137 if (has_shadow) {
138 gfx::ImageSkia shadow(gfx::ImageSkiaOperations::CreateImageWithDropShadow(
139 resized, GetIconShadows()));
140 icon_->SetImage(shadow);
141 return;
144 icon_->SetImage(resized);
147 void AppListItemView::SetUIState(UIState state) {
148 if (ui_state_ == state)
149 return;
151 ui_state_ = state;
153 switch (ui_state_) {
154 case UI_STATE_NORMAL:
155 title_->SetVisible(!is_installing_);
156 progress_bar_->SetVisible(is_installing_);
157 break;
158 case UI_STATE_DRAGGING:
159 title_->SetVisible(false);
160 progress_bar_->SetVisible(false);
161 break;
162 case UI_STATE_DROPPING_IN_FOLDER:
163 break;
165 #if !defined(OS_WIN)
166 ui::ScopedLayerAnimationSettings settings(layer()->GetAnimator());
167 switch (ui_state_) {
168 case UI_STATE_NORMAL:
169 layer()->SetTransform(gfx::Transform());
170 break;
171 case UI_STATE_DRAGGING: {
172 const gfx::Rect bounds(layer()->bounds().size());
173 layer()->SetTransform(gfx::GetScaleTransform(
174 bounds.CenterPoint(),
175 kDraggingIconScale));
176 break;
178 case UI_STATE_DROPPING_IN_FOLDER:
179 break;
181 #endif // !OS_WIN
183 SchedulePaint();
186 void AppListItemView::SetTouchDragging(bool touch_dragging) {
187 if (touch_dragging_ == touch_dragging)
188 return;
190 touch_dragging_ = touch_dragging;
191 SetUIState(touch_dragging_ ? UI_STATE_DRAGGING : UI_STATE_NORMAL);
194 void AppListItemView::OnMouseDragTimer() {
195 DCHECK(apps_grid_view_->IsDraggedView(this));
196 SetUIState(UI_STATE_DRAGGING);
199 void AppListItemView::SetTitleSubpixelAA() {
200 // TODO(tapted): Enable AA for folders as well, taking care to play nice with
201 // the folder bubble animation.
202 bool enable_aa = !is_in_folder_ && ui_state_ == UI_STATE_NORMAL &&
203 !is_highlighted_ && !apps_grid_view_->IsSelectedView(this) &&
204 !apps_grid_view_->IsAnimatingView(this);
206 bool currently_enabled = title_->background() != NULL;
207 if (currently_enabled == enable_aa)
208 return;
210 if (enable_aa) {
211 title_->SetBackgroundColor(app_list::kContentsBackgroundColor);
212 title_->set_background(views::Background::CreateSolidBackground(
213 app_list::kContentsBackgroundColor));
214 } else {
215 // In other cases, keep the background transparent to ensure correct
216 // interactions with animations. This will temporarily disable subpixel AA.
217 title_->SetBackgroundColor(0);
218 title_->set_background(NULL);
220 title_->Invalidate();
221 title_->SchedulePaint();
224 void AppListItemView::Prerender() {
225 title_->PaintToBackingImage();
228 void AppListItemView::CancelContextMenu() {
229 if (context_menu_runner_)
230 context_menu_runner_->Cancel();
233 gfx::ImageSkia AppListItemView::GetDragImage() {
234 return icon_->GetImage();
237 void AppListItemView::OnDragEnded() {
238 mouse_drag_timer_.Stop();
239 SetUIState(UI_STATE_NORMAL);
242 gfx::Point AppListItemView::GetDragImageOffset() {
243 gfx::Point image = icon_->GetImageBounds().origin();
244 return gfx::Point(icon_->x() + image.x(), icon_->y() + image.y());
247 void AppListItemView::SetAsAttemptedFolderTarget(bool is_target_folder) {
248 if (is_target_folder)
249 SetUIState(UI_STATE_DROPPING_IN_FOLDER);
250 else
251 SetUIState(UI_STATE_NORMAL);
254 void AppListItemView::SetItemName(const base::string16& display_name,
255 const base::string16& full_name) {
256 title_->SetText(display_name);
257 title_->Invalidate();
259 title_->SetTooltipText(display_name == full_name ? base::string16()
260 : full_name);
262 // Use full name for accessibility.
263 SetAccessibleName(
264 is_folder_ ? l10n_util::GetStringFUTF16(
265 IDS_APP_LIST_FOLDER_BUTTON_ACCESSIBILE_NAME, full_name)
266 : full_name);
267 Layout();
270 void AppListItemView::SetItemIsHighlighted(bool is_highlighted) {
271 is_highlighted_ = is_highlighted;
272 apps_grid_view_->EnsureViewVisible(this);
273 SchedulePaint();
276 void AppListItemView::SetItemIsInstalling(bool is_installing) {
277 is_installing_ = is_installing;
278 if (is_installing_)
279 apps_grid_view_->EnsureViewVisible(this);
281 if (ui_state_ == UI_STATE_NORMAL) {
282 title_->SetVisible(!is_installing);
283 progress_bar_->SetVisible(is_installing);
285 SchedulePaint();
288 void AppListItemView::SetItemPercentDownloaded(int percent_downloaded) {
289 // A percent_downloaded() of -1 can mean it's not known how much percent is
290 // completed, or the download hasn't been marked complete, as is the case
291 // while an extension is being installed after being downloaded.
292 if (percent_downloaded == -1)
293 return;
294 progress_bar_->SetValue(percent_downloaded / 100.0);
297 const char* AppListItemView::GetClassName() const {
298 return kViewClassName;
301 void AppListItemView::Layout() {
302 gfx::Rect rect(GetContentsBounds());
304 const int left_right_padding =
305 title_->font_list().GetExpectedTextWidth(kLeftRightPaddingChars);
306 rect.Inset(left_right_padding, kTopPadding, left_right_padding, 0);
307 const int y = rect.y();
309 icon_->SetBoundsRect(GetIconBoundsForTargetViewBounds(GetContentsBounds()));
310 const gfx::Size title_size = title_->GetPreferredSize();
311 gfx::Rect title_bounds(rect.x() + (rect.width() - title_size.width()) / 2,
312 y + kGridIconDimension + kIconTitleSpacing,
313 title_size.width(),
314 title_size.height());
315 title_bounds.Intersect(rect);
316 title_->SetBoundsRect(title_bounds);
318 gfx::Rect progress_bar_bounds(progress_bar_->GetPreferredSize());
319 progress_bar_bounds.set_x(GetContentsBounds().x() +
320 kProgressBarHorizontalPadding);
321 progress_bar_bounds.set_y(title_bounds.y());
322 progress_bar_->SetBoundsRect(progress_bar_bounds);
325 void AppListItemView::SchedulePaintInRect(const gfx::Rect& r) {
326 SetTitleSubpixelAA();
327 views::CustomButton::SchedulePaintInRect(r);
330 void AppListItemView::OnPaint(gfx::Canvas* canvas) {
331 if (apps_grid_view_->IsDraggedView(this))
332 return;
334 gfx::Rect rect(GetContentsBounds());
335 if (is_highlighted_ && !is_installing_) {
336 canvas->FillRect(rect, kHighlightedColor);
337 return;
339 if (apps_grid_view_->IsSelectedView(this))
340 canvas->FillRect(rect, kSelectedColor);
342 if (ui_state_ == UI_STATE_DROPPING_IN_FOLDER) {
343 DCHECK(apps_grid_view_->model()->folders_enabled());
345 // Draw folder dropping preview circle.
346 gfx::Point center = gfx::Point(icon_->x() + icon_->size().width() / 2,
347 icon_->y() + icon_->size().height() / 2);
348 SkPaint paint;
349 paint.setStyle(SkPaint::kFill_Style);
350 paint.setAntiAlias(true);
351 paint.setColor(kFolderBubbleColor);
352 canvas->DrawCircle(center, kFolderPreviewRadius, paint);
356 void AppListItemView::ShowContextMenuForView(views::View* source,
357 const gfx::Point& point,
358 ui::MenuSourceType source_type) {
359 ui::MenuModel* menu_model =
360 item_weak_ ? item_weak_->GetContextMenuModel() : NULL;
361 if (!menu_model)
362 return;
364 context_menu_runner_.reset(
365 new views::MenuRunner(menu_model, views::MenuRunner::HAS_MNEMONICS));
366 if (context_menu_runner_->RunMenuAt(GetWidget(),
367 NULL,
368 gfx::Rect(point, gfx::Size()),
369 views::MENU_ANCHOR_TOPLEFT,
370 source_type) ==
371 views::MenuRunner::MENU_DELETED) {
372 return;
376 void AppListItemView::StateChanged() {
377 const bool is_folder_ui_enabled = apps_grid_view_->model()->folders_enabled();
378 if (is_folder_ui_enabled)
379 apps_grid_view_->ClearAnySelectedView();
381 if (state() == STATE_HOVERED || state() == STATE_PRESSED) {
382 if (!is_folder_ui_enabled)
383 apps_grid_view_->SetSelectedView(this);
384 title_->SetEnabledColor(kGridTitleHoverColor);
385 } else {
386 if (!is_folder_ui_enabled)
387 apps_grid_view_->ClearSelectedView(this);
388 is_highlighted_ = false;
389 if (item_weak_)
390 item_weak_->SetHighlighted(false);
391 title_->SetEnabledColor(kGridTitleColor);
393 title_->Invalidate();
396 bool AppListItemView::ShouldEnterPushedState(const ui::Event& event) {
397 // Don't enter pushed state for ET_GESTURE_TAP_DOWN so that hover gray
398 // background does not show up during scroll.
399 if (event.type() == ui::ET_GESTURE_TAP_DOWN)
400 return false;
402 return views::CustomButton::ShouldEnterPushedState(event);
405 bool AppListItemView::OnMousePressed(const ui::MouseEvent& event) {
406 CustomButton::OnMousePressed(event);
408 if (!ShouldEnterPushedState(event))
409 return true;
411 apps_grid_view_->InitiateDrag(this, AppsGridView::MOUSE, event);
413 if (apps_grid_view_->IsDraggedView(this)) {
414 mouse_drag_timer_.Start(FROM_HERE,
415 base::TimeDelta::FromMilliseconds(kMouseDragUIDelayInMs),
416 this, &AppListItemView::OnMouseDragTimer);
418 return true;
421 bool AppListItemView::OnKeyPressed(const ui::KeyEvent& event) {
422 // Disable space key to press the button. The keyboard events received
423 // by this view are forwarded from a Textfield (SearchBoxView) and key
424 // released events are not forwarded. This leaves the button in pressed
425 // state.
426 if (event.key_code() == ui::VKEY_SPACE)
427 return false;
429 return CustomButton::OnKeyPressed(event);
432 void AppListItemView::OnMouseReleased(const ui::MouseEvent& event) {
433 CustomButton::OnMouseReleased(event);
434 apps_grid_view_->EndDrag(false);
437 void AppListItemView::OnMouseCaptureLost() {
438 // We don't cancel the dag on mouse capture lost for windows as entering a
439 // synchronous drag causes mouse capture to be lost and pressing escape
440 // dismisses the app list anyway.
441 #if !defined(OS_WIN)
442 CustomButton::OnMouseCaptureLost();
443 apps_grid_view_->EndDrag(true);
444 #endif
447 bool AppListItemView::OnMouseDragged(const ui::MouseEvent& event) {
448 CustomButton::OnMouseDragged(event);
449 if (apps_grid_view_->IsDraggedView(this)) {
450 // If the drag is no longer happening, it could be because this item
451 // got removed, in which case this item has been destroyed. So, bail out
452 // now as there will be nothing else to do anyway as
453 // apps_grid_view_->dragging() will be false.
454 if (!apps_grid_view_->UpdateDragFromItem(AppsGridView::MOUSE, event))
455 return true;
458 // Shows dragging UI when it's confirmed without waiting for the timer.
459 if (ui_state_ != UI_STATE_DRAGGING &&
460 apps_grid_view_->dragging() &&
461 apps_grid_view_->IsDraggedView(this)) {
462 mouse_drag_timer_.Stop();
463 SetUIState(UI_STATE_DRAGGING);
465 return true;
468 void AppListItemView::OnGestureEvent(ui::GestureEvent* event) {
469 switch (event->type()) {
470 case ui::ET_GESTURE_SCROLL_BEGIN:
471 if (touch_dragging_) {
472 apps_grid_view_->InitiateDrag(this, AppsGridView::TOUCH, *event);
473 event->SetHandled();
475 break;
476 case ui::ET_GESTURE_SCROLL_UPDATE:
477 if (touch_dragging_ && apps_grid_view_->IsDraggedView(this)) {
478 apps_grid_view_->UpdateDragFromItem(AppsGridView::TOUCH, *event);
479 event->SetHandled();
481 break;
482 case ui::ET_GESTURE_SCROLL_END:
483 case ui::ET_SCROLL_FLING_START:
484 if (touch_dragging_) {
485 SetTouchDragging(false);
486 apps_grid_view_->EndDrag(false);
487 event->SetHandled();
489 break;
490 case ui::ET_GESTURE_LONG_PRESS:
491 if (!apps_grid_view_->has_dragged_view())
492 SetTouchDragging(true);
493 event->SetHandled();
494 break;
495 case ui::ET_GESTURE_LONG_TAP:
496 case ui::ET_GESTURE_END:
497 if (touch_dragging_)
498 SetTouchDragging(false);
499 break;
500 default:
501 break;
503 if (!event->handled())
504 CustomButton::OnGestureEvent(event);
507 void AppListItemView::OnSyncDragEnd() {
508 SetUIState(UI_STATE_NORMAL);
511 const gfx::Rect& AppListItemView::GetIconBounds() const {
512 return icon_->bounds();
515 void AppListItemView::SetDragUIState() {
516 SetUIState(UI_STATE_DRAGGING);
519 gfx::Rect AppListItemView::GetIconBoundsForTargetViewBounds(
520 const gfx::Rect& target_bounds) {
521 gfx::Rect rect(target_bounds);
523 const int left_right_padding =
524 title_->font_list().GetExpectedTextWidth(kLeftRightPaddingChars);
525 rect.Inset(left_right_padding, kTopPadding, left_right_padding, 0);
527 gfx::Rect icon_bounds(rect.x(), rect.y(), rect.width(), kGridIconDimension);
528 icon_bounds.Inset(gfx::ShadowValue::GetMargin(GetIconShadows()));
529 return icon_bounds;
532 void AppListItemView::ItemIconChanged() {
533 SetIcon(item_weak_->icon(), item_weak_->has_shadow());
536 void AppListItemView::ItemNameChanged() {
537 SetItemName(base::UTF8ToUTF16(item_weak_->GetDisplayName()),
538 base::UTF8ToUTF16(item_weak_->name()));
541 void AppListItemView::ItemHighlightedChanged() {
542 SetItemIsHighlighted(item_weak_->highlighted());
545 void AppListItemView::ItemIsInstallingChanged() {
546 SetItemIsInstalling(item_weak_->is_installing());
549 void AppListItemView::ItemPercentDownloadedChanged() {
550 SetItemPercentDownloaded(item_weak_->percent_downloaded());
553 void AppListItemView::ItemBeingDestroyed() {
554 DCHECK(item_weak_);
555 item_weak_->RemoveObserver(this);
556 item_weak_ = NULL;
559 } // namespace app_list