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_model.h"
12 #include "ui/app_list/views/apps_grid_view.h"
13 #include "ui/app_list/views/cached_label.h"
14 #include "ui/app_list/views/progress_bar_view.h"
15 #include "ui/base/accessibility/accessible_view_state.h"
16 #include "ui/base/dragdrop/drag_utils.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/animation/throb_animation.h"
21 #include "ui/gfx/canvas.h"
22 #include "ui/gfx/font.h"
23 #include "ui/gfx/image/image_skia_operations.h"
24 #include "ui/gfx/transform_util.h"
25 #include "ui/views/controls/image_view.h"
26 #include "ui/views/controls/label.h"
27 #include "ui/views/controls/menu/menu_item_view.h"
28 #include "ui/views/controls/menu/menu_runner.h"
29 #include "ui/views/drag_controller.h"
35 const int kTopPadding = 20;
36 const int kIconTitleSpacing = 7;
37 const int kProgressBarHorizontalPadding = 12;
39 const int kLeftRightPaddingChars = 1;
41 // Scale to transform the icon when a drag starts.
42 const float kDraggingIconScale = 1.5f;
44 // Delay in milliseconds of when the dragging UI should be shown for mouse drag.
45 const int kMouseDragUIDelayInMs = 100;
50 const char AppListItemView::kViewClassName[] = "ui/app_list/AppListItemView";
52 AppListItemView::AppListItemView(AppsGridView* apps_grid_view,
53 AppListItemModel* model)
54 : CustomButton(apps_grid_view),
56 apps_grid_view_(apps_grid_view),
57 icon_(new views::ImageView),
58 title_(new CachedLabel),
59 progress_bar_(new ProgressBarView),
60 ui_state_(UI_STATE_NORMAL),
61 touch_dragging_(false) {
62 icon_->set_interactive(false);
64 ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
65 title_->SetBackgroundColor(0);
66 title_->SetAutoColorReadabilityEnabled(false);
67 title_->SetEnabledColor(kGridTitleColor);
68 title_->SetFont(rb.GetFont(kItemTextFontStyle));
69 title_->SetHorizontalAlignment(gfx::ALIGN_LEFT);
70 title_->SetVisible(!model_->is_installing());
73 const gfx::ShadowValue kIconShadows[] = {
74 gfx::ShadowValue(gfx::Point(0, 2), 2, SkColorSetARGB(0x24, 0, 0, 0)),
76 icon_shadows_.assign(kIconShadows, kIconShadows + arraysize(kIconShadows));
80 AddChildView(progress_bar_);
84 ItemIsInstallingChanged();
85 model_->AddObserver(this);
87 set_context_menu_controller(this);
88 set_request_focus_on_press(false);
90 SetAnimationDuration(0);
93 AppListItemView::~AppListItemView() {
94 model_->RemoveObserver(this);
97 void AppListItemView::SetIconSize(const gfx::Size& size) {
98 if (icon_size_ == size)
105 void AppListItemView::UpdateIcon() {
106 // Skip if |icon_size_| has not been determined.
107 if (icon_size_.IsEmpty())
110 gfx::ImageSkia icon = model_->icon();
111 // Clear icon and bail out if model icon is empty.
113 icon_->SetImage(NULL);
117 gfx::ImageSkia resized(gfx::ImageSkiaOperations::CreateResizedImage(icon,
118 skia::ImageOperations::RESIZE_BEST, icon_size_));
119 if (model_->has_shadow()) {
120 gfx::ImageSkia shadow(
121 gfx::ImageSkiaOperations::CreateImageWithDropShadow(resized,
123 icon_->SetImage(shadow);
127 icon_->SetImage(resized);
130 void AppListItemView::UpdateTooltip() {
131 title_->SetTooltipText(model_->title() == model_->full_name() ?
132 string16() : UTF8ToUTF16(model_->full_name()));
135 void AppListItemView::SetUIState(UIState state) {
136 if (ui_state_ == state)
141 #if defined(USE_AURA)
142 ui::ScopedLayerAnimationSettings settings(layer()->GetAnimator());
144 case UI_STATE_NORMAL:
145 title_->SetVisible(!model_->is_installing());
146 progress_bar_->SetVisible(model_->is_installing());
147 layer()->SetTransform(gfx::Transform());
149 case UI_STATE_DRAGGING:
150 title_->SetVisible(false);
151 progress_bar_->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::CancelContextMenu() {
179 if (context_menu_runner_)
180 context_menu_runner_->Cancel();
183 gfx::ImageSkia AppListItemView::GetDragImage() {
184 return icon_->GetImage();
187 void AppListItemView::ItemIconChanged() {
191 void AppListItemView::ItemTitleChanged() {
192 title_->SetText(UTF8ToUTF16(model_->title()));
193 title_->Invalidate();
198 void AppListItemView::ItemHighlightedChanged() {
199 apps_grid_view_->EnsureViewVisible(this);
203 void AppListItemView::ItemIsInstallingChanged() {
204 if (model_->is_installing())
205 apps_grid_view_->EnsureViewVisible(this);
206 title_->SetVisible(!model_->is_installing());
207 progress_bar_->SetVisible(model_->is_installing());
211 void AppListItemView::ItemPercentDownloadedChanged() {
212 // A percent_downloaded() of -1 can mean it's not known how much percent is
213 // completed, or the download hasn't been marked complete, as is the case
214 // while an extension is being installed after being downloaded.
215 if (model_->percent_downloaded() == -1)
217 progress_bar_->SetValue(model_->percent_downloaded() / 100.0);
220 const char* AppListItemView::GetClassName() const {
221 return kViewClassName;
224 void AppListItemView::Layout() {
225 gfx::Rect rect(GetContentsBounds());
227 const int left_right_padding = kLeftRightPaddingChars *
228 title_->font().GetAverageCharacterWidth();
229 rect.Inset(left_right_padding, kTopPadding, left_right_padding, 0);
230 const int y = rect.y();
232 gfx::Rect icon_bounds(rect.x(), y, rect.width(), icon_size_.height());
233 icon_bounds.Inset(gfx::ShadowValue::GetMargin(icon_shadows_));
234 icon_->SetBoundsRect(icon_bounds);
236 const gfx::Size title_size = title_->GetPreferredSize();
237 gfx::Rect title_bounds(rect.x() + (rect.width() - title_size.width()) / 2,
238 y + icon_size_.height() + kIconTitleSpacing,
240 title_size.height());
241 title_bounds.Intersect(rect);
242 title_->SetBoundsRect(title_bounds);
244 gfx::Rect progress_bar_bounds(progress_bar_->GetPreferredSize());
245 progress_bar_bounds.set_x(GetContentsBounds().x() +
246 kProgressBarHorizontalPadding);
247 progress_bar_bounds.set_y(title_bounds.y());
248 progress_bar_->SetBoundsRect(progress_bar_bounds);
251 void AppListItemView::OnPaint(gfx::Canvas* canvas) {
252 if (apps_grid_view_->IsDraggedView(this))
255 gfx::Rect rect(GetContentsBounds());
257 if (apps_grid_view_->IsSelectedView(this)) {
258 canvas->FillRect(rect, kSelectedColor);
259 } else if (model_->highlighted() && !model_->is_installing()) {
260 canvas->FillRect(rect, kHighlightedColor);
261 } else if (state() == STATE_HOVERED || state() == STATE_PRESSED) {
262 canvas->FillRect(rect, kHighlightedColor);
266 void AppListItemView::GetAccessibleState(ui::AccessibleViewState* state) {
267 state->role = ui::AccessibilityTypes::ROLE_PUSHBUTTON;
268 state->name = UTF8ToUTF16(model_->title());
271 void AppListItemView::ShowContextMenuForView(views::View* source,
272 const gfx::Point& point,
273 ui::MenuSourceType source_type) {
274 ui::MenuModel* menu_model = model_->GetContextMenuModel();
278 context_menu_runner_.reset(new views::MenuRunner(menu_model));
279 if (context_menu_runner_->RunMenuAt(
280 GetWidget(), NULL, gfx::Rect(point, gfx::Size()),
281 views::MenuItemView::TOPLEFT, source_type,
282 views::MenuRunner::HAS_MNEMONICS) ==
283 views::MenuRunner::MENU_DELETED)
287 void AppListItemView::StateChanged() {
288 if (state() == STATE_HOVERED || state() == STATE_PRESSED) {
289 apps_grid_view_->SetSelectedView(this);
290 title_->SetEnabledColor(kGridTitleHoverColor);
292 apps_grid_view_->ClearSelectedView(this);
293 model_->SetHighlighted(false);
294 title_->SetEnabledColor(kGridTitleColor);
296 title_->Invalidate();
299 bool AppListItemView::ShouldEnterPushedState(const ui::Event& event) {
300 // Don't enter pushed state for ET_GESTURE_TAP_DOWN so that hover gray
301 // background does not show up during scroll.
302 if (event.type() == ui::ET_GESTURE_TAP_DOWN)
305 return views::CustomButton::ShouldEnterPushedState(event);
308 bool AppListItemView::OnMousePressed(const ui::MouseEvent& event) {
309 CustomButton::OnMousePressed(event);
311 if (!ShouldEnterPushedState(event))
314 apps_grid_view_->InitiateDrag(this, AppsGridView::MOUSE, event);
316 if (apps_grid_view_->IsDraggedView(this)) {
317 mouse_drag_timer_.Start(FROM_HERE,
318 base::TimeDelta::FromMilliseconds(kMouseDragUIDelayInMs),
319 this, &AppListItemView::OnMouseDragTimer);
324 bool AppListItemView::OnKeyPressed(const ui::KeyEvent& event) {
325 // Disable space key to press the button. The keyboard events received
326 // by this view are forwarded from a Textfield (SearchBoxView) and key
327 // released events are not forwarded. This leaves the button in pressed
329 if (event.key_code() == ui::VKEY_SPACE)
332 return CustomButton::OnKeyPressed(event);
335 void AppListItemView::OnMouseReleased(const ui::MouseEvent& event) {
336 CustomButton::OnMouseReleased(event);
337 apps_grid_view_->EndDrag(false);
338 mouse_drag_timer_.Stop();
339 SetUIState(UI_STATE_NORMAL);
342 void AppListItemView::OnMouseCaptureLost() {
343 // We don't cancel the dag on mouse capture lost for windows as entering a
344 // synchronous drag causes mouse capture to be lost and pressing escape
345 // dismisses the app list anyway.
347 CustomButton::OnMouseCaptureLost();
348 apps_grid_view_->EndDrag(true);
349 mouse_drag_timer_.Stop();
350 SetUIState(UI_STATE_NORMAL);
354 bool AppListItemView::OnMouseDragged(const ui::MouseEvent& event) {
355 CustomButton::OnMouseDragged(event);
356 apps_grid_view_->UpdateDragFromItem(AppsGridView::MOUSE, event);
358 // Shows dragging UI when it's confirmed without waiting for the timer.
359 if (ui_state_ != UI_STATE_DRAGGING &&
360 apps_grid_view_->dragging() &&
361 apps_grid_view_->IsDraggedView(this)) {
362 mouse_drag_timer_.Stop();
363 SetUIState(UI_STATE_DRAGGING);
368 void AppListItemView::OnGestureEvent(ui::GestureEvent* event) {
369 switch (event->type()) {
370 case ui::ET_GESTURE_SCROLL_BEGIN:
371 if (touch_dragging_) {
372 apps_grid_view_->InitiateDrag(this, AppsGridView::TOUCH, *event);
376 case ui::ET_GESTURE_SCROLL_UPDATE:
377 if (touch_dragging_) {
378 apps_grid_view_->UpdateDragFromItem(AppsGridView::TOUCH, *event);
382 case ui::ET_GESTURE_SCROLL_END:
383 case ui::ET_SCROLL_FLING_START:
384 if (touch_dragging_) {
385 SetTouchDragging(false);
386 apps_grid_view_->EndDrag(false);
390 case ui::ET_GESTURE_LONG_PRESS:
391 if (!apps_grid_view_->has_dragged_view())
392 SetTouchDragging(true);
395 case ui::ET_GESTURE_LONG_TAP:
396 case ui::ET_GESTURE_END:
398 SetTouchDragging(false);
403 if (!event->handled())
404 CustomButton::OnGestureEvent(event);
407 } // namespace app_list