summaryrefslogtreecommitdiffstats
path: root/ui
diff options
context:
space:
mode:
authorxiyuan@chromium.org <xiyuan@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2012-05-09 04:11:45 +0000
committerxiyuan@chromium.org <xiyuan@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2012-05-09 04:11:45 +0000
commit0039db9b4f365a4c6b1bbd72b4ca4e2aeb65c354 (patch)
tree5a50f8caa29ce4038da05b20f18161887c3c8fc3 /ui
parenteb951d02de207130faca3a648d481108c97a8525 (diff)
downloadchromium_src-0039db9b4f365a4c6b1bbd72b4ca4e2aeb65c354.zip
chromium_src-0039db9b4f365a4c6b1bbd72b4ca4e2aeb65c354.tar.gz
chromium_src-0039db9b4f365a4c6b1bbd72b4ca4e2aeb65c354.tar.bz2
Move app list from ash to ui.
The goal is to make app list an independent component so that it could be reused in elsewhere. BUG=none. TEST=none. Review URL: https://chromiumcodereview.appspot.com/10388032 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@135981 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'ui')
-rw-r--r--ui/app_list/DEPS6
-rw-r--r--ui/app_list/OWNERS1
-rw-r--r--ui/app_list/app_list.gyp70
-rw-r--r--ui/app_list/app_list_bubble_border.cc213
-rw-r--r--ui/app_list/app_list_bubble_border.h53
-rw-r--r--ui/app_list/app_list_export.h30
-rw-r--r--ui/app_list/app_list_item_model.cc50
-rw-r--r--ui/app_list/app_list_item_model.h65
-rw-r--r--ui/app_list/app_list_item_model_observer.h30
-rw-r--r--ui/app_list/app_list_item_view.cc404
-rw-r--r--ui/app_list/app_list_item_view.h112
-rw-r--r--ui/app_list/app_list_model.cc39
-rw-r--r--ui/app_list/app_list_model.h48
-rw-r--r--ui/app_list/app_list_model_view.cc309
-rw-r--r--ui/app_list/app_list_model_view.h98
-rw-r--r--ui/app_list/app_list_model_view_unittest.cc69
-rw-r--r--ui/app_list/app_list_view.cc275
-rw-r--r--ui/app_list/app_list_view.h86
-rw-r--r--ui/app_list/app_list_view_delegate.h40
-rw-r--r--ui/app_list/drop_shadow_label.cc63
-rw-r--r--ui/app_list/drop_shadow_label.h46
-rw-r--r--ui/app_list/icon_cache.cc90
-rw-r--r--ui/app_list/icon_cache.h59
-rw-r--r--ui/app_list/page_switcher.cc158
-rw-r--r--ui/app_list/page_switcher.h49
-rw-r--r--ui/app_list/pagination_model.cc50
-rw-r--r--ui/app_list/pagination_model.h49
-rw-r--r--ui/app_list/pagination_model_observer.h27
-rw-r--r--ui/app_list/run_all_unittests.cc9
29 files changed, 2598 insertions, 0 deletions
diff --git a/ui/app_list/DEPS b/ui/app_list/DEPS
new file mode 100644
index 0000000..428414c
--- /dev/null
+++ b/ui/app_list/DEPS
@@ -0,0 +1,6 @@
+include_rules = [
+ "+base",
+ "+ui",
+ "+testing",
+ "+third_party/skia",
+]
diff --git a/ui/app_list/OWNERS b/ui/app_list/OWNERS
new file mode 100644
index 0000000..ff3209c
--- /dev/null
+++ b/ui/app_list/OWNERS
@@ -0,0 +1 @@
+xiyuan@chromium.org
diff --git a/ui/app_list/app_list.gyp b/ui/app_list/app_list.gyp
new file mode 100644
index 0000000..5e771de
--- /dev/null
+++ b/ui/app_list/app_list.gyp
@@ -0,0 +1,70 @@
+# Copyright (c) 2012 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+{
+ 'variables': {
+ 'chromium_code': 1,
+ },
+ 'targets': [
+ {
+ 'target_name': 'app_list',
+ 'type': '<(component)',
+ 'dependencies': [
+ '../../base/base.gyp:base',
+ '../../base/third_party/dynamic_annotations/dynamic_annotations.gyp:dynamic_annotations',
+ '../../skia/skia.gyp:skia',
+ '../aura/aura.gyp:aura',
+ '../compositor/compositor.gyp:compositor',
+ '../ui.gyp:ui',
+ '../views/views.gyp:views',
+ ],
+ 'defines': [
+ 'APP_LIST_IMPLEMENTATION',
+ ],
+ 'sources': [
+ 'app_list_bubble_border.cc',
+ 'app_list_bubble_border.h',
+ 'app_list_item_model.cc',
+ 'app_list_item_model.h',
+ 'app_list_item_model_observer.h',
+ 'app_list_item_view.cc',
+ 'app_list_item_view.h',
+ 'app_list_model.cc',
+ 'app_list_model.h',
+ 'app_list_model_view.cc',
+ 'app_list_model_view.h',
+ 'app_list_view.cc',
+ 'app_list_view.h',
+ 'app_list_view_delegate.h',
+ 'drop_shadow_label.cc',
+ 'drop_shadow_label.h',
+ 'icon_cache.cc',
+ 'icon_cache.h',
+ 'page_switcher.cc',
+ 'page_switcher.h',
+ 'pagination_model.cc',
+ 'pagination_model.h',
+ 'pagination_model_observer.h',
+ ],
+ },
+ {
+ 'target_name': 'app_list_unittests',
+ 'type': 'executable',
+ 'dependencies': [
+ '../../base/base.gyp:base',
+ '../../base/base.gyp:test_support_base',
+ '../../skia/skia.gyp:skia',
+ '../../testing/gtest.gyp:gtest',
+ '../compositor/compositor.gyp:compositor',
+ '../compositor/compositor.gyp:compositor_test_support',
+ '../views/views.gyp:views',
+ 'app_list',
+ ],
+ 'sources': [
+ 'app_list_model_view_unittest.cc',
+ 'run_all_unittests.cc',
+ ],
+ },
+ ],
+}
diff --git a/ui/app_list/app_list_bubble_border.cc b/ui/app_list/app_list_bubble_border.cc
new file mode 100644
index 0000000..145dea9
--- /dev/null
+++ b/ui/app_list/app_list_bubble_border.cc
@@ -0,0 +1,213 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "ui/app_list/app_list_bubble_border.h"
+
+#include "third_party/skia/include/core/SkPath.h"
+#include "third_party/skia/include/core/SkPaint.h"
+#include "third_party/skia/include/effects/SkBlurDrawLooper.h"
+#include "third_party/skia/include/effects/SkGradientShader.h"
+#include "ui/gfx/canvas.h"
+
+namespace {
+
+const int kCornerRadius = 3;
+
+const int kArrowHeight = 10;
+const int kArrowWidth = 20;
+
+const SkColor kBorderColor = SkColorSetARGB(0xFF, 0, 0, 0);
+const int kBorderSize = 1;
+
+const SkColor kShadowColor = SkColorSetARGB(0xFF, 0, 0, 0);
+const int kShadowRadius = 4;
+
+const SkColor kModelViewGradientColor1 = SkColorSetARGB(0xFF, 0xFE, 0xFE, 0xFE);
+const SkColor kModelViewGradientColor2 = SkColorSetARGB(0xFF, 0xF8, 0xF8, 0xF8);
+const int kModelViewGradientSize = 10;
+
+const SkColor kFooterBorderGradientColor1 =
+ SkColorSetARGB(0xFF, 0xA0, 0xA0, 0xA0);
+const SkColor kFooterBorderGradientColor2 =
+ SkColorSetARGB(0xFF, 0xD4, 0xD4, 0xD4);
+const int kFooterBorderSize = 3;
+const SkColor kFooterBackground = SkColorSetARGB(0xFF, 0xDC, 0xDC, 0xDC);
+
+// TODO(xiyuan): Merge this with the one in skia_util.
+SkShader* CreateVerticalGradientShader(int start_point,
+ int end_point,
+ SkColor start_color,
+ SkColor end_color,
+ SkShader::TileMode mode) {
+ SkColor grad_colors[2] = { start_color, end_color};
+ SkPoint grad_points[2];
+ grad_points[0].iset(0, start_point);
+ grad_points[1].iset(0, end_point);
+
+ return SkGradientShader::CreateLinear(grad_points,
+ grad_colors,
+ NULL,
+ 2,
+ mode);
+}
+
+// Builds a bubble shape for given |bounds|.
+void BuildShape(const gfx::Rect& bounds,
+ SkScalar padding,
+ SkScalar arrow_offset,
+ SkPath* path) {
+ const SkScalar left = SkIntToScalar(bounds.x()) + padding;
+ const SkScalar top = SkIntToScalar(bounds.y()) + padding;
+ const SkScalar right = SkIntToScalar(bounds.right()) - padding;
+ const SkScalar bottom = SkIntToScalar(bounds.bottom()) - padding;
+
+ const SkScalar center_x = SkIntToScalar((bounds.x() + bounds.right()) / 2);
+ const SkScalar center_y = SkIntToScalar((bounds.y() + bounds.bottom()) / 2);
+
+ const SkScalar half_array_width = SkIntToScalar(kArrowWidth / 2);
+ const SkScalar arrow_height = SkIntToScalar(kArrowHeight) - padding;
+
+ path->reset();
+ path->incReserve(12);
+
+ path->moveTo(center_x, top);
+ path->arcTo(left, top, left, center_y, SkIntToScalar(kCornerRadius));
+ path->arcTo(left, bottom, center_x - half_array_width, bottom,
+ SkIntToScalar(kCornerRadius));
+ path->lineTo(center_x + arrow_offset - half_array_width, bottom);
+ path->lineTo(center_x + arrow_offset, bottom + arrow_height);
+ path->lineTo(center_x + arrow_offset + half_array_width, bottom);
+ path->arcTo(right, bottom, right, center_y, SkIntToScalar(kCornerRadius));
+ path->arcTo(right, top, center_x, top, SkIntToScalar(kCornerRadius));
+ path->close();
+}
+
+} // namespace
+
+namespace app_list {
+
+AppListBubbleBorder::AppListBubbleBorder(views::View* app_list_view)
+ : views::BubbleBorder(views::BubbleBorder::BOTTOM_RIGHT,
+ views::BubbleBorder::NO_SHADOW),
+ app_list_view_(app_list_view),
+ arrow_offset_(0) {
+}
+
+AppListBubbleBorder::~AppListBubbleBorder() {
+}
+
+void AppListBubbleBorder::PaintModelViewBackground(
+ gfx::Canvas* canvas,
+ const gfx::Rect& bounds) const {
+ const views::View* page_switcher = app_list_view_->child_at(1);
+ const gfx::Rect page_switcher_bounds =
+ app_list_view_->ConvertRectToWidget(page_switcher->bounds());
+ gfx::Rect rect(bounds.x(),
+ bounds.y(),
+ bounds.width(),
+ page_switcher_bounds.y() - bounds.y());
+
+ SkPaint paint;
+ paint.setStyle(SkPaint::kFill_Style);
+ SkSafeUnref(paint.setShader(CreateVerticalGradientShader(
+ rect.y(),
+ rect.y() + kModelViewGradientSize,
+ kModelViewGradientColor1,
+ kModelViewGradientColor2,
+ SkShader::kClamp_TileMode)));
+ canvas->DrawRect(rect, paint);
+}
+
+void AppListBubbleBorder::PaintPageSwitcherBackground(
+ gfx::Canvas* canvas,
+ const gfx::Rect& bounds) const {
+ const views::View* page_switcher = app_list_view_->child_at(1);
+ const gfx::Rect page_switcher_bounds =
+ app_list_view_->ConvertRectToWidget(page_switcher->bounds());
+
+ gfx::Rect rect(bounds.x(),
+ page_switcher_bounds.y(),
+ bounds.width(),
+ kFooterBorderSize);
+ SkPaint paint;
+ paint.setStyle(SkPaint::kFill_Style);
+ SkSafeUnref(paint.setShader(CreateVerticalGradientShader(
+ rect.y(),
+ rect.bottom(),
+ kFooterBorderGradientColor1,
+ kFooterBorderGradientColor2,
+ SkShader::kClamp_TileMode)));
+ canvas->DrawRect(rect, paint);
+
+ rect.set_y(rect.bottom());
+ rect.set_height(bounds.bottom() - rect.y() + kArrowHeight - kBorderSize);
+ canvas->FillRect(rect, kFooterBackground);
+}
+
+void AppListBubbleBorder::GetInsets(gfx::Insets* insets) const {
+ insets->Set(kShadowRadius + kBorderSize,
+ kShadowRadius + kBorderSize,
+ kShadowRadius + kBorderSize + kArrowHeight,
+ kShadowRadius + kBorderSize);
+}
+
+gfx::Rect AppListBubbleBorder::GetBounds(
+ const gfx::Rect& position_relative_to,
+ const gfx::Size& contents_size) const {
+ gfx::Size border_size(contents_size);
+ gfx::Insets insets;
+ GetInsets(&insets);
+ border_size.Enlarge(insets.width(), insets.height());
+
+ int anchor_x = (position_relative_to.x() + position_relative_to.right()) / 2;
+ int arrow_tip_x = border_size.width() / 2 + arrow_offset_;
+
+ return gfx::Rect(
+ gfx::Point(anchor_x - arrow_tip_x,
+ position_relative_to.y() - border_size.height() +
+ kShadowRadius),
+ border_size);
+}
+
+void AppListBubbleBorder::Paint(const views::View& view,
+ gfx::Canvas* canvas) const {
+ gfx::Insets insets;
+ GetInsets(&insets);
+
+ gfx::Rect bounds = view.bounds();
+ bounds.Inset(insets);
+
+ SkPath path;
+
+ SkPaint paint;
+ paint.setAntiAlias(true);
+ paint.setStyle(SkPaint::kStroke_Style);
+ paint.setColor(kBorderColor);
+ SkSafeUnref(paint.setLooper(
+ new SkBlurDrawLooper(kShadowRadius,
+ 0, 0,
+ kShadowColor,
+ SkBlurDrawLooper::kHighQuality_BlurFlag)));
+ // Pads with 0.5 pixel since anti alias is used.
+ BuildShape(bounds,
+ SkDoubleToScalar(0.5),
+ SkIntToScalar(arrow_offset_),
+ &path);
+ canvas->DrawPath(path, paint);
+
+ // Pads with kBoprderSize pixels to leave space for border lines.
+ BuildShape(bounds,
+ SkIntToScalar(kBorderSize),
+ SkIntToScalar(arrow_offset_),
+ &path);
+ canvas->Save();
+ canvas->ClipPath(path);
+
+ PaintModelViewBackground(canvas, bounds);
+ PaintPageSwitcherBackground(canvas, bounds);
+
+ canvas->Restore();
+}
+
+} // namespace app_list
diff --git a/ui/app_list/app_list_bubble_border.h b/ui/app_list/app_list_bubble_border.h
new file mode 100644
index 0000000..53d7bed
--- /dev/null
+++ b/ui/app_list/app_list_bubble_border.h
@@ -0,0 +1,53 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef UI_APP_LIST_APP_LIST_BUBBLE_BORDER_H_
+#define UI_APP_LIST_APP_LIST_BUBBLE_BORDER_H_
+#pragma once
+
+#include "base/basictypes.h"
+#include "ui/views/bubble/bubble_border.h"
+
+namespace app_list {
+
+// A class to paint bubble border and background.
+class AppListBubbleBorder : public views::BubbleBorder {
+ public:
+ explicit AppListBubbleBorder(views::View* app_list_view);
+ virtual ~AppListBubbleBorder();
+
+ int arrow_offset() const {
+ return arrow_offset_;
+ }
+ void set_arrow_offset(int arrow_offset) {
+ arrow_offset_ = arrow_offset;
+ }
+
+ private:
+ void PaintModelViewBackground(gfx::Canvas* canvas,
+ const gfx::Rect& bounds) const;
+ void PaintPageSwitcherBackground(gfx::Canvas* canvas,
+ const gfx::Rect& bounds) const;
+
+ // views::BubbleBorder overrides:
+ virtual void GetInsets(gfx::Insets* insets) const OVERRIDE;
+ virtual gfx::Rect GetBounds(const gfx::Rect& position_relative_to,
+ const gfx::Size& contents_size) const OVERRIDE;
+
+ // views::Border overrides:
+ virtual void Paint(const views::View& view,
+ gfx::Canvas* canvas) const OVERRIDE;
+
+ // AppListView hosted inside this bubble.
+ views::View* app_list_view_;
+
+ // Offset in pixels relative the default middle position.
+ int arrow_offset_;
+
+ DISALLOW_COPY_AND_ASSIGN(AppListBubbleBorder);
+};
+
+} // namespace app_list
+
+#endif // UI_APP_LIST_APP_LIST_BUBBLE_BORDER_H_
diff --git a/ui/app_list/app_list_export.h b/ui/app_list/app_list_export.h
new file mode 100644
index 0000000..0dbcb6e
--- /dev/null
+++ b/ui/app_list/app_list_export.h
@@ -0,0 +1,30 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef UI_APP_LIST_APP_LIST_EXPORT_H_
+#define UI_APP_LIST_APP_LIST_EXPORT_H_
+#pragma once
+
+// Defines APP_LIST_EXPORT so that functionality implemented by the aura_shell
+// module can be exported to consumers.
+
+#if defined(COMPONENT_BUILD)
+#if defined(WIN32)
+
+#if defined(APP_LIST_IMPLEMENTATION)
+#define APP_LIST_EXPORT __declspec(dllexport)
+#else
+#define APP_LIST_EXPORT __declspec(dllimport)
+#endif // defined(APP_LIST_IMPLEMENTATION)
+
+#else // defined(WIN32)
+#define APP_LIST_EXPORT __attribute__((visibility("default")))
+#endif
+
+#else // defined(COMPONENT_BUILD)
+#define APP_LIST_EXPORT
+#endif
+
+#endif // UI_APP_LIST_APP_LIST_EXPORT_H_
+
diff --git a/ui/app_list/app_list_item_model.cc b/ui/app_list/app_list_item_model.cc
new file mode 100644
index 0000000..6c052cb
--- /dev/null
+++ b/ui/app_list/app_list_item_model.cc
@@ -0,0 +1,50 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "ui/app_list/app_list_item_model.h"
+
+#include "ui/app_list/app_list_item_model_observer.h"
+
+namespace app_list {
+
+AppListItemModel::AppListItemModel() : highlighted_(false) {
+}
+
+AppListItemModel::~AppListItemModel() {
+}
+
+void AppListItemModel::SetIcon(const SkBitmap& icon) {
+ icon_ = icon;
+ FOR_EACH_OBSERVER(AppListItemModelObserver, observers_,
+ ItemIconChanged());
+}
+
+void AppListItemModel::SetTitle(const std::string& title) {
+ title_ = title;
+ FOR_EACH_OBSERVER(AppListItemModelObserver, observers_,
+ ItemTitleChanged());
+}
+
+void AppListItemModel::SetHighlighted(bool highlighted) {
+ if (highlighted_ == highlighted)
+ return;
+
+ highlighted_ = highlighted;
+ FOR_EACH_OBSERVER(AppListItemModelObserver, observers_,
+ ItemHighlightedChanged());
+}
+
+void AppListItemModel::AddObserver(AppListItemModelObserver* observer) {
+ observers_.AddObserver(observer);
+}
+
+void AppListItemModel::RemoveObserver(AppListItemModelObserver* observer) {
+ observers_.RemoveObserver(observer);
+}
+
+ui::MenuModel* AppListItemModel::GetContextMenuModel() {
+ return NULL;
+}
+
+} // namespace app_list
diff --git a/ui/app_list/app_list_item_model.h b/ui/app_list/app_list_item_model.h
new file mode 100644
index 0000000..3729b95
--- /dev/null
+++ b/ui/app_list/app_list_item_model.h
@@ -0,0 +1,65 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef UI_APP_LIST_APP_LIST_ITEM_MODEL_H_
+#define UI_APP_LIST_APP_LIST_ITEM_MODEL_H_
+#pragma once
+
+#include <string>
+
+#include "base/basictypes.h"
+#include "base/observer_list.h"
+#include "third_party/skia/include/core/SkBitmap.h"
+#include "ui/app_list/app_list_export.h"
+
+namespace ui {
+class MenuModel;
+}
+
+namespace app_list {
+
+class AppListItemModelObserver;
+
+// AppListItemModel provides icon and title to be shown in a AppListItemView
+// and action to be executed when the AppListItemView is activated.
+class APP_LIST_EXPORT AppListItemModel {
+ public:
+ AppListItemModel();
+ virtual ~AppListItemModel();
+
+ void SetIcon(const SkBitmap& icon);
+ const SkBitmap& icon() const {
+ return icon_;
+ }
+
+ void SetTitle(const std::string& title);
+ const std::string& title() const {
+ return title_;
+ }
+
+ void SetHighlighted(bool highlighted);
+ bool highlighted() const {
+ return highlighted_;
+ }
+
+ void AddObserver(AppListItemModelObserver* observer);
+ void RemoveObserver(AppListItemModelObserver* observer);
+
+ // Returns the context menu model for this item.
+ // Note the menu model is owned by this item.
+ virtual ui::MenuModel* GetContextMenuModel();
+
+ private:
+ SkBitmap icon_;
+ std::string title_;
+ bool highlighted_;
+
+ ObserverList<AppListItemModelObserver> observers_;
+
+ DISALLOW_COPY_AND_ASSIGN(AppListItemModel);
+};
+
+} // namespace app_list
+
+#endif // UI_APP_LIST_APP_LIST_ITEM_MODEL_H_
diff --git a/ui/app_list/app_list_item_model_observer.h b/ui/app_list/app_list_item_model_observer.h
new file mode 100644
index 0000000..813b6d7
--- /dev/null
+++ b/ui/app_list/app_list_item_model_observer.h
@@ -0,0 +1,30 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef UI_APP_LIST_APP_LIST_ITEM_MODEL_OBSERVER_H_
+#define UI_APP_LIST_APP_LIST_ITEM_MODEL_OBSERVER_H_
+#pragma once
+
+#include "ui/app_list/app_list_export.h"
+
+namespace app_list {
+
+class APP_LIST_EXPORT AppListItemModelObserver {
+ public:
+ // Invoked after item's icon is changed.
+ virtual void ItemIconChanged() = 0;
+
+ // Invoked after item's title is changed.
+ virtual void ItemTitleChanged() = 0;
+
+ // Invoked after item's highlighted state is changed.
+ virtual void ItemHighlightedChanged() = 0;
+
+ protected:
+ virtual ~AppListItemModelObserver() {}
+};
+
+} // namespace app_list
+
+#endif // UI_APP_LIST_APP_LIST_ITEM_MODEL_OBSERVER_H_
diff --git a/ui/app_list/app_list_item_view.cc b/ui/app_list/app_list_item_view.cc
new file mode 100644
index 0000000..26bff6a
--- /dev/null
+++ b/ui/app_list/app_list_item_view.cc
@@ -0,0 +1,404 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "ui/app_list/app_list_item_view.h"
+
+#include <algorithm>
+
+#include "base/bind.h"
+#include "base/message_loop.h"
+#include "base/synchronization/cancellation_flag.h"
+#include "base/threading/worker_pool.h"
+#include "base/utf_string_conversions.h"
+#include "ui/app_list/app_list_item_model.h"
+#include "ui/app_list/app_list_model_view.h"
+#include "ui/app_list/drop_shadow_label.h"
+#include "ui/app_list/icon_cache.h"
+#include "ui/base/accessibility/accessible_view_state.h"
+#include "ui/base/animation/throb_animation.h"
+#include "ui/base/resource/resource_bundle.h"
+#include "ui/gfx/canvas.h"
+#include "ui/gfx/font.h"
+#include "ui/gfx/shadow_value.h"
+#include "ui/gfx/skbitmap_operations.h"
+#include "ui/views/controls/image_view.h"
+#include "ui/views/controls/menu/menu_item_view.h"
+#include "ui/views/controls/menu/menu_model_adapter.h"
+#include "ui/views/controls/menu/menu_runner.h"
+
+namespace app_list {
+
+namespace {
+
+const int kTopBottomPadding = 10;
+const int kIconTitleSpacing = 10;
+
+const SkColor kTitleColor = SK_ColorWHITE;
+const SkColor kTitleColorV2 = SkColorSetARGB(0xFF, 0x88, 0x88, 0x88);
+
+// 0.33 black
+const SkColor kHoverAndPushedColor = SkColorSetARGB(0x55, 0x00, 0x00, 0x00);
+
+// 0.16 black
+const SkColor kSelectedColor = SkColorSetARGB(0x2A, 0x00, 0x00, 0x00);
+
+const SkColor kHighlightedColor = kHoverAndPushedColor;
+
+// FontSize/IconSize ratio = 24 / 128, which means we should get 24 font size
+// when icon size is 128.
+const float kFontSizeToIconSizeRatio = 0.1875f;
+
+// Font smaller than kBoldFontSize needs to be bold.
+const int kBoldFontSize = 14;
+
+const int kMinFontSize = 12;
+
+const int kMinTitleChars = 15;
+
+const int kLeftRightPaddingChars = 1;
+
+const gfx::Font& GetTitleFontForIconSize(const gfx::Size& size) {
+ static int icon_height;
+ static gfx::Font* font = NULL;
+
+ if (font && icon_height == size.height())
+ return *font;
+
+ delete font;
+
+ icon_height = size.height();
+ int font_size = std::max(
+ static_cast<int>(icon_height * kFontSizeToIconSizeRatio),
+ kMinFontSize);
+
+ ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
+ gfx::Font title_font(rb.GetFont(ui::ResourceBundle::BaseFont).GetFontName(),
+ font_size);
+ if (font_size <= kBoldFontSize)
+ title_font = title_font.DeriveFont(0, gfx::Font::BOLD);
+ font = new gfx::Font(title_font);
+ return *font;
+}
+
+// An image view that is not interactive.
+class StaticImageView : public views::ImageView {
+ public:
+ StaticImageView() : ImageView() {
+ }
+
+ private:
+ // views::View overrides:
+ virtual bool HitTest(const gfx::Point& l) const OVERRIDE {
+ return false;
+ }
+
+ DISALLOW_COPY_AND_ASSIGN(StaticImageView);
+};
+
+// A minimum title width set by test to override the default logic that derives
+// the min width from font.
+int g_min_title_width = 0;
+
+} // namespace
+
+// static
+const char AppListItemView::kViewClassName[] = "ui/app_list/AppListItemView";
+
+// AppListItemView::IconOperation wraps background icon processing.
+class AppListItemView::IconOperation
+ : public base::RefCountedThreadSafe<AppListItemView::IconOperation> {
+ public:
+ IconOperation(const SkBitmap& bitmap, const gfx::Size& size)
+ : bitmap_(bitmap),
+ size_(size) {
+ }
+
+ static void Run(scoped_refptr<IconOperation> op) {
+ op->ResizeAndGenerateShadow();
+ }
+
+ // Padding space around icon to contain its shadow. Note it should be at least
+ // the max size of shadow radius + shadow offset in shadow generation code.
+ static const int kShadowPadding = 15;
+
+ void ResizeAndGenerateShadow() {
+ // If you change shadow radius and shadow offset, please also update
+ // kShadowPaddingAbove.
+ const SkColor kShadowColor[] = {
+ SkColorSetARGB(0xCC, 0, 0, 0),
+ SkColorSetARGB(0x33, 0, 0, 0),
+ SkColorSetARGB(0x4C, 0, 0, 0),
+ };
+ const gfx::Point kShadowOffset[] = {
+ gfx::Point(0, 0),
+ gfx::Point(0, 4),
+ gfx::Point(0, 5),
+ };
+ const SkScalar kShadowRadius[] = {
+ SkIntToScalar(2),
+ SkIntToScalar(4),
+ SkIntToScalar(10),
+ };
+
+ if (cancel_flag_.IsSet())
+ return;
+
+ if (size_ != gfx::Size(bitmap_.width(), bitmap_.height()))
+ bitmap_ = SkBitmapOperations::CreateResizedBitmap(bitmap_, size_);
+
+ if (cancel_flag_.IsSet())
+ return;
+
+ bitmap_ = SkBitmapOperations::CreateDropShadow(
+ bitmap_,
+ arraysize(kShadowColor),
+ kShadowColor,
+ kShadowOffset,
+ kShadowRadius);
+ }
+
+ void Cancel() {
+ cancel_flag_.Set();
+ }
+
+ const SkBitmap& bitmap() const {
+ return bitmap_;
+ }
+
+ private:
+ friend class base::RefCountedThreadSafe<AppListItemView::IconOperation>;
+
+ base::CancellationFlag cancel_flag_;
+
+ SkBitmap bitmap_;
+ const gfx::Size size_;
+
+ DISALLOW_COPY_AND_ASSIGN(IconOperation);
+};
+
+AppListItemView::AppListItemView(AppListModelView* list_model_view,
+ AppListItemModel* model,
+ views::ButtonListener* listener)
+ : CustomButton(listener),
+ model_(model),
+ list_model_view_(list_model_view),
+ icon_(new StaticImageView),
+ title_(new DropShadowLabel),
+ selected_(false),
+ ALLOW_THIS_IN_INITIALIZER_LIST(apply_shadow_factory_(this)) {
+ title_->SetBackgroundColor(0);
+
+ if (list_model_view_->fixed_layout()) {
+ title_->SetEnabledColor(kTitleColorV2);
+ } else {
+ title_->SetEnabledColor(kTitleColor);
+ const gfx::ShadowValue kTitleShadows[] = {
+ gfx::ShadowValue(gfx::Point(0, 0), 1, SkColorSetARGB(0x66, 0, 0, 0)),
+ gfx::ShadowValue(gfx::Point(0, 0), 10, SkColorSetARGB(0x66, 0, 0, 0)),
+ gfx::ShadowValue(gfx::Point(0, 2), 2, SkColorSetARGB(0x66, 0, 0, 0)),
+ gfx::ShadowValue(gfx::Point(0, 2), 4, SkColorSetARGB(0x66, 0, 0, 0)),
+ };
+ title_->SetTextShadows(arraysize(kTitleShadows), kTitleShadows);
+ }
+
+ AddChildView(icon_);
+ AddChildView(title_);
+
+ ItemIconChanged();
+ ItemTitleChanged();
+ model_->AddObserver(this);
+
+ set_context_menu_controller(this);
+ set_request_focus_on_press(false);
+ set_focusable(true);
+}
+
+AppListItemView::~AppListItemView() {
+ model_->RemoveObserver(this);
+ CancelPendingIconOperation();
+}
+
+// static
+gfx::Size AppListItemView::GetPreferredSizeForIconSize(
+ const gfx::Size& icon_size) {
+ int min_title_width = g_min_title_width;
+ // Fixed 20px is used for left/right padding before switching to padding
+ // based on number of chars. It is also a number used for test case
+ // AppList.ModelViewCalculateLayout.
+ int left_right_padding = 20;
+ if (min_title_width == 0) {
+ const gfx::Font& title_font = GetTitleFontForIconSize(icon_size);
+ // Use big char such as 'G' to calculate min title width.
+ min_title_width = kMinTitleChars *
+ title_font.GetStringWidth(ASCIIToUTF16("G"));
+ left_right_padding = kLeftRightPaddingChars *
+ title_font.GetAverageCharacterWidth();
+ }
+
+ int dimension = std::max(icon_size.width() * 2, min_title_width);
+ gfx::Size size(dimension, dimension);
+ size.Enlarge(left_right_padding, kTopBottomPadding);
+ return size;
+}
+
+// static
+void AppListItemView::SetMinTitleWidth(int width) {
+ g_min_title_width = width;
+}
+
+void AppListItemView::SetIconSize(const gfx::Size& size) {
+ if (icon_size_ == size)
+ return;
+
+ icon_size_ = size;
+ title_->SetFont(GetTitleFontForIconSize(size));
+ UpdateIcon();
+}
+
+void AppListItemView::SetSelected(bool selected) {
+ if (selected == selected_)
+ return;
+
+ RequestFocus();
+ selected_ = selected;
+ SchedulePaint();
+}
+
+void AppListItemView::UpdateIcon() {
+ // Skip if |icon_size_| has not been determined.
+ if (icon_size_.IsEmpty())
+ return;
+
+ SkBitmap icon = model_->icon();
+ // Clear icon and bail out if model icon is empty.
+ if (icon.empty()) {
+ icon_->SetImage(NULL);
+ return;
+ }
+
+ CancelPendingIconOperation();
+
+ SkBitmap shadow;
+ if (IconCache::GetInstance()->Get(icon, icon_size_, &shadow)) {
+ icon_->SetImage(shadow);
+ } else {
+ // Schedule resize and shadow generation.
+ icon_op_ = new IconOperation(icon, icon_size_);
+ base::WorkerPool::PostTaskAndReply(
+ FROM_HERE,
+ base::Bind(&IconOperation::Run, icon_op_),
+ base::Bind(&AppListItemView::ApplyShadow,
+ apply_shadow_factory_.GetWeakPtr(),
+ icon_op_),
+ true /* task_is_slow */);
+ }
+}
+
+void AppListItemView::CancelPendingIconOperation() {
+ // Set canceled flag of previous request to skip unneeded processing.
+ if (icon_op_.get())
+ icon_op_->Cancel();
+
+ // Cancel reply callback for previous request.
+ apply_shadow_factory_.InvalidateWeakPtrs();
+}
+
+void AppListItemView::ApplyShadow(scoped_refptr<IconOperation> op) {
+ icon_->SetImage(op->bitmap());
+ IconCache::GetInstance()->Put(model_->icon(), icon_size_, op->bitmap());
+
+ DCHECK(op.get() == icon_op_.get());
+ icon_op_ = NULL;
+}
+
+void AppListItemView::ItemIconChanged() {
+ UpdateIcon();
+}
+
+void AppListItemView::ItemTitleChanged() {
+ title_->SetText(UTF8ToUTF16(model_->title()));
+}
+
+void AppListItemView::ItemHighlightedChanged() {
+ SchedulePaint();
+}
+
+std::string AppListItemView::GetClassName() const {
+ return kViewClassName;
+}
+
+gfx::Size AppListItemView::GetPreferredSize() {
+ return GetPreferredSizeForIconSize(icon_size_);
+}
+
+void AppListItemView::Layout() {
+ gfx::Rect rect(GetContentsBounds());
+
+ int left_right_padding = kLeftRightPaddingChars *
+ title_->font().GetAverageCharacterWidth();
+ rect.Inset(left_right_padding, kTopBottomPadding);
+
+ gfx::Size title_size = title_->GetPreferredSize();
+ int height = icon_size_.height() + kIconTitleSpacing +
+ title_size.height();
+ int y = rect.y() + (rect.height() - height) / 2;
+
+ gfx::Rect icon_bounds(rect.x(), y, rect.width(), icon_size_.height());
+ icon_bounds.Inset(0, -IconOperation::kShadowPadding);
+ icon_->SetBoundsRect(icon_bounds);
+
+ title_->SetBounds(rect.x(),
+ y + icon_size_.height() + kIconTitleSpacing,
+ rect.width(),
+ title_size.height());
+}
+
+void AppListItemView::OnPaint(gfx::Canvas* canvas) {
+ gfx::Rect rect(GetContentsBounds());
+
+ if (model_->highlighted()) {
+ canvas->FillRect(rect, kHighlightedColor);
+ } else if (hover_animation_->is_animating()) {
+ int alpha = SkColorGetA(kHoverAndPushedColor) *
+ hover_animation_->GetCurrentValue();
+ canvas->FillRect(rect, SkColorSetA(kHoverAndPushedColor, alpha));
+ } else if (state() == BS_HOT || state() == BS_PUSHED) {
+ canvas->FillRect(rect, kHoverAndPushedColor);
+ } else if (selected_) {
+ canvas->FillRect(rect, kSelectedColor);
+ }
+}
+
+void AppListItemView::GetAccessibleState(ui::AccessibleViewState* state) {
+ state->role = ui::AccessibilityTypes::ROLE_PUSHBUTTON;
+ state->name = UTF8ToUTF16(model_->title());
+}
+
+void AppListItemView::ShowContextMenuForView(views::View* source,
+ const gfx::Point& point) {
+ ui::MenuModel* menu_model = model_->GetContextMenuModel();
+ if (!menu_model)
+ return;
+
+ views::MenuModelAdapter menu_adapter(menu_model);
+ context_menu_runner_.reset(
+ new views::MenuRunner(new views::MenuItemView(&menu_adapter)));
+ menu_adapter.BuildMenu(context_menu_runner_->GetMenu());
+ if (context_menu_runner_->RunMenuAt(
+ GetWidget(), NULL, gfx::Rect(point, gfx::Size()),
+ views::MenuItemView::TOPLEFT, views::MenuRunner::HAS_MNEMONICS) ==
+ views::MenuRunner::MENU_DELETED)
+ return;
+}
+
+void AppListItemView::StateChanged() {
+ if (state() == BS_HOT || state() == BS_PUSHED) {
+ list_model_view_->SetSelectedItem(this);
+ } else {
+ list_model_view_->ClearSelectedItem(this);
+ model_->SetHighlighted(false);
+ }
+}
+
+} // namespace app_list
diff --git a/ui/app_list/app_list_item_view.h b/ui/app_list/app_list_item_view.h
new file mode 100644
index 0000000..7d293d0
--- /dev/null
+++ b/ui/app_list/app_list_item_view.h
@@ -0,0 +1,112 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef UI_APP_LIST_APP_LIST_ITEM_VIEW_H_
+#define UI_APP_LIST_APP_LIST_ITEM_VIEW_H_
+#pragma once
+
+#include <string>
+
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/memory/weak_ptr.h"
+#include "ui/app_list/app_list_export.h"
+#include "ui/app_list/app_list_item_model_observer.h"
+#include "ui/views/context_menu_controller.h"
+#include "ui/views/controls/button/custom_button.h"
+
+class SkBitmap;
+
+namespace views {
+class ImageView;
+class MenuRunner;
+}
+
+namespace app_list {
+
+class AppListItemModel;
+class AppListModelView;
+class DropShadowLabel;
+
+class APP_LIST_EXPORT AppListItemView : public views::CustomButton,
+ public views::ContextMenuController,
+ public AppListItemModelObserver {
+ public:
+ AppListItemView(AppListModelView* list_model_view,
+ AppListItemModel* model,
+ views::ButtonListener* listener);
+ virtual ~AppListItemView();
+
+ static gfx::Size GetPreferredSizeForIconSize(const gfx::Size& icon_size);
+
+ // For testing. Testing calls this function to set minimum title width in
+ // pixels to get rid dependency on default font width.
+ static void SetMinTitleWidth(int width);
+
+ void SetSelected(bool selected);
+ bool selected() const {
+ return selected_;
+ }
+
+ void SetIconSize(const gfx::Size& size);
+
+ AppListItemModel* model() const {
+ return model_;
+ }
+
+ // Internal class name.
+ static const char kViewClassName[];
+
+ private:
+ class IconOperation;
+
+ // Get icon from model and schedule background processing.
+ void UpdateIcon();
+
+ // Cancel pending icon operation and reply callback.
+ void CancelPendingIconOperation();
+
+ // Reply callback from background shadow generation. |op| is the finished
+ // operation and holds the result image.
+ void ApplyShadow(scoped_refptr<IconOperation> op);
+
+ // AppListItemModelObserver overrides:
+ virtual void ItemIconChanged() OVERRIDE;
+ virtual void ItemTitleChanged() OVERRIDE;
+ virtual void ItemHighlightedChanged() OVERRIDE;
+
+ // views::View overrides:
+ virtual std::string GetClassName() const OVERRIDE;
+ virtual gfx::Size GetPreferredSize() OVERRIDE;
+ virtual void Layout() OVERRIDE;
+ virtual void OnPaint(gfx::Canvas* canvas) OVERRIDE;
+ virtual void GetAccessibleState(ui::AccessibleViewState* state) OVERRIDE;
+
+ // views::ContextMenuController overrides:
+ virtual void ShowContextMenuForView(views::View* source,
+ const gfx::Point& point) OVERRIDE;
+
+ // views::CustomButton overrides:
+ virtual void StateChanged() OVERRIDE;
+
+ AppListItemModel* model_;
+
+ AppListModelView* list_model_view_;
+ views::ImageView* icon_;
+ DropShadowLabel* title_;
+
+ scoped_ptr<views::MenuRunner> context_menu_runner_;
+
+ gfx::Size icon_size_;
+ bool selected_;
+
+ scoped_refptr<IconOperation> icon_op_;
+ base::WeakPtrFactory<AppListItemView> apply_shadow_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(AppListItemView);
+};
+
+} // namespace app_list
+
+#endif // UI_APP_LIST_APP_LIST_ITEM_VIEW_H_
diff --git a/ui/app_list/app_list_model.cc b/ui/app_list/app_list_model.cc
new file mode 100644
index 0000000..f19f89d
--- /dev/null
+++ b/ui/app_list/app_list_model.cc
@@ -0,0 +1,39 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "ui/app_list/app_list_model.h"
+
+namespace app_list {
+
+AppListModel::AppListModel() {
+}
+
+AppListModel::~AppListModel() {
+}
+
+void AppListModel::AddItem(AppListItemModel* item) {
+ items_.Add(item);
+}
+
+void AppListModel::AddItemAt(int index, AppListItemModel* item) {
+ items_.AddAt(index, item);
+}
+
+void AppListModel::DeleteItemAt(int index) {
+ items_.DeleteAt(index);
+}
+
+AppListItemModel* AppListModel::GetItem(int index) {
+ return items_.item_at(index);
+}
+
+void AppListModel::AddObserver(ui::ListModelObserver* observer) {
+ items_.AddObserver(observer);
+}
+
+void AppListModel::RemoveObserver(ui::ListModelObserver* observer) {
+ items_.RemoveObserver(observer);
+}
+
+} // namespace app_list
diff --git a/ui/app_list/app_list_model.h b/ui/app_list/app_list_model.h
new file mode 100644
index 0000000..2424f6e
--- /dev/null
+++ b/ui/app_list/app_list_model.h
@@ -0,0 +1,48 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef UI_APP_LIST_APP_LIST_MODEL_H_
+#define UI_APP_LIST_APP_LIST_MODEL_H_
+#pragma once
+
+#include "base/basictypes.h"
+#include "ui/app_list/app_list_export.h"
+#include "ui/app_list/app_list_item_model.h"
+#include "ui/base/models/list_model.h"
+
+namespace app_list {
+
+class AppListItemModel;
+
+// Model for AppListModelView that owns a list of AppListItemModels.
+class APP_LIST_EXPORT AppListModel {
+ public:
+ AppListModel();
+ virtual ~AppListModel();
+
+ // Adds an item to the model. The model takes ownership of |item|.
+ void AddItem(AppListItemModel* item);
+ void AddItemAt(int index, AppListItemModel* item);
+
+ void DeleteItemAt(int index);
+
+ AppListItemModel* GetItem(int index);
+
+ void AddObserver(ui::ListModelObserver* observer);
+ void RemoveObserver(ui::ListModelObserver* observer);
+
+ int item_count() const {
+ return items_.item_count();
+ }
+
+ private:
+ typedef ui::ListModel<AppListItemModel> Items;
+ Items items_;
+
+ DISALLOW_COPY_AND_ASSIGN(AppListModel);
+};
+
+} // namespace app_list
+
+#endif // UI_APP_LIST_APP_LIST_MODEL_H_
diff --git a/ui/app_list/app_list_model_view.cc b/ui/app_list/app_list_model_view.cc
new file mode 100644
index 0000000..3afe0d4
--- /dev/null
+++ b/ui/app_list/app_list_model_view.cc
@@ -0,0 +1,309 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "ui/app_list/app_list_model_view.h"
+
+#include <algorithm>
+
+#include "ui/app_list/app_list_item_view.h"
+#include "ui/app_list/app_list_model.h"
+#include "ui/app_list/pagination_model.h"
+
+namespace app_list {
+
+AppListModelView::AppListModelView(views::ButtonListener* listener,
+ PaginationModel* pagination_model)
+ : model_(NULL),
+ listener_(listener),
+ pagination_model_(pagination_model),
+ fixed_layout_(false),
+ cols_(0),
+ rows_per_page_(0),
+ selected_item_index_(-1) {
+ set_focusable(true);
+ pagination_model_->AddObserver(this);
+}
+
+AppListModelView::~AppListModelView() {
+ if (model_)
+ model_->RemoveObserver(this);
+ pagination_model_->RemoveObserver(this);
+}
+
+void AppListModelView::SetLayout(int icon_size, int cols, int rows_per_page) {
+ fixed_layout_ = true;
+
+ icon_size_.SetSize(icon_size, icon_size);
+ cols_ = cols;
+ rows_per_page_ = rows_per_page;
+}
+
+void AppListModelView::CalculateLayout(const gfx::Size& content_size,
+ int num_of_tiles,
+ gfx::Size* icon_size,
+ int* rows,
+ int* cols) {
+ DCHECK(!content_size.IsEmpty() && num_of_tiles);
+
+ // Icon sizes to try.
+ const int kIconSizes[] = { 128, 96, 64, 48, 32 };
+
+ double aspect = static_cast<double>(content_size.width()) /
+ content_size.height();
+
+ // Chooses the biggest icon size that could fit all tiles.
+ gfx::Size tile_size;
+ for (size_t i = 0; i < arraysize(kIconSizes); ++i) {
+ icon_size->SetSize(kIconSizes[i], kIconSizes[i]);
+ tile_size = AppListItemView::GetPreferredSizeForIconSize(
+ *icon_size);
+
+ int max_cols = content_size.width() / tile_size.width();
+ int max_rows = content_size.height() / tile_size.height();
+
+ // Skip if |tile_size| could not fit into |content_size|.
+ if (max_cols * max_rows < num_of_tiles)
+ continue;
+
+ // Find a rows/cols pair that has a aspect ratio closest to |aspect|.
+ double min_aspect_diff = 1e5;
+ for (int c = std::max(max_cols / 2, 1); c <= max_cols; ++c) {
+ int r = std::min((num_of_tiles - 1) / c + 1, max_rows);
+ if (c * r < num_of_tiles)
+ continue;
+
+ double aspect_diff = fabs(static_cast<double>(c) / r - aspect);
+ if (aspect_diff < min_aspect_diff) {
+ *cols = c;
+ *rows = r;
+ min_aspect_diff = aspect_diff;
+ }
+ }
+
+ DCHECK((*rows) * (*cols) >= num_of_tiles);
+ return;
+ }
+
+ // No icon size that could fit all tiles.
+ *cols = std::max(content_size.width() / tile_size.width(), 1);
+ *rows = (num_of_tiles - 1) / (*cols) + 1;
+}
+
+void AppListModelView::SetModel(AppListModel* model) {
+ if (model_)
+ model_->RemoveObserver(this);
+
+ model_ = model;
+ if (model_)
+ model_->AddObserver(this);
+ Update();
+}
+
+void AppListModelView::SetSelectedItem(AppListItemView* item) {
+ int index = GetIndexOf(item);
+ if (index >= 0)
+ SetSelectedItemByIndex(index);
+}
+
+void AppListModelView::ClearSelectedItem(AppListItemView* item) {
+ int index = GetIndexOf(item);
+ if (index == selected_item_index_)
+ SetSelectedItemByIndex(-1);
+}
+
+void AppListModelView::Update() {
+ selected_item_index_ = -1;
+ RemoveAllChildViews(true);
+ if (!model_ || model_->item_count() == 0)
+ return;
+
+ for (int i = 0; i < model_->item_count(); ++i)
+ AddChildView(new AppListItemView(this, model_->GetItem(i), listener_));
+
+ Layout();
+ SchedulePaint();
+}
+
+AppListItemView* AppListModelView::GetItemViewAtIndex(int index) {
+ return static_cast<AppListItemView*>(child_at(index));
+}
+
+void AppListModelView::SetSelectedItemByIndex(int index) {
+ if (selected_item_index_ == index)
+ return;
+
+ if (selected_item_index_ >= 0)
+ GetItemViewAtIndex(selected_item_index_)->SetSelected(false);
+
+ if (index < 0 || index >= child_count()) {
+ selected_item_index_ = -1;
+ } else {
+ selected_item_index_ = index;
+ GetItemViewAtIndex(selected_item_index_)->SetSelected(true);
+
+ if (tiles_per_page())
+ pagination_model_->SelectPage(selected_item_index_ / tiles_per_page());
+ }
+}
+
+gfx::Size AppListModelView::GetPreferredSize() {
+ if (!fixed_layout_)
+ return gfx::Size();
+
+ gfx::Size tile_size = AppListItemView::GetPreferredSizeForIconSize(
+ icon_size_);
+ return gfx::Size(tile_size.width() * cols_,
+ tile_size.height() * rows_per_page_);
+}
+
+void AppListModelView::Layout() {
+ gfx::Rect rect(GetContentsBounds());
+ if (rect.IsEmpty() || child_count() == 0)
+ return;
+
+ gfx::Size tile_size;
+ if (fixed_layout_) {
+ tile_size = AppListItemView::GetPreferredSizeForIconSize(icon_size_);
+ } else {
+ int rows = 0;
+ CalculateLayout(rect.size(), child_count(), &icon_size_, &rows, &cols_);
+
+ tile_size = AppListItemView::GetPreferredSizeForIconSize(
+ icon_size_);
+ rows_per_page_ = tile_size.height() ?
+ std::max(rect.height() / tile_size.height(), 1) : 1;
+
+ tile_size.set_width(std::max(rect.width() / (cols_ + 1),
+ tile_size.width()));
+ tile_size.set_height(std::max(rect.height() / (rows_per_page_ + 1),
+ tile_size.height()));
+ }
+
+ if (!tiles_per_page())
+ return;
+
+ pagination_model_->SetTotalPages((child_count() - 1) / tiles_per_page() + 1);
+ if (pagination_model_->selected_page() < 0)
+ pagination_model_->SelectPage(0);
+
+ gfx::Rect grid_rect = rect.Center(
+ gfx::Size(tile_size.width() * cols_,
+ tile_size.height() * rows_per_page_));
+ grid_rect = grid_rect.Intersect(rect);
+
+ // Layouts items.
+ const int page = pagination_model_->selected_page();
+ const int first_visible_index = page * tiles_per_page();
+ const int last_visible_index = (page + 1) * tiles_per_page() - 1;
+ gfx::Rect current_tile(grid_rect.origin(), tile_size);
+ for (int i = 0; i < child_count(); ++i) {
+ views::View* view = child_at(i);
+ static_cast<AppListItemView*>(view)->SetIconSize(icon_size_);
+
+ if (i < first_visible_index || i > last_visible_index) {
+ view->SetVisible(false);
+ continue;
+ }
+
+ view->SetBoundsRect(current_tile);
+ view->SetVisible(rect.Contains(current_tile));
+
+ current_tile.Offset(tile_size.width(), 0);
+ if ((i + 1) % cols_ == 0) {
+ current_tile.set_x(grid_rect.x());
+ current_tile.set_y(current_tile.y() + tile_size.height());
+ }
+ }
+}
+
+bool AppListModelView::OnKeyPressed(const views::KeyEvent& event) {
+ bool handled = false;
+ if (selected_item_index_ >= 0)
+ handled = GetItemViewAtIndex(selected_item_index_)->OnKeyPressed(event);
+
+ if (!handled) {
+ switch (event.key_code()) {
+ case ui::VKEY_LEFT:
+ SetSelectedItemByIndex(std::max(selected_item_index_ - 1, 0));
+ return true;
+ case ui::VKEY_RIGHT:
+ SetSelectedItemByIndex(std::min(selected_item_index_ + 1,
+ child_count() - 1));
+ return true;
+ case ui::VKEY_UP:
+ SetSelectedItemByIndex(std::max(selected_item_index_ - cols_,
+ 0));
+ return true;
+ case ui::VKEY_DOWN:
+ if (selected_item_index_ < 0) {
+ SetSelectedItemByIndex(0);
+ } else {
+ SetSelectedItemByIndex(std::min(selected_item_index_ + cols_,
+ child_count() - 1));
+ }
+ return true;
+ case ui::VKEY_PRIOR: {
+ SetSelectedItemByIndex(
+ std::max(selected_item_index_ - tiles_per_page(),
+ 0));
+ return true;
+ }
+ case ui::VKEY_NEXT: {
+ if (selected_item_index_ < 0) {
+ SetSelectedItemByIndex(0);
+ } else {
+ SetSelectedItemByIndex(
+ std::min(selected_item_index_ + tiles_per_page(),
+ child_count() - 1));
+ }
+ }
+ default:
+ break;
+ }
+ }
+
+ return handled;
+}
+
+bool AppListModelView::OnKeyReleased(const views::KeyEvent& event) {
+ bool handled = false;
+ if (selected_item_index_ >= 0)
+ handled = GetItemViewAtIndex(selected_item_index_)->OnKeyReleased(event);
+
+ return handled;
+}
+
+void AppListModelView::OnPaintFocusBorder(gfx::Canvas* canvas) {
+ // Override to not paint focus frame.
+}
+
+void AppListModelView::ListItemsAdded(int start, int count) {
+ for (int i = start; i < start + count; ++i) {
+ AddChildViewAt(new AppListItemView(this, model_->GetItem(i), listener_),
+ i);
+ }
+ Layout();
+ SchedulePaint();
+}
+
+void AppListModelView::ListItemsRemoved(int start, int count) {
+ for (int i = 0; i < count; ++i)
+ delete child_at(start);
+
+ Layout();
+ SchedulePaint();
+}
+
+void AppListModelView::ListItemsChanged(int start, int count) {
+ NOTREACHED();
+}
+
+void AppListModelView::TotalPagesChanged() {
+}
+
+void AppListModelView::SelectedPageChanged(int old_selected, int new_selected) {
+ Layout();
+}
+
+} // namespace app_list
diff --git a/ui/app_list/app_list_model_view.h b/ui/app_list/app_list_model_view.h
new file mode 100644
index 0000000..90095fa
--- /dev/null
+++ b/ui/app_list/app_list_model_view.h
@@ -0,0 +1,98 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef UI_APP_LIST_APP_LIST_MODEL_VIEW_H_
+#define UI_APP_LIST_APP_LIST_MODEL_VIEW_H_
+#pragma once
+
+#include "ui/app_list/app_list_export.h"
+#include "ui/app_list/pagination_model_observer.h"
+#include "ui/base/models/list_model_observer.h"
+#include "ui/views/view.h"
+
+namespace views {
+class ButtonListener;
+}
+
+namespace app_list {
+
+class AppListItemView;
+class AppListModel;
+class PaginationModel;
+
+// AppListModelView displays the UI for an AppListModel.
+class APP_LIST_EXPORT AppListModelView : public views::View,
+ public ui::ListModelObserver,
+ public PaginationModelObserver {
+ public:
+ AppListModelView(views::ButtonListener* listener,
+ PaginationModel* pagination_model);
+ virtual ~AppListModelView();
+
+ // Sets fixed layout parameters. After setting this, CalculateLayout below
+ // is no longer called to dynamically choosing those layout params.
+ void SetLayout(int icon_size, int cols, int rows_per_page);
+
+ // Calculate preferred icon size, rows and cols for given |content_size| and
+ // |num_of_tiles|.
+ static void CalculateLayout(const gfx::Size& content_size,
+ int num_of_tiles,
+ gfx::Size* icon_size,
+ int* rows,
+ int* cols);
+
+ // Sets |model| to use. Note this does not take ownership of |model|.
+ void SetModel(AppListModel* model);
+
+ void SetSelectedItem(AppListItemView* item);
+ void ClearSelectedItem(AppListItemView* item);
+
+ int tiles_per_page() const {
+ return cols_ * rows_per_page_;
+ }
+
+ bool fixed_layout() const {
+ return fixed_layout_;
+ }
+
+ private:
+ // Updates from model.
+ void Update();
+
+ AppListItemView* GetItemViewAtIndex(int index);
+ void SetSelectedItemByIndex(int index);
+
+ // Overridden from views::View:
+ virtual gfx::Size GetPreferredSize() OVERRIDE;
+ virtual void Layout() OVERRIDE;
+ virtual bool OnKeyPressed(const views::KeyEvent& event) OVERRIDE;
+ virtual bool OnKeyReleased(const views::KeyEvent& event) OVERRIDE;
+ virtual void OnPaintFocusBorder(gfx::Canvas* canvas) OVERRIDE;
+
+ // Overridden from ListModelObserver:
+ virtual void ListItemsAdded(int start, int count) OVERRIDE;
+ virtual void ListItemsRemoved(int start, int count) OVERRIDE;
+ virtual void ListItemsChanged(int start, int count) OVERRIDE;
+
+ // Overridden from PaginationModelObserver:
+ virtual void TotalPagesChanged() OVERRIDE;
+ virtual void SelectedPageChanged(int old_selected, int new_selected) OVERRIDE;
+
+ AppListModel* model_; // Owned by parent AppListView.
+ views::ButtonListener* listener_;
+ PaginationModel* pagination_model_; // Owned by AppListView.
+
+ bool fixed_layout_;
+ gfx::Size icon_size_;
+ int cols_;
+ int rows_per_page_;
+
+ int selected_item_index_;
+
+ DISALLOW_COPY_AND_ASSIGN(AppListModelView);
+};
+
+} // namespace app_list
+
+#endif // UI_APP_LIST_APP_LIST_MODEL_VIEW_H_
diff --git a/ui/app_list/app_list_model_view_unittest.cc b/ui/app_list/app_list_model_view_unittest.cc
new file mode 100644
index 0000000..16cc112
--- /dev/null
+++ b/ui/app_list/app_list_model_view_unittest.cc
@@ -0,0 +1,69 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "ui/app_list/app_list_model_view.h"
+
+#include "testing/gtest/include/gtest/gtest.h"
+#include "ui/app_list/app_list_item_view.h"
+
+namespace {
+
+struct ModelViewCalculateLayoutCase {
+ gfx::Size screen_size;
+ int num_of_tiles;
+ gfx::Size expected_icon_size;
+ int expected_rows;
+ int expected_cols;
+};
+
+} // namespace
+
+namespace app_list {
+
+TEST(AppListModelViewTest, ModelViewCalculateLayout) {
+ // kMinTitleWidth is the average width of 15 chars of default size 12 font in
+ // chromeos. Override here to avoid flakiness from different default font on
+ // bots.
+ const int kMinTitleWidth = 90;
+ AppListItemView::SetMinTitleWidth(kMinTitleWidth);
+
+ const int kLauncherHeight = 50;
+ const ModelViewCalculateLayoutCase kCases[] = {
+ { gfx::Size(1024, 768), 4, gfx::Size(128, 128), 2, 3 },
+ { gfx::Size(1024, 768), 29, gfx::Size(64, 64), 5, 6 },
+ { gfx::Size(1024, 768), 40, gfx::Size(48, 48), 5, 8 },
+ { gfx::Size(1024, 768), 48, gfx::Size(48, 48), 6, 8 },
+
+ { gfx::Size(1280, 1024), 4, gfx::Size(128, 128), 2, 3 },
+ { gfx::Size(1280, 1024), 29, gfx::Size(64, 64), 5, 7 },
+ { gfx::Size(1280, 1024), 50, gfx::Size(64, 64), 7, 8 },
+ { gfx::Size(1280, 1024), 70, gfx::Size(48, 48), 7, 10 },
+ { gfx::Size(1280, 1024), 99, gfx::Size(48, 48), 9, 11 },
+
+ { gfx::Size(1600, 900), 4, gfx::Size(128, 128), 2, 3 },
+ { gfx::Size(1600, 900), 29, gfx::Size(64, 64), 4, 8 },
+
+ // Not able to fit into screen case.
+ { gfx::Size(1024, 768), 50, gfx::Size(32, 32), 6, 9 },
+ { gfx::Size(1280, 1024), 100, gfx::Size(32, 32), 10, 11 },
+ };
+
+ for (size_t i = 0; i < arraysize(kCases); ++i) {
+ gfx::Size icon_size;
+ int rows = 0;
+ int cols = 0;
+ gfx::Size content_size(kCases[i].screen_size.width(),
+ kCases[i].screen_size.height() - kLauncherHeight);
+ AppListModelView::CalculateLayout(content_size,
+ kCases[i].num_of_tiles,
+ &icon_size,
+ &rows,
+ &cols);
+ EXPECT_EQ(kCases[i].expected_icon_size, icon_size) << "i=" << i;
+ EXPECT_EQ(kCases[i].expected_rows, rows) << "i=" << i;
+ EXPECT_EQ(kCases[i].expected_cols, cols) << "i=" << i;
+ }
+}
+
+} // namespace app_list
diff --git a/ui/app_list/app_list_view.cc b/ui/app_list/app_list_view.cc
new file mode 100644
index 0000000..14289a6
--- /dev/null
+++ b/ui/app_list/app_list_view.cc
@@ -0,0 +1,275 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "ui/app_list/app_list_view.h"
+
+#include <string>
+
+#include "ui/app_list/app_list_bubble_border.h"
+#include "ui/app_list/app_list_item_view.h"
+#include "ui/app_list/app_list_model.h"
+#include "ui/app_list/app_list_model_view.h"
+#include "ui/app_list/app_list_view_delegate.h"
+#include "ui/app_list/page_switcher.h"
+#include "ui/app_list/pagination_model.h"
+#include "ui/compositor/layer.h"
+#include "ui/compositor/scoped_layer_animation_settings.h"
+#include "ui/gfx/screen.h"
+#include "ui/gfx/transform_util.h"
+#include "ui/views/background.h"
+#include "ui/views/bubble/bubble_frame_view.h"
+#include "ui/views/layout/box_layout.h"
+#include "ui/views/widget/widget.h"
+
+namespace app_list {
+
+namespace {
+
+// 0.2 black
+const SkColor kWidgetBackgroundColor = SkColorSetARGB(0x33, 0, 0, 0);
+
+const float kModelViewAnimationScaleFactor = 0.9f;
+
+const int kPreferredIconDimension = 48;
+const int kPreferredCols = 4;
+const int kPreferredRows = 4;
+// Padding space in pixels between model view and page switcher footer.
+const int kModelViewFooterPadding = 10;
+
+ui::Transform GetScaleTransform(AppListModelView* model_view) {
+ gfx::Rect pixel_bounds = model_view->GetLayerBoundsInPixel();
+ gfx::Point center(pixel_bounds.width() / 2, pixel_bounds.height() / 2);
+ return ui::GetScaleTransform(center, kModelViewAnimationScaleFactor);
+}
+
+} // namespace
+
+////////////////////////////////////////////////////////////////////////////////
+// AppListView:
+
+AppListView::AppListView(AppListViewDelegate* delegate)
+ : delegate_(delegate),
+ pagination_model_(new PaginationModel),
+ bubble_style_(false),
+ bubble_border_(NULL),
+ model_view_(NULL) {
+}
+
+AppListView::~AppListView() {
+ // Model is going away, so set to NULL before superclass deletes child views.
+ if (model_view_)
+ model_view_->SetModel(NULL);
+}
+
+void AppListView::InitAsFullscreenWidget(gfx::NativeView parent,
+ const gfx::Rect& screen_bounds,
+ const gfx::Rect& work_area) {
+ bubble_style_ = false;
+ set_background(views::Background::CreateSolidBackground(
+ kWidgetBackgroundColor));
+ work_area_ = work_area;
+
+ model_view_ = new AppListModelView(this, pagination_model_.get());
+ model_view_->SetPaintToLayer(true);
+ model_view_->layer()->SetFillsBoundsOpaquely(false);
+ AddChildView(model_view_);
+
+ views::Widget::InitParams widget_params(
+ views::Widget::InitParams::TYPE_WINDOW_FRAMELESS);
+ widget_params.delegate = this;
+ widget_params.transparent = true;
+ widget_params.parent = parent;
+
+ views::Widget* widget = new views::Widget;
+ widget->Init(widget_params);
+ widget->SetContentsView(this);
+ widget->SetBounds(screen_bounds);
+
+ // Turns off default animation.
+ widget->SetVisibilityChangedAnimationsEnabled(false);
+
+ // Sets initial transform. AnimateShow changes it back to identity transform.
+ model_view_->SetTransform(GetScaleTransform(model_view_));
+ UpdateModel();
+}
+
+void AppListView::InitAsBubble(gfx::NativeView parent, views::View* anchor) {
+ bubble_style_ = true;
+ set_background(NULL);
+
+ SetLayoutManager(new views::BoxLayout(
+ views::BoxLayout::kVertical, 0, 0, kModelViewFooterPadding));
+
+ model_view_ = new AppListModelView(this, pagination_model_.get());
+ model_view_->SetLayout(kPreferredIconDimension,
+ kPreferredCols,
+ kPreferredRows);
+ AddChildView(model_view_);
+
+ PageSwitcher* page_switcher = new PageSwitcher(pagination_model_.get());
+ AddChildView(page_switcher);
+
+ set_anchor_view(anchor);
+ set_parent_window(parent);
+ set_close_on_deactivate(false);
+ views::BubbleDelegateView::CreateBubble(this);
+
+ // Resets default background since AppListBubbleBorder paints background.
+ GetBubbleFrameView()->set_background(NULL);
+
+ // Overrides border with AppListBubbleBorder.
+ bubble_border_ = new AppListBubbleBorder(this);
+ GetBubbleFrameView()->SetBubbleBorder(bubble_border_);
+ SizeToContents(); // Recalcuates with new border.
+
+ UpdateModel();
+}
+
+void AppListView::AnimateShow(int duration_ms) {
+ if (bubble_style_)
+ return;
+
+ ui::Layer* layer = model_view_->layer();
+ ui::ScopedLayerAnimationSettings animation(layer->GetAnimator());
+ animation.SetTransitionDuration(
+ base::TimeDelta::FromMilliseconds(duration_ms));
+ animation.SetTweenType(ui::Tween::EASE_OUT);
+ model_view_->SetTransform(ui::Transform());
+}
+
+void AppListView::AnimateHide(int duration_ms) {
+ if (bubble_style_)
+ return;
+
+ ui::Layer* layer = model_view_->layer();
+ ui::ScopedLayerAnimationSettings animation(layer->GetAnimator());
+ animation.SetTransitionDuration(
+ base::TimeDelta::FromMilliseconds(duration_ms));
+ animation.SetTweenType(ui::Tween::EASE_IN);
+ model_view_->SetTransform(GetScaleTransform(model_view_));
+}
+
+void AppListView::Close() {
+ if (delegate_.get())
+ delegate_->Close();
+ else
+ GetWidget()->Close();
+}
+
+void AppListView::UpdateBounds(const gfx::Rect& screen_bounds,
+ const gfx::Rect& work_area) {
+ if (bubble_style_) {
+ SizeToContents();
+ } else {
+ work_area_ = work_area;
+ GetWidget()->SetBounds(screen_bounds);
+ }
+}
+
+void AppListView::UpdateModel() {
+ if (delegate_.get()) {
+ scoped_ptr<AppListModel> new_model(new AppListModel);
+ delegate_->SetModel(new_model.get());
+ delegate_->UpdateModel(std::string());
+ model_view_->SetModel(new_model.get());
+ model_.reset(new_model.release());
+ }
+}
+
+views::View* AppListView::GetInitiallyFocusedView() {
+ return model_view_;
+}
+
+void AppListView::Layout() {
+ gfx::Rect rect(GetContentsBounds());
+ if (rect.IsEmpty())
+ return;
+
+ if (bubble_style_) {
+ views::View::Layout();
+ } else {
+ // Gets work area rect, which is in screen coordinates.
+ gfx::Rect workarea(work_area_);
+
+ // Converts |workarea| into view's coordinates.
+ gfx::Point origin(workarea.origin());
+ views::View::ConvertPointFromScreen(this, &origin);
+ workarea.Offset(-origin.x(), -origin.y());
+
+ rect = rect.Intersect(workarea);
+ model_view_->SetBoundsRect(rect);
+ }
+}
+
+bool AppListView::OnKeyPressed(const views::KeyEvent& event) {
+ if (event.key_code() == ui::VKEY_ESCAPE) {
+ Close();
+ return true;
+ }
+
+ return false;
+}
+
+bool AppListView::OnMousePressed(const views::MouseEvent& event) {
+ // For full screen mode, if mouse click reaches us, this means user clicks
+ // on blank area. So close.
+ if (!bubble_style_)
+ Close();
+
+ return true;
+}
+
+void AppListView::ButtonPressed(views::Button* sender,
+ const views::Event& event) {
+ if (sender->GetClassName() != AppListItemView::kViewClassName)
+ return;
+
+ if (delegate_.get()) {
+ delegate_->OnAppListItemActivated(
+ static_cast<AppListItemView*>(sender)->model(),
+ event.flags());
+ }
+ Close();
+}
+
+gfx::Rect AppListView::GetBubbleBounds() {
+ // This happens before replacing the default border.
+ if (!bubble_border_)
+ return views::BubbleDelegateView::GetBubbleBounds();
+
+ const int old_arrow_offset = bubble_border_->arrow_offset();
+ const gfx::Rect anchor_rect = GetAnchorRect();
+
+ bubble_border_->set_arrow_offset(0);
+ gfx::Rect bubble_rect = GetBubbleFrameView()->GetUpdatedWindowBounds(
+ anchor_rect,
+ GetPreferredSize(),
+ false /* try_mirroring_arrow */);
+
+ gfx::Rect monitor_rect = gfx::Screen::GetMonitorNearestPoint(
+ anchor_rect.CenterPoint()).work_area();
+ if (monitor_rect.IsEmpty() || monitor_rect.Contains(bubble_rect))
+ return bubble_rect;
+
+ int offset = 0;
+ if (bubble_rect.x() < monitor_rect.x())
+ offset = monitor_rect.x() - bubble_rect.x();
+ else if (bubble_rect.right() > monitor_rect.right())
+ offset = monitor_rect.right() - bubble_rect.right();
+
+ bubble_rect.set_x(bubble_rect.x() + offset);
+
+ // Moves bubble arrow in the opposite direction. i.e. If bubble bounds is
+ // moved to right (positive offset), we need to move arrow to left so that
+ // it points to the same position before the move.
+ bubble_border_->set_arrow_offset(-offset);
+
+ // Repaints border if arrow offset is changed.
+ if (bubble_border_->arrow_offset() != old_arrow_offset)
+ GetBubbleFrameView()->SchedulePaint();
+
+ return bubble_rect;
+}
+
+} // namespace app_list
diff --git a/ui/app_list/app_list_view.h b/ui/app_list/app_list_view.h
new file mode 100644
index 0000000..cb938c63
--- /dev/null
+++ b/ui/app_list/app_list_view.h
@@ -0,0 +1,86 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef UI_APP_LIST_APP_LIST_VIEW_H_
+#define UI_APP_LIST_APP_LIST_VIEW_H_
+#pragma once
+
+#include "base/memory/scoped_ptr.h"
+#include "ui/app_list/app_list_export.h"
+#include "ui/views/bubble/bubble_delegate.h"
+#include "ui/views/controls/button/button.h"
+
+namespace views {
+class View;
+}
+
+namespace app_list {
+
+class AppListBubbleBorder;
+class AppListModel;
+class AppListModelView;
+class AppListViewDelegate;
+class PaginationModel;
+
+// AppListView is the top-level view and controller of app list UI. It creates
+// and hosts a AppListModelView and passes AppListModel to it for display.
+class APP_LIST_EXPORT AppListView : public views::BubbleDelegateView,
+ public views::ButtonListener {
+ public:
+ // Takes ownership of |delegate|.
+ explicit AppListView(AppListViewDelegate* delegate);
+ virtual ~AppListView();
+
+ // Initializes the widget.
+ void InitAsFullscreenWidget(gfx::NativeView parent,
+ const gfx::Rect& screen_bounds,
+ const gfx::Rect& work_area);
+ void InitAsBubble(gfx::NativeView parent, views::View* anchor);
+
+ void AnimateShow(int duration_ms);
+ void AnimateHide(int duration_ms);
+
+ void Close();
+ void UpdateBounds(const gfx::Rect& screen_bounds,
+ const gfx::Rect& work_area);
+
+ private:
+ // Updates model using query text in search box.
+ void UpdateModel();
+
+ // Overridden from views::WidgetDelegateView:
+ virtual views::View* GetInitiallyFocusedView() OVERRIDE;
+
+ // Overridden from views::View:
+ virtual void Layout() OVERRIDE;
+ virtual bool OnKeyPressed(const views::KeyEvent& event) OVERRIDE;
+ virtual bool OnMousePressed(const views::MouseEvent& event) OVERRIDE;
+
+ // Overridden from views::ButtonListener:
+ virtual void ButtonPressed(views::Button* sender,
+ const views::Event& event) OVERRIDE;
+
+ // Overridden from views::BubbleDelegate:
+ virtual gfx::Rect GetBubbleBounds() OVERRIDE;
+
+ scoped_ptr<AppListModel> model_;
+ scoped_ptr<AppListViewDelegate> delegate_;
+
+ // PaginationModel for model view and page switcher.
+ scoped_ptr<PaginationModel> pagination_model_;
+
+ bool bubble_style_;
+ AppListBubbleBorder* bubble_border_; // Owned by views hierarchy.
+ AppListModelView* model_view_;
+
+ // Work area in screen coordinates to layout app list. This is used for
+ // full screen mode.
+ gfx::Rect work_area_;
+
+ DISALLOW_COPY_AND_ASSIGN(AppListView);
+};
+
+} // namespace app_list
+
+#endif // UI_APP_LIST_APP_LIST_VIEW_H_
diff --git a/ui/app_list/app_list_view_delegate.h b/ui/app_list/app_list_view_delegate.h
new file mode 100644
index 0000000..57ca3ac
--- /dev/null
+++ b/ui/app_list/app_list_view_delegate.h
@@ -0,0 +1,40 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef UI_APP_LIST_APP_LIST_VIEW_DELEGATE_H_
+#define UI_APP_LIST_APP_LIST_VIEW_DELEGATE_H_
+#pragma once
+
+#include <string>
+
+#include "ui/app_list/app_list_export.h"
+
+namespace app_list {
+
+class AppListItemModel;
+class AppListModel;
+
+class APP_LIST_EXPORT AppListViewDelegate {
+ public:
+ // AppListView owns the delegate.
+ virtual ~AppListViewDelegate() {}
+
+ // Invoked to set the model that AppListView uses.
+ // Note that AppListView owns the model.
+ virtual void SetModel(AppListModel* model) = 0;
+
+ // Invoked to ask the delegate to populate the model for given |query|.
+ virtual void UpdateModel(const std::string& query) = 0;
+
+ // Invoked an AppListeItemModelView is activated by click or enter key.
+ virtual void OnAppListItemActivated(AppListItemModel* item,
+ int event_flags) = 0;
+
+ // Invoked to close app list.
+ virtual void Close() = 0;
+};
+
+} // namespace app_list
+
+#endif // UI_APP_LIST_APP_LIST_VIEW_DELEGATE_H_
diff --git a/ui/app_list/drop_shadow_label.cc b/ui/app_list/drop_shadow_label.cc
new file mode 100644
index 0000000..64cffbb
--- /dev/null
+++ b/ui/app_list/drop_shadow_label.cc
@@ -0,0 +1,63 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "ui/app_list/drop_shadow_label.h"
+
+#include "base/utf_string_conversions.h"
+#include "third_party/skia/include/effects/SkGradientShader.h"
+#include "ui/gfx/canvas.h"
+#include "ui/gfx/color_utils.h"
+#include "ui/gfx/insets.h"
+#include "ui/gfx/skbitmap_operations.h"
+
+using views::Label;
+
+namespace app_list {
+
+DropShadowLabel::DropShadowLabel() {
+}
+
+DropShadowLabel::~DropShadowLabel() {
+}
+
+void DropShadowLabel::SetTextShadows(int shadow_count,
+ const gfx::ShadowValue* shadows) {
+ text_shadows_.clear();
+
+ if (shadow_count && shadows) {
+ for (int i = 0; i < shadow_count; ++i)
+ text_shadows_.push_back(shadows[i]);
+ }
+}
+
+gfx::Insets DropShadowLabel::GetInsets() const {
+ gfx::Insets insets = views::Label::GetInsets();
+ gfx::Insets shadow_margin = gfx::ShadowValue::GetMargin(text_shadows_);
+ // Negate |shadow_margin| to convert it to a padding insets needed inside
+ // the bounds and combine with label's insets.
+ insets += -shadow_margin;
+ return insets;
+}
+
+void DropShadowLabel::PaintText(gfx::Canvas* canvas,
+ const string16& text,
+ const gfx::Rect& text_bounds,
+ int flags) {
+ SkColor text_color = enabled() ? enabled_color() : disabled_color();
+ canvas->DrawStringWithShadows(text,
+ font(),
+ text_color,
+ text_bounds,
+ flags,
+ text_shadows_);
+
+ if (HasFocus() || paint_as_focused()) {
+ gfx::Rect focus_bounds = text_bounds;
+ focus_bounds.Inset(-Label::kFocusBorderPadding,
+ -Label::kFocusBorderPadding);
+ canvas->DrawFocusRect(focus_bounds);
+ }
+}
+
+} // namespace app_list
diff --git a/ui/app_list/drop_shadow_label.h b/ui/app_list/drop_shadow_label.h
new file mode 100644
index 0000000..faee5f0
--- /dev/null
+++ b/ui/app_list/drop_shadow_label.h
@@ -0,0 +1,46 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef UI_APP_LIST_DROP_SHADOW_LABEL_H_
+#define UI_APP_LIST_DROP_SHADOW_LABEL_H_
+#pragma once
+
+#include <vector>
+
+#include "ui/gfx/shadow_value.h"
+#include "ui/views/controls/label.h"
+
+namespace app_list {
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// DropShadowLabel class
+//
+// A drop shadow label is a view subclass that can display a string
+// with a drop shadow.
+//
+/////////////////////////////////////////////////////////////////////////////
+class DropShadowLabel : public views::Label {
+ public:
+ DropShadowLabel();
+ virtual ~DropShadowLabel();
+
+ void SetTextShadows(int shadow_count, const gfx::ShadowValue* shadows);
+
+ private:
+ // Overridden from views::Label:
+ virtual gfx::Insets GetInsets() const OVERRIDE;
+ virtual void PaintText(gfx::Canvas* canvas,
+ const string16& text,
+ const gfx::Rect& text_bounds,
+ int flags) OVERRIDE;
+
+ std::vector<gfx::ShadowValue> text_shadows_;
+
+ DISALLOW_COPY_AND_ASSIGN(DropShadowLabel);
+};
+
+} // namespace app_list
+
+#endif // UI_APP_LIST_DROP_SHADOW_LABEL_H_
diff --git a/ui/app_list/icon_cache.cc b/ui/app_list/icon_cache.cc
new file mode 100644
index 0000000..b0b9997
--- /dev/null
+++ b/ui/app_list/icon_cache.cc
@@ -0,0 +1,90 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "ui/app_list/icon_cache.h"
+
+#include "base/logging.h"
+#include "base/md5.h"
+#include "ui/gfx/size.h"
+
+namespace {
+
+// Gets cache key based on |image| contents and desired |size|.
+std::string GetKey(const SkBitmap& image, const gfx::Size& size) {
+ SkAutoLockPixels image_lock(image);
+ base::MD5Digest digest;
+ MD5Sum(image.getPixels(), image.getSize(), &digest);
+
+ return MD5DigestToBase16(digest) + "." + size.ToString();
+}
+
+} // namespace
+
+namespace app_list {
+
+// static
+IconCache* IconCache::instance_ = NULL;
+
+// static
+void IconCache::CreateInstance() {
+ DCHECK(!instance_);
+ instance_ = new IconCache;
+}
+
+// static
+void IconCache::DeleteInstance() {
+ DCHECK(instance_);
+ delete instance_;
+ instance_ = NULL;
+}
+
+// static
+IconCache* IconCache::GetInstance() {
+ DCHECK(instance_);
+ return instance_;
+}
+
+void IconCache::MarkAllEntryUnused() {
+ for (Cache::iterator i = cache_.begin(); i != cache_.end(); ++i)
+ i->second.used = false;
+}
+
+void IconCache::PurgeAllUnused() {
+ for (Cache::iterator i = cache_.begin(); i != cache_.end();) {
+ Cache::iterator current(i);
+ ++i;
+ if (!current->second.used)
+ cache_.erase(current);
+ }
+}
+
+bool IconCache::Get(const SkBitmap& src,
+ const gfx::Size& size,
+ SkBitmap* processed) {
+ Cache::iterator it = cache_.find(GetKey(src, size));
+ if (it == cache_.end())
+ return false;
+
+ it->second.used = true;
+
+ if (processed)
+ *processed = it->second.image;
+ return true;
+}
+
+void IconCache::Put(const SkBitmap& src,
+ const gfx::Size& size,
+ const SkBitmap& processed) {
+ const std::string key = GetKey(src, size);
+ cache_[key].image = processed;
+ cache_[key].used = true;
+}
+
+IconCache::IconCache() {
+}
+
+IconCache::~IconCache() {
+}
+
+} // namespace app_list
diff --git a/ui/app_list/icon_cache.h b/ui/app_list/icon_cache.h
new file mode 100644
index 0000000..75ffa067
--- /dev/null
+++ b/ui/app_list/icon_cache.h
@@ -0,0 +1,59 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef UI_APP_LIST_ICON_CACHE_H_
+#define UI_APP_LIST_ICON_CACHE_H_
+#pragma once
+
+#include <map>
+#include <string>
+
+#include "base/basictypes.h"
+#include "third_party/skia/include/core/SkBitmap.h"
+#include "ui/app_list/app_list_export.h"
+
+namespace gfx {
+class Size;
+}
+
+namespace app_list {
+
+// IconCache stores processed image, keyed by the source image and desired size.
+class APP_LIST_EXPORT IconCache {
+ public:
+ static void CreateInstance();
+ static void DeleteInstance();
+
+ static IconCache* GetInstance();
+
+ void MarkAllEntryUnused();
+ void PurgeAllUnused();
+
+ bool Get(const SkBitmap& src,
+ const gfx::Size& size,
+ SkBitmap* processed);
+ void Put(const SkBitmap& src,
+ const gfx::Size& size,
+ const SkBitmap& processed);
+
+ private:
+ struct Item {
+ SkBitmap image;
+ bool used;
+ };
+ typedef std::map<std::string, Item> Cache;
+
+ IconCache();
+ ~IconCache();
+
+ static IconCache* instance_;
+
+ Cache cache_;
+
+ DISALLOW_COPY_AND_ASSIGN(IconCache);
+};
+
+} // namespace app_list
+
+#endif // UI_APP_LIST_ICON_CACHE_H_
diff --git a/ui/app_list/page_switcher.cc b/ui/app_list/page_switcher.cc
new file mode 100644
index 0000000..a35c8be
--- /dev/null
+++ b/ui/app_list/page_switcher.cc
@@ -0,0 +1,158 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "ui/app_list/page_switcher.h"
+
+#include "third_party/skia/include/core/SkPath.h"
+#include "ui/app_list/pagination_model.h"
+#include "ui/base/animation/throb_animation.h"
+#include "ui/gfx/canvas.h"
+#include "ui/views/controls/button/custom_button.h"
+#include "ui/views/layout/box_layout.h"
+
+namespace {
+
+const int kButtonSpacing = 10;
+const int kButtonWidth = 60;
+const int kButtonHeight = 6;
+const int kButtonHeightPadding = 10;
+const int kButtonCornerRadius = 2;
+
+// 0.16 black
+const SkColor kHoverColor = SkColorSetARGB(0x2A, 0x00, 0x00, 0x00);
+
+// 0.2 black
+const SkColor kNormalColor = SkColorSetARGB(0x33, 0x00, 0x00, 0x00);
+
+// 0.33 black
+const SkColor kSelectedColor = SkColorSetARGB(0x55, 0x00, 0x00, 0x00);
+
+class PageSwitcherButton : public views::CustomButton {
+ public:
+ explicit PageSwitcherButton(views::ButtonListener* listener)
+ : views::CustomButton(listener),
+ selected_(false) {
+ }
+ virtual ~PageSwitcherButton() {}
+
+ void SetSelected(bool selected) {
+ if (selected == selected_)
+ return;
+
+ selected_ = selected;
+ SchedulePaint();
+ }
+
+ // Overridden from views::View:
+ virtual gfx::Size GetPreferredSize() OVERRIDE {
+ return gfx::Size(kButtonWidth, kButtonHeight + kButtonHeightPadding);
+ }
+
+ virtual void OnPaint(gfx::Canvas* canvas) OVERRIDE {
+ if (selected_ || state() == BS_PUSHED) {
+ PaintButton(canvas, kSelectedColor);
+ } else if (state() == BS_HOT) {
+ PaintButton(canvas, kHoverColor);
+ } else {
+ PaintButton(canvas, kNormalColor);
+ }
+ }
+
+ private:
+ // Paints a button that has two rounded corner at bottom.
+ void PaintButton(gfx::Canvas* canvas, SkColor color) {
+ gfx::Rect rect(GetContentsBounds());
+ rect.set_height(kButtonHeight);
+
+ gfx::Point center = rect.CenterPoint();
+
+ SkPath path;
+ path.incReserve(12);
+ path.moveTo(SkIntToScalar(rect.x()), SkIntToScalar(rect.y()));
+ path.arcTo(SkIntToScalar(rect.x()), SkIntToScalar(rect.bottom()),
+ SkIntToScalar(center.x()), SkIntToScalar(rect.bottom()),
+ SkIntToScalar(kButtonCornerRadius));
+ path.arcTo(SkIntToScalar(rect.right()), SkIntToScalar(rect.bottom()),
+ SkIntToScalar(rect.right()), SkIntToScalar(rect.y()),
+ SkIntToScalar(kButtonCornerRadius));
+ path.lineTo(SkIntToScalar(rect.right()), SkIntToScalar(rect.y()));
+ path.close();
+
+ SkPaint paint;
+ paint.setStyle(SkPaint::kFill_Style);
+ paint.setColor(color);
+ canvas->DrawPath(path, paint);
+ }
+
+ bool selected_;
+
+ DISALLOW_COPY_AND_ASSIGN(PageSwitcherButton);
+};
+
+// Gets PageSwitcherButton at |index| in |buttons|.
+PageSwitcherButton* GetButtonByIndex(views::View* buttons, int index) {
+ return static_cast<PageSwitcherButton*>(buttons->child_at(index));
+}
+
+} // namespace
+
+namespace app_list {
+
+PageSwitcher::PageSwitcher(PaginationModel* model)
+ : model_(model),
+ buttons_(NULL) {
+ buttons_ = new views::View;
+ buttons_->SetLayoutManager(new views::BoxLayout(
+ views::BoxLayout::kHorizontal, 0, 0, kButtonSpacing));
+ AddChildView(buttons_);
+
+ TotalPagesChanged();
+ SelectedPageChanged(-1, model->selected_page());
+ model_->AddObserver(this);
+}
+
+PageSwitcher::~PageSwitcher() {
+ model_->RemoveObserver(this);
+}
+
+gfx::Size PageSwitcher::GetPreferredSize() {
+ // Always return a size with correct height so that container resize is not
+ // needed when more pages are added.
+ return gfx::Size(kButtonWidth, kButtonHeight + kButtonHeightPadding);
+}
+
+void PageSwitcher::Layout() {
+ gfx::Rect rect(GetContentsBounds());
+ buttons_->SetBoundsRect(rect.Center(buttons_->GetPreferredSize()));
+}
+
+void PageSwitcher::ButtonPressed(views::Button* sender,
+ const views::Event& event) {
+ for (int i = 0; i < buttons_->child_count(); ++i) {
+ if (sender == static_cast<views::Button*>(buttons_->child_at(i))) {
+ model_->SelectPage(i);
+ break;
+ }
+ }
+}
+
+void PageSwitcher::TotalPagesChanged() {
+ buttons_->RemoveAllChildViews(true);
+ for (int i = 0; i < model_->total_pages(); ++i) {
+ PageSwitcherButton* button = new PageSwitcherButton(this);
+ button->SetSelected(i == model_->selected_page());
+ buttons_->AddChildView(button);
+ }
+ buttons_->SetVisible(model_->total_pages() > 1);
+ Layout();
+}
+
+void PageSwitcher::SelectedPageChanged(int old_selected, int new_selected) {
+ if (old_selected >= 0 && old_selected < buttons_->child_count())
+ GetButtonByIndex(buttons_, old_selected)->SetSelected(false);
+ if (new_selected >= 0 && new_selected < buttons_->child_count())
+ GetButtonByIndex(buttons_, new_selected)->SetSelected(true);
+}
+
+} // namespace app_list
diff --git a/ui/app_list/page_switcher.h b/ui/app_list/page_switcher.h
new file mode 100644
index 0000000..46c534e
--- /dev/null
+++ b/ui/app_list/page_switcher.h
@@ -0,0 +1,49 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef UI_APP_LIST_PAGE_SWITCHER_H_
+#define UI_APP_LIST_PAGE_SWITCHER_H_
+#pragma once
+
+#include "base/basictypes.h"
+#include "ui/app_list/pagination_model_observer.h"
+#include "ui/views/controls/button/button.h"
+#include "ui/views/view.h"
+
+namespace app_list {
+
+class PaginationModel;
+
+// PageSwitcher represents its underlying PaginationModel with a button strip.
+// Each page in the PageinationModel has a button in the strip and when the
+// button is clicked, the corresponding page becomes selected.
+class PageSwitcher : public views::View,
+ public views::ButtonListener,
+ public PaginationModelObserver {
+ public:
+ explicit PageSwitcher(PaginationModel* model);
+ virtual ~PageSwitcher();
+
+ private:
+ // Overridden from views::View:
+ virtual gfx::Size GetPreferredSize() OVERRIDE;
+ virtual void Layout() OVERRIDE;
+
+ // Overridden from views::ButtonListener:
+ virtual void ButtonPressed(views::Button* sender,
+ const views::Event& event) OVERRIDE;
+
+ // Overridden from PaginationModelObserver:
+ virtual void TotalPagesChanged() OVERRIDE;
+ virtual void SelectedPageChanged(int old_selected, int new_selected) OVERRIDE;
+
+ PaginationModel* model_; // Owned by parent AppListView.
+ views::View* buttons_; // Owned by views hierarchy.
+
+ DISALLOW_COPY_AND_ASSIGN(PageSwitcher);
+};
+
+} // namespace app_list
+
+#endif // UI_APP_LIST_PAGE_SWITCHER_H_
diff --git a/ui/app_list/pagination_model.cc b/ui/app_list/pagination_model.cc
new file mode 100644
index 0000000..64556a5
--- /dev/null
+++ b/ui/app_list/pagination_model.cc
@@ -0,0 +1,50 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "ui/app_list/pagination_model.h"
+
+#include "ui/app_list/pagination_model_observer.h"
+
+namespace app_list {
+
+PaginationModel::PaginationModel()
+ : total_pages_(-1),
+ selected_page_(-1) {
+}
+
+PaginationModel::~PaginationModel() {
+}
+
+void PaginationModel::SetTotalPages(int total_pages) {
+ if (total_pages == total_pages_)
+ return;
+
+ total_pages_ = total_pages;
+ FOR_EACH_OBSERVER(PaginationModelObserver,
+ observers_,
+ TotalPagesChanged());
+}
+
+void PaginationModel::SelectPage(int page) {
+ DCHECK(page >= 0 && page < total_pages_);
+
+ if (page == selected_page_)
+ return;
+
+ int old_selected = selected_page_;
+ selected_page_ = page;
+ FOR_EACH_OBSERVER(PaginationModelObserver,
+ observers_,
+ SelectedPageChanged(old_selected, selected_page_));
+}
+
+void PaginationModel::AddObserver(PaginationModelObserver* observer) {
+ observers_.AddObserver(observer);
+}
+
+void PaginationModel::RemoveObserver(PaginationModelObserver* observer) {
+ observers_.RemoveObserver(observer);
+}
+
+} // namespace app_list
diff --git a/ui/app_list/pagination_model.h b/ui/app_list/pagination_model.h
new file mode 100644
index 0000000..39820d8
--- /dev/null
+++ b/ui/app_list/pagination_model.h
@@ -0,0 +1,49 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef UI_APP_LIST_PAGINATION_MODEL_H_
+#define UI_APP_LIST_PAGINATION_MODEL_H_
+#pragma once
+
+#include "ui/app_list/app_list_export.h"
+#include "base/basictypes.h"
+#include "base/observer_list.h"
+
+namespace app_list {
+
+class PaginationModelObserver;
+
+// A simple pagination model that consists of two numbers: the total pages and
+// the currently selected page. The model is a single selection model that at
+// the most one page can become selected at any time.
+class APP_LIST_EXPORT PaginationModel {
+ public:
+ PaginationModel();
+ ~PaginationModel();
+
+ void SetTotalPages(int total_pages);
+ void SelectPage(int page);
+
+ void AddObserver(PaginationModelObserver* observer);
+ void RemoveObserver(PaginationModelObserver* observer);
+
+ int total_pages() const {
+ return total_pages_;
+ }
+
+ int selected_page() const {
+ return selected_page_;
+ }
+
+ private:
+ int total_pages_;
+ int selected_page_;
+ ObserverList<PaginationModelObserver> observers_;
+
+ DISALLOW_COPY_AND_ASSIGN(PaginationModel);
+};
+
+} // namespace app_list
+
+#endif // UI_APP_LIST_PAGINATION_MODEL_H_
diff --git a/ui/app_list/pagination_model_observer.h b/ui/app_list/pagination_model_observer.h
new file mode 100644
index 0000000..10e670d
--- /dev/null
+++ b/ui/app_list/pagination_model_observer.h
@@ -0,0 +1,27 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef UI_APP_LIST_PAGINATION_MODEL_OBSERVER_H_
+#define UI_APP_LIST_PAGINATION_MODEL_OBSERVER_H_
+#pragma once
+
+#include "ui/app_list/app_list_export.h"
+
+namespace app_list {
+
+class APP_LIST_EXPORT PaginationModelObserver {
+ public:
+ // Invoked when the total number of page is changed.
+ virtual void TotalPagesChanged() = 0;
+
+ // Invoked when the selected page index is changed.
+ virtual void SelectedPageChanged(int old_selected, int new_selected) = 0;
+
+ protected:
+ virtual ~PaginationModelObserver() {}
+};
+
+} // namespace app_list
+
+#endif // UI_APP_LIST_PAGINATION_MODEL_OBSERVER_H_
diff --git a/ui/app_list/run_all_unittests.cc b/ui/app_list/run_all_unittests.cc
new file mode 100644
index 0000000..2c8d29c
--- /dev/null
+++ b/ui/app_list/run_all_unittests.cc
@@ -0,0 +1,9 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/test/test_suite.h"
+
+int main(int argc, char** argv) {
+ return base::TestSuite(argc, argv).Run();
+}