app_list: Fix label misalignment after installation.
[chromium-blink-merge.git] / ui / app_list / views / app_list_item_view.cc
blob4f41df4906bbedf8d9a67e88ff9f045a7f187bba
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/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"
29 namespace app_list {
31 namespace {
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;
57 } // namespace
59 // static
60 const char AppListItemView::kViewClassName[] = "ui/app_list/AppListItemView";
62 AppListItemView::AppListItemView(AppsGridView* apps_grid_view,
63 AppListItemModel* model)
64 : CustomButton(apps_grid_view),
65 model_(model),
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());
80 title_->Invalidate();
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));
87 AddChildView(icon_);
88 AddChildView(title_);
90 ItemIconChanged();
91 ItemTitleChanged();
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)
106 return;
108 icon_size_ = size;
109 UpdateIcon();
112 void AppListItemView::UpdateIcon() {
113 // Skip if |icon_size_| has not been determined.
114 if (icon_size_.IsEmpty())
115 return;
117 gfx::ImageSkia icon = model_->icon();
118 // Clear icon and bail out if model icon is empty.
119 if (icon.isNull()) {
120 icon_->SetImage(NULL);
121 return;
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,
129 icon_shadows_));
130 icon_->SetImage(shadow);
131 return;
134 icon_->SetImage(resized);
137 void AppListItemView::SetUIState(UIState state) {
138 if (ui_state_ == state)
139 return;
141 ui_state_ = state;
143 #if defined(USE_AURA)
144 ui::ScopedLayerAnimationSettings settings(layer()->GetAnimator());
145 switch (ui_state_) {
146 case UI_STATE_NORMAL:
147 title_->SetVisible(true);
148 layer()->SetTransform(gfx::Transform());
149 break;
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));
156 break;
158 #endif
161 void AppListItemView::SetTouchDragging(bool touch_dragging) {
162 if (touch_dragging_ == touch_dragging)
163 return;
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() {
179 UpdateIcon();
182 void AppListItemView::ItemTitleChanged() {
183 title_->SetText(UTF8ToUTF16(model_->title()));
184 title_->Invalidate();
185 Layout();
188 void AppListItemView::ItemHighlightedChanged() {
189 apps_grid_view_->EnsureViewVisible(this);
190 SchedulePaint();
193 void AppListItemView::ItemIsInstallingChanged() {
194 if (model_->is_installing())
195 apps_grid_view_->EnsureViewVisible(this);
196 title_->SetVisible(!model_->is_installing());
197 SchedulePaint();
200 void AppListItemView::ItemPercentDownloadedChanged() {
201 SchedulePaint();
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,
223 title_size.width(),
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))
231 return;
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());
269 x += bar_width;
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();
283 if (!menu_model)
284 return;
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)
291 return;
294 void AppListItemView::StateChanged() {
295 if (state() == STATE_HOVERED || state() == STATE_PRESSED) {
296 apps_grid_view_->SetSelectedView(this);
297 title_->SetEnabledColor(kTitleHoverColor);
298 } else {
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)
310 return false;
312 return views::CustomButton::ShouldEnterPushedState(event);
315 bool AppListItemView::OnMousePressed(const ui::MouseEvent& event) {
316 CustomButton::OnMousePressed(event);
318 if (!ShouldEnterPushedState(event))
319 return true;
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);
328 return true;
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
335 // state.
336 if (event.key_code() == ui::VKEY_SPACE)
337 return false;
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);
367 return true;
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);
375 event->SetHandled();
377 break;
378 case ui::ET_GESTURE_SCROLL_UPDATE:
379 if (touch_dragging_) {
380 apps_grid_view_->UpdateDrag(this, AppsGridView::TOUCH, *event);
381 event->SetHandled();
383 break;
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);
389 event->SetHandled();
391 break;
392 case ui::ET_GESTURE_LONG_PRESS:
393 if (!apps_grid_view_->has_dragged_view())
394 SetTouchDragging(true);
395 event->SetHandled();
396 break;
397 case ui::ET_GESTURE_LONG_TAP:
398 case ui::ET_GESTURE_END:
399 if (touch_dragging_)
400 SetTouchDragging(false);
401 break;
402 default:
403 break;
405 if (!event->handled())
406 CustomButton::OnGestureEvent(event);
409 } // namespace app_list