Views Omnibox: tolerate minor click-to-select-all dragging.
[chromium-blink-merge.git] / ui / app_list / views / app_list_item_view.cc
blob54eda7d62cb146bd2b46de6d5d2f45d6a2173db7
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/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"
33 namespace app_list {
35 namespace {
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;
52 } // namespace
54 // static
55 const char AppListItemView::kViewClassName[] = "ui/app_list/AppListItemView";
57 AppListItemView::AppListItemView(AppsGridView* apps_grid_view,
58 AppListItem* item)
59 : CustomButton(apps_grid_view),
60 item_(item),
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());
76 title_->Invalidate();
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));
83 AddChildView(icon_);
84 AddChildView(title_);
85 AddChildView(progress_bar_);
87 ItemIconChanged();
88 ItemTitleChanged();
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)
104 return;
106 icon_size_ = size;
107 UpdateIcon();
110 void AppListItemView::UpdateIcon() {
111 // Skip if |icon_size_| has not been determined.
112 if (icon_size_.IsEmpty())
113 return;
115 gfx::ImageSkia icon = item_->icon();
116 // Clear icon and bail out if item icon is empty.
117 if (icon.isNull()) {
118 icon_->SetImage(NULL);
119 return;
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,
127 icon_shadows_));
128 icon_->SetImage(shadow);
129 return;
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)
142 return;
144 ui_state_ = state;
146 #if defined(USE_AURA)
147 switch (ui_state_) {
148 case UI_STATE_NORMAL:
149 title_->SetVisible(!item_->is_installing());
150 progress_bar_->SetVisible(item_->is_installing());
151 break;
152 case UI_STATE_DRAGGING:
153 title_->SetVisible(false);
154 progress_bar_->SetVisible(false);
155 break;
156 case UI_STATE_DROPPING_IN_FOLDER:
157 break;
159 #if !defined(OS_WIN)
160 ui::ScopedLayerAnimationSettings settings(layer()->GetAnimator());
161 switch (ui_state_) {
162 case UI_STATE_NORMAL:
163 layer()->SetTransform(gfx::Transform());
164 break;
165 case UI_STATE_DRAGGING: {
166 const gfx::Rect bounds(layer()->bounds().size());
167 layer()->SetTransform(gfx::GetScaleTransform(
168 bounds.CenterPoint(),
169 kDraggingIconScale));
170 break;
172 case UI_STATE_DROPPING_IN_FOLDER:
173 break;
176 SchedulePaint();
177 #endif // !OS_WIN
178 #endif // USE_AURA
181 void AppListItemView::SetTouchDragging(bool touch_dragging) {
182 if (touch_dragging_ == touch_dragging)
183 return;
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);
220 else
221 SetUIState(UI_STATE_NORMAL);
224 void AppListItemView::ItemIconChanged() {
225 UpdateIcon();
228 void AppListItemView::ItemTitleChanged() {
229 title_->SetText(base::UTF8ToUTF16(item_->title()));
230 title_->Invalidate();
231 UpdateTooltip();
232 Layout();
235 void AppListItemView::ItemHighlightedChanged() {
236 apps_grid_view_->EnsureViewVisible(this);
237 SchedulePaint();
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());
245 SchedulePaint();
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)
253 return;
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,
275 title_size.width(),
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))
289 return;
291 gfx::Rect rect(GetContentsBounds());
292 if (item_->highlighted() && !item_->is_installing()) {
293 canvas->FillRect(rect, kHighlightedColor);
294 return;
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);
309 SkPaint paint;
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();
326 if (!menu_model)
327 return;
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)
335 return;
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);
347 } else {
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)
360 return false;
362 return views::CustomButton::ShouldEnterPushedState(event);
365 bool AppListItemView::OnMousePressed(const ui::MouseEvent& event) {
366 CustomButton::OnMousePressed(event);
368 if (!ShouldEnterPushedState(event))
369 return true;
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);
378 return true;
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
385 // state.
386 if (event.key_code() == ui::VKEY_SPACE)
387 return false;
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.
401 #if !defined(OS_WIN)
402 CustomButton::OnMouseCaptureLost();
403 apps_grid_view_->EndDrag(true);
404 #endif
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);
419 return true;
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);
427 event->SetHandled();
429 break;
430 case ui::ET_GESTURE_SCROLL_UPDATE:
431 if (touch_dragging_ && apps_grid_view_->IsDraggedView(this)) {
432 apps_grid_view_->UpdateDragFromItem(AppsGridView::TOUCH, *event);
433 event->SetHandled();
435 break;
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);
441 event->SetHandled();
443 break;
444 case ui::ET_GESTURE_LONG_PRESS:
445 if (!apps_grid_view_->has_dragged_view())
446 SetTouchDragging(true);
447 event->SetHandled();
448 break;
449 case ui::ET_GESTURE_LONG_TAP:
450 case ui::ET_GESTURE_END:
451 if (touch_dragging_)
452 SetTouchDragging(false);
453 break;
454 default:
455 break;
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