Update path of checkdeps to buildtools checkout
[chromium-blink-merge.git] / ui / app_list / views / app_list_item_view.cc
blob2364a79502342f7a3e1c3792ed51fb873b4a7c11
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 "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/app_list_switches.h"
16 #include "ui/app_list/views/apps_grid_view.h"
17 #include "ui/app_list/views/cached_label.h"
18 #include "ui/app_list/views/progress_bar_view.h"
19 #include "ui/base/dragdrop/drag_utils.h"
20 #include "ui/base/l10n/l10n_util.h"
21 #include "ui/base/resource/resource_bundle.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/image/image_skia_operations.h"
28 #include "ui/gfx/point.h"
29 #include "ui/gfx/transform_util.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 } // namespace
66 // static
67 const char AppListItemView::kViewClassName[] = "ui/app_list/AppListItemView";
69 AppListItemView::AppListItemView(AppsGridView* apps_grid_view,
70 AppListItem* item)
71 : CustomButton(apps_grid_view),
72 item_(item),
73 apps_grid_view_(apps_grid_view),
74 icon_(new views::ImageView),
75 title_(new CachedLabel),
76 progress_bar_(new ProgressBarView),
77 ui_state_(UI_STATE_NORMAL),
78 touch_dragging_(false) {
79 icon_->set_interactive(false);
81 ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
82 title_->SetBackgroundColor(0);
83 title_->SetAutoColorReadabilityEnabled(false);
84 title_->SetEnabledColor(kGridTitleColor);
85 title_->SetFontList(
86 rb.GetFontList(kItemTextFontStyle).DeriveWithSizeDelta(kFontSizeDelta));
87 title_->SetHorizontalAlignment(gfx::ALIGN_LEFT);
88 title_->SetVisible(!item_->is_installing());
89 title_->Invalidate();
90 SetTitleSubpixelAA();
92 const gfx::ShadowValue kIconShadows[] = {
93 gfx::ShadowValue(gfx::Point(0, 2), 2, SkColorSetARGB(0x24, 0, 0, 0)),
95 icon_shadows_.assign(kIconShadows, kIconShadows + arraysize(kIconShadows));
97 AddChildView(icon_);
98 AddChildView(title_);
99 AddChildView(progress_bar_);
101 ItemIconChanged();
102 ItemNameChanged();
103 ItemIsInstallingChanged();
104 item_->AddObserver(this);
106 set_context_menu_controller(this);
107 set_request_focus_on_press(false);
109 SetAnimationDuration(0);
112 AppListItemView::~AppListItemView() {
113 item_->RemoveObserver(this);
116 void AppListItemView::SetIconSize(const gfx::Size& size) {
117 if (icon_size_ == size)
118 return;
120 icon_size_ = size;
121 UpdateIcon();
124 void AppListItemView::UpdateIcon() {
125 // Skip if |icon_size_| has not been determined.
126 if (icon_size_.IsEmpty())
127 return;
129 gfx::ImageSkia icon = item_->icon();
130 // Clear icon and bail out if item icon is empty.
131 if (icon.isNull()) {
132 icon_->SetImage(NULL);
133 return;
136 gfx::ImageSkia resized(gfx::ImageSkiaOperations::CreateResizedImage(icon,
137 skia::ImageOperations::RESIZE_BEST, icon_size_));
138 if (item_->has_shadow()) {
139 gfx::ImageSkia shadow(
140 gfx::ImageSkiaOperations::CreateImageWithDropShadow(resized,
141 icon_shadows_));
142 icon_->SetImage(shadow);
143 return;
146 icon_->SetImage(resized);
149 void AppListItemView::UpdateTooltip() {
150 std::string display_name = item_->GetDisplayName();
151 title_->SetTooltipText(display_name == item_->name() ? base::string16()
152 : base::UTF8ToUTF16(item_->name()));
155 void AppListItemView::SetUIState(UIState state) {
156 if (ui_state_ == state)
157 return;
159 ui_state_ = state;
161 switch (ui_state_) {
162 case UI_STATE_NORMAL:
163 title_->SetVisible(!item_->is_installing());
164 progress_bar_->SetVisible(item_->is_installing());
165 break;
166 case UI_STATE_DRAGGING:
167 title_->SetVisible(false);
168 progress_bar_->SetVisible(false);
169 break;
170 case UI_STATE_DROPPING_IN_FOLDER:
171 break;
173 #if !defined(OS_WIN)
174 ui::ScopedLayerAnimationSettings settings(layer()->GetAnimator());
175 switch (ui_state_) {
176 case UI_STATE_NORMAL:
177 layer()->SetTransform(gfx::Transform());
178 break;
179 case UI_STATE_DRAGGING: {
180 const gfx::Rect bounds(layer()->bounds().size());
181 layer()->SetTransform(gfx::GetScaleTransform(
182 bounds.CenterPoint(),
183 kDraggingIconScale));
184 break;
186 case UI_STATE_DROPPING_IN_FOLDER:
187 break;
189 #endif // !OS_WIN
191 SchedulePaint();
194 void AppListItemView::SetTouchDragging(bool touch_dragging) {
195 if (touch_dragging_ == touch_dragging)
196 return;
198 touch_dragging_ = touch_dragging;
199 SetUIState(touch_dragging_ ? UI_STATE_DRAGGING : UI_STATE_NORMAL);
202 void AppListItemView::OnMouseDragTimer() {
203 DCHECK(apps_grid_view_->IsDraggedView(this));
204 SetUIState(UI_STATE_DRAGGING);
207 void AppListItemView::SetTitleSubpixelAA() {
208 // TODO(tapted): Enable AA for folders as well, taking care to play nice with
209 // the folder bubble animation.
210 bool enable_aa = !item_->IsInFolder() && ui_state_ == UI_STATE_NORMAL &&
211 !item_->highlighted() &&
212 !apps_grid_view_->IsSelectedView(this) &&
213 !apps_grid_view_->IsAnimatingView(this);
215 bool currently_enabled = title_->background() != NULL;
216 if (currently_enabled == enable_aa)
217 return;
219 if (enable_aa) {
220 title_->SetBackgroundColor(app_list::kContentsBackgroundColor);
221 title_->set_background(views::Background::CreateSolidBackground(
222 app_list::kContentsBackgroundColor));
223 } else {
224 // In other cases, keep the background transparent to ensure correct
225 // interactions with animations. This will temporarily disable subpixel AA.
226 title_->SetBackgroundColor(0);
227 title_->set_background(NULL);
229 title_->Invalidate();
230 title_->SchedulePaint();
233 void AppListItemView::Prerender() {
234 title_->PaintToBackingImage();
237 void AppListItemView::CancelContextMenu() {
238 if (context_menu_runner_)
239 context_menu_runner_->Cancel();
242 gfx::ImageSkia AppListItemView::GetDragImage() {
243 return icon_->GetImage();
246 void AppListItemView::OnDragEnded() {
247 mouse_drag_timer_.Stop();
248 SetUIState(UI_STATE_NORMAL);
251 gfx::Point AppListItemView::GetDragImageOffset() {
252 gfx::Point image = icon_->GetImageBounds().origin();
253 return gfx::Point(icon_->x() + image.x(), icon_->y() + image.y());
256 void AppListItemView::SetAsAttemptedFolderTarget(bool is_target_folder) {
257 if (is_target_folder)
258 SetUIState(UI_STATE_DROPPING_IN_FOLDER);
259 else
260 SetUIState(UI_STATE_NORMAL);
263 void AppListItemView::ItemIconChanged() {
264 UpdateIcon();
267 void AppListItemView::ItemNameChanged() {
268 title_->SetText(base::UTF8ToUTF16(item_->GetDisplayName()));
269 title_->Invalidate();
270 UpdateTooltip();
271 // Use full name for accessibility.
272 SetAccessibleName(item_->GetItemType() == AppListFolderItem::kItemType
273 ? l10n_util::GetStringFUTF16(
274 IDS_APP_LIST_FOLDER_BUTTON_ACCESSIBILE_NAME,
275 base::UTF8ToUTF16(item_->name()))
276 : base::UTF8ToUTF16(item_->name()));
277 Layout();
280 void AppListItemView::ItemHighlightedChanged() {
281 apps_grid_view_->EnsureViewVisible(this);
282 SchedulePaint();
285 void AppListItemView::ItemIsInstallingChanged() {
286 if (item_->is_installing())
287 apps_grid_view_->EnsureViewVisible(this);
288 title_->SetVisible(!item_->is_installing());
289 progress_bar_->SetVisible(item_->is_installing());
290 SchedulePaint();
293 void AppListItemView::ItemPercentDownloadedChanged() {
294 // A percent_downloaded() of -1 can mean it's not known how much percent is
295 // completed, or the download hasn't been marked complete, as is the case
296 // while an extension is being installed after being downloaded.
297 if (item_->percent_downloaded() == -1)
298 return;
299 progress_bar_->SetValue(item_->percent_downloaded() / 100.0);
302 const char* AppListItemView::GetClassName() const {
303 return kViewClassName;
306 void AppListItemView::Layout() {
307 gfx::Rect rect(GetContentsBounds());
309 const int left_right_padding =
310 title_->font_list().GetExpectedTextWidth(kLeftRightPaddingChars);
311 rect.Inset(left_right_padding, kTopPadding, left_right_padding, 0);
312 const int y = rect.y();
314 icon_->SetBoundsRect(GetIconBoundsForTargetViewBounds(GetContentsBounds()));
315 const gfx::Size title_size = title_->GetPreferredSize();
316 gfx::Rect title_bounds(rect.x() + (rect.width() - title_size.width()) / 2,
317 y + icon_size_.height() + kIconTitleSpacing,
318 title_size.width(),
319 title_size.height());
320 title_bounds.Intersect(rect);
321 title_->SetBoundsRect(title_bounds);
323 gfx::Rect progress_bar_bounds(progress_bar_->GetPreferredSize());
324 progress_bar_bounds.set_x(GetContentsBounds().x() +
325 kProgressBarHorizontalPadding);
326 progress_bar_bounds.set_y(title_bounds.y());
327 progress_bar_->SetBoundsRect(progress_bar_bounds);
330 void AppListItemView::SchedulePaintInRect(const gfx::Rect& r) {
331 SetTitleSubpixelAA();
332 views::CustomButton::SchedulePaintInRect(r);
335 void AppListItemView::OnPaint(gfx::Canvas* canvas) {
336 if (apps_grid_view_->IsDraggedView(this))
337 return;
339 gfx::Rect rect(GetContentsBounds());
340 if (item_->highlighted() && !item_->is_installing()) {
341 canvas->FillRect(rect, kHighlightedColor);
342 return;
343 } else if (apps_grid_view_->IsSelectedView(this)) {
344 canvas->FillRect(rect, kSelectedColor);
347 if (!switches::IsFolderUIEnabled()) {
348 if (apps_grid_view_->IsSelectedView(this)) {
349 canvas->FillRect(rect, kSelectedColor);
350 } else if (state() == STATE_HOVERED || state() == STATE_PRESSED) {
351 canvas->FillRect(rect, kHighlightedColor);
353 } else if (ui_state_ == UI_STATE_DROPPING_IN_FOLDER) {
354 // Draw folder dropping preview circle.
355 gfx::Point center = gfx::Point(icon_->x() + icon_->size().width() / 2,
356 icon_->y() + icon_->size().height() / 2);
357 SkPaint paint;
358 paint.setStyle(SkPaint::kFill_Style);
359 paint.setAntiAlias(true);
360 paint.setColor(kFolderBubbleColor);
361 canvas->DrawCircle(center, kFolderPreviewRadius, paint);
365 void AppListItemView::ShowContextMenuForView(views::View* source,
366 const gfx::Point& point,
367 ui::MenuSourceType source_type) {
368 ui::MenuModel* menu_model = item_->GetContextMenuModel();
369 if (!menu_model)
370 return;
372 context_menu_runner_.reset(new views::MenuRunner(menu_model));
373 if (context_menu_runner_->RunMenuAt(GetWidget(),
374 NULL,
375 gfx::Rect(point, gfx::Size()),
376 views::MENU_ANCHOR_TOPLEFT,
377 source_type,
378 views::MenuRunner::HAS_MNEMONICS) ==
379 views::MenuRunner::MENU_DELETED) {
380 return;
384 void AppListItemView::StateChanged() {
385 const bool is_folder_ui_enabled = switches::IsFolderUIEnabled();
386 if (is_folder_ui_enabled)
387 apps_grid_view_->ClearAnySelectedView();
389 if (state() == STATE_HOVERED || state() == STATE_PRESSED) {
390 if (!is_folder_ui_enabled)
391 apps_grid_view_->SetSelectedView(this);
392 title_->SetEnabledColor(kGridTitleHoverColor);
393 } else {
394 if (!is_folder_ui_enabled)
395 apps_grid_view_->ClearSelectedView(this);
396 item_->SetHighlighted(false);
397 title_->SetEnabledColor(kGridTitleColor);
399 title_->Invalidate();
402 bool AppListItemView::ShouldEnterPushedState(const ui::Event& event) {
403 // Don't enter pushed state for ET_GESTURE_TAP_DOWN so that hover gray
404 // background does not show up during scroll.
405 if (event.type() == ui::ET_GESTURE_TAP_DOWN)
406 return false;
408 return views::CustomButton::ShouldEnterPushedState(event);
411 bool AppListItemView::OnMousePressed(const ui::MouseEvent& event) {
412 CustomButton::OnMousePressed(event);
414 if (!ShouldEnterPushedState(event))
415 return true;
417 apps_grid_view_->InitiateDrag(this, AppsGridView::MOUSE, event);
419 if (apps_grid_view_->IsDraggedView(this)) {
420 mouse_drag_timer_.Start(FROM_HERE,
421 base::TimeDelta::FromMilliseconds(kMouseDragUIDelayInMs),
422 this, &AppListItemView::OnMouseDragTimer);
424 return true;
427 bool AppListItemView::OnKeyPressed(const ui::KeyEvent& event) {
428 // Disable space key to press the button. The keyboard events received
429 // by this view are forwarded from a Textfield (SearchBoxView) and key
430 // released events are not forwarded. This leaves the button in pressed
431 // state.
432 if (event.key_code() == ui::VKEY_SPACE)
433 return false;
435 return CustomButton::OnKeyPressed(event);
438 void AppListItemView::OnMouseReleased(const ui::MouseEvent& event) {
439 CustomButton::OnMouseReleased(event);
440 apps_grid_view_->EndDrag(false);
443 void AppListItemView::OnMouseCaptureLost() {
444 // We don't cancel the dag on mouse capture lost for windows as entering a
445 // synchronous drag causes mouse capture to be lost and pressing escape
446 // dismisses the app list anyway.
447 #if !defined(OS_WIN)
448 CustomButton::OnMouseCaptureLost();
449 apps_grid_view_->EndDrag(true);
450 #endif
453 bool AppListItemView::OnMouseDragged(const ui::MouseEvent& event) {
454 CustomButton::OnMouseDragged(event);
455 if (apps_grid_view_->IsDraggedView(this)) {
456 // If the drag is no longer happening, it could be because this item
457 // got removed, in which case this item has been destroyed. So, bail out
458 // now as there will be nothing else to do anyway as
459 // apps_grid_view_->dragging() will be false.
460 if (!apps_grid_view_->UpdateDragFromItem(AppsGridView::MOUSE, event))
461 return true;
464 // Shows dragging UI when it's confirmed without waiting for the timer.
465 if (ui_state_ != UI_STATE_DRAGGING &&
466 apps_grid_view_->dragging() &&
467 apps_grid_view_->IsDraggedView(this)) {
468 mouse_drag_timer_.Stop();
469 SetUIState(UI_STATE_DRAGGING);
471 return true;
474 void AppListItemView::OnGestureEvent(ui::GestureEvent* event) {
475 switch (event->type()) {
476 case ui::ET_GESTURE_SCROLL_BEGIN:
477 if (touch_dragging_) {
478 apps_grid_view_->InitiateDrag(this, AppsGridView::TOUCH, *event);
479 event->SetHandled();
481 break;
482 case ui::ET_GESTURE_SCROLL_UPDATE:
483 if (touch_dragging_ && apps_grid_view_->IsDraggedView(this)) {
484 apps_grid_view_->UpdateDragFromItem(AppsGridView::TOUCH, *event);
485 event->SetHandled();
487 break;
488 case ui::ET_GESTURE_SCROLL_END:
489 case ui::ET_SCROLL_FLING_START:
490 if (touch_dragging_) {
491 SetTouchDragging(false);
492 apps_grid_view_->EndDrag(false);
493 event->SetHandled();
495 break;
496 case ui::ET_GESTURE_LONG_PRESS:
497 if (!apps_grid_view_->has_dragged_view())
498 SetTouchDragging(true);
499 event->SetHandled();
500 break;
501 case ui::ET_GESTURE_LONG_TAP:
502 case ui::ET_GESTURE_END:
503 if (touch_dragging_)
504 SetTouchDragging(false);
505 break;
506 default:
507 break;
509 if (!event->handled())
510 CustomButton::OnGestureEvent(event);
513 void AppListItemView::OnSyncDragEnd() {
514 SetUIState(UI_STATE_NORMAL);
517 const gfx::Rect& AppListItemView::GetIconBounds() const {
518 return icon_->bounds();
521 void AppListItemView::SetDragUIState() {
522 SetUIState(UI_STATE_DRAGGING);
525 gfx::Rect AppListItemView::GetIconBoundsForTargetViewBounds(
526 const gfx::Rect& target_bounds) {
527 gfx::Rect rect(target_bounds);
529 const int left_right_padding =
530 title_->font_list().GetExpectedTextWidth(kLeftRightPaddingChars);
531 rect.Inset(left_right_padding, kTopPadding, left_right_padding, 0);
533 gfx::Rect icon_bounds(rect.x(), rect.y(), rect.width(), icon_size_.height());
534 icon_bounds.Inset(gfx::ShadowValue::GetMargin(icon_shadows_));
535 return icon_bounds;
538 } // namespace app_list