diff options
author | xiyuan@chromium.org <xiyuan@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2012-05-09 04:11:45 +0000 |
---|---|---|
committer | xiyuan@chromium.org <xiyuan@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2012-05-09 04:11:45 +0000 |
commit | 0039db9b4f365a4c6b1bbd72b4ca4e2aeb65c354 (patch) | |
tree | 5a50f8caa29ce4038da05b20f18161887c3c8fc3 /ui | |
parent | eb951d02de207130faca3a648d481108c97a8525 (diff) | |
download | chromium_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')
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(); +} |