Commit 4b9c829d authored by mgiuca's avatar mgiuca Committed by Commit bot

Experimental app list: "All apps" button has a folder-like icon.

The icon shows the first four icons of the apps grid view, much like a
folder would show the first four icons inside the folder.

BUG=425444

Review URL: https://codereview.chromium.org/682843004

Cr-Commit-Position: refs/heads/master@{#302719}
parent 968e6105
......@@ -26,6 +26,8 @@ component("app_list") {
"app_list_switches.h",
"app_list_view_delegate.cc",
"app_list_view_delegate.h",
"folder_image.cc",
"folder_image.h",
"folder_image_source.cc",
"folder_image_source.h",
"pagination_controller.cc",
......@@ -217,6 +219,7 @@ test("app_list_unittests") {
sources = [
"app_list_item_list_unittest.cc",
"app_list_model_unittest.cc",
"folder_image_unittest.cc",
"pagination_model_unittest.cc",
"search/history_data_store_unittest.cc",
"search/mixer_unittest.cc",
......
......@@ -74,6 +74,8 @@
'cocoa/item_drag_controller.mm',
'cocoa/scroll_view_with_no_scrollbars.h',
'cocoa/scroll_view_with_no_scrollbars.mm',
'folder_image.cc',
'folder_image.h',
'folder_image_source.cc',
'folder_image_source.h',
'pagination_controller.cc',
......@@ -256,6 +258,7 @@
'cocoa/apps_search_results_controller_unittest.mm',
'cocoa/test/apps_grid_controller_test_helper.h',
'cocoa/test/apps_grid_controller_test_helper.mm',
'folder_image_unittest.cc',
'search/history_data_store_unittest.cc',
'search/mixer_unittest.cc',
'search/term_break_iterator_unittest.cc',
......
......@@ -7,8 +7,8 @@
#include "base/guid.h"
#include "ui/app_list/app_list_constants.h"
#include "ui/app_list/app_list_item_list.h"
#include "ui/app_list/folder_image_source.h"
#include "ui/gfx/geometry/rect.h"
#include "ui/gfx/image/image_skia.h"
namespace app_list {
......@@ -16,47 +16,24 @@ AppListFolderItem::AppListFolderItem(const std::string& id,
FolderType folder_type)
: AppListItem(id),
folder_type_(folder_type),
item_list_(new AppListItemList) {
item_list_->AddObserver(this);
item_list_(new AppListItemList),
folder_image_(item_list_.get()) {
folder_image_.AddObserver(this);
}
AppListFolderItem::~AppListFolderItem() {
for (size_t i = 0; i < top_items_.size(); ++i)
top_items_[i]->RemoveObserver(this);
item_list_->RemoveObserver(this);
}
void AppListFolderItem::UpdateIcon() {
FolderImageSource::Icons top_icons;
for (size_t i = 0; i < top_items_.size(); ++i)
top_icons.push_back(top_items_[i]->icon());
const gfx::Size icon_size = gfx::Size(kGridIconDimension, kGridIconDimension);
gfx::ImageSkia icon = gfx::ImageSkia(
new FolderImageSource(top_icons, icon_size),
icon_size);
SetIcon(icon, false);
folder_image_.RemoveObserver(this);
}
const gfx::ImageSkia& AppListFolderItem::GetTopIcon(size_t item_index) {
CHECK_LT(item_index, top_items_.size());
return top_items_[item_index]->icon();
return folder_image_.GetTopIcon(item_index);
}
gfx::Rect AppListFolderItem::GetTargetIconRectInFolderForItem(
AppListItem* item,
const gfx::Rect& folder_icon_bounds) {
for (size_t i = 0; i < top_items_.size(); ++i) {
if (item->id() == top_items_[i]->id()) {
std::vector<gfx::Rect> rects =
FolderImageSource::GetTopIconsBounds(folder_icon_bounds);
return rects[i];
}
}
gfx::Rect target_rect(folder_icon_bounds);
target_rect.ClampToCenteredSize(FolderImageSource::ItemIconSize());
return target_rect;
return folder_image_.GetTargetIconRectInFolderForItem(item,
folder_icon_bounds);
}
void AppListFolderItem::Activate(int event_flags) {
......@@ -107,41 +84,8 @@ std::string AppListFolderItem::GenerateId() {
return base::GenerateGUID();
}
void AppListFolderItem::ItemIconChanged() {
UpdateIcon();
}
void AppListFolderItem::OnListItemAdded(size_t index,
AppListItem* item) {
if (index < kNumFolderTopItems)
UpdateTopItems();
}
void AppListFolderItem::OnListItemRemoved(size_t index,
AppListItem* item) {
if (index < kNumFolderTopItems)
UpdateTopItems();
}
void AppListFolderItem::OnListItemMoved(size_t from_index,
size_t to_index,
AppListItem* item) {
if (from_index < kNumFolderTopItems || to_index < kNumFolderTopItems)
UpdateTopItems();
}
void AppListFolderItem::UpdateTopItems() {
for (size_t i = 0; i < top_items_.size(); ++i)
top_items_[i]->RemoveObserver(this);
top_items_.clear();
for (size_t i = 0;
i < kNumFolderTopItems && i < item_list_->item_count(); ++i) {
AppListItem* item = item_list_->item_at(i);
item->AddObserver(this);
top_items_.push_back(item);
}
UpdateIcon();
void AppListFolderItem::OnFolderImageUpdated() {
SetIcon(folder_image_.icon(), false);
}
} // namespace app_list
......@@ -12,8 +12,10 @@
#include "ui/app_list/app_list_item.h"
#include "ui/app_list/app_list_item_list_observer.h"
#include "ui/app_list/app_list_item_observer.h"
#include "ui/app_list/folder_image.h"
namespace gfx {
class ImageSkia;
class Rect;
}
......@@ -23,8 +25,7 @@ class AppListItemList;
// AppListFolderItem implements the model/controller for folders.
class APP_LIST_EXPORT AppListFolderItem : public AppListItem,
public AppListItemListObserver,
public AppListItemObserver {
public FolderImageObserver {
public:
// The folder type affects folder behavior.
enum FolderType {
......@@ -39,9 +40,6 @@ class APP_LIST_EXPORT AppListFolderItem : public AppListItem,
AppListFolderItem(const std::string& id, FolderType folder_type);
~AppListFolderItem() override;
// Updates the folder's icon.
void UpdateIcon();
// Returns the icon of one of the top items with |item_index|.
const gfx::ImageSkia& GetTopIcon(size_t item_index);
......@@ -57,9 +55,14 @@ class APP_LIST_EXPORT AppListFolderItem : public AppListItem,
AppListItemList* item_list() { return item_list_.get(); }
const AppListItemList* item_list() const { return item_list_.get(); }
// For tests.
// TODO(mgiuca): return a const FolderImage& (requires that
// base::ObserverList::HasObserver takes a const*).
FolderImage* folder_image() { return &folder_image_; }
FolderType folder_type() const { return folder_type_; }
// AppListItem
// AppListItem overrides:
void Activate(int event_flags) override;
const char* GetItemType() const override;
ui::MenuModel* GetContextMenuModel() override;
......@@ -71,28 +74,17 @@ class APP_LIST_EXPORT AppListFolderItem : public AppListItem,
// Returns an id for a new folder.
static std::string GenerateId();
private:
// AppListItemObserver
void ItemIconChanged() override;
// AppListItemListObserver
void OnListItemAdded(size_t index, AppListItem* item) override;
void OnListItemRemoved(size_t index, AppListItem* item) override;
;
void OnListItemMoved(size_t from_index,
size_t to_index,
AppListItem* item) override;
void UpdateTopItems();
// FolderImageObserver overrides:
void OnFolderImageUpdated() override;
private:
// The type of folder; may affect behavior of folder views.
const FolderType folder_type_;
// List of items in the folder.
scoped_ptr<AppListItemList> item_list_;
// Top items for generating folder icon.
std::vector<AppListItem*> top_items_;
FolderImage folder_image_;
DISALLOW_COPY_AND_ASSIGN(AppListFolderItem);
};
......
......@@ -77,7 +77,7 @@ class AppListModelTest : public testing::Test {
protected:
bool ItemObservedByFolder(AppListFolderItem* folder,
AppListItem* item) {
return item->observers_.HasObserver(folder);
return item->observers_.HasObserver(folder->folder_image());
}
std::string GetItemListContents(AppListItemList* item_list) {
......
// Copyright 2014 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/folder_image.h"
#include "ui/app_list/app_list_constants.h"
#include "ui/app_list/app_list_item.h"
#include "ui/app_list/app_list_item_list.h"
#include "ui/app_list/folder_image_source.h"
#include "ui/app_list/views/contents_view.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/gfx/geometry/rect.h"
#include "ui/strings/grit/ui_strings.h"
namespace app_list {
FolderImage::FolderImage(AppListItemList* item_list) : item_list_(item_list) {
item_list_->AddObserver(this);
}
FolderImage::~FolderImage() {
for (auto* item : top_items_)
item->RemoveObserver(this);
item_list_->RemoveObserver(this);
}
void FolderImage::UpdateIcon() {
for (auto* item : top_items_)
item->RemoveObserver(this);
top_items_.clear();
for (size_t i = 0; i < kNumFolderTopItems && i < item_list_->item_count();
++i) {
AppListItem* item = item_list_->item_at(i);
item->AddObserver(this);
top_items_.push_back(item);
}
RedrawIconAndNotify();
}
const gfx::ImageSkia& FolderImage::GetTopIcon(size_t item_index) {
CHECK_LT(item_index, top_items_.size());
return top_items_[item_index]->icon();
}
gfx::Rect FolderImage::GetTargetIconRectInFolderForItem(
AppListItem* item,
const gfx::Rect& folder_icon_bounds) {
for (size_t i = 0; i < top_items_.size(); ++i) {
if (item->id() == top_items_[i]->id()) {
std::vector<gfx::Rect> rects =
FolderImageSource::GetTopIconsBounds(folder_icon_bounds);
return rects[i];
}
}
gfx::Rect target_rect(folder_icon_bounds);
target_rect.ClampToCenteredSize(FolderImageSource::ItemIconSize());
return target_rect;
}
void FolderImage::AddObserver(FolderImageObserver* observer) {
observers_.AddObserver(observer);
}
void FolderImage::RemoveObserver(FolderImageObserver* observer) {
observers_.RemoveObserver(observer);
}
void FolderImage::ItemIconChanged() {
// Note: Must update the image only (cannot simply call UpdateIcon), because
// UpdateIcon removes and re-adds the FolderImage as an observer of the
// AppListItems, which causes the current iterator to call ItemIconChanged
// again, and goes into an infinite loop.
RedrawIconAndNotify();
}
void FolderImage::OnListItemAdded(size_t index, AppListItem* item) {
if (index < kNumFolderTopItems)
UpdateIcon();
}
void FolderImage::OnListItemRemoved(size_t index, AppListItem* item) {
if (index < kNumFolderTopItems)
UpdateIcon();
}
void FolderImage::OnListItemMoved(size_t from_index,
size_t to_index,
AppListItem* item) {
if (from_index < kNumFolderTopItems || to_index < kNumFolderTopItems)
UpdateIcon();
}
void FolderImage::RedrawIconAndNotify() {
FolderImageSource::Icons top_icons;
for (const auto* item : top_items_)
top_icons.push_back(item->icon());
const gfx::Size icon_size = gfx::Size(kGridIconDimension, kGridIconDimension);
icon_ =
gfx::ImageSkia(new FolderImageSource(top_icons, icon_size), icon_size);
FOR_EACH_OBSERVER(FolderImageObserver, observers_, OnFolderImageUpdated());
}
} // namespace app_list
// Copyright 2014 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_FOLDER_IMAGE_H_
#define UI_APP_LIST_FOLDER_IMAGE_H_
#include <vector>
#include "base/observer_list.h"
#include "ui/app_list/app_list_export.h"
#include "ui/app_list/app_list_item_list_observer.h"
#include "ui/app_list/app_list_item_observer.h"
#include "ui/gfx/image/image_skia.h"
namespace gfx {
class Rect;
}
namespace app_list {
class AppListItem;
class AppListItemList;
class APP_LIST_EXPORT FolderImageObserver {
public:
// Called when the folder icon has changed.
virtual void OnFolderImageUpdated() {}
protected:
virtual ~FolderImageObserver() {}
};
// The icon for an app list folder, dynamically generated by drawing the
// folder's items inside a circle. Automatically keeps itself up to date, and
// notifies observers when it changes.
class APP_LIST_EXPORT FolderImage : public AppListItemListObserver,
public AppListItemObserver {
public:
FolderImage(AppListItemList* item_list);
~FolderImage() override;
// Generates the folder's icon from the icons of the items in the item list,
// and notifies observers that the icon has changed.
void UpdateIcon();
const gfx::ImageSkia& icon() const { return icon_; }
// Returns the icon of one of the top items with |item_index|.
const gfx::ImageSkia& GetTopIcon(size_t item_index);
// Returns the target icon bounds for |item| to fly back to its parent folder
// icon in animation UI. If |item| is one of the top item icon, this will
// match its corresponding top item icon in the folder icon. Otherwise,
// the target icon bounds is centered at the |folder_icon_bounds| with
// the same size of the top item icon.
// The Rect returned is in the same coordinates of |folder_icon_bounds|.
gfx::Rect GetTargetIconRectInFolderForItem(
AppListItem* item,
const gfx::Rect& folder_icon_bounds);
void AddObserver(FolderImageObserver* observer);
void RemoveObserver(FolderImageObserver* observer);
// AppListItemObserver overrides:
void ItemIconChanged() override;
// AppListItemListObserver overrides:
void OnListItemAdded(size_t index, AppListItem* item) override;
void OnListItemRemoved(size_t index, AppListItem* item) override;
void OnListItemMoved(size_t from_index,
size_t to_index,
AppListItem* item) override;
private:
// Updates the folder's icon from the icons of |top_items_| and calls
// OnFolderImageUpdated. Does not refresh the |top_items_| list, so should
// only be called if the |item_list_| has not been changed (see UpdateIcon).
void RedrawIconAndNotify();
// The icon image.
gfx::ImageSkia icon_;
// List of top-level app list items (to display small in the icon).
AppListItemList* item_list_;
// Top items for generating folder icon.
std::vector<AppListItem*> top_items_;
ObserverList<FolderImageObserver> observers_;
};
} // namespace app_list
#endif // UI_APP_LIST_FOLDER_IMAGE_H_
// Copyright 2014 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/folder_image.h"
#include "base/macros.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/skia/include/core/SkBitmap.h"
#include "third_party/skia/include/core/SkColor.h"
#include "ui/app_list/app_list_constants.h"
#include "ui/app_list/app_list_item.h"
#include "ui/app_list/app_list_item_list.h"
#include "ui/app_list/app_list_model.h"
#include "ui/gfx/skia_util.h"
namespace app_list {
namespace {
gfx::ImageSkia CreateSquareBitmapWithColor(int size, SkColor color) {
SkBitmap bitmap;
bitmap.allocN32Pixels(size, size);
bitmap.eraseColor(color);
return gfx::ImageSkia::CreateFrom1xBitmap(bitmap);
}
bool ImagesAreEqual(const gfx::ImageSkia& image1,
const gfx::ImageSkia& image2) {
return gfx::BitmapsAreEqual(*image1.bitmap(), *image2.bitmap());
}
// Listens for OnFolderImageUpdated and sets a flag upon receiving the signal.
class TestFolderImageObserver : public FolderImageObserver {
public:
TestFolderImageObserver() : updated_flag_(false) {}
bool updated() const { return updated_flag_; }
void Reset() { updated_flag_ = false; }
// FolderImageObserver overrides:
void OnFolderImageUpdated() override { updated_flag_ = true; }
private:
bool updated_flag_;
DISALLOW_COPY_AND_ASSIGN(TestFolderImageObserver);
};
} // namespace
class FolderImageTest : public testing::Test {
public:
FolderImageTest() : folder_image_(app_list_model_.top_level_item_list()) {}
~FolderImageTest() {}
void SetUp() {
// Populate the AppListModel with three items (to test that the FolderImage
// correctly supports having fewer than four icons).
AddAppWithColoredIcon("app1", SK_ColorRED);
AddAppWithColoredIcon("app2", SK_ColorGREEN);
AddAppWithColoredIcon("app3", SK_ColorBLUE);
observer_.Reset();
folder_image_.AddObserver(&observer_);
}
void TearDown() { folder_image_.RemoveObserver(&observer_); }
protected:
void AddAppWithColoredIcon(const std::string& id, SkColor icon_color) {
scoped_ptr<AppListItem> item(new AppListItem(id));
item->SetIcon(CreateSquareBitmapWithColor(kListIconSize, icon_color),
false);
app_list_model_.AddItem(item.Pass());
}
AppListModel app_list_model_;
FolderImage folder_image_;
TestFolderImageObserver observer_;
DISALLOW_COPY_AND_ASSIGN(FolderImageTest);
};
TEST_F(FolderImageTest, UpdateListTest) {
gfx::ImageSkia icon1 = folder_image_.icon();
// Call UpdateIcon and ensure that the observer event fired.
folder_image_.UpdateIcon();
EXPECT_TRUE(observer_.updated());
observer_.Reset();
// The icon should not have changed.
EXPECT_TRUE(ImagesAreEqual(icon1, folder_image_.icon()));
// Swap two items. Ensure that the observer fired and the icon changed.
app_list_model_.top_level_item_list()->MoveItem(2, 1);
EXPECT_TRUE(observer_.updated());
observer_.Reset();
gfx::ImageSkia icon2 = folder_image_.icon();
EXPECT_FALSE(ImagesAreEqual(icon1, icon2));
// Swap back items. Ensure that the observer fired and the icon changed back.
app_list_model_.top_level_item_list()->MoveItem(2, 1);
EXPECT_TRUE(observer_.updated());
observer_.Reset();
EXPECT_TRUE(ImagesAreEqual(icon1, folder_image_.icon()));
// Add a new item. Ensure that the observer fired and the icon changed.
AddAppWithColoredIcon("app4", SK_ColorYELLOW);
EXPECT_TRUE(observer_.updated());
observer_.Reset();
gfx::ImageSkia icon3 = folder_image_.icon();
EXPECT_FALSE(ImagesAreEqual(icon1, icon3));
// Add a new item. The observer should not fire, nor should the icon change
// (because it does not affect the first four icons).
AddAppWithColoredIcon("app5", SK_ColorCYAN);
EXPECT_FALSE(observer_.updated());
observer_.Reset();
EXPECT_TRUE(ImagesAreEqual(icon3, folder_image_.icon()));
// Delete an item. Ensure that the observer fired and the icon changed.
app_list_model_.DeleteItem("app2");
EXPECT_TRUE(observer_.updated());
observer_.Reset();
gfx::ImageSkia icon4 = folder_image_.icon();
EXPECT_FALSE(ImagesAreEqual(icon3, icon4));
}
TEST_F(FolderImageTest, UpdateItemTest) {
gfx::ImageSkia icon1 = folder_image_.icon();
// Change an item's icon. Ensure that the observer fired and the icon changed.
app_list_model_.FindItem("app2")->SetIcon(
CreateSquareBitmapWithColor(kListIconSize, SK_ColorMAGENTA), false);
EXPECT_TRUE(observer_.updated());
observer_.Reset();
EXPECT_FALSE(ImagesAreEqual(icon1, folder_image_.icon()));
}
} // namespace app_list
......@@ -10,13 +10,19 @@
namespace app_list {
AllAppsTileItemView::AllAppsTileItemView(ContentsView* contents_view)
: contents_view_(contents_view) {
AllAppsTileItemView::AllAppsTileItemView(ContentsView* contents_view,
AppListItemList* item_list)
: contents_view_(contents_view), folder_image_(item_list) {
SetTitle(l10n_util::GetStringUTF16(IDS_APP_LIST_ALL_APPS));
// TODO(mgiuca): Set the button's icon.
folder_image_.AddObserver(this);
}
AllAppsTileItemView::~AllAppsTileItemView() {
folder_image_.RemoveObserver(this);
}
void AllAppsTileItemView::UpdateIcon() {
folder_image_.UpdateIcon();
}
void AllAppsTileItemView::ButtonPressed(views::Button* sender,
......@@ -25,4 +31,8 @@ void AllAppsTileItemView::ButtonPressed(views::Button* sender,
contents_view_->GetPageIndexForState(AppListModel::STATE_APPS));
}
void AllAppsTileItemView::OnFolderImageUpdated() {
SetIcon(folder_image_.icon());
}
} // namespace app_list
......@@ -7,24 +7,34 @@
#include <vector>
#include "ui/app_list/folder_image.h"
#include "ui/app_list/views/tile_item_view.h"
namespace app_list {
class AppListItemList;
class ContentsView;
// A tile item for the "All apps" button on the start page.
class AllAppsTileItemView : public TileItemView {
class AllAppsTileItemView : public TileItemView, public FolderImageObserver {
public:
AllAppsTileItemView(ContentsView* contents_view);
AllAppsTileItemView(ContentsView* contents_view, AppListItemList* item_list);
~AllAppsTileItemView() override;
// Generates the folder's icon from the icons of the items in the item list.
void UpdateIcon();
// TileItemView overrides:
void ButtonPressed(views::Button* sender, const ui::Event& event) override;
// FolderImageObserver overrides:
void OnFolderImageUpdated() override;
private:
ContentsView* contents_view_;
FolderImage folder_image_;
};
} // namespace app_list
......
......@@ -144,8 +144,10 @@ void StartPageView::InitTilesContainer() {
}
// Also add a special "all apps" button to the end of the container.
all_apps_button_ =
new AllAppsTileItemView(app_list_main_view_->contents_view());
all_apps_button_ = new AllAppsTileItemView(
app_list_main_view_->contents_view(),
view_delegate_->GetModel()->top_level_item_list());
all_apps_button_->UpdateIcon();
tiles_container_->AddChildView(all_apps_button_);
}
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment