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 "ui/app_list/app_list_constants.h"
11 #include "ui/app_list/app_list_item.h"
12 #include "ui/app_list/app_list_switches.h"
13 #include "ui/app_list/views/apps_grid_view.h"
14 #include "ui/app_list/views/cached_label.h"
15 #include "ui/app_list/views/progress_bar_view.h"
16 #include "ui/base/accessibility/accessible_view_state.h"
17 #include "ui/base/dragdrop/drag_utils.h"
18 #include "ui/base/resource/resource_bundle.h"
19 #include "ui/compositor/layer.h"
20 #include "ui/compositor/scoped_layer_animation_settings.h"
21 #include "ui/gfx/animation/throb_animation.h"
22 #include "ui/gfx/canvas.h"
23 #include "ui/gfx/font_list.h"
24 #include "ui/gfx/image/image_skia_operations.h"
25 #include "ui/gfx/point.h"
26 #include "ui/gfx/transform_util.h"
27 #include "ui/views/controls/image_view.h"
28 #include "ui/views/controls/label.h"
29 #include "ui/views/controls/menu/menu_item_view.h"
30 #include "ui/views/controls/menu/menu_runner.h"
31 #include "ui/views/drag_controller.h"
37 const int kTopPadding
= 20;
38 const int kIconTitleSpacing
= 7;
39 const int kProgressBarHorizontalPadding
= 12;
41 // Radius of the folder dropping preview circle.
42 const int kFolderPreviewRadius
= 40;
44 const int kLeftRightPaddingChars
= 1;
46 // Scale to transform the icon when a drag starts.
47 const float kDraggingIconScale
= 1.5f
;
49 // Delay in milliseconds of when the dragging UI should be shown for mouse drag.
50 const int kMouseDragUIDelayInMs
= 200;
55 const char AppListItemView::kViewClassName
[] = "ui/app_list/AppListItemView";
57 AppListItemView::AppListItemView(AppsGridView
* apps_grid_view
,
59 : CustomButton(apps_grid_view
),
61 apps_grid_view_(apps_grid_view
),
62 icon_(new views::ImageView
),
63 title_(new CachedLabel
),
64 progress_bar_(new ProgressBarView
),
65 ui_state_(UI_STATE_NORMAL
),
66 touch_dragging_(false) {
67 icon_
->set_interactive(false);
69 ui::ResourceBundle
& rb
= ui::ResourceBundle::GetSharedInstance();
70 title_
->SetBackgroundColor(0);
71 title_
->SetAutoColorReadabilityEnabled(false);
72 title_
->SetEnabledColor(kGridTitleColor
);
73 title_
->SetFontList(rb
.GetFontList(kItemTextFontStyle
));
74 title_
->SetHorizontalAlignment(gfx::ALIGN_LEFT
);
75 title_
->SetVisible(!item_
->is_installing());
78 const gfx::ShadowValue kIconShadows
[] = {
79 gfx::ShadowValue(gfx::Point(0, 2), 2, SkColorSetARGB(0x24, 0, 0, 0)),
81 icon_shadows_
.assign(kIconShadows
, kIconShadows
+ arraysize(kIconShadows
));
85 AddChildView(progress_bar_
);
89 ItemIsInstallingChanged();
90 item_
->AddObserver(this);
92 set_context_menu_controller(this);
93 set_request_focus_on_press(false);
95 SetAnimationDuration(0);
98 AppListItemView::~AppListItemView() {
99 item_
->RemoveObserver(this);
102 void AppListItemView::SetIconSize(const gfx::Size
& size
) {
103 if (icon_size_
== size
)
110 void AppListItemView::UpdateIcon() {
111 // Skip if |icon_size_| has not been determined.
112 if (icon_size_
.IsEmpty())
115 gfx::ImageSkia icon
= item_
->icon();
116 // Clear icon and bail out if item icon is empty.
118 icon_
->SetImage(NULL
);
122 gfx::ImageSkia
resized(gfx::ImageSkiaOperations::CreateResizedImage(icon
,
123 skia::ImageOperations::RESIZE_BEST
, icon_size_
));
124 if (item_
->has_shadow()) {
125 gfx::ImageSkia
shadow(
126 gfx::ImageSkiaOperations::CreateImageWithDropShadow(resized
,
128 icon_
->SetImage(shadow
);
132 icon_
->SetImage(resized
);
135 void AppListItemView::UpdateTooltip() {
136 title_
->SetTooltipText(item_
->title() == item_
->full_name() ? base::string16()
137 : base::UTF8ToUTF16(item_
->full_name()));
140 void AppListItemView::SetUIState(UIState state
) {
141 if (ui_state_
== state
)
146 #if defined(USE_AURA)
148 case UI_STATE_NORMAL
:
149 title_
->SetVisible(!item_
->is_installing());
150 progress_bar_
->SetVisible(item_
->is_installing());
152 case UI_STATE_DRAGGING
:
153 title_
->SetVisible(false);
154 progress_bar_
->SetVisible(false);
156 case UI_STATE_DROPPING_IN_FOLDER
:
160 ui::ScopedLayerAnimationSettings
settings(layer()->GetAnimator());
162 case UI_STATE_NORMAL
:
163 layer()->SetTransform(gfx::Transform());
165 case UI_STATE_DRAGGING
: {
166 const gfx::Rect
bounds(layer()->bounds().size());
167 layer()->SetTransform(gfx::GetScaleTransform(
168 bounds
.CenterPoint(),
169 kDraggingIconScale
));
172 case UI_STATE_DROPPING_IN_FOLDER
:
181 void AppListItemView::SetTouchDragging(bool touch_dragging
) {
182 if (touch_dragging_
== touch_dragging
)
185 touch_dragging_
= touch_dragging
;
186 SetUIState(touch_dragging_
? UI_STATE_DRAGGING
: UI_STATE_NORMAL
);
189 void AppListItemView::OnMouseDragTimer() {
190 DCHECK(apps_grid_view_
->IsDraggedView(this));
191 SetUIState(UI_STATE_DRAGGING
);
194 void AppListItemView::Prerender() {
195 title_
->PaintToBackingImage();
198 void AppListItemView::CancelContextMenu() {
199 if (context_menu_runner_
)
200 context_menu_runner_
->Cancel();
203 gfx::ImageSkia
AppListItemView::GetDragImage() {
204 return icon_
->GetImage();
207 void AppListItemView::OnDragEnded() {
208 mouse_drag_timer_
.Stop();
209 SetUIState(UI_STATE_NORMAL
);
212 gfx::Point
AppListItemView::GetDragImageOffset() {
213 gfx::Point image
= icon_
->GetImageBounds().origin();
214 return gfx::Point(icon_
->x() + image
.x(), icon_
->y() + image
.y());
217 void AppListItemView::SetAsAttemptedFolderTarget(bool is_target_folder
) {
218 if (is_target_folder
)
219 SetUIState(UI_STATE_DROPPING_IN_FOLDER
);
221 SetUIState(UI_STATE_NORMAL
);
224 void AppListItemView::ItemIconChanged() {
228 void AppListItemView::ItemTitleChanged() {
229 title_
->SetText(base::UTF8ToUTF16(item_
->title()));
230 title_
->Invalidate();
235 void AppListItemView::ItemHighlightedChanged() {
236 apps_grid_view_
->EnsureViewVisible(this);
240 void AppListItemView::ItemIsInstallingChanged() {
241 if (item_
->is_installing())
242 apps_grid_view_
->EnsureViewVisible(this);
243 title_
->SetVisible(!item_
->is_installing());
244 progress_bar_
->SetVisible(item_
->is_installing());
248 void AppListItemView::ItemPercentDownloadedChanged() {
249 // A percent_downloaded() of -1 can mean it's not known how much percent is
250 // completed, or the download hasn't been marked complete, as is the case
251 // while an extension is being installed after being downloaded.
252 if (item_
->percent_downloaded() == -1)
254 progress_bar_
->SetValue(item_
->percent_downloaded() / 100.0);
257 const char* AppListItemView::GetClassName() const {
258 return kViewClassName
;
261 void AppListItemView::Layout() {
262 gfx::Rect
rect(GetContentsBounds());
264 const int left_right_padding
=
265 title_
->font_list().GetExpectedTextWidth(kLeftRightPaddingChars
);
266 rect
.Inset(left_right_padding
, kTopPadding
, left_right_padding
, 0);
267 const int y
= rect
.y();
269 gfx::Rect
icon_bounds(rect
.x(), y
, rect
.width(), icon_size_
.height());
270 icon_bounds
.Inset(gfx::ShadowValue::GetMargin(icon_shadows_
));
271 icon_
->SetBoundsRect(icon_bounds
);
272 const gfx::Size title_size
= title_
->GetPreferredSize();
273 gfx::Rect
title_bounds(rect
.x() + (rect
.width() - title_size
.width()) / 2,
274 y
+ icon_size_
.height() + kIconTitleSpacing
,
276 title_size
.height());
277 title_bounds
.Intersect(rect
);
278 title_
->SetBoundsRect(title_bounds
);
280 gfx::Rect
progress_bar_bounds(progress_bar_
->GetPreferredSize());
281 progress_bar_bounds
.set_x(GetContentsBounds().x() +
282 kProgressBarHorizontalPadding
);
283 progress_bar_bounds
.set_y(title_bounds
.y());
284 progress_bar_
->SetBoundsRect(progress_bar_bounds
);
287 void AppListItemView::OnPaint(gfx::Canvas
* canvas
) {
288 if (apps_grid_view_
->IsDraggedView(this))
291 gfx::Rect
rect(GetContentsBounds());
292 if (item_
->highlighted() && !item_
->is_installing()) {
293 canvas
->FillRect(rect
, kHighlightedColor
);
295 } else if (apps_grid_view_
->IsSelectedView(this)) {
296 canvas
->FillRect(rect
, kSelectedColor
);
299 if (!switches::IsFolderUIEnabled()) {
300 if (apps_grid_view_
->IsSelectedView(this)) {
301 canvas
->FillRect(rect
, kSelectedColor
);
302 } else if (state() == STATE_HOVERED
|| state() == STATE_PRESSED
) {
303 canvas
->FillRect(rect
, kHighlightedColor
);
305 } else if (ui_state_
== UI_STATE_DROPPING_IN_FOLDER
) {
306 // Draw folder dropping preview circle.
307 gfx::Point center
= gfx::Point(icon_
->x() + icon_
->size().width() / 2,
308 icon_
->y() + icon_
->size().height() / 2);
310 paint
.setStyle(SkPaint::kFill_Style
);
311 paint
.setAntiAlias(true);
312 paint
.setColor(kFolderBubbleColor
);
313 canvas
->DrawCircle(center
, kFolderPreviewRadius
, paint
);
317 void AppListItemView::GetAccessibleState(ui::AccessibleViewState
* state
) {
318 state
->role
= ui::AccessibilityTypes::ROLE_PUSHBUTTON
;
319 state
->name
= base::UTF8ToUTF16(item_
->title());
322 void AppListItemView::ShowContextMenuForView(views::View
* source
,
323 const gfx::Point
& point
,
324 ui::MenuSourceType source_type
) {
325 ui::MenuModel
* menu_model
= item_
->GetContextMenuModel();
329 context_menu_runner_
.reset(new views::MenuRunner(menu_model
));
330 if (context_menu_runner_
->RunMenuAt(
331 GetWidget(), NULL
, gfx::Rect(point
, gfx::Size()),
332 views::MenuItemView::TOPLEFT
, source_type
,
333 views::MenuRunner::HAS_MNEMONICS
) ==
334 views::MenuRunner::MENU_DELETED
)
338 void AppListItemView::StateChanged() {
339 const bool is_folder_ui_enabled
= switches::IsFolderUIEnabled();
340 if (is_folder_ui_enabled
)
341 apps_grid_view_
->ClearAnySelectedView();
343 if (state() == STATE_HOVERED
|| state() == STATE_PRESSED
) {
344 if (!is_folder_ui_enabled
)
345 apps_grid_view_
->SetSelectedView(this);
346 title_
->SetEnabledColor(kGridTitleHoverColor
);
348 if (!is_folder_ui_enabled
)
349 apps_grid_view_
->ClearSelectedView(this);
350 item_
->SetHighlighted(false);
351 title_
->SetEnabledColor(kGridTitleColor
);
353 title_
->Invalidate();
356 bool AppListItemView::ShouldEnterPushedState(const ui::Event
& event
) {
357 // Don't enter pushed state for ET_GESTURE_TAP_DOWN so that hover gray
358 // background does not show up during scroll.
359 if (event
.type() == ui::ET_GESTURE_TAP_DOWN
)
362 return views::CustomButton::ShouldEnterPushedState(event
);
365 bool AppListItemView::OnMousePressed(const ui::MouseEvent
& event
) {
366 CustomButton::OnMousePressed(event
);
368 if (!ShouldEnterPushedState(event
))
371 apps_grid_view_
->InitiateDrag(this, AppsGridView::MOUSE
, event
);
373 if (apps_grid_view_
->IsDraggedView(this)) {
374 mouse_drag_timer_
.Start(FROM_HERE
,
375 base::TimeDelta::FromMilliseconds(kMouseDragUIDelayInMs
),
376 this, &AppListItemView::OnMouseDragTimer
);
381 bool AppListItemView::OnKeyPressed(const ui::KeyEvent
& event
) {
382 // Disable space key to press the button. The keyboard events received
383 // by this view are forwarded from a Textfield (SearchBoxView) and key
384 // released events are not forwarded. This leaves the button in pressed
386 if (event
.key_code() == ui::VKEY_SPACE
)
389 return CustomButton::OnKeyPressed(event
);
392 void AppListItemView::OnMouseReleased(const ui::MouseEvent
& event
) {
393 CustomButton::OnMouseReleased(event
);
394 apps_grid_view_
->EndDrag(false);
397 void AppListItemView::OnMouseCaptureLost() {
398 // We don't cancel the dag on mouse capture lost for windows as entering a
399 // synchronous drag causes mouse capture to be lost and pressing escape
400 // dismisses the app list anyway.
402 CustomButton::OnMouseCaptureLost();
403 apps_grid_view_
->EndDrag(true);
407 bool AppListItemView::OnMouseDragged(const ui::MouseEvent
& event
) {
408 CustomButton::OnMouseDragged(event
);
409 if (apps_grid_view_
->IsDraggedView(this))
410 apps_grid_view_
->UpdateDragFromItem(AppsGridView::MOUSE
, event
);
412 // Shows dragging UI when it's confirmed without waiting for the timer.
413 if (ui_state_
!= UI_STATE_DRAGGING
&&
414 apps_grid_view_
->dragging() &&
415 apps_grid_view_
->IsDraggedView(this)) {
416 mouse_drag_timer_
.Stop();
417 SetUIState(UI_STATE_DRAGGING
);
422 void AppListItemView::OnGestureEvent(ui::GestureEvent
* event
) {
423 switch (event
->type()) {
424 case ui::ET_GESTURE_SCROLL_BEGIN
:
425 if (touch_dragging_
) {
426 apps_grid_view_
->InitiateDrag(this, AppsGridView::TOUCH
, *event
);
430 case ui::ET_GESTURE_SCROLL_UPDATE
:
431 if (touch_dragging_
&& apps_grid_view_
->IsDraggedView(this)) {
432 apps_grid_view_
->UpdateDragFromItem(AppsGridView::TOUCH
, *event
);
436 case ui::ET_GESTURE_SCROLL_END
:
437 case ui::ET_SCROLL_FLING_START
:
438 if (touch_dragging_
) {
439 SetTouchDragging(false);
440 apps_grid_view_
->EndDrag(false);
444 case ui::ET_GESTURE_LONG_PRESS
:
445 if (!apps_grid_view_
->has_dragged_view())
446 SetTouchDragging(true);
449 case ui::ET_GESTURE_LONG_TAP
:
450 case ui::ET_GESTURE_END
:
452 SetTouchDragging(false);
457 if (!event
->handled())
458 CustomButton::OnGestureEvent(event
);
461 void AppListItemView::OnSyncDragEnd() {
462 SetUIState(UI_STATE_NORMAL
);
465 const gfx::Rect
& AppListItemView::GetIconBounds() const {
466 return icon_
->bounds();
469 } // namespace app_list