Files.app: Use core-icon for the dropdown button of combobutton.
[chromium-blink-merge.git] / ui / app_list / views / app_list_item_view.cc
blob0215149ff9dceae32bcae3080426d06b7dc1c59d
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/profiler/scoped_tracker.h"
10 #include "base/strings/utf_string_conversions.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/base/ui_base_switches_util.h"
22 #include "ui/compositor/layer.h"
23 #include "ui/compositor/scoped_layer_animation_settings.h"
24 #include "ui/gfx/animation/throb_animation.h"
25 #include "ui/gfx/canvas.h"
26 #include "ui/gfx/font_list.h"
27 #include "ui/gfx/geometry/point.h"
28 #include "ui/gfx/geometry/vector2d.h"
29 #include "ui/gfx/image/image_skia_operations.h"
30 #include "ui/gfx/shadow_value.h"
31 #include "ui/gfx/transform_util.h"
32 #include "ui/strings/grit/ui_strings.h"
33 #include "ui/views/background.h"
34 #include "ui/views/controls/image_view.h"
35 #include "ui/views/controls/label.h"
36 #include "ui/views/controls/menu/menu_runner.h"
37 #include "ui/views/drag_controller.h"
39 namespace app_list {
41 namespace {
43 const int kTopPadding = 18;
44 const int kIconTitleSpacing = 6;
45 const int kProgressBarHorizontalPadding = 12;
47 // Radius of the folder dropping preview circle.
48 const int kFolderPreviewRadius = 40;
50 const int kLeftRightPaddingChars = 1;
52 // Scale to transform the icon when a drag starts.
53 const float kDraggingIconScale = 1.5f;
55 // Delay in milliseconds of when the dragging UI should be shown for mouse drag.
56 const int kMouseDragUIDelayInMs = 200;
58 const gfx::ShadowValues& GetIconShadows() {
59 CR_DEFINE_STATIC_LOCAL(const gfx::ShadowValues, icon_shadows,
60 (1, gfx::ShadowValue(gfx::Vector2d(0, 2), 2,
61 SkColorSetARGB(0x24, 0, 0, 0))));
62 return icon_shadows;
65 gfx::FontList GetFontList() {
66 ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
67 const gfx::FontList& font_list = rb.GetFontList(kItemTextFontStyle);
68 // The font is different on each platform. The font size is adjusted on some
69 // platforms to keep a consistent look.
70 #if defined(OS_LINUX) && !defined(OS_CHROMEOS)
71 // Reducing the font size by 2 makes it the same as the Windows font size.
72 const int kFontSizeDelta = -2;
73 return font_list.DeriveWithSizeDelta(kFontSizeDelta);
74 #else
75 return font_list;
76 #endif
79 } // namespace
81 // static
82 const char AppListItemView::kViewClassName[] = "ui/app_list/AppListItemView";
84 AppListItemView::AppListItemView(AppsGridView* apps_grid_view,
85 AppListItem* item)
86 : CustomButton(apps_grid_view),
87 is_folder_(item->GetItemType() == AppListFolderItem::kItemType),
88 is_in_folder_(item->IsInFolder()),
89 item_weak_(item),
90 apps_grid_view_(apps_grid_view),
91 icon_(new views::ImageView),
92 title_(new CachedLabel),
93 progress_bar_(new ProgressBarView),
94 ui_state_(UI_STATE_NORMAL),
95 touch_dragging_(false),
96 is_installing_(false),
97 is_highlighted_(false) {
98 icon_->set_interactive(false);
100 title_->SetBackgroundColor(0);
101 title_->SetAutoColorReadabilityEnabled(false);
102 title_->SetEnabledColor(kGridTitleColor);
103 title_->SetHandlesTooltips(false);
105 static const gfx::FontList font_list = GetFontList();
106 title_->SetFontList(font_list);
107 title_->SetHorizontalAlignment(gfx::ALIGN_LEFT);
108 title_->Invalidate();
109 SetTitleSubpixelAA();
111 AddChildView(icon_);
112 AddChildView(title_);
113 AddChildView(progress_bar_);
115 SetIcon(item->icon(), item->has_shadow());
116 SetItemName(base::UTF8ToUTF16(item->GetDisplayName()),
117 base::UTF8ToUTF16(item->name()));
118 SetItemIsInstalling(item->is_installing());
119 SetItemIsHighlighted(item->highlighted());
120 item->AddObserver(this);
122 set_context_menu_controller(this);
123 set_request_focus_on_press(false);
125 SetAnimationDuration(0);
128 AppListItemView::~AppListItemView() {
129 if (item_weak_)
130 item_weak_->RemoveObserver(this);
133 void AppListItemView::SetIcon(const gfx::ImageSkia& icon, bool has_shadow) {
134 // Clear icon and bail out if item icon is empty.
135 if (icon.isNull()) {
136 icon_->SetImage(NULL);
137 return;
140 gfx::ImageSkia resized(gfx::ImageSkiaOperations::CreateResizedImage(
141 icon,
142 skia::ImageOperations::RESIZE_BEST,
143 gfx::Size(kGridIconDimension, kGridIconDimension)));
144 if (has_shadow) {
145 gfx::ImageSkia shadow(gfx::ImageSkiaOperations::CreateImageWithDropShadow(
146 resized, GetIconShadows()));
147 icon_->SetImage(shadow);
148 return;
151 icon_->SetImage(resized);
154 void AppListItemView::SetUIState(UIState state) {
155 if (ui_state_ == state)
156 return;
158 ui_state_ = state;
160 switch (ui_state_) {
161 case UI_STATE_NORMAL:
162 title_->SetVisible(!is_installing_);
163 progress_bar_->SetVisible(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;
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;
185 case UI_STATE_DROPPING_IN_FOLDER:
186 break;
188 #endif // !OS_WIN
190 SetTitleSubpixelAA();
191 SchedulePaint();
194 void AppListItemView::SetTouchDragging(bool touch_dragging) {
195 if (touch_dragging_ == touch_dragging)
196 return;
198 touch_dragging_ = touch_dragging;
199 SetState(STATE_NORMAL);
200 SetUIState(touch_dragging_ ? UI_STATE_DRAGGING : UI_STATE_NORMAL);
203 void AppListItemView::OnMouseDragTimer() {
204 DCHECK(apps_grid_view_->IsDraggedView(this));
205 SetUIState(UI_STATE_DRAGGING);
208 void AppListItemView::Prerender() {
209 title_->PaintToBackingImage();
212 void AppListItemView::CancelContextMenu() {
213 if (context_menu_runner_)
214 context_menu_runner_->Cancel();
217 gfx::ImageSkia AppListItemView::GetDragImage() {
218 return icon_->GetImage();
221 void AppListItemView::OnDragEnded() {
222 mouse_drag_timer_.Stop();
223 SetUIState(UI_STATE_NORMAL);
226 gfx::Point AppListItemView::GetDragImageOffset() {
227 gfx::Point image = icon_->GetImageBounds().origin();
228 return gfx::Point(icon_->x() + image.x(), icon_->y() + image.y());
231 void AppListItemView::SetAsAttemptedFolderTarget(bool is_target_folder) {
232 if (is_target_folder)
233 SetUIState(UI_STATE_DROPPING_IN_FOLDER);
234 else
235 SetUIState(UI_STATE_NORMAL);
238 void AppListItemView::SetItemName(const base::string16& display_name,
239 const base::string16& full_name) {
240 title_->SetText(display_name);
241 title_->Invalidate();
243 tooltip_text_ = display_name == full_name ? base::string16() : full_name;
245 // Use full name for accessibility.
246 SetAccessibleName(
247 is_folder_ ? l10n_util::GetStringFUTF16(
248 IDS_APP_LIST_FOLDER_BUTTON_ACCESSIBILE_NAME, full_name)
249 : full_name);
250 Layout();
253 void AppListItemView::SetItemIsHighlighted(bool is_highlighted) {
254 is_highlighted_ = is_highlighted;
255 SetTitleSubpixelAA();
256 SchedulePaint();
259 void AppListItemView::SetItemIsInstalling(bool is_installing) {
260 is_installing_ = is_installing;
261 if (ui_state_ == UI_STATE_NORMAL) {
262 title_->SetVisible(!is_installing);
263 progress_bar_->SetVisible(is_installing);
265 SetTitleSubpixelAA();
266 SchedulePaint();
269 void AppListItemView::SetItemPercentDownloaded(int percent_downloaded) {
270 // A percent_downloaded() of -1 can mean it's not known how much percent is
271 // completed, or the download hasn't been marked complete, as is the case
272 // while an extension is being installed after being downloaded.
273 if (percent_downloaded == -1)
274 return;
275 progress_bar_->SetValue(percent_downloaded / 100.0);
278 const char* AppListItemView::GetClassName() const {
279 return kViewClassName;
282 void AppListItemView::Layout() {
283 // TODO(vadimt): Remove ScopedTracker below once crbug.com/431326 is fixed.
284 tracked_objects::ScopedTracker tracking_profile1(
285 FROM_HERE_WITH_EXPLICIT_FUNCTION("431326 AppListItemView::Layout1"));
287 gfx::Rect rect(GetContentsBounds());
289 const int left_right_padding =
290 title_->font_list().GetExpectedTextWidth(kLeftRightPaddingChars);
291 rect.Inset(left_right_padding, kTopPadding, left_right_padding, 0);
292 const int y = rect.y();
294 icon_->SetBoundsRect(GetIconBoundsForTargetViewBounds(GetContentsBounds()));
296 // TODO(vadimt): Remove ScopedTracker below once crbug.com/431326 is fixed.
297 tracked_objects::ScopedTracker tracking_profile2(
298 FROM_HERE_WITH_EXPLICIT_FUNCTION("431326 AppListItemView::Layout2"));
300 const gfx::Size title_size = title_->GetPreferredSize();
302 // TODO(vadimt): Remove ScopedTracker below once crbug.com/431326 is fixed.
303 tracked_objects::ScopedTracker tracking_profile3(
304 FROM_HERE_WITH_EXPLICIT_FUNCTION("431326 AppListItemView::Layout3"));
306 gfx::Rect title_bounds(rect.x() + (rect.width() - title_size.width()) / 2,
307 y + kGridIconDimension + kIconTitleSpacing,
308 title_size.width(),
309 title_size.height());
310 title_bounds.Intersect(rect);
311 title_->SetBoundsRect(title_bounds);
312 SetTitleSubpixelAA();
314 gfx::Rect progress_bar_bounds(progress_bar_->GetPreferredSize());
315 progress_bar_bounds.set_x(GetContentsBounds().x() +
316 kProgressBarHorizontalPadding);
317 progress_bar_bounds.set_y(title_bounds.y());
318 progress_bar_->SetBoundsRect(progress_bar_bounds);
321 void AppListItemView::OnPaint(gfx::Canvas* canvas) {
322 if (apps_grid_view_->IsDraggedView(this))
323 return;
325 gfx::Rect rect(GetContentsBounds());
326 if (apps_grid_view_->IsSelectedView(this)) {
327 canvas->FillRect(rect, kSelectedColor);
328 } else if (is_highlighted_ && !is_installing_) {
329 canvas->FillRect(rect, kHighlightedColor);
330 return;
333 if (ui_state_ == UI_STATE_DROPPING_IN_FOLDER) {
334 DCHECK(apps_grid_view_->model()->folders_enabled());
336 // Draw folder dropping preview circle.
337 gfx::Point center = gfx::Point(icon_->x() + icon_->size().width() / 2,
338 icon_->y() + icon_->size().height() / 2);
339 SkPaint paint;
340 paint.setStyle(SkPaint::kFill_Style);
341 paint.setAntiAlias(true);
342 paint.setColor(kFolderBubbleColor);
343 canvas->DrawCircle(center, kFolderPreviewRadius, paint);
347 void AppListItemView::ShowContextMenuForView(views::View* source,
348 const gfx::Point& point,
349 ui::MenuSourceType source_type) {
350 ui::MenuModel* menu_model =
351 item_weak_ ? item_weak_->GetContextMenuModel() : NULL;
352 if (!menu_model)
353 return;
355 if (!apps_grid_view_->IsSelectedView(this))
356 apps_grid_view_->ClearAnySelectedView();
357 context_menu_runner_.reset(
358 new views::MenuRunner(menu_model, views::MenuRunner::HAS_MNEMONICS));
359 if (context_menu_runner_->RunMenuAt(GetWidget(),
360 NULL,
361 gfx::Rect(point, gfx::Size()),
362 views::MENU_ANCHOR_TOPLEFT,
363 source_type) ==
364 views::MenuRunner::MENU_DELETED) {
365 return;
369 void AppListItemView::StateChanged() {
370 if (state() == STATE_HOVERED || state() == STATE_PRESSED) {
371 // Show the hover/tap highlight: for tap, lighter highlight replaces darker
372 // keyboard selection; for mouse hover, keyboard selection takes precedence.
373 if (!apps_grid_view_->IsSelectedView(this) || state() == STATE_PRESSED)
374 SetItemIsHighlighted(true);
375 title_->SetEnabledColor(kGridTitleHoverColor);
376 } else {
377 SetItemIsHighlighted(false);
378 if (item_weak_)
379 item_weak_->set_highlighted(false);
380 title_->SetEnabledColor(kGridTitleColor);
382 SetTitleSubpixelAA();
385 bool AppListItemView::ShouldEnterPushedState(const ui::Event& event) {
386 // Don't enter pushed state for ET_GESTURE_TAP_DOWN so that hover gray
387 // background does not show up during scroll.
388 if (event.type() == ui::ET_GESTURE_TAP_DOWN)
389 return false;
391 return views::CustomButton::ShouldEnterPushedState(event);
394 bool AppListItemView::OnMousePressed(const ui::MouseEvent& event) {
395 CustomButton::OnMousePressed(event);
397 if (!ShouldEnterPushedState(event))
398 return true;
400 apps_grid_view_->InitiateDrag(this, AppsGridView::MOUSE, event);
402 if (apps_grid_view_->IsDraggedView(this)) {
403 mouse_drag_timer_.Start(FROM_HERE,
404 base::TimeDelta::FromMilliseconds(kMouseDragUIDelayInMs),
405 this, &AppListItemView::OnMouseDragTimer);
407 return true;
410 bool AppListItemView::OnKeyPressed(const ui::KeyEvent& event) {
411 // Disable space key to press the button. The keyboard events received
412 // by this view are forwarded from a Textfield (SearchBoxView) and key
413 // released events are not forwarded. This leaves the button in pressed
414 // state.
415 if (event.key_code() == ui::VKEY_SPACE)
416 return false;
418 return CustomButton::OnKeyPressed(event);
421 void AppListItemView::OnMouseReleased(const ui::MouseEvent& event) {
422 CustomButton::OnMouseReleased(event);
423 apps_grid_view_->EndDrag(false);
426 void AppListItemView::OnMouseCaptureLost() {
427 // We don't cancel the dag on mouse capture lost for windows as entering a
428 // synchronous drag causes mouse capture to be lost and pressing escape
429 // dismisses the app list anyway.
430 #if !defined(OS_WIN)
431 CustomButton::OnMouseCaptureLost();
432 apps_grid_view_->EndDrag(true);
433 #endif
436 bool AppListItemView::OnMouseDragged(const ui::MouseEvent& event) {
437 CustomButton::OnMouseDragged(event);
438 if (apps_grid_view_->IsDraggedView(this)) {
439 // If the drag is no longer happening, it could be because this item
440 // got removed, in which case this item has been destroyed. So, bail out
441 // now as there will be nothing else to do anyway as
442 // apps_grid_view_->dragging() will be false.
443 if (!apps_grid_view_->UpdateDragFromItem(AppsGridView::MOUSE, event))
444 return true;
447 if (!apps_grid_view_->IsSelectedView(this))
448 apps_grid_view_->ClearAnySelectedView();
450 // Shows dragging UI when it's confirmed without waiting for the timer.
451 if (ui_state_ != UI_STATE_DRAGGING &&
452 apps_grid_view_->dragging() &&
453 apps_grid_view_->IsDraggedView(this)) {
454 mouse_drag_timer_.Stop();
455 SetUIState(UI_STATE_DRAGGING);
457 return true;
460 void AppListItemView::OnGestureEvent(ui::GestureEvent* event) {
461 switch (event->type()) {
462 case ui::ET_GESTURE_SCROLL_BEGIN:
463 if (touch_dragging_) {
464 apps_grid_view_->InitiateDrag(this, AppsGridView::TOUCH, *event);
465 event->SetHandled();
467 break;
468 case ui::ET_GESTURE_SCROLL_UPDATE:
469 if (touch_dragging_ && apps_grid_view_->IsDraggedView(this)) {
470 apps_grid_view_->UpdateDragFromItem(AppsGridView::TOUCH, *event);
471 event->SetHandled();
473 break;
474 case ui::ET_GESTURE_SCROLL_END:
475 case ui::ET_SCROLL_FLING_START:
476 if (touch_dragging_) {
477 SetTouchDragging(false);
478 apps_grid_view_->EndDrag(false);
479 event->SetHandled();
481 break;
482 case ui::ET_GESTURE_TAP_DOWN:
483 if (switches::IsTouchFeedbackEnabled() && state_ != STATE_DISABLED) {
484 SetState(STATE_PRESSED);
485 event->SetHandled();
487 break;
488 case ui::ET_GESTURE_TAP:
489 case ui::ET_GESTURE_TAP_CANCEL:
490 if (switches::IsTouchFeedbackEnabled() && state_ != STATE_DISABLED)
491 SetState(STATE_NORMAL);
492 break;
493 case ui::ET_GESTURE_LONG_PRESS:
494 if (!apps_grid_view_->has_dragged_view())
495 SetTouchDragging(true);
496 event->SetHandled();
497 break;
498 case ui::ET_GESTURE_LONG_TAP:
499 case ui::ET_GESTURE_END:
500 if (touch_dragging_)
501 SetTouchDragging(false);
502 break;
503 default:
504 break;
506 if (!event->handled())
507 CustomButton::OnGestureEvent(event);
510 bool AppListItemView::GetTooltipText(const gfx::Point& p,
511 base::string16* tooltip) const {
512 // Use the label to generate a tooltip, so that it will consider its text
513 // truncation in making the tooltip. We do not want the label itself to have a
514 // tooltip, so we only temporarily enable it to get the tooltip text from the
515 // label, then disable it again.
516 title_->SetHandlesTooltips(true);
517 title_->SetTooltipText(tooltip_text_);
518 bool handled = title_->GetTooltipText(p, tooltip);
519 title_->SetHandlesTooltips(false);
520 return handled;
523 void AppListItemView::OnSyncDragEnd() {
524 SetUIState(UI_STATE_NORMAL);
527 const gfx::Rect& AppListItemView::GetIconBounds() const {
528 return icon_->bounds();
531 void AppListItemView::SetDragUIState() {
532 SetUIState(UI_STATE_DRAGGING);
535 gfx::Rect AppListItemView::GetIconBoundsForTargetViewBounds(
536 const gfx::Rect& target_bounds) {
537 gfx::Rect rect(target_bounds);
539 const int left_right_padding =
540 title_->font_list().GetExpectedTextWidth(kLeftRightPaddingChars);
541 rect.Inset(left_right_padding, kTopPadding, left_right_padding, 0);
543 gfx::Rect icon_bounds(rect.x(), rect.y(), rect.width(), kGridIconDimension);
544 icon_bounds.Inset(gfx::ShadowValue::GetMargin(GetIconShadows()));
545 return icon_bounds;
548 void AppListItemView::SetTitleSubpixelAA() {
549 // TODO(tapted): Enable AA for folders as well, taking care to play nice with
550 // the folder bubble animation.
551 bool enable_aa = !is_in_folder_ && ui_state_ == UI_STATE_NORMAL &&
552 !is_highlighted_ && !apps_grid_view_->IsSelectedView(this) &&
553 !apps_grid_view_->IsAnimatingView(this);
555 title_->SetSubpixelRenderingEnabled(enable_aa);
556 if (enable_aa) {
557 title_->SetBackgroundColor(app_list::kLabelBackgroundColor);
558 title_->set_background(views::Background::CreateSolidBackground(
559 app_list::kLabelBackgroundColor));
560 } else {
561 // In other cases, keep the background transparent to ensure correct
562 // interactions with animations. This will temporarily disable subpixel AA.
563 title_->SetBackgroundColor(0);
564 title_->set_background(NULL);
566 title_->Invalidate();
567 title_->SchedulePaint();
570 void AppListItemView::ItemIconChanged() {
571 SetIcon(item_weak_->icon(), item_weak_->has_shadow());
574 void AppListItemView::ItemNameChanged() {
575 SetItemName(base::UTF8ToUTF16(item_weak_->GetDisplayName()),
576 base::UTF8ToUTF16(item_weak_->name()));
579 void AppListItemView::ItemIsInstallingChanged() {
580 SetItemIsInstalling(item_weak_->is_installing());
583 void AppListItemView::ItemPercentDownloadedChanged() {
584 SetItemPercentDownloaded(item_weak_->percent_downloaded());
587 void AppListItemView::ItemBeingDestroyed() {
588 DCHECK(item_weak_);
589 item_weak_->RemoveObserver(this);
590 item_weak_ = NULL;
593 } // namespace app_list