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/utf_string_conversions.h"
10 #include "grit/ui_resources.h"
11 #include "ui/app_list/app_list_constants.h"
12 #include "ui/app_list/app_list_item_model.h"
13 #include "ui/app_list/views/apps_grid_view.h"
14 #include "ui/app_list/views/cached_label.h"
15 #include "ui/base/accessibility/accessible_view_state.h"
16 #include "ui/base/animation/throb_animation.h"
17 #include "ui/base/resource/resource_bundle.h"
18 #include "ui/compositor/layer.h"
19 #include "ui/compositor/scoped_layer_animation_settings.h"
20 #include "ui/gfx/canvas.h"
21 #include "ui/gfx/font.h"
22 #include "ui/gfx/image/image_skia_operations.h"
23 #include "ui/gfx/transform_util.h"
24 #include "ui/views/controls/image_view.h"
25 #include "ui/views/controls/label.h"
26 #include "ui/views/controls/menu/menu_item_view.h"
27 #include "ui/views/controls/menu/menu_runner.h"
33 const int kTopBottomPadding
= 10;
34 const int kTopPadding
= 20;
35 const int kIconTitleSpacing
= 7;
36 const int kProgressBarHorizontalPadding
= 12;
37 const int kProgressBarVerticalPadding
= 4;
38 const int kProgressBarHeight
= 4;
40 const SkColor kTitleColor
= SkColorSetRGB(0x5A, 0x5A, 0x5A);
41 const SkColor kTitleHoverColor
= SkColorSetRGB(0x3C, 0x3C, 0x3C);
43 const SkColor kSelectedColor
= SkColorSetARGB(0x0D, 0, 0, 0);
44 const SkColor kHighlightedColor
= kHoverAndPushedColor
;
45 const SkColor kDownloadProgressBackgroundColor
=
46 SkColorSetRGB(0x90, 0x90, 0x90);
47 const SkColor kDownloadProgressColor
= SkColorSetRGB(0x20, 0xAA, 0x20);
49 const int kLeftRightPaddingChars
= 1;
51 // Scale to transform the icon when a drag starts.
52 const float kDraggingIconScale
= 1.5f
;
54 // Delay in milliseconds of when the dragging UI should be shown for mouse drag.
55 const int kMouseDragUIDelayInMs
= 100;
60 const char AppListItemView::kViewClassName
[] = "ui/app_list/AppListItemView";
62 AppListItemView::AppListItemView(AppsGridView
* apps_grid_view
,
63 AppListItemModel
* model
)
64 : CustomButton(apps_grid_view
),
66 apps_grid_view_(apps_grid_view
),
67 icon_(new views::ImageView
),
68 title_(new CachedLabel
),
69 ui_state_(UI_STATE_NORMAL
),
70 touch_dragging_(false) {
71 icon_
->set_interactive(false);
73 ui::ResourceBundle
& rb
= ui::ResourceBundle::GetSharedInstance();
74 title_
->SetBackgroundColor(0);
75 title_
->SetAutoColorReadabilityEnabled(false);
76 title_
->SetEnabledColor(kTitleColor
);
77 title_
->SetFont(rb
.GetFont(kItemTextFontStyle
));
78 title_
->SetHorizontalAlignment(gfx::ALIGN_LEFT
);
79 title_
->SetVisible(!model_
->is_installing());
82 const gfx::ShadowValue kIconShadows
[] = {
83 gfx::ShadowValue(gfx::Point(0, 2), 2, SkColorSetARGB(0x24, 0, 0, 0)),
85 icon_shadows_
.assign(kIconShadows
, kIconShadows
+ arraysize(kIconShadows
));
92 model_
->AddObserver(this);
94 set_context_menu_controller(this);
95 set_request_focus_on_press(false);
97 SetAnimationDuration(0);
100 AppListItemView::~AppListItemView() {
101 model_
->RemoveObserver(this);
104 void AppListItemView::SetIconSize(const gfx::Size
& size
) {
105 if (icon_size_
== size
)
112 void AppListItemView::UpdateIcon() {
113 // Skip if |icon_size_| has not been determined.
114 if (icon_size_
.IsEmpty())
117 gfx::ImageSkia icon
= model_
->icon();
118 // Clear icon and bail out if model icon is empty.
120 icon_
->SetImage(NULL
);
124 gfx::ImageSkia
resized(gfx::ImageSkiaOperations::CreateResizedImage(icon
,
125 skia::ImageOperations::RESIZE_BEST
, icon_size_
));
126 if (model_
->has_shadow()) {
127 gfx::ImageSkia
shadow(
128 gfx::ImageSkiaOperations::CreateImageWithDropShadow(resized
,
130 icon_
->SetImage(shadow
);
134 icon_
->SetImage(resized
);
137 void AppListItemView::SetUIState(UIState state
) {
138 if (ui_state_
== state
)
143 #if defined(USE_AURA)
144 ui::ScopedLayerAnimationSettings
settings(layer()->GetAnimator());
146 case UI_STATE_NORMAL
:
147 title_
->SetVisible(true);
148 layer()->SetTransform(gfx::Transform());
150 case UI_STATE_DRAGGING
:
151 title_
->SetVisible(false);
152 const gfx::Rect
bounds(layer()->bounds().size());
153 layer()->SetTransform(gfx::GetScaleTransform(
154 bounds
.CenterPoint(),
155 kDraggingIconScale
));
161 void AppListItemView::SetTouchDragging(bool touch_dragging
) {
162 if (touch_dragging_
== touch_dragging
)
165 touch_dragging_
= touch_dragging
;
166 SetUIState(touch_dragging_
? UI_STATE_DRAGGING
: UI_STATE_NORMAL
);
169 void AppListItemView::OnMouseDragTimer() {
170 DCHECK(apps_grid_view_
->IsDraggedView(this));
171 SetUIState(UI_STATE_DRAGGING
);
174 void AppListItemView::Prerender() {
175 title_
->PaintToBackingImage();
178 void AppListItemView::ItemIconChanged() {
182 void AppListItemView::ItemTitleChanged() {
183 title_
->SetText(UTF8ToUTF16(model_
->title()));
184 title_
->Invalidate();
188 void AppListItemView::ItemHighlightedChanged() {
189 apps_grid_view_
->EnsureViewVisible(this);
193 void AppListItemView::ItemIsInstallingChanged() {
194 if (model_
->is_installing())
195 apps_grid_view_
->EnsureViewVisible(this);
196 title_
->SetVisible(!model_
->is_installing());
200 void AppListItemView::ItemPercentDownloadedChanged() {
204 std::string
AppListItemView::GetClassName() const {
205 return kViewClassName
;
208 void AppListItemView::Layout() {
209 gfx::Rect
rect(GetContentsBounds());
211 const int left_right_padding
= kLeftRightPaddingChars
*
212 title_
->font().GetAverageCharacterWidth();
213 rect
.Inset(left_right_padding
, kTopPadding
, left_right_padding
, 0);
214 const int y
= rect
.y();
216 gfx::Rect
icon_bounds(rect
.x(), y
, rect
.width(), icon_size_
.height());
217 icon_bounds
.Inset(gfx::ShadowValue::GetMargin(icon_shadows_
));
218 icon_
->SetBoundsRect(icon_bounds
);
220 const gfx::Size title_size
= title_
->GetPreferredSize();
221 gfx::Rect
title_bounds(rect
.x() + (rect
.width() - title_size
.width()) / 2,
222 y
+ icon_size_
.height() + kIconTitleSpacing
,
224 title_size
.height());
225 title_bounds
.Intersect(rect
);
226 title_
->SetBoundsRect(title_bounds
);
229 void AppListItemView::OnPaint(gfx::Canvas
* canvas
) {
230 if (apps_grid_view_
->IsDraggedView(this))
233 gfx::Rect
rect(GetContentsBounds());
235 if (model_
->highlighted() && !model_
->is_installing()) {
236 canvas
->FillRect(rect
, kHighlightedColor
);
237 } else if (hover_animation_
->is_animating()) {
238 int alpha
= SkColorGetA(kHoverAndPushedColor
) *
239 hover_animation_
->GetCurrentValue();
240 canvas
->FillRect(rect
, SkColorSetA(kHoverAndPushedColor
, alpha
));
241 } else if (state() == STATE_HOVERED
|| state() == STATE_PRESSED
) {
242 canvas
->FillRect(rect
, kHoverAndPushedColor
);
243 } else if (apps_grid_view_
->IsSelectedView(this)) {
244 canvas
->FillRect(rect
, kSelectedColor
);
247 if (model_
->is_installing()) {
248 gfx::ImageSkia background
= *ResourceBundle::GetSharedInstance().
249 GetImageSkiaNamed(IDR_APP_LIST_ITEM_PROGRESS_BACKGROUND
);
250 gfx::ImageSkia left
= *ResourceBundle::GetSharedInstance().
251 GetImageSkiaNamed(IDR_APP_LIST_ITEM_PROGRESS_LEFT
);
252 gfx::ImageSkia center
= *ResourceBundle::GetSharedInstance().
253 GetImageSkiaNamed(IDR_APP_LIST_ITEM_PROGRESS_CENTER
);
254 gfx::ImageSkia right
= *ResourceBundle::GetSharedInstance().
255 GetImageSkiaNamed(IDR_APP_LIST_ITEM_PROGRESS_RIGHT
);
257 int bar_x
= rect
.x() + kProgressBarHorizontalPadding
;
258 int bar_y
= icon_
->bounds().bottom() + kIconTitleSpacing
;
260 canvas
->DrawImageInt(background
, bar_x
, bar_y
);
261 if (model_
->percent_downloaded() != -1) {
262 float percent
= model_
->percent_downloaded() / 100.0;
263 int bar_width
= percent
*
264 (background
.width() - (left
.width() + right
.width()));
266 canvas
->DrawImageInt(left
, bar_x
, bar_y
);
267 int x
= bar_x
+ left
.width();
268 canvas
->TileImageInt(center
, x
, bar_y
, bar_width
, center
.height());
270 canvas
->DrawImageInt(right
, x
, bar_y
);
275 void AppListItemView::GetAccessibleState(ui::AccessibleViewState
* state
) {
276 state
->role
= ui::AccessibilityTypes::ROLE_PUSHBUTTON
;
277 state
->name
= UTF8ToUTF16(model_
->title());
280 void AppListItemView::ShowContextMenuForView(views::View
* source
,
281 const gfx::Point
& point
) {
282 ui::MenuModel
* menu_model
= model_
->GetContextMenuModel();
286 context_menu_runner_
.reset(new views::MenuRunner(menu_model
));
287 if (context_menu_runner_
->RunMenuAt(
288 GetWidget(), NULL
, gfx::Rect(point
, gfx::Size()),
289 views::MenuItemView::TOPLEFT
, views::MenuRunner::HAS_MNEMONICS
) ==
290 views::MenuRunner::MENU_DELETED
)
294 void AppListItemView::StateChanged() {
295 if (state() == STATE_HOVERED
|| state() == STATE_PRESSED
) {
296 apps_grid_view_
->SetSelectedView(this);
297 title_
->SetEnabledColor(kTitleHoverColor
);
299 apps_grid_view_
->ClearSelectedView(this);
300 model_
->SetHighlighted(false);
301 title_
->SetEnabledColor(kTitleColor
);
303 title_
->Invalidate();
306 bool AppListItemView::ShouldEnterPushedState(const ui::Event
& event
) {
307 // Don't enter pushed state for ET_GESTURE_TAP_DOWN so that hover gray
308 // background does not show up during scroll.
309 if (event
.type() == ui::ET_GESTURE_TAP_DOWN
)
312 return views::CustomButton::ShouldEnterPushedState(event
);
315 bool AppListItemView::OnMousePressed(const ui::MouseEvent
& event
) {
316 CustomButton::OnMousePressed(event
);
318 if (!ShouldEnterPushedState(event
))
321 apps_grid_view_
->InitiateDrag(this, AppsGridView::MOUSE
, event
);
323 if (apps_grid_view_
->IsDraggedView(this)) {
324 mouse_drag_timer_
.Start(FROM_HERE
,
325 base::TimeDelta::FromMilliseconds(kMouseDragUIDelayInMs
),
326 this, &AppListItemView::OnMouseDragTimer
);
331 bool AppListItemView::OnKeyPressed(const ui::KeyEvent
& event
) {
332 // Disable space key to press the button. The keyboard events received
333 // by this view are forwarded from a Textfield (SearchBoxView) and key
334 // released events are not forwarded. This leaves the button in pressed
336 if (event
.key_code() == ui::VKEY_SPACE
)
339 return CustomButton::OnKeyPressed(event
);
342 void AppListItemView::OnMouseReleased(const ui::MouseEvent
& event
) {
343 CustomButton::OnMouseReleased(event
);
344 apps_grid_view_
->EndDrag(false);
345 mouse_drag_timer_
.Stop();
346 SetUIState(UI_STATE_NORMAL
);
349 void AppListItemView::OnMouseCaptureLost() {
350 CustomButton::OnMouseCaptureLost();
351 apps_grid_view_
->EndDrag(true);
352 mouse_drag_timer_
.Stop();
353 SetUIState(UI_STATE_NORMAL
);
356 bool AppListItemView::OnMouseDragged(const ui::MouseEvent
& event
) {
357 CustomButton::OnMouseDragged(event
);
358 apps_grid_view_
->UpdateDrag(this, AppsGridView::MOUSE
, event
);
360 // Shows dragging UI when it's confirmed without waiting for the timer.
361 if (ui_state_
!= UI_STATE_DRAGGING
&&
362 apps_grid_view_
->dragging() &&
363 apps_grid_view_
->IsDraggedView(this)) {
364 mouse_drag_timer_
.Stop();
365 SetUIState(UI_STATE_DRAGGING
);
370 void AppListItemView::OnGestureEvent(ui::GestureEvent
* event
) {
371 switch (event
->type()) {
372 case ui::ET_GESTURE_SCROLL_BEGIN
:
373 if (touch_dragging_
) {
374 apps_grid_view_
->InitiateDrag(this, AppsGridView::TOUCH
, *event
);
378 case ui::ET_GESTURE_SCROLL_UPDATE
:
379 if (touch_dragging_
) {
380 apps_grid_view_
->UpdateDrag(this, AppsGridView::TOUCH
, *event
);
384 case ui::ET_GESTURE_SCROLL_END
:
385 case ui::ET_SCROLL_FLING_START
:
386 if (touch_dragging_
) {
387 SetTouchDragging(false);
388 apps_grid_view_
->EndDrag(false);
392 case ui::ET_GESTURE_LONG_PRESS
:
393 if (!apps_grid_view_
->has_dragged_view())
394 SetTouchDragging(true);
397 case ui::ET_GESTURE_LONG_TAP
:
398 case ui::ET_GESTURE_END
:
400 SetTouchDragging(false);
405 if (!event
->handled())
406 CustomButton::OnGestureEvent(event
);
409 } // namespace app_list