1 // Copyright 2014 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "ui/app_list/views/start_page_view.h"
9 #include "base/i18n/rtl.h"
10 #include "base/metrics/histogram_macros.h"
11 #include "base/strings/utf_string_conversions.h"
12 #include "ui/accessibility/ax_view_state.h"
13 #include "ui/app_list/app_list_constants.h"
14 #include "ui/app_list/app_list_item.h"
15 #include "ui/app_list/app_list_model.h"
16 #include "ui/app_list/app_list_view_delegate.h"
17 #include "ui/app_list/search_result.h"
18 #include "ui/app_list/views/all_apps_tile_item_view.h"
19 #include "ui/app_list/views/app_list_main_view.h"
20 #include "ui/app_list/views/contents_view.h"
21 #include "ui/app_list/views/search_box_view.h"
22 #include "ui/app_list/views/search_result_container_view.h"
23 #include "ui/app_list/views/search_result_tile_item_view.h"
24 #include "ui/app_list/views/tile_item_view.h"
25 #include "ui/gfx/canvas.h"
26 #include "ui/views/background.h"
27 #include "ui/views/controls/image_view.h"
28 #include "ui/views/controls/label.h"
29 #include "ui/views/controls/textfield/textfield.h"
30 #include "ui/views/layout/box_layout.h"
31 #include "ui/views/widget/widget.h"
38 const int kInstantContainerSpacing
= 24;
39 const int kSearchBoxAndTilesSpacing
= 35;
40 const int kStartPageSearchBoxWidth
= 480;
43 const int kWebViewWidth
= 700;
44 const int kWebViewHeight
= 244;
46 // Tile container constants.
47 const size_t kNumStartPageTiles
= 4;
48 const int kTileSpacing
= 7;
50 const int kLauncherPageBackgroundWidth
= 400;
52 // An invisible placeholder view which fills the space for the search box view
53 // in a box layout. The search box view itself is a child of the AppListView
54 // (because it is visible on many different pages).
55 class SearchBoxSpacerView
: public views::View
{
57 explicit SearchBoxSpacerView(const gfx::Size
& search_box_size
)
58 : size_(kStartPageSearchBoxWidth
, search_box_size
.height()) {}
60 ~SearchBoxSpacerView() override
{}
62 // Overridden from views::View:
63 gfx::Size
GetPreferredSize() const override
{ return size_
; }
68 DISALLOW_COPY_AND_ASSIGN(SearchBoxSpacerView
);
73 class CustomLauncherPageBackgroundView
: public views::View
{
75 explicit CustomLauncherPageBackgroundView(
76 const std::string
& custom_launcher_page_name
)
78 custom_launcher_page_name_(custom_launcher_page_name
) {
79 set_background(views::Background::CreateSolidBackground(kSelectedColor
));
81 ~CustomLauncherPageBackgroundView() override
{}
83 void SetSelected(bool selected
) {
87 NotifyAccessibilityEvent(ui::AX_EVENT_FOCUS
, true);
90 bool selected() { return selected_
; }
92 // Overridden from views::View:
93 void GetAccessibleState(ui::AXViewState
* state
) override
{
94 state
->role
= ui::AX_ROLE_BUTTON
;
95 state
->name
= base::UTF8ToUTF16(custom_launcher_page_name_
);
100 std::string custom_launcher_page_name_
;
102 DISALLOW_COPY_AND_ASSIGN(CustomLauncherPageBackgroundView
);
105 // A container that holds the start page recommendation tiles and the all apps
107 class StartPageView::StartPageTilesContainer
108 : public SearchResultContainerView
{
110 StartPageTilesContainer(ContentsView
* contents_view
,
111 AllAppsTileItemView
* all_apps_button
);
112 ~StartPageTilesContainer() override
;
114 TileItemView
* GetTileItemView(int index
);
116 const std::vector
<SearchResultTileItemView
*>& tile_views() const {
117 return search_result_tile_views_
;
120 AllAppsTileItemView
* all_apps_button() { return all_apps_button_
; }
122 // Overridden from SearchResultContainerView:
123 int Update() override
;
124 void UpdateSelectedIndex(int old_selected
, int new_selected
) override
;
125 void OnContainerSelected(bool from_bottom
,
126 bool directional_movement
) override
;
129 ContentsView
* contents_view_
;
131 std::vector
<SearchResultTileItemView
*> search_result_tile_views_
;
132 AllAppsTileItemView
* all_apps_button_
;
134 DISALLOW_COPY_AND_ASSIGN(StartPageTilesContainer
);
137 StartPageView::StartPageTilesContainer::StartPageTilesContainer(
138 ContentsView
* contents_view
,
139 AllAppsTileItemView
* all_apps_button
)
140 : contents_view_(contents_view
), all_apps_button_(all_apps_button
) {
141 views::BoxLayout
* tiles_layout_manager
=
142 new views::BoxLayout(views::BoxLayout::kHorizontal
, 0, 0, kTileSpacing
);
143 tiles_layout_manager
->set_main_axis_alignment(
144 views::BoxLayout::MAIN_AXIS_ALIGNMENT_CENTER
);
145 SetLayoutManager(tiles_layout_manager
);
147 views::Background::CreateSolidBackground(kLabelBackgroundColor
));
149 // Add SearchResultTileItemViews to the container.
150 for (size_t i
= 0; i
< kNumStartPageTiles
; ++i
) {
151 SearchResultTileItemView
* tile_item
= new SearchResultTileItemView(this);
152 AddChildView(tile_item
);
153 tile_item
->SetParentBackgroundColor(kLabelBackgroundColor
);
154 search_result_tile_views_
.push_back(tile_item
);
157 // Also add a special "all apps" button to the end of the container.
158 all_apps_button_
->UpdateIcon();
159 all_apps_button_
->SetParentBackgroundColor(kLabelBackgroundColor
);
160 AddChildView(all_apps_button_
);
163 StartPageView::StartPageTilesContainer::~StartPageTilesContainer() {
166 TileItemView
* StartPageView::StartPageTilesContainer::GetTileItemView(
168 DCHECK_GT(num_results(), index
);
169 if (index
== num_results() - 1)
170 return all_apps_button_
;
172 return search_result_tile_views_
[index
];
175 int StartPageView::StartPageTilesContainer::Update() {
176 // Ignore updates and disable buttons when transitioning to a different
178 if (contents_view_
->GetActiveState() != AppListModel::STATE_START
) {
179 for (auto* view
: search_result_tile_views_
)
180 view
->SetEnabled(false);
182 return num_results();
185 std::vector
<SearchResult
*> display_results
=
186 AppListModel::FilterSearchResultsByDisplayType(
187 results(), SearchResult::DISPLAY_RECOMMENDATION
, kNumStartPageTiles
);
189 // Update the tile item results.
190 for (size_t i
= 0; i
< search_result_tile_views_
.size(); ++i
) {
191 SearchResult
* item
= NULL
;
192 if (i
< display_results
.size())
193 item
= display_results
[i
];
194 search_result_tile_views_
[i
]->SetSearchResult(item
);
195 search_result_tile_views_
[i
]->SetEnabled(true);
200 // Add 1 to the results size to account for the all apps button.
201 return display_results
.size() + 1;
204 void StartPageView::StartPageTilesContainer::UpdateSelectedIndex(
207 if (old_selected
>= 0)
208 GetTileItemView(old_selected
)->SetSelected(false);
210 if (new_selected
>= 0)
211 GetTileItemView(new_selected
)->SetSelected(true);
214 void StartPageView::StartPageTilesContainer::OnContainerSelected(
215 bool /*from_bottom*/,
216 bool /*directional_movement*/) {
219 ////////////////////////////////////////////////////////////////////////////////
220 // StartPageView implementation:
221 StartPageView::StartPageView(AppListMainView
* app_list_main_view
,
222 AppListViewDelegate
* view_delegate
)
223 : app_list_main_view_(app_list_main_view
),
224 view_delegate_(view_delegate
),
225 search_box_spacer_view_(new SearchBoxSpacerView(
226 app_list_main_view
->search_box_view()->GetPreferredSize())),
227 instant_container_(new views::View
),
228 custom_launcher_page_background_(new CustomLauncherPageBackgroundView(
229 view_delegate_
->GetModel()->custom_launcher_page_name())),
230 tiles_container_(new StartPageTilesContainer(
231 app_list_main_view
->contents_view(),
232 new AllAppsTileItemView(
233 app_list_main_view_
->contents_view(),
234 view_delegate_
->GetModel()->top_level_item_list()))) {
235 // The view containing the start page WebContents and SearchBoxSpacerView.
236 InitInstantContainer();
237 AddChildView(instant_container_
);
239 // The view containing the start page tiles.
240 AddChildView(tiles_container_
);
242 AddChildView(custom_launcher_page_background_
);
244 tiles_container_
->SetResults(view_delegate_
->GetModel()->results());
248 StartPageView::~StartPageView() {
251 void StartPageView::InitInstantContainer() {
252 views::BoxLayout
* instant_layout_manager
= new views::BoxLayout(
253 views::BoxLayout::kVertical
, 0, 0, kInstantContainerSpacing
);
254 instant_layout_manager
->set_inside_border_insets(
255 gfx::Insets(0, 0, kSearchBoxAndTilesSpacing
, 0));
256 instant_layout_manager
->set_main_axis_alignment(
257 views::BoxLayout::MAIN_AXIS_ALIGNMENT_END
);
258 instant_layout_manager
->set_cross_axis_alignment(
259 views::BoxLayout::CROSS_AXIS_ALIGNMENT_CENTER
);
260 instant_container_
->SetLayoutManager(instant_layout_manager
);
262 views::View
* web_view
= view_delegate_
->CreateStartPageWebView(
263 gfx::Size(kWebViewWidth
, kWebViewHeight
));
265 web_view
->SetFocusable(false);
266 instant_container_
->AddChildView(web_view
);
269 instant_container_
->AddChildView(search_box_spacer_view_
);
272 void StartPageView::MaybeOpenCustomLauncherPage() {
273 // Switch to the custom page.
274 ContentsView
* contents_view
= app_list_main_view_
->contents_view();
275 if (!app_list_main_view_
->ShouldShowCustomLauncherPage())
278 UMA_HISTOGRAM_ENUMERATION(kPageOpenedHistogram
,
279 AppListModel::STATE_CUSTOM_LAUNCHER_PAGE
,
280 AppListModel::STATE_LAST
);
282 contents_view
->SetActiveState(AppListModel::STATE_CUSTOM_LAUNCHER_PAGE
);
285 void StartPageView::Reset() {
286 tiles_container_
->Update();
289 void StartPageView::UpdateForTesting() {
290 tiles_container_
->Update();
293 const std::vector
<SearchResultTileItemView
*>& StartPageView::tile_views()
295 return tiles_container_
->tile_views();
298 TileItemView
* StartPageView::all_apps_button() const {
299 return tiles_container_
->all_apps_button();
302 void StartPageView::OnShow() {
303 tiles_container_
->Update();
304 tiles_container_
->ClearSelectedIndex();
305 custom_launcher_page_background_
->SetSelected(false);
308 void StartPageView::Layout() {
309 gfx::Rect
bounds(GetContentsBounds());
310 bounds
.set_height(instant_container_
->GetHeightForWidth(bounds
.width()));
311 instant_container_
->SetBoundsRect(bounds
);
313 // Tiles begin where the instant container ends.
314 bounds
.set_y(bounds
.bottom());
315 bounds
.set_height(tiles_container_
->GetHeightForWidth(bounds
.width()));
316 tiles_container_
->SetBoundsRect(bounds
);
318 bounds
= app_list_main_view_
->contents_view()->GetCustomPageCollapsedBounds();
319 bounds
.Intersect(GetContentsBounds());
320 bounds
.ClampToCenteredSize(
321 gfx::Size(kLauncherPageBackgroundWidth
, bounds
.height()));
322 custom_launcher_page_background_
->SetBoundsRect(bounds
);
325 bool StartPageView::OnKeyPressed(const ui::KeyEvent
& event
) {
326 const int forward_dir
= base::i18n::IsRTL() ? -1 : 1;
327 int selected_index
= tiles_container_
->selected_index();
329 if (custom_launcher_page_background_
->selected()) {
330 selected_index
= tiles_container_
->num_results();
331 switch (event
.key_code()) {
332 case ui::VKEY_RETURN
:
333 MaybeOpenCustomLauncherPage();
338 } else if (selected_index
>= 0 &&
339 tiles_container_
->GetTileItemView(selected_index
)
340 ->OnKeyPressed(event
)) {
345 switch (event
.key_code()) {
350 // Don't go to the custom launcher page from the All apps tile.
351 if (selected_index
!= tiles_container_
->num_results() - 1)
355 // Up selects the first tile if the custom launcher is selected.
356 if (custom_launcher_page_background_
->selected()) {
362 // Down selects the first tile if nothing is selected.
364 // If something is selected, select the custom launcher page.
365 if (tiles_container_
->IsValidSelectionIndex(selected_index
))
366 selected_index
= tiles_container_
->num_results() - 1;
369 dir
= event
.IsShiftDown() ? -1 : 1;
378 if (selected_index
== -1) {
379 custom_launcher_page_background_
->SetSelected(false);
380 tiles_container_
->SetSelectedIndex(
381 dir
== -1 ? tiles_container_
->num_results() - 1 : 0);
385 int selection_index
= selected_index
+ dir
;
386 if (tiles_container_
->IsValidSelectionIndex(selection_index
)) {
387 custom_launcher_page_background_
->SetSelected(false);
388 tiles_container_
->SetSelectedIndex(selection_index
);
392 if (selection_index
== tiles_container_
->num_results() &&
393 app_list_main_view_
->ShouldShowCustomLauncherPage()) {
394 custom_launcher_page_background_
->SetSelected(true);
395 tiles_container_
->ClearSelectedIndex();
399 if (event
.key_code() == ui::VKEY_TAB
&& selection_index
== -1)
400 tiles_container_
->ClearSelectedIndex(); // ContentsView will handle focus.
405 bool StartPageView::OnMousePressed(const ui::MouseEvent
& event
) {
406 ContentsView
* contents_view
= app_list_main_view_
->contents_view();
407 if (!contents_view
->GetCustomPageCollapsedBounds().Contains(event
.location()))
410 MaybeOpenCustomLauncherPage();
414 bool StartPageView::OnMouseWheel(const ui::MouseWheelEvent
& event
) {
415 // Negative y_offset is a downward scroll.
416 if (event
.y_offset() < 0) {
417 MaybeOpenCustomLauncherPage();
424 void StartPageView::OnGestureEvent(ui::GestureEvent
* event
) {
425 if (event
->type() == ui::ET_GESTURE_SCROLL_BEGIN
&&
426 event
->details().scroll_y_hint() < 0)
427 MaybeOpenCustomLauncherPage();
429 ContentsView
* contents_view
= app_list_main_view_
->contents_view();
430 if (event
->type() == ui::ET_GESTURE_TAP
&&
431 contents_view
->GetCustomPageCollapsedBounds().Contains(event
->location()))
432 MaybeOpenCustomLauncherPage();
435 void StartPageView::OnScrollEvent(ui::ScrollEvent
* event
) {
436 // Negative y_offset is a downward scroll (or upward, if Australian Scrolling
438 if (event
->type() == ui::ET_SCROLL
&& event
->y_offset() < 0)
439 MaybeOpenCustomLauncherPage();
442 gfx::Rect
StartPageView::GetSearchBoxBounds() const {
443 return search_box_spacer_view_
->bounds();
446 TileItemView
* StartPageView::GetTileItemView(size_t index
) {
447 return tiles_container_
->GetTileItemView(index
);
450 } // namespace app_list