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"
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"
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;
50 const int kFontSizeDelta
= 0;
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;
67 const char AppListItemView::kViewClassName
[] = "ui/app_list/AppListItemView";
69 AppListItemView::AppListItemView(AppsGridView
* apps_grid_view
,
71 : CustomButton(apps_grid_view
),
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
);
86 rb
.GetFontList(kItemTextFontStyle
).DeriveWithSizeDelta(kFontSizeDelta
));
87 title_
->SetHorizontalAlignment(gfx::ALIGN_LEFT
);
88 title_
->SetVisible(!item_
->is_installing());
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
));
99 AddChildView(progress_bar_
);
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
)
124 void AppListItemView::UpdateIcon() {
125 // Skip if |icon_size_| has not been determined.
126 if (icon_size_
.IsEmpty())
129 gfx::ImageSkia icon
= item_
->icon();
130 // Clear icon and bail out if item icon is empty.
132 icon_
->SetImage(NULL
);
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
,
142 icon_
->SetImage(shadow
);
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
)
162 case UI_STATE_NORMAL
:
163 title_
->SetVisible(!item_
->is_installing());
164 progress_bar_
->SetVisible(item_
->is_installing());
166 case UI_STATE_DRAGGING
:
167 title_
->SetVisible(false);
168 progress_bar_
->SetVisible(false);
170 case UI_STATE_DROPPING_IN_FOLDER
:
174 ui::ScopedLayerAnimationSettings
settings(layer()->GetAnimator());
176 case UI_STATE_NORMAL
:
177 layer()->SetTransform(gfx::Transform());
179 case UI_STATE_DRAGGING
: {
180 const gfx::Rect
bounds(layer()->bounds().size());
181 layer()->SetTransform(gfx::GetScaleTransform(
182 bounds
.CenterPoint(),
183 kDraggingIconScale
));
186 case UI_STATE_DROPPING_IN_FOLDER
:
194 void AppListItemView::SetTouchDragging(bool touch_dragging
) {
195 if (touch_dragging_
== touch_dragging
)
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
)
220 title_
->SetBackgroundColor(app_list::kContentsBackgroundColor
);
221 title_
->set_background(views::Background::CreateSolidBackground(
222 app_list::kContentsBackgroundColor
));
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
);
260 SetUIState(UI_STATE_NORMAL
);
263 void AppListItemView::ItemIconChanged() {
267 void AppListItemView::ItemNameChanged() {
268 title_
->SetText(base::UTF8ToUTF16(item_
->GetDisplayName()));
269 title_
->Invalidate();
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()));
280 void AppListItemView::ItemHighlightedChanged() {
281 apps_grid_view_
->EnsureViewVisible(this);
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());
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)
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
,
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))
339 gfx::Rect
rect(GetContentsBounds());
340 if (item_
->highlighted() && !item_
->is_installing()) {
341 canvas
->FillRect(rect
, kHighlightedColor
);
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);
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();
372 context_menu_runner_
.reset(new views::MenuRunner(menu_model
));
373 if (context_menu_runner_
->RunMenuAt(GetWidget(),
375 gfx::Rect(point
, gfx::Size()),
376 views::MENU_ANCHOR_TOPLEFT
,
378 views::MenuRunner::HAS_MNEMONICS
) ==
379 views::MenuRunner::MENU_DELETED
) {
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
);
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
)
408 return views::CustomButton::ShouldEnterPushedState(event
);
411 bool AppListItemView::OnMousePressed(const ui::MouseEvent
& event
) {
412 CustomButton::OnMousePressed(event
);
414 if (!ShouldEnterPushedState(event
))
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
);
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
432 if (event
.key_code() == ui::VKEY_SPACE
)
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.
448 CustomButton::OnMouseCaptureLost();
449 apps_grid_view_
->EndDrag(true);
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
))
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
);
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
);
482 case ui::ET_GESTURE_SCROLL_UPDATE
:
483 if (touch_dragging_
&& apps_grid_view_
->IsDraggedView(this)) {
484 apps_grid_view_
->UpdateDragFromItem(AppsGridView::TOUCH
, *event
);
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);
496 case ui::ET_GESTURE_LONG_PRESS
:
497 if (!apps_grid_view_
->has_dragged_view())
498 SetTouchDragging(true);
501 case ui::ET_GESTURE_LONG_TAP
:
502 case ui::ET_GESTURE_END
:
504 SetTouchDragging(false);
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_
));
538 } // namespace app_list